Margoo

...?

使用 EasyX 实现 UI 原理教程(章一 触发器)

[返回:本文目录](/margoo/easyx-control-menu)

触发器是判断鼠标是否浮动到控件上的判断器,分为两大类:

  1. 异形触发器。
  2. 矩形触发器。

先说矩形触发器吧。

矩形 1

如上图,这是一个使用 fillreactangle 函数画出来的矩形,这个时候让你想办法判断鼠标是否点击了该矩形,你一定会想到,用鼠标 x、y 坐标与矩形 x、y 坐标和长、宽来解决,确实这是一个方法,这里放一下伪代码:

// 伪代码
如果 (鼠标_x >= 矩形_x && 鼠标_x <= 矩形_x + 矩形长 &&
	  鼠标_y >= 矩形_y && 鼠标_y <= 矩形_y + 矩形宽)
{
	鼠标点击处理();
}

基于上面的逻辑,我们尝试在 C++ 中把他封装成类:

class rectangle_trigger
{
public:
	int x;
	int y;
	int width;
	int height;

public:
	// 默认构造函数
	rectangle_trigger()
	{
		x = 0;
		y = 0;
		width = 0;
		height = 0;
	}

	rectangle_trigger(int control_x, int control_y, int control_width, int control_height)
		: x(control_x), y(control_y), width(control_width), height(control_height)
	{

	}

	// 鼠标是否触发
	bool is_trigger(ExMessage message)
	{
		// 判断鼠标是否在矩形范围内
		return (message.x >= x && message.x <= x + width && 
			message.y >= y && message.y <= y + height);
	}
};

至此我们就完成了一个矩形触发器。

接下来,我们来实现异形触发器,什么是异形触发器呢?就是非矩形形状的触发器,举个例子:

异形触发器截图

这是一个圆,如果我要以这个圆为触发器的形状来触发,明显只应该判断白色部分,忽略黑色部分,这种场景下单纯矩形判断无法实现的叫做异形触发器。

这里我将异形触发器分为两种:

  1. 几何图形触发器。
  2. 不规则触发器。

其实两种触发器的基本思路都是一样的-三元光栅判断(三元光栅相关知识请看慢羊羊的详解透明贴图和三元光栅操作)。

几何图形可以由程序生成掩码图,而不规则图形则需要用户自己提供掩码图。

注:不规则触发器有很多种判断触发方法,这里以三元光棚为例子。

我们只需要得到掩码图,这边我们规定以黑色为判断颜色,那么异形触发器的大概思路如下:

  1. 判断鼠标是否在矩形区域内:
// 伪代码
如果 (鼠标_x >= 矩形_x && 鼠标_x <= 矩形_x + 矩形长 &&
	  鼠标_y >= 矩形_y && 鼠标_y <= 矩形_y + 矩形宽)
  1. 将鼠标的客户端坐标转换为相对于掩码图的坐标:
// 伪代码
相对_x = 转换_x(鼠标_x)
相对_y = 转换_y(鼠标_y)

如果 (获取掩码图颜色(相对_x, 相对_y) == 黑色)
{
	鼠标点击触发();
}

几何图形的掩码图我们可以直接绘制一个黑色填充的几何图形就可以了。

接下来我们来按照这个思路写一个 geometry_trigger 来实现异形触发器(派生于 reactangle_trigger ):

// 定义几何图形形状
enum class shape_geometry
{
	CIRCLE, ELLIPSE, OTHER
};
// 描述形状大小
struct shape_size
{
	int x;
	int y;
	int width;
	int height;
	int radius;
};

