FabGL
ESP32 Display Controller and Graphics Library
fabui.cpp
1/*
2 Created by Fabrizio Di Vittorio (fdivitto2013@gmail.com) - <http://www.fabgl.com>
3 Copyright (c) 2019-2022 Fabrizio Di Vittorio.
4 All rights reserved.
5
6
7* Please contact fdivitto2013@gmail.com if you need a commercial license.
8
9
10* This library and related software is available under GPL v3.
11
12 FabGL is free software: you can redistribute it and/or modify
13 it under the terms of the GNU General Public License as published by
14 the Free Software Foundation, either version 3 of the License, or
15 (at your option) any later version.
16
17 FabGL is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 GNU General Public License for more details.
21
22 You should have received a copy of the GNU General Public License
23 along with FabGL. If not, see <http://www.gnu.org/licenses/>.
24 */
25
26
27#include "freertos/FreeRTOS.h"
28#include "freertos/timers.h"
29
30#include <string.h>
31#include <ctype.h>
32#include <stdarg.h>
33
34#include "fabutils.h"
35#include "fabui.h"
36#include "canvas.h"
37#include "devdrivers/mouse.h"
38#include "devdrivers/keyboard.h"
39#include "images/bitmaps.h"
40
41
42
43#pragma GCC optimize ("O2")
44
45
46
47#define DUMPEVENTS 0
48
49
50namespace fabgl {
51
52
53
54// debug only!
55#if DUMPEVENTS
56void dumpEvent(uiEvent * event)
57{
58 static int idx = 0;
59 static const char * TOSTR[] = { "UIEVT_NULL", "UIEVT_DEBUGMSG", "UIEVT_APPINIT", "UIEVT_GENPAINTEVENTS", "UIEVT_PAINT", "UIEVT_ACTIVATE",
60 "UIEVT_DEACTIVATE", "UIEVT_MOUSEMOVE", "UIEVT_MOUSEWHEEL", "UIEVT_MOUSEBUTTONDOWN",
61 "UIEVT_MOUSEBUTTONUP", "UIEVT_SETPOS", "UIEVT_SETSIZE", "UIEVT_RESHAPEWINDOW",
62 "UIEVT_MOUSEENTER", "UIEVT_MOUSELEAVE", "UIEVT_MAXIMIZE", "UIEVT_MINIMIZE", "UIEVT_RESTORE",
63 "UIEVT_SHOW", "UIEVT_HIDE", "UIEVT_SETFOCUS", "UIEVT_KILLFOCUS", "UIEVT_KEYDOWN", "UIEVT_KEYUP", "UIEVT_KEYTYPE",
64 "UIEVT_TIMER", "UIEVT_CLICK", "UIEVT_DBLCLICK", "UIEVT_EXITMODAL", "UIEVT_DESTROY", "UIEVT_CLOSE",
65 "UIEVT_QUIT", "UIEVT_CREATE", "UIEVT_CHILDSETFOCUS", "UIEVT_CHILDKILLFOCUS"
66 };
67 printf("#%d ", idx++);
68 printf(TOSTR[event->id]);
69 if (event->dest && event->dest->objectType().uiFrame && ((uiFrame*)(event->dest))->title())
70 printf(" dst=\"%s\"(%p) ", ((uiFrame*)(event->dest))->title(), event->dest);
71 else
72 printf(" dst=%p ", event->dest);
73 if (event->dest) {
74 auto ot = event->dest->objectType();
75 printf("[");
76 if (ot.uiApp) printf("uiApp ");
77 if (ot.uiEvtHandler) printf("uiEvtHandler ");
78 if (ot.uiWindow) printf("uiWindow ");
79 if (ot.uiFrame) printf("uiFrame ");
80 if (ot.uiControl) printf("uiControl ");
81 if (ot.uiScrollableControl) printf("uiScrollableControl ");
82 if (ot.uiButton) printf("uiButton ");
83 if (ot.uiTextEdit) printf("uiTextEdit ");
84 if (ot.uiLabel) printf("uiLabel ");
85 if (ot.uiImage) printf("uiImage ");
86 if (ot.uiPanel) printf("uiPanel ");
87 if (ot.uiPaintBox) printf("uiPaintBox ");
88 if (ot.uiCustomListBox) printf("uiCustomListBox ");
89 if (ot.uiListBox) printf("uiListBox ");
90 if (ot.uiFileBrowser) printf("uiFileBrowser ");
91 if (ot.uiComboBox) printf("uiComboBox ");
92 if (ot.uiCheckBox) printf("uiCheckBox ");
93 if (ot.uiSlider) printf("uiSlider ");
94 if (ot.uiColorListBox) printf("uiColorListBox ");
95 if (ot.uiCustomComboBox) printf("uiCustomComboBox ");
96 if (ot.uiColorBox) printf("uiColorBox ");
97 if (ot.uiColorComboBox) printf("uiColorComboBox ");
98 if (ot.uiProgressBar) printf("uiProgressBar ");
99 if (ot.uiSplitButton) printf("uiSplitButton ");
100 if (ot.uiSimpleMenu) printf("uiSimpleMenu ");
101 printf("] ");
102 }
103 switch (event->id) {
104 case UIEVT_DEBUGMSG:
105 printf(event->params.debugMsg);
106 break;
107 case UIEVT_MOUSEMOVE:
108 printf("X=%d Y=%d", event->params.mouse.status.X, event->params.mouse.status.Y);
109 break;
110 case UIEVT_MOUSEWHEEL:
111 printf("delta=%d", event->params.mouse.status.wheelDelta);
112 break;
113 case UIEVT_MOUSEBUTTONDOWN:
114 case UIEVT_MOUSEBUTTONUP:
115 case UIEVT_DBLCLICK:
116 printf("btn=%d", event->params.mouse.changedButton);
117 break;
118 case UIEVT_PAINT:
119 case UIEVT_GENPAINTEVENTS:
120 case UIEVT_RESHAPEWINDOW:
121 printf("rect=%d,%d,%d,%d", event->params.rect.X1, event->params.rect.Y1, event->params.rect.X2, event->params.rect.Y2);
122 break;
123 case UIEVT_SETPOS:
124 printf("pos=%d,%d", event->params.pos.X, event->params.pos.Y);
125 break;
126 case UIEVT_SETSIZE:
127 printf("size=%d,%d", event->params.size.width, event->params.size.height);
128 break;
129 case UIEVT_KEYDOWN:
130 case UIEVT_KEYUP:
131 case UIEVT_KEYTYPE:
132 #ifdef FABGLIB_HAS_VirtualKeyO_STRING
133 printf("VK=%s ", Keyboard::virtualKeyToString(event->params.key.VK));
134 if (event->params.key.LALT) printf(" +LALT");
135 if (event->params.key.RALT) printf(" +RALT");
136 if (event->params.key.CTRL) printf(" +CTRL");
137 if (event->params.key.SHIFT) printf(" +SHIFT");
138 if (event->params.key.GUI) printf(" +GUI");
139 #endif
140 break;
141 case UIEVT_TIMER:
142 printf("handle=%p", event->params.timerHandle);
143 break;
144 default:
145 break;
146 }
147 printf("\n");
148}
149#endif
150
151
152
154// uiObject
155
156
157uiObject::uiObject()
158{
159}
160
161
162uiObject::~uiObject()
163{
164}
165
166
167// uiObject
169
170
171
173// uiEvtHandler
174
175
176uiEvtHandler::uiEvtHandler(uiApp * app)
177 : m_app(app)
178{
179 objectType().uiEvtHandler = true;
180}
181
182
183uiEvtHandler::~uiEvtHandler()
184{
185 if (m_app)
186 m_app->killEvtHandlerTimers(this);
187}
188
189
190void uiEvtHandler::processEvent(uiEvent * event)
191{
192 switch (event->id) {
193
194 default:
195 break;
196
197 }
198}
199
200
201// uiEvtHandler
203
204
205
207// uiApp
208
209
210uiApp::uiApp()
211 : uiEvtHandler(nullptr),
212 m_rootWindow(nullptr),
213 m_activeWindow(nullptr),
214 m_focusedWindow(nullptr),
215 m_lastFocusedWindow(nullptr),
216 m_capturedMouseWindow(nullptr),
217 m_freeMouseWindow(nullptr),
218 m_modalWindow(nullptr),
219 m_combineMouseMoveEvents(false),
220 m_keyDownHandler(nullptr),
221 m_caretWindow(nullptr),
222 m_caretTimer(nullptr),
223 m_caretInvertState(-1),
224 m_lastMouseUpTimeMS(0),
225 m_style(nullptr)
226{
227 objectType().uiApp = true;
228 setApp(this);
229}
230
231
232uiApp::~uiApp()
233{
234}
235
236
237int uiApp::run(BitmappedDisplayController * displayController, Keyboard * keyboard, Mouse * mouse)
238{
239 m_displayController = displayController;
240 m_displayColors = displayController->colorsCount();
241
242 m_canvas = new Canvas(m_displayController);
243
244 m_keyboard = keyboard;
245 m_mouse = mouse;
246 if (PS2Controller::initialized()) {
247 // get default keyboard and mouse from the PS/2 controller
248 if (m_keyboard == nullptr)
249 m_keyboard = PS2Controller::keyboard();
250 if (m_mouse == nullptr)
251 m_mouse = PS2Controller::mouse();
252 }
253
254 m_eventsQueue = xQueueCreate(FABGLIB_UI_EVENTS_QUEUE_SIZE, sizeof(uiEvent));
255
256 // setup absolute events from mouse
257 if (m_mouse && m_mouse->isMouseAvailable())
258 m_mouse->setupAbsolutePositioner(m_canvas->getWidth(), m_canvas->getHeight(), false, m_displayController, this);
259
260 // setup keyboard
261 if (m_keyboard)
262 m_keyboard->setUIApp(this);
263
264 // root window always stays at 0, 0 and cannot be moved
265 m_rootWindow = new uiFrame(nullptr, "", Point(0, 0), Size(m_canvas->getWidth(), m_canvas->getHeight()), false);
266 m_rootWindow->setApp(this);
267 m_rootWindow->setCanvas(m_canvas);
268
269 m_rootWindow->windowStyle().borderSize = 0;
270 m_rootWindow->frameStyle().backgroundColor = RGB888(255, 255, 255);
271
272 m_rootWindow->frameProps().resizeable = false;
273 m_rootWindow->frameProps().moveable = false;
274
275 // setup mouse cursor (otherwise it has to wait mouse first moving to show mouse pointer)
276 if (m_mouse && m_mouse->isMouseAvailable())
277 m_displayController->setMouseCursor(m_rootWindow->windowStyle().defaultCursor);
278
279 // avoid slow paint on low resolutions
280 m_displayController->enableBackgroundPrimitiveTimeout(false);
281
282 m_lastUserActionTimeMS = esp_timer_get_time() / 1000;
283
284 showWindow(m_rootWindow, true);
285
286 m_activeWindow = m_rootWindow;
287
288 // generate UIEVT_APPINIT event
289 uiEvent evt = uiEvent(this, UIEVT_APPINIT);
290 postEvent(&evt);
291
292 int exitCode = 0;
293
294 // dispatch events
295 while (true) {
296 uiEvent event;
297 if (getEvent(&event, -1)) {
298
299 preprocessEvent(&event);
300
301 #if DUMPEVENTS
302 printf("run(): ");
303 dumpEvent(&event);
304 #endif
305
306 if (event.dest)
307 event.dest->processEvent(&event);
308
309 if (event.id == UIEVT_QUIT) {
310 exitCode = event.params.exitCode;
311 break;
312 }
313 }
314 }
315
316 killEvtHandlerTimers(this);
317
318 showCaret(nullptr);
319
320 m_displayController->setMouseCursor(nullptr);
321
322 if (m_rootWindow->frameProps().fillBackground) {
323 m_canvas->setBrushColor(m_rootWindow->frameStyle().backgroundColor);
324 m_canvas->clear();
325 }
326
327 delete m_rootWindow;
328 m_rootWindow = nullptr;
329
330 if (m_keyboard)
331 m_keyboard->setUIApp(nullptr);
332
333 if (m_mouse && m_mouse->isMouseAvailable())
335
336 vQueueDelete(m_eventsQueue);
337 m_eventsQueue = nullptr;
338
339 delete m_canvas;
340
341 return exitCode;
342}
343
344
345void uiApp::asyncRunTask(void * arg)
346{
347 auto app = (uiApp*)arg;
348 app->run(app->m_displayController, app->m_keyboard, app->m_mouse);
349 if (app->m_asyncRunWait)
350 xSemaphoreGive(app->m_asyncRunWait);
351 vTaskDelete(NULL);
352}
353
354
355uiApp & uiApp::runAsync(BitmappedDisplayController * displayController, int taskStack, Keyboard * keyboard, Mouse * mouse)
356{
357 m_displayController = displayController;
358 m_keyboard = keyboard;
359 m_mouse = mouse;
360 m_asyncRunWait = nullptr;
361
362 if (CoreUsage::busiestCore() == -1)
363 xTaskCreate(&asyncRunTask, "", taskStack, this, 5, nullptr);
364 else
365 xTaskCreatePinnedToCore(&asyncRunTask, "", taskStack, this, 5, nullptr, CoreUsage::quietCore());
366
367 return *this;
368}
369
370
372{
373 m_asyncRunWait = xSemaphoreCreateBinary();
374 xSemaphoreTake(m_asyncRunWait, portMAX_DELAY);
375 vSemaphoreDelete(m_asyncRunWait);
376 m_asyncRunWait = nullptr;
377}
378
379
381{
382 uiEvent event;
383 while (getEvent(&event, 0)) {
384
385 preprocessEvent(&event);
386
387 #if DUMPEVENTS
388 printf("processEvents(): ");
389 dumpEvent(&event);
390 #endif
391
392 if (event.dest)
393 event.dest->processEvent(&event);
394 }
395}
396
397
398void uiApp::quit(int exitCode)
399{
400 for (auto child = m_rootWindow->lastChild(); child; child = child->prev())
401 destroyWindow(child);
402 uiEvent evt = uiEvent(nullptr, UIEVT_QUIT);
403 evt.params.exitCode = exitCode;
404 postEvent(&evt);
405}
406
407
408void uiApp::preprocessEvent(uiEvent * event)
409{
410 if (event->dest == nullptr) {
411 // events with no destination
412 switch (event->id) {
413 case UIEVT_MOUSEMOVE:
414 case UIEVT_MOUSEWHEEL:
415 case UIEVT_MOUSEBUTTONDOWN:
416 case UIEVT_MOUSEBUTTONUP:
417 preprocessMouseEvent(event);
418 break;
419 case UIEVT_KEYDOWN:
420 case UIEVT_KEYUP:
421 preprocessKeyboardEvent(event);
422 break;
423 default:
424 break;
425 }
426 } else {
427 // events with destination
428 switch (event->id) {
429 case UIEVT_TIMER:
430 if (event->params.timerHandle == m_caretTimer) {
431 blinkCaret();
432 event->dest = nullptr; // do not send this event to the root window
433 }
434 break;
435 case UIEVT_PAINT:
436 blinkCaret(true);
437 break;
438 default:
439 break;
440 }
441 }
442 if (m_modalWindow != nullptr)
443 filterModalEvent(event);
444}
445
446
447// decide if an event with destination to non-modal windows can pass when a modal window is active
448void uiApp::filterModalEvent(uiEvent * event)
449{
450 if (event->dest != nullptr &&
451 event->dest->objectType().uiWindow &&
452 event->dest != m_modalWindow &&
453 event->dest != m_activeWindow &&
454 event->dest != m_focusedWindow &&
455 !m_modalWindow->isChild((uiWindow*)event->dest)) {
456 switch (event->id) {
457 case UIEVT_MOUSEMOVE:
458 case UIEVT_MOUSEWHEEL:
459 case UIEVT_MOUSEBUTTONDOWN:
460 case UIEVT_MOUSEBUTTONUP:
461 case UIEVT_MOUSEENTER:
462 case UIEVT_DBLCLICK:
463 // block these events
464 event->dest = nullptr;
465 if (event->id == UIEVT_MOUSEENTER) {
466 // a little hack to set the right mouse pointer exiting from modal window
467 m_displayController->setMouseCursor(m_modalWindow->windowStyle().defaultCursor);
468 }
469 break;
470
471 default:
472 break;
473 }
474 }
475}
476
477
478// look for destination window at event X, Y coordinates, then set "dest" field and modify mouse X, Y coordinates (convert to window coordinates)
479// generate UIEVT_MOUSEENTER and UIEVT_MOUSELEAVE events
480void uiApp::preprocessMouseEvent(uiEvent * event)
481{
482 // combine a sequence of UIEVT_MOUSEMOVE events?
483 if (m_combineMouseMoveEvents && event->id == UIEVT_MOUSEMOVE) {
484 uiEvent nextEvent;
485 while (peekEvent(&nextEvent, 0) && nextEvent.id == UIEVT_MOUSEMOVE)
486 getEvent(event, -1);
487 }
488
489 m_lastUserActionTimeMS = esp_timer_get_time() / 1000;
490
491 Point mousePos = Point(event->params.mouse.status.X, event->params.mouse.status.Y);
492
493 // search for window under the mouse or mouse capturing window
494 // insert UIEVT_MOUSELEAVE when mouse capture is finished over a non-capturing window
495 uiWindow * oldFreeMouseWindow = m_freeMouseWindow;
496 Point winMousePos = mousePos;
497 if (m_capturedMouseWindow) {
498 // mouse captured, just go back up to m_rootWindow
499 for (uiWindow * cur = m_capturedMouseWindow; cur != m_rootWindow; cur = cur->parent())
500 winMousePos = winMousePos.sub(cur->pos());
501 event->dest = m_capturedMouseWindow;
502 // left mouse button UP?
503 if (event->id == UIEVT_MOUSEBUTTONUP && event->params.mouse.changedButton == 1) {
504 // mouse up will end mouse capture, check that mouse is still inside
505 if (!m_capturedMouseWindow->rect(uiOrigin::Window).contains(winMousePos)) {
506 // mouse is not inside, post mouse leave and enter events
507 uiEvent evt = uiEvent(m_capturedMouseWindow, UIEVT_MOUSELEAVE);
508 postEvent(&evt);
509 m_freeMouseWindow = oldFreeMouseWindow = nullptr;
510 }
511 captureMouse(nullptr);
512 }
513 } else {
514 m_freeMouseWindow = screenToWindow(winMousePos); // translates winMousePos
515 event->dest = m_freeMouseWindow;
516 }
517 event->params.mouse.status.X = winMousePos.X;
518 event->params.mouse.status.Y = winMousePos.Y;
519
520 // insert UIEVT_MOUSEENTER and UIEVT_MOUSELEAVE events
521 if (oldFreeMouseWindow != m_freeMouseWindow) {
522 if (m_freeMouseWindow) {
523 uiEvent evt = uiEvent(m_freeMouseWindow, UIEVT_MOUSEENTER);
524 insertEvent(&evt);
525 }
526 if (oldFreeMouseWindow) {
527 uiEvent evt = uiEvent(oldFreeMouseWindow, UIEVT_MOUSELEAVE);
528 insertEvent(&evt);
529 }
530 }
531
532 // double click?
533 if (event->id == UIEVT_MOUSEBUTTONUP && event->params.mouse.changedButton == 1) {
534 int curTime = esp_timer_get_time() / 1000; // uS -> MS
535 if (m_lastMouseUpPos == mousePos && curTime - m_lastMouseUpTimeMS <= m_appProps.doubleClickTime) {
536 // post double click message
537 uiEvent evt = *event;
538 evt.id = UIEVT_DBLCLICK;
539 postEvent(&evt);
540 }
541 m_lastMouseUpTimeMS = curTime;
542 m_lastMouseUpPos = mousePos;
543 }
544}
545
546
547void uiApp::preprocessKeyboardEvent(uiEvent * event)
548{
549 m_lastUserActionTimeMS = esp_timer_get_time() / 1000;
550
551 // keyboard events go to focused window
552 if (m_focusedWindow) {
553 event->dest = m_focusedWindow;
554 }
555 // keyboard events go also to active window (if not focused)
556 if (m_focusedWindow != m_activeWindow) {
557 uiEvent evt = *event;
558 evt.dest = m_activeWindow;
559 insertEvent(&evt);
560 }
561 // eventually produce UIEVT_KEYTYPE if keydown and keyup delivered to the same window
562 if (event->id == UIEVT_KEYDOWN)
563 m_keyDownHandler = event->dest;
564 else if (event->id == UIEVT_KEYUP && m_keyDownHandler == event->dest) {
565 uiEvent evt = uiEvent(event->dest, UIEVT_KEYTYPE);
566 evt.params.key = event->params.key;
567 postEvent(&evt);
568 }
569}
570
571
572// allow a window to capture mouse. window = nullptr to end mouse capture
573void uiApp::captureMouse(uiWindow * window)
574{
575 m_capturedMouseWindow = window;
576 if (m_capturedMouseWindow)
577 suspendCaret(true);
578 else
579 suspendCaret(false);
580}
581
582
583// convert screen coordinates to window coordinates (the most visible window)
584// return the window under specified absolute coordinates
586{
587 uiWindow * win = m_rootWindow;
588 while (win->hasChildren()) {
589 uiWindow * child = win->lastChild();
590 for (; child; child = child->prev()) {
591 if (child->state().visible && win->clientRect(uiOrigin::Window).contains(point) && child->rect(uiOrigin::Parent).contains(point)) {
592 win = child;
593 point = point.sub(child->pos());
594 break;
595 }
596 }
597 if (child == nullptr)
598 break;
599 }
600 return win;
601}
602
603
605{
606}
607
608
609bool uiApp::postEvent(uiEvent const * event)
610{
611 return xQueueSendToBack(m_eventsQueue, event, 0) == pdTRUE;
612}
613
614
615bool uiApp::insertEvent(uiEvent const * event)
616{
617 return xQueueSendToFront(m_eventsQueue, event, 0) == pdTRUE;
618}
619
620
621void uiApp::postDebugMsg(char const * msg)
622{
623 uiEvent evt = uiEvent(nullptr, UIEVT_DEBUGMSG);
624 evt.params.debugMsg = msg;
625 postEvent(&evt);
626}
627
628
629bool uiApp::getEvent(uiEvent * event, int timeOutMS)
630{
631 return xQueueReceive(m_eventsQueue, event, msToTicks(timeOutMS)) == pdTRUE;
632}
633
634
635bool uiApp::peekEvent(uiEvent * event, int timeOutMS)
636{
637 return xQueuePeek(m_eventsQueue, event, msToTicks(timeOutMS)) == pdTRUE;
638}
639
640
641void uiApp::processEvent(uiEvent * event)
642{
643 uiEvtHandler::processEvent(event);
644
645 switch (event->id) {
646
647 case UIEVT_APPINIT:
648 init();
649 break;
650
651 case UIEVT_TIMER:
652 onTimer(event->params.timerHandle);
653 break;
654
655 default:
656 break;
657
658 }
659}
660
661
663{
664 // move on top of the children
665 uiWindow * prev = m_activeWindow;
666
667 if (value != m_activeWindow) {
668 // is "value" window activable? If not turn "value" to the first activable parent
669 while (value && !value->m_windowProps.activable) {
670 value = value->m_parent;
671 if (!value)
672 return prev; // no parent is activable
673 }
674 if (value == m_activeWindow)
675 return prev; // already active, nothing to do
676
677 // changed active window, disable focus
678 setFocusedWindow(nullptr);
679
680 // ...and caret (setFocusedWindow() may not disable caret)
681 showCaret(nullptr);
682
683 m_activeWindow = value;
684
685 if (prev) {
686 // deactivate previous window
687 uiEvent evt = uiEvent(prev, UIEVT_DEACTIVATE);
688 postEvent(&evt);
689 }
690
691 if (m_activeWindow) {
692 // activate window
693 uiEvent evt = uiEvent(m_activeWindow, UIEVT_ACTIVATE);
694 postEvent(&evt);
695 }
696 }
697
698 return prev;
699}
700
701
702// value = nullptr -> kill focus on old focused window
703// value = focusable window -> kill focus on old focused window, set focus on new window
704// value = non-focusable window -> kill focus on old focused window (same of nullptr)
706{
707 uiWindow * prev = m_focusedWindow;
708
709 if (value && !value->isFocusable())
710 value = nullptr;
711
712 if (m_focusedWindow != value) {
713
714 if (prev) {
715
716 // assign m_lastFocusedWindow here because it is necessary it is not null
717 // that is the case when a change of active window occurs, or "value" pointed window is not focusable
718 m_lastFocusedWindow = prev;
719
720 uiEvent evt = uiEvent(prev, UIEVT_KILLFOCUS);
721 evt.params.focusInfo.oldFocused = m_lastFocusedWindow;
722 evt.params.focusInfo.newFocused = value;
723 postEvent(&evt);
724 if (prev->parent()) {
725 // send UIEVT_CHILDKILLFOCUS to its parent
726 evt = uiEvent(prev->parent(), UIEVT_CHILDKILLFOCUS);
727 evt.params.focusInfo.oldFocused = m_lastFocusedWindow;
728 evt.params.focusInfo.newFocused = value;
729 postEvent(&evt);
730 }
731 }
732
733 m_focusedWindow = value;
734
735 // changed focus, disable caret
736 showCaret(nullptr);
737
738 if (m_focusedWindow) {
739 uiEvent evt = uiEvent(m_focusedWindow, UIEVT_SETFOCUS);
740 evt.params.focusInfo.oldFocused = m_lastFocusedWindow;
741 evt.params.focusInfo.newFocused = m_focusedWindow;
742 postEvent(&evt);
743 if (m_focusedWindow->parent()) {
744 // send UIEVT_CHILDSETFOCUS to its parent
745 evt = uiEvent(m_focusedWindow->parent(), UIEVT_CHILDSETFOCUS);
746 evt.params.focusInfo.oldFocused = m_lastFocusedWindow;
747 evt.params.focusInfo.newFocused = m_focusedWindow;
748 postEvent(&evt);
749 }
750 }
751
752 }
753
754 return prev;
755}
756
757
758// delta = 1, go next focused index
759// delta = -1, go previous focused index
761{
762 if (delta) {
763 uiWindow * parent = m_focusedWindow ? m_focusedWindow->parentFrame() : m_activeWindow;
764 int startingIndex = m_focusedWindow ? m_focusedWindow->focusIndex() + delta : 0;
765 delta /= abs(delta); // from here delta must be in -1...+1 range
766 int newIndex = startingIndex;
767 do {
768 int maxIndex = -1;
769 uiWindow * newFocusedCtrl = parent->findChildWithFocusIndex(newIndex, &maxIndex);
770 if (maxIndex == -1) {
771 return m_focusedWindow; // no change
772 }
773 if (newFocusedCtrl) {
774 setFocusedWindow(newFocusedCtrl);
775 return newFocusedCtrl;
776 }
777 if (delta > 0)
778 newIndex = (newIndex >= maxIndex ? 0 : newIndex + delta);
779 else
780 newIndex = (newIndex <= 0 ? maxIndex : newIndex + delta);
781 } while (newIndex != startingIndex);
782 }
783 return m_focusedWindow; // no change
784}
785
786
788{
790}
791
792
793void uiApp::repaintRect(Rect const & rect)
794{
795 uiEvent evt = uiEvent(m_rootWindow, UIEVT_GENPAINTEVENTS);
796 evt.params.rect = rect;
797 postEvent(&evt);
798}
799
800
801// move to position (x, y) relative to the parent window
802void uiApp::moveWindow(uiWindow * window, int x, int y)
803{
804 reshapeWindow(window, Rect(x, y, x + window->size().width - 1, y + window->size().height - 1));
805}
806
807
808void uiApp::resizeWindow(uiWindow * window, int width, int height)
809{
810 reshapeWindow(window, window->rect(uiOrigin::Parent).resize(width, height));
811}
812
813
815{
816 reshapeWindow(window, window->rect(uiOrigin::Parent).resize(size));
817}
818
819
820// coordinates relative to the parent window
821void uiApp::reshapeWindow(uiWindow * window, Rect const & rect)
822{
823 uiEvent evt = uiEvent(window, UIEVT_RESHAPEWINDOW);
824 evt.params.rect = rect;
825 postEvent(&evt);
826}
827
828
829void uiApp::showWindow(uiWindow * window, bool value)
830{
831 window->m_state.visible = value; // set now so setFocusedWindow can focus a just made visible window
832 uiEvent evt = uiEvent(window, value ? UIEVT_SHOW : UIEVT_HIDE);
833 postEvent(&evt);
834}
835
836
837ModalWindowState * uiApp::initModalWindow(uiWindow * window)
838{
839 showWindow(window, true);
840
841 auto state = new ModalWindowState;
842 state->window = window;
843 state->modalResult = -1;
844 state->prevFocusedWindow = setFocusedWindow(nullptr);
845 state->prevActiveWindow = setActiveWindow(window);
846 state->prevModal = m_modalWindow;
847
848 return state;
849}
850
851
852// ret:
853// false = EXIT or CLOSE received, modal window should close (call endModalWindow)
854// true = other processModalWindowEvents required, continue outer loop
855bool uiApp::processModalWindowEvents(ModalWindowState * state, int timeout)
856{
857 // a new inner event loop...
858 uiEvent event;
859 while (getEvent(&event, timeout)) {
860
861 if (m_modalWindow != state->window && event.dest == state->window) {
862 // becomes modal when first message arrives
863 m_modalWindow = state->window;
864 }
865
866 preprocessEvent(&event);
867
868 #if DUMPEVENTS
869 printf("processModalWindowEvents(): ");
870 dumpEvent(&event);
871 #endif
872
873 if (event.id == UIEVT_EXITMODAL && event.dest == state->window) {
874 // clean exit using exitModal() method
875 state->modalResult = event.params.modalResult;
876 return false;
877 } else if (event.id == UIEVT_CLOSE) {
878 // exit using Close button (default return value remains -1)
879 return false;
880 }
881
882 if (event.dest)
883 event.dest->processEvent(&event);
884
885 }
886 return true;
887}
888
889
890// ret: modal result
891int uiApp::endModalWindow(ModalWindowState * state)
892{
893 m_modalWindow = state->prevModal;
894 setActiveWindow(state->prevActiveWindow);
895 showWindow(state->window, false);
896 setFocusedWindow(state->prevFocusedWindow);
897 int result = state->modalResult;
898 free(state);
899 return result;
900}
901
902
904{
905 auto state = initModalWindow(window);
906 while (processModalWindowEvents(state, -1))
907 ;
908 return endModalWindow(state);
909}
910
911
912void uiApp::maximizeFrame(uiFrame * frame, bool value)
913{
914 uiEvent evt = uiEvent(frame, value ? UIEVT_MAXIMIZE : UIEVT_RESTORE);
915 postEvent(&evt);
916}
917
918
919void uiApp::minimizeFrame(uiFrame * frame, bool value)
920{
921 uiEvent evt = uiEvent(frame, value ? UIEVT_MINIMIZE : UIEVT_RESTORE);
922 postEvent(&evt);
923}
924
925
926void uiApp::timerFunc(TimerHandle_t xTimer)
927{
928 uiEvtHandler * dest = (uiEvtHandler*) pvTimerGetTimerID(xTimer);
929 uiEvent evt = uiEvent(dest, UIEVT_TIMER);
930 evt.params.timerHandle = xTimer;
931 dest->app()->postEvent(&evt);
932}
933
934
935// return handler to pass to deleteTimer()
936uiTimerHandle uiApp::setTimer(uiEvtHandler * dest, int periodMS)
937{
938 TimerHandle_t h = xTimerCreate("", pdMS_TO_TICKS(periodMS), pdTRUE, dest, &uiApp::timerFunc);
939 m_timers.push_back(uiTimerAssoc(dest, h));
940 xTimerStart(h, 0);
941 return h;
942}
943
944
945void uiApp::killTimer(uiTimerHandle handle)
946{
947 auto dest = (uiEvtHandler *) pvTimerGetTimerID(handle);
948 m_timers.remove(uiTimerAssoc(dest, handle));
949 xTimerStop(handle, portMAX_DELAY);
950 xTimerDelete(handle, portMAX_DELAY);
951}
952
953
954void uiApp::killEvtHandlerTimers(uiEvtHandler * dest)
955{
956 for (auto t : m_timers)
957 if (t.first == dest) {
958 xTimerStop(t.second, portMAX_DELAY);
959 xTimerDelete(t.second, portMAX_DELAY);
960 }
961 m_timers.remove_if([&](uiTimerAssoc const & p) { return p.first == dest; });
962}
963
964
965// window = nullptr -> disable caret
966// "window" must be focused window (and top-level window, otherwise caret is painted wrongly)
967void uiApp::showCaret(uiWindow * window)
968{
969 if (m_caretWindow != window) {
970 if (window && window == m_focusedWindow) {
971 // enable caret
972 m_caretWindow = window;
973 m_caretTimer = setTimer(m_rootWindow, m_appProps.caretBlinkingTime);
974 m_caretInvertState = 0;
975 blinkCaret();
976 } else if (m_caretTimer) {
977 // disable caret
978 suspendCaret(true);
979 killTimer(m_caretTimer);
980 m_caretTimer = nullptr;
981 m_caretWindow = NULL;
982 }
983 }
984}
985
986
987void uiApp::suspendCaret(bool value)
988{
989 if (m_caretTimer) {
990 if (value) {
991 if (m_caretInvertState != -1) {
992 xTimerStop(m_caretTimer, 0);
993 blinkCaret(true); // force off
994 m_caretInvertState = -1;
995 }
996 } else {
997 if (m_caretInvertState == -1) {
998 xTimerStart(m_caretTimer, 0);
999 m_caretInvertState = 0;
1000 blinkCaret();
1001 }
1002 }
1003 }
1004}
1005
1006
1007// just to force blinking
1008void uiApp::setCaret(bool value)
1009{
1010 blinkCaret(!value);
1011}
1012
1013
1014void uiApp::setCaret(Point const & pos)
1015{
1016 setCaret(m_caretRect.move(pos));
1017}
1018
1019
1020void uiApp::setCaret(Rect const & rect)
1021{
1022 blinkCaret(true);
1023 m_caretRect = rect;
1024 blinkCaret();
1025}
1026
1027
1028void uiApp::blinkCaret(bool forceOFF)
1029{
1030 if (m_caretWindow && m_caretInvertState != -1 && (forceOFF == false || m_caretInvertState == 1)) {
1031 m_canvas->resetPaintOptions();
1032 m_canvas->setOrigin(m_rootWindow->pos());
1033 m_canvas->setClippingRect(m_caretWindow->clientRect(uiOrigin::Screen));
1034 Rect aRect = m_caretWindow->transformRect(m_caretRect, m_rootWindow);
1035 m_canvas->invertRectangle(aRect);
1036 m_caretInvertState = m_caretInvertState ? 0 : 1;
1037 }
1038}
1039
1040
1041// this is the unique way to manually destroy a window
1043{
1044 if (window) {
1045 // first destroy children
1046 for (auto child = window->lastChild(); child; child = child->prev())
1047 destroyWindow(child);
1048 // is this window used for something?
1049 if (m_caretWindow == window)
1050 showCaret(nullptr);
1051 if (m_focusedWindow == window)
1052 setFocusedWindow(nullptr);
1053 if (m_activeWindow == window)
1054 setActiveWindow(nullptr);
1055 // to send Hide event and repaint area
1056 showWindow(window, false);
1057 // to actualy detach from parent and destroy the object
1058 uiEvent evt = uiEvent(window, UIEVT_DESTROY);
1059 postEvent(&evt);
1060 }
1061}
1062
1063
1064void uiApp::cleanWindowReferences(uiWindow * window)
1065{
1066 if (m_capturedMouseWindow == window)
1067 m_capturedMouseWindow = nullptr;
1068 if (m_freeMouseWindow == window)
1069 m_freeMouseWindow = nullptr;
1070 if (m_activeWindow == window)
1071 m_activeWindow = nullptr;
1072 if (m_focusedWindow == window)
1073 m_focusedWindow = nullptr;
1074 if (m_modalWindow == window)
1075 m_modalWindow = nullptr;
1076 if (m_caretWindow == window)
1077 m_caretWindow = nullptr;
1078}
1079
1080
1081uiMessageBoxResult uiApp::messageBox(char const * title, char const * text, char const * button1Text, char const * button2Text, char const * button3Text, uiMessageBoxIcon icon)
1082{
1083 auto font = &FONT_std_14;
1084 const int titleHeight = title && strlen(title) ? font->height : 0;
1085 const int textExtent = m_canvas->textExtent(font, text);
1086 const int button1Extent = button1Text ? m_canvas->textExtent(font, button1Text) + 10 : 0;
1087 const int button2Extent = button2Text ? m_canvas->textExtent(font, button2Text) + 10 : 0;
1088 const int button3Extent = button3Text ? m_canvas->textExtent(font, button3Text) + 10 : 0;
1089 const int buttonsWidth = imax(imax(imax(button1Extent, button2Extent), button3Extent), 40);
1090 int totButtons = 0;
1091 if (button1Extent)
1092 ++totButtons;
1093 if (button2Extent)
1094 ++totButtons;
1095 if (button3Extent)
1096 ++totButtons;
1097 const int buttonsHeight = font->height + 6;
1098 auto bitmap = (icon == uiMessageBoxIcon::Question ? &questionBitmap :
1099 (icon == uiMessageBoxIcon::Info ? &infoBitmap :
1100 (icon == uiMessageBoxIcon::Warning ? &warnBitmap :
1101 (icon == uiMessageBoxIcon::Error ? &errorBitmap : nullptr))));
1102 const int bitmapWidth = bitmap ? bitmap->width : 0;
1103 const int bitmapHeight = bitmap ? bitmap->height : 0;
1104 constexpr int buttonsSpace = 10;
1105 const int bitmapSpace = bitmap ? 8 : 0;
1106 const int textHeight = imax(font->height, bitmapHeight);
1107 const int requiredWidth = imin(imax(bitmapWidth + bitmapSpace + textExtent + 10, buttonsWidth * totButtons + (2 + buttonsSpace) * totButtons), m_canvas->getWidth());
1108 const int requiredHeight = textHeight + buttonsHeight + titleHeight + font->height * 3;
1109 const int frameX = (m_canvas->getWidth() - requiredWidth) / 2;
1110 const int frameY = (m_canvas->getHeight() - requiredHeight) / 2;
1111
1112 auto mainFrame = new uiFrame(m_rootWindow, title, Point(frameX, frameY), Size(requiredWidth, requiredHeight), false);
1113 mainFrame->frameProps().resizeable = false;
1114 mainFrame->frameProps().hasMaximizeButton = false;
1115 mainFrame->frameProps().hasMinimizeButton = false;
1116
1117 int x = (requiredWidth - bitmapWidth - bitmapSpace - textExtent) / 2;
1118 if (bitmap) {
1119 int y = font->height + titleHeight + (textHeight - bitmapHeight) / 2;
1120 new uiImage(mainFrame, bitmap, Point(x, y));
1121 x += bitmapWidth + bitmapSpace;
1122 }
1123
1124 int y = font->height + titleHeight + (textHeight - font->height) / 2;
1125 new uiLabel(mainFrame, text, Point(x, y));
1126
1127 // setup panel (where buttons are positioned)
1128
1129 y += textHeight + titleHeight;
1130 auto panel = new uiPanel(mainFrame, Point(0, y), Size(mainFrame->size().width, mainFrame->size().height - y));
1131 panel->windowStyle().borderColor = RGB888(128, 128, 128);
1132 panel->panelStyle().backgroundColor = mainFrame->frameStyle().backgroundColor;
1133
1134 // setup buttons
1135
1136 y = (panel->size().height - buttonsHeight) / 2;
1137 x = mainFrame->windowStyle().borderSize + requiredWidth - buttonsWidth * totButtons - buttonsSpace * totButtons; // right aligned
1138
1139 auto button1 = button1Text ? new uiButton(panel, button1Text, Point(x, y), Size(buttonsWidth, buttonsHeight)) : nullptr;
1140 if (button1) {
1141 button1->onClick = [&]() { mainFrame->exitModal(1); };
1142 x += buttonsWidth + buttonsSpace;
1143 }
1144
1145 auto button2 = button2Text ? new uiButton(panel, button2Text, Point(x, y), Size(buttonsWidth, buttonsHeight)) : nullptr;
1146 if (button2) {
1147 button2->onClick = [&]() { mainFrame->exitModal(2); };
1148 x += buttonsWidth + buttonsSpace;
1149 }
1150
1151 auto button3 = button3Text ? new uiButton(panel, button3Text, Point(x, y), Size(buttonsWidth, buttonsHeight)) : nullptr;
1152 if (button3) {
1153 button3->onClick = [&]() { mainFrame->exitModal(3); };
1154 x += buttonsWidth + buttonsSpace;
1155 }
1156
1157 // focus on fist button
1158 mainFrame->onShow = [&]() {
1159 setFocusedWindow(button1);
1160 };
1161
1162 // run
1163
1164 int modalResult = showModalWindow(mainFrame);
1165 destroyWindow(mainFrame);
1166
1167 switch (modalResult) {
1168 case 1:
1170 case 2:
1172 case 3:
1174 default:
1176 }
1177}
1178
1179
1180uiMessageBoxResult uiApp::inputBox(char const * title, char const * text, char * inOutString, int maxLength, char const * button1Text, char const * button2Text)
1181{
1182 auto font = &FONT_std_14;
1183 const int titleHeight = title && strlen(title) ? font->height : 0;
1184 const int textExtent = m_canvas->textExtent(font, text);
1185 const int editExtent = imin(maxLength * m_canvas->textExtent(font, "M"), m_rootWindow->clientSize().width / 2 - textExtent);
1186 const int button1Extent = button1Text ? m_canvas->textExtent(font, button1Text) + 10 : 0;
1187 const int button2Extent = button2Text ? m_canvas->textExtent(font, button2Text) + 10 : 0;
1188 const int buttonsWidth = imax(imax(button1Extent, button2Extent), 40);
1189 int totButtons = 0;
1190 if (button1Extent)
1191 ++totButtons;
1192 if (button2Extent)
1193 ++totButtons;
1194 const int buttonsHeight = font->height + 6;
1195 const int textHeight = font->height;
1196 constexpr int buttonsSpace = 10;
1197 const int requiredWidth = imin(imax(editExtent + textExtent + 10, buttonsWidth * totButtons + (2 + buttonsSpace) * totButtons), m_canvas->getWidth());
1198 const int requiredHeight = textHeight + buttonsHeight + titleHeight + font->height * 3;
1199 const int frameX = (m_canvas->getWidth() - requiredWidth) / 2;
1200 const int frameY = (m_canvas->getHeight() - requiredHeight) / 2;
1201
1202 auto mainFrame = new uiFrame(m_rootWindow, title, Point(frameX, frameY), Size(requiredWidth, requiredHeight), false);
1203 mainFrame->frameProps().resizeable = false;
1204 mainFrame->frameProps().hasMaximizeButton = false;
1205 mainFrame->frameProps().hasMinimizeButton = false;
1206 mainFrame->onKeyUp = [&](uiKeyEventInfo const & key) {
1207 if (key.VK == VK_RETURN || key.VK == VK_KP_ENTER)
1208 mainFrame->exitModal(1);
1209 else if (key.VK == VK_ESCAPE)
1210 mainFrame->exitModal(0);
1211 };
1212
1213 int x = 10;
1214 int y = font->height + titleHeight + (textHeight - font->height) / 2;
1215 new uiLabel(mainFrame, text, Point(x, y));
1216
1217 auto edit = new uiTextEdit(mainFrame, inOutString, Point(x + textExtent + 5, y - 4), Size(editExtent - 15, textHeight + 6));
1218
1219 // setup panel (where buttons are positioned)
1220
1221 y += textHeight + titleHeight;
1222 auto panel = new uiPanel(mainFrame, Point(0, y), Size(mainFrame->size().width, mainFrame->size().height - y));
1223 panel->windowStyle().borderColor = RGB888(128, 128, 128);
1224 panel->panelStyle().backgroundColor = mainFrame->frameStyle().backgroundColor;
1225
1226 // setup buttons
1227
1228 y = (panel->size().height - buttonsHeight) / 2;
1229 x = mainFrame->windowStyle().borderSize + requiredWidth - buttonsWidth * totButtons - buttonsSpace * totButtons; // right aligned
1230
1231 auto button1 = button1Text ? new uiButton(panel, button1Text, Point(x, y), Size(buttonsWidth, buttonsHeight)) : nullptr;
1232 if (button1) {
1233 button1->onClick = [&]() { mainFrame->exitModal(1); };
1234 x += buttonsWidth + buttonsSpace;
1235 }
1236
1237 auto button2 = button2Text ? new uiButton(panel, button2Text, Point(x, y), Size(buttonsWidth, buttonsHeight)) : nullptr;
1238 if (button2) {
1239 button2->onClick = [&]() { mainFrame->exitModal(2); };
1240 x += buttonsWidth + buttonsSpace;
1241 }
1242
1243 // focus on edit
1244 mainFrame->onShow = [&]() {
1245 setFocusedWindow(edit);
1246 };
1247
1248 // run
1249
1250 int modalResult = showModalWindow(mainFrame);
1251 destroyWindow(mainFrame);
1252
1253 switch (modalResult) {
1254 case 1:
1255 {
1256 int len = imin(maxLength, strlen(edit->text()));
1257 memcpy(inOutString, edit->text(), len);
1258 inOutString[len] = 0;
1260 }
1261 case 2:
1263 default:
1265 }
1266}
1267
1268
1269uiMessageBoxResult uiApp::fileDialog(char const * title, char * inOutDirectory, int maxDirNameSize, char * inOutFilename, int maxFileNameSize, char const * buttonOKText, char const * buttonCancelText, int frameWidth, int frameHeight)
1270{
1271 auto mainFrame = new uiFrame(m_rootWindow, title, UIWINDOW_PARENTCENTER, Size(frameWidth, frameHeight), false);
1272 mainFrame->frameProps().resizeable = false;
1273 mainFrame->frameProps().hasMaximizeButton = false;
1274 mainFrame->frameProps().hasMinimizeButton = false;
1275 mainFrame->onKeyUp = [&](uiKeyEventInfo const & key) {
1276 if (key.VK == VK_RETURN || key.VK == VK_KP_ENTER)
1277 mainFrame->exitModal(1);
1278 else if (key.VK == VK_ESCAPE)
1279 mainFrame->exitModal(0);
1280 };
1281
1282 int y = 25;
1283 constexpr int x = 8;
1284 constexpr int hh = 20;
1285 constexpr int dy = hh + 8;
1286 constexpr int lbloy = 3;
1287
1288 constexpr int fnBorder = 20;
1289 new uiLabel(mainFrame, "Filename", Point(x + fnBorder, y + lbloy));
1290 auto filenameEdit = new uiTextEdit(mainFrame, inOutFilename, Point(x + 50 + fnBorder, y), Size(frameWidth - x - 58 - fnBorder * 2, hh));
1291
1292 y += dy;
1293
1294 auto browser = new uiFileBrowser(mainFrame, Point(x, y), Size(frameWidth - x * 2, frameHeight - y - 35));
1295 browser->setDirectory(inOutDirectory);
1296 browser->onChange = [&]() {
1297 if (!browser->isDirectory()) {
1298 filenameEdit->setText(browser->filename());
1299 filenameEdit->repaint();
1300 }
1301 };
1302 browser->onDblClick = [&]() {
1303 if (!browser->isDirectory())
1304 mainFrame->exitModal(1);
1305 };
1306
1307 y += browser->clientSize().height + (dy - hh);
1308
1309 auto buttonCancelLen = m_canvas->textExtent(uiButtonStyle().textFont, buttonCancelText) + 10;
1310 auto buttonOKLen = m_canvas->textExtent(uiButtonStyle().textFont, buttonOKText) + 10;
1311
1312 auto buttonCancel = new uiButton(mainFrame, buttonCancelText, Point(frameWidth - buttonCancelLen - buttonOKLen - 20, y), Size(buttonCancelLen, hh));
1313 auto buttonOK = new uiButton(mainFrame, buttonOKText, Point(frameWidth - buttonOKLen - 8, y), Size(buttonOKLen, hh));
1314
1315 buttonCancel->onClick = [&]() { mainFrame->exitModal(0); };
1316 buttonOK->onClick = [&]() { mainFrame->exitModal(1); };
1317
1318 // focus on edit
1319 mainFrame->onShow = [&]() {
1320 setFocusedWindow(filenameEdit);
1321 };
1322
1323 int modalResult = showModalWindow(mainFrame);
1324 destroyWindow(mainFrame);
1325
1326 switch (modalResult) {
1327 case 1:
1328 {
1329 int len = imin(maxDirNameSize, strlen(browser->directory()));
1330 memcpy(inOutDirectory, browser->directory(), len);
1331 inOutDirectory[len] = 0;
1332
1333 len = imin(maxFileNameSize, strlen(filenameEdit->text()));
1334 memcpy(inOutFilename, filenameEdit->text(), len);
1335 inOutFilename[len] = 0;
1336
1338 }
1339 default:
1341 }
1342}
1343
1344
1346{
1347 if (value) {
1348 if (m_keyboard)
1349 m_keyboard->setUIApp(this);
1350 if (m_mouse && m_mouse->isMouseAvailable()) {
1351 m_mouse->setupAbsolutePositioner(m_canvas->getWidth(), m_canvas->getHeight(), false, m_displayController, this);
1352 m_displayController->setMouseCursor(m_rootWindow->windowStyle().defaultCursor);
1353 }
1354 } else {
1355 if (m_keyboard)
1356 m_keyboard->setUIApp(nullptr);
1357 if (m_mouse && m_mouse->isMouseAvailable()) {
1358 m_mouse->terminateAbsolutePositioner();
1359 m_displayController->setMouseCursor(nullptr);
1360 }
1361 }
1362}
1363
1364
1365// uiApp
1367
1368
1369
1371// uiWindow
1372
1373
1374uiWindow::uiWindow(uiWindow * parent, const Point & pos, const Size & size, bool visible, uint32_t styleClassID)
1375 : uiEvtHandler(parent ? parent->app() : nullptr),
1376 m_parent(parent),
1377 m_pos(pos),
1378 m_size(size),
1379 m_isMouseOver(false),
1380 m_styleClassID(styleClassID),
1381 m_next(nullptr),
1382 m_prev(nullptr),
1383 m_firstChild(nullptr),
1384 m_lastChild(nullptr),
1385 m_parentProcessKbdEvents(false)
1386{
1387 objectType().uiWindow = true;
1388
1389 m_state.visible = false;
1390 m_state.active = false;
1391
1392 if (app()) {
1393 m_windowStyle.adaptToDisplayColors(app()->displayColors());
1394 m_canvas = app()->canvas();
1395 if (app()->style() && styleClassID)
1396 app()->style()->setStyle(this, styleClassID);
1397 }
1398
1399 if (m_pos == UIWINDOW_PARENTCENTER) {
1400 if (parent) {
1401 m_pos = Point((parent->size().width - size.width) / 2, (parent->size().height - size.height) / 2);
1402 } else {
1403 m_pos = Point(0, 0);
1404 }
1405 }
1406
1407 if (parent)
1408 parent->addChild(this);
1409
1410 if (visible && app())
1411 app()->showWindow(this, true);
1412
1413 auto pframe = parentFrame();
1414 m_focusIndex = pframe ? ((uiFrame*)pframe)->getNextFreeFocusIndex() : 0;
1415
1416 if (app()) {
1417 uiEvent evt = uiEvent(this, UIEVT_CREATE);
1418 app()->postEvent(&evt);
1419 }
1420}
1421
1422
1423uiWindow::~uiWindow()
1424{
1425 freeChildren();
1426}
1427
1428
1429void uiWindow::freeChildren()
1430{
1431 for (uiWindow * next, * cur = m_firstChild; cur; cur = next) {
1432 next = cur->m_next;
1433 delete cur;
1434 }
1435 m_firstChild = m_lastChild = nullptr;
1436}
1437
1438
1439void uiWindow::addChild(uiWindow * child)
1440{
1441 if (m_firstChild) {
1442 // append after last child
1443 m_lastChild->m_next = child;
1444 child->m_prev = m_lastChild;
1445 m_lastChild = child;
1446 } else {
1447 // there are no children
1448 m_firstChild = m_lastChild = child;
1449 }
1450}
1451
1452
1453// insert child over (one position after) underlyingChild
1454// underlyingChild = nullptr, first position
1455void uiWindow::insertAfter(uiWindow * child, uiWindow * underlyingChild)
1456{
1457 if (!hasChildren()) {
1458 // this is the first child, just add
1459 addChild(child);
1460 return;
1461 }
1462 child->m_prev = underlyingChild;
1463 if (underlyingChild) {
1464 // insert before underlyingChild
1465 child->m_next = underlyingChild->m_next;
1466 if (child->m_next)
1467 child->m_next->m_prev = child;
1468 underlyingChild->m_next = child;
1469 if (m_lastChild == underlyingChild)
1470 m_lastChild = child;
1471 } else {
1472 // insert at first position (before m_firstChild)
1473 m_firstChild->m_prev = child;
1474 child->m_next = m_firstChild;
1475 m_firstChild = child;
1476 }
1477}
1478
1479
1480void uiWindow::removeChild(uiWindow * child, bool freeChild)
1481{
1482 if (child) {
1483 if (child == m_firstChild)
1484 m_firstChild = child->m_next;
1485 else
1486 child->m_prev->m_next = child->m_next;
1487
1488 if (child == m_lastChild)
1489 m_lastChild = child->m_prev;
1490 else
1491 child->m_next->m_prev = child->m_prev;
1492
1493 if (freeChild) {
1494 delete child;
1495 app()->cleanWindowReferences(child);
1496 } else
1497 child->m_prev = child->m_next = nullptr;
1498
1499 }
1500}
1501
1502
1503// move to the last position (top window)
1504void uiWindow::moveChildOnTop(uiWindow * child)
1505{
1506 removeChild(child, false);
1507 addChild(child);
1508}
1509
1510
1511// move child over (one position after) underlyingChild
1512// underlyingChild = nullptr, first position
1513void uiWindow::moveAfter(uiWindow * child, uiWindow * underlyingChild)
1514{
1515 removeChild(child, false);
1516 insertAfter(child, underlyingChild);
1517}
1518
1519
1521{
1522 parent()->moveChildOnTop(this);
1523}
1524
1525
1526void uiWindow::bringAfter(uiWindow * insertionPoint)
1527{
1528 parent()->moveAfter(this, insertionPoint);
1529}
1530
1531
1532// return true if "window" is a child (or descendant) of this window
1533bool uiWindow::isChild(uiWindow * window)
1534{
1535 for (auto child = firstChild(); child; child = child->next())
1536 if (child == window || (child->hasChildren() && child->isChild(window)))
1537 return true;
1538 return false;
1539}
1540
1541
1542// transform a rect relative to this window to a rect relative to the specified base window
1543Rect uiWindow::transformRect(Rect const & rect, uiWindow * baseWindow)
1544{
1545 Rect r = rect;
1546 for (uiWindow * win = this; win != baseWindow; win = win->m_parent)
1547 r = r.translate(win->m_pos);
1548 return r;
1549}
1550
1551
1552// rect is based on window coordinates
1553void uiWindow::repaint(Rect const & rect)
1554{
1555 app()->repaintRect(transformRect(rect, app()->rootWindow()));
1556}
1557
1558
1560{
1562}
1563
1564
1566{
1567 switch (origin) {
1568 case uiOrigin::Screen:
1569 return transformRect(Rect(0, 0, m_size.width - 1, m_size.height - 1), app()->rootWindow());
1570
1571 case uiOrigin::Parent:
1572 return Rect(m_pos.X, m_pos.Y, m_pos.X + m_size.width - 1, m_pos.Y + m_size.height - 1);
1573
1574 case uiOrigin::Window:
1575 return Rect(0, 0, m_size.width - 1, m_size.height - 1);
1576 }
1577 return Rect();
1578}
1579
1580
1582{
1583 int bSize = hasFocus() ? m_windowStyle.focusedBorderSize : m_windowStyle.borderSize;
1584 return rect(origin).shrink(bSize);
1585}
1586
1587
1589{
1590 return clientRect(uiOrigin::Window).size();
1591}
1592
1593
1595{
1596 return clientRect(uiOrigin::Window).pos();
1597}
1598
1599
1600void uiWindow::beginPaint(uiEvent * paintEvent, Rect const & clippingRect)
1601{
1602 Rect srect = rect(uiOrigin::Screen);
1603 canvas()->setOrigin(srect.X1, srect.Y1);
1604 canvas()->setClippingRect( clippingRect.intersection(paintEvent->params.rect) );
1605 canvas()->resetGlyphOptions();
1606 canvas()->resetPaintOptions();
1607}
1608
1609
1610void uiWindow::processEvent(uiEvent * event)
1611{
1612 uiEvtHandler::processEvent(event);
1613
1614 switch (event->id) {
1615
1616 case UIEVT_DESTROY:
1617 m_parent->removeChild(this);
1618 break;
1619
1620 case UIEVT_CLOSE:
1621 // for default a Close request just hides the window
1622 app()->showWindow(this, false);
1623 break;
1624
1625 case UIEVT_ACTIVATE:
1626 {
1627 m_state.active = true;
1628 uiWindow * winToRepaint = this;
1629 // move this window and parent windows on top (last position), and select the window to actually repaint
1630 for (uiWindow * child = this; child->parent() != nullptr; child = child->parent()) {
1631 if (child != child->parent()->lastChild()) {
1632 child->parent()->moveChildOnTop(child);
1633 winToRepaint = child;
1634 }
1635 }
1636 winToRepaint->repaint();
1637 break;
1638 }
1639
1640 case UIEVT_DEACTIVATE:
1641 m_state.active = false;
1642 repaint();
1643 break;
1644
1645 case UIEVT_MOUSEBUTTONDOWN:
1646 if (event->params.mouse.changedButton == 1) {
1647 // activate window? setActiveWindow() will activate the right window (maybe a parent)
1648 if (!m_state.active)
1649 app()->setActiveWindow(this);
1650 // focus window?
1651 app()->setFocusedWindow(this);
1652 // capture mouse
1653 app()->captureMouse(this);
1654 }
1655 break;
1656
1657 case UIEVT_MOUSEBUTTONUP:
1658 // end capture mouse if left button is up
1659 if (event->params.mouse.changedButton == 1) {
1660 // generate UIEVT_CLICK. The check is required to avoid onclick event when mouse is captured and moved out of button area
1661 if (rect(uiOrigin::Window).contains(event->params.mouse.status.X, event->params.mouse.status.Y)) {
1662 uiEvent evt = *event;
1663 evt.id = UIEVT_CLICK;
1664 app()->postEvent(&evt);
1665 }
1666 }
1667 break;
1668
1669 case UIEVT_SHOW:
1670 repaint();
1671 break;
1672
1673 case UIEVT_HIDE:
1674 repaint();
1675 break;
1676
1677 case UIEVT_RESHAPEWINDOW:
1678 reshape(event->params.rect);
1679 break;
1680
1681 case UIEVT_GENPAINTEVENTS:
1682 generatePaintEvents(event->params.rect);
1683 break;
1684
1685 case UIEVT_MOUSEENTER:
1686 m_isMouseOver = true;
1687 app()->displayController()->setMouseCursor(m_windowStyle.defaultCursor);
1688 break;
1689
1690 case UIEVT_MOUSELEAVE:
1691 m_isMouseOver = false;
1692 break;
1693
1694 case UIEVT_KEYDOWN:
1695 if (m_parentProcessKbdEvents)
1696 m_parent->processEvent(event);
1697 break;
1698
1699 case UIEVT_KEYUP:
1700 if (m_parentProcessKbdEvents)
1701 m_parent->processEvent(event);
1702 break;
1703
1704 case UIEVT_PAINT:
1705 beginPaint(event, rect(uiOrigin::Window));
1706 paintWindow();
1707 break;
1708
1709 case UIEVT_SETFOCUS:
1710 case UIEVT_KILLFOCUS:
1711 repaint(); // to update border
1712 break;
1713
1714 default:
1715 break;
1716 }
1717}
1718
1719
1720void uiWindow::paintWindow()
1721{
1722 // border
1723 int bSize = hasFocus() ? m_windowStyle.focusedBorderSize : m_windowStyle.borderSize;
1724 if (bSize > 0) {
1725 canvas()->setPenColor(hasFocus() ? m_windowStyle.focusedBorderColor : (state().active || windowProps().activeLook ? m_windowStyle.activeBorderColor : m_windowStyle.borderColor));
1726 for (int i = 0; i < bSize; ++i)
1727 canvas()->drawRectangle(i, i, m_size.width - 1 - i, m_size.height - 1 - i);
1728 }
1729}
1730
1731
1732// given a relative paint rect generate a set of UIEVT_PAINT events
1733void uiWindow::generatePaintEvents(Rect const & paintRect)
1734{
1735 app()->setCaret(false);
1736 Stack<Rect> rects;
1737 rects.push(paintRect);
1738 while (!rects.isEmpty()) {
1739 Rect thisRect = rects.pop();
1740 bool noIntesections = true;
1741 for (uiWindow * win = lastChild(); win; win = win->prev()) {
1742 Rect winRect = clientRect(uiOrigin::Window).intersection(win->rect(uiOrigin::Parent));
1743 if (win->state().visible && thisRect.intersects(winRect)) {
1744 noIntesections = false;
1745 removeRectangle(rects, thisRect, winRect);
1746 Rect newRect = thisRect.intersection(winRect).translate(-win->pos().X, -win->pos().Y);
1747 win->generatePaintEvents(newRect);
1748 break;
1749 }
1750 }
1751 if (noIntesections) {
1752 uiEvent evt = uiEvent(nullptr, UIEVT_PAINT);
1753 evt.dest = this;
1754 evt.params.rect = thisRect;
1755 // process event now. insertEvent() may dry events queue. On the other side, this may use too much stack!
1756 processEvent(&evt);
1757 }
1758 }
1759}
1760
1761
1762// insert/post UIEVT_PAINT, UIEVT_SETPOS and UIEVT_SETSIZE events in order to modify window bounding rect
1763// rect: new window rectangle based on parent coordinates
1764// handle anchors of its children
1765void uiWindow::reshape(Rect const & r)
1766{
1767 // new rect based on root window coordiantes
1768 Rect newRect = parent()->transformRect(r, app()->rootWindow());
1769
1770 // old rect based on root window coordinates
1771 Rect oldRect = rect(uiOrigin::Screen);
1772
1773 if (oldRect == newRect)
1774 return;
1775
1776 // set here because generatePaintEvents() requires updated window pos() and size()
1777 m_pos = Point(r.X1, r.Y1);
1778 m_size = r.size();
1779
1780 if (!oldRect.intersects(newRect)) {
1781 // old position and new position do not intersect, just repaint old rect
1782 app()->rootWindow()->generatePaintEvents(oldRect);
1783 } else {
1784 Stack<Rect> rects;
1785 removeRectangle(rects, oldRect, newRect); // remove newRect from oldRect
1786 while (!rects.isEmpty())
1787 app()->rootWindow()->generatePaintEvents(rects.pop());
1788 }
1789
1790 // generate set position event
1791 uiEvent evt = uiEvent(this, UIEVT_SETPOS);
1792 evt.params.pos = pos();
1793 app()->postEvent(&evt);
1794
1795 // generate set size event
1796 evt = uiEvent(this, UIEVT_SETSIZE);
1797 evt.params.size = size();
1798 app()->postEvent(&evt);
1799
1800 // handle children's anchors
1801 int dx = newRect.width() - oldRect.width();
1802 int dy = newRect.height() - oldRect.height();
1803 if (dx != 0 || dy != 0) {
1804 for (auto child = firstChild(); child; child = child->next()) {
1805 Rect childRect = child->rect(uiOrigin::Parent);
1806 Rect newChildRect = childRect;
1807 if (dx) {
1808 if (!child->m_anchors.left && !child->m_anchors.right) {
1809 // TODO: due the integer division the window may not stay at center when resizing by odd values. "ofs" is just a bad workaround
1810 int ofs = dx > 0 ? imax(1, dx / 2) : imin(-1, dx / 2);
1811 newChildRect.X1 += ofs;
1812 newChildRect.X2 += ofs;
1813 } else if (!child->m_anchors.left)
1814 newChildRect.X1 += dx;
1815 if (child->m_anchors.right)
1816 newChildRect.X2 += dx;
1817 }
1818 if (dy) {
1819 if (!child->m_anchors.top && !child->m_anchors.bottom) {
1820 // TODO: due the integer division the window may not stay at center when resizing by odd values. "ofs" is just a bad workaround
1821 int ofs = dy > 0 ? imax(1, dy / 2) : imin(-1, dy / 2);
1822 newChildRect.Y1 += ofs;
1823 newChildRect.Y2 += ofs;
1824 } else if (!child->m_anchors.top)
1825 newChildRect.Y1 += dy;
1826 if (child->m_anchors.bottom)
1827 newChildRect.Y2 += dy;
1828 }
1829 if (newChildRect != childRect) {
1830 uiEvent evt = uiEvent(child, UIEVT_RESHAPEWINDOW);
1831 evt.params.rect = newChildRect;
1832 app()->postEvent(&evt);
1833 }
1834 }
1835 }
1836
1837 app()->rootWindow()->generatePaintEvents(newRect);
1838}
1839
1840
1841void uiWindow::exitModal(int modalResult)
1842{
1843 uiEvent evt = uiEvent(this, UIEVT_EXITMODAL);
1844 evt.params.modalResult = modalResult;
1845 app()->postEvent(&evt);
1846}
1847
1848
1850{
1851 return app()->activeWindow() == this;
1852}
1853
1854
1856{
1857 return app()->focusedWindow() == this;
1858}
1859
1860
1861bool uiWindow::isFocusable()
1862{
1863 return windowProps().focusable && state().visible;
1864}
1865
1866
1867// set maxIndex = -1 at first call
1868uiWindow * uiWindow::findChildWithFocusIndex(int focusIndex, int * maxIndex)
1869{
1870 for (auto child = m_firstChild; child; child = child->m_next) {
1871 if (child->isFocusable()) {
1872 *maxIndex = imax(*maxIndex, child->m_focusIndex);
1873 if (child->m_focusIndex == focusIndex) {
1874 return child;
1875 }
1876 }
1877 if (child->hasChildren()) {
1878 auto r = child->findChildWithFocusIndex(focusIndex, maxIndex);
1879 if (r) {
1880 return r;
1881 }
1882 }
1883 }
1884 return nullptr;
1885}
1886
1887
1889{
1890 uiWindow * ret = m_parent;
1891 while (ret && ret->objectType().uiFrame == 0)
1892 ret = ret->parent();
1893 return ret;
1894}
1895
1896
1897// uiWindow
1899
1900
1901
1903// uiFrame
1904
1905
1906uiFrame::uiFrame(uiWindow * parent, char const * title, const Point & pos, const Size & size, bool visible, uint32_t styleClassID)
1907 : uiWindow(parent, pos, size, visible, 0),
1908 m_title(nullptr),
1909 m_titleLength(0),
1910 m_mouseDownFrameItem(uiFrameItem::None),
1911 m_mouseMoveFrameItem(uiFrameItem::None),
1912 m_lastReshapingBox(Rect(0, 0, 0, 0)),
1913 m_nextFreeFocusIndex(0),
1914 m_mouseDownPos(Point(-1, -1))
1915{
1916 objectType().uiFrame = true;
1917
1918 m_frameState.maximized = false;
1919 m_frameState.minimized = false;
1920
1921 if (app()) {
1922 m_frameStyle.adaptToDisplayColors(app()->displayColors());
1923 if (app()->style() && styleClassID)
1924 app()->style()->setStyle(this, styleClassID);
1925 }
1926 setTitle(title);
1927}
1928
1929
1930uiFrame::~uiFrame()
1931{
1932 free(m_title);
1933}
1934
1935
1936void uiFrame::setTitle(char const * value)
1937{
1938 if (value) {
1939 m_titleLength = strlen(value);
1940 m_title = (char*) realloc(m_title, m_titleLength + 1);
1941 strcpy(m_title, value);
1942 } else {
1943 free(m_title);
1944 m_title = nullptr;
1945 m_titleLength = 0;
1946 }
1947}
1948
1949
1950void uiFrame::setTitleFmt(const char *format, ...)
1951{
1952 va_list ap;
1953 va_start(ap, format);
1954 int size = vsnprintf(nullptr, 0, format, ap) + 1;
1955 if (size > 0) {
1956 va_end(ap);
1957 va_start(ap, format);
1958 char buf[size + 1];
1959 vsnprintf(buf, size, format, ap);
1960 setTitle(buf);
1961 }
1962 va_end(ap);
1963}
1964
1965
1966int uiFrame::titleBarHeight()
1967{
1968 return m_frameStyle.titleFont->height + 3;
1969}
1970
1971
1972Rect uiFrame::titleBarRect()
1973{
1975 r.Y2 = r.Y1 + titleBarHeight() - 1;
1976 return r;
1977}
1978
1979
1981{
1982 Rect r = uiWindow::clientRect(origin);
1983
1984 // title bar
1985 if (m_titleLength > 0)
1986 r.Y1 += titleBarHeight();
1987
1988 return r;
1989}
1990
1991
1992Size uiFrame::minWindowSize()
1993{
1994 Size r = Size(0, 0);
1995 if (m_frameProps.resizeable && !m_frameState.minimized && m_titleLength == 0) {
1996 r.width += CORNERSENSE * 2;
1997 r.height += CORNERSENSE * 2;
1998 }
1999 r.width += windowStyle().borderSize * 2;
2000 r.height += windowStyle().borderSize * 2;
2001 if (m_titleLength > 0) {
2002 int barHeight = titleBarHeight(); // titleBarHeight is also the button width
2003 r.height += barHeight;
2004 if (m_frameProps.hasCloseButton || m_frameProps.hasMaximizeButton || m_frameProps.hasMinimizeButton)
2005 r.width += barHeight * 3;
2006 r.width += barHeight * 4; // additional space to let some characters visible
2007 }
2008 return r;
2009}
2010
2011
2012// buttonIndex:
2013// 0 = close button
2014// 1 = maximize button
2015// 2 = minimize button
2016Rect uiFrame::getBtnRect(int buttonIndex)
2017{
2018 int btnSize = titleBarHeight(); // horiz and vert size of each button
2019 Rect barRect = titleBarRect();
2020 Rect btnRect = Rect(barRect.X2 - btnSize - CORNERSENSE / 2, barRect.Y1,
2021 barRect.X2 - CORNERSENSE / 2, barRect.Y2);
2022 while (buttonIndex--)
2023 btnRect = btnRect.translate(-btnSize, 0);
2024 return btnRect;
2025}
2026
2027
2028void uiFrame::paintFrame()
2029{
2030 Rect bkgRect = uiWindow::clientRect(uiOrigin::Window);
2031 // title bar
2032 if (m_titleLength > 0) {
2033 int barHeight = titleBarHeight();
2034 // title bar background
2035 RGB888 titleBarBrushColor = state().active || windowProps().activeLook ? m_frameStyle.activeTitleBackgroundColor : m_frameStyle.titleBackgroundColor;
2036 canvas()->setBrushColor(titleBarBrushColor);
2037 canvas()->fillRectangle(titleBarRect());
2038 // close, maximize and minimze buttons
2039 int btnX = paintButtons(bkgRect);
2040 // title
2041 canvas()->setPenColor(state().active || windowProps().activeLook ? m_frameStyle.activeTitleColor : m_frameStyle.titleColor);
2042 canvas()->setGlyphOptions(GlyphOptions().FillBackground(false).DoubleWidth(0).Bold(false).Italic(false).Underline(false).Invert(0));
2043 canvas()->drawTextWithEllipsis(m_frameStyle.titleFont, 1 + bkgRect.X1, 1 + bkgRect.Y1, m_title, btnX);
2044 // adjust background rect
2045 bkgRect.Y1 += barHeight;
2046 }
2047 // background
2048 if (m_frameProps.fillBackground && !m_frameState.minimized && bkgRect.width() > 0 && bkgRect.height() > 0) {
2049 canvas()->setBrushColor(m_frameStyle.backgroundColor);
2050 canvas()->fillRectangle(bkgRect);
2051 }
2052}
2053
2054
2055// return the X coordinate where button start
2056int uiFrame::paintButtons(Rect const & bkgRect)
2057{
2058 int buttonsX = bkgRect.X2;
2059 if (m_frameProps.hasCloseButton) {
2060 // close button
2061 Rect r = getBtnRect(0);
2062 buttonsX = r.X1;
2063 if (m_mouseMoveFrameItem == uiFrameItem::CloseButton) {
2064 canvas()->setBrushColor(m_frameStyle.mouseOverBackgroundButtonColor);
2065 canvas()->fillRectangle(r);
2066 canvas()->setPenColor(m_frameStyle.mouseOverButtonColor);
2067 } else
2068 canvas()->setPenColor(state().active || windowProps().activeLook ? m_frameStyle.activeButtonColor : m_frameStyle.buttonColor);
2069 r = r.shrink(4);
2070 canvas()->drawLine(r.X1, r.Y1, r.X2, r.Y2);
2071 canvas()->drawLine(r.X2, r.Y1, r.X1, r.Y2);
2072 }
2073 if (m_frameProps.hasMaximizeButton) {
2074 // maximize/restore button
2075 Rect r = getBtnRect(1);
2076 buttonsX = r.X1;
2077 if (m_mouseMoveFrameItem == uiFrameItem::MaximizeButton) {
2078 canvas()->setBrushColor(m_frameStyle.mouseOverBackgroundButtonColor);
2079 canvas()->fillRectangle(r);
2080 canvas()->setPenColor(m_frameStyle.mouseOverButtonColor);
2081 } else
2082 canvas()->setPenColor(state().active || windowProps().activeLook ? m_frameStyle.activeButtonColor : m_frameStyle.buttonColor);
2083 r = r.shrink(4);
2084 if (m_frameState.maximized || m_frameState.minimized) {
2085 // draw restore (from maximize or minimize) button
2086 r = r.shrink(1).translate(-1, +1);
2087 canvas()->drawRectangle(r);
2088 r = r.translate(+2, -2);
2089 canvas()->moveTo(r.X1, r.Y1 + 2);
2090 canvas()->lineTo(r.X1, r.Y1);
2091 canvas()->lineTo(r.X2, r.Y1);
2092 canvas()->lineTo(r.X2, r.Y2);
2093 canvas()->lineTo(r.X2 - 2, r.Y2);
2094 } else
2095 canvas()->drawRectangle(r);
2096 }
2097 if (m_frameProps.hasMinimizeButton && !m_frameState.minimized) {
2098 // minimize button
2099 Rect r = getBtnRect(2);
2100 buttonsX = r.X1;
2101 if (m_mouseMoveFrameItem == uiFrameItem::MinimizeButton) {
2102 canvas()->setBrushColor(m_frameStyle.mouseOverBackgroundButtonColor);
2103 canvas()->fillRectangle(r);
2104 canvas()->setPenColor(m_frameStyle.mouseOverButtonColor);
2105 } else
2106 canvas()->setPenColor(state().active || windowProps().activeLook ? m_frameStyle.activeButtonColor : m_frameStyle.buttonColor);
2107 r = r.shrink(4);
2108 int h = (r.Y2 - r.Y1 + 1) / 2;
2109 canvas()->drawLine(r.X1, r.Y1 + h, r.X2, r.Y1 + h);
2110 }
2111 return buttonsX;
2112}
2113
2114
2115void uiFrame::processEvent(uiEvent * event)
2116{
2117 uiWindow::processEvent(event);
2118
2119 switch (event->id) {
2120
2121 case UIEVT_PAINT:
2122 beginPaint(event, uiWindow::clientRect(uiOrigin::Window));
2123 paintFrame();
2124 onPaint();
2125 break;
2126
2127 case UIEVT_MOUSEBUTTONDOWN:
2128 if (event->params.mouse.changedButton == 1) {
2129 m_mouseDownPos = Point(event->params.mouse.status.X, event->params.mouse.status.Y);
2130 m_mouseDownFrameItem = getFrameItemAt(event->params.mouse.status.X, event->params.mouse.status.Y);
2131 m_sizeAtMouseDown = size();
2132 app()->combineMouseMoveEvents(true);
2133 }
2134 break;
2135
2136 case UIEVT_MOUSEBUTTONUP:
2137 if (event->params.mouse.changedButton == 1) {
2138 int mouseX = event->params.mouse.status.X;
2139 int mouseY = event->params.mouse.status.Y;
2140
2141 // this actually moves or resizes the window in case of non-realtime mode
2142 movingCapturedMouse(mouseX, mouseY, false);
2143
2144 // this sets the right mouse cursor in case of end of capturing
2145 movingFreeMouse(mouseX, mouseY);
2146
2147 // handle buttons clicks
2148 handleButtonsClick(mouseX, mouseY, false);
2149
2150 app()->combineMouseMoveEvents(false);
2151 }
2152 break;
2153
2154 case UIEVT_MOUSEMOVE:
2155 if (app()->capturedMouseWindow() == this)
2156 movingCapturedMouse(event->params.mouse.status.X, event->params.mouse.status.Y, true);
2157 else
2158 movingFreeMouse(event->params.mouse.status.X, event->params.mouse.status.Y);
2159 break;
2160
2161 case UIEVT_MOUSELEAVE:
2162 if (m_mouseMoveFrameItem == uiFrameItem::CloseButton)
2163 repaint(getBtnRect(0));
2164 if (m_mouseMoveFrameItem == uiFrameItem::MaximizeButton)
2165 repaint(getBtnRect(1));
2166 if (m_mouseMoveFrameItem == uiFrameItem::MinimizeButton)
2167 repaint(getBtnRect(2));
2168 m_mouseMoveFrameItem = uiFrameItem::None;
2169 break;
2170
2171 case UIEVT_DBLCLICK:
2172 handleButtonsClick(event->params.mouse.status.X, event->params.mouse.status.Y, true);
2173 break;
2174
2175 case UIEVT_SHOW:
2176 onShow();
2177 break;
2178
2179 case UIEVT_HIDE:
2180 onHide();
2181 break;
2182
2183 case UIEVT_MAXIMIZE:
2184 if (!m_frameState.minimized)
2185 m_savedScreenRect = rect(uiOrigin::Parent);
2186 m_frameState.maximized = true;
2187 m_frameState.minimized = false;
2189 break;
2190
2191 case UIEVT_MINIMIZE:
2192 if (!m_frameState.maximized)
2193 m_savedScreenRect = rect(uiOrigin::Parent);
2194 m_frameState.maximized = false;
2195 m_frameState.minimized = true;
2196 app()->resizeWindow(this, minWindowSize());
2197 break;
2198
2199 case UIEVT_RESTORE:
2200 m_frameState.maximized = false;
2201 m_frameState.minimized = false;
2202 app()->reshapeWindow(this, m_savedScreenRect);
2203 break;
2204
2205 case UIEVT_SETSIZE:
2206 onResize();
2207 break;
2208
2209 case UIEVT_TIMER:
2210 onTimer(event->params.timerHandle);
2211 break;
2212
2213 case UIEVT_KEYDOWN:
2214 // move focused child
2215 if (event->params.key.VK == VK_TAB) {
2216 if (event->params.key.SHIFT)
2217 app()->moveFocus(-1);
2218 else
2219 app()->moveFocus(1);
2220 }
2221 onKeyDown(event->params.key);
2222 break;
2223
2224 case UIEVT_KEYUP:
2225 onKeyUp(event->params.key);
2226 break;
2227
2228 default:
2229 break;
2230 }
2231}
2232
2233
2234uiFrameItem uiFrame::getFrameItemAt(int x, int y)
2235{
2236 Point p = Point(x, y);
2237
2238 if (m_titleLength > 0) {
2239 if (m_frameProps.hasCloseButton && getBtnRect(0).contains(p))
2240 return uiFrameItem::CloseButton; // on Close Button area
2241
2242 if (m_frameProps.hasMaximizeButton && getBtnRect(1).contains(p))
2243 return uiFrameItem::MaximizeButton; // on maximize button area
2244
2245 if (m_frameProps.hasMinimizeButton && !m_frameState.minimized && getBtnRect(2).contains(p))
2246 return uiFrameItem::MinimizeButton; // on minimize button area
2247 }
2248
2249 if (m_frameProps.resizeable && !m_frameState.maximized && !m_frameState.minimized) {
2250
2251 int w = size().width;
2252 int h = size().height;
2253
2254 // on top center, resize
2255 if (Rect(CORNERSENSE, 0, w - CORNERSENSE, windowStyle().borderSize).contains(p))
2256 return uiFrameItem::TopCenterResize;
2257
2258 // on left center side, resize
2259 if (Rect(0, CORNERSENSE, windowStyle().borderSize, h - CORNERSENSE).contains(p))
2260 return uiFrameItem::CenterLeftResize;
2261
2262 // on right center side, resize
2263 if (Rect(w - windowStyle().borderSize, CORNERSENSE, w - 1, h - CORNERSENSE).contains(p))
2264 return uiFrameItem::CenterRightResize;
2265
2266 // on bottom center, resize
2267 if (Rect(CORNERSENSE, h - windowStyle().borderSize, w - CORNERSENSE, h - 1).contains(p))
2268 return uiFrameItem::BottomCenterResize;
2269
2270 // on top left side, resize
2271 if (Rect(0, 0, CORNERSENSE, CORNERSENSE).contains(p))
2272 return uiFrameItem::TopLeftResize;
2273
2274 // on top right side, resize
2275 if (Rect(w - CORNERSENSE, 0, w - 1, CORNERSENSE).contains(p))
2276 return uiFrameItem::TopRightResize;
2277
2278 // on bottom left side, resize
2279 if (Rect(0, h - CORNERSENSE, CORNERSENSE, h - 1).contains(p))
2280 return uiFrameItem::BottomLeftResize;
2281
2282 // on bottom right side, resize
2283 if (Rect(w - CORNERSENSE, h - CORNERSENSE, w - 1, h - 1).contains(p))
2284 return uiFrameItem::BottomRightResize;
2285
2286 }
2287
2288 // on title bar, moving area
2289 if (m_titleLength > 0 && m_frameProps.moveable && !m_frameState.maximized && titleBarRect().contains(p))
2290 return uiFrameItem::MoveArea;
2291
2292 return uiFrameItem::None;
2293}
2294
2295
2296void uiFrame::movingCapturedMouse(int mouseX, int mouseY, bool mouseIsDown)
2297{
2298 int dx = mouseX - m_mouseDownPos.X;
2299 int dy = mouseY - m_mouseDownPos.Y;
2300
2301 Size minSize = minWindowSize();
2302
2303 Rect newRect = rect(uiOrigin::Parent);
2304
2305 switch (m_mouseDownFrameItem) {
2306
2307 case uiFrameItem::MoveArea:
2308 newRect = newRect.move(pos().X + dx, pos().Y + dy);
2309 break;
2310
2311 case uiFrameItem::CenterRightResize:
2312 newRect = newRect.resize(imax(m_sizeAtMouseDown.width + dx, minSize.width), newRect.height());
2313 break;
2314
2315 case uiFrameItem::CenterLeftResize:
2316 {
2317 Rect r = newRect;
2318 r.X1 = pos().X + dx;
2319 newRect.X1 = r.X1 - imax(0, minSize.width - r.size().width);
2320 break;
2321 }
2322
2323 case uiFrameItem::TopLeftResize:
2324 {
2325 Rect r = newRect;
2326 r.X1 = pos().X + dx;
2327 newRect.X1 = r.X1 - imax(0, minSize.width - r.size().width);
2328 r.Y1 = pos().Y + dy;
2329 newRect.Y1 = r.Y1 - imax(0, minSize.height - r.size().height);
2330 break;
2331 }
2332
2333 case uiFrameItem::TopCenterResize:
2334 {
2335 Rect r = newRect;
2336 r.Y1 = pos().Y + dy;
2337 newRect.Y1 = r.Y1 - imax(0, minSize.height - r.size().height);
2338 break;
2339 }
2340
2341 case uiFrameItem::TopRightResize:
2342 {
2343 Rect r = newRect;
2344 r.X2 = pos().X + m_sizeAtMouseDown.width + dx;
2345 newRect.X2 = r.X2 + imax(0, minSize.width - r.size().width);
2346 r.Y1 = pos().Y + dy;
2347 newRect.Y1 = r.Y1 - imax(0, minSize.height - r.size().height);
2348 break;
2349 }
2350
2351 case uiFrameItem::BottomLeftResize:
2352 {
2353 Rect r = newRect;
2354 r.X1 = pos().X + dx;
2355 newRect.X1 = r.X1 - imax(0, minSize.width - r.size().width);
2356 r.Y2 = pos().Y + m_sizeAtMouseDown.height + dy;
2357 newRect.Y2 = r.Y2 + imax(0, minSize.height - r.size().height);
2358 break;
2359 }
2360
2361 case uiFrameItem::BottomCenterResize:
2362 newRect = newRect.resize(newRect.width(), imax(m_sizeAtMouseDown.height + dy, minSize.height));
2363 break;
2364
2365 case uiFrameItem::BottomRightResize:
2366 newRect = newRect.resize(imax(m_sizeAtMouseDown.width + dx, minSize.width), imax(m_sizeAtMouseDown.height + dy, minSize.height));
2367 break;
2368
2369 default:
2370 return; // no action
2371 }
2372
2373 // reshape to newRect or draw the reshaping box)
2374 if (mouseIsDown == false || (app()->appProps().realtimeReshaping && m_mouseDownFrameItem != uiFrameItem::MoveArea) || (app()->appProps().realtimeMoving && m_mouseDownFrameItem == uiFrameItem::MoveArea)) {
2375 m_lastReshapingBox = Rect();
2376 app()->reshapeWindow(this, newRect);
2377 } else
2378 drawReshapingBox(newRect);
2379}
2380
2381
2382void uiFrame::drawReshapingBox(Rect boxRect)
2383{
2384 int clientOffsetY = clientRect(uiOrigin::Window).Y1;
2385 canvas()->setOrigin(parent()->rect(uiOrigin::Screen).pos());
2387 PaintOptions popt;
2388 popt.NOT = true;
2389 canvas()->setPaintOptions(popt);
2390 if (m_lastReshapingBox != Rect()) {
2391 canvas()->drawRectangle(m_lastReshapingBox);
2392 if (m_titleLength > 0)
2393 canvas()->drawLine(m_lastReshapingBox.X1, m_lastReshapingBox.Y1 + clientOffsetY, m_lastReshapingBox.X2, m_lastReshapingBox.Y1 + clientOffsetY);
2394 }
2395 if (boxRect != Rect()) {
2396 canvas()->drawRectangle(boxRect);
2397 if (m_titleLength > 0)
2398 canvas()->drawLine(boxRect.X1, boxRect.Y1 + clientOffsetY, boxRect.X2, boxRect.Y1 + clientOffsetY);
2399 }
2400 canvas()->setPaintOptions(PaintOptions());
2401 m_lastReshapingBox = boxRect;
2402}
2403
2404
2405void uiFrame::movingFreeMouse(int mouseX, int mouseY)
2406{
2407 uiFrameItem prevSensPos = m_mouseMoveFrameItem;
2408
2409 m_mouseMoveFrameItem = getFrameItemAt(mouseX, mouseY);
2410
2411 if ((m_mouseMoveFrameItem == uiFrameItem::CloseButton || prevSensPos == uiFrameItem::CloseButton) && m_mouseMoveFrameItem != prevSensPos)
2412 repaint(getBtnRect(0));
2413
2414 if ((m_mouseMoveFrameItem == uiFrameItem::MaximizeButton || prevSensPos == uiFrameItem::MaximizeButton) && m_mouseMoveFrameItem != prevSensPos)
2415 repaint(getBtnRect(1));
2416
2417 if ((m_mouseMoveFrameItem == uiFrameItem::MinimizeButton || prevSensPos == uiFrameItem::MinimizeButton) && m_mouseMoveFrameItem != prevSensPos)
2418 repaint(getBtnRect(2));
2419
2421
2422 switch (m_mouseMoveFrameItem) {
2423
2424 case uiFrameItem::TopLeftResize:
2426 break;
2427
2428 case uiFrameItem::TopCenterResize:
2430 break;
2431
2432 case uiFrameItem::TopRightResize:
2434 break;
2435
2436 case uiFrameItem::CenterLeftResize:
2438 break;
2439
2440 case uiFrameItem::CenterRightResize:
2442 break;
2443
2444 case uiFrameItem::BottomLeftResize:
2446 break;
2447
2448 case uiFrameItem::BottomCenterResize:
2450 break;
2451
2452 case uiFrameItem::BottomRightResize:
2454 break;
2455
2456 default:
2457 break;
2458 }
2459
2460 app()->displayController()->setMouseCursor(cur);
2461}
2462
2463
2464void uiFrame::handleButtonsClick(int x, int y, bool doubleClick)
2465{
2466 if (m_titleLength > 0) {
2467 if (m_frameProps.hasCloseButton && getBtnRect(0).contains(x, y) && getBtnRect(0).contains(m_mouseDownPos)) {
2468 // generate UIEVT_CLOSE event
2469 uiEvent evt = uiEvent(this, UIEVT_CLOSE);
2470 app()->postEvent(&evt);
2471 } else if (m_frameProps.hasMaximizeButton && ((getBtnRect(1).contains(x, y) && getBtnRect(1).contains(m_mouseDownPos)) ||
2472 (doubleClick && titleBarRect().contains(x, y)))) {
2473 // maximimize or restore on:
2474 // - click on maximize/restore button
2475 // - double click on the title bar
2476 app()->maximizeFrame(this, !m_frameState.maximized && !m_frameState.minimized); // used also for "restore" from minimized
2477 } else if (m_frameProps.hasMinimizeButton && !m_frameState.minimized && getBtnRect(2).contains(x, y) && getBtnRect(2).contains(m_mouseDownPos)) {
2478 app()->minimizeFrame(this, !m_frameState.minimized);
2479 } else
2480 return;
2481 // this avoids the button remains selected (background colored) when window change size
2482 m_mouseMoveFrameItem = uiFrameItem::None;
2483 }
2484}
2485
2486
2487// uiFrame
2489
2490
2491
2493// uiControl
2494
2495
2496uiControl::uiControl(uiWindow * parent, const Point & pos, const Size & size, bool visible, uint32_t styleClassID)
2497 : uiWindow(parent, pos, size, visible, 0)
2498{
2499 objectType().uiControl = true;
2500 windowProps().activable = false;
2501
2502 if (app()->style() && styleClassID)
2503 app()->style()->setStyle(this, styleClassID);
2504}
2505
2506
2507uiControl::~uiControl()
2508{
2509}
2510
2511
2512void uiControl::processEvent(uiEvent * event)
2513{
2514 uiWindow::processEvent(event);
2515}
2516
2517
2518
2519// uiControl
2521
2522
2523
2525// uiButton
2526
2527
2528uiButton::uiButton(uiWindow * parent, char const * text, const Point & pos, const Size & size, uiButtonKind kind, bool visible, uint32_t styleClassID)
2529 : uiControl(parent, pos, size, visible, 0),
2530 m_text(nullptr),
2531 m_textExtent(0),
2532 m_down(false),
2533 m_kind(kind)
2534{
2535 objectType().uiButton = true;
2536
2537 windowProps().focusable = true;
2538
2539 windowStyle().borderSize = 1;
2541 windowStyle().borderColor = RGB888(64, 64, 64);
2542
2543 if (app()) {
2544 m_buttonStyle.adaptToDisplayColors(app()->displayColors());
2545 if (app()->style() && styleClassID)
2546 app()->style()->setStyle(this, styleClassID);
2547 }
2548
2549 setText(text);
2550}
2551
2552
2553uiButton::~uiButton()
2554{
2555 free(m_text);
2556}
2557
2558
2559void uiButton::setText(char const * value)
2560{
2561 int len = strlen(value);
2562 m_text = (char*) realloc(m_text, len + 1);
2563 strcpy(m_text, value);
2564
2565 m_textExtent = canvas()->textExtent(m_buttonStyle.textFont, value);
2566}
2567
2568
2569void uiButton::paintButton()
2570{
2572 // background
2573 RGB888 bkColor = m_down ? m_buttonStyle.downBackgroundColor : m_buttonStyle.backgroundColor;
2574 if (app()->capturedMouseWindow() == this)
2575 bkColor = m_buttonStyle.mouseDownBackgroundColor;
2576 else if (isMouseOver())
2577 bkColor = m_buttonStyle.mouseOverBackgroundColor;
2578 canvas()->setBrushColor(bkColor);
2579 canvas()->fillRectangle(bkgRect);
2580 // content (text and bitmap)
2581 paintContent(bkgRect);
2582}
2583
2584
2585void uiButton::paintContent(Rect const & rect)
2586{
2587 Bitmap const * bitmap = m_down ? m_buttonStyle.downBitmap : m_buttonStyle.bitmap;
2588 int textHeight = m_buttonStyle.textFont->height;
2589 int bitmapWidth = bitmap ? bitmap->width : 0;
2590 int bitmapHeight = bitmap ? bitmap->height : 0;
2591 int bitmapTextSpace = bitmap ? m_buttonStyle.bitmapTextSpace : 0;
2592
2593 int x = rect.X1 + (rect.size().width - m_textExtent - bitmapTextSpace - bitmapWidth) / 2;
2594 int y = rect.Y1 + (rect.size().height - imax(textHeight, bitmapHeight)) / 2;
2595
2596 if (bitmap) {
2597 canvas()->drawBitmap(x, y, bitmap);
2598 x += bitmapWidth + bitmapTextSpace;
2599 y += (imax(textHeight, bitmapHeight) - textHeight) / 2;
2600 }
2601 canvas()->setGlyphOptions(GlyphOptions().FillBackground(false).DoubleWidth(0).Bold(false).Italic(false).Underline(false).Invert(0));
2602 if (isMouseOver())
2603 canvas()->setPenColor(m_buttonStyle.mouseOverTextColor);
2604 else if (m_down)
2605 canvas()->setPenColor(m_buttonStyle.downTextColor);
2606 else
2607 canvas()->setPenColor(m_buttonStyle.textColor);
2608 canvas()->drawText(m_buttonStyle.textFont, x, y, m_text);
2609}
2610
2611
2612void uiButton::processEvent(uiEvent * event)
2613{
2614 uiControl::processEvent(event);
2615
2616 switch (event->id) {
2617
2618 case UIEVT_PAINT:
2619 beginPaint(event, uiControl::clientRect(uiOrigin::Window));
2620 paintButton();
2621 break;
2622
2623 case UIEVT_CLICK:
2624 trigger();
2625 onClick();
2626 break;
2627
2628 case UIEVT_MOUSEENTER:
2629 repaint(); // to update background color
2630 break;
2631
2632 case UIEVT_MOUSEBUTTONDOWN:
2633 if (event->params.mouse.changedButton == 1)
2634 repaint();
2635 onMouseDown(event->params.mouse);
2636 break;
2637
2638 case UIEVT_MOUSEBUTTONUP:
2639 onMouseUp(event->params.mouse);
2640 break;
2641
2642 case UIEVT_MOUSELEAVE:
2643 repaint(); // to update background
2644 break;
2645
2646 case UIEVT_KEYUP:
2647 if (event->params.key.VK == VK_RETURN || event->params.key.VK == VK_KP_ENTER || event->params.key.VK == VK_SPACE) {
2648 trigger();
2649 onClick();
2650 }
2651 break;
2652
2653 default:
2654 break;
2655 }
2656}
2657
2658
2659// action to perfom on mouse up or keyboard space/enter
2660void uiButton::trigger()
2661{
2662 if (m_kind == uiButtonKind::Switch) {
2663 m_down = !m_down;
2664 onChange();
2665 }
2666 repaint();
2667}
2668
2669
2670void uiButton::setDown(bool value)
2671{
2672 if (value != m_down) {
2673 m_down = value;
2674 repaint();
2675 }
2676}
2677
2678
2679
2680// uiButton
2682
2683
2684
2685
2687// uiTextEdit
2688
2689
2690uiTextEdit::uiTextEdit(uiWindow * parent, char const * text, const Point & pos, const Size & size, bool visible, uint32_t styleClassID)
2691 : uiControl(parent, pos, size, visible, 0),
2692 m_text(nullptr),
2693 m_textLength(0),
2694 m_textSpace(0),
2695 m_viewX(0),
2696 m_cursorCol(0),
2697 m_selCursorCol(0),
2698 m_codepage(nullptr)
2699{
2700 objectType().uiTextEdit = true;
2701
2702 windowProps().focusable = true;
2703
2705 windowStyle().borderColor = RGB888(64, 64, 64);
2706 windowStyle().borderSize = 1;
2707
2708 if (app()) {
2709 m_textEditStyle.adaptToDisplayColors(app()->displayColors());
2710 if (app()->style() && styleClassID)
2711 app()->style()->setStyle(this, styleClassID);
2712 }
2713
2714 setText(text);
2715}
2716
2717
2718uiTextEdit::~uiTextEdit()
2719{
2720 free(m_text);
2721}
2722
2723
2724void uiTextEdit::setText(char const * value)
2725{
2726 if (value) {
2727 m_textLength = strlen(value);
2728 checkAllocatedSpace(m_textLength);
2729 strcpy(m_text, value);
2730 } else {
2731 m_text = strdup("");
2732 m_textLength = 0;
2733 }
2734}
2735
2736
2737void uiTextEdit::setTextFmt(const char *format, ...)
2738{
2739 va_list ap;
2740 va_start(ap, format);
2741 int size = vsnprintf(nullptr, 0, format, ap) + 1;
2742 if (size > 0) {
2743 va_end(ap);
2744 va_start(ap, format);
2745 checkAllocatedSpace(size + 1);
2746 vsnprintf(m_text, size, format, ap);
2747 m_textLength = strlen(m_text);
2748 }
2749 va_end(ap);
2750}
2751
2752
2753void uiTextEdit::processEvent(uiEvent * event)
2754{
2755 uiControl::processEvent(event);
2756
2757 switch (event->id) {
2758
2759 case UIEVT_PAINT:
2760 beginPaint(event, uiControl::clientRect(uiOrigin::Window));
2761 paintTextEdit();
2762 if (m_textEditProps.hasCaret)
2763 app()->setCaret(true); // force blinking (previous painting may cover caret)
2764 break;
2765
2766 case UIEVT_MOUSEBUTTONDOWN:
2767 if (event->params.mouse.changedButton == 1) {
2768 int col = getColFromMouseX(event->params.mouse.status.X);
2769 moveCursor(col, col);
2770 repaint();
2771 }
2772 break;
2773
2774 case UIEVT_MOUSEBUTTONUP:
2775 break;
2776
2777 case UIEVT_MOUSEENTER:
2778 repaint(); // to update background color
2779 break;
2780
2781 case UIEVT_MOUSELEAVE:
2782 repaint(); // to update background and border
2783 break;
2784
2785 case UIEVT_MOUSEMOVE:
2786 // dragging mouse? select
2787 if (app()->capturedMouseWindow() == this)
2788 moveCursor(getColFromMouseX(event->params.mouse.status.X), m_selCursorCol);
2789 break;
2790
2791 case UIEVT_SETFOCUS:
2792 if (m_textEditProps.hasCaret) {
2793 updateCaret();
2794 app()->showCaret(this);
2795 }
2796 repaint();
2797 break;
2798
2799 case UIEVT_KILLFOCUS:
2800 if (m_textEditProps.hasCaret)
2801 app()->showCaret(NULL);
2802 moveCursor(0, 0);
2803 repaint();
2804 break;
2805
2806 case UIEVT_KEYDOWN:
2807 handleKeyDown(event->params.key);
2808 break;
2809
2810 case UIEVT_KEYTYPE:
2811 onKeyType(event->params.key);
2812 break;
2813
2814 case UIEVT_DBLCLICK:
2815 selectWordAt(event->params.mouse.status.X);
2816 break;
2817
2818 default:
2819 break;
2820 }
2821}
2822
2823
2824int uiTextEdit::keyToASCII(uiKeyEventInfo const & key)
2825{
2826 // check codepage consistency
2827 if (m_codepage == nullptr || m_codepage->codepage != m_textEditStyle.textFont->codepage)
2828 m_codepage = CodePages::get(m_textEditStyle.textFont->codepage);
2829
2830 VirtualKeyItem item = { };
2831 item.vk = key.VK;
2832 item.CTRL = key.CTRL;
2833 item.SHIFT = key.SHIFT;
2834 return virtualKeyToASCII(item, m_codepage);
2835}
2836
2837
2838void uiTextEdit::handleKeyDown(uiKeyEventInfo const & key)
2839{
2840 if (m_textEditProps.allowEdit) {
2841 switch (key.VK) {
2842
2843 case VK_BACKSPACE:
2844 if (m_cursorCol != m_selCursorCol)
2845 removeSel(); // there is a selection, same behavior of VK_DELETE
2846 else if (m_cursorCol > 0) {
2847 // remove character at left
2848 moveCursor(m_cursorCol - 1, m_cursorCol - 1);
2849 removeSel();
2850 }
2851 break;
2852
2853 case VK_DELETE:
2854 case VK_KP_DELETE:
2855 removeSel();
2856 break;
2857
2858 default:
2859 {
2860 // normal keys
2861
2862 // we don't use key.ASCII because it uses codepage stored in Keyboard object but
2863 // each textedit may have a different font and codepage
2864 auto ASCII = keyToASCII(key);
2865
2866 if (ASCII >= 0x20 && ASCII != 0x7F) {
2867 if (m_cursorCol != m_selCursorCol)
2868 removeSel(); // there is a selection, same behavior of VK_DELETE
2869 insert(ASCII);
2870 }
2871 break;
2872 }
2873 }
2874 }
2875
2876 switch (key.VK) {
2877
2878 case VK_LEFT:
2879 case VK_KP_LEFT:
2880 {
2881 // LEFT : cancel selection and move cursor by one character
2882 // SHIFT + LEFT : move cursor and select
2883 // CTRL + LEFT : cancel selection and move cursor by one word
2884 // SHIFT + CTRL + LEFT : move cursor by one word and select
2885 int newCurCol = key.CTRL ? getWordPosAtLeft() : m_cursorCol - 1;
2886 moveCursor(newCurCol, (key.SHIFT ? m_selCursorCol : newCurCol));
2887 break;
2888 }
2889
2890 case VK_RIGHT:
2891 case VK_KP_RIGHT:
2892 {
2893 // RIGHT : cancel selection and move cursor by one character
2894 // SHIFT + RIGHT : move cursor and select
2895 // CTRL + RIGHT : cancel selection and move cursor by one word
2896 // SHIFT + CTRL + RIGHT : move cursor by one word and select
2897 int newCurCol = key.CTRL ? getWordPosAtRight() : m_cursorCol + 1;
2898 moveCursor(newCurCol, (key.SHIFT ? m_selCursorCol : newCurCol));
2899 break;
2900 }
2901
2902 case VK_HOME:
2903 case VK_KP_HOME:
2904 // SHIFT + HOME, select up to Home
2905 // HOME, move cursor to home
2906 moveCursor(0, (key.SHIFT ? m_selCursorCol : 0));
2907 break;
2908
2909 case VK_END:
2910 case VK_KP_END:
2911 // SHIFT + END, select up to End
2912 // END, move cursor to End
2913 moveCursor(m_textLength, (key.SHIFT ? m_selCursorCol : m_textLength));
2914 break;
2915
2916 default:
2917 {
2918 if (key.CTRL) {
2919 // keys with CTRL
2920 switch (key.VK) {
2921 case VK_a:
2922 // CTRL+A, select all
2923 moveCursor(m_textLength, 0);
2924 break;
2925 default:
2926 break;
2927 }
2928 }
2929 break;
2930 }
2931 }
2932}
2933
2934
2935Rect uiTextEdit::getEditRect()
2936{
2938}
2939
2940
2941void uiTextEdit::paintTextEdit()
2942{
2943 m_contentRect = getEditRect();
2944 // background
2945 RGB888 bkColor = hasFocus() ? m_textEditStyle.focusedBackgroundColor : (isMouseOver() ? m_textEditStyle.mouseOverBackgroundColor : m_textEditStyle.backgroundColor);
2946 canvas()->setBrushColor(bkColor);
2947 canvas()->fillRectangle(m_contentRect);
2948 // content
2949 paintContent();
2950}
2951
2952
2953// get width of specified characted
2954// return glyph data of the specified character
2955uint8_t const * uiTextEdit::getCharInfo(char ch, int * width)
2956{
2957 if (m_textEditProps.passwordMode)
2958 ch = '*';
2959 uint8_t const * chptr;
2960 if (m_textEditStyle.textFont->chptr) {
2961 // variable width
2962 chptr = m_textEditStyle.textFont->data + m_textEditStyle.textFont->chptr[(int)(ch)];
2963 *width = *chptr++;
2964 } else {
2965 // fixed width
2966 chptr = m_textEditStyle.textFont->data + ch;
2967 *width = m_textEditStyle.textFont->width;
2968 }
2969 return chptr;
2970}
2971
2972
2973void uiTextEdit::paintContent()
2974{
2975 m_contentRect = m_contentRect.shrink(2);
2976 canvas()->setClippingRect(canvas()->getClippingRect().intersection(m_contentRect));
2977 canvas()->setPenColor(m_textEditStyle.textColor);
2978
2979 GlyphOptions glyphOpt = GlyphOptions().FillBackground(false).DoubleWidth(0).Bold(false).Italic(false).Underline(false).Invert(0);
2980 if (m_selCursorCol != m_cursorCol)
2981 glyphOpt.FillBackground(true);
2982 canvas()->setGlyphOptions(glyphOpt);
2983
2984 for (int x = m_contentRect.X1 + m_viewX, y = m_contentRect.Y1, col = 0, fontWidth; m_text[col]; ++col, x += fontWidth) {
2985 uint8_t const * chptr = getCharInfo(m_text[col], &fontWidth);
2986 if (m_selCursorCol != m_cursorCol && (col == m_selCursorCol || col == m_cursorCol)) {
2987 glyphOpt.invert = !glyphOpt.invert;
2988 canvas()->setGlyphOptions(glyphOpt);
2989 }
2990 if (x >= m_contentRect.X1 && x <= m_contentRect.X2)
2991 canvas()->drawGlyph(x, y, fontWidth, m_textEditStyle.textFont->height, chptr, 0);
2992 }
2993}
2994
2995
2996// returns the X coordinate where is character "col"
2997// return value is < m_contentRect.X1 if "col" is at left of visible area
2998// return value is > m_contentRect.X2 if "col" is at the right of visible area
2999int uiTextEdit::charColumnToWindowX(int col)
3000{
3001 int x = m_contentRect.X1 + m_viewX;
3002 for (int curcol = 0, fontWidth; m_text[curcol]; ++curcol, x += fontWidth) {
3003 getCharInfo(m_text[curcol], &fontWidth);
3004 if (curcol == col)
3005 break;
3006 }
3007 return x;
3008}
3009
3010
3011// update caret coordinates from current pos (m_cursorCol)
3012void uiTextEdit::updateCaret()
3013{
3014 if (m_textEditProps.hasCaret) {
3015 int x = charColumnToWindowX(m_cursorCol);
3016 app()->setCaret(Rect(x, m_contentRect.Y1, x, m_contentRect.Y1 + m_textEditStyle.textFont->height));
3017 }
3018}
3019
3020
3021// col (cursor position):
3022// 0 up to m_textLength. For example having a m_text="1234", min col is 0, max col is 4 (passing last char).
3023// selCol (selection position):
3024// 0 up to m_textLength
3025void uiTextEdit::moveCursor(int col, int selCol)
3026{
3027 col = iclamp(col, 0, m_textLength);
3028 selCol = iclamp(selCol, 0, m_textLength);
3029
3030 if (col == m_cursorCol && selCol == m_selCursorCol)
3031 return; // nothing to do
3032
3033 bool doRepaint = false;
3034
3035 // there was a selection, now there is no selection
3036 if (m_cursorCol != m_selCursorCol && col == selCol)
3037 doRepaint = true;
3038
3039 m_cursorCol = col;
3040 m_selCursorCol = selCol;
3041
3042 if (m_cursorCol != m_selCursorCol)
3043 doRepaint = true;
3044
3045 // need to scroll?
3046 int x = charColumnToWindowX(m_cursorCol);
3047
3048 int prevCharWidth = 0;
3049 if (col > 0)
3050 getCharInfo(m_text[col - 1], &prevCharWidth);
3051
3052 int charWidth;
3053 getCharInfo(m_text[col < m_textLength ? col : col - 1], &charWidth);
3054
3055 if (x - prevCharWidth < m_contentRect.X1) {
3056 // scroll right
3057 m_viewX += m_contentRect.X1 - (x - prevCharWidth);
3058 doRepaint = true;
3059 } else if (x + charWidth > m_contentRect.X2) {
3060 // scroll left
3061 m_viewX -= (x + charWidth - m_contentRect.X2);
3062 doRepaint = true;
3063 }
3064
3065 updateCaret();
3066
3067 if (doRepaint)
3068 repaint();
3069}
3070
3071
3072// return column (that is character index in m_text[]) from specified mouseX
3073int uiTextEdit::getColFromMouseX(int mouseX)
3074{
3075 int col = 0;
3076 for (int x = m_contentRect.X1 + m_viewX, fontWidth; m_text[col]; ++col, x += fontWidth) {
3077 getCharInfo(m_text[col], &fontWidth);
3078 if (mouseX < x || (mouseX >= x && mouseX < x + fontWidth))
3079 break;
3080 }
3081 return col;
3082}
3083
3084
3085// requiredLength does NOT include ending zero
3086void uiTextEdit::checkAllocatedSpace(int requiredLength)
3087{
3088 ++requiredLength; // add ending zero
3089 if (m_textSpace < requiredLength) {
3090 if (m_textSpace == 0) {
3091 // first time allocates exact space
3092 m_textSpace = requiredLength;
3093 } else {
3094 // next times allocate double
3095 while (m_textSpace < requiredLength)
3096 m_textSpace *= 2;
3097 }
3098 m_text = (char*) realloc(m_text, m_textSpace);
3099 }
3100}
3101
3102
3103// insert specified char at m_cursorCol
3104void uiTextEdit::insert(char c)
3105{
3106 ++m_textLength;
3107 checkAllocatedSpace(m_textLength);
3108 memmove(m_text + m_cursorCol + 1, m_text + m_cursorCol, m_textLength - m_cursorCol);
3109 m_text[m_cursorCol] = c;
3110 moveCursor(m_cursorCol + 1, m_cursorCol + 1);
3111 onChange();
3112 repaint();
3113}
3114
3115
3116// remove from m_cursorCol to m_selCursorCol
3117void uiTextEdit::removeSel()
3118{
3119 if (m_textLength > 0) {
3120 if (m_cursorCol > m_selCursorCol)
3121 iswap(m_cursorCol, m_selCursorCol);
3122 int count = imax(1, m_selCursorCol - m_cursorCol);
3123 if (m_cursorCol < m_textLength) {
3124 memmove(m_text + m_cursorCol, m_text + m_cursorCol + count, m_textLength - m_cursorCol - count + 1);
3125 m_textLength -= count;
3126 moveCursor(m_cursorCol, m_cursorCol);
3127 onChange();
3128 repaint();
3129 }
3130 }
3131}
3132
3133
3134// return starting position of next word at left of m_cursorCol
3135int uiTextEdit::getWordPosAtLeft()
3136{
3137 int col = m_cursorCol - 1;
3138 while (col > 0 && (!isspace(m_text[col - 1]) || isspace(m_text[col])))
3139 --col;
3140 return imax(0, col);
3141}
3142
3143
3144// return starting position of next word at right of m_cursorCol
3145int uiTextEdit::getWordPosAtRight()
3146{
3147 int col = m_cursorCol + 1;
3148 while (col < m_textLength && (!isspace(m_text[col - 1]) || isspace(m_text[col])))
3149 ++col;
3150 return imin(m_textLength, col);
3151}
3152
3153
3154// if mouseX is at space, select all space at left and right
3155// if mouseX is at character, select all characters at left and right
3156void uiTextEdit::selectWordAt(int mouseX)
3157{
3158 int col = getColFromMouseX(mouseX), left = col, right = col;
3159 bool lspc = isspace(m_text[col]); // look for spaces?
3160 while (left > 0 && (bool)isspace(m_text[left - 1]) == lspc)
3161 --left;
3162 while (right < m_textLength && (bool)isspace(m_text[right]) == lspc)
3163 ++right;
3164 moveCursor(left, right);
3165}
3166
3167
3168
3169// uiTextEdit
3171
3172
3173
3174
3176// uiLabel
3177
3178
3179uiLabel::uiLabel(uiWindow * parent, char const * text, const Point & pos, const Size & size, bool visible, uint32_t styleClassID)
3180 : uiControl(parent, pos, size, visible, 0),
3181 m_text(nullptr),
3182 m_textExtent(0)
3183{
3184 objectType().uiLabel = true;
3185
3186 windowProps().focusable = false;
3187 windowStyle().borderSize = 0;
3188
3189 if (app()) {
3190 m_labelStyle.adaptToDisplayColors(app()->displayColors());
3191 if (app()->style() && styleClassID)
3192 app()->style()->setStyle(this, styleClassID);
3193 }
3194
3195 m_autoSize = (size.width == 0 && size.height == 0);
3196
3197 setText(text);
3198}
3199
3200
3201uiLabel::~uiLabel()
3202{
3203 free(m_text);
3204}
3205
3206
3207void uiLabel::setText(char const * value)
3208{
3209 int len = strlen(value);
3210 m_text = (char*) realloc(m_text, len + 1);
3211 strcpy(m_text, value);
3212 update();
3213}
3214
3215
3216void uiLabel::setTextFmt(const char *format, ...)
3217{
3218 va_list ap;
3219 va_start(ap, format);
3220 int size = vsnprintf(nullptr, 0, format, ap) + 1;
3221 if (size > 0) {
3222 va_end(ap);
3223 va_start(ap, format);
3224 m_text = (char*) realloc(m_text, size + 1);
3225 vsnprintf(m_text, size, format, ap);
3226 update();
3227 }
3228 va_end(ap);
3229}
3230
3231
3233{
3234 m_textExtent = canvas()->textExtent(m_labelStyle.textFont, m_text);
3235 if (m_autoSize)
3236 app()->resizeWindow(this, m_textExtent, m_labelStyle.textFont->height);
3237 repaint();
3238}
3239
3240
3241void uiLabel::paintLabel()
3242{
3244 canvas()->setBrushColor(m_labelStyle.backgroundColor);
3245 canvas()->fillRectangle(r);
3246 canvas()->setGlyphOptions(GlyphOptions().FillBackground(false).DoubleWidth(0).Bold(false).Italic(false).Underline(false).Invert(0));
3247 canvas()->setPenColor(m_labelStyle.textColor);
3248
3249 int x = r.X1; // default left align
3250
3251 switch (m_labelStyle.textAlign) {
3252 case uiHAlign::Right:
3253 x = r.X2 - canvas()->textExtent(m_labelStyle.textFont, m_text);
3254 break;
3255 case uiHAlign::Center:
3256 x = r.X1 + (r.width() - canvas()->textExtent(m_labelStyle.textFont, m_text)) / 2;
3257 break;
3258 default:
3259 break;
3260 }
3261
3262 int y = r.Y1 + (r.height() - m_labelStyle.textFont->height) / 2;
3263 canvas()->drawText(m_labelStyle.textFont, x, y, m_text);
3264}
3265
3266
3267void uiLabel::processEvent(uiEvent * event)
3268{
3269 uiControl::processEvent(event);
3270
3271 switch (event->id) {
3272
3273 case UIEVT_PAINT:
3274 beginPaint(event, uiControl::clientRect(uiOrigin::Window));
3275 paintLabel();
3276 break;
3277
3278 case UIEVT_CLICK:
3279 onClick();
3280 break;
3281
3282 default:
3283 break;
3284 }
3285}
3286
3287
3288// uiLabel
3290
3291
3292
3293
3295// uiImage
3296// TODO? bitmap is not copied, but just referenced
3297
3298
3299uiImage::uiImage(uiWindow * parent, Bitmap const * bitmap, const Point & pos, const Size & size, bool visible, uint32_t styleClassID)
3300 : uiControl(parent, pos, size, visible, 0),
3301 m_bitmap(nullptr)
3302{
3303 objectType().uiImage = true;
3304
3305 windowProps().focusable = false;
3306 windowStyle().borderSize = 0;
3307
3308 if (app()) {
3309 m_imageStyle.adaptToDisplayColors(app()->displayColors());
3310 if (app()->style() && styleClassID)
3311 app()->style()->setStyle(this, styleClassID);
3312 }
3313
3314 m_autoSize = (size.width == 0 && size.height == 0);
3315
3317}
3318
3319
3320uiImage::~uiImage()
3321{
3322}
3323
3324
3325void uiImage::setBitmap(Bitmap const * bitmap)
3326{
3327 m_bitmap = bitmap;
3328
3329 if (m_autoSize && bitmap)
3331}
3332
3333
3334void uiImage::paintImage()
3335{
3337 canvas()->setBrushColor(m_imageStyle.backgroundColor);
3338 canvas()->fillRectangle(r);
3339 if (m_bitmap) {
3340 int x = r.X1 + (r.X2 + 1 - m_bitmap->width) / 2;
3341 int y = r.Y1 + (r.Y2 + 1 - m_bitmap->height) / 2;
3342 canvas()->drawBitmap(x, y, m_bitmap);
3343 }
3344}
3345
3346
3347void uiImage::processEvent(uiEvent * event)
3348{
3349 uiControl::processEvent(event);
3350
3351 switch (event->id) {
3352
3353 case UIEVT_PAINT:
3354 beginPaint(event, uiControl::clientRect(uiOrigin::Window));
3355 paintImage();
3356 break;
3357
3358 default:
3359 break;
3360 }
3361}
3362
3363
3364// uiImage
3366
3367
3368
3370// uiPanel
3371
3372
3373uiPanel::uiPanel(uiWindow * parent, const Point & pos, const Size & size, bool visible, uint32_t styleClassID)
3374 : uiControl(parent, pos, size, visible, 0)
3375{
3376 objectType().uiPanel = true;
3377
3378 windowProps().focusable = false;
3379 windowStyle().borderSize = 1;
3380 windowStyle().borderColor = RGB888(64, 64, 64);
3381
3382 if (app()) {
3383 m_panelStyle.adaptToDisplayColors(app()->displayColors());
3384 if (app()->style() && styleClassID)
3385 app()->style()->setStyle(this, styleClassID);
3386 }
3387}
3388
3389
3390uiPanel::~uiPanel()
3391{
3392}
3393
3394
3395void uiPanel::paintPanel()
3396{
3398 // background
3399 canvas()->setBrushColor(m_panelStyle.backgroundColor);
3400 canvas()->fillRectangle(bkgRect);
3401}
3402
3403
3404void uiPanel::processEvent(uiEvent * event)
3405{
3406 uiControl::processEvent(event);
3407
3408 switch (event->id) {
3409
3410 case UIEVT_PAINT:
3411 beginPaint(event, uiControl::clientRect(uiOrigin::Window));
3412 paintPanel();
3413 break;
3414
3415 default:
3416 break;
3417 }
3418}
3419
3420
3421// uiPanel
3423
3424
3425
3427// uiPaintBox
3428
3429
3430uiPaintBox::uiPaintBox(uiWindow * parent, const Point & pos, const Size & size, bool visible, uint32_t styleClassID)
3431 : uiScrollableControl(parent, pos, size, visible, 0)
3432{
3433 objectType().uiPaintBox = true;
3434
3435 windowProps().focusable = false;
3436 windowStyle().borderSize = 1;
3437 windowStyle().borderColor = RGB888(64, 64, 64);
3438
3439 if (app()) {
3440 m_paintBoxStyle.adaptToDisplayColors(app()->displayColors());
3441 if (app()->style() && styleClassID)
3442 app()->style()->setStyle(this, styleClassID);
3443 }
3444}
3445
3446
3447uiPaintBox::~uiPaintBox()
3448{
3449}
3450
3451
3452void uiPaintBox::paintPaintBox()
3453{
3455
3456 // background
3457 canvas()->setBrushColor(m_paintBoxStyle.backgroundColor);
3458 canvas()->fillRectangle(bkgRect);
3459
3460 onPaint(bkgRect);
3461}
3462
3463
3464void uiPaintBox::processEvent(uiEvent * event)
3465{
3466 uiScrollableControl::processEvent(event);
3467
3468 switch (event->id) {
3469
3470 case UIEVT_PAINT:
3472 paintPaintBox();
3473 break;
3474
3475 default:
3476 break;
3477 }
3478}
3479
3480
3481// uiPaintBox
3483
3484
3485
3487// uiColorBox
3488
3489
3490uiColorBox::uiColorBox(uiWindow * parent, const Point & pos, const Size & size, Color color, bool visible, uint32_t styleClassID)
3491 : uiControl(parent, pos, size, visible, 0),
3492 m_color(color)
3493{
3494 objectType().uiColorBox = true;
3495
3496 windowProps().focusable = true;
3497 windowStyle().borderSize = 1;
3498 windowStyle().borderColor = RGB888(64, 64, 64);
3499
3500 if (app()->style() && styleClassID)
3501 app()->style()->setStyle(this, styleClassID);
3502}
3503
3504
3505uiColorBox::~uiColorBox()
3506{
3507}
3508
3509
3511{
3512 m_color = value;
3513 repaint();
3514}
3515
3516
3517void uiColorBox::paintColorBox()
3518{
3520 // main color
3521 canvas()->setBrushColor(m_color);
3522 canvas()->fillRectangle(bkgRect);
3523}
3524
3525
3526void uiColorBox::processEvent(uiEvent * event)
3527{
3528 uiControl::processEvent(event);
3529
3530 switch (event->id) {
3531
3532 case UIEVT_PAINT:
3533 beginPaint(event, uiControl::clientRect(uiOrigin::Window));
3534 paintColorBox();
3535 break;
3536
3537 default:
3538 break;
3539 }
3540}
3541
3542
3543// uiPanel
3545
3546
3547
3549// uiScrollableControl
3550
3551
3552uiScrollableControl::uiScrollableControl(uiWindow * parent, const Point & pos, const Size & size, bool visible, uint32_t styleClassID)
3553 : uiControl(parent, pos, size, visible, 0),
3554 m_HScrollBarPosition(0),
3555 m_HScrollBarVisible(0),
3556 m_HScrollBarRange(0),
3557 m_VScrollBarPosition(0),
3558 m_VScrollBarVisible(0),
3559 m_VScrollBarRange(0),
3560 m_mouseOverItem(uiScrollBarItem::None),
3561 m_scrollTimer(nullptr),
3562 m_mouseDownPos(Point(-1, -1))
3563{
3564 objectType().uiScrollableControl = true;
3565
3566 if (app()) {
3567 m_scrollableControlStyle.adaptToDisplayColors(app()->displayColors());
3568 if (app()->style() && styleClassID)
3569 app()->style()->setStyle(this, styleClassID);
3570 }
3571}
3572
3573
3574uiScrollableControl::~uiScrollableControl()
3575{
3576 if (m_scrollTimer)
3577 app()->killTimer(m_scrollTimer);
3578}
3579
3580
3581// position: The position of the scrollbar in scroll units.
3582// visible: The size of the visible portion of the scrollbar, in scroll units.
3583// range: The maximum position of the scrollbar
3584void uiScrollableControl::setScrollBar(uiOrientation orientation, int position, int visible, int range, bool repaintScrollbar)
3585{
3586 position = iclamp(position, 0, range - visible);
3587 switch (orientation) {
3589 {
3590 bool changedPos = (m_VScrollBarPosition != position);
3591 if (m_VScrollBarVisible != visible || m_VScrollBarRange != range || changedPos) {
3592 m_VScrollBarVisible = visible;
3593 m_VScrollBarRange = range;
3594 m_VScrollBarPosition = position;
3595 if (repaintScrollbar)
3596 repaintScrollBar(orientation);
3597 if (changedPos)
3599 }
3600 break;
3601 }
3603 {
3604 bool changedPos = (m_HScrollBarPosition != position);
3605 if (m_HScrollBarVisible != visible || m_HScrollBarRange != range || changedPos) {
3606 m_HScrollBarVisible = visible;
3607 m_HScrollBarRange = range;
3608 m_HScrollBarPosition = position;
3609 if (repaintScrollbar)
3610 repaintScrollBar(orientation);
3611 if (changedPos)
3613 }
3614 break;
3615 }
3616 };
3617}
3618
3619
3620void uiScrollableControl::repaintScrollBar(uiOrientation orientation)
3621{
3622 repaint( orientation == uiOrientation::Vertical ? getVScrollBarRects() : getHScrollBarRects() );
3623}
3624
3625
3626void uiScrollableControl::processEvent(uiEvent * event)
3627{
3628 uiControl::processEvent(event);
3629
3630 switch (event->id) {
3631
3632 case UIEVT_PAINT:
3633 beginPaint(event, uiControl::clientRect(uiOrigin::Window));
3634 paintScrollableControl();
3635 break;
3636
3637 case UIEVT_MOUSEBUTTONDOWN:
3638 if (event->params.mouse.changedButton == 1) {
3639 m_mouseDownPos = Point(event->params.mouse.status.X, event->params.mouse.status.Y);
3640 m_mouseDownHScrollBarPosition = m_HScrollBarPosition;
3641 m_mouseDownVScrollBarPosition = m_VScrollBarPosition;
3642 if (m_mouseOverItem == uiScrollBarItem::LeftButton || m_mouseOverItem == uiScrollBarItem::RightButton ||
3643 m_mouseOverItem == uiScrollBarItem::TopButton || m_mouseOverItem == uiScrollBarItem::BottomButton) {
3644 // start the timer to repeat buttons
3645 m_scrollTimer = app()->setTimer(this, 250);
3646 handleButtonsScroll();
3647 } else {
3648 handlePageScroll();
3649 }
3650 app()->combineMouseMoveEvents(true);
3651 }
3652 break;
3653
3654 case UIEVT_MOUSEBUTTONUP:
3655 if (event->params.mouse.changedButton == 1) {
3656 app()->combineMouseMoveEvents(false);
3657 if (m_scrollTimer) {
3658 app()->killTimer(m_scrollTimer);
3659 m_scrollTimer = nullptr;
3660 }
3661 }
3662 break;
3663
3664 case UIEVT_MOUSEMOVE:
3665 if (app()->capturedMouseWindow() == this)
3666 handleCapturedMouseMove(event->params.mouse.status.X, event->params.mouse.status.Y);
3667 else
3668 handleFreeMouseMove(event->params.mouse.status.X, event->params.mouse.status.Y);
3669 break;
3670
3671 case UIEVT_MOUSEWHEEL:
3672 if (m_VScrollBarRange)
3673 setScrollBar(uiOrientation::Vertical, m_VScrollBarPosition + event->params.mouse.status.wheelDelta, m_VScrollBarVisible, m_VScrollBarRange);
3674 break;
3675
3676 case UIEVT_TIMER:
3677 if (event->params.timerHandle == m_scrollTimer)
3678 handleButtonsScroll();
3679 break;
3680
3681 default:
3682 break;
3683 }
3684}
3685
3686
3687void uiScrollableControl::handleButtonsScroll()
3688{
3689 switch (m_mouseOverItem) {
3690 case uiScrollBarItem::LeftButton:
3691 setScrollBar(uiOrientation::Horizontal, m_HScrollBarPosition - 1, m_HScrollBarVisible, m_HScrollBarRange);
3692 break;
3693 case uiScrollBarItem::RightButton:
3694 setScrollBar(uiOrientation::Horizontal, m_HScrollBarPosition + 1, m_HScrollBarVisible, m_HScrollBarRange);
3695 break;
3696 case uiScrollBarItem::TopButton:
3697 setScrollBar(uiOrientation::Vertical, m_VScrollBarPosition - 1, m_VScrollBarVisible, m_VScrollBarRange);
3698 break;
3699 case uiScrollBarItem::BottomButton:
3700 setScrollBar(uiOrientation::Vertical, m_VScrollBarPosition + 1, m_VScrollBarVisible, m_VScrollBarRange);
3701 break;
3702 default:
3703 break;
3704 }
3705}
3706
3707
3708void uiScrollableControl::handlePageScroll()
3709{
3710 switch (m_mouseOverItem) {
3711 case uiScrollBarItem::PageLeft:
3712 setScrollBar(uiOrientation::Horizontal, m_HScrollBarPosition - m_HScrollBarVisible, m_HScrollBarVisible, m_HScrollBarRange);
3713 break;
3714 case uiScrollBarItem::PageRight:
3715 setScrollBar(uiOrientation::Horizontal, m_HScrollBarPosition + m_HScrollBarVisible, m_HScrollBarVisible, m_HScrollBarRange);
3716 break;
3717 case uiScrollBarItem::PageUp:
3718 setScrollBar(uiOrientation::Vertical, m_VScrollBarPosition - m_VScrollBarVisible, m_VScrollBarVisible, m_VScrollBarRange);
3719 break;
3720 case uiScrollBarItem::PageDown:
3721 setScrollBar(uiOrientation::Vertical, m_VScrollBarPosition + m_VScrollBarVisible, m_VScrollBarVisible, m_VScrollBarRange);
3722 break;
3723 default:
3724 break;
3725 }
3726}
3727
3728
3729void uiScrollableControl::handleFreeMouseMove(int mouseX, int mouseY)
3730{
3731 auto prev = m_mouseOverItem;
3732 m_mouseOverItem = getItemAt(mouseX, mouseY);
3733 if (m_mouseOverItem != prev) {
3734 if (m_VScrollBarRange)
3735 repaintScrollBar(uiOrientation::Vertical);
3736 if (m_HScrollBarRange)
3737 repaintScrollBar(uiOrientation::Horizontal);
3738 }
3739}
3740
3741
3742void uiScrollableControl::handleCapturedMouseMove(int mouseX, int mouseY)
3743{
3744 if (m_mouseOverItem == uiScrollBarItem::HBar) {
3745 // dragging horizontal bar
3746 int offset = mouseX - m_mouseDownPos.X;
3747 int newPos = m_mouseDownHScrollBarPosition + offset * m_HScrollBarRange / m_HBarArea;
3748 setScrollBar(uiOrientation::Horizontal, newPos, m_HScrollBarVisible, m_HScrollBarRange);
3749 } else if (m_mouseOverItem == uiScrollBarItem::VBar) {
3750 // dragging vertical bar
3751 int offset = mouseY - m_mouseDownPos.Y;
3752 int newPos = m_mouseDownVScrollBarPosition + offset * m_VScrollBarRange / m_VBarArea;
3753 setScrollBar(uiOrientation::Vertical, newPos, m_VScrollBarVisible, m_VScrollBarRange);
3754 }
3755}
3756
3757
3758uiScrollBarItem uiScrollableControl::getItemAt(int x, int y)
3759{
3760 if (m_HScrollBarRange) {
3761 Rect lbtn, rbtn, bar;
3762 Rect box = getHScrollBarRects(&lbtn, &rbtn, &bar);
3763 if (lbtn.contains(x, y))
3764 return uiScrollBarItem::LeftButton;
3765 if (rbtn.contains(x, y))
3766 return uiScrollBarItem::RightButton;
3767 if (bar.contains(x, y))
3768 return uiScrollBarItem::HBar;
3769 if (box.contains(x, y))
3770 return x < bar.X1 ? uiScrollBarItem::PageLeft : uiScrollBarItem::PageRight;
3771 }
3772 if (m_VScrollBarRange) {
3773 Rect tbtn, bbtn, bar;
3774 Rect box = getVScrollBarRects(&tbtn, &bbtn, &bar);
3775 if (tbtn.contains(x, y))
3776 return uiScrollBarItem::TopButton;
3777 if (bbtn.contains(x, y))
3778 return uiScrollBarItem::BottomButton;
3779 if (bar.contains(x, y))
3780 return uiScrollBarItem::VBar;
3781 if (box.contains(x, y))
3782 return y < bar.Y1 ? uiScrollBarItem::PageUp: uiScrollBarItem::PageDown;
3783 }
3784 return uiScrollBarItem::None;
3785}
3786
3787
3788Rect uiScrollableControl::getVScrollBarRects(Rect * topButton, Rect * bottomButton, Rect * bar)
3789{
3791 const int sbSize = m_scrollableControlStyle.scrollBarSize;
3792 Rect box = Rect(cArea.X2 + 1 - sbSize, cArea.Y1, cArea.X2, cArea.Y2 - (m_HScrollBarRange ? sbSize : 0));
3793 if (topButton && bottomButton && bar) {
3794 // buttons
3795 *topButton = Rect(box.X1 + 2, box.Y1 + 2, box.X2 - 2, box.Y1 + sbSize - 2);
3796 *bottomButton = Rect(box.X1 + 2, box.Y2 - sbSize + 2, box.X2 - 2, box.Y2 - 2);
3797 // the bar
3798 int barAreaY1 = topButton->Y2 + 2;
3799 int barAreaY2 = bottomButton->Y1 - 2;
3800 m_VBarArea = barAreaY2 - barAreaY1 + 1;
3801 int barOffsetY = m_VScrollBarPosition * m_VBarArea / m_VScrollBarRange;
3802 int barHeight = m_VScrollBarVisible * m_VBarArea / m_VScrollBarRange;
3803 *bar = Rect(box.X1 + 1, barAreaY1 + barOffsetY, box.X2 - 1, barAreaY1 + barOffsetY + barHeight);
3804 }
3805 return box;
3806}
3807
3808
3809Rect uiScrollableControl::getHScrollBarRects(Rect * leftButton, Rect * rightButton, Rect * bar)
3810{
3812 const int sbSize = m_scrollableControlStyle.scrollBarSize;
3813 Rect box = Rect(cArea.X1, cArea.Y2 + 1 - sbSize, cArea.X2 - (m_VScrollBarRange ? sbSize : 0), cArea.Y2);
3814 if (leftButton && rightButton && bar) {
3815 // buttons
3816 *leftButton = Rect(box.X1 + 2, box.Y1 + 2, box.X1 + sbSize - 2, box.Y2 - 2);
3817 *rightButton = Rect(box.X2 - sbSize + 2, box.Y1 + 2, box.X2 - 2, box.Y2 - 2);
3818 // the bar
3819 int barAreaX1 = leftButton->X2 + 2;
3820 int barAreaX2 = rightButton->X1 - 2;
3821 m_HBarArea = barAreaX2 - barAreaX1 + 1;
3822 int barOffsetX = m_HScrollBarPosition * m_HBarArea / m_HScrollBarRange;
3823 int barWidth = m_HScrollBarVisible * m_HBarArea / m_HScrollBarRange;
3824 *bar = Rect(barAreaX1 + barOffsetX, box.Y1 + 1, barAreaX1 + barOffsetX + barWidth, box.Y2 - 1);
3825 }
3826 return box;
3827}
3828
3829
3830void uiScrollableControl::paintScrollableControl()
3831{
3832 RGB888 FColor = m_scrollableControlStyle.scrollBarForegroundColor;
3833 RGB888 mouseOverFColor = m_scrollableControlStyle.mouseOverScrollBarForegroundColor;
3834 if (m_HScrollBarRange) {
3836 Rect lbtn, rbtn, bar;
3837 Rect box = getHScrollBarRects(&lbtn, &rbtn, &bar);
3838 // background
3839 canvas()->setBrushColor(m_scrollableControlStyle.scrollBarBackgroundColor);
3840 canvas()->setPenColor(m_scrollableControlStyle.scrollBarBackgroundColor);
3841 canvas()->fillRectangle(Rect(box.X1, box.Y1, bar.X1 - 1, box.Y2)); // left part
3842 canvas()->fillRectangle(Rect(bar.X2 + 1, box.Y1, box.X2, box.Y2)); // right part
3843 canvas()->drawLine(bar.X1, box.Y1, bar.X2, box.Y1); // fill line above the bar
3844 canvas()->drawLine(bar.X1, box.Y2, bar.X2, box.Y2); // fill line below the bar
3845 // buttons (arrows)
3846 canvas()->setPenColor(m_mouseOverItem == uiScrollBarItem::LeftButton ? mouseOverFColor : FColor);
3847 canvas()->drawLine(lbtn.X2, lbtn.Y1, lbtn.X1, lbtn.Y1 + lbtn.height() / 2);
3848 canvas()->drawLine(lbtn.X1, lbtn.Y1 + lbtn.height() / 2, lbtn.X2, lbtn.Y2);
3849 canvas()->setPenColor(m_mouseOverItem == uiScrollBarItem::RightButton ? mouseOverFColor : FColor);
3850 canvas()->drawLine(rbtn.X1, rbtn.Y1, rbtn.X2, rbtn.Y1 + lbtn.height() / 2);
3851 canvas()->drawLine(rbtn.X2, rbtn.Y1 + lbtn.height() / 2, rbtn.X1, rbtn.Y2);
3852 // the bar
3853 canvas()->setBrushColor(m_mouseOverItem == uiScrollBarItem::HBar ? mouseOverFColor : FColor);
3854 canvas()->fillRectangle(bar);
3855 }
3856 if (m_VScrollBarRange) {
3857 // paint vertical scroll bar (at right side of the window)
3858 Rect ubtn, bbtn, bar;
3859 Rect box = getVScrollBarRects(&ubtn, &bbtn, &bar);
3860 // background
3861 canvas()->setBrushColor(m_scrollableControlStyle.scrollBarBackgroundColor);
3862 canvas()->setPenColor(m_scrollableControlStyle.scrollBarBackgroundColor);
3863 canvas()->fillRectangle(Rect(box.X1, box.Y1, box.X2, bar.Y1 - 1)); // upper part
3864 canvas()->fillRectangle(Rect(box.X1, bar.Y2 + 1, box.X2, box.Y2)); // bottom part
3865 canvas()->drawLine(box.X1, bar.Y1, box.X1, bar.Y2); // fill line at left of the bar
3866 canvas()->drawLine(box.X2, bar.Y1, box.X2, bar.Y2); // fill line at right of the bar
3867 // fill box between scrollbars
3868 if (m_HScrollBarRange)
3869 canvas()->fillRectangle(Rect(box.X1, box.Y2 + 1, box.X2, box.Y2 + m_scrollableControlStyle.scrollBarSize));
3870 // buttons (arrows)
3871 canvas()->setPenColor(m_mouseOverItem == uiScrollBarItem::TopButton ? mouseOverFColor : FColor);
3872 canvas()->drawLine(ubtn.X1, ubtn.Y2, ubtn.X1 + ubtn.width() / 2, ubtn.Y1);
3873 canvas()->drawLine(ubtn.X1 + ubtn.width() / 2, ubtn.Y1, ubtn.X2, ubtn.Y2);
3874 canvas()->setPenColor(m_mouseOverItem == uiScrollBarItem::BottomButton ? mouseOverFColor : FColor);
3875 canvas()->drawLine(bbtn.X1, bbtn.Y1, bbtn.X1 + bbtn.width() / 2, bbtn.Y2);
3876 canvas()->drawLine(bbtn.X1 + bbtn.width() / 2, bbtn.Y2, bbtn.X2, bbtn.Y1);
3877 // the bar
3878 canvas()->setBrushColor(m_mouseOverItem == uiScrollBarItem::VBar ? mouseOverFColor : FColor);
3879 canvas()->fillRectangle(bar);
3880 }
3881}
3882
3883
3885{
3886 Rect r = uiControl::clientRect(origin);
3887 r.X2 -= (m_VScrollBarRange ? m_scrollableControlStyle.scrollBarSize : 0);
3888 r.Y2 -= (m_HScrollBarRange ? m_scrollableControlStyle.scrollBarSize : 0);
3889 return r;
3890}
3891
3892
3893// uiScrollableControl
3895
3896
3897
3898
3900// uiCustomListBox
3901
3902
3903uiCustomListBox::uiCustomListBox(uiWindow * parent, const Point & pos, const Size & size, bool visible, uint32_t styleClassID)
3904 : uiScrollableControl(parent, pos, size, visible, 0),
3905 m_firstVisibleItem(0)
3906{
3907 objectType().uiCustomListBox = true;
3908
3909 windowProps().focusable = true;
3910
3911 windowStyle().borderSize = 1;
3912 windowStyle().borderColor = RGB888(64, 64, 64);
3913
3914 if (app()) {
3915 m_listBoxStyle.adaptToDisplayColors(app()->displayColors());
3916 if (app()->style() && styleClassID)
3917 app()->style()->setStyle(this, styleClassID);
3918 }
3919}
3920
3921
3922uiCustomListBox::~uiCustomListBox()
3923{
3924}
3925
3926
3927void uiCustomListBox::processEvent(uiEvent * event)
3928{
3929 uiScrollableControl::processEvent(event);
3930
3931 switch (event->id) {
3932
3933 case UIEVT_PAINT:
3935 paintListBox();
3936 break;
3937
3938 case UIEVT_MOUSEBUTTONDOWN:
3939 if (event->params.mouse.changedButton == 1)
3940 mouseDownSelect(event->params.mouse.status.X, event->params.mouse.status.Y);
3941 break;
3942
3943 case UIEVT_MOUSEMOVE:
3944 if (m_listBoxProps.selectOnMouseOver)
3945 mouseMoveSelect(event->params.mouse.status.X, event->params.mouse.status.Y);
3946 break;
3947
3948 case UIEVT_KEYDOWN:
3949 handleKeyDown(event->params.key);
3950 break;
3951
3952 case UIEVT_KEYUP:
3953 onKeyUp(event->params.key);
3954 break;
3955
3956 case UIEVT_KEYTYPE:
3957 onKeyType(event->params.key);
3958 break;
3959
3960 case UIEVT_KILLFOCUS:
3961 onKillFocus();
3962 break;
3963
3964 case UIEVT_SHOW:
3965 makeItemVisible(firstSelectedItem());
3966 break;
3967
3968 case UIEVT_CLICK:
3969 onClick();
3970 break;
3971
3972 case UIEVT_DBLCLICK:
3973 onDblClick();
3974 break;
3975
3976 default:
3977 break;
3978 }
3979}
3980
3981
3982void uiCustomListBox::handleKeyDown(uiKeyEventInfo key)
3983{
3984 bool shift = key.SHIFT;
3985 switch (key.VK) {
3986 case VK_UP:
3987 case VK_KP_UP:
3988 selectItem(firstSelectedItem() - 1, shift, false);
3989 break;
3990
3991 case VK_DOWN:
3992 case VK_KP_DOWN:
3993 selectItem(lastSelectedItem() + 1, shift, false);
3994 break;
3995
3996 case VK_PAGEUP:
3997 case VK_KP_PAGEUP:
3998 selectItem(firstSelectedItem() - VScrollBarVisible(), shift, false);
3999 break;
4000
4001 case VK_PAGEDOWN:
4002 case VK_KP_PAGEDOWN:
4003 selectItem(lastSelectedItem() + VScrollBarVisible(), shift, false);
4004 break;
4005
4006 case VK_HOME:
4007 case VK_KP_HOME:
4008 selectItem(0, shift, shift);
4009 break;
4010
4011 case VK_END:
4012 case VK_KP_END:
4013 selectItem(items_getCount() - 1, shift, shift);
4014 break;
4015
4016 default:
4017 break;
4018 }
4019}
4020
4021
4022void uiCustomListBox::selectItem(int index, bool add, bool range)
4023{
4024 if (items_getCount() > 0) {
4025 index = iclamp(index, 0, items_getCount() - 1);
4026 int first = firstSelectedItem();
4027 if (!add || !m_listBoxProps.allowMultiSelect)
4028 items_deselectAll();
4029 if (m_listBoxProps.allowMultiSelect && range) {
4030 if (index <= first) {
4031 for (int i = index; i <= first; ++i)
4032 items_select(i, true);
4033 } else {
4034 for (int i = index; i >= first; --i)
4035 items_select(i, true);
4036 }
4037 } else {
4038 items_select(index, true);
4039 }
4040
4041 // make sure the selected item is visible
4042 makeItemVisible(index);
4043
4044 onChange();
4045
4046 repaint();
4047 }
4048}
4049
4050
4051void uiCustomListBox::makeItemVisible(int index)
4052{
4053 if (VScrollBarVisible()) {
4054 if (index < m_firstVisibleItem)
4055 m_firstVisibleItem = index;
4056 else if (index >= m_firstVisibleItem + VScrollBarVisible())
4057 m_firstVisibleItem = index - VScrollBarVisible() + 1;
4058 }
4059}
4060
4061
4063{
4064 items_deselectAll();
4065 onChange();
4066 repaint();
4067}
4068
4069
4070void uiCustomListBox::paintListBox()
4071{
4073 Rect itmRect = Rect(cliRect.X1, cliRect.Y1, cliRect.X2, cliRect.Y1 + m_listBoxStyle.itemHeight - 1);
4074
4075 // do we need a vert scrollbar?
4076 if (itmRect.height() * items_getCount() > cliRect.height()) {
4077 int visible = cliRect.height() / itmRect.height();
4078 int range = items_getCount();
4079 if (!VScrollBarVisible() || visible != VScrollBarVisible() || range != VScrollBarRange() || m_firstVisibleItem != VScrollBarPos()) {
4080 // show vertical scrollbar
4081 setScrollBar(uiOrientation::Vertical, m_firstVisibleItem, visible, range, false);
4082 repaint();
4083 return;
4084 }
4085 } else if (VScrollBarVisible()) {
4086 // hide vertical scrollbar
4087 m_firstVisibleItem = 0;
4088 setScrollBar(uiOrientation::Vertical, 0, 0, 0, false);
4089 repaint();
4090 return;
4091 }
4092
4093 int index = m_firstVisibleItem;
4094 while (true) {
4095 if (!itmRect.intersects(cliRect))
4096 break;
4097
4098 // background
4099 RGB888 bkColor = hasFocus() ? m_listBoxStyle.focusedBackgroundColor : m_listBoxStyle.backgroundColor;
4100 if (index < items_getCount() && items_selected(index))
4101 bkColor = (hasFocus() ? m_listBoxStyle.focusedSelectedBackgroundColor : m_listBoxStyle.selectedBackgroundColor);
4102 canvas()->setBrushColor(bkColor);
4103 canvas()->fillRectangle(itmRect);
4104
4105 if (index < items_getCount()) {
4106 // text
4107 canvas()->setPenColor(items_selected(index) ? m_listBoxStyle.selectedTextColor : m_listBoxStyle.textColor);
4108 items_draw(index, itmRect);
4109 }
4110
4111 // move to next item
4112 itmRect = itmRect.translate(0, m_listBoxStyle.itemHeight);
4113 ++index;
4114 }
4115
4116}
4117
4118
4119// get first selected item (-1 = no selected item)
4121{
4122 for (int i = 0; i < items_getCount(); ++i)
4123 if (items_selected(i))
4124 return i;
4125 return -1;
4126}
4127
4128
4129// get last selected item (-1 = no selected item)
4131{
4132 for (int i = items_getCount() - 1; i >= 0; --i)
4133 if (items_selected(i))
4134 return i;
4135 return -1;
4136}
4137
4138
4139void uiCustomListBox::setScrollBar(uiOrientation orientation, int position, int visible, int range, bool repaintScrollbar)
4140{
4141 uiScrollableControl::setScrollBar(orientation, position, visible, range, false);
4142 if (VScrollBarVisible() && m_firstVisibleItem != VScrollBarPos()) {
4143 m_firstVisibleItem = VScrollBarPos();
4144 repaint();
4145 }
4146}
4147
4148
4149// >= 0 : mouse point an item
4150// -1 : mouse inside items area, but doesn't point an item (ie just below last item)
4151// -2 : mouse outside items area (ie over vertical or horizontal scrollbar)
4152int uiCustomListBox::getItemAtMousePos(int mouseX, int mouseY)
4153{
4155 if (cliRect.contains(mouseX, mouseY)) {
4156 int idx = m_firstVisibleItem + (mouseY - cliRect.Y1) / m_listBoxStyle.itemHeight;
4157 return idx < items_getCount() ? idx : -1;
4158 }
4159 return -2;
4160}
4161
4162
4163void uiCustomListBox::mouseDownSelect(int mouseX, int mouseY)
4164{
4165 int idx = getItemAtMousePos(mouseX, mouseY);
4166 if (idx >= 0) {
4167 if (app()->keyboard()->isVKDown(VK_LCTRL) || app()->keyboard()->isVKDown(VK_RCTRL)) {
4168 // CTRL is down
4169 bool wasSelected = items_selected(idx);
4170 if (m_listBoxProps.allowMultiSelect) {
4171 items_select(idx, !wasSelected);
4172 } else {
4173 items_deselectAll();
4174 if (!wasSelected)
4175 items_select(idx, true);
4176 }
4177 } else {
4178 // CTRL is up
4179 items_deselectAll();
4180 items_select(idx, true);
4181 }
4182 } else if (idx == -1)
4183 items_deselectAll();
4184 else
4185 return;
4186 onChange();
4187 repaint();
4188}
4189
4190
4191void uiCustomListBox::mouseMoveSelect(int mouseX, int mouseY)
4192{
4193 int idx = getItemAtMousePos(mouseX, mouseY);
4194 if (idx >= 0 && !items_selected(idx)) {
4195 items_deselectAll();
4196 items_select(idx, true);
4197 onChange();
4198 repaint();
4199 }
4200}
4201
4202
4203// uiCustomListBox
4205
4206
4207
4209// uiListBox
4210
4211
4212uiListBox::uiListBox(uiWindow * parent, const Point & pos, const Size & size, bool visible, uint32_t styleClassID)
4213 : uiCustomListBox(parent, pos, size, visible, 0)
4214{
4215 objectType().uiListBox = true;
4216
4217 if (app()->style() && styleClassID)
4218 app()->style()->setStyle(this, styleClassID);
4219}
4220
4221
4222void uiListBox::items_draw(int index, const Rect & itemRect)
4223{
4224 int x = itemRect.X1 + 1;
4225 int y = itemRect.Y1 + (itemRect.height() - listBoxStyle().textFont->height) / 2;
4226 canvas()->drawText(listBoxStyle().textFont, x, y, m_items.get(index));
4227}
4228
4229
4230// uiListBox
4232
4233
4234
4236// uiColorListBox
4237
4238
4239uiColorListBox::uiColorListBox(uiWindow * parent, const Point & pos, const Size & size, bool visible, uint32_t styleClassID)
4240 : uiCustomListBox(parent, pos, size, visible, 0),
4241 m_selectedColor((Color)0)
4242{
4243 objectType().uiColorListBox = true;
4244
4245 listBoxStyle().itemHeight = 10;
4246
4247 if (app()->style() && styleClassID)
4248 app()->style()->setStyle(this, styleClassID);
4249}
4250
4251
4252void uiColorListBox::items_draw(int index, const Rect & itemRect)
4253{
4254 constexpr int BORDER = 1;
4255 canvas()->setBrushColor((Color)index);
4256 canvas()->fillRectangle(itemRect.X1 + BORDER, itemRect.Y1 + BORDER, itemRect.X2 - BORDER, itemRect.Y2 - BORDER);
4257}
4258
4259
4260// uiColorListBox
4262
4263
4264
4266// uiFileBrowser
4267
4268
4269uiFileBrowser::uiFileBrowser(uiWindow * parent, const Point & pos, const Size & size, bool visible, uint32_t styleClassID)
4270 : uiCustomListBox(parent, pos, size, visible, 0),
4271 m_selected(-1)
4272{
4273 objectType().uiFileBrowser = true;
4274
4275 if (app()->style() && styleClassID)
4276 app()->style()->setStyle(this, styleClassID);
4277}
4278
4279
4280void uiFileBrowser::items_draw(int index, const Rect & itemRect)
4281{
4282 int x = itemRect.X1 + 1;
4283 int y = itemRect.Y1 + (itemRect.height() - listBoxStyle().textFont->height) / 2;
4284 canvas()->drawText(listBoxStyle().textFont, x, y, m_dir.get(index)->name);
4285 if (m_dir.get(index)->isDir) {
4286 static const char * DIRTXT = "[dir]";
4287 int x = itemRect.X2 - canvas()->textExtent(listBoxStyle().textFont, DIRTXT);
4288 canvas()->drawText(listBoxStyle().textFont, x, y, DIRTXT);
4289 }
4290}
4291
4292
4293void uiFileBrowser::items_select(int index, bool select)
4294{
4295 if (select)
4296 m_selected = index;
4297 else if (index == m_selected || index == -1)
4298 m_selected = -1;
4299}
4300
4301
4302void uiFileBrowser::setDirectory(char const * path)
4303{
4304 m_dir.setDirectory(path);
4305 m_selected = m_dir.count() > 0 ? 0 : -1;
4306 repaint();
4307}
4308
4309
4310void uiFileBrowser::changeDirectory(char const * path)
4311{
4312 m_dir.changeDirectory(path);
4313 m_selected = m_dir.count() > 0 ? 0 : -1;
4314 repaint();
4315}
4316
4317
4319{
4320 return m_selected >= 0 ? m_dir.get(m_selected)->name : nullptr;
4321}
4322
4323
4325{
4326 return m_selected >= 0 ? m_dir.get(m_selected)->isDir : false;
4327}
4328
4329
4330void uiFileBrowser::enterSubDir()
4331{
4332 if (m_selected >= 0) {
4333 auto selItem = m_dir.get(m_selected);
4334 if (selItem->isDir) {
4335 m_dir.changeDirectory(selItem->name);
4336 m_selected = 0;
4337 onChange();
4338 repaint();
4339 }
4340 }
4341}
4342
4343
4345{
4346 m_dir.reload();
4347 m_selected = imin(m_dir.count() - 1, m_selected);
4348 onChange();
4349 repaint();
4350}
4351
4352
4353void uiFileBrowser::processEvent(uiEvent * event)
4354{
4355 uiCustomListBox::processEvent(event);
4356
4357 switch (event->id) {
4358
4359 case UIEVT_KEYDOWN:
4360 if (event->params.key.VK == VK_RETURN)
4361 enterSubDir();
4362 else if (event->params.key.VK == VK_BACKSPACE) {
4363 // backspace moves to parent dir
4364 m_selected = 0; // select ".."
4365 enterSubDir();
4366 }
4367 break;
4368
4369 case UIEVT_KEYUP:
4370 break;
4371
4372 case UIEVT_DBLCLICK:
4373 enterSubDir();
4374 break;
4375
4376 default:
4377 break;
4378 }
4379}
4380
4381
4382// uiFileBrowser
4384
4385
4386
4388// uiCustomComboBox
4389
4390
4391uiCustomComboBox::uiCustomComboBox(uiWindow * parent, const Point & pos, const Size & size, int listHeight, bool visible, uint32_t styleClassID)
4392 : uiControl(parent, pos, size, visible, 0),
4393 m_listHeight(listHeight),
4394 m_loseFocusBy(0),
4395 m_listBoxParent(nullptr)
4396{
4397 objectType().uiCustomComboBox = true;
4398
4399 windowProps().focusable = true;
4400
4401 windowStyle().borderSize = 0;
4402
4403 if (app()) {
4404 m_comboBoxStyle.adaptToDisplayColors(app()->displayColors());
4405 if (app()->style() && styleClassID)
4406 app()->style()->setStyle(this, styleClassID);
4407
4408 m_listBoxParent = new uiWindow(app()->rootWindow(), Point(0, 0), Size(0, 0), false, 0);
4409 m_listBoxParent->windowStyle().borderSize = 0;
4410 m_listBoxParent->windowStyle().focusedBorderSize = 0;
4411 }
4412
4413}
4414
4415
4416uiCustomComboBox::~uiCustomComboBox()
4417{
4418}
4419
4420
4421// index = -1 -> deselect all
4423{
4424 if (index < 0)
4425 listbox()->deselectAll();
4426 else
4427 listbox()->selectItem(index);
4428 updateEditControl();
4429}
4430
4431
4432void uiCustomComboBox::processEvent(uiEvent * event)
4433{
4434 uiControl::processEvent(event);
4435
4436 switch (event->id) {
4437
4438 case UIEVT_CREATE:
4439 listbox()->onKillFocus = [&]() {
4440 closeListBox();
4441 };
4442 listbox()->onChange = [&]() {
4443 updateEditControl();
4444 onChange();
4445 };
4446 listbox()->onKeyType = [&](uiKeyEventInfo const & key) {
4447 if (key.VK == VK_TAB || key.VK == VK_RETURN) {
4448 closeListBox();
4449 if (key.VK == VK_TAB)
4450 m_loseFocusBy = key.SHIFT ? -1 : 2;
4451 } else {
4452 uiEvent evt = uiEvent(parentFrame(), UIEVT_KEYDOWN);
4453 evt.params.key = key;
4454 app()->postEvent(&evt);
4455 evt.id = UIEVT_KEYUP;
4456 app()->postEvent(&evt);
4457 }
4458 };
4459 editcontrol()->setParentProcessKbdEvents(true); // we want keyboard events also here
4460 break;
4461
4462 case UIEVT_PAINT:
4463 beginPaint(event, uiControl::clientRect(uiOrigin::Window));
4464 paintButton();
4465 break;
4466
4467 case UIEVT_MOUSEBUTTONDOWN:
4468 if (event->params.mouse.changedButton == 1 && getButtonRect().contains(event->params.mouse.status.X, event->params.mouse.status.Y))
4469 switchListBox();
4470 break;
4471
4472 case UIEVT_CHILDSETFOCUS:
4473 if (m_comboBoxProps.openOnFocus && event->params.focusInfo.newFocused == editcontrol()
4474 && event->params.focusInfo.oldFocused != listbox()
4475 && event->params.focusInfo.oldFocused != this) {
4476 openListBox();
4477 }
4478 break;
4479
4480 case UIEVT_SETFOCUS:
4481 if (m_loseFocusBy) {
4482 app()->moveFocus(m_loseFocusBy);
4483 m_loseFocusBy = 0;
4484 } else {
4485 if (event->params.focusInfo.oldFocused != listbox() && event->params.focusInfo.oldFocused != editcontrol()) {
4486 if (m_comboBoxProps.openOnFocus) {
4487 openListBox();
4488 } else if (!isListBoxOpen()) {
4489 app()->setFocusedWindow(editcontrol());
4490 }
4491 } else if (event->params.focusInfo.oldFocused == listbox()) {
4492 app()->setFocusedWindow(editcontrol());
4493 }
4494 }
4495 break;
4496
4497 case UIEVT_KEYDOWN:
4498 listbox()->processEvent(event);
4499 break;
4500
4501 case UIEVT_KEYUP:
4502 // ALT-DOWN or ALT-UP or ENTER opens listbox
4503 if (((event->params.key.RALT || event->params.key.LALT) && (event->params.key.VK == VK_DOWN || event->params.key.VK == VK_UP)) || (event->params.key.VK == VK_RETURN))
4504 switchListBox();
4505 break;
4506
4507 case UIEVT_DESTROY:
4508 app()->destroyWindow(m_listBoxParent);
4509 break;
4510
4511 default:
4512 break;
4513 }
4514}
4515
4516
4517void uiCustomComboBox::openListBox()
4518{
4519 Rect r = rect(uiOrigin::Screen);
4520 if (r.Y2 + m_listHeight + 1 >= app()->rootWindow()->size().height) {
4521 // open upwards
4522 r.Y2 = r.Y1 - 1;
4523 r.Y1 = r.Y2 - m_listHeight;
4524 } else {
4525 // open downwards
4526 r.Y1 = r.Y2 + 1;
4527 r.Y2 = r.Y1 + m_listHeight;
4528 }
4529 app()->reshapeWindow(m_listBoxParent, r);
4530
4531 m_listBoxParent->bringOnTop();
4532 app()->showWindow(m_listBoxParent, true);
4533 app()->setActiveWindow(m_listBoxParent);
4534 app()->reshapeWindow(listbox(), r.translate(-r.X1, -r.Y1));
4535 app()->setFocusedWindow(listbox());
4537}
4538
4539
4540void uiCustomComboBox::closeListBox()
4541{
4542 app()->showWindow(m_listBoxParent, false);
4543 if (app()->focusedWindow() == nullptr || app()->focusedWindow() == listbox()) {
4545 app()->setFocusedWindow(this);
4546 }
4547 parentFrame()->windowProps().activeLook = false;
4548}
4549
4550
4551void uiCustomComboBox::switchListBox()
4552{
4553 if (isListBoxOpen()) {
4554 closeListBox();
4555 app()->setFocusedWindow(editcontrol());
4556 } else {
4557 openListBox();
4558 }
4559}
4560
4561
4562Size uiCustomComboBox::getEditControlSize()
4563{
4564 Rect clientArea = uiControl::clientRect(uiOrigin::Window);
4565 return Size(clientArea.width() - buttonWidth(), clientArea.height());
4566}
4567
4568
4569int uiCustomComboBox::buttonWidth()
4570{
4571 Rect clientArea = uiControl::clientRect(uiOrigin::Window);
4572 return clientArea.height() / 2;
4573}
4574
4575
4576Rect uiCustomComboBox::getButtonRect()
4577{
4579 btnRect.X1 = btnRect.X2 - buttonWidth() + 1;
4580 return btnRect;
4581}
4582
4583
4584void uiCustomComboBox::paintButton()
4585{
4586 Rect btnRect = getButtonRect();
4587
4588 // button background
4589 canvas()->setBrushColor(m_comboBoxStyle.buttonBackgroundColor);
4590 canvas()->fillRectangle(btnRect);
4591
4592 // button glyph
4593 canvas()->setPenColor(m_comboBoxStyle.buttonColor);
4594 Rect arrowRect = btnRect.hShrink(1).vShrink(2);
4595 int hHeight = arrowRect.height() / 2;
4596 int hWidth = arrowRect.width() / 2;
4597 constexpr int vDist = 2;
4598 canvas()->drawLine(arrowRect.X1, arrowRect.Y1 + hHeight - vDist, arrowRect.X1 + hWidth, arrowRect.Y1);
4599 canvas()->drawLine(arrowRect.X1 + hWidth, arrowRect.Y1, arrowRect.X2, arrowRect.Y1 + hHeight - vDist);
4600 canvas()->drawLine(arrowRect.X1, arrowRect.Y1 + hHeight + vDist, arrowRect.X1 + hWidth, arrowRect.Y2);
4601 canvas()->drawLine(arrowRect.X1 + hWidth, arrowRect.Y2, arrowRect.X2, arrowRect.Y1 + hHeight + vDist);
4602}
4603
4604
4605// uiCustomComboBox
4607
4608
4609
4611// uiComboBox
4612
4613
4614uiComboBox::uiComboBox(uiWindow * parent, const Point & pos, const Size & size, int listHeight, bool visible, uint32_t styleClassID)
4615 : uiCustomComboBox(parent, pos, size, listHeight, visible, 0),
4616 m_textEdit(nullptr),
4617 m_listBox(nullptr)
4618{
4619 objectType().uiComboBox = true;
4620
4621 m_textEdit = new uiTextEdit(this, "", Point(windowStyle().borderSize, windowStyle().borderSize), getEditControlSize(), true, 0);
4622 m_textEdit->textEditProps().hasCaret = false;
4623 m_textEdit->textEditProps().allowEdit = false;
4624
4625 m_listBox = new uiListBox(getListBoxParent(), Point(0, 0), Size(0, 0), true, 0);
4626
4627 if (app()->style() && styleClassID)
4628 app()->style()->setStyle(this, styleClassID);
4629}
4630
4631
4632uiComboBox::~uiComboBox()
4633{
4634}
4635
4636
4637// refresh text edit with the selected listbox item
4638void uiComboBox::updateEditControl()
4639{
4640 int idx = selectedItem();
4641 m_textEdit->setText(idx > -1 ? items().get(idx) : "");
4642 m_textEdit->repaint();
4643}
4644
4645
4646// uiComboBox
4648
4649
4650
4652// uiColorComboBox
4653
4654
4655uiColorComboBox::uiColorComboBox(uiWindow * parent, const Point & pos, const Size & size, int listHeight, bool visible, uint32_t styleClassID)
4656 : uiCustomComboBox(parent, pos, size, listHeight, visible, 0),
4657 m_colorBox(nullptr),
4658 m_colorListBox(nullptr)
4659{
4660 objectType().uiColorComboBox = true;
4661
4662 m_colorBox = new uiColorBox(this, Point(windowStyle().borderSize, windowStyle().borderSize), getEditControlSize(), Color::BrightWhite, true, 0);
4663 m_colorListBox = new uiColorListBox(getListBoxParent(), Point(0, 0), Size(0, 0), true, 0);
4664
4665 if (app()->style() && styleClassID)
4666 app()->style()->setStyle(this, styleClassID);
4667}
4668
4669
4670uiColorComboBox::~uiColorComboBox()
4671{
4672}
4673
4674
4675// refresh text edit with the selected listbox item
4676void uiColorComboBox::updateEditControl()
4677{
4678 m_colorBox->setColor( (Color) selectedItem() );
4679}
4680
4681
4682// uiColorComboBox
4684
4685
4686
4688// uiCheckBox
4689
4690
4691uiCheckBox::uiCheckBox(uiWindow * parent, const Point & pos, const Size & size, uiCheckBoxKind kind, bool visible, uint32_t styleClassID)
4692 : uiControl(parent, pos, size, visible, 0),
4693 m_checked(false),
4694 m_kind(kind),
4695 m_groupIndex(-1)
4696{
4697 objectType().uiCheckBox = true;
4698
4699 windowProps().focusable = true;
4700
4701 windowStyle().borderSize = 1;
4703 windowStyle().borderColor = RGB888(64, 64, 64);
4704
4705 if (app()) {
4706 m_checkBoxStyle.adaptToDisplayColors(app()->displayColors());
4707 if (app()->style() && styleClassID)
4708 app()->style()->setStyle(this, styleClassID);
4709 }
4710}
4711
4712
4713uiCheckBox::~uiCheckBox()
4714{
4715}
4716
4717
4718void uiCheckBox::paintCheckBox()
4719{
4721 // background
4722 RGB888 bkColor = m_checked ? m_checkBoxStyle.checkedBackgroundColor : m_checkBoxStyle.backgroundColor;
4723 if (isMouseOver())
4724 bkColor = m_checkBoxStyle.mouseOverBackgroundColor;
4725 canvas()->setBrushColor(bkColor);
4726 canvas()->fillRectangle(bkgRect);
4727 // content
4728 if (m_checked) {
4729 Rect r = rect(uiOrigin::Window).shrink(5);
4730 switch (m_kind) {
4732 canvas()->setPenColor(isMouseOver() ? m_checkBoxStyle.mouseOverForegroundColor : m_checkBoxStyle.foregroundColor);
4733 canvas()->drawLine(r.X1, r.Y2 - r.height() / 3, r.X1 + r.width() / 3, r.Y2);
4734 canvas()->drawLine(r.X1 + r.width() / 3, r.Y2, r.X2, r.Y1);
4735 break;
4737 canvas()->setBrushColor(isMouseOver() ? m_checkBoxStyle.mouseOverForegroundColor : m_checkBoxStyle.foregroundColor);
4738 canvas()->fillEllipse(r.X1 + r.width() / 2 - 1, r.Y1 + r.height() / 2 - 1, r.width(), r.height());
4739 break;
4740 }
4741 }
4742}
4743
4744
4745void uiCheckBox::processEvent(uiEvent * event)
4746{
4747 uiControl::processEvent(event);
4748
4749 switch (event->id) {
4750
4751 case UIEVT_PAINT:
4752 beginPaint(event, uiControl::clientRect(uiOrigin::Window));
4753 paintCheckBox();
4754 break;
4755
4756 case UIEVT_CLICK:
4757 trigger();
4758 onClick();
4759 break;
4760
4761 case UIEVT_MOUSEENTER:
4762 repaint(); // to update background color
4763 break;
4764
4765 case UIEVT_MOUSEBUTTONDOWN:
4766 if (event->params.mouse.changedButton == 1)
4767 repaint();
4768 break;
4769
4770 case UIEVT_MOUSELEAVE:
4771 repaint(); // to update background
4772 break;
4773
4774 case UIEVT_KEYUP:
4775 if (event->params.key.VK == VK_RETURN || event->params.key.VK == VK_KP_ENTER || event->params.key.VK == VK_SPACE) {
4776 trigger();
4777 onClick();
4778 }
4779 break;
4780
4781 default:
4782 break;
4783 }
4784}
4785
4786
4787// action to perfom on mouse up or keyboard space/enter
4788void uiCheckBox::trigger()
4789{
4790 switch (m_kind) {
4792 m_checked = !m_checked;
4793 break;
4795 m_checked = true;
4796 unCheckGroup();
4797 break;
4798 }
4799 onChange();
4800 repaint();
4801}
4802
4803
4805{
4806 if (value != m_checked) {
4807 m_checked = value;
4808 if (m_kind == uiCheckBoxKind::RadioButton && value == true)
4809 unCheckGroup();
4810 repaint();
4811 }
4812}
4813
4814
4815// unchecks all other items of the same group
4816void uiCheckBox::unCheckGroup()
4817{
4818 if (m_groupIndex == -1)
4819 return;
4820 for (auto sibling = parent()->firstChild(); sibling; sibling = sibling->next()) {
4821 if (sibling != this && objectType().uiCheckBox) {
4822 uiCheckBox * chk = (uiCheckBox*) sibling;
4823 if (chk->m_groupIndex == m_groupIndex)
4824 chk->setChecked(false);
4825 }
4826 }
4827}
4828
4829
4830
4831// uiCheckBox
4833
4834
4835
4837// uiSlider
4838
4839
4840uiSlider::uiSlider(uiWindow * parent, const Point & pos, const Size & size, uiOrientation orientation, bool visible, uint32_t styleClassID)
4841 : uiControl(parent, pos, size, visible, 0),
4842 m_orientation(orientation),
4843 m_position(0),
4844 m_min(0),
4845 m_max(99),
4846 m_ticksFrequency(25)
4847{
4848 objectType().uiSlider = true;
4849
4850 windowStyle().borderSize = 1;
4851 windowStyle().borderColor = RGB888(255, 255, 255);
4852
4853 windowProps().focusable = true;
4854
4855 if (app()) {
4856 m_sliderStyle.adaptToDisplayColors(app()->displayColors());
4857 if (app()->style() && styleClassID)
4858 app()->style()->setStyle(this, styleClassID);
4859 }
4860}
4861
4862
4863uiSlider::~uiSlider()
4864{
4865}
4866
4867
4869{
4870 if (value != m_position) {
4871 m_position = iclamp(value, m_min, m_max);
4872 repaint();
4873 onChange();
4874 }
4875}
4876
4877
4878void uiSlider::setup(int min, int max, int ticksFrequency)
4879{
4880 m_min = min;
4881 m_max = max;
4882 m_ticksFrequency = ticksFrequency;
4883 m_position = iclamp(m_position, m_min, m_max);
4884}
4885
4886
4887void uiSlider::paintSlider()
4888{
4890 Rect slideRect = cRect.shrink(4);
4891 Rect gripRect = getGripRect();
4892 // background
4893 canvas()->setBrushColor(m_sliderStyle.backgroundColor);
4894 canvas()->fillRectangle(cRect);
4895 // slide
4896 canvas()->setBrushColor(m_sliderStyle.slideColor);
4897 switch (m_orientation) {
4899 canvas()->fillRectangle(gripRect.X2, slideRect.Y1, slideRect.X2, slideRect.Y2); // right slide
4900 canvas()->setBrushColor(m_sliderStyle.rangeColor);
4901 canvas()->fillRectangle(slideRect.X1, slideRect.Y1, gripRect.X1, slideRect.Y2); // left slide
4902 break;
4904 canvas()->fillRectangle(slideRect.X1, slideRect.Y1, slideRect.X2, gripRect.Y1); // upper slide
4905 canvas()->setBrushColor(m_sliderStyle.rangeColor);
4906 canvas()->fillRectangle(slideRect.X1, gripRect.Y2, slideRect.X2, slideRect.Y2); // bottom slide
4907 break;
4908 }
4909 // ticks
4910 if (m_ticksFrequency > 0) {
4911 canvas()->setPenColor(m_sliderStyle.ticksColor);
4912 int range = m_max - m_min + 0;
4913 for (int p = m_min; p <= m_max; p += m_ticksFrequency) {
4914 switch (m_orientation) {
4916 {
4917 int x = slideRect.X1 + slideRect.width() * (p - m_min) / range;
4918 canvas()->drawLine(x, slideRect.Y1, x, slideRect.Y2);
4919 break;
4920 }
4922 {
4923 int y = slideRect.Y2 - slideRect.height() * (p - m_min) / range;
4924 canvas()->drawLine(slideRect.X1, y, slideRect.X2, y);
4925 break;
4926 }
4927 }
4928 }
4929 }
4930 // grip
4931 canvas()->setBrushColor(isMouseOver() ? m_sliderStyle.mouseOverGripColor : m_sliderStyle.gripColor);
4932 canvas()->fillRectangle(gripRect);
4933 canvas()->setPenColor(m_sliderStyle.gripColor);
4934 canvas()->drawRectangle(gripRect);
4935}
4936
4937
4938Rect uiSlider::getGripRect()
4939{
4941 Rect slideRect = cRect.shrink(4);
4942 int range = m_max - m_min + 0;
4943 switch (m_orientation) {
4945 {
4946 int x = slideRect.X1 + slideRect.width() * (m_position - m_min) / range;
4947 return Rect(x - 4, cRect.Y1, x + 4, cRect.Y2);
4948 }
4950 {
4951 int y = slideRect.Y2 - slideRect.height() * (m_position - m_min) / range;
4952 return Rect(cRect.X1, y - 4, cRect.X2, y + 4);
4953 }
4954 default:
4955 return Rect();
4956 }
4957}
4958
4959
4960void uiSlider::moveGripTo(int x, int y)
4961{
4963 Rect slideRect = cRect.shrink(4);
4964 int range = m_max - m_min + 1;
4965 switch (m_orientation) {
4967 setPosition(m_min + (x - slideRect.X1) * range / slideRect.width());
4968 break;
4970 setPosition(m_min + (slideRect.Y2 - y) * range / slideRect.height());
4971 break;
4972 }
4973}
4974
4975
4976void uiSlider::processEvent(uiEvent * event)
4977{
4978 uiControl::processEvent(event);
4979
4980 switch (event->id) {
4981
4982 case UIEVT_PAINT:
4983 beginPaint(event, uiControl::clientRect(uiOrigin::Window));
4984 paintSlider();
4985 break;
4986
4987 case UIEVT_MOUSEBUTTONDOWN:
4988 moveGripTo(event->params.mouse.status.X, event->params.mouse.status.Y);
4989 break;
4990
4991 case UIEVT_MOUSEMOVE:
4992 if (app()->capturedMouseWindow() == this)
4993 moveGripTo(event->params.mouse.status.X, event->params.mouse.status.Y);
4994 break;
4995
4996 case UIEVT_KEYDOWN:
4997 handleKeyDown(event->params.key);
4998 break;
4999
5000 case UIEVT_MOUSEENTER:
5001 repaint(); // to update background color
5002 break;
5003
5004 case UIEVT_MOUSELEAVE:
5005 repaint(); // to update background
5006 break;
5007
5008 default:
5009 break;
5010 }
5011}
5012
5013
5014void uiSlider::handleKeyDown(uiKeyEventInfo key)
5015{
5016 switch (key.VK) {
5017 case VK_UP:
5018 case VK_KP_UP:
5019 case VK_LEFT:
5020 case VK_KP_LEFT:
5021 setPosition(m_position - 1);
5022 break;
5023
5024 case VK_DOWN:
5025 case VK_KP_DOWN:
5026 case VK_RIGHT:
5027 case VK_KP_RIGHT:
5028 setPosition(m_position + 1);
5029 break;
5030
5031 case VK_PAGEUP:
5032 case VK_KP_PAGEUP:
5033 setPosition(m_position + m_ticksFrequency);
5034 break;
5035
5036 case VK_PAGEDOWN:
5037 case VK_KP_PAGEDOWN:
5038 setPosition(m_position - m_ticksFrequency);
5039 break;
5040
5041 case VK_HOME:
5042 case VK_KP_HOME:
5043 setPosition(m_min);
5044 break;
5045
5046 case VK_END:
5047 case VK_KP_END:
5048 setPosition(m_max);
5049 break;
5050
5051 default:
5052 break;
5053 }
5054}
5055
5056
5057
5058
5059// uiSlider
5061
5062
5063
5065// uiProgressBar
5066
5067
5068uiProgressBar::uiProgressBar(uiWindow * parent, const Point & pos, const Size & size, bool visible, uint32_t styleClassID)
5069 : uiControl(parent, pos, size, visible, 0)
5070{
5071 objectType().uiProgressBar = true;
5072
5073 windowProps().focusable = false;
5074 windowStyle().borderSize = 1;
5075 windowStyle().borderColor = RGB888(64, 64, 64);
5076
5077 if (app()) {
5078 m_progressBarStyle.adaptToDisplayColors(app()->displayColors());
5079 if (app()->style() && styleClassID)
5080 app()->style()->setStyle(this, styleClassID);
5081 }
5082
5083 m_percentage = 0;
5084}
5085
5086
5087uiProgressBar::~uiProgressBar()
5088{
5089}
5090
5091
5092void uiProgressBar::paintProgressBar()
5093{
5095
5096 int splitPos = cRect.width() * m_percentage / 100;
5097 Rect fRect = Rect(cRect.X1, cRect.Y1, cRect.X1 + splitPos, cRect.Y2);
5098 Rect bRect = Rect(cRect.X1 + splitPos + 1, cRect.Y1, cRect.X2, cRect.Y2);
5099
5100 // the bar
5101 canvas()->setBrushColor(m_progressBarStyle.foregroundColor);
5102 canvas()->fillRectangle(fRect);
5103 canvas()->setBrushColor(m_progressBarStyle.backgroundColor);
5104 canvas()->fillRectangle(bRect);
5105
5106 if (m_progressBarProps.showPercentage) {
5107 char txt[5];
5108 sprintf(txt, "%d%%", m_percentage);
5109 canvas()->setGlyphOptions(GlyphOptions().FillBackground(false).DoubleWidth(0).Bold(false).Italic(false).Underline(false).Invert(0));
5110 canvas()->setPenColor(m_progressBarStyle.textColor);
5111 int x = fRect.X2 - canvas()->textExtent(m_progressBarStyle.textFont, txt);
5112 int y = cRect.Y1 + (cRect.height() - m_progressBarStyle.textFont->height) / 2;
5113 canvas()->drawText(m_progressBarStyle.textFont, x, y, txt);
5114 }
5115}
5116
5117
5118void uiProgressBar::processEvent(uiEvent * event)
5119{
5120 uiControl::processEvent(event);
5121
5122 switch (event->id) {
5123
5124 case UIEVT_PAINT:
5125 beginPaint(event, uiControl::clientRect(uiOrigin::Window));
5126 paintProgressBar();
5127 break;
5128
5129 default:
5130 break;
5131 }
5132}
5133
5134
5136{
5137 value = imin(imax(0, value), 100);
5138 if (value != m_percentage) {
5139 m_percentage = value;
5140 repaint();
5141 }
5142}
5143
5144
5145// uiProgressBar
5147
5148
5149
5151// uiSimpleMenu
5152
5153
5154uiSimpleMenu::uiSimpleMenu(uiWindow * parent, const Point & pos, const Size & size, bool visible, uint32_t styleClassID)
5155 : uiCustomListBox(parent, pos, size, visible, 0)
5156{
5157 objectType().uiSimpleMenu = true;
5158
5161
5162 if (app()->style() && styleClassID)
5163 app()->style()->setStyle(this, styleClassID);
5164}
5165
5166
5167void uiSimpleMenu::processEvent(uiEvent * event)
5168{
5169 uiCustomListBox::processEvent(event);
5170
5171 switch (event->id) {
5172
5173 case UIEVT_MOUSEBUTTONUP:
5174 if (event->params.mouse.changedButton == 1) {
5175 int idx = getItemAtMousePos(event->params.mouse.status.X, event->params.mouse.status.Y);
5176 if (idx >= 0)
5177 onSelect(idx);
5178 }
5179 break;
5180
5181 case UIEVT_KEYUP:
5182 if (event->params.key.VK == VK_RETURN || event->params.key.VK == VK_KP_ENTER || event->params.key.VK == VK_SPACE) {
5183 int idx = firstSelectedItem();
5184 if (idx >= 0)
5185 onSelect(idx);
5186 } else if (event->params.key.VK == VK_ESCAPE) {
5187 onSelect(-1);
5188 }
5189 break;
5190
5191 default:
5192 break;
5193
5194 }
5195}
5196
5197
5198void uiSimpleMenu::items_draw(int index, const Rect & itemRect)
5199{
5200 int x = itemRect.X1 + 1;
5201 int y = itemRect.Y1 + (itemRect.height() - listBoxStyle().textFont->height) / 2;
5202 canvas()->drawText(listBoxStyle().textFont, x, y, m_items.get(index));
5203}
5204
5205
5206// uiSimpleMenu
5208
5209
5210
5212// uiSplitButton
5213
5214
5215uiSplitButton::uiSplitButton(uiWindow * parent, char const * text, const Point & pos, const Size & size, int listHeight, char const * itemsText, char separator, bool visible, uint32_t styleClassID)
5216 : uiCustomComboBox(parent, pos, size, listHeight, visible, 0),
5217 m_button(nullptr),
5218 m_menu(nullptr),
5219 m_selectedItem(-1)
5220{
5221 objectType().uiSplitButton = true;
5222
5223 comboBoxProps().openOnFocus = false;
5224
5225 m_button = new uiButton(this, text, Point(windowStyle().borderSize, windowStyle().borderSize), getEditControlSize(), uiButtonKind::Button, true, 0);
5226 m_button->onMouseDown = [&](uiMouseEventInfo const & ev) {
5227 if (!isListBoxOpen())
5228 openListBox();
5229 };
5230
5231 m_menu = new uiSimpleMenu(getListBoxParent(), Point(0, 0), Size(0, 0), true, 0);
5232 m_menu->items().appendSepList(itemsText, separator);
5233 m_menu->onSelect = [&](int idx) {
5234 closeListBox();
5235 app()->setFocusedWindow(this);
5236 m_selectedItem = idx;
5237 };
5238
5239 if (app()->style() && styleClassID)
5240 app()->style()->setStyle(this, styleClassID);
5241}
5242
5243
5244uiSplitButton::~uiSplitButton()
5245{
5246}
5247
5248
5249void uiSplitButton::processEvent(uiEvent * event)
5250{
5251 uiCustomComboBox::processEvent(event);
5252
5253 switch (event->id) {
5254
5255 case UIEVT_SETFOCUS:
5256 if (m_selectedItem > -1) {
5257 onSelect(m_selectedItem);
5258 m_selectedItem = -1;
5259 }
5260 break;
5261
5262 default:
5263 break;
5264 };
5265}
5266
5267
5268void uiSplitButton::openListBox()
5269{
5270 m_menu->deselectAll();
5271 uiCustomComboBox::openListBox();
5272}
5273
5274
5275void uiSplitButton::updateEditControl()
5276{
5277}
5278
5279
5280void uiSplitButton::paintButton()
5281{
5282 Rect btnRect = getButtonRect();
5283
5284 // button background
5285 canvas()->setBrushColor(comboBoxStyle().buttonBackgroundColor);
5286 canvas()->fillRectangle(btnRect);
5287
5288 // button glyph
5289 Rect arrowRect = btnRect.hShrink(btnRect.width() / 4).vShrink(btnRect.height() / 4);
5290 if ((arrowRect.X1 + arrowRect.X2) & 1)
5291 --arrowRect.X1;
5292 bool up = isListBoxOpen();
5293 Point points[3] = { { arrowRect.X1, up ? arrowRect.Y2 : arrowRect.Y1 },
5294 { arrowRect.X2, up ? arrowRect.Y2 : arrowRect.Y1 },
5295 { (arrowRect.X1 + arrowRect.X2) / 2, up ? arrowRect.Y1 : arrowRect.Y2 } };
5296 canvas()->setBrushColor(comboBoxStyle().buttonColor);
5297 canvas()->setPenColor(comboBoxStyle().buttonColor);
5298 canvas()->fillPath(points, 3);
5299 canvas()->drawPath(points, 3);
5300}
5301
5302
5303// uiSplitButton
5305
5306
5307
5308} // end of namespace
5309
This file contains fabgl::Canvas definition.
virtual int colorsCount()=0
Determines number of colors this display can provide.
void enableBackgroundPrimitiveTimeout(bool value)
Enables or disables execution time limitation inside vertical retracing interrupt.
void setMouseCursor(Cursor *cursor)
Sets mouse cursor and make it visible.
Represents the base abstract class for bitmapped display controllers.
void fillRectangle(int X1, int Y1, int X2, int Y2)
Fills a rectangle using the current brush color.
Definition: canvas.cpp:278
int getHeight()
Determines the canvas height in pixels.
Definition: canvas.h:92
void drawTextWithEllipsis(FontInfo const *fontInfo, int X, int Y, char const *text, int maxX)
Draws a string at specified position. Add ellipses before truncation.
Definition: canvas.cpp:430
void drawBitmap(int X, int Y, Bitmap const *bitmap)
Draws a bitmap at specified position.
Definition: canvas.cpp:502
void setPaintOptions(PaintOptions options)
Sets paint options.
Definition: canvas.cpp:374
void setOrigin(int X, int Y)
Sets the axes origin.
Definition: canvas.cpp:52
void setClippingRect(Rect const &rect)
Sets clipping rectangle relative to the origin.
Definition: canvas.cpp:67
void drawRectangle(int X1, int Y1, int X2, int Y2)
Draws a rectangle using the current pen color.
Definition: canvas.cpp:263
void drawGlyph(int X, int Y, int width, int height, uint8_t const *data, int index=0)
Draws a glyph at specified position.
Definition: canvas.cpp:340
int getWidth()
Determines the canvas width in pixels.
Definition: canvas.h:83
void drawPath(Point const *points, int pointsCount)
Draws a sequence of lines.
Definition: canvas.cpp:521
void fillEllipse(int X, int Y, int width, int height)
Fills an ellipse specifying center and size, using current brush color.
Definition: canvas.cpp:320
void setBrushColor(uint8_t red, uint8_t green, uint8_t blue)
Sets brush (background) color specifying color components.
Definition: canvas.cpp:214
int textExtent(FontInfo const *fontInfo, char const *text)
Calculates text extension in pixels.
Definition: canvas.cpp:453
void invertRectangle(int X1, int Y1, int X2, int Y2)
Inverts a rectangle.
Definition: canvas.cpp:296
void drawLine(int X1, int Y1, int X2, int Y2)
Draws a line specifying initial and ending coordinates.
Definition: canvas.cpp:256
void setPenColor(uint8_t red, uint8_t green, uint8_t blue)
Sets pen (foreground) color specifying color components.
Definition: canvas.cpp:193
void clear()
Fills the entire canvas with the brush color.
Definition: canvas.cpp:109
void resetPaintOptions()
Resets paint options.
Definition: canvas.cpp:383
void resetGlyphOptions()
Resets glyph options.
Definition: canvas.cpp:368
void drawText(int X, int Y, char const *text, bool wrap=false)
Draws a string at specified position.
Definition: canvas.cpp:401
void lineTo(int X, int Y)
Draws a line starting from current pen position.
Definition: canvas.cpp:247
void moveTo(int X, int Y)
Moves current pen position to the spcified coordinates.
Definition: canvas.cpp:178
void setGlyphOptions(GlyphOptions options)
Sets drawing options for the next glyphs.
Definition: canvas.cpp:358
void fillPath(Point const *points, int pointsCount)
Fills the polygon enclosed in a sequence of lines.
Definition: canvas.cpp:532
A class with a set of drawing methods.
Definition: canvas.h:70
void changeDirectory(const char *subdir)
Sets relative directory path.
Definition: fabutils.cpp:682
bool reload()
Reloads directory content.
Definition: fabutils.cpp:850
bool setDirectory(const char *path)
Sets absolute directory path.
Definition: fabutils.cpp:669
int count()
Determines number of files in current directory.
Definition: fabutils.h:594
DirItem const * get(int index)
Gets file/directory at index.
Definition: fabutils.h:603
void setUIApp(uiApp *app)
Sets current UI app.
Definition: keyboard.h:149
The PS2 Keyboard controller class.
Definition: keyboard.h:77
void setupAbsolutePositioner(int width, int height, bool createAbsolutePositionsQueue, BitmappedDisplayController *updateDisplayController=nullptr, uiApp *app=nullptr)
Initializes absolute position handler.
Definition: mouse.cpp:194
void terminateAbsolutePositioner()
Terminates absolute position handler.
Definition: mouse.cpp:224
bool isMouseAvailable()
Checks if mouse has been detected and correctly initialized.
Definition: mouse.h:168
The PS2 Mouse controller class.
Definition: mouse.h:111
static Mouse * mouse()
Returns the instance of Mouse object automatically created by PS2Controller.
static Keyboard * keyboard()
Returns the instance of Keyboard object automatically created by PS2Controller.
void killTimer(uiTimerHandle handle)
Kills a timer.
Definition: fabui.cpp:945
virtual void init()
Method to inherit to implement an application.
Definition: fabui.cpp:604
int run(BitmappedDisplayController *displayController, Keyboard *keyboard=nullptr, Mouse *mouse=nullptr)
Initializes application and executes the main event loop.
Definition: fabui.cpp:237
void resizeWindow(uiWindow *window, int width, int height)
Resizes a window.
Definition: fabui.cpp:808
bool postEvent(uiEvent const *event)
Places an event in the event queue and returns without waiting for the receiver to process the event.
Definition: fabui.cpp:609
void destroyWindow(uiWindow *window)
Destroys a window.
Definition: fabui.cpp:1042
uiWindow * screenToWindow(Point &point)
Determines which window a point belongs to.
Definition: fabui.cpp:585
int showModalWindow(uiWindow *window)
Makes a window visible and handles it has a modal window.
Definition: fabui.cpp:903
uiWindow * moveFocus(int delta)
Move focus to a control with current focus index plus a delta.
Definition: fabui.cpp:760
void showWindow(uiWindow *window, bool value)
Makes a window visible or invisible.
Definition: fabui.cpp:829
uiMessageBoxResult messageBox(char const *title, char const *text, char const *button1Text, char const *button2Text=nullptr, char const *button3Text=nullptr, uiMessageBoxIcon icon=uiMessageBoxIcon::Question)
Displays a modal dialog box with an icon, text and some buttons.
Definition: fabui.cpp:1081
void repaintRect(Rect const &rect)
Repaints a screen area.
Definition: fabui.cpp:793
uiMessageBoxResult inputBox(char const *title, char const *text, char *inOutString, int maxLength, char const *button1Text, char const *button2Text=nullptr)
Displays a modal dialog box with a text, a text edit and up to two buttons.
Definition: fabui.cpp:1180
int endModalWindow(ModalWindowState *state)
Ends modal window processing.
Definition: fabui.cpp:891
uiWindow * setFocusedWindow(uiWindow *value)
Sets the focused window (control)
Definition: fabui.cpp:705
void enableKeyboardAndMouseEvents(bool value)
Enables or disables mouse and keyboard events.
Definition: fabui.cpp:1345
void quit(int exitCode)
Terminates application and free resources.
Definition: fabui.cpp:398
ModalWindowState * initModalWindow(uiWindow *window)
Begins modal window processing.
Definition: fabui.cpp:837
uiWindow * activeWindow()
Gets a pointer to the currently active window.
Definition: fabui.h:3120
uiTimerHandle setTimer(uiEvtHandler *dest, int periodMS)
Setups a timer.
Definition: fabui.cpp:936
uiMessageBoxResult fileDialog(char const *title, char *inOutDirectory, int maxDirNameSize, char *inOutFilename, int maxFileNameSize, char const *buttonOKText, char const *buttonCancelText, int frameWidth=200, int frameHeight=250)
Displays a modal open/save dialog box.
Definition: fabui.cpp:1269
uiStyle * style()
Gets current application controls style.
Definition: fabui.h:3435
Delegate< uiTimerHandle > onTimer
Timer event delegate.
Definition: fabui.h:3463
void joinAsyncRun()
Waits for runAsync termination.
Definition: fabui.cpp:371
uiFrame * rootWindow()
Gets a pointer to the root window.
Definition: fabui.h:3110
void repaintWindow(uiWindow *window)
Repaints a window.
Definition: fabui.cpp:787
uiWindow * setActiveWindow(uiWindow *value)
Sets the active window.
Definition: fabui.cpp:662
void processEvents()
Processes all events in queue.
Definition: fabui.cpp:380
void minimizeFrame(uiFrame *frame, bool value)
Minimizes or restores a frame.
Definition: fabui.cpp:919
uiWindow * focusedWindow()
Gets the focused window (control)
Definition: fabui.h:3145
void reshapeWindow(uiWindow *window, Rect const &rect)
Reshapes a window.
Definition: fabui.cpp:821
void moveWindow(uiWindow *window, int x, int y)
Moves a window.
Definition: fabui.cpp:802
uiApp & runAsync(BitmappedDisplayController *displayController, int taskStack=3000, Keyboard *keyboard=nullptr, Mouse *mouse=nullptr)
Initializes application and executes asynchronously the main event loop.
Definition: fabui.cpp:355
bool insertEvent(uiEvent const *event)
Inserts (first position) an event in the event queue and returns without waiting for the receiver to ...
Definition: fabui.cpp:615
void maximizeFrame(uiFrame *frame, bool value)
Maximizes or restores a frame.
Definition: fabui.cpp:912
bool processModalWindowEvents(ModalWindowState *state, int timeout)
Processes all messages from modal window.
Definition: fabui.cpp:855
Represents the whole application base class.
Definition: fabui.h:3030
char const * text()
Determines button text.
Definition: fabui.h:1295
uiButton(uiWindow *parent, char const *text, const Point &pos, const Size &size, uiButtonKind kind=uiButtonKind::Button, bool visible=true, uint32_t styleClassID=0)
Creates an instance of the object.
Definition: fabui.cpp:2528
Delegate onChange
Button changed event delegate.
Definition: fabui.h:1330
Delegate< uiMouseEventInfo const & > onMouseUp
Mouse up event delegate.
Definition: fabui.h:1351
Delegate< uiMouseEventInfo const & > onMouseDown
Mouse down event delegate.
Definition: fabui.h:1344
void setDown(bool value)
Sets button state of a switch button.
Definition: fabui.cpp:2670
Delegate onClick
Mouse click event delegate.
Definition: fabui.h:1337
void setText(char const *value)
Sets button text.
Definition: fabui.cpp:2559
Represents a button control. A button can have text and optionally a bitmap.
Definition: fabui.h:1260
void setChecked(bool value)
Sets current checkbox or radiobutton checked status.
Definition: fabui.cpp:4804
Delegate onChange
Change event delegate.
Definition: fabui.h:2606
uiCheckBox(uiWindow *parent, const Point &pos, const Size &size, uiCheckBoxKind kind=uiCheckBoxKind::CheckBox, bool visible=true, uint32_t styleClassID=0)
Creates an instance of the object.
Definition: fabui.cpp:4691
Delegate onClick
Mouse click event delegate.
Definition: fabui.h:2613
Represents a checkbox or a radiobutton.
Definition: fabui.h:2541
void setColor(Color value)
Sets current colorbox color.
Definition: fabui.cpp:3510
uiColorBox(uiWindow *parent, const Point &pos, const Size &size, Color color=Color::BrightWhite, bool visible=true, uint32_t styleClassID=0)
Creates an instance of the object.
Definition: fabui.cpp:3490
A color box is a control that shows a single color.
Definition: fabui.h:1843
uiColorComboBox(uiWindow *parent, const Point &pos, const Size &size, int listHeight, bool visible=true, uint32_t styleClassID=0)
Creates an instance of the object.
Definition: fabui.cpp:4655
uiColorListBox(uiWindow *parent, const Point &pos, const Size &size, bool visible=true, uint32_t styleClassID=0)
Creates an instance of the object.
Definition: fabui.cpp:4239
Shows a list of 16 colors, one selectable.
Definition: fabui.h:2206
StringList & items()
A list of strings representing items of the combobox.
Definition: fabui.h:2405
uiComboBox(uiWindow *parent, const Point &pos, const Size &size, int listHeight, bool visible=true, uint32_t styleClassID=0)
Creates an instance of the object.
Definition: fabui.cpp:4614
uiControl(uiWindow *parent, const Point &pos, const Size &size, bool visible, uint32_t styleClassID=0)
Creates an instance of the object.
Definition: fabui.cpp:2496
This is the base class for all controls. A control can have focus and is not activable.
Definition: fabui.h:998
uiComboBoxProps & comboBoxProps()
Sets or gets combobox properties.
Definition: fabui.h:2313
Delegate onChange
Change event delegate.
Definition: fabui.h:2337
uiComboBoxStyle & comboBoxStyle()
Sets or gets combobox style.
Definition: fabui.h:2299
uiCustomComboBox(uiWindow *parent, const Point &pos, const Size &size, int listHeight, bool visible, uint32_t styleClassID)
Creates an instance of the object.
Definition: fabui.cpp:4391
int selectedItem()
Represents currently selected item.
Definition: fabui.h:2320
void selectItem(int index)
Selects an item.
Definition: fabui.cpp:4422
This is a combination of a listbox and another component, base of all combobox components.
Definition: fabui.h:2274
uiCustomListBox(uiWindow *parent, const Point &pos, const Size &size, bool visible=true, uint32_t styleClassID=0)
Creates an instance of the object.
Definition: fabui.cpp:3903
int firstSelectedItem()
Gets the first selected item.
Definition: fabui.cpp:4120
Delegate onKillFocus
Kill focus event delegate.
Definition: fabui.h:2002
void deselectAll()
Deselects all selected items.
Definition: fabui.cpp:4062
int lastSelectedItem()
Gets the last selected item.
Definition: fabui.cpp:4130
Delegate onChange
Change event delegate.
Definition: fabui.h:1997
uiListBoxStyle & listBoxStyle()
Sets or gets listbox style.
Definition: fabui.h:1952
Delegate onClick
Mouse click event delegate.
Definition: fabui.h:2019
Delegate< uiKeyEventInfo const & > onKeyUp
Key-up event delegate.
Definition: fabui.h:2012
uiListBoxProps & listBoxProps()
Sets or gets list box properties.
Definition: fabui.h:1959
void selectItem(int index, bool add=false, bool range=false)
Selects a listbox item.
Definition: fabui.cpp:4022
Delegate< uiKeyEventInfo const & > onKeyType
Key-type event delegate.
Definition: fabui.h:2007
Delegate onDblClick
Mouse double click event delegate.
Definition: fabui.h:2027
void setScrollBar(uiOrientation orientation, int position, int visible, int range, bool repaintScrollbar)
Sets scrollbar position, visible portion and range.
Definition: fabui.cpp:4139
Shows generic a list of selectable items.
Definition: fabui.h:1928
uiApp * app()
Determines the app that owns this object.
Definition: fabui.h:318
Base class of all UI elements that can receive events.
Definition: fabui.h:303
void changeDirectory(char const *path)
Changes current directory as relative path.
Definition: fabui.cpp:4310
void setDirectory(char const *path)
Sets current directory as absolute path.
Definition: fabui.cpp:4302
char const * filename()
Currently selected filename.
Definition: fabui.cpp:4318
bool isDirectory()
Determines whether currently selected item is a directory.
Definition: fabui.cpp:4324
void update()
Reloads current directory content and repaints.
Definition: fabui.cpp:4344
uiFileBrowser(uiWindow *parent, const Point &pos, const Size &size, bool visible=true, uint32_t styleClassID=0)
Creates an instance of the object.
Definition: fabui.cpp:4269
Shows and navigates Virtual Filesystem content.
Definition: fabui.h:2108
void setTitle(char const *value)
Sets window title.
Definition: fabui.cpp:1936
Delegate onHide
Hide window event delegate.
Definition: fabui.h:911
uiFrame(uiWindow *parent, char const *title, const Point &pos, const Size &size, bool visible=true, uint32_t styleClassID=0)
Creates an instance of the object.
Definition: fabui.cpp:1906
Delegate onShow
Show window event delegate.
Definition: fabui.h:904
uiFrameProps & frameProps()
Sets or gets frame properties.
Definition: fabui.h:880
Delegate onPaint
Paint event delegate.
Definition: fabui.h:941
Delegate< uiKeyEventInfo const & > onKeyDown
Key-down event delegate.
Definition: fabui.h:931
void setTitleFmt(const char *format,...)
Sets window title as formatted text.
Definition: fabui.cpp:1950
Delegate< uiKeyEventInfo const & > onKeyUp
Key-up event delegate.
Definition: fabui.h:936
Delegate onResize
Resize window event delegate.
Definition: fabui.h:918
uiFrameStyle & frameStyle()
Sets or gets frame style.
Definition: fabui.h:873
Delegate< uiTimerHandle > onTimer
Timer event delegate.
Definition: fabui.h:926
char const * title()
Determines the window title.
Definition: fabui.h:848
Rect clientRect(uiOrigin origin)
Determines the client area bounding box.
Definition: fabui.cpp:1980
A frame is a window with a title bar, maximize/minimize/close buttons and that is resizeable or movea...
Definition: fabui.h:823
Bitmap const * bitmap()
Gets image bitmap.
Definition: fabui.h:1697
void setBitmap(Bitmap const *bitmap)
Sets image bitmap.
Definition: fabui.cpp:3325
uiImage(uiWindow *parent, Bitmap const *bitmap, const Point &pos, const Size &size=Size(0, 0), bool visible=true, uint32_t styleClassID=0)
Creates an instance of the object.
Definition: fabui.cpp:3299
Image control to display a static bitmap.
Definition: fabui.h:1663
char const * text()
Determines label text.
Definition: fabui.h:1606
void setTextFmt(const char *format,...)
Sets label formatted text.
Definition: fabui.cpp:3216
uiLabel(uiWindow *parent, char const *text, const Point &pos, const Size &size=Size(0, 0), bool visible=true, uint32_t styleClassID=0)
Creates an instance of the object.
Definition: fabui.cpp:3179
Delegate onClick
Mouse click event delegate.
Definition: fabui.h:1627
void update()
Updates the label content.
Definition: fabui.cpp:3232
void setText(char const *value)
Sets label text.
Definition: fabui.cpp:3207
A label is a static text UI element.
Definition: fabui.h:1563
uiListBox(uiWindow *parent, const Point &pos, const Size &size, bool visible=true, uint32_t styleClassID=0)
Creates an instance of the object.
Definition: fabui.cpp:4212
Shows a list of selectable string items.
Definition: fabui.h:2064
uiObjectType & objectType()
Determines the object type.
Definition: fabui.h:290
Delegate< Rect const & > onPaint
Paint event delegate.
Definition: fabui.h:1826
uiPaintBox(uiWindow *parent, const Point &pos, const Size &size, bool visible=true, uint32_t styleClassID=0)
Creates an instance of the object.
Definition: fabui.cpp:3430
uiPanel(uiWindow *parent, const Point &pos, const Size &size, bool visible=true, uint32_t styleClassID=0)
Creates an instance of the object.
Definition: fabui.cpp:3373
A panel is used to contain and to group some controls.
Definition: fabui.h:1738
void setPercentage(int value)
Sets percentage.
Definition: fabui.cpp:5135
uiProgressBar(uiWindow *parent, const Point &pos, const Size &size, bool visible=true, uint32_t styleClassID=0)
Creates an instance of the object.
Definition: fabui.cpp:5068
int VScrollBarRange()
Determines vertical scrollbar range.
Definition: fabui.h:1144
uiScrollableControl(uiWindow *parent, const Point &pos, const Size &size, bool visible=true, uint32_t styleClassID=0)
Creates an instance of the object.
Definition: fabui.cpp:3552
virtual void setScrollBar(uiOrientation orientation, int position, int visible, int range, bool repaintScrollbar=true)
Sets scrollbar position, visible portion and range.
Definition: fabui.cpp:3584
Delegate onChangeHScrollBar
Horizontal scrollbar change event delegate.
Definition: fabui.h:1152
int VScrollBarVisible()
Determines vertical scrollbar visible portion (aka thumb size) of the scrollable content.
Definition: fabui.h:1134
int VScrollBarPos()
Determines position of the vertical scrollbar thumb.
Definition: fabui.h:1125
Delegate onChangeVScrollBar
Vertical scrollbar change event delegate.
Definition: fabui.h:1157
Rect clientRect(uiOrigin origin)
Determines the client area bounding box.
Definition: fabui.cpp:3884
A scrollable control is a control with optionally vertical and/or horizontal scrollbars.
Definition: fabui.h:1060
StringList & items()
A list of strings representing the simplemenu content.
Definition: fabui.h:2867
Delegate< int > onSelect
Item select event.
Definition: fabui.h:2879
uiSimpleMenu(uiWindow *parent, const Point &pos, const Size &size, bool visible=true, uint32_t styleClassID=0)
Creates an instance of the object.
Definition: fabui.cpp:5154
Shows a list of selectable string items. Selection is done clicking or pressing ENTER or SPACE key.
Definition: fabui.h:2844
void setup(int min, int max, int ticksFrequency)
Sets minimum, maximum position and ticks frequency.
Definition: fabui.cpp:4878
void setPosition(int value)
Sets the slider position.
Definition: fabui.cpp:4868
Delegate onChange
Slider changed event delegate.
Definition: fabui.h:2730
uiSlider(uiWindow *parent, const Point &pos, const Size &size, uiOrientation orientation, bool visible=true, uint32_t styleClassID=0)
Creates an instance of the object.
Definition: fabui.cpp:4840
int max()
Gets maximum position.
Definition: fabui.h:2713
int min()
Gets minimum position.
Definition: fabui.h:2706
Delegate< int > onSelect
Item select event.
Definition: fabui.h:2942
uiSplitButton(uiWindow *parent, char const *text, const Point &pos, const Size &size, int listHeight, char const *itemsText, char separator=';', bool visible=true, uint32_t styleClassID=0)
Creates an instance of the object.
Definition: fabui.cpp:5215
char const * text()
Gets current content of the text edit.
Definition: fabui.h:1478
Delegate onChange
Text edit event delegate.
Definition: fabui.h:1486
void setTextFmt(const char *format,...)
Replaces current text.
Definition: fabui.cpp:2737
uiTextEdit(uiWindow *parent, char const *text, const Point &pos, const Size &size, bool visible=true, uint32_t styleClassID=0)
Creates an instance of the object.
Definition: fabui.cpp:2690
uiTextEditProps & textEditProps()
Sets or gets text edit properties.
Definition: fabui.h:1453
Delegate< uiKeyEventInfo const & > onKeyType
Key-type event delegate.
Definition: fabui.h:1491
void setText(char const *value)
Replaces current text.
Definition: fabui.cpp:2724
Represents a text edit control.
Definition: fabui.h:1421
uiWindow * prev()
Gets previous sibling.
Definition: fabui.h:442
bool hasChildren()
Determines whether this window has children.
Definition: fabui.h:463
void repaint(Rect const &rect)
Repaints a rectangle of this window.
Definition: fabui.cpp:1553
uiWindow * firstChild()
Gets first child.
Definition: fabui.h:449
bool isMouseOver()
Determines whether the mouse is over this window.
Definition: fabui.h:595
void bringOnTop()
Brings this window on top.
Definition: fabui.cpp:1520
void exitModal(int modalResult)
Exits from a modal window.
Definition: fabui.cpp:1841
Rect transformRect(Rect const &rect, uiWindow *baseWindow)
Transforms rectangle origins from current window to another one.
Definition: fabui.cpp:1543
uiWindow * parentFrame()
Determines the parent frame.
Definition: fabui.cpp:1888
Point pos()
Determines the window position relative to parent window.
Definition: fabui.h:484
Rect rect(uiOrigin origin)
Determines the window bounding box.
Definition: fabui.cpp:1565
uiWindow * next()
Gets next sibling.
Definition: fabui.h:433
uiWindow * lastChild()
Gets last child.
Definition: fabui.h:456
uint16_t styleClassID()
Determines current style class for this UI element.
Definition: fabui.h:659
void setParentProcessKbdEvents(bool value)
Enables a child window to send keyboard events to its parent.
Definition: fabui.h:668
Size size()
Determines the window size.
Definition: fabui.h:500
uiWindowProps & windowProps()
Sets or gets window properties.
Definition: fabui.h:543
uiWindowStyle & windowStyle()
Sets or gets window style.
Definition: fabui.h:550
int focusIndex()
Determines the focus index (aka tab-index)
Definition: fabui.h:643
Size clientSize()
Determines the client area size.
Definition: fabui.cpp:1588
bool isActiveWindow()
Determines wheter this window is the active window.
Definition: fabui.cpp:1849
void repaint()
Repaints this window.
Definition: fabui.cpp:1559
void bringAfter(uiWindow *insertionPoint)
Brings this window after another one.
Definition: fabui.cpp:1526
bool hasFocus()
Determines whether this window or control has focus.
Definition: fabui.cpp:1855
uiWindow * parent()
Determines the parent window.
Definition: fabui.h:557
uiWindow(uiWindow *parent, const Point &pos, const Size &size, bool visible, uint32_t styleClassID=0)
Creates an instance of the object.
Definition: fabui.cpp:1374
Point clientPos()
Determines position of the client area.
Definition: fabui.cpp:1594
uiWindowState state()
Determines the window state.
Definition: fabui.h:536
virtual Rect clientRect(uiOrigin origin)
Determines the client area bounding box.
Definition: fabui.cpp:1581
Base class for all visible UI elements (Frames and Controls)
Definition: fabui.h:403
GlyphOptions & DoubleWidth(uint8_t value)
Helper method to set or reset doubleWidth.
uint8_t width
GlyphOptions & Invert(uint8_t value)
Helper method to set or reset foreground and background swapping.
GlyphOptions & Underline(bool value)
Helper method to set or reset underlined.
GlyphOptions & Italic(bool value)
Helper method to set or reset italic.
GlyphOptions & Bold(bool value)
Helper method to set or reset bold.
GlyphOptions & FillBackground(bool value)
Helper method to set or reset fillBackground.
int16_t X
int16_t Y
uint8_t height
This file contains all classes related to FabGL Graphical User Interface.
This file contains some utility classes and functions.
uiButtonKind
Specifies the button kind.
Definition: fabui.h:1253
uiMessageBoxResult
Return values from uiApp.messageBox() method.
Definition: fabui.h:2987
uiOrientation
Item direction/orientation.
Definition: fabui.h:219
uiMessageBoxIcon
Icon displayed by the uiApp.messageBox() method.
Definition: fabui.h:2999
Color
This enum defines named colors.
CursorName
This enum defines a set of predefined mouse cursors.
uiCheckBoxKind
Specifies the combobox behaviour.
Definition: fabui.h:2529
@ VK_KP_LEFT
Definition: fabutils.h:1218
@ VK_KP_RIGHT
Definition: fabutils.h:1220
@ VK_RETURN
Definition: fabutils.h:1206
@ VK_ESCAPE
Definition: fabutils.h:1186
@ VK_PAGEUP
Definition: fabutils.h:1209
@ VK_KP_END
Definition: fabutils.h:1199
@ VK_SPACE
Definition: fabutils.h:1057
@ VK_KP_PAGEDOWN
Definition: fabutils.h:1212
@ VK_UP
Definition: fabutils.h:1213
@ VK_a
Definition: fabutils.h:1080
@ VK_KP_UP
Definition: fabutils.h:1214
@ VK_END
Definition: fabutils.h:1198
@ VK_PAGEDOWN
Definition: fabutils.h:1211
@ VK_KP_DELETE
Definition: fabutils.h:1194
@ VK_LCTRL
Definition: fabutils.h:1181
@ VK_DOWN
Definition: fabutils.h:1215
@ VK_HOME
Definition: fabutils.h:1196
@ VK_TAB
Definition: fabutils.h:1205
@ VK_KP_DOWN
Definition: fabutils.h:1216
@ VK_DELETE
Definition: fabutils.h:1193
@ VK_KP_ENTER
Definition: fabutils.h:1207
@ VK_BACKSPACE
Definition: fabutils.h:1195
@ VK_RCTRL
Definition: fabutils.h:1182
@ VK_LEFT
Definition: fabutils.h:1217
@ VK_KP_HOME
Definition: fabutils.h:1197
@ VK_RIGHT
Definition: fabutils.h:1219
@ VK_KP_PAGEUP
Definition: fabutils.h:1210
uiOrigin
Specifies window rectangle origin.
Definition: fabui.h:339
This file contains fabgl::Keyboard definition.
This file contains fabgl::Mouse definition.
Represents an image.
char const * name
Definition: fabutils.h:531
int16_t X
Definition: fabutils.h:210
int16_t Y
Definition: fabutils.h:211
Represents the coordinate of a point.
Definition: fabutils.h:209
Represents a 24 bit RGB color.
int16_t X1
Definition: fabutils.h:245
int16_t Y2
Definition: fabutils.h:248
int16_t X2
Definition: fabutils.h:247
int16_t Y1
Definition: fabutils.h:246
Represents a rectangle.
Definition: fabutils.h:244
int16_t height
Definition: fabutils.h:229
int16_t width
Definition: fabutils.h:228
Represents a bidimensional size.
Definition: fabutils.h:227
uint16_t doubleClickTime
Definition: fabui.h:2978
uint16_t caretBlinkingTime
Definition: fabui.h:2977
RGB888 mouseDownBackgroundColor
Definition: fabui.h:1222
FontInfo const * textFont
Definition: fabui.h:1226
RGB888 downTextColor
Definition: fabui.h:1225
RGB888 mouseOverTextColor
Definition: fabui.h:1223
Bitmap const * downBitmap
Definition: fabui.h:1229
Bitmap const * bitmap
Definition: fabui.h:1228
uint8_t bitmapTextSpace
Definition: fabui.h:1227
RGB888 mouseOverBackgroundColor
Definition: fabui.h:1221
RGB888 downBackgroundColor
Definition: fabui.h:1220
RGB888 backgroundColor
Definition: fabui.h:1219
Contains the button style.
Definition: fabui.h:1218
RGB888 mouseOverForegroundColor
Definition: fabui.h:2514
RGB888 foregroundColor
Definition: fabui.h:2515
RGB888 checkedBackgroundColor
Definition: fabui.h:2512
RGB888 mouseOverBackgroundColor
Definition: fabui.h:2513
RGB888 backgroundColor
Definition: fabui.h:2511
RGB888 buttonBackgroundColor
Definition: fabui.h:2251
uint8_t fillBackground
Definition: fabui.h:778
uint8_t moveable
Definition: fabui.h:774
uint8_t hasMaximizeButton
Definition: fabui.h:776
uint8_t resizeable
Definition: fabui.h:773
uint8_t hasCloseButton
Definition: fabui.h:775
uint8_t hasMinimizeButton
Definition: fabui.h:777
uint8_t minimized
Definition: fabui.h:794
uint8_t maximized
Definition: fabui.h:793
RGB888 buttonColor
Definition: fabui.h:744
RGB888 mouseOverButtonColor
Definition: fabui.h:747
RGB888 activeTitleBackgroundColor
Definition: fabui.h:740
RGB888 activeTitleColor
Definition: fabui.h:742
RGB888 mouseOverBackgroundButtonColor
Definition: fabui.h:746
FontInfo const * titleFont
Definition: fabui.h:743
RGB888 titleColor
Definition: fabui.h:741
RGB888 activeButtonColor
Definition: fabui.h:745
RGB888 backgroundColor
Definition: fabui.h:738
RGB888 titleBackgroundColor
Definition: fabui.h:739
RGB888 backgroundColor
Definition: fabui.h:1653
VirtualKey VK
Definition: fabui.h:157
Contains details about the key event.
Definition: fabui.h:156
FontInfo const * textFont
Definition: fabui.h:1550
RGB888 textColor
Definition: fabui.h:1552
uiHAlign textAlign
Definition: fabui.h:1553
RGB888 backgroundColor
Definition: fabui.h:1551
uint8_t selectOnMouseOver
Definition: fabui.h:1917
uint8_t allowMultiSelect
Definition: fabui.h:1916
FontInfo const * textFont
Definition: fabui.h:1898
RGB888 selectedTextColor
Definition: fabui.h:1900
RGB888 selectedBackgroundColor
Definition: fabui.h:1895
RGB888 focusedSelectedBackgroundColor
Definition: fabui.h:1896
RGB888 backgroundColor
Definition: fabui.h:1893
RGB888 focusedBackgroundColor
Definition: fabui.h:1894
Contains details about the mouse event.
Definition: fabui.h:168
RGB888 backgroundColor
Definition: fabui.h:1781
RGB888 backgroundColor
Definition: fabui.h:1728
FontInfo const * textFont
Definition: fabui.h:2760
RGB888 mouseOverScrollBarForegroundColor
Definition: fabui.h:1028
RGB888 mouseOverGripColor
Definition: fabui.h:2643
RGB888 backgroundColor
Definition: fabui.h:2638
uint8_t passwordMode
Definition: fabui.h:1405
FontInfo const * textFont
Definition: fabui.h:1390
RGB888 mouseOverBackgroundColor
Definition: fabui.h:1387
RGB888 backgroundColor
Definition: fabui.h:1386
RGB888 focusedBackgroundColor
Definition: fabui.h:1388
uint8_t activeLook
Definition: fabui.h:357
uint8_t activable
Definition: fabui.h:355
uint8_t focusable
Definition: fabui.h:356
uint8_t active
Definition: fabui.h:349
uint8_t visible
Definition: fabui.h:348
CursorName defaultCursor
Definition: fabui.h:369
RGB888 borderColor
Definition: fabui.h:370
uint8_t borderSize
Definition: fabui.h:373
RGB888 activeBorderColor
Definition: fabui.h:371
RGB888 focusedBorderColor
Definition: fabui.h:372
uint8_t focusedBorderSize
Definition: fabui.h:374
Specifies various glyph painting options.