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
)
29 int64_t minus
, zero
, plus
, neutral
;
31 bool pressure
, disabled
;
32 lsnes_gamepads
[jid
].get_calibration(num
, minus
, zero
, plus
, neutral
, threshold
, pressure
,
36 wxSizer
* top_s
= new wxBoxSizer(wxVERTICAL
);
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,
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,
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,
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,
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,
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);
78 ~edit_axis_properties()
81 void on_ok(wxCommandEvent
& e
)
84 int64_t minus
, zero
, plus
, neutral
;
86 bool pressure
, disabled
;
87 const char* bad_what
= NULL
;
90 bad_what
= "Bad low calibration value";
91 minus
= boost::lexical_cast
<int64_t>(tostdstring(low
->GetValue()));
92 bad_what
= "Bad middle calibration value";
93 zero
= boost::lexical_cast
<int64_t>(tostdstring(mid
->GetValue()));
94 bad_what
= "Bad high calibration value";
95 plus
= boost::lexical_cast
<int32_t>(tostdstring(hi
->GetValue()));
96 bad_what
= "Bad neutral zone width";
97 neutral
= boost::lexical_cast
<int64_t>(tostdstring(null
->GetValue()));
98 bad_what
= "Bad threshold (range is 0 - 1)";
99 threshold
= boost::lexical_cast
<double>(tostdstring(thresh
->GetValue()));
100 if(threshold
<= 0 || threshold
>= 1)
102 pressure
= _pressure
->GetValue();
103 disabled
= _disabled
->GetValue();
105 wxMessageBox(towxstring(bad_what
), _T("Error"), wxICON_EXCLAMATION
| wxOK
);
108 lsnes_gamepads
[jid
].calibrate_axis(num
, minus
, zero
, plus
, neutral
, threshold
, pressure
,
112 void on_cancel(wxCommandEvent
& e
)
114 EndModal(wxID_CANCEL
);
119 std::string
get_title(unsigned id
, unsigned num
)
121 return (stringfmt() << "Configure axis " << num
<< " of joystick " << id
).str();
128 wxCheckBox
* _disabled
;
129 wxCheckBox
* _pressure
;
134 size_t numwidth(unsigned num
)
139 return 1 + numwidth(num
/ 10);
142 class joystick_panel
: public text_framebuffer_panel
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
)
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");
158 selected_row
= gp
.axes();
161 set_size(pwidth
, rows
);
163 write((stringfmt() << "joystick" << _jid
<< " [" << gp
.name() << "]").str(), 256, 0, 0,
165 write(std::string("Status: ") + (gp
.online() ? "Online" : "Offline"), 256, 0, 1, 0,
169 write("Axes:", 256, 0, y
++, 0, 0xFFFFFF);
171 for(unsigned i
= 0; i
< gp
.axes(); i
++) {
172 axes_val
.push_back(std::make_pair(xp
, y
));
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
));
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
));
208 Connect(wxEVT_LEFT_UP
, wxMouseEventHandler(joystick_panel::on_mouse
), NULL
, this);
209 Connect(wxEVT_MOTION
, wxMouseEventHandler(joystick_panel::on_mouse
), NULL
, this);
215 void on_change(wxCommandEvent
& e
)
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
)
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)
241 if(y
< axes_val
[i
].second
|| y
>= axes_val
[i
].second
+ 5)
244 //Open dialog for axis i.
245 wxDialog
* d
= new edit_axis_properties(this, _jid
, i
);
253 struct axis_state_info
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
,
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
;
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
)
298 unsigned stride
= get_characters().first
;
299 text_framebuffer::element
* fb
= get_buffer() + (y
* stride
+ x
);
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
);
308 write("Offline", 8, x
+ 1, y
+ 2, fg
, bg
);
310 write("Disabled", 8, x
+ 1, y
+ 3, fg
, bg
);
312 std::string r1
= (stringfmt() << state
.rawvalue
).str();
313 std::string r2
= (stringfmt() << state
.value
<< "%").str();
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
) {
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
,
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
);
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
);
361 } else if(state
> 0) {
368 draw_box(fb
, stride
, fg
, bg
);
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
);
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
);
388 draw_box(fb
, stride
, fg
, bg
);
389 unsigned sbits
= 0020;
391 sbits
= sbits_lookup
[state
& 15];
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
);
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)
419 return U
'0' + ((val
/ div
) % 10);
423 class joystick_config_window
: public settings_tab
426 joystick_config_window(wxWindow
* parent
, emulator_instance
& _inst
)
427 : settings_tab(parent
, _inst
)
430 if(!lsnes_gamepads
.gamepads())
431 throw std::runtime_error("No joysticks available");
432 wxSizer
* top1_s
= new wxBoxSizer(wxVERTICAL
);
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();
442 jsp
->AddPage(tmp
= new joystick_panel(jsp
, inst
, i
, gp
), towxstring(tname
));
445 top1_s
->SetSizeHints(this);
447 timer
= new update_timer(this);
450 ~joystick_config_window()
467 for(auto i
: panels
) {
472 class update_timer
: public wxTimer
475 update_timer(joystick_config_window
* p
)
484 joystick_config_window
* w
;
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
);