// 异形触发器
class geometry_trigger : 
	public rectangle_trigger
{
public:
	// 掩码图
	IMAGE* mask;

	// 触发器属性
public:
	int x = 0;
	int y = 0;
	int width = 0;
	int height = 0;

public:
	void set_shape(shape_geometry shape, int radius = 0, IMAGE* mask_image = nullptr)
	{
		// 设置填充颜色
		setfillcolor(BLACK);

		mask = new IMAGE(width, height);

		// 生成几何图形掩码图
		if (shape == shape_geometry::CIRCLE)
		{
			// 获取当前工作图片
			auto image = GetWorkingImage();

			// 绘制掩码图
			SetWorkingImage(mask);
			solidellipse(0, 0, width, height);

			// 重新设置工作图片
			SetWorkingImage(image);

			putimage(40, 40, mask);
		}
		else if (shape == shape_geometry::ELLIPSE)
		{
			// 获取当前工作图片
			auto image = GetWorkingImage();

			// 绘制掩码图
			SetWorkingImage(mask);
			solidellipse(0, 0, width, height);

			// 重新设置工作图片
			SetWorkingImage(image);
		}
		else
		{
			// 否则设置用户掩码图
			*mask = *(mask_image);

			mask_image = nullptr;

			// 因为用户设置的掩码图没有必要进行后面的 alpha 信息处理
			// 所以直接 return 掉
			return;
		}

		// 为掩码图上 alpha 信息
		auto buffer = GetImageBuffer(mask);

		BYTE alpha = 255;

		for (int count = 0; count < mask->getheight() * mask->getwidth(); ++count)
		{
			if ((DWORD)buffer[count] != 0)
			{
				alpha = 255;

				buffer[count] = alpha << 24 | RGB(0 * alpha / 255, 0 * alpha / 255, 0 * alpha / 255);
			}
			else
			{
				alpha = 255;

				buffer[count] = alpha << 24 | RGB(255 * alpha / 255, 255 * alpha / 255,
					255 * alpha / 255);
			}
		}
	}

public:
	// 默认构造函数
	geometry_trigger()
	{
		mask = nullptr;
	}

	geometry_trigger(shape_geometry shape, shape_size size, IMAGE* mask_image = nullptr)
	{
		x = (size.x);
		y = (size.y);
		width = (size.width);
		height = (size.height);

		// 设置填充颜色
		setfillcolor(BLACK);

		// 生成几何图形掩码图
		if (shape == shape_geometry::CIRCLE)
		{
			mask = new IMAGE(size.width, size.height);

			// 获取当前工作图片
			auto image = GetWorkingImage();

			// 绘制掩码图
			SetWorkingImage(mask);
			fillcircle(size.x + size.radius, size.y + size.radius, size.radius);

			// 重新设置工作图片
			SetWorkingImage(image);
		}
		else if (shape == shape_geometry::ELLIPSE)
		{
			mask = new IMAGE(size.width, size.height);

			// 获取当前工作图片
			auto image = GetWorkingImage();

			// 绘制掩码图
			SetWorkingImage(mask);
			fillellipse(size.x, size.y, size.x + size.width, size.y + size.height);

			// 重新设置工作图片
			SetWorkingImage(image);
		}
		else
		{
			// 否则设置用户掩码图
			*mask = *(mask_image);

			mask_image = nullptr;

			// 因为用户设置的掩码图没有必要进行后面的 alpha 信息处理
			// 所以直接 return 掉
			return;
		}
	}

	bool is_trigger(ExMessage message)
	{
		// 判断鼠标是否在矩形范围内
		if (message.x >= x && message.x <= x + width && 
			message.y >= y && message.y <= y + height)
		{
			// 判断是否处于掩码图范围内
			message.x -= x;
			message.y -= y;

			// 获取当前工作图片
			auto image = GetWorkingImage();

			// 判断是否处于掩码范围内
			SetWorkingImage(mask);

			// 如果是返回true
			if (getpixel(message.x, message.y) == BLACK)
			{
				// 重新设置工作图片
				SetWorkingImage(image);

				return true;
			}

			// 重新设置工作图片
			SetWorkingImage(image);
		}

		return false;
	}
};

接下来,测试一下我们的代码吧~

int main()
{
	initgraph(640, 480, SHOWCONSOLE);

	setfillcolor(WHITE);

	// 绘制矩形和圆
	fillrectangle(40, 40, 90, 90);
	solidellipse(120, 40, 160, 80);

	// 测试触发器
	rectangle_trigger  rect_trigger(40, 40, 50, 50);
	geometry_trigger   geom_trigger(shape_geometry::CIRCLE, {120 - 20, 40 - 20, 40, 40, 20});

	ExMessage message;

	while (true)
	{
		getmessage(&message);

		if (message.message == WM_LBUTTONDOWN)
		{
			if (rect_trigger.is_trigger(message) == true)
			{
				printf("■");
			}
			if (geom_trigger.is_trigger(message) == true)
			{
				printf("●");
			}
		}
	}

	_getch();

	return 0;
}

