libacfutils
A general purpose library of utility functions designed to make it easier to develop addons for the X-Plane flight simulator.
Loading...
Searching...
No Matches
widget.c
1/*
2 * CDDL HEADER START
3 *
4 * This file and its contents are supplied under the terms of the
5 * Common Development and Distribution License ("CDDL"), version 1.0.
6 * You may only use this file in accordance with the terms of version
7 * 1.0 of the CDDL.
8 *
9 * A full copy of the text of the CDDL should have accompanied this
10 * source. A copy of the CDDL is also available via the Internet at
11 * http://www.illumos.org/license/CDDL.
12 *
13 * CDDL HEADER END
14 */
15/*
16 * Copyright 2023 Saso Kiselkov. All rights reserved.
17 */
18
19#include <ctype.h>
20#include <stdlib.h>
21#include <stddef.h>
22#include <string.h>
23#include <stdbool.h>
24
25#include <XPLMGraphics.h>
26#include <XPLMDisplay.h>
27#include <XPLMProcessing.h>
28#include <XPStandardWidgets.h>
29
30#include "acfutils/assert.h"
31#include "acfutils/dr.h"
32#include "acfutils/geom.h"
33#include "acfutils/helpers.h"
34#include "acfutils/list.h"
36#include "acfutils/safe_alloc.h"
37#include "acfutils/widget.h"
38#include "acfutils/time.h"
39
40enum {
41 TOOLTIP_WINDOW_OFFSET = 15,
42 TOOLTIP_WINDOW_MARGIN = 10,
43 TOOLTIP_WINDOW_WIDTH = 600
44};
45
46#define TOOLTIP_INTVAL 0.1
47#define DEFAULT_DISPLAY_DELAY 1 /* secs */
48
49typedef struct {
50 int x, y, w, h;
51 const char *text;
52 list_node_t node;
53} tooltip_t;
54
56 XPLMWindowID window;
57 int orig_w;
58 int orig_h;
59 list_t tooltips;
60 list_node_t node;
61 double display_delay;
62 /* These are only used for text size measurement */
63 cairo_surface_t *surf;
64 cairo_t *cr;
65 cairo_font_face_t *font_face;
66 double font_size;
67 double line_height;
68 double font_color[4];
69 double bg_color[4];
70};
71
72#define TT_FONT_SIZE 18
73#define TT_LINE_HEIGHT_MULT 1.5
74#define TT_BACKGROUND_RGBA 0, 0, 0, 0.85
75#define TT_TEXT_RGBA 1, 1, 1, 1
76
77static list_t tooltip_sets;
78static bool inited = false;
79static tooltip_t *cur_tt = NULL;
80static const tooltip_set_t *cur_tts = NULL;
81static mt_cairo_render_t *cur_tt_mtcr = NULL;
82static XPLMWindowID cur_tt_win = NULL;
83static size_t n_cur_tt_lines = 0;
84static char **cur_tt_lines = NULL;
85static int last_mouse_x, last_mouse_y;
86static uint64_t mouse_moved_time;
87
88static void
89tt_draw_cb(XPLMWindowID win, void *refcon)
90{
91 int left, top, right, bottom;
92
93 ASSERT(win != NULL);
94 LACF_UNUSED(refcon);
95
96 ASSERT(cur_tt_mtcr != NULL);
97 XPLMGetWindowGeometry(win, &left, &top, &right, &bottom);
98 mt_cairo_render_draw(cur_tt_mtcr, VECT2(left, bottom),
99 VECT2(right - left, top - bottom));
100}
101
102static void
103tt_render_cb(cairo_t *cr, unsigned w, unsigned h, void *userinfo)
104{
105 double x = TOOLTIP_WINDOW_MARGIN;
106 double y;
107 cairo_text_extents_t te;
108 const tooltip_set_t *tts;
109
110 ASSERT(cr != NULL);
111 ASSERT(userinfo != NULL);
112 tts = userinfo;
113 y = TOOLTIP_WINDOW_MARGIN + tts->line_height / 2;
114
115 if (tts->font_face != NULL)
116 cairo_set_font_face(cr, tts->font_face);
117 cairo_set_font_size(cr, tts->font_size);
118
119 cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
120 cairo_paint(cr);
121 cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
122
123 cairo_set_source_rgba(cr, tts->bg_color[0], tts->bg_color[1],
124 tts->bg_color[2], tts->bg_color[3]);
126 TOOLTIP_WINDOW_MARGIN);
127 cairo_fill(cr);
128
129 cairo_set_source_rgba(cr, TT_TEXT_RGBA);
130
131 ASSERT(n_cur_tt_lines != 0);
132 cairo_text_extents(cr, cur_tt_lines[0], &te);
133 for (size_t i = 0; i < n_cur_tt_lines; i++) {
134 cairo_move_to(cr, x, y - te.height / 2 - te.y_bearing);
135 cairo_show_text(cr, cur_tt_lines[i]);
136 y += tts->line_height;
137 }
138}
139
140XPWidgetID
141create_widget_rel(int x, int y, bool_t y_from_bottom, int width, int height,
142 int visible, const char *descr, int root, XPWidgetID container,
143 XPWidgetClass cls)
144{
145 return (create_widget_rel2(x, y, y_from_bottom, width, height, visible,
146 descr, root, container, container, cls));
147}
148
149XPWidgetID
150create_widget_rel2(int x, int y, bool_t y_from_bottom, int width, int height,
151 int visible, const char *descr, int root, XPWidgetID container,
152 XPWidgetID coord_ref, XPWidgetClass cls)
153{
154 int wleft = 0, wtop = 0, wright = 0, wbottom = 0;
155 int bottom, right;
156
157 if (container != NULL) {
158 XPGetWidgetGeometry(coord_ref, &wleft, &wtop, &wright,
159 &wbottom);
160 } else {
161 XPLMGetScreenSize(&wright, &wtop);
162 if (!y_from_bottom && y + height > wtop)
163 y = wtop - height;
164 else if (y_from_bottom && y - height < 0)
165 y = height;
166 if (x + width > wright)
167 x = wright - width;
168 }
169
170 x += wleft;
171 if (!y_from_bottom) {
172 y = wtop - y;
173 bottom = y - height;
174 } else {
175 y = wbottom + y;
176 bottom = y - height;
177 }
178 right = x + width;
179
180 return (XPCreateWidget(x, y, right, bottom, visible, descr, root,
181 container, cls));
182}
183
184static void
185find_first_monitor(int idx, int left, int top, int right, int bottom,
186 void *refcon)
187{
188 monitor_t *mon = refcon;
189
190 LACF_UNUSED(idx);
191
192 if (mon->left == 0 && mon->right == 0 &&
193 mon->top == 0 && mon->bottom == 0) {
194 mon->left = left;
195 mon->right = right;
196 mon->top = top;
197 mon->bottom = bottom;
198 }
199}
200
202lacf_get_first_monitor_bounds(void)
203{
204 monitor_t mon = {};
205
206 memset(&mon, 0, sizeof (mon));
207
208 XPLMGetAllMonitorBoundsGlobal(find_first_monitor, &mon);
209 if (mon.left == 0 && mon.right == 0 && mon.top == 0 &&
210 mon.bottom == 0) {
211 XPLMGetScreenBoundsGlobal(&mon.left, &mon.top, &mon.right,
212 &mon.bottom);
213 }
214
215 return (mon);
216}
217
218static void
219center_window_coords(int *left, int *top, int *right, int *bottom)
220{
221 monitor_t mon = lacf_get_first_monitor_bounds();
222 int width = (*right) - (*left);
223 int height = (*top) - (*bottom);
224
225 *left = (mon.right + mon.left - width) / 2;
226 *right = (*left) + width;
227 *bottom = (mon.bottom + mon.top - height) / 2;
228 *top = (*bottom) + height;
229}
230
231API_EXPORT void
232widget_win_center(XPWidgetID window)
233{
234 ASSERT(window != NULL);
235 classic_win_center(XPGetWidgetUnderlyingWindow(window));
236}
237
238API_EXPORT void
239classic_win_center(XPLMWindowID window)
240{
241 int left, top, right, bottom;
242 ASSERT(window != NULL);
243 XPLMGetWindowGeometry(window, &left, &top, &right, &bottom);
244 center_window_coords(&left, &top, &right, &bottom);
245 XPLMSetWindowGeometry(window, left, top, right, bottom);
246}
247
248tooltip_set_t *
249tooltip_set_new(XPWidgetID window)
250{
251 return (tooltip_set_new_native(XPGetWidgetUnderlyingWindow(window)));
252}
253
254tooltip_set_t *
255tooltip_set_new_native(XPLMWindowID window)
256{
257 int left, top, right, bottom;
258 cairo_text_extents_t te;
259
260 tooltip_set_t *tts = safe_calloc(1, sizeof (*tts));
261 tts->window = window;
262 list_create(&tts->tooltips, sizeof (tooltip_t),
263 offsetof(tooltip_t, node));
264 list_insert_tail(&tooltip_sets, tts);
265 XPLMGetWindowGeometry(window, &left, &top, &right, &bottom);
266 tts->orig_w = right - left;
267 tts->orig_h = top - bottom;
268 tts->display_delay = DEFAULT_DISPLAY_DELAY;
269 tts->surf = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1);
270 tts->cr = cairo_create(tts->surf);
271 tts->font_size = TT_FONT_SIZE;
272 cairo_set_font_size(tts->cr, tts->font_size);
273 memcpy(tts->bg_color, (double[4]){TT_BACKGROUND_RGBA},
274 sizeof (tts->bg_color));
275 cairo_text_extents(tts->cr, "X", &te);
276 tts->line_height = te.height * TT_LINE_HEIGHT_MULT;
277
278 return (tts);
279}
280
281void
282tooltip_set_orig_win_size(tooltip_set_t *tts, unsigned orig_w, unsigned orig_h)
283{
284 ASSERT(tts != NULL);
285 ASSERT(orig_w != 0);
286 ASSERT(orig_h != 0);
287 tts->orig_w = orig_w;
288 tts->orig_h = orig_h;
289}
290
291void
292tooltip_set_delay(tooltip_set_t *set, double secs)
293{
294 ASSERT(set != NULL);
295 set->display_delay = secs;
296}
297
298static void
299destroy_cur_tt(void)
300{
301 if (cur_tt != NULL) {
302 mt_cairo_render_fini(cur_tt_mtcr);
303 cur_tt_mtcr = NULL;
304 XPLMDestroyWindow(cur_tt_win);
305 cur_tt_win = NULL;
306 free_strlist(cur_tt_lines, n_cur_tt_lines);
307 cur_tt_lines = NULL;
308 n_cur_tt_lines = 0;
309 cur_tt = NULL;
310 cur_tts = NULL;
311 }
312}
313
314void
315tooltip_set_destroy(tooltip_set_t *tts)
316{
317 tooltip_t *tt;
318 while ((tt = list_head(&tts->tooltips)) != NULL) {
319 if (cur_tt == tt)
320 destroy_cur_tt();
321 list_remove(&tts->tooltips, tt);
322 free(tt);
323 }
324 list_destroy(&tts->tooltips);
325 list_remove(&tooltip_sets, tts);
326 cairo_destroy(tts->cr);
327 cairo_surface_destroy(tts->surf);
328 ZERO_FREE(tts);
329}
330
331void
332tooltip_set_font_face(tooltip_set_t *tts, cairo_font_face_t *font)
333{
334 cairo_text_extents_t te;
335
336 ASSERT(tts != NULL);
337 ASSERT(font != NULL);
338 tts->font_face = font;
339 cairo_set_font_face(tts->cr, font);
340 cairo_text_extents(tts->cr, "X", &te);
341 tts->line_height = te.height * TT_LINE_HEIGHT_MULT;
342}
343
344cairo_font_face_t *
345tooltip_get_font_face(const tooltip_set_t *tts)
346{
347 ASSERT(tts != NULL);
348 return (tts->font_face);
349}
350
351void
352tooltip_set_font_size(tooltip_set_t *tts, double size)
353{
354 cairo_text_extents_t te;
355
356 ASSERT(tts != NULL);
357 tts->font_size = size;
358 cairo_set_font_size(tts->cr, tts->font_size);
359 cairo_text_extents(tts->cr, "X", &te);
360 tts->line_height = te.height * TT_LINE_HEIGHT_MULT;
361}
362
363double
364tooltip_get_font_size(const tooltip_set_t *tts)
365{
366 ASSERT(tts != NULL);
367 return (tts->font_size);
368}
369
370void
371tooltip_new(tooltip_set_t *tts, int x, int y, int w, int h, const char *text)
372{
373 ASSERT(text != NULL);
374
375 tooltip_t *tt = safe_calloc(1, sizeof (*tt));
376 tt->x = x;
377 tt->y = y;
378 tt->w = w;
379 tt->h = h;
380 tt->text = text;
381 list_insert_tail(&tts->tooltips, tt);
382}
383
384static inline const char *
385find_whitespace(const char *p, const char *end)
386{
387 while (!isspace(*p) && p < end)
388 p++;
389 return (p);
390}
391
392static cairo_text_extents_t
393tts_measure_string(const tooltip_set_t *tts, const char *text, unsigned len)
394{
395 cairo_text_extents_t ts;
396 char *buf = safe_calloc(len + 1, sizeof (*buf));
397
398 ASSERT(tts != NULL);
399 ASSERT(tts->cr != NULL);
400 ASSERT(text != NULL);
401
402 strlcpy(buf, text, len + 1);
403 cairo_text_extents(tts->cr, buf, &ts);
404 free(buf);
405
406 return (ts);
407}
408
409static char **
410auto_wrap_text(const tooltip_set_t *tts, const char *text, double max_width,
411 size_t *n_lines)
412{
413 char **lines = NULL;
414 const char *start, *end;
415 size_t n = 0;
416
417 ASSERT(tts != NULL);
418 ASSERT(text != NULL);
419 ASSERT(n_lines != NULL);
420
421 start = text;
422 end = text + strlen(text);
423
424 for (const char *p = text, *sp_prev = text; p < end;) {
425 const char *const sp_here = find_whitespace(p, end);
426 double width = tts_measure_string(tts, start,
427 sp_here - start).width;
428
429 if (width > max_width || *sp_prev == '\n') {
430 lines = safe_realloc(lines, (n + 1) * sizeof (*lines));
431 lines[n] = safe_malloc(sp_prev - start + 1);
432 lacf_strlcpy(lines[n], start, sp_prev - start + 1);
433 n++;
434 start = sp_prev + 1;
435 }
436 p = sp_here + 1;
437 sp_prev = sp_here;
438 }
439 /* Append any remaining stuff as a new line */
440 if (start < end) {
441 lines = safe_realloc(lines, (n + 1) * sizeof (*lines));
442 lines[n] = safe_malloc(end - start + 1);
443 lacf_strlcpy(lines[n], start, end - start + 1);
444 n++;
445 }
446 *n_lines = n;
447 return (lines);
448}
449
450static void
451set_cur_tt(const tooltip_set_t *tts, tooltip_t *tt, int mouse_x, int mouse_y)
452{
453 int width = 2 * TOOLTIP_WINDOW_MARGIN;
454 int height = 2 * TOOLTIP_WINDOW_MARGIN;
455 XPLMCreateWindow_t cr = {
456 .structSize = sizeof (cr),
457 .visible = true,
458 .drawWindowFunc = tt_draw_cb,
459 .decorateAsFloatingWindow = xplm_WindowDecorationNone,
460 .layer = xplm_WindowLayerGrowlNotifications
461 };
462
463 ASSERT(tts != NULL);
464 ASSERT(tt != NULL);
465 ASSERT3P(cur_tt, ==, NULL);
466 ASSERT3P(cur_tt_win, ==, NULL);
467 ASSERT3P(cur_tt_mtcr, ==, NULL);
468 ASSERT3P(cur_tt_lines, ==, NULL);
469
470 cur_tt_lines = auto_wrap_text(tts, tt->text, TOOLTIP_WINDOW_WIDTH,
471 &n_cur_tt_lines);
472 height += n_cur_tt_lines * tts->line_height;
473 for (size_t i = 0; i < n_cur_tt_lines; i++) {
474 cairo_text_extents_t sz = tts_measure_string(tts,
475 cur_tt_lines[i], strlen(cur_tt_lines[i]));
476 width = MAX(sz.width + 2 * TOOLTIP_WINDOW_MARGIN, width);
477 }
478 cur_tt = tt;
479 cur_tts = tts;
480 cur_tt_mtcr = mt_cairo_render_init(width, height, 0, NULL,
481 tt_render_cb, NULL, (void *)tts);
482 mt_cairo_render_once_wait(cur_tt_mtcr);
483
484 cr.left = mouse_x + TOOLTIP_WINDOW_OFFSET;
485 cr.right = mouse_x + width + TOOLTIP_WINDOW_OFFSET;
486 cr.top = mouse_y - TOOLTIP_WINDOW_OFFSET;
487 cr.bottom = mouse_y - height - TOOLTIP_WINDOW_OFFSET;
488
489 int lim_left, lim_top, lim_right, lim_bottom;
490 if (XPLMWindowIsPoppedOut(tts->window)) {
491 XPLMGetWindowGeometry(tts->window, &lim_left, &lim_top,
492 &lim_right, &lim_bottom);
493 } else {
494 XPLMGetScreenBoundsGlobal(&lim_left, &lim_top, &lim_right,
495 &lim_bottom);
496 }
497 if (cr.left < lim_left) {
498 int delta = lim_left - cr.left;
499 cr.left += delta;
500 cr.right += delta;
501 }
502 if (cr.right > lim_right) {
503 int delta = cr.right - lim_right;
504 cr.left -= delta;
505 cr.right -= delta;
506 }
507 if (cr.bottom < lim_bottom) {
508 /*
509 * If we can place the tooltip above the mouse cursor,
510 * do that instead of shifting the window up, as that
511 * will cover the tooltip subject.
512 */
513 if (mouse_y + height + TOOLTIP_WINDOW_OFFSET <= lim_top) {
514 cr.top = mouse_y + height + TOOLTIP_WINDOW_OFFSET;
515 cr.bottom = mouse_y + TOOLTIP_WINDOW_OFFSET;
516 } else {
517 int delta = lim_bottom - cr.bottom;
518 cr.top += delta;
519 cr.bottom += delta;
520 }
521 } else if (cr.top > lim_top) {
522 int delta = cr.top - lim_top;
523 cr.top -= delta;
524 cr.bottom -= delta;
525 }
526 cur_tt_win = XPLMCreateWindowEx(&cr);
527}
528
529static bool
530tooltip_invalid(void)
531{
532 return (cur_tts != NULL && !XPLMIsWindowInFront(cur_tts->window));
533}
534
535static float
536tooltip_floop_cb(float elapsed_since_last_call, float elapsed_since_last_floop,
537 int counter, void *refcon)
538{
539 int mouse_x, mouse_y;
540 long long now = microclock();
541 tooltip_set_t *hit_tts = NULL;
542 tooltip_t *hit_tt = NULL;
543
544 LACF_UNUSED(elapsed_since_last_call);
545 LACF_UNUSED(elapsed_since_last_floop);
546 LACF_UNUSED(counter);
547 LACF_UNUSED(refcon);
548
549 XPLMGetMouseLocationGlobal(&mouse_x, &mouse_y);
550
551 if (last_mouse_x != mouse_x || last_mouse_y != mouse_y ||
552 tooltip_invalid()) {
553 last_mouse_x = mouse_x;
554 last_mouse_y = mouse_y;
555 mouse_moved_time = now;
556 if (cur_tt != NULL)
557 destroy_cur_tt();
558 return (TOOLTIP_INTVAL);
559 }
560
561 if (cur_tt != NULL)
562 return (TOOLTIP_INTVAL);
563
564 for (tooltip_set_t *tts = list_head(&tooltip_sets); tts != NULL;
565 tts = list_next(&tooltip_sets, tts)) {
566 int wleft, wtop, wright, wbottom;
567 double scalex, scaley;
568
569 if (now - mouse_moved_time < SEC2USEC(tts->display_delay))
570 continue;
571
572 XPLMGetWindowGeometry(tts->window, &wleft, &wtop, &wright,
573 &wbottom);
574 if (!XPLMGetWindowIsVisible(tts->window) ||
575 !XPLMIsWindowInFront(tts->window) ||
576 mouse_x < wleft || mouse_x > wright ||
577 mouse_y < wbottom || mouse_y > wtop)
578 continue;
579
580 scalex = (wright - wleft) / (double)tts->orig_w;
581 scaley = (wtop - wbottom) / (double)tts->orig_h;
582
583 for (tooltip_t *tt = list_head(&tts->tooltips); tt != NULL;
584 tt = list_next(&tts->tooltips, tt)) {
585 int x1 = wleft + tt->x * scalex;
586 int x2 = wleft + (tt->x + tt->w) * scalex;
587 int y1 = wtop - (tt->y + tt->h) * scaley;
588 int y2 = wtop - tt->y * scaley;
589
590 if (mouse_x >= x1 && mouse_x <= x2 &&
591 mouse_y >= y1 && mouse_y <= y2) {
592 hit_tts = tts;
593 hit_tt = tt;
594 goto out;
595 }
596 }
597 }
598out:
599 if (hit_tt != NULL)
600 set_cur_tt(hit_tts, hit_tt, mouse_x, mouse_y);
601
602 return (TOOLTIP_INTVAL);
603}
604
605void
606tooltip_init(void)
607{
608 ASSERT(!inited);
609 inited = true;
610
611 list_create(&tooltip_sets, sizeof (tooltip_set_t),
612 offsetof(tooltip_set_t, node));
613
614 XPLMRegisterFlightLoopCallback(tooltip_floop_cb, TOOLTIP_INTVAL, NULL);
615}
616
617void
618tooltip_fini(void)
619{
620 tooltip_set_t *tts;
621
622 if (!inited)
623 return;
624 inited = false;
625
626 while ((tts = list_head(&tooltip_sets)) != NULL)
627 tooltip_set_destroy(tts);
628 list_destroy(&tooltip_sets);
629
630 XPLMUnregisterFlightLoopCallback(tooltip_floop_cb, NULL);
631}
632
633static bool_t
634is_in_VR(void)
635{
636 static bool_t dr_looked_up = B_FALSE;
637 static dr_t VR_enabled;
638
639 if (!dr_looked_up) {
640 dr_looked_up = B_TRUE;
641 fdr_find(&VR_enabled, "sim/graphics/VR/enabled");
642 }
643
644 return (dr_geti(&VR_enabled) != 0);
645}
646
647bool_t
648window_follow_VR(XPLMWindowID win)
649{
650 bool_t vr = is_in_VR();
651
652 ASSERT(win != NULL);
653 XPLMWindowPositioningMode mode = (XPLMWindowIsPoppedOut(win) ?
654 xplm_WindowPopOut : xplm_WindowPositionFree);
655 if (vr) {
656 mode = xplm_WindowVR;
657 }
658 XPLMSetWindowPositioningMode(win, mode, -1);
659
660 return (vr);
661}
662
663bool_t
664widget_follow_VR(XPWidgetID win)
665{
666 ASSERT(win != NULL);
667 return (window_follow_VR(XPGetWidgetUnderlyingWindow(win)));
668}
669
670typedef struct {
671 int left, top, right, bottom;
672 bool on_screen;
674
675static void
676find_window(int idx, int left, int top, int right, int bottom, void *refcon)
677{
678 enum { MARGIN = 50 };
679 find_window_t *info;
680
681 LACF_UNUSED(idx);
682 ASSERT(refcon != NULL);
683 info = refcon;
684
685 if (info->right > left + MARGIN && info->left < right - MARGIN &&
686 info->top > bottom + MARGIN && info->bottom < top - MARGIN) {
687 info->on_screen = B_TRUE;
688 }
689}
690
691bool_t
692window_is_on_screen(XPLMWindowID win)
693{
694 int left, top, right, bottom;
695 find_window_t info = {};
696 ASSERT(win != NULL);
697
698 XPLMGetWindowGeometry(win, &info.left, &info.top, &info.right,
699 &info.bottom);
700 XPLMGetAllMonitorBoundsGlobal(find_window, &info);
701 /*
702 * Fallback - maybe we don't have any fullscreen windows at all,
703 * so try the global X-Plane desktop space.
704 */
705 if (!info.on_screen) {
706 enum { MARGIN = 50 };
707 XPLMGetScreenBoundsGlobal(&left, &top, &right, &bottom);
708
709 return (info.right > left + MARGIN &&
710 info.left < right - MARGIN &&
711 info.top > bottom + MARGIN &&
712 info.bottom < top - MARGIN);
713 } else {
714 return (B_TRUE);
715 }
716}
717
718void
719win_resize_ctl_init(win_resize_ctl_t *ctl, XPLMWindowID win,
720 unsigned norm_w, unsigned norm_h)
721{
722 ASSERT(ctl != NULL);
723 ASSERT(win != NULL);
724 ASSERT3F(norm_w / 10, >, 0);
725 ASSERT3F(norm_h / 10, >, 0);
726
727 ctl->win = win;
728 ctl->norm_w = norm_w;
729 ctl->norm_h = norm_h;
730 ctl->w_h_ratio = norm_w / (double)norm_h;
731
732 XPLMGetWindowGeometry(win, &ctl->left, &ctl->top, &ctl->right,
733 &ctl->bottom);
734 /*
735 * We need a resizing limit to avoid div-by-zero when trying to
736 * calculate the required scaling factor.
737 */
738 XPLMSetWindowResizingLimits(win, norm_w / 10, norm_h / 10,
739 norm_w * 10, norm_h * 10);
740 /*
741 * X-Plane's resizing controls are kinda weird in that they always
742 * work relative to the window's "current" size. That means if we
743 * keep the window snapped to the normal size, it would be very
744 * hard for the user to make an input large enough to "unsnap" it
745 * out of the snapping range. So we instead turn the snapping
746 * behavior on/off based on a time delay. When the user first moves
747 * into the size snapping region (within 5% of the window's normal
748 * size), we snap the size for up to 0.75 seconds. If the user keeps
749 * resizing the window past that timeout, we follow their resizing
750 * input, even if it is in the snapping region. When they move
751 * outside of the snapping region, we reset the snapping behavior,
752 * to allow us to snap to the normal size again.
753 */
754 delay_line_init(&ctl->snap_hold_delay, SEC2USEC(0.75));
755 delay_line_push_imm_u64(&ctl->snap_hold_delay, B_TRUE);
756}
757
758static void
759calc_resize_geometry(win_resize_ctl_t *ctl, int top, int right,
760 int *left_p, int *top_p, int *right_p, int *bottom_p,
761 vect2_t grav_pt, bool_t grav_horiz)
762{
763 int w, h;
764
765 ASSERT(ctl != NULL);
766 ASSERT(left_p != NULL);
767 ASSERT(top_p != NULL);
768 ASSERT(right_p != NULL);
769 ASSERT(bottom_p != NULL);
770 ASSERT(!IS_NULL_VECT(grav_pt));
771
772 w = *right_p - *left_p;
773 h = *top_p - *bottom_p;
774
775 if (grav_horiz) {
776 int new_h = w / ctl->w_h_ratio;
777 double scale;
778
779 if (fabs(w / (double)ctl->norm_w - 1.0) < 0.05) {
780 if (!delay_line_push_u64(&ctl->snap_hold_delay,
781 B_TRUE)) {
782 scale = ctl->norm_w / (double)w;
783 *right_p = grav_pt.x +
784 (right - grav_pt.x) * scale;
785 *left_p = *right_p - ctl->norm_w;
786 new_h = ctl->norm_h;
787 }
788 } else {
789 delay_line_push_imm_u64(&ctl->snap_hold_delay, B_FALSE);
790 }
791 scale = new_h / (double)h;
792 *top_p = grav_pt.y + (top - grav_pt.y) * scale;
793 *bottom_p = *top_p - new_h;
794 } else {
795 int new_w = h * ctl->w_h_ratio;
796 double scale;
797
798 if (fabs(h / (double)ctl->norm_h - 1.0) < 0.05) {
799 if (!delay_line_push_u64(&ctl->snap_hold_delay,
800 B_TRUE)) {
801 scale = ctl->norm_h / (double)h;
802 *top_p = grav_pt.y + (top - grav_pt.y) * scale;
803 *bottom_p = *top_p - ctl->norm_h;
804 new_w = ctl->norm_w;
805 }
806 } else {
807 delay_line_push_imm_u64(&ctl->snap_hold_delay, B_FALSE);
808 }
809 scale = new_w / (double)w;
810 *right_p = grav_pt.x + (right - grav_pt.x) * scale;
811 *left_p = *right_p - new_w;
812 }
813}
814
815void
816win_resize_ctl_update(win_resize_ctl_t *ctl)
817{
818 int left, top, right, bottom;
819 int new_left, new_top, new_right, new_bottom;
820
821 ASSERT(ctl != NULL);
822 ASSERT(ctl->win != NULL);
823
824 XPLMGetWindowGeometry(ctl->win, &left, &top, &right, &bottom);
825 new_left = left;
826 new_top = top;
827 new_right = right;
828 new_bottom = bottom;
829
830 if (left != ctl->left && top != ctl->top && right != ctl->right &&
831 bottom != ctl->bottom) {
832 /*
833 * If all 4 coordinates changed, it means the window is
834 * being moved diagonally. We ignore this and simply store
835 * the new window geometry.
836 */
837 ctl->left = new_left = left;
838 ctl->top = new_top = top;
839 ctl->right = new_right = right;
840 ctl->bottom = new_bottom = bottom;
841 } else if (left != ctl->left && top != ctl->top) {
842 /* Resizing from top left corner, lock to lower right corner */
843 calc_resize_geometry(ctl, top, right,
844 &new_left, &new_top, &new_right, &new_bottom,
845 VECT2(right, bottom), B_TRUE);
846 } else if (left != ctl->left && bottom != ctl->bottom) {
847 /* Resizing from bottom left corner, lock to upper right */
848 calc_resize_geometry(ctl, top, right,
849 &new_left, &new_top, &new_right, &new_bottom,
850 VECT2(right, top), B_TRUE);
851 } else if (right != ctl->right && top != ctl->top) {
852 /* Resizing from top right corner, lock to lower left */
853 calc_resize_geometry(ctl, top, right,
854 &new_left, &new_top, &new_right, &new_bottom,
855 VECT2(left, bottom), B_TRUE);
856 } else if (right != ctl->right && bottom != ctl->bottom) {
857 /* Resizing from bottom right corner, lock to upper left */
858 calc_resize_geometry(ctl, top, right,
859 &new_left, &new_top, &new_right, &new_bottom,
860 VECT2(left, top), B_TRUE);
861 } else if (left != ctl->left && right == ctl->right) {
862 /* Resizing from bottom left edge, lock to right edge center */
863 calc_resize_geometry(ctl, top, right,
864 &new_left, &new_top, &new_right, &new_bottom,
865 VECT2(right, AVG(top, bottom)), B_TRUE);
866 } else if (right != ctl->right && left == ctl->left) {
867 /* Resizing from bottom right edge, lock to left edge center */
868 calc_resize_geometry(ctl, top, right,
869 &new_left, &new_top, &new_right, &new_bottom,
870 VECT2(left, AVG(top, bottom)), B_TRUE);
871 } else if (top != ctl->top && bottom == ctl->bottom) {
872 /* Resizing from top edge, lock to bottom edge center */
873 calc_resize_geometry(ctl, top, right,
874 &new_left, &new_top, &new_right, &new_bottom,
875 VECT2(AVG(left, right), bottom), B_FALSE);
876 } else if (bottom != ctl->bottom && top == ctl->top) {
877 /* Resizing from bottom edge, lock to top edge center */
878 calc_resize_geometry(ctl, top, right,
879 &new_left, &new_top, &new_right, &new_bottom,
880 VECT2(AVG(left, right), top), B_FALSE);
881 } else {
882 /* Horizontal/vertical window movement along a single axis. */
883 ctl->left = new_left = left;
884 ctl->top = new_top = top;
885 ctl->right = new_right = right;
886 ctl->bottom = new_bottom = bottom;
887 }
888 if (ctl->left != new_left || left != new_left ||
889 ctl->top != new_top || top != new_top ||
890 ctl->right != new_right || right != new_right ||
891 ctl->bottom != new_bottom || bottom != new_bottom) {
892 ctl->left = new_left;
893 ctl->right = new_right;
894 ctl->top = new_top;
895 ctl->bottom = new_bottom;
896 XPLMSetWindowGeometry(ctl->win, new_left, new_top, new_right,
897 new_bottom);
898 }
899}
#define ASSERT3P(x, op, y)
Definition assert.h:212
#define ASSERT(x)
Definition assert.h:208
#define ASSERT3F(x, op, y)
Definition assert.h:211
#define mt_cairo_render_rounded_rectangle
Definition cairo_utils.h:54
static uint64_t delay_line_push_imm_u64(delay_line_t *line, uint64_t value)
Definition delay_line.h:333
static uint64_t delay_line_push_u64(delay_line_t *line, uint64_t value)
Definition delay_line.h:310
static void delay_line_init(delay_line_t *line, uint64_t delay_us)
Definition delay_line.h:89
#define fdr_find(dr,...)
Definition dr.h:318
#define dr_geti(__dr)
Definition dr.h:348
#define IS_NULL_VECT(a)
Definition geom.h:228
#define VECT2(x, y)
Definition geom.h:190
void lacf_strlcpy(char *dest, const char *src, size_t cap)
Definition helpers.c:1667
void free_strlist(char **comps, size_t num)
Definition helpers.c:703
void list_destroy(list_t *)
Definition list.c:136
void * list_head(const list_t *)
Definition list.c:292
void list_create(list_t *, size_t, size_t)
Definition list.c:113
void * list_next(const list_t *, const void *)
Definition list.c:344
void list_remove(list_t *, void *)
Definition list.c:226
void list_insert_tail(list_t *, void *)
Definition list.c:213
void mt_cairo_render_once_wait(mt_cairo_render_t *mtcr)
void mt_cairo_render_fini(mt_cairo_render_t *mtcr)
void mt_cairo_render_draw(mt_cairo_render_t *mtcr, vect2_t pos, vect2_t size)
#define mt_cairo_render_init(w, h, fps, init_cb, render_cb, fini_cb, userinfo)
static void * safe_realloc(void *oldptr, size_t size)
Definition safe_alloc.h:86
#define ZERO_FREE(ptr)
Definition safe_alloc.h:253
static void * safe_calloc(size_t nmemb, size_t size)
Definition safe_alloc.h:71
static void * safe_malloc(size_t size)
Definition safe_alloc.h:56