10 #include <XPLMDisplay.h>
11 #include <XPLMProcessing.h>
13 #include <acfutils/helpers.h>
14 #include <acfutils/widget.h>
16 #include "libelec_types_impl.h"
21 #define WIN_HEIGHT 600
23 #define WIN_FPS_FAST 30
26 const elec_sys_t *sys;
27 mt_cairo_render_t *mtcr;
37 const elec_comp_t *highlight;
38 const elec_comp_t *selected;
40 XPLMFlightLoopID floop;
59 return (VECT2(3.5, 3.5));
61 if (info->gui.sz == 0)
63 return (VECT2(2, 1 + 2 * info->gui.sz));
73 cursor_coords_xlate(libelec_vis_t *vis,
int x,
int y)
75 int left, top, right, bottom, w, h;
80 XPLMGetWindowGeometry(vis->win, &left, &top, &right, &bottom);
86 mouse = vect2_scmul(VECT2(x, y), 1 / vis->zoom);
87 mouse = vect2_sub(mouse, vect2_scmul(vis->offset, 1 / vis->zoom));
88 mouse = vect2_scmul(mouse, 1 / vis->pos_scale);
93 static const elec_comp_t *
94 hit_test(libelec_vis_t *vis,
int x,
int y)
99 mouse = cursor_coords_xlate(vis, x, y);
101 for (
const elec_comp_t *comp = list_head(&vis->sys->comps);
102 comp != NULL; comp = list_next(&vis->sys->comps, comp)) {
105 pos = comp->info->gui.pos;
106 if (IS_NULL_VECT(pos) || comp->info->gui.virt ||
107 comp->info->gui.invis) {
110 sz = comp_info2sz(comp->info);
111 if (mouse.x >= pos.x - sz.x / 2 &&
112 mouse.x < pos.x + sz.x / 2 &&
113 mouse.y >= pos.y - sz.y / 2 &&
114 mouse.y < pos.y + sz.y / 2) {
123 draw_highlight(cairo_t *cr, libelec_vis_t *vis)
128 mutex_enter(&vis->lock);
129 if (vis->highlight != NULL) {
130 vect2_t pos = vis->highlight->info->gui.pos;
131 vect2_t sz = comp_info2sz(vis->highlight->info);
133 cairo_set_line_width(cr, 3);
134 cairo_set_source_rgb(cr, 0, 0, 0);
135 cairo_rectangle(cr, vis->pos_scale * (pos.x - sz.x / 2),
136 vis->pos_scale * (pos.y - sz.y / 2),
137 vis->pos_scale * sz.x, vis->pos_scale * sz.y);
140 cairo_set_line_width(cr, 2);
141 cairo_set_source_rgb(cr, 0, 1, 1);
142 cairo_rectangle(cr, vis->pos_scale * (pos.x - sz.x / 2),
143 vis->pos_scale * (pos.y - sz.y / 2),
144 vis->pos_scale * sz.x, vis->pos_scale * sz.y);
147 mutex_exit(&vis->lock);
151 draw_selected(cairo_t *cr, libelec_vis_t *vis)
156 mutex_enter(&vis->lock);
159 vis->font_sz, vis->selected->info->gui.pos);
161 mutex_exit(&vis->lock);
165 render_cb(cairo_t *cr,
unsigned w,
unsigned h,
void *userinfo)
172 ASSERT(userinfo != NULL);
175 cairo_select_font_face(cr,
"monospace", CAIRO_FONT_SLANT_NORMAL,
176 CAIRO_FONT_WEIGHT_NORMAL);
178 cairo_identity_matrix(cr);
180 cairo_translate(cr, w / 2, h / 2);
181 cairo_translate(cr, vis->offset.x, vis->offset.y);
182 cairo_scale(cr, vis->zoom, vis->zoom);
183 cairo_set_source_rgb(cr, 1, 1, 1);
187 draw_highlight(cr, vis);
188 draw_selected(cr, vis);
192 recreate_mtcr(libelec_vis_t *vis)
194 int left, top, right, bottom;
198 if (vis->mtcr != NULL)
199 mt_cairo_render_fini(vis->mtcr);
200 ASSERT(vis->win != NULL);
201 XPLMGetWindowGeometry(vis->win, &left, &top, &right, &bottom);
202 vis->mtcr = mt_cairo_render_init(right - left, top - bottom, WIN_FPS,
203 NULL, render_cb, NULL, vis);
207 win_click(XPLMWindowID win,
int x,
int y, XPLMMouseStatus mouse,
void *refcon)
209 int left, top, x_rel, y_rel;
215 ASSERT(refcon != NULL);
218 XPLMGetWindowGeometry(vis->win, &left, &top, NULL, NULL);
222 if (mouse == xplm_MouseDown)
223 vis->mouse_prev = vis->mouse_down = VECT2(x_rel, y_rel);
224 off = vect2_sub(VECT2(x_rel, y_rel), vis->mouse_prev);
225 vis->offset = vect2_add(vis->offset, off);
226 vis->mouse_prev = VECT2(x_rel, y_rel);
229 if (mouse == xplm_MouseDown || mouse == xplm_MouseDrag) {
230 if (mt_cairo_render_get_fps(vis->mtcr) != WIN_FPS_FAST)
231 mt_cairo_render_set_fps(vis->mtcr, WIN_FPS_FAST);
233 mt_cairo_render_set_fps(vis->mtcr, WIN_FPS);
235 if (mouse == xplm_MouseUp &&
236 vect2_abs(vect2_sub(vis->mouse_down, vis->mouse_prev)) < 4) {
237 mutex_enter(&vis->lock);
238 vis->selected = hit_test(vis, x, y);
239 mutex_exit(&vis->lock);
246 win_wheel(XPLMWindowID win,
int x,
int y,
int wheel,
int clicks,
void *refcon)
248 int left, top, right, bottom, w, h;
254 ASSERT(refcon != NULL);
261 XPLMGetWindowGeometry(vis->win, &left, &top, &right, &bottom);
271 clicks = MIN(clicks, 0);
273 clicks = MAX(clicks, 0);
274 for (; clicks > 0; clicks--) {
276 vis->offset = vect2_scmul(vis->offset, 1.25);
278 for (; clicks < 0; clicks++) {
280 vis->offset = vect2_scmul(vis->offset, 1.0 / 1.25);
287 win_draw(XPLMWindowID win,
void *refcon)
289 int left, top, right, bottom, w, h;
293 ASSERT(refcon != NULL);
296 XPLMGetWindowGeometry(vis->win, &left, &top, &right, &bottom);
299 ASSERT(vis->mtcr != NULL);
300 if (w != (
int)mt_cairo_render_get_width(vis->mtcr) ||
301 h != (
int)mt_cairo_render_get_height(vis->mtcr)) {
304 mt_cairo_render_draw(vis->mtcr, VECT2(left, bottom), VECT2(w, h));
307 static XPLMCursorStatus
308 win_cursor(XPLMWindowID win,
int x,
int y,
void *refcon)
313 ASSERT(refcon != NULL);
316 mutex_enter(&vis->lock);
317 vis->highlight = hit_test(vis, x, y);
318 mutex_exit(&vis->lock);
320 return (xplm_CursorArrow);
324 vis_floop_cb(UNUSED_ATTR
float unused1, UNUSED_ATTR
float unused2,
325 UNUSED_ATTR
int unused3,
void *refcon)
329 ASSERT(refcon != NULL);
333 mt_cairo_render_fini(vis->mtcr);
367 libelec_vis_t *vis = safe_calloc(1,
sizeof (*vis));
368 XPLMCreateWindow_t cr = {
369 .structSize =
sizeof (cr),
371 .top = 100 + WIN_HEIGHT,
372 .right = 100 + WIN_WIDTH,
374 .handleMouseClickFunc = win_click,
375 .handleMouseWheelFunc = win_wheel,
376 .handleCursorFunc = win_cursor,
377 .drawWindowFunc = win_draw,
378 .decorateAsFloatingWindow = xplm_WindowDecorationRoundRectangle,
379 .layer = xplm_WindowLayerFloatingWindows,
382 XPLMCreateFlightLoop_t floop = {
383 .structSize =
sizeof (floop),
384 .phase = xplm_FlightLoop_Phase_BeforeFlightModel,
385 .callbackFunc = vis_floop_cb,
392 vis->win = XPLMCreateWindowEx(&cr);
393 ASSERT(vis->win != NULL);
394 vis->pos_scale = pos_scale;
395 vis->font_sz = font_sz;
397 mutex_init(&vis->lock);
398 vis->floop = XPLMCreateFlightLoop(&floop);
400 XPLMSetWindowTitle(vis->win,
"Electrical Network");
401 XPLMSetWindowResizingLimits(vis->win, 200, 200, 100000, 100000);
402 classic_win_center(vis->win);
417 if (vis->mtcr != NULL)
418 mt_cairo_render_fini(vis->mtcr);
419 mutex_destroy(&vis->lock);
420 XPLMDestroyWindow(vis->win);
421 XPLMDestroyFlightLoop(vis->floop);
437 vis->offset = offset;
447 return (vis->offset);
457 ASSERT(vis->win != NULL);
458 return (XPLMGetWindowIsVisible(vis->win));
470 ASSERT(vis->win != NULL);
472 if (!XPLMGetWindowIsVisible(vis->win)) {
474 mt_cairo_render_once_wait(vis->mtcr);
475 XPLMSetWindowIsVisible(vis->win,
true);
476 XPLMScheduleFlightLoop(vis->floop, -1,
true);
478 window_follow_VR(vis->win);
495 ASSERT(vis->win != NULL);
496 XPLMSetWindowIsVisible(vis->win,
false);
void libelec_draw_comp_info(const elec_comp_t *comp, cairo_t *cr, double pos_scale, double font_sz, vect2_t pos)
void libelec_draw_layout(const elec_sys_t *sys, cairo_t *cr, double pos_scale, double font_sz)
void libelec_vis_destroy(libelec_vis_t *vis)
void libelec_vis_set_offset(libelec_vis_t *vis, vect2_t offset)
libelec_vis_t * libelec_vis_new(const elec_sys_t *sys, double pos_scale, double font_sz)
bool libelec_vis_is_open(libelec_vis_t *vis)
void libelec_vis_open(libelec_vis_t *vis)
void libelec_vis_close(libelec_vis_t *vis)
vect2_t libelec_vis_get_offset(const libelec_vis_t *vis)