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