您好,欢迎访问代理记账网站
移动应用 微信公众号 联系我们

咨询热线 -

电话 15988168888

联系客服
  • 价格透明
  • 信息保密
  • 进度掌控
  • 售后无忧

EGE绘图之五 按钮

目录

  • 一、按钮类型
  • 二、按钮样式
  • 三、 按钮点击
    • 1. 按钮点击的确定
    • 2.按钮的点击判定
        • 2.1 矩形区域点击判定
          • 2.1.1 矩形区域的表示
          • 2.1.2 点在矩形区域内的判定
          • 2.1.3 矩形区域点击判断示例
        • 2.2 圆形区域的点击判定
          • 2.2.1 圆形区域的表示
          • 2.2.2 点在圆形区域内的判定
          • 2.2.3 圆形区域点击判定示例
        • 2.2 圆角矩形区域的点击判定
          • 2.2.1 圆角矩形区域的表示
          • 2.2.2 点在圆角矩形区域内的判定
          • 2.2.3 圆角矩形区域点击判定示例
      • 3. 多个按钮的点击检测
        • 3.1 多个按钮检测示例
  • 四、按钮状态

文章最后修改时间:2021年3月9日23:41:26

待完善

一、按钮类型

二、按钮样式

下面是一些按钮的常见样式
在这里插入图片描述

在这里插入图片描述

  • CSS按钮|菜鸟教程
  • Buttons

还有各种各样的按钮特效
在这里插入图片描述
用户界面中按钮的基本类型

三、 按钮点击

1. 按钮点击的确定

在GUI界面中按钮是最常见的,通过鼠标悬停,点击,拖动等执行不同的动作。

接下来就讲解如何自定义按钮来执行鼠标点击。

鼠标点击事件前面已经讲解过了,确定鼠标消息是按下抬起就可以了,指定按键的时候还要加上区分左中右键的判断。

如,鼠标左键按下:

if (msg.is_left() && msg.is_down()) {
	//鼠标左键按下
}

对于按钮,当前一般的处理方式是:

  • 鼠标按下:不执行,仅记录按下位置pos
  • 鼠标抬起:判断位置pos是否在按钮区域,才按键区域处才执行相应的按钮点击动作。

当然,为了简单,也可以直接在鼠标按键按下时直接进行按钮点击判断,点击处按钮上时直接执行,而不是等到鼠标按键抬起。这样可以简化代码,并且适合需要快速响应的情况。

这里主要以鼠标按键抬起时刻确定按键点击。

2.按钮的点击判定

按钮的点击判定,需要当鼠标进行点击时,判断点击位置是否在按钮的点击区域内。即判定一个点是否在区域内部

通常按钮的形状是矩形的,也有圆形的,还有圆角矩形的,这几种形状最多。
按钮的形状区域可点击区域并不一定重合。矩形区域和圆形区域的判定较为容易,圆角矩形复杂一些,因此,有些圆角矩形形状的按钮,圆角较小的,仍用矩形区域判定。

2.1 矩形区域点击判定

2.1.1 矩形区域的表示

矩形区域有多种表示方式,但都表示同一个区域,进行简单的转换即可。
矩形区域表示方式

  • 左上角位置(x, y), 宽 width, 高 height
    在这里插入图片描述
  • 左上角位置(left, top), 右下角位置(right, bottom)
    在这里插入图片描述
    这两种方式的区别就在于一个是右下角,一个宽高,这两种分别适用不同的情况。转换很简单,关系就是
    w i d t h = r i g h t − l e f t width = right - left width=rightleft
    h e i g h t = b o t t o m − t o p height = bottom - top height=bottomtop
    根据等式稍微变换计算一下即可。
2.1.2 点在矩形区域内的判定

判断点在矩形区域内,点(x, y)矩形区域(left, top,right, bottom) 内,则满足以下关系:
l e f t ⩽ x < r i g h t t o p ⩽ y < b o t t o m left \leqslant x < right \\ top \leqslant y < bottom leftx<righttopy<bottom
在这里插入图片描述
用代码表示即为

 (left <= x) && (x < right) && (top <= y) && (y < bottom)
2.1.3 矩形区域点击判断示例

