我想做三国志

努力学习做游戏的小白

按钮 铜牌收录

程序截图

操作方法

点击按钮操作。

更新说明

原先版本仅支持 Unicode 字符集,此次经过修改可支持多字节字符集。

原先按钮仅支持文字,此次按钮可支持图片,需传入 normal 状态下图片的路径,touch 状态下图片的路径,press 状态下图片的路径,图片可支持透明通道,会自动缩放。不过用图片时需要传几张图片才能演示,不能代码复制进去就能跑起来。

2023/2/20 修了一些 bug 如更改字体大小按钮尺寸会改变的问题

函数说明

// 添加一个按钮进按钮管理者,由于插入的按钮是拷贝不是引用,所以返回一个插入的按钮指针供操作按钮
Button* ButtonManager::AddButton(Button button)
{
	this->buttonlist.push_back(button);
	return &buttonlist.back();
}

// Vec2 构造函数
Vec2(double xx, double yy) :x(xx), y(yy) {};

// Rect 构造函数
Rect(Vec2 position, Vec2 size) :size(size), position(position) {}

// 按钮构造函数
// boundingbox 是边框,报错按钮位置和尺寸的信息,buttontext 是按钮文字信息,releaseFunc 是松开按钮后调用的函数,releaseParam 是 releaseFunc 的参数
Button(Rect boundingbox, std::wstring buttontext, std::function<int(void*)> releaseFunc, void* releaseParam) :
	boundingbox(boundingbox), buttontext(buttontext), releaseFunc(releaseFunc), releaseParam(releaseParam) {}

// 按钮用构造函数生成
ButtonManager manager;
	manager.AddButton(Button(Rect(Vec2(WIDTH / 2.0, HEIGHT / 2.0), Vec2(WIDTH / 2.0, HEIGHT / 2.0)), L"",
		[](void* param)
		{
			if (param == nullptr)return 1;
			Button* button = (Button*)param;
			WCHAR arr[128];
			InputBox(arr, 128, L"请输入按钮内的文字", L"输入框", button->buttontext.c_str());
			button->buttontext = arr;
			double width = 0, height = 0;
			InputBox(arr, 128, L"请输入按钮的宽度", L"输入框", L"");
			swscanf_s(arr, TEXT("%lf"), &width);
			InputBox(arr, 128, L"请输入按钮的高度", L"输入框", L"");
			swscanf_s(arr, TEXT("%lf"), &height);
			if (width != 0.0 && height != 0.0)
				button->boundingbox.size = Vec2(width, height);
			return 0;
		}, nullptr));
// 接受鼠标信息
void ButtonManager::ReceiveMessage(ExMessage* msg)
{
	for (Button& button : this->buttonlist)
	{
		button.receiver(msg);
	}
}

// 画按钮
void ButtonManager::DrawButton()
{
	for (Button& button : this->buttonlist)
	{
		button.DrawButton();
	}
}

几个重要参数

Button 类里有两个重要参数,textsize 和 textareasize,textsize 控制字体高度,textareasize 控制按钮内文字所能占据的区域在按钮区域中的占比。改 textsize 字体会改变大小,改 textareasize 字体所能占据的区域变大或变小。

class Button
{
private:
	double textsize = 20;
	double textareasize = 0.9;
	Vec2 defaultsize = Vec2(textwidth(L"...") / textareasize, textheight(L"...") / textareasize);
	Vec2 defaulttext = Vec2(textwidth(L"..."), textheight(L"..."));
	State nowstate = State::general;
	void DrawButton_General();
	void DrawButton_Touch();
	void DrawButton_Press();
	void DrawButton_Forbidden();
	bool isPress = false;
public:
	Button() :boundingbox(), buttontext() {}
	Button(Rect boundingbox, std::wstring buttontext, std::function<int(void*)> releaseFunc, void* releaseParam) :
		boundingbox(boundingbox), buttontext(buttontext), releaseFunc(releaseFunc), releaseParam(releaseParam) {}
	std::wstring buttontext;
	Rect boundingbox;
	std::function<int(void*)> releaseFunc = nullptr;
	void* releaseParam = nullptr;
	void DrawButton();
	void receiver(ExMessage* msg);
	void ForbidButton() { this->nowstate = State::forbidden; }	// 禁用按钮
	void RefreshButton() { this->nowstate = State::general; }	// 恢复按钮
	void SetTextSize(double size)
	{
		textsize = size;
		defaultsize = Vec2(textsize * 1.5 / textareasize, textsize / textareasize);
		defaulttext = Vec2(textsize * 1.5, textsize);
	}
	void SetTextAreaSize(double size)
	{
		textareasize = size;
		defaultsize = Vec2(textsize * 1.5 / textareasize, textsize / textareasize);
		defaulttext = Vec2(textsize * 1.5, textsize);
	}
};

