25#include <XPLMGraphics.h>
26#include <XPLMDisplay.h>
27#include <XPLMProcessing.h>
28#include <XPStandardWidgets.h>
37#include "acfutils/widget.h"
38#include "acfutils/time.h"
41 TOOLTIP_WINDOW_OFFSET = 15,
42 TOOLTIP_WINDOW_MARGIN = 10,
43 TOOLTIP_WINDOW_WIDTH = 600
46#define TOOLTIP_INTVAL 0.1
47#define DEFAULT_DISPLAY_DELAY 1
63 cairo_surface_t *surf;
65 cairo_font_face_t *font_face;
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
78static bool inited =
false;
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;
89tt_draw_cb(XPLMWindowID win,
void *refcon)
91 int left, top, right, bottom;
96 ASSERT(cur_tt_mtcr != NULL);
97 XPLMGetWindowGeometry(win, &left, &top, &right, &bottom);
99 VECT2(right - left, top - bottom));
103tt_render_cb(cairo_t *cr,
unsigned w,
unsigned h,
void *userinfo)
105 double x = TOOLTIP_WINDOW_MARGIN;
107 cairo_text_extents_t te;
108 const tooltip_set_t *tts;
113 y = TOOLTIP_WINDOW_MARGIN + tts->line_height / 2;
115 if (tts->font_face != NULL)
116 cairo_set_font_face(cr, tts->font_face);
117 cairo_set_font_size(cr, tts->font_size);
119 cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
121 cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
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);
129 cairo_set_source_rgba(cr, TT_TEXT_RGBA);
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;
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,
145 return (create_widget_rel2(x, y, y_from_bottom, width, height, visible,
146 descr, root, container, container, cls));
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)
154 int wleft = 0, wtop = 0, wright = 0, wbottom = 0;
157 if (container != NULL) {
158 XPGetWidgetGeometry(coord_ref, &wleft, &wtop, &wright,
161 XPLMGetScreenSize(&wright, &wtop);
162 if (!y_from_bottom && y + height > wtop)
164 else if (y_from_bottom && y - height < 0)
166 if (x + width > wright)
171 if (!y_from_bottom) {
180 return (XPCreateWidget(x, y, right, bottom, visible, descr, root,
185find_first_monitor(
int idx,
int left,
int top,
int right,
int bottom,
192 if (mon->left == 0 && mon->right == 0 &&
193 mon->top == 0 && mon->bottom == 0) {
197 mon->bottom = bottom;
202lacf_get_first_monitor_bounds(
void)
206 memset(&mon, 0,
sizeof (mon));
208 XPLMGetAllMonitorBoundsGlobal(find_first_monitor, &mon);
209 if (mon.left == 0 && mon.right == 0 && mon.top == 0 &&
211 XPLMGetScreenBoundsGlobal(&mon.left, &mon.top, &mon.right,
219center_window_coords(
int *left,
int *top,
int *right,
int *bottom)
221 monitor_t mon = lacf_get_first_monitor_bounds();
222 int width = (*right) - (*left);
223 int height = (*top) - (*bottom);
225 *left = (mon.right + mon.left - width) / 2;
226 *right = (*left) + width;
227 *bottom = (mon.bottom + mon.top - height) / 2;
228 *top = (*bottom) + height;
232widget_win_center(XPWidgetID window)
235 classic_win_center(XPGetWidgetUnderlyingWindow(window));
239classic_win_center(XPLMWindowID window)
241 int left, top, right, bottom;
243 XPLMGetWindowGeometry(window, &left, &top, &right, &bottom);
244 center_window_coords(&left, &top, &right, &bottom);
245 XPLMSetWindowGeometry(window, left, top, right, bottom);
249tooltip_set_new(XPWidgetID window)
251 return (tooltip_set_new_native(XPGetWidgetUnderlyingWindow(window)));
255tooltip_set_new_native(XPLMWindowID window)
257 int left, top, right, bottom;
258 cairo_text_extents_t te;
260 tooltip_set_t *tts =
safe_calloc(1,
sizeof (*tts));
261 tts->window = window;
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;
282tooltip_set_orig_win_size(tooltip_set_t *tts,
unsigned orig_w,
unsigned orig_h)
287 tts->orig_w = orig_w;
288 tts->orig_h = orig_h;
292tooltip_set_delay(tooltip_set_t *set,
double secs)
295 set->display_delay = secs;
301 if (cur_tt != NULL) {
304 XPLMDestroyWindow(cur_tt_win);
315tooltip_set_destroy(tooltip_set_t *tts)
318 while ((tt =
list_head(&tts->tooltips)) != NULL) {
326 cairo_destroy(tts->cr);
327 cairo_surface_destroy(tts->surf);
332tooltip_set_font_face(tooltip_set_t *tts, cairo_font_face_t *font)
334 cairo_text_extents_t te;
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;
345tooltip_get_font_face(
const tooltip_set_t *tts)
348 return (tts->font_face);
352tooltip_set_font_size(tooltip_set_t *tts,
double size)
354 cairo_text_extents_t te;
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;
364tooltip_get_font_size(
const tooltip_set_t *tts)
367 return (tts->font_size);
371tooltip_new(tooltip_set_t *tts,
int x,
int y,
int w,
int h,
const char *text)
384static inline const char *
385find_whitespace(
const char *p,
const char *end)
387 while (!isspace(*p) && p < end)
392static cairo_text_extents_t
393tts_measure_string(
const tooltip_set_t *tts,
const char *text,
unsigned len)
395 cairo_text_extents_t ts;
402 strlcpy(buf, text, len + 1);
403 cairo_text_extents(tts->cr, buf, &ts);
410auto_wrap_text(
const tooltip_set_t *tts,
const char *text,
double max_width,
414 const char *start, *end;
422 end = text + strlen(text);
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;
429 if (width > max_width || *sp_prev ==
'\n') {
451set_cur_tt(
const tooltip_set_t *tts,
tooltip_t *tt,
int mouse_x,
int mouse_y)
453 int width = 2 * TOOLTIP_WINDOW_MARGIN;
454 int height = 2 * TOOLTIP_WINDOW_MARGIN;
455 XPLMCreateWindow_t cr = {
456 .structSize =
sizeof (cr),
458 .drawWindowFunc = tt_draw_cb,
459 .decorateAsFloatingWindow = xplm_WindowDecorationNone,
460 .layer = xplm_WindowLayerGrowlNotifications
470 cur_tt_lines = auto_wrap_text(tts, tt->text, TOOLTIP_WINDOW_WIDTH,
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);
481 tt_render_cb, NULL, (
void *)tts);
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;
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);
494 XPLMGetScreenBoundsGlobal(&lim_left, &lim_top, &lim_right,
497 if (cr.left < lim_left) {
498 int delta = lim_left - cr.left;
502 if (cr.right > lim_right) {
503 int delta = cr.right - lim_right;
507 if (cr.bottom < lim_bottom) {
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;
517 int delta = lim_bottom - cr.bottom;
521 }
else if (cr.top > lim_top) {
522 int delta = cr.top - lim_top;
526 cur_tt_win = XPLMCreateWindowEx(&cr);
532 return (cur_tts != NULL && !XPLMIsWindowInFront(cur_tts->window));
536tooltip_floop_cb(
float elapsed_since_last_call,
float elapsed_since_last_floop,
537 int counter,
void *refcon)
539 int mouse_x, mouse_y;
540 long long now = microclock();
541 tooltip_set_t *hit_tts = NULL;
544 LACF_UNUSED(elapsed_since_last_call);
545 LACF_UNUSED(elapsed_since_last_floop);
546 LACF_UNUSED(counter);
549 XPLMGetMouseLocationGlobal(&mouse_x, &mouse_y);
551 if (last_mouse_x != mouse_x || last_mouse_y != mouse_y ||
553 last_mouse_x = mouse_x;
554 last_mouse_y = mouse_y;
555 mouse_moved_time = now;
558 return (TOOLTIP_INTVAL);
562 return (TOOLTIP_INTVAL);
564 for (tooltip_set_t *tts =
list_head(&tooltip_sets); tts != NULL;
566 int wleft, wtop, wright, wbottom;
567 double scalex, scaley;
569 if (now - mouse_moved_time < SEC2USEC(tts->display_delay))
572 XPLMGetWindowGeometry(tts->window, &wleft, &wtop, &wright,
574 if (!XPLMGetWindowIsVisible(tts->window) ||
575 !XPLMIsWindowInFront(tts->window) ||
576 mouse_x < wleft || mouse_x > wright ||
577 mouse_y < wbottom || mouse_y > wtop)
580 scalex = (wright - wleft) / (
double)tts->orig_w;
581 scaley = (wtop - wbottom) / (
double)tts->orig_h;
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;
590 if (mouse_x >= x1 && mouse_x <= x2 &&
591 mouse_y >= y1 && mouse_y <= y2) {
600 set_cur_tt(hit_tts, hit_tt, mouse_x, mouse_y);
602 return (TOOLTIP_INTVAL);
612 offsetof(tooltip_set_t, node));
614 XPLMRegisterFlightLoopCallback(tooltip_floop_cb, TOOLTIP_INTVAL, NULL);
626 while ((tts =
list_head(&tooltip_sets)) != NULL)
627 tooltip_set_destroy(tts);
630 XPLMUnregisterFlightLoopCallback(tooltip_floop_cb, NULL);
636 static bool_t dr_looked_up = B_FALSE;
637 static dr_t VR_enabled;
640 dr_looked_up = B_TRUE;
641 fdr_find(&VR_enabled,
"sim/graphics/VR/enabled");
644 return (
dr_geti(&VR_enabled) != 0);
648window_follow_VR(XPLMWindowID win)
650 bool_t vr = is_in_VR();
653 XPLMWindowPositioningMode mode = (XPLMWindowIsPoppedOut(win) ?
654 xplm_WindowPopOut : xplm_WindowPositionFree);
656 mode = xplm_WindowVR;
658 XPLMSetWindowPositioningMode(win, mode, -1);
664widget_follow_VR(XPWidgetID win)
667 return (window_follow_VR(XPGetWidgetUnderlyingWindow(win)));
671 int left, top, right, bottom;
676find_window(
int idx,
int left,
int top,
int right,
int bottom,
void *refcon)
678 enum { MARGIN = 50 };
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;
692window_is_on_screen(XPLMWindowID win)
694 int left, top, right, bottom;
698 XPLMGetWindowGeometry(win, &info.left, &info.top, &info.right,
700 XPLMGetAllMonitorBoundsGlobal(find_window, &info);
705 if (!info.on_screen) {
706 enum { MARGIN = 50 };
707 XPLMGetScreenBoundsGlobal(&left, &top, &right, &bottom);
709 return (info.right > left + MARGIN &&
710 info.left < right - MARGIN &&
711 info.top > bottom + MARGIN &&
712 info.bottom < top - MARGIN);
720 unsigned norm_w,
unsigned norm_h)
728 ctl->norm_w = norm_w;
729 ctl->norm_h = norm_h;
730 ctl->w_h_ratio = norm_w / (double)norm_h;
732 XPLMGetWindowGeometry(win, &ctl->left, &ctl->top, &ctl->right,
738 XPLMSetWindowResizingLimits(win, norm_w / 10, norm_h / 10,
739 norm_w * 10, norm_h * 10);
760 int *left_p,
int *top_p,
int *right_p,
int *bottom_p,
761 vect2_t grav_pt, bool_t grav_horiz)
772 w = *right_p - *left_p;
773 h = *top_p - *bottom_p;
776 int new_h = w / ctl->w_h_ratio;
779 if (fabs(w / (
double)ctl->norm_w - 1.0) < 0.05) {
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;
791 scale = new_h / (double)h;
792 *top_p = grav_pt.y + (top - grav_pt.y) * scale;
793 *bottom_p = *top_p - new_h;
795 int new_w = h * ctl->w_h_ratio;
798 if (fabs(h / (
double)ctl->norm_h - 1.0) < 0.05) {
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;
809 scale = new_w / (double)w;
810 *right_p = grav_pt.x + (right - grav_pt.x) * scale;
811 *left_p = *right_p - new_w;
818 int left, top, right, bottom;
819 int new_left, new_top, new_right, new_bottom;
824 XPLMGetWindowGeometry(ctl->win, &left, &top, &right, &bottom);
830 if (left != ctl->left && top != ctl->top && right != ctl->right &&
831 bottom != ctl->bottom) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
878 calc_resize_geometry(ctl, top, right,
879 &new_left, &new_top, &new_right, &new_bottom,
880 VECT2(AVG(left, right), top), B_FALSE);
883 ctl->left = new_left = left;
884 ctl->top = new_top = top;
885 ctl->right = new_right = right;
886 ctl->bottom = new_bottom = bottom;
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;
895 ctl->bottom = new_bottom;
896 XPLMSetWindowGeometry(ctl->win, new_left, new_top, new_right,
#define ASSERT3P(x, op, y)
#define ASSERT3F(x, op, y)
#define mt_cairo_render_rounded_rectangle
static uint64_t delay_line_push_imm_u64(delay_line_t *line, uint64_t value)
static uint64_t delay_line_push_u64(delay_line_t *line, uint64_t value)
static void delay_line_init(delay_line_t *line, uint64_t delay_us)
void lacf_strlcpy(char *dest, const char *src, size_t cap)
void free_strlist(char **comps, size_t num)
void list_destroy(list_t *)
void * list_head(const list_t *)
void list_create(list_t *, size_t, size_t)
void * list_next(const list_t *, const void *)
void list_remove(list_t *, void *)
void list_insert_tail(list_t *, void *)
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)
static void * safe_calloc(size_t nmemb, size_t size)
static void * safe_malloc(size_t size)