在这里插入图片描述

#include <graphics.h>

struct RectButton
{
	int x, y;
	int width, height;
};

bool clickRectButton(RectButton* button, int x, int y)
{
	return (x >= button->x) && (y >= button->y)
		&& (x < button->x + button->width)
		&& (y < button->y + button->height);
}

void drawRectButton(RectButton* button)
{
	setfillcolor(EGERGB(0x1E, 0x90, 0xFF));
	bar(button->x, button->y, button->x + button->width, button->y + button->height);
}

//定义按钮,确定区域
RectButton button = {
	220, 200,  /* x, y */
	200, 80,   /* width, height */
};


int main()
{
	initgraph(640, 480, INIT_RENDERMANUAL);
	setbkcolor(WHITE);

	//鼠标左键按下位置
	int xLeftPress  = 0, yLeftPress = 0;

	//鼠标左键松开位置
	int xLeftRelease = 0, yLeftRelease = 0;

	int clickCount = 0;

	for (; is_run(); delay_fps(60)) {
		bool leftClick = false;

		while (mousemsg()) {
			mouse_msg msg = getmouse();

			//判断鼠标左键点击(左键按下确定位置,抬起为执行时刻)
			if (msg.is_left()) {
				if (msg.is_down()) {
					//记录左键按下位置
					xLeftPress = msg.x;
					yLeftPress = msg.y;
				}
				else {
					xLeftRelease = msg.x;
					yLeftRelease = msg.y;
					//左键抬起,点击动作执行
					leftClick = true;
				}
			}
		}

		//当鼠标左键存在点击
		if (leftClick) {
			//对按钮进行检查,点击位置是否在按钮区域内
			if (clickRectButton(&button, xLeftPress, yLeftPress)) {
				clickCount++;
			}
		}

		//绘制
		cleardevice();
		drawRectButton(&button);
		setcolor(BLACK);
		setfont(24, 0, "");
		xyprintf(240, 360, "点击按钮次数:%d", clickCount);
	}

	return 0;
}

2.2 圆形区域的点击判定

2.2.1 圆形区域的表示

圆形区域有两种表示方法:

  • 圆心和半径
    在这里插入图片描述
  • 用包围矩形区域的表述
    在这里插入图片描述
    这两种转换关系也很简单

