自制按键显示的程序
0. 介绍
在看一些 up 主讲解 vim/emacs 等软件的界面操作时,使用了显示按键的软件。
据说蒋炎炎讲课用的是自己写的,一度以为很神奇。 现在我们用 SFML 手搓一个极其简陋的 demo 版本。
1. 界面设计
不需要复杂界面, 不需要托管图标, 不需要隐藏窗口, 不需要动态变化的窗口。
就弄一个长条形的窗口即可。 不考虑多行显示。
清除内容: 每当用户超过1秒没有输入内容, 或者本次输入内容和上次输入相差了1秒以上, 就清空以前的内容。
如果连续两次按下的按键,在1秒之内, 那么追加内容。 判断1秒, 是通过判断60帧来决定的, 因为我设定了 fps=60.
2. 代码
严格说,用 SFML 的 text 显示输入文本,不是特别合适, 比如可能只需要一次渲染而不是每个字符渲染一次。 但是没关系, 原型设计,20分钟写出代码验证想法。
关于键盘按键的表示:看一下官方 tutorial 文档,以及 SFML 源代码中 include/SFML/Window/keyboard.hpp . 简单起见我只处理了A-Z的字符, 以及Ctrl, Esc, [ 和 ].
#include <iostream>
#include <vector>
#include <iterator>
#include <SFML/Graphics.hpp>
int main()
{
constexpr int win_width = 800;
constexpr int win_height = 200;
const std::string title = "show typed keys - SFML";
sf::RenderWindow window(sf::VideoMode(win_width, win_height), title);
window.setFramerateLimit(60);
sf::Font font;
const std::string asset_dir = "../../games/Resources";
if (!font.loadFromFile(asset_dir + "/Arial.ttf"))
{
std::cerr << "failed to load font\n";
return 1;
}
std::vector<std::string> keys;
int frame_index = 0;
int prev_press_frame = 0;
while (window.isOpen())
{
int press_frame = -1;
sf::Event event;
std::vector<std::string> cur_keys;
while (window.pollEvent(event))
{
switch (event.type)
{
case sf::Event::Closed: window.close(); break;
case sf::Event::KeyReleased: // see include/SFML/Window/Keyboard.hpp for ref
if (event.key.code >= sf::Keyboard::Key::A && event.key.code <= sf::Keyboard::Key::Z)
{
std::string key(1, 'A' + (event.key.code - sf::Keyboard::Key::A));
cur_keys.push_back(key);
}
if (event.key.code == sf::Keyboard::Key::Space) cur_keys.push_back(" ");
if (event.key.code == sf::Keyboard::Key::LControl) cur_keys.push_back("Ctrl");
if (event.key.code == sf::Keyboard::Key::RControl) cur_keys.push_back("Ctrl");
if (event.key.code == sf::Keyboard::Key::Escape) cur_keys.push_back("Esc");
if (event.key.code == sf::Keyboard::Key::LBracket) cur_keys.push_back("[");
if (event.key.code == sf::Keyboard::Key::RBracket) cur_keys.push_back("]");
press_frame = frame_index;
break;
default:
break;
}
}
window.clear();
printf("[debug] frame_index=%d, press_frame=%d, prev_press_frame=%d\n",
frame_index, press_frame, prev_press_frame
);
if (cur_keys.size() > 0)
{
if (press_frame - prev_press_frame >= 60)
{
keys.clear();
keys = cur_keys;
}
else
{
std::copy(cur_keys.begin(), cur_keys.end(), std::back_inserter(keys));
}
prev_press_frame = press_frame;
}
else if (cur_keys.size() == 0)
{
if (frame_index - prev_press_frame >= 60)
{
keys.clear();
frame_index = 0;
prev_press_frame = 0;
}
}
std::string content = "";
for (int i = 0; i < keys.size(); i++)
{
content = content + keys[i];
}
sf::Text text;
text.setFont(font);
text.setString(content);
text.setCharacterSize(30);
text.setFillColor(sf::Color::White);
text.setPosition(20, 20);
window.draw(text);
window.display();
frame_index++;
}
return 0;
}
3. 效果
4. 总结
由于先前几篇的练习, 对 SFML 有了基本的使用经验的情况下, 对于显示按键的实现能够快速上手。 主要思考点在于,怎样显示按键内容, 怎样清空内容。 通过判断相邻两次按键的时间差,大于1秒就清空, 否则保持原内容。 通过使用 switch case 语句,简化了 A-Z 的26个字母的判断。
这是一个非常简陋的 demo, 有时间的话可以做改进:
- 界面改进: 显示多行, 把空格显示为
|-|
的字符 - 框架库的更换: 使用 GUI 库,例如 imgui 作为替代
- 添加托盘:需要为每个操作系统分别适配