EGE绘图之五 按钮(上)

48 篇文章 0 订阅

EGE专栏:EGE专栏

上一篇:EGE绘图之四 Gif动图播放

下一篇:EGE绘图之五 按钮(下)


EGE绘图之(五) 按钮(上)

文章最后修改时间:2021年7月7日22:24:34

目录

一、按钮功能

按钮最基本的功能便是响应点击事件,再有便是长按事件,长按事件不一定会响应。

1. 普通按钮

普通按钮一般只响应点击和长按事件,事件触发后,进行相应动作。
在这里插入图片描述

2. 可选按钮

可选按钮会添加选中状态,可以进行选中取消选中操作,是按钮处于选中状态非选中状态。一般由点击事件或长按事件触发。

在这里插入图片描述
当多个可选按钮组成一个组时,根据可选行为的不同,一般分为 单选按钮复选框

2.1 单选按钮 (Radio Button)

在一个按钮组中,单选按钮最多只能有一个被选中。当一个按钮被选中时,按钮组中的其它按钮都要处于非选中状态。(就和常见的单选题类似)

2.2 复选框 (Check Box)

在一个按钮组中,每个按钮都可以独立地被设置为选中或非选中状态。(就和常见的多选题类似)

如下所示:

  • 上面一排为Radio Button,每一列都为一组,一组中只能有一个被选中。
  • 下面一排为Check Box, 每一列为一组,一组种可以有多个被选中。
    在这里插入图片描述

3. 多功能按钮

按钮可以添加不同的属性,根据点击和长按事件,响应不同的动作。
如下方的带进度条和执行结果的按钮,弹出和收入菜单的按钮等,这些效果和行为,可以根据需要进行自定义。

在这里插入图片描述

二、按钮样式

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

在这里插入图片描述

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

三、 按钮点击

1. 按钮点击的确定

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

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

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

如,鼠标左键按下:

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

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

  • 鼠标按下:仅确定鼠标所点击的按钮,而不执行实际的按钮点击动作
  • 鼠标抬起:执行相应的按钮点击动作。

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

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

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 h e i g h t = b o t t o m t o p width = right - left \ height = bottom - top width=rightleftheight=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>
#include <math.h>

// 矩形按钮
struct RectButton
{
	int x, y;
	int width, height;
};

// 判断点(x, y) 是否在按钮点击区域内
bool insideRectButton(const RectButton* button, int x, int y);

// 绘制按钮
void drawRectButton(const RectButton* button);


void draw();

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

int clickCount = 0;

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

	bool clickButton = false;
	bool redraw = true;

	for (; is_run(); delay_fps(60)) {
		while (mousemsg()) {
			mouse_msg msg = getmouse();

			//判断鼠标左键点击(左键按下确定位置,抬起为执行时刻)
			if (msg.is_left()) {
				if (msg.is_down()) {
					//检测点击的按钮
					clickButton = insideRectButton(&button, msg.x, msg.y);
				}
				else {
					//左键抬起,点击动作执行
					if (clickButton)
					{
						clickButton = false;
						redraw = true;
						clickCount++;
					}
				}
			}
		}

		//绘制
		if (redraw) {
			cleardevice();
			draw();
			redraw = false;
		}	
	}

	return 0;
}