宏 TCW_GUI_BUTTON_MYSELF,当按钮回调函数调用时,若回调函数的参数为 TCW_GUI_BUTTON_MYSELF 也即为空,则参数默认为按钮自身的指针。

#define TCW_GUI_BUTTON_MYSELF 0

void Button::DrawButton()
{
	switch (this->nowstate)
	{
	case State::general:
		DrawButton_General();
		break;
	case State::touch:
		DrawButton_Touch();
		break;
	case State::press:
		DrawButton_Press();
		break;
	case State::release:
		DrawButton_Touch();
		if (releaseFunc != nullptr)
		{
			if (releaseParam == TCW_GUI_BUTTON_MYSELF)releaseFunc(this);
			else releaseFunc(releaseParam);
		}
		this->nowstate = State::touch;
		break;
	case State::forbidden:
		DrawButton_Forbidden();
		break;
	default:
		break;
	}
}

写这个按钮要注意字体宽度不要设置,传 0 就可以,不然会拉伸字体。

代码实现

TCW_GUI.h

#pragma once
#include<graphics.h>
#include<string>
#include<list>
#include<functional>
#include<stdio.h>
#pragma comment( lib, "MSIMG32.LIB")
#define TCW_GUI_BUTTON_MYSELF 0
#ifdef UNICODE
#define STRING std::wstring
#define SPRINTF swprintf_s
#define SSCANF swscanf_s
#else
#define STRING std::string
#define SPRINTF sprintf
#define SSCANF sscanf
#endif // UNICODE

namespace TCW_GUI
{
	enum class State
	{
		general = 0,
		touch = 1,
		press = 2,
		release = 3,
		forbidden = 4
	};
	enum class DrawDecision
	{
		text = 0,
		graph = 1
	};

	class Vec2
	{
	public:
		double x, y;
		Vec2() :x(0), y(0) {}
		Vec2(double xx, double yy) :x(xx), y(yy) {};
		Vec2 operator+(Vec2 num)
		{
			return Vec2(x + num.x, y + num.y);
		}
		Vec2 operator-(Vec2 num)
		{
			return Vec2(x - num.x, y - num.y);
		}
		Vec2 operator/(double num)
		{
			return Vec2(x / num, y / num);
		}
		Vec2 operator*(double num)
		{
			return Vec2(x * num, y * num);
		}
	};

	class Rect
	{
	public:
		Rect() :size(), position() {}
		Rect(Vec2 position, Vec2 size) :size(size), position(position) {}
		Vec2 size;
		Vec2 position;
		bool isInRect(Vec2 point)
		{
			Vec2 left_top = position - size / 2.0;
			Vec2 right_buttom = position + size / 2.0;
			if (point.x >= left_top.x && point.y >= left_top.y &&
				point.x <= right_buttom.x && point.y <= right_buttom.y)return true;
			return false;
		}
	};

