e8555c89 Andy McCall 1 month ago
1
#include " ImGuiSettings.hh"
2
3
#include " ImGuiCpp.hh"
4
#include " ImGuiManager.hh"
5
#include " ImGuiMessages.hh"
6
#include " ImGuiOsdIcons.hh"
7
#include " ImGuiSoundChip.hh"
8
#include " ImGuiUtils.hh"
9
#include " VectorPath.hh"
10
#include " VectorPathDsl.hh"
11
12
#include " AnalogInput.hh"
13
#include " BooleanInput.hh"
14
#include " BooleanSetting.hh"
15
#include " CPUCore.hh"
16
#include " Display.hh"
17
#include " EventDistributor.hh"
18
#include " FileContext.hh"
19
#include " FilenameSetting.hh"
20
#include " FloatSetting.hh"
21
#include " GlobalCommandController.hh"
22
#include " GlobalSettings.hh"
23
#include " InputEventFactory.hh"
24
#include " InputEventGenerator.hh"
25
#include " IntegerSetting.hh"
26
#include " JoyMega.hh"
27
#include " JoyHandle.hh"
28
#include " KeyCodeSetting.hh"
29
#include " KeyboardSettings.hh"
30
#include " MSXCPU.hh"
31
#include " MSXCommandController.hh"
32
#include " MSXJoystick.hh"
33
#include " MSXMotherBoard.hh"
34
#include " Mixer.hh"
35
#include " Plotter.hh"
36
#include " PluggingController.hh"
37
#include " ProxySetting.hh"
38
#include " R800.hh"
39
#include " Reactor.hh"
40
#include " ReadOnlySetting.hh"
41
#include " SettingsManager.hh"
42
#include " StringSetting.hh"
43
#include " Version.hh"
44
#include " VideoSourceSetting.hh"
45
#include " Z80.hh"
46
47
#include " StringOp.hh"
48
#include " checked_cast.hh"
49
#include " foreach_file.hh"
50
#include " narrow.hh"
51
#include " strCat.hh"
52
#include " zstring_view.hh"
53
54
#include < imgui.h>
55
#include < imgui_impl_opengl3.h>
56
#include < imgui_stdlib.h>
57
58
#include < SDL.h>
59
60
#include < algorithm>
61
#include < functional>
62
#include < optional>
63
#include < utility>
64
65
using namespace std:: literals;
66
67
namespace openmsx {
68
69
ImGuiSettings:: ImGuiSettings ( ImGuiManager& manager_ )
70
: ImGuiPart ( manager_ )
71
, saveLayout ( " layout" , " .ini" , " layouts" )
72
, loadLayout ( " layout" , " .ini" , " layouts" )
73
, confirmOverwrite ( " Confirm##overwrite-layout" )
74
{
75
saveLayout. drawAction = [ & ] {
76
saveLayout. drawTable ( ) ;
77
ImGui:: TextUnformatted ( " Enter name:" sv) ;
78
ImGui:: InputText ( " ##save-layout-name" , & saveLayoutName) ;
79
ImGui:: SameLine ( ) ;
80
im:: Disabled ( saveLayoutName. empty ( ) , [ & ] {
81
if ( ImGui:: Button ( " Save as" ) ) {
82
( void ) manager. getReactor ( ) . getDisplay ( ) . getWindowPosition ( ) ;
83
84
auto filename = FileOperations:: parseCommandFileArgument (
85
saveLayoutName, " layouts" , " " , " .ini" ) ;
86
auto action = [ filename] {
87
ImGui:: SaveIniSettingsToDisk ( filename. c_str ( ) ) ;
88
ImGui:: CloseCurrentPopup ( ) ;
89
} ;
90
if ( FileOperations:: exists ( filename ) ) {
91
confirmOverwrite. open (
92
strCat ( " Overwrite layout: " , saveLayoutName) ,
93
action ) ;
94
} else {
95
action ( ) ;
96
}
97
}
98
} ) ;
99
confirmOverwrite. execute ( ) ;
100
} ;
101
saveLayout. singleClickAction = [ & ] ( const FileListWidget:: Entry& entry) {
102
saveLayoutName = entry. getDefaultDisplayName ( ) ;
103
} ;
104
105
loadLayout. singleClickAction = [ & ] ( const FileListWidget:: Entry& entry) {
106
manager. loadIniFile = entry. fullName ;
107
ImGui:: CloseCurrentPopup ( ) ;
108
} ;
109
}
110
111
ImGuiSettings:: ~ImGuiSettings ( )
112
{
113
deinitListener ( ) ;
114
}
115
116
void ImGuiSettings:: save ( ImGuiTextBuffer& buf )
117
{
118
savePersistent ( buf, * this , persistentElements ) ;
119
}
120
121
void ImGuiSettings:: loadLine ( std:: string_view name , zstring_view value )
122
{
123
loadOnePersistent ( name, value, * this , persistentElements ) ;
124
}
125
126
void ImGuiSettings:: loadEnd ( )
127
{
128
setStyle ( ) ;
129
}
130
131
void ImGuiSettings:: setStyle ( ) const
132
{
133
switch ( selectedStyle) {
134
case 0 : ImGui:: StyleColorsDark ( ) ; break ;
135
case 1 : ImGui:: StyleColorsLight ( ) ; break ;
136
case 2 : ImGui:: StyleColorsClassic ( ) ; break ;
137
case 3 : ImGui:: StyleColorsClassic ( ) ; break ;
138
}
139
setColors ( selectedStyle ) ;
140
}
141
142
143
144
145
146
[ [ nodiscard] ] static ImGuiKeyChord getCurrentlyPressedKeyChord ( )
147
{
148
static constexpr auto mods = std:: array{
149
ImGuiKey_LeftCtrl, ImGuiKey_LeftShift, ImGuiKey_LeftAlt, ImGuiKey_LeftSuper,
150
ImGuiKey_RightCtrl, ImGuiKey_RightShift, ImGuiKey_RightAlt, ImGuiKey_RightSuper,
151
ImGuiKey_ReservedForModCtrl, ImGuiKey_ReservedForModShift, ImGuiKey_ReservedForModAlt,
152
ImGuiKey_ReservedForModSuper, ImGuiKey_MouseLeft, ImGuiKey_MouseRight, ImGuiKey_MouseMiddle,
153
ImGuiKey_MouseX1, ImGuiKey_MouseX2, ImGuiKey_MouseWheelX, ImGuiKey_MouseWheelY,
154
} ;
155
for ( int key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; + + key) {
156
157
if ( contains ( mods, key ) ) continue ;
158
if ( ImGui:: IsKeyPressed ( static_cast < ImGuiKey> ( key) ) ) {
159
const ImGuiIO& io = ImGui:: GetIO ( ) ;
160
return key
161
| ( io. KeyCtrl ? ImGuiMod_Ctrl : 0 )
162
| ( io. KeyShift ? ImGuiMod_Shift : 0 )
163
| ( io. KeyAlt ? ImGuiMod_Alt : 0 )
164
| ( io. KeySuper ? ImGuiMod_Super : 0 ) ;
165
}
166
}
167
return ImGuiKey_None;
168
}
169
170
void ImGuiSettings:: showMenu ( MSXMotherBoard* motherBoard )
171
{
172
im:: Menu ( " Settings" , [ & ] {
173
auto & reactor = manager. getReactor ( ) ;
174
auto & globalSettings = reactor. getGlobalSettings ( ) ;
175
auto & renderSettings = reactor. getDisplay ( ) . getRenderSettings ( ) ;
176
const auto & settingsManager = reactor. getGlobalCommandController ( ) . getSettingsManager ( ) ;
177
const auto & hotKey = reactor. getHotKey ( ) ;
178
179
im:: Menu ( " Video" , [ & ] {
180
im:: TreeNode ( " Look and feel" , ImGuiTreeNodeFlags_DefaultOpen, [ & ] {
181
auto & scaler = renderSettings. getScaleAlgorithmSetting ( ) ;
182
ComboBox ( " Scaler" , scaler) ;
183
im:: Indent ( [ & ] {
184
struct AlgoEnable {
185
RenderSettings:: ScaleAlgorithm algo;
186
bool hasScanline;
187
bool hasBlur;
188
} ;
189
using enum RenderSettings:: ScaleAlgorithm;
190
static constexpr std:: array algoEnables = {
191
AlgoEnable{ . algo = SIMPLE, . hasScanline = true , . hasBlur = true } ,
192
AlgoEnable{ . algo = SCALE, . hasScanline = false , . hasBlur = false } ,
193
AlgoEnable{ . algo = HQ, . hasScanline = false , . hasBlur = false } ,
194
AlgoEnable{ . algo = RGBTRIPLET, . hasScanline = true , . hasBlur = true } ,
195
AlgoEnable{ . algo = TV, . hasScanline = true , . hasBlur = false } ,
196
} ;
197
auto it = std:: ranges:: find ( algoEnables, scaler. getEnum ( ) , & AlgoEnable:: algo ) ;
198
assert ( it != algoEnables. end ( ) ) ;
199
im:: Disabled ( ! it-> hasScanline , [ & ] {
200
SliderInt ( " Scanline (%)" , renderSettings. getScanlineSetting ( ) ) ;
201
} ) ;
202
im:: Disabled ( ! it-> hasBlur , [ & ] {
203
SliderInt ( " Blur (%)" , renderSettings. getBlurSetting ( ) ) ;
204
} ) ;
205
} ) ;
206
207
SliderInt ( " Scale factor" , renderSettings. getScaleFactorSetting ( ) ) ;
208
Checkbox ( hotKey, " Deinterlace" , renderSettings. getDeinterlaceSetting ( ) ) ;
209
Checkbox ( hotKey, " Deflicker" , renderSettings. getDeflickerSetting ( ) ) ;
210
} ) ;
211
im:: TreeNode ( " Colors" , ImGuiTreeNodeFlags_DefaultOpen, [ & ] {
212
SliderFloat ( " Noise (%)" , renderSettings. getNoiseSetting ( ) ) ;
213
SliderFloat ( " Brightness" , renderSettings. getBrightnessSetting ( ) ) ;
214
SliderFloat ( " Contrast" , renderSettings. getContrastSetting ( ) ) ;
215
SliderFloat ( " Gamma" , renderSettings. getGammaSetting ( ) ) ;
216
SliderInt ( " Glow (%)" , renderSettings. getGlowSetting ( ) ) ;
217
if ( auto * monitor = dynamic_cast < Setting* > ( settingsManager. findSetting ( " monitor_type" ) ) ) {
218
ComboBox ( " Monitor type" , * monitor, [ ] ( std:: string s) {
219
std:: ranges:: replace ( s, ' _' , ' ' ) ;
220
return s;
221
} ) ;
222
}
223
} ) ;
224
im:: TreeNode ( " Shape" , ImGuiTreeNodeFlags_DefaultOpen, [ & ] {
225
Checkbox ( hotKey, " Full Stretch" , renderSettings. getFullStretchSetting ( ) ) ;
226
SliderFloat ( " Horizontal stretch" , renderSettings. getHorizontalStretchSetting ( ) , " %.0f " ) ;
227
ComboBox ( " Display deformation" , renderSettings. getDisplayDeformSetting ( ) ) ;
228
} ) ;
229
im:: TreeNode ( " Misc" , ImGuiTreeNodeFlags_DefaultOpen, [ & ] {
230
Checkbox ( hotKey, " Full screen" , renderSettings. getFullScreenSetting ( ) ) ;
231
if ( motherBoard) {
232
ComboBox ( " Video source to display" , motherBoard-> getVideoSource ( ) ) ;
233
}
234
Checkbox ( hotKey, " VSync" , renderSettings. getVSyncSetting ( ) ) ;
235
SliderInt ( " Minimum frame-skip" , renderSettings. getMinFrameSkipSetting ( ) ) ;
236
SliderInt ( " Maximum frame-skip" , renderSettings. getMaxFrameSkipSetting ( ) ) ;
237
} ) ;
238
im:: TreeNode ( " Advanced (for debugging)" , [ & ] {
239
Checkbox ( hotKey, " Enforce VDP sprites-per-line limit" , renderSettings. getLimitSpritesSetting ( ) ) ;
240
Checkbox ( hotKey, " Disable sprites" , renderSettings. getDisableSpritesSetting ( ) ) ;
241
ComboBox ( " Way to handle too fast VDP access" , renderSettings. getTooFastAccessSetting ( ) ) ;
242
ComboBox ( " Emulate VDP command timing" , renderSettings. getCmdTimingSetting ( ) ) ;
243
ComboBox ( " Rendering accuracy" , renderSettings. getAccuracySetting ( ) ) ;
244
} ) ;
245
} ) ;
246
im:: Menu ( " Sound" , [ & ] {
247
auto & mixer = reactor. getMixer ( ) ;
248
auto & muteSetting = mixer. getMuteSetting ( ) ;
249
im:: Disabled ( muteSetting. getBoolean ( ) , [ & ] {
250
SliderInt ( " Master volume" , mixer. getMasterVolume ( ) ) ;
251
} ) ;
252
Checkbox ( hotKey, " Mute" , muteSetting ) ;
253
ImGui:: Separator ( ) ;
254
static constexpr std:: array resamplerToolTips = {
255
EnumToolTip{ . value = " hq" , . tip = " best quality, uses more CPU" } ,
256
EnumToolTip{ . value = " blip" , . tip = " good speed/quality tradeoff" } ,
257
EnumToolTip{ . value = " fast" , . tip = " fast but low quality" } ,
258
} ;
259
ComboBox ( " Resampler" , globalSettings. getResampleSetting ( ) , resamplerToolTips) ;
260
ImGui:: Separator ( ) ;
261
262
ImGui:: MenuItem ( " Show sound chip settings" , nullptr , & manager. soundChip -> showSoundChipSettings ) ;
263
} ) ;
264
im:: Menu ( " Speed" , [ & ] {
265
im:: TreeNode ( " Emulation" , ImGuiTreeNodeFlags_DefaultOpen, [ & ] {
266
ImGui:: SameLine ( ) ;
267
HelpMarker ( " These control the speed of the whole MSX machine, "
268
" the running MSX software can't tell the difference." ) ;
269
270
auto & speedManager = globalSettings. getSpeedManager ( ) ;
271
auto & fwdSetting = speedManager. getFastForwardSetting ( ) ;
272
int fastForward = fwdSetting. getBoolean ( ) ? 1 : 0 ;
273
ImGui:: TextUnformatted ( " Speed:" sv) ;
274
ImGui:: SameLine ( ) ;
275
bool fwdChanged = ImGui:: RadioButton ( " normal" , & fastForward, 0 ) ;
276
ImGui:: SameLine ( ) ;
277
fwdChanged |= ImGui:: RadioButton ( " fast forward" , & fastForward, 1 ) ;
278
if ( auto fastForwardShortCut = getShortCutForCommand ( reactor. getHotKey ( ) , " toggle fastforward" ) ;
279
! fastForwardShortCut. empty ( ) ) {
280
HelpMarker ( strCat ( " Use '" , fastForwardShortCut , " ' to quickly toggle between these two" ) ) ;
281
}
282
if ( fwdChanged) {
283
fwdSetting. setBoolean ( fastForward != 0 ) ;
284
}
285
im:: Indent ( [ & ] {
286
im:: Disabled ( fastForward != 0 , [ & ] {
287
SliderFloat ( " Speed (%)" , speedManager. getSpeedSetting ( ) , " %.1f " , ImGuiSliderFlags_Logarithmic) ;
288
} ) ;
289
im:: Disabled ( fastForward != 1 , [ & ] {
290
SliderFloat ( " Fast forward speed (%)" , speedManager. getFastForwardSpeedSetting ( ) , " %.1f " , ImGuiSliderFlags_Logarithmic) ;
291
} ) ;
292
} ) ;
293
Checkbox ( hotKey, " Go full speed when loading" , globalSettings. getThrottleManager ( ) . getFullSpeedLoadingSetting ( ) ) ;
294
} ) ;
295
if ( motherBoard) {
296
im:: TreeNode ( " MSX devices" , ImGuiTreeNodeFlags_DefaultOpen, [ & ] {
297
ImGui:: SameLine ( ) ;
298
HelpMarker ( " These control the speed of the specific components in the MSX machine. "
299
" So the relative speed between components can change. "
300
" And this may lead the emulation problems." ) ;
301
302
MSXCPU& cpu = motherBoard-> getCPU ( ) ;
303
auto showFreqSettings = [ & ] ( std:: string_view name, auto * core) {
304
if ( ! core) return ;
305
auto & locked = core-> getFreqLockedSetting ( ) ;
306
auto & value = core-> getFreqValueSetting ( ) ;
307
308
bool unlocked = ! locked. getBoolean ( ) ;
309
if ( ImGui:: Checkbox ( tmpStrCat ( " unlock custom " , name, " frequency" ) . c_str ( ) , & unlocked) ) {
310
locked. setBoolean ( ! unlocked) ;
311
}
312
simpleToolTip ( [ & ] { return locked. getDescription ( ) ; } ) ;
313
im:: Indent ( [ & ] {
314
im:: Disabled ( ! unlocked, [ & ] {
315
float fval = float ( value. getInt ( ) ) / 1.0e6f ;
316
if ( ImGui:: InputFloat ( tmpStrCat ( " frequency (MHz)##" , name) . c_str ( ) , & fval, 0.01f , 1.0f , " %.2f " ) ) {
317
value. setInt ( int ( fval * 1.0e6f ) ) ;
318
}
319
im:: PopupContextItem ( tmpStrCat ( " freq-context##" , name) . c_str ( ) , [ & ] {
320
const char * F358 = name == " Z80" ? " 3.58 MHz (default)"
321
: " 3.58 MHz" ;
322
if ( ImGui:: Selectable ( F358 ) ) {
323
value. setInt ( 3'579'545 ) ;
324
}
325
if ( ImGui:: Selectable ( " 5.37 MHz" ) ) {
326
value. setInt ( 5'369'318 ) ;
327
}
328
const char * F716 = name == " R800" ? " 7.16 MHz (default)"
329
: " 7.16 MHz" ;
330
if ( ImGui:: Selectable ( F716 ) ) {
331
value. setInt ( 7'159'090 ) ;
332
}
333
334
} ) ;
335
HelpMarker ( " Right-click to select commonly used values" ) ;
336
} ) ;
337
} ) ;
338
} ;
339
showFreqSettings ( " Z80" , cpu. getZ80 ( ) ) ;
340
showFreqSettings ( " R800" , cpu. getR800 ( ) ) ;
341
} ) ;
342
}
343
} ) ;
344
im:: Menu ( " Input" , [ & ] {
345
static constexpr std:: array kbdModeToolTips = {
346
EnumToolTip{ . value = " CHARACTER" , . tip = " Tries to understand the character you are typing and then attempts to type that character using the current MSX keyboard. May not work very well when using a non-US host keyboard." } ,
347
EnumToolTip{ . value = " KEY" , . tip = " Tries to map a key you press to the corresponding MSX key" } ,
348
EnumToolTip{ . value = " POSITIONAL" , . tip = " Tries to map the keyboard key positions to the MSX keyboard key positions" } ,
349
} ;
350
if ( motherBoard) {
351
const auto & controller = motherBoard-> getMSXCommandController ( ) ;
352
if ( auto * turbo = dynamic_cast < IntegerSetting* > ( controller. findSetting ( " renshaturbo" ) ) ) {
353
SliderInt ( " Ren Sha Turbo (%)" , * turbo) ;
354
}
355
if ( auto * mappingModeSetting = dynamic_cast < EnumSetting< KeyboardSettings:: MappingMode> * > ( controller. findSetting ( " kbd_mapping_mode" ) ) ) {
356
ComboBox ( " Keyboard mapping mode" , * mappingModeSetting, kbdModeToolTips) ;
357
}
358
}
359
ImGui:: MenuItem ( " Configure MSX joysticks" , nullptr , & showConfigureJoystick) ;
360
auto & grabInputSetting = reactor. getInputEventGenerator ( ) . getGrabInput ( ) ;
361
bool grabInput = grabInputSetting. getBoolean ( ) ;
362
if ( auto shortCut = getShortCutForCommand ( hotKey, " toggle grabinput" ) ;
363
ImGui:: MenuItem ( " Grab input" , shortCut. c_str ( ) , & grabInput) ) {
364
grabInputSetting. setBoolean ( grabInput ) ;
365
}
366
simpleToolTip ( " Enable this to help you keep the mouse cursor in the MSX window, e.g. when using drawing programs with mouse in windowed mode." ) ;
367
} ) ;
368
im:: Menu ( " GUI" , [ & ] {
369
saveLayout. menu ( " Save layout" ) ;
370
loadLayout. menu ( " Restore layout" ) ;
371
ImGui:: Separator ( ) ;
372
im:: Menu ( " Select style" , [ & ] {
373
std:: optional< int > newStyle;
374
static constexpr std:: array names = { " Dark" , " Light" , " Classic" , " Colorblind" } ;
375
for ( auto i : xrange ( narrow < int > ( names. size ( ) ) ) ) {
376
if ( ImGui:: Selectable ( names[ i] , selectedStyle == i ) ) {
377
newStyle = i;
378
}
379
}
380
if ( newStyle) {
381
selectedStyle = * newStyle;
382
setStyle ( ) ;
383
}
384
} ) ;
385
ImGui:: MenuItem ( " Select font" , nullptr , & showFont) ;
386
ImGui:: MenuItem ( " Edit shortcuts" , nullptr , & showShortcut) ;
387
ImGui:: MenuItem ( " Configure OSD icons" , nullptr , & manager. osdIcons -> showConfigureIcons ) ;
388
ImGui:: MenuItem ( " Fade out menu bar" , nullptr , & manager. menuFade ) ;
389
im:: Menu ( " Status bar" , [ & ] {
390
ImGui:: Checkbox ( " Show" , & manager. statusBarVisible ) ;
391
im:: DisabledIndent ( ! manager. statusBarVisible , [ & ] {
392
manager. configStatusBarVisibilityItems ( ) ;
393
} ) ;
394
} ) ;
395
ImGui:: MenuItem ( " Configure messages" , nullptr , & manager. messages -> configureWindow . open ) ;
396
} ) ;
397
im:: Menu ( " MSX Plotter" , motherBoard != nullptr , [ & ] {
398
399
MSXPlotter* plotter = nullptr ;
400
for ( const auto & plug : motherBoard-> getPluggingController ( ) . getPluggables ( ) ) {
401
if ( auto * p = dynamic_cast < MSXPlotter* > ( plug. get ( ) ) ) {
402
plotter = p;
403
break ;
404
}
405
}
406
407
408
static constexpr std:: array charSetNames = {
409
" International" , " Japanese" , " DIN (German)"
410
} ;
411
auto currentCharSet = int ( plotter-> getCharacterSet ( ) ) ;
412
if ( ImGui:: Combo ( " Character set" , & currentCharSet, charSetNames. data ( ) , int ( charSetNames. size ( ) ) ) ) {
413
plotter-> setCharacterSet ( static_cast < MSXPlotter:: CharacterSet> ( currentCharSet) ) ;
414
}
415
416
417
bool dipSwitch4 = plotter-> getDipSwitch4 ( ) ;
418
if ( ImGui:: Checkbox ( " Dipswitch 4" , & dipSwitch4) ) {
419
plotter-> setDipSwitch4 ( dipSwitch4 ) ;
420
}
421
422
423
static constexpr std:: array thicknessNames = {
424
" Standard (PRK-C41)" , " Thick (PRK-C42)"
425
} ;
426
auto currentThickness = int ( plotter-> getPenThicknessSetting ( ) . getEnum ( ) ) ;
427
if ( ImGui:: Combo ( " Pen thickness" , & currentThickness, thicknessNames. data ( ) , int ( thicknessNames. size ( ) ) ) ) {
428
plotter-> getPenThicknessSetting ( ) . setEnum ( static_cast < MSXPlotter:: PenThickness> ( currentThickness) ) ;
429
}
430
} ) ;
431
ImGui:: Separator ( ) ;
432
im:: Menu ( " Advanced" , [ & ] {
433
ImGui:: TextUnformatted ( " All settings" sv) ;
434
ImGui:: Separator ( ) ;
435
std:: vector< Setting* > settings;
436
for ( auto * setting : settingsManager. getAllSettings ( ) ) {
437
if ( dynamic_cast < ProxySetting* > ( setting) ) continue ;
438
if ( dynamic_cast < ReadOnlySetting* > ( setting) ) continue ;
439
settings. push_back ( checked_cast < Setting* > ( setting ) ) ;
440
}
441
std:: ranges:: sort ( settings, StringOp:: caseless{ } , & Setting:: getBaseName ) ;
442
for ( auto * setting : settings) {
443
if ( auto * bSetting = dynamic_cast < BooleanSetting* > ( setting) ) {
444
Checkbox ( hotKey, * bSetting ) ;
445
} else if ( auto * iSetting = dynamic_cast < IntegerSetting* > ( setting) ) {
446
SliderInt ( * iSetting) ;
447
} else if ( auto * fSetting = dynamic_cast < FloatSetting* > ( setting) ) {
448
SliderFloat ( * fSetting ) ;
449
} else if ( auto * sSetting = dynamic_cast < StringSetting* > ( setting) ) {
450
InputText ( * sSetting ) ;
451
} else if ( auto * fnSetting = dynamic_cast < FilenameSetting* > ( setting) ) {
452
InputText ( * fnSetting) ;
453
} else if ( auto * kSetting = dynamic_cast < KeyCodeSetting* > ( setting) ) {
454
InputText ( * kSetting ) ;
455
} else if ( dynamic_cast < EnumSettingBase* > ( setting) ) {
456
ComboBox ( * setting) ;
457
} else if ( auto * vSetting = dynamic_cast < VideoSourceSetting* > ( setting) ) {
458
ComboBox ( * vSetting) ;
459
} else {
460
assert ( false ) ;
461
}
462
}
463
} ) ;
464
} ) ;
465
}
466
467
468
469
470
[ [ nodiscard] ] static std:: string settingName ( unsigned joystick)
471
{
472
return ( joystick < 2 ) ? strCat ( " msxjoystick" , joystick + 1 , " _config" )
473
: ( joystick < 4 ) ? strCat ( " joymega" , joystick - 1 , " _config" )
474
: strCat ( " joyhandle" , joystick - 3 , " _config" ) ;
475
}
476
477
478
[ [ nodiscard] ] static std:: string joystickToGuiString ( unsigned joystick)
479
{
480
return ( joystick < 2 ) ? strCat ( " MSX joystick " , joystick + 1 )
481
: ( joystick < 4 ) ? strCat ( " JoyMega controller " , joystick - 1 )
482
: strCat ( " Panasonic FS-JH1 " , joystick - 3 ) ;
483
}
484
485
[ [ nodiscard] ] static std:: string toGuiString ( const AnalogInput& input, const JoystickManager& joystickManager)
486
{
487
return std:: visit ( overloaded{
488
[ ] ( const AnalogMouseAxis& m) {
489
return toString ( m ) ;
490
} ,
491
[ & ] ( const AnalogJoystickAxis& a) {
492
return strCat ( joystickManager. getDisplayName ( a. getJoystick ( ) ) ,
493
" stick axis " , a. getAxis ( ) ) ;
494
}
495
} , input) ;
496
}
497
[ [ nodiscard] ] static std:: string toGuiString ( const BooleanInput& input, const JoystickManager& joystickManager)
498
{
499
return std:: visit ( overloaded{
500
[ ] ( const BooleanKeyboard& k) {
501
return strCat ( " keyboard key " , SDLKey:: toString ( k. getKeyCode ( ) ) ) ;
502
} ,
503
[ ] ( const BooleanMouseButton& m) {
504
return strCat ( " mouse button " , m. getButton ( ) ) ;
505
} ,
506
[ & ] ( const BooleanJoystickButton& j) {
507
return strCat ( joystickManager. getDisplayName ( j. getJoystick ( ) ) , " button " , j. getButton ( ) ) ;
508
} ,
509
[ & ] ( const BooleanJoystickHat& h) {
510
return strCat ( joystickManager. getDisplayName ( h. getJoystick ( ) ) , " D-pad " , h. getHat ( ) , ' ' , toString ( h. getValue ( ) ) ) ;
511
} ,
512
[ & ] ( const BooleanJoystickAxis& a) {
513
return strCat ( joystickManager. getDisplayName ( a. getJoystick ( ) ) ,
514
" stick axis " , a. getAxis ( ) , " , " ,
515
( a. getDirection ( ) == BooleanJoystickAxis:: Direction:: POS ? " positive" sv : " negative" sv) , " direction" ) ;
516
}
517
} , input) ;
518
}
519
[ [ nodiscard] ] static std:: string toGuiString ( bool wheel, zstring_view str, const JoystickManager& joystickManager)
520
{
521
return wheel ? toGuiString ( * parseAnalogInput ( str ) , joystickManager)
522
: toGuiString ( * parseBooleanInput ( str ) , joystickManager) ;
523
}
524
525
[ [ nodiscard] ] static bool insideCircle ( gl:: vec2 mouse, gl:: vec2 center, float radius )
526
{
527
auto delta = center - mouse;
528
return gl:: sum ( delta * delta ) <= ( radius * radius) ;
529
}
530
[ [ nodiscard] ] static bool between ( float x, float min, float max)
531
{
532
return ( min <= x) && ( x <= max) ;
533
}
534
535
namespace {
536
struct Rectangle {
537
gl:: vec2 topLeft;
538
gl:: vec2 bottomRight;
539
} ;
540
}
541
[ [ nodiscard] ] static bool insideRectangle ( gl:: vec2 mouse, Rectangle r )
542
{
543
return between ( mouse. x , r. topLeft . x , r. bottomRight . x ) &&
544
between ( mouse. y , r. topLeft . y , r. bottomRight . y ) ;
545
}
546
547
548
static constexpr auto fractionDPad = 1.0f / 3.0f ;
549
static constexpr auto thickness = 3.0f ;
550
551
static void drawDPad ( gl:: vec2 center , float size , std:: span< const uint8_t , 4 > hovered , int hoveredRow )
552
{
553
const auto F = fractionDPad;
554
std:: array< std:: array< ImVec2, 5 + 1 > , 4 > points = {
555
std:: array< ImVec2, 5 + 1 > {
556
center + size * gl:: vec2{ 0 , 0 } ,
557
center + size * gl:: vec2{ - F, - F} ,
558
center + size * gl:: vec2{ - F, - 1 } ,
559
center + size * gl:: vec2{ F, - 1 } ,
560
center + size * gl:: vec2{ F, - F} ,
561
center + size * gl:: vec2{ 0 , 0 } ,
562
} ,
563
std:: array< ImVec2, 5 + 1 > {
564
center + size * gl:: vec2{ 0 , 0 } ,
565
center + size * gl:: vec2{ F, F} ,
566
center + size * gl:: vec2{ F, 1 } ,
567
center + size * gl:: vec2{ - F, 1 } ,
568
center + size * gl:: vec2{ - F, F} ,
569
center + size * gl:: vec2{ 0 , 0 } ,
570
} ,
571
std:: array< ImVec2, 5 + 1 > {
572
center + size * gl:: vec2{ 0 , 0 } ,
573
center + size * gl:: vec2{ - F, F} ,
574
center + size * gl:: vec2{ - 1 , F} ,
575
center + size * gl:: vec2{ - 1 , - F} ,
576
center + size * gl:: vec2{ - F, - F} ,
577
center + size * gl:: vec2{ 0 , 0 } ,
578
} ,
579
std:: array< ImVec2, 5 + 1 > {
580
center + size * gl:: vec2{ 0 , 0 } ,
581
center + size * gl:: vec2{ F, - F} ,
582
center + size * gl:: vec2{ 1 , - F} ,
583
center + size * gl:: vec2{ 1 , F} ,
584
center + size * gl:: vec2{ F, F} ,
585
center + size * gl:: vec2{ 0 , 0 } ,
586
} ,
587
} ;
588
589
auto * drawList = ImGui:: GetWindowDrawList ( ) ;
590
auto hoverColor = ImGui:: GetColorU32 ( ImGuiCol_ButtonHovered ) ;
591
592
auto color = getColor ( imColor:: TEXT ) ;
593
for ( auto i : xrange ( 4 ) ) {
594
if ( hovered[ i] || ( hoveredRow == i) ) {
595
drawList-> AddConvexPolyFilled ( points[ i] . data ( ) , 5 , hoverColor ) ;
596
}
597
drawList-> AddPolyline ( points[ i] . data ( ) , 5 + 1 , color, thickness ) ;
598
}
599
}
600
601
static void drawFilledCircle ( gl:: vec2 center , float radius , bool fill )
602
{
603
auto * drawList = ImGui:: GetWindowDrawList ( ) ;
604
if ( fill) {
605
auto hoverColor = ImGui:: GetColorU32 ( ImGuiCol_ButtonHovered ) ;
606
drawList-> AddCircleFilled ( center, radius, hoverColor ) ;
607
}
608
auto color = getColor ( imColor:: TEXT ) ;
609
drawList-> AddCircle ( center, radius, color, 0 , thickness ) ;
610
}
611
static void drawFilledRectangle ( Rectangle r , float corner , bool fill )
612
{
613
auto * drawList = ImGui:: GetWindowDrawList ( ) ;
614
if ( fill) {
615
auto hoverColor = ImGui:: GetColorU32 ( ImGuiCol_ButtonHovered ) ;
616
drawList-> AddRectFilled ( r. topLeft , r. bottomRight , hoverColor, corner ) ;
617
}
618
auto color = getColor ( imColor:: TEXT ) ;
619
drawList-> AddRect ( r. topLeft , r. bottomRight , color, corner, thickness ) ;
620
}
621
622
static void drawLetterA ( gl:: vec2 center )
623
{
624
auto * drawList = ImGui:: GetWindowDrawList ( ) ;
625
auto tr = [ & ] ( gl:: vec2 p) { return center + p; } ;
626
const std:: array< ImVec2, 3 > lines = { tr ( { - 6 , 7 } ) , tr ( { 0 , - 7 } ) , tr ( { 6 , 7 } ) } ;
627
auto color = getColor ( imColor:: TEXT ) ;
628
drawList-> AddPolyline ( lines. data ( ) , narrow < int > ( lines. size ( ) ) , color, thickness ) ;
629
drawList-> AddLine ( tr ( { - 3 , 1 } ) , tr ( { 3 , 1 } ) , color, thickness) ;
630
}
631
static void drawLetterB ( gl:: vec2 center )
632
{
633
auto * drawList = ImGui:: GetWindowDrawList ( ) ;
634
auto tr = [ & ] ( gl:: vec2 p) { return center + p; } ;
635
const std:: array< ImVec2, 4 > lines = { tr ( { 1 , - 7 } ) , tr ( { - 4 , - 7 } ) , tr ( { - 4 , 7 } ) , tr ( { 2 , 7 } ) } ;
636
auto color = getColor ( imColor:: TEXT ) ;
637
drawList-> AddPolyline ( lines. data ( ) , narrow < int > ( lines. size ( ) ) , color, thickness ) ;
638
drawList-> AddLine ( tr ( { - 4 , - 1 } ) , tr ( { 2 , - 1 } ) , color, thickness) ;
639
drawList-> AddBezierQuadratic ( tr ( { 1 , - 7 } ) , tr ( { 4 , - 7 } ) , tr ( { 4 , - 4 } ) , color, thickness) ;
640
drawList-> AddBezierQuadratic ( tr ( { 4 , - 4 } ) , tr ( { 4 , - 1 } ) , tr ( { 1 , - 1 } ) , color, thickness) ;
641
drawList-> AddBezierQuadratic ( tr ( { 2 , - 1 } ) , tr ( { 6 , - 1 } ) , tr ( { 6 , 3 } ) , color, thickness) ;
642
drawList-> AddBezierQuadratic ( tr ( { 6 , 3 } ) , tr ( { 6 , 7 } ) , tr ( { 2 , 7 } ) , color, thickness) ;
643
}
644
static void drawLetterC ( gl:: vec2 center )
645
{
646
auto * drawList = ImGui:: GetWindowDrawList ( ) ;
647
auto tr = [ & ] ( gl:: vec2 p) { return center + p; } ;
648
auto color = getColor ( imColor:: TEXT ) ;
649
drawList-> AddBezierCubic ( tr ( { 5 , - 5 } ) , tr ( { - 8 , - 16 } ) , tr ( { - 8 , 16 } ) , tr ( { 5 , 5 } ) , color, thickness) ;
650
}
651
static void drawLetterX ( gl:: vec2 center )
652
{
653
auto * drawList = ImGui:: GetWindowDrawList ( ) ;
654
auto tr = [ & ] ( gl:: vec2 p) { return center + p; } ;
655
auto color = getColor ( imColor:: TEXT ) ;
656
drawList-> AddLine ( tr ( { - 4 , - 6 } ) , tr ( { 4 , 6 } ) , color, thickness) ;
657
drawList-> AddLine ( tr ( { - 4 , 6 } ) , tr ( { 4 , - 6 } ) , color, thickness) ;
658
}
659
static void drawLetterY ( gl:: vec2 center )
660
{
661
auto * drawList = ImGui:: GetWindowDrawList ( ) ;
662
auto tr = [ & ] ( gl:: vec2 p) { return center + p; } ;
663
auto color = getColor ( imColor:: TEXT ) ;
664
drawList-> AddLine ( tr ( { - 4 , - 6 } ) , tr ( { 0 , 0 } ) , color, thickness) ;
665
drawList-> AddLine ( tr ( { - 4 , 6 } ) , tr ( { 4 , - 6 } ) , color, thickness) ;
666
}
667
static void drawLetterZ ( gl:: vec2 center )
668
{
669
auto * drawList = ImGui:: GetWindowDrawList ( ) ;
670
auto tr = [ & ] ( gl:: vec2 p) { return center + p; } ;
671
const std:: array< ImVec2, 4 > linesZ2 = { tr ( { - 4 , - 6 } ) , tr ( { 4 , - 6 } ) , tr ( { - 4 , 6 } ) , tr ( { 4 , 6 } ) } ;
672
auto color = getColor ( imColor:: TEXT ) ;
673
drawList-> AddPolyline ( linesZ2. data ( ) , 4 , color, thickness ) ;
674
}
675
676
namespace msxjoystick {
677
678
enum : uint8_t { UP, DOWN, LEFT, RIGHT, TRIG_A, TRIG_B, NUM_BUTTONS } ;
679
680
static constexpr std:: array< zstring_view, NUM_BUTTONS> buttonNames = {
681
" Up" , " Down" , " Left" , " Right" , " A" , " B"
682
} ;
683
static constexpr std:: array< zstring_view, NUM_BUTTONS> keyNames = {
684
" UP" , " DOWN" , " LEFT" , " RIGHT" , " A" , " B"
685
} ;
686
687
688
static constexpr auto boundingBox = gl:: vec2{ 300.0f , 100.0f } ;
689
static constexpr auto radius = 20.0f ;
690
static constexpr auto corner = 10.0f ;
691
static constexpr auto centerA = gl:: vec2{ 200.0f , 50.0f } ;
692
static constexpr auto centerB = gl:: vec2{ 260.0f , 50.0f } ;
693
static constexpr auto centerDPad = gl:: vec2{ 50.0f , 50.0f } ;
694
static constexpr auto sizeDPad = 30.0f ;
695
696
[ [ nodiscard] ] static std:: vector< uint8_t > buttonsHovered ( gl:: vec2 mouse )
697
{
698
std:: vector< uint8_t > result ( NUM_BUTTONS ) ;
699
auto mouseDPad = ( mouse - centerDPad) * ( 1.0f / sizeDPad) ;
700
if ( insideRectangle ( mouseDPad, Rectangle{ { - 1 , - 1 } , { 1 , 1 } } ) &&
701
( between ( mouseDPad. x , - fractionDPad, fractionDPad ) ||
702
between ( mouseDPad. y , - fractionDPad, fractionDPad ) ) ) {
703
bool t1 = mouseDPad. x < mouseDPad. y ;
704
bool t2 = mouseDPad. x < - mouseDPad. y ;
705
result[ UP] = ! t1 && t2;
706
result[ DOWN] = t1 && ! t2;
707
result[ LEFT] = t1 && t2;
708
result[ RIGHT] = ! t1 && ! t2;
709
}
710
result[ TRIG_A] = insideCircle ( mouse, centerA, radius ) ;
711
result[ TRIG_B] = insideCircle ( mouse, centerB, radius ) ;
712
return result;
713
}
714
715
static void draw ( gl:: vec2 scrnPos , std:: span< uint8_t > hovered , int hoveredRow )
716
{
717
auto * drawList = ImGui:: GetWindowDrawList ( ) ;
718
719
auto color = getColor ( imColor:: TEXT ) ;
720
drawList-> AddRect ( scrnPos, scrnPos + boundingBox, color, corner, thickness ) ;
721
722
drawDPad ( scrnPos + centerDPad, sizeDPad, subspan < 4 > ( hovered ) , hoveredRow ) ;
723
724
auto scrnCenterA = scrnPos + centerA;
725
drawFilledCircle ( scrnCenterA, radius, hovered[ TRIG_A] || ( hoveredRow == TRIG_A) ) ;
726
drawLetterA ( scrnCenterA ) ;
727
728
auto scrnCenterB = scrnPos + centerB;
729
drawFilledCircle ( scrnCenterB, radius, hovered[ TRIG_B] || ( hoveredRow == TRIG_B) ) ;
730
drawLetterB ( scrnCenterB ) ;
731
}
732
733
}
734
735
namespace joymega {
736
737
enum : uint8_t { UP, DOWN, LEFT, RIGHT,
738
TRIG_A, TRIG_B, TRIG_C,
739
TRIG_X, TRIG_Y, TRIG_Z,
740
TRIG_SELECT, TRIG_START,
741
NUM_BUTTONS } ;
742
743
static constexpr std:: array< zstring_view, NUM_BUTTONS> buttonNames = {
744
" Up" , " Down" , " Left" , " Right" ,
745
" A" , " B" , " C" ,
746
" X" , " Y" , " Z" ,
747
" Select" , " Start" ,
748
} ;
749
static constexpr std:: array< zstring_view, NUM_BUTTONS> keyNames = {
750
" UP" , " DOWN" , " LEFT" , " RIGHT" ,
751
" A" , " B" , " C" ,
752
" X" , " Y" , " Z" ,
753
" SELECT" , " START" ,
754
} ;
755
756
757
static constexpr auto thickness = 3.0f ;
758
static constexpr auto boundingBox = gl:: vec2{ 300.0f , 158.0f } ;
759
static constexpr auto centerA = gl:: vec2{ 205.0f , 109.9f } ;
760
static constexpr auto centerB = gl:: vec2{ 235.9f , 93.5f } ;
761
static constexpr auto centerC = gl:: vec2{ 269.7f , 83.9f } ;
762
static constexpr auto centerX = gl:: vec2{ 194.8f , 75.2f } ;
763
static constexpr auto centerY = gl:: vec2{ 223.0f , 61.3f } ;
764
static constexpr auto centerZ = gl:: vec2{ 252.2f , 52.9f } ;
765
static constexpr auto selectBox = Rectangle{ gl:: vec2{ 130.0f , 60.0f } , gl:: vec2{ 160.0f , 70.0f } } ;
766
static constexpr auto startBox = Rectangle{ gl:: vec2{ 130.0f , 86.0f } , gl:: vec2{ 160.0f , 96.0f } } ;
767
static constexpr auto radiusABC = 16.2f ;
768
static constexpr auto radiusXYZ = 12.2f ;
769
static constexpr auto centerDPad = gl:: vec2{ 65.6f , 82.7f } ;
770
static constexpr auto sizeDPad = 34.0f ;
771
static constexpr auto fractionDPad = 1.0f / 3.0f ;
772
773
[ [ nodiscard] ] static std:: vector< uint8_t > buttonsHovered ( gl:: vec2 mouse )
774
{
775
std:: vector< uint8_t > result ( NUM_BUTTONS ) ;
776
auto mouseDPad = ( mouse - centerDPad) * ( 1.0f / sizeDPad) ;
777
if ( insideRectangle ( mouseDPad, Rectangle{ { - 1 , - 1 } , { 1 , 1 } } ) &&
778
( between ( mouseDPad. x , - fractionDPad, fractionDPad ) ||
779
between ( mouseDPad. y , - fractionDPad, fractionDPad ) ) ) {
780
bool t1 = mouseDPad. x < mouseDPad. y ;
781
bool t2 = mouseDPad. x < - mouseDPad. y ;
782
result[ UP] = ! t1 && t2;
783
result[ DOWN] = t1 && ! t2;
784
result[ LEFT] = t1 && t2;
785
result[ RIGHT] = ! t1 && ! t2;
786
}
787
result[ TRIG_A] = insideCircle ( mouse, centerA, radiusABC ) ;
788
result[ TRIG_B] = insideCircle ( mouse, centerB, radiusABC ) ;
789
result[ TRIG_C] = insideCircle ( mouse, centerC, radiusABC ) ;
790
result[ TRIG_X] = insideCircle ( mouse, centerX, radiusXYZ ) ;
791
result[ TRIG_Y] = insideCircle ( mouse, centerY, radiusXYZ ) ;
792
result[ TRIG_Z] = insideCircle ( mouse, centerZ, radiusXYZ ) ;
793
result[ TRIG_START] = insideRectangle ( mouse, startBox ) ;
794
result[ TRIG_SELECT] = insideRectangle ( mouse, selectBox ) ;
795
return result;
796
}
797
798
static void draw ( gl:: vec2 scrnPos , std:: span< uint8_t > hovered , int hoveredRow )
799
{
800
auto * drawList = ImGui:: GetWindowDrawList ( ) ;
801
auto tr = [ & ] ( gl:: vec2 p) { return scrnPos + p; } ;
802
auto color = getColor ( imColor:: TEXT ) ;
803
804
auto drawBezierCurve = [ & ] ( std:: span< const gl:: vec2> points, float thick = 1.0f ) {
805
assert ( ( points. size ( ) % 2 ) == 0 ) ;
806
for ( size_t i = 0 ; i < points. size ( ) - 2 ; i += 2 ) {
807
auto ap = points[ i + 0 ] ;
808
auto ad = points[ i + 1 ] ;
809
auto bp = points[ i + 2 ] ;
810
auto bd = points[ i + 3 ] ;
811
drawList-> AddBezierCubic ( tr ( ap ) , tr ( ap + ad ) , tr ( bp - bd ) , tr ( bp ) , color, thick) ;
812
}
813
} ;
814
815
std:: array outLine = {
816
gl:: vec2{ 150.0f , 0.0f } , gl:: vec2{ 23.1f , 0.0f } ,
817
gl:: vec2{ 258.3f , 30.3f } , gl:: vec2{ 36.3f , 26.4f } ,
818
gl:: vec2{ 300.0f , 107.0f } , gl:: vec2{ 0.0f , 13.2f } ,
819
gl:: vec2{ 285.2f , 145.1f } , gl:: vec2{ - 9.9f , 9.9f } ,
820
gl:: vec2{ 255.3f , 157.4f } , gl:: vec2{ - 9.0f , 0.0f } ,
821
gl:: vec2{ 206.0f , 141.8f } , gl:: vec2{ - 16.2f , - 5.6f } ,
822
gl:: vec2{ 150.0f , 131.9f } , gl:: vec2{ - 16.5f , 0.0f } ,
823
gl:: vec2{ 94.0f , 141.8f } , gl:: vec2{ - 16.2f , 5.6f } ,
824
gl:: vec2{ 44.7f , 157.4f } , gl:: vec2{ - 9.0f , 0.0f } ,
825
gl:: vec2{ 14.8f , 145.1f } , gl:: vec2{ - 9.9f , - 9.9f } ,
826
gl:: vec2{ 0.0f , 107.0f } , gl:: vec2{ 0.0f , - 13.2f } ,
827
gl:: vec2{ 41.7f , 30.3f } , gl:: vec2{ 36.3f , - 26.4f } ,
828
gl:: vec2{ 150.0f , 0.0f } , gl:: vec2{ 23.1f , 0.0f } ,
829
} ;
830
drawBezierCurve ( outLine, thickness ) ;
831
832
drawDPad ( tr ( centerDPad ) , sizeDPad, subspan < 4 > ( hovered ) , hoveredRow) ;
833
drawList-> AddCircle ( tr ( centerDPad ) , 43.0f , color) ;
834
std:: array dPadCurve = {
835
gl:: vec2{ 77.0f , 33.0f } , gl:: vec2{ 69.2f , 0.0f } ,
836
gl:: vec2{ 54.8f , 135.2f } , gl:: vec2{ - 66.9f , 0.0f } ,
837
gl:: vec2{ 77.0f , 33.0f } , gl:: vec2{ 69.2f , 0.0f } ,
838
} ;
839
drawBezierCurve ( dPadCurve ) ;
840
841
drawFilledCircle ( tr ( centerA ) , radiusABC, hovered[ TRIG_A] || ( hoveredRow == TRIG_A) ) ;
842
drawLetterA ( tr ( centerA ) ) ;
843
drawFilledCircle ( tr ( centerB ) , radiusABC, hovered[ TRIG_B] || ( hoveredRow == TRIG_B) ) ;
844
drawLetterB ( tr ( centerB ) ) ;
845
drawFilledCircle ( tr ( centerC ) , radiusABC, hovered[ TRIG_C] || ( hoveredRow == TRIG_C) ) ;
846
drawLetterC ( tr ( centerC ) ) ;
847
drawFilledCircle ( tr ( centerX ) , radiusXYZ, hovered[ TRIG_X] || ( hoveredRow == TRIG_X) ) ;
848
drawLetterX ( tr ( centerX ) ) ;
849
drawFilledCircle ( tr ( centerY ) , radiusXYZ, hovered[ TRIG_Y] || ( hoveredRow == TRIG_Y) ) ;
850
drawLetterY ( tr ( centerY ) ) ;
851
drawFilledCircle ( tr ( centerZ ) , radiusXYZ, hovered[ TRIG_Z] || ( hoveredRow == TRIG_Z) ) ;
852
drawLetterZ ( tr ( centerZ ) ) ;
853
std:: array buttonCurve = {
854
gl:: vec2{ 221.1f , 28.9f } , gl:: vec2{ 80.1f , 0.0f } ,
855
gl:: vec2{ 236.9f , 139.5f } , gl:: vec2{ - 76.8f , 0.0f } ,
856
gl:: vec2{ 221.1f , 28.9f } , gl:: vec2{ 80.1f , 0.0f } ,
857
} ;
858
drawBezierCurve ( buttonCurve ) ;
859
860
auto corner = ( selectBox. bottomRight . y - selectBox. topLeft . y ) * 0.5f ;
861
auto trR = [ & ] ( Rectangle r) { return Rectangle{ tr ( r. topLeft ) , tr ( r. bottomRight ) } ; } ;
862
drawFilledRectangle ( trR ( selectBox ) , corner, hovered[ TRIG_SELECT] || ( hoveredRow == TRIG_SELECT) ) ;
863
drawList-> AddText ( ImGui:: GetFont ( ) , ImGui:: GetFontSize ( ) , tr ( { 123.0f , 46.0f } ) , color, " Select" ) ;
864
drawFilledRectangle ( trR ( startBox ) , corner, hovered[ TRIG_START] || ( hoveredRow == TRIG_START) ) ;
865
drawList-> AddText ( ImGui:: GetFont ( ) , ImGui:: GetFontSize ( ) , tr ( { 128.0f , 97.0f } ) , color, " Start" ) ;
866
}
867
868
}
869
870
namespace joyhandle {
871
872
enum : uint8_t {
873
UP, DOWN, LEFT, RIGHT,
874
TRIG_A, TRIG_B, WHEEL,
875
NUM_BUTTONS
876
} ;
877
static constexpr std:: array< zstring_view, NUM_BUTTONS> buttonNames = {
878
" Up" , " Down" , " Left" , " Right" ,
879
" A" , " B" , " Wheel (analog)" ,
880
} ;
881
static constexpr std:: array< zstring_view, NUM_BUTTONS> keyNames = {
882
" UP" , " DOWN" , " LEFT" , " RIGHT" ,
883
" A" , " B" , " WHEEL" ,
884
} ;
885
886
[ [ nodiscard] ] static std:: vector< uint8_t > buttonsHovered ( gl:: vec2 mouse )
887
{
888
std:: vector< uint8_t > result ( NUM_BUTTONS ) ;
889
auto mouse1 = mouse - gl:: vec2{ 39 , 149 } ;
890
if ( insideCircle ( mouse1, { } , 24 ) ) {
891
auto ax = std:: abs ( mouse1. x ) ;
892
auto ay = std:: abs ( mouse1. y ) ;
893
if ( mouse1. y < 0 && ax < ay) result[ UP ] = true ;
894
if ( mouse1. y > 0 && ax < ay) result[ DOWN ] = true ;
895
if ( mouse1. x < 0 && ay < ax) result[ LEFT ] = true ;
896
if ( mouse1. x > 0 && ay < ax) result[ RIGHT] = true ;
897
898
}
899
result[ TRIG_A] = insideCircle ( mouse, { 77 , 186 } , 5 )
900
|| insideCircle ( mouse, { 319 , 36 } , 15 ) ;
901
result[ TRIG_B] = insideCircle ( mouse, { 63 , 186 } , 5 )
902
|| insideCircle ( mouse, { 134 , 36 } , 15 ) ;
903
if ( insideRectangle ( mouse, { { 110 , 80 } , { 343 , 190 } } ) ) result[ WHEEL] = true ;
904
return result;
905
}
906
907
using namespace VectorPath:: dsl;
908
static constexpr auto boundingBox = gl:: vec2{ 385.0f , 242.0f } ;
909
static constexpr auto body = strokeCollection< int16_t > (
910
openPath ( from ( 698 , 220 ) , line ( 712 , 220 ) , curve ( 719 , 220 , 722 , 223 , 722 , 230 ) , line ( 727 , 408 ) ,
911
line ( 731 , 454 ) , line ( 731 , 463 ) , curve ( 731 , 466 , 728 , 468 , 726 , 468 ) , line ( 14 , 468 ) ,
912
curve ( 11 , 468 , 9 , 465 , 9 , 463 ) , line ( 9 , 454 ) , line ( 13 , 408 ) , line ( 18 , 230 ) ,
913
curve ( 18 , 223 , 25 , 220 , 28 , 220 ) , line ( 212 , 220 ) ) ,
914
openPath ( from ( 9 , 454 ) , line ( 731 , 454 ) ) ,
915
openPath ( from ( 13 , 408 ) , line ( 201 , 408 ) ) ,
916
openPath ( from ( 270 , 408 ) , line ( 640 , 408 ) ) ,
917
openPath ( from ( 709 , 408 ) , line ( 727 , 408 ) ) ,
918
919
openPath ( from ( 20 , 224 ) , line ( 69 , 168 ) , curve ( 73 , 164 , 76 , 164 , 80 , 164 ) , line ( 660 , 164 ) ,
920
curve ( 664 , 164 , 667 , 164 , 671 , 168 ) , line ( 720 , 223 ) ) ,
921
922
openPath ( from ( 84 , 468 ) , line ( 84 , 477 ) , curve ( 84 , 479 , 83 , 480 , 81 , 480 ) , line ( 21 , 480 ) ,
923
curve ( 19 , 480 , 18 , 479 , 18 , 477 ) , line ( 18 , 468 ) ) ,
924
openPath ( from ( 488 , 468 ) , line ( 488 , 477 ) , curve ( 488 , 479 , 487 , 480 , 485 , 480 ) , line ( 425 , 480 ) ,
925
curve ( 423 , 480 , 422 , 479 , 422 , 477 ) , line ( 422 , 468 ) ) ,
926
openPath ( from ( 722 , 468 ) , line ( 722 , 477 ) , curve ( 722 , 479 , 721 , 480 , 719 , 480 ) , line ( 659 , 480 ) ,
927
curve ( 657 , 480 , 656 , 479 , 656 , 477 ) , line ( 656 , 468 ) ) ,
928
929
closedPath ( from ( 183 , 233 ) , curve ( 186 , 233 , 188 , 235 , 188 , 238 ) , line ( 188 , 387 ) ,
930
curve ( 188 , 390 , 186 , 392 , 183 , 392 ) , line ( 31 , 392 ) , curve ( 28 , 392 , 26 , 390 , 26 , 387 ) ,
931
line ( 26 , 238 ) , curve ( 26 , 235 , 28 , 233 , 31 , 233 ) ) ,
932
933
closedPath ( from ( 133 , 285 ) , line ( 151 , 285 ) , line ( 151 , 323 ) , line ( 133 , 323 ) ) ,
934
openPath ( from ( 138 , 299 ) , line ( 146 , 299 ) ) ,
935
openPath ( from ( 138 , 305 ) , line ( 146 , 305 ) ) ,
936
937
closedPath ( from ( 162 , 285 ) , line ( 180 , 285 ) , line ( 180 , 323 ) , line ( 162 , 323 ) ) ,
938
openPath ( from ( 167 , 299 ) , line ( 175 , 299 ) ) ,
939
openPath ( from ( 167 , 305 ) , line ( 175 , 305 ) ) ,
940
941
closedPath ( from ( 217 , 212 ) , curve ( 214 , 212 , 212 , 214 , 212 , 217 ) , line ( 201 , 392 ) , line ( 201 , 444 ) ,
942
line ( 263 , 444 ) , line ( 263 , 217 ) , curve ( 263 , 214 , 261 , 212 , 258 , 212 ) ) ,
943
openPath ( from ( 201 , 392 ) , line ( 250 , 392 ) , curve ( 253 , 392 , 255 , 390 , 255 , 387 ) , line ( 255 , 212 ) ) ,
944
openPath ( from ( 263 , 444 ) , line ( 270 , 408 ) , line ( 270 , 210 ) , curve ( 270 , 207 , 268 , 205 , 265 , 205 ) ) ,
945
946
closedPath ( from ( 693 , 212 ) , curve ( 696 , 212 , 698 , 214 , 698 , 217 ) , line ( 709 , 392 ) , line ( 709 , 444 ) ,
947
line ( 647 , 444 ) , line ( 647 , 217 ) , curve ( 647 , 214 , 649 , 212 , 652 , 212 ) ) ,
948
openPath ( from ( 709 , 392 ) , line ( 660 , 392 ) , curve ( 657 , 392 , 655 , 390 , 655 , 387 ) , line ( 655 , 212 ) ) ,
949
openPath ( from ( 647 , 444 ) , line ( 640 , 408 ) , line ( 640 , 210 ) , curve ( 640 , 207 , 642 , 205 , 645 , 205 ) ) ,
950
951
openPath ( from ( 212 , 217 ) , line ( 213 , 210 ) , curve ( 213 , 208 , 215 , 205 , 218 , 205 ) , line ( 692 , 205 ) ,
952
curve ( 695 , 205 , 697 , 208 , 697 , 210 ) , line ( 698 , 217 ) ) ) ;
953
static constexpr auto wheelOutline = path< int16_t > (
954
from ( 78 , 56 ) ,
955
curve ( 75 , 64 , 67 , 68 , 57 , 68 ) , line ( - 57 , 68 ) , curve ( - 67 , 68 , - 75 , 64 , - 78 , 56 ) , line ( - 156 , 72 ) ,
956
curve ( - 186 , 78 , - 231 , 58 , - 231 , 19 ) , line ( - 231 , - 21 ) , curve ( - 236 , - 24 , - 240 , - 26 , - 240 , - 29 ) ,
957
line ( - 214 , - 245 ) , curve ( - 214 , - 257 , - 155 , - 255 , - 155 , - 243 ) , line ( - 163 , - 27 ) ,
958
curve ( - 163 , - 23 , - 168 , - 21 , - 175 , - 19 ) , curve ( - 175 , 5 , - 167 , 17 , - 147 , 7 ) , line ( - 72 , - 32 ) ,
959
line ( - 71 , - 47 ) , curve ( - 69 , - 60 , - 62 , - 65 , - 50 , - 65 ) , line ( 50 , - 65 ) ,
960
curve ( 62 , - 65 , 69 , - 60 , 71 , - 47 ) , line ( 72 , - 32 ) , line ( 147 , 7 ) ,
961
curve ( 167 , 17 , 175 , 5 , 175 , - 19 ) , curve ( 168 , - 21 , 163 , - 23 , 163 , - 27 ) , line ( 155 , - 243 ) ,
962
curve ( 155 , - 255 , 214 , - 257 , 214 , - 245 ) , line ( 240 , - 29 ) , curve ( 240 , - 26 , 237 , - 24 , 231 , - 21 ) ,
963
line ( 231 , 22 ) , curve ( 231 , 61 , 186 , 78 , 156 , 72 ) ) ;
964
static constexpr auto wheelInside = strokeCollection< int16_t > (
965
openPath ( from ( - 163 , - 27 ) , curve ( - 163 , - 13 , - 240 , - 15 , - 240 , - 29 ) ) ,
966
openPath ( from ( 163 , - 27 ) , curve ( 163 , - 13 , 240 , - 15 , 240 , - 29 ) ) ,
967
closedPath ( from ( 47 , - 51 ) , curve ( 53 , - 51 , 56 , - 49 , 57 , - 43 ) , line ( 63 , 40 ) ,
968
curve ( 63 , 47 , 61 , 51 , 52 , 51 ) , line ( - 52 , 51 ) , curve ( - 61 , 51 , - 63 , 47 , - 63 , 40 ) ,
969
line ( - 57 , - 43 ) , curve ( - 56 , - 49 , - 53 , - 51 , - 47 , - 51 ) ) ,
970
openPath ( from ( - 215 , - 18 ) , line ( - 215 , 19 ) , curve ( - 215 , 49 , - 185 , 60 , - 161 , 55 ) , line ( - 79 , 40 ) ) ,
971
openPath ( from ( - 74 , - 15 ) , line ( - 148 , 23 ) , curve ( - 175 , 33 , - 190 , 13 , - 190 , - 17 ) ) ,
972
openPath ( from ( - 57 , 68 ) , curve ( - 71 , 68 , - 80 , 61 , - 79 , 46 ) , line ( - 72 , - 32 ) ) ,
973
openPath ( from ( 215 , - 18 ) , line ( 215 , 19 ) , curve ( 215 , 49 , 185 , 60 , 161 , 55 ) , line ( 79 , 40 ) ) ,
974
openPath ( from ( 74 , - 15 ) , line ( 148 , 23 ) , curve ( 175 , 33 , 190 , 13 , 190 , - 17 ) ) ,
975
openPath ( from ( 57 , 68 ) , curve ( 71 , 68 , 80 , 61 , 79 , 46 ) , line ( 72 , - 32 ) ) ) ;
976
static constexpr auto wheelButtonA = path< int16_t > (
977
from ( 155 , - 243 ) ,
978
curve ( 155 , - 255 , 214 , - 257 , 214 , - 245 ) ,
979
curve ( 214 , - 233 , 155 , - 231 , 155 , - 243 ) ) ;
980
static constexpr auto wheelButtonB = path< int16_t > (
981
from ( - 155 , - 243 ) ,
982
curve ( - 155 , - 231 , - 214 , - 233 , - 214 , - 245 ) ,
983
curve ( - 214 , - 257 , - 155 , - 255 , - 155 , - 243 ) ) ;
984
static constexpr auto wheelButtonInside = strokeCollection< int16_t > (
985
openPath ( from ( - 164 , - 243 ) , curve ( - 164 , - 236 , - 207 , - 238 , - 207 , - 245 ) ) ,
986
openPath ( from ( 164 , - 243 ) , curve ( 164 , - 236 , 207 , - 238 , 207 , - 245 ) ) ) ;
987
988
static void draw ( gl:: vec2 scrnPos , std:: span< uint8_t > hovered , int hoveredRow , float analogValue )
989
{
990
static constexpr ImU32 eraseColor = IM_COL32 ( 0 , 0 , 0 , 1 ) ;
991
auto color = getColor ( imColor:: TEXT ) ;
992
auto hoverColor = ImGui:: GetColorU32 ( ImGuiCol_ButtonHovered ) ;
993
994
bool up = hovered[ UP ] || hoveredRow == UP;
995
bool down = hovered[ DOWN ] || hoveredRow == DOWN;
996
bool left = hovered[ LEFT ] || hoveredRow == LEFT;
997
bool right = hovered[ RIGHT ] || hoveredRow == RIGHT;
998
bool buttonA = hovered[ TRIG_A] || hoveredRow == TRIG_A;
999
bool buttonB = hovered[ TRIG_B] || hoveredRow == TRIG_B;
1000
bool wheel = hovered[ WHEEL ] || hoveredRow == WHEEL;
1001
1002
gl:: vec2 stickOffset{ 0 } ;
1003
gl:: vec2 stickRadius{ 1 } ;
1004
if ( up ) { stickOffset. y = - 17 ; stickRadius. y = 0.85f ; }
1005
if ( down ) { stickOffset. y = 17 ; stickRadius. y = 0.85f ; }
1006
if ( left ) { stickOffset. x = - 17 ; stickRadius. x = 0.85f ; }
1007
if ( right) { stickOffset. x = 17 ; stickRadius. x = 0.85f ; }
1008
auto stickColor = ( up || down || left || right) ? hoverColor : eraseColor;
1009
1010
auto wheelColor = wheel ? hoverColor : eraseColor;
1011
1012
using namespace VectorPath;
1013
static constexpr float scale = 0.5f ;
1014
static constexpr float thickness = 1.0f ;
1015
1016
1017
ImDrawList dl ( ImGui:: GetDrawListSharedData ( ) ) ;
1018
dl. _ResetForNewFrame ( ) ;
1019
dl. PushTexture ( ImGui:: GetIO ( ) . Fonts -> TexRef ) ;
1020
gl:: vec2 logicalSize = boundingBox;
1021
dl. PushClipRect ( { 0 , 0 } , logicalSize) ;
1022
1023
1024
ScaleTransform transform{ scale} ;
1025
1026
float angle = analogValue * ( 30.0f / 180.0f * float ( Math:: pi) ) ;
1027
auto wheelTransform = makeAffineTransform ( angle, scale, gl:: vec2{ 453 , 317 } * scale ) ;
1028
1029
drawPathStrokes < int16_t > ( & dl, body, transform, color, thickness) ;
1030
static constexpr gl:: vec2 stickCenter1{ 78 , 298 } ;
1031
dl. AddCircle ( transform ( stickCenter1 ) , 44 * scale, color) ;
1032
1033
1034
if ( buttonA) {
1035
dl. AddCircleFilled ( transform ( 155 , 372 ) , 10 * scale, hoverColor) ;
1036
}
1037
if ( buttonB) {
1038
dl. AddCircleFilled ( transform ( 127 , 372 ) , 10 * scale, hoverColor) ;
1039
}
1040
dl. AddCircle ( transform ( 155 , 372 ) , 10 * scale, color, 0 , thickness) ;
1041
dl. AddCircle ( transform ( 127 , 372 ) , 10 * scale, color, 0 , thickness) ;
1042
1043
auto stickCenter = transform ( stickCenter1 + stickOffset ) ;
1044
1045
1046
{
1047
dl. AddCallback ( [ ] ( const ImDrawList* , const ImDrawCmd* ) {
1048
1049
glBlendFunc ( GL_ONE, GL_ZERO ) ;
1050
} , nullptr ) ;
1051
dl. AddEllipseFilled ( stickCenter, 35 * scale * stickRadius, stickColor ) ;
1052
drawPathFilled < int16_t > ( & dl, wheelOutline, wheelTransform, wheelColor) ;
1053
dl. AddCallback ( [ ] ( const ImDrawList* , const ImDrawCmd* ) {
1054
glBlendFunc ( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ) ;
1055
} , nullptr ) ;
1056
}
1057
1058
dl. AddEllipse ( stickCenter, 35 * scale * stickRadius, color ) ;
1059
dl. AddEllipseFilled ( stickCenter, 5 * scale * stickRadius, color ) ;
1060
1061
drawPathStroke < int16_t > ( & dl, wheelOutline, wheelTransform, color, thickness, ImDrawFlags_Closed) ;
1062
drawPathStrokes < int16_t > ( & dl, wheelInside, wheelTransform, color, thickness) ;
1063
1064
if ( buttonA) {
1065
drawPathFilled < int16_t > ( & dl, wheelButtonA, wheelTransform, hoverColor) ;
1066
}
1067
drawPathStroke < int16_t > ( & dl, wheelButtonA, wheelTransform, color, thickness) ;
1068
if ( buttonB) {
1069
drawPathFilled < int16_t > ( & dl, wheelButtonB, wheelTransform, hoverColor) ;
1070
}
1071
drawPathStroke < int16_t > ( & dl, wheelButtonB, wheelTransform, color, thickness) ;
1072
drawPathStrokes < int16_t > ( & dl, wheelButtonInside, wheelTransform, color, thickness) ;
1073
1074
dl. PopClipRect ( ) ;
1075
1076
1077
auto fbScale = ImGui:: GetIO ( ) . DisplayFramebufferScale ;
1078
auto fbSize = logicalSize * fbScale;
1079
1080
struct ShapeLayerRT {
1081
gl:: ColorTexture tex{ 0 , 0 } ;
1082
gl:: FrameBufferObject fbo;
1083
1084
void ensure ( gl:: ivec2 newSize ) {
1085
if ( tex. size ( ) == newSize) return ;
1086
1087
tex. resize ( newSize. x , newSize. y ) ;
1088
tex. setInterpolation ( true ) ;
1089
fbo = gl:: FrameBufferObject ( tex ) ;
1090
}
1091
} ;
1092
static ShapeLayerRT rt;
1093
rt. ensure ( gl:: ivec2 ( fbSize ) ) ;
1094
1095
ImDrawData dd;
1096
dd. Valid = true ;
1097
dd. DisplayPos = { 0 , 0 } ;
1098
dd. DisplaySize = logicalSize;
1099
dd. FramebufferScale = fbScale;
1100
dd. Textures = & ImGui:: GetPlatformIO ( ) . Textures ;
1101
dd. AddDrawList ( & dl) ;
1102
1103
rt. fbo . push ( ) ;
1104
glViewport ( 0 , 0 , int ( fbSize. x ) , int ( fbSize. y ) ) ;
1105
glClearColor ( 0.f , 0.f , 0.f , 0.f ) ;
1106
glClear ( GL_COLOR_BUFFER_BIT ) ;
1107
ImGui_ImplOpenGL3_RenderDrawData ( & dd) ;
1108
rt. fbo . pop ( ) ;
1109
1110
1111
auto pos = ImGui:: GetCursorPos ( ) ;
1112
ImGui:: SetCursorScreenPos ( scrnPos ) ;
1113
ImGui:: GetWindowDrawList ( ) -> AddCallback ( [ ] ( const ImDrawList* , const ImDrawCmd* ) {
1114
1115
glBlendFunc ( GL_ONE, GL_ONE_MINUS_SRC_ALPHA ) ;
1116
} , nullptr ) ;
1117
ImGui:: Image ( rt. tex . getImGui ( ) , logicalSize, { 0 , 1 } , { 1 , 0 } ) ;
1118
ImGui:: GetWindowDrawList ( ) -> AddCallback ( ImGui:: GetPlatformIO ( ) . DrawCallback_ResetRenderState , nullptr ) ;
1119
ImGui:: SetCursorPos ( pos ) ;
1120
}
1121
1122
}
1123
1124
void ImGuiSettings:: paintJoystick ( MSXMotherBoard& motherBoard , JoystickManager& joystickManager )
1125
{
1126
ImGui:: SetNextWindowSize ( gl:: vec2{ 316 , 323 } , ImGuiCond_FirstUseEver ) ;
1127
im:: Window ( " Configure MSX joysticks" , & showConfigureJoystick, [ & ] {
1128
ImGui:: SetNextItemWidth ( 13.0f * ImGui:: GetFontSize ( ) ) ;
1129
bool justChanged = im:: Combo ( " Select joystick" , joystickToGuiString ( joystick ) . c_str ( ) , [ & ] {
1130
for ( const auto & j : xrange ( 6 ) ) {
1131
if ( ImGui:: Selectable ( joystickToGuiString ( j ) . c_str ( ) ) ) {
1132
joystick = j;
1133
}
1134
}
1135
} ) ;
1136
1137
const auto & controller = motherBoard. getMSXCommandController ( ) ;
1138
auto * setting = dynamic_cast < StringSetting* > ( controller. findSetting ( settingName ( joystick ) ) ) ;
1139
if ( ! setting) return ;
1140
auto & interp = setting-> getInterpreter ( ) ;
1141
TclObject bindings = setting-> getValue ( ) ;
1142
1143
gl:: vec2 scrnPos = ImGui:: GetCursorScreenPos ( ) ;
1144
gl:: vec2 mouse = gl:: vec2 ( ImGui:: GetIO ( ) . MousePos ) - scrnPos;
1145
1146
1147
auto hovered = joystick < 2 ? msxjoystick:: buttonsHovered ( mouse )
1148
: joystick < 4 ? joymega :: buttonsHovered ( mouse )
1149
: joyhandle :: buttonsHovered ( mouse ) ;
1150
const auto numButtons = hovered. size ( ) ;
1151
using SP = std:: span< const zstring_view> ;
1152
auto keyNames = joystick < 2 ? SP{ msxjoystick:: keyNames}
1153
: joystick < 4 ? SP{ joymega :: keyNames}
1154
: SP{ joyhandle :: keyNames} ;
1155
auto buttonNames = joystick < 2 ? SP{ msxjoystick:: buttonNames}
1156
: joystick < 4 ? SP{ joymega :: buttonNames}
1157
: SP{ joyhandle :: buttonNames} ;
1158
1159
1160
std:: optional< int > addAction;
1161
std:: optional< int > removeAction;
1162
if ( ImGui:: IsMouseReleased ( ImGuiMouseButton_Left ) && ! justChanged) {
1163
for ( auto i : xrange ( numButtons ) ) {
1164
if ( hovered[ i] ) addAction = narrow < int > ( i ) ;
1165
}
1166
}
1167
1168
ImGui:: Dummy ( joystick < 2 ? msxjoystick:: boundingBox
1169
: joystick < 4 ? joymega :: boundingBox
1170
: joyhandle :: boundingBox ) ;
1171
1172
1173
bool anyWheel = false ;
1174
int hoveredRow = - 1 ;
1175
const auto & style = ImGui:: GetStyle ( ) ;
1176
auto textHeight = ImGui:: GetTextLineHeight ( ) ;
1177
float rowHeight = 2.0f * style. FramePadding . y + textHeight;
1178
float bottomHeight = style. ItemSpacing . y + 2.0f * style. FramePadding . y + textHeight;
1179
im:: Table ( " ##joystick-table" , 2 , ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollX, { 0.0f , - bottomHeight} , [ & ] {
1180
im:: ID_for_range ( numButtons, [ & ] ( int i) {
1181
TclObject key ( keyNames[ i] ) ;
1182
bool wheel = key == " WHEEL" ;
1183
anyWheel |= wheel;
1184
TclObject bindingList = bindings. getDictValue ( interp, key ) ;
1185
if ( wheel && analogBindings. empty ( ) ) {
1186
for ( auto str : bindingList) {
1187
if ( auto a = parseAnalogInput ( str ) ) {
1188
analogBindings. emplace_back ( * a, 0.0f ) ;
1189
}
1190
}
1191
}
1192
1193
if ( ImGui:: TableNextColumn ( ) ) {
1194
auto pos = ImGui:: GetCursorPos ( ) ;
1195
ImGui:: Selectable ( " ##row" , hovered[ i] , ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap, ImVec2 ( 0 , rowHeight) ) ;
1196
if ( ImGui:: IsItemHovered ( ) ) {
1197
hoveredRow = i;
1198
}
1199
1200
ImGui:: SetCursorPos ( pos ) ;
1201
ImGui:: AlignTextToFramePadding ( ) ;
1202
ImGui:: TextUnformatted ( buttonNames[ i] ) ;
1203
}
1204
if ( ImGui:: TableNextColumn ( ) ) {
1205
if ( ImGui:: Button ( " Add" ) ) {
1206
addAction = i;
1207
}
1208
ImGui:: SameLine ( ) ;
1209
auto numBindings = bindingList. size ( ) ;
1210
im:: Disabled ( numBindings == 0 , [ & ] {
1211
if ( ImGui:: Button ( " Remove" ) ) {
1212
if ( numBindings == 1 ) {
1213
bindings. setDictValue ( interp, key, TclObject{ } ) ;
1214
setting-> setValue ( bindings ) ;
1215
} else {
1216
removeAction = i;
1217
}
1218
}
1219
} ) ;
1220
ImGui:: SameLine ( ) ;
1221
if ( numBindings == 0 ) {
1222
ImGui:: TextDisabledUnformatted ( " no bindings" ) ;
1223
} else {
1224
size_t lastBindingIndex = numBindings - 1 ;
1225
size_t bindingIndex = 0 ;
1226
for ( auto binding : bindingList) {
1227
ImGui:: TextUnformatted ( binding ) ;
1228
simpleToolTip ( toGuiString ( wheel, binding, joystickManager ) ) ;
1229
if ( bindingIndex < lastBindingIndex) {
1230
ImGui:: SameLine ( ) ;
1231
ImGui:: TextUnformatted ( " |" sv) ;
1232
ImGui:: SameLine ( ) ;
1233
}
1234
+ + bindingIndex;
1235
}
1236
}
1237
}
1238
} ) ;
1239
} ) ;
1240
float analogValue = 0.0f ;
1241
if ( anyWheel) {
1242
for ( auto & [ b, v] : analogBindings) {
1243
if ( std:: abs ( v ) > std:: abs ( analogValue ) ) {
1244
analogValue = v;
1245
}
1246
}
1247
} else {
1248
analogBindings. clear ( ) ;
1249
}
1250
if ( ! analogBindings. empty ( ) ) {
1251
initListener ( ) ;
1252
} else {
1253
deinitListenerIfIdle ( ) ;
1254
}
1255
1256
joystick < 2 ? msxjoystick:: draw ( scrnPos, hovered, hoveredRow )
1257
: joystick < 4 ? joymega :: draw ( scrnPos, hovered, hoveredRow )
1258
: joyhandle :: draw ( scrnPos, hovered, hoveredRow, analogValue ) ;
1259
1260
if ( ImGui:: Button ( " Default bindings..." ) ) {
1261
ImGui:: OpenPopup ( " bindings" ) ;
1262
}
1263
ImGui:: SameLine ( ) ;
1264
if ( ImGui:: Button ( " Calibrate joystick axes" ) ) showCalibrateJoystick = true ;
1265
im:: Popup ( " bindings" , [ & ] {
1266
auto addOrSet = [ & ] ( auto getBindings) {
1267
if ( ImGui:: MenuItem ( " Add to current bindings" ) ) {
1268
1269
auto newBindings = getBindings ( ) ;
1270
for ( auto k : xrange ( int ( numButtons) ) ) {
1271
TclObject key ( keyNames[ k] ) ;
1272
TclObject dstList = bindings. getDictValue ( interp, key ) ;
1273
TclObject srcList = newBindings. getDictValue ( interp, key ) ;
1274
1275
for ( auto b : srcList) {
1276
if ( ! contains ( dstList, b ) ) {
1277
dstList. addListElement ( b ) ;
1278
}
1279
}
1280
bindings. setDictValue ( interp, key, dstList ) ;
1281
}
1282
setting-> setValue ( bindings ) ;
1283
}
1284
if ( ImGui:: MenuItem ( " Replace current bindings" ) ) {
1285
setting-> setValue ( getBindings ( ) ) ;
1286
}
1287
} ;
1288
im:: Menu ( " Keyboard" , [ & ] {
1289
addOrSet ( [ ] {
1290
return TclObject ( TclObject:: MakeDictTag{ } ,
1291
" UP" , makeTclList ( " keyb Up" ) ,
1292
" DOWN" , makeTclList ( " keyb Down" ) ,
1293
" LEFT" , makeTclList ( " keyb Left" ) ,
1294
" RIGHT" , makeTclList ( " keyb Right" ) ,
1295
" A" , makeTclList ( " keyb Space" ) ,
1296
" B" , makeTclList ( " keyb M" ) ) ;
1297
} ) ;
1298
} ) ;
1299
for ( auto joyId : joystickManager. getConnectedJoysticks ( ) ) {
1300
im:: Menu ( tmpStrCat ( joystickManager. getDisplayName ( joyId ) , " ##" , joyId. raw ( ) ) . c_str ( ) , [ & ] {
1301
addOrSet ( [ & ] {
1302
return joystick < 2 ? MSXJoystick:: getDefaultConfig ( joyId, joystickManager )
1303
: joystick < 4 ? JoyMega :: getDefaultConfig ( joyId, joystickManager )
1304
: JoyHandle :: getDefaultConfig ( joyId, joystickManager ) ;
1305
} ) ;
1306
} ) ;
1307
}
1308
} ) ;
1309
1310
1311
static constexpr auto addTitle = " Waiting for input" ;
1312
if ( addAction) {
1313
addPopupForKey = * addAction;
1314
popupTimeout = 5.0f ;
1315
initListener ( ) ;
1316
ImGui:: OpenPopup ( addTitle ) ;
1317
}
1318
im:: PopupModal ( addTitle, nullptr , ImGuiWindowFlags_NoSavedSettings, [ & ] {
1319
auto close = [ & ] {
1320
ImGui:: CloseCurrentPopup ( ) ;
1321
addPopupForKey = unsigned ( - 1 ) ;
1322
deinitListenerIfIdle ( ) ;
1323
} ;
1324
if ( addPopupForKey >= numButtons) {
1325
close ( ) ;
1326
return ;
1327
}
1328
1329
ImGui:: Text ( " Enter event for joystick button '%s '" , buttonNames[ addPopupForKey] . c_str ( ) ) ;
1330
ImGui:: Text ( " Or press ESC to cancel. Timeout in %d seconds." , int ( popupTimeout) ) ;
1331
1332
popupTimeout -= ImGui:: GetIO ( ) . DeltaTime ;
1333
if ( popupTimeout <= 0.0f ) {
1334
close ( ) ;
1335
}
1336
} ) ;
1337
1338
1339
if ( removeAction) {
1340
removePopupForKey = * removeAction;
1341
ImGui:: OpenPopup ( " remove" ) ;
1342
}
1343
im:: Popup ( " remove" , [ & ] {
1344
auto close = [ & ] {
1345
ImGui:: CloseCurrentPopup ( ) ;
1346
removePopupForKey = unsigned ( - 1 ) ;
1347
} ;
1348
if ( removePopupForKey >= numButtons) {
1349
close ( ) ;
1350
return ;
1351
}
1352
TclObject key ( keyNames[ removePopupForKey] ) ;
1353
TclObject bindingList = bindings. getDictValue ( interp, key ) ;
1354
bool wheel = key == " WHEEL" ;
1355
1356
auto remove = size_t ( - 1 ) ;
1357
size_t counter = 0 ;
1358
for ( const auto & b : bindingList) {
1359
if ( ImGui:: Selectable ( b. c_str ( ) ) ) {
1360
remove = counter;
1361
}
1362
simpleToolTip ( toGuiString ( wheel, b, joystickManager ) ) ;
1363
+ + counter;
1364
}
1365
if ( remove != size_t ( - 1 ) ) {
1366
bindingList. removeListIndex ( interp, remove ) ;
1367
bindings. setDictValue ( interp, key, bindingList ) ;
1368
setting-> setValue ( bindings ) ;
1369
close ( ) ;
1370
if ( wheel) analogBindings. clear ( ) ;
1371
}
1372
1373
if ( ImGui:: Selectable ( " all bindings" ) ) {
1374
bindings. setDictValue ( interp, key, TclObject{ } ) ;
1375
setting-> setValue ( bindings ) ;
1376
close ( ) ;
1377
if ( wheel) analogBindings. clear ( ) ;
1378
}
1379
} ) ;
1380
} ) ;
1381
}
1382
1383
static void verticalText ( ImDrawList* drawList , gl:: vec2 pos , ImU32 color , std:: string_view text )
1384
{
1385
auto vtxIdxStart = drawList-> _VtxCurrentIdx ;
1386
drawList-> AddText ( pos, color, text. data ( ) , text. data ( ) + text. size ( ) ) ;
1387
auto vtxIdxEnd = drawList-> _VtxCurrentIdx ;
1388
1389
auto * verts = drawList-> VtxBuffer . Data ;
1390
for ( auto i = vtxIdxStart; i < vtxIdxEnd; + + i) {
1391
auto offset = gl:: vec2 ( verts[ i] . pos ) - pos;
1392
verts[ i] . pos = pos + gl:: vec2{ offset. y , - offset. x } ;
1393
}
1394
}
1395
1396
static float drawControlEdge ( const char * id , gl:: vec2 pos , float height , float dotY , float min , float max )
1397
{
1398
ImGui:: SetCursorScreenPos ( pos - gl:: vec2{ 2 , 0 } ) ;
1399
ImGui:: InvisibleButton ( id, gl:: vec2 ( 5 , height) ) ;
1400
bool hovered = ImGui:: IsItemHovered ( ) ;
1401
bool active = ImGui:: IsItemActive ( ) ;
1402
auto current = pos. x ;
1403
if ( active && ImGui:: IsMouseDragging ( ImGuiMouseButton_Left ) ) {
1404
current = std:: clamp ( current + ImGui:: GetIO ( ) . MouseDelta . x , min, max ) ;
1405
}
1406
if ( hovered) ImGui:: SetMouseCursor ( ImGuiMouseCursor_ResizeEW ) ;
1407
1408
auto colSep = ImGui:: GetColorU32 ( ImGuiCol_Separator ) ;
1409
auto colActive = ImGui:: GetColorU32 ( ImGuiCol_SeparatorActive ) ;
1410
auto colHover = ImGui:: GetColorU32 ( ImGuiCol_SeparatorHovered ) ;
1411
auto * drawList = ImGui:: GetWindowDrawList ( ) ;
1412
auto color = active ? colActive : ( hovered ? colHover : colSep) ;
1413
drawList-> AddLine ( pos, pos + gl:: vec2{ 0 , height} , color, hovered ? 3.0f : 1.0f ) ;
1414
drawList-> AddCircleFilled ( pos + gl:: vec2{ 0 , dotY} , 10 , color ) ;
1415
return current;
1416
}
1417
1418
static void generateCurvePoints (
1419
std:: function< float ( float ) > func ,
1420
std:: function< gl:: vec2( gl:: vec2) > transform ,
1421
float xStart , float xEnd ,
1422
std:: vector< ImVec2> & points )
1423
{
1424
auto evalScreen = [ & ] ( float x) { return transform ( gl:: vec2{ x, func ( x ) } ) ; } ;
1425
1426
std:: function< void ( float , gl:: vec2, float , gl:: vec2) > subDivide =
1427
[ & ] ( float x0, gl:: vec2 p0, float x1, gl:: vec2 p1) {
1428
float xm = ( x0 + x1) * 0.5f ;
1429
auto pMidCurve = evalScreen ( xm ) ;
1430
auto pMidLineY = ( p0. y + p1. y ) * 0.5f ;
1431
float dy = pMidCurve. y - pMidLineY;
1432
1433
if ( std:: abs ( dy ) > 2.0f ) {
1434
1435
subDivide ( x0, p0, xm, pMidCurve ) ;
1436
subDivide ( xm, pMidCurve, x1, p1 ) ;
1437
} else {
1438
1439
points. emplace_back ( pMidCurve ) ;
1440
points. emplace_back ( p1 ) ;
1441
}
1442
} ;
1443
auto pStart = evalScreen ( xStart ) ;
1444
auto pEnd = evalScreen ( xEnd ) ;
1445
points. emplace_back ( pStart ) ;
1446
subDivide ( xStart, pStart, xEnd, pEnd ) ;
1447
}
1448
1449
void ImGuiSettings:: paintCalibrate ( JoystickManager& joystickManager )
1450
{
1451
auto s = ImGui:: GetFontSize ( ) ;
1452
ImGui:: SetNextWindowSize ( gl:: vec2{ 32 , 25 } * s, ImGuiCond_FirstUseEver ) ;
1453
im:: Window ( " Calibrate joysticks" , & showCalibrateJoystick, [ & ] {
1454
auto joysticks = joystickManager. getConnectedJoysticks ( ) ;
1455
bool disabled = joysticks. empty ( ) ;
1456
if ( selectedJoystick >= joysticks. size ( ) ) selectedJoystick = 0 ;
1457
1458
im:: Disabled ( disabled, [ & ] {
1459
auto * settings = disabled ? nullptr : joystickManager. getSettings ( joysticks[ selectedJoystick] ) ;
1460
im:: Table ( " ##table" , 2 , [ & ] {
1461
ImGui:: TableSetupColumn ( " joystick" , ImGuiTableColumnFlags_WidthFixed, 18.0f * s) ;
1462
ImGui:: TableSetupColumn ( " values" ) ;
1463
if ( ImGui:: TableNextColumn ( ) ) {
1464
auto selectedName = disabled
1465
? " none available"
1466
: joystickManager. getDisplayName ( joysticks[ selectedJoystick] ) ;
1467
im:: Combo ( " Joystick" , selectedName. c_str ( ) , [ & ] {
1468
for ( auto i : xrange ( joysticks. size ( ) ) ) {
1469
auto name = joystickManager. getDisplayName ( joysticks[ selectedJoystick] ) ;
1470
if ( ImGui:: Selectable ( tmpStrCat ( name, " ##" , i ) . c_str ( ) ) , selectedJoystick == i) {
1471
selectedJoystick = unsigned ( i) ;
1472
}
1473
}
1474
} ) ;
1475
im:: DisabledIndent ( disabled, [ & ] {
1476
if ( ImGui:: Button ( " Restore default" ) ) {
1477
auto restore = [ ] ( Setting& setting) {
1478
setting. setValue ( setting. getDefaultValue ( ) ) ;
1479
} ;
1480
assert ( settings ) ;
1481
restore ( * settings-> deadZoneSetting ) ;
1482
restore ( * settings-> midPointSetting ) ;
1483
restore ( * settings-> saturationSetting ) ;
1484
}
1485
} ) ;
1486
HelpMarkerNewLine (
1487
" Set the translation curve from host joystick-axis raw-input to how openMSX sees it.\n "
1488
" \n "
1489
" Try changing the settings by dragging the 3 vertical lines.\n "
1490
" \n "
1491
" The most important setting is \" deadzone\" . For most MSX input devices this is the "
1492
" only setting that matters. Only for the FS-JH1 steering wheel the \" middle\" and "
1493
" \" saturation\" settings can also be relevant." ) ;
1494
}
1495
if ( ImGui:: TableNextColumn ( ) ) {
1496
im:: Disabled ( disabled, [ & ] {
1497
float inner = settings ? settings-> deadZoneSetting -> getFloat ( ) : 25.0f ;
1498
float middle = settings ? settings-> midPointSetting -> getFloat ( ) : 75.0f ;
1499
float outer = settings ? settings-> saturationSetting -> getFloat ( ) : 90.0f ;
1500
ImGui:: SetNextItemWidth ( 2.0f * s) ;
1501
if ( ImGui:: InputFloat ( " deadzone" , & inner, { } , { } , " %.0f " ) ) {
1502
inner = std:: clamp ( inner, 0.0f , middle - 0.1f ) ;
1503
settings-> deadZoneSetting -> setFloat ( inner ) ;
1504
}
1505
ImGui:: SetNextItemWidth ( 2.0f * s) ;
1506
if ( ImGui:: InputFloat ( " midpoint" , & middle, { } , { } , " %.0f " ) ) {
1507
middle = std:: clamp ( middle, inner + 0.1f , outer - 0.1f ) ;
1508
settings-> midPointSetting -> setFloat ( middle ) ;
1509
}
1510
ImGui:: SetNextItemWidth ( 2.0f * s) ;
1511
if ( ImGui:: InputFloat ( " saturation" , & outer, { } , { } , " %.0f " ) ) {
1512
outer = std:: clamp ( outer, middle + 0.1f , 100.0f ) ;
1513
settings-> saturationSetting -> setFloat ( outer ) ;
1514
}
1515
} ) ;
1516
}
1517
} ) ;
1518
1519
auto inner = settings ? ( settings-> deadZoneSetting -> getFloat ( ) * ( 1.0f / 100.0f ) ) : 0.25f ;
1520
auto middle = settings ? ( settings-> midPointSetting -> getFloat ( ) * ( 1.0f / 100.0f ) ) : 0.75f ;
1521
auto outer = settings ? ( settings-> saturationSetting -> getFloat ( ) * ( 1.0f / 100.0f ) ) : 0.90f ;
1522
1523
gl:: vec2 pos = ImGui:: GetCursorScreenPos ( ) ;
1524
gl:: vec2 size = ImGui:: GetContentRegionAvail ( ) ;
1525
auto * drawList = ImGui:: GetWindowDrawList ( ) ;
1526
auto white = getColor ( imColor:: TEXT ) ;
1527
auto gray = getColor ( imColor:: TEXT_DISABLED ) ;
1528
1529
std:: string_view xLabel = " raw input" ;
1530
std:: string_view yLabel = " output" ;
1531
auto xTextSize = ImGui:: CalcTextSize ( xLabel ) ;
1532
auto yTextSize = ImGui:: CalcTextSize ( yLabel ) ;
1533
auto unit = xTextSize. y * 0.5f ;
1534
auto origin = pos + gl:: vec2{ 3 * unit, size. y - 3 * unit} ;
1535
drawList-> AddText ( pos + gl:: vec2{ ( size. x - xTextSize. x ) * 0.5f , size. y - xTextSize. y } , white, xLabel. data ( ) , xLabel. data ( ) + xLabel. size ( ) ) ;
1536
verticalText ( drawList, pos + gl:: vec2{ 0 , ( size. y + yTextSize. x ) * 0.5f } , white, yLabel ) ;
1537
1538
auto arrowRight = gl:: vec2{ pos. x + size. x , origin. y } ;
1539
auto arrowTop = gl:: vec2{ origin. x , pos. y } ;
1540
1541
auto graphMin = origin. x + 4 * unit;
1542
auto graphMax = arrowRight. x - 4 * unit;
1543
auto fullWidth = graphMax - graphMin;
1544
1545
auto graphLeft = graphMin + inner * fullWidth;
1546
auto graphMidX = graphMin + middle * fullWidth;
1547
auto graphRight = graphMin + outer * fullWidth;
1548
1549
auto graphTop = arrowTop. y + 2 * unit;
1550
auto graphBottom = origin. y ;
1551
auto graphMidY = ( graphTop + graphBottom) * 0.5f ;
1552
auto graphWidth = graphRight - graphLeft;
1553
auto graphHeight = graphBottom - graphTop;
1554
1555
drawList-> AddLine ( { origin. x , graphTop} , { arrowRight. x , graphTop} , gray) ;
1556
drawList-> AddLine ( { origin. x , graphMidY} , { arrowRight. x , graphMidY} , gray) ;
1557
drawList-> AddLine ( { graphMin, origin. y } , { graphMin, origin. y + unit} , gray) ;
1558
drawList-> AddLine ( { graphMax, origin. y } , { graphMax, origin. y + unit} , gray) ;
1559
1560
auto newLeft = drawControlEdge ( " ##inner" , { graphLeft, graphTop} , graphHeight, graphHeight, graphMin, graphMidX - 1 ) ;
1561
auto newMid = drawControlEdge ( " ##middle" , { graphMidX, graphTop} , graphHeight, graphHeight * 0.5f , graphLeft + 1 , graphRight - 1 ) ;
1562
auto newRight = drawControlEdge ( " ##outer" , { graphRight, graphTop} , graphHeight, 0 , graphMidX + 1 , graphMax) ;
1563
bool changed = false ;
1564
if ( newLeft != graphLeft) {
1565
changed = true ;
1566
auto old = inner;
1567
inner = std:: clamp ( ( ( newLeft - graphMin) / fullWidth) , 0.0f , 1.0f ) ;
1568
middle = outer - ( ( outer - middle) * ( outer - inner) / ( outer - old) ) ;
1569
}
1570
if ( newMid != graphMidX) {
1571
changed = true ;
1572
middle = std:: clamp ( ( ( newMid - graphMin) / fullWidth) , 0.0f , 1.0f ) ;
1573
}
1574
if ( newRight != graphRight) {
1575
changed = true ;
1576
auto old = outer;
1577
outer = std:: clamp ( ( ( newRight - graphMin) / fullWidth) , 0.0f , 1.0f ) ;
1578
middle = inner + ( ( middle - inner) * ( outer - inner) / ( old - inner) ) ;
1579
}
1580
if ( changed) {
1581
assert ( settings ) ;
1582
auto innerInt = inner * 100.0f ;
1583
auto middleInt = middle * 100.0f ;
1584
auto outerInt = outer * 100.0f ;
1585
innerInt = std:: clamp ( innerInt, 0.0f , middleInt - 0.1f ) ;
1586
middleInt = std:: clamp ( middleInt, innerInt + 0.1f , outerInt - 0.1f ) ;
1587
outerInt = std:: clamp ( outerInt, middleInt + 0.1f , 100.0f ) ;
1588
settings-> deadZoneSetting -> setFloat ( innerInt ) ;
1589
settings-> midPointSetting -> setFloat ( middleInt ) ;
1590
settings-> saturationSetting -> setFloat ( outerInt ) ;
1591
}
1592
auto f = std:: log ( 0.5f ) / std:: log ( ( middle - inner) / ( outer - inner) ) ;
1593
1594
drawList-> AddLine ( arrowRight, origin - unit * gl:: vec2{ 2 , 0 } , white, 2.0f ) ;
1595
drawList-> AddLine ( arrowRight, arrowRight + unit * gl:: vec2{ - 1 , - 1 } , white, 2.0f ) ;
1596
drawList-> AddLine ( arrowRight, arrowRight + unit * gl:: vec2{ - 1 , 1 } , white, 2.0f ) ;
1597
1598
drawList-> AddLine ( arrowTop, origin + unit * gl:: vec2{ 0 , 2 } , white, 2.0f ) ;
1599
drawList-> AddLine ( arrowTop, arrowTop + unit * gl:: vec2{ - 1 , 1 } , white, 2.0f ) ;
1600
drawList-> AddLine ( arrowTop, arrowTop + unit * gl:: vec2{ 1 , 1 } , white, 2.0f ) ;
1601
1602
std:: vector< ImVec2> points;
1603
points. reserve ( 40 ) ;
1604
points. emplace_back ( origin ) ;
1605
generateCurvePoints (
1606
[ & ] ( float x) { return std:: pow ( x, f ) ; } ,
1607
[ & ] ( gl:: vec2 p) { return p * gl:: vec2{ graphWidth, - graphHeight} + gl:: vec2{ graphLeft, origin. y } ; } ,
1608
0.0f , 1.0f , points ) ;
1609
points. emplace_back ( arrowRight. x , graphTop ) ;
1610
drawList-> AddPolyline ( points. data ( ) , points. size ( ) , white, 4.0f ) ;
1611
1612
auto numAxes = disabled ? 0 : joystickManager. getNumAxes ( joysticks[ selectedJoystick] ) . value_or ( 0 ) ;
1613
for ( auto axis : xrange ( int ( numAxes) ) ) {
1614
auto [ r, g, b] = generateDistinctColor ( axis ) ;
1615
ImU32 color = ImColor{ r, g, b} ;
1616
1617
auto axisValue = joystickManager. getAxis ( joysticks[ selectedJoystick] , axis ) . value_or ( 0 ) ;
1618
auto input = float ( std:: abs ( axisValue ) ) / 32768.0f ;
1619
auto output = pow ( std:: clamp ( ( input - inner) / ( outer - inner) , 0.0f , 1.0f ) , f) ;
1620
1621
1622
auto cross = gl:: vec2{ graphMin + input * fullWidth, origin. y - output * graphHeight} ;
1623
if ( output > 0.0f ) {
1624
drawList-> AddLine ( { origin. x , cross. y } , gl:: vec2{ origin. x , cross. y } + unit * gl:: vec2{ 1 , - 1 } , color, 2.0f ) ;
1625
drawList-> AddLine ( { origin. x , cross. y } , gl:: vec2{ origin. x , cross. y } + unit * gl:: vec2{ 1 , 1 } , color, 2.0f ) ;
1626
}
1627
drawList-> AddLine ( { cross. x , origin. y } , gl:: vec2{ cross. x , origin. y } + unit * gl:: vec2{ - 1 , - 1 } , color, 2.0f ) ;
1628
drawList-> AddLine ( { cross. x , origin. y } , gl:: vec2{ cross. x , origin. y } + unit * gl:: vec2{ 1 , - 1 } , color, 2.0f ) ;
1629
drawList-> AddLine ( cross + unit * gl:: vec2{ - 1 , - 1 } , cross + unit * gl:: vec2{ 1 , 1 } , color, 2.0f ) ;
1630
drawList-> AddLine ( cross + unit * gl:: vec2{ - 1 , 1 } , cross + unit * gl:: vec2{ 1 , - 1 } , color, 2.0f ) ;
1631
1632
1633
auto lPos = gl:: vec2{ graphMin, graphTop + float ( axis) * unit * 2.5f + unit} ;
1634
drawList-> AddRectFilled ( lPos, lPos + gl:: vec2{ 2.0f * unit} , color ) ;
1635
auto text = tmpStrCat ( " axis " , axis) ;
1636
drawList-> AddText ( lPos + gl:: vec2{ 3 * unit, 0.0f } , white, text. data ( ) , text. data ( ) + text. size ( ) ) ;
1637
}
1638
} ) ;
1639
} ) ;
1640
}
1641
1642
void ImGuiSettings:: paintFont ( )
1643
{
1644
im:: Window ( " Select font" , & showFont, [ & ] {
1645
auto selectFilename = [ & ] ( FilenameSetting& fileSetting, IntegerSetting& faceSetting, zstring_view name, float width) {
1646
auto currentFile = fileSetting. getString ( ) ;
1647
auto currentFace = faceSetting. getInt ( ) ;
1648
1649
ImGui:: SetNextItemWidth ( width ) ;
1650
im:: Combo ( tmpStrCat ( " ##" , fileSetting. getBaseName ( ) ) . c_str ( ) , name. c_str ( ) , [ & ] {
1651
for ( const auto & font : getAvailableFonts ( ) ) {
1652
bool isSelected = currentFile == font. filename &&
1653
currentFace == font. faceIndex ;
1654
if ( ImGui:: Selectable ( font. displayName . c_str ( ) , isSelected ) ) {
1655
fileSetting. setString ( font. filename ) ;
1656
faceSetting. setInt ( font. faceIndex ) ;
1657
}
1658
}
1659
} ) ;
1660
} ;
1661
auto selectSize = [ ] ( IntegerSetting& setting) {
1662
auto display = [ ] ( int s) { return strCat ( s ) ; } ;
1663
auto current = setting. getInt ( ) ;
1664
ImGui:: SetNextItemWidth ( 4.0f * ImGui:: GetFontSize ( ) ) ;
1665
im:: Combo ( tmpStrCat ( " ##" , setting. getBaseName ( ) ) . c_str ( ) , display ( current ) . c_str ( ) , [ & ] {
1666
for ( int size : { 9 , 10 , 11 , 12 , 13 , 14 , 15 , 16 , 17 , 18 , 20 , 22 , 24 , 26 , 28 , 30 , 32 } ) {
1667
if ( ImGui:: Selectable ( display ( size ) . c_str ( ) , current == size) ) {
1668
setting. setInt ( size ) ;
1669
}
1670
}
1671
} ) ;
1672
} ;
1673
1674
auto pos = ImGui:: CalcTextSize ( " Monospace" ) . x + 2.0f * ImGui:: GetStyle ( ) . ItemSpacing . x ;
1675
auto width = 12.0f * ImGui:: GetFontSize ( ) ;
1676
1677
ImGui:: AlignTextToFramePadding ( ) ;
1678
ImGui:: TextUnformatted ( " Normal" ) ;
1679
ImGui:: SameLine ( pos ) ;
1680
selectFilename ( manager. fontPropFilename , manager. fontPropIndex , manager. fontPropName , width ) ;
1681
ImGui:: SameLine ( ) ;
1682
selectSize ( manager. fontPropSize ) ;
1683
HelpMarker ( " You can install more fonts by copying .ttf file(s) to your \" <openmsx>/share/skins\" directory." ) ;
1684
1685
ImGui:: AlignTextToFramePadding ( ) ;
1686
ImGui:: TextUnformatted ( " Monospace" ) ;
1687
ImGui:: SameLine ( pos ) ;
1688
im:: Font ( manager. fontMono , [ & ] {
1689
selectFilename ( manager. fontMonoFilename , manager. fontMonoIndex , manager. fontMonoName , width ) ;
1690
} ) ;
1691
ImGui:: SameLine ( ) ;
1692
selectSize ( manager. fontMonoSize ) ;
1693
HelpMarker ( " Some GUI elements (e.g. the console) require a monospaced font." ) ;
1694
} ) ;
1695
}
1696
1697
[ [ nodiscard] ] static std:: string formatShortcutWithAnnotations ( const Shortcuts:: Shortcut& shortcut)
1698
{
1699
auto result = getKeyChordName ( shortcut. keyChord ) ;
1700
1701
if ( shortcut. type == Shortcuts:: Type:: GLOBAL) result += " , global" ;
1702
return result;
1703
}
1704
1705
[ [ nodiscard] ] static gl:: vec2 buttonSize ( std:: string_view text, float defaultSize_ )
1706
{
1707
const auto & style = ImGui:: GetStyle ( ) ;
1708
auto textSize = ImGui:: CalcTextSize ( text ) . x + 2.0f * style. FramePadding . x ;
1709
auto defaultSize = ImGui:: GetFontSize ( ) * defaultSize_;
1710
return { std:: max ( textSize, defaultSize ) , 0.0f } ;
1711
}
1712
1713
void ImGuiSettings:: paintEditShortcut ( )
1714
{
1715
using enum Shortcuts:: Type;
1716
1717
bool editShortcutWindow = editShortcutId != Shortcuts:: ID:: INVALID;
1718
if ( ! editShortcutWindow) return ;
1719
1720
im:: Window ( " Edit shortcut" , & editShortcutWindow, ImGuiWindowFlags_AlwaysAutoResize, [ & ] {
1721
auto & shortcuts = manager. getShortcuts ( ) ;
1722
auto shortcut = shortcuts. getShortcut ( editShortcutId ) ;
1723
1724
im:: Table ( " table" , 2 , [ & ] {
1725
ImGui:: TableSetupColumn ( " " , ImGuiTableColumnFlags_WidthFixed) ;
1726
ImGui:: TableSetupColumn ( " " , ImGuiTableColumnFlags_WidthStretch) ;
1727
1728
if ( ImGui:: TableNextColumn ( ) ) {
1729
ImGui:: AlignTextToFramePadding ( ) ;
1730
ImGui:: TextUnformatted ( " key" ) ;
1731
}
1732
static constexpr auto waitKeyTitle = " Waiting for key" ;
1733
if ( ImGui:: TableNextColumn ( ) ) {
1734
auto text = getKeyChordName ( shortcut. keyChord ) ;
1735
if ( ImGui:: Button ( text. c_str ( ) , buttonSize ( text, 4.0f ) ) ) {
1736
popupTimeout = 10.0f ;
1737
centerNextWindowOverCurrent ( ) ;
1738
ImGui:: OpenPopup ( waitKeyTitle ) ;
1739
}
1740
}
1741
bool isOpen = true ;
1742
im:: PopupModal ( waitKeyTitle, & isOpen, ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize, [ & ] {
1743
ImGui:: Text ( " Enter key combination for shortcut '%s '" ,
1744
Shortcuts:: getShortcutDescription ( editShortcutId ) . c_str ( ) ) ;
1745
ImGui:: Text ( " Timeout in %d seconds." , int ( popupTimeout) ) ;
1746
1747
popupTimeout -= ImGui:: GetIO ( ) . DeltaTime ;
1748
if ( ! isOpen || popupTimeout <= 0.0f ) {
1749
ImGui:: CloseCurrentPopup ( ) ;
1750
}
1751
if ( auto keyChord = getCurrentlyPressedKeyChord ( ) ; keyChord != ImGuiKey_None) {
1752
shortcut. keyChord = keyChord;
1753
shortcuts. setShortcut ( editShortcutId, shortcut ) ;
1754
editShortcutWindow = false ;
1755
ImGui:: CloseCurrentPopup ( ) ;
1756
}
1757
} ) ;
1758
1759
if ( shortcut. type == one_of ( LOCAL, GLOBAL ) ) {
1760
if ( ImGui:: TableNextColumn ( ) ) {
1761
ImGui:: AlignTextToFramePadding ( ) ;
1762
ImGui:: TextUnformatted ( " global" ) ;
1763
}
1764
if ( ImGui:: TableNextColumn ( ) ) {
1765
bool global = shortcut. type == GLOBAL;
1766
if ( ImGui:: Checkbox ( " ##global" , & global) ) {
1767
shortcut. type = global ? GLOBAL : LOCAL;
1768
shortcuts. setShortcut ( editShortcutId, shortcut ) ;
1769
}
1770
simpleToolTip (
1771
" Global shortcuts react when any GUI window has focus.\n "
1772
" Local shortcuts only react when the specific GUI window has focus.\n " sv ) ;
1773
}
1774
}
1775
} ) ;
1776
ImGui:: Separator ( ) ;
1777
const auto & defaultShortcut = Shortcuts:: getDefaultShortcut ( editShortcutId ) ;
1778
im:: Disabled ( shortcut == defaultShortcut, [ & ] {
1779
if ( ImGui:: Button ( " Restore default" ) ) {
1780
shortcuts. setShortcut ( editShortcutId, defaultShortcut ) ;
1781
editShortcutWindow = false ;
1782
}
1783
simpleToolTip ( [ & ] { return formatShortcutWithAnnotations ( defaultShortcut ) ; } ) ;
1784
} ) ;
1785
1786
ImGui:: SameLine ( ) ;
1787
im:: Disabled ( shortcut == Shortcuts:: Shortcut{ } , [ & ] {
1788
if ( ImGui:: Button ( " Set None" ) ) {
1789
shortcuts. setShortcut ( editShortcutId, Shortcuts:: Shortcut{ } ) ;
1790
editShortcutWindow = false ;
1791
}
1792
simpleToolTip ( " Set no binding for this shortcut" sv) ;
1793
} ) ;
1794
} ) ;
1795
if ( ! editShortcutWindow) editShortcutId = Shortcuts:: ID:: INVALID;
1796
}
1797
1798
void ImGuiSettings:: paintShortcut ( )
1799
{
1800
im:: Window ( " Edit shortcuts" , & showShortcut, [ & ] {
1801
int flags = ImGuiTableFlags_Resizable
1802
| ImGuiTableFlags_RowBg
1803
| ImGuiTableFlags_NoBordersInBodyUntilResize
1804
| ImGuiTableFlags_SizingStretchProp;
1805
im:: Table ( " table" , 2 , flags, { - FLT_MIN, 0.0f } , [ & ] {
1806
ImGui:: TableSetupColumn ( " description" ) ;
1807
ImGui:: TableSetupColumn ( " key" ) ;
1808
1809
const auto & shortcuts = manager. getShortcuts ( ) ;
1810
im:: ID_for_range ( std:: to_underlying ( Shortcuts:: ID:: NUM ) , [ & ] ( int i) {
1811
auto id = static_cast < Shortcuts:: ID> ( i) ;
1812
auto shortcut = shortcuts. getShortcut ( id ) ;
1813
1814
if ( ImGui:: TableNextColumn ( ) ) {
1815
ImGui:: AlignTextToFramePadding ( ) ;
1816
ImGui:: TextUnformatted ( Shortcuts:: getShortcutDescription ( id ) ) ;
1817
}
1818
if ( ImGui:: TableNextColumn ( ) ) {
1819
auto text = formatShortcutWithAnnotations ( shortcut ) ;
1820
if ( ImGui:: Button ( text. c_str ( ) , buttonSize ( text, 9.0f ) ) ) {
1821
editShortcutId = id;
1822
centerNextWindowOverCurrent ( ) ;
1823
}
1824
}
1825
} ) ;
1826
} ) ;
1827
} ) ;
1828
paintEditShortcut ( ) ;
1829
}
1830
1831
void ImGuiSettings:: paint ( MSXMotherBoard* motherBoard )
1832
{
1833
if ( selectedStyle < 0 ) {
1834
1835
selectedStyle = 0 ;
1836
setStyle ( ) ;
1837
}
1838
auto & joystickManager = manager. getReactor ( ) . getInputEventGenerator ( ) . getJoystickManager ( ) ;
1839
if ( motherBoard && showConfigureJoystick) {
1840
paintJoystick ( * motherBoard, joystickManager) ;
1841
} else {
1842
deinitListener ( ) ;
1843
}
1844
if ( showCalibrateJoystick) paintCalibrate ( joystickManager ) ;
1845
if ( showFont) paintFont ( ) ;
1846
if ( showShortcut) paintShortcut ( ) ;
1847
}
1848
1849
std:: span< const ImGuiSettings:: FontInfo> ImGuiSettings:: getAvailableFonts ( )
1850
{
1851
if ( availableFonts. empty ( ) ) {
1852
1853
std:: vector< std:: string> files;
1854
const auto & context = systemFileContext ( ) ;
1855
for ( const auto & path : context. getPaths ( ) ) {
1856
foreach_file ( FileOperations:: join ( path, " skins" ) , [ & ] ( const std:: string& , std:: string_view name) {
1857
auto nameGz = name. ends_with ( " .gz" ) ? name. substr ( 0 , name. size ( ) - 3 ) : name;
1858
if ( nameGz. ends_with ( " .ttf" ) || nameGz. ends_with ( " .ttc" ) || nameGz. ends_with ( " .otf" ) ) {
1859
files. emplace_back ( name ) ;
1860
}
1861
} ) ;
1862
}
1863
1864
std:: ranges:: sort ( files ) ;
1865
auto u = std:: ranges:: unique ( files ) ;
1866
files. erase ( u. begin ( ) , u. end ( ) ) ;
1867
1868
1869
for ( const auto & file : files) {
1870
try {
1871
auto resolved = context. resolve ( FileOperations:: join ( " skins" , file) ) ;
1872
auto fontData = File ( resolved ) . mmap < uint8_t > ( ) ;
1873
for ( auto i : xrange ( getNumFaces ( manager. freeTypeLibrary , fontData ) ) ) {
1874
FontInfo info;
1875
info. filename = file;
1876
info. displayName = manager. getFontDisplayName ( fontData, i, file ) ;
1877
info. faceIndex = narrow < int > ( i ) ;
1878
availableFonts. emplace_back ( std:: move ( info ) ) ;
1879
}
1880
} catch ( MSXException& ) {
1881
1882
}
1883
}
1884
}
1885
return availableFonts;
1886
}
1887
1888
bool ImGuiSettings:: signalEvent ( const Event& event )
1889
{
1890
auto getJoyDeadThreshold = [ & ] ( JoystickId joyId) {
1891
const auto & joyMan = manager. getReactor ( ) . getInputEventGenerator ( ) . getJoystickManager ( ) ;
1892
return joyMan. getJoyDeadThreshold ( joyId ) ;
1893
} ;
1894
auto getJoyValue = [ & ] ( JoystickId joyId, const JoystickAxisMotionEvent& e) {
1895
const auto & joyMan = manager. getReactor ( ) . getInputEventGenerator ( ) . getJoystickManager ( ) ;
1896
return joyMan. getJoyValue ( joyId, e ) ;
1897
} ;
1898
1899
for ( auto & [ binding, value] : analogBindings) {
1900
if ( auto v = match ( binding, event, getJoyValue ) ) {
1901
value = * v;
1902
}
1903
}
1904
1905
using SP = std:: span< const zstring_view> ;
1906
auto keyNames = joystick < 2 ? SP{ msxjoystick:: keyNames}
1907
: joystick < 4 ? SP{ joymega :: keyNames}
1908
: SP{ joyhandle :: keyNames} ;
1909
if ( const auto numButtons = keyNames. size ( ) ; addPopupForKey >= numButtons) {
1910
deinitListenerIfIdle ( ) ;
1911
return false ;
1912
}
1913
1914
bool escape = false ;
1915
if ( const auto * keyDown = get_event_if < KeyDownEvent> ( event ) ) {
1916
escape = keyDown-> getKeyCode ( ) == SDLK_ESCAPE;
1917
}
1918
if ( ! escape) {
1919
TclObject key ( keyNames[ addPopupForKey] ) ;
1920
bool wheel = key == " WHEEL" ;
1921
1922
std:: string bs;
1923
if ( wheel) {
1924
auto b = captureAnalogInput ( event, getJoyDeadThreshold ) ;
1925
if ( ! b) return true ;
1926
bs = toString ( * b) ;
1927
} else {
1928
auto b = captureBooleanInput ( event, getJoyDeadThreshold ) ;
1929
if ( ! b) return true ;
1930
bs = toString ( * b) ;
1931
}
1932
1933
auto * motherBoard = manager. getReactor ( ) . getMotherBoard ( ) ;
1934
if ( ! motherBoard) return true ;
1935
const auto & controller = motherBoard-> getMSXCommandController ( ) ;
1936
auto * setting = dynamic_cast < StringSetting* > ( controller. findSetting ( settingName ( joystick ) ) ) ;
1937
if ( ! setting) return true ;
1938
auto & interp = setting-> getInterpreter ( ) ;
1939
1940
TclObject bindings = setting-> getValue ( ) ;
1941
TclObject bindingList = bindings. getDictValue ( interp, key ) ;
1942
1943
if ( ! contains ( bindingList, bs ) ) {
1944
bindingList. addListElement ( bs ) ;
1945
bindings. setDictValue ( interp, key, bindingList ) ;
1946
setting-> setValue ( bindings ) ;
1947
if ( wheel) analogBindings. clear ( ) ;
1948
}
1949
}
1950
1951
addPopupForKey = unsigned ( - 1 ) ;
1952
deinitListenerIfIdle ( ) ;
1953
return true ;
1954
}
1955
1956
void ImGuiSettings:: initListener ( )
1957
{
1958
if ( listening) return ;
1959
listening = true ;
1960
1961
auto & distributor = manager. getReactor ( ) . getEventDistributor ( ) ;
1962
1963
using enum EventType;
1964
for ( auto type : { KEY_DOWN, MOUSE_BUTTON_DOWN,
1965
JOY_BUTTON_DOWN, JOY_HAT, JOY_AXIS_MOTION} ) {
1966
distributor. registerEventListener ( type, * this ) ;
1967
}
1968
}
1969
1970
void ImGuiSettings:: deinitListenerIfIdle ( )
1971
{
1972
if ( analogBindings. empty ( ) && ( addPopupForKey == unsigned ( - 1 ) ) ) {
1973
deinitListener ( ) ;
1974
}
1975
}
1976
1977
void ImGuiSettings:: deinitListener ( )
1978
{
1979
if ( ! listening) return ;
1980
listening = false ;
1981
1982
addPopupForKey = unsigned ( - 1 ) ;
1983
analogBindings. clear ( ) ;
1984
1985
auto & distributor = manager. getReactor ( ) . getEventDistributor ( ) ;
1986
using enum EventType;
1987
for ( auto type : { JOY_AXIS_MOTION, JOY_HAT, JOY_BUTTON_DOWN,
1988
MOUSE_BUTTON_DOWN, KEY_DOWN} ) {
1989
distributor. unregisterEventListener ( type, * this ) ;
1990
}
1991
}
1992
1993
}