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