	class Button
	{
	private:
		double textsize = 20;
		double textareasize = 0.9;
		Vec2 defaultsize;
		Vec2 defaulttext;
		State nowstate = State::general;
		DrawDecision drawdecision = DrawDecision::text;
		void DrawButton_General();
		void DrawButton_Touch();
		void DrawButton_Press();
		void DrawButton_Forbidden();
		bool isPress = false;
	public:
		Button() :boundingbox(), buttontext()
		{
			settextstyle(textsize, 0, TEXT("微软雅黑"));
			Vec2 defaultsize = Vec2(textwidth(TEXT("...")) / textareasize, textheight(TEXT("...")) / textareasize);
			Vec2 defaulttext = Vec2(textwidth(TEXT("...")), textheight(TEXT("...")));
		}
		Button(Rect boundingbox, STRING buttontext, std::function<int(void*)> releaseFunc, void* releaseParam) :
			boundingbox(boundingbox), buttontext(buttontext), releaseFunc(releaseFunc), releaseParam(releaseParam)
		{
			drawdecision = DrawDecision::text;
			settextstyle(textsize, 0, TEXT("微软雅黑"));
			Vec2 defaultsize = Vec2(textwidth(TEXT("...")) / textareasize, textheight(TEXT("...")) / textareasize);
			Vec2 defaulttext = Vec2(textwidth(TEXT("...")), textheight(TEXT("...")));
		}
		Button(Rect boundingbox, STRING graphsrc_normal, STRING graphsrc_touch, STRING graphsrc_press,
			std::function<int(void*)> releaseFunc, void* releaseParam) :
			boundingbox(boundingbox), graphsrc_normal(graphsrc_normal), graphsrc_touch(graphsrc_touch), graphsrc_press(graphsrc_press),
			releaseFunc(releaseFunc), releaseParam(releaseParam)
		{
			drawdecision = DrawDecision::graph;
		}
		STRING buttontext;
		STRING graphsrc_normal;
		STRING graphsrc_touch;
		STRING graphsrc_press;

		Rect boundingbox;
		std::function<int(void*)> releaseFunc = nullptr;
		void* releaseParam = nullptr;
		void DrawButton();
		void DrawButton_Text(State state);
		void DrawButton_Graph(State state);
		void receiver(ExMessage* msg);
		void ForbidButton() { this->nowstate = State::forbidden; }	// 禁用按钮
		void RefreshButton() { this->nowstate = State::general; }	// 恢复按钮
		void SetTextSize(double size)
		{
			textsize = size;
			defaultsize = Vec2(textsize * 1.5 / textareasize, textsize / textareasize);
			defaulttext = Vec2(textsize * 1.5, textsize);
		}
		void SetTextAreaSize(double size)
		{
			textareasize = size;
			defaultsize = Vec2(textsize * 1.5 / textareasize, textsize / textareasize);
			defaulttext = Vec2(textsize * 1.5, textsize);
		}
	};

	class ButtonManager
	{
		std::list<Button> buttonlist;
	public:
		Button* AddButton(Button button);
		void ReceiveMessage(ExMessage* msg);
		void DrawButton();
	};

	void Rectangle_TCW(Vec2 left_top, Vec2 right_buttom)
	{
		rectangle(left_top.x, left_top.y, right_buttom.x, right_buttom.y);
	}

	void Fillrectangle_TCW(Vec2 left_top, Vec2 right_buttom)
	{
		fillrectangle(left_top.x, left_top.y, right_buttom.x, right_buttom.y);
	}

	void Outtextxy_TCW(Vec2 position, const TCHAR* str)
	{
		outtextxy(position.x, position.y, str);
	}