效果图

但是,这里要注意了矩形触发器其实只是异形触发器的一种,所以我们的继承关系是错误的!

我之所选择这样错误的介绍主要是为了由易到难让读者好接受,如果你都看到了这里,那么不妨修改一下代码并封装成 trigger.h 供第二章使用:

////////////////////////////////////
//          trigger.h
// <创 建 时 间> : 2021/11/6
// <最后修改时间> : 2021/11/12
// <作      者>  : Margoo
// <邮      箱>  : 1683691371@qq.com
// 
#include <graphics.h>
#include <conio.h>

#include <utility>

// 定义几何图形形状
enum class shape_geometry
{
	CIRCLE, ELLIPSE, OTHER
};
// 描述形状大小
struct shape_size
{
	int x;
	int y;
	int width;
	int height;
	int radius;
};

// 异形触发器
class geometry_trigger
{
public:
	// 掩码图
	IMAGE* mask;

	// 触发器属性
public:
	int x = 0;
	int y = 0;
	int width = 0;
	int height = 0;

public:
	void set_shape(shape_geometry shape, int radius = 0, IMAGE* mask_image = nullptr)
	{
		// 设置填充颜色
		setfillcolor(BLACK);

		mask = new IMAGE(width, height);

		// 生成几何图形掩码图
		if (shape == shape_geometry::CIRCLE)
		{
			// 获取当前工作图片
			auto image = GetWorkingImage();

			// 绘制掩码图
			SetWorkingImage(mask);
			solidellipse(0, 0, width, height);

			// 重新设置工作图片
			SetWorkingImage(image);

			putimage(40, 40, mask);
		}
		else if (shape == shape_geometry::ELLIPSE)
		{
			// 获取当前工作图片
			auto image = GetWorkingImage();

			// 绘制掩码图
			SetWorkingImage(mask);
			solidellipse(0, 0, width, height);

			// 重新设置工作图片
			SetWorkingImage(image);
		}
		else
		{
			// 否则设置用户掩码图
			mask = std::move(mask_image);

			mask_image = nullptr;

			// 因为用户设置的掩码图没有必要进行后面的 alpha 信息处理
			// 所以直接 return 掉
			return;
		}

		// 为掩码图上 alpha 信息
		auto buffer = GetImageBuffer(mask);

		BYTE alpha = 255;

		for (int count = 0; count < mask->getheight() * mask->getwidth(); ++count)
		{
			if ((DWORD)buffer[count] != 0)
			{
				alpha = 255;

				buffer[count] = alpha << 24 | RGB(0 * alpha / 255, 0 * alpha / 255, 0 * alpha / 255);
			}
			else
			{
				alpha = 255;

				buffer[count] = alpha << 24 | RGB(255 * alpha / 255, 255 * alpha / 255,
					255 * alpha / 255);
			}
		}
	}

public:
	// 默认构造函数
	geometry_trigger()
	{
		mask = nullptr;
	}

	geometry_trigger(shape_geometry shape, shape_size size, IMAGE* mask_image = nullptr)
	{
		x = (size.x);
		y = (size.y);
		width = (size.width);
		height = (size.height);

		// 设置填充颜色
		setfillcolor(BLACK);

		// 生成几何图形掩码图
		if (shape == shape_geometry::CIRCLE)
		{
			mask = new IMAGE(size.width, size.height);

			// 获取当前工作图片
			auto image = GetWorkingImage();

			// 绘制掩码图
			SetWorkingImage(mask);
			fillcircle(size.x + size.radius, size.y + size.radius, size.radius);

			// 重新设置工作图片
			SetWorkingImage(image);
		}
		else if (shape == shape_geometry::ELLIPSE)
		{
			mask = new IMAGE(size.width, size.height);

			// 获取当前工作图片
			auto image = GetWorkingImage();

			// 绘制掩码图
			SetWorkingImage(mask);
			fillellipse(size.x, size.y, size.x + size.width, size.y + size.height);

			// 重新设置工作图片
			SetWorkingImage(image);
		}
		else
		{
			// 否则设置用户掩码图
			*mask = *(mask_image);

			mask_image = nullptr;

			// 因为用户设置的掩码图没有必要进行后面的 alpha 信息处理
			// 所以直接 return 掉
			return;
		}
	}

