|
1 | 1 | #include "keyboard_module/keyboard/keyboard.h" |
2 | | -#include <iostream> |
| 2 | +#include <cmath> |
| 3 | +#include <map> |
| 4 | +#include <stdexcept> |
3 | 5 |
|
4 | 6 | namespace my_robot::keyboard_module { |
5 | 7 |
|
6 | 8 | Keyboard::Keyboard() { |
7 | | - printUsage(); |
8 | | - input_thread_ = std::thread(&Keyboard::inputLoop, this); |
| 9 | + if (SDL_Init(SDL_INIT_VIDEO) < 0) { |
| 10 | + throw std::runtime_error("SDL init failed"); |
| 11 | + } |
| 12 | + |
| 13 | + if (TTF_Init() < 0) { |
| 14 | + SDL_Quit(); |
| 15 | + throw std::runtime_error("TTF init failed"); |
| 16 | + } |
| 17 | + |
| 18 | + window_ = |
| 19 | + SDL_CreateWindow("Robot Control Interface", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, |
| 20 | + UIConfig::WINDOW_WIDTH, UIConfig::WINDOW_HEIGHT, SDL_WINDOW_SHOWN); |
| 21 | + if (!window_) { |
| 22 | + TTF_Quit(); |
| 23 | + SDL_Quit(); |
| 24 | + throw std::runtime_error("Window creation failed"); |
| 25 | + } |
| 26 | + |
| 27 | + renderer_ = SDL_CreateRenderer(window_, -1, SDL_RENDERER_ACCELERATED); |
| 28 | + if (!renderer_) { |
| 29 | + SDL_DestroyWindow(window_); |
| 30 | + TTF_Quit(); |
| 31 | + SDL_Quit(); |
| 32 | + throw std::runtime_error("Renderer creation failed"); |
| 33 | + } |
| 34 | + |
| 35 | + const char* fonts[] = { |
| 36 | + "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", |
| 37 | + "/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf", |
| 38 | + }; |
| 39 | + for (const auto* path : fonts) { |
| 40 | + font_ = TTF_OpenFont(path, UIConfig::FONT_SIZE); |
| 41 | + if (font_) break; |
| 42 | + } |
| 43 | + |
| 44 | + current_event_ = KeyboardEvent::None(); |
| 45 | + last_axis_state_.fill(0.0); |
| 46 | + |
| 47 | + for (const auto& btn : UIConfig::MODE_BTNS) { |
| 48 | + last_button_state_[btn.state_id] = false; |
| 49 | + } |
| 50 | + |
| 51 | + render(); |
| 52 | + |
| 53 | + event_thread_ = std::thread(&Keyboard::eventThread, this); |
9 | 54 | } |
10 | 55 |
|
11 | 56 | Keyboard::~Keyboard() { |
12 | 57 | running_ = false; |
13 | | - // 由于 std::cin 是阻塞的,在某些系统上可能需要按下回车才能完全退出 |
14 | | - if (input_thread_.joinable()) { |
15 | | - input_thread_.detach(); |
| 58 | + if (event_thread_.joinable()) { |
| 59 | + event_thread_.join(); |
16 | 60 | } |
| 61 | + if (font_) TTF_CloseFont(font_); |
| 62 | + if (renderer_) SDL_DestroyRenderer(renderer_); |
| 63 | + if (window_) SDL_DestroyWindow(window_); |
| 64 | + TTF_Quit(); |
| 65 | + SDL_Quit(); |
17 | 66 | } |
18 | 67 |
|
19 | 68 | KeyboardEvent Keyboard::GetKeyboardEvent() { |
20 | 69 | std::lock_guard<std::mutex> lock(mutex_); |
21 | 70 | auto event = current_event_; |
22 | | - current_event_ = KeyboardEvent::None(); // 消费掉事件 |
| 71 | + current_event_ = KeyboardEvent::None(); |
23 | 72 | return event; |
24 | 73 | } |
25 | 74 |
|
26 | | -void Keyboard::printUsage() { |
27 | | - std::cout << "\n========== Robot CLI Control ==========\n" |
28 | | - << "[W/S] Forward/Backward [A/D] Left/Right\n" |
29 | | - << "[Q/E] Turn Left/Right [Space] Stop\n" |
30 | | - << "---------------------------------------\n" |
31 | | - << "[1] Stand [2] Zero [3] Walk [0] Idle\n" |
32 | | - << "=======================================\n" |
33 | | - << "Enter command: " << std::flush; |
| 75 | +void Keyboard::handleMouseButton(int x, int y, bool is_down) { |
| 76 | + std::lock_guard<std::mutex> lock(mutex_); |
| 77 | + |
| 78 | + // 处理鼠标按下 |
| 79 | + if (is_down) { |
| 80 | + for (const auto& btn : UIConfig::MODE_BTNS) { |
| 81 | + if (x >= btn.x && x <= btn.x + btn.w && y >= btn.y && y <= btn.y + btn.h) { |
| 82 | + if (!last_button_state_[btn.state_id]) { |
| 83 | + current_event_ = KeyboardEvent::StateChange(btn.state_id, btn.text); |
| 84 | + last_button_state_[btn.state_id] = true; |
| 85 | + } |
| 86 | + return; |
| 87 | + } |
| 88 | + } |
| 89 | + |
| 90 | + for (const auto& key : UIConfig::DIR_KEYS) { |
| 91 | + if (x >= key.x && x <= key.x + key.w && y >= key.y && y <= key.y + key.h) { |
| 92 | + current_event_ = |
| 93 | + KeyboardEvent::VelocityChange(key.linear_x, key.linear_y, 0.0, 0.0, 0.0, key.angular_z); |
| 94 | + return; |
| 95 | + } |
| 96 | + } |
| 97 | + } else { |
| 98 | + for (const auto& btn : UIConfig::MODE_BTNS) { |
| 99 | + if (x >= btn.x && x <= btn.x + btn.w && y >= btn.y && y <= btn.y + btn.h) { |
| 100 | + last_button_state_[btn.state_id] = false; |
| 101 | + return; |
| 102 | + } |
| 103 | + } |
| 104 | + } |
| 105 | +} |
| 106 | + |
| 107 | +void Keyboard::render() { |
| 108 | + if (!renderer_) return; |
| 109 | + |
| 110 | + SDL_SetRenderDrawColor(renderer_, 245, 245, 250, 255); |
| 111 | + SDL_RenderClear(renderer_); |
| 112 | + |
| 113 | + // 绘制方向按键 |
| 114 | + for (const auto& k : UIConfig::DIR_KEYS) { |
| 115 | + SDL_Rect rect = {k.x, k.y, k.w, k.h}; |
| 116 | + |
| 117 | + SDL_SetRenderDrawColor(renderer_, 220, 220, 220, 255); |
| 118 | + SDL_RenderFillRect(renderer_, &rect); |
| 119 | + |
| 120 | + SDL_SetRenderDrawColor(renderer_, 100, 100, 100, 255); |
| 121 | + SDL_RenderDrawRect(renderer_, &rect); |
| 122 | + |
| 123 | + if (font_) { |
| 124 | + SDL_Color color = {60, 60, 60, 255}; |
| 125 | + |
| 126 | + // 按键字母 |
| 127 | + SDL_Surface* s = TTF_RenderText_Blended(font_, k.key, color); |
| 128 | + if (s) { |
| 129 | + SDL_Texture* t = SDL_CreateTextureFromSurface(renderer_, s); |
| 130 | + SDL_Rect r = {k.x + (k.w - s->w) / 2, k.y + 15, s->w, s->h}; |
| 131 | + SDL_RenderCopy(renderer_, t, nullptr, &r); |
| 132 | + SDL_DestroyTexture(t); |
| 133 | + SDL_FreeSurface(s); |
| 134 | + } |
| 135 | + |
| 136 | + // 功能标签 |
| 137 | + TTF_SetFontSize(font_, UIConfig::LABEL_FONT_SIZE); |
| 138 | + s = TTF_RenderText_Blended(font_, k.label, color); |
| 139 | + if (s) { |
| 140 | + SDL_Texture* t = SDL_CreateTextureFromSurface(renderer_, s); |
| 141 | + SDL_Rect r = {k.x + (k.w - s->w) / 2, k.y + 45, s->w, s->h}; |
| 142 | + SDL_RenderCopy(renderer_, t, nullptr, &r); |
| 143 | + SDL_DestroyTexture(t); |
| 144 | + SDL_FreeSurface(s); |
| 145 | + } |
| 146 | + TTF_SetFontSize(font_, UIConfig::FONT_SIZE); |
| 147 | + } |
| 148 | + } |
| 149 | + |
| 150 | + // 绘制模式按钮 |
| 151 | + for (const auto& b : UIConfig::MODE_BTNS) { |
| 152 | + SDL_Rect rect = {b.x, b.y, b.w, b.h}; |
| 153 | + |
| 154 | + SDL_SetRenderDrawColor(renderer_, b.color.r, b.color.g, b.color.b, 255); |
| 155 | + SDL_RenderFillRect(renderer_, &rect); |
| 156 | + |
| 157 | + SDL_SetRenderDrawColor(renderer_, 50, 50, 50, 255); |
| 158 | + SDL_RenderDrawRect(renderer_, &rect); |
| 159 | + |
| 160 | + if (font_) { |
| 161 | + SDL_Color white = {255, 255, 255, 255}; |
| 162 | + SDL_Surface* s = TTF_RenderText_Blended(font_, b.text, white); |
| 163 | + if (s) { |
| 164 | + SDL_Texture* t = SDL_CreateTextureFromSurface(renderer_, s); |
| 165 | + SDL_Rect r = {b.x + (b.w - s->w) / 2, b.y + (b.h - s->h) / 2, s->w, s->h}; |
| 166 | + SDL_RenderCopy(renderer_, t, nullptr, &r); |
| 167 | + SDL_DestroyTexture(t); |
| 168 | + SDL_FreeSurface(s); |
| 169 | + } |
| 170 | + } |
| 171 | + } |
| 172 | + |
| 173 | + SDL_RenderPresent(renderer_); |
34 | 174 | } |
35 | 175 |
|
36 | | -void Keyboard::inputLoop() { |
37 | | - char cmd; |
| 176 | +void Keyboard::eventThread() { |
38 | 177 | while (running_) { |
39 | | - if (!(std::cin >> cmd)) break; |
40 | | - |
41 | | - std::lock_guard<std::mutex> lock(mutex_); |
42 | | - switch (cmd) { |
43 | | - // 速度控制 |
44 | | - case 'w': |
45 | | - case 'W': |
46 | | - current_event_ = KeyboardEvent::VelocityChange(0.5, 0.0, 0.0); |
47 | | - break; |
48 | | - case 's': |
49 | | - case 'S': |
50 | | - current_event_ = KeyboardEvent::VelocityChange(-0.5, 0.0, 0.0); |
51 | | - break; |
52 | | - case 'a': |
53 | | - case 'A': |
54 | | - current_event_ = KeyboardEvent::VelocityChange(0.0, 0.3, 0.0); |
55 | | - break; |
56 | | - case 'd': |
57 | | - case 'D': |
58 | | - current_event_ = KeyboardEvent::VelocityChange(0.0, -0.3, 0.0); |
59 | | - break; |
60 | | - case 'q': |
61 | | - case 'Q': |
62 | | - current_event_ = KeyboardEvent::VelocityChange(0.0, 0.0, 0.5); |
63 | | - break; |
64 | | - case 'e': |
65 | | - case 'E': |
66 | | - current_event_ = KeyboardEvent::VelocityChange(0.0, 0.0, -0.5); |
67 | | - break; |
68 | | - case ' ': |
69 | | - current_event_ = KeyboardEvent::VelocityChange(0.0, 0.0, 0.0); |
70 | | - break; |
| 178 | + SDL_Event e; |
71 | 179 |
|
72 | | - // 状态控制 |
73 | | - case '1': |
74 | | - current_event_ = KeyboardEvent::StateChange(1, "Stand"); |
75 | | - break; |
76 | | - case '2': |
77 | | - current_event_ = KeyboardEvent::StateChange(2, "Zero"); |
78 | | - break; |
79 | | - case '3': |
80 | | - current_event_ = KeyboardEvent::StateChange(3, "Walk"); |
81 | | - break; |
82 | | - case '0': |
83 | | - current_event_ = KeyboardEvent::StateChange(0, "Idle"); |
| 180 | + while (SDL_PollEvent(&e)) { |
| 181 | + if (e.type == SDL_QUIT) { |
| 182 | + running_ = false; |
84 | 183 | break; |
| 184 | + } |
85 | 185 |
|
86 | | - default: |
87 | | - std::cout << "Unknown command: " << cmd << "\n"; |
88 | | - printUsage(); |
89 | | - continue; |
| 186 | + switch (e.type) { |
| 187 | + case SDL_MOUSEBUTTONDOWN: |
| 188 | + handleMouseButton(e.button.x, e.button.y, true); |
| 189 | + break; |
| 190 | + case SDL_MOUSEBUTTONUP: |
| 191 | + handleMouseButton(e.button.x, e.button.y, false); |
| 192 | + break; |
| 193 | + } |
90 | 194 | } |
91 | | - std::cout << "Command Sent: " << cmd << "\nEnter command: " << std::flush; |
| 195 | + |
| 196 | + { |
| 197 | + std::lock_guard<std::mutex> lock(mutex_); |
| 198 | + render(); |
| 199 | + } |
| 200 | + |
| 201 | + std::this_thread::sleep_for(std::chrono::milliseconds(33)); |
92 | 202 | } |
93 | 203 | } |
94 | 204 |
|
|
0 commit comments