	void Button::DrawButton_Text(State state)
	{
		LOGFONT log;
		COLORREF textcol;
		COLORREF linecol;
		COLORREF fillcol;
		int bkmode;
		gettextstyle(&log);
		bkmode = getbkmode();
		textcol = gettextcolor();
		linecol = getlinecolor();
		fillcol = getfillcolor();

		// 默认数值
		settextstyle(textsize, 0, TEXT("微软雅黑"));
		settextcolor(BLACK);
		setbkmode(TRANSPARENT);
		setlinecolor(BLACK);
		setfillcolor(WHITE);
		switch (state)
		{
		case TCW_GUI::State::general:
			break;
		case TCW_GUI::State::touch:
			setfillcolor(RGB(240, 240, 240));
			break;
		case TCW_GUI::State::press:
			setfillcolor(RGB(240, 240, 240));
			break;
		case TCW_GUI::State::release:
			break;
		case TCW_GUI::State::forbidden:
			settextcolor(RGB(128, 128, 128));
			break;
		default:
			break;
		}
		Vec2 size_button = Vec2(this->boundingbox.size * textareasize);
		Vec2 size_text = Vec2(textwidth(this->buttontext.c_str()), textsize);

		if (boundingbox.size.x > defaultsize.x && boundingbox.size.y > defaultsize.y)	// 比最小值大
		{
			Rectangle_TCW(this->boundingbox.position - this->boundingbox.size / 2.0,
				this->boundingbox.position + this->boundingbox.size / 2.0);
			Fillrectangle_TCW(this->boundingbox.position - this->boundingbox.size / 2.0,
				this->boundingbox.position + this->boundingbox.size / 2.0);
			if (size_button.x >= size_text.x && size_button.y >= size_text.y)	// 字数没超
			{
				switch (state)
				{
				case TCW_GUI::State::general:
					Outtextxy_TCW(this->boundingbox.position - size_text / 2.0, buttontext.c_str());
					break;
				case TCW_GUI::State::touch:
					Outtextxy_TCW(this->boundingbox.position - size_text / 2.0, buttontext.c_str());
					break;
				case TCW_GUI::State::press:
					Outtextxy_TCW(this->boundingbox.position - size_text / 2.0 + Vec2(2, 2), buttontext.c_str());
					break;
				case TCW_GUI::State::release:
					break;
				case TCW_GUI::State::forbidden:
					Outtextxy_TCW(this->boundingbox.position - size_text / 2.0, buttontext.c_str());
					break;
				default:
					break;
				}
			}
			else	// 字数超了
			{
				int wordnum = size_button.x / textwidth(buttontext.c_str()) * buttontext.size() - 2;
				STRING realstr = buttontext.substr(0, wordnum);
				realstr += TEXT("...");
				Outtextxy_TCW(this->boundingbox.position - size_text / 2.0, realstr.c_str());
				switch (state)
				{
				case TCW_GUI::State::general:
					Outtextxy_TCW(this->boundingbox.position - size_text / 2.0, realstr.c_str());
					break;
				case TCW_GUI::State::touch:
					Outtextxy_TCW(this->boundingbox.position - size_text / 2.0, realstr.c_str());
					break;
				case TCW_GUI::State::press:
					Outtextxy_TCW(this->boundingbox.position - size_text / 2.0 + Vec2(2, 2), realstr.c_str());
					break;
				case TCW_GUI::State::release:
					break;
				case TCW_GUI::State::forbidden:
					Outtextxy_TCW(this->boundingbox.position - size_text / 2.0, realstr.c_str());
					break;
				default:
					break;
				}
			}
		}
		else	// 比最小值小
		{
			Rectangle_TCW(this->boundingbox.position - this->defaultsize / 2.0,
				this->boundingbox.position + this->defaultsize / 2.0);
			Fillrectangle_TCW(this->boundingbox.position - this->defaultsize / 2.0,
				this->boundingbox.position + this->defaultsize / 2.0);
			if (defaulttext.x >= size_text.x && defaulttext.y >= size_text.y)	// 字宽比三个点小
			{
				switch (state)
				{
				case TCW_GUI::State::general:
					Outtextxy_TCW(this->boundingbox.position - size_text / 2.0, buttontext.c_str());
					break;
				case TCW_GUI::State::touch:
					Outtextxy_TCW(this->boundingbox.position - size_text / 2.0, buttontext.c_str());
					break;
				case TCW_GUI::State::press:
					Outtextxy_TCW(this->boundingbox.position - size_text / 2.0 + Vec2(2, 2), buttontext.c_str());
					break;
				case TCW_GUI::State::release:
					break;
				case TCW_GUI::State::forbidden:
					Outtextxy_TCW(this->boundingbox.position - size_text / 2.0, buttontext.c_str());
					break;
				default:
					break;
				}
			}
			else	// 字宽比三个点大
			{
				switch (state)
				{
				case TCW_GUI::State::general:
					Outtextxy_TCW(this->boundingbox.position - defaulttext / 2.0, TEXT("..."));
					break;
				case TCW_GUI::State::touch:
					Outtextxy_TCW(this->boundingbox.position - defaulttext / 2.0, TEXT("..."));
					break;
				case TCW_GUI::State::press:
					Outtextxy_TCW(this->boundingbox.position - size_text / 2.0 + Vec2(2, 2), TEXT("..."));
					break;
				case TCW_GUI::State::release:
					break;
				case TCW_GUI::State::forbidden:
					Outtextxy_TCW(this->boundingbox.position - defaulttext / 2.0, TEXT("..."));
					break;
				default:
					break;
				}
			}
		}

		settextstyle(&log);
		settextcolor(textcol);
		setbkmode(bkmode);
		setlinecolor(linecol);
		setfillcolor(fillcol);
	}

