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.uiStaticLabel) printf("uiStaticLabel ");
86 if (ot.uiImage) printf("uiImage ");
87 if (ot.uiPanel) printf("uiPanel ");
88 if (ot.uiPaintBox) printf("uiPaintBox ");
89 if (ot.uiCustomListBox) printf("uiCustomListBox ");
90 if (ot.uiListBox) printf("uiListBox ");
91 if (ot.uiFileBrowser) printf("uiFileBrowser ");
92 if (ot.uiComboBox) printf("uiComboBox ");
93 if (ot.uiCheckBox) printf("uiCheckBox ");
94 if (ot.uiSlider) printf("uiSlider ");
95 if (ot.uiColorListBox) printf("uiColorListBox ");
96 if (ot.uiCustomComboBox) printf("uiCustomComboBox ");
97 if (ot.uiColorBox) printf("uiColorBox ");
98 if (ot.uiColorComboBox) printf("uiColorComboBox ");
99 if (ot.uiProgressBar) printf("uiProgressBar ");
100 if (ot.uiSplitButton) printf("uiSplitButton ");
101 if (ot.uiSimpleMenu) printf("uiSimpleMenu ");
102 printf("] ");
103 }
104 switch (event->id) {
105 case UIEVT_DEBUGMSG:
106 printf(event->params.debugMsg);
107 break;
108 case UIEVT_MOUSEMOVE:
109 printf("X=%d Y=%d", event->params.mouse.status.X, event->params.mouse.status.Y);
110 break;
111 case UIEVT_MOUSEWHEEL:
112 printf("delta=%d", event->params.mouse.status.wheelDelta);
113 break;
114 case UIEVT_MOUSEBUTTONDOWN:
115 case UIEVT_MOUSEBUTTONUP:
116 case UIEVT_DBLCLICK:
117 printf("btn=%d", event->params.mouse.changedButton);
118 break;
119 case UIEVT_PAINT:
120 case UIEVT_GENPAINTEVENTS:
121 case UIEVT_RESHAPEWINDOW:
122 printf("rect=%d,%d,%d,%d", event->params.rect.X1, event->params.rect.Y1, event->params.rect.X2, event->params.rect.Y2);
123 break;
124 case UIEVT_SETPOS:
125 printf("pos=%d,%d", event->params.pos.X, event->params.pos.Y);
126 break;
127 case UIEVT_SETSIZE:
128 printf("size=%d,%d", event->params.size.width, event->params.size.height);
129 break;
130 case UIEVT_KEYDOWN:
131 case UIEVT_KEYUP:
132 case UIEVT_KEYTYPE:
133 #ifdef FABGLIB_HAS_VirtualKeyO_STRING
134 printf("VK=%s ", Keyboard::virtualKeyToString(event->params.key.VK));
135 if (event->params.key.LALT) printf(" +LALT");
136 if (event->params.key.RALT) printf(" +RALT");
137 if (event->params.key.CTRL) printf(" +CTRL");
138 if (event->params.key.SHIFT) printf(" +SHIFT");
139 if (event->params.key.GUI) printf(" +GUI");
140 #endif
141 break;
142 case UIEVT_TIMER:
143 printf("handle=%p", event->params.timerHandle);
144 break;
145 default:
146 break;
147 }
148 printf("\n");
149}
150#endif
151
152
153
155// uiObject
156
157
158uiObject::uiObject()
159{
160}
161
162
163uiObject::~uiObject()
164{
165}
166
167
168// uiObject
170
171
172
174// uiEvtHandler
175
176
177uiEvtHandler::uiEvtHandler(uiApp * app)
178 : m_app(app)
179{
180 objectType().uiEvtHandler = true;
181}
182
183
184uiEvtHandler::~uiEvtHandler()
185{
186 if (m_app)
187 m_app->killEvtHandlerTimers(this);
188}
189
190
191void uiEvtHandler::processEvent(uiEvent * event)
192{
193 switch (event->id) {
194
195 default:
196 break;
197
198 }
199}
200
201
202// uiEvtHandler
204
205
206
208// uiApp
209
210
211uiApp::uiApp()
212 : uiEvtHandler(nullptr),
213 m_rootWindow(nullptr),
214 m_activeWindow(nullptr),
215 m_focusedWindow(nullptr),
216 m_lastFocusedWindow(nullptr),
217 m_capturedMouseWindow(nullptr),
218 m_freeMouseWindow(nullptr),
219 m_modalWindow(nullptr),
220 m_combineMouseMoveEvents(false),
221 m_keyDownHandler(nullptr),
222 m_caretWindow(nullptr),
223 m_caretTimer(nullptr),
224 m_caretInvertState(-1),
225 m_lastMouseUpTimeMS(0),
226 m_style(nullptr)
227{
228 objectType().uiApp = true;
229 setApp(this);
230}
231
232
233uiApp::~uiApp()
234{
235}
236
237
238int uiApp::run(BitmappedDisplayController * displayController, Keyboard * keyboard, Mouse * mouse)
239{
240 m_displayController = displayController;
241 m_displayColors = displayController->colorsCount();
242
243 m_canvas = new Canvas(m_displayController);
244
245 m_keyboard = keyboard;
246 m_mouse = mouse;
247 if (PS2Controller::initialized()) {
248 // get default keyboard and mouse from the PS/2 controller
249 if (m_keyboard == nullptr)
250 m_keyboard = PS2Controller::keyboard();
251 if (m_mouse == nullptr)
252 m_mouse = PS2Controller::mouse();
253 }
254
255 m_eventsQueue = xQueueCreate(FABGLIB_UI_EVENTS_QUEUE_SIZE, sizeof(uiEvent));
256
257 // setup absolute events from mouse
258 if (m_mouse && m_mouse->isMouseAvailable())
259 m_mouse->setupAbsolutePositioner(m_canvas->getWidth(), m_canvas->getHeight(), false, m_displayController, this);
260
261 // setup keyboard
262 if (m_keyboard)
263 m_keyboard->setUIApp(this);
264
265 // root window always stays at 0, 0 and cannot be moved
266 m_rootWindow = new uiFrame(nullptr, "", Point(0, 0), Size(m_canvas->getWidth(), m_canvas->getHeight()), false);
267 m_rootWindow->setApp(this);
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 uiStaticLabel(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 uiStaticLabel(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 uiStaticLabel(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_next(nullptr),
1380 m_prev(nullptr),
1381 m_firstChild(nullptr),
1382 m_lastChild(nullptr),
1383 m_styleClassID(styleClassID),
1384 m_isMouseOver(false),
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 if (app()->style() && styleClassID)
1395 app()->style()->setStyle(this, styleClassID);
1396 }
1397
1398 if (m_pos == UIWINDOW_PARENTCENTER) {
1399 if (parent) {
1400 m_pos = Point((parent->size().width - size.width) / 2, (parent->size().height - size.height) / 2);
1401 } else {
1402 m_pos = Point(0, 0);
1403 }
1404 }
1405
1406 if (parent)
1407 parent->addChild(this);
1408
1409 if (visible && app())
1410 app()->showWindow(this, true);
1411
1412 auto pframe = parentFrame();
1413 m_focusIndex = pframe ? ((uiFrame*)pframe)->getNextFreeFocusIndex() : 0;
1414
1415 if (app()) {
1416 uiEvent evt = uiEvent(this, UIEVT_CREATE);
1417 app()->postEvent(&evt);
1418 }
1419}
1420
1421
1422uiWindow::~uiWindow()
1423{
1424 freeChildren();
1425}
1426
1427
1428void uiWindow::freeChildren()
1429{
1430 for (uiWindow * next, * cur = m_firstChild; cur; cur = next) {
1431 next = cur->m_next;
1432 delete cur;
1433 }
1434 m_firstChild = m_lastChild = nullptr;
1435}
1436
1437
1438void uiWindow::addChild(uiWindow * child)
1439{
1440 if (m_firstChild) {
1441 // append after last child
1442 m_lastChild->m_next = child;
1443 child->m_prev = m_lastChild;
1444 m_lastChild = child;
1445 } else {
1446 // there are no children
1447 m_firstChild = m_lastChild = child;
1448 }
1449}
1450
1451
1452// insert child over (one position after) underlyingChild
1453// underlyingChild = nullptr, first position
1454void uiWindow::insertAfter(uiWindow * child, uiWindow * underlyingChild)
1455{
1456 if (!hasChildren()) {
1457 // this is the first child, just add
1458 addChild(child);
1459 return;
1460 }
1461 child->m_prev = underlyingChild;
1462 if (underlyingChild) {
1463 // insert before underlyingChild
1464 child->m_next = underlyingChild->m_next;
1465 if (child->m_next)
1466 child->m_next->m_prev = child;
1467 underlyingChild->m_next = child;
1468 if (m_lastChild == underlyingChild)
1469 m_lastChild = child;
1470 } else {
1471 // insert at first position (before m_firstChild)
1472 m_firstChild->m_prev = child;
1473 child->m_next = m_firstChild;
1474 m_firstChild = child;
1475 }
1476}
1477
1478
1479void uiWindow::removeChild(uiWindow * child, bool freeChild)
1480{
1481 if (child) {
1482 if (child == m_firstChild)
1483 m_firstChild = child->m_next;
1484 else
1485 child->m_prev->m_next = child->m_next;
1486
1487 if (child == m_lastChild)
1488 m_lastChild = child->m_prev;
1489 else
1490 child->m_next->m_prev = child->m_prev;
1491
1492 if (freeChild) {
1493 delete child;
1494 app()->cleanWindowReferences(child);
1495 } else
1496 child->m_prev = child->m_next = nullptr;
1497
1498 }
1499}
1500
1501
1502// move to the last position (top window)
1503void uiWindow::moveChildOnTop(uiWindow * child)
1504{
1505 removeChild(child, false);
1506 addChild(child);
1507}
1508
1509
1510// move child over (one position after) underlyingChild
1511// underlyingChild = nullptr, first position
1512void uiWindow::moveAfter(uiWindow * child, uiWindow * underlyingChild)
1513{
1514 removeChild(child, false);
1515 insertAfter(child, underlyingChild);
1516}
1517
1518
1520{
1521 parent()->moveChildOnTop(this);
1522}
1523
1524
1525void uiWindow::bringAfter(uiWindow * insertionPoint)
1526{
1527 parent()->moveAfter(this, insertionPoint);
1528}
1529
1530
1531// return true if "window" is a child (or descendant) of this window
1532bool uiWindow::isChild(uiWindow * window)
1533{
1534 for (auto child = firstChild(); child; child = child->next())
1535 if (child == window || (child->hasChildren() && child->isChild(window)))
1536 return true;
1537 return false;
1538}
1539
1540
1541// transform a rect relative to this window to a rect relative to the specified base window
1542Rect uiWindow::transformRect(Rect const & rect, uiWindow * baseWindow)
1543{
1544 Rect r = rect;
1545 for (uiWindow * win = this; win != baseWindow; win = win->m_parent)
1546 r = r.translate(win->m_pos);
1547 return r;
1548}
1549
1550
1551// rect is based on window coordinates
1552void uiWindow::repaint(Rect const & rect)
1553{
1554 app()->repaintRect(transformRect(rect, app()->rootWindow()));
1555}
1556
1557
1559{
1561}
1562
1563
1565{
1566 switch (origin) {
1567 case uiOrigin::Screen:
1568 return transformRect(Rect(0, 0, m_size.width - 1, m_size.height - 1), app()->rootWindow());
1569
1570 case uiOrigin::Parent:
1571 return Rect(m_pos.X, m_pos.Y, m_pos.X + m_size.width - 1, m_pos.Y + m_size.height - 1);
1572
1573 case uiOrigin::Window:
1574 return Rect(0, 0, m_size.width - 1, m_size.height - 1);
1575 }
1576 return Rect();
1577}
1578
1579
1581{
1582 int bSize = hasFocus() ? m_windowStyle.focusedBorderSize : m_windowStyle.borderSize;
1583 return rect(origin).shrink(bSize);
1584}
1585
1586
1588{
1589 return clientRect(uiOrigin::Window).size();
1590}
1591
1592
1594{
1595 return clientRect(uiOrigin::Window).pos();
1596}
1597
1598
1599void uiWindow::beginPaint(uiEvent * paintEvent, Rect const & clippingRect)
1600{
1601 Rect srect = rect(uiOrigin::Screen);
1602 canvas()->setOrigin(srect.X1, srect.Y1);
1603 canvas()->setClippingRect( clippingRect.intersection(paintEvent->params.rect) );
1604 canvas()->resetGlyphOptions();
1605 canvas()->resetPaintOptions();
1606}
1607
1608
1609void uiWindow::processEvent(uiEvent * event)
1610{
1611 uiEvtHandler::processEvent(event);
1612
1613 switch (event->id) {
1614
1615 case UIEVT_DESTROY:
1616 m_parent->removeChild(this);
1617 break;
1618
1619 case UIEVT_CLOSE:
1620 // for default a Close request just hides the window
1621 app()->showWindow(this, false);
1622 break;
1623
1624 case UIEVT_ACTIVATE:
1625 {
1626 m_state.active = true;
1627 uiWindow * winToRepaint = this;
1628 // move this window and parent windows on top (last position), and select the window to actually repaint
1629 for (uiWindow * child = this; child->parent() != nullptr; child = child->parent()) {
1630 if (child != child->parent()->lastChild()) {
1631 child->parent()->moveChildOnTop(child);
1632 winToRepaint = child;
1633 }
1634 }
1635 winToRepaint->repaint();
1636 break;
1637 }
1638
1639 case UIEVT_DEACTIVATE:
1640 m_state.active = false;
1641 repaint();
1642 break;
1643
1644 case UIEVT_MOUSEBUTTONDOWN:
1645 if (event->params.mouse.changedButton == 1) {
1646 // activate window? setActiveWindow() will activate the right window (maybe a parent)
1647 if (!m_state.active)
1648 app()->setActiveWindow(this);
1649 // focus window?
1650 app()->setFocusedWindow(this);
1651 // capture mouse
1652 app()->captureMouse(this);
1653 }
1654 break;
1655
1656 case UIEVT_MOUSEBUTTONUP:
1657 // end capture mouse if left button is up
1658 if (event->params.mouse.changedButton == 1) {
1659 // generate UIEVT_CLICK. The check is required to avoid onclick event when mouse is captured and moved out of button area
1660 if (rect(uiOrigin::Window).contains(event->params.mouse.status.X, event->params.mouse.status.Y)) {
1661 uiEvent evt = *event;
1662 evt.id = UIEVT_CLICK;
1663 app()->postEvent(&evt);
1664 }
1665 }
1666 break;
1667
1668 case UIEVT_SHOW:
1669 repaint();
1670 break;
1671
1672 case UIEVT_HIDE:
1673 repaint();
1674 break;
1675
1676 case UIEVT_RESHAPEWINDOW:
1677 reshape(event->params.rect);
1678 break;
1679
1680 case UIEVT_GENPAINTEVENTS:
1681 generatePaintEvents(event->params.rect);
1682 break;
1683
1684 case UIEVT_MOUSEENTER:
1685 m_isMouseOver = true;
1686 app()->displayController()->setMouseCursor(m_windowStyle.defaultCursor);
1687 break;
1688
1689 case UIEVT_MOUSELEAVE:
1690 m_isMouseOver = false;
1691 break;
1692
1693 case UIEVT_KEYDOWN:
1694 if (m_parentProcessKbdEvents)
1695 m_parent->processEvent(event);
1696 break;
1697
1698 case UIEVT_KEYUP:
1699 if (m_parentProcessKbdEvents)
1700 m_parent->processEvent(event);
1701 break;
1702
1703 case UIEVT_PAINT:
1704 beginPaint(event, rect(uiOrigin::Window));
1705 paintWindow();
1706 break;
1707
1708 case UIEVT_SETFOCUS:
1709 case UIEVT_KILLFOCUS:
1710 repaint(); // to update border
1711 break;
1712
1713 default:
1714 break;
1715 }
1716}
1717
1718
1719void uiWindow::paintWindow()
1720{
1721 // border
1722 int bSize = hasFocus() ? m_windowStyle.focusedBorderSize : m_windowStyle.borderSize;
1723 if (bSize > 0) {
1724 canvas()->setPenColor(hasFocus() ? m_windowStyle.focusedBorderColor : (state().active || windowProps().activeLook ? m_windowStyle.activeBorderColor : m_windowStyle.borderColor));
1725 for (int i = 0; i < bSize; ++i)
1726 canvas()->drawRectangle(i, i, m_size.width - 1 - i, m_size.height - 1 - i);
1727 }
1728}
1729
1730
1731// given a relative paint rect generate a set of UIEVT_PAINT events
1732void uiWindow::generatePaintEvents(Rect const & paintRect)
1733{
1734 app()->setCaret(false);
1735 Stack<Rect> rects;
1736 rects.push(paintRect);
1737 while (!rects.isEmpty()) {
1738 Rect thisRect = rects.pop();
1739 bool noIntesections = true;
1740 for (uiWindow * win = lastChild(); win; win = win->prev()) {
1741 Rect winRect = clientRect(uiOrigin::Window).intersection(win->rect(uiOrigin::Parent));
1742 if (win->state().visible && thisRect.intersects(winRect)) {
1743 noIntesections = false;
1744 removeRectangle(rects, thisRect, winRect);
1745 Rect newRect = thisRect.intersection(winRect).translate(-win->pos().X, -win->pos().Y);
1746 win->generatePaintEvents(newRect);
1747 break;
1748 }
1749 }
1750 if (noIntesections) {
1751 uiEvent evt = uiEvent(nullptr, UIEVT_PAINT);
1752 evt.dest = this;
1753 evt.params.rect = thisRect;
1754 // process event now. insertEvent() may dry events queue. On the other side, this may use too much stack!
1755 processEvent(&evt);
1756 }
1757 }
1758}
1759
1760
1761// insert/post UIEVT_PAINT, UIEVT_SETPOS and UIEVT_SETSIZE events in order to modify window bounding rect
1762// rect: new window rectangle based on parent coordinates
1763// handle anchors of its children
1764void uiWindow::reshape(Rect const & r)
1765{
1766 // new rect based on root window coordiantes
1767 Rect newRect = parent()->transformRect(r, app()->rootWindow());
1768
1769 // old rect based on root window coordinates
1770 Rect oldRect = rect(uiOrigin::Screen);
1771
1772 if (oldRect == newRect)
1773 return;
1774
1775 // set here because generatePaintEvents() requires updated window pos() and size()
1776 m_pos = Point(r.X1, r.Y1);
1777 m_size = r.size();
1778
1779 if (!oldRect.intersects(newRect)) {
1780 // old position and new position do not intersect, just repaint old rect
1781 app()->rootWindow()->generatePaintEvents(oldRect);
1782 } else {
1783 Stack<Rect> rects;
1784 removeRectangle(rects, oldRect, newRect); // remove newRect from oldRect
1785 while (!rects.isEmpty())
1786 app()->rootWindow()->generatePaintEvents(rects.pop());
1787 }
1788
1789 // generate set position event
1790 uiEvent evt = uiEvent(this, UIEVT_SETPOS);
1791 evt.params.pos = pos();
1792 app()->postEvent(&evt);
1793
1794 // generate set size event
1795 evt = uiEvent(this, UIEVT_SETSIZE);
1796 evt.params.size = size();
1797 app()->postEvent(&evt);
1798
1799 // handle children's anchors
1800 int dx = newRect.width() - oldRect.width();
1801 int dy = newRect.height() - oldRect.height();
1802 if (dx != 0 || dy != 0) {
1803 for (auto child = firstChild(); child; child = child->next()) {
1804 Rect childRect = child->rect(uiOrigin::Parent);
1805 Rect newChildRect = childRect;
1806 if (dx) {
1807 if (!child->m_anchors.left && !child->m_anchors.right) {
1808 // TODO: due the integer division the window may not stay at center when resizing by odd values. "ofs" is just a bad workaround
1809 int ofs = dx > 0 ? imax(1, dx / 2) : imin(-1, dx / 2);
1810 newChildRect.X1 += ofs;
1811 newChildRect.X2 += ofs;
1812 } else if (!child->m_anchors.left)
1813 newChildRect.X1 += dx;
1814 if (child->m_anchors.right)
1815 newChildRect.X2 += dx;
1816 }
1817 if (dy) {
1818 if (!child->m_anchors.top && !child->m_anchors.bottom) {
1819 // TODO: due the integer division the window may not stay at center when resizing by odd values. "ofs" is just a bad workaround
1820 int ofs = dy > 0 ? imax(1, dy / 2) : imin(-1, dy / 2);
1821 newChildRect.Y1 += ofs;
1822 newChildRect.Y2 += ofs;
1823 } else if (!child->m_anchors.top)
1824 newChildRect.Y1 += dy;
1825 if (child->m_anchors.bottom)
1826 newChildRect.Y2 += dy;
1827 }
1828 if (newChildRect != childRect) {
1829 uiEvent evt = uiEvent(child, UIEVT_RESHAPEWINDOW);
1830 evt.params.rect = newChildRect;
1831 app()->postEvent(&evt);
1832 }
1833 }
1834 }
1835
1836 app()->rootWindow()->generatePaintEvents(newRect);
1837}
1838
1839
1840Canvas * uiWindow::canvas()
1841{
1842 return app()->canvas();
1843}
1844
1845
1846void uiWindow::exitModal(int modalResult)
1847{
1848 uiEvent evt = uiEvent(this, UIEVT_EXITMODAL);
1849 evt.params.modalResult = modalResult;
1850 app()->postEvent(&evt);
1851}
1852
1853
1855{
1856 return app()->activeWindow() == this;
1857}
1858
1859
1861{
1862 return app()->focusedWindow() == this;
1863}
1864
1865
1866bool uiWindow::isFocusable()
1867{
1868 return windowProps().focusable && state().visible;
1869}
1870
1871
1872// set maxIndex = -1 at first call
1873uiWindow * uiWindow::findChildWithFocusIndex(int focusIndex, int * maxIndex)
1874{
1875 for (auto child = m_firstChild; child; child = child->m_next) {
1876 if (child->isFocusable()) {
1877 *maxIndex = imax(*maxIndex, child->m_focusIndex);
1878 if (child->m_focusIndex == focusIndex) {
1879 return child;
1880 }
1881 }
1882 if (child->hasChildren()) {
1883 auto r = child->findChildWithFocusIndex(focusIndex, maxIndex);
1884 if (r) {
1885 return r;
1886 }
1887 }
1888 }
1889 return nullptr;
1890}
1891
1892
1894{
1895 uiWindow * ret = m_parent;
1896 while (ret && ret->objectType().uiFrame == 0)
1897 ret = ret->parent();
1898 return ret;
1899}
1900
1901
1902// uiWindow
1904
1905
1906
1908// uiFrame
1909
1910
1911uiFrame::uiFrame(uiWindow * parent, char const * title, const Point & pos, const Size & size, bool visible, uint32_t styleClassID)
1912 : uiWindow(parent, pos, size, visible, 0),
1913 m_title(nullptr),
1914 m_titleLength(0),
1915 m_mouseDownFrameItem(uiFrameItem::None),
1916 m_mouseMoveFrameItem(uiFrameItem::None),
1917 m_lastReshapingBox(Rect(0, 0, 0, 0)),
1918 m_nextFreeFocusIndex(0),
1919 m_mouseDownPos(Point(-1, -1))
1920{
1921 objectType().uiFrame = true;
1922
1923 m_frameState.maximized = false;
1924 m_frameState.minimized = false;
1925
1926 if (app()) {
1927 m_frameStyle.adaptToDisplayColors(app()->displayColors());
1928 if (app()->style() && styleClassID)
1929 app()->style()->setStyle(this, styleClassID);
1930 }
1931 setTitle(title);
1932}
1933
1934
1935uiFrame::~uiFrame()
1936{
1937 free(m_title);
1938}
1939
1940
1941void uiFrame::setTitle(char const * value)
1942{
1943 if (value) {
1944 m_titleLength = strlen(value);
1945 m_title = (char*) realloc(m_title, m_titleLength + 1);
1946 strcpy(m_title, value);
1947 } else {
1948 free(m_title);
1949 m_title = nullptr;
1950 m_titleLength = 0;
1951 }
1952}
1953
1954
1955void uiFrame::setTitleFmt(const char *format, ...)
1956{
1957 va_list ap;
1958 va_start(ap, format);
1959 int size = vsnprintf(nullptr, 0, format, ap) + 1;
1960 if (size > 0) {
1961 va_end(ap);
1962 va_start(ap, format);
1963 char buf[size + 1];
1964 vsnprintf(buf, size, format, ap);
1965 setTitle(buf);
1966 }
1967 va_end(ap);
1968}
1969
1970
1971int uiFrame::titleBarHeight()
1972{
1973 return m_frameStyle.titleFont->height + 3;
1974}
1975
1976
1977Rect uiFrame::titleBarRect()
1978{
1980 r.Y2 = r.Y1 + titleBarHeight() - 1;
1981 return r;
1982}
1983
1984
1986{
1987 Rect r = uiWindow::clientRect(origin);
1988
1989 // title bar
1990 if (m_titleLength > 0)
1991 r.Y1 += titleBarHeight();
1992
1993 return r;
1994}
1995
1996
1997Size uiFrame::minWindowSize()
1998{
1999 Size r = Size(0, 0);
2000 if (m_frameProps.resizeable && !m_frameState.minimized && m_titleLength == 0) {
2001 r.width += CORNERSENSE * 2;
2002 r.height += CORNERSENSE * 2;
2003 }
2004 r.width += windowStyle().borderSize * 2;
2005 r.height += windowStyle().borderSize * 2;
2006 if (m_titleLength > 0) {
2007 int barHeight = titleBarHeight(); // titleBarHeight is also the button width
2008 r.height += barHeight;
2009 if (m_frameProps.hasCloseButton || m_frameProps.hasMaximizeButton || m_frameProps.hasMinimizeButton)
2010 r.width += barHeight * 3;
2011 r.width += barHeight * 4; // additional space to let some characters visible
2012 }
2013 return r;
2014}
2015
2016
2017// buttonIndex:
2018// 0 = close button
2019// 1 = maximize button
2020// 2 = minimize button
2021Rect uiFrame::getBtnRect(int buttonIndex)
2022{
2023 int btnSize = titleBarHeight(); // horiz and vert size of each button
2024 Rect barRect = titleBarRect();
2025 Rect btnRect = Rect(barRect.X2 - btnSize - CORNERSENSE / 2, barRect.Y1,
2026 barRect.X2 - CORNERSENSE / 2, barRect.Y2);
2027 while (buttonIndex--)
2028 btnRect = btnRect.translate(-btnSize, 0);
2029 return btnRect;
2030}
2031
2032
2033void uiFrame::paintFrame()
2034{
2035 Rect bkgRect = uiWindow::clientRect(uiOrigin::Window);
2036 // title bar
2037 if (m_titleLength > 0) {
2038 int barHeight = titleBarHeight();
2039 // title bar background
2040 RGB888 titleBarBrushColor = state().active || windowProps().activeLook ? m_frameStyle.activeTitleBackgroundColor : m_frameStyle.titleBackgroundColor;
2041 canvas()->setBrushColor(titleBarBrushColor);
2042 canvas()->fillRectangle(titleBarRect());
2043 // close, maximize and minimze buttons
2044 int btnX = paintButtons(bkgRect);
2045 // title
2046 canvas()->setPenColor(state().active || windowProps().activeLook ? m_frameStyle.activeTitleColor : m_frameStyle.titleColor);
2047 canvas()->setGlyphOptions(GlyphOptions().FillBackground(false).DoubleWidth(0).Bold(false).Italic(false).Underline(false).Invert(0));
2048 canvas()->drawTextWithEllipsis(m_frameStyle.titleFont, 1 + bkgRect.X1, 1 + bkgRect.Y1, m_title, btnX);
2049 // adjust background rect
2050 bkgRect.Y1 += barHeight;
2051 }
2052 // background
2053 if (m_frameProps.fillBackground && !m_frameState.minimized && bkgRect.width() > 0 && bkgRect.height() > 0) {
2054 canvas()->setBrushColor(m_frameStyle.backgroundColor);
2055 canvas()->fillRectangle(bkgRect);
2056 }
2057}
2058
2059
2060// return the X coordinate where button start
2061int uiFrame::paintButtons(Rect const & bkgRect)
2062{
2063 int buttonsX = bkgRect.X2;
2064 if (m_frameProps.hasCloseButton) {
2065 // close button
2066 Rect r = getBtnRect(0);
2067 buttonsX = r.X1;
2068 if (m_mouseMoveFrameItem == uiFrameItem::CloseButton) {
2069 canvas()->setBrushColor(m_frameStyle.mouseOverBackgroundButtonColor);
2070 canvas()->fillRectangle(r);
2071 canvas()->setPenColor(m_frameStyle.mouseOverButtonColor);
2072 } else
2073 canvas()->setPenColor(state().active || windowProps().activeLook ? m_frameStyle.activeButtonColor : m_frameStyle.buttonColor);
2074 r = r.shrink(4);
2075 canvas()->drawLine(r.X1, r.Y1, r.X2, r.Y2);
2076 canvas()->drawLine(r.X2, r.Y1, r.X1, r.Y2);
2077 }
2078 if (m_frameProps.hasMaximizeButton) {
2079 // maximize/restore button
2080 Rect r = getBtnRect(1);
2081 buttonsX = r.X1;
2082 if (m_mouseMoveFrameItem == uiFrameItem::MaximizeButton) {
2083 canvas()->setBrushColor(m_frameStyle.mouseOverBackgroundButtonColor);
2084 canvas()->fillRectangle(r);
2085 canvas()->setPenColor(m_frameStyle.mouseOverButtonColor);
2086 } else
2087 canvas()->setPenColor(state().active || windowProps().activeLook ? m_frameStyle.activeButtonColor : m_frameStyle.buttonColor);
2088 r = r.shrink(4);
2089 if (m_frameState.maximized || m_frameState.minimized) {
2090 // draw restore (from maximize or minimize) button
2091 r = r.shrink(1).translate(-1, +1);
2092 canvas()->drawRectangle(r);
2093 r = r.translate(+2, -2);
2094 canvas()->moveTo(r.X1, r.Y1 + 2);
2095 canvas()->lineTo(r.X1, r.Y1);
2096 canvas()->lineTo(r.X2, r.Y1);
2097 canvas()->lineTo(r.X2, r.Y2);
2098 canvas()->lineTo(r.X2 - 2, r.Y2);
2099 } else
2100 canvas()->drawRectangle(r);
2101 }
2102 if (m_frameProps.hasMinimizeButton && !m_frameState.minimized) {
2103 // minimize button
2104 Rect r = getBtnRect(2);
2105 buttonsX = r.X1;
2106 if (m_mouseMoveFrameItem == uiFrameItem::MinimizeButton) {
2107 canvas()->setBrushColor(m_frameStyle.mouseOverBackgroundButtonColor);
2108 canvas()->fillRectangle(r);
2109 canvas()->setPenColor(m_frameStyle.mouseOverButtonColor);
2110 } else
2111 canvas()->setPenColor(state().active || windowProps().activeLook ? m_frameStyle.activeButtonColor : m_frameStyle.buttonColor);
2112 r = r.shrink(4);
2113 int h = (r.Y2 - r.Y1 + 1) / 2;
2114 canvas()->drawLine(r.X1, r.Y1 + h, r.X2, r.Y1 + h);
2115 }
2116 return buttonsX;
2117}
2118
2119
2120void uiFrame::processEvent(uiEvent * event)
2121{
2122 uiWindow::processEvent(event);
2123
2124 switch (event->id) {
2125
2126 case UIEVT_PAINT:
2127 beginPaint(event, uiWindow::clientRect(uiOrigin::Window));
2128 paintFrame();
2129 onPaint();
2130 break;
2131
2132 case UIEVT_MOUSEBUTTONDOWN:
2133 if (event->params.mouse.changedButton == 1) {
2134 m_mouseDownPos = Point(event->params.mouse.status.X, event->params.mouse.status.Y);
2135 m_mouseDownFrameItem = getFrameItemAt(event->params.mouse.status.X, event->params.mouse.status.Y);
2136 m_sizeAtMouseDown = size();
2137 app()->combineMouseMoveEvents(true);
2138 }
2139 break;
2140
2141 case UIEVT_MOUSEBUTTONUP:
2142 if (event->params.mouse.changedButton == 1) {
2143 int mouseX = event->params.mouse.status.X;
2144 int mouseY = event->params.mouse.status.Y;
2145
2146 // this actually moves or resizes the window in case of non-realtime mode
2147 movingCapturedMouse(mouseX, mouseY, false);
2148
2149 // this sets the right mouse cursor in case of end of capturing
2150 movingFreeMouse(mouseX, mouseY);
2151
2152 // handle buttons clicks
2153 handleButtonsClick(mouseX, mouseY, false);
2154
2155 app()->combineMouseMoveEvents(false);
2156 }
2157 break;
2158
2159 case UIEVT_MOUSEMOVE:
2160 if (app()->capturedMouseWindow() == this)
2161 movingCapturedMouse(event->params.mouse.status.X, event->params.mouse.status.Y, true);
2162 else
2163 movingFreeMouse(event->params.mouse.status.X, event->params.mouse.status.Y);
2164 break;
2165
2166 case UIEVT_MOUSELEAVE:
2167 if (m_mouseMoveFrameItem == uiFrameItem::CloseButton)
2168 repaint(getBtnRect(0));
2169 if (m_mouseMoveFrameItem == uiFrameItem::MaximizeButton)
2170 repaint(getBtnRect(1));
2171 if (m_mouseMoveFrameItem == uiFrameItem::MinimizeButton)
2172 repaint(getBtnRect(2));
2173 m_mouseMoveFrameItem = uiFrameItem::None;
2174 break;
2175
2176 case UIEVT_DBLCLICK:
2177 handleButtonsClick(event->params.mouse.status.X, event->params.mouse.status.Y, true);
2178 break;
2179
2180 case UIEVT_SHOW:
2181 onShow();
2182 break;
2183
2184 case UIEVT_HIDE:
2185 onHide();
2186 break;
2187
2188 case UIEVT_MAXIMIZE:
2189 if (!m_frameState.minimized)
2190 m_savedScreenRect = rect(uiOrigin::Parent);
2191 m_frameState.maximized = true;
2192 m_frameState.minimized = false;
2194 break;
2195
2196 case UIEVT_MINIMIZE:
2197 if (!m_frameState.maximized)
2198 m_savedScreenRect = rect(uiOrigin::Parent);
2199 m_frameState.maximized = false;
2200 m_frameState.minimized = true;
2201 app()->resizeWindow(this, minWindowSize());
2202 break;
2203
2204 case UIEVT_RESTORE:
2205 m_frameState.maximized = false;
2206 m_frameState.minimized = false;
2207 app()->reshapeWindow(this, m_savedScreenRect);
2208 break;
2209
2210 case UIEVT_SETSIZE:
2211 onResize();
2212 break;
2213
2214 case UIEVT_TIMER:
2215 onTimer(event->params.timerHandle);
2216 break;
2217
2218 case UIEVT_KEYDOWN:
2219 // move focused child
2220 if (event->params.key.VK == VK_TAB) {
2221 if (event->params.key.SHIFT)
2222 app()->moveFocus(-1);
2223 else
2224 app()->moveFocus(1);
2225 }
2226 onKeyDown(event->params.key);
2227 break;
2228
2229 case UIEVT_KEYUP:
2230 onKeyUp(event->params.key);
2231 break;
2232
2233 default:
2234 break;
2235 }
2236}
2237
2238
2239uiFrameItem uiFrame::getFrameItemAt(int x, int y)
2240{
2241 Point p = Point(x, y);
2242
2243 if (m_titleLength > 0) {
2244 if (m_frameProps.hasCloseButton && getBtnRect(0).contains(p))
2245 return uiFrameItem::CloseButton; // on Close Button area
2246
2247 if (m_frameProps.hasMaximizeButton && getBtnRect(1).contains(p))
2248 return uiFrameItem::MaximizeButton; // on maximize button area
2249
2250 if (m_frameProps.hasMinimizeButton && !m_frameState.minimized && getBtnRect(2).contains(p))
2251 return uiFrameItem::MinimizeButton; // on minimize button area
2252 }
2253
2254 if (m_frameProps.resizeable && !m_frameState.maximized && !m_frameState.minimized) {
2255
2256 int w = size().width;
2257 int h = size().height;
2258
2259 // on top center, resize
2260 if (Rect(CORNERSENSE, 0, w - CORNERSENSE, windowStyle().borderSize).contains(p))
2261 return uiFrameItem::TopCenterResize;
2262
2263 // on left center side, resize
2264 if (Rect(0, CORNERSENSE, windowStyle().borderSize, h - CORNERSENSE).contains(p))
2265 return uiFrameItem::CenterLeftResize;
2266
2267 // on right center side, resize
2268 if (Rect(w - windowStyle().borderSize, CORNERSENSE, w - 1, h - CORNERSENSE).contains(p))
2269 return uiFrameItem::CenterRightResize;
2270
2271 // on bottom center, resize
2272 if (Rect(CORNERSENSE, h - windowStyle().borderSize, w - CORNERSENSE, h - 1).contains(p))
2273 return uiFrameItem::BottomCenterResize;
2274
2275 // on top left side, resize
2276 if (Rect(0, 0, CORNERSENSE, CORNERSENSE).contains(p))
2277 return uiFrameItem::TopLeftResize;
2278
2279 // on top right side, resize
2280 if (Rect(w - CORNERSENSE, 0, w - 1, CORNERSENSE).contains(p))
2281 return uiFrameItem::TopRightResize;
2282
2283 // on bottom left side, resize
2284 if (Rect(0, h - CORNERSENSE, CORNERSENSE, h - 1).contains(p))
2285 return uiFrameItem::BottomLeftResize;
2286
2287 // on bottom right side, resize
2288 if (Rect(w - CORNERSENSE, h - CORNERSENSE, w - 1, h - 1).contains(p))
2289 return uiFrameItem::BottomRightResize;
2290
2291 }
2292
2293 // on title bar, moving area
2294 if (m_titleLength > 0 && m_frameProps.moveable && !m_frameState.maximized && titleBarRect().contains(p))
2295 return uiFrameItem::MoveArea;
2296
2297 return uiFrameItem::None;
2298}
2299
2300
2301void uiFrame::movingCapturedMouse(int mouseX, int mouseY, bool mouseIsDown)
2302{
2303 int dx = mouseX - m_mouseDownPos.X;
2304 int dy = mouseY - m_mouseDownPos.Y;
2305
2306 Size minSize = minWindowSize();
2307
2308 Rect newRect = rect(uiOrigin::Parent);
2309
2310 switch (m_mouseDownFrameItem) {
2311
2312 case uiFrameItem::MoveArea:
2313 newRect = newRect.move(pos().X + dx, pos().Y + dy);
2314 break;
2315
2316 case uiFrameItem::CenterRightResize:
2317 newRect = newRect.resize(imax(m_sizeAtMouseDown.width + dx, minSize.width), newRect.height());
2318 break;
2319
2320 case uiFrameItem::CenterLeftResize:
2321 {
2322 Rect r = newRect;
2323 r.X1 = pos().X + dx;
2324 newRect.X1 = r.X1 - imax(0, minSize.width - r.size().width);
2325 break;
2326 }
2327
2328 case uiFrameItem::TopLeftResize:
2329 {
2330 Rect r = newRect;
2331 r.X1 = pos().X + dx;
2332 newRect.X1 = r.X1 - imax(0, minSize.width - r.size().width);
2333 r.Y1 = pos().Y + dy;
2334 newRect.Y1 = r.Y1 - imax(0, minSize.height - r.size().height);
2335 break;
2336 }
2337
2338 case uiFrameItem::TopCenterResize:
2339 {
2340 Rect r = newRect;
2341 r.Y1 = pos().Y + dy;
2342 newRect.Y1 = r.Y1 - imax(0, minSize.height - r.size().height);
2343 break;
2344 }
2345
2346 case uiFrameItem::TopRightResize:
2347 {
2348 Rect r = newRect;
2349 r.X2 = pos().X + m_sizeAtMouseDown.width + dx;
2350 newRect.X2 = r.X2 + imax(0, minSize.width - r.size().width);
2351 r.Y1 = pos().Y + dy;
2352 newRect.Y1 = r.Y1 - imax(0, minSize.height - r.size().height);
2353 break;
2354 }
2355
2356 case uiFrameItem::BottomLeftResize:
2357 {
2358 Rect r = newRect;
2359 r.X1 = pos().X + dx;
2360 newRect.X1 = r.X1 - imax(0, minSize.width - r.size().width);
2361 r.Y2 = pos().Y + m_sizeAtMouseDown.height + dy;
2362 newRect.Y2 = r.Y2 + imax(0, minSize.height - r.size().height);
2363 break;
2364 }
2365
2366 case uiFrameItem::BottomCenterResize:
2367 newRect = newRect.resize(newRect.width(), imax(m_sizeAtMouseDown.height + dy, minSize.height));
2368 break;
2369
2370 case uiFrameItem::BottomRightResize:
2371 newRect = newRect.resize(imax(m_sizeAtMouseDown.width + dx, minSize.width), imax(m_sizeAtMouseDown.height + dy, minSize.height));
2372 break;
2373
2374 default:
2375 return; // no action
2376 }
2377
2378 // reshape to newRect or draw the reshaping box)
2379 if (mouseIsDown == false || (app()->appProps().realtimeReshaping && m_mouseDownFrameItem != uiFrameItem::MoveArea) || (app()->appProps().realtimeMoving && m_mouseDownFrameItem == uiFrameItem::MoveArea)) {
2380 m_lastReshapingBox = Rect();
2381 app()->reshapeWindow(this, newRect);
2382 } else
2383 drawReshapingBox(newRect);
2384}
2385
2386
2387void uiFrame::drawReshapingBox(Rect boxRect)
2388{
2389 int clientOffsetY = clientRect(uiOrigin::Window).Y1;
2390 canvas()->setOrigin(parent()->rect(uiOrigin::Screen).pos());
2392 PaintOptions popt;
2393 popt.NOT = true;
2394 canvas()->setPaintOptions(popt);
2395 if (m_lastReshapingBox != Rect()) {
2396 canvas()->drawRectangle(m_lastReshapingBox);
2397 if (m_titleLength > 0)
2398 canvas()->drawLine(m_lastReshapingBox.X1, m_lastReshapingBox.Y1 + clientOffsetY, m_lastReshapingBox.X2, m_lastReshapingBox.Y1 + clientOffsetY);
2399 }
2400 if (boxRect != Rect()) {
2401 canvas()->drawRectangle(boxRect);
2402 if (m_titleLength > 0)
2403 canvas()->drawLine(boxRect.X1, boxRect.Y1 + clientOffsetY, boxRect.X2, boxRect.Y1 + clientOffsetY);
2404 }
2405 canvas()->setPaintOptions(PaintOptions());
2406 m_lastReshapingBox = boxRect;
2407}
2408
2409
2410void uiFrame::movingFreeMouse(int mouseX, int mouseY)
2411{
2412 uiFrameItem prevSensPos = m_mouseMoveFrameItem;
2413
2414 m_mouseMoveFrameItem = getFrameItemAt(mouseX, mouseY);
2415
2416 if ((m_mouseMoveFrameItem == uiFrameItem::CloseButton || prevSensPos == uiFrameItem::CloseButton) && m_mouseMoveFrameItem != prevSensPos)
2417 repaint(getBtnRect(0));
2418
2419 if ((m_mouseMoveFrameItem == uiFrameItem::MaximizeButton || prevSensPos == uiFrameItem::MaximizeButton) && m_mouseMoveFrameItem != prevSensPos)
2420 repaint(getBtnRect(1));
2421
2422 if ((m_mouseMoveFrameItem == uiFrameItem::MinimizeButton || prevSensPos == uiFrameItem::MinimizeButton) && m_mouseMoveFrameItem != prevSensPos)
2423 repaint(getBtnRect(2));
2424
2426
2427 switch (m_mouseMoveFrameItem) {
2428
2429 case uiFrameItem::TopLeftResize:
2431 break;
2432
2433 case uiFrameItem::TopCenterResize:
2435 break;
2436
2437 case uiFrameItem::TopRightResize:
2439 break;
2440
2441 case uiFrameItem::CenterLeftResize:
2443 break;
2444
2445 case uiFrameItem::CenterRightResize:
2447 break;
2448
2449 case uiFrameItem::BottomLeftResize:
2451 break;
2452
2453 case uiFrameItem::BottomCenterResize:
2455 break;
2456
2457 case uiFrameItem::BottomRightResize:
2459 break;
2460
2461 default:
2462 break;
2463 }
2464
2465 app()->displayController()->setMouseCursor(cur);
2466}
2467
2468
2469void uiFrame::handleButtonsClick(int x, int y, bool doubleClick)
2470{
2471 if (m_titleLength > 0) {
2472 if (m_frameProps.hasCloseButton && getBtnRect(0).contains(x, y) && getBtnRect(0).contains(m_mouseDownPos)) {
2473 // generate UIEVT_CLOSE event
2474 uiEvent evt = uiEvent(this, UIEVT_CLOSE);
2475 app()->postEvent(&evt);
2476 } else if (m_frameProps.hasMaximizeButton && ((getBtnRect(1).contains(x, y) && getBtnRect(1).contains(m_mouseDownPos)) ||
2477 (doubleClick && titleBarRect().contains(x, y)))) {
2478 // maximimize or restore on:
2479 // - click on maximize/restore button
2480 // - double click on the title bar
2481 app()->maximizeFrame(this, !m_frameState.maximized && !m_frameState.minimized); // used also for "restore" from minimized
2482 } else if (m_frameProps.hasMinimizeButton && !m_frameState.minimized && getBtnRect(2).contains(x, y) && getBtnRect(2).contains(m_mouseDownPos)) {
2483 app()->minimizeFrame(this, !m_frameState.minimized);
2484 } else
2485 return;
2486 // this avoids the button remains selected (background colored) when window change size
2487 m_mouseMoveFrameItem = uiFrameItem::None;
2488 }
2489}
2490
2491
2492// uiFrame
2494
2495
2496
2498// uiControl
2499
2500
2501uiControl::uiControl(uiWindow * parent, const Point & pos, const Size & size, bool visible, uint32_t styleClassID)
2502 : uiWindow(parent, pos, size, visible, 0)
2503{
2504 objectType().uiControl = true;
2505 windowProps().activable = false;
2506
2507 if (app()->style() && styleClassID)
2508 app()->style()->setStyle(this, styleClassID);
2509}
2510
2511
2512uiControl::~uiControl()
2513{
2514}
2515
2516
2517void uiControl::processEvent(uiEvent * event)
2518{
2519 uiWindow::processEvent(event);
2520}
2521
2522
2523
2524// uiControl
2526
2527
2528
2530// uiButton
2531
2532
2533uiButton::uiButton(uiWindow * parent, char const * text, const Point & pos, const Size & size, uiButtonKind kind, bool visible, uint32_t styleClassID)
2534 : uiControl(parent, pos, size, visible, 0),
2535 m_text(nullptr),
2536 m_textExtent(0),
2537 m_down(false),
2538 m_kind(kind)
2539{
2540 objectType().uiButton = true;
2541
2542 windowProps().focusable = true;
2543
2544 windowStyle().borderSize = 1;
2546 windowStyle().borderColor = RGB888(64, 64, 64);
2547
2548 if (app()) {
2549 m_buttonStyle.adaptToDisplayColors(app()->displayColors());
2550 if (app()->style() && styleClassID)
2551 app()->style()->setStyle(this, styleClassID);
2552 }
2553
2554 setText(text);
2555}
2556
2557
2558uiButton::~uiButton()
2559{
2560 free(m_text);
2561}
2562
2563
2564void uiButton::setText(char const * value)
2565{
2566 int len = strlen(value);
2567 m_text = (char*) realloc(m_text, len + 1);
2568 strcpy(m_text, value);
2569
2570 m_textExtent = canvas()->textExtent(m_buttonStyle.textFont, value);
2571}
2572
2573
2574void uiButton::paintButton()
2575{
2577 // background
2578 RGB888 bkColor = m_down ? m_buttonStyle.downBackgroundColor : m_buttonStyle.backgroundColor;
2579 if (app()->capturedMouseWindow() == this)
2580 bkColor = m_buttonStyle.mouseDownBackgroundColor;
2581 else if (isMouseOver())
2582 bkColor = m_buttonStyle.mouseOverBackgroundColor;
2583 canvas()->setBrushColor(bkColor);
2584 canvas()->fillRectangle(bkgRect);
2585 // content (text and bitmap)
2586 paintContent(bkgRect);
2587}
2588
2589
2590void uiButton::paintContent(Rect const & rect)
2591{
2592 Bitmap const * bitmap = m_down ? m_buttonStyle.downBitmap : m_buttonStyle.bitmap;
2593 int textHeight = m_buttonStyle.textFont->height;
2594 int bitmapWidth = bitmap ? bitmap->width : 0;
2595 int bitmapHeight = bitmap ? bitmap->height : 0;
2596 int bitmapTextSpace = bitmap ? m_buttonStyle.bitmapTextSpace : 0;
2597
2598 int x = rect.X1 + (rect.size().width - m_textExtent - bitmapTextSpace - bitmapWidth) / 2;
2599 int y = rect.Y1 + (rect.size().height - imax(textHeight, bitmapHeight)) / 2;
2600
2601 if (bitmap) {
2602 canvas()->drawBitmap(x, y, bitmap);
2603 x += bitmapWidth + bitmapTextSpace;
2604 y += (imax(textHeight, bitmapHeight) - textHeight) / 2;
2605 }
2606 canvas()->setGlyphOptions(GlyphOptions().FillBackground(false).DoubleWidth(0).Bold(false).Italic(false).Underline(false).Invert(0));
2607 if (isMouseOver())
2608 canvas()->setPenColor(m_buttonStyle.mouseOverTextColor);
2609 else if (m_down)
2610 canvas()->setPenColor(m_buttonStyle.downTextColor);
2611 else
2612 canvas()->setPenColor(m_buttonStyle.textColor);
2613 canvas()->drawText(m_buttonStyle.textFont, x, y, m_text);
2614}
2615
2616
2617void uiButton::processEvent(uiEvent * event)
2618{
2619 uiControl::processEvent(event);
2620
2621 switch (event->id) {
2622
2623 case UIEVT_PAINT:
2624 beginPaint(event, uiControl::clientRect(uiOrigin::Window));
2625 paintButton();
2626 break;
2627
2628 case UIEVT_CLICK:
2629 trigger();
2630 onClick();
2631 break;
2632
2633 case UIEVT_MOUSEENTER:
2634 repaint(); // to update background color
2635 break;
2636
2637 case UIEVT_MOUSEBUTTONDOWN:
2638 if (event->params.mouse.changedButton == 1)
2639 repaint();
2640 onMouseDown(event->params.mouse);
2641 break;
2642
2643 case UIEVT_MOUSEBUTTONUP:
2644 onMouseUp(event->params.mouse);
2645 break;
2646
2647 case UIEVT_MOUSELEAVE:
2648 repaint(); // to update background
2649 break;
2650
2651 case UIEVT_KEYUP:
2652 if (event->params.key.VK == VK_RETURN || event->params.key.VK == VK_KP_ENTER || event->params.key.VK == VK_SPACE) {
2653 trigger();
2654 onClick();
2655 }
2656 break;
2657
2658 default:
2659 break;
2660 }
2661}
2662
2663
2664// action to perfom on mouse up or keyboard space/enter
2665void uiButton::trigger()
2666{
2667 if (m_kind == uiButtonKind::Switch) {
2668 m_down = !m_down;
2669 onChange();
2670 }
2671 repaint();
2672}
2673
2674
2675void uiButton::setDown(bool value)
2676{
2677 if (value != m_down) {
2678 m_down = value;
2679 repaint();
2680 }
2681}
2682
2683
2684
2685// uiButton
2687
2688
2689
2690
2692// uiTextEdit
2693
2694
2695uiTextEdit::uiTextEdit(uiWindow * parent, char const * text, const Point & pos, const Size & size, bool visible, uint32_t styleClassID)
2696 : uiControl(parent, pos, size, visible, 0),
2697 m_text(nullptr),
2698 m_textLength(0),
2699 m_textSpace(0),
2700 m_viewX(0),
2701 m_cursorCol(0),
2702 m_selCursorCol(0),
2703 m_codepage(nullptr)
2704{
2705 objectType().uiTextEdit = true;
2706
2707 windowProps().focusable = true;
2708
2710 windowStyle().borderColor = RGB888(64, 64, 64);
2711 windowStyle().borderSize = 1;
2712
2713 if (app()) {
2714 m_textEditStyle.adaptToDisplayColors(app()->displayColors());
2715 if (app()->style() && styleClassID)
2716 app()->style()->setStyle(this, styleClassID);
2717 }
2718
2719 setText(text);
2720}
2721
2722
2723uiTextEdit::~uiTextEdit()
2724{
2725 free(m_text);
2726}
2727
2728
2729void uiTextEdit::setText(char const * value)
2730{
2731 if (value) {
2732 m_textLength = strlen(value);
2733 checkAllocatedSpace(m_textLength);
2734 strcpy(m_text, value);
2735 } else {
2736 m_text = strdup("");
2737 m_textLength = 0;
2738 }
2739}
2740
2741
2742void uiTextEdit::setTextFmt(const char *format, ...)
2743{
2744 va_list ap;
2745 va_start(ap, format);
2746 int size = vsnprintf(nullptr, 0, format, ap) + 1;
2747 if (size > 0) {
2748 va_end(ap);
2749 va_start(ap, format);
2750 checkAllocatedSpace(size + 1);
2751 vsnprintf(m_text, size, format, ap);
2752 m_textLength = strlen(m_text);
2753 }
2754 va_end(ap);
2755}
2756
2757
2758void uiTextEdit::processEvent(uiEvent * event)
2759{
2760 uiControl::processEvent(event);
2761
2762 switch (event->id) {
2763
2764 case UIEVT_PAINT:
2765 beginPaint(event, uiControl::clientRect(uiOrigin::Window));
2766 paintTextEdit();
2767 if (m_textEditProps.hasCaret)
2768 app()->setCaret(true); // force blinking (previous painting may cover caret)
2769 break;
2770
2771 case UIEVT_MOUSEBUTTONDOWN:
2772 if (event->params.mouse.changedButton == 1) {
2773 int col = getColFromMouseX(event->params.mouse.status.X);
2774 moveCursor(col, col);
2775 repaint();
2776 }
2777 break;
2778
2779 case UIEVT_MOUSEBUTTONUP:
2780 break;
2781
2782 case UIEVT_MOUSEENTER:
2783 repaint(); // to update background color
2784 break;
2785
2786 case UIEVT_MOUSELEAVE:
2787 repaint(); // to update background and border
2788 break;
2789
2790 case UIEVT_MOUSEMOVE:
2791 // dragging mouse? select
2792 if (app()->capturedMouseWindow() == this)
2793 moveCursor(getColFromMouseX(event->params.mouse.status.X), m_selCursorCol);
2794 break;
2795
2796 case UIEVT_SETFOCUS:
2797 if (m_textEditProps.hasCaret) {
2798 updateCaret();
2799 app()->showCaret(this);
2800 }
2801 repaint();
2802 break;
2803
2804 case UIEVT_KILLFOCUS:
2805 if (m_textEditProps.hasCaret)
2806 app()->showCaret(NULL);
2807 moveCursor(0, 0);
2808 repaint();
2809 break;
2810
2811 case UIEVT_KEYDOWN:
2812 handleKeyDown(event->params.key);
2813 break;
2814
2815 case UIEVT_KEYTYPE:
2816 onKeyType(event->params.key);
2817 break;
2818
2819 case UIEVT_DBLCLICK:
2820 selectWordAt(event->params.mouse.status.X);
2821 break;
2822
2823 default:
2824 break;
2825 }
2826}
2827
2828
2829int uiTextEdit::keyToASCII(uiKeyEventInfo const & key)
2830{
2831 // check codepage consistency
2832 if (m_codepage == nullptr || m_codepage->codepage != m_textEditStyle.textFont->codepage)
2833 m_codepage = CodePages::get(m_textEditStyle.textFont->codepage);
2834
2835 VirtualKeyItem item = { };
2836 item.vk = key.VK;
2837 item.CTRL = key.CTRL;
2838 item.SHIFT = key.SHIFT;
2839 return virtualKeyToASCII(item, m_codepage);
2840}
2841
2842
2843void uiTextEdit::handleKeyDown(uiKeyEventInfo const & key)
2844{
2845 if (m_textEditProps.allowEdit) {
2846 switch (key.VK) {
2847
2848 case VK_BACKSPACE:
2849 if (m_cursorCol != m_selCursorCol)
2850 removeSel(); // there is a selection, same behavior of VK_DELETE
2851 else if (m_cursorCol > 0) {
2852 // remove character at left
2853 moveCursor(m_cursorCol - 1, m_cursorCol - 1);
2854 removeSel();
2855 }
2856 break;
2857
2858 case VK_DELETE:
2859 case VK_KP_DELETE:
2860 removeSel();
2861 break;
2862
2863 default:
2864 {
2865 // normal keys
2866
2867 // we don't use key.ASCII because it uses codepage stored in Keyboard object but
2868 // each textedit may have a different font and codepage
2869 auto ASCII = keyToASCII(key);
2870
2871 if (ASCII >= 0x20 && ASCII != 0x7F) {
2872 if (m_cursorCol != m_selCursorCol)
2873 removeSel(); // there is a selection, same behavior of VK_DELETE
2874 insert(ASCII);
2875 }
2876 break;
2877 }
2878 }
2879 }
2880
2881 switch (key.VK) {
2882
2883 case VK_LEFT:
2884 case VK_KP_LEFT:
2885 {
2886 // LEFT : cancel selection and move cursor by one character
2887 // SHIFT + LEFT : move cursor and select
2888 // CTRL + LEFT : cancel selection and move cursor by one word
2889 // SHIFT + CTRL + LEFT : move cursor by one word and select
2890 int newCurCol = key.CTRL ? getWordPosAtLeft() : m_cursorCol - 1;
2891 moveCursor(newCurCol, (key.SHIFT ? m_selCursorCol : newCurCol));
2892 break;
2893 }
2894
2895 case VK_RIGHT:
2896 case VK_KP_RIGHT:
2897 {
2898 // RIGHT : cancel selection and move cursor by one character
2899 // SHIFT + RIGHT : move cursor and select
2900 // CTRL + RIGHT : cancel selection and move cursor by one word
2901 // SHIFT + CTRL + RIGHT : move cursor by one word and select
2902 int newCurCol = key.CTRL ? getWordPosAtRight() : m_cursorCol + 1;
2903 moveCursor(newCurCol, (key.SHIFT ? m_selCursorCol : newCurCol));
2904 break;
2905 }
2906
2907 case VK_HOME:
2908 case VK_KP_HOME:
2909 // SHIFT + HOME, select up to Home
2910 // HOME, move cursor to home
2911 moveCursor(0, (key.SHIFT ? m_selCursorCol : 0));
2912 break;
2913
2914 case VK_END:
2915 case VK_KP_END:
2916 // SHIFT + END, select up to End
2917 // END, move cursor to End
2918 moveCursor(m_textLength, (key.SHIFT ? m_selCursorCol : m_textLength));
2919 break;
2920
2921 default:
2922 {
2923 if (key.CTRL) {
2924 // keys with CTRL
2925 switch (key.VK) {
2926 case VK_a:
2927 // CTRL+A, select all
2928 moveCursor(m_textLength, 0);
2929 break;
2930 default:
2931 break;
2932 }
2933 }
2934 break;
2935 }
2936 }
2937}
2938
2939
2940Rect uiTextEdit::getEditRect()
2941{
2943}
2944
2945
2946void uiTextEdit::paintTextEdit()
2947{
2948 m_contentRect = getEditRect();
2949 // background
2950 RGB888 bkColor = hasFocus() ? m_textEditStyle.focusedBackgroundColor : (isMouseOver() ? m_textEditStyle.mouseOverBackgroundColor : m_textEditStyle.backgroundColor);
2951 canvas()->setBrushColor(bkColor);
2952 canvas()->fillRectangle(m_contentRect);
2953 // content
2954 paintContent();
2955}
2956
2957
2958// get width of specified characted
2959// return glyph data of the specified character
2960uint8_t const * uiTextEdit::getCharInfo(char ch, int * width)
2961{
2962 if (m_textEditProps.passwordMode)
2963 ch = '*';
2964 uint8_t const * chptr;
2965 if (m_textEditStyle.textFont->chptr) {
2966 // variable width
2967 chptr = m_textEditStyle.textFont->data + m_textEditStyle.textFont->chptr[(int)(ch)];
2968 *width = *chptr++;
2969 } else {
2970 // fixed width
2971 chptr = m_textEditStyle.textFont->data + ch;
2972 *width = m_textEditStyle.textFont->width;
2973 }
2974 return chptr;
2975}
2976
2977
2978void uiTextEdit::paintContent()
2979{
2980 m_contentRect = m_contentRect.shrink(2);
2981 canvas()->setClippingRect(canvas()->getClippingRect().intersection(m_contentRect));
2982 canvas()->setPenColor(m_textEditStyle.textColor);
2983
2984 GlyphOptions glyphOpt = GlyphOptions().FillBackground(false).DoubleWidth(0).Bold(false).Italic(false).Underline(false).Invert(0);
2985 if (m_selCursorCol != m_cursorCol)
2986 glyphOpt.FillBackground(true);
2987 canvas()->setGlyphOptions(glyphOpt);
2988
2989 for (int x = m_contentRect.X1 + m_viewX, y = m_contentRect.Y1, col = 0, fontWidth; m_text[col]; ++col, x += fontWidth) {
2990 uint8_t const * chptr = getCharInfo(m_text[col], &fontWidth);
2991 if (m_selCursorCol != m_cursorCol && (col == m_selCursorCol || col == m_cursorCol)) {
2992 glyphOpt.invert = !glyphOpt.invert;
2993 canvas()->setGlyphOptions(glyphOpt);
2994 }
2995 if (x >= m_contentRect.X1 && x <= m_contentRect.X2)
2996 canvas()->drawGlyph(x, y, fontWidth, m_textEditStyle.textFont->height, chptr, 0);
2997 }
2998}
2999
3000
3001// returns the X coordinate where is character "col"
3002// return value is < m_contentRect.X1 if "col" is at left of visible area
3003// return value is > m_contentRect.X2 if "col" is at the right of visible area
3004int uiTextEdit::charColumnToWindowX(int col)
3005{
3006 int x = m_contentRect.X1 + m_viewX;
3007 for (int curcol = 0, fontWidth; m_text[curcol]; ++curcol, x += fontWidth) {
3008 getCharInfo(m_text[curcol], &fontWidth);
3009 if (curcol == col)
3010 break;
3011 }
3012 return x;
3013}
3014
3015
3016// update caret coordinates from current pos (m_cursorCol)
3017void uiTextEdit::updateCaret()
3018{
3019 if (m_textEditProps.hasCaret) {
3020 int x = charColumnToWindowX(m_cursorCol);
3021 app()->setCaret(Rect(x, m_contentRect.Y1, x, m_contentRect.Y1 + m_textEditStyle.textFont->height));
3022 }
3023}
3024
3025
3026// col (cursor position):
3027// 0 up to m_textLength. For example having a m_text="1234", min col is 0, max col is 4 (passing last char).
3028// selCol (selection position):
3029// 0 up to m_textLength
3030void uiTextEdit::moveCursor(int col, int selCol)
3031{
3032 col = iclamp(col, 0, m_textLength);
3033 selCol = iclamp(selCol, 0, m_textLength);
3034
3035 if (col == m_cursorCol && selCol == m_selCursorCol)
3036 return; // nothing to do
3037
3038 bool doRepaint = false;
3039
3040 // there was a selection, now there is no selection
3041 if (m_cursorCol != m_selCursorCol && col == selCol)
3042 doRepaint = true;
3043
3044 m_cursorCol = col;
3045 m_selCursorCol = selCol;
3046
3047 if (m_cursorCol != m_selCursorCol)
3048 doRepaint = true;
3049
3050 // need to scroll?
3051 int x = charColumnToWindowX(m_cursorCol);
3052
3053 int prevCharWidth = 0;
3054 if (col > 0)
3055 getCharInfo(m_text[col - 1], &prevCharWidth);
3056
3057 int charWidth;
3058 getCharInfo(m_text[col < m_textLength ? col : col - 1], &charWidth);
3059
3060 if (x - prevCharWidth < m_contentRect.X1) {
3061 // scroll right
3062 m_viewX += m_contentRect.X1 - (x - prevCharWidth);
3063 doRepaint = true;
3064 } else if (x + charWidth > m_contentRect.X2) {
3065 // scroll left
3066 m_viewX -= (x + charWidth - m_contentRect.X2);
3067 doRepaint = true;
3068 }
3069
3070 updateCaret();
3071
3072 if (doRepaint)
3073 repaint();
3074}
3075
3076
3077// return column (that is character index in m_text[]) from specified mouseX
3078int uiTextEdit::getColFromMouseX(int mouseX)
3079{
3080 int col = 0;
3081 for (int x = m_contentRect.X1 + m_viewX, fontWidth; m_text[col]; ++col, x += fontWidth) {
3082 getCharInfo(m_text[col], &fontWidth);
3083 if (mouseX < x || (mouseX >= x && mouseX < x + fontWidth))
3084 break;
3085 }
3086 return col;
3087}
3088
3089
3090// requiredLength does NOT include ending zero
3091void uiTextEdit::checkAllocatedSpace(int requiredLength)
3092{
3093 ++requiredLength; // add ending zero
3094 if (m_textSpace < requiredLength) {
3095 if (m_textSpace == 0) {
3096 // first time allocates exact space
3097 m_textSpace = requiredLength;
3098 } else {
3099 // next times allocate double
3100 while (m_textSpace < requiredLength)
3101 m_textSpace *= 2;
3102 }
3103 m_text = (char*) realloc(m_text, m_textSpace);
3104 }
3105}
3106
3107
3108// insert specified char at m_cursorCol
3109void uiTextEdit::insert(char c)
3110{
3111 ++m_textLength;
3112 checkAllocatedSpace(m_textLength);
3113 memmove(m_text + m_cursorCol + 1, m_text + m_cursorCol, m_textLength - m_cursorCol);
3114 m_text[m_cursorCol] = c;
3115 moveCursor(m_cursorCol + 1, m_cursorCol + 1);
3116 onChange();
3117 repaint();
3118}
3119
3120
3121// remove from m_cursorCol to m_selCursorCol
3122void uiTextEdit::removeSel()
3123{
3124 if (m_textLength > 0) {
3125 if (m_cursorCol > m_selCursorCol)
3126 iswap(m_cursorCol, m_selCursorCol);
3127 int count = imax(1, m_selCursorCol - m_cursorCol);
3128 if (m_cursorCol < m_textLength) {
3129 memmove(m_text + m_cursorCol, m_text + m_cursorCol + count, m_textLength - m_cursorCol - count + 1);
3130 m_textLength -= count;
3131 moveCursor(m_cursorCol, m_cursorCol);
3132 onChange();
3133 repaint();
3134 }
3135 }
3136}
3137
3138
3139// return starting position of next word at left of m_cursorCol
3140int uiTextEdit::getWordPosAtLeft()
3141{
3142 int col = m_cursorCol - 1;
3143 while (col > 0 && (!isspace(m_text[col - 1]) || isspace(m_text[col])))
3144 --col;
3145 return imax(0, col);
3146}
3147
3148
3149// return starting position of next word at right of m_cursorCol
3150int uiTextEdit::getWordPosAtRight()
3151{
3152 int col = m_cursorCol + 1;
3153 while (col < m_textLength && (!isspace(m_text[col - 1]) || isspace(m_text[col])))
3154 ++col;
3155 return imin(m_textLength, col);
3156}
3157
3158
3159// if mouseX is at space, select all space at left and right
3160// if mouseX is at character, select all characters at left and right
3161void uiTextEdit::selectWordAt(int mouseX)
3162{
3163 int col = getColFromMouseX(mouseX), left = col, right = col;
3164 bool lspc = isspace(m_text[col]); // look for spaces?
3165 while (left > 0 && (bool)isspace(m_text[left - 1]) == lspc)
3166 --left;
3167 while (right < m_textLength && (bool)isspace(m_text[right]) == lspc)
3168 ++right;
3169 moveCursor(left, right);
3170}
3171
3172
3173
3174// uiTextEdit
3176
3177
3178
3179
3181// uiLabel
3182
3183
3184uiLabel::uiLabel(uiWindow * parent, char const * text, const Point & pos, const Size & size, bool visible, uint32_t styleClassID)
3185 : uiControl(parent, pos, size, visible, 0),
3186 m_text(nullptr)
3187{
3188 objectType().uiLabel = true;
3189
3190 windowProps().focusable = false;
3191 windowStyle().borderSize = 0;
3192
3193 if (app()) {
3194 m_labelStyle.adaptToDisplayColors(app()->displayColors());
3195 if (app()->style() && styleClassID)
3196 app()->style()->setStyle(this, styleClassID);
3197 }
3198
3199 m_autoSize = (size.width == 0 && size.height == 0);
3200
3201 setText(text);
3202}
3203
3204
3205uiLabel::~uiLabel()
3206{
3207 free(m_text);
3208}
3209
3210
3211void uiLabel::setText(char const * value)
3212{
3213 int len = strlen(value);
3214 m_text = (char*) realloc(m_text, len + 1);
3215 strcpy(m_text, value);
3216 update();
3217}
3218
3219
3220void uiLabel::setTextFmt(const char *format, ...)
3221{
3222 va_list ap;
3223 va_start(ap, format);
3224 int size = vsnprintf(nullptr, 0, format, ap) + 1;
3225 if (size > 0) {
3226 va_end(ap);
3227 va_start(ap, format);
3228 m_text = (char*) realloc(m_text, size + 1);
3229 vsnprintf(m_text, size, format, ap);
3230 update();
3231 }
3232 va_end(ap);
3233}
3234
3235
3237{
3238 if (m_autoSize)
3239 app()->resizeWindow(this, canvas()->textExtent(m_labelStyle.textFont, m_text), m_labelStyle.textFont->height);
3240 repaint();
3241}
3242
3243
3244void uiLabel::paintLabel()
3245{
3247 canvas()->setBrushColor(m_labelStyle.backgroundColor);
3248 canvas()->fillRectangle(r);
3249 canvas()->setGlyphOptions(GlyphOptions().FillBackground(false).DoubleWidth(0).Bold(false).Italic(false).Underline(false).Invert(0));
3250 canvas()->setPenColor(m_labelStyle.textColor);
3251
3252 int x = r.X1; // default left align
3253
3254 switch (m_labelStyle.textAlign) {
3255 case uiHAlign::Right:
3256 x = r.X2 - canvas()->textExtent(m_labelStyle.textFont, m_text);
3257 break;
3258 case uiHAlign::Center:
3259 x = r.X1 + (r.width() - canvas()->textExtent(m_labelStyle.textFont, m_text)) / 2;
3260 break;
3261 default:
3262 break;
3263 }
3264
3265 int y = r.Y1 + (r.height() - m_labelStyle.textFont->height) / 2;
3266 canvas()->drawText(m_labelStyle.textFont, x, y, m_text);
3267}
3268
3269
3270void uiLabel::processEvent(uiEvent * event)
3271{
3272 uiControl::processEvent(event);
3273
3274 switch (event->id) {
3275
3276 case UIEVT_PAINT:
3277 beginPaint(event, uiControl::clientRect(uiOrigin::Window));
3278 paintLabel();
3279 break;
3280
3281 case UIEVT_CLICK:
3282 onClick();
3283 break;
3284
3285 default:
3286 break;
3287 }
3288}
3289
3290
3291// uiLabel
3293
3294
3295
3297// uiStaticLabel
3298
3299
3300uiStaticLabel::uiStaticLabel(uiWindow * parent, char const * text, const Point & pos, bool visible, uint32_t styleClassID)
3301 : uiControl(parent, pos, Size(0, 0), visible, 0),
3302 m_text(nullptr)
3303{
3304 objectType().uiStaticLabel = true;
3305
3306 windowProps().focusable = false;
3307 windowStyle().borderSize = 0;
3308
3309 if (app()) {
3310 m_staticLabelStyle.adaptToDisplayColors(app()->displayColors());
3311 if (app()->style() && styleClassID)
3312 app()->style()->setStyle(this, styleClassID);
3313 }
3314
3315 setText(text);
3316}
3317
3318
3319void uiStaticLabel::setText(char const * value)
3320{
3321 m_text = value;
3322 update();
3323}
3324
3325
3327{
3328 app()->resizeWindow(this, canvas()->textExtent(m_staticLabelStyle.textFont, m_text), m_staticLabelStyle.textFont->height);
3329 repaint();
3330}
3331
3332
3333void uiStaticLabel::paintLabel()
3334{
3336 canvas()->setBrushColor(m_staticLabelStyle.backgroundColor);
3337 canvas()->fillRectangle(r);
3338 canvas()->setGlyphOptions(GlyphOptions().FillBackground(false).DoubleWidth(0).Bold(false).Italic(false).Underline(false).Invert(0));
3339 canvas()->setPenColor(m_staticLabelStyle.textColor);
3340
3341 int x = r.X1; // default left align
3342 int y = r.Y1 + (r.height() - m_staticLabelStyle.textFont->height) / 2;
3343 canvas()->drawText(m_staticLabelStyle.textFont, x, y, m_text);
3344}
3345
3346
3347void uiStaticLabel::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 paintLabel();
3356 break;
3357
3358 default:
3359 break;
3360 }
3361}
3362
3363
3364// uiStaticLabel
3366
3367
3368
3369
3371// uiImage
3372// TODO? bitmap is not copied, but just referenced
3373
3374
3375uiImage::uiImage(uiWindow * parent, Bitmap const * bitmap, const Point & pos, const Size & size, bool visible, uint32_t styleClassID)
3376 : uiControl(parent, pos, size, visible, 0),
3377 m_bitmap(nullptr)
3378{
3379 objectType().uiImage = true;
3380
3381 windowProps().focusable = false;
3382 windowStyle().borderSize = 0;
3383
3384 if (app()) {
3385 m_imageStyle.adaptToDisplayColors(app()->displayColors());
3386 if (app()->style() && styleClassID)
3387 app()->style()->setStyle(this, styleClassID);
3388 }
3389
3390 m_autoSize = (size.width == 0 && size.height == 0);
3391
3393}
3394
3395
3396uiImage::~uiImage()
3397{
3398}
3399
3400
3401void uiImage::setBitmap(Bitmap const * bitmap)
3402{
3403 m_bitmap = bitmap;
3404
3405 if (m_autoSize && bitmap)
3407}
3408
3409
3410void uiImage::paintImage()
3411{
3413 canvas()->setBrushColor(m_imageStyle.backgroundColor);
3414 canvas()->fillRectangle(r);
3415 if (m_bitmap) {
3416 int x = r.X1 + (r.X2 + 1 - m_bitmap->width) / 2;
3417 int y = r.Y1 + (r.Y2 + 1 - m_bitmap->height) / 2;
3418 canvas()->drawBitmap(x, y, m_bitmap);
3419 }
3420}
3421
3422
3423void uiImage::processEvent(uiEvent * event)
3424{
3425 uiControl::processEvent(event);
3426
3427 switch (event->id) {
3428
3429 case UIEVT_PAINT:
3430 beginPaint(event, uiControl::clientRect(uiOrigin::Window));
3431 paintImage();
3432 break;
3433
3434 default:
3435 break;
3436 }
3437}
3438
3439
3440// uiImage
3442
3443
3444
3446// uiPanel
3447
3448
3449uiPanel::uiPanel(uiWindow * parent, const Point & pos, const Size & size, bool visible, uint32_t styleClassID)
3450 : uiControl(parent, pos, size, visible, 0)
3451{
3452 objectType().uiPanel = true;
3453
3454 windowProps().focusable = false;
3455 windowStyle().borderSize = 1;
3456 windowStyle().borderColor = RGB888(64, 64, 64);
3457
3458 if (app()) {
3459 m_panelStyle.adaptToDisplayColors(app()->displayColors());
3460 if (app()->style() && styleClassID)
3461 app()->style()->setStyle(this, styleClassID);
3462 }
3463}
3464
3465
3466uiPanel::~uiPanel()
3467{
3468}
3469
3470
3471void uiPanel::paintPanel()
3472{
3474 // background
3475 canvas()->setBrushColor(m_panelStyle.backgroundColor);
3476 canvas()->fillRectangle(bkgRect);
3477}
3478
3479
3480void uiPanel::processEvent(uiEvent * event)
3481{
3482 uiControl::processEvent(event);
3483
3484 switch (event->id) {
3485
3486 case UIEVT_PAINT:
3487 beginPaint(event, uiControl::clientRect(uiOrigin::Window));
3488 paintPanel();
3489 break;
3490
3491 default:
3492 break;
3493 }
3494}
3495
3496
3497// uiPanel
3499
3500
3501
3503// uiPaintBox
3504
3505
3506uiPaintBox::uiPaintBox(uiWindow * parent, const Point & pos, const Size & size, bool visible, uint32_t styleClassID)
3507 : uiScrollableControl(parent, pos, size, visible, 0)
3508{
3509 objectType().uiPaintBox = true;
3510
3511 windowProps().focusable = false;
3512 windowStyle().borderSize = 1;
3513 windowStyle().borderColor = RGB888(64, 64, 64);
3514
3515 if (app()) {
3516 m_paintBoxStyle.adaptToDisplayColors(app()->displayColors());
3517 if (app()->style() && styleClassID)
3518 app()->style()->setStyle(this, styleClassID);
3519 }
3520}
3521
3522
3523uiPaintBox::~uiPaintBox()
3524{
3525}
3526
3527
3528void uiPaintBox::paintPaintBox()
3529{
3531
3532 // background
3533 canvas()->setBrushColor(m_paintBoxStyle.backgroundColor);
3534 canvas()->fillRectangle(bkgRect);
3535
3536 onPaint(bkgRect);
3537}
3538
3539
3540void uiPaintBox::processEvent(uiEvent * event)
3541{
3542 uiScrollableControl::processEvent(event);
3543
3544 switch (event->id) {
3545
3546 case UIEVT_PAINT:
3548 paintPaintBox();
3549 break;
3550
3551 default:
3552 break;
3553 }
3554}
3555
3556
3557// uiPaintBox
3559
3560
3561
3563// uiColorBox
3564
3565
3566uiColorBox::uiColorBox(uiWindow * parent, const Point & pos, const Size & size, Color color, bool visible, uint32_t styleClassID)
3567 : uiControl(parent, pos, size, visible, 0),
3568 m_color(color)
3569{
3570 objectType().uiColorBox = true;
3571
3572 windowProps().focusable = true;
3573 windowStyle().borderSize = 1;
3574 windowStyle().borderColor = RGB888(64, 64, 64);
3575
3576 if (app()->style() && styleClassID)
3577 app()->style()->setStyle(this, styleClassID);
3578}
3579
3580
3581uiColorBox::~uiColorBox()
3582{
3583}
3584
3585
3587{
3588 m_color = value;
3589 repaint();
3590}
3591
3592
3593void uiColorBox::paintColorBox()
3594{
3596 // main color
3597 canvas()->setBrushColor(m_color);
3598 canvas()->fillRectangle(bkgRect);
3599}
3600
3601
3602void uiColorBox::processEvent(uiEvent * event)
3603{
3604 uiControl::processEvent(event);
3605
3606 switch (event->id) {
3607
3608 case UIEVT_PAINT:
3609 beginPaint(event, uiControl::clientRect(uiOrigin::Window));
3610 paintColorBox();
3611 break;
3612
3613 default:
3614 break;
3615 }
3616}
3617
3618
3619// uiPanel
3621
3622
3623
3625// uiScrollableControl
3626
3627
3628uiScrollableControl::uiScrollableControl(uiWindow * parent, const Point & pos, const Size & size, bool visible, uint32_t styleClassID)
3629 : uiControl(parent, pos, size, visible, 0),
3630 m_HScrollBarPosition(0),
3631 m_HScrollBarVisible(0),
3632 m_HScrollBarRange(0),
3633 m_VScrollBarPosition(0),
3634 m_VScrollBarVisible(0),
3635 m_VScrollBarRange(0),
3636 m_mouseOverItem(uiScrollBarItem::None),
3637 m_scrollTimer(nullptr),
3638 m_mouseDownPos(Point(-1, -1))
3639{
3640 objectType().uiScrollableControl = true;
3641
3642 if (app()) {
3643 m_scrollableControlStyle.adaptToDisplayColors(app()->displayColors());
3644 if (app()->style() && styleClassID)
3645 app()->style()->setStyle(this, styleClassID);
3646 }
3647}
3648
3649
3650uiScrollableControl::~uiScrollableControl()
3651{
3652 if (m_scrollTimer)
3653 app()->killTimer(m_scrollTimer);
3654}
3655
3656
3657// position: The position of the scrollbar in scroll units.
3658// visible: The size of the visible portion of the scrollbar, in scroll units.
3659// range: The maximum position of the scrollbar
3660void uiScrollableControl::setScrollBar(uiOrientation orientation, int position, int visible, int range, bool repaintScrollbar)
3661{
3662 position = iclamp(position, 0, range - visible);
3663 switch (orientation) {
3665 {
3666 bool changedPos = (m_VScrollBarPosition != position);
3667 if (m_VScrollBarVisible != visible || m_VScrollBarRange != range || changedPos) {
3668 m_VScrollBarVisible = visible;
3669 m_VScrollBarRange = range;
3670 m_VScrollBarPosition = position;
3671 if (repaintScrollbar)
3672 repaintScrollBar(orientation);
3673 if (changedPos)
3675 }
3676 break;
3677 }
3679 {
3680 bool changedPos = (m_HScrollBarPosition != position);
3681 if (m_HScrollBarVisible != visible || m_HScrollBarRange != range || changedPos) {
3682 m_HScrollBarVisible = visible;
3683 m_HScrollBarRange = range;
3684 m_HScrollBarPosition = position;
3685 if (repaintScrollbar)
3686 repaintScrollBar(orientation);
3687 if (changedPos)
3689 }
3690 break;
3691 }
3692 };
3693}
3694
3695
3696void uiScrollableControl::repaintScrollBar(uiOrientation orientation)
3697{
3698 repaint( orientation == uiOrientation::Vertical ? getVScrollBarRects() : getHScrollBarRects() );
3699}
3700
3701
3702void uiScrollableControl::processEvent(uiEvent * event)
3703{
3704 uiControl::processEvent(event);
3705
3706 switch (event->id) {
3707
3708 case UIEVT_PAINT:
3709 beginPaint(event, uiControl::clientRect(uiOrigin::Window));
3710 paintScrollableControl();
3711 break;
3712
3713 case UIEVT_MOUSEBUTTONDOWN:
3714 if (event->params.mouse.changedButton == 1) {
3715 m_mouseDownPos = Point(event->params.mouse.status.X, event->params.mouse.status.Y);
3716 m_mouseDownHScrollBarPosition = m_HScrollBarPosition;
3717 m_mouseDownVScrollBarPosition = m_VScrollBarPosition;
3718 if (m_mouseOverItem == uiScrollBarItem::LeftButton || m_mouseOverItem == uiScrollBarItem::RightButton ||
3719 m_mouseOverItem == uiScrollBarItem::TopButton || m_mouseOverItem == uiScrollBarItem::BottomButton) {
3720 // start the timer to repeat buttons
3721 m_scrollTimer = app()->setTimer(this, 250);
3722 handleButtonsScroll();
3723 } else {
3724 handlePageScroll();
3725 }
3726 app()->combineMouseMoveEvents(true);
3727 }
3728 break;
3729
3730 case UIEVT_MOUSEBUTTONUP:
3731 if (event->params.mouse.changedButton == 1) {
3732 app()->combineMouseMoveEvents(false);
3733 if (m_scrollTimer) {
3734 app()->killTimer(m_scrollTimer);
3735 m_scrollTimer = nullptr;
3736 }
3737 }
3738 break;
3739
3740 case UIEVT_MOUSEMOVE:
3741 if (app()->capturedMouseWindow() == this)
3742 handleCapturedMouseMove(event->params.mouse.status.X, event->params.mouse.status.Y);
3743 else
3744 handleFreeMouseMove(event->params.mouse.status.X, event->params.mouse.status.Y);
3745 break;
3746
3747 case UIEVT_MOUSEWHEEL:
3748 if (m_VScrollBarRange)
3749 setScrollBar(uiOrientation::Vertical, m_VScrollBarPosition + event->params.mouse.status.wheelDelta, m_VScrollBarVisible, m_VScrollBarRange);
3750 break;
3751
3752 case UIEVT_TIMER:
3753 if (event->params.timerHandle == m_scrollTimer)
3754 handleButtonsScroll();
3755 break;
3756
3757 default:
3758 break;
3759 }
3760}
3761
3762
3763void uiScrollableControl::handleButtonsScroll()
3764{
3765 switch (m_mouseOverItem) {
3766 case uiScrollBarItem::LeftButton:
3767 setScrollBar(uiOrientation::Horizontal, m_HScrollBarPosition - 1, m_HScrollBarVisible, m_HScrollBarRange);
3768 break;
3769 case uiScrollBarItem::RightButton:
3770 setScrollBar(uiOrientation::Horizontal, m_HScrollBarPosition + 1, m_HScrollBarVisible, m_HScrollBarRange);
3771 break;
3772 case uiScrollBarItem::TopButton:
3773 setScrollBar(uiOrientation::Vertical, m_VScrollBarPosition - 1, m_VScrollBarVisible, m_VScrollBarRange);
3774 break;
3775 case uiScrollBarItem::BottomButton:
3776 setScrollBar(uiOrientation::Vertical, m_VScrollBarPosition + 1, m_VScrollBarVisible, m_VScrollBarRange);
3777 break;
3778 default:
3779 break;
3780 }
3781}
3782
3783
3784void uiScrollableControl::handlePageScroll()
3785{
3786 switch (m_mouseOverItem) {
3787 case uiScrollBarItem::PageLeft:
3788 setScrollBar(uiOrientation::Horizontal, m_HScrollBarPosition - m_HScrollBarVisible, m_HScrollBarVisible, m_HScrollBarRange);
3789 break;
3790 case uiScrollBarItem::PageRight:
3791 setScrollBar(uiOrientation::Horizontal, m_HScrollBarPosition + m_HScrollBarVisible, m_HScrollBarVisible, m_HScrollBarRange);
3792 break;
3793 case uiScrollBarItem::PageUp:
3794 setScrollBar(uiOrientation::Vertical, m_VScrollBarPosition - m_VScrollBarVisible, m_VScrollBarVisible, m_VScrollBarRange);
3795 break;
3796 case uiScrollBarItem::PageDown:
3797 setScrollBar(uiOrientation::Vertical, m_VScrollBarPosition + m_VScrollBarVisible, m_VScrollBarVisible, m_VScrollBarRange);
3798 break;
3799 default:
3800 break;
3801 }
3802}
3803
3804
3805void uiScrollableControl::handleFreeMouseMove(int mouseX, int mouseY)
3806{
3807 auto prev = m_mouseOverItem;
3808 m_mouseOverItem = getItemAt(mouseX, mouseY);
3809 if (m_mouseOverItem != prev) {
3810 if (m_VScrollBarRange)
3811 repaintScrollBar(uiOrientation::Vertical);
3812 if (m_HScrollBarRange)
3813 repaintScrollBar(uiOrientation::Horizontal);
3814 }
3815}
3816
3817
3818void uiScrollableControl::handleCapturedMouseMove(int mouseX, int mouseY)
3819{
3820 if (m_mouseOverItem == uiScrollBarItem::HBar) {
3821 // dragging horizontal bar
3822 int offset = mouseX - m_mouseDownPos.X;
3823 int newPos = m_mouseDownHScrollBarPosition + offset * m_HScrollBarRange / m_HBarArea;
3824 setScrollBar(uiOrientation::Horizontal, newPos, m_HScrollBarVisible, m_HScrollBarRange);
3825 } else if (m_mouseOverItem == uiScrollBarItem::VBar) {
3826 // dragging vertical bar
3827 int offset = mouseY - m_mouseDownPos.Y;
3828 int newPos = m_mouseDownVScrollBarPosition + offset * m_VScrollBarRange / m_VBarArea;
3829 setScrollBar(uiOrientation::Vertical, newPos, m_VScrollBarVisible, m_VScrollBarRange);
3830 }
3831}
3832
3833
3834uiScrollBarItem uiScrollableControl::getItemAt(int x, int y)
3835{
3836 if (m_HScrollBarRange) {
3837 Rect lbtn, rbtn, bar;
3838 Rect box = getHScrollBarRects(&lbtn, &rbtn, &bar);
3839 if (lbtn.contains(x, y))
3840 return uiScrollBarItem::LeftButton;
3841 if (rbtn.contains(x, y))
3842 return uiScrollBarItem::RightButton;
3843 if (bar.contains(x, y))
3844 return uiScrollBarItem::HBar;
3845 if (box.contains(x, y))
3846 return x < bar.X1 ? uiScrollBarItem::PageLeft : uiScrollBarItem::PageRight;
3847 }
3848 if (m_VScrollBarRange) {
3849 Rect tbtn, bbtn, bar;
3850 Rect box = getVScrollBarRects(&tbtn, &bbtn, &bar);
3851 if (tbtn.contains(x, y))
3852 return uiScrollBarItem::TopButton;
3853 if (bbtn.contains(x, y))
3854 return uiScrollBarItem::BottomButton;
3855 if (bar.contains(x, y))
3856 return uiScrollBarItem::VBar;
3857 if (box.contains(x, y))
3858 return y < bar.Y1 ? uiScrollBarItem::PageUp: uiScrollBarItem::PageDown;
3859 }
3860 return uiScrollBarItem::None;
3861}
3862
3863
3864Rect uiScrollableControl::getVScrollBarRects(Rect * topButton, Rect * bottomButton, Rect * bar)
3865{
3867 const int sbSize = m_scrollableControlStyle.scrollBarSize;
3868 Rect box = Rect(cArea.X2 + 1 - sbSize, cArea.Y1, cArea.X2, cArea.Y2 - (m_HScrollBarRange ? sbSize : 0));
3869 if (topButton && bottomButton && bar) {
3870 // buttons
3871 *topButton = Rect(box.X1 + 2, box.Y1 + 2, box.X2 - 2, box.Y1 + sbSize - 2);
3872 *bottomButton = Rect(box.X1 + 2, box.Y2 - sbSize + 2, box.X2 - 2, box.Y2 - 2);
3873 // the bar
3874 int barAreaY1 = topButton->Y2 + 2;
3875 int barAreaY2 = bottomButton->Y1 - 2;
3876 m_VBarArea = barAreaY2 - barAreaY1 + 1;
3877 int barOffsetY = m_VScrollBarPosition * m_VBarArea / m_VScrollBarRange;
3878 int barHeight = m_VScrollBarVisible * m_VBarArea / m_VScrollBarRange;
3879 *bar = Rect(box.X1 + 1, barAreaY1 + barOffsetY, box.X2 - 1, barAreaY1 + barOffsetY + barHeight);
3880 }
3881 return box;
3882}
3883
3884
3885Rect uiScrollableControl::getHScrollBarRects(Rect * leftButton, Rect * rightButton, Rect * bar)
3886{
3888 const int sbSize = m_scrollableControlStyle.scrollBarSize;
3889 Rect box = Rect(cArea.X1, cArea.Y2 + 1 - sbSize, cArea.X2 - (m_VScrollBarRange ? sbSize : 0), cArea.Y2);
3890 if (leftButton && rightButton && bar) {
3891 // buttons
3892 *leftButton = Rect(box.X1 + 2, box.Y1 + 2, box.X1 + sbSize - 2, box.Y2 - 2);
3893 *rightButton = Rect(box.X2 - sbSize + 2, box.Y1 + 2, box.X2 - 2, box.Y2 - 2);
3894 // the bar
3895 int barAreaX1 = leftButton->X2 + 2;
3896 int barAreaX2 = rightButton->X1 - 2;
3897 m_HBarArea = barAreaX2 - barAreaX1 + 1;
3898 int barOffsetX = m_HScrollBarPosition * m_HBarArea / m_HScrollBarRange;
3899 int barWidth = m_HScrollBarVisible * m_HBarArea / m_HScrollBarRange;
3900 *bar = Rect(barAreaX1 + barOffsetX, box.Y1 + 1, barAreaX1 + barOffsetX + barWidth, box.Y2 - 1);
3901 }
3902 return box;
3903}
3904
3905
3906void uiScrollableControl::paintScrollableControl()
3907{
3908 RGB888 FColor = m_scrollableControlStyle.scrollBarForegroundColor;
3909 RGB888 mouseOverFColor = m_scrollableControlStyle.mouseOverScrollBarForegroundColor;
3910 if (m_HScrollBarRange) {
3912 Rect lbtn, rbtn, bar;
3913 Rect box = getHScrollBarRects(&lbtn, &rbtn, &bar);
3914 // background
3915 canvas()->setBrushColor(m_scrollableControlStyle.scrollBarBackgroundColor);
3916 canvas()->setPenColor(m_scrollableControlStyle.scrollBarBackgroundColor);
3917 canvas()->fillRectangle(Rect(box.X1, box.Y1, bar.X1 - 1, box.Y2)); // left part
3918 canvas()->fillRectangle(Rect(bar.X2 + 1, box.Y1, box.X2, box.Y2)); // right part
3919 canvas()->drawLine(bar.X1, box.Y1, bar.X2, box.Y1); // fill line above the bar
3920 canvas()->drawLine(bar.X1, box.Y2, bar.X2, box.Y2); // fill line below the bar
3921 // buttons (arrows)
3922 canvas()->setPenColor(m_mouseOverItem == uiScrollBarItem::LeftButton ? mouseOverFColor : FColor);
3923 canvas()->drawLine(lbtn.X2, lbtn.Y1, lbtn.X1, lbtn.Y1 + lbtn.height() / 2);
3924 canvas()->drawLine(lbtn.X1, lbtn.Y1 + lbtn.height() / 2, lbtn.X2, lbtn.Y2);
3925 canvas()->setPenColor(m_mouseOverItem == uiScrollBarItem::RightButton ? mouseOverFColor : FColor);
3926 canvas()->drawLine(rbtn.X1, rbtn.Y1, rbtn.X2, rbtn.Y1 + lbtn.height() / 2);
3927 canvas()->drawLine(rbtn.X2, rbtn.Y1 + lbtn.height() / 2, rbtn.X1, rbtn.Y2);
3928 // the bar
3929 canvas()->setBrushColor(m_mouseOverItem == uiScrollBarItem::HBar ? mouseOverFColor : FColor);
3930 canvas()->fillRectangle(bar);
3931 }
3932 if (m_VScrollBarRange) {
3933 // paint vertical scroll bar (at right side of the window)
3934 Rect ubtn, bbtn, bar;
3935 Rect box = getVScrollBarRects(&ubtn, &bbtn, &bar);
3936 // background
3937 canvas()->setBrushColor(m_scrollableControlStyle.scrollBarBackgroundColor);
3938 canvas()->setPenColor(m_scrollableControlStyle.scrollBarBackgroundColor);
3939 canvas()->fillRectangle(Rect(box.X1, box.Y1, box.X2, bar.Y1 - 1)); // upper part
3940 canvas()->fillRectangle(Rect(box.X1, bar.Y2 + 1, box.X2, box.Y2)); // bottom part
3941 canvas()->drawLine(box.X1, bar.Y1, box.X1, bar.Y2); // fill line at left of the bar
3942 canvas()->drawLine(box.X2, bar.Y1, box.X2, bar.Y2); // fill line at right of the bar
3943 // fill box between scrollbars
3944 if (m_HScrollBarRange)
3945 canvas()->fillRectangle(Rect(box.X1, box.Y2 + 1, box.X2, box.Y2 + m_scrollableControlStyle.scrollBarSize));
3946 // buttons (arrows)
3947 canvas()->setPenColor(m_mouseOverItem == uiScrollBarItem::TopButton ? mouseOverFColor : FColor);
3948 canvas()->drawLine(ubtn.X1, ubtn.Y2, ubtn.X1 + ubtn.width() / 2, ubtn.Y1);
3949 canvas()->drawLine(ubtn.X1 + ubtn.width() / 2, ubtn.Y1, ubtn.X2, ubtn.Y2);
3950 canvas()->setPenColor(m_mouseOverItem == uiScrollBarItem::BottomButton ? mouseOverFColor : FColor);
3951 canvas()->drawLine(bbtn.X1, bbtn.Y1, bbtn.X1 + bbtn.width() / 2, bbtn.Y2);
3952 canvas()->drawLine(bbtn.X1 + bbtn.width() / 2, bbtn.Y2, bbtn.X2, bbtn.Y1);
3953 // the bar
3954 canvas()->setBrushColor(m_mouseOverItem == uiScrollBarItem::VBar ? mouseOverFColor : FColor);
3955 canvas()->fillRectangle(bar);
3956 }
3957}
3958
3959
3961{
3962 Rect r = uiControl::clientRect(origin);
3963 r.X2 -= (m_VScrollBarRange ? m_scrollableControlStyle.scrollBarSize : 0);
3964 r.Y2 -= (m_HScrollBarRange ? m_scrollableControlStyle.scrollBarSize : 0);
3965 return r;
3966}
3967
3968
3969// uiScrollableControl
3971
3972
3973
3974
3976// uiCustomListBox
3977
3978
3979uiCustomListBox::uiCustomListBox(uiWindow * parent, const Point & pos, const Size & size, bool visible, uint32_t styleClassID)
3980 : uiScrollableControl(parent, pos, size, visible, 0),
3981 m_firstVisibleItem(0)
3982{
3983 objectType().uiCustomListBox = true;
3984
3985 windowProps().focusable = true;
3986
3987 windowStyle().borderSize = 1;
3988 windowStyle().borderColor = RGB888(64, 64, 64);
3989
3990 if (app()) {
3991 m_listBoxStyle.adaptToDisplayColors(app()->displayColors());
3992 if (app()->style() && styleClassID)
3993 app()->style()->setStyle(this, styleClassID);
3994 }
3995}
3996
3997
3998uiCustomListBox::~uiCustomListBox()
3999{
4000}
4001
4002
4003void uiCustomListBox::processEvent(uiEvent * event)
4004{
4005 uiScrollableControl::processEvent(event);
4006
4007 switch (event->id) {
4008
4009 case UIEVT_PAINT:
4011 paintListBox();
4012 break;
4013
4014 case UIEVT_MOUSEBUTTONDOWN:
4015 if (event->params.mouse.changedButton == 1)
4016 mouseDownSelect(event->params.mouse.status.X, event->params.mouse.status.Y);
4017 break;
4018
4019 case UIEVT_MOUSEMOVE:
4020 if (m_listBoxProps.selectOnMouseOver)
4021 mouseMoveSelect(event->params.mouse.status.X, event->params.mouse.status.Y);
4022 break;
4023
4024 case UIEVT_KEYDOWN:
4025 handleKeyDown(event->params.key);
4026 break;
4027
4028 case UIEVT_KEYUP:
4029 onKeyUp(event->params.key);
4030 break;
4031
4032 case UIEVT_KEYTYPE:
4033 onKeyType(event->params.key);
4034 break;
4035
4036 case UIEVT_KILLFOCUS:
4037 onKillFocus();
4038 break;
4039
4040 case UIEVT_SHOW:
4041 makeItemVisible(firstSelectedItem());
4042 break;
4043
4044 case UIEVT_CLICK:
4045 onClick();
4046 break;
4047
4048 case UIEVT_DBLCLICK:
4049 onDblClick();
4050 break;
4051
4052 default:
4053 break;
4054 }
4055}
4056
4057
4058void uiCustomListBox::handleKeyDown(uiKeyEventInfo key)
4059{
4060 bool shift = key.SHIFT;
4061 switch (key.VK) {
4062 case VK_UP:
4063 case VK_KP_UP:
4064 selectItem(firstSelectedItem() - 1, shift, false);
4065 break;
4066
4067 case VK_DOWN:
4068 case VK_KP_DOWN:
4069 selectItem(lastSelectedItem() + 1, shift, false);
4070 break;
4071
4072 case VK_PAGEUP:
4073 case VK_KP_PAGEUP:
4074 selectItem(firstSelectedItem() - VScrollBarVisible(), shift, false);
4075 break;
4076
4077 case VK_PAGEDOWN:
4078 case VK_KP_PAGEDOWN:
4079 selectItem(lastSelectedItem() + VScrollBarVisible(), shift, false);
4080 break;
4081
4082 case VK_HOME:
4083 case VK_KP_HOME:
4084 selectItem(0, shift, shift);
4085 break;
4086
4087 case VK_END:
4088 case VK_KP_END:
4089 selectItem(items_getCount() - 1, shift, shift);
4090 break;
4091
4092 default:
4093 break;
4094 }
4095}
4096
4097
4098void uiCustomListBox::selectItem(int index, bool add, bool range)
4099{
4100 if (items_getCount() > 0) {
4101 index = iclamp(index, 0, items_getCount() - 1);
4102 int first = firstSelectedItem();
4103 if (!add || !m_listBoxProps.allowMultiSelect)
4104 items_deselectAll();
4105 if (m_listBoxProps.allowMultiSelect && range) {
4106 if (index <= first) {
4107 for (int i = index; i <= first; ++i)
4108 items_select(i, true);
4109 } else {
4110 for (int i = index; i >= first; --i)
4111 items_select(i, true);
4112 }
4113 } else {
4114 items_select(index, true);
4115 }
4116
4117 // make sure the selected item is visible
4118 makeItemVisible(index);
4119
4120 onChange();
4121
4122 repaint();
4123 }
4124}
4125
4126
4127void uiCustomListBox::makeItemVisible(int index)
4128{
4129 if (VScrollBarVisible()) {
4130 if (index < m_firstVisibleItem)
4131 m_firstVisibleItem = index;
4132 else if (index >= m_firstVisibleItem + VScrollBarVisible())
4133 m_firstVisibleItem = index - VScrollBarVisible() + 1;
4134 }
4135}
4136
4137
4139{
4140 items_deselectAll();
4141 onChange();
4142 repaint();
4143}
4144
4145
4146void uiCustomListBox::paintListBox()
4147{
4149 Rect itmRect = Rect(cliRect.X1, cliRect.Y1, cliRect.X2, cliRect.Y1 + m_listBoxStyle.itemHeight - 1);
4150
4151 // do we need a vert scrollbar?
4152 if (itmRect.height() * items_getCount() > cliRect.height()) {
4153 int visible = cliRect.height() / itmRect.height();
4154 int range = items_getCount();
4155 if (!VScrollBarVisible() || visible != VScrollBarVisible() || range != VScrollBarRange() || m_firstVisibleItem != VScrollBarPos()) {
4156 // show vertical scrollbar
4157 setScrollBar(uiOrientation::Vertical, m_firstVisibleItem, visible, range, false);
4158 repaint();
4159 return;
4160 }
4161 } else if (VScrollBarVisible()) {
4162 // hide vertical scrollbar
4163 m_firstVisibleItem = 0;
4164 setScrollBar(uiOrientation::Vertical, 0, 0, 0, false);
4165 repaint();
4166 return;
4167 }
4168
4169 int index = m_firstVisibleItem;
4170 while (true) {
4171 if (!itmRect.intersects(cliRect))
4172 break;
4173
4174 // background
4175 RGB888 bkColor = hasFocus() ? m_listBoxStyle.focusedBackgroundColor : m_listBoxStyle.backgroundColor;
4176 if (index < items_getCount() && items_selected(index))
4177 bkColor = (hasFocus() ? m_listBoxStyle.focusedSelectedBackgroundColor : m_listBoxStyle.selectedBackgroundColor);
4178 canvas()->setBrushColor(bkColor);
4179 canvas()->fillRectangle(itmRect);
4180
4181 if (index < items_getCount()) {
4182 // text
4183 canvas()->setPenColor(items_selected(index) ? m_listBoxStyle.selectedTextColor : m_listBoxStyle.textColor);
4184 items_draw(index, itmRect);
4185 }
4186
4187 // move to next item
4188 itmRect = itmRect.translate(0, m_listBoxStyle.itemHeight);
4189 ++index;
4190 }
4191
4192}
4193
4194
4195// get first selected item (-1 = no selected item)
4197{
4198 for (int i = 0; i < items_getCount(); ++i)
4199 if (items_selected(i))
4200 return i;
4201 return -1;
4202}
4203
4204
4205// get last selected item (-1 = no selected item)
4207{
4208 for (int i = items_getCount() - 1; i >= 0; --i)
4209 if (items_selected(i))
4210 return i;
4211 return -1;
4212}
4213
4214
4215void uiCustomListBox::setScrollBar(uiOrientation orientation, int position, int visible, int range, bool repaintScrollbar)
4216{
4217 uiScrollableControl::setScrollBar(orientation, position, visible, range, false);
4218 if (VScrollBarVisible() && m_firstVisibleItem != VScrollBarPos()) {
4219 m_firstVisibleItem = VScrollBarPos();
4220 repaint();
4221 }
4222}
4223
4224
4225// >= 0 : mouse point an item
4226// -1 : mouse inside items area, but doesn't point an item (ie just below last item)
4227// -2 : mouse outside items area (ie over vertical or horizontal scrollbar)
4228int uiCustomListBox::getItemAtMousePos(int mouseX, int mouseY)
4229{
4231 if (cliRect.contains(mouseX, mouseY)) {
4232 int idx = m_firstVisibleItem + (mouseY - cliRect.Y1) / m_listBoxStyle.itemHeight;
4233 return idx < items_getCount() ? idx : -1;
4234 }
4235 return -2;
4236}
4237
4238
4239void uiCustomListBox::mouseDownSelect(int mouseX, int mouseY)
4240{
4241 int idx = getItemAtMousePos(mouseX, mouseY);
4242 if (idx >= 0) {
4243 if (app()->keyboard()->isVKDown(VK_LCTRL) || app()->keyboard()->isVKDown(VK_RCTRL)) {
4244 // CTRL is down
4245 bool wasSelected = items_selected(idx);
4246 if (m_listBoxProps.allowMultiSelect) {
4247 items_select(idx, !wasSelected);
4248 } else {
4249 items_deselectAll();
4250 if (!wasSelected)
4251 items_select(idx, true);
4252 }
4253 } else {
4254 // CTRL is up
4255 items_deselectAll();
4256 items_select(idx, true);
4257 }
4258 } else if (idx == -1)
4259 items_deselectAll();
4260 else
4261 return;
4262 onChange();
4263 repaint();
4264}
4265
4266
4267void uiCustomListBox::mouseMoveSelect(int mouseX, int mouseY)
4268{
4269 int idx = getItemAtMousePos(mouseX, mouseY);
4270 if (idx >= 0 && !items_selected(idx)) {
4271 items_deselectAll();
4272 items_select(idx, true);
4273 onChange();
4274 repaint();
4275 }
4276}
4277
4278
4279// uiCustomListBox
4281
4282
4283
4285// uiListBox
4286
4287
4288uiListBox::uiListBox(uiWindow * parent, const Point & pos, const Size & size, bool visible, uint32_t styleClassID)
4289 : uiCustomListBox(parent, pos, size, visible, 0)
4290{
4291 objectType().uiListBox = true;
4292
4293 if (app()->style() && styleClassID)
4294 app()->style()->setStyle(this, styleClassID);
4295}
4296
4297
4298void uiListBox::items_draw(int index, const Rect & itemRect)
4299{
4300 int x = itemRect.X1 + 1;
4301 int y = itemRect.Y1 + (itemRect.height() - listBoxStyle().textFont->height) / 2;
4302 canvas()->drawText(listBoxStyle().textFont, x, y, m_items.get(index));
4303}
4304
4305
4306// uiListBox
4308
4309
4310
4312// uiColorListBox
4313
4314
4315uiColorListBox::uiColorListBox(uiWindow * parent, const Point & pos, const Size & size, bool visible, uint32_t styleClassID)
4316 : uiCustomListBox(parent, pos, size, visible, 0),
4317 m_selectedColor((Color)0)
4318{
4319 objectType().uiColorListBox = true;
4320
4321 listBoxStyle().itemHeight = 10;
4322
4323 if (app()->style() && styleClassID)
4324 app()->style()->setStyle(this, styleClassID);
4325}
4326
4327
4328void uiColorListBox::items_draw(int index, const Rect & itemRect)
4329{
4330 constexpr int BORDER = 1;
4331 canvas()->setBrushColor((Color)index);
4332 canvas()->fillRectangle(itemRect.X1 + BORDER, itemRect.Y1 + BORDER, itemRect.X2 - BORDER, itemRect.Y2 - BORDER);
4333}
4334
4335
4336// uiColorListBox
4338
4339
4340
4342// uiFileBrowser
4343
4344
4345uiFileBrowser::uiFileBrowser(uiWindow * parent, const Point & pos, const Size & size, bool visible, uint32_t styleClassID)
4346 : uiCustomListBox(parent, pos, size, visible, 0),
4347 m_selected(-1)
4348{
4349 objectType().uiFileBrowser = true;
4350
4351 if (app()->style() && styleClassID)
4352 app()->style()->setStyle(this, styleClassID);
4353}
4354
4355
4356void uiFileBrowser::items_draw(int index, const Rect & itemRect)
4357{
4358 int x = itemRect.X1 + 1;
4359 int y = itemRect.Y1 + (itemRect.height() - listBoxStyle().textFont->height) / 2;
4360 canvas()->drawText(listBoxStyle().textFont, x, y, m_dir.get(index)->name);
4361 if (m_dir.get(index)->isDir) {
4362 static const char * DIRTXT = "[dir]";
4363 int x = itemRect.X2 - canvas()->textExtent(listBoxStyle().textFont, DIRTXT);
4364 canvas()->drawText(listBoxStyle().textFont, x, y, DIRTXT);
4365 }
4366}
4367
4368
4369void uiFileBrowser::items_select(int index, bool select)
4370{
4371 if (select)
4372 m_selected = index;
4373 else if (index == m_selected || index == -1)
4374 m_selected = -1;
4375}
4376
4377
4378void uiFileBrowser::setDirectory(char const * path)
4379{
4380 m_dir.setDirectory(path);
4381 m_selected = m_dir.count() > 0 ? 0 : -1;
4382 repaint();
4383}
4384
4385
4386void uiFileBrowser::changeDirectory(char const * path)
4387{
4388 m_dir.changeDirectory(path);
4389 m_selected = m_dir.count() > 0 ? 0 : -1;
4390 repaint();
4391}
4392
4393
4395{
4396 return m_selected >= 0 ? m_dir.get(m_selected)->name : nullptr;
4397}
4398
4399
4401{
4402 return m_selected >= 0 ? m_dir.get(m_selected)->isDir : false;
4403}
4404
4405
4406void uiFileBrowser::enterSubDir()
4407{
4408 if (m_selected >= 0) {
4409 auto selItem = m_dir.get(m_selected);
4410 if (selItem->isDir) {
4411 m_dir.changeDirectory(selItem->name);
4412 m_selected = 0;
4413 onChange();
4414 repaint();
4415 }
4416 }
4417}
4418
4419
4421{
4422 m_dir.reload();
4423 m_selected = imin(m_dir.count() - 1, m_selected);
4424 onChange();
4425 repaint();
4426}
4427
4428
4429void uiFileBrowser::processEvent(uiEvent * event)
4430{
4431 uiCustomListBox::processEvent(event);
4432
4433 switch (event->id) {
4434
4435 case UIEVT_KEYDOWN:
4436 if (event->params.key.VK == VK_RETURN)
4437 enterSubDir();
4438 else if (event->params.key.VK == VK_BACKSPACE) {
4439 // backspace moves to parent dir
4440 m_selected = 0; // select ".."
4441 enterSubDir();
4442 }
4443 break;
4444
4445 case UIEVT_KEYUP:
4446 break;
4447
4448 case UIEVT_DBLCLICK:
4449 enterSubDir();
4450 break;
4451
4452 default:
4453 break;
4454 }
4455}
4456
4457
4458// uiFileBrowser
4460
4461
4462
4464// uiCustomComboBox
4465
4466
4467uiCustomComboBox::uiCustomComboBox(uiWindow * parent, const Point & pos, const Size & size, int listHeight, bool visible, uint32_t styleClassID)
4468 : uiControl(parent, pos, size, visible, 0),
4469 m_listHeight(listHeight),
4470 m_loseFocusBy(0),
4471 m_listBoxParent(nullptr)
4472{
4473 objectType().uiCustomComboBox = true;
4474
4475 windowProps().focusable = true;
4476
4477 windowStyle().borderSize = 0;
4478
4479 if (app()) {
4480 m_comboBoxStyle.adaptToDisplayColors(app()->displayColors());
4481 if (app()->style() && styleClassID)
4482 app()->style()->setStyle(this, styleClassID);
4483
4484 m_listBoxParent = new uiWindow(app()->rootWindow(), Point(0, 0), Size(0, 0), false, 0);
4485 m_listBoxParent->windowStyle().borderSize = 0;
4486 m_listBoxParent->windowStyle().focusedBorderSize = 0;
4487 }
4488
4489}
4490
4491
4492uiCustomComboBox::~uiCustomComboBox()
4493{
4494}
4495
4496
4497// index = -1 -> deselect all
4499{
4500 if (index < 0)
4501 listbox()->deselectAll();
4502 else
4503 listbox()->selectItem(index);
4504 updateEditControl();
4505}
4506
4507
4508void uiCustomComboBox::processEvent(uiEvent * event)
4509{
4510 uiControl::processEvent(event);
4511
4512 switch (event->id) {
4513
4514 case UIEVT_CREATE:
4515 listbox()->onKillFocus = [&]() {
4516 closeListBox();
4517 };
4518 listbox()->onChange = [&]() {
4519 updateEditControl();
4520 onChange();
4521 };
4522 listbox()->onKeyType = [&](uiKeyEventInfo const & key) {
4523 if (key.VK == VK_TAB || key.VK == VK_RETURN) {
4524 closeListBox();
4525 if (key.VK == VK_TAB)
4526 m_loseFocusBy = key.SHIFT ? -1 : 2;
4527 } else {
4528 uiEvent evt = uiEvent(parentFrame(), UIEVT_KEYDOWN);
4529 evt.params.key = key;
4530 app()->postEvent(&evt);
4531 evt.id = UIEVT_KEYUP;
4532 app()->postEvent(&evt);
4533 }
4534 };
4535 editcontrol()->setParentProcessKbdEvents(true); // we want keyboard events also here
4536 break;
4537
4538 case UIEVT_PAINT:
4539 beginPaint(event, uiControl::clientRect(uiOrigin::Window));
4540 paintButton();
4541 break;
4542
4543 case UIEVT_MOUSEBUTTONDOWN:
4544 if (event->params.mouse.changedButton == 1 && getButtonRect().contains(event->params.mouse.status.X, event->params.mouse.status.Y))
4545 switchListBox();
4546 break;
4547
4548 case UIEVT_CHILDSETFOCUS:
4549 if (m_comboBoxProps.openOnFocus && event->params.focusInfo.newFocused == editcontrol()
4550 && event->params.focusInfo.oldFocused != listbox()
4551 && event->params.focusInfo.oldFocused != this) {
4552 openListBox();
4553 }
4554 break;
4555
4556 case UIEVT_SETFOCUS:
4557 if (m_loseFocusBy) {
4558 app()->moveFocus(m_loseFocusBy);
4559 m_loseFocusBy = 0;
4560 } else {
4561 if (event->params.focusInfo.oldFocused != listbox() && event->params.focusInfo.oldFocused != editcontrol()) {
4562 if (m_comboBoxProps.openOnFocus) {
4563 openListBox();
4564 } else if (!isListBoxOpen()) {
4565 app()->setFocusedWindow(editcontrol());
4566 }
4567 } else if (event->params.focusInfo.oldFocused == listbox()) {
4568 app()->setFocusedWindow(editcontrol());
4569 }
4570 }
4571 break;
4572
4573 case UIEVT_KEYDOWN:
4574 listbox()->processEvent(event);
4575 break;
4576
4577 case UIEVT_KEYUP:
4578 // ALT-DOWN or ALT-UP or ENTER opens listbox
4579 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))
4580 switchListBox();
4581 break;
4582
4583 case UIEVT_DESTROY:
4584 app()->destroyWindow(m_listBoxParent);
4585 break;
4586
4587 default:
4588 break;
4589 }
4590}
4591
4592
4593void uiCustomComboBox::openListBox()
4594{
4595 Rect r = rect(uiOrigin::Screen);
4596 if (r.Y2 + m_listHeight + 1 >= app()->rootWindow()->size().height) {
4597 // open upwards
4598 r.Y2 = r.Y1 - 1;
4599 r.Y1 = r.Y2 - m_listHeight;
4600 } else {
4601 // open downwards
4602 r.Y1 = r.Y2 + 1;
4603 r.Y2 = r.Y1 + m_listHeight;
4604 }
4605 app()->reshapeWindow(m_listBoxParent, r);
4606
4607 m_listBoxParent->bringOnTop();
4608 app()->showWindow(m_listBoxParent, true);
4609 app()->setActiveWindow(m_listBoxParent);
4610 app()->reshapeWindow(listbox(), r.translate(-r.X1, -r.Y1));
4611 app()->setFocusedWindow(listbox());
4613}
4614
4615
4616void uiCustomComboBox::closeListBox()
4617{
4618 app()->showWindow(m_listBoxParent, false);
4619 if (app()->focusedWindow() == nullptr || app()->focusedWindow() == listbox()) {
4621 app()->setFocusedWindow(this);
4622 }
4623 parentFrame()->windowProps().activeLook = false;
4624}
4625
4626
4627void uiCustomComboBox::switchListBox()
4628{
4629 if (isListBoxOpen()) {
4630 closeListBox();
4631 app()->setFocusedWindow(editcontrol());
4632 } else {
4633 openListBox();
4634 }
4635}
4636
4637
4638Size uiCustomComboBox::getEditControlSize()
4639{
4640 Rect clientArea = uiControl::clientRect(uiOrigin::Window);
4641 return Size(clientArea.width() - buttonWidth(), clientArea.height());
4642}
4643
4644
4645int uiCustomComboBox::buttonWidth()
4646{
4647 Rect clientArea = uiControl::clientRect(uiOrigin::Window);
4648 return clientArea.height() / 2;
4649}
4650
4651
4652Rect uiCustomComboBox::getButtonRect()
4653{
4655 btnRect.X1 = btnRect.X2 - buttonWidth() + 1;
4656 return btnRect;
4657}
4658
4659
4660void uiCustomComboBox::paintButton()
4661{
4662 Rect btnRect = getButtonRect();
4663
4664 // button background
4665 canvas()->setBrushColor(m_comboBoxStyle.buttonBackgroundColor);
4666 canvas()->fillRectangle(btnRect);
4667
4668 // button glyph
4669 canvas()->setPenColor(m_comboBoxStyle.buttonColor);
4670 Rect arrowRect = btnRect.hShrink(1).vShrink(2);
4671 int hHeight = arrowRect.height() / 2;
4672 int hWidth = arrowRect.width() / 2;
4673 constexpr int vDist = 2;
4674 canvas()->drawLine(arrowRect.X1, arrowRect.Y1 + hHeight - vDist, arrowRect.X1 + hWidth, arrowRect.Y1);
4675 canvas()->drawLine(arrowRect.X1 + hWidth, arrowRect.Y1, arrowRect.X2, arrowRect.Y1 + hHeight - vDist);
4676 canvas()->drawLine(arrowRect.X1, arrowRect.Y1 + hHeight + vDist, arrowRect.X1 + hWidth, arrowRect.Y2);
4677 canvas()->drawLine(arrowRect.X1 + hWidth, arrowRect.Y2, arrowRect.X2, arrowRect.Y1 + hHeight + vDist);
4678}
4679
4680
4681// uiCustomComboBox
4683
4684
4685
4687// uiComboBox
4688
4689
4690uiComboBox::uiComboBox(uiWindow * parent, const Point & pos, const Size & size, int listHeight, bool visible, uint32_t styleClassID)
4691 : uiCustomComboBox(parent, pos, size, listHeight, visible, 0),
4692 m_textEdit(nullptr),
4693 m_listBox(nullptr)
4694{
4695 objectType().uiComboBox = true;
4696
4697 m_textEdit = new uiTextEdit(this, "", Point(windowStyle().borderSize, windowStyle().borderSize), getEditControlSize(), true, 0);
4698 m_textEdit->textEditProps().hasCaret = false;
4699 m_textEdit->textEditProps().allowEdit = false;
4700
4701 m_listBox = new uiListBox(getListBoxParent(), Point(0, 0), Size(0, 0), true, 0);
4702
4703 if (app()->style() && styleClassID)
4704 app()->style()->setStyle(this, styleClassID);
4705}
4706
4707
4708uiComboBox::~uiComboBox()
4709{
4710}
4711
4712
4713// refresh text edit with the selected listbox item
4714void uiComboBox::updateEditControl()
4715{
4716 int idx = selectedItem();
4717 m_textEdit->setText(idx > -1 ? items().get(idx) : "");
4718 m_textEdit->repaint();
4719}
4720
4721
4722// uiComboBox
4724
4725
4726
4728// uiColorComboBox
4729
4730
4731uiColorComboBox::uiColorComboBox(uiWindow * parent, const Point & pos, const Size & size, int listHeight, bool visible, uint32_t styleClassID)
4732 : uiCustomComboBox(parent, pos, size, listHeight, visible, 0),
4733 m_colorBox(nullptr),
4734 m_colorListBox(nullptr)
4735{
4736 objectType().uiColorComboBox = true;
4737
4738 m_colorBox = new uiColorBox(this, Point(windowStyle().borderSize, windowStyle().borderSize), getEditControlSize(), Color::BrightWhite, true, 0);
4739 m_colorListBox = new uiColorListBox(getListBoxParent(), Point(0, 0), Size(0, 0), true, 0);
4740
4741 if (app()->style() && styleClassID)
4742 app()->style()->setStyle(this, styleClassID);
4743}
4744
4745
4746uiColorComboBox::~uiColorComboBox()
4747{
4748}
4749
4750
4751// refresh text edit with the selected listbox item
4752void uiColorComboBox::updateEditControl()
4753{
4754 m_colorBox->setColor( (Color) selectedItem() );
4755}
4756
4757
4758// uiColorComboBox
4760
4761
4762
4764// uiCheckBox
4765
4766
4767uiCheckBox::uiCheckBox(uiWindow * parent, const Point & pos, const Size & size, uiCheckBoxKind kind, bool visible, uint32_t styleClassID)
4768 : uiControl(parent, pos, size, visible, 0),
4769 m_checked(false),
4770 m_kind(kind),
4771 m_groupIndex(-1)
4772{
4773 objectType().uiCheckBox = true;
4774
4775 windowProps().focusable = true;
4776
4777 windowStyle().borderSize = 1;
4779 windowStyle().borderColor = RGB888(64, 64, 64);
4780
4781 if (app()) {
4782 m_checkBoxStyle.adaptToDisplayColors(app()->displayColors());
4783 if (app()->style() && styleClassID)
4784 app()->style()->setStyle(this, styleClassID);
4785 }
4786}
4787
4788
4789uiCheckBox::~uiCheckBox()
4790{
4791}
4792
4793
4794void uiCheckBox::paintCheckBox()
4795{
4797 // background
4798 RGB888 bkColor = m_checked ? m_checkBoxStyle.checkedBackgroundColor : m_checkBoxStyle.backgroundColor;
4799 if (isMouseOver())
4800 bkColor = m_checkBoxStyle.mouseOverBackgroundColor;
4801 canvas()->setBrushColor(bkColor);
4802 canvas()->fillRectangle(bkgRect);
4803 // content
4804 if (m_checked) {
4805 Rect r = rect(uiOrigin::Window).shrink(5);
4806 switch (m_kind) {
4808 canvas()->setPenColor(isMouseOver() ? m_checkBoxStyle.mouseOverForegroundColor : m_checkBoxStyle.foregroundColor);
4809 canvas()->drawLine(r.X1, r.Y2 - r.height() / 3, r.X1 + r.width() / 3, r.Y2);
4810 canvas()->drawLine(r.X1 + r.width() / 3, r.Y2, r.X2, r.Y1);
4811 break;
4813 canvas()->setBrushColor(isMouseOver() ? m_checkBoxStyle.mouseOverForegroundColor : m_checkBoxStyle.foregroundColor);
4814 canvas()->fillEllipse(r.X1 + r.width() / 2 - 1, r.Y1 + r.height() / 2 - 1, r.width(), r.height());
4815 break;
4816 }
4817 }
4818}
4819
4820
4821void uiCheckBox::processEvent(uiEvent * event)
4822{
4823 uiControl::processEvent(event);
4824
4825 switch (event->id) {
4826
4827 case UIEVT_PAINT:
4828 beginPaint(event, uiControl::clientRect(uiOrigin::Window));
4829 paintCheckBox();
4830 break;
4831
4832 case UIEVT_CLICK:
4833 trigger();
4834 onClick();
4835 break;
4836
4837 case UIEVT_MOUSEENTER:
4838 repaint(); // to update background color
4839 break;
4840
4841 case UIEVT_MOUSEBUTTONDOWN:
4842 if (event->params.mouse.changedButton == 1)
4843 repaint();
4844 break;
4845
4846 case UIEVT_MOUSELEAVE:
4847 repaint(); // to update background
4848 break;
4849
4850 case UIEVT_KEYUP:
4851 if (event->params.key.VK == VK_RETURN || event->params.key.VK == VK_KP_ENTER || event->params.key.VK == VK_SPACE) {
4852 trigger();
4853 onClick();
4854 }
4855 break;
4856
4857 default:
4858 break;
4859 }
4860}
4861
4862
4863// action to perfom on mouse up or keyboard space/enter
4864void uiCheckBox::trigger()
4865{
4866 switch (m_kind) {
4868 m_checked = !m_checked;
4869 break;
4871 m_checked = true;
4872 unCheckGroup();
4873 break;
4874 }
4875 onChange();
4876 repaint();
4877}
4878
4879
4881{
4882 if (value != m_checked) {
4883 m_checked = value;
4884 if (m_kind == uiCheckBoxKind::RadioButton && value == true)
4885 unCheckGroup();
4886 repaint();
4887 }
4888}
4889
4890
4891// unchecks all other items of the same group
4892void uiCheckBox::unCheckGroup()
4893{
4894 if (m_groupIndex == -1)
4895 return;
4896 for (auto sibling = parent()->firstChild(); sibling; sibling = sibling->next()) {
4897 if (sibling != this && objectType().uiCheckBox) {
4898 uiCheckBox * chk = (uiCheckBox*) sibling;
4899 if (chk->m_groupIndex == m_groupIndex)
4900 chk->setChecked(false);
4901 }
4902 }
4903}
4904
4905
4906
4907// uiCheckBox
4909
4910
4911
4913// uiSlider
4914
4915
4916uiSlider::uiSlider(uiWindow * parent, const Point & pos, const Size & size, uiOrientation orientation, bool visible, uint32_t styleClassID)
4917 : uiControl(parent, pos, size, visible, 0),
4918 m_orientation(orientation),
4919 m_position(0),
4920 m_min(0),
4921 m_max(99),
4922 m_ticksFrequency(25)
4923{
4924 objectType().uiSlider = true;
4925
4926 windowStyle().borderSize = 1;
4927 windowStyle().borderColor = RGB888(255, 255, 255);
4928
4929 windowProps().focusable = true;
4930
4931 if (app()) {
4932 m_sliderStyle.adaptToDisplayColors(app()->displayColors());
4933 if (app()->style() && styleClassID)
4934 app()->style()->setStyle(this, styleClassID);
4935 }
4936}
4937
4938
4939uiSlider::~uiSlider()
4940{
4941}
4942
4943
4945{
4946 if (value != m_position) {
4947 m_position = iclamp(value, m_min, m_max);
4948 repaint();
4949 onChange();
4950 }
4951}
4952
4953
4954void uiSlider::setup(int min, int max, int ticksFrequency)
4955{
4956 m_min = min;
4957 m_max = max;
4958 m_ticksFrequency = ticksFrequency;
4959 m_position = iclamp(m_position, m_min, m_max);
4960}
4961
4962
4963void uiSlider::paintSlider()
4964{
4966 Rect slideRect = cRect.shrink(4);
4967 Rect gripRect = getGripRect();
4968 // background
4969 canvas()->setBrushColor(m_sliderStyle.backgroundColor);
4970 canvas()->fillRectangle(cRect);
4971 // slide
4972 canvas()->setBrushColor(m_sliderStyle.slideColor);
4973 switch (m_orientation) {
4975 canvas()->fillRectangle(gripRect.X2, slideRect.Y1, slideRect.X2, slideRect.Y2); // right slide
4976 canvas()->setBrushColor(m_sliderStyle.rangeColor);
4977 canvas()->fillRectangle(slideRect.X1, slideRect.Y1, gripRect.X1, slideRect.Y2); // left slide
4978 break;
4980 canvas()->fillRectangle(slideRect.X1, slideRect.Y1, slideRect.X2, gripRect.Y1); // upper slide
4981 canvas()->setBrushColor(m_sliderStyle.rangeColor);
4982 canvas()->fillRectangle(slideRect.X1, gripRect.Y2, slideRect.X2, slideRect.Y2); // bottom slide
4983 break;
4984 }
4985 // ticks
4986 if (m_ticksFrequency > 0) {
4987 canvas()->setPenColor(m_sliderStyle.ticksColor);
4988 int range = m_max - m_min + 0;
4989 for (int p = m_min; p <= m_max; p += m_ticksFrequency) {
4990 switch (m_orientation) {
4992 {
4993 int x = slideRect.X1 + slideRect.width() * (p - m_min) / range;
4994 canvas()->drawLine(x, slideRect.Y1, x, slideRect.Y2);
4995 break;
4996 }
4998 {
4999 int y = slideRect.Y2 - slideRect.height() * (p - m_min) / range;
5000 canvas()->drawLine(slideRect.X1, y, slideRect.X2, y);
5001 break;
5002 }
5003 }
5004 }
5005 }
5006 // grip
5007 canvas()->setBrushColor(isMouseOver() ? m_sliderStyle.mouseOverGripColor : m_sliderStyle.gripColor);
5008 canvas()->fillRectangle(gripRect);
5009 canvas()->setPenColor(m_sliderStyle.gripColor);
5010 canvas()->drawRectangle(gripRect);
5011}
5012
5013
5014Rect uiSlider::getGripRect()
5015{
5017 Rect slideRect = cRect.shrink(4);
5018 int range = m_max - m_min + 0;
5019 switch (m_orientation) {
5021 {
5022 int x = slideRect.X1 + slideRect.width() * (m_position - m_min) / range;
5023 return Rect(x - 4, cRect.Y1, x + 4, cRect.Y2);
5024 }
5026 {
5027 int y = slideRect.Y2 - slideRect.height() * (m_position - m_min) / range;
5028 return Rect(cRect.X1, y - 4, cRect.X2, y + 4);
5029 }
5030 default:
5031 return Rect();
5032 }
5033}
5034
5035
5036void uiSlider::moveGripTo(int x, int y)
5037{
5039 Rect slideRect = cRect.shrink(4);
5040 int range = m_max - m_min + 1;
5041 switch (m_orientation) {
5043 setPosition(m_min + (x - slideRect.X1) * range / slideRect.width());
5044 break;
5046 setPosition(m_min + (slideRect.Y2 - y) * range / slideRect.height());
5047 break;
5048 }
5049}
5050
5051
5052void uiSlider::processEvent(uiEvent * event)
5053{
5054 uiControl::processEvent(event);
5055
5056 switch (event->id) {
5057
5058 case UIEVT_PAINT:
5059 beginPaint(event, uiControl::clientRect(uiOrigin::Window));
5060 paintSlider();
5061 break;
5062
5063 case UIEVT_MOUSEBUTTONDOWN:
5064 moveGripTo(event->params.mouse.status.X, event->params.mouse.status.Y);
5065 break;
5066
5067 case UIEVT_MOUSEMOVE:
5068 if (app()->capturedMouseWindow() == this)
5069 moveGripTo(event->params.mouse.status.X, event->params.mouse.status.Y);
5070 break;
5071
5072 case UIEVT_KEYDOWN:
5073 handleKeyDown(event->params.key);
5074 break;
5075
5076 case UIEVT_MOUSEENTER:
5077 repaint(); // to update background color
5078 break;
5079
5080 case UIEVT_MOUSELEAVE:
5081 repaint(); // to update background
5082 break;
5083
5084 default:
5085 break;
5086 }
5087}
5088
5089
5090void uiSlider::handleKeyDown(uiKeyEventInfo key)
5091{
5092 switch (key.VK) {
5093 case VK_UP:
5094 case VK_KP_UP:
5095 case VK_LEFT:
5096 case VK_KP_LEFT:
5097 setPosition(m_position - 1);
5098 break;
5099
5100 case VK_DOWN:
5101 case VK_KP_DOWN:
5102 case VK_RIGHT:
5103 case VK_KP_RIGHT:
5104 setPosition(m_position + 1);
5105 break;
5106
5107 case VK_PAGEUP:
5108 case VK_KP_PAGEUP:
5109 setPosition(m_position + m_ticksFrequency);
5110 break;
5111
5112 case VK_PAGEDOWN:
5113 case VK_KP_PAGEDOWN:
5114 setPosition(m_position - m_ticksFrequency);
5115 break;
5116
5117 case VK_HOME:
5118 case VK_KP_HOME:
5119 setPosition(m_min);
5120 break;
5121
5122 case VK_END:
5123 case VK_KP_END:
5124 setPosition(m_max);
5125 break;
5126
5127 default:
5128 break;
5129 }
5130}
5131
5132
5133
5134
5135// uiSlider
5137
5138
5139
5141// uiProgressBar
5142
5143
5144uiProgressBar::uiProgressBar(uiWindow * parent, const Point & pos, const Size & size, bool visible, uint32_t styleClassID)
5145 : uiControl(parent, pos, size, visible, 0)
5146{
5147 objectType().uiProgressBar = true;
5148
5149 windowProps().focusable = false;
5150 windowStyle().borderSize = 1;
5151 windowStyle().borderColor = RGB888(64, 64, 64);
5152
5153 if (app()) {
5154 m_progressBarStyle.adaptToDisplayColors(app()->displayColors());
5155 if (app()->style() && styleClassID)
5156 app()->style()->setStyle(this, styleClassID);
5157 }
5158
5159 m_percentage = 0;
5160}
5161
5162
5163uiProgressBar::~uiProgressBar()
5164{
5165}
5166
5167
5168void uiProgressBar::paintProgressBar()
5169{
5171
5172 int splitPos = cRect.width() * m_percentage / 100;
5173 Rect fRect = Rect(cRect.X1, cRect.Y1, cRect.X1 + splitPos, cRect.Y2);
5174 Rect bRect = Rect(cRect.X1 + splitPos + 1, cRect.Y1, cRect.X2, cRect.Y2);
5175
5176 // the bar
5177 canvas()->setBrushColor(m_progressBarStyle.foregroundColor);
5178 canvas()->fillRectangle(fRect);
5179 canvas()->setBrushColor(m_progressBarStyle.backgroundColor);
5180 canvas()->fillRectangle(bRect);
5181
5182 if (m_progressBarProps.showPercentage) {
5183 char txt[5];
5184 sprintf(txt, "%d%%", m_percentage);
5185 canvas()->setGlyphOptions(GlyphOptions().FillBackground(false).DoubleWidth(0).Bold(false).Italic(false).Underline(false).Invert(0));
5186 canvas()->setPenColor(m_progressBarStyle.textColor);
5187 int x = fRect.X2 - canvas()->textExtent(m_progressBarStyle.textFont, txt);
5188 int y = cRect.Y1 + (cRect.height() - m_progressBarStyle.textFont->height) / 2;
5189 canvas()->drawText(m_progressBarStyle.textFont, x, y, txt);
5190 }
5191}
5192
5193
5194void uiProgressBar::processEvent(uiEvent * event)
5195{
5196 uiControl::processEvent(event);
5197
5198 switch (event->id) {
5199
5200 case UIEVT_PAINT:
5201 beginPaint(event, uiControl::clientRect(uiOrigin::Window));
5202 paintProgressBar();
5203 break;
5204
5205 default:
5206 break;
5207 }
5208}
5209
5210
5212{
5213 value = imin(imax(0, value), 100);
5214 if (value != m_percentage) {
5215 m_percentage = value;
5216 repaint();
5217 }
5218}
5219
5220
5221// uiProgressBar
5223
5224
5225
5227// uiSimpleMenu
5228
5229
5230uiSimpleMenu::uiSimpleMenu(uiWindow * parent, const Point & pos, const Size & size, bool visible, uint32_t styleClassID)
5231 : uiCustomListBox(parent, pos, size, visible, 0)
5232{
5233 objectType().uiSimpleMenu = true;
5234
5237
5238 if (app()->style() && styleClassID)
5239 app()->style()->setStyle(this, styleClassID);
5240}
5241
5242
5243void uiSimpleMenu::processEvent(uiEvent * event)
5244{
5245 uiCustomListBox::processEvent(event);
5246
5247 switch (event->id) {
5248
5249 case UIEVT_MOUSEBUTTONUP:
5250 if (event->params.mouse.changedButton == 1) {
5251 int idx = getItemAtMousePos(event->params.mouse.status.X, event->params.mouse.status.Y);
5252 if (idx >= 0)
5253 onSelect(idx);
5254 }
5255 break;
5256
5257 case UIEVT_KEYUP:
5258 if (event->params.key.VK == VK_RETURN || event->params.key.VK == VK_KP_ENTER || event->params.key.VK == VK_SPACE) {
5259 int idx = firstSelectedItem();
5260 if (idx >= 0)
5261 onSelect(idx);
5262 } else if (event->params.key.VK == VK_ESCAPE) {
5263 onSelect(-1);
5264 }
5265 break;
5266
5267 default:
5268 break;
5269
5270 }
5271}
5272
5273
5274void uiSimpleMenu::items_draw(int index, const Rect & itemRect)
5275{
5276 int x = itemRect.X1 + 1;
5277 int y = itemRect.Y1 + (itemRect.height() - listBoxStyle().textFont->height) / 2;
5278 canvas()->drawText(listBoxStyle().textFont, x, y, m_items.get(index));
5279}
5280
5281
5282// uiSimpleMenu
5284
5285
5286
5288// uiSplitButton
5289
5290
5291uiSplitButton::uiSplitButton(uiWindow * parent, char const * text, const Point & pos, const Size & size, int listHeight, char const * itemsText, char separator, bool visible, uint32_t styleClassID)
5292 : uiCustomComboBox(parent, pos, size, listHeight, visible, 0),
5293 m_button(nullptr),
5294 m_menu(nullptr),
5295 m_selectedItem(-1)
5296{
5297 objectType().uiSplitButton = true;
5298
5299 comboBoxProps().openOnFocus = false;
5300
5301 m_button = new uiButton(this, text, Point(windowStyle().borderSize, windowStyle().borderSize), getEditControlSize(), uiButtonKind::Button, true, 0);
5302 m_button->onMouseDown = [&](uiMouseEventInfo const & ev) {
5303 if (!isListBoxOpen())
5304 openListBox();
5305 };
5306
5307 m_menu = new uiSimpleMenu(getListBoxParent(), Point(0, 0), Size(0, 0), true, 0);
5308 m_menu->items().appendSepList(itemsText, separator);
5309 m_menu->onSelect = [&](int idx) {
5310 closeListBox();
5311 app()->setFocusedWindow(this);
5312 m_selectedItem = idx;
5313 };
5314
5315 if (app()->style() && styleClassID)
5316 app()->style()->setStyle(this, styleClassID);
5317}
5318
5319
5320uiSplitButton::~uiSplitButton()
5321{
5322}
5323
5324
5325void uiSplitButton::processEvent(uiEvent * event)
5326{
5327 uiCustomComboBox::processEvent(event);
5328
5329 switch (event->id) {
5330
5331 case UIEVT_SETFOCUS:
5332 if (m_selectedItem > -1) {
5333 onSelect(m_selectedItem);
5334 m_selectedItem = -1;
5335 }
5336 break;
5337
5338 default:
5339 break;
5340 };
5341}
5342
5343
5344void uiSplitButton::openListBox()
5345{
5346 m_menu->deselectAll();
5347 uiCustomComboBox::openListBox();
5348}
5349
5350
5351void uiSplitButton::updateEditControl()
5352{
5353}
5354
5355
5356void uiSplitButton::paintButton()
5357{
5358 Rect btnRect = getButtonRect();
5359
5360 // button background
5361 canvas()->setBrushColor(comboBoxStyle().buttonBackgroundColor);
5362 canvas()->fillRectangle(btnRect);
5363
5364 // button glyph
5365 Rect arrowRect = btnRect.hShrink(btnRect.width() / 4).vShrink(btnRect.height() / 4);
5366 if ((arrowRect.X1 + arrowRect.X2) & 1)
5367 --arrowRect.X1;
5368 bool up = isListBoxOpen();
5369 Point points[3] = { { arrowRect.X1, up ? arrowRect.Y2 : arrowRect.Y1 },
5370 { arrowRect.X2, up ? arrowRect.Y2 : arrowRect.Y1 },
5371 { (arrowRect.X1 + arrowRect.X2) / 2, up ? arrowRect.Y1 : arrowRect.Y2 } };
5372 canvas()->setBrushColor(comboBoxStyle().buttonColor);
5373 canvas()->setPenColor(comboBoxStyle().buttonColor);
5374 canvas()->fillPath(points, 3);
5375 canvas()->drawPath(points, 3);
5376}
5377
5378
5379// uiSplitButton
5381
5382
5383
5384} // end of namespace
5385
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:684
bool reload()
Reloads directory content.
Definition: fabutils.cpp:852
bool setDirectory(const char *path)
Sets absolute directory path.
Definition: fabutils.cpp:671
int count()
Determines number of files in current directory.
Definition: fabutils.h:598
DirItem const * get(int index)
Gets file/directory at index.
Definition: fabutils.h:607
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:238
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:3195
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:3510
Delegate< uiTimerHandle > onTimer
Timer event delegate.
Definition: fabui.h:3538
void joinAsyncRun()
Waits for runAsync termination.
Definition: fabui.cpp:371
uiFrame * rootWindow()
Gets a pointer to the root window.
Definition: fabui.h:3185
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:3220
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:3105