	bool is_trigger(ExMessage message)
	{
		// 判断鼠标是否在矩形范围内
		if (message.x >= x && message.x <= x + width &&
			message.y >= y && message.y <= y + height)
		{
			// 判断是否处于掩码图范围内
			message.x -= x;
			message.y -= y;

			// 获取当前工作图片
			auto image = GetWorkingImage();

			// 判断是否处于掩码范围内
			SetWorkingImage(mask);

			// 如果是返回true
			if (getpixel(message.x, message.y) == BLACK)
			{
				// 重新设置工作图片
				SetWorkingImage(image);

				return true;
			}

			// 重新设置工作图片
			SetWorkingImage(image);
		}

		return false;
	}
};

// 矩形触发器
class rectangle_trigger : public geometry_trigger
{
public:
	int x;
	int y;
	int width;
	int height;

public:
	// 默认构造函数
	rectangle_trigger()
	{
		x = 0;
		y = 0;
		width = 0;
		height = 0;
	}

	rectangle_trigger(int control_x, int control_y, int control_width, int control_height)
		: x(control_x), y(control_y), width(control_width), height(control_height)
	{

	}

	// 鼠标是否触发
	bool is_trigger(ExMessage message)
	{
		// 判断鼠标是否在矩形范围内
		return (message.x >= x && message.x <= x + width && 
			message.y >= y && message.y <= y + height);
	}
};

这样这才是一个正确的继承关系。

结语

至此本章内容结束,后续我会更新其他的文章,将会以单独的控件为主题,例如按钮控件或输入框控件等,如若遇到两个难度较低的我可能会合并在一起讲,下一章我将会向大家介绍控件的展示器-“绘图单元”,它与触发器一样是属于必学基础,也是一个 UI 里最基本的东西,至于文章中 is_trigger 和消息循环的书写方法是我们后面要讨论到的 UI 结构,这里不多讲述以防止读者迷茫。

文章的完整代码如下:

////////////////////////////////////
//      EasyX UI 教程 DEMO 1
// <创 建 时 间> : 2021/11/6
// <最后修改时间> : 2021/11/12
// <作      者>  : Margoo
// <邮      箱>  : 1683691371@qq.com
// 
#include <graphics.h>
#include <conio.h>

#include <stdio.h>

#include <utility>

// 定义几何图形形状
enum class shape_geometry
{
	CIRCLE, ELLIPSE, OTHER
};
// 描述形状大小
struct shape_size
{
	int x;
	int y;
	int width;
	int height;
	int radius;
};

// 异形触发器
class geometry_trigger
{
public:
	// 掩码图
	IMAGE* mask;

	// 触发器属性
public:
	int x = 0;
	int y = 0;
	int width = 0;
	int height = 0;

public:
	void set_shape(shape_geometry shape, int radius = 0, IMAGE* mask_image = nullptr)
	{
		// 设置填充颜色
		setfillcolor(BLACK);

		mask = new IMAGE(width, height);

		// 生成几何图形掩码图
		if (shape == shape_geometry::CIRCLE)
		{
			// 获取当前工作图片
			auto image = GetWorkingImage();

			// 绘制掩码图
			SetWorkingImage(mask);
			solidellipse(0, 0, width, height);

			// 重新设置工作图片
			SetWorkingImage(image);

			putimage(40, 40, mask);
		}
		else if (shape == shape_geometry::ELLIPSE)
		{
			// 获取当前工作图片
			auto image = GetWorkingImage();

			// 绘制掩码图
			SetWorkingImage(mask);
			solidellipse(0, 0, width, height);

			// 重新设置工作图片
			SetWorkingImage(image);
		}
		else
		{
			// 否则设置用户掩码图
			mask = std::move(mask_image);

			mask_image = nullptr;

			// 因为用户设置的掩码图没有必要进行后面的 alpha 信息处理
			// 所以直接 return 掉
			return;
		}

		// 为掩码图上 alpha 信息
		auto buffer = GetImageBuffer(mask);

		BYTE alpha = 255;

		for (int count = 0; count < mask->getheight() * mask->getwidth(); ++count)
		{
			if ((DWORD)buffer[count] != 0)
			{
				alpha = 255;

				buffer[count] = alpha << 24 | RGB(0 * alpha / 255, 0 * alpha / 255, 0 * alpha / 255);
			}
			else
			{
				alpha = 255;

				buffer[count] = alpha << 24 | RGB(255 * alpha / 255, 255 * alpha / 255,
					255 * alpha / 255);
			}
		}
	}

public:
	// 默认构造函数
	geometry_trigger()
	{
		mask = nullptr;
	}