	void Button::DrawButton_Graph(State state)
	{
		IMAGE img;
		switch (state)
		{
		case TCW_GUI::State::general:
			loadimage(&img, (this->graphsrc_normal).c_str());
			break;
		case TCW_GUI::State::touch:
			loadimage(&img, (this->graphsrc_touch).c_str());
			break;
		case TCW_GUI::State::press:
			loadimage(&img, (this->graphsrc_press).c_str());
			break;
		case TCW_GUI::State::release:
			break;
		case TCW_GUI::State::forbidden:
			loadimage(&img, (this->graphsrc_normal).c_str());
			break;
		default:
			break;
		}
		IMAGE* srcimg = &img;
		HDC dstDC = GetImageHDC(NULL);
		HDC srcDC = GetImageHDC(srcimg);
		int w = srcimg->getwidth();
		int h = srcimg->getheight();

		// 结构体的第三个成员表示额外的透明度,0 表示全透明,255 表示不透明。
		BLENDFUNCTION bf = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
		// 使用 Windows GDI 函数实现半透明位图
		Vec2 left_top = this->boundingbox.position - this->boundingbox.size / 2.0;
		AlphaBlend(dstDC, left_top.x, left_top.y, boundingbox.size.x, boundingbox.size.y, srcDC, 0, 0, w, h, bf);
	}

	void Button::DrawButton_General()
	{
		switch (this->drawdecision)
		{
		case TCW_GUI::DrawDecision::text:DrawButton_Text(State::general);
			break;
		case TCW_GUI::DrawDecision::graph:DrawButton_Graph(State::general);
			break;
		default:
			break;
		}
	}

	void Button::DrawButton_Touch()
	{
		switch (this->drawdecision)
		{
		case TCW_GUI::DrawDecision::text:DrawButton_Text(State::touch);
			break;
		case TCW_GUI::DrawDecision::graph:DrawButton_Graph(State::touch);
			break;
		default:
			break;
		}
	}

	void Button::DrawButton_Press()
	{
		switch (this->drawdecision)
		{
		case TCW_GUI::DrawDecision::text:DrawButton_Text(State::press);
			break;
		case TCW_GUI::DrawDecision::graph:DrawButton_Graph(State::press);
			break;
		default:
			break;
		}
	}

	void Button::DrawButton_Forbidden()
	{
		switch (drawdecision)
		{
		case TCW_GUI::DrawDecision::text:DrawButton_Text(State::forbidden);
			break;
		case TCW_GUI::DrawDecision::graph:DrawButton_Graph(State::forbidden);
			break;
		default:
			break;
		}
	}

	void Button::DrawButton()
	{
		switch (this->nowstate)
		{
		case State::general:
			DrawButton_General();
			break;
		case State::touch:
			DrawButton_Touch();
			break;
		case State::press:
			DrawButton_Press();
			break;
		case State::release:
			DrawButton_Touch();
			if (releaseFunc != nullptr)
			{
				if (releaseParam == TCW_GUI_BUTTON_MYSELF)releaseFunc(this);
				else releaseFunc(releaseParam);
			}
			this->nowstate = State::touch;
			break;
		case State::forbidden:
			DrawButton_Forbidden();
			break;
		default:
			break;
		}
	}

