Refactor emulator status reporting (and fix the statusbar doesn't update bug)
[lsnes.git] / src / platform / wxwidgets / settings-joysticks.cpp
blobbae203ce894f4d03d0af0f6437d554056c316680
1 #include "platform/wxwidgets/settings-common.hpp"
2 #include "platform/wxwidgets/textrender.hpp"
3 #include "core/keymapper.hpp"
4 #include <wx/choicebk.h>
5 #include "library/minmax.hpp"
7 namespace
9 const char32_t* box_symbols = U"\u250c\u2500\u2500\u2500\u2510"
10 U"\u2502\u0020\u0020\u0020\u2502"
11 U"\u2502\u0020\u0020\u0020\u2502"
12 U"\u2502\u0020\u0020\u0020\u2502"
13 U"\u2514\u2500\u2500\u2500\u2518";
14 const char32_t* bbox_symbols = U"\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"
15 U"\u2502\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u2502"
16 U"\u2502\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u2502"
17 U"\u2502\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u2502"
18 U"\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518";
19 unsigned sbits_lookup[] = {0020, 0002, 0040, 0004, 0200, 0202, 0400, 0404, 0010, 0001, 0050, 0005,
20 0100, 0101, 0500, 0252};
22 class edit_axis_properties : public wxDialog
24 public:
25 edit_axis_properties(wxWindow* parent, unsigned _jid, unsigned _num)
26 : wxDialog(parent, -1, towxstring(get_title(_jid, _num))), jid(_jid), num(_num)
28 int64_t minus, zero, plus, neutral;
29 double threshold;
30 bool pressure, disabled;
31 lsnes_gamepads[jid].get_calibration(num, minus, zero, plus, neutral, threshold, pressure,
32 disabled);
34 Centre();
35 wxSizer* top_s = new wxBoxSizer(wxVERTICAL);
36 SetSizer(top_s);
37 wxSizer* t_s = new wxFlexGridSizer(2);
38 t_s->Add(new wxStaticText(this, -1, wxT("Minus:")));
39 t_s->Add(low = new wxTextCtrl(this, -1, wxT(""), wxDefaultPosition, wxSize(100, -1)), 1,
40 wxGROW);
41 t_s->Add(new wxStaticText(this, -1, wxT("Center:")));
42 t_s->Add(mid = new wxTextCtrl(this, -1, wxT(""), wxDefaultPosition, wxSize(100, -1)), 1,
43 wxGROW);
44 t_s->Add(new wxStaticText(this, -1, wxT("Plus:")));
45 t_s->Add(hi = new wxTextCtrl(this, -1, wxT(""), wxDefaultPosition, wxSize(100, -1)), 1,
46 wxGROW);
47 t_s->Add(new wxStaticText(this, -1, wxT("Neutral:")));
48 t_s->Add(null = new wxTextCtrl(this, -1, wxT(""), wxDefaultPosition, wxSize(100, -1)), 1,
49 wxGROW);
50 t_s->Add(new wxStaticText(this, -1, wxT("Threshold:")));
51 t_s->Add(thresh = new wxTextCtrl(this, -1, wxT(""), wxDefaultPosition, wxSize(100, -1)), 1,
52 wxGROW);
53 t_s->Add(_disabled = new wxCheckBox(this, -1, wxT("Disabled")));
54 t_s->Add(_pressure = new wxCheckBox(this, -1, wxT("Pressure")));
55 top_s->Add(t_s, 1, wxGROW);
57 low->SetValue(towxstring((stringfmt() << minus).str()));
58 mid->SetValue(towxstring((stringfmt() << zero).str()));
59 hi->SetValue(towxstring((stringfmt() << plus).str()));
60 null->SetValue(towxstring((stringfmt() << neutral).str()));
61 thresh->SetValue(towxstring((stringfmt() << threshold).str()));
62 _pressure->SetValue(pressure);
63 _disabled->SetValue(disabled);
65 wxBoxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL);
66 pbutton_s->AddStretchSpacer();
67 pbutton_s->Add(okbutton = new wxButton(this, wxID_OK, wxT("OK")), 0, wxGROW);
68 pbutton_s->Add(cancel = new wxButton(this, wxID_CANCEL, wxT("Cancel")), 0, wxGROW);
69 okbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
70 wxCommandEventHandler(edit_axis_properties::on_ok), NULL, this);
71 cancel->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
72 wxCommandEventHandler(edit_axis_properties::on_cancel), NULL, this);
73 top_s->Add(pbutton_s, 0, wxGROW);
74 top_s->SetSizeHints(this);
75 Fit();
77 ~edit_axis_properties()
80 void on_ok(wxCommandEvent& e)
82 int64_t minus, zero, plus, neutral;
83 double threshold;
84 bool pressure, disabled;
85 const char* bad_what = NULL;
87 try {
88 bad_what = "Bad low calibration value";
89 minus = boost::lexical_cast<int64_t>(tostdstring(low->GetValue()));
90 bad_what = "Bad middle calibration value";
91 zero = boost::lexical_cast<int64_t>(tostdstring(mid->GetValue()));
92 bad_what = "Bad high calibration value";
93 plus = boost::lexical_cast<int32_t>(tostdstring(hi->GetValue()));
94 bad_what = "Bad neutral zone width";
95 neutral = boost::lexical_cast<int64_t>(tostdstring(null->GetValue()));
96 bad_what = "Bad threshold (range is 0 - 1)";
97 threshold = boost::lexical_cast<double>(tostdstring(thresh->GetValue()));
98 if(threshold <= 0 || threshold >= 1)
99 throw 42;
100 pressure = _pressure->GetValue();
101 disabled = _disabled->GetValue();
102 } catch(...) {
103 wxMessageBox(towxstring(bad_what), _T("Error"), wxICON_EXCLAMATION | wxOK);
104 return;
106 lsnes_gamepads[jid].calibrate_axis(num, minus, zero, plus, neutral, threshold, pressure,
107 disabled);
108 EndModal(wxID_OK);
110 void on_cancel(wxCommandEvent& e)
112 EndModal(wxID_CANCEL);
114 private:
115 unsigned jid;
116 unsigned num;
117 std::string get_title(unsigned id, unsigned num)
119 return (stringfmt() << "Configure axis " << num << " of joystick " << id).str();
121 wxTextCtrl* low;
122 wxTextCtrl* mid;
123 wxTextCtrl* hi;
124 wxTextCtrl* null;
125 wxTextCtrl* thresh;
126 wxCheckBox* _disabled;
127 wxCheckBox* _pressure;
128 wxButton* okbutton;
129 wxButton* cancel;
132 size_t numwidth(unsigned num)
134 if(num < 10)
135 return 1;
136 else
137 return 1 + numwidth(num / 10);
140 class joystick_panel : public text_framebuffer_panel
142 public:
143 joystick_panel(wxWindow* parent, unsigned jid, gamepad::pad& gp)
144 : text_framebuffer_panel(parent, 60, 32, -1, NULL), _jid(jid), _gp(gp)
146 const unsigned pwidth = 80;
147 const unsigned b_perrow = 8;
148 const unsigned s_perrow = 16;
149 unsigned rows = 9 + (gp.axes() + b_perrow - 1) / b_perrow * 5 + (gp.buttons() +
150 s_perrow - 1) / s_perrow * 5 + (gp.hats() + s_perrow - 1) / s_perrow * 5;
152 std::string jname = (stringfmt() << "joystick" << _jid << " [" << gp.name() << "]").str();
153 std::string status = std::string("Status: ") + (gp.online() ? "Online" : "Offline");
154 unsigned y = 2;
155 selected_row = gp.axes();
158 set_size(pwidth, rows);
160 write((stringfmt() << "joystick" << _jid << " [" << gp.name() << "]").str(), 256, 0, 0,
161 0, 0xFFFFFF);
162 write(std::string("Status: ") + (gp.online() ? "Online" : "Offline"), 256, 0, 1, 0,
163 0xFFFFFF);
165 if(gp.axes())
166 write("Axes:", 256, 0, y++, 0, 0xFFFFFF);
167 unsigned xp = 0;
168 for(unsigned i = 0; i < gp.axes(); i++) {
169 axes_val.push_back(std::make_pair(xp, y));
170 xp += 10;
171 if(xp >= pwidth) {
172 xp = 0;
173 y += 5;
176 if(xp)
177 y += 5;
178 xp = 0;
179 if(gp.buttons())
180 write("Buttons:", 256, 0, y++, 0, 0xFFFFFF);
181 for(unsigned i = 0; i < gp.buttons(); i++) {
182 buttons_val.push_back(std::make_pair(xp, y));
183 xp += 5;
184 if(xp >= pwidth) {
185 xp = 0;
186 y += 5;
189 if(xp)
190 y += 5;
191 xp = 0;
192 if(gp.hats())
193 write("Hats:", 256, 0, y++, 0, 0xFFFFFF);
194 for(unsigned i = 0; i < gp.hats(); i++) {
195 hats_val.push_back(std::make_pair(xp, y));
196 xp += 5;
197 if(xp >= pwidth) {
198 xp = 0;
199 y += 5;
202 if(xp)
203 y += 5;
204 cal_row = y;
205 Connect(wxEVT_LEFT_UP, wxMouseEventHandler(joystick_panel::on_mouse), NULL, this);
206 Connect(wxEVT_MOTION, wxMouseEventHandler(joystick_panel::on_mouse), NULL, this);
207 Fit();
209 ~joystick_panel()
212 void on_change(wxCommandEvent& e)
215 void prepare_paint()
217 for(unsigned i = 0; i < 4; i++)
218 write("", 256, 0, cal_row + i, 0, 0xFFFFFF);
219 for(unsigned i = 0; i < axes_val.size(); i++)
220 draw_axis(axes_val[i].first, axes_val[i].second, i, axis_info(i));
221 for(unsigned i = 0; i < buttons_val.size(); i++)
222 draw_button(buttons_val[i].first, buttons_val[i].second, i, button_info(i));
223 for(unsigned i = 0; i < hats_val.size(); i++)
224 draw_hat(hats_val[i].first, hats_val[i].second, i, hat_info(i));
226 std::pair<unsigned, unsigned> size_needed() { return std::make_pair(width_need, height_need); }
227 void on_mouse(wxMouseEvent& e)
229 auto cell = get_cell();
230 size_t x = e.GetX() / cell.first;
231 size_t y = e.GetY() / cell.second;
232 selected_row = axes_val.size();
233 for(size_t i = 0; i < axes_val.size(); i++) {
234 if(x < axes_val[i].first || x >= axes_val[i].first + 10)
235 continue;
236 if(y < axes_val[i].second || y >= axes_val[i].second + 5)
237 continue;
238 if(e.LeftUp()) {
239 //Open dialog for axis i.
240 wxDialog* d = new edit_axis_properties(this, _jid, i);
241 d->ShowModal();
242 d->Destroy();
243 } else
244 selected_row = i;
247 private:
248 struct axis_state_info
250 bool offline;
251 int64_t rawvalue;
252 int16_t value;
253 int64_t minus;
254 int64_t zero;
255 int64_t plus;
256 int64_t neutral;
257 double threshold;
258 bool pressure;
259 bool disabled;
261 struct axis_state_info axis_info(unsigned i)
263 struct axis_state_info ax;
264 ax.offline = (_gp.online_axes().count(i) == 0);
265 _gp.axis_status(i, ax.rawvalue, ax.value);
266 _gp.get_calibration(i, ax.minus, ax.zero, ax.plus, ax.neutral, ax.threshold, ax.pressure,
267 ax.disabled);
268 return ax;
270 int button_info(unsigned i)
272 return _gp.button_status(i);
274 int hat_info(unsigned i)
276 return _gp.hat_status(i);
278 unsigned _jid;
279 gamepad::pad& _gp;
280 size_t base_width;
281 size_t width_need;
282 size_t height_need;
283 size_t maxtitle;
284 size_t cal_row;
285 size_t selected_row;
286 std::vector<std::pair<unsigned, unsigned>> axes_val;
287 std::vector<std::pair<unsigned, unsigned>> buttons_val;
288 std::vector<std::pair<unsigned, unsigned>> hats_val;
289 void draw_axis(unsigned x, unsigned y, unsigned num, axis_state_info state)
291 unsigned stride = get_characters().first;
292 text_framebuffer::element* fb = get_buffer() + (y * stride + x);
293 uint32_t fg, bg;
294 fg = state.offline ? 0x808080 : 0x000000;
295 bg = (num != selected_row) ? 0xFFFFFF : 0xFFFFC0;
296 draw_bbox(fb, stride, fg, bg);
297 fb[1 * stride + 6] = E(D(num, 100), fg, bg);
298 fb[1 * stride + 7] = E(D(num, 10), fg, bg);
299 fb[1 * stride + 8] = E(D(num, 1), fg, bg);
300 if(state.offline) {
301 write("Offline", 8, x + 1, y + 2, fg, bg);
302 if(state.disabled)
303 write("Disabled", 8, x + 1, y + 3, fg, bg);
304 } else {
305 std::string r1 = (stringfmt() << state.rawvalue).str();
306 std::string r2 = (stringfmt() << state.value << "%").str();
307 if(state.disabled)
308 r2 = "Disabled";
309 size_t r1l = (r1.length() < 8) ? r1.length() : 8;
310 size_t r2l = (r2.length() < 8) ? r2.length() : 8;
311 write(r1, r1l, x + 9 - r1l, y + 2, fg, bg);
312 write(r2, r2l, x + 9 - r2l, y + 3, fg, bg);
314 if(num == selected_row) {
315 uint32_t fg2 = 0;
316 uint32_t bg2 = 0xFFFFFF;
317 //This is selected, Also draw the calibration parameters.
318 write((stringfmt() << "Calibration for axis" << num << ":").str(), 256, 0, cal_row,
319 fg2, bg2);
320 if(state.pressure) {
321 write((stringfmt() << "Released: " << state.zero).str(), 40, 0,
322 cal_row + 1, fg2, bg2);
323 write((stringfmt() << "Pressed: " << state.plus).str(), 40, 40,
324 cal_row + 1, fg2, bg2);
325 write((stringfmt() << "Analog threshold: " << state.neutral).str(), 40, 0,
326 cal_row + 2, fg2, bg2);
327 write((stringfmt() << "Digital threshold: " << state.threshold).str(), 40, 40,
328 cal_row + 2, fg2, bg2);
329 write("Pressure sensitive button", 40, 0, cal_row + 3, fg2, bg2);
330 } else {
331 write((stringfmt() << "Left/Up: " << state.minus).str(), 40, 0,
332 cal_row + 1, fg2, bg2);
333 write((stringfmt() << "Right/Down: " << state.plus).str(), 40, 40,
334 cal_row + 1, fg2, bg2);
335 write((stringfmt() << "Center: " << state.zero).str(), 40, 0,
336 cal_row + 2, fg2, bg2);
337 write((stringfmt() << "Analog threshold: " << state.neutral).str(), 40, 40,
338 cal_row + 2, fg2, bg2);
339 write((stringfmt() << "Digital threshold: " << state.threshold).str(), 40, 0,
340 cal_row + 3, fg2, bg2);
341 write("Axis", 40, 40, cal_row + 3, fg2, bg2);
346 void draw_button(unsigned x, unsigned y, unsigned num, int state)
348 unsigned stride = get_characters().first;
349 text_framebuffer::element* fb = get_buffer() + (y * stride + x);
350 uint32_t fg, bg;
351 if(state < 0) {
352 fg = 0x808080;
353 bg = 0xFFFFFF;
354 } else if(state > 0) {
355 fg = 0xFFFFFF;
356 bg = 0x000000;
357 } else {
358 fg = 0x000000;
359 bg = 0xFFFFFF;
361 draw_box(fb, stride, fg, bg);
362 if(num > 9) {
363 fb[2 * stride + 1] = E(D(num, 100), fg, bg);
364 fb[2 * stride + 2] = E(D(num, 10), fg, bg);
365 fb[2 * stride + 3] = E(D(num, 1), fg, bg);
366 } else
367 fb[2 * stride + 2] = E(D(num, 1), fg, bg);
369 void draw_hat(unsigned x, unsigned y, unsigned num, int state)
371 unsigned stride = get_characters().first;
372 text_framebuffer::element* fb = get_buffer() + (y * stride + x);
373 uint32_t fg, bg;
374 if(state < 0) {
375 fg = 0x808080;
376 bg = 0xFFFFFF;
377 } else {
378 fg = 0x000000;
379 bg = 0xFFFFFF;
381 draw_box(fb, stride, fg, bg);
382 unsigned sbits = 0020;
383 if(state > 0)
384 sbits = sbits_lookup[state & 15];
385 if(num > 9) {
386 fb[2 * stride + 1] = E(D(num, 100), fg, bg);
387 fb[2 * stride + 2] = E(D(num, 10), fg, bg);
388 fb[2 * stride + 3] = E(D(num, 1), fg, bg);
389 } else
390 fb[2 * stride + 2] = E(D(num, 1), fg, bg);
391 for(unsigned y = 1; y < 4; y++)
392 for(unsigned x = 1; x < 4; x++)
393 if((sbits >> (y * 3 + x - 4)) & 1)
394 std::swap(fb[y * stride + x].fg, fb[y * stride + x].bg);
396 void draw_box(text_framebuffer::element* fb, unsigned stride, uint32_t fg, uint32_t bg)
398 for(unsigned y = 0; y < 5; y++)
399 for(unsigned x = 0; x < 5; x++)
400 fb[y * stride + x] = E(box_symbols[5 * y + x], fg, bg);
402 void draw_bbox(text_framebuffer::element* fb, unsigned stride, uint32_t fg, uint32_t bg)
404 for(unsigned y = 0; y < 5; y++)
405 for(unsigned x = 0; x < 10; x++)
406 fb[y * stride + x] = E(bbox_symbols[10 * y + x], fg, bg);
408 char32_t D(unsigned val, unsigned div)
410 if(val < div && div != 1)
411 return U' ';
412 return U'0' + ((val / div) % 10);
416 class joystick_config_window : public settings_tab
418 public:
419 joystick_config_window(wxWindow* parent)
420 : settings_tab(parent)
422 if(!lsnes_gamepads.gamepads())
423 throw std::runtime_error("No joysticks available");
424 wxSizer* top1_s = new wxBoxSizer(wxVERTICAL);
425 SetSizer(top1_s);
426 std::map<std::string, unsigned> jsnum;
427 top1_s->Add(jsp = new wxChoicebook(this, -1), 1, wxGROW);
428 for(unsigned i = 0; i < lsnes_gamepads.gamepads(); i++) {
429 gamepad::pad& gp = lsnes_gamepads[i];
430 std::string name = gp.name();
431 jsnum[name] = jsnum.count(name) ? (jsnum[name] + 1) : 1;
432 std::string tname = (stringfmt() << "joystick" << i << ": " << name).str();
433 joystick_panel* tmp;
434 jsp->AddPage(tmp = new joystick_panel(jsp, i, gp), towxstring(tname));
435 panels.insert(tmp);
437 top1_s->SetSizeHints(this);
438 Fit();
439 timer = new update_timer(this);
440 timer->Start(100);
442 ~joystick_config_window()
444 if(timer) {
445 timer->Stop();
446 delete timer;
449 void update_all()
451 if(closing()) {
452 timer->Stop();
453 delete timer;
454 timer = NULL;
455 return;
457 for(auto i : panels) {
458 i->request_paint();
461 private:
462 class update_timer : public wxTimer
464 public:
465 update_timer(joystick_config_window* p)
467 w = p;
469 void Notify()
471 w->update_all();
473 private:
474 joystick_config_window* w;
476 update_timer* timer;
477 wxChoicebook* jsp;
478 std::set<joystick_panel*> panels;
481 settings_tab_factory joysticks("Joysticks", [](wxWindow* parent) -> settings_tab* {
482 return new joystick_config_window(parent);