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