luigi.h (201264B)
1 // TODO UITextbox features - mouse input, multi-line, clipboard, undo, IME support, number dragging. 2 // TODO New elements - list view, menu bar. 3 // TODO Keyboard navigation - menus, dialogs, tables. 4 // TODO Easier to use fonts; GDI font support. 5 // TODO Formalize the notion of size-stability? See _UIExpandPaneButtonInvoke. 6 7 #include <stdint.h> 8 #include <stddef.h> 9 #include <stdbool.h> 10 #include <stdarg.h> 11 12 #ifdef UI_LINUX 13 #include <X11/Xlib.h> 14 #include <X11/Xutil.h> 15 #include <X11/Xatom.h> 16 #include <X11/cursorfont.h> 17 18 #if defined(__x86_64__) || defined(_M_X64) 19 #include <xmmintrin.h> 20 #endif 21 22 #endif 23 24 #define _UI_TO_STRING_1(x) #x 25 #define _UI_TO_STRING_2(x) _UI_TO_STRING_1(x) 26 27 #ifdef UI_WINDOWS 28 #undef _UNICODE 29 #undef UNICODE 30 #include <windows.h> 31 32 #define UI_ASSERT(x) do { if (!(x)) { ui.assertionFailure = true; \ 33 MessageBox(0, "Assertion failure on line " _UI_TO_STRING_2(__LINE__), 0, 0); \ 34 ExitProcess(1); } } while (0) 35 #define UI_CALLOC(x) HeapAlloc(ui.heap, HEAP_ZERO_MEMORY, (x)) 36 #define UI_FREE(x) HeapFree(ui.heap, 0, (x)) 37 #define UI_MALLOC(x) HeapAlloc(ui.heap, 0, (x)) 38 #define UI_REALLOC _UIHeapReAlloc 39 #define UI_CLOCK GetTickCount 40 #define UI_CLOCKS_PER_SECOND (1000) 41 #define UI_CLOCK_T DWORD 42 #endif 43 44 #if defined(UI_LINUX) 45 #include <stdlib.h> 46 #include <string.h> 47 #include <assert.h> 48 #include <time.h> 49 #include <math.h> 50 51 #define UI_ASSERT assert 52 #define UI_CALLOC(x) calloc(1, (x)) 53 #define UI_FREE free 54 #define UI_MALLOC malloc 55 #define UI_REALLOC realloc 56 #define UI_CLOCK clock 57 #define UI_CLOCKS_PER_SECOND CLOCKS_PER_SEC 58 #define UI_CLOCK_T clock_t 59 #endif 60 61 #if defined(UI_ESSENCE) 62 #include <essence.h> 63 64 #define UI_ASSERT EsAssert 65 #define UI_CALLOC(x) EsHeapAllocate((x), true) 66 #define UI_FREE EsHeapFree 67 #define UI_MALLOC(x) EsHeapAllocate((x), false) 68 #define UI_REALLOC(x, y) EsHeapReallocate((x), (y), false) 69 #define UI_CLOCK EsTimeStampMs 70 #define UI_CLOCKS_PER_SECOND 1000 71 #define UI_CLOCK_T uint64_t 72 73 // Callback to allow the application to process messages. 74 void _UIMessageProcess(EsMessage *message); 75 #endif 76 77 #ifdef UI_DEBUG 78 #include <stdio.h> 79 #endif 80 81 #ifdef UI_FREETYPE 82 #include <ft2build.h> 83 #include FT_FREETYPE_H 84 #include <freetype/ftbitmap.h> 85 #endif 86 87 #define UI_SIZE_BUTTON_MINIMUM_WIDTH (100) 88 #define UI_SIZE_BUTTON_PADDING (16) 89 #define UI_SIZE_BUTTON_HEIGHT (27) 90 #define UI_SIZE_BUTTON_CHECKED_AREA (4) 91 92 #define UI_SIZE_CHECKBOX_BOX (14) 93 #define UI_SIZE_CHECKBOX_GAP (8) 94 95 #define UI_SIZE_MENU_ITEM_HEIGHT (24) 96 #define UI_SIZE_MENU_ITEM_MINIMUM_WIDTH (160) 97 #define UI_SIZE_MENU_ITEM_MARGIN (9) 98 99 #define UI_SIZE_GAUGE_WIDTH (200) 100 #define UI_SIZE_GAUGE_HEIGHT (22) 101 102 #define UI_SIZE_SLIDER_WIDTH (200) 103 #define UI_SIZE_SLIDER_HEIGHT (25) 104 #define UI_SIZE_SLIDER_THUMB (15) 105 #define UI_SIZE_SLIDER_TRACK (5) 106 107 #define UI_SIZE_TEXTBOX_MARGIN (3) 108 #define UI_SIZE_TEXTBOX_WIDTH (200) 109 #define UI_SIZE_TEXTBOX_HEIGHT (27) 110 111 #define UI_SIZE_TAB_PANE_SPACE_TOP (2) 112 #define UI_SIZE_TAB_PANE_SPACE_LEFT (4) 113 114 #define UI_SIZE_SPLITTER (8) 115 116 #define UI_SIZE_SCROLL_BAR (16) 117 #define UI_SIZE_SCROLL_MINIMUM_THUMB (20) 118 119 #define UI_SIZE_CODE_MARGIN (ui.activeFont->glyphWidth * 5) 120 #define UI_SIZE_CODE_MARGIN_GAP (ui.activeFont->glyphWidth * 1) 121 122 #define UI_SIZE_TABLE_HEADER (26) 123 #define UI_SIZE_TABLE_COLUMN_GAP (20) 124 #define UI_SIZE_TABLE_ROW (20) 125 126 #define UI_SIZE_PANE_MEDIUM_BORDER (5) 127 #define UI_SIZE_PANE_MEDIUM_GAP (5) 128 #define UI_SIZE_PANE_SMALL_BORDER (3) 129 #define UI_SIZE_PANE_SMALL_GAP (3) 130 131 #define UI_SIZE_MDI_CHILD_BORDER (6) 132 #define UI_SIZE_MDI_CHILD_TITLE (30) 133 #define UI_SIZE_MDI_CHILD_CORNER (12) 134 #define UI_SIZE_MDI_CHILD_MINIMUM_WIDTH (100) 135 #define UI_SIZE_MDI_CHILD_MINIMUM_HEIGHT (50) 136 #define UI_SIZE_MDI_CASCADE (30) 137 138 #define UI_UPDATE_HOVERED (1) 139 #define UI_UPDATE_PRESSED (2) 140 #define UI_UPDATE_FOCUSED (3) 141 142 typedef enum UIMessage { 143 UI_MSG_PAINT, // dp = pointer to UIPainter 144 UI_MSG_LAYOUT, 145 UI_MSG_DESTROY, 146 UI_MSG_UPDATE, // di = UI_UPDATE_... constant 147 UI_MSG_ANIMATE, 148 UI_MSG_SCROLLED, 149 UI_MSG_GET_WIDTH, // di = height (if known); return width 150 UI_MSG_GET_HEIGHT, // di = width (if known); return height 151 UI_MSG_FIND_BY_POINT, // dp = pointer to UIFindByPoint; return 1 if handled 152 UI_MSG_CLIENT_PARENT, // dp = pointer to UIElement *, set it to the parent for client elements 153 154 UI_MSG_INPUT_EVENTS_START, // not sent to disabled elements 155 UI_MSG_LEFT_DOWN, 156 UI_MSG_LEFT_UP, 157 UI_MSG_MIDDLE_DOWN, 158 UI_MSG_MIDDLE_UP, 159 UI_MSG_RIGHT_DOWN, 160 UI_MSG_RIGHT_UP, 161 UI_MSG_KEY_TYPED, // dp = pointer to UIKeyTyped; return 1 if handled 162 UI_MSG_MOUSE_MOVE, 163 UI_MSG_MOUSE_DRAG, 164 UI_MSG_MOUSE_WHEEL, // di = delta; return 1 if handled 165 UI_MSG_CLICKED, 166 UI_MSG_GET_CURSOR, // return cursor code 167 UI_MSG_PRESSED_DESCENDENT, // dp = pointer to child that is/contains pressed element 168 UI_MSG_INPUT_EVENTS_END, 169 170 UI_MSG_VALUE_CHANGED, // sent to notify that the element's value has changed 171 UI_MSG_TABLE_GET_ITEM, // dp = pointer to UITableGetItem; return string length 172 UI_MSG_CODE_GET_MARGIN_COLOR, // di = line index (starts at 1); return color 173 UI_MSG_CODE_DECORATE_LINE, // dp = pointer to UICodeDecorateLine 174 UI_MSG_WINDOW_CLOSE, // return 1 to prevent default (process exit for UIWindow; close for UIMDIChild) 175 UI_MSG_TAB_SELECTED, // sent to the tab that was selected (not the tab pane itself) 176 UI_MSG_WINDOW_DROP_FILES, // di = count, dp = char ** of paths 177 UI_MSG_WINDOW_ACTIVATE, 178 179 UI_MSG_USER, 180 } UIMessage; 181 182 #ifdef UI_ESSENCE 183 #define UIRectangle EsRectangle 184 #else 185 typedef struct UIRectangle { 186 int l, r, t, b; 187 } UIRectangle; 188 #endif 189 190 typedef struct UITheme { 191 uint32_t panel1, panel2, selected, border; 192 uint32_t text, textDisabled, textSelected; 193 uint32_t buttonNormal, buttonHovered, buttonPressed, buttonDisabled; 194 uint32_t textboxNormal, textboxFocused; 195 uint32_t codeFocused, codeBackground, codeDefault, codeComment, codeString, codeNumber, codeOperator, codePreprocessor; 196 } UITheme; 197 198 typedef struct UIPainter { 199 UIRectangle clip; 200 uint32_t *bits; 201 int width, height; 202 #ifdef UI_DEBUG 203 int fillCount; 204 #endif 205 } UIPainter; 206 207 typedef struct UIFont { 208 int glyphWidth, glyphHeight; 209 210 #ifdef UI_FREETYPE 211 bool isFreeType; 212 FT_Face font; 213 FT_Bitmap glyphs[128]; 214 bool glyphsRendered[128]; 215 int glyphOffsetsX[128], glyphOffsetsY[128]; 216 #endif 217 } UIFont; 218 219 typedef struct UIShortcut { 220 intptr_t code; 221 bool ctrl, shift, alt; 222 void (*invoke)(void *cp); 223 void *cp; 224 } UIShortcut; 225 226 typedef struct UIStringSelection { 227 int carets[2]; 228 uint32_t colorText, colorBackground; 229 } UIStringSelection; 230 231 typedef struct UIKeyTyped { 232 char *text; 233 int textBytes; 234 intptr_t code; 235 } UIKeyTyped; 236 237 typedef struct UITableGetItem { 238 char *buffer; 239 size_t bufferBytes; 240 int index, column; 241 bool isSelected; 242 } UITableGetItem; 243 244 typedef struct UICodeDecorateLine { 245 UIRectangle bounds; 246 int index; // Starting at 1! 247 int x, y; // Position where additional text can be drawn. 248 UIPainter *painter; 249 } UICodeDecorateLine; 250 251 typedef struct UIFindByPoint { 252 int x, y; 253 struct UIElement *result; 254 } UIFindByPoint; 255 256 #define UI_RECT_1(x) ((UIRectangle) { (x), (x), (x), (x) }) 257 #define UI_RECT_1I(x) ((UIRectangle) { (x), -(x), (x), -(x) }) 258 #define UI_RECT_2(x, y) ((UIRectangle) { (x), (x), (y), (y) }) 259 #define UI_RECT_2I(x, y) ((UIRectangle) { (x), -(x), (y), -(y) }) 260 #define UI_RECT_2S(x, y) ((UIRectangle) { 0, (x), 0, (y) }) 261 #define UI_RECT_4(x, y, z, w) ((UIRectangle) { (x), (y), (z), (w) }) 262 #define UI_RECT_WIDTH(_r) ((_r).r - (_r).l) 263 #define UI_RECT_HEIGHT(_r) ((_r).b - (_r).t) 264 #define UI_RECT_TOTAL_H(_r) ((_r).r + (_r).l) 265 #define UI_RECT_TOTAL_V(_r) ((_r).b + (_r).t) 266 #define UI_RECT_SIZE(_r) UI_RECT_WIDTH(_r), UI_RECT_HEIGHT(_r) 267 #define UI_RECT_TOP_LEFT(_r) (_r).l, (_r).t 268 #define UI_RECT_BOTTOM_LEFT(_r) (_r).l, (_r).b 269 #define UI_RECT_BOTTOM_RIGHT(_r) (_r).r, (_r).b 270 #define UI_RECT_ALL(_r) (_r).l, (_r).r, (_r).t, (_r).b 271 #define UI_RECT_VALID(_r) (UI_RECT_WIDTH(_r) > 0 && UI_RECT_HEIGHT(_r) > 0) 272 273 #define UI_COLOR_ALPHA_F(x) ((((x) >> 24) & 0xFF) / 255.0f) 274 #define UI_COLOR_RED_F(x) ((((x) >> 16) & 0xFF) / 255.0f) 275 #define UI_COLOR_GREEN_F(x) ((((x) >> 8) & 0xFF) / 255.0f) 276 #define UI_COLOR_BLUE_F(x) ((((x) >> 0) & 0xFF) / 255.0f) 277 #define UI_COLOR_ALPHA(x) ((((x) >> 24) & 0xFF)) 278 #define UI_COLOR_RED(x) ((((x) >> 16) & 0xFF)) 279 #define UI_COLOR_GREEN(x) ((((x) >> 8) & 0xFF)) 280 #define UI_COLOR_BLUE(x) ((((x) >> 0) & 0xFF)) 281 #define UI_COLOR_FROM_FLOAT(r, g, b) (((uint32_t) ((r) * 255.0f) << 16) | ((uint32_t) ((g) * 255.0f) << 8) | ((uint32_t) ((b) * 255.0f) << 0)) 282 #define UI_COLOR_FROM_RGBA_F(r, g, b, a) (((uint32_t) ((r) * 255.0f) << 16) | ((uint32_t) ((g) * 255.0f) << 8) \ 283 | ((uint32_t) ((b) * 255.0f) << 0) | ((uint32_t) ((a) * 255.0f) << 24)) 284 285 #define UI_SWAP(s, a, b) do { s t = (a); (a) = (b); (b) = t; } while (0) 286 287 #define UI_CURSOR_ARROW (0) 288 #define UI_CURSOR_TEXT (1) 289 #define UI_CURSOR_SPLIT_V (2) 290 #define UI_CURSOR_SPLIT_H (3) 291 #define UI_CURSOR_FLIPPED_ARROW (4) 292 #define UI_CURSOR_CROSS_HAIR (5) 293 #define UI_CURSOR_HAND (6) 294 #define UI_CURSOR_RESIZE_UP (7) 295 #define UI_CURSOR_RESIZE_LEFT (8) 296 #define UI_CURSOR_RESIZE_UP_RIGHT (9) 297 #define UI_CURSOR_RESIZE_UP_LEFT (10) 298 #define UI_CURSOR_RESIZE_DOWN (11) 299 #define UI_CURSOR_RESIZE_RIGHT (12) 300 #define UI_CURSOR_RESIZE_DOWN_RIGHT (13) 301 #define UI_CURSOR_RESIZE_DOWN_LEFT (14) 302 #define UI_CURSOR_COUNT (15) 303 304 #define UI_ALIGN_LEFT (1) 305 #define UI_ALIGN_RIGHT (2) 306 #define UI_ALIGN_CENTER (3) 307 308 extern const int UI_KEYCODE_A; 309 extern const int UI_KEYCODE_BACKSPACE; 310 extern const int UI_KEYCODE_DELETE; 311 extern const int UI_KEYCODE_DOWN; 312 extern const int UI_KEYCODE_END; 313 extern const int UI_KEYCODE_ENTER; 314 extern const int UI_KEYCODE_ESCAPE; 315 extern const int UI_KEYCODE_F1; 316 extern const int UI_KEYCODE_HOME; 317 extern const int UI_KEYCODE_LEFT; 318 extern const int UI_KEYCODE_RIGHT; 319 extern const int UI_KEYCODE_SPACE; 320 extern const int UI_KEYCODE_TAB; 321 extern const int UI_KEYCODE_UP; 322 extern const int UI_KEYCODE_INSERT; 323 extern const int UI_KEYCODE_0; 324 325 #define UI_KEYCODE_LETTER(x) (UI_KEYCODE_A + (x) - 'A') 326 #define UI_KEYCODE_DIGIT(x) (UI_KEYCODE_0 + (x) - '0') 327 #define UI_KEYCODE_FKEY(x) (UI_KEYCODE_F1 + (x) - 1) 328 329 typedef struct UIElement { 330 #define UI_ELEMENT_V_FILL (1 << 16) 331 #define UI_ELEMENT_H_FILL (1 << 17) 332 #define UI_ELEMENT_WINDOW (1 << 18) 333 #define UI_ELEMENT_PARENT_PUSH (1 << 19) 334 #define UI_ELEMENT_TAB_STOP (1 << 20) 335 #define UI_ELEMENT_NON_CLIENT (1 << 21) // Don't destroy in UIElementDestroyDescendents, like scroll bars. 336 #define UI_ELEMENT_DISABLED (1 << 22) // Don't receive input events. 337 338 #define UI_ELEMENT_HIDE (1 << 29) 339 #define UI_ELEMENT_DESTROY (1 << 30) 340 #define UI_ELEMENT_DESTROY_DESCENDENT (1 << 31) 341 342 uint32_t flags; // First 16 bits are element specific. 343 uint32_t id; 344 345 struct UIElement *parent; 346 struct UIElement *next; 347 struct UIElement *children; 348 struct UIWindow *window; 349 350 UIRectangle bounds, clip; 351 352 void *cp; // Context pointer (for user). 353 354 int (*messageClass)(struct UIElement *element, UIMessage message, int di /* data integer */, void *dp /* data pointer */); 355 int (*messageUser)(struct UIElement *element, UIMessage message, int di, void *dp); 356 357 const char *cClassName; 358 } UIElement; 359 360 #define UI_SHORTCUT(code, ctrl, shift, alt, invoke, cp) ((UIShortcut) { (code), (ctrl), (shift), (alt), (invoke), (cp) }) 361 362 typedef struct UIWindow { 363 #define UI_WINDOW_MENU (1 << 0) 364 #define UI_WINDOW_INSPECTOR (1 << 1) 365 #define UI_WINDOW_CENTER_IN_OWNER (1 << 2) 366 #define UI_WINDOW_MAXIMIZE (1 << 3) 367 368 UIElement e; 369 370 UIElement *dialog; 371 372 UIShortcut *shortcuts; 373 size_t shortcutCount, shortcutAllocated; 374 375 float scale; 376 377 uint32_t *bits; 378 int width, height; 379 struct UIWindow *next; 380 381 UIElement *hovered, *pressed, *focused, *dialogOldFocus; 382 int pressedButton; 383 384 int cursorX, cursorY; 385 int cursorStyle; 386 387 // Set when a textbox is modified. 388 // Useful for tracking whether changes to the loaded document have been saved. 389 bool textboxModifiedFlag; 390 391 bool ctrl, shift, alt; 392 393 UIRectangle updateRegion; 394 395 #ifdef UI_DEBUG 396 float lastFullFillCount; 397 #endif 398 399 #ifdef UI_LINUX 400 Window window; 401 XImage *image; 402 XIC xic; 403 unsigned ctrlCode, shiftCode, altCode; 404 Window dragSource; 405 #endif 406 407 #ifdef UI_WINDOWS 408 HWND hwnd; 409 bool trackingLeave; 410 #endif 411 412 #ifdef UI_ESSENCE 413 EsWindow *window; 414 EsElement *canvas; 415 int cursor; 416 #endif 417 } UIWindow; 418 419 typedef struct UIPanel { 420 #define UI_PANEL_HORIZONTAL (1 << 0) 421 #define UI_PANEL_GRAY (1 << 2) 422 #define UI_PANEL_WHITE (1 << 3) 423 #define UI_PANEL_EXPAND (1 << 4) 424 #define UI_PANEL_MEDIUM_SPACING (1 << 5) 425 #define UI_PANEL_SMALL_SPACING (1 << 6) 426 #define UI_PANEL_SCROLL (1 << 7) 427 #define UI_PANEL_BORDER (1 << 8) 428 UIElement e; 429 struct UIScrollBar *scrollBar; 430 UIRectangle border; 431 int gap; 432 } UIPanel; 433 434 typedef struct UIButton { 435 #define UI_BUTTON_SMALL (1 << 0) 436 #define UI_BUTTON_MENU_ITEM (1 << 1) 437 #define UI_BUTTON_CAN_FOCUS (1 << 2) 438 #define UI_BUTTON_DROP_DOWN (1 << 3) 439 #define UI_BUTTON_CHECKED (1 << 15) 440 UIElement e; 441 char *label; 442 ptrdiff_t labelBytes; 443 void (*invoke)(void *cp); 444 } UIButton; 445 446 typedef struct UICheckbox { 447 #define UI_CHECKBOX_ALLOW_INDETERMINATE (1 << 0) 448 UIElement e; 449 #define UI_CHECK_UNCHECKED (0) 450 #define UI_CHECK_CHECKED (1) 451 #define UI_CHECK_INDETERMINATE (2) 452 uint8_t check; 453 char *label; 454 ptrdiff_t labelBytes; 455 void (*invoke)(void *cp); 456 } UICheckbox; 457 458 typedef struct UILabel { 459 UIElement e; 460 char *label; 461 ptrdiff_t labelBytes; 462 } UILabel; 463 464 typedef struct UISpacer { 465 #define UI_SPACER_LINE (1 << 0) 466 UIElement e; 467 int width, height; 468 } UISpacer; 469 470 typedef struct UISplitPane { 471 #define UI_SPLIT_PANE_VERTICAL (1 << 0) 472 UIElement e; 473 float weight; 474 } UISplitPane; 475 476 typedef struct UITabPane { 477 UIElement e; 478 char *tabs; 479 int active; 480 } UITabPane; 481 482 typedef struct UIScrollBar { 483 #define UI_SCROLL_BAR_HORIZONTAL (1 << 0) 484 UIElement e; 485 int64_t maximum, page; 486 int64_t dragOffset; 487 double position; 488 uint64_t lastAnimateTime; 489 bool inDrag, horizontal; 490 } UIScrollBar; 491 492 typedef struct UICodeLine { 493 int offset, bytes; 494 } UICodeLine; 495 496 typedef struct UICode { 497 #define UI_CODE_NO_MARGIN (1 << 0) 498 UIElement e; 499 UIScrollBar *vScroll; 500 UICodeLine *lines; 501 UIFont *font; 502 int lineCount, focused; 503 bool moveScrollToFocusNextLayout; 504 char *content; 505 size_t contentBytes; 506 int tabSize; 507 } UICode; 508 509 typedef struct UIGauge { 510 UIElement e; 511 float position; 512 } UIGauge; 513 514 typedef struct UITable { 515 UIElement e; 516 UIScrollBar *vScroll; 517 int itemCount; 518 char *columns; 519 int *columnWidths, columnCount, columnHighlight; 520 } UITable; 521 522 typedef struct UITextbox { 523 UIElement e; 524 char *string; 525 ptrdiff_t bytes; 526 int carets[2]; 527 int scroll; 528 bool rejectNextKey; 529 } UITextbox; 530 531 #define UI_MENU_PLACE_ABOVE (1 << 0) 532 #define UI_MENU_NO_SCROLL (1 << 1) 533 #ifdef UI_ESSENCE 534 typedef EsMenu UIMenu; 535 #else 536 typedef struct UIMenu { 537 UIElement e; 538 int pointX, pointY; 539 UIScrollBar *vScroll; 540 } UIMenu; 541 #endif 542 543 typedef struct UISlider { 544 UIElement e; 545 float position; 546 int steps; 547 } UISlider; 548 549 typedef struct UIColorPicker { 550 #define UI_COLOR_PICKER_HAS_OPACITY (1 << 0) 551 UIElement e; 552 float hue, saturation, value, opacity; 553 } UIColorPicker; 554 555 typedef struct UIMDIClient { 556 #define UI_MDI_CLIENT_TRANSPARENT (1 << 0) 557 UIElement e; 558 struct UIMDIChild *active; 559 int cascade; 560 } UIMDIClient; 561 562 typedef struct UIMDIChild { 563 #define UI_MDI_CHILD_CLOSE_BUTTON (1 << 0) 564 UIElement e; 565 UIRectangle bounds; 566 char *title; 567 ptrdiff_t titleBytes; 568 int dragHitTest; 569 UIRectangle dragOffset; 570 struct UIMDIChild *previous; 571 } UIMDIChild; 572 573 typedef struct UIExpandPane { 574 UIElement e; 575 UIButton *button; 576 UIPanel *panel; 577 bool expanded; 578 } UIExpandPane; 579 580 typedef struct UIImageDisplay { 581 #define UI_IMAGE_DISPLAY_INTERACTIVE (1 << 0) 582 #define _UI_IMAGE_DISPLAY_ZOOM_FIT (1 << 1) 583 584 UIElement e; 585 uint32_t *bits; 586 int width, height; 587 float panX, panY, zoom; 588 589 // Internals: 590 int previousWidth, previousHeight; 591 int previousPanPointX, previousPanPointY; 592 } UIImageDisplay; 593 594 typedef struct UIWrapPanel { 595 UIElement e; 596 } UIWrapPanel; 597 598 void UIInitialise(); 599 int UIMessageLoop(); 600 601 UIElement *UIElementCreate(size_t bytes, UIElement *parent, uint32_t flags, 602 int (*messageClass)(UIElement *, UIMessage, int, void *), const char *cClassName); 603 604 UIButton *UIButtonCreate(UIElement *parent, uint32_t flags, const char *label, ptrdiff_t labelBytes); 605 UICheckbox *UICheckboxCreate(UIElement *parent, uint32_t flags, const char *label, ptrdiff_t labelBytes); 606 UIColorPicker *UIColorPickerCreate(UIElement *parent, uint32_t flags); 607 UIExpandPane *UIExpandPaneCreate(UIElement *parent, uint32_t flags, const char *label, ptrdiff_t labelBytes, uint32_t panelFlags); 608 UIGauge *UIGaugeCreate(UIElement *parent, uint32_t flags); 609 UIMDIClient *UIMDIClientCreate(UIElement *parent, uint32_t flags); 610 UIMDIChild *UIMDIChildCreate(UIElement *parent, uint32_t flags, UIRectangle initialBounds, const char *title, ptrdiff_t titleBytes); 611 UIPanel *UIPanelCreate(UIElement *parent, uint32_t flags); 612 UIScrollBar *UIScrollBarCreate(UIElement *parent, uint32_t flags); 613 UISlider *UISliderCreate(UIElement *parent, uint32_t flags); 614 UISpacer *UISpacerCreate(UIElement *parent, uint32_t flags, int width, int height); 615 UISplitPane *UISplitPaneCreate(UIElement *parent, uint32_t flags, float weight); 616 UITabPane *UITabPaneCreate(UIElement *parent, uint32_t flags, const char *tabs /* separate with \t, terminate with \0 */); 617 UIWrapPanel *UIWrapPanelCreate(UIElement *parent, uint32_t flags); 618 619 UILabel *UILabelCreate(UIElement *parent, uint32_t flags, const char *label, ptrdiff_t labelBytes); 620 void UILabelSetContent(UILabel *code, const char *content, ptrdiff_t byteCount); 621 622 UIImageDisplay *UIImageDisplayCreate(UIElement *parent, uint32_t flags, uint32_t *bits, size_t width, size_t height, size_t stride); 623 void UIImageDisplaySetContent(UIImageDisplay *display, uint32_t *bits, size_t width, size_t height, size_t stride); 624 625 UIWindow *UIWindowCreate(UIWindow *owner, uint32_t flags, const char *cTitle, int width, int height); 626 void UIWindowRegisterShortcut(UIWindow *window, UIShortcut shortcut); 627 void UIWindowPostMessage(UIWindow *window, UIMessage message, void *dp); // Thread-safe. 628 void UIWindowPack(UIWindow *window, int width); // Change the size of the window to best match its contents. 629 630 typedef void (*UIDialogUserCallback)(UIElement *); 631 const char *UIDialogShow(UIWindow *window, uint32_t flags, const char *format, ...); 632 633 UIMenu *UIMenuCreate(UIElement *parent, uint32_t flags); 634 void UIMenuAddItem(UIMenu *menu, uint32_t flags, const char *label, ptrdiff_t labelBytes, void (*invoke)(void *cp), void *cp); 635 void UIMenuShow(UIMenu *menu); 636 637 UITextbox *UITextboxCreate(UIElement *parent, uint32_t flags); 638 void UITextboxReplace(UITextbox *textbox, const char *text, ptrdiff_t bytes, bool sendChangedMessage); 639 void UITextboxClear(UITextbox *textbox, bool sendChangedMessage); 640 void UITextboxMoveCaret(UITextbox *textbox, bool backward, bool word); 641 642 UITable *UITableCreate(UIElement *parent, uint32_t flags, const char *columns /* separate with \t, terminate with \0 */); 643 int UITableHitTest(UITable *table, int x, int y); // Returns item index. Returns -1 if not on an item. 644 int UITableHeaderHitTest(UITable *table, int x, int y); // Returns column index or -1. 645 bool UITableEnsureVisible(UITable *table, int index); // Returns false if the item was already visible. 646 void UITableResizeColumns(UITable *table); 647 648 UICode *UICodeCreate(UIElement *parent, uint32_t flags); 649 void UICodeFocusLine(UICode *code, int index); // Line numbers are 1-indexed!! 650 int UICodeHitTest(UICode *code, int x, int y); // Returns line number; negates if in margin. Returns 0 if not on a line. 651 void UICodeInsertContent(UICode *code, const char *content, ptrdiff_t byteCount, bool replace); 652 653 void UIDrawBlock(UIPainter *painter, UIRectangle rectangle, uint32_t color); 654 void UIDrawInvert(UIPainter *painter, UIRectangle rectangle); 655 bool UIDrawLine(UIPainter *painter, int x0, int y0, int x1, int y1, uint32_t color); // Returns false if the line was not visible. 656 void UIDrawTriangle(UIPainter *painter, int x0, int y0, int x1, int y1, int x2, int y2, uint32_t color); 657 void UIDrawTriangleOutline(UIPainter *painter, int x0, int y0, int x1, int y1, int x2, int y2, uint32_t color); 658 void UIDrawGlyph(UIPainter *painter, int x, int y, int c, uint32_t color); 659 void UIDrawRectangle(UIPainter *painter, UIRectangle r, uint32_t mainColor, uint32_t borderColor, UIRectangle borderSize); 660 void UIDrawBorder(UIPainter *painter, UIRectangle r, uint32_t borderColor, UIRectangle borderSize); 661 void UIDrawString(UIPainter *painter, UIRectangle r, const char *string, ptrdiff_t bytes, uint32_t color, int align, UIStringSelection *selection); 662 int UIDrawStringHighlighted(UIPainter *painter, UIRectangle r, const char *string, ptrdiff_t bytes, int tabSize); 663 664 int UIMeasureStringWidth(const char *string, ptrdiff_t bytes); 665 int UIMeasureStringHeight(); 666 667 uint64_t UIAnimateClock(); // In ms. 668 669 bool UIElementAnimate(UIElement *element, bool stop); 670 void UIElementDestroy(UIElement *element); 671 void UIElementDestroyDescendents(UIElement *element); 672 UIElement *UIElementFindByPoint(UIElement *element, int x, int y); 673 void UIElementFocus(UIElement *element); 674 UIRectangle UIElementScreenBounds(UIElement *element); // Returns bounds of element in same coordinate system as used by UIWindowCreate. 675 void UIElementRefresh(UIElement *element); 676 void UIElementRepaint(UIElement *element, UIRectangle *region); 677 void UIElementMove(UIElement *element, UIRectangle bounds, bool alwaysLayout); 678 int UIElementMessage(UIElement *element, UIMessage message, int di, void *dp); 679 void UIElementChangeParent(UIElement *element, UIElement *newParent, UIElement *insertBefore); // Set insertBefore to null to insert at the end. 680 681 UIElement *UIParentPush(UIElement *element); 682 UIElement *UIParentPop(); 683 684 UIRectangle UIRectangleIntersection(UIRectangle a, UIRectangle b); 685 UIRectangle UIRectangleBounding(UIRectangle a, UIRectangle b); 686 UIRectangle UIRectangleAdd(UIRectangle a, UIRectangle b); 687 UIRectangle UIRectangleTranslate(UIRectangle a, UIRectangle b); 688 bool UIRectangleEquals(UIRectangle a, UIRectangle b); 689 bool UIRectangleContains(UIRectangle a, int x, int y); 690 691 bool UIColorToHSV(uint32_t rgb, float *hue, float *saturation, float *value); 692 void UIColorToRGB(float hue, float saturation, float value, uint32_t *rgb); 693 694 char *UIStringCopy(const char *in, ptrdiff_t inBytes); 695 696 UIFont *UIFontCreate(const char *cPath, uint32_t size); 697 UIFont *UIFontActivate(UIFont *font); // Returns the previously active font. 698 699 #ifdef UI_DEBUG 700 void UIInspectorLog(const char *cFormat, ...); 701 #endif 702 703 #ifdef UI_IMPLEMENTATION 704 705 struct { 706 UIWindow *windows; 707 UIElement *animating; 708 UITheme theme; 709 710 UIElement *parentStack[16]; 711 int parentStackCount; 712 713 bool quit; 714 const char *dialogResult; 715 UIElement *dialogOldFocus; 716 717 UIFont *activeFont; 718 719 #ifdef UI_DEBUG 720 UIWindow *inspector; 721 UITable *inspectorTable; 722 UIWindow *inspectorTarget; 723 UICode *inspectorLog; 724 #endif 725 726 #ifdef UI_LINUX 727 Display *display; 728 Visual *visual; 729 XIM xim; 730 Atom windowClosedID, primaryID, uriListID, plainTextID; 731 Atom dndEnterID, dndPositionID, dndStatusID, dndActionCopyID, dndDropID, dndSelectionID, dndFinishedID, dndAwareID; 732 Atom clipboardID, xSelectionDataID, textID, targetID, incrID; 733 Cursor cursors[UI_CURSOR_COUNT]; 734 char *pasteText; 735 XEvent copyEvent; 736 #endif 737 738 #ifdef UI_WINDOWS 739 HCURSOR cursors[UI_CURSOR_COUNT]; 740 HANDLE heap; 741 bool assertionFailure; 742 #endif 743 744 #ifdef UI_ESSENCE 745 EsInstance *instance; 746 747 void *menuData[256]; // HACK This limits the number of menu items to 128. 748 uintptr_t menuIndex; 749 #endif 750 751 #ifdef UI_FREETYPE 752 FT_Library ft; 753 #endif 754 } ui; 755 756 UITheme _uiThemeClassic = { 757 .panel1 = 0xFFF0F0F0, 758 .panel2 = 0xFFFFFFFF, 759 .selected = 0xFF94BEFE, 760 .border = 0xFF404040, 761 762 .text = 0xFF000000, 763 .textDisabled = 0xFF404040, 764 .textSelected = 0xFF000000, 765 766 .buttonNormal = 0xFFE0E0E0, 767 .buttonHovered = 0xFFF0F0F0, 768 .buttonPressed = 0xFFA0A0A0, 769 .buttonDisabled = 0xFFF0F0F0, 770 771 .textboxNormal = 0xFFF8F8F8, 772 .textboxFocused = 0xFFFFFFFF, 773 774 .codeFocused = 0xFFE0E0E0, 775 .codeBackground = 0xFFFFFFFF, 776 .codeDefault = 0xFF000000, 777 .codeComment = 0xFFA11F20, 778 .codeString = 0xFF037E01, 779 .codeNumber = 0xFF213EF1, 780 .codeOperator = 0xFF7F0480, 781 .codePreprocessor = 0xFF545D70, 782 }; 783 784 UITheme _uiThemeDark = { 785 .panel1 = 0xFF252B31, 786 .panel2 = 0xFF14181E, 787 .selected = 0xFF94BEFE, 788 .border = 0xFF000000, 789 790 .text = 0xFFFFFFFF, 791 .textDisabled = 0xFF787D81, 792 .textSelected = 0xFF000000, 793 794 .buttonNormal = 0xFF383D41, 795 .buttonHovered = 0xFF4B5874, 796 .buttonPressed = 0xFF0D0D0F, 797 .buttonDisabled = 0xFF1B1F23, 798 799 .textboxNormal = 0xFF31353C, 800 .textboxFocused = 0xFF4D4D59, 801 802 .codeFocused = 0xFF505055, 803 .codeBackground = 0xFF212126, 804 .codeDefault = 0xFFFFFFFF, 805 .codeComment = 0xFFB4B4B4, 806 .codeString = 0xFFF5DDD1, 807 .codeNumber = 0xFFC3F5D3, 808 .codeOperator = 0xFFF5D499, 809 .codePreprocessor = 0xFFF5F3D1, 810 }; 811 812 // Taken from https://commons.wikimedia.org/wiki/File:Codepage-437.png 813 // Public domain. 814 815 const uint64_t _uiFont[] = { 816 0x0000000000000000UL, 0x0000000000000000UL, 0xBD8181A5817E0000UL, 0x000000007E818199UL, 0xC3FFFFDBFF7E0000UL, 0x000000007EFFFFE7UL, 0x7F7F7F3600000000UL, 0x00000000081C3E7FUL, 817 0x7F3E1C0800000000UL, 0x0000000000081C3EUL, 0xE7E73C3C18000000UL, 0x000000003C1818E7UL, 0xFFFF7E3C18000000UL, 0x000000003C18187EUL, 0x3C18000000000000UL, 0x000000000000183CUL, 818 0xC3E7FFFFFFFFFFFFUL, 0xFFFFFFFFFFFFE7C3UL, 0x42663C0000000000UL, 0x00000000003C6642UL, 0xBD99C3FFFFFFFFFFUL, 0xFFFFFFFFFFC399BDUL, 0x331E4C5870780000UL, 0x000000001E333333UL, 819 0x3C666666663C0000UL, 0x0000000018187E18UL, 0x0C0C0CFCCCFC0000UL, 0x00000000070F0E0CUL, 0xC6C6C6FEC6FE0000UL, 0x0000000367E7E6C6UL, 0xE73CDB1818000000UL, 0x000000001818DB3CUL, 820 0x1F7F1F0F07030100UL, 0x000000000103070FUL, 0x7C7F7C7870604000UL, 0x0000000040607078UL, 0x1818187E3C180000UL, 0x0000000000183C7EUL, 0x6666666666660000UL, 0x0000000066660066UL, 821 0xD8DEDBDBDBFE0000UL, 0x00000000D8D8D8D8UL, 0x6363361C06633E00UL, 0x0000003E63301C36UL, 0x0000000000000000UL, 0x000000007F7F7F7FUL, 0x1818187E3C180000UL, 0x000000007E183C7EUL, 822 0x1818187E3C180000UL, 0x0000000018181818UL, 0x1818181818180000UL, 0x00000000183C7E18UL, 0x7F30180000000000UL, 0x0000000000001830UL, 0x7F060C0000000000UL, 0x0000000000000C06UL, 823 0x0303000000000000UL, 0x0000000000007F03UL, 0xFF66240000000000UL, 0x0000000000002466UL, 0x3E1C1C0800000000UL, 0x00000000007F7F3EUL, 0x3E3E7F7F00000000UL, 0x0000000000081C1CUL, 824 0x0000000000000000UL, 0x0000000000000000UL, 0x18183C3C3C180000UL, 0x0000000018180018UL, 0x0000002466666600UL, 0x0000000000000000UL, 0x36367F3636000000UL, 0x0000000036367F36UL, 825 0x603E0343633E1818UL, 0x000018183E636160UL, 0x1830634300000000UL, 0x000000006163060CUL, 0x3B6E1C36361C0000UL, 0x000000006E333333UL, 0x000000060C0C0C00UL, 0x0000000000000000UL, 826 0x0C0C0C0C18300000UL, 0x0000000030180C0CUL, 0x30303030180C0000UL, 0x000000000C183030UL, 0xFF3C660000000000UL, 0x000000000000663CUL, 0x7E18180000000000UL, 0x0000000000001818UL, 827 0x0000000000000000UL, 0x0000000C18181800UL, 0x7F00000000000000UL, 0x0000000000000000UL, 0x0000000000000000UL, 0x0000000018180000UL, 0x1830604000000000UL, 0x000000000103060CUL, 828 0xDBDBC3C3663C0000UL, 0x000000003C66C3C3UL, 0x1818181E1C180000UL, 0x000000007E181818UL, 0x0C183060633E0000UL, 0x000000007F630306UL, 0x603C6060633E0000UL, 0x000000003E636060UL, 829 0x7F33363C38300000UL, 0x0000000078303030UL, 0x603F0303037F0000UL, 0x000000003E636060UL, 0x633F0303061C0000UL, 0x000000003E636363UL, 0x18306060637F0000UL, 0x000000000C0C0C0CUL, 830 0x633E6363633E0000UL, 0x000000003E636363UL, 0x607E6363633E0000UL, 0x000000001E306060UL, 0x0000181800000000UL, 0x0000000000181800UL, 0x0000181800000000UL, 0x000000000C181800UL, 831 0x060C183060000000UL, 0x000000006030180CUL, 0x00007E0000000000UL, 0x000000000000007EUL, 0x6030180C06000000UL, 0x00000000060C1830UL, 0x18183063633E0000UL, 0x0000000018180018UL, 832 0x7B7B63633E000000UL, 0x000000003E033B7BUL, 0x7F6363361C080000UL, 0x0000000063636363UL, 0x663E6666663F0000UL, 0x000000003F666666UL, 0x03030343663C0000UL, 0x000000003C664303UL, 833 0x66666666361F0000UL, 0x000000001F366666UL, 0x161E1646667F0000UL, 0x000000007F664606UL, 0x161E1646667F0000UL, 0x000000000F060606UL, 0x7B030343663C0000UL, 0x000000005C666363UL, 834 0x637F636363630000UL, 0x0000000063636363UL, 0x18181818183C0000UL, 0x000000003C181818UL, 0x3030303030780000UL, 0x000000001E333333UL, 0x1E1E366666670000UL, 0x0000000067666636UL, 835 0x06060606060F0000UL, 0x000000007F664606UL, 0xC3DBFFFFE7C30000UL, 0x00000000C3C3C3C3UL, 0x737B7F6F67630000UL, 0x0000000063636363UL, 0x63636363633E0000UL, 0x000000003E636363UL, 836 0x063E6666663F0000UL, 0x000000000F060606UL, 0x63636363633E0000UL, 0x000070303E7B6B63UL, 0x363E6666663F0000UL, 0x0000000067666666UL, 0x301C0663633E0000UL, 0x000000003E636360UL, 837 0x18181899DBFF0000UL, 0x000000003C181818UL, 0x6363636363630000UL, 0x000000003E636363UL, 0xC3C3C3C3C3C30000UL, 0x00000000183C66C3UL, 0xDBC3C3C3C3C30000UL, 0x000000006666FFDBUL, 838 0x18183C66C3C30000UL, 0x00000000C3C3663CUL, 0x183C66C3C3C30000UL, 0x000000003C181818UL, 0x0C183061C3FF0000UL, 0x00000000FFC38306UL, 0x0C0C0C0C0C3C0000UL, 0x000000003C0C0C0CUL, 839 0x1C0E070301000000UL, 0x0000000040607038UL, 0x30303030303C0000UL, 0x000000003C303030UL, 0x0000000063361C08UL, 0x0000000000000000UL, 0x0000000000000000UL, 0x0000FF0000000000UL, 840 0x0000000000180C0CUL, 0x0000000000000000UL, 0x3E301E0000000000UL, 0x000000006E333333UL, 0x66361E0606070000UL, 0x000000003E666666UL, 0x03633E0000000000UL, 0x000000003E630303UL, 841 0x33363C3030380000UL, 0x000000006E333333UL, 0x7F633E0000000000UL, 0x000000003E630303UL, 0x060F0626361C0000UL, 0x000000000F060606UL, 0x33336E0000000000UL, 0x001E33303E333333UL, 842 0x666E360606070000UL, 0x0000000067666666UL, 0x18181C0018180000UL, 0x000000003C181818UL, 0x6060700060600000UL, 0x003C666660606060UL, 0x1E36660606070000UL, 0x000000006766361EUL, 843 0x18181818181C0000UL, 0x000000003C181818UL, 0xDBFF670000000000UL, 0x00000000DBDBDBDBUL, 0x66663B0000000000UL, 0x0000000066666666UL, 0x63633E0000000000UL, 0x000000003E636363UL, 844 0x66663B0000000000UL, 0x000F06063E666666UL, 0x33336E0000000000UL, 0x007830303E333333UL, 0x666E3B0000000000UL, 0x000000000F060606UL, 0x06633E0000000000UL, 0x000000003E63301CUL, 845 0x0C0C3F0C0C080000UL, 0x00000000386C0C0CUL, 0x3333330000000000UL, 0x000000006E333333UL, 0xC3C3C30000000000UL, 0x00000000183C66C3UL, 0xC3C3C30000000000UL, 0x0000000066FFDBDBUL, 846 0x3C66C30000000000UL, 0x00000000C3663C18UL, 0x6363630000000000UL, 0x001F30607E636363UL, 0x18337F0000000000UL, 0x000000007F63060CUL, 0x180E181818700000UL, 0x0000000070181818UL, 847 0x1800181818180000UL, 0x0000000018181818UL, 0x18701818180E0000UL, 0x000000000E181818UL, 0x000000003B6E0000UL, 0x0000000000000000UL, 0x63361C0800000000UL, 0x00000000007F6363UL, 848 }; 849 850 void _UIWindowEndPaint(UIWindow *window, UIPainter *painter); 851 void _UIWindowSetCursor(UIWindow *window, int cursor); 852 void _UIWindowGetScreenPosition(UIWindow *window, int *x, int *y); 853 void _UIWindowSetPressed(UIWindow *window, UIElement *element, int button); 854 void _UIClipboardWriteText(UIWindow *window, char *text); 855 char *_UIClipboardReadTextStart(UIWindow *window, size_t *bytes); 856 void _UIClipboardReadTextEnd(UIWindow *window, char *text); 857 bool _UIMessageLoopSingle(int *result); 858 void _UIInspectorRefresh(); 859 void _UIUpdate(); 860 861 #ifdef UI_WINDOWS 862 void *_UIHeapReAlloc(void *pointer, size_t size); 863 #endif 864 865 UIRectangle UIRectangleIntersection(UIRectangle a, UIRectangle b) { 866 if (a.l < b.l) a.l = b.l; 867 if (a.t < b.t) a.t = b.t; 868 if (a.r > b.r) a.r = b.r; 869 if (a.b > b.b) a.b = b.b; 870 return a; 871 } 872 873 UIRectangle UIRectangleBounding(UIRectangle a, UIRectangle b) { 874 if (a.l > b.l) a.l = b.l; 875 if (a.t > b.t) a.t = b.t; 876 if (a.r < b.r) a.r = b.r; 877 if (a.b < b.b) a.b = b.b; 878 return a; 879 } 880 881 UIRectangle UIRectangleAdd(UIRectangle a, UIRectangle b) { 882 a.l += b.l; 883 a.t += b.t; 884 a.r += b.r; 885 a.b += b.b; 886 return a; 887 } 888 889 UIRectangle UIRectangleTranslate(UIRectangle a, UIRectangle b) { 890 a.l += b.l; 891 a.t += b.t; 892 a.r += b.l; 893 a.b += b.t; 894 return a; 895 } 896 897 bool UIRectangleEquals(UIRectangle a, UIRectangle b) { 898 return a.l == b.l && a.r == b.r && a.t == b.t && a.b == b.b; 899 } 900 901 bool UIRectangleContains(UIRectangle a, int x, int y) { 902 return a.l <= x && a.r > x && a.t <= y && a.b > y; 903 } 904 905 #if defined(__x86_64__) || defined(_M_X64) 906 #include <xmmintrin.h> 907 #endif 908 909 typedef union _UIConvertFloatInteger { 910 float f; 911 uint32_t i; 912 } _UIConvertFloatInteger; 913 914 float _UIFloorFloat(float x) { 915 _UIConvertFloatInteger convert = {x}; 916 uint32_t sign = convert.i & 0x80000000; 917 int exponent = (int) ((convert.i >> 23) & 0xFF) - 0x7F; 918 919 if (exponent >= 23) { 920 // There aren't any bits representing a fractional part. 921 } else if (exponent >= 0) { 922 // Positive exponent. 923 uint32_t mask = 0x7FFFFF >> exponent; 924 if (!(mask & convert.i)) return x; // Already an integer. 925 if (sign) convert.i += mask; 926 convert.i &= ~mask; // Mask out the fractional bits. 927 } else if (exponent < 0) { 928 // Negative exponent. 929 return sign ? -1.0 : 0.0; 930 } 931 932 return convert.f; 933 } 934 935 float _UISquareRootFloat(float x) { 936 #if defined(__x86_64__) || defined(_M_X64) 937 float result[4]; 938 _mm_storeu_ps(result, _mm_sqrt_ps(_mm_set_ps(0, 0, 0, x))); 939 return result[0]; 940 #else 941 //@todo fsqrt.s for risc-v? 942 //clang already does that with this and -ffast-math 943 return sqrtf(x); 944 #endif 945 } 946 947 #define _F(x) (((_UIConvertFloatInteger) { .i = (x) }).f) 948 949 float _UIArcTanFloatI(float x) { 950 float x2 = x * x; 951 return x * (_F(0x3F7FFFF8) + x2 * (_F(0xBEAAA53C) + x2 * (_F(0x3E4BC990) + x2 * (_F(0xBE084A60) + x2 * _F(0x3D8864B0))))); 952 } 953 954 float _UISinFloatI(float x) { 955 float x2 = x * x; 956 return x * (_F(0x3F800000) + x2 * (_F(0xBE2AAAA0) + x2 * (_F(0x3C0882C0) + x2 * _F(0xB94C6000)))); 957 } 958 959 float _UICosFloatI(float x) { 960 float x2 = x * x; 961 return _F(0x3F800000) + x2 * (_F(0xBEFFFFDA) + x2 * (_F(0x3D2A9F60) + x2 * _F(0xBAB22C00))); 962 } 963 964 #undef _F 965 966 float _UISinFloat(float x) { 967 bool negate = false; 968 if (x < 0) { x = -x; negate = true; } 969 x -= 2 * 3.141592654f * _UIFloorFloat(x / (2 * 3.141592654f)); 970 if (x < 3.141592654f / 2) {} 971 else if (x < 3.141592654f) { x = 3.141592654f - x; } 972 else if (x < 3 * 3.141592654f / 2) { x = x - 3.141592654f; negate = !negate; } 973 else { x = 3.141592654f * 2 - x; negate = !negate; } 974 float y = x < 3.141592654f / 4 ? _UISinFloatI(x) : _UICosFloatI(3.141592654f / 2 - x); 975 return negate ? -y : y; 976 } 977 978 float _UICosFloat(float x) { 979 return _UISinFloat(3.141592654f / 2 - x); 980 } 981 982 float _UIArcTanFloat(float x) { 983 bool negate = false, reciprocalTaken = false; 984 if (x < 0) { x = -x; negate = true; } 985 if (x > 1) { x = 1 / x; reciprocalTaken = true; } 986 float y = x < 0.5f ? _UIArcTanFloatI(x) : (0.463647609f + _UIArcTanFloatI((2 * x - 1) / (2 + x))); 987 if (reciprocalTaken) { y = 3.141592654f / 2 - y; } 988 return negate ? -y : y; 989 } 990 991 float _UIArcTan2Float(float y, float x) { 992 if (x == 0) return y > 0 ? 3.141592654f / 2 : -3.141592654f / 2; 993 else if (x > 0) return _UIArcTanFloat(y / x); 994 else if (y >= 0) return 3.141592654f + _UIArcTanFloat(y / x); 995 else return -3.141592654f + _UIArcTanFloat(y / x); 996 } 997 998 float _UILinearMap(float value, float inFrom, float inTo, float outFrom, float outTo) { 999 float inRange = inTo - inFrom, outRange = outTo - outFrom; 1000 float normalisedValue = (value - inFrom) / inRange; 1001 return normalisedValue * outRange + outFrom; 1002 } 1003 1004 bool UIColorToHSV(uint32_t rgb, float *hue, float *saturation, float *value) { 1005 float r = UI_COLOR_RED_F(rgb); 1006 float g = UI_COLOR_GREEN_F(rgb); 1007 float b = UI_COLOR_BLUE_F(rgb); 1008 1009 float maximum = (r > g && r > b) ? r : (g > b ? g : b), 1010 minimum = (r < g && r < b) ? r : (g < b ? g : b), 1011 difference = maximum - minimum; 1012 *value = maximum; 1013 1014 if (!difference) { 1015 *saturation = 0; 1016 return false; 1017 } else { 1018 if (r == maximum) *hue = (g - b) / difference + 0; 1019 if (g == maximum) *hue = (b - r) / difference + 2; 1020 if (b == maximum) *hue = (r - g) / difference + 4; 1021 if (*hue < 0) *hue += 6; 1022 *saturation = difference / maximum; 1023 return true; 1024 } 1025 } 1026 1027 void UIColorToRGB(float h, float s, float v, uint32_t *rgb) { 1028 float r, g, b; 1029 1030 if (!s) { 1031 r = g = b = v; 1032 } else { 1033 int h0 = ((int) h) % 6; 1034 float f = h - _UIFloorFloat(h); 1035 float x = v * (1 - s), y = v * (1 - s * f), z = v * (1 - s * (1 - f)); 1036 1037 switch (h0) { 1038 case 0: r = v, g = z, b = x; break; 1039 case 1: r = y, g = v, b = x; break; 1040 case 2: r = x, g = v, b = z; break; 1041 case 3: r = x, g = y, b = v; break; 1042 case 4: r = z, g = x, b = v; break; 1043 default: r = v, g = x, b = y; break; 1044 } 1045 } 1046 1047 *rgb = UI_COLOR_FROM_FLOAT(r, g, b); 1048 } 1049 1050 void UIElementRefresh(UIElement *element) { 1051 UIElementMessage(element, UI_MSG_LAYOUT, 0, 0); 1052 UIElementRepaint(element, NULL); 1053 } 1054 1055 void UIElementRepaint(UIElement *element, UIRectangle *region) { 1056 if (!region) { 1057 region = &element->bounds; 1058 } 1059 1060 UIRectangle r = UIRectangleIntersection(*region, element->clip); 1061 1062 if (!UI_RECT_VALID(r)) { 1063 return; 1064 } 1065 1066 if (UI_RECT_VALID(element->window->updateRegion)) { 1067 element->window->updateRegion = UIRectangleBounding(element->window->updateRegion, r); 1068 } else { 1069 element->window->updateRegion = r; 1070 } 1071 } 1072 1073 bool UIElementAnimate(UIElement *element, bool stop) { 1074 if (stop) { 1075 if (ui.animating != element) { 1076 return false; 1077 } 1078 1079 ui.animating = NULL; 1080 } else { 1081 if (ui.animating && ui.animating != element) { 1082 return false; 1083 } 1084 1085 ui.animating = element; 1086 } 1087 1088 return true; 1089 } 1090 1091 uint64_t UIAnimateClock() { 1092 return (uint64_t) UI_CLOCK() * 1000 / UI_CLOCKS_PER_SECOND; 1093 } 1094 1095 void _UIElementDestroyDescendents(UIElement *element, bool topLevel) { 1096 UIElement *child = element->children; 1097 1098 while (child) { 1099 if (!topLevel || (~child->flags & UI_ELEMENT_NON_CLIENT)) { 1100 UIElementDestroy(child); 1101 } 1102 1103 child = child->next; 1104 } 1105 1106 #ifdef UI_DEBUG 1107 _UIInspectorRefresh(); 1108 #endif 1109 } 1110 1111 void UIElementDestroyDescendents(UIElement *element) { 1112 _UIElementDestroyDescendents(element, true); 1113 } 1114 1115 void UIElementDestroy(UIElement *element) { 1116 if (element->flags & UI_ELEMENT_DESTROY) { 1117 return; 1118 } 1119 1120 element->flags |= UI_ELEMENT_DESTROY | UI_ELEMENT_HIDE; 1121 1122 UIElement *ancestor = element->parent; 1123 1124 while (ancestor) { 1125 ancestor->flags |= UI_ELEMENT_DESTROY_DESCENDENT; 1126 ancestor = ancestor->parent; 1127 } 1128 1129 _UIElementDestroyDescendents(element, false); 1130 } 1131 1132 void UIDrawBlock(UIPainter *painter, UIRectangle rectangle, uint32_t color) { 1133 rectangle = UIRectangleIntersection(painter->clip, rectangle); 1134 1135 if (!UI_RECT_VALID(rectangle)) { 1136 return; 1137 } 1138 1139 #ifdef UI_SSE2 1140 __m128i color4 = _mm_set_epi32(color, color, color, color); 1141 #endif 1142 1143 for (int line = rectangle.t; line < rectangle.b; line++) { 1144 uint32_t *bits = painter->bits + line * painter->width + rectangle.l; 1145 int count = UI_RECT_WIDTH(rectangle); 1146 1147 #ifdef UI_SSE2 1148 while (count >= 4) { 1149 _mm_storeu_si128((__m128i *) bits, color4); 1150 bits += 4; 1151 count -= 4; 1152 } 1153 #endif 1154 1155 while (count--) { 1156 *bits++ = color; 1157 } 1158 } 1159 1160 #ifdef UI_DEBUG 1161 painter->fillCount += UI_RECT_WIDTH(rectangle) * UI_RECT_HEIGHT(rectangle); 1162 #endif 1163 } 1164 1165 bool UIDrawLine(UIPainter *painter, int x0, int y0, int x1, int y1, uint32_t color) { 1166 // Apply the clip. 1167 1168 UIRectangle c = painter->clip; 1169 if (!UI_RECT_VALID(c)) return false; 1170 int dx = x1 - x0, dy = y1 - y0; 1171 const int p[4] = { -dx, dx, -dy, dy }; 1172 const int q[4] = { x0 - c.l, c.r - 1 - x0, y0 - c.t, c.b - 1 - y0 }; 1173 float t0 = 0.0f, t1 = 1.0f; // How far along the line the points end up. 1174 1175 for (int i = 0; i < 4; i++) { 1176 if (!p[i] && q[i] < 0) return false; 1177 float r = (float) q[i] / p[i]; 1178 if (p[i] < 0 && r > t1) return false; 1179 if (p[i] > 0 && r < t0) return false; 1180 if (p[i] < 0 && r > t0) t0 = r; 1181 if (p[i] > 0 && r < t1) t1 = r; 1182 } 1183 1184 x1 = x0 + t1 * dx, y1 = y0 + t1 * dy; 1185 x0 += t0 * dx, y0 += t0 * dy; 1186 1187 // Calculate the delta X and delta Y. 1188 1189 if (y1 < y0) { 1190 int t; 1191 t = x0, x0 = x1, x1 = t; 1192 t = y0, y0 = y1, y1 = t; 1193 } 1194 1195 dx = x1 - x0, dy = y1 - y0; 1196 int dxs = dx < 0 ? -1 : 1; 1197 if (dx < 0) dx = -dx; 1198 1199 // Draw the line using Bresenham's line algorithm. 1200 1201 uint32_t *bits = painter->bits + y0 * painter->width + x0; 1202 1203 if (dy * dy < dx * dx) { 1204 int m = 2 * dy - dx; 1205 1206 for (int i = 0; i < dx; i++, bits += dxs) { 1207 *bits = color; 1208 if (m > 0) bits += painter->width, m -= 2 * dx; 1209 m += 2 * dy; 1210 } 1211 } else { 1212 int m = 2 * dx - dy; 1213 1214 for (int i = 0; i < dy; i++, bits += painter->width) { 1215 *bits = color; 1216 if (m > 0) bits += dxs, m -= 2 * dy; 1217 m += 2 * dx; 1218 } 1219 } 1220 1221 return true; 1222 } 1223 1224 void UIDrawTriangle(UIPainter *painter, int x0, int y0, int x1, int y1, int x2, int y2, uint32_t color) { 1225 // Step 1: Sort the points by their y-coordinate. 1226 if (y1 < y0) { int xt = x0; x0 = x1, x1 = xt; int yt = y0; y0 = y1, y1 = yt; } 1227 if (y2 < y1) { int xt = x1; x1 = x2, x2 = xt; int yt = y1; y1 = y2, y2 = yt; } 1228 if (y1 < y0) { int xt = x0; x0 = x1, x1 = xt; int yt = y0; y0 = y1, y1 = yt; } 1229 if (y2 == y0) return; 1230 1231 // Step 2: Clip the triangle. 1232 if (x0 < painter->clip.l && x1 < painter->clip.l && x2 < painter->clip.l) return; 1233 if (x0 >= painter->clip.r && x1 >= painter->clip.r && x2 >= painter->clip.r) return; 1234 if (y2 < painter->clip.t || y0 >= painter->clip.b) return; 1235 bool needsXClip = x0 < painter->clip.l + 1 || x0 >= painter->clip.r - 1 1236 || x1 < painter->clip.l + 1 || x1 >= painter->clip.r - 1 1237 || x2 < painter->clip.l + 1 || x2 >= painter->clip.r - 1; 1238 bool needsYClip = y0 < painter->clip.t + 1 || y2 >= painter->clip.b - 1; 1239 #define _UI_DRAW_TRIANGLE_APPLY_CLIP(xo, yo) \ 1240 if (needsYClip && (yi + yo < painter->clip.t || yi + yo >= painter->clip.b)) continue; \ 1241 if (needsXClip && xf + xo < painter->clip.l) xf = painter->clip.l - xo; \ 1242 if (needsXClip && xt + xo > painter->clip.r) xt = painter->clip.r - xo; 1243 1244 // Step 3: Split into 2 triangles with bases aligned with the x-axis. 1245 float xm0 = (x2 - x0) * (y1 - y0) / (y2 - y0), xm1 = x1 - x0; 1246 if (xm1 < xm0) { float xmt = xm0; xm0 = xm1, xm1 = xmt; } 1247 float xe0 = xm0 + x0 - x2, xe1 = xm1 + x0 - x2; 1248 int ym = y1 - y0, ye = y2 - y1; 1249 float ymr = 1.0f / ym, yer = 1.0f / ye; 1250 1251 // Step 4: Draw the top part. 1252 for (float y = 0; y < ym; y++) { 1253 int xf = xm0 * y * ymr, xt = xm1 * y * ymr, yi = (int) y; 1254 _UI_DRAW_TRIANGLE_APPLY_CLIP(x0, y0); 1255 uint32_t *b = &painter->bits[(yi + y0) * painter->width + x0]; 1256 for (int x = xf; x < xt; x++) b[x] = color; 1257 } 1258 1259 // Step 5: Draw the bottom part. 1260 for (float y = 0; y < ye; y++) { 1261 int xf = xe0 * (ye - y) * yer, xt = xe1 * (ye - y) * yer, yi = (int) y; 1262 _UI_DRAW_TRIANGLE_APPLY_CLIP(x2, y1); 1263 uint32_t *b = &painter->bits[(yi + y1) * painter->width + x2]; 1264 for (int x = xf; x < xt; x++) b[x] = color; 1265 } 1266 } 1267 1268 void UIDrawTriangleOutline(UIPainter *painter, int x0, int y0, int x1, int y1, int x2, int y2, uint32_t color) { 1269 UIDrawLine(painter, x0, y0, x1, y1, color); 1270 UIDrawLine(painter, x1, y1, x2, y2, color); 1271 UIDrawLine(painter, x2, y2, x0, y0, color); 1272 } 1273 1274 void UIDrawInvert(UIPainter *painter, UIRectangle rectangle) { 1275 rectangle = UIRectangleIntersection(painter->clip, rectangle); 1276 1277 if (!UI_RECT_VALID(rectangle)) { 1278 return; 1279 } 1280 1281 for (int line = rectangle.t; line < rectangle.b; line++) { 1282 uint32_t *bits = painter->bits + line * painter->width + rectangle.l; 1283 int count = UI_RECT_WIDTH(rectangle); 1284 1285 while (count--) { 1286 uint32_t in = *bits; 1287 *bits = in ^ 0xFFFFFF; 1288 bits++; 1289 } 1290 } 1291 } 1292 1293 void UIDrawGlyph(UIPainter *painter, int x0, int y0, int c, uint32_t color) { 1294 #ifdef UI_FREETYPE 1295 UIFont *font = ui.activeFont; 1296 1297 if (font->isFreeType) { 1298 if (c < 0 || c > 127) c = '?'; 1299 if (c == '\r') c = ' '; 1300 1301 if (!font->glyphsRendered[c]) { 1302 FT_Load_Char(font->font, c == 24 ? 0x2191 : c == 25 ? 0x2193 : c == 26 ? 0x2192 : c == 27 ? 0x2190 : c, FT_LOAD_DEFAULT); 1303 #ifdef UI_FREETYPE_SUBPIXEL 1304 FT_Render_Glyph(font->font->glyph, FT_RENDER_MODE_LCD); 1305 #else 1306 FT_Render_Glyph(font->font->glyph, FT_RENDER_MODE_NORMAL); 1307 #endif 1308 FT_Bitmap_Copy(ui.ft, &font->font->glyph->bitmap, &font->glyphs[c]); 1309 font->glyphOffsetsX[c] = font->font->glyph->bitmap_left; 1310 font->glyphOffsetsY[c] = font->font->size->metrics.ascender / 64 - font->font->glyph->bitmap_top; 1311 font->glyphsRendered[c] = true; 1312 } 1313 1314 FT_Bitmap *bitmap = &font->glyphs[c]; 1315 x0 += font->glyphOffsetsX[c], y0 += font->glyphOffsetsY[c]; 1316 1317 for (int y = 0; y < (int) bitmap->rows; y++) { 1318 if (y0 + y < painter->clip.t) continue; 1319 if (y0 + y >= painter->clip.b) break; 1320 1321 int width = bitmap->width; 1322 #ifdef UI_FREETYPE_SUBPIXEL 1323 width /= 3; 1324 #endif 1325 1326 for (int x = 0; x < width; x++) { 1327 if (x0 + x < painter->clip.l) continue; 1328 if (x0 + x >= painter->clip.r) break; 1329 1330 uint32_t *destination = painter->bits + (x0 + x) + (y0 + y) * painter->width; 1331 uint32_t original = *destination; 1332 1333 #ifdef UI_FREETYPE_SUBPIXEL 1334 uint32_t ra = ((uint8_t *) bitmap->buffer)[x * 3 + y * bitmap->pitch + 0]; 1335 uint32_t ga = ((uint8_t *) bitmap->buffer)[x * 3 + y * bitmap->pitch + 1]; 1336 uint32_t ba = ((uint8_t *) bitmap->buffer)[x * 3 + y * bitmap->pitch + 2]; 1337 ra += (ga - ra) / 2, ba += (ga - ba) / 2; 1338 #else 1339 uint32_t ra = ((uint8_t *) bitmap->buffer)[x + y * bitmap->pitch]; 1340 uint32_t ga = ra, ba = ra; 1341 #endif 1342 uint32_t r2 = (255 - ra) * ((original & 0x000000FF) >> 0); 1343 uint32_t g2 = (255 - ga) * ((original & 0x0000FF00) >> 8); 1344 uint32_t b2 = (255 - ba) * ((original & 0x00FF0000) >> 16); 1345 uint32_t r1 = ra * ((color & 0x000000FF) >> 0); 1346 uint32_t g1 = ga * ((color & 0x0000FF00) >> 8); 1347 uint32_t b1 = ba * ((color & 0x00FF0000) >> 16); 1348 1349 uint32_t result = 0xFF000000 | (0x00FF0000 & ((b1 + b2) << 8)) 1350 | (0x0000FF00 & ((g1 + g2) << 0)) 1351 | (0x000000FF & ((r1 + r2) >> 8)); 1352 *destination = result; 1353 } 1354 } 1355 1356 return; 1357 } 1358 #endif 1359 1360 if (c < 0 || c > 127) c = '?'; 1361 1362 UIRectangle rectangle = UIRectangleIntersection(painter->clip, UI_RECT_4(x0, x0 + 8, y0, y0 + 16)); 1363 1364 const uint8_t *data = (const uint8_t *) _uiFont + c * 16; 1365 1366 for (int i = rectangle.t; i < rectangle.b; i++) { 1367 uint32_t *bits = painter->bits + i * painter->width + rectangle.l; 1368 uint8_t byte = data[i - y0]; 1369 1370 for (int j = rectangle.l; j < rectangle.r; j++) { 1371 if (byte & (1 << (j - x0))) { 1372 *bits = color; 1373 } 1374 1375 bits++; 1376 } 1377 } 1378 } 1379 1380 ptrdiff_t _UIStringLength(const char *cString) { 1381 if (!cString) return 0; 1382 ptrdiff_t length; 1383 for (length = 0; cString[length]; length++); 1384 return length; 1385 } 1386 1387 char *UIStringCopy(const char *in, ptrdiff_t inBytes) { 1388 if (inBytes == -1) { 1389 inBytes = _UIStringLength(in); 1390 } 1391 1392 char *buffer = (char *) UI_MALLOC(inBytes + 1); 1393 1394 for (intptr_t i = 0; i < inBytes; i++) { 1395 buffer[i] = in[i]; 1396 } 1397 1398 buffer[inBytes] = 0; 1399 return buffer; 1400 } 1401 1402 int UIMeasureStringWidth(const char *string, ptrdiff_t bytes) { 1403 if (bytes == -1) { 1404 bytes = _UIStringLength(string); 1405 } 1406 1407 return bytes * ui.activeFont->glyphWidth; 1408 } 1409 1410 int UIMeasureStringHeight() { 1411 return ui.activeFont->glyphHeight; 1412 } 1413 1414 void UIDrawString(UIPainter *painter, UIRectangle r, const char *string, ptrdiff_t bytes, uint32_t color, int align, UIStringSelection *selection) { 1415 UIRectangle oldClip = painter->clip; 1416 painter->clip = UIRectangleIntersection(r, oldClip); 1417 1418 if (!UI_RECT_VALID(painter->clip)) { 1419 painter->clip = oldClip; 1420 return; 1421 } 1422 1423 if (bytes == -1) { 1424 bytes = _UIStringLength(string); 1425 } 1426 1427 int width = UIMeasureStringWidth(string, bytes); 1428 int height = UIMeasureStringHeight(); 1429 int x = align == UI_ALIGN_CENTER ? ((r.l + r.r - width) / 2) : align == UI_ALIGN_RIGHT ? (r.r - width) : r.l; 1430 int y = (r.t + r.b - height) / 2; 1431 int i = 0, j = 0; 1432 1433 int selectFrom = -1, selectTo = -1; 1434 1435 if (selection) { 1436 selectFrom = selection->carets[0]; 1437 selectTo = selection->carets[1]; 1438 1439 if (selectFrom > selectTo) { 1440 UI_SWAP(int, selectFrom, selectTo); 1441 } 1442 } 1443 1444 for (; j < bytes; j++) { 1445 char c = *string++; 1446 uint32_t colorText = color; 1447 1448 if (j >= selectFrom && j < selectTo) { 1449 UIDrawBlock(painter, UI_RECT_4(x, x + ui.activeFont->glyphWidth, y, y + height), selection->colorBackground); 1450 colorText = selection->colorText; 1451 } 1452 1453 if (c != '\t') { 1454 UIDrawGlyph(painter, x, y, c, colorText); 1455 } 1456 1457 if (selection && selection->carets[0] == j) { 1458 UIDrawInvert(painter, UI_RECT_4(x, x + 1, y, y + height)); 1459 } 1460 1461 x += ui.activeFont->glyphWidth, i++; 1462 1463 if (c == '\t') { 1464 while (i & 3) x += ui.activeFont->glyphWidth, i++; 1465 } 1466 } 1467 1468 if (selection && selection->carets[0] == j) { 1469 UIDrawInvert(painter, UI_RECT_4(x, x + 1, y, y + height)); 1470 } 1471 1472 painter->clip = oldClip; 1473 } 1474 1475 void UIDrawBorder(UIPainter *painter, UIRectangle r, uint32_t borderColor, UIRectangle borderSize) { 1476 UIDrawBlock(painter, UI_RECT_4(r.l, r.r, r.t, r.t + borderSize.t), borderColor); 1477 UIDrawBlock(painter, UI_RECT_4(r.l, r.l + borderSize.l, r.t + borderSize.t, r.b - borderSize.b), borderColor); 1478 UIDrawBlock(painter, UI_RECT_4(r.r - borderSize.r, r.r, r.t + borderSize.t, r.b - borderSize.b), borderColor); 1479 UIDrawBlock(painter, UI_RECT_4(r.l, r.r, r.b - borderSize.b, r.b), borderColor); 1480 } 1481 1482 void UIDrawRectangle(UIPainter *painter, UIRectangle r, uint32_t mainColor, uint32_t borderColor, UIRectangle borderSize) { 1483 UIDrawBorder(painter, r, borderColor, borderSize); 1484 UIDrawBlock(painter, UI_RECT_4(r.l + borderSize.l, r.r - borderSize.r, r.t + borderSize.t, r.b - borderSize.b), mainColor); 1485 } 1486 1487 void UIElementMove(UIElement *element, UIRectangle bounds, bool alwaysLayout) { 1488 UIRectangle oldClip = element->clip; 1489 element->clip = UIRectangleIntersection(element->parent->clip, bounds); 1490 1491 if (!UIRectangleEquals(element->bounds, bounds) || !UIRectangleEquals(element->clip, oldClip) || alwaysLayout) { 1492 element->bounds = bounds; 1493 UIElementMessage(element, UI_MSG_LAYOUT, 0, 0); 1494 } 1495 } 1496 1497 int UIElementMessage(UIElement *element, UIMessage message, int di, void *dp) { 1498 if (message != UI_MSG_DESTROY && (element->flags & UI_ELEMENT_DESTROY)) { 1499 return 0; 1500 } 1501 1502 if (message >= UI_MSG_INPUT_EVENTS_START && message <= UI_MSG_INPUT_EVENTS_END && (element->flags & UI_ELEMENT_DISABLED)) { 1503 return 0; 1504 } 1505 1506 if (element->messageUser) { 1507 int result = element->messageUser(element, message, di, dp); 1508 1509 if (result) { 1510 return result; 1511 } 1512 } 1513 1514 if (element->messageClass) { 1515 return element->messageClass(element, message, di, dp); 1516 } else { 1517 return 0; 1518 } 1519 } 1520 1521 void UIElementChangeParent(UIElement *element, UIElement *newParent, UIElement *insertBefore) { 1522 UIElement **link = &element->parent->children; 1523 1524 while (true) { 1525 if (*link == element) { 1526 *link = element->next; 1527 break; 1528 } else { 1529 link = &(*link)->next; 1530 } 1531 } 1532 1533 link = &newParent->children; 1534 element->next = insertBefore; 1535 1536 while (true) { 1537 if ((*link) == insertBefore) { 1538 *link = element; 1539 break; 1540 } else { 1541 link = &(*link)->next; 1542 } 1543 } 1544 1545 element->parent = newParent; 1546 element->window = newParent->window; 1547 } 1548 1549 UIElement *UIElementCreate(size_t bytes, UIElement *parent, uint32_t flags, int (*message)(UIElement *, UIMessage, int, void *), const char *cClassName) { 1550 UI_ASSERT(bytes >= sizeof(UIElement)); 1551 UIElement *element = (UIElement *) UI_CALLOC(bytes); 1552 element->flags = flags; 1553 element->messageClass = message; 1554 1555 if (!parent && (~flags & UI_ELEMENT_WINDOW)) { 1556 UI_ASSERT(ui.parentStackCount); 1557 parent = ui.parentStack[ui.parentStackCount - 1]; 1558 } 1559 1560 if ((~flags & UI_ELEMENT_NON_CLIENT) && parent) { 1561 UIElementMessage(parent, UI_MSG_CLIENT_PARENT, 0, &parent); 1562 } 1563 1564 if (parent) { 1565 element->window = parent->window; 1566 element->parent = parent; 1567 1568 if (parent->children) { 1569 UIElement *sibling = parent->children; 1570 1571 while (sibling->next) { 1572 sibling = sibling->next; 1573 } 1574 1575 sibling->next = element; 1576 } else { 1577 parent->children = element; 1578 } 1579 1580 UI_ASSERT(~parent->flags & UI_ELEMENT_DESTROY); 1581 } 1582 1583 element->cClassName = cClassName; 1584 static uint32_t id = 0; 1585 element->id = ++id; 1586 1587 #ifdef UI_DEBUG 1588 _UIInspectorRefresh(); 1589 #endif 1590 1591 if (flags & UI_ELEMENT_PARENT_PUSH) { 1592 UIParentPush(element); 1593 } 1594 1595 return element; 1596 } 1597 1598 UIElement *UIParentPush(UIElement *element) { 1599 UI_ASSERT(ui.parentStackCount != sizeof(ui.parentStack) / sizeof(ui.parentStack[0])); 1600 ui.parentStack[ui.parentStackCount++] = element; 1601 return element; 1602 } 1603 1604 UIElement *UIParentPop() { 1605 UI_ASSERT(ui.parentStackCount); 1606 ui.parentStackCount--; 1607 return ui.parentStack[ui.parentStackCount]; 1608 } 1609 1610 int _UIPanelMeasure(UIPanel *panel) { 1611 bool horizontal = panel->e.flags & UI_PANEL_HORIZONTAL; 1612 int size = 0; 1613 UIElement *child = panel->e.children; 1614 1615 while (child) { 1616 if (~child->flags & UI_ELEMENT_HIDE) { 1617 if (horizontal) { 1618 int height = UIElementMessage(child, UI_MSG_GET_HEIGHT, 0, 0); 1619 1620 if (height > size) { 1621 size = height; 1622 } 1623 } else { 1624 int width = UIElementMessage(child, UI_MSG_GET_WIDTH, 0, 0); 1625 1626 if (width > size) { 1627 size = width; 1628 } 1629 } 1630 } 1631 1632 child = child->next; 1633 } 1634 1635 int border = 0; 1636 1637 if (horizontal) { 1638 border = panel->border.t + panel->border.b; 1639 } else { 1640 border = panel->border.l + panel->border.r; 1641 } 1642 1643 return size + border * panel->e.window->scale; 1644 } 1645 1646 int _UIPanelLayout(UIPanel *panel, UIRectangle bounds, bool measure) { 1647 bool horizontal = panel->e.flags & UI_PANEL_HORIZONTAL; 1648 float scale = panel->e.window->scale; 1649 int position = (horizontal ? panel->border.l : panel->border.t) * scale; 1650 if (panel->scrollBar && !measure) position -= panel->scrollBar->position; 1651 int hSpace = UI_RECT_WIDTH(bounds) - UI_RECT_TOTAL_H(panel->border) * scale; 1652 int vSpace = UI_RECT_HEIGHT(bounds) - UI_RECT_TOTAL_V(panel->border) * scale; 1653 1654 int available = horizontal ? hSpace : vSpace; 1655 int fill = 0, count = 0, perFill = 0; 1656 1657 for (UIElement *child = panel->e.children; child; child = child->next) { 1658 if (child->flags & (UI_ELEMENT_HIDE | UI_ELEMENT_NON_CLIENT)) { 1659 continue; 1660 } 1661 1662 count++; 1663 1664 if (horizontal) { 1665 if (child->flags & UI_ELEMENT_H_FILL) { 1666 fill++; 1667 } else if (available > 0) { 1668 available -= UIElementMessage(child, UI_MSG_GET_WIDTH, vSpace, 0); 1669 } 1670 } else { 1671 if (child->flags & UI_ELEMENT_V_FILL) { 1672 fill++; 1673 } else if (available > 0) { 1674 available -= UIElementMessage(child, UI_MSG_GET_HEIGHT, hSpace, 0); 1675 } 1676 } 1677 } 1678 1679 if (count) { 1680 available -= (count - 1) * (int) (panel->gap * scale); 1681 } 1682 1683 if (available > 0 && fill) { 1684 perFill = available / fill; 1685 } 1686 1687 bool expand = panel->e.flags & UI_PANEL_EXPAND; 1688 int scaledBorder2 = (horizontal ? panel->border.t : panel->border.l) * panel->e.window->scale; 1689 1690 for (UIElement *child = panel->e.children; child; child = child->next) { 1691 if (child->flags & (UI_ELEMENT_HIDE | UI_ELEMENT_NON_CLIENT)) { 1692 continue; 1693 } 1694 1695 if (horizontal) { 1696 int height = ((child->flags & UI_ELEMENT_V_FILL) || expand) ? vSpace : UIElementMessage(child, UI_MSG_GET_HEIGHT, 0, 0); 1697 int width = (child->flags & UI_ELEMENT_H_FILL) ? perFill : UIElementMessage(child, UI_MSG_GET_WIDTH, height, 0); 1698 UIRectangle relative = UI_RECT_4(position, position + width, 1699 scaledBorder2 + (vSpace - height) / 2, 1700 scaledBorder2 + (vSpace + height) / 2); 1701 if (!measure) UIElementMove(child, UIRectangleTranslate(relative, bounds), false); 1702 position += width + panel->gap * scale; 1703 } else { 1704 int width = ((child->flags & UI_ELEMENT_H_FILL) || expand) ? hSpace : UIElementMessage(child, UI_MSG_GET_WIDTH, 0, 0); 1705 int height = (child->flags & UI_ELEMENT_V_FILL) ? perFill : UIElementMessage(child, UI_MSG_GET_HEIGHT, width, 0); 1706 UIRectangle relative = UI_RECT_4(scaledBorder2 + (hSpace - width) / 2, 1707 scaledBorder2 + (hSpace + width) / 2, position, position + height); 1708 if (!measure) UIElementMove(child, UIRectangleTranslate(relative, bounds), false); 1709 position += height + panel->gap * scale; 1710 } 1711 } 1712 1713 return position - panel->gap * scale + (horizontal ? panel->border.r : panel->border.b) * scale; 1714 } 1715 1716 int _UIPanelMessage(UIElement *element, UIMessage message, int di, void *dp) { 1717 UIPanel *panel = (UIPanel *) element; 1718 bool horizontal = element->flags & UI_PANEL_HORIZONTAL; 1719 1720 if (message == UI_MSG_LAYOUT) { 1721 int scrollBarWidth = panel->scrollBar ? (UI_SIZE_SCROLL_BAR * element->window->scale) : 0; 1722 UIRectangle bounds = element->bounds; 1723 bounds.r -= scrollBarWidth; 1724 1725 if (panel->scrollBar) { 1726 UIRectangle scrollBarBounds = element->bounds; 1727 scrollBarBounds.l = scrollBarBounds.r - scrollBarWidth; 1728 panel->scrollBar->maximum = _UIPanelLayout(panel, bounds, true); 1729 panel->scrollBar->page = UI_RECT_HEIGHT(element->bounds); 1730 UIElementMove(&panel->scrollBar->e, scrollBarBounds, true); 1731 } 1732 1733 _UIPanelLayout(panel, bounds, false); 1734 } else if (message == UI_MSG_GET_WIDTH) { 1735 if (horizontal) { 1736 return _UIPanelLayout(panel, UI_RECT_4(0, 0, 0, di), true); 1737 } else { 1738 return _UIPanelMeasure(panel); 1739 } 1740 } else if (message == UI_MSG_GET_HEIGHT) { 1741 if (horizontal) { 1742 return _UIPanelMeasure(panel); 1743 } else { 1744 int width = di && panel->scrollBar ? (di - UI_SIZE_SCROLL_BAR * element->window->scale) : di; 1745 return _UIPanelLayout(panel, UI_RECT_4(0, width, 0, 0), true); 1746 } 1747 } else if (message == UI_MSG_PAINT) { 1748 if (element->flags & UI_PANEL_GRAY) { 1749 UIDrawBlock((UIPainter *) dp, element->bounds, ui.theme.panel1); 1750 } else if (element->flags & UI_PANEL_WHITE) { 1751 UIDrawBlock((UIPainter *) dp, element->bounds, ui.theme.panel2); 1752 } 1753 1754 if (element->flags & UI_PANEL_BORDER) { 1755 UIDrawBorder((UIPainter *) dp, element->bounds, ui.theme.border, UI_RECT_1((int) element->window->scale)); 1756 } 1757 } else if (message == UI_MSG_MOUSE_WHEEL && panel->scrollBar) { 1758 return UIElementMessage(&panel->scrollBar->e, message, di, dp); 1759 } else if (message == UI_MSG_SCROLLED) { 1760 UIElementRefresh(element); 1761 } 1762 1763 return 0; 1764 } 1765 1766 UIPanel *UIPanelCreate(UIElement *parent, uint32_t flags) { 1767 UIPanel *panel = (UIPanel *) UIElementCreate(sizeof(UIPanel), parent, flags, _UIPanelMessage, "Panel"); 1768 1769 if (flags & UI_PANEL_MEDIUM_SPACING) { 1770 panel->border = UI_RECT_1(UI_SIZE_PANE_MEDIUM_BORDER); 1771 panel->gap = UI_SIZE_PANE_MEDIUM_GAP; 1772 } else if (flags & UI_PANEL_SMALL_SPACING) { 1773 panel->border = UI_RECT_1(UI_SIZE_PANE_SMALL_BORDER); 1774 panel->gap = UI_SIZE_PANE_SMALL_GAP; 1775 } 1776 1777 if (flags & UI_PANEL_SCROLL) { 1778 panel->scrollBar = UIScrollBarCreate(&panel->e, UI_ELEMENT_NON_CLIENT); 1779 } 1780 1781 return panel; 1782 } 1783 1784 void _UIWrapPanelLayoutRow(UIWrapPanel *panel, UIElement *child, UIElement *rowEnd, int rowY, int rowHeight) { 1785 int rowPosition = 0; 1786 1787 while (child != rowEnd) { 1788 int height = UIElementMessage(child, UI_MSG_GET_HEIGHT, 0, 0); 1789 int width = UIElementMessage(child, UI_MSG_GET_WIDTH, 0, 0); 1790 UIRectangle relative = UI_RECT_4(rowPosition, rowPosition + width, rowY + rowHeight / 2 - height / 2, rowY + rowHeight / 2 + height / 2); 1791 UIElementMove(child, UIRectangleTranslate(relative, panel->e.bounds), false); 1792 child = child->next; 1793 rowPosition += width; 1794 } 1795 } 1796 1797 int _UIWrapPanelMessage(UIElement *element, UIMessage message, int di, void *dp) { 1798 UIWrapPanel *panel = (UIWrapPanel *) element; 1799 bool horizontal = element->flags & UI_PANEL_HORIZONTAL; 1800 1801 if (message == UI_MSG_LAYOUT || message == UI_MSG_GET_HEIGHT) { 1802 int totalHeight = 0; 1803 int rowPosition = 0; 1804 int rowHeight = 0; 1805 int rowLimit = message == UI_MSG_LAYOUT ? UI_RECT_WIDTH(element->bounds) : di; 1806 1807 UIElement *child = panel->e.children; 1808 UIElement *rowStart = child; 1809 1810 while (child) { 1811 if (~child->flags & UI_ELEMENT_HIDE) { 1812 int height = UIElementMessage(child, UI_MSG_GET_HEIGHT, 0, 0); 1813 int width = UIElementMessage(child, UI_MSG_GET_WIDTH, 0, 0); 1814 1815 if (rowLimit && rowPosition + width > rowLimit) { 1816 _UIWrapPanelLayoutRow(panel, rowStart, child, totalHeight, rowHeight); 1817 totalHeight += rowHeight; 1818 rowPosition = rowHeight = 0; 1819 rowStart = child; 1820 } 1821 1822 if (height > rowHeight) { 1823 rowHeight = height; 1824 } 1825 1826 rowPosition += width; 1827 } 1828 1829 child = child->next; 1830 } 1831 1832 if (message == UI_MSG_GET_HEIGHT) { 1833 return totalHeight + rowHeight; 1834 } else { 1835 _UIWrapPanelLayoutRow(panel, rowStart, child, totalHeight, rowHeight); 1836 } 1837 } 1838 1839 return 0; 1840 } 1841 1842 UIWrapPanel *UIWrapPanelCreate(UIElement *parent, uint32_t flags) { 1843 return (UIWrapPanel *) UIElementCreate(sizeof(UIWrapPanel), parent, flags, _UIWrapPanelMessage, "Wrap Panel"); 1844 } 1845 1846 void _UIButtonCalculateColors(UIElement *element, uint32_t *color, uint32_t *textColor) { 1847 bool disabled = element->flags & UI_ELEMENT_DISABLED; 1848 bool focused = element == element->window->focused; 1849 bool pressed = element == element->window->pressed; 1850 bool hovered = element == element->window->hovered; 1851 *color = disabled ? ui.theme.buttonDisabled 1852 : (pressed && hovered) ? ui.theme.buttonPressed 1853 : (pressed || hovered) ? ui.theme.buttonHovered 1854 : focused ? ui.theme.selected : ui.theme.buttonNormal; 1855 *textColor = disabled ? ui.theme.textDisabled 1856 : *color == ui.theme.selected ? ui.theme.textSelected : ui.theme.text; 1857 } 1858 1859 1860 int _UIButtonMessage(UIElement *element, UIMessage message, int di, void *dp) { 1861 UIButton *button = (UIButton *) element; 1862 bool isMenuItem = element->flags & UI_BUTTON_MENU_ITEM; 1863 bool isDropDown = element->flags & UI_BUTTON_DROP_DOWN; 1864 1865 if (message == UI_MSG_GET_HEIGHT) { 1866 if (isMenuItem) { 1867 return UI_SIZE_MENU_ITEM_HEIGHT * element->window->scale; 1868 } else { 1869 return UI_SIZE_BUTTON_HEIGHT * element->window->scale; 1870 } 1871 } else if (message == UI_MSG_GET_WIDTH) { 1872 int labelSize = UIMeasureStringWidth(button->label, button->labelBytes); 1873 int paddedSize = labelSize + UI_SIZE_BUTTON_PADDING * element->window->scale; 1874 if (isDropDown) paddedSize += ui.activeFont->glyphWidth * 2; 1875 int minimumSize = ((element->flags & UI_BUTTON_SMALL) ? 0 1876 : isMenuItem ? UI_SIZE_MENU_ITEM_MINIMUM_WIDTH 1877 : UI_SIZE_BUTTON_MINIMUM_WIDTH) 1878 * element->window->scale; 1879 return paddedSize > minimumSize ? paddedSize : minimumSize; 1880 } else if (message == UI_MSG_PAINT) { 1881 UIPainter *painter = (UIPainter *) dp; 1882 1883 uint32_t color, textColor; 1884 _UIButtonCalculateColors(element, &color, &textColor); 1885 1886 UIDrawRectangle(painter, element->bounds, color, ui.theme.border, UI_RECT_1(isMenuItem ? 0 : 1)); 1887 1888 if (element->flags & UI_BUTTON_CHECKED) { 1889 UIDrawBlock(painter, UIRectangleAdd(element->bounds, 1890 UI_RECT_1I((int) (UI_SIZE_BUTTON_CHECKED_AREA * element->window->scale))), ui.theme.buttonPressed); 1891 } 1892 1893 UIRectangle bounds = UIRectangleAdd(element->bounds, UI_RECT_2I((int) (UI_SIZE_MENU_ITEM_MARGIN * element->window->scale), 0)); 1894 1895 if (isMenuItem) { 1896 if (button->labelBytes == -1) { 1897 button->labelBytes = _UIStringLength(button->label); 1898 } 1899 1900 int tab = 0; 1901 for (; tab < button->labelBytes && button->label[tab] != '\t'; tab++); 1902 1903 UIDrawString(painter, bounds, button->label, tab, textColor, UI_ALIGN_LEFT, NULL); 1904 1905 if (button->labelBytes > tab) { 1906 UIDrawString(painter, bounds, button->label + tab + 1, button->labelBytes - tab - 1, textColor, UI_ALIGN_RIGHT, NULL); 1907 } 1908 } else if (isDropDown) { 1909 UIDrawString(painter, bounds, button->label, button->labelBytes, textColor, UI_ALIGN_LEFT, NULL); 1910 UIDrawString(painter, bounds, "\x19", 1, textColor, UI_ALIGN_RIGHT, NULL); 1911 } else { 1912 UIDrawString(painter, element->bounds, button->label, button->labelBytes, textColor, UI_ALIGN_CENTER, NULL); 1913 } 1914 } else if (message == UI_MSG_UPDATE) { 1915 UIElementRepaint(element, NULL); 1916 } else if (message == UI_MSG_DESTROY) { 1917 UI_FREE(button->label); 1918 } else if (message == UI_MSG_LEFT_DOWN) { 1919 if (element->flags & UI_BUTTON_CAN_FOCUS) { 1920 UIElementFocus(element); 1921 } 1922 } else if (message == UI_MSG_KEY_TYPED) { 1923 UIKeyTyped *m = (UIKeyTyped *) dp; 1924 1925 if (m->textBytes == 1 && m->text[0] == ' ') { 1926 UIElementMessage(element, UI_MSG_CLICKED, 0, 0); 1927 UIElementRepaint(element, NULL); 1928 } 1929 } else if (message == UI_MSG_CLICKED) { 1930 if (button->invoke) { 1931 button->invoke(element->cp); 1932 } 1933 } 1934 1935 return 0; 1936 } 1937 1938 UIButton *UIButtonCreate(UIElement *parent, uint32_t flags, const char *label, ptrdiff_t labelBytes) { 1939 UIButton *button = (UIButton *) UIElementCreate(sizeof(UIButton), parent, flags | UI_ELEMENT_TAB_STOP, _UIButtonMessage, "Button"); 1940 button->label = UIStringCopy(label, (button->labelBytes = labelBytes)); 1941 return button; 1942 } 1943 1944 int _UICheckboxMessage(UIElement *element, UIMessage message, int di, void *dp) { 1945 UICheckbox *box = (UICheckbox *) element; 1946 1947 if (message == UI_MSG_GET_HEIGHT) { 1948 return UI_SIZE_BUTTON_HEIGHT * element->window->scale; 1949 } else if (message == UI_MSG_GET_WIDTH) { 1950 int labelSize = UIMeasureStringWidth(box->label, box->labelBytes); 1951 return (labelSize + UI_SIZE_CHECKBOX_BOX + UI_SIZE_CHECKBOX_GAP) * element->window->scale; 1952 } else if (message == UI_MSG_PAINT) { 1953 UIPainter *painter = (UIPainter *) dp; 1954 uint32_t color, textColor; 1955 _UIButtonCalculateColors(element, &color, &textColor); 1956 int midY = (element->bounds.t + element->bounds.b) / 2; 1957 UIRectangle boxBounds = UI_RECT_4(element->bounds.l, element->bounds.l + UI_SIZE_CHECKBOX_BOX, 1958 midY - UI_SIZE_CHECKBOX_BOX / 2, midY + UI_SIZE_CHECKBOX_BOX / 2); 1959 UIDrawRectangle(painter, boxBounds, color, ui.theme.border, UI_RECT_1(1)); 1960 UIDrawString(painter, UIRectangleAdd(boxBounds, UI_RECT_4(1, 0, 0, 0)), 1961 box->check == UI_CHECK_CHECKED ? "*" : box->check == UI_CHECK_INDETERMINATE ? "-" : " ", -1, 1962 textColor, UI_ALIGN_CENTER, NULL); 1963 UIDrawString(painter, UIRectangleAdd(element->bounds, UI_RECT_4(UI_SIZE_CHECKBOX_BOX + UI_SIZE_CHECKBOX_GAP, 0, 0, 0)), 1964 box->label, box->labelBytes, textColor, UI_ALIGN_LEFT, NULL); 1965 } else if (message == UI_MSG_UPDATE) { 1966 UIElementRepaint(element, NULL); 1967 } else if (message == UI_MSG_DESTROY) { 1968 UI_FREE(box->label); 1969 } else if (message == UI_MSG_KEY_TYPED) { 1970 UIKeyTyped *m = (UIKeyTyped *) dp; 1971 1972 if (m->textBytes == 1 && m->text[0] == ' ') { 1973 UIElementMessage(element, UI_MSG_CLICKED, 0, 0); 1974 UIElementRepaint(element, NULL); 1975 } 1976 } else if (message == UI_MSG_CLICKED) { 1977 box->check = (box->check + 1) % ((element->flags & UI_CHECKBOX_ALLOW_INDETERMINATE) ? 3 : 2); 1978 UIElementRepaint(element, NULL); 1979 if (box->invoke) box->invoke(element->cp); 1980 } 1981 1982 return 0; 1983 } 1984 1985 UICheckbox *UICheckboxCreate(UIElement *parent, uint32_t flags, const char *label, ptrdiff_t labelBytes) { 1986 UICheckbox *box = (UICheckbox *) UIElementCreate(sizeof(UICheckbox), parent, flags | UI_ELEMENT_TAB_STOP, _UICheckboxMessage, "Checkbox"); 1987 box->label = UIStringCopy(label, (box->labelBytes = labelBytes)); 1988 return box; 1989 } 1990 1991 int _UILabelMessage(UIElement *element, UIMessage message, int di, void *dp) { 1992 UILabel *label = (UILabel *) element; 1993 1994 if (message == UI_MSG_GET_HEIGHT) { 1995 return UIMeasureStringHeight(); 1996 } else if (message == UI_MSG_GET_WIDTH) { 1997 return UIMeasureStringWidth(label->label, label->labelBytes); 1998 } else if (message == UI_MSG_PAINT) { 1999 UIPainter *painter = (UIPainter *) dp; 2000 UIDrawString(painter, element->bounds, label->label, label->labelBytes, ui.theme.text, UI_ALIGN_LEFT, NULL); 2001 } else if (message == UI_MSG_DESTROY) { 2002 UI_FREE(label->label); 2003 } 2004 2005 return 0; 2006 } 2007 2008 void UILabelSetContent(UILabel *label, const char *string, ptrdiff_t stringBytes) { 2009 UI_FREE(label->label); 2010 label->label = UIStringCopy(string, (label->labelBytes = stringBytes)); 2011 } 2012 2013 UILabel *UILabelCreate(UIElement *parent, uint32_t flags, const char *string, ptrdiff_t stringBytes) { 2014 UILabel *label = (UILabel *) UIElementCreate(sizeof(UILabel), parent, flags, _UILabelMessage, "Label"); 2015 label->label = UIStringCopy(string, (label->labelBytes = stringBytes)); 2016 return label; 2017 } 2018 2019 int _UISplitPaneMessage(UIElement *element, UIMessage message, int di, void *dp); 2020 2021 int _UISplitterMessage(UIElement *element, UIMessage message, int di, void *dp) { 2022 UISplitPane *splitPane = (UISplitPane *) element->parent; 2023 bool vertical = splitPane->e.flags & UI_SPLIT_PANE_VERTICAL; 2024 2025 if (message == UI_MSG_PAINT) { 2026 UIRectangle borders = vertical ? UI_RECT_2(0, 1) : UI_RECT_2(1, 0); 2027 UIDrawRectangle((UIPainter *) dp, element->bounds, ui.theme.buttonNormal, ui.theme.border, borders); 2028 } else if (message == UI_MSG_GET_CURSOR) { 2029 return vertical ? UI_CURSOR_SPLIT_V : UI_CURSOR_SPLIT_H; 2030 } else if (message == UI_MSG_MOUSE_DRAG) { 2031 int cursor = vertical ? element->window->cursorY : element->window->cursorX; 2032 int splitterSize = UI_SIZE_SPLITTER * element->window->scale; 2033 int space = (vertical ? UI_RECT_HEIGHT(splitPane->e.bounds) : UI_RECT_WIDTH(splitPane->e.bounds)) - splitterSize; 2034 float oldWeight = splitPane->weight; 2035 splitPane->weight = (float) (cursor - splitterSize / 2 - (vertical ? splitPane->e.bounds.t : splitPane->e.bounds.l)) / space; 2036 if (splitPane->weight < 0.05f) splitPane->weight = 0.05f; 2037 if (splitPane->weight > 0.95f) splitPane->weight = 0.95f; 2038 2039 if (element->next->next->messageClass == _UISplitPaneMessage 2040 && (element->next->next->flags & UI_SPLIT_PANE_VERTICAL) == (splitPane->e.flags & UI_SPLIT_PANE_VERTICAL)) { 2041 UISplitPane *subSplitPane = (UISplitPane *) element->next->next; 2042 subSplitPane->weight = (splitPane->weight - oldWeight - subSplitPane->weight + oldWeight * subSplitPane->weight) / (-1 + splitPane->weight); 2043 if (subSplitPane->weight < 0.05f) subSplitPane->weight = 0.05f; 2044 if (subSplitPane->weight > 0.95f) subSplitPane->weight = 0.95f; 2045 } 2046 2047 UIElementRefresh(&splitPane->e); 2048 } 2049 2050 return 0; 2051 } 2052 2053 int _UISplitPaneMessage(UIElement *element, UIMessage message, int di, void *dp) { 2054 UISplitPane *splitPane = (UISplitPane *) element; 2055 bool vertical = splitPane->e.flags & UI_SPLIT_PANE_VERTICAL; 2056 2057 if (message == UI_MSG_LAYOUT) { 2058 UIElement *splitter = element->children; 2059 UI_ASSERT(splitter); 2060 UIElement *left = splitter->next; 2061 UI_ASSERT(left); 2062 UIElement *right = left->next; 2063 UI_ASSERT(right); 2064 UI_ASSERT(!right->next); 2065 2066 int splitterSize = UI_SIZE_SPLITTER * element->window->scale; 2067 int space = (vertical ? UI_RECT_HEIGHT(element->bounds) : UI_RECT_WIDTH(element->bounds)) - splitterSize; 2068 int leftSize = space * splitPane->weight; 2069 int rightSize = space - leftSize; 2070 2071 if (vertical) { 2072 UIElementMove(left, UI_RECT_4(element->bounds.l, element->bounds.r, element->bounds.t, element->bounds.t + leftSize), false); 2073 UIElementMove(splitter, UI_RECT_4(element->bounds.l, element->bounds.r, element->bounds.t + leftSize, element->bounds.t + leftSize + splitterSize), false); 2074 UIElementMove(right, UI_RECT_4(element->bounds.l, element->bounds.r, element->bounds.b - rightSize, element->bounds.b), false); 2075 } else { 2076 UIElementMove(left, UI_RECT_4(element->bounds.l, element->bounds.l + leftSize, element->bounds.t, element->bounds.b), false); 2077 UIElementMove(splitter, UI_RECT_4(element->bounds.l + leftSize, element->bounds.l + leftSize + splitterSize, element->bounds.t, element->bounds.b), false); 2078 UIElementMove(right, UI_RECT_4(element->bounds.r - rightSize, element->bounds.r, element->bounds.t, element->bounds.b), false); 2079 } 2080 } 2081 2082 return 0; 2083 } 2084 2085 UISplitPane *UISplitPaneCreate(UIElement *parent, uint32_t flags, float weight) { 2086 UISplitPane *splitPane = (UISplitPane *) UIElementCreate(sizeof(UISplitPane), parent, flags, _UISplitPaneMessage, "Split Pane"); 2087 splitPane->weight = weight; 2088 UIElementCreate(sizeof(UIElement), &splitPane->e, 0, _UISplitterMessage, "Splitter"); 2089 return splitPane; 2090 } 2091 2092 int _UITabPaneMessage(UIElement *element, UIMessage message, int di, void *dp) { 2093 UITabPane *tabPane = (UITabPane *) element; 2094 2095 if (message == UI_MSG_PAINT) { 2096 UIPainter *painter = (UIPainter *) dp; 2097 UIRectangle top = element->bounds; 2098 top.b = top.t + UI_SIZE_BUTTON_HEIGHT * element->window->scale; 2099 UIDrawRectangle(painter, top, ui.theme.panel1, ui.theme.border, UI_RECT_4(0, 0, 0, 1)); 2100 2101 UIRectangle tab = top; 2102 tab.l += UI_SIZE_TAB_PANE_SPACE_LEFT * element->window->scale; 2103 tab.t += UI_SIZE_TAB_PANE_SPACE_TOP * element->window->scale; 2104 2105 int position = 0; 2106 int index = 0; 2107 2108 while (true) { 2109 int end = position; 2110 for (; tabPane->tabs[end] != '\t' && tabPane->tabs[end]; end++); 2111 2112 int width = UIMeasureStringWidth(tabPane->tabs, end - position); 2113 tab.r = tab.l + width + UI_SIZE_BUTTON_PADDING; 2114 2115 uint32_t color = tabPane->active == index ? ui.theme.buttonPressed : ui.theme.buttonNormal; 2116 2117 UIRectangle t = tab; 2118 2119 if (tabPane->active == index) { 2120 t.b++; 2121 t.t--; 2122 } else { 2123 t.t++; 2124 } 2125 2126 UIDrawRectangle(painter, t, color, ui.theme.border, UI_RECT_1(1)); 2127 UIDrawString(painter, tab, tabPane->tabs + position, end - position, ui.theme.text, UI_ALIGN_CENTER, NULL); 2128 tab.l = tab.r - 1; 2129 2130 if (tabPane->tabs[end] == '\t') { 2131 position = end + 1; 2132 index++; 2133 } else { 2134 break; 2135 } 2136 } 2137 } else if (message == UI_MSG_LEFT_DOWN) { 2138 UIRectangle tab = element->bounds; 2139 tab.b = tab.t + UI_SIZE_BUTTON_HEIGHT * element->window->scale; 2140 tab.l += UI_SIZE_TAB_PANE_SPACE_LEFT * element->window->scale; 2141 tab.t += UI_SIZE_TAB_PANE_SPACE_TOP * element->window->scale; 2142 2143 int position = 0; 2144 int index = 0; 2145 2146 while (true) { 2147 int end = position; 2148 for (; tabPane->tabs[end] != '\t' && tabPane->tabs[end]; end++); 2149 2150 int width = UIMeasureStringWidth(tabPane->tabs, end - position); 2151 tab.r = tab.l + width + UI_SIZE_BUTTON_PADDING; 2152 2153 if (UIRectangleContains(tab, element->window->cursorX, element->window->cursorY)) { 2154 tabPane->active = index; 2155 UIElementMessage(element, UI_MSG_LAYOUT, 0, 0); 2156 UIElementRepaint(element, NULL); 2157 break; 2158 } 2159 2160 tab.l = tab.r - 1; 2161 2162 if (tabPane->tabs[end] == '\t') { 2163 position = end + 1; 2164 index++; 2165 } else { 2166 break; 2167 } 2168 } 2169 } else if (message == UI_MSG_LAYOUT) { 2170 UIElement *child = element->children; 2171 int index = 0; 2172 2173 UIRectangle content = element->bounds; 2174 content.t += UI_SIZE_BUTTON_HEIGHT * element->window->scale; 2175 2176 while (child) { 2177 if (tabPane->active == index) { 2178 child->flags &= ~UI_ELEMENT_HIDE; 2179 UIElementMove(child, content, false); 2180 UIElementMessage(child, UI_MSG_TAB_SELECTED, 0, 0); 2181 } else { 2182 child->flags |= UI_ELEMENT_HIDE; 2183 } 2184 2185 child = child->next; 2186 index++; 2187 } 2188 } else if (message == UI_MSG_GET_HEIGHT) { 2189 UIElement *child = element->children; 2190 int index = 0; 2191 int baseHeight = UI_SIZE_BUTTON_HEIGHT * element->window->scale; 2192 2193 while (child) { 2194 if (tabPane->active == index) { 2195 return baseHeight + UIElementMessage(child, UI_MSG_GET_HEIGHT, di, dp); 2196 } 2197 2198 child = child->next; 2199 index++; 2200 } 2201 } else if (message == UI_MSG_DESTROY) { 2202 UI_FREE(tabPane->tabs); 2203 } 2204 2205 return 0; 2206 } 2207 2208 UITabPane *UITabPaneCreate(UIElement *parent, uint32_t flags, const char *tabs) { 2209 UITabPane *tabPane = (UITabPane *) UIElementCreate(sizeof(UITabPane), parent, flags, _UITabPaneMessage, "Tab Pane"); 2210 tabPane->tabs = UIStringCopy(tabs, -1); 2211 return tabPane; 2212 } 2213 2214 int _UISpacerMessage(UIElement *element, UIMessage message, int di, void *dp) { 2215 UISpacer *spacer = (UISpacer *) element; 2216 2217 if (message == UI_MSG_GET_HEIGHT) { 2218 return spacer->height * element->window->scale; 2219 } else if (message == UI_MSG_GET_WIDTH) { 2220 return spacer->width * element->window->scale; 2221 } else if (message == UI_MSG_PAINT && (element->flags & UI_SPACER_LINE)) { 2222 UIDrawBlock((UIPainter *) dp, element->bounds, ui.theme.border); 2223 } 2224 2225 return 0; 2226 } 2227 2228 UISpacer *UISpacerCreate(UIElement *parent, uint32_t flags, int width, int height) { 2229 UISpacer *spacer = (UISpacer *) UIElementCreate(sizeof(UISpacer), parent, flags, _UISpacerMessage, "Spacer"); 2230 spacer->width = width; 2231 spacer->height = height; 2232 return spacer; 2233 } 2234 2235 int _UIScrollBarMessage(UIElement *element, UIMessage message, int di, void *dp) { 2236 UIScrollBar *scrollBar = (UIScrollBar *) element; 2237 2238 if (message == UI_MSG_GET_WIDTH || message == UI_MSG_GET_HEIGHT) { 2239 return UI_SIZE_SCROLL_BAR * element->window->scale; 2240 } else if (message == UI_MSG_LAYOUT) { 2241 UIElement *up = element->children; 2242 UIElement *thumb = up->next; 2243 UIElement *down = thumb->next; 2244 2245 if (scrollBar->page >= scrollBar->maximum || scrollBar->maximum <= 0 || scrollBar->page <= 0) { 2246 up->flags |= UI_ELEMENT_HIDE; 2247 thumb->flags |= UI_ELEMENT_HIDE; 2248 down->flags |= UI_ELEMENT_HIDE; 2249 2250 scrollBar->position = 0; 2251 } else { 2252 up->flags &= ~UI_ELEMENT_HIDE; 2253 thumb->flags &= ~UI_ELEMENT_HIDE; 2254 down->flags &= ~UI_ELEMENT_HIDE; 2255 2256 int size = scrollBar->horizontal ? UI_RECT_WIDTH(element->bounds) : UI_RECT_HEIGHT(element->bounds); 2257 int thumbSize = size * scrollBar->page / scrollBar->maximum; 2258 2259 if (thumbSize < UI_SIZE_SCROLL_MINIMUM_THUMB * element->window->scale) { 2260 thumbSize = UI_SIZE_SCROLL_MINIMUM_THUMB * element->window->scale; 2261 } 2262 2263 if (scrollBar->position < 0) { 2264 scrollBar->position = 0; 2265 } else if (scrollBar->position > scrollBar->maximum - scrollBar->page) { 2266 scrollBar->position = scrollBar->maximum - scrollBar->page; 2267 } 2268 2269 int thumbPosition = scrollBar->position / (scrollBar->maximum - scrollBar->page) * (size - thumbSize); 2270 2271 if (scrollBar->position == scrollBar->maximum - scrollBar->page) { 2272 thumbPosition = size - thumbSize; 2273 } 2274 2275 if (scrollBar->horizontal) { 2276 UIRectangle r = element->bounds; 2277 r.r = r.l + thumbPosition; 2278 UIElementMove(up, r, false); 2279 r.l = r.r, r.r = r.l + thumbSize; 2280 UIElementMove(thumb, r, false); 2281 r.l = r.r, r.r = element->bounds.r; 2282 UIElementMove(down, r, false); 2283 } else { 2284 UIRectangle r = element->bounds; 2285 r.b = r.t + thumbPosition; 2286 UIElementMove(up, r, false); 2287 r.t = r.b, r.b = r.t + thumbSize; 2288 UIElementMove(thumb, r, false); 2289 r.t = r.b, r.b = element->bounds.b; 2290 UIElementMove(down, r, false); 2291 } 2292 } 2293 } else if (message == UI_MSG_PAINT) { 2294 if (scrollBar->page >= scrollBar->maximum || scrollBar->maximum <= 0 || scrollBar->page <= 0) { 2295 UIDrawBlock((UIPainter *) dp, element->bounds, ui.theme.panel1); 2296 } 2297 } else if (message == UI_MSG_MOUSE_WHEEL) { 2298 scrollBar->position += di; 2299 UIElementRefresh(element); 2300 UIElementMessage(element->parent, UI_MSG_SCROLLED, 0, 0); 2301 return 1; 2302 } 2303 2304 return 0; 2305 } 2306 2307 int _UIScrollUpDownMessage(UIElement *element, UIMessage message, int di, void *dp) { 2308 UIScrollBar *scrollBar = (UIScrollBar *) element->parent; 2309 bool isDown = element->cp; 2310 2311 if (message == UI_MSG_PAINT) { 2312 UIPainter *painter = (UIPainter *) dp; 2313 uint32_t color = element == element->window->pressed ? ui.theme.buttonPressed 2314 : element == element->window->hovered ? ui.theme.buttonHovered : ui.theme.panel2; 2315 UIDrawRectangle(painter, element->bounds, color, ui.theme.border, UI_RECT_1(0)); 2316 2317 if (scrollBar->horizontal) { 2318 UIDrawGlyph(painter, isDown ? (element->bounds.r - ui.activeFont->glyphWidth - 2 * element->window->scale) 2319 : (element->bounds.l + 2 * element->window->scale), 2320 (element->bounds.t + element->bounds.b - ui.activeFont->glyphHeight) / 2, 2321 isDown ? 26 : 27, ui.theme.text); 2322 } else { 2323 UIDrawGlyph(painter, (element->bounds.l + element->bounds.r - ui.activeFont->glyphWidth) / 2 + 1, 2324 isDown ? (element->bounds.b - ui.activeFont->glyphHeight - 2 * element->window->scale) 2325 : (element->bounds.t + 2 * element->window->scale), 2326 isDown ? 25 : 24, ui.theme.text); 2327 } 2328 } else if (message == UI_MSG_UPDATE) { 2329 UIElementRepaint(element, NULL); 2330 } else if (message == UI_MSG_LEFT_DOWN) { 2331 UIElementAnimate(element, false); 2332 scrollBar->lastAnimateTime = UI_CLOCK(); 2333 } else if (message == UI_MSG_LEFT_UP) { 2334 UIElementAnimate(element, true); 2335 } else if (message == UI_MSG_ANIMATE) { 2336 UI_CLOCK_T previous = scrollBar->lastAnimateTime; 2337 UI_CLOCK_T current = UI_CLOCK(); 2338 UI_CLOCK_T delta = current - previous; 2339 double deltaSeconds = (double) delta / UI_CLOCKS_PER_SECOND; 2340 if (deltaSeconds > 0.1) deltaSeconds = 0.1; 2341 double deltaPixels = deltaSeconds * scrollBar->page * 3; 2342 scrollBar->lastAnimateTime = current; 2343 if (isDown) scrollBar->position += deltaPixels; 2344 else scrollBar->position -= deltaPixels; 2345 UIElementRefresh(&scrollBar->e); 2346 UIElementMessage(scrollBar->e.parent, UI_MSG_SCROLLED, 0, 0); 2347 } 2348 2349 return 0; 2350 } 2351 2352 int _UIScrollThumbMessage(UIElement *element, UIMessage message, int di, void *dp) { 2353 UIScrollBar *scrollBar = (UIScrollBar *) element->parent; 2354 2355 if (message == UI_MSG_PAINT) { 2356 UIPainter *painter = (UIPainter *) dp; 2357 uint32_t color = element == element->window->pressed ? ui.theme.buttonPressed 2358 : element == element->window->hovered ? ui.theme.buttonHovered : ui.theme.buttonNormal; 2359 UIDrawRectangle(painter, element->bounds, color, ui.theme.border, UI_RECT_1(2)); 2360 } else if (message == UI_MSG_UPDATE) { 2361 UIElementRepaint(element, NULL); 2362 } else if (message == UI_MSG_MOUSE_DRAG && element->window->pressedButton == 1) { 2363 if (!scrollBar->inDrag) { 2364 scrollBar->inDrag = true; 2365 2366 if (scrollBar->horizontal) { 2367 scrollBar->dragOffset = element->bounds.l - scrollBar->e.bounds.l - element->window->cursorX; 2368 } else { 2369 scrollBar->dragOffset = element->bounds.t - scrollBar->e.bounds.t - element->window->cursorY; 2370 } 2371 } 2372 2373 int thumbPosition = (scrollBar->horizontal ? element->window->cursorX : element->window->cursorY) + scrollBar->dragOffset; 2374 int size = scrollBar->horizontal ? (UI_RECT_WIDTH(scrollBar->e.bounds) - UI_RECT_WIDTH(element->bounds)) 2375 : (UI_RECT_HEIGHT(scrollBar->e.bounds) - UI_RECT_HEIGHT(element->bounds)); 2376 scrollBar->position = (double) thumbPosition / size * (scrollBar->maximum - scrollBar->page); 2377 UIElementRefresh(&scrollBar->e); 2378 UIElementMessage(scrollBar->e.parent, UI_MSG_SCROLLED, 0, 0); 2379 } else if (message == UI_MSG_LEFT_UP) { 2380 scrollBar->inDrag = false; 2381 } 2382 2383 return 0; 2384 } 2385 2386 UIScrollBar *UIScrollBarCreate(UIElement *parent, uint32_t flags) { 2387 UIScrollBar *scrollBar = (UIScrollBar *) UIElementCreate(sizeof(UIScrollBar), parent, flags, _UIScrollBarMessage, "Scroll Bar"); 2388 bool horizontal = scrollBar->horizontal = flags & UI_SCROLL_BAR_HORIZONTAL; 2389 UIElementCreate(sizeof(UIElement), &scrollBar->e, flags, _UIScrollUpDownMessage, !horizontal ? "Scroll Up" : "Scroll Left")->cp = (void *) (uintptr_t) 0; 2390 UIElementCreate(sizeof(UIElement), &scrollBar->e, flags, _UIScrollThumbMessage, "Scroll Thumb"); 2391 UIElementCreate(sizeof(UIElement), &scrollBar->e, flags, _UIScrollUpDownMessage, !horizontal ? "Scroll Down" : "Scroll Right")->cp = (void *) (uintptr_t) 1; 2392 return scrollBar; 2393 } 2394 2395 bool _UICharIsAlpha(char c) { 2396 return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); 2397 } 2398 2399 bool _UICharIsDigit(char c) { 2400 return c >= '0' && c <= '9'; 2401 } 2402 2403 bool _UICharIsAlphaOrDigitOrUnderscore(char c) { 2404 return _UICharIsAlpha(c) || _UICharIsDigit(c) || c == '_'; 2405 } 2406 2407 int UICodeHitTest(UICode *code, int x, int y) { 2408 x -= code->e.bounds.l; 2409 2410 if (x < 0 || x >= UI_RECT_WIDTH(code->e.bounds) - UI_SIZE_SCROLL_BAR * code->e.window->scale) { 2411 return 0; 2412 } 2413 2414 y -= code->e.bounds.t - code->vScroll->position; 2415 2416 UIFont *previousFont = UIFontActivate(code->font); 2417 int lineHeight = UIMeasureStringHeight(); 2418 bool inMargin = x < UI_SIZE_CODE_MARGIN + UI_SIZE_CODE_MARGIN_GAP / 2 && (~code->e.flags & UI_CODE_NO_MARGIN); 2419 UIFontActivate(previousFont); 2420 2421 if (y < 0 || y >= lineHeight * code->lineCount) { 2422 return 0; 2423 } 2424 2425 int line = y / lineHeight + 1; 2426 return inMargin ? -line : line; 2427 } 2428 2429 int UIDrawStringHighlighted(UIPainter *painter, UIRectangle lineBounds, const char *string, ptrdiff_t bytes, int tabSize) { 2430 if (bytes == -1) bytes = _UIStringLength(string); 2431 if (bytes > 10000) bytes = 10000; 2432 2433 uint32_t colors[] = { 2434 ui.theme.codeDefault, 2435 ui.theme.codeComment, 2436 ui.theme.codeString, 2437 ui.theme.codeNumber, 2438 ui.theme.codeOperator, 2439 ui.theme.codePreprocessor, 2440 }; 2441 2442 int x = lineBounds.l; 2443 int y = (lineBounds.t + lineBounds.b - UIMeasureStringHeight()) / 2; 2444 int ti = 0; 2445 int lexState = 0; 2446 bool inComment = false, inIdentifier = false, inChar = false, startedString = false; 2447 uint32_t last = 0; 2448 2449 while (bytes--) { 2450 char c = *string++; 2451 2452 last <<= 8; 2453 last |= c; 2454 2455 if (lexState == 4) { 2456 lexState = 0; 2457 } else if (lexState == 1) { 2458 if ((last & 0xFF0000) == ('*' << 16) && (last & 0xFF00) == ('/' << 8) && inComment) { 2459 lexState = 0, inComment = false; 2460 } 2461 } else if (lexState == 3) { 2462 if (!_UICharIsAlpha(c) && !_UICharIsDigit(c)) { 2463 lexState = 0; 2464 } 2465 } else if (lexState == 2) { 2466 if (!startedString) { 2467 if (!inChar && ((last >> 8) & 0xFF) == '"' && ((last >> 16) & 0xFF) != '\\') { 2468 lexState = 0; 2469 } else if (inChar && ((last >> 8) & 0xFF) == '\'' && ((last >> 16) & 0xFF) != '\\') { 2470 lexState = 0; 2471 } 2472 } 2473 2474 startedString = false; 2475 } 2476 2477 if (lexState == 0) { 2478 if (c == '#') { 2479 lexState = 5; 2480 } else if (c == '/' && *string == '/') { 2481 lexState = 1; 2482 } else if (c == '/' && *string == '*') { 2483 lexState = 1, inComment = true; 2484 } else if (c == '"') { 2485 lexState = 2; 2486 inChar = false; 2487 startedString = true; 2488 } else if (c == '\'') { 2489 lexState = 2; 2490 inChar = true; 2491 startedString = true; 2492 } else if (_UICharIsDigit(c) && !inIdentifier) { 2493 lexState = 3; 2494 } else if (!_UICharIsAlpha(c) && !_UICharIsDigit(c)) { 2495 lexState = 4; 2496 inIdentifier = false; 2497 } else { 2498 inIdentifier = true; 2499 } 2500 } 2501 2502 if (c == '\t') { 2503 x += ui.activeFont->glyphWidth, ti++; 2504 while (ti % tabSize) x += ui.activeFont->glyphWidth, ti++; 2505 } else { 2506 UIDrawGlyph(painter, x, y, c, colors[lexState]); 2507 x += ui.activeFont->glyphWidth, ti++; 2508 } 2509 } 2510 2511 return x; 2512 } 2513 2514 int _UICodeMessage(UIElement *element, UIMessage message, int di, void *dp) { 2515 UICode *code = (UICode *) element; 2516 2517 if (message == UI_MSG_LAYOUT) { 2518 UIFont *previousFont = UIFontActivate(code->font); 2519 2520 if (code->moveScrollToFocusNextLayout) { 2521 code->vScroll->position = (code->focused + 0.5) * UIMeasureStringHeight() - UI_RECT_HEIGHT(code->e.bounds) / 2; 2522 } 2523 2524 UIRectangle scrollBarBounds = element->bounds; 2525 scrollBarBounds.l = scrollBarBounds.r - UI_SIZE_SCROLL_BAR * code->e.window->scale; 2526 code->vScroll->maximum = code->lineCount * UIMeasureStringHeight(); 2527 code->vScroll->page = UI_RECT_HEIGHT(element->bounds); 2528 UIFontActivate(previousFont); 2529 UIElementMove(&code->vScroll->e, scrollBarBounds, true); 2530 } else if (message == UI_MSG_PAINT) { 2531 UIFont *previousFont = UIFontActivate(code->font); 2532 2533 UIPainter *painter = (UIPainter *) dp; 2534 UIRectangle lineBounds = element->bounds; 2535 lineBounds.r -= UI_SIZE_SCROLL_BAR * code->e.window->scale; 2536 2537 if (~code->e.flags & UI_CODE_NO_MARGIN) { 2538 lineBounds.l += UI_SIZE_CODE_MARGIN + UI_SIZE_CODE_MARGIN_GAP; 2539 } 2540 2541 int lineHeight = UIMeasureStringHeight(); 2542 lineBounds.t -= (int64_t) code->vScroll->position % lineHeight; 2543 2544 UIDrawBlock(painter, element->bounds, ui.theme.codeBackground); 2545 2546 for (int i = code->vScroll->position / lineHeight; i < code->lineCount; i++) { 2547 if (lineBounds.t > element->clip.b) { 2548 break; 2549 } 2550 2551 lineBounds.b = lineBounds.t + lineHeight; 2552 2553 if (~code->e.flags & UI_CODE_NO_MARGIN) { 2554 char string[16]; 2555 int p = 16; 2556 int lineNumber = i + 1; 2557 2558 while (lineNumber) { 2559 string[--p] = (lineNumber % 10) + '0'; 2560 lineNumber /= 10; 2561 } 2562 2563 UIRectangle marginBounds = lineBounds; 2564 marginBounds.r = marginBounds.l - UI_SIZE_CODE_MARGIN_GAP; 2565 marginBounds.l -= UI_SIZE_CODE_MARGIN + UI_SIZE_CODE_MARGIN_GAP; 2566 2567 uint32_t marginColor = UIElementMessage(element, UI_MSG_CODE_GET_MARGIN_COLOR, i + 1, 0); 2568 2569 if (marginColor) { 2570 UIDrawBlock(painter, marginBounds, marginColor); 2571 } 2572 2573 UIDrawString(painter, marginBounds, string + p, 16 - p, ui.theme.codeDefault, UI_ALIGN_RIGHT, NULL); 2574 } 2575 2576 if (code->focused == i) { 2577 UIDrawBlock(painter, lineBounds, ui.theme.codeFocused); 2578 } 2579 2580 int x = UIDrawStringHighlighted(painter, lineBounds, code->content + code->lines[i].offset, code->lines[i].bytes, code->tabSize); 2581 int y = (lineBounds.t + lineBounds.b - UIMeasureStringHeight()) / 2; 2582 2583 UICodeDecorateLine m = { 0 }; 2584 m.x = x, m.y = y, m.bounds = lineBounds, m.index = i + 1, m.painter = painter; 2585 UIElementMessage(element, UI_MSG_CODE_DECORATE_LINE, 0, &m); 2586 2587 lineBounds.t += lineHeight; 2588 } 2589 2590 UIFontActivate(previousFont); 2591 } else if (message == UI_MSG_SCROLLED) { 2592 code->moveScrollToFocusNextLayout = false; 2593 UIElementRefresh(element); 2594 } else if (message == UI_MSG_MOUSE_WHEEL) { 2595 return UIElementMessage(&code->vScroll->e, message, di, dp); 2596 } else if (message == UI_MSG_GET_CURSOR) { 2597 if (UICodeHitTest(code, element->window->cursorX, element->window->cursorY) < 0) { 2598 return UI_CURSOR_FLIPPED_ARROW; 2599 } 2600 } else if (message == UI_MSG_DESTROY) { 2601 UI_FREE(code->content); 2602 UI_FREE(code->lines); 2603 } 2604 2605 return 0; 2606 } 2607 2608 void UICodeFocusLine(UICode *code, int index) { 2609 code->focused = index - 1; 2610 code->moveScrollToFocusNextLayout = true; 2611 } 2612 2613 void UICodeInsertContent(UICode *code, const char *content, ptrdiff_t byteCount, bool replace) { 2614 UIFont *previousFont = UIFontActivate(code->font); 2615 2616 if (byteCount == -1) { 2617 byteCount = _UIStringLength(content); 2618 } 2619 2620 if (byteCount > 1000000000) { 2621 byteCount = 1000000000; 2622 } 2623 2624 if (replace) { 2625 UI_FREE(code->content); 2626 UI_FREE(code->lines); 2627 code->content = NULL; 2628 code->lines = NULL; 2629 code->contentBytes = 0; 2630 code->lineCount = 0; 2631 } 2632 2633 code->content = (char *) UI_REALLOC(code->content, code->contentBytes + byteCount); 2634 2635 if (!byteCount) { 2636 return; 2637 } 2638 2639 int lineCount = content[byteCount - 1] != '\n'; 2640 2641 for (int i = 0; i < byteCount; i++) { 2642 code->content[i + code->contentBytes] = content[i]; 2643 2644 if (content[i] == '\n') { 2645 lineCount++; 2646 } 2647 } 2648 2649 code->lines = (UICodeLine *) UI_REALLOC(code->lines, sizeof(UICodeLine) * (code->lineCount + lineCount)); 2650 int offset = 0, lineIndex = 0; 2651 2652 for (intptr_t i = 0; i <= byteCount && lineIndex < lineCount; i++) { 2653 if (content[i] == '\n' || i == byteCount) { 2654 UICodeLine line = { 0 }; 2655 line.offset = offset + code->contentBytes; 2656 line.bytes = i - offset; 2657 code->lines[code->lineCount + lineIndex] = line; 2658 lineIndex++; 2659 offset = i + 1; 2660 } 2661 } 2662 2663 code->lineCount += lineCount; 2664 code->contentBytes += byteCount; 2665 2666 if (!replace) { 2667 code->vScroll->position = code->lineCount * UIMeasureStringHeight(); 2668 } 2669 2670 UIFontActivate(previousFont); 2671 } 2672 2673 UICode *UICodeCreate(UIElement *parent, uint32_t flags) { 2674 UICode *code = (UICode *) UIElementCreate(sizeof(UICode), parent, flags, _UICodeMessage, "Code"); 2675 code->font = ui.activeFont; 2676 code->vScroll = UIScrollBarCreate(&code->e, 0); 2677 code->focused = -1; 2678 code->tabSize = 4; 2679 return code; 2680 } 2681 2682 int _UIGaugeMessage(UIElement *element, UIMessage message, int di, void *dp) { 2683 UIGauge *gauge = (UIGauge *) element; 2684 2685 if (message == UI_MSG_GET_HEIGHT) { 2686 return UI_SIZE_GAUGE_HEIGHT * element->window->scale; 2687 } else if (message == UI_MSG_GET_WIDTH) { 2688 return UI_SIZE_GAUGE_WIDTH * element->window->scale; 2689 } else if (message == UI_MSG_PAINT) { 2690 UIPainter *painter = (UIPainter *) dp; 2691 UIDrawRectangle(painter, element->bounds, ui.theme.buttonNormal, ui.theme.border, UI_RECT_1(1)); 2692 UIRectangle filled = UIRectangleAdd(element->bounds, UI_RECT_1I(1)); 2693 filled.r = filled.l + UI_RECT_WIDTH(filled) * gauge->position; 2694 UIDrawBlock(painter, filled, ui.theme.selected); 2695 } 2696 2697 return 0; 2698 } 2699 2700 UIGauge *UIGaugeCreate(UIElement *parent, uint32_t flags) { 2701 return (UIGauge *) UIElementCreate(sizeof(UIGauge), parent, flags, _UIGaugeMessage, "Gauge"); 2702 } 2703 2704 int _UISliderMessage(UIElement *element, UIMessage message, int di, void *dp) { 2705 UISlider *slider = (UISlider *) element; 2706 2707 if (message == UI_MSG_GET_HEIGHT) { 2708 return UI_SIZE_SLIDER_HEIGHT * element->window->scale; 2709 } else if (message == UI_MSG_GET_WIDTH) { 2710 return UI_SIZE_SLIDER_WIDTH * element->window->scale; 2711 } else if (message == UI_MSG_PAINT) { 2712 UIPainter *painter = (UIPainter *) dp; 2713 UIRectangle bounds = element->bounds; 2714 int centerY = (bounds.t + bounds.b) / 2; 2715 int trackSize = UI_SIZE_SLIDER_TRACK * element->window->scale; 2716 int thumbSize = UI_SIZE_SLIDER_THUMB * element->window->scale; 2717 int thumbPosition = (UI_RECT_WIDTH(bounds) - thumbSize) * slider->position; 2718 UIRectangle track = UI_RECT_4(bounds.l, bounds.r, centerY - (trackSize + 1) / 2, centerY + trackSize / 2); 2719 UIDrawRectangle(painter, track, ui.theme.buttonNormal, ui.theme.border, UI_RECT_1(1)); 2720 bool pressed = element == element->window->pressed; 2721 bool hovered = element == element->window->hovered; 2722 bool disabled = element->flags & UI_ELEMENT_DISABLED; 2723 uint32_t color = disabled ? ui.theme.buttonDisabled : pressed ? ui.theme.buttonPressed : hovered ? ui.theme.buttonHovered : ui.theme.buttonNormal; 2724 UIRectangle thumb = UI_RECT_4(bounds.l + thumbPosition, bounds.l + thumbPosition + thumbSize, centerY - (thumbSize + 1) / 2, centerY + thumbSize / 2); 2725 UIDrawRectangle(painter, thumb, color, ui.theme.border, UI_RECT_1(1)); 2726 } else if (message == UI_MSG_LEFT_DOWN || (message == UI_MSG_MOUSE_DRAG && element->window->pressedButton == 1)) { 2727 UIRectangle bounds = element->bounds; 2728 int thumbSize = UI_SIZE_SLIDER_THUMB * element->window->scale; 2729 slider->position = (float) (element->window->cursorX - thumbSize / 2 - bounds.l) / (UI_RECT_WIDTH(bounds) - thumbSize); 2730 if (slider->steps > 1) slider->position = (int) (slider->position * (slider->steps - 1) + 0.5f) / (float) (slider->steps - 1); 2731 if (slider->position < 0) slider->position = 0; 2732 if (slider->position > 1) slider->position = 1; 2733 UIElementMessage(element, UI_MSG_VALUE_CHANGED, 0, 0); 2734 UIElementRepaint(element, NULL); 2735 } else if (message == UI_MSG_UPDATE) { 2736 UIElementRepaint(element, NULL); 2737 } 2738 2739 return 0; 2740 } 2741 2742 UISlider *UISliderCreate(UIElement *parent, uint32_t flags) { 2743 return (UISlider *) UIElementCreate(sizeof(UISlider), parent, flags, _UISliderMessage, "Slider"); 2744 } 2745 2746 int UITableHitTest(UITable *table, int x, int y) { 2747 x -= table->e.bounds.l; 2748 2749 if (x < 0 || x >= UI_RECT_WIDTH(table->e.bounds) - UI_SIZE_SCROLL_BAR * table->e.window->scale) { 2750 return -1; 2751 } 2752 2753 y -= (table->e.bounds.t + UI_SIZE_TABLE_HEADER * table->e.window->scale) - table->vScroll->position; 2754 2755 int rowHeight = UI_SIZE_TABLE_ROW * table->e.window->scale; 2756 2757 if (y < 0 || y >= rowHeight * table->itemCount) { 2758 return -1; 2759 } 2760 2761 return y / rowHeight; 2762 } 2763 2764 int UITableHeaderHitTest(UITable *table, int x, int y) { 2765 if (!table->columnCount) return -1; 2766 UIRectangle header = table->e.bounds; 2767 header.b = header.t + UI_SIZE_TABLE_HEADER * table->e.window->scale; 2768 header.l += UI_SIZE_TABLE_COLUMN_GAP * table->e.window->scale; 2769 int position = 0, index = 0; 2770 2771 while (true) { 2772 int end = position; 2773 for (; table->columns[end] != '\t' && table->columns[end]; end++); 2774 header.r = header.l + table->columnWidths[index]; 2775 if (UIRectangleContains(header, x, y)) return index; 2776 header.l += table->columnWidths[index] + UI_SIZE_TABLE_COLUMN_GAP * table->e.window->scale; 2777 if (table->columns[end] != '\t') break; 2778 position = end + 1, index++; 2779 } 2780 2781 return -1; 2782 } 2783 2784 bool UITableEnsureVisible(UITable *table, int index) { 2785 int rowHeight = UI_SIZE_TABLE_ROW * table->e.window->scale; 2786 int y = index * rowHeight; 2787 y -= table->vScroll->position; 2788 int height = UI_RECT_HEIGHT(table->e.bounds) - UI_SIZE_TABLE_HEADER * table->e.window->scale - rowHeight; 2789 2790 if (y < 0) { 2791 table->vScroll->position += y; 2792 UIElementRefresh(&table->e); 2793 return true; 2794 } else if (y > height) { 2795 table->vScroll->position -= height - y; 2796 UIElementRefresh(&table->e); 2797 return true; 2798 } else { 2799 return false; 2800 } 2801 } 2802 2803 void UITableResizeColumns(UITable *table) { 2804 int position = 0; 2805 int count = 0; 2806 2807 while (true) { 2808 int end = position; 2809 for (; table->columns[end] != '\t' && table->columns[end]; end++); 2810 count++; 2811 if (table->columns[end] == '\t') position = end + 1; 2812 else break; 2813 } 2814 2815 UI_FREE(table->columnWidths); 2816 table->columnWidths = (int *) UI_MALLOC(count * sizeof(int)); 2817 table->columnCount = count; 2818 2819 position = 0; 2820 2821 char buffer[256]; 2822 UITableGetItem m = { 0 }; 2823 m.buffer = buffer; 2824 m.bufferBytes = sizeof(buffer); 2825 2826 while (true) { 2827 int end = position; 2828 for (; table->columns[end] != '\t' && table->columns[end]; end++); 2829 2830 int longest = UIMeasureStringWidth(table->columns + position, end - position); 2831 2832 for (int i = 0; i < table->itemCount; i++) { 2833 m.index = i; 2834 int bytes = UIElementMessage(&table->e, UI_MSG_TABLE_GET_ITEM, 0, &m); 2835 int width = UIMeasureStringWidth(buffer, bytes); 2836 2837 if (width > longest) { 2838 longest = width; 2839 } 2840 } 2841 2842 table->columnWidths[m.column] = longest; 2843 m.column++; 2844 if (table->columns[end] == '\t') position = end + 1; 2845 else break; 2846 } 2847 } 2848 2849 int _UITableMessage(UIElement *element, UIMessage message, int di, void *dp) { 2850 UITable *table = (UITable *) element; 2851 2852 if (message == UI_MSG_PAINT) { 2853 UIPainter *painter = (UIPainter *) dp; 2854 UIRectangle bounds = element->bounds; 2855 bounds.r -= UI_SIZE_SCROLL_BAR * element->window->scale; 2856 UIDrawBlock(painter, bounds, ui.theme.panel2); 2857 char buffer[256]; 2858 UIRectangle row = bounds; 2859 int rowHeight = UI_SIZE_TABLE_ROW * element->window->scale; 2860 UITableGetItem m = { 0 }; 2861 m.buffer = buffer; 2862 m.bufferBytes = sizeof(buffer); 2863 row.t += UI_SIZE_TABLE_HEADER * table->e.window->scale; 2864 row.t -= (int64_t) table->vScroll->position % rowHeight; 2865 int hovered = UITableHitTest(table, element->window->cursorX, element->window->cursorY); 2866 2867 for (int i = table->vScroll->position / rowHeight; i < table->itemCount; i++) { 2868 if (row.t > element->clip.b) { 2869 break; 2870 } 2871 2872 row.b = row.t + rowHeight; 2873 m.index = i; 2874 m.isSelected = false; 2875 m.column = 0; 2876 int bytes = UIElementMessage(element, UI_MSG_TABLE_GET_ITEM, 0, &m); 2877 uint32_t textColor = ui.theme.text; 2878 2879 if (m.isSelected) { 2880 UIDrawBlock(painter, row, ui.theme.selected); 2881 textColor = ui.theme.textSelected; 2882 } else if (hovered == i) { 2883 UIDrawBlock(painter, row, ui.theme.buttonHovered); 2884 } 2885 2886 UIRectangle cell = row; 2887 cell.l += UI_SIZE_TABLE_COLUMN_GAP * table->e.window->scale; 2888 2889 for (int j = 0; j < table->columnCount; j++) { 2890 if (j) { 2891 m.column = j; 2892 bytes = UIElementMessage(element, UI_MSG_TABLE_GET_ITEM, 0, &m); 2893 } 2894 2895 cell.r = cell.l + table->columnWidths[j]; 2896 if ((size_t) bytes > m.bufferBytes && bytes > 0) bytes = m.bufferBytes; 2897 UIDrawString(painter, cell, buffer, bytes, textColor, UI_ALIGN_LEFT, NULL); 2898 cell.l += table->columnWidths[j] + UI_SIZE_TABLE_COLUMN_GAP * table->e.window->scale; 2899 } 2900 2901 row.t += rowHeight; 2902 } 2903 2904 UIRectangle header = bounds; 2905 header.b = header.t + UI_SIZE_TABLE_HEADER * table->e.window->scale; 2906 UIDrawRectangle(painter, header, ui.theme.panel1, ui.theme.border, UI_RECT_4(0, 0, 0, 1)); 2907 header.l += UI_SIZE_TABLE_COLUMN_GAP * table->e.window->scale; 2908 2909 int position = 0; 2910 int index = 0; 2911 2912 if (table->columnCount) { 2913 while (true) { 2914 int end = position; 2915 for (; table->columns[end] != '\t' && table->columns[end]; end++); 2916 2917 header.r = header.l + table->columnWidths[index]; 2918 UIDrawString(painter, header, table->columns + position, end - position, ui.theme.text, UI_ALIGN_LEFT, NULL); 2919 if (index == table->columnHighlight) UIDrawInvert(painter, header); 2920 header.l += table->columnWidths[index] + UI_SIZE_TABLE_COLUMN_GAP * table->e.window->scale; 2921 2922 if (table->columns[end] == '\t') { 2923 position = end + 1; 2924 index++; 2925 } else { 2926 break; 2927 } 2928 } 2929 } 2930 } else if (message == UI_MSG_LAYOUT) { 2931 UIRectangle scrollBarBounds = element->bounds; 2932 scrollBarBounds.l = scrollBarBounds.r - UI_SIZE_SCROLL_BAR * element->window->scale; 2933 table->vScroll->maximum = table->itemCount * UI_SIZE_TABLE_ROW * element->window->scale; 2934 table->vScroll->page = UI_RECT_HEIGHT(element->bounds) - UI_SIZE_TABLE_HEADER * table->e.window->scale; 2935 UIElementMove(&table->vScroll->e, scrollBarBounds, true); 2936 } else if (message == UI_MSG_MOUSE_MOVE || message == UI_MSG_UPDATE) { 2937 UIElementRepaint(element, NULL); 2938 } else if (message == UI_MSG_SCROLLED) { 2939 UIElementRefresh(element); 2940 } else if (message == UI_MSG_MOUSE_WHEEL) { 2941 return UIElementMessage(&table->vScroll->e, message, di, dp); 2942 } else if (message == UI_MSG_DESTROY) { 2943 UI_FREE(table->columns); 2944 UI_FREE(table->columnWidths); 2945 } 2946 2947 return 0; 2948 } 2949 2950 UITable *UITableCreate(UIElement *parent, uint32_t flags, const char *columns) { 2951 UITable *table = (UITable *) UIElementCreate(sizeof(UITable), parent, flags, _UITableMessage, "Table"); 2952 table->vScroll = UIScrollBarCreate(&table->e, 0); 2953 table->columns = UIStringCopy(columns, -1); 2954 table->columnHighlight = -1; 2955 return table; 2956 } 2957 2958 void UITextboxReplace(UITextbox *textbox, const char *text, ptrdiff_t bytes, bool sendChangedMessage) { 2959 if (bytes == -1) { 2960 bytes = _UIStringLength(text); 2961 } 2962 2963 int deleteFrom = textbox->carets[0], deleteTo = textbox->carets[1]; 2964 2965 if (deleteFrom > deleteTo) { 2966 UI_SWAP(int, deleteFrom, deleteTo); 2967 } 2968 2969 for (int i = deleteTo; i < textbox->bytes; i++) { 2970 textbox->string[i - deleteTo + deleteFrom] = textbox->string[i]; 2971 } 2972 2973 textbox->bytes -= deleteTo - deleteFrom; 2974 textbox->carets[0] = textbox->carets[1] = deleteFrom; 2975 2976 textbox->string = (char *) UI_REALLOC(textbox->string, textbox->bytes + bytes); 2977 2978 for (int i = textbox->bytes + bytes - 1; i >= textbox->carets[0] + bytes; i--) { 2979 textbox->string[i] = textbox->string[i - bytes]; 2980 } 2981 2982 for (int i = textbox->carets[0]; i < textbox->carets[0] + bytes; i++) { 2983 textbox->string[i] = text[i - textbox->carets[0]]; 2984 } 2985 2986 textbox->bytes += bytes; 2987 textbox->carets[0] += bytes; 2988 textbox->carets[1] = textbox->carets[0]; 2989 2990 if (sendChangedMessage) { 2991 UIElementMessage(&textbox->e, UI_MSG_VALUE_CHANGED, 0, 0); 2992 } 2993 2994 textbox->e.window->textboxModifiedFlag = true; 2995 } 2996 2997 void UITextboxClear(UITextbox *textbox, bool sendChangedMessage) { 2998 textbox->carets[1] = 0; 2999 textbox->carets[0] = textbox->bytes; 3000 UITextboxReplace(textbox, "", 0, sendChangedMessage); 3001 } 3002 3003 void UITextboxMoveCaret(UITextbox *textbox, bool backward, bool word) { 3004 while (true) { 3005 if (textbox->carets[0] > 0 && backward) { 3006 textbox->carets[0]--; 3007 } else if (textbox->carets[0] < textbox->bytes && !backward) { 3008 textbox->carets[0]++; 3009 } else { 3010 return; 3011 } 3012 3013 if (!word) { 3014 return; 3015 } else if (textbox->carets[0] != textbox->bytes && textbox->carets[0] != 0) { 3016 char c1 = textbox->string[textbox->carets[0] - 1]; 3017 char c2 = textbox->string[textbox->carets[0]]; 3018 3019 if (_UICharIsAlphaOrDigitOrUnderscore(c1) != _UICharIsAlphaOrDigitOrUnderscore(c2)) { 3020 return; 3021 } 3022 } 3023 } 3024 } 3025 3026 int _UITextboxMessage(UIElement *element, UIMessage message, int di, void *dp) { 3027 UITextbox *textbox = (UITextbox *) element; 3028 3029 if (message == UI_MSG_GET_HEIGHT) { 3030 return UI_SIZE_TEXTBOX_HEIGHT * element->window->scale; 3031 } else if (message == UI_MSG_GET_WIDTH) { 3032 return UI_SIZE_TEXTBOX_WIDTH * element->window->scale; 3033 } else if (message == UI_MSG_PAINT) { 3034 int scaledMargin = UI_SIZE_TEXTBOX_MARGIN * element->window->scale; 3035 int totalWidth = UIMeasureStringWidth(textbox->string, textbox->bytes) + scaledMargin * 2; 3036 UIRectangle textBounds = UIRectangleAdd(element->bounds, UI_RECT_1I(scaledMargin)); 3037 3038 if (textbox->scroll > totalWidth - UI_RECT_WIDTH(textBounds)) { 3039 textbox->scroll = totalWidth - UI_RECT_WIDTH(textBounds); 3040 } 3041 3042 if (textbox->scroll < 0) { 3043 textbox->scroll = 0; 3044 } 3045 3046 int caretX = UIMeasureStringWidth(textbox->string, textbox->carets[0]) - textbox->scroll; 3047 3048 if (caretX < 0) { 3049 textbox->scroll = caretX + textbox->scroll; 3050 } else if (caretX > UI_RECT_WIDTH(textBounds)) { 3051 textbox->scroll = caretX - UI_RECT_WIDTH(textBounds) + textbox->scroll + 1; 3052 } 3053 3054 UIPainter *painter = (UIPainter *) dp; 3055 bool focused = element->window->focused == element; 3056 bool disabled = element->flags & UI_ELEMENT_DISABLED; 3057 UIDrawRectangle(painter, element->bounds, 3058 disabled ? ui.theme.buttonDisabled : focused ? ui.theme.textboxFocused : ui.theme.textboxNormal, 3059 ui.theme.border, UI_RECT_1(1)); 3060 #ifdef __cplusplus 3061 UIStringSelection selection = {}; 3062 #else 3063 UIStringSelection selection = { 0 }; 3064 #endif 3065 selection.carets[0] = textbox->carets[0]; 3066 selection.carets[1] = textbox->carets[1]; 3067 selection.colorBackground = ui.theme.selected; 3068 selection.colorText = ui.theme.textSelected; 3069 textBounds.l -= textbox->scroll; 3070 UIDrawString(painter, textBounds, textbox->string, textbox->bytes, 3071 disabled ? ui.theme.textDisabled : ui.theme.text, UI_ALIGN_LEFT, focused ? &selection : NULL); 3072 } else if (message == UI_MSG_GET_CURSOR) { 3073 return UI_CURSOR_TEXT; 3074 } else if (message == UI_MSG_LEFT_DOWN) { 3075 UIElementFocus(element); 3076 } else if (message == UI_MSG_UPDATE) { 3077 UIElementRepaint(element, NULL); 3078 } else if (message == UI_MSG_DESTROY) { 3079 UI_FREE(textbox->string); 3080 } else if (message == UI_MSG_KEY_TYPED) { 3081 UIKeyTyped *m = (UIKeyTyped *) dp; 3082 bool handled = true; 3083 3084 if (textbox->rejectNextKey) { 3085 textbox->rejectNextKey = false; 3086 handled = false; 3087 } else if (m->code == UI_KEYCODE_BACKSPACE || m->code == UI_KEYCODE_DELETE) { 3088 if (textbox->carets[0] == textbox->carets[1]) { 3089 UITextboxMoveCaret(textbox, m->code == UI_KEYCODE_BACKSPACE, element->window->ctrl); 3090 } 3091 3092 UITextboxReplace(textbox, NULL, 0, true); 3093 } else if (m->code == UI_KEYCODE_LEFT || m->code == UI_KEYCODE_RIGHT) { 3094 UITextboxMoveCaret(textbox, m->code == UI_KEYCODE_LEFT, element->window->ctrl); 3095 3096 if (!element->window->shift) { 3097 textbox->carets[1] = textbox->carets[0]; 3098 } 3099 } else if (m->code == UI_KEYCODE_HOME || m->code == UI_KEYCODE_END) { 3100 if (m->code == UI_KEYCODE_HOME) { 3101 textbox->carets[0] = 0; 3102 } else { 3103 textbox->carets[0] = textbox->bytes; 3104 } 3105 3106 if (!element->window->shift) { 3107 textbox->carets[1] = textbox->carets[0]; 3108 } 3109 } else if (m->code == UI_KEYCODE_LETTER('A') && element->window->ctrl) { 3110 textbox->carets[1] = 0; 3111 textbox->carets[0] = textbox->bytes; 3112 } else if (m->textBytes && !element->window->alt && !element->window->ctrl && m->text[0] >= 0x20) { 3113 UITextboxReplace(textbox, m->text, m->textBytes, true); 3114 } else if ((m->code == UI_KEYCODE_LETTER('C') || m->code == UI_KEYCODE_LETTER('X') || m->code == UI_KEYCODE_INSERT) 3115 && element->window->ctrl && !element->window->alt && !element->window->shift) { 3116 int to = textbox->carets[0] > textbox->carets[1] ? textbox->carets[0] : textbox->carets[1]; 3117 int from = textbox->carets[0] < textbox->carets[1] ? textbox->carets[0] : textbox->carets[1]; 3118 3119 if (from != to) { 3120 char *pasteText = (char *) UI_CALLOC(to - from + 1); 3121 for (int i = from; i < to; i++) pasteText[i - from] = textbox->string[i]; 3122 _UIClipboardWriteText(element->window, pasteText); 3123 } 3124 3125 if (m->code == UI_KEYCODE_LETTER('X')) { 3126 UITextboxReplace(textbox, NULL, 0, true); 3127 } 3128 } else if ((m->code == UI_KEYCODE_LETTER('V') && element->window->ctrl && !element->window->alt && !element->window->shift) 3129 || (m->code == UI_KEYCODE_INSERT && !element->window->ctrl && !element->window->alt && element->window->shift)) { 3130 size_t bytes; 3131 char *text = _UIClipboardReadTextStart(element->window, &bytes); 3132 if (text) UITextboxReplace(textbox, text, bytes, true); 3133 _UIClipboardReadTextEnd(element->window, text); 3134 } else { 3135 handled = false; 3136 } 3137 3138 if (handled) { 3139 UIElementRepaint(element, NULL); 3140 return 1; 3141 } 3142 } 3143 3144 return 0; 3145 } 3146 3147 UITextbox *UITextboxCreate(UIElement *parent, uint32_t flags) { 3148 return (UITextbox *) UIElementCreate(sizeof(UITextbox), parent, flags | UI_ELEMENT_TAB_STOP, _UITextboxMessage, "Textbox"); 3149 } 3150 3151 int _UIColorCircleMessage(UIElement *element, UIMessage message, int di, void *dp) { 3152 UIColorPicker *colorPicker = (UIColorPicker *) element->parent; 3153 3154 if (message == UI_MSG_PAINT) { 3155 UIPainter *painter = (UIPainter *) dp; 3156 3157 int startY = element->bounds.t, endY = element->bounds.b; 3158 int startX = element->bounds.l, endX = element->bounds.r; 3159 int size = endY - startY; 3160 3161 for (int i = startY; i < endY; i++) { 3162 uint32_t *out = painter->bits + i * painter->width + startX; 3163 int j = startX; 3164 float y0 = i - startY - size / 2, x0 = -size / 2; 3165 float angle = _UIArcTan2Float((i - startY) * 2.0f / size - 1, -1); 3166 3167 do { 3168 float distanceFromCenterSquared = x0 * x0 + y0 * y0; 3169 float hue = (angle + 3.14159f) * 0.954929658f; 3170 float saturation = _UISquareRootFloat(distanceFromCenterSquared * 4.0f / size / size); 3171 3172 if (saturation <= 1 && UIRectangleContains(painter->clip, j, i)) { 3173 UIColorToRGB(hue, saturation, colorPicker->value, out); 3174 *out |= 0xFF000000; 3175 } 3176 3177 out++, j++, x0++; 3178 3179 if (distanceFromCenterSquared) { 3180 angle -= y0 / distanceFromCenterSquared; 3181 } else { 3182 angle = _UIArcTan2Float((i - startY) * 2.0f / size - 1, 0.01f); 3183 } 3184 } while (j < endX); 3185 } 3186 3187 float angle = colorPicker->hue / 0.954929658f - 3.14159f; 3188 float radius = colorPicker->saturation * size / 2; 3189 int cx = (startX + endX) / 2 + radius * _UICosFloat(angle); 3190 int cy = (startY + endY) / 2 + radius * _UISinFloat(angle); 3191 UIDrawInvert(painter, UI_RECT_4(cx - 1, cx + 1, startY, endY)); 3192 UIDrawInvert(painter, UI_RECT_4(startX, endX, cy - 1, cy + 1)); 3193 } else if (message == UI_MSG_GET_CURSOR) { 3194 return UI_CURSOR_CROSS_HAIR; 3195 } else if (message == UI_MSG_LEFT_DOWN || message == UI_MSG_MOUSE_DRAG) { 3196 int startY = element->bounds.t, endY = element->bounds.b, cursorY = element->window->cursorY; 3197 int startX = element->bounds.l, endX = element->bounds.r, cursorX = element->window->cursorX; 3198 int dx = (startX + endX) / 2, dy = (startY + endY) / 2; 3199 int size = endY - startY; 3200 3201 float angle = _UIArcTan2Float((cursorY - startY) * 2.0f / size - 1, (cursorX - startX) * 2.0f / size - 1); 3202 float distanceFromCenterSquared = (cursorX - dx) * (cursorX - dx) + (cursorY - dy) * (cursorY - dy); 3203 colorPicker->hue = (angle + 3.14159f) * 0.954929658f; 3204 colorPicker->saturation = _UISquareRootFloat(distanceFromCenterSquared * 4.0f / size / size);; 3205 if (colorPicker->saturation > 1) colorPicker->saturation = 1; 3206 3207 UIElementMessage(&colorPicker->e, UI_MSG_VALUE_CHANGED, 0, 0); 3208 UIElementRepaint(&colorPicker->e, NULL); 3209 } 3210 3211 return 0; 3212 } 3213 3214 int _UIColorSliderMessage(UIElement *element, UIMessage message, int di, void *dp) { 3215 UIColorPicker *colorPicker = (UIColorPicker *) element->parent; 3216 float opacitySlider = element->flags & 1; 3217 3218 if (message == UI_MSG_PAINT) { 3219 UIPainter *painter = (UIPainter *) dp; 3220 3221 int startY = element->bounds.t, endY = element->bounds.b; 3222 int startX = element->bounds.l, endX = element->bounds.r; 3223 int size = endY - startY; 3224 3225 for (int i = startY; i < endY; i++) { 3226 if (i < painter->clip.t || i >= painter->clip.b) continue; 3227 uint32_t *out = painter->bits + i * painter->width + startX; 3228 int j = element->clip.l; 3229 uint32_t color; 3230 float p = 1.0f - (float) (i - startY) / size; 3231 3232 if (opacitySlider) { 3233 UIColorToRGB(colorPicker->hue, colorPicker->saturation, colorPicker->value, &color); 3234 color = UI_COLOR_FROM_FLOAT(p * (UI_COLOR_RED_F(color) - 0.5f) + 0.5f, 3235 p * (UI_COLOR_GREEN_F(color) - 0.5f) + 0.5f, 3236 p * (UI_COLOR_BLUE_F(color) - 0.5f) + 0.5f); 3237 } else { 3238 UIColorToRGB(colorPicker->hue, colorPicker->saturation, p, &color); 3239 } 3240 3241 color |= 0xFF000000; 3242 3243 do { 3244 *out = color; 3245 out++, j++; 3246 } while (j < element->clip.r); 3247 } 3248 3249 int cy = (size - 1) * (1 - (opacitySlider ? colorPicker->opacity : colorPicker->value)) + startY; 3250 UIDrawInvert(painter, UI_RECT_4(startX, endX, cy - 1, cy + 1)); 3251 } else if (message == UI_MSG_GET_CURSOR) { 3252 return UI_CURSOR_CROSS_HAIR; 3253 } else if (message == UI_MSG_LEFT_DOWN || message == UI_MSG_MOUSE_DRAG) { 3254 int startY = element->bounds.t, endY = element->bounds.b, cursorY = element->window->cursorY; 3255 float *value = opacitySlider ? &colorPicker->opacity : &colorPicker->value; 3256 *value = 1 - (float) (cursorY - startY) / (endY - startY); 3257 if (*value < 0) *value = 0; 3258 if (*value > 1) *value = 1; 3259 UIElementMessage(&colorPicker->e, UI_MSG_VALUE_CHANGED, 0, 0); 3260 UIElementRepaint(&colorPicker->e, NULL); 3261 } 3262 3263 return 0; 3264 } 3265 3266 int _UIColorPickerMessage(UIElement *element, UIMessage message, int di, void *dp) { 3267 bool hasOpacity = element->flags & UI_COLOR_PICKER_HAS_OPACITY; 3268 3269 if (message == UI_MSG_GET_WIDTH) { 3270 return (hasOpacity ? 280 : 240) * element->window->scale; 3271 } else if (message == UI_MSG_GET_HEIGHT) { 3272 return 200 * element->window->scale; 3273 } else if (message == UI_MSG_LAYOUT) { 3274 UIRectangle bounds = element->bounds; 3275 3276 int sliderSize = 35 * element->window->scale; 3277 int gap = 5 * element->window->scale; 3278 3279 if (hasOpacity) { 3280 UIElementMove(element->children, UI_RECT_4(bounds.l, bounds.r - (sliderSize + gap) * 2, bounds.t, bounds.b), false); 3281 UIElementMove(element->children->next, UI_RECT_4(bounds.r - sliderSize * 2 - gap, bounds.r - sliderSize - gap, bounds.t, bounds.b), false); 3282 UIElementMove(element->children->next->next, UI_RECT_4(bounds.r - sliderSize, bounds.r, bounds.t, bounds.b), false); 3283 } else { 3284 UIElementMove(element->children, UI_RECT_4(bounds.l, bounds.r - sliderSize - gap, bounds.t, bounds.b), false); 3285 UIElementMove(element->children->next, UI_RECT_4(bounds.r - sliderSize, bounds.r, bounds.t, bounds.b), false); 3286 } 3287 } 3288 3289 return 0; 3290 } 3291 3292 UIColorPicker *UIColorPickerCreate(UIElement *parent, uint32_t flags) { 3293 UIColorPicker *colorPicker = (UIColorPicker *) UIElementCreate(sizeof(UIColorPicker), parent, flags, _UIColorPickerMessage, "ColorPicker"); 3294 UIElementCreate(sizeof(UIElement), &colorPicker->e, 0, _UIColorCircleMessage, "ColorCircle"); 3295 UIElementCreate(sizeof(UIElement), &colorPicker->e, 0, _UIColorSliderMessage, "ColorSlider"); 3296 3297 if (flags & UI_COLOR_PICKER_HAS_OPACITY) { 3298 UIElementCreate(sizeof(UIElement), &colorPicker->e, 1, _UIColorSliderMessage, "ColorSlider"); 3299 } 3300 3301 return colorPicker; 3302 } 3303 3304 #define UI_MDI_CHILD_CALCULATE_LAYOUT() \ 3305 int titleSize = UI_SIZE_MDI_CHILD_TITLE * element->window->scale; \ 3306 int borderSize = UI_SIZE_MDI_CHILD_BORDER * element->window->scale; \ 3307 UIRectangle title = UIRectangleAdd(element->bounds, UI_RECT_4(borderSize, -borderSize, 0, 0)); \ 3308 title.b = title.t + titleSize; \ 3309 UIRectangle content = UIRectangleAdd(element->bounds, UI_RECT_4(borderSize, -borderSize, titleSize, -borderSize)); 3310 3311 int _UIMDIChildHitTest(UIMDIChild *mdiChild, int x, int y) { 3312 UIElement *element = &mdiChild->e; 3313 UI_MDI_CHILD_CALCULATE_LAYOUT(); 3314 int cornerSize = UI_SIZE_MDI_CHILD_CORNER * element->window->scale; 3315 if (!UIRectangleContains(element->bounds, x, y) || UIRectangleContains(content, x, y)) return -1; 3316 else if (x < element->bounds.l + cornerSize && y < element->bounds.t + cornerSize) return 0b1010; 3317 else if (x > element->bounds.r - cornerSize && y < element->bounds.t + cornerSize) return 0b0110; 3318 else if (x < element->bounds.l + cornerSize && y > element->bounds.b - cornerSize) return 0b1001; 3319 else if (x > element->bounds.r - cornerSize && y > element->bounds.b - cornerSize) return 0b0101; 3320 else if (x < element->bounds.l + borderSize) return 0b1000; 3321 else if (x > element->bounds.r - borderSize) return 0b0100; 3322 else if (y < element->bounds.t + borderSize) return 0b0010; 3323 else if (y > element->bounds.b - borderSize) return 0b0001; 3324 else if (UIRectangleContains(title, x, y)) return 0b1111; 3325 else return -1; 3326 } 3327 3328 void _UIMDIChildCloseButton(void *_child) { 3329 UIElement *child = (UIElement *) _child; 3330 3331 if (!UIElementMessage(child, UI_MSG_WINDOW_CLOSE, 0, 0)) { 3332 UIElementDestroy(child); 3333 UIElementRefresh(child->parent); 3334 } 3335 } 3336 3337 int _UIMDIChildMessage(UIElement *element, UIMessage message, int di, void *dp) { 3338 UIMDIChild *mdiChild = (UIMDIChild *) element; 3339 3340 if (message == UI_MSG_PAINT) { 3341 UI_MDI_CHILD_CALCULATE_LAYOUT(); 3342 UIPainter *painter = (UIPainter *) dp; 3343 UIRectangle borders = UI_RECT_4(borderSize, borderSize, titleSize, borderSize); 3344 UIDrawBorder(painter, element->bounds, ui.theme.buttonNormal, borders); 3345 UIDrawBorder(painter, element->bounds, ui.theme.border, UI_RECT_1((int) element->window->scale)); 3346 UIDrawBorder(painter, UIRectangleAdd(content, UI_RECT_1I(-1)), ui.theme.border, UI_RECT_1((int) element->window->scale)); 3347 UIDrawString(painter, title, mdiChild->title, mdiChild->titleBytes, ui.theme.text, UI_ALIGN_LEFT, NULL); 3348 } else if (message == UI_MSG_GET_WIDTH) { 3349 UIElement *child = element->children; 3350 while (child && child->next) child = child->next; 3351 int width = 2 * UI_SIZE_MDI_CHILD_BORDER; 3352 width += (child ? UIElementMessage(child, message, di ? (di - UI_SIZE_MDI_CHILD_TITLE + UI_SIZE_MDI_CHILD_BORDER) : 0, dp) : 0); 3353 if (width < UI_SIZE_MDI_CHILD_MINIMUM_WIDTH) width = UI_SIZE_MDI_CHILD_MINIMUM_WIDTH; 3354 return width; 3355 } else if (message == UI_MSG_GET_HEIGHT) { 3356 UIElement *child = element->children; 3357 while (child && child->next) child = child->next; 3358 int height = UI_SIZE_MDI_CHILD_TITLE + UI_SIZE_MDI_CHILD_BORDER; 3359 height += (child ? UIElementMessage(child, message, di ? (di - 2 * UI_SIZE_MDI_CHILD_BORDER) : 0, dp) : 0); 3360 if (height < UI_SIZE_MDI_CHILD_MINIMUM_HEIGHT) height = UI_SIZE_MDI_CHILD_MINIMUM_HEIGHT; 3361 return height; 3362 } else if (message == UI_MSG_LAYOUT) { 3363 UI_MDI_CHILD_CALCULATE_LAYOUT(); 3364 3365 UIElement *child = element->children; 3366 int position = title.r; 3367 3368 while (child && child->next) { 3369 int width = UIElementMessage(child, UI_MSG_GET_WIDTH, 0, 0); 3370 UIElementMove(child, UI_RECT_4(position - width, position, title.t, title.b), false); 3371 position -= width, child = child->next; 3372 } 3373 3374 if (child) { 3375 UIElementMove(child, content, false); 3376 } 3377 } else if (message == UI_MSG_GET_CURSOR) { 3378 int hitTest = _UIMDIChildHitTest(mdiChild, element->window->cursorX, element->window->cursorY); 3379 if (hitTest == 0b1000) return UI_CURSOR_RESIZE_LEFT; 3380 if (hitTest == 0b0010) return UI_CURSOR_RESIZE_UP; 3381 if (hitTest == 0b0110) return UI_CURSOR_RESIZE_UP_RIGHT; 3382 if (hitTest == 0b1010) return UI_CURSOR_RESIZE_UP_LEFT; 3383 if (hitTest == 0b0100) return UI_CURSOR_RESIZE_RIGHT; 3384 if (hitTest == 0b0001) return UI_CURSOR_RESIZE_DOWN; 3385 if (hitTest == 0b1001) return UI_CURSOR_RESIZE_DOWN_LEFT; 3386 if (hitTest == 0b0101) return UI_CURSOR_RESIZE_DOWN_RIGHT; 3387 return UI_CURSOR_ARROW; 3388 } else if (message == UI_MSG_LEFT_DOWN) { 3389 mdiChild->dragHitTest = _UIMDIChildHitTest(mdiChild, element->window->cursorX, element->window->cursorY); 3390 mdiChild->dragOffset = UIRectangleAdd(element->bounds, UI_RECT_2(-element->window->cursorX, -element->window->cursorY)); 3391 } else if (message == UI_MSG_LEFT_UP) { 3392 if (mdiChild->bounds.l < 0) mdiChild->bounds.r -= mdiChild->bounds.l, mdiChild->bounds.l = 0; 3393 if (mdiChild->bounds.t < 0) mdiChild->bounds.b -= mdiChild->bounds.t, mdiChild->bounds.t = 0; 3394 UIElementRefresh(element->parent); 3395 } else if (message == UI_MSG_MOUSE_DRAG) { 3396 if (mdiChild->dragHitTest > 0) { 3397 #define _UI_MDI_CHILD_MOVE_EDGE(bit, edge, cursor, size, opposite, negate, minimum, offset) \ 3398 if (mdiChild->dragHitTest & bit) mdiChild->bounds.edge = mdiChild->dragOffset.edge + element->window->cursor - element->parent->bounds.offset; \ 3399 if ((mdiChild->dragHitTest & bit) && size(mdiChild->bounds) < minimum) mdiChild->bounds.edge = mdiChild->bounds.opposite negate minimum; 3400 _UI_MDI_CHILD_MOVE_EDGE(0b1000, l, cursorX, UI_RECT_WIDTH, r, -, UI_SIZE_MDI_CHILD_MINIMUM_WIDTH, l); 3401 _UI_MDI_CHILD_MOVE_EDGE(0b0100, r, cursorX, UI_RECT_WIDTH, l, +, UI_SIZE_MDI_CHILD_MINIMUM_WIDTH, l); 3402 _UI_MDI_CHILD_MOVE_EDGE(0b0010, t, cursorY, UI_RECT_HEIGHT, b, -, UI_SIZE_MDI_CHILD_MINIMUM_HEIGHT, t); 3403 _UI_MDI_CHILD_MOVE_EDGE(0b0001, b, cursorY, UI_RECT_HEIGHT, t, +, UI_SIZE_MDI_CHILD_MINIMUM_HEIGHT, t); 3404 UIElementRefresh(element->parent); 3405 } 3406 } else if (message == UI_MSG_DESTROY) { 3407 UI_FREE(mdiChild->title); 3408 UIMDIClient *client = (UIMDIClient *) element->parent; 3409 if (client->e.children == element) client->e.children = element->next; 3410 if (mdiChild->previous) mdiChild->previous->e.next = element->next; 3411 if (element->next) ((UIMDIChild *) element->next)->previous = mdiChild->previous; 3412 if (client->active == mdiChild) client->active = mdiChild->previous; 3413 } 3414 3415 return 0; 3416 } 3417 3418 int _UIMDIClientMessage(UIElement *element, UIMessage message, int di, void *dp) { 3419 UIMDIClient *client = (UIMDIClient *) element; 3420 3421 if (message == UI_MSG_PAINT) { 3422 UIDrawBlock((UIPainter *) dp, element->bounds, (element->flags & UI_MDI_CLIENT_TRANSPARENT) ? 0 : ui.theme.panel2); 3423 } else if (message == UI_MSG_LAYOUT) { 3424 UIElement *child = element->children; 3425 3426 while (child) { 3427 UI_ASSERT(child->messageClass == _UIMDIChildMessage); 3428 3429 UIMDIChild *mdiChild = (UIMDIChild *) child; 3430 3431 if (UIRectangleEquals(mdiChild->bounds, UI_RECT_1(0))) { 3432 int width = UIElementMessage(&mdiChild->e, UI_MSG_GET_WIDTH, 0, 0); 3433 int height = UIElementMessage(&mdiChild->e, UI_MSG_GET_HEIGHT, width, 0); 3434 if (client->cascade + width > element->bounds.r || client->cascade + height > element->bounds.b) client->cascade = 0; 3435 mdiChild->bounds = UI_RECT_4(client->cascade, client->cascade + width, client->cascade, client->cascade + height); 3436 client->cascade += UI_SIZE_MDI_CASCADE * element->window->scale; 3437 } 3438 3439 UIRectangle bounds = UIRectangleAdd(mdiChild->bounds, UI_RECT_2(element->bounds.l, element->bounds.t)); 3440 UIElementMove(child, bounds, false); 3441 child = child->next; 3442 } 3443 } else if (message == UI_MSG_FIND_BY_POINT) { 3444 UIFindByPoint *m = (UIFindByPoint *) dp; 3445 UIMDIChild *child = client->active; 3446 3447 while (child) { 3448 if (UIRectangleContains(child->e.bounds, m->x, m->y)) { 3449 m->result = UIElementFindByPoint(&child->e, m->x, m->y); 3450 return 1; 3451 } 3452 3453 child = child->previous; 3454 } 3455 3456 return 1; 3457 } else if (message == UI_MSG_PRESSED_DESCENDENT) { 3458 UIMDIChild *child = (UIMDIChild *) dp; 3459 3460 if (child && child != client->active) { 3461 if (client->e.children == &child->e) client->e.children = child->e.next; 3462 if (child->previous) child->previous->e.next = child->e.next; 3463 if (child->e.next) ((UIMDIChild *) child->e.next)->previous = child->previous; 3464 if (client->active) client->active->e.next = &child->e; 3465 child->previous = client->active; 3466 child->e.next = NULL; 3467 client->active = child; 3468 ((UIMDIChild *) client->e.children)->previous = NULL; 3469 UIElementRefresh(element); 3470 } 3471 } 3472 3473 return 0; 3474 } 3475 3476 UIMDIChild *UIMDIChildCreate(UIElement *parent, uint32_t flags, UIRectangle initialBounds, const char *title, ptrdiff_t titleBytes) { 3477 UI_ASSERT(parent->messageClass == _UIMDIClientMessage); 3478 3479 UIMDIChild *mdiChild = (UIMDIChild *) UIElementCreate(sizeof(UIMDIChild), parent, flags, _UIMDIChildMessage, "MDIChild"); 3480 UIMDIClient *mdiClient = (UIMDIClient *) parent; 3481 3482 mdiChild->bounds = initialBounds; 3483 mdiChild->title = UIStringCopy(title, (mdiChild->titleBytes = titleBytes)); 3484 mdiChild->previous = mdiClient->active; 3485 mdiClient->active = mdiChild; 3486 3487 if (flags & UI_MDI_CHILD_CLOSE_BUTTON) { 3488 UIButton *closeButton = UIButtonCreate(&mdiChild->e, UI_BUTTON_SMALL | UI_ELEMENT_NON_CLIENT, "X", 1); 3489 closeButton->invoke = _UIMDIChildCloseButton; 3490 closeButton->e.cp = mdiChild; 3491 } 3492 3493 return mdiChild; 3494 } 3495 3496 UIMDIClient *UIMDIClientCreate(UIElement *parent, uint32_t flags) { 3497 return (UIMDIClient *) UIElementCreate(sizeof(UIMDIClient), parent, flags, _UIMDIClientMessage, "MDIClient"); 3498 } 3499 3500 int _UIExpandPaneMessage(UIElement *element, UIMessage message, int di, void *dp) { 3501 UIExpandPane *pane = (UIExpandPane *) element; 3502 3503 if (message == UI_MSG_GET_HEIGHT) { 3504 int height = UIElementMessage(&pane->button->e, message, di, dp); 3505 3506 if (pane->expanded) { 3507 height += UIElementMessage(&pane->panel->e, message, di, dp); 3508 } 3509 3510 return height; 3511 } else if (message == UI_MSG_LAYOUT) { 3512 UIRectangle bounds = pane->e.bounds; 3513 int buttonHeight = UIElementMessage(&pane->button->e, UI_MSG_GET_HEIGHT, UI_RECT_WIDTH(bounds), NULL); 3514 UIElementMove(&pane->button->e, UI_RECT_4(bounds.l, bounds.r, bounds.t, bounds.t + buttonHeight), false); 3515 3516 if (pane->expanded) { 3517 pane->panel->e.flags &= ~UI_ELEMENT_HIDE; 3518 UIElementMove(&pane->panel->e, UI_RECT_4(bounds.l, bounds.r, bounds.t + buttonHeight, bounds.b), false); 3519 } else { 3520 pane->panel->e.flags |= UI_ELEMENT_HIDE; 3521 } 3522 } else if (message == UI_MSG_CLIENT_PARENT) { 3523 *(UIElement **) dp = &pane->panel->e; 3524 } 3525 3526 return 0; 3527 } 3528 3529 void _UIExpandPaneButtonInvoke(void *cp) { 3530 UIExpandPane *pane = (UIExpandPane *) cp; 3531 pane->expanded = !pane->expanded; 3532 if (pane->expanded) pane->button->e.flags |= UI_BUTTON_CHECKED; 3533 else pane->button->e.flags &= ~UI_BUTTON_CHECKED; 3534 3535 UIElement *ancestor = &pane->e; 3536 3537 while (ancestor) { 3538 UIElementRefresh(ancestor); 3539 3540 if ((ancestor->messageClass == _UIPanelMessage && (ancestor->flags & UI_PANEL_SCROLL)) 3541 || (ancestor->messageClass == _UIMDIChildMessage) 3542 || (ancestor->flags & UI_ELEMENT_V_FILL)) { 3543 break; 3544 } 3545 3546 ancestor = ancestor->parent; 3547 } 3548 } 3549 3550 UIExpandPane *UIExpandPaneCreate(UIElement *parent, uint32_t flags, const char *label, ptrdiff_t labelBytes, uint32_t panelFlags) { 3551 UIExpandPane *pane = (UIExpandPane *) UIElementCreate(sizeof(UIExpandPane), parent, flags, _UIExpandPaneMessage, "ExpandPane"); 3552 pane->button = UIButtonCreate(parent, UI_ELEMENT_NON_CLIENT, label, labelBytes); 3553 pane->button->e.cp = pane; 3554 pane->button->invoke = _UIExpandPaneButtonInvoke; 3555 pane->panel = UIPanelCreate(parent, UI_ELEMENT_NON_CLIENT | panelFlags); 3556 return pane; 3557 } 3558 3559 void _UIImageDisplayUpdateViewport(UIImageDisplay *display) { 3560 UIRectangle bounds = display->e.bounds; 3561 bounds.r -= bounds.l, bounds.b -= bounds.t; 3562 3563 float minimumZoomX = 1, minimumZoomY = 1; 3564 if (display->width > bounds.r) minimumZoomX = (float) bounds.r / display->width; 3565 if (display->height > bounds.b) minimumZoomY = (float) bounds.b / display->height; 3566 float minimumZoom = minimumZoomX < minimumZoomY ? minimumZoomX : minimumZoomY; 3567 3568 if (display->zoom < minimumZoom || (display->e.flags & _UI_IMAGE_DISPLAY_ZOOM_FIT)) { 3569 display->zoom = minimumZoom; 3570 display->e.flags |= _UI_IMAGE_DISPLAY_ZOOM_FIT; 3571 } 3572 3573 if (display->panX < 0) display->panX = 0; 3574 if (display->panY < 0) display->panY = 0; 3575 if (display->panX > display->width - bounds.r / display->zoom) display->panX = display->width - bounds.r / display->zoom; 3576 if (display->panY > display->height - bounds.b / display->zoom) display->panY = display->height - bounds.b / display->zoom; 3577 3578 if (bounds.r && display->width * display->zoom <= bounds.r) display->panX = display->width / 2 - bounds.r / display->zoom / 2; 3579 if (bounds.b && display->height * display->zoom <= bounds.b) display->panY = display->height / 2 - bounds.b / display->zoom / 2; 3580 } 3581 3582 int _UIImageDisplayMessage(UIElement *element, UIMessage message, int di, void *dp) { 3583 UIImageDisplay *display = (UIImageDisplay *) element; 3584 3585 if (message == UI_MSG_GET_HEIGHT) { 3586 return display->height; 3587 } else if (message == UI_MSG_GET_WIDTH) { 3588 return display->width; 3589 } else if (message == UI_MSG_DESTROY) { 3590 UI_FREE(display->bits); 3591 } else if (message == UI_MSG_PAINT) { 3592 UIPainter *painter = (UIPainter *) dp; 3593 3594 int w = UI_RECT_WIDTH(element->bounds), h = UI_RECT_HEIGHT(element->bounds); 3595 int x = _UILinearMap(0, display->panX, display->panX + w / display->zoom, 0, w) + element->bounds.l; 3596 int y = _UILinearMap(0, display->panY, display->panY + h / display->zoom, 0, h) + element->bounds.t; 3597 3598 UIRectangle image = UI_RECT_4(x, x + (int) (display->width * display->zoom), y, (int) (y + display->height * display->zoom)); 3599 UIRectangle bounds = UIRectangleIntersection(painter->clip, UIRectangleIntersection(display->e.bounds, image)); 3600 if (!UI_RECT_VALID(bounds)) return 0; 3601 3602 if (display->zoom == 1) { 3603 uint32_t *lineStart = (uint32_t *) painter->bits + bounds.t * painter->width + bounds.l; 3604 uint32_t *sourceLineStart = display->bits + (bounds.l - image.l) + display->width * (bounds.t - image.t); 3605 3606 for (int i = 0; i < bounds.b - bounds.t; i++, lineStart += painter->width, sourceLineStart += display->width) { 3607 uint32_t *destination = lineStart; 3608 uint32_t *source = sourceLineStart; 3609 int j = bounds.r - bounds.l; 3610 3611 do { 3612 *destination = *source; 3613 destination++; 3614 source++; 3615 } while (--j); 3616 } 3617 } else { 3618 float zr = 1.0f / display->zoom; 3619 uint32_t *destination = (uint32_t *) painter->bits; 3620 3621 for (int i = bounds.t; i < bounds.b; i++) { 3622 int ty = (i - image.t) * zr; 3623 3624 for (int j = bounds.l; j < bounds.r; j++) { 3625 int tx = (j - image.l) * zr; 3626 destination[i * painter->width + j] = display->bits[ty * display->width + tx]; 3627 } 3628 } 3629 } 3630 } else if (message == UI_MSG_MOUSE_WHEEL && (element->flags & UI_IMAGE_DISPLAY_INTERACTIVE)) { 3631 display->e.flags &= ~_UI_IMAGE_DISPLAY_ZOOM_FIT; 3632 int divisions = -di / 72; 3633 float factor = 1; 3634 float perDivision = element->window->ctrl ? 2.0f : element->window->alt ? 1.01f : 1.2f; 3635 while (divisions > 0) factor *= perDivision, divisions--; 3636 while (divisions < 0) factor /= perDivision, divisions++; 3637 if (display->zoom * factor > 64) factor = 64 / display->zoom; 3638 int mx = element->window->cursorX - element->bounds.l; 3639 int my = element->window->cursorY - element->bounds.t; 3640 display->zoom *= factor; 3641 display->panX -= mx / display->zoom * (1 - factor); 3642 display->panY -= my / display->zoom * (1 - factor); 3643 _UIImageDisplayUpdateViewport(display); 3644 UIElementRepaint(&display->e, NULL); 3645 } else if (message == UI_MSG_LAYOUT && (element->flags & UI_IMAGE_DISPLAY_INTERACTIVE)) { 3646 UIRectangle bounds = display->e.bounds; 3647 bounds.r -= bounds.l, bounds.b -= bounds.t; 3648 display->panX -= (bounds.r - display->previousWidth ) / 2 / display->zoom; 3649 display->panY -= (bounds.b - display->previousHeight) / 2 / display->zoom; 3650 display->previousWidth = bounds.r, display->previousHeight = bounds.b; 3651 _UIImageDisplayUpdateViewport(display); 3652 } else if (message == UI_MSG_GET_CURSOR && (element->flags & UI_IMAGE_DISPLAY_INTERACTIVE) 3653 && (UI_RECT_WIDTH(element->bounds) < display->width * display->zoom 3654 || UI_RECT_HEIGHT(element->bounds) < display->height * display->zoom)) { 3655 return UI_CURSOR_HAND; 3656 } else if (message == UI_MSG_MOUSE_DRAG) { 3657 display->panX -= (element->window->cursorX - display->previousPanPointX) / display->zoom; 3658 display->panY -= (element->window->cursorY - display->previousPanPointY) / display->zoom; 3659 _UIImageDisplayUpdateViewport(display); 3660 display->previousPanPointX = element->window->cursorX; 3661 display->previousPanPointY = element->window->cursorY; 3662 UIElementRepaint(element, NULL); 3663 } else if (message == UI_MSG_LEFT_DOWN) { 3664 display->e.flags &= ~_UI_IMAGE_DISPLAY_ZOOM_FIT; 3665 display->previousPanPointX = element->window->cursorX; 3666 display->previousPanPointY = element->window->cursorY; 3667 } 3668 3669 return 0; 3670 } 3671 3672 void UIImageDisplaySetContent(UIImageDisplay *display, uint32_t *bits, size_t width, size_t height, size_t stride) { 3673 UI_FREE(display->bits); 3674 3675 display->bits = (uint32_t *) UI_MALLOC(width * height * 4); 3676 display->width = width; 3677 display->height = height; 3678 3679 uint32_t *destination = display->bits; 3680 uint32_t *source = bits; 3681 3682 for (uintptr_t row = 0; row < height; row++, source += stride / 4) { 3683 for (uintptr_t i = 0; i < width; i++) { 3684 *destination++ = source[i]; 3685 } 3686 } 3687 } 3688 3689 UIImageDisplay *UIImageDisplayCreate(UIElement *parent, uint32_t flags, uint32_t *bits, size_t width, size_t height, size_t stride) { 3690 UIImageDisplay *display = (UIImageDisplay *) UIElementCreate(sizeof(UIImageDisplay), parent, flags, _UIImageDisplayMessage, "ImageDisplay"); 3691 display->zoom = 1.0f; 3692 UIImageDisplaySetContent(display, bits, width, height, stride); 3693 return display; 3694 } 3695 3696 int _UIDialogWrapperMessage(UIElement *element, UIMessage message, int di, void *dp) { 3697 if (message == UI_MSG_LAYOUT) { 3698 int width = UIElementMessage(element->children, UI_MSG_GET_WIDTH, 0, 0); 3699 int height = UIElementMessage(element->children, UI_MSG_GET_HEIGHT, width, 0); 3700 int cx = (element->bounds.l + element->bounds.r) / 2; 3701 int cy = (element->bounds.t + element->bounds.b) / 2; 3702 UIRectangle bounds = UI_RECT_4(cx - (width + 1) / 2, cx + width / 2, cy - (height + 1) / 2, cy + height / 2); 3703 UIElementMove(element->children, bounds, false); 3704 UIElementRepaint(element, NULL); 3705 } else if (message == UI_MSG_PAINT) { 3706 UIRectangle bounds = UIRectangleAdd(element->children->bounds, UI_RECT_1I(-1)); 3707 UIDrawBorder((UIPainter *) dp, bounds, ui.theme.border, UI_RECT_1(1)); 3708 UIDrawBorder((UIPainter *) dp, UIRectangleAdd(bounds, UI_RECT_1(1)), ui.theme.border, UI_RECT_1(1)); 3709 } else if (message == UI_MSG_KEY_TYPED) { 3710 UIKeyTyped *typed = (UIKeyTyped *) dp; 3711 3712 if (element->window->ctrl) return 0; 3713 if (element->window->shift) return 0; 3714 3715 char c0 = 0, c1 = 0; 3716 3717 if (typed->textBytes == 1 && typed->text[0] >= 'a' && typed->text[0] <= 'z') { 3718 c0 = typed->text[0], c1 = typed->text[0] - 'a' + 'A'; 3719 } else { 3720 return 0; 3721 } 3722 3723 UIElement *row = element->children->children; 3724 UIElement *target = NULL; 3725 bool duplicate = false; 3726 3727 while (row) { 3728 UIElement *item = row->children; 3729 3730 while (item) { 3731 if (item->messageClass == _UIButtonMessage) { 3732 UIButton *button = (UIButton *) item; 3733 3734 if (button->label && button->labelBytes && (button->label[0] == c0 || button->label[0] == c1)) { 3735 if (!target) { 3736 target = &button->e; 3737 } else { 3738 duplicate = true; 3739 } 3740 } 3741 } 3742 3743 item = item->next; 3744 } 3745 3746 row = row->next; 3747 } 3748 3749 if (target) { 3750 if (duplicate) { 3751 UIElementFocus(target); 3752 } else { 3753 UIElementMessage(target, UI_MSG_CLICKED, 0, 0); 3754 } 3755 3756 return 1; 3757 } 3758 } 3759 3760 return 0; 3761 } 3762 3763 void _UIDialogButtonInvoke(void *cp) { 3764 ui.dialogResult = (const char *) cp; 3765 } 3766 3767 int _UIDialogTextboxMessage(UIElement *element, UIMessage message, int di, void *dp) { 3768 if (message == UI_MSG_VALUE_CHANGED) { 3769 UITextbox *textbox = (UITextbox *) element; 3770 char **buffer = (char **) element->cp; 3771 *buffer = (char *) UI_REALLOC(*buffer, textbox->bytes + 1); 3772 (*buffer)[textbox->bytes] = 0; 3773 3774 for (ptrdiff_t i = 0; i < textbox->bytes; i++) { 3775 (*buffer)[i] = textbox->string[i]; 3776 } 3777 } 3778 3779 return 0; 3780 } 3781 3782 const char *UIDialogShow(UIWindow *window, uint32_t flags, const char *format, ...) { 3783 // TODO Enter and escape. 3784 3785 // Create the dialog wrapper and panel. 3786 3787 UI_ASSERT(!window->dialog); 3788 window->dialog = UIElementCreate(sizeof(UIElement), &window->e, 0, _UIDialogWrapperMessage, "DialogWrapper"); 3789 UIPanel *panel = UIPanelCreate(window->dialog, UI_PANEL_MEDIUM_SPACING | UI_PANEL_GRAY | UI_PANEL_EXPAND); 3790 panel->border = UI_RECT_1(UI_SIZE_PANE_MEDIUM_BORDER * 2); 3791 window->e.children->flags |= UI_ELEMENT_DISABLED; 3792 3793 // Create the dialog contents. 3794 3795 va_list arguments; 3796 va_start(arguments, format); 3797 UIPanel *row = NULL; 3798 UIElement *focus = NULL; 3799 3800 for (int i = 0; format[i]; i++) { 3801 if (i == 0 || format[i - 1] == '\n') { 3802 row = UIPanelCreate(&panel->e, UI_PANEL_HORIZONTAL); 3803 row->gap = UI_SIZE_PANE_SMALL_GAP; 3804 } 3805 3806 if (format[i] == ' ' || format[i] == '\n') { 3807 } else if (format[i] == '%') { 3808 i++; 3809 3810 if (format[i] == 'b' /* button */) { 3811 const char *label = va_arg(arguments, const char *); 3812 UIButton *button = UIButtonCreate(&row->e, 0, label, -1); 3813 if (!focus) focus = &button->e; 3814 button->invoke = _UIDialogButtonInvoke; 3815 button->e.cp = (void *) label; 3816 } else if (format[i] == 's' /* label from string */) { 3817 const char *label = va_arg(arguments, const char *); 3818 UILabelCreate(&row->e, 0, label, -1); 3819 } else if (format[i] == 't' /* textbox */) { 3820 char **buffer = va_arg(arguments, char **); 3821 UITextbox *textbox = UITextboxCreate(&row->e, UI_ELEMENT_H_FILL); 3822 if (!focus) focus = &textbox->e; 3823 if (*buffer) UITextboxReplace(textbox, *buffer, _UIStringLength(*buffer), false); 3824 textbox->e.cp = buffer; 3825 textbox->e.messageUser = _UIDialogTextboxMessage; 3826 } else if (format[i] == 'f' /* horizontal fill */) { 3827 UISpacerCreate(&row->e, UI_ELEMENT_H_FILL, 0, 0); 3828 } else if (format[i] == 'l' /* horizontal line */) { 3829 UISpacerCreate(&row->e, UI_SPACER_LINE | UI_ELEMENT_H_FILL, 0, 1); 3830 } else if (format[i] == 'u' /* user */) { 3831 UIDialogUserCallback callback = va_arg(arguments, UIDialogUserCallback); 3832 callback(&row->e); 3833 } 3834 } else { 3835 int j = i; 3836 while (format[j] && format[j] != '%' && format[j] != '\n') j++; 3837 UILabelCreate(&row->e, 0, format + i, j - i); 3838 i = j - 1; 3839 } 3840 } 3841 3842 va_end(arguments); 3843 3844 window->dialogOldFocus = window->focused; 3845 UIElementFocus(focus ? focus : window->dialog); 3846 3847 // Run the modal message loop. 3848 3849 int result; 3850 ui.dialogResult = NULL; 3851 for (int i = 1; i <= 3; i++) _UIWindowSetPressed(window, NULL, i); 3852 UIElementRefresh(&window->e); 3853 _UIUpdate(); 3854 while (!ui.dialogResult && _UIMessageLoopSingle(&result)); 3855 ui.quit = !ui.dialogResult; 3856 3857 // Destroy the dialog. 3858 3859 window->e.children->flags &= ~UI_ELEMENT_DISABLED; 3860 UIElementDestroy(window->dialog); 3861 window->dialog = NULL; 3862 UIElementRefresh(&window->e); 3863 if (window->dialogOldFocus) UIElementFocus(window->dialogOldFocus); 3864 return ui.dialogResult ? ui.dialogResult : ""; 3865 } 3866 3867 bool _UIMenusClose() { 3868 UIWindow *window = ui.windows; 3869 bool anyClosed = false; 3870 3871 while (window) { 3872 if (window->e.flags & UI_WINDOW_MENU) { 3873 UIElementDestroy(&window->e); 3874 anyClosed = true; 3875 } 3876 3877 window = window->next; 3878 } 3879 3880 return anyClosed; 3881 } 3882 3883 #ifndef UI_ESSENCE 3884 int _UIMenuItemMessage(UIElement *element, UIMessage message, int di, void *dp) { 3885 if (message == UI_MSG_CLICKED) { 3886 _UIMenusClose(); 3887 } 3888 3889 return 0; 3890 } 3891 3892 int _UIMenuMessage(UIElement *element, UIMessage message, int di, void *dp) { 3893 UIMenu *menu = (UIMenu *) element; 3894 3895 if (message == UI_MSG_GET_WIDTH) { 3896 UIElement *child = element->children; 3897 int width = 0; 3898 3899 while (child) { 3900 if (~child->flags & UI_ELEMENT_NON_CLIENT) { 3901 int w = UIElementMessage(child, UI_MSG_GET_WIDTH, 0, 0); 3902 if (w > width) width = w; 3903 } 3904 3905 child = child->next; 3906 } 3907 3908 return width + 4 + UI_SIZE_SCROLL_BAR; 3909 } else if (message == UI_MSG_GET_HEIGHT) { 3910 UIElement *child = element->children; 3911 int height = 0; 3912 3913 while (child) { 3914 if (~child->flags & UI_ELEMENT_NON_CLIENT) { 3915 height += UIElementMessage(child, UI_MSG_GET_HEIGHT, 0, 0); 3916 } 3917 3918 child = child->next; 3919 } 3920 3921 return height + 4; 3922 } else if (message == UI_MSG_PAINT) { 3923 UIDrawBlock((UIPainter *) dp, element->bounds, ui.theme.border); 3924 } else if (message == UI_MSG_LAYOUT) { 3925 UIElement *child = element->children; 3926 int position = element->bounds.t + 2 - menu->vScroll->position; 3927 int totalHeight = 0; 3928 int scrollBarSize = (menu->e.flags & UI_MENU_NO_SCROLL) ? 0 : UI_SIZE_SCROLL_BAR; 3929 3930 while (child) { 3931 if (~child->flags & UI_ELEMENT_NON_CLIENT) { 3932 int height = UIElementMessage(child, UI_MSG_GET_HEIGHT, 0, 0); 3933 UIElementMove(child, UI_RECT_4(element->bounds.l + 2, element->bounds.r - scrollBarSize - 2, 3934 position, position + height), false); 3935 position += height; 3936 totalHeight += height; 3937 } 3938 3939 child = child->next; 3940 } 3941 3942 UIRectangle scrollBarBounds = element->bounds; 3943 scrollBarBounds.l = scrollBarBounds.r - scrollBarSize * element->window->scale; 3944 menu->vScroll->maximum = totalHeight; 3945 menu->vScroll->page = UI_RECT_HEIGHT(element->bounds); 3946 UIElementMove(&menu->vScroll->e, scrollBarBounds, true); 3947 } else if (message == UI_MSG_KEY_TYPED) { 3948 UIKeyTyped *m = (UIKeyTyped *) dp; 3949 3950 if (m->code == UI_KEYCODE_ESCAPE) { 3951 _UIMenusClose(); 3952 return 1; 3953 } 3954 } else if (message == UI_MSG_MOUSE_WHEEL) { 3955 return UIElementMessage(&menu->vScroll->e, message, di, dp); 3956 } else if (message == UI_MSG_SCROLLED) { 3957 UIElementRefresh(element); 3958 } 3959 3960 return 0; 3961 } 3962 3963 void UIMenuAddItem(UIMenu *menu, uint32_t flags, const char *label, ptrdiff_t labelBytes, void (*invoke)(void *cp), void *cp) { 3964 UIButton *button = UIButtonCreate(&menu->e, flags | UI_BUTTON_MENU_ITEM, label, labelBytes); 3965 button->invoke = invoke; 3966 button->e.messageUser = _UIMenuItemMessage; 3967 button->e.cp = cp; 3968 } 3969 3970 void _UIMenuPrepare(UIMenu *menu, int *width, int *height) { 3971 *width = UIElementMessage(&menu->e, UI_MSG_GET_WIDTH, 0, 0); 3972 *height = UIElementMessage(&menu->e, UI_MSG_GET_HEIGHT, 0, 0); 3973 3974 if (menu->e.flags & UI_MENU_PLACE_ABOVE) { 3975 menu->pointY -= *height; 3976 } 3977 } 3978 3979 UIMenu *UIMenuCreate(UIElement *parent, uint32_t flags) { 3980 UIWindow *window = UIWindowCreate(parent->window, UI_WINDOW_MENU, 0, 0, 0); 3981 UIMenu *menu = (UIMenu *) UIElementCreate(sizeof(UIMenu), &window->e, flags, _UIMenuMessage, "Menu"); 3982 menu->vScroll = UIScrollBarCreate(&menu->e, UI_ELEMENT_NON_CLIENT); 3983 3984 if (parent->parent) { 3985 UIRectangle screenBounds = UIElementScreenBounds(parent); 3986 menu->pointX = screenBounds.l; 3987 menu->pointY = (flags & UI_MENU_PLACE_ABOVE) ? (screenBounds.t + 1) : (screenBounds.b - 1); 3988 } else { 3989 int x = 0, y = 0; 3990 _UIWindowGetScreenPosition(parent->window, &x, &y); 3991 3992 menu->pointX = parent->window->cursorX + x; 3993 menu->pointY = parent->window->cursorY + y; 3994 } 3995 3996 return menu; 3997 } 3998 #endif 3999 4000 UIRectangle UIElementScreenBounds(UIElement *element) { 4001 int x = 0, y = 0; 4002 _UIWindowGetScreenPosition(element->window, &x, &y); 4003 return UIRectangleAdd(element->bounds, UI_RECT_2(x, y)); 4004 } 4005 4006 void UIWindowRegisterShortcut(UIWindow *window, UIShortcut shortcut) { 4007 if (window->shortcutCount + 1 > window->shortcutAllocated) { 4008 window->shortcutAllocated = (window->shortcutCount + 1) * 2; 4009 window->shortcuts = (UIShortcut *) UI_REALLOC(window->shortcuts, window->shortcutAllocated * sizeof(UIShortcut)); 4010 } 4011 4012 window->shortcuts[window->shortcutCount++] = shortcut; 4013 } 4014 4015 void _UIElementPaint(UIElement *element, UIPainter *painter) { 4016 if (element->flags & UI_ELEMENT_HIDE) { 4017 return; 4018 } 4019 4020 // Clip painting to the element's clip. 4021 4022 painter->clip = UIRectangleIntersection(element->clip, painter->clip); 4023 4024 if (!UI_RECT_VALID(painter->clip)) { 4025 return; 4026 } 4027 4028 // Paint the element. 4029 4030 UIElementMessage(element, UI_MSG_PAINT, 0, painter); 4031 4032 // Paint its children. 4033 4034 UIElement *child = element->children; 4035 UIRectangle previousClip = painter->clip; 4036 4037 while (child) { 4038 painter->clip = previousClip; 4039 _UIElementPaint(child, painter); 4040 child = child->next; 4041 } 4042 } 4043 4044 void UIElementFocus(UIElement *element) { 4045 UIElement *previous = element->window->focused; 4046 if (previous == element) return; 4047 element->window->focused = element; 4048 if (previous) UIElementMessage(previous, UI_MSG_UPDATE, UI_UPDATE_FOCUSED, 0); 4049 if (element) UIElementMessage(element, UI_MSG_UPDATE, UI_UPDATE_FOCUSED, 0); 4050 4051 #ifdef UI_DEBUG 4052 _UIInspectorRefresh(); 4053 #endif 4054 } 4055 4056 void _UIWindowSetPressed(UIWindow *window, UIElement *element, int button) { 4057 UIElement *previous = window->pressed; 4058 window->pressed = element; 4059 window->pressedButton = button; 4060 if (previous) UIElementMessage(previous, UI_MSG_UPDATE, UI_UPDATE_PRESSED, 0); 4061 if (element) UIElementMessage(element, UI_MSG_UPDATE, UI_UPDATE_PRESSED, 0); 4062 4063 UIElement *ancestor = element; 4064 UIElement *child = NULL; 4065 4066 while (ancestor) { 4067 UIElementMessage(ancestor, UI_MSG_PRESSED_DESCENDENT, 0, child); 4068 child = ancestor; 4069 ancestor = ancestor->parent; 4070 } 4071 } 4072 4073 bool _UIDestroy(UIElement *element) { 4074 if (element->flags & UI_ELEMENT_DESTROY_DESCENDENT) { 4075 element->flags &= ~UI_ELEMENT_DESTROY_DESCENDENT; 4076 4077 UIElement *child = element->children; 4078 UIElement **link = &element->children; 4079 4080 while (child) { 4081 UIElement *next = child->next; 4082 4083 if (_UIDestroy(child)) { 4084 *link = next; 4085 } else { 4086 link = &child->next; 4087 } 4088 4089 child = next; 4090 } 4091 } 4092 4093 if (element->flags & UI_ELEMENT_DESTROY) { 4094 UIElementMessage(element, UI_MSG_DESTROY, 0, 0); 4095 4096 if (element->window->pressed == element) { 4097 _UIWindowSetPressed(element->window, NULL, 0); 4098 } 4099 4100 if (element->window->hovered == element) { 4101 element->window->hovered = &element->window->e; 4102 } 4103 4104 if (element->window->focused == element) { 4105 element->window->focused = NULL; 4106 } 4107 4108 if (element->window->dialogOldFocus == element) { 4109 element->window->dialogOldFocus = NULL; 4110 } 4111 4112 if (ui.animating == element) { 4113 ui.animating = NULL; 4114 } 4115 4116 UI_FREE(element); 4117 return true; 4118 } else { 4119 return false; 4120 } 4121 } 4122 4123 void _UIUpdate() { 4124 UIWindow *window = ui.windows; 4125 UIWindow **link = &ui.windows; 4126 4127 while (window) { 4128 UIWindow *next = window->next; 4129 4130 if (_UIDestroy(&window->e)) { 4131 *link = next; 4132 } else { 4133 link = &window->next; 4134 4135 if (UI_RECT_VALID(window->updateRegion)) { 4136 #ifdef __cplusplus 4137 UIPainter painter = {}; 4138 #else 4139 UIPainter painter = { 0 }; 4140 #endif 4141 painter.bits = window->bits; 4142 painter.width = window->width; 4143 painter.height = window->height; 4144 painter.clip = UIRectangleIntersection(UI_RECT_2S(window->width, window->height), window->updateRegion); 4145 _UIElementPaint(&window->e, &painter); 4146 _UIWindowEndPaint(window, &painter); 4147 window->updateRegion = UI_RECT_1(0); 4148 4149 #ifdef UI_DEBUG 4150 window->lastFullFillCount = (float) painter.fillCount / (UI_RECT_WIDTH(window->updateRegion) * UI_RECT_HEIGHT(window->updateRegion)); 4151 #endif 4152 } 4153 } 4154 4155 window = next; 4156 } 4157 } 4158 4159 UIElement *UIElementFindByPoint(UIElement *element, int x, int y) { 4160 UIFindByPoint m = { 0 }; 4161 m.x = x, m.y = y; 4162 4163 if (UIElementMessage(element, UI_MSG_FIND_BY_POINT, 0, &m)) { 4164 return m.result ? m.result : element; 4165 } 4166 4167 UIElement *child = element->children; 4168 4169 while (child) { 4170 if ((~child->flags & UI_ELEMENT_HIDE) && UIRectangleContains(child->clip, x, y)) { 4171 return UIElementFindByPoint(child, x, y); 4172 } 4173 4174 child = child->next; 4175 } 4176 4177 return element; 4178 } 4179 4180 void _UIProcessAnimations() { 4181 if (ui.animating) { 4182 UIElementMessage(ui.animating, UI_MSG_ANIMATE, 0, 0); 4183 _UIUpdate(); 4184 } 4185 } 4186 4187 bool _UIMenusOpen() { 4188 UIWindow *window = ui.windows; 4189 4190 while (window) { 4191 if (window->e.flags & UI_WINDOW_MENU) { 4192 return true; 4193 } 4194 4195 window = window->next; 4196 } 4197 4198 return false; 4199 } 4200 4201 void _UIWindowDestroyCommon(UIWindow *window) { 4202 UI_FREE(window->bits); 4203 UI_FREE(window->shortcuts); 4204 } 4205 4206 UIElement *_UIElementLastChild(UIElement *element) { 4207 if (!element->children) { 4208 return NULL; 4209 } 4210 4211 UIElement *child = element->children; 4212 4213 while (child->next) { 4214 child = child->next; 4215 } 4216 4217 return child; 4218 } 4219 4220 UIElement *_UIElementPreviousSibling(UIElement *element) { 4221 if (!element->parent) { 4222 return NULL; 4223 } 4224 4225 UIElement *sibling = element->parent->children; 4226 4227 if (sibling == element) { 4228 return NULL; 4229 } 4230 4231 while (sibling->next != element) { 4232 sibling = sibling->next; 4233 UI_ASSERT(sibling); 4234 } 4235 4236 return sibling; 4237 } 4238 4239 bool _UIWindowInputEvent(UIWindow *window, UIMessage message, int di, void *dp) { 4240 bool handled = true; 4241 4242 if (window->pressed) { 4243 if (message == UI_MSG_MOUSE_MOVE) { 4244 UIElementMessage(window->pressed, UI_MSG_MOUSE_DRAG, di, dp); 4245 } else if (message == UI_MSG_LEFT_UP && window->pressedButton == 1) { 4246 if (window->hovered == window->pressed) { 4247 UIElementMessage(window->pressed, UI_MSG_CLICKED, di, dp); 4248 if (ui.quit || ui.dialogResult) goto end; 4249 } 4250 4251 if (window->pressed) { 4252 UIElementMessage(window->pressed, UI_MSG_LEFT_UP, di, dp); 4253 if (ui.quit || ui.dialogResult) goto end; 4254 _UIWindowSetPressed(window, NULL, 1); 4255 } 4256 } else if (message == UI_MSG_MIDDLE_UP && window->pressedButton == 2) { 4257 UIElementMessage(window->pressed, UI_MSG_MIDDLE_UP, di, dp); 4258 if (ui.quit || ui.dialogResult) goto end; 4259 _UIWindowSetPressed(window, NULL, 2); 4260 } else if (message == UI_MSG_RIGHT_UP && window->pressedButton == 3) { 4261 UIElementMessage(window->pressed, UI_MSG_RIGHT_UP, di, dp); 4262 if (ui.quit || ui.dialogResult) goto end; 4263 _UIWindowSetPressed(window, NULL, 3); 4264 } 4265 } 4266 4267 if (window->pressed) { 4268 bool inside = UIRectangleContains(window->pressed->clip, window->cursorX, window->cursorY); 4269 4270 if (inside && window->hovered == &window->e) { 4271 window->hovered = window->pressed; 4272 UIElementMessage(window->pressed, UI_MSG_UPDATE, UI_UPDATE_HOVERED, 0); 4273 } else if (!inside && window->hovered == window->pressed) { 4274 window->hovered = &window->e; 4275 UIElementMessage(window->pressed, UI_MSG_UPDATE, UI_UPDATE_HOVERED, 0); 4276 } 4277 4278 if (ui.quit || ui.dialogResult) goto end; 4279 } 4280 4281 if (!window->pressed) { 4282 UIElement *hovered = UIElementFindByPoint(&window->e, window->cursorX, window->cursorY); 4283 4284 if (message == UI_MSG_MOUSE_MOVE) { 4285 UIElementMessage(hovered, UI_MSG_MOUSE_MOVE, di, dp); 4286 4287 int cursor = UIElementMessage(window->hovered, UI_MSG_GET_CURSOR, di, dp); 4288 4289 if (cursor != window->cursorStyle) { 4290 window->cursorStyle = cursor; 4291 _UIWindowSetCursor(window, cursor); 4292 } 4293 } else if (message == UI_MSG_LEFT_DOWN) { 4294 if ((window->e.flags & UI_WINDOW_MENU) || !_UIMenusClose()) { 4295 _UIWindowSetPressed(window, hovered, 1); 4296 UIElementMessage(hovered, UI_MSG_LEFT_DOWN, di, dp); 4297 } 4298 } else if (message == UI_MSG_MIDDLE_DOWN) { 4299 if ((window->e.flags & UI_WINDOW_MENU) || !_UIMenusClose()) { 4300 _UIWindowSetPressed(window, hovered, 2); 4301 UIElementMessage(hovered, UI_MSG_MIDDLE_DOWN, di, dp); 4302 } 4303 } else if (message == UI_MSG_RIGHT_DOWN) { 4304 if ((window->e.flags & UI_WINDOW_MENU) || !_UIMenusClose()) { 4305 _UIWindowSetPressed(window, hovered, 3); 4306 UIElementMessage(hovered, UI_MSG_RIGHT_DOWN, di, dp); 4307 } 4308 } else if (message == UI_MSG_MOUSE_WHEEL) { 4309 UIElement *element = hovered; 4310 4311 while (element) { 4312 if (UIElementMessage(element, UI_MSG_MOUSE_WHEEL, di, dp)) { 4313 break; 4314 } 4315 4316 element = element->parent; 4317 } 4318 } else if (message == UI_MSG_KEY_TYPED) { 4319 handled = false; 4320 4321 if (window->focused) { 4322 UIElement *element = window->focused; 4323 4324 while (element) { 4325 if (UIElementMessage(element, UI_MSG_KEY_TYPED, di, dp)) { 4326 handled = true; 4327 break; 4328 } 4329 4330 element = element->parent; 4331 } 4332 } else { 4333 if (UIElementMessage(&window->e, UI_MSG_KEY_TYPED, di, dp)) { 4334 handled = true; 4335 } 4336 } 4337 4338 if (!handled && !_UIMenusOpen()) { 4339 UIKeyTyped *m = (UIKeyTyped *) dp; 4340 4341 if (m->code == UI_KEYCODE_TAB && !window->ctrl && !window->alt) { 4342 UIElement *start = window->focused ? window->focused : &window->e; 4343 UIElement *element = start; 4344 4345 do { 4346 if (element->children && !(element->flags & (UI_ELEMENT_HIDE | UI_ELEMENT_DISABLED))) { 4347 element = window->shift ? _UIElementLastChild(element) : element->children; 4348 continue; 4349 } 4350 4351 while (element) { 4352 if (window->shift ? (element->parent && element->parent->children != element) : !!element->next) { 4353 element = window->shift ? _UIElementPreviousSibling(element) : element->next; 4354 break; 4355 } else { 4356 element = element->parent; 4357 } 4358 } 4359 4360 if (!element) { 4361 element = &window->e; 4362 } 4363 } while (element != start && ((~element->flags & UI_ELEMENT_TAB_STOP) 4364 || (element->flags & (UI_ELEMENT_HIDE | UI_ELEMENT_DISABLED)))); 4365 4366 if (~element->flags & UI_ELEMENT_WINDOW) { 4367 UIElementFocus(element); 4368 } 4369 4370 handled = true; 4371 } else if (!window->dialog) { 4372 for (intptr_t i = window->shortcutCount - 1; i >= 0; i--) { 4373 UIShortcut *shortcut = window->shortcuts + i; 4374 4375 if (shortcut->code == m->code && shortcut->ctrl == window->ctrl 4376 && shortcut->shift == window->shift && shortcut->alt == window->alt) { 4377 shortcut->invoke(shortcut->cp); 4378 handled = true; 4379 break; 4380 } 4381 } 4382 } 4383 } 4384 } 4385 4386 if (ui.quit || ui.dialogResult) goto end; 4387 4388 if (hovered != window->hovered) { 4389 UIElement *previous = window->hovered; 4390 window->hovered = hovered; 4391 UIElementMessage(previous, UI_MSG_UPDATE, UI_UPDATE_HOVERED, 0); 4392 UIElementMessage(window->hovered, UI_MSG_UPDATE, UI_UPDATE_HOVERED, 0); 4393 } 4394 } 4395 4396 end: _UIUpdate(); 4397 return handled; 4398 } 4399 4400 UIFont *UIFontCreate(const char *cPath, uint32_t size) { 4401 UIFont *font = (UIFont *) UI_CALLOC(sizeof(UIFont)); 4402 4403 #ifdef UI_FREETYPE 4404 if (cPath) { 4405 if (!FT_New_Face(ui.ft, cPath, 0, &font->font)) { 4406 FT_Set_Char_Size(font->font, 0, size * 64, 100, 100); 4407 FT_Load_Char(font->font, 'a', FT_LOAD_DEFAULT); 4408 font->glyphWidth = font->font->glyph->advance.x / 64; 4409 font->glyphHeight = (font->font->size->metrics.ascender - font->font->size->metrics.descender) / 64; 4410 font->isFreeType = true; 4411 return font; 4412 } 4413 } 4414 #endif 4415 4416 font->glyphWidth = 9; 4417 font->glyphHeight = 16; 4418 return font; 4419 } 4420 4421 UIFont *UIFontActivate(UIFont *font) { 4422 UIFont *previous = ui.activeFont; 4423 ui.activeFont = font; 4424 return previous; 4425 } 4426 4427 void _UIInitialiseCommon() { 4428 ui.theme = _uiThemeDark; 4429 4430 #ifdef UI_FREETYPE 4431 FT_Init_FreeType(&ui.ft); 4432 UIFontActivate(UIFontCreate(_UI_TO_STRING_2(UI_FONT_PATH), 11)); 4433 #else 4434 UIFontActivate(UIFontCreate(0, 0)); 4435 #endif 4436 } 4437 4438 void _UIWindowAdd(UIWindow *window) { 4439 window->scale = 1.0f; 4440 window->e.window = window; 4441 window->hovered = &window->e; 4442 window->next = ui.windows; 4443 ui.windows = window; 4444 } 4445 4446 int _UIWindowMessageCommon(UIElement *element, UIMessage message, int di, void *dp) { 4447 if (message == UI_MSG_LAYOUT && element->children) { 4448 UIElementMove(element->children, element->bounds, false); 4449 if (element->window->dialog) UIElementMove(element->window->dialog, element->bounds, false); 4450 UIElementRepaint(element, NULL); 4451 } else if (message == UI_MSG_FIND_BY_POINT) { 4452 UIFindByPoint *m = (UIFindByPoint *) dp; 4453 if (element->window->dialog) m->result = UIElementFindByPoint(element->window->dialog, m->x, m->y); 4454 else if (!element->children) m->result = NULL; 4455 else m->result = UIElementFindByPoint(element->children, m->x, m->y); 4456 return 1; 4457 } 4458 4459 return 0; 4460 } 4461 4462 #ifdef UI_DEBUG 4463 4464 void UIInspectorLog(const char *cFormat, ...) { 4465 va_list arguments; 4466 va_start(arguments, cFormat); 4467 char buffer[4096]; 4468 vsnprintf(buffer, sizeof(buffer), cFormat, arguments); 4469 UICodeInsertContent(ui.inspectorLog, buffer, -1, false); 4470 va_end(arguments); 4471 UIElementRefresh(&ui.inspectorLog->e); 4472 } 4473 4474 UIElement *_UIInspectorFindNthElement(UIElement *element, int *index, int *depth) { 4475 if (*index == 0) { 4476 return element; 4477 } 4478 4479 *index = *index - 1; 4480 4481 UIElement *child = element->children; 4482 4483 while (child) { 4484 if (!(child->flags & (UI_ELEMENT_DESTROY | UI_ELEMENT_HIDE))) { 4485 UIElement *result = _UIInspectorFindNthElement(child, index, depth); 4486 4487 if (result) { 4488 if (depth) { 4489 *depth = *depth + 1; 4490 } 4491 4492 return result; 4493 } 4494 } 4495 4496 child = child->next; 4497 } 4498 4499 return NULL; 4500 } 4501 4502 int _UIInspectorTableMessage(UIElement *element, UIMessage message, int di, void *dp) { 4503 if (!ui.inspectorTarget) { 4504 return 0; 4505 } 4506 4507 if (message == UI_MSG_TABLE_GET_ITEM) { 4508 UITableGetItem *m = (UITableGetItem *) dp; 4509 int index = m->index; 4510 int depth = 0; 4511 UIElement *element = _UIInspectorFindNthElement(&ui.inspectorTarget->e, &index, &depth); 4512 if (!element) return 0; 4513 4514 if (m->column == 0) { 4515 return snprintf(m->buffer, m->bufferBytes, "%.*s%s", depth * 2, " ", element->cClassName); 4516 } else if (m->column == 1) { 4517 return snprintf(m->buffer, m->bufferBytes, "%d:%d, %d:%d", UI_RECT_ALL(element->bounds)); 4518 } else if (m->column == 2) { 4519 return snprintf(m->buffer, m->bufferBytes, "%d%c", element->id, element->window->focused == element ? '*' : ' '); 4520 } 4521 } else if (message == UI_MSG_MOUSE_MOVE) { 4522 int index = UITableHitTest(ui.inspectorTable, element->window->cursorX, element->window->cursorY); 4523 UIElement *element = NULL; 4524 if (index >= 0) element = _UIInspectorFindNthElement(&ui.inspectorTarget->e, &index, NULL); 4525 UIWindow *window = ui.inspectorTarget; 4526 UIPainter painter = { 0 }; 4527 window->updateRegion = window->e.bounds; 4528 painter.bits = window->bits; 4529 painter.width = window->width; 4530 painter.height = window->height; 4531 painter.clip = UI_RECT_2S(window->width, window->height); 4532 4533 for (int i = 0; i < window->width * window->height; i++) { 4534 window->bits[i] = 0xFF00FF; 4535 } 4536 4537 _UIElementPaint(&window->e, &painter); 4538 painter.clip = UI_RECT_2S(window->width, window->height); 4539 4540 if (element) { 4541 UIDrawInvert(&painter, element->bounds); 4542 UIDrawInvert(&painter, UIRectangleAdd(element->bounds, UI_RECT_1I(4))); 4543 } 4544 4545 _UIWindowEndPaint(window, &painter); 4546 } 4547 4548 return 0; 4549 } 4550 4551 void _UIInspectorCreate() { 4552 ui.inspector = UIWindowCreate(0, UI_WINDOW_INSPECTOR, "Inspector", 0, 0); 4553 UISplitPane *splitPane = UISplitPaneCreate(&ui.inspector->e, 0, 0.5f); 4554 ui.inspectorTable = UITableCreate(&splitPane->e, 0, "Class\tBounds\tID"); 4555 ui.inspectorTable->e.messageUser = _UIInspectorTableMessage; 4556 ui.inspectorLog = UICodeCreate(&splitPane->e, 0); 4557 } 4558 4559 int _UIInspectorCountElements(UIElement *element) { 4560 UIElement *child = element->children; 4561 int count = 1; 4562 4563 while (child) { 4564 if (!(child->flags & (UI_ELEMENT_DESTROY | UI_ELEMENT_HIDE))) { 4565 count += _UIInspectorCountElements(child); 4566 } 4567 4568 child = child->next; 4569 } 4570 4571 return count; 4572 } 4573 4574 void _UIInspectorRefresh() { 4575 if (!ui.inspectorTarget || !ui.inspector || !ui.inspectorTable) return; 4576 ui.inspectorTable->itemCount = _UIInspectorCountElements(&ui.inspectorTarget->e); 4577 UITableResizeColumns(ui.inspectorTable); 4578 UIElementRefresh(&ui.inspectorTable->e); 4579 } 4580 4581 void _UIInspectorSetFocusedWindow(UIWindow *window) { 4582 if (!ui.inspector || !ui.inspectorTable) return; 4583 4584 if (window->e.flags & UI_WINDOW_INSPECTOR) { 4585 return; 4586 } 4587 4588 if (ui.inspectorTarget != window) { 4589 ui.inspectorTarget = window; 4590 _UIInspectorRefresh(); 4591 } 4592 } 4593 4594 #else 4595 4596 void _UIInspectorCreate() {} 4597 void _UIInspectorSetFocusedWindow(UIWindow *window) {} 4598 void _UIInspectorRefresh() {} 4599 4600 #endif 4601 4602 #ifdef UI_AUTOMATION_TESTS 4603 4604 int UIAutomationRunTests(); 4605 4606 void UIAutomationProcessMessage() { 4607 int result; 4608 _UIMessageLoopSingle(&result); 4609 } 4610 4611 void UIAutomationKeyboardTypeSingle(intptr_t code, bool ctrl, bool shift, bool alt) { 4612 UIWindow *window = ui.windows; // TODO Get the focused window. 4613 UIKeyTyped m = { 0 }; 4614 m.code = code; 4615 window->ctrl = ctrl; 4616 window->alt = alt; 4617 window->shift = shift; 4618 _UIWindowInputEvent(window, UI_MSG_KEY_TYPED, 0, &m); 4619 window->ctrl = false; 4620 window->alt = false; 4621 window->shift = false; 4622 } 4623 4624 void UIAutomationKeyboardType(const char *string) { 4625 UIWindow *window = ui.windows; // TODO Get the focused window. 4626 4627 UIKeyTyped m = { 0 }; 4628 char c[2]; 4629 m.text = c; 4630 m.textBytes = 1; 4631 c[1] = 0; 4632 4633 for (int i = 0; string[i]; i++) { 4634 window->ctrl = false; 4635 window->alt = false; 4636 window->shift = (c[0] >= 'A' && c[0] <= 'Z'); 4637 c[0] = string[i]; 4638 m.code = (c[0] >= 'A' && c[0] <= 'Z') ? UI_KEYCODE_LETTER(c[0]) 4639 : c[0] == '\n' ? UI_KEYCODE_ENTER 4640 : c[0] == '\t' ? UI_KEYCODE_TAB 4641 : c[0] == ' ' ? UI_KEYCODE_SPACE 4642 : (c[0] >= '0' && c[0] <= '9') ? UI_KEYCODE_DIGIT(c[0]) : 0; 4643 _UIWindowInputEvent(window, UI_MSG_KEY_TYPED, 0, &m); 4644 } 4645 4646 window->ctrl = false; 4647 window->alt = false; 4648 window->shift = false; 4649 } 4650 4651 bool UIAutomationCheckCodeLineMatches(UICode *code, int lineIndex, const char *input) { 4652 if (lineIndex < 1 || lineIndex > code->lineCount) return false; 4653 int bytes = 0; 4654 for (int i = 0; input[i]; i++) bytes++; 4655 if (bytes != code->lines[lineIndex - 1].bytes) return false; 4656 for (int i = 0; input[i]; i++) if (code->content[code->lines[lineIndex - 1].offset + i] != input[i]) return false; 4657 return true; 4658 } 4659 4660 bool UIAutomationCheckTableItemMatches(UITable *table, int row, int column, const char *input) { 4661 int bytes = 0; 4662 for (int i = 0; input[i]; i++) bytes++; 4663 if (row < 0 || row >= table->itemCount) return false; 4664 if (column < 0 || column >= table->columnCount) return false; 4665 char *buffer = (char *) UI_MALLOC(bytes + 1); 4666 UITableGetItem m = { 0 }; 4667 m.buffer = buffer; 4668 m.bufferBytes = bytes + 1; 4669 m.column = column; 4670 m.index = row; 4671 int length = UIElementMessage(&table->e, UI_MSG_TABLE_GET_ITEM, 0, &m); 4672 if (length != bytes) return false; 4673 for (int i = 0; input[i]; i++) if (buffer[i] != input[i]) return false; 4674 return true; 4675 } 4676 4677 #endif 4678 4679 int UIMessageLoop() { 4680 _UIInspectorCreate(); 4681 _UIUpdate(); 4682 #ifdef UI_AUTOMATION_TESTS 4683 return UIAutomationRunTests(); 4684 #else 4685 int result = 0; 4686 while (!ui.quit && _UIMessageLoopSingle(&result)) ui.dialogResult = NULL; 4687 return result; 4688 #endif 4689 } 4690 4691 #ifdef UI_LINUX 4692 4693 const int UI_KEYCODE_A = XK_a; 4694 const int UI_KEYCODE_BACKSPACE = XK_BackSpace; 4695 const int UI_KEYCODE_DELETE = XK_Delete; 4696 const int UI_KEYCODE_DOWN = XK_Down; 4697 const int UI_KEYCODE_END = XK_End; 4698 const int UI_KEYCODE_ENTER = XK_Return; 4699 const int UI_KEYCODE_ESCAPE = XK_Escape; 4700 const int UI_KEYCODE_F1 = XK_F1; 4701 const int UI_KEYCODE_HOME = XK_Home; 4702 const int UI_KEYCODE_LEFT = XK_Left; 4703 const int UI_KEYCODE_RIGHT = XK_Right; 4704 const int UI_KEYCODE_SPACE = XK_space; 4705 const int UI_KEYCODE_TAB = XK_Tab; 4706 const int UI_KEYCODE_UP = XK_Up; 4707 const int UI_KEYCODE_INSERT = XK_Insert; 4708 const int UI_KEYCODE_0 = XK_0; 4709 4710 int _UIWindowMessage(UIElement *element, UIMessage message, int di, void *dp) { 4711 if (message == UI_MSG_DESTROY) { 4712 UIWindow *window = (UIWindow *) element; 4713 _UIWindowDestroyCommon(window); 4714 window->image->data = NULL; 4715 XDestroyImage(window->image); 4716 XDestroyIC(window->xic); 4717 XDestroyWindow(ui.display, ((UIWindow *) element)->window); 4718 } 4719 4720 return _UIWindowMessageCommon(element, message, di, dp); 4721 } 4722 4723 UIWindow *UIWindowCreate(UIWindow *owner, uint32_t flags, const char *cTitle, int _width, int _height) { 4724 _UIMenusClose(); 4725 4726 UIWindow *window = (UIWindow *) UIElementCreate(sizeof(UIWindow), NULL, flags | UI_ELEMENT_WINDOW, _UIWindowMessage, "Window"); 4727 _UIWindowAdd(window); 4728 if (owner) window->scale = owner->scale; 4729 4730 int width = (flags & UI_WINDOW_MENU) ? 1 : _width ? _width : 800; 4731 int height = (flags & UI_WINDOW_MENU) ? 1 : _height ? _height : 600; 4732 4733 XSetWindowAttributes attributes = {}; 4734 attributes.override_redirect = flags & UI_WINDOW_MENU; 4735 4736 window->window = XCreateWindow(ui.display, DefaultRootWindow(ui.display), 0, 0, width, height, 0, 0, 4737 InputOutput, CopyFromParent, CWOverrideRedirect, &attributes); 4738 if (cTitle) XStoreName(ui.display, window->window, cTitle); 4739 XSelectInput(ui.display, window->window, SubstructureNotifyMask | ExposureMask | PointerMotionMask 4740 | ButtonPressMask | ButtonReleaseMask | KeyPressMask | KeyReleaseMask | StructureNotifyMask 4741 | EnterWindowMask | LeaveWindowMask | ButtonMotionMask | KeymapStateMask | FocusChangeMask | PropertyChangeMask); 4742 4743 if (flags & UI_WINDOW_MAXIMIZE) { 4744 Atom atoms[2] = { XInternAtom(ui.display, "_NET_WM_STATE_MAXIMIZED_HORZ", 0), XInternAtom(ui.display, "_NET_WM_STATE_MAXIMIZED_VERT", 0) }; 4745 XChangeProperty(ui.display, window->window, XInternAtom(ui.display, "_NET_WM_STATE", 0), XA_ATOM, 32, PropModeReplace, (unsigned char *) atoms, 2); 4746 } 4747 4748 if (~flags & UI_WINDOW_MENU) { 4749 XMapRaised(ui.display, window->window); 4750 } 4751 4752 if (flags & UI_WINDOW_CENTER_IN_OWNER) { 4753 int x = 0, y = 0; 4754 _UIWindowGetScreenPosition(owner, &x, &y); 4755 XMoveResizeWindow(ui.display, window->window, x + owner->width / 2 - width / 2, y + owner->height / 2 - height / 2, width, height); 4756 } 4757 4758 XSetWMProtocols(ui.display, window->window, &ui.windowClosedID, 1); 4759 window->image = XCreateImage(ui.display, ui.visual, 24, ZPixmap, 0, NULL, 10, 10, 32, 0); 4760 4761 window->xic = XCreateIC(ui.xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, window->window, XNFocusWindow, window->window, NULL); 4762 4763 int dndVersion = 4; 4764 XChangeProperty(ui.display, window->window, ui.dndAwareID, XA_ATOM, 32 /* bits */, PropModeReplace, (uint8_t *) &dndVersion, 1); 4765 4766 return window; 4767 } 4768 4769 Display *_UIX11GetDisplay() { 4770 return ui.display; 4771 } 4772 4773 UIWindow *_UIFindWindow(Window window) { 4774 UIWindow *w = ui.windows; 4775 4776 while (w) { 4777 if (w->window == window) { 4778 return w; 4779 } 4780 4781 w = w->next; 4782 } 4783 4784 return NULL; 4785 } 4786 4787 void _UIClipboardWriteText(UIWindow *window, char *text) { 4788 UI_FREE(ui.pasteText); 4789 ui.pasteText = text; 4790 XSetSelectionOwner(ui.display, ui.clipboardID, window->window, 0); 4791 } 4792 4793 char *_UIClipboardReadTextStart(UIWindow *window, size_t *bytes) { 4794 Window clipboardOwner = XGetSelectionOwner(ui.display, ui.clipboardID); 4795 4796 if (clipboardOwner == None) { 4797 return NULL; 4798 } 4799 4800 if (_UIFindWindow(clipboardOwner)) { 4801 *bytes = strlen(ui.pasteText); 4802 char *copy = (char *) UI_MALLOC(*bytes); 4803 memcpy(copy, ui.pasteText, *bytes); 4804 return copy; 4805 } 4806 4807 XConvertSelection(ui.display, ui.clipboardID, XA_STRING, ui.xSelectionDataID, window->window, CurrentTime); 4808 XSync(ui.display, 0); 4809 XNextEvent(ui.display, &ui.copyEvent); 4810 4811 // Hack to get around the fact that PropertyNotify arrives before SelectionNotify. 4812 // We need PropertyNotify for incremental transfers. 4813 while (ui.copyEvent.type == PropertyNotify) { 4814 XNextEvent(ui.display, &ui.copyEvent); 4815 } 4816 4817 if (ui.copyEvent.type == SelectionNotify && ui.copyEvent.xselection.selection == ui.clipboardID && ui.copyEvent.xselection.property) { 4818 Atom target; 4819 // This `itemAmount` is actually `bytes_after_return` 4820 unsigned long size, itemAmount; 4821 char *data; 4822 int format; 4823 XGetWindowProperty(ui.copyEvent.xselection.display, ui.copyEvent.xselection.requestor, ui.copyEvent.xselection.property, 0L, ~0L, 0, 4824 AnyPropertyType, &target, &format, &size, &itemAmount, (unsigned char **) &data); 4825 4826 // We have to allocate for incremental transfers but we don't have to allocate for non-incremental transfers. 4827 // I'm allocating for both here to make _UIClipboardReadTextEnd work the same for both 4828 if (target != ui.incrID) { 4829 *bytes = size; 4830 char *copy = (char *) UI_MALLOC(*bytes); 4831 memcpy(copy, data, *bytes); 4832 XFree(data); 4833 XDeleteProperty(ui.copyEvent.xselection.display, ui.copyEvent.xselection.requestor, ui.copyEvent.xselection.property); 4834 return copy; 4835 } 4836 4837 XFree(data); 4838 XDeleteProperty(ui.display, ui.copyEvent.xselection.requestor, ui.copyEvent.xselection.property); 4839 XSync(ui.display, 0); 4840 4841 *bytes = 0; 4842 char *fullData = NULL; 4843 4844 while (true) { 4845 // TODO Timeout. 4846 XNextEvent(ui.display, &ui.copyEvent); 4847 4848 if (ui.copyEvent.type == PropertyNotify) { 4849 // The other case - PropertyDelete would be caused by us and can be ignored 4850 if (ui.copyEvent.xproperty.state == PropertyNewValue) { 4851 unsigned long chunkSize; 4852 4853 // Note that this call deletes the property. 4854 XGetWindowProperty(ui.display, ui.copyEvent.xproperty.window, ui.copyEvent.xproperty.atom, 0L, ~0L, 4855 True, AnyPropertyType, &target, &format, &chunkSize, &itemAmount, (unsigned char **) &data); 4856 4857 if (chunkSize == 0) { 4858 return fullData; 4859 } else { 4860 ptrdiff_t currentOffset = *bytes; 4861 *bytes += chunkSize; 4862 fullData = (char *) UI_REALLOC(fullData, *bytes); 4863 memcpy(fullData + currentOffset, data, chunkSize); 4864 } 4865 4866 XFree(data); 4867 } 4868 } 4869 } 4870 } else { 4871 // TODO What should happen in this case? Is the next event always going to be the selection event? 4872 return NULL; 4873 } 4874 } 4875 4876 void _UIClipboardReadTextEnd(UIWindow *window, char *text) { 4877 if (text) { 4878 //XFree(text); 4879 //XDeleteProperty(ui.copyEvent.xselection.display, ui.copyEvent.xselection.requestor, ui.copyEvent.xselection.property); 4880 UI_FREE(text); 4881 } 4882 } 4883 4884 void UIInitialise() { 4885 _UIInitialiseCommon(); 4886 4887 XInitThreads(); 4888 4889 ui.display = XOpenDisplay(NULL); 4890 ui.visual = XDefaultVisual(ui.display, 0); 4891 4892 ui.windowClosedID = XInternAtom(ui.display, "WM_DELETE_WINDOW", 0); 4893 ui.primaryID = XInternAtom(ui.display, "PRIMARY", 0); 4894 ui.dndEnterID = XInternAtom(ui.display, "XdndEnter", 0); 4895 ui.dndPositionID = XInternAtom(ui.display, "XdndPosition", 0); 4896 ui.dndStatusID = XInternAtom(ui.display, "XdndStatus", 0); 4897 ui.dndActionCopyID = XInternAtom(ui.display, "XdndActionCopy", 0); 4898 ui.dndDropID = XInternAtom(ui.display, "XdndDrop", 0); 4899 ui.dndSelectionID = XInternAtom(ui.display, "XdndSelection", 0); 4900 ui.dndFinishedID = XInternAtom(ui.display, "XdndFinished", 0); 4901 ui.dndAwareID = XInternAtom(ui.display, "XdndAware", 0); 4902 ui.uriListID = XInternAtom(ui.display, "text/uri-list", 0); 4903 ui.plainTextID = XInternAtom(ui.display, "text/plain", 0); 4904 ui.clipboardID = XInternAtom(ui.display, "CLIPBOARD", 0); 4905 ui.xSelectionDataID = XInternAtom(ui.display, "XSEL_DATA", 0); 4906 ui.textID = XInternAtom(ui.display, "TEXT", 0); 4907 ui.targetID = XInternAtom(ui.display, "TARGETS", 0); 4908 ui.incrID = XInternAtom(ui.display, "INCR", 0); 4909 4910 ui.cursors[UI_CURSOR_ARROW] = XCreateFontCursor(ui.display, XC_left_ptr); 4911 ui.cursors[UI_CURSOR_TEXT] = XCreateFontCursor(ui.display, XC_xterm); 4912 ui.cursors[UI_CURSOR_SPLIT_V] = XCreateFontCursor(ui.display, XC_sb_v_double_arrow); 4913 ui.cursors[UI_CURSOR_SPLIT_H] = XCreateFontCursor(ui.display, XC_sb_h_double_arrow); 4914 ui.cursors[UI_CURSOR_FLIPPED_ARROW] = XCreateFontCursor(ui.display, XC_right_ptr); 4915 ui.cursors[UI_CURSOR_CROSS_HAIR] = XCreateFontCursor(ui.display, XC_crosshair); 4916 ui.cursors[UI_CURSOR_HAND] = XCreateFontCursor(ui.display, XC_hand1); 4917 ui.cursors[UI_CURSOR_RESIZE_UP] = XCreateFontCursor(ui.display, XC_top_side); 4918 ui.cursors[UI_CURSOR_RESIZE_LEFT] = XCreateFontCursor(ui.display, XC_left_side); 4919 ui.cursors[UI_CURSOR_RESIZE_UP_RIGHT] = XCreateFontCursor(ui.display, XC_top_right_corner); 4920 ui.cursors[UI_CURSOR_RESIZE_UP_LEFT] = XCreateFontCursor(ui.display, XC_top_left_corner); 4921 ui.cursors[UI_CURSOR_RESIZE_DOWN] = XCreateFontCursor(ui.display, XC_bottom_side); 4922 ui.cursors[UI_CURSOR_RESIZE_RIGHT] = XCreateFontCursor(ui.display, XC_right_side); 4923 ui.cursors[UI_CURSOR_RESIZE_DOWN_LEFT] = XCreateFontCursor(ui.display, XC_bottom_left_corner); 4924 ui.cursors[UI_CURSOR_RESIZE_DOWN_RIGHT] = XCreateFontCursor(ui.display, XC_bottom_right_corner); 4925 4926 XSetLocaleModifiers(""); 4927 4928 ui.xim = XOpenIM(ui.display, 0, 0, 0); 4929 4930 if(!ui.xim){ 4931 XSetLocaleModifiers("@im=none"); 4932 ui.xim = XOpenIM(ui.display, 0, 0, 0); 4933 } 4934 } 4935 4936 void _UIWindowSetCursor(UIWindow *window, int cursor) { 4937 XDefineCursor(ui.display, window->window, ui.cursors[cursor]); 4938 } 4939 4940 void _UIX11ResetCursor(UIWindow *window) { 4941 XDefineCursor(ui.display, window->window, ui.cursors[UI_CURSOR_ARROW]); 4942 } 4943 4944 void _UIWindowEndPaint(UIWindow *window, UIPainter *painter) { 4945 (void) painter; 4946 4947 XPutImage(ui.display, window->window, DefaultGC(ui.display, 0), window->image, 4948 UI_RECT_TOP_LEFT(window->updateRegion), UI_RECT_TOP_LEFT(window->updateRegion), 4949 UI_RECT_SIZE(window->updateRegion)); 4950 } 4951 4952 void _UIWindowGetScreenPosition(UIWindow *window, int *_x, int *_y) { 4953 Window child; 4954 XTranslateCoordinates(ui.display, window->window, DefaultRootWindow(ui.display), 0, 0, _x, _y, &child); 4955 } 4956 4957 void UIMenuShow(UIMenu *menu) { 4958 int width, height; 4959 _UIMenuPrepare(menu, &width, &height); 4960 4961 for (int i = 0; i < ScreenCount(ui.display); i++) { 4962 Screen *screen = ScreenOfDisplay(ui.display, i); 4963 4964 int x, y; 4965 Window child; 4966 XTranslateCoordinates(ui.display, screen->root, DefaultRootWindow(ui.display), 0, 0, &x, &y, &child); 4967 4968 if (menu->pointX >= x && menu->pointX < x + screen->width 4969 && menu->pointY >= y && menu->pointY < y + screen->height) { 4970 if (menu->pointX + width > x + screen->width) menu->pointX = x + screen->width - width; 4971 if (menu->pointY + height > y + screen->height) menu->pointY = y + screen->height - height; 4972 if (menu->pointX < x) menu->pointX = x; 4973 if (menu->pointY < y) menu->pointY = y; 4974 if (menu->pointX + width > x + screen->width) width = x + screen->width - menu->pointX; 4975 if (menu->pointY + height > y + screen->height) height = y + screen->height - menu->pointY; 4976 break; 4977 } 4978 } 4979 4980 Atom properties[] = { 4981 XInternAtom(ui.display, "_NET_WM_WINDOW_TYPE", true), 4982 XInternAtom(ui.display, "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU", true), 4983 XInternAtom(ui.display, "_MOTIF_WM_HINTS", true), 4984 }; 4985 4986 XChangeProperty(ui.display, menu->e.window->window, properties[0], XA_ATOM, 32, PropModeReplace, (uint8_t *) properties, 2); 4987 XSetTransientForHint(ui.display, menu->e.window->window, DefaultRootWindow(ui.display)); 4988 4989 struct Hints { 4990 int flags; 4991 int functions; 4992 int decorations; 4993 int inputMode; 4994 int status; 4995 }; 4996 4997 struct Hints hints = { 0 }; 4998 hints.flags = 2; 4999 XChangeProperty(ui.display, menu->e.window->window, properties[2], properties[2], 32, PropModeReplace, (uint8_t *) &hints, 5); 5000 5001 XMapWindow(ui.display, menu->e.window->window); 5002 XMoveResizeWindow(ui.display, menu->e.window->window, menu->pointX, menu->pointY, width, height); 5003 } 5004 5005 void UIWindowPack(UIWindow *window, int _width) { 5006 int width = _width ? _width : UIElementMessage(window->e.children, UI_MSG_GET_WIDTH, 0, 0); 5007 int height = UIElementMessage(window->e.children, UI_MSG_GET_HEIGHT, width, 0); 5008 XResizeWindow(ui.display, window->window, width, height); 5009 } 5010 5011 bool _UIProcessEvent(XEvent *event) { 5012 if (event->type == ClientMessage && (Atom) event->xclient.data.l[0] == ui.windowClosedID) { 5013 UIWindow *window = _UIFindWindow(event->xclient.window); 5014 if (!window) return false; 5015 bool exit = !UIElementMessage(&window->e, UI_MSG_WINDOW_CLOSE, 0, 0); 5016 if (exit) return true; 5017 _UIUpdate(); 5018 return false; 5019 } else if (event->type == Expose) { 5020 UIWindow *window = _UIFindWindow(event->xexpose.window); 5021 if (!window) return false; 5022 XPutImage(ui.display, window->window, DefaultGC(ui.display, 0), window->image, 0, 0, 0, 0, window->width, window->height); 5023 } else if (event->type == ConfigureNotify) { 5024 UIWindow *window = _UIFindWindow(event->xconfigure.window); 5025 if (!window) return false; 5026 5027 if (window->width != event->xconfigure.width || window->height != event->xconfigure.height) { 5028 window->width = event->xconfigure.width; 5029 window->height = event->xconfigure.height; 5030 window->bits = (uint32_t *) UI_REALLOC(window->bits, window->width * window->height * 4); 5031 window->image->width = window->width; 5032 window->image->height = window->height; 5033 window->image->bytes_per_line = window->width * 4; 5034 window->image->data = (char *) window->bits; 5035 window->e.bounds = UI_RECT_2S(window->width, window->height); 5036 window->e.clip = UI_RECT_2S(window->width, window->height); 5037 #ifdef UI_DEBUG 5038 for (int i = 0; i < window->width * window->height; i++) window->bits[i] = 0xFF00FF; 5039 #endif 5040 UIElementMessage(&window->e, UI_MSG_LAYOUT, 0, 0); 5041 _UIUpdate(); 5042 } 5043 } else if (event->type == MotionNotify) { 5044 UIWindow *window = _UIFindWindow(event->xmotion.window); 5045 if (!window) return false; 5046 window->cursorX = event->xmotion.x; 5047 window->cursorY = event->xmotion.y; 5048 _UIWindowInputEvent(window, UI_MSG_MOUSE_MOVE, 0, 0); 5049 } else if (event->type == LeaveNotify) { 5050 UIWindow *window = _UIFindWindow(event->xcrossing.window); 5051 if (!window) return false; 5052 5053 if (!window->pressed) { 5054 window->cursorX = -1; 5055 window->cursorY = -1; 5056 } 5057 5058 _UIWindowInputEvent(window, UI_MSG_MOUSE_MOVE, 0, 0); 5059 } else if (event->type == ButtonPress || event->type == ButtonRelease) { 5060 UIWindow *window = _UIFindWindow(event->xbutton.window); 5061 if (!window) return false; 5062 window->cursorX = event->xbutton.x; 5063 window->cursorY = event->xbutton.y; 5064 5065 if (event->xbutton.button >= 1 && event->xbutton.button <= 3) { 5066 _UIWindowInputEvent(window, (UIMessage) ((event->type == ButtonPress ? UI_MSG_LEFT_DOWN : UI_MSG_LEFT_UP) 5067 + event->xbutton.button * 2 - 2), 0, 0); 5068 } else if (event->xbutton.button == 4) { 5069 _UIWindowInputEvent(window, UI_MSG_MOUSE_WHEEL, -72, 0); 5070 } else if (event->xbutton.button == 5) { 5071 _UIWindowInputEvent(window, UI_MSG_MOUSE_WHEEL, 72, 0); 5072 } 5073 5074 _UIInspectorSetFocusedWindow(window); 5075 } else if (event->type == KeyPress) { 5076 UIWindow *window = _UIFindWindow(event->xkey.window); 5077 if (!window) return false; 5078 5079 if (event->xkey.x == 0x7123 && event->xkey.y == 0x7456) { 5080 // HACK! See UIWindowPostMessage. 5081 UIElementMessage(&window->e, (UIMessage) event->xkey.state, 0, 5082 (void *) (((uintptr_t) (event->xkey.time & 0xFFFFFFFF) << 32) 5083 | ((uintptr_t) (event->xkey.x_root & 0xFFFF) << 0) 5084 | ((uintptr_t) (event->xkey.y_root & 0xFFFF) << 16))); 5085 _UIUpdate(); 5086 } else { 5087 char text[32]; 5088 KeySym symbol = NoSymbol; 5089 Status status; 5090 // printf("%ld, %s\n", symbol, text); 5091 UIKeyTyped m = { 0 }; 5092 m.textBytes = Xutf8LookupString(window->xic, &event->xkey, text, sizeof(text) - 1, &symbol, &status); 5093 m.text = text; 5094 m.code = XLookupKeysym(&event->xkey, 0); 5095 5096 if (symbol == XK_Control_L || symbol == XK_Control_R) { 5097 window->ctrl = true; 5098 window->ctrlCode = event->xkey.keycode; 5099 _UIWindowInputEvent(window, UI_MSG_MOUSE_MOVE, 0, 0); 5100 } else if (symbol == XK_Shift_L || symbol == XK_Shift_R) { 5101 window->shift = true; 5102 window->shiftCode = event->xkey.keycode; 5103 _UIWindowInputEvent(window, UI_MSG_MOUSE_MOVE, 0, 0); 5104 } else if (symbol == XK_Alt_L || symbol == XK_Alt_R) { 5105 window->alt = true; 5106 window->altCode = event->xkey.keycode; 5107 _UIWindowInputEvent(window, UI_MSG_MOUSE_MOVE, 0, 0); 5108 } else if (symbol == XK_KP_Left) { 5109 m.code = UI_KEYCODE_LEFT; 5110 } else if (symbol == XK_KP_Right) { 5111 m.code = UI_KEYCODE_RIGHT; 5112 } else if (symbol == XK_KP_Up) { 5113 m.code = UI_KEYCODE_UP; 5114 } else if (symbol == XK_KP_Down) { 5115 m.code = UI_KEYCODE_DOWN; 5116 } else if (symbol == XK_KP_Home) { 5117 m.code = UI_KEYCODE_HOME; 5118 } else if (symbol == XK_KP_End) { 5119 m.code = UI_KEYCODE_END; 5120 } else if (symbol == XK_KP_Enter) { 5121 m.code = UI_KEYCODE_ENTER; 5122 } else if (symbol == XK_KP_Delete) { 5123 m.code = UI_KEYCODE_DELETE; 5124 } 5125 5126 _UIWindowInputEvent(window, UI_MSG_KEY_TYPED, 0, &m); 5127 } 5128 } else if (event->type == KeyRelease) { 5129 UIWindow *window = _UIFindWindow(event->xkey.window); 5130 if (!window) return false; 5131 5132 if (event->xkey.keycode == window->ctrlCode) { 5133 window->ctrl = false; 5134 _UIWindowInputEvent(window, UI_MSG_MOUSE_MOVE, 0, 0); 5135 } else if (event->xkey.keycode == window->shiftCode) { 5136 window->shift = false; 5137 _UIWindowInputEvent(window, UI_MSG_MOUSE_MOVE, 0, 0); 5138 } else if (event->xkey.keycode == window->altCode) { 5139 window->alt = false; 5140 _UIWindowInputEvent(window, UI_MSG_MOUSE_MOVE, 0, 0); 5141 } 5142 } else if (event->type == FocusIn) { 5143 UIWindow *window = _UIFindWindow(event->xfocus.window); 5144 if (!window) return false; 5145 window->ctrl = window->shift = window->alt = false; 5146 UIElementMessage(&window->e, UI_MSG_WINDOW_ACTIVATE, 0, 0); 5147 } else if (event->type == ClientMessage && event->xclient.message_type == ui.dndEnterID) { 5148 UIWindow *window = _UIFindWindow(event->xclient.window); 5149 if (!window) return false; 5150 window->dragSource = (Window) event->xclient.data.l[0]; 5151 } else if (event->type == ClientMessage && event->xclient.message_type == ui.dndPositionID) { 5152 UIWindow *window = _UIFindWindow(event->xclient.window); 5153 if (!window) return false; 5154 XClientMessageEvent m = { 0 }; 5155 m.type = ClientMessage; 5156 m.display = event->xclient.display; 5157 m.window = (Window) event->xclient.data.l[0]; 5158 m.message_type = ui.dndStatusID; 5159 m.format = 32; 5160 m.data.l[0] = window->window; 5161 m.data.l[1] = true; 5162 m.data.l[4] = ui.dndActionCopyID; 5163 XSendEvent(ui.display, m.window, False, NoEventMask, (XEvent *) &m); 5164 XFlush(ui.display); 5165 } else if (event->type == ClientMessage && event->xclient.message_type == ui.dndDropID) { 5166 UIWindow *window = _UIFindWindow(event->xclient.window); 5167 if (!window) return false; 5168 5169 // TODO Dropping text. 5170 5171 if (!XConvertSelection(ui.display, ui.dndSelectionID, ui.uriListID, ui.primaryID, window->window, event->xclient.data.l[2])) { 5172 XClientMessageEvent m = { 0 }; 5173 m.type = ClientMessage; 5174 m.display = ui.display; 5175 m.window = window->dragSource; 5176 m.message_type = ui.dndFinishedID; 5177 m.format = 32; 5178 m.data.l[0] = window->window; 5179 m.data.l[1] = 0; 5180 m.data.l[2] = ui.dndActionCopyID; 5181 XSendEvent(ui.display, m.window, False, NoEventMask, (XEvent *) &m); 5182 XFlush(ui.display); 5183 } 5184 } else if (event->type == SelectionNotify) { 5185 UIWindow *window = _UIFindWindow(event->xselection.requestor); 5186 if (!window) return false; 5187 if (!window->dragSource) return false; 5188 5189 Atom type = None; 5190 int format = 0; 5191 uint64_t count = 0, bytesLeft = 0; 5192 uint8_t *data = NULL; 5193 XGetWindowProperty(ui.display, window->window, ui.primaryID, 0, 65536, False, AnyPropertyType, &type, &format, &count, &bytesLeft, &data); 5194 5195 if (format == 8 /* bits per character */) { 5196 if (event->xselection.target == ui.uriListID) { 5197 char *copy = (char *) UI_MALLOC(count); 5198 int fileCount = 0; 5199 5200 for (int i = 0; i < (int) count; i++) { 5201 copy[i] = data[i]; 5202 5203 if (i && data[i - 1] == '\r' && data[i] == '\n') { 5204 fileCount++; 5205 } 5206 } 5207 5208 char **files = (char **) UI_MALLOC(sizeof(char *) * fileCount); 5209 fileCount = 0; 5210 5211 for (int i = 0; i < (int) count; i++) { 5212 char *s = copy + i; 5213 while (!(i && data[i - 1] == '\r' && data[i] == '\n' && i < (int) count)) i++; 5214 copy[i - 1] = 0; 5215 5216 for (int j = 0; s[j]; j++) { 5217 if (s[j] == '%' && s[j + 1] && s[j + 2]) { 5218 char n[3]; 5219 n[0] = s[j + 1], n[1] = s[j + 2], n[2] = 0; 5220 s[j] = strtol(n, NULL, 16); 5221 if (!s[j]) break; 5222 memmove(s + j + 1, s + j + 3, strlen(s) - j - 2); 5223 } 5224 } 5225 5226 if (s[0] == 'f' && s[1] == 'i' && s[2] == 'l' && s[3] == 'e' && s[4] == ':' && s[5] == '/' && s[6] == '/') { 5227 files[fileCount++] = s + 7; 5228 } 5229 } 5230 5231 UIElementMessage(&window->e, UI_MSG_WINDOW_DROP_FILES, fileCount, files); 5232 5233 UI_FREE(files); 5234 UI_FREE(copy); 5235 } else if (event->xselection.target == ui.plainTextID) { 5236 // TODO. 5237 } 5238 } 5239 5240 XFree(data); 5241 5242 XClientMessageEvent m = { 0 }; 5243 m.type = ClientMessage; 5244 m.display = ui.display; 5245 m.window = window->dragSource; 5246 m.message_type = ui.dndFinishedID; 5247 m.format = 32; 5248 m.data.l[0] = window->window; 5249 m.data.l[1] = true; 5250 m.data.l[2] = ui.dndActionCopyID; 5251 XSendEvent(ui.display, m.window, False, NoEventMask, (XEvent *) &m); 5252 XFlush(ui.display); 5253 5254 window->dragSource = 0; // Drag complete. 5255 _UIUpdate(); 5256 } else if (event->type == SelectionRequest) { 5257 UIWindow *window = _UIFindWindow(event->xclient.window); 5258 if (!window) return false; 5259 5260 if ((XGetSelectionOwner(ui.display, ui.clipboardID) == window->window) 5261 && (event->xselectionrequest.selection == ui.clipboardID)) { 5262 XSelectionRequestEvent requestEvent = event->xselectionrequest; 5263 Atom utf8ID = XInternAtom(ui.display, "UTF8_STRING", 1); 5264 if (utf8ID == None) utf8ID = XA_STRING; 5265 5266 Atom type = requestEvent.target; 5267 type = (type == ui.textID) ? XA_STRING : type; 5268 int changePropertyResult = 0; 5269 5270 if(requestEvent.target == XA_STRING || requestEvent.target == ui.textID || requestEvent.target == utf8ID) { 5271 changePropertyResult = XChangeProperty(requestEvent.display, requestEvent.requestor, requestEvent.property, 5272 type, 8, PropModeReplace, (const unsigned char *) ui.pasteText, strlen(ui.pasteText)); 5273 } else if (requestEvent.target == ui.targetID) { 5274 changePropertyResult = XChangeProperty(requestEvent.display, requestEvent.requestor, requestEvent.property, 5275 XA_ATOM, 32, PropModeReplace, (unsigned char *) &utf8ID, 1); 5276 } 5277 5278 if(changePropertyResult == 0 || changePropertyResult == 1) { 5279 XSelectionEvent sendEvent = { 5280 .type = SelectionNotify, 5281 .serial = requestEvent.serial, 5282 .send_event = requestEvent.send_event, 5283 .display = requestEvent.display, 5284 .requestor = requestEvent.requestor, 5285 .selection = requestEvent.selection, 5286 .target = requestEvent.target, 5287 .property = requestEvent.property, 5288 .time = requestEvent.time 5289 }; 5290 5291 XSendEvent(ui.display, requestEvent.requestor, 0, 0, (XEvent *) &sendEvent); 5292 } 5293 } 5294 } 5295 5296 return false; 5297 } 5298 5299 bool _UIMessageLoopSingle(int *result) { 5300 XEvent events[64]; 5301 5302 if (ui.animating) { 5303 if (XPending(ui.display)) { 5304 XNextEvent(ui.display, events + 0); 5305 } else { 5306 _UIProcessAnimations(); 5307 return true; 5308 } 5309 } else { 5310 XNextEvent(ui.display, events + 0); 5311 } 5312 5313 int p = 1; 5314 5315 int configureIndex = -1, motionIndex = -1, exposeIndex = -1; 5316 5317 while (p < 64 && XPending(ui.display)) { 5318 XNextEvent(ui.display, events + p); 5319 5320 #define _UI_MERGE_EVENTS(a, b) \ 5321 if (events[p].type == a) { \ 5322 if (b != -1) events[b].type = 0; \ 5323 b = p; \ 5324 } 5325 5326 _UI_MERGE_EVENTS(ConfigureNotify, configureIndex); 5327 _UI_MERGE_EVENTS(MotionNotify, motionIndex); 5328 _UI_MERGE_EVENTS(Expose, exposeIndex); 5329 5330 p++; 5331 } 5332 5333 for (int i = 0; i < p; i++) { 5334 if (!events[i].type) { 5335 continue; 5336 } 5337 5338 if (_UIProcessEvent(events + i)) { 5339 return false; 5340 } 5341 } 5342 5343 return true; 5344 } 5345 5346 void UIWindowPostMessage(UIWindow *window, UIMessage message, void *_dp) { 5347 // HACK! Xlib doesn't seem to have a nice way to do this, 5348 // so send a specially crafted key press event instead. 5349 // TODO Maybe ClientMessage is what this should use? 5350 uintptr_t dp = (uintptr_t) _dp; 5351 XKeyEvent event = { 0 }; 5352 event.display = ui.display; 5353 event.window = window->window; 5354 event.root = DefaultRootWindow(ui.display); 5355 event.subwindow = None; 5356 event.time = dp >> 32; 5357 event.x = 0x7123; 5358 event.y = 0x7456; 5359 event.x_root = (dp >> 0) & 0xFFFF; 5360 event.y_root = (dp >> 16) & 0xFFFF; 5361 event.same_screen = True; 5362 event.keycode = 1; 5363 event.state = message; 5364 event.type = KeyPress; 5365 XSendEvent(ui.display, window->window, True, KeyPressMask, (XEvent *) &event); 5366 XFlush(ui.display); 5367 } 5368 5369 #endif 5370 5371 #ifdef UI_WINDOWS 5372 5373 const int UI_KEYCODE_A = 'A'; 5374 const int UI_KEYCODE_0 = '0'; 5375 const int UI_KEYCODE_BACKSPACE = VK_BACK; 5376 const int UI_KEYCODE_DELETE = VK_DELETE; 5377 const int UI_KEYCODE_DOWN = VK_DOWN; 5378 const int UI_KEYCODE_END = VK_END; 5379 const int UI_KEYCODE_ENTER = VK_RETURN; 5380 const int UI_KEYCODE_ESCAPE = VK_ESCAPE; 5381 const int UI_KEYCODE_F1 = VK_F1; 5382 const int UI_KEYCODE_HOME = VK_HOME; 5383 const int UI_KEYCODE_LEFT = VK_LEFT; 5384 const int UI_KEYCODE_RIGHT = VK_RIGHT; 5385 const int UI_KEYCODE_SPACE = VK_SPACE; 5386 const int UI_KEYCODE_TAB = VK_TAB; 5387 const int UI_KEYCODE_UP = VK_UP; 5388 const int UI_KEYCODE_INSERT = VK_INSERT; 5389 5390 int _UIWindowMessage(UIElement *element, UIMessage message, int di, void *dp) { 5391 if (message == UI_MSG_DESTROY) { 5392 UIWindow *window = (UIWindow *) element; 5393 _UIWindowDestroyCommon(window); 5394 SetWindowLongPtr(window->hwnd, GWLP_USERDATA, 0); 5395 DestroyWindow(window->hwnd); 5396 } 5397 5398 return _UIWindowMessageCommon(element, message, di, dp); 5399 } 5400 5401 LRESULT CALLBACK _UIWindowProcedure(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { 5402 UIWindow *window = (UIWindow *) GetWindowLongPtr(hwnd, GWLP_USERDATA); 5403 5404 if (!window || ui.assertionFailure) { 5405 return DefWindowProc(hwnd, message, wParam, lParam); 5406 } 5407 5408 if (message == WM_CLOSE) { 5409 if (UIElementMessage(&window->e, UI_MSG_WINDOW_CLOSE, 0, 0)) { 5410 _UIUpdate(); 5411 return 0; 5412 } else { 5413 PostQuitMessage(0); 5414 } 5415 } else if (message == WM_SIZE) { 5416 RECT client; 5417 GetClientRect(hwnd, &client); 5418 window->width = client.right; 5419 window->height = client.bottom; 5420 window->bits = (uint32_t *) UI_REALLOC(window->bits, window->width * window->height * 4); 5421 window->e.bounds = UI_RECT_2S(window->width, window->height); 5422 window->e.clip = UI_RECT_2S(window->width, window->height); 5423 UIElementMessage(&window->e, UI_MSG_LAYOUT, 0, 0); 5424 _UIUpdate(); 5425 } else if (message == WM_MOUSEMOVE) { 5426 if (!window->trackingLeave) { 5427 window->trackingLeave = true; 5428 TRACKMOUSEEVENT leave = { 0 }; 5429 leave.cbSize = sizeof(TRACKMOUSEEVENT); 5430 leave.dwFlags = TME_LEAVE; 5431 leave.hwndTrack = hwnd; 5432 TrackMouseEvent(&leave); 5433 } 5434 5435 POINT cursor; 5436 GetCursorPos(&cursor); 5437 ScreenToClient(hwnd, &cursor); 5438 window->cursorX = cursor.x; 5439 window->cursorY = cursor.y; 5440 _UIWindowInputEvent(window, UI_MSG_MOUSE_MOVE, 0, 0); 5441 } else if (message == WM_MOUSELEAVE) { 5442 window->trackingLeave = false; 5443 5444 if (!window->pressed) { 5445 window->cursorX = -1; 5446 window->cursorY = -1; 5447 } 5448 5449 _UIWindowInputEvent(window, UI_MSG_MOUSE_MOVE, 0, 0); 5450 } else if (message == WM_LBUTTONDOWN) { 5451 SetCapture(hwnd); 5452 _UIWindowInputEvent(window, UI_MSG_LEFT_DOWN, 0, 0); 5453 } else if (message == WM_LBUTTONUP) { 5454 if (window->pressedButton == 1) ReleaseCapture(); 5455 _UIWindowInputEvent(window, UI_MSG_LEFT_UP, 0, 0); 5456 } else if (message == WM_MBUTTONDOWN) { 5457 SetCapture(hwnd); 5458 _UIWindowInputEvent(window, UI_MSG_MIDDLE_DOWN, 0, 0); 5459 } else if (message == WM_MBUTTONUP) { 5460 if (window->pressedButton == 2) ReleaseCapture(); 5461 _UIWindowInputEvent(window, UI_MSG_MIDDLE_UP, 0, 0); 5462 } else if (message == WM_RBUTTONDOWN) { 5463 SetCapture(hwnd); 5464 _UIWindowInputEvent(window, UI_MSG_RIGHT_DOWN, 0, 0); 5465 } else if (message == WM_RBUTTONUP) { 5466 if (window->pressedButton == 3) ReleaseCapture(); 5467 _UIWindowInputEvent(window, UI_MSG_RIGHT_UP, 0, 0); 5468 } else if (message == WM_MOUSEWHEEL) { 5469 int delta = (int) wParam >> 16; 5470 _UIWindowInputEvent(window, UI_MSG_MOUSE_WHEEL, -delta, 0); 5471 } else if (message == WM_KEYDOWN) { 5472 window->ctrl = GetKeyState(VK_CONTROL) & 0x8000; 5473 window->shift = GetKeyState(VK_SHIFT) & 0x8000; 5474 window->alt = GetKeyState(VK_MENU) & 0x8000; 5475 5476 UIKeyTyped m = { 0 }; 5477 m.code = wParam; 5478 _UIWindowInputEvent(window, UI_MSG_KEY_TYPED, 0, &m); 5479 } else if (message == WM_CHAR) { 5480 UIKeyTyped m = { 0 }; 5481 char c = wParam; 5482 m.text = &c; 5483 m.textBytes = 1; 5484 _UIWindowInputEvent(window, UI_MSG_KEY_TYPED, 0, &m); 5485 } else if (message == WM_PAINT) { 5486 PAINTSTRUCT paint; 5487 HDC dc = BeginPaint(hwnd, &paint); 5488 BITMAPINFOHEADER info = { 0 }; 5489 info.biSize = sizeof(info); 5490 info.biWidth = window->width, info.biHeight = -window->height; 5491 info.biPlanes = 1, info.biBitCount = 32; 5492 StretchDIBits(dc, 0, 0, UI_RECT_SIZE(window->e.bounds), 0, 0, UI_RECT_SIZE(window->e.bounds), 5493 window->bits, (BITMAPINFO *) &info, DIB_RGB_COLORS, SRCCOPY); 5494 EndPaint(hwnd, &paint); 5495 } else if (message == WM_SETCURSOR && LOWORD(lParam) == HTCLIENT) { 5496 SetCursor(ui.cursors[window->cursorStyle]); 5497 return 1; 5498 } else if (message == WM_SETFOCUS || message == WM_KILLFOCUS) { 5499 _UIMenusClose(); 5500 5501 if (message == WM_SETFOCUS) { 5502 _UIInspectorSetFocusedWindow(window); 5503 UIElementMessage(&window->e, UI_MSG_WINDOW_ACTIVATE, 0, 0); 5504 } 5505 } else if (message == WM_MOUSEACTIVATE && (window->e.flags & UI_WINDOW_MENU)) { 5506 return MA_NOACTIVATE; 5507 } else if (message == WM_DROPFILES) { 5508 HDROP drop = (HDROP) wParam; 5509 int count = DragQueryFile(drop, 0xFFFFFFFF, NULL, 0); 5510 char **files = (char **) UI_MALLOC(sizeof(char *) * count); 5511 5512 for (int i = 0; i < count; i++) { 5513 int length = DragQueryFile(drop, i, NULL, 0); 5514 files[i] = (char *) UI_MALLOC(length + 1); 5515 files[i][length] = 0; 5516 DragQueryFile(drop, i, files[i], length + 1); 5517 } 5518 5519 UIElementMessage(&window->e, UI_MSG_WINDOW_DROP_FILES, count, files); 5520 for (int i = 0; i < count; i++) UI_FREE(files[i]); 5521 UI_FREE(files); 5522 DragFinish(drop); 5523 _UIUpdate(); 5524 } else if (message == WM_APP + 1) { 5525 UIElementMessage(&window->e, (UIMessage) wParam, 0, (void *) lParam); 5526 _UIUpdate(); 5527 } else { 5528 if (message == WM_NCLBUTTONDOWN || message == WM_NCMBUTTONDOWN || message == WM_NCRBUTTONDOWN) { 5529 if (~window->e.flags & UI_WINDOW_MENU) { 5530 _UIMenusClose(); 5531 _UIUpdate(); 5532 } 5533 } 5534 5535 return DefWindowProc(hwnd, message, wParam, lParam); 5536 } 5537 5538 return 0; 5539 } 5540 5541 void UIInitialise() { 5542 ui.heap = GetProcessHeap(); 5543 5544 _UIInitialiseCommon(); 5545 5546 ui.cursors[UI_CURSOR_ARROW] = LoadCursor(NULL, IDC_ARROW); 5547 ui.cursors[UI_CURSOR_TEXT] = LoadCursor(NULL, IDC_IBEAM); 5548 ui.cursors[UI_CURSOR_SPLIT_V] = LoadCursor(NULL, IDC_SIZENS); 5549 ui.cursors[UI_CURSOR_SPLIT_H] = LoadCursor(NULL, IDC_SIZEWE); 5550 ui.cursors[UI_CURSOR_FLIPPED_ARROW] = LoadCursor(NULL, IDC_ARROW); 5551 ui.cursors[UI_CURSOR_CROSS_HAIR] = LoadCursor(NULL, IDC_CROSS); 5552 ui.cursors[UI_CURSOR_HAND] = LoadCursor(NULL, IDC_HAND); 5553 ui.cursors[UI_CURSOR_RESIZE_UP] = LoadCursor(NULL, IDC_SIZENS); 5554 ui.cursors[UI_CURSOR_RESIZE_LEFT] = LoadCursor(NULL, IDC_SIZEWE); 5555 ui.cursors[UI_CURSOR_RESIZE_UP_RIGHT] = LoadCursor(NULL, IDC_SIZENESW); 5556 ui.cursors[UI_CURSOR_RESIZE_UP_LEFT] = LoadCursor(NULL, IDC_SIZENWSE); 5557 ui.cursors[UI_CURSOR_RESIZE_DOWN] = LoadCursor(NULL, IDC_SIZENS); 5558 ui.cursors[UI_CURSOR_RESIZE_RIGHT] = LoadCursor(NULL, IDC_SIZEWE); 5559 ui.cursors[UI_CURSOR_RESIZE_DOWN_LEFT] = LoadCursor(NULL, IDC_SIZENESW); 5560 ui.cursors[UI_CURSOR_RESIZE_DOWN_RIGHT] = LoadCursor(NULL, IDC_SIZENWSE); 5561 5562 WNDCLASS windowClass = { 0 }; 5563 windowClass.lpfnWndProc = _UIWindowProcedure; 5564 windowClass.lpszClassName = "normal"; 5565 RegisterClass(&windowClass); 5566 windowClass.style |= CS_DROPSHADOW; 5567 windowClass.lpszClassName = "shadow"; 5568 RegisterClass(&windowClass); 5569 } 5570 5571 bool _UIMessageLoopSingle(int *result) { 5572 MSG message = { 0 }; 5573 5574 if (ui.animating) { 5575 if (PeekMessage(&message, NULL, 0, 0, PM_REMOVE)) { 5576 if (message.message == WM_QUIT) { 5577 *result = message.wParam; 5578 return false; 5579 } 5580 5581 TranslateMessage(&message); 5582 DispatchMessage(&message); 5583 } else { 5584 _UIProcessAnimations(); 5585 } 5586 } else { 5587 if (!GetMessage(&message, NULL, 0, 0)) { 5588 *result = message.wParam; 5589 return false; 5590 } 5591 5592 TranslateMessage(&message); 5593 DispatchMessage(&message); 5594 } 5595 5596 return true; 5597 } 5598 5599 void UIMenuShow(UIMenu *menu) { 5600 int width, height; 5601 _UIMenuPrepare(menu, &width, &height); 5602 MoveWindow(menu->e.window->hwnd, menu->pointX, menu->pointY, width, height, FALSE); 5603 ShowWindow(menu->e.window->hwnd, SW_SHOWNOACTIVATE); 5604 } 5605 5606 UIWindow *UIWindowCreate(UIWindow *owner, uint32_t flags, const char *cTitle, int width, int height) { 5607 _UIMenusClose(); 5608 5609 UIWindow *window = (UIWindow *) UIElementCreate(sizeof(UIWindow), NULL, flags | UI_ELEMENT_WINDOW, _UIWindowMessage, "Window"); 5610 _UIWindowAdd(window); 5611 if (owner) window->scale = owner->scale; 5612 5613 if (flags & UI_WINDOW_MENU) { 5614 UI_ASSERT(owner); 5615 5616 window->hwnd = CreateWindowEx(WS_EX_TOPMOST | WS_EX_NOACTIVATE, "shadow", 0, WS_POPUP, 5617 0, 0, 0, 0, owner->hwnd, NULL, NULL, NULL); 5618 } else { 5619 window->hwnd = CreateWindowEx(WS_EX_ACCEPTFILES, "normal", cTitle, WS_OVERLAPPEDWINDOW, 5620 CW_USEDEFAULT, CW_USEDEFAULT, width ? width : CW_USEDEFAULT, height ? height : CW_USEDEFAULT, 5621 owner ? owner->hwnd : NULL, NULL, NULL, NULL); 5622 } 5623 5624 SetWindowLongPtr(window->hwnd, GWLP_USERDATA, (LONG_PTR) window); 5625 5626 if (~flags & UI_WINDOW_MENU) { 5627 ShowWindow(window->hwnd, SW_SHOW); 5628 PostMessage(window->hwnd, WM_SIZE, 0, 0); 5629 } 5630 5631 return window; 5632 } 5633 5634 void _UIWindowEndPaint(UIWindow *window, UIPainter *painter) { 5635 HDC dc = GetDC(window->hwnd); 5636 BITMAPINFOHEADER info = { 0 }; 5637 info.biSize = sizeof(info); 5638 info.biWidth = window->width, info.biHeight = window->height; 5639 info.biPlanes = 1, info.biBitCount = 32; 5640 StretchDIBits(dc, 5641 UI_RECT_TOP_LEFT(window->updateRegion), UI_RECT_SIZE(window->updateRegion), 5642 window->updateRegion.l, window->updateRegion.b + 1, 5643 UI_RECT_WIDTH(window->updateRegion), -UI_RECT_HEIGHT(window->updateRegion), 5644 window->bits, (BITMAPINFO *) &info, DIB_RGB_COLORS, SRCCOPY); 5645 ReleaseDC(window->hwnd, dc); 5646 } 5647 5648 void _UIWindowSetCursor(UIWindow *window, int cursor) { 5649 SetCursor(ui.cursors[cursor]); 5650 } 5651 5652 void _UIWindowGetScreenPosition(UIWindow *window, int *_x, int *_y) { 5653 POINT p; 5654 p.x = 0; 5655 p.y = 0; 5656 ClientToScreen(window->hwnd, &p); 5657 *_x = p.x; 5658 *_y = p.y; 5659 } 5660 5661 void UIWindowPostMessage(UIWindow *window, UIMessage message, void *_dp) { 5662 PostMessage(window->hwnd, WM_APP + 1, (WPARAM) message, (LPARAM) _dp); 5663 } 5664 5665 void *_UIHeapReAlloc(void *pointer, size_t size) { 5666 if (pointer) { 5667 if (size) { 5668 return HeapReAlloc(ui.heap, 0, pointer, size); 5669 } else { 5670 UI_FREE(pointer); 5671 return NULL; 5672 } 5673 } else { 5674 if (size) { 5675 return UI_MALLOC(size); 5676 } else { 5677 return NULL; 5678 } 5679 } 5680 } 5681 5682 void _UIClipboardWriteText(UIWindow *window, char *text) { 5683 if (OpenClipboard(window->hwnd)) { 5684 EmptyClipboard(); 5685 HGLOBAL memory = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, _UIStringLength(text) + 1); 5686 char *copy = (char *) GlobalLock(memory); 5687 for (uintptr_t i = 0; text[i]; i++) copy[i] = text[i]; 5688 GlobalUnlock(copy); 5689 SetClipboardData(CF_TEXT, memory); 5690 CloseClipboard(); 5691 } 5692 } 5693 5694 char *_UIClipboardReadTextStart(UIWindow *window, size_t *bytes) { 5695 if (!OpenClipboard(window->hwnd)) { 5696 return NULL; 5697 } 5698 5699 HANDLE memory = GetClipboardData(CF_TEXT); 5700 5701 if (!memory) { 5702 CloseClipboard(); 5703 return NULL; 5704 } 5705 5706 char *buffer = (char *) GlobalLock(memory); 5707 5708 if (!buffer) { 5709 CloseClipboard(); 5710 return NULL; 5711 } 5712 5713 size_t byteCount = GlobalSize(memory); 5714 5715 if (byteCount < 1) { 5716 GlobalUnlock(memory); 5717 CloseClipboard(); 5718 return NULL; 5719 } 5720 5721 char *copy = (char *) UI_MALLOC(byteCount + 1); 5722 for (uintptr_t i = 0; i < byteCount; i++) copy[i] = buffer[i]; 5723 copy[byteCount] = 0; // Just in case. 5724 5725 GlobalUnlock(memory); 5726 CloseClipboard(); 5727 5728 if (bytes) *bytes = _UIStringLength(copy); 5729 return copy; 5730 } 5731 5732 void _UIClipboardReadTextEnd(UIWindow *window, char *text) { 5733 UI_FREE(text); 5734 } 5735 5736 #endif 5737 5738 #ifdef UI_ESSENCE 5739 5740 const int UI_KEYCODE_A = ES_SCANCODE_A; 5741 const int UI_KEYCODE_0 = ES_SCANCODE_0; 5742 const int UI_KEYCODE_BACKSPACE = ES_SCANCODE_BACKSPACE; 5743 const int UI_KEYCODE_DELETE = ES_SCANCODE_DELETE; 5744 const int UI_KEYCODE_DOWN = ES_SCANCODE_DOWN_ARROW; 5745 const int UI_KEYCODE_END = ES_SCANCODE_END; 5746 const int UI_KEYCODE_ENTER = ES_SCANCODE_ENTER; 5747 const int UI_KEYCODE_ESCAPE = ES_SCANCODE_ESCAPE; 5748 const int UI_KEYCODE_F1 = ES_SCANCODE_F1; 5749 const int UI_KEYCODE_HOME = ES_SCANCODE_HOME; 5750 const int UI_KEYCODE_LEFT = ES_SCANCODE_LEFT_ARROW; 5751 const int UI_KEYCODE_RIGHT = ES_SCANCODE_RIGHT_ARROW; 5752 const int UI_KEYCODE_SPACE = ES_SCANCODE_SPACE; 5753 const int UI_KEYCODE_TAB = ES_SCANCODE_TAB; 5754 const int UI_KEYCODE_UP = ES_SCANCODE_UP_ARROW; 5755 const int UI_KEYCODE_INSERT = ES_SCANCODE_INSERT; 5756 5757 int _UIWindowMessage(UIElement *element, UIMessage message, int di, void *dp) { 5758 if (message == UI_MSG_DESTROY) { 5759 // TODO Non-main windows. 5760 element->window = NULL; 5761 EsInstanceDestroy(ui.instance); 5762 } 5763 5764 return _UIWindowMessageCommon(element, message, di, dp); 5765 } 5766 5767 void UIInitialise() { 5768 _UIInitialiseCommon(); 5769 5770 while (true) { 5771 EsMessage *message = EsMessageReceive(); 5772 5773 if (message->type == ES_MSG_INSTANCE_CREATE) { 5774 ui.instance = EsInstanceCreate(message, NULL, 0); 5775 break; 5776 } 5777 } 5778 } 5779 5780 bool _UIMessageLoopSingle(int *result) { 5781 if (ui.animating) { 5782 // TODO. 5783 } else { 5784 _UIMessageProcess(EsMessageReceive()); 5785 } 5786 5787 return true; 5788 } 5789 5790 UIMenu *UIMenuCreate(UIElement *parent, uint32_t flags) { 5791 ui.menuIndex = 0; 5792 return EsMenuCreate(parent->window->window, ES_MENU_AT_CURSOR); 5793 } 5794 5795 void _UIMenuItemCallback(EsMenu *menu, EsGeneric context) { 5796 ((void (*)(void *)) ui.menuData[context.u * 2 + 0])(ui.menuData[context.u * 2 + 1]); 5797 } 5798 5799 void UIMenuAddItem(UIMenu *menu, uint32_t flags, const char *label, ptrdiff_t labelBytes, void (*invoke)(void *cp), void *cp) { 5800 EsAssert(ui.menuIndex < 128); 5801 ui.menuData[ui.menuIndex * 2 + 0] = (void *) invoke; 5802 ui.menuData[ui.menuIndex * 2 + 1] = cp; 5803 EsMenuAddItem(menu, (flags & UI_BUTTON_CHECKED) ? ES_MENU_ITEM_CHECKED : ES_FLAGS_DEFAULT, 5804 label, labelBytes, _UIMenuItemCallback, ui.menuIndex); 5805 ui.menuIndex++; 5806 } 5807 5808 void UIMenuShow(UIMenu *menu) { 5809 EsMenuShow(menu); 5810 } 5811 5812 int _UIWindowCanvasMessage(EsElement *element, EsMessage *message) { 5813 UIWindow *window = (UIWindow *) element->window->userData.p; 5814 5815 if (!window) { 5816 return 0; 5817 } else if (message->type == ES_MSG_PAINT) { 5818 EsRectangle bounds = ES_RECT_4PD(message->painter->offsetX, message->painter->offsetY, window->width, window->height); 5819 EsDrawBitmap(message->painter, bounds, window->bits, window->width * 4, 0xFFFF); 5820 } else if (message->type == ES_MSG_LAYOUT) { 5821 EsElementGetSize(element, &window->width, &window->height); 5822 window->bits = (uint32_t *) UI_REALLOC(window->bits, window->width * window->height * 4); 5823 window->e.bounds = UI_RECT_2S(window->width, window->height); 5824 window->e.clip = UI_RECT_2S(window->width, window->height); 5825 UIElementMessage(&window->e, UI_MSG_LAYOUT, 0, 0); 5826 _UIUpdate(); 5827 } else if (message->type == ES_MSG_SCROLL_WHEEL) { 5828 _UIWindowInputEvent(window, UI_MSG_MOUSE_WHEEL, -message->scrollWheel.dy, 0); 5829 } else if (message->type == ES_MSG_MOUSE_MOVED || message->type == ES_MSG_HOVERED_END 5830 || message->type == ES_MSG_MOUSE_LEFT_DRAG || message->type == ES_MSG_MOUSE_RIGHT_DRAG || message->type == ES_MSG_MOUSE_MIDDLE_DRAG) { 5831 EsPoint point = EsMouseGetPosition(element); 5832 window->cursorX = point.x, window->cursorY = point.y; 5833 _UIWindowInputEvent(window, UI_MSG_MOUSE_MOVE, 0, 0); 5834 } else if (message->type == ES_MSG_KEY_UP) { 5835 window->ctrl = EsKeyboardIsCtrlHeld(); 5836 window->shift = EsKeyboardIsShiftHeld(); 5837 window->alt = EsKeyboardIsAltHeld(); 5838 } else if (message->type == ES_MSG_KEY_DOWN) { 5839 window->ctrl = EsKeyboardIsCtrlHeld(); 5840 window->shift = EsKeyboardIsShiftHeld(); 5841 window->alt = EsKeyboardIsAltHeld(); 5842 UIKeyTyped m = { 0 }; 5843 char c[64]; 5844 m.text = c; 5845 m.textBytes = EsMessageGetInputText(message, c); 5846 m.code = message->keyboard.scancode; 5847 return _UIWindowInputEvent(window, UI_MSG_KEY_TYPED, 0, &m) ? ES_HANDLED : 0; 5848 } else if (message->type == ES_MSG_MOUSE_LEFT_CLICK) { 5849 _UIInspectorSetFocusedWindow(window); 5850 } else if (message->type == ES_MSG_USER_START) { 5851 UIElementMessage(&window->e, (UIMessage) message->user.context1.u, 0, (void *) message->user.context2.p); 5852 _UIUpdate(); 5853 } else if (message->type == ES_MSG_GET_CURSOR) { 5854 message->cursorStyle = ES_CURSOR_NORMAL; 5855 if (window->cursor == UI_CURSOR_TEXT) message->cursorStyle = ES_CURSOR_TEXT; 5856 if (window->cursor == UI_CURSOR_SPLIT_V) message->cursorStyle = ES_CURSOR_SPLIT_VERTICAL; 5857 if (window->cursor == UI_CURSOR_SPLIT_H) message->cursorStyle = ES_CURSOR_SPLIT_HORIZONTAL; 5858 if (window->cursor == UI_CURSOR_FLIPPED_ARROW) message->cursorStyle = ES_CURSOR_SELECT_LINES; 5859 if (window->cursor == UI_CURSOR_CROSS_HAIR) message->cursorStyle = ES_CURSOR_CROSS_HAIR_PICK; 5860 if (window->cursor == UI_CURSOR_HAND) message->cursorStyle = ES_CURSOR_HAND_HOVER; 5861 if (window->cursor == UI_CURSOR_RESIZE_UP) message->cursorStyle = ES_CURSOR_RESIZE_VERTICAL; 5862 if (window->cursor == UI_CURSOR_RESIZE_LEFT) message->cursorStyle = ES_CURSOR_RESIZE_HORIZONTAL; 5863 if (window->cursor == UI_CURSOR_RESIZE_UP_RIGHT) message->cursorStyle = ES_CURSOR_RESIZE_DIAGONAL_1; 5864 if (window->cursor == UI_CURSOR_RESIZE_UP_LEFT) message->cursorStyle = ES_CURSOR_RESIZE_DIAGONAL_2; 5865 if (window->cursor == UI_CURSOR_RESIZE_DOWN) message->cursorStyle = ES_CURSOR_RESIZE_VERTICAL; 5866 if (window->cursor == UI_CURSOR_RESIZE_RIGHT) message->cursorStyle = ES_CURSOR_RESIZE_HORIZONTAL; 5867 if (window->cursor == UI_CURSOR_RESIZE_DOWN_RIGHT) message->cursorStyle = ES_CURSOR_RESIZE_DIAGONAL_1; 5868 if (window->cursor == UI_CURSOR_RESIZE_DOWN_LEFT) message->cursorStyle = ES_CURSOR_RESIZE_DIAGONAL_2; 5869 } 5870 5871 else if (message->type == ES_MSG_MOUSE_LEFT_DOWN) _UIWindowInputEvent(window, UI_MSG_LEFT_DOWN, 0, 0); 5872 else if (message->type == ES_MSG_MOUSE_LEFT_UP) _UIWindowInputEvent(window, UI_MSG_LEFT_UP, 0, 0); 5873 else if (message->type == ES_MSG_MOUSE_MIDDLE_DOWN) _UIWindowInputEvent(window, UI_MSG_MIDDLE_DOWN, 0, 0); 5874 else if (message->type == ES_MSG_MOUSE_MIDDLE_UP) _UIWindowInputEvent(window, UI_MSG_MIDDLE_UP, 0, 0); 5875 else if (message->type == ES_MSG_MOUSE_RIGHT_DOWN) _UIWindowInputEvent(window, UI_MSG_RIGHT_DOWN, 0, 0); 5876 else if (message->type == ES_MSG_MOUSE_RIGHT_UP) _UIWindowInputEvent(window, UI_MSG_RIGHT_UP, 0, 0); 5877 5878 else return 0; 5879 5880 return ES_HANDLED; 5881 } 5882 5883 UIWindow *UIWindowCreate(UIWindow *owner, uint32_t flags, const char *cTitle, int width, int height) { 5884 _UIMenusClose(); 5885 5886 UIWindow *window = (UIWindow *) UIElementCreate(sizeof(UIWindow), NULL, flags | UI_ELEMENT_WINDOW, _UIWindowMessage, "Window"); 5887 _UIWindowAdd(window); 5888 if (owner) window->scale = owner->scale; 5889 5890 if (flags & UI_WINDOW_MENU) { 5891 // TODO. 5892 } else { 5893 // TODO Non-main windows. 5894 window->window = ui.instance->window; 5895 window->window->userData = window; 5896 window->canvas = EsCustomElementCreate(window->window, ES_CELL_FILL | ES_ELEMENT_FOCUSABLE); 5897 window->canvas->messageUser = _UIWindowCanvasMessage; 5898 EsWindowSetTitle(window->window, cTitle, -1); 5899 EsElementFocus(window->canvas); 5900 } 5901 5902 return window; 5903 } 5904 5905 void _UIWindowEndPaint(UIWindow *window, UIPainter *painter) { 5906 EsElementRepaint(window->canvas, &window->updateRegion); 5907 } 5908 5909 void _UIWindowSetCursor(UIWindow *window, int cursor) { 5910 window->cursor = cursor; 5911 } 5912 5913 void _UIWindowGetScreenPosition(UIWindow *window, int *_x, int *_y) { 5914 EsRectangle r = EsElementGetScreenBounds(window->window); 5915 *_x = r.l, *_y = r.t; 5916 } 5917 5918 void UIWindowPostMessage(UIWindow *window, UIMessage message, void *_dp) { 5919 EsMessage m = {}; 5920 m.type = ES_MSG_USER_START; 5921 m.user.context1.u = message; 5922 m.user.context2.p = _dp; 5923 EsMessagePost(window->canvas, &m); 5924 } 5925 5926 void _UIClipboardWriteText(UIWindow *window, char *text) { 5927 EsClipboardAddText(ES_CLIPBOARD_PRIMARY, text, -1); 5928 UI_FREE(text); 5929 } 5930 5931 char *_UIClipboardReadTextStart(UIWindow *window, size_t *bytes) { 5932 return EsClipboardReadText(ES_CLIPBOARD_PRIMARY, bytes, NULL); 5933 } 5934 5935 void _UIClipboardReadTextEnd(UIWindow *window, char *text) { 5936 EsHeapFree(text); 5937 } 5938 5939 #endif 5940 5941 #endif