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"
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
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
;
30 bool pressure
, disabled
;
31 lsnes_gamepads
[jid
].get_calibration(num
, minus
, zero
, plus
, neutral
, threshold
, pressure
,
35 wxSizer
* top_s
= new wxBoxSizer(wxVERTICAL
);
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,
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,
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,
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,
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,
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);
77 ~edit_axis_properties()
80 void on_ok(wxCommandEvent
& e
)
82 int64_t minus
, zero
, plus
, neutral
;
84 bool pressure
, disabled
;
85 const char* bad_what
= NULL
;
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)
100 pressure
= _pressure
->GetValue();
101 disabled
= _disabled
->GetValue();
103 wxMessageBox(towxstring(bad_what
), _T("Error"), wxICON_EXCLAMATION
| wxOK
);
106 lsnes_gamepads
[jid
].calibrate_axis(num
, minus
, zero
, plus
, neutral
, threshold
, pressure
,
110 void on_cancel(wxCommandEvent
& e
)
112 EndModal(wxID_CANCEL
);
117 std::string
get_title(unsigned id
, unsigned num
)
119 return (stringfmt() << "Configure axis " << num
<< " of joystick " << id
).str();
126 wxCheckBox
* _disabled
;
127 wxCheckBox
* _pressure
;
132 size_t numwidth(unsigned num
)
137 return 1 + numwidth(num
/ 10);
140 class joystick_panel
: public text_framebuffer_panel
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");
155 selected_row
= gp
.axes();
158 set_size(pwidth
, rows
);
160 write((stringfmt() << "joystick" << _jid
<< " [" << gp
.name() << "]").str(), 256, 0, 0,
162 write(std::string("Status: ") + (gp
.online() ? "Online" : "Offline"), 256, 0, 1, 0,
166 write("Axes:", 256, 0, y
++, 0, 0xFFFFFF);
168 for(unsigned i
= 0; i
< gp
.axes(); i
++) {
169 axes_val
.push_back(std::make_pair(xp
, y
));
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
));
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
));
205 Connect(wxEVT_LEFT_UP
, wxMouseEventHandler(joystick_panel::on_mouse
), NULL
, this);
206 Connect(wxEVT_MOTION
, wxMouseEventHandler(joystick_panel::on_mouse
), NULL
, this);
212 void on_change(wxCommandEvent
& e
)
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)
236 if(y
< axes_val
[i
].second
|| y
>= axes_val
[i
].second
+ 5)
239 //Open dialog for axis i.
240 wxDialog
* d
= new edit_axis_properties(this, _jid
, i
);
248 struct axis_state_info
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
,
270 int button_info(unsigned i
)
272 return _gp
.button_status(i
);
274 int hat_info(unsigned i
)
276 return _gp
.hat_status(i
);
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
);
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
);
301 write("Offline", 8, x
+ 1, y
+ 2, fg
, bg
);
303 write("Disabled", 8, x
+ 1, y
+ 3, fg
, bg
);
305 std::string r1
= (stringfmt() << state
.rawvalue
).str();
306 std::string r2
= (stringfmt() << state
.value
<< "%").str();
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
) {
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
,
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
);
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
);
354 } else if(state
> 0) {
361 draw_box(fb
, stride
, fg
, bg
);
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
);
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
);
381 draw_box(fb
, stride
, fg
, bg
);
382 unsigned sbits
= 0020;
384 sbits
= sbits_lookup
[state
& 15];
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
);
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)
412 return U
'0' + ((val
/ div
) % 10);
416 class joystick_config_window
: public settings_tab
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
);
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();
434 jsp
->AddPage(tmp
= new joystick_panel(jsp
, i
, gp
), towxstring(tname
));
437 top1_s
->SetSizeHints(this);
439 timer
= new update_timer(this);
442 ~joystick_config_window()
457 for(auto i
: panels
) {
462 class update_timer
: public wxTimer
465 update_timer(joystick_config_window
* p
)
474 joystick_config_window
* w
;
478 std::set
<joystick_panel
*> panels
;
481 settings_tab_factory
joysticks("Joysticks", [](wxWindow
* parent
) -> settings_tab
* {
482 return new joystick_config_window(parent
);