forked from cpcitor/cpcec
-
Notifications
You must be signed in to change notification settings - Fork 0
/
cpcec-ox.h
1786 lines (1707 loc) · 61.7 KB
/
cpcec-ox.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// #### ###### #### ####### #### ----------------------- //
// ## ## ## ## ## ## ## # ## ## CPCEC, plain text Amstrad //
// ## ## ## ## ## # ## CPC emulator written in C //
// ## ##### ## #### ## as a postgraduate project //
// ## ## ## ## # ## by Cesar Nicolas-Gonzalez //
// ## ## ## ## ## ## # ## ## since 2018-12-01 till now //
// #### #### #### ####### #### ----------------------- //
// SDL2 is the second supported platform; to compile the emulator type
// "$(CC) -DSDL2 -xc cpcec.c -lSDL2" for GCC, TCC, CLANG et al.
// Support for Unix-like systems is provided by providing different
// snippets of code upon whether the symbol "_WIN32" exists or not.
// START OF SDL 2.0+ DEFINITIONS ==================================== //
#ifndef SDL_MAIN_HANDLED
#define SDL_MAIN_HANDLED // required!
#endif
#include <SDL2/SDL.h>
#ifdef _WIN32
#define STRMAX 288 // widespread in Windows
#define PATHCHAR '\\' // WIN32
#include <windows.h> // FindFirstFile...
#include <io.h> // _chsize(),_fileno()...
#define fsetsize(f,l) _chsize(_fileno(f),(l))
#define strcasecmp _stricmp // SDL_strcasecmp
#else
#ifdef PATH_MAX
#define STRMAX PATH_MAX
#else
#define STRMAX 512 // MacOS lacks PATH_MAX; TCC's LIMITS.H sets it to 512 and seems safe, with the exception of realpath()
#endif
#define PATHCHAR '/' // POSIX
#include <dirent.h> // opendir()...
#include <sys/stat.h> // stat()...
#include <unistd.h> // ftruncate(),fileno()...
#define fsetsize(f,l) (!ftruncate(fileno(f),(l)))
#define BYTE Uint8 // can this be safely reduced to "unsigned char"?
#define WORD Uint16 // can this be safely reduced to "unsigned short"?
#define DWORD Uint32 // this CANNOT be safely reduced to "unsigned int"!
#define SDL2_UTF8 // is there any POSIX system that does NOT rely on UTF8?
#endif
#define MESSAGEBOX_WIDETAB "\t\t" // rely on monospace font
// general engine constants and variables --------------------------- //
#define VIDEO_UNIT DWORD // 0x00RRGGBB style
#define VIDEO_FILTER_HALF(x,y) ((x<y?(((x&0XFF00FF)+(y&0XFF00FF)+0X10001)&0X1FE01FE)+(((x&0XFF00)+(y&0XFF00)+0X100)&0X1FE00):(((x&0XFF00FF)+(y&0XFF00FF))&0X1FE01FE)+(((x&0XFF00)+(y&0XFF00))&0X1FE00))>>1) // 50:50
//#define VIDEO_FILTER_BLURDATA vxh,vxl,vzh,vzl
//#define VIDEO_FILTER_BLUR0(z) vxh=z&0XFF00FF,vxl=z&0XFF00
//#define VIDEO_FILTER_BLUR(r,z) r=((((vzh=z&0XFF00FF)+vxh+0X10001)&0X1FE01FE)+(((vzl=z&0XFF00)+vxl+0X100)&0X1FE00))>>1,vxh=vzh,vxl=vzl // 50:50 blur
#define VIDEO_FILTER_BLURDATA vxh,vxl,vyh,vyl,vzh,vzl
#define VIDEO_FILTER_BLUR0(z) vxh=vyh=z&0XFF00FF,vxl=vyl=z&0XFF00
#define VIDEO_FILTER_BLUR(r,z) r=((((vzh=z&0XFF00FF)+vyh*2+vxh+0X20002)&0X3FC03FC)+(((vzl=z&0XFF00)+vyl*2+vxl+0X200)&0X3FC00))>>2,vxh=vyh,vyh=vzh,vxl=vyl,vyl=vzl // 25:50:25 blur
//#define VIDEO_FILTER_X1(x) (((x>>1)&0X7F7F7F)+0X2B2B2B) // average
//#define VIDEO_FILTER_X1(x) (((x>>2)&0X3F3F3F)+0X404040) // heavier
//#define VIDEO_FILTER_X1(x) (((x>>2)&0X3F3F3F)*3+0X161616) // lighter
#define VIDEO_FILTER_X1(x) ((((x&0XFF0000)*76+(x&0XFF00)*(150<<8)+(x&0XFF)*(30<<16)+128)>>24)*0X10101) // greyscale
#define VIDEO_FILTER_SCAN(w,b) (((((w&0xFF00FF)+(b&0xFF00FF)*7)&0x7F807F8)+(((w&0xFF00)+(b&0xFF00)*7)&0x7F800))>>3) // white:black 1:7
#if 0 // 8 bits
#define AUDIO_UNIT unsigned char
#define AUDIO_BITDEPTH 8
#define AUDIO_ZERO 128
#else // 16 bits
#define AUDIO_UNIT signed short
#define AUDIO_BITDEPTH 16
#define AUDIO_ZERO 0
#endif // bitsize
#define AUDIO_CHANNELS 2 // 1 mono, 2 stereo
VIDEO_UNIT *video_frame,*menus_frame,*video_blend; // video and UI frames, allocated on runtime
AUDIO_UNIT *audio_frame,audio_buffer[AUDIO_LENGTH_Z*AUDIO_CHANNELS]; // audio frame
VIDEO_UNIT *video_target; // pointer to current video pixel
AUDIO_UNIT *audio_target; // pointer to current audio sample
int video_pos_x,video_pos_y,audio_pos_z; // counters to keep pointers within range
BYTE video_interlaced=0,video_interlaces=0; // video scanline status
char video_framelimit=0,video_framecount=0; // video frameskip counters; must be signed!
BYTE audio_disabled=0,audio_session=0; // audio status and counter
unsigned char session_path[STRMAX],session_parmtr[STRMAX],session_tmpstr[STRMAX],session_substr[STRMAX],session_info[STRMAX]="";
int session_timer,session_event=0; // timing synchronisation and user command
BYTE session_fast=0,session_wait=0,session_audio=1,session_softblit=1,session_hardblit; // timing and devices ; software blitting is enabled by default because it's safer
BYTE session_stick=1,session_shift=0,session_key2joy=0; // keyboard and joystick
#ifdef MAUS_EMULATION
int session_maus_z=0,session_maus_x=0,session_maus_y=0; // optional mouse
#endif
BYTE video_scanline=0,video_scanlinez=8; // 0 = solid, 1 = scanlines, 2 = full interlace, 3 = half interlace
BYTE video_filter=0,audio_filter=0; // filter flags
BYTE session_intzoom=0; int session_joybits=0;
FILE *session_wavefile=NULL; // audio recording is done on each session update
BYTE session_paused=0,session_signal=0,session_version[8];
#define SESSION_SIGNAL_FRAME 1
#define SESSION_SIGNAL_DEBUG 2
#define SESSION_SIGNAL_PAUSE 4
BYTE session_dirtymenu=1; // to force new status text
#define kbd_bit_set(k) (kbd_bit[k>>3]|=1<<(k&7))
#define kbd_bit_res(k) (kbd_bit[k>>3]&=~(1<<(k&7)))
#define joy_bit_set(k) (joy_bit[k>>3]|=1<<(k&7))
#define joy_bit_res(k) (joy_bit[k>>3]&=~(1<<(k&7)))
#define kbd_bit_tst(k) ((kbd_bit[k>>3]|joy_bit[k>>3])&(1<<(k&7)))
BYTE kbd_bit[16],joy_bit[16]; // up to 128 keys in 16 rows of 8 bits
// SDL2 follows the USB keyboard standard, so we're using the same values here!
// function keys
#define KBCODE_F1 58
#define KBCODE_F2 59
#define KBCODE_F3 60
#define KBCODE_F4 61
#define KBCODE_F5 62
#define KBCODE_F6 63
#define KBCODE_F7 64
#define KBCODE_F8 65
#define KBCODE_F9 66
#define KBCODE_F10 67
#define KBCODE_F11 68
#define KBCODE_F12 69
// leftmost keys
#define KBCODE_ESCAPE 41
#define KBCODE_TAB 43
#define KBCODE_CAPSLOCK 57
#define KBCODE_L_SHIFT 225
#define KBCODE_L_CTRL 224
//#define KBCODE_L_ALT 226 // trapped by Win32
// alphanumeric row 1
#define KBCODE_1 30
#define KBCODE_2 31
#define KBCODE_3 32
#define KBCODE_4 33
#define KBCODE_5 34
#define KBCODE_6 35
#define KBCODE_7 36
#define KBCODE_8 37
#define KBCODE_9 38
#define KBCODE_0 39
#define KBCODE_CHR1_1 45
#define KBCODE_CHR1_2 46
// alphanumeric row 2
#define KBCODE_Q 20
#define KBCODE_W 26
#define KBCODE_E 8
#define KBCODE_R 21
#define KBCODE_T 23
#define KBCODE_Y 28
#define KBCODE_U 24
#define KBCODE_I 12
#define KBCODE_O 18
#define KBCODE_P 19
#define KBCODE_CHR2_1 47
#define KBCODE_CHR2_2 48
// alphanumeric row 3
#define KBCODE_A 4
#define KBCODE_S 22
#define KBCODE_D 7
#define KBCODE_F 9
#define KBCODE_G 10
#define KBCODE_H 11
#define KBCODE_J 13
#define KBCODE_K 14
#define KBCODE_L 15
#define KBCODE_CHR3_1 51
#define KBCODE_CHR3_2 52
#define KBCODE_CHR3_3 49
// alphanumeric row 4
#define KBCODE_Z 29
#define KBCODE_X 27
#define KBCODE_C 6
#define KBCODE_V 25
#define KBCODE_B 5
#define KBCODE_N 17
#define KBCODE_M 16
#define KBCODE_CHR4_1 54
#define KBCODE_CHR4_2 55
#define KBCODE_CHR4_3 56
#define KBCODE_CHR4_4 53
#define KBCODE_CHR4_5 100
// rightmost keys
#define KBCODE_SPACE 44
#define KBCODE_BKSPACE 42
#define KBCODE_ENTER 40
#define KBCODE_R_SHIFT 229
#define KBCODE_R_CTRL 228
// #define KBCODE_R_ALT 230 // trapped by Win32
// extended keys
// #define KBCODE_PRINT 70 // trapped by Win32
#define KBCODE_SCR_LOCK 71
#define KBCODE_HOLD 72
#define KBCODE_INSERT 73
#define KBCODE_DELETE 76
#define KBCODE_HOME 74
#define KBCODE_END 77
#define KBCODE_PRIOR 75
#define KBCODE_NEXT 78
#define KBCODE_UP 82
#define KBCODE_DOWN 81
#define KBCODE_LEFT 80
#define KBCODE_RIGHT 79
// numeric keypad
#define KBCODE_NUM_LOCK 83
#define KBCODE_X_7 95
#define KBCODE_X_8 96
#define KBCODE_X_9 97
#define KBCODE_X_4 92
#define KBCODE_X_5 93
#define KBCODE_X_6 94
#define KBCODE_X_1 89
#define KBCODE_X_2 90
#define KBCODE_X_3 91
#define KBCODE_X_0 98
#define KBCODE_X_DOT 99
#define KBCODE_X_ENTER 88
#define KBCODE_X_ADD 87
#define KBCODE_X_SUB 86
#define KBCODE_X_MUL 85
#define KBCODE_X_DIV 84
const BYTE kbd_k2j[]= // these keys can simulate a 4-button joystick
{ KBCODE_UP, KBCODE_DOWN, KBCODE_LEFT, KBCODE_RIGHT, KBCODE_Z, KBCODE_X, KBCODE_C, KBCODE_V };
unsigned char kbd_map[256]; // key-to-key translation map
// general engine functions and procedures -------------------------- //
void session_user(int k); // handle the user's commands; must be defined later on!
void session_debug_show(void);
int session_debug_user(int k); // debug logic is a bit different: 0 UNKNOWN COMMAND, !0 OK
int debug_xlat(int k); // translate debug keys into codes. Must be defined later on!
INLINE void audio_playframe(int q,AUDIO_UNIT *ao); // handle the sound filtering; is defined in CPCEC-RT.H!
int session_audioqueue; // unlike in Windows, we cannot use the audio device as the timer in SDL2
void session_please(void) // stop activity for a short while
{
if (!session_wait)
{
if (session_audio)
SDL_PauseAudioDevice(session_audio,1);
//video_framecount=-1;
session_wait=1;
}
}
void session_kbdclear(void)
{
session_joybits=0;
memset(kbd_bit,0,sizeof(kbd_bit));
memset(joy_bit,0,sizeof(joy_bit));
}
#define session_kbdreset() memset(kbd_map,~~~0,sizeof(kbd_map)) // init and clean key map up
void session_kbdsetup(const unsigned char *s,char l) // maps a series of virtual keys to the real ones
{
session_kbdclear();
while (l--)
{
int k=*s++;
kbd_map[k]=*s++;
}
}
int session_key_n_joy(int k) // handle some keys as joystick motions
{
if (session_key2joy)
for (int i=0;i<KBD_JOY_UNIQUE;++i)
if (kbd_k2j[i]==k)
return kbd_joy[i];
return kbd_map[k];
}
void *session_joy=NULL; int session_pad; // SDL_Joystick + SDL_GameController
SDL_Window *session_hwnd=NULL;
// unlike Windows, where the user interface is internally provided by the system and as well as the compositing work,
// we must provide our own UI here, and this means we must use one canvas for the emulation and another one for the UI.
// we show the first canvas during normal operation, but switch to the second one when the UI is active.
// the graphical debugger effectively behaves as a temporary substitute of the emulation canvas.
// notice that SDL_Texture, unlike SDL_Surface, doesn't rely on anything like SDL_GetWindowSurface()
VIDEO_UNIT *debug_frame;
BYTE debug_buffer[DEBUG_LENGTH_X*DEBUG_LENGTH_Y]; // [0] can be a valid character, 128 (new redraw required) or 0 (redraw not required)
SDL_Texture *session_dbg=NULL;
#define session_hidemenu *debug_buffer // dummy, useless on SDL2
SDL_Texture *session_dib=NULL,*session_gui_dib=NULL; SDL_Renderer *session_blitter=NULL;
SDL_Rect session_ideal; // used for calculations, see below
void session_backupvideo(VIDEO_UNIT *t); // make a clipped copy of the current screen. Must be defined later on!
int session_r_x,session_r_y,session_r_w,session_r_h; // actual location and size of the bitmap
void session_redraw(int q) // redraw main canvas (!0) or user interface (0)
{
SDL_GetRendererOutputSize(session_blitter,&session_ideal.w,&session_ideal.h);
if ((session_r_w=session_ideal.w)>0&&(session_r_h=session_ideal.h)>0) // don't redraw invalid windows!
{
if (session_r_w>session_r_h*VIDEO_PIXELS_X/VIDEO_PIXELS_Y) // window area is too wide?
session_r_w=session_r_h*VIDEO_PIXELS_X/VIDEO_PIXELS_Y;
if (session_r_h>session_r_w*VIDEO_PIXELS_Y/VIDEO_PIXELS_X) // window area is too tall?
session_r_h=session_r_w*VIDEO_PIXELS_Y/VIDEO_PIXELS_X;
if (session_intzoom) // integer zoom? (100%, 150%, 200%, 250%, 300%...)
session_r_w=((session_r_w*17)/VIDEO_PIXELS_X/8)*VIDEO_PIXELS_X/2,
session_r_h=((session_r_h*17)/VIDEO_PIXELS_Y/8)*VIDEO_PIXELS_Y/2;
if (session_r_w<VIDEO_PIXELS_X||session_r_h<VIDEO_PIXELS_Y)
session_r_w=VIDEO_PIXELS_X,session_r_h=VIDEO_PIXELS_Y; // window area is too small!
session_ideal.x=session_r_x=(session_ideal.w-session_r_w)/2; session_ideal.w=session_r_w;
session_ideal.y=session_r_y=(session_ideal.h-session_r_h)/2; session_ideal.h=session_r_h;
SDL_Texture *s; VIDEO_UNIT *t; int ox,oy;
if (!q)
s=session_gui_dib,ox=0,oy=0;
else if (session_signal&SESSION_SIGNAL_DEBUG)
s=session_dbg,ox=0,oy=0;
else
s=session_dib,ox=VIDEO_OFFSET_X,oy=VIDEO_OFFSET_Y;
SDL_Rect r;
r.x=ox; r.w=VIDEO_PIXELS_X;
r.y=oy; r.h=VIDEO_PIXELS_Y;
SDL_UnlockTexture(s); // prepare for sending
if (SDL_RenderCopy(session_blitter,s,&r,&session_ideal)>=0) // send! (warning: this operation has a memory leak on several SDL2 versions)
SDL_RenderPresent(session_blitter); // update window!
int dummy; SDL_LockTexture(s,NULL,(void**)&t,&dummy); // allow editing again
if (!q)
menus_frame=t;
else if (session_signal&SESSION_SIGNAL_DEBUG)
debug_frame=t;
else
{
dummy=video_target-video_frame; // the old cursor...
video_frame=t;
video_target=video_frame+dummy; // ...may need to move!
}
}
}
#ifdef __TINYC__
#define session_clrscr() 0 // TCC causes a segmentation fault (!?)
#else
#define session_clrscr() SDL_RenderClear(session_blitter) // defaults to black
#endif
int session_fullscreen=0;
void session_togglefullscreen(void)
{
SDL_SetWindowFullscreen(session_hwnd,session_fullscreen=((SDL_GetWindowFlags(session_hwnd)&SDL_WINDOW_FULLSCREEN_DESKTOP)?0:SDL_WINDOW_FULLSCREEN_DESKTOP));
session_clrscr(); // SDL2 cleans up, but not on all systems
session_dirtymenu=1; // update "Full screen" option (if any)
}
#ifdef SDL2_UTF8
// SDL2 relies on the UTF-8 encoding for character and string manipulation; hence the following functions. //
// Similarly, non-Windows systems are going to encode filenames with UTF-8, instead of sticking to 8 bits. //
// Valid codes are those within the 0..2097151 range; technically 0..1114111 but we miss the binary logic. //
// The logic is very overengineered anyway: the built-in font is limited to the 8-bit ISO-8859-1 codepage. //
void utf8put(char **s,int i) // send a valid code `i` to a UTF-8 pointer `s`
{
if (i&-2097152) ; else if (i<128) *((*s)++)=i; else
{ if (i<2048) *((*s)++)=(i>>6)-64; else
{ if (i<65536) *((*s)++)=(i>>12)-32; else
*((*s)++)=(i>>18)-16,*((*s)++)=((i>>12)&63)-128;
*((*s)++)=((i>>6)&63)-128; }
*((*s)++)=(i&63)-128; }
}
int utf8get(char **s) // get a code `i` from a valid UTF-8 pointer `s`; <0 ERROR
{
int i=*((*s)++); if (i>=0) return i; if (i<-64) return -1;
int j=0; char k; while ((k=**s)<-64) j=j*64+k+128,(*s)++;
if (i<-32) return ((i=((i+64)<<6)+j)>=128&&i<2048)?i:-1;
if (i<-16) return ((i=((i+32)<<12)+j)>=2048&&i<65536)?i:-1;
return ((i=((i+16)<<18)+j)>=65536&&i<2097152)?i:-1;
}
int utf8len(char *s) // length (in codes, not bytes) of a UTF-8 string `s`
{ int i=0,k; while (k=*s++) if (k>-64) ++i; return i; }
int utf8chk(int i) // size in bytes of a valid UTF-8 code `i`; 0 ERROR
{ return i&-2097152?0:i<128?1:i<2048?2:i<65536?3:4; }
int utf8add(char *s,int i) // get offset `i` within a valid UTF-8 pointer `s`
{
char *r=s; if (i>0) do { while (*++s<-64) ; } while (--i);
else if (i<0) do { while (*--s<-64) ; } while (++i); return s-r;
}
#define utf8tst(s) (((char)*s)>=-64)
#else
#define utf8put(s,i) (*((*(s))++)=i)
#define utf8get(s) (*((*(s))++))
#define utf8len(s) strlen((s))
#define utf8chk(i) (1)
#define utf8add(s,i) (i)
#define utf8tst(s) (1)
#endif
// extremely tiny graphical user interface: SDL2 provides no widgets! //
#define SESSION_UI_HEIGHT 14
BYTE session_ui_chrs[ONSCREEN_CEIL*SESSION_UI_HEIGHT];//,session_ui_chrlen[256];
void session_ui_makechrs(void)
{
memset(session_ui_chrs,0,sizeof(session_ui_chrs));
for (int i=0;i<ONSCREEN_CEIL;++i)
for (int j=0;j<ONSCREEN_SIZE;++j)
{
int z=onscreen_chrs[i*ONSCREEN_SIZE+j];
session_ui_chrs[i*SESSION_UI_HEIGHT+(SESSION_UI_HEIGHT-ONSCREEN_SIZE)/2+j]=z|(z>>1); // normal, not thin or bold
}
/*{ // experimental monospace-to-proportional font rendering
int bits=0; for (int j=0;j<SESSION_UI_HEIGHT;++j)
bits|=session_ui_chrs[i*SESSION_UI_HEIGHT+j];
if (bits)
{
int k=0,l=8+1; // extra space after char
while (bits<128)
++k,bits<<=1;
while (!(bits&1))
--l,bits>>=1;
session_ui_chrlen[i]=l;
for (int j=0;j<SESSION_UI_HEIGHT;++j)
l=session_ui_chrs[i*SESSION_UI_HEIGHT+j]<<k;
}
}
session_ui_chrlen[0]=session_ui_chrlen[1];*/ // space is as wide as "!"
}
/*int session_ui_strlen(char *s) // get proportional string length in pixels
{ int i=0; while (*s) i+=session_ui_chrlen[*s++-32]; return i; }*/
unsigned char session_ui_menudata[1<<12],session_ui_menusize; // encoded menu data
#ifdef _WIN32
int session_ui_drives=0; char session_ui_drive[]="::\\";
#else
SDL_Surface *session_ui_icon=NULL; // the window icon
#endif
void session_fillrect(int rx,int ry,int rw,int rh,VIDEO_UNIT a) // n.b.: coords in pixels
{
for (VIDEO_UNIT *p=&menus_frame[ry*VIDEO_PIXELS_X+rx];rh>0;--rh,p+=VIDEO_PIXELS_X-rw)
for (int x=rw;x>0;--x)
*p++=a;
}
#define session_ui_fillrect(x,y,w,h,a) session_fillrect((x)*8,(y)*SESSION_UI_HEIGHT,(w)*8,(h)*SESSION_UI_HEIGHT,(a)) // coords in characters
int session_ui_skew=0; // vertical skew for frames and glyphs
void session_ui_drawframes(int x,int y,int w,int h) // coords in characters
{
x*=8; w*=8; y=y*SESSION_UI_HEIGHT+session_ui_skew; h*=SESSION_UI_HEIGHT;
int rx,ry,rw,rh;
// top border
rx=x-2; ry=y-2;
rw=w+4; rh=2; session_fillrect(rx,ry,rw,rh,0x00C0C0C0);
// bottom border
ry=y+h; session_fillrect(rx,ry,rw,rh,0x00404040);
// left border
ry=y-1;
rw=2; rh=h+2; session_fillrect(rx,ry,rw,rh,0x00808080);
// right border
rx=x+w; session_fillrect(rx,ry,rw,rh,0x00808080);
}
int session_ui_printglyph(VIDEO_UNIT *p,int z,int q)
{
const int w=8; //=session_ui_chrlen[z];
if (z<1||z>=ONSCREEN_CEIL) z=31; // :-/
{
q=q?-1:0;
unsigned char const *r=&session_ui_chrs[z*SESSION_UI_HEIGHT];
for (int yy=0;yy<SESSION_UI_HEIGHT;++yy)
{
int rr=q^*r++;
for (int xx=0;xx<w;++xx)
*p++=(rr&(128>>xx))?0:0xFFFFFF;
p+=VIDEO_PIXELS_X-w;
}
}
return w; // pixel width
}
int session_ui_printasciz(unsigned char *s,int x,int y,int prae,int w,int post,int q1,int q2) // coords in characters
{
if (w<0) w=utf8len(s); if ((q1|q2)<0) q1=q2=-1; // default values
int n=prae+w+post; // remember for later
VIDEO_UNIT *t=&menus_frame[x*8+(y*SESSION_UI_HEIGHT+session_ui_skew)*VIDEO_PIXELS_X];
while (prae-->0)
t+=session_ui_printglyph(t,' ',q1<0);
int i=w-utf8len(s),q=q1<0; unsigned char *r=s;
if (i>=0)
{
post+=i;
do
{
if (s-r==q1) q=1; if (s-r==q2) q=0;
if (i=(utf8get(&s))) t+=session_ui_printglyph(t,i,q);
}
while (i);
}
else
{
w-=2; // ellipsis, see below
while (w-->0)
{
if (s-r==q1) q=1; if (s-r==q2) q=0;
t+=session_ui_printglyph(t,utf8get(&s),q);
}
t+=session_ui_printglyph(t,127,q); // ellipsis,
t+=session_ui_printglyph(t,'.',q); // see above
}
while (post-->0)
t+=session_ui_printglyph(t,' ',q1<0);
return n;
}
int session_ui_base_x,session_ui_base_y,session_ui_size_x,session_ui_size_y; // used to calculate mouse clicks relative to widget
int session_ui_maus_x,session_ui_maus_y; // mouse X+Y, when the "key" is -1 (move), -2 (left click) or -3 (right click)
int session_ui_char,session_ui_shift,session_ui_focusing=0; // ASCII+Shift of the latest keystroke, and focus flag
#define SESSION_UI_MAXX (VIDEO_PIXELS_X/8-6)
#define SESSION_UI_MAXY (VIDEO_PIXELS_Y/SESSION_UI_HEIGHT-2)
int session_ui_exchange(void) // wait for a keystroke or a mouse motion
{
session_ui_char=0; for (SDL_Event event;SDL_WaitEvent(&event);)
switch (event.type)
{
case SDL_WINDOWEVENT:
if (event.window.event==SDL_WINDOWEVENT_FOCUS_LOST&&session_ui_focusing)
return KBCODE_ESCAPE;
if (event.window.event==SDL_WINDOWEVENT_EXPOSED)
SDL_RenderPresent(session_blitter);//SDL_UpdateWindowSurface(session_hwnd); // fast redraw
break;
case SDL_MOUSEWHEEL:
if (event.wheel.direction==SDL_MOUSEWHEEL_FLIPPED) event.wheel.y=-event.wheel.y;
if (event.wheel.y<0) return KBCODE_NEXT;
if (event.wheel.y) return KBCODE_PRIOR;
break;
case SDL_MOUSEBUTTONUP: // better than SDL_MOUSEBUTTONDOWN
case SDL_MOUSEMOTION:
session_ui_maus_x=(event.button.x-session_ideal.x)*VIDEO_PIXELS_X/session_ideal.w/8-session_ui_base_x,session_ui_maus_y=(event.button.y-session_ideal.y)*VIDEO_PIXELS_Y/session_ideal.h/SESSION_UI_HEIGHT-session_ui_base_y;
return event.type==SDL_MOUSEBUTTONUP?event.button.button==SDL_BUTTON_RIGHT?-3:-2:-1;
case SDL_KEYDOWN:
session_ui_shift=!!(event.key.keysym.mod&KMOD_SHIFT);
return event.key.keysym.mod&KMOD_ALT?0:event.key.keysym.scancode;
case SDL_TEXTINPUT: // always follows SDL_KEYDOWN
if ((session_ui_char=event.text.text[0])&128) // UTF-8? (not that it matters, we stick to ASCII)
session_ui_char=128+(session_ui_char&1)*64+(event.text.text[1]&63);
return 0;
case SDL_QUIT:
return KBCODE_ESCAPE;
}
return 0; // can this ever happen?
}
void session_ui_loop(void) // get background painted again to erase old widgets
{
if (session_signal&SESSION_SIGNAL_DEBUG)
memcpy(menus_frame,debug_frame,sizeof(VIDEO_UNIT)*VIDEO_PIXELS_X*VIDEO_PIXELS_Y); // use the debugger as the background, rather than the emulation
else
session_backupvideo(menus_frame);
}
void session_ui_init(void) { session_kbdclear(); session_please(); session_ui_loop(); }
void session_ui_exit(void) // wipe all widgets and restore the window contents
{
if (session_signal&(SESSION_SIGNAL_DEBUG|SESSION_SIGNAL_PAUSE)) // redraw if waiting
session_redraw(1);
}
void session_ui_menu(void) // show the menu and set session_event accordingly
{
if (!session_ui_menusize)
return;
session_ui_init();
int menuxs[SESSION_UI_MAXY],events[SESSION_UI_MAXY]; // the limit is the canvas height
int menu=0,menus=0,menuz=-1,item=0,items=0,itemz=-1,itemx=0,itemw=0;
char *zz=NULL,*z=NULL; // `zz` points to the current submenu's first item, `z` points to the current item
int done,ox=session_ui_base_x=1,oy=session_ui_base_y=1,q=1;
session_event=0x8000; do
{
done=0; do // redraw menus and items as required, then obey the user
{
if (menuz!=menu)
{
menuz=menu; q=itemz=-1;
int menux=menus=item=items=0; unsigned char *m=session_ui_menudata;
session_ui_skew=-4; // extra space for the bottom and top borders (2px each)
while (*m) // scan menu data for menus
{
if (menus==menu)
{
itemx=menux;
menux+=session_ui_printasciz(m,ox+menux,oy+0,1,-1,1,0,-1);
}
else
menux+=session_ui_printasciz(m,ox+menux,oy+0,1,-1,1,0,+0);
int i=0,j=0,k=0;
while (*m++) // skip menu name
++j;
int l; while (l=*m++,l|=*m++,l) // skip menu items
{
++k; i=0;
while (*m++) // skip item name
++i;
if (j<i)
j=i;
}
if (menus==menu)
itemw=j,items=k;
menuxs[menus++]=menux;
}
session_ui_drawframes(ox,oy,menux,1);
session_ui_skew=0;
}
if (itemz!=item)
{
itemz=item;
q=1; int i=0;
unsigned char *m=session_ui_menudata;
while (*m) // scan menu data for items
{
while (*m++) // skip menu name
;
int j,k=0;
if (i==menu)
zz=m;
while (j=*m++<<8,j+=*m++,j)
{
if (i==menu)
{
if (item==k)
z=&m[-2],session_event=j;
session_ui_printasciz(m,itemx+ox+0,1+oy+k,1,itemw,1,0,item==k?-1:+0);
events[k++]=j;
}
while (*m++) // skip item name
;
}
++i;
}
session_ui_drawframes(itemx+ox,1+oy,itemw+2,items);
}
if (q)
session_redraw(q=0);
//session_ui_focusing=1; // not too useful, enable it if you wish
switch (session_ui_exchange())
{
case KBCODE_UP:
if (--item<0)
case KBCODE_END:
item=items-1;
break;
case KBCODE_DOWN:
if (++item>=items)
case KBCODE_HOME:
item=0;
break;
case KBCODE_PRIOR:
case KBCODE_LEFT:
if (--menu<0)
menu=menus-1;
break;
case KBCODE_NEXT:
case KBCODE_RIGHT:
if (++menu>=menus)
menu=0;
break;
case KBCODE_ESCAPE:
case KBCODE_F10:
done=-1;
break;
case KBCODE_X_ENTER:
case KBCODE_ENTER:
//if (session_event!=0x8000) // gap?
done=1;
break;
case -1: // mouse move
if (session_ui_maus_y<=0&&session_ui_maus_x>=0&&session_ui_maus_x<session_ui_menusize) // select menu?
{
menu=menus; while (menu>0&&session_ui_maus_x<menuxs[menu-1]) --menu;
}
else if (session_ui_maus_y>0&&session_ui_maus_y<=items&&session_ui_maus_x>=itemx&&session_ui_maus_x<itemx+itemw+2) // select item?
item=session_ui_maus_y-1; // hover, not click!
break;
case -3: // mouse right click
case -2: // mouse left click
if (session_ui_maus_y<=0&&session_ui_maus_x>=0&&session_ui_maus_x<session_ui_menusize) // select menu?
; // already done above
else if (session_ui_maus_y>0&&session_ui_maus_y<=items&&session_ui_maus_x>=itemx&&session_ui_maus_x<itemx+itemw+2) // select item?
session_event=events[session_ui_maus_y-1],done=1;
else // quit?
session_event=0,done=1;
break;
default:
if (session_ui_char>=32)
for (int o=lcase(session_ui_char),n=items;n;--n)
{
++z,++z; // skip ID
while (*z++)
;
if (++item>=items)
item=0,z=zz;
if (lcase(z[4])==o)
break;
}
break;
}
session_ui_focusing=0;
if (menuz!=menu)
session_ui_loop(); // redrawing the items doesn't need any wiping, unlike the menus
}
until (done);
}
while (session_event==0x8000&&done>0); // empty menu items must be ignored, unless we're quitting
session_ui_exit();
if (done<0)
session_shift=session_event=0; // quit!
else
session_shift=!!(session_event&0x4000),session_event&=0xBFFF;
return;
}
int session_ui_text(char *s,char *t,char q) // see session_message
{
if (!s||!t)
return -1;
session_ui_init();
int i,j,textw=0,texth=1+2; // caption + blank + text + blank
unsigned char *m=t;
while (*m++)
++textw;
i=0;
m=s;
while (*m) // get full text width
{
if (*m=='\n')
{
++texth;
if (textw<i)
textw=i;
i=0;
}
else if (*m=='\t')
i=(i|7)+1;
else
++i;
++m;
}
if (textw<i)
textw=i;
if (textw>SESSION_UI_MAXX)
textw=SESSION_UI_MAXX;
if (i)
++texth;
if (q)
textw+=q=6; // 6: four characters for the icon, plus one extra char per side
textw+=2; // include left+right margins
int textx=((VIDEO_PIXELS_X/8)-textw)/2,texty=((VIDEO_PIXELS_Y/SESSION_UI_HEIGHT)-texth)/2;
session_ui_drawframes(session_ui_base_x=textx,session_ui_base_y=texty,session_ui_size_x=textw,session_ui_size_y=texth);
textw-=2;
session_ui_printasciz(t,textx+0,texty+0,1,textw,1,0,-1);
i=j=0;
m=session_parmtr;
session_ui_printasciz("",textx+q,texty+(++i),1,textw-q,1,0,+0); // blank
while (*s) // render text proper
{
int k=(*m++=*s++);
++j;
if (k=='\n')
{
m[-1]=j=0;
session_ui_printasciz(m=session_parmtr,textx+q,texty+(++i),1,textw-q,1,0,+0);
}
else if (k=='\t')
{
m[-1]=' ';
while (j&7)
++j,*m++=' ';
}
}
if (m!=session_parmtr) // last line lacks a line feed?
*m=0,session_ui_printasciz(m=session_parmtr,textx+q,texty+(++i),1,textw-q,1,0,+0);
session_ui_printasciz("",textx+q,texty+(++i),1,textw-q,1,0,+0); // blank
if (q) // draw icon?
{
session_ui_fillrect(textx,texty+1,q,texth-1,0x00C0C0C0);
//for (int z=0;z<q;++z) session_ui_fillrect(textx+z,texty+1,1,texth-1,0x00010101*((0xFF*z+0xC0*(q-z)+q/2)/q)); // gradient!
VIDEO_UNIT *tgt=&menus_frame[(texty+2)*SESSION_UI_HEIGHT*VIDEO_PIXELS_X+(textx+1)*8];
for (int z=0,y=0;y<32;++y,tgt+=VIDEO_PIXELS_X-32)
for (int a,x=0;x<32;++x,++tgt)
if ((a=session_icon32xx16[z++])&0x8000) // crude alpha channel
*tgt=(a&0x00F)*0x11+(a&0x0F0)*0x110+(a&0xF00)*0x1100; // from 0X0RGB to 0X00RRGGBB
}
session_redraw(0);
for (;;)
switch (session_ui_exchange())
{
case -3: // mouse right click
case -2: // mouse left click
//if (session_ui_maus_x>=0&&session_ui_maus_x<session_ui_size_x&&session_ui_maus_y>=0&&session_ui_maus_y<session_ui_size_y) break; // click anywhere, it doesn't matter
case KBCODE_ESCAPE:
case KBCODE_SPACE:
case KBCODE_X_ENTER:
case KBCODE_ENTER:
session_ui_exit();
return 0;
}
}
int session_ui_input(char *s,char *t) // see session_input
{
int i=0,j=strlen(s),q,dirty=1,textw=SESSION_UI_MAXX;
if ((q=utf8len(s))>=textw)
return -1; // error!
strcpy(session_substr,s);
session_ui_init();
textw+=2; // include left+right margins
int textx=((VIDEO_PIXELS_X/8)-textw)/2,texty=((VIDEO_PIXELS_Y/SESSION_UI_HEIGHT)-2)/2;
session_ui_drawframes(session_ui_base_x=textx,session_ui_base_y=texty,session_ui_size_x=textw,session_ui_size_y=2);
textw-=2;
session_ui_printasciz(t,textx+0,texty+0,1,textw,1,0,-1);
int done=0; do
{
if (dirty)
{
if (q)
session_ui_printasciz(session_substr,textx+0,texty+1,1,textw,1,0,j); // the whole string is selected
else if (i<j)
session_ui_printasciz(session_substr,textx+0,texty+1,1,textw,1,i,i+utf8add(&session_substr[i],+1)); // the cursor is an inverse char
else
{
session_substr[i]=' '; session_substr[i+1]=0; // the cursor is actually an inverse space at the end
session_ui_printasciz(session_substr,textx+0,texty+1,1,textw,1,i,i+1);
session_substr[i]=0; // end of string
}
session_redraw(0);
dirty=0;
}
switch (dirty=session_ui_exchange())
{
case KBCODE_LEFT:
if (q||(i+=utf8add(&session_substr[i],-1))<0)
case KBCODE_HOME:
i=0;
q=0;
break;
case KBCODE_RIGHT:
if (q||(i+=utf8add(&session_substr[i],+1))>j)
case KBCODE_END:
i=j;
q=0;
break;
case -3: // mouse right click
case -2: // mouse left click
if (session_ui_maus_x<0||session_ui_maus_x>=session_ui_size_x||session_ui_maus_y<0||session_ui_maus_y>=session_ui_size_y)
j=-1,done=1; // quit!
else if (session_ui_maus_y==1)
{
q=0; if ((i=utf8add(session_substr,session_ui_maus_x-1))>j)
i=j;
else if (i<0)
i=0;
}
break;
case KBCODE_ESCAPE:
j=-1;
//break;
case KBCODE_X_ENTER:
case KBCODE_ENTER:
done=1;
break;
case KBCODE_BKSPACE:
case KBCODE_DELETE:
if (q) // erase all?
*session_substr=i=j=q=0;
else if (dirty==KBCODE_BKSPACE?i>0:i<j)
{
int o; if (dirty==KBCODE_BKSPACE) { o=i; i+=utf8add(&session_substr[i],-1); }
else { unsigned char *z=&session_substr[i]; utf8get(&z); o=z-session_substr; }
memmove(&session_substr[i],&session_substr[o],j-i+1); j+=i-o;
}
break;
default:
if (dirty=(session_ui_char>=32&&session_ui_char<ONSCREEN_CEIL))
{
int o=utf8chk(session_ui_char); if (q)
{
char *z=session_substr; utf8put(&z,session_ui_char);
*z=q=0; i=j=o;
}
#ifdef SDL2_UTF8
else if (dirty=(utf8len(session_substr)<textw-1))
#else
else if (dirty=(j<textw-1))
#endif
{
memmove(&session_substr[i+o],&session_substr[i],j-i+1);
char *z=&session_substr[i]; utf8put(&z,session_ui_char);
i+=o,j+=o;
}
}
break;
}
}
until (done);
if (j>=0)
strcpy(s,session_substr);
session_ui_exit();
return j;
}
int session_ui_list(int item,char *s,char *t,void x(void),int q) // see session_list
{
int i,dirty=1,dblclk=0,items=0,itemz=-2,listw=0,listh,listz=0;
char *m=t,*z=NULL;
while (*m++)
++listw;
m=s;
while (*m)
{
++items; i=0;
while (*m++)
++i;
if (listw<i)
listw=i;
}
if (!items)
return -1;
session_ui_init();
if (listw>SESSION_UI_MAXX)
listw=SESSION_UI_MAXX;
if ((listh=items+1)>SESSION_UI_MAXY)
listh=SESSION_UI_MAXY;
int listx=((VIDEO_PIXELS_X/8)-listw-2)/2,listy=((VIDEO_PIXELS_Y/SESSION_UI_HEIGHT)-listh)/2;
if (items>=listh) // list is long enough?
if (item>=listh/2) // go to center?
if ((listz=item-listh/2+1)>items-listh) // too deep?
listz=items-listh+1;
int done=0; do
{
if (dirty) // showing x() must request a redraw!
{
session_ui_drawframes(session_ui_base_x=listx,session_ui_base_y=listy,session_ui_size_x=listw+2,session_ui_size_y=listh);
session_ui_printasciz(t,listx+0,listy+0,1,listw,1,0,-1);
dirty=0; itemz=~item;
}
if (itemz!=item)
{
itemz=item;
if (listz>item)
if ((listz=item)<0)
listz=0; // allow index -1
if (listz<item-listh+2)
listz=item-listh+2;
m=s; i=0;
while (*m&&i<listz+listh-1)
{
if (i>=listz)
{
if (i==item)
z=m;
session_ui_printasciz(m,listx+0,listy+1+i-listz,1,listw,1,0,i==item?-1:0);
}
if (*m) while (*m++)
;
++i;
}
session_redraw(0);
}
switch (session_ui_exchange())
{
case KBCODE_PRIOR:
case KBCODE_LEFT:
item-=listh-2;
case KBCODE_UP:
if (--item<0)
case KBCODE_HOME:
item=0;
dblclk=0;
break;
case KBCODE_NEXT:
case KBCODE_RIGHT:
item+=listh-2;
case KBCODE_DOWN:
if (++item>=items)
case KBCODE_END:
item=items-1;
dblclk=0;
break;
case KBCODE_TAB:
if (q) // browse items if `q` is true
{
if (session_ui_shift)
{
if (--item<0) // prev?
item=items-1; // wrap
}
else
{
if (++item>=items) // next?
item=0; // wrap
}
}
else if (x) // special context widget
x(),dblclk=0,dirty=1;
break;
case -1: // mouse move
if (q&&session_ui_maus_x>=0&&session_ui_maus_x<session_ui_size_x&&session_ui_maus_y>0&&session_ui_maus_y<=items) // single click mode?