bool insideRectButton(const 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(const RectButton* button)
{
	setfillcolor(EGERGB(0x1E, 0x90, 0xFF));
	bar(button->x, button->y, button->x + button->width, button->y + button->height);
}



void draw()
{
	drawRectButton(&button);
	setcolor(BLACK);
	setfont(24, 0, "");
	xyprintf(240, 360, "点击按钮次数:%d", clickCount);
}
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 egin{cases} x= rac{left+right}{2}\ y= rac{top+bottom}{2}\ radius= rac{right-left}{2}\ end{cases} x=2left+righty=2top+bottomradius=2rightleft

{ l e f t = x r a d i u s t o p = y r a d i u s w i d t h = 2 r a d i u s h e i g h t = 2 r a d i u s left{ egin{aligned} left&=x-radius\ top&=y-radius\ width&=2cdot radius\ height&=2cdot radius\ end{aligned} ight. lefttopwidthheight=xradius=yradius=2radius=2radius

2.2.2 点在圆形区域内的判定

如果点 P ( x , y ) P(x, y) P(x,y)在一个圆心为 C ( x 0 , y 0 ) C(x_0,y_0) C(x0,y0),半径为 r r r的 圆内,有以下关系:
点 P P P与圆心 C C C的距离
d = ( x x 0 ) 2 + ( y y 0 ) 2 r d =sqrt{left( x-x_0 ight) ^2+left( y-y_0 ight) ^2}leqslant r d=(xx0)2+(yy0)2 r
由于开方运行复杂,可将两边同时平方,不等号方向保持不变,可得:
( x x 0 ) 2 + ( y y 0 ) 2 r a d i u s 2 left( x-x_0 ight) ^2+left( y-y_0 ight) ^2leqslant radius^2 (xx0)2+(yy0)2radius2

在这里插入图片描述

用代码表示:

//计算点与圆心的xy差值
int dx = x - x0;
int dy = y - y0;

//(点到圆心距离的平方) 小于等于 (半径的平方)
(dx * dx + dy * dy) <= (r * r)
2.2.3 圆形区域点击判定示例

在这里插入图片描述

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

// 圆形按钮
struct CircleButton
{
	int x, y;
	int radius;
};

// 判断点(x, y) 是否在按钮点击区域内
bool insideCircleButton(const CircleButton* button, int x, int y);

// 绘制按钮
void drawCircleButton(const CircleButton* button);


void draw();

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

int clickCount = 0;

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

	bool clickButton = false;
	bool redraw = true;

	for (; is_run(); delay_fps(60)) {
		while (mousemsg()) {
			mouse_msg msg = getmouse();

			//判断鼠标左键点击(左键按下确定位置,抬起为执行时刻)
			if (msg.is_left()) {
				if (msg.is_down()) {
					//检测点击的按钮
					clickButton = insideCircleButton(&button, msg.x, msg.y);
				}
				else {
					//左键抬起,点击动作执行
					if (clickButton)
					{
						clickButton = false;
						redraw = true;
						clickCount++;
					}
				}
			}
		}

		//绘制
		if (redraw) {
			cleardevice();
			draw();
			redraw = false;
		}	
	}

	return 0;
}

bool insideCircleButton(const 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(const 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);
}


void draw()
{
	drawCircleButton(&button);
	setcolor(BLACK);
	setfont(24, 0, "");
	xyprintf(240, 360, "点击按钮次数:%d", clickCount);
}
2.3 圆角矩形区域的点击判定

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

2.3.1 圆角矩形区域的表示

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

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

首先,如果点在圆角矩形区域内部,则肯定满足点在圆角矩形外部包围矩形内部的条件。
如果点在圆角矩形区域内部,需要两个条件同时成立。

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

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

与矩形不同的是,即使位于包围矩形内,但是点也可能在圆角处。因此增加在圆角处的判断:
由对称性质,以圆角矩形中心为原点,可以将圆角矩形分成四个区域,并对称映射到第一象限,如下图所示。

在这里插入图片描述
当点位于图中所示橙色正方形内部时,可以根据点是否在圆角所在的圆内来判断。

条件2: 在满足条件1的情况下:

  • 当点P位于圆角处时,点P位于圆角所在的圆内。
  • 当点P不在圆角处。

下图为判断流程:
点P位于圆角矩形内部的判断流程

比较复杂的是在圆角处的判断:
当 x w 2 r xgeqslant rac{w}{2}-r x2wr 且 y h 2 r ygeqslant rac{h}{2}-r y2hr 时,点P在圆角矩形的圆角处。此时,如果点P在圆角矩形内,则满足关系:
( x ( w 2 r ) ) 2 + ( y ( h 2 r ) ) 2 r 2 left( x-left( rac{w}{2}-r ight) ight) ^2+left( y-left( rac{h}{2}-r ight) ight) ^2leqslant ,r^2 (x(2wr))2+(y(2hr))2r2

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

在这里插入图片描述

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

// 圆角矩形按钮
struct RoundRectButton
{
	int x, y;
	int width, height;
	float radius;
};

// 判断点(x, y) 是否在按钮点击区域内
bool insideRoundRectButton(const RoundRectButton* button, int x, int y);

// 绘制按钮
void drawRoundRectButton(const RoundRectButton* button);


void draw();

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

int clickCount = 0;

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

	bool clickButton = false;
	bool redraw = true;

	for (; is_run(); delay_fps(60)) {
		while (mousemsg()) {
			mouse_msg msg = getmouse();

			//判断鼠标左键点击(左键按下确定位置,抬起为执行时刻)
			if (msg.is_left()) {
				if (msg.is_down()) {
					//检测点击的按钮
					clickButton = insideRoundRectButton(&button, msg.x, msg.y);
				}
				else {
					//左键抬起,点击动作执行
					if (clickButton)
					{
						clickButton = false;
						redraw = true;
						clickCount++;
					}
				}
			}
		}

		//绘制
		if (redraw) {
			cleardevice();
			draw();
			redraw = false;
		}	
	}

	return 0;
}

bool insideRoundRectButton(const RoundRectButton* button, int x, int y)
{
	bool inside = false;

	// 点在包围矩形内
	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 = (float)fabs(x - centerx);
		float dy = (float)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)
				)
			) {
			inside = true;
		}
	}

	return inside;
}