	void Button::receiver(ExMessage* msg)
	{
		if (this->nowstate == State::forbidden)return;
		// 先 general 后 touch 再 press 一个 release 后重新 general
		if (!isPress && !this->boundingbox.isInRect(Vec2(msg->x, msg->y)))
		{
			this->nowstate = State::general;
		}
		else if (!isPress && this->boundingbox.isInRect(Vec2(msg->x, msg->y)))
		{
			if (!msg->lbutton)
				this->nowstate = State::touch;
			else if (this->nowstate == State::touch)
			{
				isPress = true;
				this->nowstate = State::press;
			}
		}
		else if (isPress && this->boundingbox.isInRect(Vec2(msg->x, msg->y)))
		{
			if (!msg->lbutton)
			{
				isPress = false;
				this->nowstate = State::release;
			}
			else this->nowstate = State::press;
		}
		else if (isPress && !this->boundingbox.isInRect(Vec2(msg->x, msg->y)))
		{
			if (!msg->lbutton)
			{
				isPress = false;
				this->nowstate = State::general;
			}
			else this->nowstate = State::press;
		}
	}

	Button* ButtonManager::AddButton(Button button)
	{
		this->buttonlist.push_back(button);
		return &buttonlist.back();
	}

	void ButtonManager::ReceiveMessage(ExMessage* msg)
	{
		for (Button& button : this->buttonlist)
		{
			button.receiver(msg);
		}
	}

	void ButtonManager::DrawButton()
	{
		for (Button& button : this->buttonlist)
		{
			button.DrawButton();
		}
	}
}


示例程序:

#include"TCW_GUI.h"
#include<time.h>
#define WIDTH 640
#define HEIGHT 480

int SrandCircle(void* param)
{
	int radius = rand() % 200 + 1;
	int x = rand() % WIDTH;
	int y = rand() % HEIGHT;
	circle(x, y, radius);
	return 0;
}

void Rectangle_Middle_Size(int middle_x, int middle_y, int size_width, int size_height)
{
	int left = middle_x - (size_width >> 1);
	int top = middle_y - (size_height >> 1);
	int right = middle_x + (size_width >> 1);
	int buttom = middle_y + (size_height >> 1);
	rectangle(left, top, right, buttom);
}

int SrandRectangle(void* param)
{
	int middle_x = rand() % WIDTH;
	int middle_y = rand() % HEIGHT;
	int size_width = rand() % 300 + 2;
	int size_height = rand() % 200 + 2;
	Rectangle_Middle_Size(middle_x, middle_y, size_width, size_height);
	return 0;
}

int ClearDrawArea(void* param)
{
	cleardevice();
	return 0;
}

int main()
{
	srand((unsigned)time(NULL));
	initgraph(WIDTH, HEIGHT);
	setbkcolor(BROWN);
	cleardevice();

	ExMessage msg;
	bool isExit = false;

	int margin = 10;

	TCW_GUI::ButtonManager manager;
	manager.AddButton(TCW_GUI::Button(TCW_GUI::Rect(TCW_GUI::Vec2(WIDTH - 50 - margin, 20 + margin),
		TCW_GUI::Vec2(100, 40)), TEXT("随机画圆"), SrandCircle, nullptr));
	manager.AddButton(TCW_GUI::Button(TCW_GUI::Rect(TCW_GUI::Vec2(WIDTH - 50 - margin, 60 + margin * 2),
		TCW_GUI::Vec2(100, 40)), TEXT("随机画矩形"), SrandRectangle, nullptr));
	manager.AddButton(TCW_GUI::Button(TCW_GUI::Rect(TCW_GUI::Vec2(WIDTH - 50 - margin, 100 + margin * 3),
		TCW_GUI::Vec2(100, 40)), TEXT("清空绘图区域"), ClearDrawArea, nullptr));

	while (!isExit)
	{
		if (peekmessage(&msg, EM_MOUSE))
		{
			manager.ReceiveMessage(&msg);
			manager.DrawButton();
		}
	}

	EndBatchDraw();
	closegraph();
	return 0;
}

添加评论