{ x = l e f t + r i g h t 2 y = t o p + b o t t o m 2 r a d i u s = r i g h t − l e f t 2 \begin{cases} x=\frac{left+right}{2}\\ y=\frac{top+bottom}{2}\\ radius=\frac{right-left}{2}\\ \end{cases} x=2left+righty=2top+bottomradius=2rightleft

2.2.2 点在圆形区域内的判定

( x , y ) (x, y) (x,y)在一个圆心为 ( x 0 , y 0 ) (x_0,y_0) (x0,y0),半径为 r a d i u s radius radius的 圆内,有以下关系
( x − x 0 ) 2 + ( y − y 0 ) 2 ⩽ r a d i u s \sqrt{\left( x-x_0 \right) ^2+\left( y-y_0 \right) ^2}\leqslant radius (xx0)2+(yy0)2 radius
由于开方运行复杂,两边同时平方,得到如下:
( x − x 0 ) 2 + ( y − y 0 ) 2 ⩽ r a d i u s 2 \left( x-x_0 \right) ^2+\left( y-y_0 \right) ^2\leqslant radius^2 (xx0)2+(yy0)2radius2

用代码表示:

//计算点与圆心的xy差值
int dx = x - x0, dy = y - y0;
//比较
(dx * dx + dy * dy) <= (radius * radius)

在这里插入图片描述

2.2.3 圆形区域点击判定示例

在这里插入图片描述

#include <graphics.h>

struct CircleButton
{
	int x, y;
	int radius;
};

bool clickCircleButton(CircleButton* button, int x, int y)
{
	int dx = x - button->x, dy = y - button->y;
	return (dx * dx + dy * dy) <= (button->radius * button->radius);
}

void drawCircleButton(CircleButton* button)
{
	setfillcolor(EGEARGB(0xFF, 0x1E, 0x90, 0xFF));
	//高级绘图函数
	ege_fillellipse(button->x - button->radius, button->y - button->radius,
		2 * button->radius, 2 * button->radius);
	//或者使用下面的普通绘图函数
	//fillellipse(button->x, button->y, button->radius, button->radius);
}

//定义按钮,确定区域
CircleButton button = {
	320, 240,  /* x, y */
	100,       /* radius */
};


int main()
{
	initgraph(640, 480, INIT_RENDERMANUAL);
	setbkcolor(WHITE);
	ege_enable_aa(true);

	//鼠标左键按下位置
	int xLeftPress  = 0, yLeftPress = 0;

	//鼠标左键松开位置
	int xLeftRelease = 0, yLeftRelease = 0;

	int clickCount = 0;

	for (; is_run(); delay_fps(60)) {
		bool leftClick = false;

		while (mousemsg()) {
			mouse_msg msg = getmouse();

			//判断鼠标左键点击(左键按下确定位置,抬起为执行时刻)
			if (msg.is_left()) {
				if (msg.is_down()) {
					//记录左键按下位置
					xLeftPress = msg.x;
					yLeftPress = msg.y;
				}
				else {
					xLeftRelease = msg.x;
					yLeftRelease = msg.y;
					//左键抬起,点击动作执行
					leftClick = true;
				}
			}
		}

		//当鼠标左键存在点击
		if (leftClick) {
			//对按钮进行检查,点击位置是否在按钮区域内
			if (clickCircleButton(&button, xLeftPress, yLeftPress)) {
				clickCount++;
			}
		}

		//绘制
		cleardevice();
		drawCircleButton(&button);
		setcolor(BLACK);
		setfont(24, 0, "");
		xyprintf(240, 360, "点击按钮次数:%d", clickCount);
	}

	return 0;
}

2.2 圆角矩形区域的点击判定

  圆角矩形即矩形的四个角不再是直角,取而代之的是四个90°的圆弧。一般圆角矩形四个圆角的半径都是相等的,有些可以不相等,这里的圆角矩形是前者。

2.2.1 圆角矩形区域的表示

  取圆角矩形的包围矩形的位置和大小参数,额外一个参数设定圆角的半径。圆角的半径最大不能大于短边的一半。
在这里插入图片描述

2.2.2 点在圆角矩形区域内的判定

  首先,如果点在圆角矩形区域内部,则肯定满足点在圆角矩形外部包围矩形内部的条件。

条件1: 点P位与圆角矩形外部包围矩形的内部

(left <= x) && (x < right) && (top <= y) && (y < bottom)

  与矩形不同的是,即使位于包围矩形内,但是点也可能在圆角处。因此增加在圆角处的判断:
  由对称性质,以圆角矩形中心为原点,可以将圆角矩形分成四个区域,并对称映射到第一象限,如下图所示。
在这里插入图片描述
当点位于图中所示橙色正方形内部时,可以根据点是否在圆角所在的圆内来判断。
条件2: 点P位于圆角处时,位于圆角所在的圆内。

x ⩾ w 2 − r x\geqslant \frac{w}{2}-r x2wr y ⩾ h 2 − r y\geqslant \frac{h}{2}-r y2hr 时,若点P在圆角矩形内,则有
( x − ( w 2 − r ) ) 2 + ( y − ( h 2 − r ) ) 2 ⩽    r 2 \left( x-\left( \frac{w}{2}-r \right) \right) ^2+\left( y-\left( \frac{h}{2}-r \right) \right) ^2\leqslant \,\,r^2 (x(2wr))2+(y(2hr))2r2

2.2.3 圆角矩形区域点击判定示例

在这里插入图片描述

#include <graphics.h>
#include <math.h>

struct CircleButton
{
	int x, y;
	int radius;
};

struct RoundRectButton
{
	int x, y;
	int width, height;
	int radius;
};

bool clickRoundRectButton(const RoundRectButton* button, int x, int y)
{
	if ((x >= button->x) && (y >= button->y)
		&& (x < button->x + button->width)
		&& (y < button->y + button->height)
		){
		float centerx = button->x + button->width  / 2.0f;
		float centery = button->y + button->height / 2.0f;
		float dx = fabs(x - centerx);
		float dy = fabs(y - centery);
		float interWidth  = button->width / 2.0f - button->radius;
		float interHeight = button->height / 2.0f - button->radius;

		if (	(dx > interWidth)
			&&	(dy > interHeight)
			&&	((dx - interWidth) * (dx - interWidth) + (dy - interHeight) * (dy - interHeight) > button->radius * button->radius)
			){
			return false;
		}
		else {
			return true;
		}
	}
	else {
		return false;
	}

}

void drawRoundRectButton(const RoundRectButton* button)
{
	setfillcolor(EGEARGB(0xFF, 0x1E, 0x90, 0xFF));
	
	ege_fillrect(button->x + button->radius, button->y,
		button->width - 2 * button->radius, button->height);

	ege_fillrect(button->x, button->y + button->radius,
		button->radius, button->height - 2 * button->radius);

	ege_fillrect(button->x + button->width - button->radius, 
		button->y + button->radius,
		button->radius, button->height - 2 * button->radius);


	float diameter = 2 * button->radius;
	float dx = button->width - diameter;
	float dy = button->height - diameter;
	
	
	ege_fillpie(button->x + dx, button->y + dy, diameter, diameter, 0.0f, 90.0f);
	ege_fillpie(button->x	  , button->y + dy, diameter, diameter, 90.0f, 90.0f);
	ege_fillpie(button->x,		button->y,		diameter, diameter, 180.0f, 90.0f);
	ege_fillpie(button->x + dx, button->y,		diameter, diameter, 270.0f, 90.0f);
}

//定义按钮,确定区域
RoundRectButton button = {
	320 - 100, 240 - 80,	/* x, y */
	200, 160,				/* width, height */
	40,						/* cornerRadius */
};


int main()
{
	initgraph(640, 480, INIT_RENDERMANUAL);
	setbkcolor(WHITE);
	ege_enable_aa(true);

	//鼠标左键按下位置
	int xLeftPress = 0, yLeftPress = 0;

	//鼠标左键松开位置
	int xLeftRelease = 0, yLeftRelease = 0;

	int clickCount = 0;

	for (; is_run(); delay_fps(60)) {
		bool leftClick = false;

		while (mousemsg()) {
			mouse_msg msg = getmouse();

			//判断鼠标左键点击(左键按下确定位置,抬起为执行时刻)
			if (msg.is_left()) {
				if (msg.is_down()) {
					//记录左键按下位置
					xLeftPress = msg.x;
					yLeftPress = msg.y;
				}
				else {
					xLeftRelease = msg.x;
					yLeftRelease = msg.y;
					//左键抬起,点击动作执行
					leftClick = true;
				}
			}
		}

		//当鼠标左键存在点击
		if (leftClick) {
			//对按钮进行检查,点击位置是否在按钮区域内
			if (clickRoundRectButton(&button, xLeftPress, yLeftPress)) {
				clickCount++;
			}
		}

		//绘制
		cleardevice();
		drawRoundRectButton(&button);
		setcolor(BLACK);
		setfont(24, 0, "");
		xyprintf(240, 360, "点击按钮次数:%d", clickCount);
	}

	return 0;
}

3. 多个按钮的点击检测

上面按钮都是定义成结构体,当存在多个按钮的时候,当鼠标点击时,简单的方法就是按一定的顺序去遍历这些按钮,逐一进行点击判断。当检测到有一个按钮被点击到,则执行动作,剩余不再检测。

为了方便,这里创建一个Button数组,逐一检查。当然,这样的前提是按钮没有相互重叠,不然重叠后,是需要按照一定的顺序的。当然,这样子效率会低一些,对于十几个按钮是没有问题的,如果是大量按钮,就需要使用算法来处理了,这里就不再深入。


for (int i = 0; i < length; i++) {
	if (clickButton(&button[i], x, y) {
		//执行
		break;   //退出,已经检测到,后面的按钮不再检测
	}
}

3.1 多个按钮检测示例

下面为多个按钮的点击检测示例
在这里插入图片描述

#include <graphics.h>

struct CircleButton
{
	int x, y;
	int radius;
};

bool clickCircleButton(CircleButton* button, int x, int y)
{
	int dx = x - button->x, dy = y - button->y;
	return (dx * dx + dy * dy) <= (button->radius * button->radius);
}

void drawCircleButton(CircleButton buttonArray[], int length)
{
	setfillcolor(EGEARGB(0xFF, 0x1E, 0x90, 0xFF));
	setcolor(WHITE);
	settextjustify(CENTER_TEXT, CENTER_TEXT);
	setfont(36, 0, "");

	for (int i = 0; i < length; i++) {
		//高级绘图函数
		ege_fillellipse(buttonArray[i].x - buttonArray[i].radius, 
			buttonArray[i].y - buttonArray[i].radius,
			2 * buttonArray[i].radius, 
			2 * buttonArray[i].radius);
		xyprintf(buttonArray[i].x, buttonArray[i].y, "%d", i);
	}
}

#define BUTTON_SIZE 8

//定义按钮,确定区域
CircleButton buttonArray[BUTTON_SIZE];


int main()
{
	initgraph(640, 480, INIT_RENDERMANUAL);
	setbkcolor(WHITE);
	setbkmode(TRANSPARENT);
	ege_enable_aa(true);

	for (int i = 0; i < BUTTON_SIZE; i++) {
		buttonArray[i].x = (i % 2 * 2 + 1) * 640 / 4;
		buttonArray[i].y = (i / 2) * 320 / 3 + 60;
		buttonArray[i].radius = 50;
	}

	//鼠标左键按下位置
	int xLeftPress  = 0, yLeftPress = 0;

	//鼠标左键松开位置
	int xLeftRelease = 0, yLeftRelease = 0;

	int clickCount = 0;
	int buttonID = 0;

	for (; is_run(); delay_fps(60)) {
		bool leftClick = false;

		while (mousemsg()) {
			mouse_msg msg = getmouse();

			//判断鼠标左键点击(左键按下确定位置,抬起为执行时刻)
			if (msg.is_left()) {
				if (msg.is_down()) {
					//记录左键按下位置
					xLeftPress = msg.x;
					yLeftPress = msg.y;
				}
				else {
					xLeftRelease = msg.x;
					yLeftRelease = msg.y;
					//左键抬起,点击动作执行
					leftClick = true;
				}
			}
		}

		//当鼠标左键存在点击
		if (leftClick) {
			//对按钮进行检查,点击位置是否在按钮区域内
			for (int i = 0; i < BUTTON_SIZE; i++) {
				if (clickCircleButton(&buttonArray[i], xLeftPress, yLeftPress)) {
					buttonID = i;
					//执行
					break;   //退出,已经检测到,后面的按钮不再检测
				}
			}
		}

		//绘制
		cleardevice();
		drawCircleButton(buttonArray, BUTTON_SIZE);
		setcolor(BLACK);
		setfont(24, 0, "");
		settextjustify(LEFT_TEXT, TOP_TEXT);
		xyprintf(240, 360, "点击按钮ID:%d", buttonID);
	}

	return 0;
}

四、按钮状态

  给按钮加上几个状态参数,比如用来标记点击,选中,悬停,可被点击等,绘图按钮时,就根据这些状态,来进行相应的绘制,通常是选用不同的颜色,也有更换样式的。

而这些状态,在鼠标按下,松开,悬停,点击等操作进行后更改。

在这里插入图片描述
这里只简单地做个选中的示例:当鼠标点击按钮后,按钮的选中状态选中非选中之间切换。

if (clickCircleButton(&buttonArray[i], xLeftPress, yLeftPress)) {
	//状态切换,这里在选中和非选中之间切换
	buttonArray[i].checked = !buttonArray[i].checked;
}

对于按钮的不同状态,设置不同的填充颜色。

for (int i = 0; i < length; i++) {
	//根据状态进行不同的绘制,这里按状态改变颜色
	if (buttonArray[i].checked) {
		setfillcolor(EGEARGB(0xFF, 0x1E, 0x90, 0xFF));
	} else {
		setfillcolor(EGEARGB(0xFF, 0x40, 0xE0, 0xD0));
	}
}

完整示例如下:

#include <graphics.h>

struct CircleButton
{
	int x, y;		/* 圆心*/
	int radius;		/* 半径*/
	bool checked;	/* 是否选中*/
};

bool clickCircleButton(CircleButton* button, int x, int y)
{
	int dx = x - button->x, dy = y - button->y;
	return (dx * dx + dy * dy) <= (button->radius * button->radius);
}

void drawCircleButton(CircleButton buttonArray[], int length)
{
	
	setfillcolor(EGEARGB(0xFF, 0x1E, 0x90, 0xFF));
	setcolor(WHITE);
	settextjustify(CENTER_TEXT, CENTER_TEXT);
	setfont(36, 0, "");

	color_t lastFillColor = getfillcolor();

	for (int i = 0; i < length; i++) {
		//根据状态进行不同的绘制,这里按状态改变颜色
		color_t curColor;
		if (buttonArray[i].checked) {
			curColor = EGEARGB(0xFF, 0x1E, 0x90, 0xFF);
		} else {
			curColor = EGEARGB(0xFF, 0x40, 0xE0, 0xD0);
		}

		//为了减少颜色设置操作进行的优化操作,少量绘制可有可无
		if (lastFillColor != curColor) {
			setfillcolor(curColor);
			lastFillColor = curColor;
		}

		//高级绘图函数
		ege_fillellipse(buttonArray[i].x - buttonArray[i].radius, 
			buttonArray[i].y - buttonArray[i].radius,
			2 * buttonArray[i].radius, 
			2 * buttonArray[i].radius);
		xyprintf(buttonArray[i].x, buttonArray[i].y, "%d", i);
	}
}

#define BUTTON_SIZE 8

//定义按钮,确定区域
CircleButton buttonArray[BUTTON_SIZE];


int main()
{
	initgraph(640, 480, INIT_RENDERMANUAL);
	setbkcolor(WHITE);
	setbkmode(TRANSPARENT);
	ege_enable_aa(true);

	for (int i = 0; i < BUTTON_SIZE; i++) {
		buttonArray[i].x = (i % 2 * 2 + 1) * 640 / 4;
		buttonArray[i].y = (i / 2) * 320 / 3 + 60;
		buttonArray[i].radius = 50;
		buttonArray[i].checked = false;
	}

	//鼠标左键按下位置
	int xLeftPress  = 0, yLeftPress = 0;

	//鼠标左键松开位置
	int xLeftRelease = 0, yLeftRelease = 0;

	int clickCount = 0;
	int buttonID = 0;

	for (; is_run(); delay_fps(60)) {
		bool leftClick = false;

		while (mousemsg()) {
			mouse_msg msg = getmouse();

			//判断鼠标左键点击(左键按下确定位置,抬起为执行时刻)
			if (msg.is_left()) {
				if (msg.is_down()) {
					//记录左键按下位置
					xLeftPress = msg.x;
					yLeftPress = msg.y;
				}
				else {
					xLeftRelease = msg.x;
					yLeftRelease = msg.y;
					//左键抬起,点击动作执行
					leftClick = true;
				}
			}
		}

		//当鼠标左键存在点击
		if (leftClick) {
			//对按钮进行检查,点击位置是否在按钮区域内
			for (int i = 0; i < BUTTON_SIZE; i++) {
				if (clickCircleButton(&buttonArray[i], xLeftPress, yLeftPress)) {
					buttonID = i;

					//状态切换,这里在选中和非选中之间切换
					buttonArray[i].checked = !buttonArray[i].checked;
					//执行
					break;   //退出,已经检测到,后面的按钮不再检测
				}
			}
		}

		//绘制
		cleardevice();
		drawCircleButton(buttonArray, BUTTON_SIZE);
		setcolor(BLACK);
		setfont(24, 0, "");
		settextjustify(LEFT_TEXT, TOP_TEXT);
		xyprintf(240, 360, "点击按钮ID:%d", buttonID);
	}

	return 0;
}

分享:

低价透明

统一报价,无隐形消费

金牌服务

一对一专属顾问7*24小时金牌服务

信息保密

个人信息安全有保障

售后无忧

服务出问题客服经理全程跟进