Make wrapper for boost::lexical_cast
[lsnes.git] / src / platform / wxwidgets / settings-joysticks.cpp
blobd5619772c9a5d5b59a93fd67a587473f8930108d
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 CHECK_UI_THREAD;
29 int64_t minus, zero, plus, neutral;
30 double threshold;
31 bool pressure, disabled;
32 lsnes_gamepads[jid].get_calibration(num, minus, zero, plus, neutral, threshold, pressure,
33 disabled);
35 Centre();
36 wxSizer* top_s = new wxBoxSizer(wxVERTICAL);
37 SetSizer(top_s);
38 wxSizer* t_s = new wxFlexGridSizer(2);
39 t_s->Add(new wxStaticText(this, -1, wxT("Minus:")));
40 t_s->Add(low = new wxTextCtrl(this, -1, wxT(""), wxDefaultPosition, wxSize(100, -1)), 1,
41 wxGROW);
42 t_s->Add(new wxStaticText(this, -1, wxT("Center:")));
43 t_s->Add(mid = new wxTextCtrl(this, -1, wxT(""), wxDefaultPosition, wxSize(100, -1)), 1,
44 wxGROW);
45 t_s->Add(new wxStaticText(this, -1, wxT("Plus:")));
46 t_s->Add(hi = new wxTextCtrl(this, -1, wxT(""), wxDefaultPosition, wxSize(100, -1)), 1,
47 wxGROW);
48 t_s->Add(new wxStaticText(this, -1, wxT("Neutral:")));
49 t_s->Add(null = new wxTextCtrl(this, -1, wxT(""), wxDefaultPosition, wxSize(100, -1)), 1,
50 wxGROW);
51 t_s->Add(new wxStaticText(this, -1, wxT("Threshold:")));
52 t_s->Add(thresh = new wxTextCtrl(this, -1, wxT(""), wxDefaultPosition, wxSize(100, -1)), 1,
53 wxGROW);
54 t_s->Add(_disabled = new wxCheckBox(this, -1, wxT("Disabled")));
55 t_s->Add(_pressure = new wxCheckBox(this, -1, wxT("Pressure")));
56 top_s->Add(t_s, 1, wxGROW);
58 low->SetValue(towxstring((stringfmt() << minus).str()));
59 mid->SetValue(towxstring((stringfmt() << zero).str()));
60 hi->SetValue(towxstring((stringfmt() << plus).str()));
61 null->SetValue(towxstring((stringfmt() << neutral).str()));
62 thresh->SetValue(towxstring((stringfmt() << threshold).str()));
63 _pressure->SetValue(pressure);
64 _disabled->SetValue(disabled);
66 wxBoxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL);
67 pbutton_s->AddStretchSpacer();
68 pbutton_s->Add(okbutton = new wxButton(this, wxID_OK, wxT("OK")), 0, wxGROW);
69 pbutton_s->Add(cancel = new wxButton(this, wxID_CANCEL, wxT("Cancel")), 0, wxGROW);
70 okbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
71 wxCommandEventHandler(edit_axis_properties::on_ok), NULL, this);
72 cancel->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
73 wxCommandEventHandler(edit_axis_properties::on_cancel), NULL, this);
74 top_s->Add(pbutton_s, 0, wxGROW);
75 top_s->SetSizeHints(this);
76 Fit();
78 ~edit_axis_properties()
81 void on_ok(wxCommandEvent& e)
83 CHECK_UI_THREAD;
84 int64_t minus, zero, plus, neutral;
85 double threshold;
86 bool pressure, disabled;
87 const char* bad_what = NULL;
89 try {
90 bad_what = "Bad low calibration value";
91 minus = raw_lexical_cast<int64_t>(tostdstring(low->GetValue()));
92 bad_what = "Bad middle calibration value";
93 zero = raw_lexical_cast<int64_t>(tostdstring(mid->GetValue()));
94 bad_what = "Bad high calibration value";
95 plus = raw_lexical_cast<int32_t>(tostdstring(hi->GetValue()));
96 bad_what = "Bad neutral zone width";
97 neutral = raw_lexical_cast<int64_t>(tostdstring(null->GetValue()));
98 bad_what = "Bad threshold (range is 0 - 1)";
99 threshold = raw_lexical_cast<double>(tostdstring(thresh->GetValue()));
100 if(threshold <= 0 || threshold >= 1)
101 throw 42;
102 pressure = _pressure->GetValue();
103 disabled = _disabled->GetValue();
104 } catch(...) {
105 wxMessageBox(towxstring(bad_what), _T("Error"), wxICON_EXCLAMATION | wxOK);
106 return;
108 lsnes_gamepads[jid].calibrate_axis(num, minus, zero, plus, neutral, threshold, pressure,
109 disabled);
110 EndModal(wxID_OK);
112 void on_cancel(wxCommandEvent& e)
114 EndModal(wxID_CANCEL);
116 private:
117 unsigned jid;
118 unsigned num;
119 std::string get_title(unsigned id, unsigned num)
121 return (stringfmt() << "Configure axis " << num << " of joystick " << id).str();
123 wxTextCtrl* low;
124 wxTextCtrl* mid;
125 wxTextCtrl* hi;
126 wxTextCtrl* null;
127 wxTextCtrl* thresh;
128 wxCheckBox* _disabled;
129 wxCheckBox* _pressure;
130 wxButton* okbutton;
131 wxButton* cancel;
134 size_t numwidth(unsigned num)
136 if(num < 10)
137 return 1;
138 else
139 return 1 + numwidth(num / 10);
142 class joystick_panel : public text_framebuffer_panel
144 public:
145 joystick_panel(wxWindow* parent, emulator_instance& _inst, unsigned jid, gamepad::pad& gp)
146 : text_framebuffer_panel(parent, 60, 32, -1, NULL), inst(_inst), _jid(jid), _gp(gp)
148 CHECK_UI_THREAD;
149 const unsigned pwidth = 80;
150 const unsigned b_perrow = 8;
151 const unsigned s_perrow = 16;
152 unsigned rows = 9 + (gp.axes() + b_perrow - 1) / b_perrow * 5 + (gp.buttons() +
153 s_perrow - 1) / s_perrow * 5 + (gp.hats() + s_perrow - 1) / s_perrow * 5;
155 std::string jname = (stringfmt() << "joystick" << _jid << " [" << gp.name() << "]").str();
156 std::string status = std::string("Status: ") + (gp.online() ? "Online" : "Offline");
157 unsigned y = 2;
158 selected_row = gp.axes();
161 set_size(pwidth, rows);
163 write((stringfmt() << "joystick" << _jid << " [" << gp.name() << "]").str(), 256, 0, 0,
164 0, 0xFFFFFF);
165 write(std::string("Status: ") + (gp.online() ? "Online" : "Offline"), 256, 0, 1, 0,
166 0xFFFFFF);
168 if(gp.axes())
169 write("Axes:", 256, 0, y++, 0, 0xFFFFFF);
170 unsigned xp = 0;
171 for(unsigned i = 0; i < gp.axes(); i++) {
172 axes_val.push_back(std::make_pair(xp, y));
173 xp += 10;
174 if(xp >= pwidth) {
175 xp = 0;
176 y += 5;
179 if(xp)
180 y += 5;
181 xp = 0;
182 if(gp.buttons())
183 write("Buttons:", 256, 0, y++, 0, 0xFFFFFF);
184 for(unsigned i = 0; i < gp.buttons(); i++) {
185 buttons_val.push_back(std::make_pair(xp, y));
186 xp += 5;
187 if(xp >= pwidth) {
188 xp = 0;
189 y += 5;
192 if(xp)
193 y += 5;
194 xp = 0;
195 if(gp.hats())
196 write("Hats:", 256, 0, y++, 0, 0xFFFFFF);
197 for(unsigned i = 0; i < gp.hats(); i++) {
198 hats_val.push_back(std::make_pair(xp, y));
199 xp += 5;
200 if(xp >= pwidth) {
201 xp = 0;
202 y += 5;
205 if(xp)
206 y += 5;
207 cal_row = y;
208 Connect(wxEVT_LEFT_UP, wxMouseEventHandler(joystick_panel::on_mouse), NULL, this);
209 Connect(wxEVT_MOTION, wxMouseEventHandler(joystick_panel::on_mouse), NULL, this);
210 Fit();
212 ~joystick_panel()
215 void on_change(wxCommandEvent& e)
218 void prepare_paint()
220 CHECK_UI_THREAD;
221 for(unsigned i = 0; i < 4; i++)
222 write("", 256, 0, cal_row + i, 0, 0xFFFFFF);
223 for(unsigned i = 0; i < axes_val.size(); i++)
224 draw_axis(axes_val[i].first, axes_val[i].second, i, axis_info(i));
225 for(unsigned i = 0; i < buttons_val.size(); i++)
226 draw_button(buttons_val[i].first, buttons_val[i].second, i, button_info(i));
227 for(unsigned i = 0; i < hats_val.size(); i++)
228 draw_hat(hats_val[i].first, hats_val[i].second, i, hat_info(i));
230 std::pair<unsigned, unsigned> size_needed() { return std::make_pair(width_need, height_need); }
231 void on_mouse(wxMouseEvent& e)
233 CHECK_UI_THREAD;
234 auto cell = get_cell();
235 size_t x = e.GetX() / cell.first;
236 size_t y = e.GetY() / cell.second;
237 selected_row = axes_val.size();
238 for(size_t i = 0; i < axes_val.size(); i++) {
239 if(x < axes_val[i].first || x >= axes_val[i].first + 10)
240 continue;
241 if(y < axes_val[i].second || y >= axes_val[i].second + 5)
242 continue;
243 if(e.LeftUp()) {
244 //Open dialog for axis i.
245 wxDialog* d = new edit_axis_properties(this, _jid, i);
246 d->ShowModal();
247 d->Destroy();
248 } else
249 selected_row = i;
252 private:
253 struct axis_state_info
255 bool offline;
256 int64_t rawvalue;
257 int16_t value;
258 int64_t minus;
259 int64_t zero;
260 int64_t plus;
261 int64_t neutral;
262 double threshold;
263 bool pressure;
264 bool disabled;
266 struct axis_state_info axis_info(unsigned i)
268 struct axis_state_info ax;
269 ax.offline = (_gp.online_axes().count(i) == 0);
270 _gp.axis_status(i, ax.rawvalue, ax.value);
271 _gp.get_calibration(i, ax.minus, ax.zero, ax.plus, ax.neutral, ax.threshold, ax.pressure,
272 ax.disabled);
273 return ax;
275 int button_info(unsigned i)
277 return _gp.button_status(i);
279 int hat_info(unsigned i)
281 return _gp.hat_status(i);
283 emulator_instance& inst;
284 unsigned _jid;
285 gamepad::pad& _gp;
286 size_t base_width;
287 size_t width_need;
288 size_t height_need;
289 size_t maxtitle;
290 size_t cal_row;
291 size_t selected_row;
292 std::vector<std::pair<unsigned, unsigned>> axes_val;
293 std::vector<std::pair<unsigned, unsigned>> buttons_val;
294 std::vector<std::pair<unsigned, unsigned>> hats_val;
295 void draw_axis(unsigned x, unsigned y, unsigned num, axis_state_info state)
297 CHECK_UI_THREAD;
298 unsigned stride = get_characters().first;
299 text_framebuffer::element* fb = get_buffer() + (y * stride + x);
300 uint32_t fg, bg;
301 fg = state.offline ? 0x808080 : 0x000000;
302 bg = (num != selected_row) ? 0xFFFFFF : 0xFFFFC0;
303 draw_bbox(fb, stride, fg, bg);
304 fb[1 * stride + 6] = E(D(num, 100), fg, bg);
305 fb[1 * stride + 7] = E(D(num, 10), fg, bg);
306 fb[1 * stride + 8] = E(D(num, 1), fg, bg);
307 if(state.offline) {
308 write("Offline", 8, x + 1, y + 2, fg, bg);
309 if(state.disabled)
310 write("Disabled", 8, x + 1, y + 3, fg, bg);
311 } else {
312 std::string r1 = (stringfmt() << state.rawvalue).str();
313 std::string r2 = (stringfmt() << state.value << "%").str();
314 if(state.disabled)
315 r2 = "Disabled";
316 size_t r1l = (r1.length() < 8) ? r1.length() : 8;
317 size_t r2l = (r2.length() < 8) ? r2.length() : 8;
318 write(r1, r1l, x + 9 - r1l, y + 2, fg, bg);
319 write(r2, r2l, x + 9 - r2l, y + 3, fg, bg);
321 if(num == selected_row) {
322 uint32_t fg2 = 0;
323 uint32_t bg2 = 0xFFFFFF;
324 //This is selected, Also draw the calibration parameters.
325 write((stringfmt() << "Calibration for axis" << num << ":").str(), 256, 0, cal_row,
326 fg2, bg2);
327 if(state.pressure) {
328 write((stringfmt() << "Released: " << state.zero).str(), 40, 0,
329 cal_row + 1, fg2, bg2);
330 write((stringfmt() << "Pressed: " << state.plus).str(), 40, 40,
331 cal_row + 1, fg2, bg2);
332 write((stringfmt() << "Analog threshold: " << state.neutral).str(), 40, 0,
333 cal_row + 2, fg2, bg2);
334 write((stringfmt() << "Digital threshold: " << state.threshold).str(), 40, 40,
335 cal_row + 2, fg2, bg2);
336 write("Pressure sensitive button", 40, 0, cal_row + 3, fg2, bg2);
337 } else {
338 write((stringfmt() << "Left/Up: " << state.minus).str(), 40, 0,
339 cal_row + 1, fg2, bg2);
340 write((stringfmt() << "Right/Down: " << state.plus).str(), 40, 40,
341 cal_row + 1, fg2, bg2);
342 write((stringfmt() << "Center: " << state.zero).str(), 40, 0,
343 cal_row + 2, fg2, bg2);
344 write((stringfmt() << "Analog threshold: " << state.neutral).str(), 40, 40,
345 cal_row + 2, fg2, bg2);
346 write((stringfmt() << "Digital threshold: " << state.threshold).str(), 40, 0,
347 cal_row + 3, fg2, bg2);
348 write("Axis", 40, 40, cal_row + 3, fg2, bg2);
353 void draw_button(unsigned x, unsigned y, unsigned num, int state)
355 unsigned stride = get_characters().first;
356 text_framebuffer::element* fb = get_buffer() + (y * stride + x);
357 uint32_t fg, bg;
358 if(state < 0) {
359 fg = 0x808080;
360 bg = 0xFFFFFF;
361 } else if(state > 0) {
362 fg = 0xFFFFFF;
363 bg = 0x000000;
364 } else {
365 fg = 0x000000;
366 bg = 0xFFFFFF;
368 draw_box(fb, stride, fg, bg);
369 if(num > 9) {
370 fb[2 * stride + 1] = E(D(num, 100), fg, bg);
371 fb[2 * stride + 2] = E(D(num, 10), fg, bg);
372 fb[2 * stride + 3] = E(D(num, 1), fg, bg);
373 } else
374 fb[2 * stride + 2] = E(D(num, 1), fg, bg);
376 void draw_hat(unsigned x, unsigned y, unsigned num, int state)
378 unsigned stride = get_characters().first;
379 text_framebuffer::element* fb = get_buffer() + (y * stride + x);
380 uint32_t fg, bg;
381 if(state < 0) {
382 fg = 0x808080;
383 bg = 0xFFFFFF;
384 } else {
385 fg = 0x000000;
386 bg = 0xFFFFFF;
388 draw_box(fb, stride, fg, bg);
389 unsigned sbits = 0020;
390 if(state > 0)
391 sbits = sbits_lookup[state & 15];
392 if(num > 9) {
393 fb[2 * stride + 1] = E(D(num, 100), fg, bg);
394 fb[2 * stride + 2] = E(D(num, 10), fg, bg);
395 fb[2 * stride + 3] = E(D(num, 1), fg, bg);
396 } else
397 fb[2 * stride + 2] = E(D(num, 1), fg, bg);
398 for(unsigned y = 1; y < 4; y++)
399 for(unsigned x = 1; x < 4; x++)
400 if((sbits >> (y * 3 + x - 4)) & 1)
401 std::swap(fb[y * stride + x].fg, fb[y * stride + x].bg);
403 void draw_box(text_framebuffer::element* fb, unsigned stride, uint32_t fg, uint32_t bg)
405 for(unsigned y = 0; y < 5; y++)
406 for(unsigned x = 0; x < 5; x++)
407 fb[y * stride + x] = E(box_symbols[5 * y + x], fg, bg);
409 void draw_bbox(text_framebuffer::element* fb, unsigned stride, uint32_t fg, uint32_t bg)
411 for(unsigned y = 0; y < 5; y++)
412 for(unsigned x = 0; x < 10; x++)
413 fb[y * stride + x] = E(bbox_symbols[10 * y + x], fg, bg);
415 char32_t D(unsigned val, unsigned div)
417 if(val < div && div != 1)
418 return U' ';
419 return U'0' + ((val / div) % 10);
423 class joystick_config_window : public settings_tab
425 public:
426 joystick_config_window(wxWindow* parent, emulator_instance& _inst)
427 : settings_tab(parent, _inst)
429 CHECK_UI_THREAD;
430 if(!lsnes_gamepads.gamepads())
431 throw std::runtime_error("No joysticks available");
432 wxSizer* top1_s = new wxBoxSizer(wxVERTICAL);
433 SetSizer(top1_s);
434 std::map<std::string, unsigned> jsnum;
435 top1_s->Add(jsp = new wxChoicebook(this, -1), 1, wxGROW);
436 for(unsigned i = 0; i < lsnes_gamepads.gamepads(); i++) {
437 gamepad::pad& gp = lsnes_gamepads[i];
438 std::string name = gp.name();
439 jsnum[name] = jsnum.count(name) ? (jsnum[name] + 1) : 1;
440 std::string tname = (stringfmt() << "joystick" << i << ": " << name).str();
441 joystick_panel* tmp;
442 jsp->AddPage(tmp = new joystick_panel(jsp, inst, i, gp), towxstring(tname));
443 panels.insert(tmp);
445 top1_s->SetSizeHints(this);
446 Fit();
447 timer = new update_timer(this);
448 timer->Start(100);
450 ~joystick_config_window()
452 CHECK_UI_THREAD;
453 if(timer) {
454 timer->Stop();
455 delete timer;
458 void update_all()
460 CHECK_UI_THREAD;
461 if(closing()) {
462 timer->Stop();
463 delete timer;
464 timer = NULL;
465 return;
467 for(auto i : panels) {
468 i->request_paint();
471 private:
472 class update_timer : public wxTimer
474 public:
475 update_timer(joystick_config_window* p)
477 w = p;
479 void Notify()
481 w->update_all();
483 private:
484 joystick_config_window* w;
486 update_timer* timer;
487 wxChoicebook* jsp;
488 std::set<joystick_panel*> panels;
491 settings_tab_factory joysticks("Joysticks", [](wxWindow* parent, emulator_instance& _inst) -> settings_tab* {
492 return new joystick_config_window(parent, _inst);