1 /*
2 * sf_video.c - SDL interface, graphics functions
3 *
4 * This file is part of Frotz.
5 *
6 * Frotz is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * Frotz is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 * Or visit http://www.fsf.org/
20 */
21
22 #include "sf_frotz.h"
23
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27
28 #include <SDL.h>
29
30 #include "generic.h"
31
32 static char banner[256];
33 static int isfullscreen;
34 static ulong *sbuffer = NULL;
35 static int sbpitch; /* in longs */
36 static int dirty = 0;
37 static int ewidth, eheight;
38 static SDL_Window *window = NULL;
39 static SDL_Renderer *renderer = NULL;
40 static SDL_Texture *texture = NULL;
41 int m_timerinterval = 100;
42
43 extern bool sdl_active;
44
45 static void sf_quitconf();
46
47 static bool ApplyPalette(sf_picture *);
48 static ulong screen_palette[16];
49
50 extern z_header_t z_header;
51
52 /* clipping region */
53 static int xmin, xmax, ymin, ymax;
54
sf_setclip(int x,int y,int w,int h)55 void sf_setclip(int x, int y, int w, int h)
56 {
57 if (x < 0) {
58 w += x;
59 x = 0;
60 }
61 if (x + w > ewidth)
62 w = ewidth - x;
63 if (y < 0) {
64 h += y;
65 y = 0;
66 }
67 if (y + h > eheight)
68 h = eheight - y;
69 xmin = x;
70 xmax = x + w;
71 ymin = y;
72 ymax = y + h;
73 }
74
75
sf_getclip(int * x,int * y,int * w,int * h)76 void sf_getclip(int *x, int *y, int *w, int *h)
77 {
78 *x = xmin;
79 *y = ymin;
80 *w = xmax - xmin;
81 *h = ymax - ymin;
82 }
83
84
mywcslen(zchar * b)85 static int mywcslen(zchar * b)
86 {
87 int n = 0;
88 while (*b++)
89 n++;
90 return n;
91 }
92
93
myGrefresh()94 static void myGrefresh()
95 {
96 SDL_RenderClear(renderer);
97 SDL_RenderCopy(renderer, texture, NULL, NULL);
98 SDL_RenderPresent(renderer);
99 }
100
101
sf_wpixel(int x,int y,ulong c)102 void sf_wpixel(int x, int y, ulong c)
103 {
104 if (x < xmin || x >= xmax || y < ymin || y >= ymax)
105 return;
106 sbuffer[x + sbpitch * y] = c;
107 dirty = 1;
108 }
109
110
sf_rpixel(int x,int y)111 ulong sf_rpixel(int x, int y)
112 {
113 if (x < 0 || x >= ewidth || y < 0 || y >= eheight)
114 return 0;
115 return sbuffer[x + sbpitch * y];
116 }
117
118 #define MAXCUR 64
119 static ulong savedcur[MAXCUR];
120
drawthecursor(int x,int y,int onoff)121 static void drawthecursor(int x, int y, int onoff)
122 {
123 SF_textsetting *ts = sf_curtextsetting();
124 int i, h = ts->font->height(ts->font);
125 if (h > MAXCUR)
126 h = MAXCUR;
127 if (onoff) {
128 for (i = 0; i < h; i++) {
129 savedcur[i] = sf_rpixel(x, y + i);
130 sf_wpixel(x, y + i, ts->fore);
131 }
132 } else {
133 for (i = 0; i < h; i++) {
134 sf_wpixel(x, y + i, savedcur[i]);
135 }
136 }
137 }
138
139
sf_IsValidChar(unsigned short c)140 bool sf_IsValidChar(unsigned short c)
141 {
142 if (c >= ZC_ASCII_MIN && c <= ZC_ASCII_MAX)
143 return true;
144 if (c >= ZC_LATIN1_MIN && c <= ZC_LATIN1_MAX)
145 return true;
146 if (c >= 0x100)
147 return true;
148 return false;
149 }
150
151
sf_drawcursor(bool c)152 void sf_drawcursor(bool c)
153 {
154 SF_textsetting *ts = sf_curtextsetting();
155 drawthecursor(ts->cx, ts->cy, c);
156 }
157
158
sf_chline(int x,int y,ulong c,int n)159 void sf_chline(int x, int y, ulong c, int n)
160 {
161 ulong *s;
162 if (y < ymin || y >= ymax)
163 return;
164 if (x < xmin) {
165 n += x - xmin;
166 x = xmin;
167 }
168 if (x + n > xmax)
169 n = xmax - x;
170 if (n <= 0)
171 return;
172 s = sbuffer + x + sbpitch * y;
173 while (n--)
174 *s++ = c;
175 dirty = 1;
176 }
177
178
sf_cvline(int x,int y,ulong c,int n)179 void sf_cvline(int x, int y, ulong c, int n)
180 {
181 ulong *s;
182 if (x < xmin || x >= xmax)
183 return;
184 if (y < xmin) {
185 n += y - ymin;
186 y = ymin;
187 }
188 if (y + n > ymax)
189 n = ymax - y;
190 if (n <= 0)
191 return;
192 s = sbuffer + x + sbpitch * y;
193 while (n--) {
194 *s = c;
195 s += sbpitch;
196 }
197 dirty = 1;
198 }
199
200
sf_blendlinear(int a,ulong s,ulong d)201 ulong sf_blendlinear(int a, ulong s, ulong d)
202 {
203 ulong r;
204 r = ((s & 0xff) * a + (d & 0xff) * (256 - a)) >> 8;
205 s >>= 8;
206 d >>= 8;
207 r |= (((s & 0xff) * a + (d & 0xff) * (256 - a)) >> 8) << 8;
208 s >>= 8;
209 d >>= 8;
210 r |= (((s & 0xff) * a + (d & 0xff) * (256 - a)) >> 8) << 16;
211 return r;
212 }
213
214
sf_writeglyph(SF_glyph * g)215 void sf_writeglyph(SF_glyph * g)
216 {
217 SF_textsetting *ts = sf_curtextsetting();
218
219 int i, j, m;
220
221 int w = g->dx;
222 int weff = g->xof + g->w;
223 byte *bmp = (byte *) (&(g->bitmap[0]));
224 int h = g->h;
225 int nby = (g->w + 7) / 8;
226 int byw = g->w;
227
228 int x = ts->cx;
229 int y = ts->cy;
230
231 int dxpre = g->xof;
232 int dypre = ts->font->ascent(ts->font) - h - (int)g->yof;
233
234 int height = ts->font->height(ts->font);
235 int width;
236
237 ulong color, bc;
238
239 if ((ts->style & REVERSE_STYLE) != 0) {
240 bc = ts->fore;
241 color = ts->back;
242 } else {
243 color = ts->fore;
244 bc = ts->back;
245 }
246
247 /* compute size and position of background rect */
248
249 if (weff < w)
250 weff = w;
251 width = weff - ts->oh;
252 if ((width > 0) && (ts->backTransparent == 0))
253 sf_fillrect(bc, x + ts->oh, y, width, height);
254
255 x += dxpre;
256 y += dypre;
257
258 for (i = 0; i < h; i++) {
259 int xx = 0;
260 if (ts->font->antialiased) {
261 for (m = 0; m < byw; m++) {
262 int t = *bmp++;
263 if (xx < byw) {
264 if (t) {
265 ulong sval = color;
266 if (t < 255)
267 sval = sf_blend((int) (t + (t >> 7)), sval, sf_rpixel(x + xx, y));
268 sf_wpixel(x + xx, y, sval);
269 }
270 }
271 xx++;
272 }
273 } else {
274 for (m = 0; m < nby; m++) {
275 int t = *bmp++;
276 for (j = 0; j < 8; j++, t *= 2) {
277 if (xx < byw) {
278 if (t & 0x80)
279 sf_wpixel(x + xx, y, color);
280 }
281 xx++;
282 }
283 }
284 }
285 y++;
286 }
287
288 ts->cx += (w);
289 ts->oh = (weff > w) ? weff - w : 0;
290 }
291
292
sf_fillrect(unsigned long color,int x,int y,int w,int h)293 void sf_fillrect(unsigned long color, int x, int y, int w, int h)
294 {
295 ulong *dst;
296 int i;
297 if (x < xmin) {
298 w += x - xmin;
299 x = xmin;
300 }
301 if (x + w > xmax)
302 w = xmax - x;
303 if (w <= 0)
304 return;
305 if (y < ymin) {
306 h += y - ymin;
307 y = ymin;
308 }
309 if (y + h > ymax)
310 h = ymax - y;
311 if (h <= 0)
312 return;
313 dst = sbuffer + x + sbpitch * y;
314 while (h--) {
315 for (i = 0; i < w; i++)
316 dst[i] = color;
317 dst += sbpitch;
318 }
319 dirty = 1;
320 }
321
322
sf_rect(unsigned long color,int x,int y,int w,int h)323 void sf_rect(unsigned long color, int x, int y, int w, int h)
324 {
325 sf_chline(x, y, color, w);
326 sf_chline(x, y + h - 1, color, w);
327 sf_cvline(x, y, color, h);
328 sf_cvline(x + w - 1, y, color, h);
329 }
330
331
sf_flushtext()332 void sf_flushtext()
333 {
334 SF_textsetting *ts = sf_curtextsetting();
335 ts->cx += ts->oh;
336 ts->oh = 0;
337 }
338
339
340 /*
341 * os_erase_area
342 *
343 * Fill a rectangular area of the screen with the current background
344 * colour. Top left coordinates are (1,1). The cursor does not move.
345 *
346 * The final argument gives the window being changed, -1 if only a
347 * portion of a window is being erased, or -2 if the whole screen is
348 * being erased.
349 *
350 */
os_erase_area(int top,int left,int bottom,int right,int win)351 void os_erase_area(int top, int left, int bottom, int right, int win)
352 {
353 sf_flushtext();
354 sf_fillrect((sf_curtextsetting())->back, left - 1, top - 1,
355 right - left + 1, bottom - top + 1);
356 }
357
358
359 /*
360 * os_peek_colour
361 *
362 * Return the colour of the screen unit below the cursor. (If the
363 * interface uses a text mode, it may return the background colour
364 * of the character at the cursor position instead.) This is used
365 * when text is printed on top of pictures. Note that this coulor
366 * need not be in the standard set of Z-machine colours. To handle
367 * this situation, Frotz entends the colour scheme: Colours above
368 * 15 (and below 256) may be used by the interface to refer to non
369 * standard colours. Of course, os_set_colour must be able to deal
370 * with these colours.
371 *
372 */
os_peek_colour(void)373 int os_peek_colour(void)
374 {
375 SF_textsetting *ts = sf_curtextsetting();
376 sf_flushtext();
377 return sf_GetColourIndex(sf_rpixel(ts->cx, ts->cy));
378 }
379
380
scroll(int x,int y,int w,int h,int n)381 static void scroll(int x, int y, int w, int h, int n)
382 {
383 ulong *src, *dst;
384 int nmove, step;
385 if (n > 0) {
386 dst = sbuffer + x + sbpitch * y;
387 src = dst + n * sbpitch;
388 nmove = h - n;
389 step = sbpitch;
390 } else if (n < 0) {
391 n = -n;
392 nmove = h - n;
393 step = -sbpitch;
394 src = sbuffer + x + sbpitch * (y + nmove - 1);
395 dst = src + n * sbpitch;
396 } else
397 return;
398 if (nmove > 0) {
399 while (nmove--) {
400 memmove(dst, src, w * sizeof(ulong));
401 dst += step;
402 src += step;
403 }
404 dirty = 1;
405 }
406 }
407
408
409 /**
410 * Update the display if contents have changed.
411 * Return whether contents had changed, i.e., display was updated.
412 */
sf_flushdisplay()413 bool sf_flushdisplay()
414 {
415 if (dirty) {
416 SDL_UpdateTexture(texture, NULL, sbuffer,
417 sbpitch * sizeof(ulong));
418 myGrefresh();
419 dirty = 0;
420 return true;
421 } else
422 return false;
423 }
424
425
426 /*
427 * os_scroll_area
428 *
429 * Scroll a rectangular area of the screen up (units > 0) or down
430 * (units < 0) and fill the empty space with the current background
431 * colour. Top left coordinates are (1,1). The cursor stays put.
432 *
433 */
os_scroll_area(int top,int left,int bottom,int right,int units)434 void os_scroll_area(int top, int left, int bottom, int right, int units)
435 {
436 sf_flushtext();
437
438 scroll(left - 1, top - 1, right - left + 1, bottom - top + 1, units);
439 if (units > 0)
440 sf_fillrect((sf_curtextsetting())->back, left - 1,
441 bottom - units, right - left + 1, units);
442 else if (units < 0)
443 sf_fillrect((sf_curtextsetting())->back, left - 1, top - 1,
444 right - left + 1, units);
445 }
446
447
os_repaint_window(int win,int ypos_old,int ypos_new,int xpos,int ysize,int xsize)448 bool os_repaint_window(int win, int ypos_old, int ypos_new, int xpos,
449 int ysize, int xsize)
450 {
451 /* TODO */
452 return FALSE;
453 }
454
455
456 int SFdticks = 200;
457 volatile bool SFticked = 0;
458 static SDL_TimerID timerid = 0;
459
mytimer(Uint32 inter,void * parm)460 static Uint32 mytimer(Uint32 inter, void *parm)
461 {
462 SFticked = true;
463 return inter;
464 }
465
466
cleanvideo()467 static void cleanvideo()
468 {
469 if (timerid)
470 SDL_RemoveTimer(timerid);
471 SDL_Quit();
472 }
473
474 #define RM 0x0000ff
475 #define GM 0x00ff00
476 #define BM 0xff0000
477
sf_toggle_fullscreen()478 static void sf_toggle_fullscreen()
479 {
480 if (SDL_SetWindowFullscreen
481 (window, isfullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP))
482 os_warn("Error switching %s fullscreen: %s",
483 isfullscreen ? "off" : "to", SDL_GetError());
484 else {
485 isfullscreen = !isfullscreen;
486 if (!isfullscreen)
487 SDL_SetWindowSize(window, AcWidth, AcHeight);
488 myGrefresh();
489 }
490 }
491
492
sf_initvideo(int W,int H,int full)493 void sf_initvideo(int W, int H, int full)
494 {
495 Uint32 video_flags = SDL_WINDOW_RESIZABLE, pixfmt;
496 Uint32 initflags = SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_AUDIO;
497
498 sprintf(banner, "SDL Frotz v%s - %s (z%d)",
499 VERSION, f_setup.story_name, z_header.version);
500
501 if (SDL_Init(initflags) < 0) {
502 os_fatal("Couldn't initialize SDL: %s", SDL_GetError());
503 }
504
505 sdl_active = TRUE;
506
507 /* We don't handle text edit events. Not that I know why anyone would
508 want to use such an IME with Frotz. */
509 SDL_SetHint(SDL_HINT_IME_INTERNAL_EDITING, "1");
510
511 CLEANREG(cleanvideo);
512
513 isfullscreen = full;
514 if (full)
515 video_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
516 if ((window =
517 SDL_CreateWindow(banner, SDL_WINDOWPOS_UNDEFINED,
518 SDL_WINDOWPOS_UNDEFINED, W, H, video_flags)))
519 renderer = SDL_CreateRenderer(window, -1, 0);
520 else
521 renderer = NULL;
522 if (renderer == NULL) {
523 os_fatal("Couldn't create %dx%d window: %s",
524 W, H, SDL_GetError());
525 }
526 SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
527 if (SDL_RenderSetLogicalSize(renderer, W, H))
528 os_fatal("Failed to set logical rendering size to %dx%d: %s",
529 W, H, SDL_GetError());
530 pixfmt = SDL_MasksToPixelFormatEnum(32, RM, GM, BM, 0);
531 if (!(texture = SDL_CreateTexture(renderer, pixfmt,
532 SDL_TEXTUREACCESS_STREAMING, W, H)))
533 os_fatal("Failed to create texture: %s", SDL_GetError());
534
535 sbuffer = calloc(W * H, sizeof(ulong));
536 if (!sbuffer)
537 os_fatal("Could not create gc");
538
539 SDL_AddTimer(SFdticks, mytimer, NULL);
540
541 xmin = ymin = 0;
542 xmax = ewidth = W;
543 ymax = eheight = H;
544 sbpitch = W;
545 dirty = 1;
546 }
547
548
549 /*
550 * os_draw_picture
551 *
552 * Display a picture at the given coordinates.
553 *
554 */
os_draw_picture(int picture,int y,int x)555 void os_draw_picture(int picture, int y, int x)
556 {
557 int ew, eh, xx, yy, ix, iy, d;
558 int ox, oy, ow, oh;
559 Zwindow *winpars;
560 sf_picture *pic = sf_getpic(picture);
561 ulong *src, *dst, sval, dval, alpha;
562
563 sf_flushtext();
564
565 if (!pic)
566 return;
567 if (!pic->pixels)
568 return;
569 src = (ulong *) pic->pixels;
570
571 x--;
572 y--;
573 ew = ceil(m_gfxScale_w * pic->width);
574 eh = ceil(m_gfxScale_h * pic->height);
575
576 /* this takes care of the fact that x, y are really 16 bit values */
577 if (x & 0x8000)
578 x |= 0xffff0000;
579 if (y & 0x8000)
580 y |= 0xffff0000;
581
582 /* get current window rect */
583 sf_getclip(&ox, &oy, &ow, &oh);
584 winpars = curwinrec();
585 sf_setclip(winpars->x_pos - 1, winpars->y_pos - 1, winpars->x_size,
586 winpars->y_size);
587
588 /* clip taking into account possible origin
589 * outside the clipping rect */
590 if (x < xmin) {
591 d = xmin - x;
592 ew -= d;
593 x = xmin;
594 src += d;
595 }
596 if (x + ew > xmax)
597 ew = xmax - x;
598 ew /= m_gfxScale_w;
599
600 if (y < ymin) {
601 d = ymin - y;
602 eh -= d;
603 y = ymin;
604 src += d * pic->width;
605 }
606 if (y + eh > ymax)
607 eh = ymax - y;
608 eh /= m_gfxScale_h;
609
610 sf_setclip(ox, oy, ow, oh);
611
612 if (ew <= 0)
613 return;
614 if (eh <= 0)
615 return;
616
617 /* Use simple scaling without interpolation in palette mode. */
618 /* Adapted from start of FrotzGfx::Paint() in Windows Frotz. */
619 if (pic->usespalette) {
620 if (!pic->adaptive && ApplyPalette(pic))
621 sf_flushdisplay();
622
623 for (yy = 0; yy < eh * m_gfxScale_h; yy++) {
624 int ys = ceil(yy / m_gfxScale_h);
625 if (ys >= pic->height)
626 ys = pic->height - 1;
627 for (xx = 0; xx < ew * m_gfxScale_w; xx++) {
628 int xs = ceil(xx / m_gfxScale_w);
629 if (xs >= pic->width)
630 xs = pic->width - 1;
631 int index = pic->pixels[ys * pic->width + xs];
632 if (index != pic->transparentcolor)
633 sf_wpixel(x + xx, y + yy,
634 screen_palette[index]);
635 }
636 }
637 } else {
638 if (pic->adaptive)
639 os_fatal("Adaptive images must be paletted");
640
641 for (yy = 0; yy < eh; yy++) {
642 for (xx = 0; xx < ew; xx++) {
643 dst =
644 sbuffer + x + (uint32_t)(xx * m_gfxScale_w) +
645 sbpitch * (y + (uint32_t)(yy * m_gfxScale_h));
646 sval = src[xx];
647 alpha = (sval >> 24);
648 if (alpha == 255)
649 dval = sval & 0xffffff;
650 else
651 dval =
652 sf_blend((int)
653 (alpha + (alpha >> 7)),
654 sval, dst[0]);
655 for (iy = 0; iy < m_gfxScale_h; iy++) {
656 for (ix = 0; ix < m_gfxScale_w; ix++)
657 dst[ix] = dval;
658 dst += sbpitch;
659 }
660 }
661
662 src += pic->width;
663 }
664 }
665
666 dirty = 1;
667 }
668
669
670 static ulong mytimeout;
671 int mouse_button;
672 static int numAltQ = 0;
673
set_mouse_xy(int x,int y)674 static void set_mouse_xy(int x, int y)
675 {
676 /* This is enough even in fullscreen:
677 * SDL maps mouse events to logical coordinates. */
678 mouse_x = x + 1;
679 mouse_y = y + 1;
680 }
681
682
683 /**
684 * Return the first character in the UTF-8-encoded str.
685 * Return 0 on encoding error or if the first character > U+FFFF.
686 */
decode_utf8(char * str)687 static zword decode_utf8(char *str)
688 {
689 int i, n = 0;
690 zword res;
691 if (!(*str & 0200))
692 return *str;
693 if (!(*str & 0100))
694 return 0;
695 if (!(*str & 040)) {
696 n = 2;
697 res = *str & 037;
698 } else if (!(*str & 020)) {
699 n = 3;
700 res = *str & 017;
701 } else
702 return 0;
703 for (i = 1; i < n; ++i) {
704 if ((str[i] & 0300) != 0200)
705 return 0;
706 res <<= 6;
707 res |= (str[i] & 077);
708 }
709 return res;
710 }
711
712
handle_window_event(SDL_Event * e)713 static void handle_window_event(SDL_Event * e)
714 {
715 switch (e->window.event) {
716 case SDL_WINDOWEVENT_EXPOSED:
717 myGrefresh();
718 }
719 }
720
721
goodzkey(SDL_Event * e,int allowed)722 static zword goodzkey(SDL_Event * e, int allowed)
723 {
724 SDL_Keycode c;
725 zword res;
726
727 switch (e->type) {
728 case SDL_QUIT:
729 sf_quitconf();
730 return 0;
731 case SDL_MOUSEBUTTONDOWN:
732 if (true) {
733 mouse_button = e->button.button;
734 set_mouse_xy(e->button.x, e->button.y);
735 return ZC_SINGLE_CLICK;
736 } else
737 return 0;
738 case SDL_KEYDOWN:
739 if ((e->key.keysym.mod & 0xfff) == (KMOD_LALT | KMOD_LCTRL)
740 && e->key.keysym.sym == 'x')
741 os_fatal("Emergency exit!\n\n(Control-Alt-X pressed)");
742 if (e->key.keysym.mod & (KMOD_LALT | KMOD_RALT)) {
743 if (e->key.keysym.sym == 'q') {
744 numAltQ++;
745 if (numAltQ > 2)
746 os_fatal("Emergency exit!\n\n"
747 "(Alt-Q pressed 3 times in succession)");
748 return 0;
749 } else
750 numAltQ = 0;
751 if (e->key.keysym.sym == SDLK_RETURN) {
752 sf_toggle_fullscreen();
753 return 0;
754 }
755 if (allowed)
756 switch (e->key.keysym.sym) {
757 case 'x':
758 return ZC_HKEY_QUIT;
759 case 'p':
760 return ZC_HKEY_PLAYBACK;
761 case 'r':
762 return ZC_HKEY_RECORD;
763 case 's':
764 return ZC_HKEY_SEED;
765 case 'u':
766 return ZC_HKEY_UNDO;
767 case 'n':
768 return ZC_HKEY_RESTART;
769 case 'd':
770 return ZC_HKEY_DEBUG;
771 case 'h':
772 return ZC_HKEY_HELP;
773 }
774 return 0;
775 } else
776 numAltQ = 0;
777 switch (e->key.keysym.sym) {
778 case SDLK_INSERT:
779 return (allowed ? VK_INS : 0);
780 case SDLK_DELETE:
781 return (allowed ? VK_DEL : 0);
782 case SDLK_BACKSPACE:
783 return ZC_BACKSPACE;
784 case SDLK_ESCAPE:
785 return ZC_ESCAPE;
786 case SDLK_RETURN:
787 return ZC_RETURN;
788 case SDLK_UP:
789 return ZC_ARROW_UP;
790 case SDLK_DOWN:
791 return ZC_ARROW_DOWN;
792 case SDLK_LEFT:
793 return ZC_ARROW_LEFT;
794 case SDLK_RIGHT:
795 return ZC_ARROW_RIGHT;
796 case SDLK_TAB:
797 return (allowed ? VK_TAB : 0);
798 case SDLK_PAGEUP:
799 return (allowed ? VK_PAGE_UP : 0);
800 case SDLK_PAGEDOWN:
801 return (allowed ? VK_PAGE_DOWN : 0);
802 case SDLK_KP_0:
803 return ZC_NUMPAD_0;
804 case SDLK_KP_1:
805 return ZC_NUMPAD_1;
806 case SDLK_KP_2:
807 return ZC_NUMPAD_2;
808 case SDLK_KP_3:
809 return ZC_NUMPAD_3;
810 case SDLK_KP_4:
811 return ZC_NUMPAD_4;
812 case SDLK_KP_5:
813 return ZC_NUMPAD_5;
814 case SDLK_KP_6:
815 return ZC_NUMPAD_6;
816 case SDLK_KP_7:
817 return ZC_NUMPAD_7;
818 case SDLK_KP_8:
819 return ZC_NUMPAD_8;
820 case SDLK_KP_9:
821 return ZC_NUMPAD_9;
822 case SDLK_F1:
823 return ZC_FKEY_F1;
824 case SDLK_F2:
825 return ZC_FKEY_F2;
826 case SDLK_F3:
827 return ZC_FKEY_F3;
828 case SDLK_F4:
829 return ZC_FKEY_F4;
830 case SDLK_F5:
831 return ZC_FKEY_F5;
832 case SDLK_F6:
833 return ZC_FKEY_F6;
834 case SDLK_F7:
835 return ZC_FKEY_F7;
836 case SDLK_F8:
837 return ZC_FKEY_F8;
838 case SDLK_F9:
839 return ZC_FKEY_F9;
840 case SDLK_F10:
841 return ZC_FKEY_F10;
842 case SDLK_F11:
843 return ZC_FKEY_F11;
844 case SDLK_F12:
845 return ZC_FKEY_F12;
846 }
847 /* XXX Maybe we should just always have text input on. */
848 if (!SDL_IsTextInputActive()) {
849 c = e->key.keysym.sym;
850 if (c >= 32 && c <= 126)
851 return c;
852 }
853 return 0;
854 case SDL_TEXTINPUT:
855 res = decode_utf8(e->text.text);
856 if ((res >= 32 && res <= 126) || res >= 160)
857 return res;
858 else
859 return 0;
860 case SDL_WINDOWEVENT:
861 handle_window_event(e);
862 }
863 return 0;
864 }
865
866
sf_read_key(int timeout,bool cursor,bool allowed,bool text)867 zword sf_read_key(int timeout, bool cursor, bool allowed, bool text)
868 {
869 SDL_Event event;
870 zword inch = 0;
871
872 sf_flushtext();
873 if (cursor)
874 sf_drawcursor(true);
875 sf_flushdisplay();
876
877 if (timeout)
878 mytimeout = sf_ticks() + m_timerinterval * timeout;
879 if (text)
880 SDL_StartTextInput();
881 while (true) {
882 /* Get the next input */
883 while (SDL_PollEvent(&event)) {
884 if ((inch = goodzkey(&event, allowed)))
885 break;
886 }
887 if (inch)
888 break;
889 if ((timeout) && (sf_ticks() >= mytimeout)) {
890 inch = ZC_TIME_OUT;
891 break;
892 }
893 sf_checksound();
894 sf_sleep(10);
895 }
896 if (text)
897 SDL_StopTextInput();
898
899 if (cursor)
900 sf_drawcursor(false);
901
902 return inch;
903 }
904
905
906 /*
907 * os_read_key
908 *
909 * Read a single character from the keyboard (or a mouse click) and
910 * return it. Input aborts after timeout/10 seconds.
911 *
912 */
os_read_key(int timeout,int cursor)913 zchar os_read_key(int timeout, int cursor)
914 {
915 return sf_read_key(timeout, cursor, false, true);
916 }
917
918
919 /*
920 * os_read_line
921 *
922 * Read a line of input from the keyboard into a buffer. The buffer
923 * may already be primed with some text. In this case, the "initial"
924 * text is already displayed on the screen. After the input action
925 * is complete, the function returns with the terminating key value.
926 * The length of the input should not exceed "max" characters plus
927 * an extra 0 terminator.
928 *
929 * Terminating keys are the return key (13) and all function keys
930 * (see the Specification of the Z-machine) which are accepted by
931 * the is_terminator function. Mouse clicks behave like function
932 * keys except that the mouse position is stored in global variables
933 * "mouse_x" and "mouse_y" (top left coordinates are (1,1)).
934 *
935 * Furthermore, Frotz introduces some special terminating keys:
936 *
937 * ZC_HKEY_PLAYBACK (Alt-P)
938 * ZC_HKEY_RECORD (Alt-R)
939 * ZC_HKEY_SEED (Alt-S)
940 * ZC_HKEY_UNDO (Alt-U)
941 * ZC_HKEY_RESTART (Alt-N, "new game")
942 * ZC_HKEY_QUIT (Alt-X, "exit game")
943 * ZC_HKEY_DEBUG (Alt-D)
944 * ZC_HKEY_HELP (Alt-H)
945 *
946 * If the timeout argument is not zero, the input gets interrupted
947 * after timeout/10 seconds (and the return value is 0).
948 *
949 * The complete input line including the cursor must fit in "width"
950 * screen units.
951 *
952 * The function may be called once again to continue after timeouts,
953 * misplaced mouse clicks or hot keys. In this case the "continued"
954 * flag will be set. This information can be useful if the interface
955 * implements input line history.
956 *
957 * The screen is not scrolled after the return key was pressed. The
958 * cursor is at the end of the input line when the function returns.
959 *
960 * Since Frotz 2.2 the helper function "completion" can be called
961 * to implement word completion (similar to tcsh under Unix).
962 *
963 */
os_read_line(int max,zchar * buf,int timeout,int width,int continued)964 zchar os_read_line(int max, zchar * buf, int timeout, int width, int continued)
965 {
966 static int pos = 0, searchpos = -1;
967 int ptx, pty;
968 int len = mywcslen(buf);
969 SF_textsetting *ts = sf_curtextsetting();
970 SDL_Event event;
971 sf_flushtext();
972
973 /* Better be careful here or it might segv. I wonder if we should just
974 ignore 'continued' and check for len > 0 instead? Might work better
975 with Beyond Zork. */
976 if (!continued || pos > len || searchpos > len) {
977 pos = len;
978 gen_history_reset(); /* Reset user's history view. */
979 searchpos = -1; /* -1 means initialize from len. */
980 }
981 /* Draw the input line */
982 ptx = ts->cx;
983 pty = ts->cy;
984 ptx -= os_string_width(buf);
985 sf_DrawInput(buf, pos, ptx, pty, width, true);
986
987 if (timeout)
988 mytimeout = sf_ticks() + m_timerinterval * timeout;
989 SDL_StartTextInput();
990 while (true) {
991 /* Get the next input */
992 while (SDL_PollEvent(&event)) {
993 zword c;
994 if ((c = goodzkey(&event, 1))) {
995 switch (c) {
996 case ZC_BACKSPACE:
997 /* Delete the character to the left of the cursor */
998 if (pos > 0) {
999 memmove(buf + pos - 1,
1000 buf + pos,
1001 sizeof(zchar) *
1002 (mywcslen(buf) - pos +
1003 1));
1004 pos--;
1005 sf_DrawInput(buf, pos, ptx, pty,
1006 width, true);
1007 }
1008 continue;
1009 case VK_DEL:
1010 /* Delete the character to the right of the cursor */
1011 if (pos < mywcslen(buf)) {
1012 memmove(buf + pos,
1013 buf + pos + 1,
1014 sizeof(zchar) *
1015 (mywcslen(buf) - pos));
1016 sf_DrawInput(buf, pos, ptx, pty,
1017 width, true);
1018 }
1019 continue;
1020 case ZC_ESCAPE: /* Delete whole line */
1021 pos = 0;
1022 buf[0] = '\0';
1023 searchpos = -1;
1024 gen_history_reset();
1025 sf_DrawInput(buf, pos, ptx, pty, width,
1026 true);
1027 continue;
1028 case VK_TAB:
1029 if (pos == (int)mywcslen(buf)) {
1030 zchar extension[10], *s;
1031 completion(buf, extension);
1032
1033 /* Add the completion to the input stream */
1034 for (s = extension; *s != 0;
1035 s++)
1036 if (sf_IsValidChar(*s))
1037 buf[pos++] =
1038 (*s);
1039 buf[pos] = 0;
1040 sf_DrawInput(buf, pos, ptx, pty,
1041 width, true);
1042 }
1043 continue;
1044 case ZC_ARROW_LEFT:
1045 /* Move the cursor left */
1046 if (pos > 0)
1047 pos--;
1048 sf_DrawInput(buf, pos, ptx, pty, width,
1049 true);
1050 continue;
1051 case ZC_ARROW_RIGHT:
1052 /* Move the cursor right */
1053 if (pos < (int)mywcslen(buf))
1054 pos++;
1055 sf_DrawInput(buf, pos, ptx, pty, width,
1056 true);
1057 continue;
1058 case ZC_ARROW_UP:
1059 case ZC_ARROW_DOWN:
1060 if (searchpos < 0)
1061 searchpos = mywcslen(buf);
1062 if ((c == ZC_ARROW_UP
1063 ? gen_history_back :
1064 gen_history_forward) (buf,
1065 searchpos,
1066 max)) {
1067 pos = mywcslen(buf);
1068 sf_DrawInput(buf, pos, ptx, pty,
1069 width, true);
1070 }
1071 continue;
1072 /* Pass through as up/down arrows for Beyond Zork. */
1073 case VK_PAGE_UP:
1074 c = ZC_ARROW_UP;
1075 break;
1076 case VK_PAGE_DOWN:
1077 c = ZC_ARROW_DOWN;
1078 break;
1079 default:
1080 if (sf_IsValidChar(c)
1081 && mywcslen(buf) < max) {
1082 /* Add a valid character to the input line
1083 * Get the width of the new input line */
1084 int len = os_string_width(buf);
1085 len += os_char_width(c);
1086 len += os_char_width('0');
1087
1088 /* printf("l%d w%d p%d\n",len,width,pos);
1089 * Only allow if the width limit is not exceeded */
1090 if (len <= width) {
1091 memmove(buf + pos + 1,
1092 buf + pos,
1093 sizeof(zchar) *
1094 (mywcslen(buf) -
1095 pos + 1));
1096 *(buf + pos) = c;
1097 pos++;
1098 sf_DrawInput(buf, pos,
1099 ptx, pty,
1100 width,
1101 true);
1102 }
1103 continue;
1104 }
1105 }
1106 if (is_terminator(c)) {
1107 /* Terminate the current input */
1108 m_exitPause = false;
1109 sf_DrawInput(buf, pos, ptx, pty, width,
1110 false);
1111
1112 if ((c == ZC_SINGLE_CLICK)
1113 || (c == ZC_DOUBLE_CLICK)) {
1114 /* mouse_x = input.mousex+1;
1115 mouse_y = input.mousey+1; */
1116 } else if (c == ZC_RETURN)
1117 gen_add_to_history(buf);
1118 SDL_StopTextInput();
1119 return c;
1120 }
1121 }
1122 }
1123 if ((timeout) && (sf_ticks() >= mytimeout)) {
1124 SDL_StopTextInput();
1125 return ZC_TIME_OUT;
1126 }
1127 sf_checksound();
1128 sf_sleep(10);
1129 }
1130 }
1131
1132 /* Draw the current input line */
sf_DrawInput(zchar * buffer,int pos,int ptx,int pty,int width,bool cursor)1133 void sf_DrawInput(zchar * buffer, int pos, int ptx, int pty, int width,
1134 bool cursor)
1135 {
1136 int height;
1137 SF_textsetting *ts = sf_curtextsetting();
1138
1139
1140 height = ts->font->height(ts->font);
1141
1142 /* Remove any previous input */
1143 sf_fillrect(ts->back, ptx, pty, width, height);
1144
1145 /* Display the input */
1146 ts->cx = ptx;
1147 ts->cy = pty;
1148 os_display_string(buffer);
1149
1150 if (cursor) {
1151 int wid = 0, i = 0, oh;
1152 while (i < pos)
1153 wid += sf_charwidth(buffer[i++], &oh);
1154 drawthecursor(ptx + wid, pty, 1);
1155 }
1156
1157 /* Update the window */
1158 sf_flushdisplay();
1159 }
1160
1161
1162 /*
1163 * os_read_mouse
1164 *
1165 * Store the mouse position in the global variables "mouse_x" and
1166 * "mouse_y", the code of the last clicked menu in "menu_selected"
1167 * and return the mouse buttons currently pressed.
1168 *
1169 */
os_read_mouse(void)1170 zword os_read_mouse(void)
1171 {
1172 byte c;
1173 int x, y;
1174 zword btn = 0;
1175 /* Get the mouse position */
1176 SDL_PumpEvents();
1177 c = SDL_GetMouseState(&x, &y);
1178 set_mouse_xy(x, y);
1179 /* Get the last selected menu item */
1180 /* menu_selected = theWnd->GetMenuClick();*/
1181
1182 /* Get the mouse buttons */
1183 if (c & SDL_BUTTON_LMASK)
1184 btn |= 1;
1185 if (c & SDL_BUTTON_RMASK)
1186 btn |= 2;
1187 if (c & SDL_BUTTON_MMASK)
1188 btn |= 4;
1189
1190 return btn;
1191 }
1192
1193
1194 /*
1195 * os_more_prompt
1196 *
1197 * Display a MORE prompt, wait for a keypress and remove the MORE
1198 * prompt from the screen.
1199 *
1200 */
os_more_prompt(void)1201 void os_more_prompt(void)
1202 {
1203 if (m_morePrompts) {
1204 SF_textsetting *ts;
1205 int x, y, h;
1206 const char *p = sf_msgstring(IDS_MORE);
1207 sf_flushtext();
1208
1209 /* Save the current text position */
1210 sf_pushtextsettings();
1211 ts = sf_curtextsetting();
1212 x = ts->cx;
1213 y = ts->cy;
1214 h = ts->font->height(ts->font);
1215 /* Show a [More] prompt */
1216 while (*p)
1217 os_display_char((zchar) (*p++));
1218
1219 /* Wait for a key press */
1220 sf_read_key(0, true, false, false);
1221 /* Remove the [More] prompt */
1222 sf_fillrect(ts->back, x, y, ts->cx - x, h);
1223
1224 /* Restore the current text position */
1225 sf_poptextsettings();
1226 }
1227 }
1228
1229
sf_savearea(int x,int y,int w,int h)1230 ulong *sf_savearea(int x, int y, int w, int h)
1231 {
1232 ulong *r, *p, *s;
1233 int i;
1234
1235 if (x < 0) {
1236 w += x;
1237 x = 0;
1238 }
1239 if (x + w > ewidth)
1240 w = ewidth - x;
1241 if (w <= 0)
1242 return NULL;
1243
1244 if (y < 0) {
1245 h += y;
1246 y = 0;
1247 }
1248 if (y + h > eheight)
1249 h = eheight - y;
1250 if (h <= 0)
1251 return NULL;
1252
1253 r = p = malloc((w * h + 4) * sizeof(ulong));
1254 if (!r)
1255 return NULL;
1256
1257 *p++ = x;
1258 *p++ = y;
1259 *p++ = w;
1260 *p++ = h;
1261
1262 s = sbuffer + x + y * sbpitch;
1263 for (i = 0; i < h; i++) {
1264 memmove(p, s, w * sizeof(ulong));
1265 p += w;
1266 s += sbpitch;
1267 }
1268 return r;
1269 }
1270
1271
sf_restoreareaandfree(ulong * s)1272 void sf_restoreareaandfree(ulong * s)
1273 {
1274 ulong *p, *d;
1275 int i, x, y, w, h;
1276 if (!s)
1277 return;
1278
1279 p = s;
1280 x = *p++;
1281 y = *p++;
1282 w = *p++;
1283 h = *p++;
1284
1285 d = sbuffer + x + y * sbpitch;
1286 for (i = 0; i < h; i++) {
1287 memmove(d, p, w * sizeof(ulong));
1288 p += w;
1289 d += sbpitch;
1290 }
1291
1292 free(s);
1293 dirty = 1;
1294 sf_flushdisplay();
1295 }
1296
1297
1298 int (*sf_osdialog)(bool ex, const char *def, const char *filt, const char *tit,
1299 char **res, ulong * sbuf, int sbp, int ew, int eh,
1300 int isfull) = NULL;
1301
1302
sf_user_fdialog(bool existing,const char * defaultname,const char * filter,const char * title,char ** result)1303 int sf_user_fdialog(bool existing, const char *defaultname, const char *filter,
1304 const char *title, char **result)
1305 {
1306 if (sf_osdialog)
1307 return sf_osdialog(existing, defaultname, filter, title, result,
1308 sbuffer, sbpitch, ewidth, eheight,
1309 isfullscreen);
1310 return SF_NOTIMP;
1311 }
1312
1313
sf_videodata(ulong ** sb,int * sp,int * ew,int * eh)1314 void sf_videodata(ulong ** sb, int *sp, int *ew, int *eh)
1315 {
1316 *sb = sbuffer;
1317 *sp = sbpitch;
1318 *ew = ewidth;
1319 *eh = eheight;
1320 }
1321
1322
1323 extern zword sf_yesnooverlay(int xc, int yc, char *t, int sr);
sf_quitconf()1324 static void sf_quitconf()
1325 {
1326 if (sf_yesnooverlay(ewidth / 2, eheight / 2, "Quit: are you sure?", 1)
1327 == ZC_RETURN) {
1328 printf
1329 ("\n\nQuitting (close button clicked on main window)\n\n");
1330 SDL_Quit();
1331 os_quit(EXIT_SUCCESS);
1332 }
1333 }
1334
1335
os_tick()1336 void os_tick()
1337 {
1338 sf_checksound();
1339 if (SFticked) {
1340 SFticked = false;
1341 if (!sf_flushdisplay()) {
1342 SDL_Event ev;
1343 SDL_PumpEvents();
1344 while (SDL_PeepEvents(&ev, 1, SDL_GETEVENT,
1345 SDL_WINDOWEVENT,
1346 SDL_WINDOWEVENT) > 0)
1347 handle_window_event(&ev);
1348 }
1349 }
1350 }
1351
1352
1353 /* Apply the picture's palette to the screen palette. */
1354 /* Adapted from FrotzGfx::ApplyPalette() in Windows Frotz. */
ApplyPalette(sf_picture * graphic)1355 static bool ApplyPalette(sf_picture * graphic)
1356 {
1357 bool changed = FALSE;
1358 int i, colors;
1359
1360 memset(&screen_palette, 0, sizeof(ulong));
1361
1362 if (graphic->usespalette) {
1363 colors = graphic->palette_entries;
1364 if (colors > 16)
1365 colors = 16;
1366 for (i = 0; i < colors; i++) {
1367 if (screen_palette[i] != graphic->palette[i]) {
1368 changed = TRUE;
1369 screen_palette[i] = graphic->palette[i];
1370 }
1371 }
1372 }
1373 return changed;
1374 }
1375