void drawRoundRectButton(const RoundRectButton* button)
{
	setfillcolor(EGEARGB(0xFF, 0x1E, 0x90, 0xFF));

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

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

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


	float diameter = 2 * button->radius;
	float dx = button->width - diameter;
	float dy = button->height - diameter;


	ege_fillpie((float)(button->x + dx), (float)(button->y + dy),	diameter, diameter, 0.0f,	90.0f);
	ege_fillpie((float)(button->x),		 (float)(button->y + dy),	diameter, diameter, 90.0f,	90.0f);
	ege_fillpie((float)(button->x),		 (float)(button->y),		diameter, diameter, 180.0f,	90.0f);
	ege_fillpie((float)(button->x + dx), (float)(button->y),		diameter, diameter, 270.0f,	90.0f);
}


void draw()
{
	drawRoundRectButton(&button);
	setcolor(BLACK);
	setfont(24, 0, "");
	xyprintf(240, 360, "点击按钮次数:%d", clickCount);
}

3. 多个按钮的点击检测

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

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

for (int i = 0; i < buttonArrayLength; i++) {
	// 点击位置在按钮内
	if (insideButton(&buttonArray[i], x, y)) {
		clickButtonId = i;
		break;   //退出,已经检测到,后面的按钮不再检测
	}
}

3.1 多个按钮检测示例

下面为多个按钮的点击检测示例。底下文字显示点击按钮的ID
在这里插入图片描述

#include <graphics.h>

// 圆形按钮
struct CircleButton
{
	int x, y;		/* 圆心*/
	int radius;		/* 半径*/
};

// 判断点(x, y)是否在按钮点击区域内部
bool insideButton(const CircleButton* button, int x, int y);

// 绘制所有按钮
void drawCircleButton(const CircleButton buttonArray[], int length);

// 查找 (x, y) 所在的按钮,返回按钮ID, 没有返回 -1
int searchButton(int x, int y, const CircleButton buttonArray[], int length);

// 绘制
void draw();

#define BUTTON_SIZE 8
#define BUTTON_ID_NONE -1

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

// 按下按钮ID
int clickButtonId = BUTTON_ID_NONE;

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;
	}

	clickButtonId = BUTTON_ID_NONE;

	bool redraw = true;
	int btnId = BUTTON_ID_NONE;

	for (; is_run(); delay_fps(60)) {

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

			// 判断鼠标左键按下(左键按下确定位置,同时判断是否为按钮区域
			// 抬起则解除按下状态
			if (msg.is_left()) {
				if (msg.is_down()) {
					// 检查是否有按钮被按下
					btnId = searchButton(msg.x, msg.y, buttonArray, BUTTON_SIZE);
				}
				else {
					//左键抬起,执行动作(这里显示设置点击按钮ID)
					if (btnId != clickButtonId) {
						clickButtonId = btnId;
						redraw = true;
					}
				}
			}
		}

		// 判断是否需要重绘,减少不必要的绘制操作
		if (redraw) {
			cleardevice();
			draw();
			redraw = false;
		}
	}

	return 0;
}


bool insideButton(const 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(const 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);
	}
}

int searchButton(int x, int y, const CircleButton buttonArray[], int length)
{
	int buttonId = BUTTON_ID_NONE;

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

	return buttonId;
}

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

EGE专栏:EGE专栏

上一篇:EGE绘图之四 Gif动图播放

下一篇:EGE绘图之五 按钮(下)

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值