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