	geometry_trigger(shape_geometry shape, shape_size size, IMAGE* mask_image = nullptr)
	{
		x = (size.x);
		y = (size.y);
		width = (size.width);
		height = (size.height);

		// 设置填充颜色
		setfillcolor(BLACK);

		// 生成几何图形掩码图
		if (shape == shape_geometry::CIRCLE)
		{
			mask = new IMAGE(size.width, size.height);

			// 获取当前工作图片
			auto image = GetWorkingImage();

			// 绘制掩码图
			SetWorkingImage(mask);
			fillcircle(size.x + size.radius, size.y + size.radius, size.radius);

			// 重新设置工作图片
			SetWorkingImage(image);
		}
		else if (shape == shape_geometry::ELLIPSE)
		{
			mask = new IMAGE(size.width, size.height);

			// 获取当前工作图片
			auto image = GetWorkingImage();

			// 绘制掩码图
			SetWorkingImage(mask);
			fillellipse(size.x, size.y, size.x + size.width, size.y + size.height);

			// 重新设置工作图片
			SetWorkingImage(image);
		}
		else
		{
			// 否则设置用户掩码图
			*mask = *(mask_image);

			mask_image = nullptr;

			// 因为用户设置的掩码图没有必要进行后面的 alpha 信息处理
			// 所以直接 return 掉
			return;
		}
	}

	bool is_trigger(ExMessage message)
	{
		// 判断鼠标是否在矩形范围内
		if (message.x >= x && message.x <= x + width && 
			message.y >= y && message.y <= y + height)
		{
			// 判断是否处于掩码图范围内
			message.x -= x;
			message.y -= y;

			// 获取当前工作图片
			auto image = GetWorkingImage();

			// 判断是否处于掩码范围内
			SetWorkingImage(mask);

			// 如果是返回true
			if (getpixel(message.x, message.y) == BLACK)
			{
				// 重新设置工作图片
				SetWorkingImage(image);

				return true;
			}

			// 重新设置工作图片
			SetWorkingImage(image);
		}

		return false;
	}
};

// 矩形触发器
class rectangle_trigger : public geometry_trigger
{
public:
	int x;
	int y;
	int width;
	int height;

public:
	// 默认构造函数
	rectangle_trigger()
	{
		x = 0;
		y = 0;
		width = 0;
		height = 0;
	}

	rectangle_trigger(int control_x, int control_y, int control_width, int control_height)
		: x(control_x), y(control_y), width(control_width), height(control_height)
	{

	}

	// 鼠标是否触发
	bool is_trigger(ExMessage message)
	{
		// 判断鼠标是否在矩形范围内
		return (message.x >= x && message.x <= x + width && 
			message.y >= y && message.y <= y + height);
	}
};

int main()
{
	initgraph(640, 480, SHOWCONSOLE);

	setfillcolor(WHITE);

	// 绘制矩形和圆
	fillrectangle(40, 40, 90, 90);
	solidellipse(120, 40, 160, 80);

	// 测试触发器
	rectangle_trigger  rect_trigger(40, 40, 50, 50);
	geometry_trigger   geom_trigger(shape_geometry::CIRCLE, {120 - 20, 40 - 20, 40, 40, 20});

	ExMessage message;

	while (true)
	{
		getmessage(&message);

		if (message.message == WM_LBUTTONDOWN)
		{
			if (rect_trigger.is_trigger(message) == true)
			{
				printf("■");
			}
			if (geom_trigger.is_trigger(message) == true)
			{
				printf("●");
			}
		}
	}

	_getch();

	return 0;
}

添加评论