luigi2.h (256637B)
1 // TODO UITextbox features - mouse input, undo, number dragging. 2 // TODO New elements - list view, menu bar. 3 // TODO Keyboard navigation in menus. 4 // TODO Easier to use fonts. 5 6 ///////////////////////////////////////// 7 // Header includes. 8 ///////////////////////////////////////// 9 #pragma once 10 11 #include <stdint.h> 12 #include <stddef.h> 13 #include <stdbool.h> 14 #include <stdarg.h> 15 16 #ifdef UI_LINUX 17 #include <X11/Xlib.h> 18 #include <X11/Xutil.h> 19 #include <X11/Xatom.h> 20 #include <X11/cursorfont.h> 21 #endif 22 23 #ifdef UI_SSE2 24 #include <xmmintrin.h> 25 #endif 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 #define UI_MEMMOVE _UIMemmove 43 #endif 44 45 #ifdef UI_COCOA 46 #import <Foundation/Foundation.h> 47 #import <Cocoa/Cocoa.h> 48 #import <Carbon/Carbon.h> 49 #endif 50 51 #if defined(UI_LINUX) || defined(UI_COCOA) 52 #include <stdlib.h> 53 #include <string.h> 54 #include <assert.h> 55 #include <time.h> 56 #include <math.h> 57 58 #define UI_ASSERT assert 59 #define UI_CALLOC(x) calloc(1, (x)) 60 #define UI_FREE free 61 #define UI_MALLOC malloc 62 #define UI_REALLOC realloc 63 #define UI_CLOCK _UIClock 64 #define UI_CLOCKS_PER_SECOND 1000 65 #define UI_CLOCK_T clock_t 66 #define UI_MEMMOVE(d, s, n) do { size_t _n = n; if (_n) { memmove(d, s, _n); } } while (0) 67 #endif 68 69 #if defined(UI_ESSENCE) 70 #include <essence.h> 71 72 #define UI_ASSERT EsAssert 73 #define UI_CALLOC(x) EsHeapAllocate((x), true) 74 #define UI_FREE EsHeapFree 75 #define UI_MALLOC(x) EsHeapAllocate((x), false) 76 #define UI_REALLOC(x, y) EsHeapReallocate((x), (y), false) 77 #define UI_CLOCK EsTimeStampMs 78 #define UI_CLOCKS_PER_SECOND 1000 79 #define UI_CLOCK_T uint64_t 80 #define UI_MEMMOVE EsCRTmemmove 81 82 // Callback to allow the application to process messages. 83 void _UIMessageProcess(EsMessage *message); 84 #endif 85 86 #ifdef UI_DEBUG 87 #include <stdio.h> 88 #endif 89 90 #ifdef UI_FREETYPE 91 #include <ft2build.h> 92 #include FT_FREETYPE_H 93 #include <freetype/ftbitmap.h> 94 #endif 95 96 ///////////////////////////////////////// 97 // Definitions. 98 ///////////////////////////////////////// 99 100 #define _UI_TO_STRING_1(x) #x 101 #define _UI_TO_STRING_2(x) _UI_TO_STRING_1(x) 102 103 #define UI_SIZE_BUTTON_MINIMUM_WIDTH (100) 104 #define UI_SIZE_BUTTON_PADDING (16) 105 #define UI_SIZE_BUTTON_HEIGHT (27) 106 #define UI_SIZE_BUTTON_CHECKED_AREA (4) 107 108 #define UI_SIZE_CHECKBOX_BOX (14) 109 #define UI_SIZE_CHECKBOX_GAP (8) 110 111 #define UI_SIZE_MENU_ITEM_HEIGHT (24) 112 #define UI_SIZE_MENU_ITEM_MINIMUM_WIDTH (160) 113 #define UI_SIZE_MENU_ITEM_MARGIN (9) 114 115 #define UI_SIZE_GAUGE_WIDTH (200) 116 #define UI_SIZE_GAUGE_HEIGHT (22) 117 118 #define UI_SIZE_SLIDER_WIDTH (200) 119 #define UI_SIZE_SLIDER_HEIGHT (25) 120 #define UI_SIZE_SLIDER_THUMB (15) 121 #define UI_SIZE_SLIDER_TRACK (5) 122 123 #define UI_SIZE_TEXTBOX_MARGIN (3) 124 #define UI_SIZE_TEXTBOX_WIDTH (200) 125 #define UI_SIZE_TEXTBOX_HEIGHT (27) 126 127 #define UI_SIZE_TAB_PANE_SPACE_TOP (2) 128 #define UI_SIZE_TAB_PANE_SPACE_LEFT (4) 129 130 #define UI_SIZE_SPLITTER (8) 131 132 #define UI_SIZE_SCROLL_BAR (16) 133 #define UI_SIZE_SCROLL_MINIMUM_THUMB (20) 134 135 #define UI_SIZE_CODE_MARGIN (ui.activeFont->glyphWidth * 5) 136 #define UI_SIZE_CODE_MARGIN_GAP (ui.activeFont->glyphWidth * 1) 137 138 #define UI_SIZE_TABLE_HEADER (26) 139 #define UI_SIZE_TABLE_COLUMN_GAP (20) 140 #define UI_SIZE_TABLE_ROW (20) 141 142 #define UI_SIZE_PANE_LARGE_BORDER (20) 143 #define UI_SIZE_PANE_LARGE_GAP (10) 144 #define UI_SIZE_PANE_MEDIUM_BORDER (5) 145 #define UI_SIZE_PANE_MEDIUM_GAP (5) 146 #define UI_SIZE_PANE_SMALL_BORDER (3) 147 #define UI_SIZE_PANE_SMALL_GAP (3) 148 149 #define UI_SIZE_MDI_CHILD_BORDER (6) 150 #define UI_SIZE_MDI_CHILD_TITLE (30) 151 #define UI_SIZE_MDI_CHILD_CORNER (12) 152 #define UI_SIZE_MDI_CHILD_MINIMUM_WIDTH (100) 153 #define UI_SIZE_MDI_CHILD_MINIMUM_HEIGHT (50) 154 #define UI_SIZE_MDI_CASCADE (30) 155 156 #define UI_MDI_CHILD_CALCULATE_LAYOUT(bounds, scale) \ 157 int titleSize = UI_SIZE_MDI_CHILD_TITLE * scale; \ 158 int borderSize = UI_SIZE_MDI_CHILD_BORDER * scale; \ 159 UIRectangle title = UIRectangleAdd(bounds, UI_RECT_4(borderSize, -borderSize, 0, 0)); \ 160 title.b = title.t + titleSize; \ 161 UIRectangle content = UIRectangleAdd(bounds, UI_RECT_4(borderSize, -borderSize, titleSize, -borderSize)); 162 163 #define UI_UPDATE_HOVERED (1) 164 #define UI_UPDATE_PRESSED (2) 165 #define UI_UPDATE_FOCUSED (3) 166 #define UI_UPDATE_DISABLED (4) 167 168 typedef enum UIMessage { 169 // General messages. 170 UI_MSG_PAINT, // dp = pointer to UIPainter 171 UI_MSG_PAINT_FOREGROUND, // after children have painted 172 UI_MSG_LAYOUT, 173 UI_MSG_DESTROY, 174 UI_MSG_DEALLOCATE, 175 UI_MSG_UPDATE, // di = UI_UPDATE_... constant 176 UI_MSG_ANIMATE, 177 UI_MSG_SCROLLED, 178 UI_MSG_GET_WIDTH, // di = height (if known); return width 179 UI_MSG_GET_HEIGHT, // di = width (if known); return height 180 UI_MSG_GET_CHILD_STABILITY, // dp = child element; return stable axes, 1 (width) | 2 (height) 181 182 // Input events. 183 UI_MSG_INPUT_EVENTS_START, // not sent to disabled elements 184 UI_MSG_LEFT_DOWN, 185 UI_MSG_LEFT_UP, 186 UI_MSG_MIDDLE_DOWN, 187 UI_MSG_MIDDLE_UP, 188 UI_MSG_RIGHT_DOWN, 189 UI_MSG_RIGHT_UP, 190 UI_MSG_KEY_TYPED, // dp = pointer to UIKeyTyped; return 1 if handled 191 UI_MSG_KEY_RELEASED, // dp = pointer to UIKeyTyped; return 1 if handled 192 UI_MSG_MOUSE_MOVE, 193 UI_MSG_MOUSE_DRAG, 194 UI_MSG_MOUSE_WHEEL, // di = delta; return 1 if handled 195 UI_MSG_CLICKED, 196 UI_MSG_GET_CURSOR, // return cursor code 197 UI_MSG_PRESSED_DESCENDENT, // dp = pointer to child that is/contains pressed element 198 UI_MSG_INPUT_EVENTS_END, 199 200 // Specific elements. 201 UI_MSG_VALUE_CHANGED, // sent to notify that the element's value has changed 202 UI_MSG_TABLE_GET_ITEM, // dp = pointer to UITableGetItem; return string length 203 UI_MSG_CODE_GET_MARGIN_COLOR, // di = line index (starts at 1); return color 204 UI_MSG_CODE_DECORATE_LINE, // dp = pointer to UICodeDecorateLine 205 UI_MSG_TAB_SELECTED, // sent to the tab that was selected (not the tab pane itself) 206 207 // Windows. 208 UI_MSG_WINDOW_DROP_FILES, // di = count, dp = char ** of paths 209 UI_MSG_WINDOW_ACTIVATE, 210 UI_MSG_WINDOW_CLOSE, // return 1 to prevent default (process exit for UIWindow; close for UIMDIChild) 211 UI_MSG_WINDOW_UPDATE_START, 212 UI_MSG_WINDOW_UPDATE_BEFORE_DESTROY, 213 UI_MSG_WINDOW_UPDATE_BEFORE_LAYOUT, 214 UI_MSG_WINDOW_UPDATE_BEFORE_PAINT, 215 UI_MSG_WINDOW_UPDATE_END, 216 217 // User-defined messages. 218 UI_MSG_USER, 219 } UIMessage; 220 221 #ifdef UI_ESSENCE 222 #define UIRectangle EsRectangle 223 #else 224 typedef struct UIRectangle { 225 int l, r, t, b; 226 } UIRectangle; 227 #endif 228 229 typedef struct UITheme { 230 uint32_t panel1, panel2, selected, border; 231 uint32_t text, textDisabled, textSelected; 232 uint32_t buttonNormal, buttonHovered, buttonPressed, buttonDisabled; 233 uint32_t textboxNormal, textboxFocused; 234 uint32_t codeFocused, codeBackground, codeDefault, codeComment, codeString, codeNumber, codeOperator, codePreprocessor; 235 uint32_t accent1, accent2; 236 } UITheme; 237 238 typedef struct UIPainter { 239 UIRectangle clip; 240 uint32_t *bits; 241 int width, height; 242 #ifdef UI_DEBUG 243 int fillCount; 244 #endif 245 } UIPainter; 246 247 typedef struct UIFont { 248 int glyphWidth, glyphHeight; 249 250 #ifdef UI_FREETYPE 251 bool isFreeType; 252 FT_Face font; 253 #ifdef UI_UNICODE 254 FT_Bitmap *glyphs; 255 bool *glyphsRendered; 256 int *glyphOffsetsX, *glyphOffsetsY; 257 #else 258 FT_Bitmap glyphs[128]; 259 bool glyphsRendered[128]; 260 int glyphOffsetsX[128], glyphOffsetsY[128]; 261 #endif 262 #endif 263 } UIFont; 264 265 typedef struct UIShortcut { 266 intptr_t code; 267 bool ctrl, shift, alt; 268 void (*invoke)(void *cp); 269 void *cp; 270 } UIShortcut; 271 272 typedef struct UIStringSelection { 273 int carets[2]; 274 uint32_t colorText, colorBackground; 275 } UIStringSelection; 276 277 typedef struct UIKeyTyped { 278 char *text; 279 int textBytes; 280 intptr_t code; 281 } UIKeyTyped; 282 283 typedef struct UITableGetItem { 284 char *buffer; 285 size_t bufferBytes; 286 int index, column; 287 bool isSelected; 288 } UITableGetItem; 289 290 typedef struct UICodeDecorateLine { 291 UIRectangle bounds; 292 int index; // Starting at 1! 293 int x, y; // Position where additional text can be drawn. 294 UIPainter *painter; 295 } UICodeDecorateLine; 296 297 #define UI_RECT_1(x) ((UIRectangle) { (x), (x), (x), (x) }) 298 #define UI_RECT_1I(x) ((UIRectangle) { (x), -(x), (x), -(x) }) 299 #define UI_RECT_2(x, y) ((UIRectangle) { (x), (x), (y), (y) }) 300 #define UI_RECT_2I(x, y) ((UIRectangle) { (x), -(x), (y), -(y) }) 301 #define UI_RECT_2S(x, y) ((UIRectangle) { 0, (x), 0, (y) }) 302 #define UI_RECT_4(x, y, z, w) ((UIRectangle) { (x), (y), (z), (w) }) 303 #define UI_RECT_4PD(x, y, w, h) ((UIRectangle) { (x), ((x) + (w)), (y), ((y) + (h)) }) 304 #define UI_RECT_WIDTH(_r) ((_r).r - (_r).l) 305 #define UI_RECT_HEIGHT(_r) ((_r).b - (_r).t) 306 #define UI_RECT_TOTAL_H(_r) ((_r).r + (_r).l) 307 #define UI_RECT_TOTAL_V(_r) ((_r).b + (_r).t) 308 #define UI_RECT_SIZE(_r) UI_RECT_WIDTH(_r), UI_RECT_HEIGHT(_r) 309 #define UI_RECT_TOP_LEFT(_r) (_r).l, (_r).t 310 #define UI_RECT_BOTTOM_LEFT(_r) (_r).l, (_r).b 311 #define UI_RECT_BOTTOM_RIGHT(_r) (_r).r, (_r).b 312 #define UI_RECT_ALL(_r) (_r).l, (_r).r, (_r).t, (_r).b 313 #define UI_RECT_VALID(_r) ((_r).l < (_r).r && (_r).t < (_r).b) 314 315 #define UI_COLOR_ALPHA_F(x) ((((x) >> 24) & 0xFF) / 255.0f) 316 #define UI_COLOR_RED_F(x) ((((x) >> 16) & 0xFF) / 255.0f) 317 #define UI_COLOR_GREEN_F(x) ((((x) >> 8) & 0xFF) / 255.0f) 318 #define UI_COLOR_BLUE_F(x) ((((x) >> 0) & 0xFF) / 255.0f) 319 #define UI_COLOR_ALPHA(x) ((((x) >> 24) & 0xFF)) 320 #define UI_COLOR_RED(x) ((((x) >> 16) & 0xFF)) 321 #define UI_COLOR_GREEN(x) ((((x) >> 8) & 0xFF)) 322 #define UI_COLOR_BLUE(x) ((((x) >> 0) & 0xFF)) 323 #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)) 324 #define UI_COLOR_FROM_RGBA_F(r, g, b, a) (((uint32_t) ((r) * 255.0f) << 16) | ((uint32_t) ((g) * 255.0f) << 8) \ 325 | ((uint32_t) ((b) * 255.0f) << 0) | ((uint32_t) ((a) * 255.0f) << 24)) 326 327 #define UI_SWAP(s, a, b) do { s t = (a); (a) = (b); (b) = t; } while (0) 328 329 #ifndef UI_DRAW_CONTROL_CUSTOM 330 #define UIDrawControl UIDrawControlDefault 331 #endif 332 #define UI_DRAW_CONTROL_PUSH_BUTTON (1) 333 #define UI_DRAW_CONTROL_DROP_DOWN (2) 334 #define UI_DRAW_CONTROL_MENU_ITEM (3) 335 #define UI_DRAW_CONTROL_CHECKBOX (4) 336 #define UI_DRAW_CONTROL_LABEL (5) 337 #define UI_DRAW_CONTROL_SPLITTER (6) 338 #define UI_DRAW_CONTROL_SCROLL_TRACK (7) 339 #define UI_DRAW_CONTROL_SCROLL_UP (8) 340 #define UI_DRAW_CONTROL_SCROLL_DOWN (9) 341 #define UI_DRAW_CONTROL_SCROLL_THUMB (10) 342 #define UI_DRAW_CONTROL_GAUGE (11) 343 #define UI_DRAW_CONTROL_SLIDER (12) 344 #define UI_DRAW_CONTROL_TEXTBOX (13) 345 #define UI_DRAW_CONTROL_MODAL_POPUP (14) 346 #define UI_DRAW_CONTROL_MENU (15) 347 #define UI_DRAW_CONTROL_TABLE_ROW (16) 348 #define UI_DRAW_CONTROL_TABLE_CELL (17) 349 #define UI_DRAW_CONTROL_TABLE_BACKGROUND (18) 350 #define UI_DRAW_CONTROL_TABLE_HEADER (19) 351 #define UI_DRAW_CONTROL_MDI_CHILD (20) 352 #define UI_DRAW_CONTROL_TAB (21) 353 #define UI_DRAW_CONTROL_TAB_BAND (22) 354 #define UI_DRAW_CONTROL_TYPE_MASK (0xFF) 355 #define UI_DRAW_CONTROL_STATE_SELECTED (1 << 24) 356 #define UI_DRAW_CONTROL_STATE_VERTICAL (1 << 25) 357 #define UI_DRAW_CONTROL_STATE_INDETERMINATE (1 << 26) 358 #define UI_DRAW_CONTROL_STATE_CHECKED (1 << 27) 359 #define UI_DRAW_CONTROL_STATE_HOVERED (1 << 28) 360 #define UI_DRAW_CONTROL_STATE_FOCUSED (1 << 29) 361 #define UI_DRAW_CONTROL_STATE_PRESSED (1 << 30) 362 #define UI_DRAW_CONTROL_STATE_DISABLED (1 << 31) 363 #define UI_DRAW_CONTROL_STATE_FROM_ELEMENT(x) ((((x)->flags & UI_ELEMENT_DISABLED) ? UI_DRAW_CONTROL_STATE_DISABLED : 0) \ 364 | (((x)->window->hovered == (x)) ? UI_DRAW_CONTROL_STATE_HOVERED : 0) \ 365 | (((x)->window->focused == (x)) ? UI_DRAW_CONTROL_STATE_FOCUSED : 0) \ 366 | (((x)->window->pressed == (x)) ? UI_DRAW_CONTROL_STATE_PRESSED : 0)) 367 368 #define UI_CURSOR_ARROW (0) 369 #define UI_CURSOR_TEXT (1) 370 #define UI_CURSOR_SPLIT_V (2) 371 #define UI_CURSOR_SPLIT_H (3) 372 #define UI_CURSOR_FLIPPED_ARROW (4) 373 #define UI_CURSOR_CROSS_HAIR (5) 374 #define UI_CURSOR_HAND (6) 375 #define UI_CURSOR_RESIZE_UP (7) 376 #define UI_CURSOR_RESIZE_LEFT (8) 377 #define UI_CURSOR_RESIZE_UP_RIGHT (9) 378 #define UI_CURSOR_RESIZE_UP_LEFT (10) 379 #define UI_CURSOR_RESIZE_DOWN (11) 380 #define UI_CURSOR_RESIZE_RIGHT (12) 381 #define UI_CURSOR_RESIZE_DOWN_RIGHT (13) 382 #define UI_CURSOR_RESIZE_DOWN_LEFT (14) 383 #define UI_CURSOR_COUNT (15) 384 385 #define UI_ALIGN_LEFT (1) 386 #define UI_ALIGN_RIGHT (2) 387 #define UI_ALIGN_CENTER (3) 388 389 extern const int UI_KEYCODE_A; 390 extern const int UI_KEYCODE_BACKSPACE; 391 extern const int UI_KEYCODE_DELETE; 392 extern const int UI_KEYCODE_DOWN; 393 extern const int UI_KEYCODE_END; 394 extern const int UI_KEYCODE_ENTER; 395 extern const int UI_KEYCODE_ESCAPE; 396 extern const int UI_KEYCODE_F1; 397 extern const int UI_KEYCODE_HOME; 398 extern const int UI_KEYCODE_LEFT; 399 extern const int UI_KEYCODE_RIGHT; 400 extern const int UI_KEYCODE_SPACE; 401 extern const int UI_KEYCODE_TAB; 402 extern const int UI_KEYCODE_UP; 403 extern const int UI_KEYCODE_INSERT; 404 extern const int UI_KEYCODE_0; 405 extern const int UI_KEYCODE_BACKTICK; 406 extern const int UI_KEYCODE_PAGE_UP; 407 extern const int UI_KEYCODE_PAGE_DOWN; 408 409 #define UI_KEYCODE_LETTER(x) (UI_KEYCODE_A + (x) - 'A') 410 #define UI_KEYCODE_DIGIT(x) (UI_KEYCODE_0 + (x) - '0') 411 #define UI_KEYCODE_FKEY(x) (UI_KEYCODE_F1 + (x) - 1) 412 413 #define UI_ELEMENT_FILL (UI_ELEMENT_V_FILL | UI_ELEMENT_H_FILL) 414 415 typedef struct UIElement { 416 #define UI_ELEMENT_V_FILL (1 << 16) 417 #define UI_ELEMENT_H_FILL (1 << 17) 418 #define UI_ELEMENT_WINDOW (1 << 18) 419 #define UI_ELEMENT_PARENT_PUSH (1 << 19) 420 #define UI_ELEMENT_TAB_STOP (1 << 20) 421 #define UI_ELEMENT_NON_CLIENT (1 << 21) // Don't destroy in UIElementDestroyDescendents, like scroll bars. 422 #define UI_ELEMENT_DISABLED (1 << 22) // Don't receive input events. 423 #define UI_ELEMENT_BORDER (1 << 23) 424 425 #define UI_ELEMENT_HIDE (1 << 27) 426 #define UI_ELEMENT_RELAYOUT (1 << 28) 427 #define UI_ELEMENT_RELAYOUT_DESCENDENT (1 << 29) 428 #define UI_ELEMENT_DESTROY (1 << 30) 429 #define UI_ELEMENT_DESTROY_DESCENDENT (1 << 31) 430 431 uint32_t flags; // First 16 bits are element specific. 432 uint32_t id; 433 uint32_t childCount; 434 uint32_t _unused0; 435 436 struct UIElement *parent; 437 struct UIElement **children; 438 struct UIWindow *window; 439 440 UIRectangle bounds, clip; 441 442 void *cp; // Context pointer (for user). 443 444 int (*messageClass)(struct UIElement *element, UIMessage message, int di /* data integer */, void *dp /* data pointer */); 445 int (*messageUser)(struct UIElement *element, UIMessage message, int di, void *dp); 446 447 const char *cClassName; 448 } UIElement; 449 450 #define UI_SHORTCUT(code, ctrl, shift, alt, invoke, cp) ((UIShortcut) { (code), (ctrl), (shift), (alt), (invoke), (cp) }) 451 452 typedef struct UIWindow { 453 #define UI_WINDOW_MENU (1 << 0) 454 #define UI_WINDOW_INSPECTOR (1 << 1) 455 #define UI_WINDOW_CENTER_IN_OWNER (1 << 2) 456 #define UI_WINDOW_MAXIMIZE (1 << 3) 457 #define UI_WINDOW_DIALOG (1 << 4) 458 459 UIElement e; 460 461 UIElement *dialog; 462 463 UIShortcut *shortcuts; 464 size_t shortcutCount, shortcutAllocated; 465 466 float scale; 467 468 uint32_t *bits; 469 int width, height; 470 struct UIWindow *next; 471 472 UIElement *hovered, *pressed, *focused, *dialogOldFocus; 473 int pressedButton; 474 475 int cursorX, cursorY; 476 int cursorStyle; 477 478 // Set when a textbox is modified. 479 // Useful for tracking whether changes to the loaded document have been saved. 480 bool textboxModifiedFlag; 481 482 bool ctrl, shift, alt; 483 484 UIRectangle updateRegion; 485 486 #ifdef UI_DEBUG 487 float lastFullFillCount; 488 #endif 489 490 #ifdef UI_LINUX 491 Window window; 492 XImage *image; 493 XIC xic; 494 unsigned ctrlCode, shiftCode, altCode; 495 Window dragSource; 496 #endif 497 498 #ifdef UI_WINDOWS 499 HWND hwnd; 500 bool trackingLeave; 501 #endif 502 503 #ifdef UI_ESSENCE 504 EsWindow *window; 505 EsElement *canvas; 506 int cursor; 507 #endif 508 509 #ifdef UI_COCOA 510 NSWindow *window; 511 void *view; 512 #endif 513 } UIWindow; 514 515 typedef struct UIPanel { 516 #define UI_PANEL_HORIZONTAL (1 << 0) 517 #define UI_PANEL_COLOR_1 (1 << 2) 518 #define UI_PANEL_COLOR_2 (1 << 3) 519 #define UI_PANEL_SMALL_SPACING (1 << 5) 520 #define UI_PANEL_MEDIUM_SPACING (1 << 6) 521 #define UI_PANEL_LARGE_SPACING (1 << 7) 522 #define UI_PANEL_SCROLL (1 << 8) 523 #define UI_PANEL_EXPAND (1 << 9) 524 UIElement e; 525 struct UIScrollBar *scrollBar; 526 UIRectangle border; 527 int gap; 528 } UIPanel; 529 530 typedef struct UIButton { 531 #define UI_BUTTON_SMALL (1 << 0) 532 #define UI_BUTTON_MENU_ITEM (1 << 1) 533 #define UI_BUTTON_CAN_FOCUS (1 << 2) 534 #define UI_BUTTON_DROP_DOWN (1 << 3) 535 #define UI_BUTTON_CHECKED (1 << 15) 536 UIElement e; 537 char *label; 538 ptrdiff_t labelBytes; 539 void (*invoke)(void *cp); 540 } UIButton; 541 542 typedef struct UICheckbox { 543 #define UI_CHECKBOX_ALLOW_INDETERMINATE (1 << 0) 544 UIElement e; 545 #define UI_CHECK_UNCHECKED (0) 546 #define UI_CHECK_CHECKED (1) 547 #define UI_CHECK_INDETERMINATE (2) 548 uint8_t check; 549 char *label; 550 ptrdiff_t labelBytes; 551 void (*invoke)(void *cp); 552 } UICheckbox; 553 554 typedef struct UILabel { 555 UIElement e; 556 char *label; 557 ptrdiff_t labelBytes; 558 } UILabel; 559 560 typedef struct UISpacer { 561 UIElement e; 562 int width, height; 563 } UISpacer; 564 565 typedef struct UISplitPane { 566 #define UI_SPLIT_PANE_VERTICAL (1 << 0) 567 UIElement e; 568 float weight; 569 } UISplitPane; 570 571 typedef struct UITabPane { 572 UIElement e; 573 char *tabs; 574 uint32_t active; 575 } UITabPane; 576 577 typedef struct UIScrollBar { 578 #define UI_SCROLL_BAR_HORIZONTAL (1 << 0) 579 UIElement e; 580 int64_t maximum, page; 581 int64_t dragOffset; 582 double position; 583 UI_CLOCK_T lastAnimateTime; 584 bool inDrag, horizontal; 585 } UIScrollBar; 586 587 #define _UI_LAYOUT_SCROLL_BAR_PAIR(element) \ 588 element->vScroll->page = vSpace - (element->hScroll->page < element->hScroll->maximum ? scrollBarSize : 0); \ 589 element->hScroll->page = hSpace - (element->vScroll->page < element->vScroll->maximum ? scrollBarSize : 0); \ 590 element->vScroll->page = vSpace - (element->hScroll->page < element->hScroll->maximum ? scrollBarSize : 0); \ 591 UIRectangle vScrollBarBounds = element->e.bounds, hScrollBarBounds = element->e.bounds; \ 592 hScrollBarBounds.r = vScrollBarBounds.l = vScrollBarBounds.r - (element->vScroll->page < element->vScroll->maximum ? scrollBarSize : 0); \ 593 vScrollBarBounds.b = hScrollBarBounds.t = hScrollBarBounds.b - (element->hScroll->page < element->hScroll->maximum ? scrollBarSize : 0); \ 594 UIElementMove(&element->vScroll->e, vScrollBarBounds, true); \ 595 UIElementMove(&element->hScroll->e, hScrollBarBounds, true); 596 #define _UI_KEY_INPUT_VSCROLL(element, rowHeight, pageHeight) \ 597 if (m->code == UI_KEYCODE_UP) element->vScroll->position -= (rowHeight); \ 598 else if (m->code == UI_KEYCODE_DOWN) element->vScroll->position += (rowHeight); \ 599 else if (m->code == UI_KEYCODE_PAGE_UP) element->vScroll->position += (pageHeight); \ 600 else if (m->code == UI_KEYCODE_PAGE_DOWN) element->vScroll->position -= (pageHeight); \ 601 else if (m->code == UI_KEYCODE_HOME) element->vScroll->position = 0; \ 602 else if (m->code == UI_KEYCODE_END) element->vScroll->position = element->vScroll->maximum; \ 603 UIElementRefresh(&element->e); 604 605 typedef struct UICodeLine { 606 int offset, bytes; 607 } UICodeLine; 608 609 typedef struct UICode { 610 #define UI_CODE_NO_MARGIN (1 << 0) 611 #define UI_CODE_SELECTABLE (1 << 1) 612 UIElement e; 613 UIScrollBar *vScroll, *hScroll; 614 UICodeLine *lines; 615 UIFont *font; 616 int lineCount, focused; 617 bool moveScrollToFocusNextLayout; 618 bool leftDownInMargin; 619 char *content; 620 size_t contentBytes; 621 int tabSize; 622 int columns; 623 UI_CLOCK_T lastAnimateTime; 624 struct { int line, offset; } selection[4 /* start, end, anchor, caret */]; 625 int verticalMotionColumn; 626 bool useVerticalMotionColumn; 627 bool moveScrollToCaretNextLayout; 628 bool centerExecutionPointer; 629 } UICode; 630 631 typedef struct UIGauge { 632 UIElement e; 633 double position; 634 } UIGauge; 635 636 typedef struct UITable { 637 UIElement e; 638 UIScrollBar *vScroll, *hScroll; 639 int itemCount; 640 char *columns; 641 int *columnWidths, columnCount, columnHighlight; 642 } UITable; 643 644 typedef struct UITextbox { 645 UIElement e; 646 char *string; 647 ptrdiff_t bytes; 648 int carets[2]; 649 int scroll; 650 bool rejectNextKey; 651 } UITextbox; 652 653 #define UI_MENU_PLACE_ABOVE (1 << 0) 654 #define UI_MENU_NO_SCROLL (1 << 1) 655 #if defined(UI_COCOA) 656 typedef NSMenu UIMenu; 657 #elif defined(UI_ESSENCE) 658 typedef EsMenu UIMenu; 659 #else 660 typedef struct UIMenu { 661 UIElement e; 662 int pointX, pointY; 663 UIScrollBar *vScroll; 664 UIWindow *parentWindow; 665 } UIMenu; 666 #endif 667 668 typedef struct UISlider { 669 UIElement e; 670 double position; 671 int steps; 672 } UISlider; 673 674 typedef struct UIMDIClient { 675 #define UI_MDI_CLIENT_TRANSPARENT (1 << 0) 676 UIElement e; 677 struct UIMDIChild *active; 678 int cascade; 679 } UIMDIClient; 680 681 typedef struct UIMDIChild { 682 #define UI_MDI_CHILD_CLOSE_BUTTON (1 << 0) 683 UIElement e; 684 UIRectangle bounds; 685 char *title; 686 ptrdiff_t titleBytes; 687 int dragHitTest; 688 UIRectangle dragOffset; 689 } UIMDIChild; 690 691 typedef struct UIExpandPane { 692 UIElement e; 693 UIButton *button; 694 UIPanel *panel; 695 bool expanded; 696 } UIExpandPane; 697 698 typedef struct UIImageDisplay { 699 #define UI_IMAGE_DISPLAY_INTERACTIVE (1 << 0) 700 #define _UI_IMAGE_DISPLAY_ZOOM_FIT (1 << 1) 701 702 UIElement e; 703 uint32_t *bits; 704 int width, height; 705 float panX, panY, zoom; 706 707 // Internals: 708 int previousWidth, previousHeight; 709 int previousPanPointX, previousPanPointY; 710 } UIImageDisplay; 711 712 typedef struct UIWrapPanel { 713 UIElement e; 714 } UIWrapPanel; 715 716 typedef struct UISwitcher { 717 UIElement e; 718 UIElement *active; 719 } UISwitcher; 720 721 void UIInitialise(); 722 int UIMessageLoop(); 723 724 UIElement *UIElementCreate(size_t bytes, UIElement *parent, uint32_t flags, 725 int (*messageClass)(UIElement *, UIMessage, int, void *), const char *cClassName); 726 727 UICheckbox *UICheckboxCreate(UIElement *parent, uint32_t flags, const char *label, ptrdiff_t labelBytes); 728 UIExpandPane *UIExpandPaneCreate(UIElement *parent, uint32_t flags, const char *label, ptrdiff_t labelBytes, uint32_t panelFlags); 729 UIMDIClient *UIMDIClientCreate(UIElement *parent, uint32_t flags); 730 UIMDIChild *UIMDIChildCreate(UIElement *parent, uint32_t flags, UIRectangle initialBounds, const char *title, ptrdiff_t titleBytes); 731 UIPanel *UIPanelCreate(UIElement *parent, uint32_t flags); 732 UIScrollBar *UIScrollBarCreate(UIElement *parent, uint32_t flags); 733 UISlider *UISliderCreate(UIElement *parent, uint32_t flags); 734 UISpacer *UISpacerCreate(UIElement *parent, uint32_t flags, int width, int height); 735 UISplitPane *UISplitPaneCreate(UIElement *parent, uint32_t flags, float weight); 736 UITabPane *UITabPaneCreate(UIElement *parent, uint32_t flags, const char *tabs /* separate with \t, terminate with \0 */); 737 UIWrapPanel *UIWrapPanelCreate(UIElement *parent, uint32_t flags); 738 739 UIGauge *UIGaugeCreate(UIElement *parent, uint32_t flags); 740 void UIGaugeSetPosition(UIGauge *gauge, float value); 741 742 UIButton *UIButtonCreate(UIElement *parent, uint32_t flags, const char *label, ptrdiff_t labelBytes); 743 void UIButtonSetLabel(UIButton *button, const char *string, ptrdiff_t stringBytes); 744 UILabel *UILabelCreate(UIElement *parent, uint32_t flags, const char *label, ptrdiff_t labelBytes); 745 void UILabelSetContent(UILabel *code, const char *content, ptrdiff_t byteCount); 746 747 UIImageDisplay *UIImageDisplayCreate(UIElement *parent, uint32_t flags, uint32_t *bits, size_t width, size_t height, size_t stride); 748 void UIImageDisplaySetContent(UIImageDisplay *display, uint32_t *bits, size_t width, size_t height, size_t stride); 749 750 UISwitcher *UISwitcherCreate(UIElement *parent, uint32_t flags); 751 void UISwitcherSwitchTo(UISwitcher *switcher, UIElement *child); 752 753 UIWindow *UIWindowCreate(UIWindow *owner, uint32_t flags, const char *cTitle, int width, int height); 754 void UIWindowRegisterShortcut(UIWindow *window, UIShortcut shortcut); 755 void UIWindowPostMessage(UIWindow *window, UIMessage message, void *dp); // Thread-safe. 756 void UIWindowPack(UIWindow *window, int width); // Change the size of the window to best match its contents. 757 758 typedef void (*UIDialogUserCallback)(UIElement *); 759 const char *UIDialogShow(UIWindow *window, uint32_t flags, const char *format, ...); 760 761 UIMenu *UIMenuCreate(UIElement *parent, uint32_t flags); 762 void UIMenuAddItem(UIMenu *menu, uint32_t flags, const char *label, ptrdiff_t labelBytes, void (*invoke)(void *cp), void *cp); 763 void UIMenuShow(UIMenu *menu); 764 bool UIMenusOpen(); 765 766 UITextbox *UITextboxCreate(UIElement *parent, uint32_t flags); 767 void UITextboxReplace(UITextbox *textbox, const char *text, ptrdiff_t bytes, bool sendChangedMessage); 768 void UITextboxClear(UITextbox *textbox, bool sendChangedMessage); 769 void UITextboxMoveCaret(UITextbox *textbox, bool backward, bool word); 770 char *UITextboxToCString(UITextbox *textbox); // Free with UI_FREE. 771 772 UITable *UITableCreate(UIElement *parent, uint32_t flags, const char *columns /* separate with \t, terminate with \0 */); 773 int UITableHitTest(UITable *table, int x, int y); // Returns item index. Returns -1 if not on an item. 774 int UITableHeaderHitTest(UITable *table, int x, int y); // Returns column index or -1. 775 bool UITableEnsureVisible(UITable *table, int index); // Returns false if the item was already visible. 776 void UITableResizeColumns(UITable *table); 777 778 UICode *UICodeCreate(UIElement *parent, uint32_t flags); 779 void UICodeFocusLine(UICode *code, int index); // Line numbers are 1-indexed!! 780 int UICodeHitTest(UICode *code, int x, int y); // Returns line number; negates if in margin. Returns 0 if not on a line. 781 void UICodePositionToByte(UICode *code, int x, int y, int *line, int *byte); 782 void UICodeInsertContent(UICode *code, const char *content, ptrdiff_t byteCount, bool replace); 783 void UICodeMoveCaret(UICode *code, bool backward, bool word); 784 785 void UIDrawBlock(UIPainter *painter, UIRectangle rectangle, uint32_t color); 786 void UIDrawCircle(UIPainter *painter, int centerX, int centerY, int radius, uint32_t fillColor, uint32_t outlineColor, bool hollow); 787 void UIDrawControl(UIPainter *painter, UIRectangle bounds, uint32_t mode /* UI_DRAW_CONTROL_* */, const char *label, ptrdiff_t labelBytes, double position, float scale); 788 void UIDrawControlDefault(UIPainter *painter, UIRectangle bounds, uint32_t mode, const char *label, ptrdiff_t labelBytes, double position, float scale); 789 void UIDrawInvert(UIPainter *painter, UIRectangle rectangle); 790 bool UIDrawLine(UIPainter *painter, int x0, int y0, int x1, int y1, uint32_t color); // Returns false if the line was not visible. 791 void UIDrawTriangle(UIPainter *painter, int x0, int y0, int x1, int y1, int x2, int y2, uint32_t color); 792 void UIDrawTriangleOutline(UIPainter *painter, int x0, int y0, int x1, int y1, int x2, int y2, uint32_t color); 793 void UIDrawGlyph(UIPainter *painter, int x, int y, int c, uint32_t color); 794 void UIDrawRectangle(UIPainter *painter, UIRectangle r, uint32_t mainColor, uint32_t borderColor, UIRectangle borderSize); 795 void UIDrawBorder(UIPainter *painter, UIRectangle r, uint32_t borderColor, UIRectangle borderSize); 796 void UIDrawString(UIPainter *painter, UIRectangle r, const char *string, ptrdiff_t bytes, uint32_t color, int align, UIStringSelection *selection); 797 int UIDrawStringHighlighted(UIPainter *painter, UIRectangle r, const char *string, ptrdiff_t bytes, int tabSize, UIStringSelection *selection); // Returns final x position. 798 799 int UIMeasureStringWidth(const char *string, ptrdiff_t bytes); 800 int UIMeasureStringHeight(); 801 802 uint64_t UIAnimateClock(); // In ms. 803 804 bool UIElementAnimate(UIElement *element, bool stop); 805 void UIElementDestroy(UIElement *element); 806 void UIElementDestroyDescendents(UIElement *element); 807 UIElement *UIElementFindByPoint(UIElement *element, int x, int y); 808 void UIElementFocus(UIElement *element); 809 UIRectangle UIElementScreenBounds(UIElement *element); // Returns bounds of element in same coordinate system as used by UIWindowCreate. 810 void UIElementRefresh(UIElement *element); 811 void UIElementRelayout(UIElement *element); 812 void UIElementRepaint(UIElement *element, UIRectangle *region); 813 void UIElementMeasurementsChanged(UIElement *element, int which); 814 void UIElementMove(UIElement *element, UIRectangle bounds, bool alwaysLayout); 815 int UIElementMessage(UIElement *element, UIMessage message, int di, void *dp); 816 UIElement *UIElementChangeParent(UIElement *element, UIElement *newParent, UIElement *insertBefore); // Set insertBefore to null to insert at the end. Returns the element it was before in its previous parent, or NULL. 817 818 UIElement *UIParentPush(UIElement *element); 819 UIElement *UIParentPop(); 820 821 UIRectangle UIRectangleIntersection(UIRectangle a, UIRectangle b); 822 UIRectangle UIRectangleBounding(UIRectangle a, UIRectangle b); 823 UIRectangle UIRectangleAdd(UIRectangle a, UIRectangle b); 824 UIRectangle UIRectangleTranslate(UIRectangle a, UIRectangle b); 825 UIRectangle UIRectangleCenter(UIRectangle parent, UIRectangle child); 826 UIRectangle UIRectangleFit(UIRectangle parent, UIRectangle child, bool allowScalingUp); 827 bool UIRectangleEquals(UIRectangle a, UIRectangle b); 828 bool UIRectangleContains(UIRectangle a, int x, int y); 829 830 bool UIColorToHSV(uint32_t rgb, float *hue, float *saturation, float *value); 831 void UIColorToRGB(float hue, float saturation, float value, uint32_t *rgb); 832 833 char *UIStringCopy(const char *in, ptrdiff_t inBytes); 834 835 UIFont *UIFontCreate(const char *cPath, uint32_t size); 836 UIFont *UIFontActivate(UIFont *font); // Returns the previously active font. 837 838 #ifdef UI_DEBUG 839 void UIInspectorLog(const char *cFormat, ...); 840 #endif 841 842 static ptrdiff_t _UIStringLength(const char *cString) { 843 if (!cString) return 0; 844 ptrdiff_t length; 845 for (length = 0; cString[length]; length++); 846 return length; 847 } 848 849 #ifdef UI_UNICODE 850 851 #ifndef UI_FREETYPE 852 #error "Unicode support requires Freetype" 853 #endif 854 855 #define _UNICODE_MAX_CODEPOINT 0x10FFFF 856 857 int Utf8GetCodePoint(const char *cString, ptrdiff_t bytesLength, ptrdiff_t *bytesConsumed) { 858 UI_ASSERT(bytesLength > 0 && "Attempted to get UTF-8 code point from an empty string"); 859 860 if (bytesConsumed == NULL) { 861 ptrdiff_t bytesConsumed; 862 return Utf8GetCodePoint(cString, bytesLength, &bytesConsumed); 863 } 864 865 ptrdiff_t numExtraBytes; 866 uint8_t first = cString[0]; 867 868 *bytesConsumed = 1; 869 if ((first & 0xF0) == 0xF0) { 870 numExtraBytes = 3; 871 } else if ((first & 0xE0) == 0xE0) { 872 numExtraBytes = 2; 873 } else if ((first & 0xC0) == 0xC0) { 874 numExtraBytes = 1; 875 } else if (first & 0x7F) { 876 return first & 0x80 ? -1 : first; 877 } else { 878 return -1; 879 } 880 881 if (bytesLength < numExtraBytes + 1) { 882 return -1; 883 } 884 885 int codePoint = ((int)first & (0x3F >> numExtraBytes)) << (6 * numExtraBytes); 886 for (ptrdiff_t idx = 1; idx < numExtraBytes + 1; idx++) { 887 char byte = cString[idx]; 888 if ((byte & 0xC0) != 0x80) { 889 return -1; 890 } 891 892 codePoint |= (byte & 0x3F) << (6 * (numExtraBytes - idx)); 893 (*bytesConsumed)++; 894 } 895 896 return codePoint > _UNICODE_MAX_CODEPOINT ? -1 : codePoint; 897 } 898 899 char * Utf8GetPreviousChar(char *string, char *offset) { 900 if (string == offset) { 901 return string; 902 } 903 904 char *prev = offset - 1; 905 while (prev > string) { 906 if ((*prev & 0xC0) == 0x80) prev--; 907 else break; 908 } 909 910 return prev; 911 } 912 913 ptrdiff_t Utf8GetCharBytes(const char *cString, ptrdiff_t bytes) { 914 if (!cString) { 915 return 0; 916 } 917 if (bytes == -1) { 918 bytes = _UIStringLength(cString); 919 } 920 921 ptrdiff_t bytesConsumed; 922 Utf8GetCodePoint(cString, bytes, &bytesConsumed); 923 return bytesConsumed; 924 } 925 926 ptrdiff_t Utf8StringLength(const char *cString, ptrdiff_t bytes) { 927 if (!cString) { 928 return 0; 929 } 930 if (bytes == -1) { 931 bytes = _UIStringLength(cString); 932 } 933 934 ptrdiff_t length = 0; 935 ptrdiff_t byteIndex = 0; 936 while (byteIndex < bytes) { 937 ptrdiff_t bytesConsumed; 938 Utf8GetCodePoint(cString+ byteIndex, bytes - byteIndex, &bytesConsumed); 939 byteIndex += bytesConsumed; 940 length++; 941 942 UI_ASSERT(byteIndex <= bytes && "Overran the end of the string while counting the number of UTF-8 code points"); 943 } 944 945 return length; 946 } 947 948 #define _UI_ADVANCE_CHAR(index, text, count) \ 949 index += Utf8GetCharBytes(text, count - index) 950 951 #define _UI_SKIP_TAB(ti, text, bytesLeft, tabSize) do { \ 952 int c = Utf8GetCodePoint(text, bytesLeft, NULL); \ 953 if (c == '\t') while (ti % tabSize) ti++; \ 954 } while (0) 955 956 #define _UI_MOVE_CARET_BACKWARD(caret, text, offset, offset2) do { \ 957 char *prev = Utf8GetPreviousChar(text, text + offset); \ 958 caret = prev - text - offset2; \ 959 } while (0) 960 961 #define _UI_MOVE_CARET_FORWARD(caret, text, bytes, offset) do { \ 962 caret += Utf8GetCharBytes(text + caret, bytes - offset); \ 963 } while (0) 964 965 #define _UI_MOVE_CARET_BY_WORD(text, bytes, offset) { \ 966 char *prev = Utf8GetPreviousChar(text, text + offset); \ 967 int c1 = Utf8GetCodePoint(prev, bytes - (prev - text), NULL); \ 968 int c2 = Utf8GetCodePoint(text + offset, bytes - offset, NULL); \ 969 if (_UICharIsAlphaOrDigitOrUnderscore(c1) != _UICharIsAlphaOrDigitOrUnderscore(c2)) break; \ 970 } 971 972 #else 973 974 #define _UI_ADVANCE_CHAR(index, code, count) index++ 975 976 #define _UI_SKIP_TAB(ti, text, bytesLeft, tabSize) \ 977 if (*(text) == '\t') while (ti % tabSize) ti++ 978 979 #define _UI_MOVE_CARET_BACKWARD(caret, text, offset, offset2) caret-- 980 #define _UI_MOVE_CARET_FORWARD(caret, text, bytes, offset) caret++ 981 982 #define _UI_MOVE_CARET_BY_WORD(text, bytes, offset) { \ 983 char c1 = (text)[offset - 1]; \ 984 char c2 = (text)[offset]; \ 985 if (_UICharIsAlphaOrDigitOrUnderscore(c1) != _UICharIsAlphaOrDigitOrUnderscore(c2)) break; \ 986 } 987 988 #endif // UI_UNICODE 989 990 #ifdef UI_IMPLEMENTATION 991 992 ///////////////////////////////////////// 993 // Global variables. 994 ///////////////////////////////////////// 995 996 struct { 997 UIWindow *windows; 998 UITheme theme; 999 1000 UIElement **animating; 1001 uint32_t animatingCount; 1002 1003 UIElement *parentStack[16]; 1004 int parentStackCount; 1005 1006 bool quit; 1007 const char *dialogResult; 1008 UIElement *dialogOldFocus; 1009 bool dialogCanExit; 1010 1011 UIFont *activeFont; 1012 1013 #ifdef UI_DEBUG 1014 UIWindow *inspector; 1015 UITable *inspectorTable; 1016 UIWindow *inspectorTarget; 1017 UICode *inspectorLog; 1018 #endif 1019 1020 #ifdef UI_LINUX 1021 Display *display; 1022 Visual *visual; 1023 XIM xim; 1024 Atom windowClosedID, primaryID, uriListID, plainTextID; 1025 Atom dndEnterID, dndPositionID, dndStatusID, dndActionCopyID, dndDropID, dndSelectionID, dndFinishedID, dndAwareID; 1026 Atom clipboardID, xSelectionDataID, textID, targetID, incrID; 1027 Cursor cursors[UI_CURSOR_COUNT]; 1028 char *pasteText; 1029 XEvent copyEvent; 1030 #endif 1031 1032 #ifdef UI_WINDOWS 1033 HCURSOR cursors[UI_CURSOR_COUNT]; 1034 HANDLE heap; 1035 bool assertionFailure; 1036 #endif 1037 1038 #ifdef UI_ESSENCE 1039 EsInstance *instance; 1040 #endif 1041 1042 #if defined(UI_ESSENCE) || defined(UI_COCOA) 1043 void *menuData[256]; // HACK This limits the number of menu items to 128. 1044 uintptr_t menuIndex; 1045 #endif 1046 1047 #ifdef UI_COCOA 1048 int menuX, menuY; 1049 UIWindow *menuWindow; 1050 #endif 1051 1052 #ifdef UI_FREETYPE 1053 FT_Library ft; 1054 #endif 1055 } ui; 1056 1057 ///////////////////////////////////////// 1058 // Themes. 1059 ///////////////////////////////////////// 1060 1061 UITheme uiThemeClassic = { 1062 .panel1 = 0xFFF0F0F0, 1063 .panel2 = 0xFFFFFFFF, 1064 .selected = 0xFF94BEFE, 1065 .border = 0xFF404040, 1066 1067 .text = 0xFF000000, 1068 .textDisabled = 0xFF404040, 1069 .textSelected = 0xFF000000, 1070 1071 .buttonNormal = 0xFFE0E0E0, 1072 .buttonHovered = 0xFFF0F0F0, 1073 .buttonPressed = 0xFFA0A0A0, 1074 .buttonDisabled = 0xFFF0F0F0, 1075 1076 .textboxNormal = 0xFFF8F8F8, 1077 .textboxFocused = 0xFFFFFFFF, 1078 1079 .codeFocused = 0xFFE0E0E0, 1080 .codeBackground = 0xFFFFFFFF, 1081 .codeDefault = 0xFF000000, 1082 .codeComment = 0xFFA11F20, 1083 .codeString = 0xFF037E01, 1084 .codeNumber = 0xFF213EF1, 1085 .codeOperator = 0xFF7F0480, 1086 .codePreprocessor = 0xFF545D70, 1087 1088 .accent1 = 0xFF0000, 1089 .accent2 = 0x00FF00, 1090 }; 1091 1092 UITheme uiThemeDark = { 1093 .panel1 = 0xFF252B31, 1094 .panel2 = 0xFF14181E, 1095 .selected = 0xFF94BEFE, 1096 .border = 0xFF000000, 1097 1098 .text = 0xFFFFFFFF, 1099 .textDisabled = 0xFF787D81, 1100 .textSelected = 0xFF000000, 1101 1102 .buttonNormal = 0xFF383D41, 1103 .buttonHovered = 0xFF4B5874, 1104 .buttonPressed = 0xFF0D0D0F, 1105 .buttonDisabled = 0xFF1B1F23, 1106 1107 .textboxNormal = 0xFF31353C, 1108 .textboxFocused = 0xFF4D4D59, 1109 1110 .codeFocused = 0xFF505055, 1111 .codeBackground = 0xFF212126, 1112 .codeDefault = 0xFFFFFFFF, 1113 .codeComment = 0xFFB4B4B4, 1114 .codeString = 0xFFF5DDD1, 1115 .codeNumber = 0xFFC3F5D3, 1116 .codeOperator = 0xFFF5D499, 1117 .codePreprocessor = 0xFFF5F3D1, 1118 1119 .accent1 = 0xF01231, 1120 .accent2 = 0x45F94E, 1121 }; 1122 1123 ///////////////////////////////////////// 1124 // Forward declarations. 1125 ///////////////////////////////////////// 1126 1127 void _UIWindowEndPaint(UIWindow *window, UIPainter *painter); 1128 void _UIWindowSetCursor(UIWindow *window, int cursor); 1129 void _UIWindowGetScreenPosition(UIWindow *window, int *x, int *y); 1130 void _UIWindowSetPressed(UIWindow *window, UIElement *element, int button); 1131 void _UIClipboardWriteText(UIWindow *window, char *text); 1132 char *_UIClipboardReadTextStart(UIWindow *window, size_t *bytes); 1133 void _UIClipboardReadTextEnd(UIWindow *window, char *text); 1134 bool _UIMessageLoopSingle(int *result); 1135 void _UIInspectorRefresh(); 1136 void _UIUpdate(); 1137 1138 #if defined(UI_LINUX) || defined(UI_COCOA) 1139 UI_CLOCK_T _UIClock() { 1140 struct timespec spec; 1141 clock_gettime(CLOCK_REALTIME, &spec); 1142 return spec.tv_sec * 1000 + spec.tv_nsec / 1000000; 1143 } 1144 #endif 1145 1146 #ifdef UI_WINDOWS 1147 void *_UIHeapReAlloc(void *pointer, size_t size); 1148 void *_UIMemmove(void *dest, const void *src, size_t n); 1149 #endif 1150 1151 ///////////////////////////////////////// 1152 // Helper functions. 1153 ///////////////////////////////////////// 1154 1155 UIRectangle UIRectangleIntersection(UIRectangle a, UIRectangle b) { 1156 if (a.l < b.l) a.l = b.l; 1157 if (a.t < b.t) a.t = b.t; 1158 if (a.r > b.r) a.r = b.r; 1159 if (a.b > b.b) a.b = b.b; 1160 return a; 1161 } 1162 1163 UIRectangle UIRectangleBounding(UIRectangle a, UIRectangle b) { 1164 if (a.l > b.l) a.l = b.l; 1165 if (a.t > b.t) a.t = b.t; 1166 if (a.r < b.r) a.r = b.r; 1167 if (a.b < b.b) a.b = b.b; 1168 return a; 1169 } 1170 1171 UIRectangle UIRectangleAdd(UIRectangle a, UIRectangle b) { 1172 a.l += b.l; 1173 a.t += b.t; 1174 a.r += b.r; 1175 a.b += b.b; 1176 return a; 1177 } 1178 1179 UIRectangle UIRectangleTranslate(UIRectangle a, UIRectangle b) { 1180 a.l += b.l; 1181 a.t += b.t; 1182 a.r += b.l; 1183 a.b += b.t; 1184 return a; 1185 } 1186 1187 UIRectangle UIRectangleCenter(UIRectangle parent, UIRectangle child) { 1188 int childWidth = UI_RECT_WIDTH(child), childHeight = UI_RECT_HEIGHT(child); 1189 int parentWidth = UI_RECT_WIDTH(parent), parentHeight = UI_RECT_HEIGHT(parent); 1190 child.l = parentWidth / 2 - childWidth / 2 + parent.l, child.r = child.l + childWidth; 1191 child.t = parentHeight / 2 - childHeight / 2 + parent.t, child.b = child.t + childHeight; 1192 return child; 1193 } 1194 1195 UIRectangle UIRectangleFit(UIRectangle parent, UIRectangle child, bool allowScalingUp) { 1196 int childWidth = UI_RECT_WIDTH(child), childHeight = UI_RECT_HEIGHT(child); 1197 int parentWidth = UI_RECT_WIDTH(parent), parentHeight = UI_RECT_HEIGHT(parent); 1198 1199 if (childWidth < parentWidth && childHeight < parentHeight && !allowScalingUp) { 1200 return UIRectangleCenter(parent, child); 1201 } 1202 1203 float childAspectRatio = (float) childWidth / childHeight; 1204 int childMaximumWidth = parentHeight * childAspectRatio; 1205 int childMaximumHeight = parentWidth / childAspectRatio; 1206 1207 if (childMaximumWidth > parentWidth) { 1208 return UIRectangleCenter(parent, UI_RECT_2S(parentWidth, childMaximumHeight)); 1209 } else { 1210 return UIRectangleCenter(parent, UI_RECT_2S(childMaximumWidth, parentHeight)); 1211 } 1212 } 1213 1214 bool UIRectangleEquals(UIRectangle a, UIRectangle b) { 1215 return a.l == b.l && a.r == b.r && a.t == b.t && a.b == b.b; 1216 } 1217 1218 bool UIRectangleContains(UIRectangle a, int x, int y) { 1219 return a.l <= x && a.r > x && a.t <= y && a.b > y; 1220 } 1221 1222 typedef union _UIConvertFloatInteger { 1223 float f; 1224 uint32_t i; 1225 } _UIConvertFloatInteger; 1226 1227 float _UIFloorFloat(float x) { 1228 _UIConvertFloatInteger convert = {x}; 1229 uint32_t sign = convert.i & 0x80000000; 1230 int exponent = (int) ((convert.i >> 23) & 0xFF) - 0x7F; 1231 1232 if (exponent >= 23) { 1233 // There aren't any bits representing a fractional part. 1234 } else if (exponent >= 0) { 1235 // Positive exponent. 1236 uint32_t mask = 0x7FFFFF >> exponent; 1237 if (!(mask & convert.i)) return x; // Already an integer. 1238 if (sign) convert.i += mask; 1239 convert.i &= ~mask; // Mask out the fractional bits. 1240 } else if (exponent < 0) { 1241 // Negative exponent. 1242 return sign ? -1.0 : 0.0; 1243 } 1244 1245 return convert.f; 1246 } 1247 1248 float _UILinearMap(float value, float inFrom, float inTo, float outFrom, float outTo) { 1249 float inRange = inTo - inFrom, outRange = outTo - outFrom; 1250 float normalisedValue = (value - inFrom) / inRange; 1251 return normalisedValue * outRange + outFrom; 1252 } 1253 1254 bool UIColorToHSV(uint32_t rgb, float *hue, float *saturation, float *value) { 1255 float r = UI_COLOR_RED_F(rgb); 1256 float g = UI_COLOR_GREEN_F(rgb); 1257 float b = UI_COLOR_BLUE_F(rgb); 1258 1259 float maximum = (r > g && r > b) ? r : (g > b ? g : b), 1260 minimum = (r < g && r < b) ? r : (g < b ? g : b), 1261 difference = maximum - minimum; 1262 *value = maximum; 1263 1264 if (!difference) { 1265 *saturation = 0; 1266 return false; 1267 } else { 1268 if (r == maximum) *hue = (g - b) / difference + 0; 1269 if (g == maximum) *hue = (b - r) / difference + 2; 1270 if (b == maximum) *hue = (r - g) / difference + 4; 1271 if (*hue < 0) *hue += 6; 1272 *saturation = difference / maximum; 1273 return true; 1274 } 1275 } 1276 1277 void UIColorToRGB(float h, float s, float v, uint32_t *rgb) { 1278 float r, g, b; 1279 1280 if (!s) { 1281 r = g = b = v; 1282 } else { 1283 int h0 = ((int) h) % 6; 1284 float f = h - _UIFloorFloat(h); 1285 float x = v * (1 - s), y = v * (1 - s * f), z = v * (1 - s * (1 - f)); 1286 1287 switch (h0) { 1288 case 0: r = v, g = z, b = x; break; 1289 case 1: r = y, g = v, b = x; break; 1290 case 2: r = x, g = v, b = z; break; 1291 case 3: r = x, g = y, b = v; break; 1292 case 4: r = z, g = x, b = v; break; 1293 default: r = v, g = x, b = y; break; 1294 } 1295 } 1296 1297 *rgb = UI_COLOR_FROM_FLOAT(r, g, b); 1298 } 1299 1300 char *UIStringCopy(const char *in, ptrdiff_t inBytes) { 1301 if (inBytes == -1) { 1302 inBytes = _UIStringLength(in); 1303 } 1304 1305 char *buffer = (char *) UI_MALLOC(inBytes + 1); 1306 1307 for (intptr_t i = 0; i < inBytes; i++) { 1308 buffer[i] = in[i]; 1309 } 1310 1311 buffer[inBytes] = 0; 1312 return buffer; 1313 } 1314 1315 int _UIByteToColumn(const char *string, int byte, int bytes, int tabSize) { 1316 int ti = 0, i = 0; 1317 1318 while (i < byte && i < bytes) { 1319 ti++; 1320 _UI_SKIP_TAB(ti, string + i, bytes - i, tabSize); 1321 _UI_ADVANCE_CHAR(i, string + i, byte); 1322 } 1323 1324 return ti; 1325 } 1326 1327 int _UIColumnToByte(const char *string, int column, int bytes, int tabSize) { 1328 int byte = 0, ti = 0; 1329 1330 while (byte < bytes) { 1331 ti++; 1332 _UI_SKIP_TAB(ti, string + byte, bytes - byte, tabSize); 1333 if (column < ti) break; 1334 1335 _UI_ADVANCE_CHAR(byte, string + byte, bytes); 1336 } 1337 1338 return byte; 1339 } 1340 1341 ///////////////////////////////////////// 1342 // Animations. 1343 ///////////////////////////////////////// 1344 1345 bool UIElementAnimate(UIElement *element, bool stop) { 1346 if (stop) { 1347 for (uint32_t i = 0; i < ui.animatingCount; i++) { 1348 if (ui.animating[i] == element) { 1349 ui.animating[i] = ui.animating[ui.animatingCount - 1]; 1350 ui.animatingCount--; 1351 return true; 1352 } 1353 } 1354 1355 return false; 1356 } else { 1357 for (uint32_t i = 0; i < ui.animatingCount; i++) { 1358 if (ui.animating[i] == element) { 1359 return true; 1360 } 1361 } 1362 1363 ui.animating = (UIElement **) UI_REALLOC(ui.animating, sizeof(UIElement *) * (ui.animatingCount + 1)); 1364 ui.animating[ui.animatingCount] = element; 1365 ui.animatingCount++; 1366 UI_ASSERT(~element->flags & UI_ELEMENT_DESTROY); 1367 return true; 1368 } 1369 } 1370 1371 uint64_t UIAnimateClock() { 1372 return (uint64_t) UI_CLOCK() * 1000 / UI_CLOCKS_PER_SECOND; 1373 } 1374 1375 void _UIProcessAnimations() { 1376 bool update = ui.animatingCount; 1377 1378 for (uint32_t i = 0; i < ui.animatingCount; i++) { 1379 UIElementMessage(ui.animating[i], UI_MSG_ANIMATE, 0, 0); 1380 } 1381 1382 if (update) { 1383 _UIUpdate(); 1384 } 1385 } 1386 1387 ///////////////////////////////////////// 1388 // Rendering. 1389 ///////////////////////////////////////// 1390 1391 void UIDrawBlock(UIPainter *painter, UIRectangle rectangle, uint32_t color) { 1392 rectangle = UIRectangleIntersection(painter->clip, rectangle); 1393 1394 if (!UI_RECT_VALID(rectangle)) { 1395 return; 1396 } 1397 1398 #ifdef UI_SSE2 1399 __m128i color4 = _mm_set_epi32(color, color, color, color); 1400 #endif 1401 1402 for (int line = rectangle.t; line < rectangle.b; line++) { 1403 uint32_t *bits = painter->bits + line * painter->width + rectangle.l; 1404 int count = UI_RECT_WIDTH(rectangle); 1405 1406 #ifdef UI_SSE2 1407 while (count >= 4) { 1408 _mm_storeu_si128((__m128i *) bits, color4); 1409 bits += 4; 1410 count -= 4; 1411 } 1412 #endif 1413 1414 while (count--) { 1415 *bits++ = color; 1416 } 1417 } 1418 1419 #ifdef UI_DEBUG 1420 painter->fillCount += UI_RECT_WIDTH(rectangle) * UI_RECT_HEIGHT(rectangle); 1421 #endif 1422 } 1423 1424 bool UIDrawLine(UIPainter *painter, int x0, int y0, int x1, int y1, uint32_t color) { 1425 // Apply the clip. 1426 1427 UIRectangle c = painter->clip; 1428 if (!UI_RECT_VALID(c)) return false; 1429 int dx = x1 - x0, dy = y1 - y0; 1430 const int p[4] = { -dx, dx, -dy, dy }; 1431 const int q[4] = { x0 - c.l, c.r - 1 - x0, y0 - c.t, c.b - 1 - y0 }; 1432 float t0 = 0.0f, t1 = 1.0f; // How far along the line the points end up. 1433 1434 for (int i = 0; i < 4; i++) { 1435 if (!p[i] && q[i] < 0) return false; 1436 float r = (float) q[i] / p[i]; 1437 if (p[i] < 0 && r > t1) return false; 1438 if (p[i] > 0 && r < t0) return false; 1439 if (p[i] < 0 && r > t0) t0 = r; 1440 if (p[i] > 0 && r < t1) t1 = r; 1441 } 1442 1443 x1 = x0 + t1 * dx, y1 = y0 + t1 * dy; 1444 x0 += t0 * dx, y0 += t0 * dy; 1445 1446 // Calculate the delta X and delta Y. 1447 1448 if (y1 < y0) { 1449 int t; 1450 t = x0, x0 = x1, x1 = t; 1451 t = y0, y0 = y1, y1 = t; 1452 } 1453 1454 dx = x1 - x0, dy = y1 - y0; 1455 int dxs = dx < 0 ? -1 : 1; 1456 if (dx < 0) dx = -dx; 1457 1458 // Draw the line using Bresenham's line algorithm. 1459 1460 uint32_t *bits = painter->bits + y0 * painter->width + x0; 1461 1462 if (dy * dy < dx * dx) { 1463 int m = 2 * dy - dx; 1464 1465 for (int i = 0; i < dx; i++, bits += dxs) { 1466 *bits = color; 1467 if (m > 0) bits += painter->width, m -= 2 * dx; 1468 m += 2 * dy; 1469 } 1470 } else { 1471 int m = 2 * dx - dy; 1472 1473 for (int i = 0; i < dy; i++, bits += painter->width) { 1474 *bits = color; 1475 if (m > 0) bits += dxs, m -= 2 * dy; 1476 m += 2 * dx; 1477 } 1478 } 1479 1480 return true; 1481 } 1482 1483 void UIDrawCircle(UIPainter *painter, int cx, int cy, int radius, uint32_t fillColor, uint32_t outlineColor, bool hollow) { 1484 // TODO There's a hole missing at the bottom of the circle! 1485 // TODO This looks bad at small radii (< 20). 1486 1487 float x = 0, y = -radius; 1488 float dx = radius, dy = 0; 1489 float step = 0.2f / radius; 1490 int px = 0, py = cy + y; 1491 1492 while (x >= 0) { 1493 x += dx * step; 1494 y += dy * step; 1495 dx += -x * step; 1496 dy += -y * step; 1497 1498 int ix = x, iy = cy + y; 1499 1500 while (py <= iy) { 1501 if (py >= painter->clip.t && py < painter->clip.b) { 1502 for (int s = 0; s <= ix || s <= px; s++) { 1503 bool inOutline = ((s <= ix) != (s <= px)) || ((ix == px) && (s == ix)); 1504 if (hollow && !inOutline) continue; 1505 bool clip0 = cx + s >= painter->clip.l && cx + s < painter->clip.r; 1506 bool clip1 = cx - s >= painter->clip.l && cx - s < painter->clip.r; 1507 if (clip0) painter->bits[painter->width * py + cx + s] = inOutline ? outlineColor : fillColor; 1508 if (clip1) painter->bits[painter->width * py + cx - s] = inOutline ? outlineColor : fillColor; 1509 } 1510 } 1511 1512 px = ix, py++; 1513 } 1514 } 1515 } 1516 1517 void UIDrawTriangle(UIPainter *painter, int x0, int y0, int x1, int y1, int x2, int y2, uint32_t color) { 1518 // Step 1: Sort the points by their y-coordinate. 1519 if (y1 < y0) { int xt = x0; x0 = x1, x1 = xt; int yt = y0; y0 = y1, y1 = yt; } 1520 if (y2 < y1) { int xt = x1; x1 = x2, x2 = xt; int yt = y1; y1 = y2, y2 = yt; } 1521 if (y1 < y0) { int xt = x0; x0 = x1, x1 = xt; int yt = y0; y0 = y1, y1 = yt; } 1522 if (y2 == y0) return; 1523 1524 // Step 2: Clip the triangle. 1525 if (x0 < painter->clip.l && x1 < painter->clip.l && x2 < painter->clip.l) return; 1526 if (x0 >= painter->clip.r && x1 >= painter->clip.r && x2 >= painter->clip.r) return; 1527 if (y2 < painter->clip.t || y0 >= painter->clip.b) return; 1528 bool needsXClip = x0 < painter->clip.l + 1 || x0 >= painter->clip.r - 1 1529 || x1 < painter->clip.l + 1 || x1 >= painter->clip.r - 1 1530 || x2 < painter->clip.l + 1 || x2 >= painter->clip.r - 1; 1531 bool needsYClip = y0 < painter->clip.t + 1 || y2 >= painter->clip.b - 1; 1532 #define _UI_DRAW_TRIANGLE_APPLY_CLIP(xo, yo) \ 1533 if (needsYClip && (yi + yo < painter->clip.t || yi + yo >= painter->clip.b)) continue; \ 1534 if (needsXClip && xf + xo < painter->clip.l) xf = painter->clip.l - xo; \ 1535 if (needsXClip && xt + xo > painter->clip.r) xt = painter->clip.r - xo; 1536 1537 // Step 3: Split into 2 triangles with bases aligned with the x-axis. 1538 float xm0 = (x2 - x0) * (y1 - y0) / (y2 - y0), xm1 = x1 - x0; 1539 if (xm1 < xm0) { float xmt = xm0; xm0 = xm1, xm1 = xmt; } 1540 float xe0 = xm0 + x0 - x2, xe1 = xm1 + x0 - x2; 1541 int ym = y1 - y0, ye = y2 - y1; 1542 float ymr = 1.0f / ym, yer = 1.0f / ye; 1543 1544 // Step 4: Draw the top part. 1545 for (float y = 0; y < ym; y++) { 1546 int xf = xm0 * y * ymr, xt = xm1 * y * ymr, yi = (int) y; 1547 _UI_DRAW_TRIANGLE_APPLY_CLIP(x0, y0); 1548 uint32_t *b = &painter->bits[(yi + y0) * painter->width + x0]; 1549 for (int x = xf; x < xt; x++) b[x] = color; 1550 } 1551 1552 // Step 5: Draw the bottom part. 1553 for (float y = 0; y < ye; y++) { 1554 int xf = xe0 * (ye - y) * yer, xt = xe1 * (ye - y) * yer, yi = (int) y; 1555 _UI_DRAW_TRIANGLE_APPLY_CLIP(x2, y1); 1556 uint32_t *b = &painter->bits[(yi + y1) * painter->width + x2]; 1557 for (int x = xf; x < xt; x++) b[x] = color; 1558 } 1559 } 1560 1561 void UIDrawTriangleOutline(UIPainter *painter, int x0, int y0, int x1, int y1, int x2, int y2, uint32_t color) { 1562 UIDrawLine(painter, x0, y0, x1, y1, color); 1563 UIDrawLine(painter, x1, y1, x2, y2, color); 1564 UIDrawLine(painter, x2, y2, x0, y0, color); 1565 } 1566 1567 void UIDrawInvert(UIPainter *painter, UIRectangle rectangle) { 1568 rectangle = UIRectangleIntersection(painter->clip, rectangle); 1569 1570 if (!UI_RECT_VALID(rectangle)) { 1571 return; 1572 } 1573 1574 for (int line = rectangle.t; line < rectangle.b; line++) { 1575 uint32_t *bits = painter->bits + line * painter->width + rectangle.l; 1576 int count = UI_RECT_WIDTH(rectangle); 1577 1578 while (count--) { 1579 uint32_t in = *bits; 1580 *bits = in ^ 0xFFFFFF; 1581 bits++; 1582 } 1583 } 1584 } 1585 1586 int UIMeasureStringWidth(const char *string, ptrdiff_t bytes) { 1587 #ifdef UI_UNICODE 1588 return Utf8StringLength(string, bytes) * ui.activeFont->glyphWidth; 1589 #else 1590 if (bytes == -1) { 1591 bytes = _UIStringLength(string); 1592 } 1593 1594 return bytes * ui.activeFont->glyphWidth; 1595 #endif 1596 } 1597 1598 int UIMeasureStringHeight() { 1599 return ui.activeFont->glyphHeight; 1600 } 1601 1602 void UIDrawString(UIPainter *painter, UIRectangle r, const char *string, ptrdiff_t bytes, uint32_t color, int align, UIStringSelection *selection) { 1603 UIRectangle oldClip = painter->clip; 1604 painter->clip = UIRectangleIntersection(r, oldClip); 1605 1606 if (!UI_RECT_VALID(painter->clip)) { 1607 painter->clip = oldClip; 1608 return; 1609 } 1610 1611 if (bytes == -1) { 1612 bytes = _UIStringLength(string); 1613 } 1614 1615 int width = UIMeasureStringWidth(string, bytes); 1616 int height = UIMeasureStringHeight(); 1617 int x = align == UI_ALIGN_CENTER ? ((r.l + r.r - width) / 2) : align == UI_ALIGN_RIGHT ? (r.r - width) : r.l; 1618 int y = (r.t + r.b - height) / 2; 1619 int i = 0, j = 0; 1620 1621 int selectFrom = -1, selectTo = -1; 1622 1623 if (selection) { 1624 selectFrom = selection->carets[0]; 1625 selectTo = selection->carets[1]; 1626 1627 if (selectFrom > selectTo) { 1628 UI_SWAP(int, selectFrom, selectTo); 1629 } 1630 } 1631 1632 while (j < bytes) { 1633 ptrdiff_t bytesConsumed = 1; 1634 #ifdef UI_UNICODE 1635 int c = Utf8GetCodePoint(string, bytes - j, &bytesConsumed); 1636 UI_ASSERT(bytesConsumed > 0); 1637 string += bytesConsumed; 1638 #else 1639 char c = *string++; 1640 #endif 1641 uint32_t colorText = color; 1642 1643 if (i >= selectFrom && i < selectTo) { 1644 int w = ui.activeFont->glyphWidth; 1645 if (c == '\t') { 1646 int ii = i; 1647 while (++ii & 3) w += ui.activeFont->glyphWidth; 1648 } 1649 UIDrawBlock(painter, UI_RECT_4(x, x + w, y, y + height), selection->colorBackground); 1650 colorText = selection->colorText; 1651 } 1652 1653 if (c != '\t') { 1654 UIDrawGlyph(painter, x, y, c, colorText); 1655 } 1656 1657 if (selection && selection->carets[0] == i) { 1658 UIDrawInvert(painter, UI_RECT_4(x, x + 1, y, y + height)); 1659 } 1660 1661 x += ui.activeFont->glyphWidth, i++; 1662 1663 if (c == '\t') { 1664 while (i & 3) x += ui.activeFont->glyphWidth, i++; 1665 } 1666 1667 j += bytesConsumed; 1668 } 1669 1670 if (selection && selection->carets[0] == i) { 1671 UIDrawInvert(painter, UI_RECT_4(x, x + 1, y, y + height)); 1672 } 1673 1674 painter->clip = oldClip; 1675 } 1676 1677 void UIDrawBorder(UIPainter *painter, UIRectangle r, uint32_t borderColor, UIRectangle borderSize) { 1678 UIDrawBlock(painter, UI_RECT_4(r.l, r.r, r.t, r.t + borderSize.t), borderColor); 1679 UIDrawBlock(painter, UI_RECT_4(r.l, r.l + borderSize.l, r.t + borderSize.t, r.b - borderSize.b), borderColor); 1680 UIDrawBlock(painter, UI_RECT_4(r.r - borderSize.r, r.r, r.t + borderSize.t, r.b - borderSize.b), borderColor); 1681 UIDrawBlock(painter, UI_RECT_4(r.l, r.r, r.b - borderSize.b, r.b), borderColor); 1682 } 1683 1684 void UIDrawRectangle(UIPainter *painter, UIRectangle r, uint32_t mainColor, uint32_t borderColor, UIRectangle borderSize) { 1685 UIDrawBorder(painter, r, borderColor, borderSize); 1686 UIDrawBlock(painter, UI_RECT_4(r.l + borderSize.l, r.r - borderSize.r, r.t + borderSize.t, r.b - borderSize.b), mainColor); 1687 } 1688 1689 void UIDrawControlDefault(UIPainter *painter, UIRectangle bounds, uint32_t mode, const char *label, ptrdiff_t labelBytes, double position, float scale) { 1690 bool checked = mode & UI_DRAW_CONTROL_STATE_CHECKED; 1691 bool disabled = mode & UI_DRAW_CONTROL_STATE_DISABLED; 1692 bool focused = mode & UI_DRAW_CONTROL_STATE_FOCUSED; 1693 bool hovered = mode & UI_DRAW_CONTROL_STATE_HOVERED; 1694 bool indeterminate = mode & UI_DRAW_CONTROL_STATE_INDETERMINATE; 1695 bool pressed = mode & UI_DRAW_CONTROL_STATE_PRESSED; 1696 bool selected = mode & UI_DRAW_CONTROL_STATE_SELECTED; 1697 uint32_t which = mode & UI_DRAW_CONTROL_TYPE_MASK; 1698 1699 uint32_t buttonColor = disabled ? ui.theme.buttonDisabled 1700 : (pressed && hovered) ? ui.theme.buttonPressed 1701 : (pressed || hovered) ? ui.theme.buttonHovered 1702 : focused ? ui.theme.selected : ui.theme.buttonNormal; 1703 uint32_t buttonTextColor = disabled ? ui.theme.textDisabled 1704 : buttonColor == ui.theme.selected ? ui.theme.textSelected : ui.theme.text; 1705 1706 if (which == UI_DRAW_CONTROL_CHECKBOX) { 1707 uint32_t color = buttonColor, textColor = buttonTextColor; 1708 int midY = (bounds.t + bounds.b) / 2; 1709 UIRectangle boxBounds = UI_RECT_4(bounds.l, bounds.l + UI_SIZE_CHECKBOX_BOX, 1710 midY - UI_SIZE_CHECKBOX_BOX / 2, midY + UI_SIZE_CHECKBOX_BOX / 2); 1711 UIDrawRectangle(painter, boxBounds, color, ui.theme.border, UI_RECT_1(1)); 1712 UIDrawString(painter, UIRectangleAdd(boxBounds, UI_RECT_4(1, 0, 0, 0)), 1713 checked ? "*" : indeterminate ? "-" : " ", -1, 1714 textColor, UI_ALIGN_CENTER, NULL); 1715 UIDrawString(painter, UIRectangleAdd(bounds, UI_RECT_4(UI_SIZE_CHECKBOX_BOX + UI_SIZE_CHECKBOX_GAP, 0, 0, 0)), 1716 label, labelBytes, disabled ? ui.theme.textDisabled : ui.theme.text, UI_ALIGN_LEFT, NULL); 1717 } else if (which == UI_DRAW_CONTROL_MENU_ITEM || which == UI_DRAW_CONTROL_DROP_DOWN || which == UI_DRAW_CONTROL_PUSH_BUTTON) { 1718 uint32_t color = buttonColor, textColor = buttonTextColor; 1719 int borderSize = which == UI_DRAW_CONTROL_MENU_ITEM ? 0 : scale; 1720 UIDrawRectangle(painter, bounds, color, ui.theme.border, UI_RECT_1(borderSize)); 1721 1722 if (checked && !focused) { 1723 UIDrawBlock(painter, UIRectangleAdd(bounds, UI_RECT_1I((int) (UI_SIZE_BUTTON_CHECKED_AREA * scale))), ui.theme.buttonPressed); 1724 } 1725 1726 UIRectangle innerBounds = UIRectangleAdd(bounds, UI_RECT_2I((int) (UI_SIZE_MENU_ITEM_MARGIN * scale), 0)); 1727 1728 if (which == UI_DRAW_CONTROL_MENU_ITEM) { 1729 if (labelBytes == -1) { 1730 labelBytes = _UIStringLength(label); 1731 } 1732 1733 int tab = 0; 1734 for (; tab < labelBytes && label[tab] != '\t'; tab++); 1735 1736 UIDrawString(painter, innerBounds, label, tab, textColor, UI_ALIGN_LEFT, NULL); 1737 1738 if (labelBytes > tab) { 1739 UIDrawString(painter, innerBounds, label + tab + 1, labelBytes - tab - 1, textColor, UI_ALIGN_RIGHT, NULL); 1740 } 1741 } else if (which == UI_DRAW_CONTROL_DROP_DOWN) { 1742 UIDrawString(painter, innerBounds, label, labelBytes, textColor, UI_ALIGN_LEFT, NULL); 1743 UIDrawString(painter, innerBounds, "\x19", 1, textColor, UI_ALIGN_RIGHT, NULL); 1744 } else { 1745 UIDrawString(painter, bounds, label, labelBytes, textColor, UI_ALIGN_CENTER, NULL); 1746 } 1747 } else if (which == UI_DRAW_CONTROL_LABEL) { 1748 UIDrawString(painter, bounds, label, labelBytes, ui.theme.text, UI_ALIGN_LEFT, NULL); 1749 } else if (which == UI_DRAW_CONTROL_SPLITTER) { 1750 UIRectangle borders = (mode & UI_DRAW_CONTROL_STATE_VERTICAL) ? UI_RECT_2(0, 1) : UI_RECT_2(1, 0); 1751 UIDrawRectangle(painter, bounds, ui.theme.buttonNormal, ui.theme.border, borders); 1752 } else if (which == UI_DRAW_CONTROL_SCROLL_TRACK) { 1753 if (disabled) UIDrawBlock(painter, bounds, ui.theme.panel1); 1754 } else if (which == UI_DRAW_CONTROL_SCROLL_DOWN || which == UI_DRAW_CONTROL_SCROLL_UP) { 1755 bool isDown = which == UI_DRAW_CONTROL_SCROLL_DOWN; 1756 uint32_t color = pressed ? ui.theme.buttonPressed : hovered ? ui.theme.buttonHovered : ui.theme.panel2; 1757 UIDrawRectangle(painter, bounds, color, ui.theme.border, UI_RECT_1(0)); 1758 1759 if (mode & UI_DRAW_CONTROL_STATE_VERTICAL) { 1760 UIDrawGlyph(painter, (bounds.l + bounds.r - ui.activeFont->glyphWidth) / 2 + 1, 1761 isDown ? (bounds.b - ui.activeFont->glyphHeight - 2 * scale) 1762 : (bounds.t + 2 * scale), 1763 isDown ? 25 : 24, ui.theme.text); 1764 } else { 1765 UIDrawGlyph(painter, isDown ? (bounds.r - ui.activeFont->glyphWidth - 2 * scale) 1766 : (bounds.l + 2 * scale), 1767 (bounds.t + bounds.b - ui.activeFont->glyphHeight) / 2, 1768 isDown ? 26 : 27, ui.theme.text); 1769 } 1770 } else if (which == UI_DRAW_CONTROL_SCROLL_THUMB) { 1771 uint32_t color = pressed ? ui.theme.buttonPressed : hovered ? ui.theme.buttonHovered : ui.theme.buttonNormal; 1772 UIDrawRectangle(painter, bounds, color, ui.theme.border, UI_RECT_1(2)); 1773 } else if (which == UI_DRAW_CONTROL_GAUGE) { 1774 UIDrawRectangle(painter, bounds, ui.theme.buttonNormal, ui.theme.border, UI_RECT_1(1)); 1775 UIRectangle filled = UIRectangleAdd(bounds, UI_RECT_1I(1)); 1776 filled.r = filled.l + UI_RECT_WIDTH(filled) * position; 1777 UIDrawBlock(painter, filled, ui.theme.selected); 1778 } else if (which == UI_DRAW_CONTROL_SLIDER) { 1779 int centerY = (bounds.t + bounds.b) / 2; 1780 int trackSize = UI_SIZE_SLIDER_TRACK * scale; 1781 int thumbSize = UI_SIZE_SLIDER_THUMB * scale; 1782 int thumbPosition = (UI_RECT_WIDTH(bounds) - thumbSize) * position; 1783 UIRectangle track = UI_RECT_4(bounds.l, bounds.r, centerY - (trackSize + 1) / 2, centerY + trackSize / 2); 1784 UIDrawRectangle(painter, track, disabled ? ui.theme.buttonDisabled : ui.theme.buttonNormal, ui.theme.border, UI_RECT_1(1)); 1785 uint32_t color = disabled ? ui.theme.buttonDisabled : pressed ? ui.theme.buttonPressed : hovered ? ui.theme.buttonHovered : ui.theme.buttonNormal; 1786 UIRectangle thumb = UI_RECT_4(bounds.l + thumbPosition, bounds.l + thumbPosition + thumbSize, centerY - (thumbSize + 1) / 2, centerY + thumbSize / 2); 1787 UIDrawRectangle(painter, thumb, color, ui.theme.border, UI_RECT_1(1)); 1788 } else if (which == UI_DRAW_CONTROL_TEXTBOX) { 1789 UIDrawRectangle(painter, bounds, 1790 disabled ? ui.theme.buttonDisabled : focused ? ui.theme.textboxFocused : ui.theme.textboxNormal, 1791 ui.theme.border, UI_RECT_1(1)); 1792 } else if (which == UI_DRAW_CONTROL_MODAL_POPUP) { 1793 UIRectangle bounds2 = UIRectangleAdd(bounds, UI_RECT_1I(-1)); 1794 UIDrawBorder(painter, bounds2, ui.theme.border, UI_RECT_1(1)); 1795 UIDrawBorder(painter, UIRectangleAdd(bounds2, UI_RECT_1(1)), ui.theme.border, UI_RECT_1(1)); 1796 } else if (which == UI_DRAW_CONTROL_MENU) { 1797 UIDrawBlock(painter, bounds, ui.theme.border); 1798 } else if (which == UI_DRAW_CONTROL_TABLE_ROW) { 1799 if (selected) UIDrawBlock(painter, bounds, ui.theme.selected); 1800 else if (hovered) UIDrawBlock(painter, bounds, ui.theme.buttonHovered); 1801 } else if (which == UI_DRAW_CONTROL_TABLE_CELL) { 1802 uint32_t textColor = selected ? ui.theme.textSelected : ui.theme.text; 1803 UIDrawString(painter, bounds, label, labelBytes, textColor, UI_ALIGN_LEFT, NULL); 1804 } else if (which == UI_DRAW_CONTROL_TABLE_BACKGROUND) { 1805 UIDrawBlock(painter, bounds, ui.theme.panel2); 1806 UIDrawRectangle(painter, UI_RECT_4(bounds.l, bounds.r, bounds.t, bounds.t + (int) (UI_SIZE_TABLE_HEADER * scale)), 1807 ui.theme.panel1, ui.theme.border, UI_RECT_4(0, 0, 0, 1)); 1808 } else if (which == UI_DRAW_CONTROL_TABLE_HEADER) { 1809 UIDrawString(painter, bounds, label, labelBytes, ui.theme.text, UI_ALIGN_LEFT, NULL); 1810 if (selected) UIDrawInvert(painter, bounds); 1811 } else if (which == UI_DRAW_CONTROL_MDI_CHILD) { 1812 UI_MDI_CHILD_CALCULATE_LAYOUT(bounds, scale); 1813 UIRectangle borders = UI_RECT_4(borderSize, borderSize, titleSize, borderSize); 1814 UIDrawBorder(painter, bounds, ui.theme.buttonNormal, borders); 1815 UIDrawBorder(painter, bounds, ui.theme.border, UI_RECT_1((int) scale)); 1816 UIDrawBorder(painter, UIRectangleAdd(content, UI_RECT_1I(-1)), ui.theme.border, UI_RECT_1((int) scale)); 1817 UIDrawString(painter, title, label, labelBytes, ui.theme.text, UI_ALIGN_LEFT, NULL); 1818 } else if (which == UI_DRAW_CONTROL_TAB) { 1819 uint32_t color = selected ? ui.theme.buttonPressed : ui.theme.buttonNormal; 1820 UIRectangle t = bounds; 1821 if (selected) t.b++, t.t--; 1822 else t.t++; 1823 UIDrawRectangle(painter, t, color, ui.theme.border, UI_RECT_1(1)); 1824 UIDrawString(painter, bounds, label, labelBytes, ui.theme.text, UI_ALIGN_CENTER, NULL); 1825 } else if (which == UI_DRAW_CONTROL_TAB_BAND) { 1826 UIDrawRectangle(painter, bounds, ui.theme.panel1, ui.theme.border, UI_RECT_4(0, 0, 0, 1)); 1827 } 1828 } 1829 1830 ///////////////////////////////////////// 1831 // Element hierarchy. 1832 ///////////////////////////////////////// 1833 1834 void _UIElementDestroyDescendents(UIElement *element, bool topLevel) { 1835 for (uint32_t i = 0; i < element->childCount; i++) { 1836 UIElement *child = element->children[i]; 1837 1838 if (!topLevel || (~child->flags & UI_ELEMENT_NON_CLIENT)) { 1839 UIElementDestroy(child); 1840 } 1841 } 1842 1843 #ifdef UI_DEBUG 1844 _UIInspectorRefresh(); 1845 #endif 1846 } 1847 1848 void UIElementDestroyDescendents(UIElement *element) { 1849 _UIElementDestroyDescendents(element, true); 1850 } 1851 1852 void UIElementDestroy(UIElement *element) { 1853 if (element->flags & UI_ELEMENT_DESTROY) { 1854 return; 1855 } 1856 1857 UIElementMessage(element, UI_MSG_DESTROY, 0, 0); 1858 element->flags |= UI_ELEMENT_DESTROY | UI_ELEMENT_HIDE; 1859 1860 UIElement *ancestor = element->parent; 1861 1862 while (ancestor) { 1863 if (ancestor->flags & UI_ELEMENT_DESTROY_DESCENDENT) break; 1864 ancestor->flags |= UI_ELEMENT_DESTROY_DESCENDENT; 1865 ancestor = ancestor->parent; 1866 } 1867 1868 _UIElementDestroyDescendents(element, false); 1869 1870 if (element->parent) { 1871 UIElementRelayout(element->parent); 1872 UIElementRepaint(element->parent, &element->bounds); 1873 UIElementMeasurementsChanged(element->parent, 3); 1874 } 1875 } 1876 1877 int UIElementMessage(UIElement *element, UIMessage message, int di, void *dp) { 1878 if (message != UI_MSG_DEALLOCATE && (element->flags & UI_ELEMENT_DESTROY)) { 1879 return 0; 1880 } 1881 1882 if (message >= UI_MSG_INPUT_EVENTS_START && message <= UI_MSG_INPUT_EVENTS_END && (element->flags & UI_ELEMENT_DISABLED)) { 1883 return 0; 1884 } 1885 1886 if (element->messageUser) { 1887 int result = element->messageUser(element, message, di, dp); 1888 1889 if (result) { 1890 return result; 1891 } 1892 } 1893 1894 if (element->messageClass) { 1895 return element->messageClass(element, message, di, dp); 1896 } else { 1897 return 0; 1898 } 1899 } 1900 1901 UIElement *UIElementChangeParent(UIElement *element, UIElement *newParent, UIElement *insertBefore) { 1902 bool found = false; 1903 UIElement *oldBefore = NULL; 1904 1905 for (uint32_t i = 0; i < element->parent->childCount; i++) { 1906 if (element->parent->children[i] == element) { 1907 UI_MEMMOVE(&element->parent->children[i], &element->parent->children[i + 1], sizeof(UIElement *) * (element->parent->childCount - i - 1)); 1908 element->parent->childCount--; 1909 oldBefore = i == element->parent->childCount ? NULL : element->parent->children[i]; 1910 found = true; 1911 break; 1912 } 1913 } 1914 1915 UI_ASSERT(found && (~element->flags & UI_ELEMENT_DESTROY)); 1916 1917 for (uint32_t i = 0; i <= newParent->childCount; i++) { 1918 if (i == newParent->childCount || newParent->children[i] == insertBefore) { 1919 newParent->children = (UIElement **) UI_REALLOC(newParent->children, sizeof(UIElement *) * (newParent->childCount + 1)); 1920 UI_MEMMOVE(&newParent->children[i + 1], &newParent->children[i], sizeof(UIElement *) * (newParent->childCount - i)); 1921 newParent->childCount++; 1922 newParent->children[i] = element; 1923 found = true; 1924 break; 1925 } 1926 } 1927 1928 UIElement *oldParent = element->parent; 1929 element->parent = newParent; 1930 element->window = newParent->window; 1931 1932 UIElementMeasurementsChanged(oldParent, 3); 1933 UIElementMeasurementsChanged(newParent, 3); 1934 1935 return oldBefore; 1936 } 1937 1938 UIElement *UIElementCreate(size_t bytes, UIElement *parent, uint32_t flags, int (*message)(UIElement *, UIMessage, int, void *), const char *cClassName) { 1939 UI_ASSERT(bytes >= sizeof(UIElement)); 1940 UIElement *element = (UIElement *) UI_CALLOC(bytes); 1941 element->flags = flags; 1942 element->messageClass = message; 1943 1944 if (!parent && (~flags & UI_ELEMENT_WINDOW)) { 1945 UI_ASSERT(ui.parentStackCount); 1946 parent = ui.parentStack[ui.parentStackCount - 1]; 1947 } 1948 1949 if (parent) { 1950 UI_ASSERT(~parent->flags & UI_ELEMENT_DESTROY); 1951 element->window = parent->window; 1952 element->parent = parent; 1953 parent->children = (UIElement **) UI_REALLOC(parent->children, sizeof(UIElement *) * (parent->childCount + 1)); 1954 parent->children[parent->childCount] = element; 1955 parent->childCount++; 1956 UIElementRelayout(parent); 1957 UIElementMeasurementsChanged(parent, 3); 1958 } 1959 1960 element->cClassName = cClassName; 1961 static uint32_t id = 0; 1962 element->id = ++id; 1963 1964 #ifdef UI_DEBUG 1965 _UIInspectorRefresh(); 1966 #endif 1967 1968 if (flags & UI_ELEMENT_PARENT_PUSH) { 1969 UIParentPush(element); 1970 } 1971 1972 return element; 1973 } 1974 1975 UIElement *UIParentPush(UIElement *element) { 1976 UI_ASSERT(ui.parentStackCount != sizeof(ui.parentStack) / sizeof(ui.parentStack[0])); 1977 ui.parentStack[ui.parentStackCount++] = element; 1978 return element; 1979 } 1980 1981 UIElement *UIParentPop() { 1982 UI_ASSERT(ui.parentStackCount); 1983 ui.parentStackCount--; 1984 return ui.parentStack[ui.parentStackCount]; 1985 } 1986 1987 ///////////////////////////////////////// 1988 // Panels. 1989 ///////////////////////////////////////// 1990 1991 int _UIPanelCalculatePerFill(UIPanel *panel, int *_count, int hSpace, int vSpace, float scale) { 1992 bool horizontal = panel->e.flags & UI_PANEL_HORIZONTAL; 1993 int available = horizontal ? hSpace : vSpace; 1994 int count = 0, fill = 0, perFill = 0; 1995 1996 for (uint32_t i = 0; i < panel->e.childCount; i++) { 1997 UIElement *child = panel->e.children[i]; 1998 1999 if (child->flags & (UI_ELEMENT_HIDE | UI_ELEMENT_NON_CLIENT)) { 2000 continue; 2001 } 2002 2003 count++; 2004 2005 if (horizontal) { 2006 if (child->flags & UI_ELEMENT_H_FILL) { 2007 fill++; 2008 } else if (available > 0) { 2009 available -= UIElementMessage(child, UI_MSG_GET_WIDTH, vSpace, 0); 2010 } 2011 } else { 2012 if (child->flags & UI_ELEMENT_V_FILL) { 2013 fill++; 2014 } else if (available > 0) { 2015 available -= UIElementMessage(child, UI_MSG_GET_HEIGHT, hSpace, 0); 2016 } 2017 } 2018 } 2019 2020 if (count) { 2021 available -= (count - 1) * (int) (panel->gap * scale); 2022 } 2023 2024 if (available > 0 && fill) { 2025 perFill = available / fill; 2026 } 2027 2028 if (_count) { 2029 *_count = count; 2030 } 2031 2032 return perFill; 2033 } 2034 2035 int _UIPanelMeasure(UIPanel *panel, int di) { 2036 bool horizontal = panel->e.flags & UI_PANEL_HORIZONTAL; 2037 int perFill = _UIPanelCalculatePerFill(panel, NULL, horizontal ? di : 0, horizontal ? 0 : di, panel->e.window->scale); 2038 int size = 0; 2039 2040 for (uint32_t i = 0; i < panel->e.childCount; i++) { 2041 UIElement *child = panel->e.children[i]; 2042 if (child->flags & (UI_ELEMENT_HIDE | UI_ELEMENT_NON_CLIENT)) continue; 2043 int childSize = UIElementMessage(child, horizontal ? UI_MSG_GET_HEIGHT : UI_MSG_GET_WIDTH, 2044 (child->flags & (horizontal ? UI_ELEMENT_H_FILL : UI_ELEMENT_V_FILL)) ? perFill : 0, 0); 2045 if (childSize > size) size = childSize; 2046 } 2047 2048 int border = horizontal ? (panel->border.t + panel->border.b) : (panel->border.l + panel->border.r); 2049 return size + border * panel->e.window->scale; 2050 } 2051 2052 int _UIPanelLayout(UIPanel *panel, UIRectangle bounds, bool measure) { 2053 bool horizontal = panel->e.flags & UI_PANEL_HORIZONTAL; 2054 float scale = panel->e.window->scale; 2055 int position = (horizontal ? panel->border.l : panel->border.t) * scale; 2056 if (panel->scrollBar && !measure) position -= panel->scrollBar->position; 2057 int hSpace = UI_RECT_WIDTH(bounds) - UI_RECT_TOTAL_H(panel->border) * scale; 2058 int vSpace = UI_RECT_HEIGHT(bounds) - UI_RECT_TOTAL_V(panel->border) * scale; 2059 int count = 0; 2060 int perFill = _UIPanelCalculatePerFill(panel, &count, hSpace, vSpace, scale); 2061 int scaledBorder2 = (horizontal ? panel->border.t : panel->border.l) * panel->e.window->scale; 2062 bool expand = panel->e.flags & UI_PANEL_EXPAND; 2063 2064 for (uint32_t i = 0; i < panel->e.childCount; i++) { 2065 UIElement *child = panel->e.children[i]; 2066 2067 if (child->flags & (UI_ELEMENT_HIDE | UI_ELEMENT_NON_CLIENT)) { 2068 continue; 2069 } 2070 2071 if (horizontal) { 2072 int height = ((child->flags & UI_ELEMENT_V_FILL) || expand) ? vSpace 2073 : UIElementMessage(child, UI_MSG_GET_HEIGHT, (child->flags & UI_ELEMENT_H_FILL) ? perFill : 0, 0); 2074 int width = (child->flags & UI_ELEMENT_H_FILL) ? perFill : UIElementMessage(child, UI_MSG_GET_WIDTH, height, 0); 2075 UIRectangle relative = UI_RECT_4(position, position + width, 2076 scaledBorder2 + (vSpace - height) / 2, 2077 scaledBorder2 + (vSpace + height) / 2); 2078 if (!measure) UIElementMove(child, UIRectangleTranslate(relative, bounds), false); 2079 position += width + panel->gap * scale; 2080 } else { 2081 int width = ((child->flags & UI_ELEMENT_H_FILL) || expand) ? hSpace 2082 : UIElementMessage(child, UI_MSG_GET_WIDTH, (child->flags & UI_ELEMENT_V_FILL) ? perFill : 0, 0); 2083 int height = (child->flags & UI_ELEMENT_V_FILL) ? perFill : UIElementMessage(child, UI_MSG_GET_HEIGHT, width, 0); 2084 UIRectangle relative = UI_RECT_4(scaledBorder2 + (hSpace - width) / 2, 2085 scaledBorder2 + (hSpace + width) / 2, position, position + height); 2086 if (!measure) UIElementMove(child, UIRectangleTranslate(relative, bounds), false); 2087 position += height + panel->gap * scale; 2088 } 2089 } 2090 2091 return position - (count ? panel->gap : 0) * scale + (horizontal ? panel->border.r : panel->border.b) * scale; 2092 } 2093 2094 int _UIPanelMessage(UIElement *element, UIMessage message, int di, void *dp) { 2095 UIPanel *panel = (UIPanel *) element; 2096 bool horizontal = element->flags & UI_PANEL_HORIZONTAL; 2097 2098 if (message == UI_MSG_LAYOUT) { 2099 int scrollBarWidth = panel->scrollBar ? (UI_SIZE_SCROLL_BAR * element->window->scale) : 0; 2100 UIRectangle bounds = element->bounds; 2101 bounds.r -= scrollBarWidth; 2102 2103 if (panel->scrollBar) { 2104 UIRectangle scrollBarBounds = element->bounds; 2105 scrollBarBounds.l = scrollBarBounds.r - scrollBarWidth; 2106 panel->scrollBar->maximum = _UIPanelLayout(panel, bounds, true); 2107 panel->scrollBar->page = UI_RECT_HEIGHT(element->bounds); 2108 UIElementMove(&panel->scrollBar->e, scrollBarBounds, true); 2109 } 2110 2111 _UIPanelLayout(panel, bounds, false); 2112 } else if (message == UI_MSG_GET_WIDTH) { 2113 if (horizontal) { 2114 return _UIPanelLayout(panel, UI_RECT_4(0, 0, 0, di), true); 2115 } else { 2116 return _UIPanelMeasure(panel, di); 2117 } 2118 } else if (message == UI_MSG_GET_HEIGHT) { 2119 if (horizontal) { 2120 return _UIPanelMeasure(panel, di); 2121 } else { 2122 int width = di && panel->scrollBar ? (di - UI_SIZE_SCROLL_BAR * element->window->scale) : di; 2123 return _UIPanelLayout(panel, UI_RECT_4(0, width, 0, 0), true); 2124 } 2125 } else if (message == UI_MSG_PAINT) { 2126 if (element->flags & UI_PANEL_COLOR_1) { 2127 UIDrawBlock((UIPainter *) dp, element->bounds, ui.theme.panel1); 2128 } else if (element->flags & UI_PANEL_COLOR_2) { 2129 UIDrawBlock((UIPainter *) dp, element->bounds, ui.theme.panel2); 2130 } 2131 } else if (message == UI_MSG_MOUSE_WHEEL && panel->scrollBar) { 2132 return UIElementMessage(&panel->scrollBar->e, message, di, dp); 2133 } else if (message == UI_MSG_SCROLLED) { 2134 UIElementRefresh(element); 2135 } else if (message == UI_MSG_GET_CHILD_STABILITY) { 2136 UIElement *child = (UIElement *) dp; 2137 return ((element->flags & UI_PANEL_EXPAND) ? (horizontal ? 2 : 1) : 0) 2138 | ((child->flags & UI_ELEMENT_H_FILL) ? 1 : 0) | ((child->flags & UI_ELEMENT_V_FILL) ? 2 : 0); 2139 } 2140 2141 return 0; 2142 } 2143 2144 UIPanel *UIPanelCreate(UIElement *parent, uint32_t flags) { 2145 UIPanel *panel = (UIPanel *) UIElementCreate(sizeof(UIPanel), parent, flags, _UIPanelMessage, "Panel"); 2146 2147 if (flags & UI_PANEL_LARGE_SPACING) { 2148 panel->border = UI_RECT_1(UI_SIZE_PANE_LARGE_BORDER); 2149 panel->gap = UI_SIZE_PANE_LARGE_GAP; 2150 } else if (flags & UI_PANEL_MEDIUM_SPACING) { 2151 panel->border = UI_RECT_1(UI_SIZE_PANE_MEDIUM_BORDER); 2152 panel->gap = UI_SIZE_PANE_MEDIUM_GAP; 2153 } else if (flags & UI_PANEL_SMALL_SPACING) { 2154 panel->border = UI_RECT_1(UI_SIZE_PANE_SMALL_BORDER); 2155 panel->gap = UI_SIZE_PANE_SMALL_GAP; 2156 } 2157 2158 if (flags & UI_PANEL_SCROLL) { 2159 panel->scrollBar = UIScrollBarCreate(&panel->e, UI_ELEMENT_NON_CLIENT); 2160 } 2161 2162 return panel; 2163 } 2164 2165 void _UIWrapPanelLayoutRow(UIWrapPanel *panel, uint32_t rowStart, uint32_t rowEnd, int rowY, int rowHeight) { 2166 int rowPosition = 0; 2167 2168 for (uint32_t i = rowStart; i < rowEnd; i++) { 2169 UIElement *child = panel->e.children[i]; 2170 if (child->flags & UI_ELEMENT_HIDE) continue; 2171 int height = UIElementMessage(child, UI_MSG_GET_HEIGHT, 0, 0); 2172 int width = UIElementMessage(child, UI_MSG_GET_WIDTH, 0, 0); 2173 UIRectangle relative = UI_RECT_4(rowPosition, rowPosition + width, rowY + rowHeight / 2 - height / 2, rowY + rowHeight / 2 + height / 2); 2174 UIElementMove(child, UIRectangleTranslate(relative, panel->e.bounds), false); 2175 rowPosition += width; 2176 } 2177 } 2178 2179 int _UIWrapPanelMessage(UIElement *element, UIMessage message, int di, void *dp) { 2180 UIWrapPanel *panel = (UIWrapPanel *) element; 2181 2182 if (message == UI_MSG_LAYOUT || message == UI_MSG_GET_HEIGHT) { 2183 int totalHeight = 0; 2184 int rowPosition = 0; 2185 int rowHeight = 0; 2186 int rowLimit = message == UI_MSG_LAYOUT ? UI_RECT_WIDTH(element->bounds) : di; 2187 2188 uint32_t rowStart = 0; 2189 2190 for (uint32_t i = 0; i < panel->e.childCount; i++) { 2191 UIElement *child = panel->e.children[i]; 2192 if (child->flags & UI_ELEMENT_HIDE) continue; 2193 2194 int height = UIElementMessage(child, UI_MSG_GET_HEIGHT, 0, 0); 2195 int width = UIElementMessage(child, UI_MSG_GET_WIDTH, 0, 0); 2196 2197 if (rowLimit && rowPosition + width > rowLimit) { 2198 _UIWrapPanelLayoutRow(panel, rowStart, i, totalHeight, rowHeight); 2199 totalHeight += rowHeight; 2200 rowPosition = rowHeight = 0; 2201 rowStart = i; 2202 } 2203 2204 if (height > rowHeight) { 2205 rowHeight = height; 2206 } 2207 2208 rowPosition += width; 2209 } 2210 2211 if (message == UI_MSG_GET_HEIGHT) { 2212 return totalHeight + rowHeight; 2213 } else { 2214 _UIWrapPanelLayoutRow(panel, rowStart, panel->e.childCount, totalHeight, rowHeight); 2215 } 2216 } 2217 2218 return 0; 2219 } 2220 2221 UIWrapPanel *UIWrapPanelCreate(UIElement *parent, uint32_t flags) { 2222 return (UIWrapPanel *) UIElementCreate(sizeof(UIWrapPanel), parent, flags, _UIWrapPanelMessage, "Wrap Panel"); 2223 } 2224 2225 int _UISwitcherMessage(UIElement *element, UIMessage message, int di, void *dp) { 2226 UISwitcher *switcher = (UISwitcher *) element; 2227 2228 if (!switcher->active) { 2229 } else if (message == UI_MSG_GET_WIDTH || message == UI_MSG_GET_HEIGHT) { 2230 return UIElementMessage(switcher->active, message, di, dp); 2231 } else if (message == UI_MSG_LAYOUT) { 2232 UIElementMove(switcher->active, element->bounds, false); 2233 } 2234 2235 return 0; 2236 } 2237 2238 void UISwitcherSwitchTo(UISwitcher *switcher, UIElement *child) { 2239 for (uint32_t i = 0; i < switcher->e.childCount; i++) { 2240 switcher->e.children[i]->flags |= UI_ELEMENT_HIDE; 2241 } 2242 2243 UI_ASSERT(child->parent == &switcher->e); 2244 child->flags &= ~UI_ELEMENT_HIDE; 2245 switcher->active = child; 2246 UIElementMeasurementsChanged(&switcher->e, 3); 2247 UIElementRefresh(&switcher->e); 2248 } 2249 2250 UISwitcher *UISwitcherCreate(UIElement *parent, uint32_t flags) { 2251 return (UISwitcher *) UIElementCreate(sizeof(UISwitcher), parent, flags, _UISwitcherMessage, "Switcher"); 2252 } 2253 2254 ///////////////////////////////////////// 2255 // Checkboxes and buttons. 2256 ///////////////////////////////////////// 2257 2258 int _UIButtonMessage(UIElement *element, UIMessage message, int di, void *dp) { 2259 UIButton *button = (UIButton *) element; 2260 bool isMenuItem = element->flags & UI_BUTTON_MENU_ITEM; 2261 bool isDropDown = element->flags & UI_BUTTON_DROP_DOWN; 2262 2263 if (message == UI_MSG_GET_HEIGHT) { 2264 if (isMenuItem) { 2265 return UI_SIZE_MENU_ITEM_HEIGHT * element->window->scale; 2266 } else { 2267 return UI_SIZE_BUTTON_HEIGHT * element->window->scale; 2268 } 2269 } else if (message == UI_MSG_GET_WIDTH) { 2270 int labelSize = UIMeasureStringWidth(button->label, button->labelBytes); 2271 int paddedSize = labelSize + UI_SIZE_BUTTON_PADDING * element->window->scale; 2272 if (isDropDown) paddedSize += ui.activeFont->glyphWidth * 2; 2273 int minimumSize = ((element->flags & UI_BUTTON_SMALL) ? 0 2274 : isMenuItem ? UI_SIZE_MENU_ITEM_MINIMUM_WIDTH 2275 : UI_SIZE_BUTTON_MINIMUM_WIDTH) 2276 * element->window->scale; 2277 return paddedSize > minimumSize ? paddedSize : minimumSize; 2278 } else if (message == UI_MSG_PAINT) { 2279 UIDrawControl((UIPainter *) dp, element->bounds, 2280 (isMenuItem ? UI_DRAW_CONTROL_MENU_ITEM : isDropDown ? UI_DRAW_CONTROL_DROP_DOWN : UI_DRAW_CONTROL_PUSH_BUTTON) 2281 | ((element->flags & UI_BUTTON_CHECKED) ? UI_DRAW_CONTROL_STATE_CHECKED : 0) | UI_DRAW_CONTROL_STATE_FROM_ELEMENT(element), 2282 button->label, button->labelBytes, 0, element->window->scale); 2283 } else if (message == UI_MSG_UPDATE) { 2284 UIElementRepaint(element, NULL); 2285 } else if (message == UI_MSG_DEALLOCATE) { 2286 UI_FREE(button->label); 2287 } else if (message == UI_MSG_LEFT_DOWN) { 2288 if (element->flags & UI_BUTTON_CAN_FOCUS) { 2289 UIElementFocus(element); 2290 } 2291 } else if (message == UI_MSG_KEY_TYPED) { 2292 UIKeyTyped *m = (UIKeyTyped *) dp; 2293 2294 if ((m->textBytes == 1 && m->text[0] == ' ') || m->code == UI_KEYCODE_ENTER) { 2295 UIElementMessage(element, UI_MSG_CLICKED, 0, 0); 2296 UIElementRepaint(element, NULL); 2297 return 1; 2298 } 2299 } else if (message == UI_MSG_CLICKED) { 2300 if (button->invoke) { 2301 button->invoke(element->cp); 2302 } 2303 } 2304 2305 return 0; 2306 } 2307 2308 void UIButtonSetLabel(UIButton *button, const char *string, ptrdiff_t stringBytes) { 2309 UI_FREE(button->label); 2310 button->label = UIStringCopy(string, (button->labelBytes = stringBytes)); 2311 UIElementMeasurementsChanged(&button->e, 1); 2312 UIElementRepaint(&button->e, NULL); 2313 } 2314 2315 UIButton *UIButtonCreate(UIElement *parent, uint32_t flags, const char *label, ptrdiff_t labelBytes) { 2316 UIButton *button = (UIButton *) UIElementCreate(sizeof(UIButton), parent, flags | UI_ELEMENT_TAB_STOP, _UIButtonMessage, "Button"); 2317 button->label = UIStringCopy(label, (button->labelBytes = labelBytes)); 2318 return button; 2319 } 2320 2321 int _UICheckboxMessage(UIElement *element, UIMessage message, int di, void *dp) { 2322 UICheckbox *box = (UICheckbox *) element; 2323 2324 if (message == UI_MSG_GET_HEIGHT) { 2325 return UI_SIZE_BUTTON_HEIGHT * element->window->scale; 2326 } else if (message == UI_MSG_GET_WIDTH) { 2327 int labelSize = UIMeasureStringWidth(box->label, box->labelBytes); 2328 return (labelSize + UI_SIZE_CHECKBOX_BOX + UI_SIZE_CHECKBOX_GAP) * element->window->scale; 2329 } else if (message == UI_MSG_PAINT) { 2330 UIDrawControl((UIPainter *) dp, element->bounds, 2331 UI_DRAW_CONTROL_CHECKBOX | (box->check == UI_CHECK_INDETERMINATE ? UI_DRAW_CONTROL_STATE_INDETERMINATE 2332 : box->check == UI_CHECK_CHECKED ? UI_DRAW_CONTROL_STATE_CHECKED : 0) 2333 | UI_DRAW_CONTROL_STATE_FROM_ELEMENT(element), 2334 box->label, box->labelBytes, 0, element->window->scale); 2335 } else if (message == UI_MSG_UPDATE) { 2336 UIElementRepaint(element, NULL); 2337 } else if (message == UI_MSG_DEALLOCATE) { 2338 UI_FREE(box->label); 2339 } else if (message == UI_MSG_KEY_TYPED) { 2340 UIKeyTyped *m = (UIKeyTyped *) dp; 2341 2342 if (m->textBytes == 1 && m->text[0] == ' ') { 2343 UIElementMessage(element, UI_MSG_CLICKED, 0, 0); 2344 UIElementRepaint(element, NULL); 2345 } 2346 } else if (message == UI_MSG_CLICKED) { 2347 box->check = (box->check + 1) % ((element->flags & UI_CHECKBOX_ALLOW_INDETERMINATE) ? 3 : 2); 2348 UIElementRepaint(element, NULL); 2349 if (box->invoke) box->invoke(element->cp); 2350 } 2351 2352 return 0; 2353 } 2354 2355 UICheckbox *UICheckboxCreate(UIElement *parent, uint32_t flags, const char *label, ptrdiff_t labelBytes) { 2356 UICheckbox *box = (UICheckbox *) UIElementCreate(sizeof(UICheckbox), parent, flags | UI_ELEMENT_TAB_STOP, _UICheckboxMessage, "Checkbox"); 2357 box->label = UIStringCopy(label, (box->labelBytes = labelBytes)); 2358 return box; 2359 } 2360 2361 ///////////////////////////////////////// 2362 // Labels. 2363 ///////////////////////////////////////// 2364 2365 int _UILabelMessage(UIElement *element, UIMessage message, int di, void *dp) { 2366 UILabel *label = (UILabel *) element; 2367 2368 if (message == UI_MSG_GET_HEIGHT) { 2369 return UIMeasureStringHeight(); 2370 } else if (message == UI_MSG_GET_WIDTH) { 2371 return UIMeasureStringWidth(label->label, label->labelBytes); 2372 } else if (message == UI_MSG_PAINT) { 2373 UIDrawControl((UIPainter *) dp, element->bounds, UI_DRAW_CONTROL_LABEL | UI_DRAW_CONTROL_STATE_FROM_ELEMENT(element), 2374 label->label, label->labelBytes, 0, element->window->scale); 2375 } else if (message == UI_MSG_DEALLOCATE) { 2376 UI_FREE(label->label); 2377 } 2378 2379 return 0; 2380 } 2381 2382 void UILabelSetContent(UILabel *label, const char *string, ptrdiff_t stringBytes) { 2383 UI_FREE(label->label); 2384 label->label = UIStringCopy(string, (label->labelBytes = stringBytes)); 2385 UIElementMeasurementsChanged(&label->e, 1); 2386 UIElementRepaint(&label->e, NULL); 2387 } 2388 2389 UILabel *UILabelCreate(UIElement *parent, uint32_t flags, const char *string, ptrdiff_t stringBytes) { 2390 UILabel *label = (UILabel *) UIElementCreate(sizeof(UILabel), parent, flags, _UILabelMessage, "Label"); 2391 label->label = UIStringCopy(string, (label->labelBytes = stringBytes)); 2392 return label; 2393 } 2394 2395 ///////////////////////////////////////// 2396 // Split panes. 2397 ///////////////////////////////////////// 2398 2399 int _UISplitPaneMessage(UIElement *element, UIMessage message, int di, void *dp); 2400 2401 int _UISplitterMessage(UIElement *element, UIMessage message, int di, void *dp) { 2402 UISplitPane *splitPane = (UISplitPane *) element->parent; 2403 bool vertical = splitPane->e.flags & UI_SPLIT_PANE_VERTICAL; 2404 2405 if (message == UI_MSG_PAINT) { 2406 UIDrawControl((UIPainter *) dp, element->bounds, UI_DRAW_CONTROL_SPLITTER | (vertical ? UI_DRAW_CONTROL_STATE_VERTICAL : 0) 2407 | UI_DRAW_CONTROL_STATE_FROM_ELEMENT(element), NULL, 0, 0, element->window->scale); 2408 } else if (message == UI_MSG_GET_CURSOR) { 2409 return vertical ? UI_CURSOR_SPLIT_V : UI_CURSOR_SPLIT_H; 2410 } else if (message == UI_MSG_MOUSE_DRAG) { 2411 int cursor = vertical ? element->window->cursorY : element->window->cursorX; 2412 int splitterSize = UI_SIZE_SPLITTER * element->window->scale; 2413 int space = (vertical ? UI_RECT_HEIGHT(splitPane->e.bounds) : UI_RECT_WIDTH(splitPane->e.bounds)) - splitterSize; 2414 float oldWeight = splitPane->weight; 2415 splitPane->weight = (float) (cursor - splitterSize / 2 - (vertical ? splitPane->e.bounds.t : splitPane->e.bounds.l)) / space; 2416 if (splitPane->weight < 0.05f) splitPane->weight = 0.05f; 2417 if (splitPane->weight > 0.95f) splitPane->weight = 0.95f; 2418 2419 if (splitPane->e.children[2]->messageClass == _UISplitPaneMessage 2420 && (splitPane->e.children[2]->flags & UI_SPLIT_PANE_VERTICAL) == (splitPane->e.flags & UI_SPLIT_PANE_VERTICAL)) { 2421 UISplitPane *subSplitPane = (UISplitPane *) splitPane->e.children[2]; 2422 subSplitPane->weight = (splitPane->weight - oldWeight - subSplitPane->weight + oldWeight * subSplitPane->weight) / (-1 + splitPane->weight); 2423 if (subSplitPane->weight < 0.05f) subSplitPane->weight = 0.05f; 2424 if (subSplitPane->weight > 0.95f) subSplitPane->weight = 0.95f; 2425 } 2426 2427 UIElementRefresh(&splitPane->e); 2428 } 2429 2430 return 0; 2431 } 2432 2433 int _UISplitPaneMessage(UIElement *element, UIMessage message, int di, void *dp) { 2434 UISplitPane *splitPane = (UISplitPane *) element; 2435 bool vertical = splitPane->e.flags & UI_SPLIT_PANE_VERTICAL; 2436 2437 if (message == UI_MSG_LAYOUT) { 2438 UIElement *splitter = element->children[0]; 2439 UIElement *left = element->children[1]; 2440 UIElement *right = element->children[2]; 2441 2442 int splitterSize = UI_SIZE_SPLITTER * element->window->scale; 2443 int space = (vertical ? UI_RECT_HEIGHT(element->bounds) : UI_RECT_WIDTH(element->bounds)) - splitterSize; 2444 int leftSize = space * splitPane->weight; 2445 int rightSize = space - leftSize; 2446 2447 if (vertical) { 2448 UIElementMove(left, UI_RECT_4(element->bounds.l, element->bounds.r, element->bounds.t, element->bounds.t + leftSize), false); 2449 UIElementMove(splitter, UI_RECT_4(element->bounds.l, element->bounds.r, element->bounds.t + leftSize, element->bounds.t + leftSize + splitterSize), false); 2450 UIElementMove(right, UI_RECT_4(element->bounds.l, element->bounds.r, element->bounds.b - rightSize, element->bounds.b), false); 2451 } else { 2452 UIElementMove(left, UI_RECT_4(element->bounds.l, element->bounds.l + leftSize, element->bounds.t, element->bounds.b), false); 2453 UIElementMove(splitter, UI_RECT_4(element->bounds.l + leftSize, element->bounds.l + leftSize + splitterSize, element->bounds.t, element->bounds.b), false); 2454 UIElementMove(right, UI_RECT_4(element->bounds.r - rightSize, element->bounds.r, element->bounds.t, element->bounds.b), false); 2455 } 2456 } 2457 2458 return 0; 2459 } 2460 2461 UISplitPane *UISplitPaneCreate(UIElement *parent, uint32_t flags, float weight) { 2462 UISplitPane *splitPane = (UISplitPane *) UIElementCreate(sizeof(UISplitPane), parent, flags, _UISplitPaneMessage, "Split Pane"); 2463 splitPane->weight = weight; 2464 UIElementCreate(sizeof(UIElement), &splitPane->e, 0, _UISplitterMessage, "Splitter"); 2465 return splitPane; 2466 } 2467 2468 ///////////////////////////////////////// 2469 // Tab panes. 2470 ///////////////////////////////////////// 2471 2472 int _UITabPaneMessage(UIElement *element, UIMessage message, int di, void *dp) { 2473 UITabPane *tabPane = (UITabPane *) element; 2474 2475 if (message == UI_MSG_PAINT) { 2476 UIPainter *painter = (UIPainter *) dp; 2477 UIRectangle top = element->bounds; 2478 top.b = top.t + UI_SIZE_BUTTON_HEIGHT * element->window->scale; 2479 UIDrawControl(painter, top, UI_DRAW_CONTROL_TAB_BAND, NULL, 0, 0, element->window->scale); 2480 2481 UIRectangle tab = top; 2482 tab.l += UI_SIZE_TAB_PANE_SPACE_LEFT * element->window->scale; 2483 tab.t += UI_SIZE_TAB_PANE_SPACE_TOP * element->window->scale; 2484 2485 int position = 0; 2486 uint32_t index = 0; 2487 2488 while (true) { 2489 int end = position; 2490 for (; tabPane->tabs[end] != '\t' && tabPane->tabs[end]; end++); 2491 2492 int width = UIMeasureStringWidth(tabPane->tabs, end - position); 2493 tab.r = tab.l + width + UI_SIZE_BUTTON_PADDING; 2494 2495 UIDrawControl(painter, tab, UI_DRAW_CONTROL_TAB | (tabPane->active == index ? UI_DRAW_CONTROL_STATE_SELECTED : 0), 2496 tabPane->tabs + position, end - position, 0, element->window->scale); 2497 tab.l = tab.r - 1; 2498 2499 if (tabPane->tabs[end] == '\t') { 2500 position = end + 1; 2501 index++; 2502 } else { 2503 break; 2504 } 2505 } 2506 } else if (message == UI_MSG_LEFT_DOWN) { 2507 UIRectangle tab = element->bounds; 2508 tab.b = tab.t + UI_SIZE_BUTTON_HEIGHT * element->window->scale; 2509 tab.l += UI_SIZE_TAB_PANE_SPACE_LEFT * element->window->scale; 2510 tab.t += UI_SIZE_TAB_PANE_SPACE_TOP * element->window->scale; 2511 2512 int position = 0; 2513 int index = 0; 2514 2515 while (true) { 2516 int end = position; 2517 for (; tabPane->tabs[end] != '\t' && tabPane->tabs[end]; end++); 2518 2519 int width = UIMeasureStringWidth(tabPane->tabs, end - position); 2520 tab.r = tab.l + width + UI_SIZE_BUTTON_PADDING; 2521 2522 if (UIRectangleContains(tab, element->window->cursorX, element->window->cursorY)) { 2523 tabPane->active = index; 2524 UIElementRelayout(element); 2525 UIElementRepaint(element, NULL); 2526 break; 2527 } 2528 2529 tab.l = tab.r - 1; 2530 2531 if (tabPane->tabs[end] == '\t') { 2532 position = end + 1; 2533 index++; 2534 } else { 2535 break; 2536 } 2537 } 2538 } else if (message == UI_MSG_LAYOUT) { 2539 UIRectangle content = element->bounds; 2540 content.t += UI_SIZE_BUTTON_HEIGHT * element->window->scale; 2541 2542 for (uint32_t index = 0; index < element->childCount; index++) { 2543 UIElement *child = element->children[index]; 2544 2545 if (tabPane->active == index) { 2546 child->flags &= ~UI_ELEMENT_HIDE; 2547 UIElementMove(child, content, false); 2548 UIElementMessage(child, UI_MSG_TAB_SELECTED, 0, 0); 2549 } else { 2550 child->flags |= UI_ELEMENT_HIDE; 2551 } 2552 } 2553 } else if (message == UI_MSG_GET_HEIGHT) { 2554 int baseHeight = UI_SIZE_BUTTON_HEIGHT * element->window->scale; 2555 2556 for (uint32_t index = 0; index < element->childCount; index++) { 2557 UIElement *child = element->children[index]; 2558 2559 if (tabPane->active == index) { 2560 return baseHeight + UIElementMessage(child, UI_MSG_GET_HEIGHT, di, dp); 2561 } 2562 } 2563 } else if (message == UI_MSG_DEALLOCATE) { 2564 UI_FREE(tabPane->tabs); 2565 } 2566 2567 return 0; 2568 } 2569 2570 UITabPane *UITabPaneCreate(UIElement *parent, uint32_t flags, const char *tabs) { 2571 UITabPane *tabPane = (UITabPane *) UIElementCreate(sizeof(UITabPane), parent, flags, _UITabPaneMessage, "Tab Pane"); 2572 tabPane->tabs = UIStringCopy(tabs, -1); 2573 return tabPane; 2574 } 2575 2576 ///////////////////////////////////////// 2577 // Spacers. 2578 ///////////////////////////////////////// 2579 2580 int _UISpacerMessage(UIElement *element, UIMessage message, int di, void *dp) { 2581 UISpacer *spacer = (UISpacer *) element; 2582 2583 if (message == UI_MSG_GET_HEIGHT) { 2584 return spacer->height * element->window->scale; 2585 } else if (message == UI_MSG_GET_WIDTH) { 2586 return spacer->width * element->window->scale; 2587 } 2588 2589 return 0; 2590 } 2591 2592 UISpacer *UISpacerCreate(UIElement *parent, uint32_t flags, int width, int height) { 2593 UISpacer *spacer = (UISpacer *) UIElementCreate(sizeof(UISpacer), parent, flags, _UISpacerMessage, "Spacer"); 2594 spacer->width = width; 2595 spacer->height = height; 2596 return spacer; 2597 } 2598 2599 ///////////////////////////////////////// 2600 // Scroll bars. 2601 ///////////////////////////////////////// 2602 2603 int _UIScrollBarMessage(UIElement *element, UIMessage message, int di, void *dp) { 2604 UIScrollBar *scrollBar = (UIScrollBar *) element; 2605 2606 if (message == UI_MSG_GET_WIDTH || message == UI_MSG_GET_HEIGHT) { 2607 return UI_SIZE_SCROLL_BAR * element->window->scale; 2608 } else if (message == UI_MSG_LAYOUT) { 2609 UIElement *up = element->children[0]; 2610 UIElement *thumb = element->children[1]; 2611 UIElement *down = element->children[2]; 2612 2613 if (scrollBar->page >= scrollBar->maximum || scrollBar->maximum <= 0 || scrollBar->page <= 0) { 2614 up->flags |= UI_ELEMENT_HIDE; 2615 thumb->flags |= UI_ELEMENT_HIDE; 2616 down->flags |= UI_ELEMENT_HIDE; 2617 2618 scrollBar->position = 0; 2619 } else { 2620 up->flags &= ~UI_ELEMENT_HIDE; 2621 thumb->flags &= ~UI_ELEMENT_HIDE; 2622 down->flags &= ~UI_ELEMENT_HIDE; 2623 2624 int size = scrollBar->horizontal ? UI_RECT_WIDTH(element->bounds) : UI_RECT_HEIGHT(element->bounds); 2625 int thumbSize = size * scrollBar->page / scrollBar->maximum; 2626 2627 if (thumbSize < UI_SIZE_SCROLL_MINIMUM_THUMB * element->window->scale) { 2628 thumbSize = UI_SIZE_SCROLL_MINIMUM_THUMB * element->window->scale; 2629 } 2630 2631 if (scrollBar->position < 0) { 2632 scrollBar->position = 0; 2633 } else if (scrollBar->position > scrollBar->maximum - scrollBar->page) { 2634 scrollBar->position = scrollBar->maximum - scrollBar->page; 2635 } 2636 2637 int thumbPosition = scrollBar->position / (scrollBar->maximum - scrollBar->page) * (size - thumbSize); 2638 2639 if (scrollBar->position == scrollBar->maximum - scrollBar->page) { 2640 thumbPosition = size - thumbSize; 2641 } 2642 2643 if (scrollBar->horizontal) { 2644 UIRectangle r = element->bounds; 2645 r.r = r.l + thumbPosition; 2646 UIElementMove(up, r, false); 2647 r.l = r.r, r.r = r.l + thumbSize; 2648 UIElementMove(thumb, r, false); 2649 r.l = r.r, r.r = element->bounds.r; 2650 UIElementMove(down, r, false); 2651 } else { 2652 UIRectangle r = element->bounds; 2653 r.b = r.t + thumbPosition; 2654 UIElementMove(up, r, false); 2655 r.t = r.b, r.b = r.t + thumbSize; 2656 UIElementMove(thumb, r, false); 2657 r.t = r.b, r.b = element->bounds.b; 2658 UIElementMove(down, r, false); 2659 } 2660 } 2661 } else if (message == UI_MSG_PAINT) { 2662 UIDrawControl((UIPainter *) dp, element->bounds, UI_DRAW_CONTROL_SCROLL_TRACK 2663 | ((scrollBar->page >= scrollBar->maximum || scrollBar->maximum <= 0 || scrollBar->page <= 0) ? UI_DRAW_CONTROL_STATE_DISABLED : 0), 2664 NULL, 0, 0, element->window->scale); 2665 } else if (message == UI_MSG_MOUSE_WHEEL) { 2666 scrollBar->position += di; 2667 UIElementRefresh(element); 2668 UIElementMessage(element->parent, UI_MSG_SCROLLED, 0, 0); 2669 return 1; 2670 } 2671 2672 return 0; 2673 } 2674 2675 int _UIScrollUpDownMessage(UIElement *element, UIMessage message, int di, void *dp) { 2676 UIScrollBar *scrollBar = (UIScrollBar *) element->parent; 2677 bool isDown = element->cp; 2678 2679 if (message == UI_MSG_PAINT) { 2680 UIDrawControl((UIPainter *) dp, element->bounds, (isDown ? UI_DRAW_CONTROL_SCROLL_DOWN : UI_DRAW_CONTROL_SCROLL_UP) 2681 | (scrollBar->horizontal ? 0 : UI_DRAW_CONTROL_STATE_VERTICAL) | UI_DRAW_CONTROL_STATE_FROM_ELEMENT(element), 2682 NULL, 0, 0, element->window->scale); 2683 } else if (message == UI_MSG_UPDATE) { 2684 UIElementRepaint(element, NULL); 2685 } else if (message == UI_MSG_LEFT_DOWN) { 2686 UIElementAnimate(element, false); 2687 scrollBar->lastAnimateTime = UI_CLOCK(); 2688 } else if (message == UI_MSG_LEFT_UP) { 2689 UIElementAnimate(element, true); 2690 } else if (message == UI_MSG_ANIMATE) { 2691 UI_CLOCK_T previous = scrollBar->lastAnimateTime; 2692 UI_CLOCK_T current = UI_CLOCK(); 2693 UI_CLOCK_T delta = current - previous; 2694 double deltaSeconds = (double) delta / UI_CLOCKS_PER_SECOND; 2695 if (deltaSeconds > 0.1) deltaSeconds = 0.1; 2696 double deltaPixels = deltaSeconds * scrollBar->page * 3; 2697 scrollBar->lastAnimateTime = current; 2698 if (isDown) scrollBar->position += deltaPixels; 2699 else scrollBar->position -= deltaPixels; 2700 UIElementRefresh(&scrollBar->e); 2701 UIElementMessage(scrollBar->e.parent, UI_MSG_SCROLLED, 0, 0); 2702 } 2703 2704 return 0; 2705 } 2706 2707 int _UIScrollThumbMessage(UIElement *element, UIMessage message, int di, void *dp) { 2708 UIScrollBar *scrollBar = (UIScrollBar *) element->parent; 2709 2710 if (message == UI_MSG_PAINT) { 2711 UIDrawControl((UIPainter *) dp, element->bounds, UI_DRAW_CONTROL_SCROLL_THUMB 2712 | (scrollBar->horizontal ? 0 : UI_DRAW_CONTROL_STATE_VERTICAL) 2713 | UI_DRAW_CONTROL_STATE_FROM_ELEMENT(element), NULL, 0, 0, element->window->scale); 2714 } else if (message == UI_MSG_UPDATE) { 2715 UIElementRepaint(element, NULL); 2716 } else if (message == UI_MSG_MOUSE_DRAG && element->window->pressedButton == 1) { 2717 if (!scrollBar->inDrag) { 2718 scrollBar->inDrag = true; 2719 2720 if (scrollBar->horizontal) { 2721 scrollBar->dragOffset = element->bounds.l - scrollBar->e.bounds.l - element->window->cursorX; 2722 } else { 2723 scrollBar->dragOffset = element->bounds.t - scrollBar->e.bounds.t - element->window->cursorY; 2724 } 2725 } 2726 2727 int thumbPosition = (scrollBar->horizontal ? element->window->cursorX : element->window->cursorY) + scrollBar->dragOffset; 2728 int size = scrollBar->horizontal ? (UI_RECT_WIDTH(scrollBar->e.bounds) - UI_RECT_WIDTH(element->bounds)) 2729 : (UI_RECT_HEIGHT(scrollBar->e.bounds) - UI_RECT_HEIGHT(element->bounds)); 2730 scrollBar->position = (double) thumbPosition / size * (scrollBar->maximum - scrollBar->page); 2731 UIElementRefresh(&scrollBar->e); 2732 UIElementMessage(scrollBar->e.parent, UI_MSG_SCROLLED, 0, 0); 2733 } else if (message == UI_MSG_LEFT_UP) { 2734 scrollBar->inDrag = false; 2735 } 2736 2737 return 0; 2738 } 2739 2740 UIScrollBar *UIScrollBarCreate(UIElement *parent, uint32_t flags) { 2741 UIScrollBar *scrollBar = (UIScrollBar *) UIElementCreate(sizeof(UIScrollBar), parent, flags, _UIScrollBarMessage, "Scroll Bar"); 2742 bool horizontal = scrollBar->horizontal = flags & UI_SCROLL_BAR_HORIZONTAL; 2743 UIElementCreate(sizeof(UIElement), &scrollBar->e, flags, _UIScrollUpDownMessage, !horizontal ? "Scroll Up" : "Scroll Left")->cp = (void *) (uintptr_t) 0; 2744 UIElementCreate(sizeof(UIElement), &scrollBar->e, flags, _UIScrollThumbMessage, "Scroll Thumb"); 2745 UIElementCreate(sizeof(UIElement), &scrollBar->e, flags, _UIScrollUpDownMessage, !horizontal ? "Scroll Down" : "Scroll Right")->cp = (void *) (uintptr_t) 1; 2746 return scrollBar; 2747 } 2748 2749 ///////////////////////////////////////// 2750 // Code views. 2751 ///////////////////////////////////////// 2752 2753 bool _UICharIsDigit(int c) { 2754 return c >= '0' && c <= '9'; 2755 } 2756 2757 bool _UICharIsAlpha(int c) { 2758 return ( 2759 ('A' <= c && c <= 'Z') || 2760 ('a' <= c && c <= 'z') || 2761 c > 127 2762 ); 2763 } 2764 2765 bool _UICharIsAlphaOrDigitOrUnderscore(int c) { 2766 return _UICharIsAlpha(c) || _UICharIsDigit(c) || c == '_'; 2767 } 2768 2769 int _UICodeByteToColumn(UICode *code, int line, int byte) { 2770 return _UIByteToColumn(&code->content[code->lines[line].offset], byte, code->lines[line].bytes, code->tabSize); 2771 } 2772 2773 int _UICodeColumnToByte(UICode *code, int line, int column) { 2774 return _UIColumnToByte(&code->content[code->lines[line].offset], column, code->lines[line].bytes, code->tabSize); 2775 } 2776 2777 void UICodePositionToByte(UICode *code, int x, int y, int *line, int *byte) { 2778 UIFont *previousFont = UIFontActivate(code->font); 2779 int lineHeight = UIMeasureStringHeight(); 2780 *line = (y - code->e.bounds.t + code->vScroll->position) / lineHeight; 2781 if (*line < 0) *line = 0; 2782 else if (*line >= code->lineCount) *line = code->lineCount - 1; 2783 int column = (x - code->e.bounds.l + code->hScroll->position + ui.activeFont->glyphWidth / 2) / ui.activeFont->glyphWidth; 2784 if (~code->e.flags & UI_CODE_NO_MARGIN) column -= (UI_SIZE_CODE_MARGIN + UI_SIZE_CODE_MARGIN_GAP) / ui.activeFont->glyphWidth; 2785 UIFontActivate(previousFont); 2786 *byte = _UICodeColumnToByte(code, *line, column); 2787 } 2788 2789 int UICodeHitTest(UICode *code, int x, int y) { 2790 x -= code->e.bounds.l; 2791 2792 if (x < 0 || x >= code->vScroll->e.bounds.l) { 2793 return 0; 2794 } 2795 2796 y -= code->e.bounds.t - code->vScroll->position; 2797 2798 UIFont *previousFont = UIFontActivate(code->font); 2799 int lineHeight = UIMeasureStringHeight(); 2800 bool inMargin = x < UI_SIZE_CODE_MARGIN + UI_SIZE_CODE_MARGIN_GAP / 2 && (~code->e.flags & UI_CODE_NO_MARGIN); 2801 UIFontActivate(previousFont); 2802 2803 if (y < 0 || y >= lineHeight * code->lineCount) { 2804 return 0; 2805 } 2806 2807 int line = y / lineHeight + 1; 2808 return inMargin ? -line : line; 2809 } 2810 2811 int UIDrawStringHighlighted(UIPainter *painter, UIRectangle lineBounds, const char *string, ptrdiff_t bytes, int tabSize, UIStringSelection *selection) { 2812 if (bytes == -1) bytes = _UIStringLength(string); 2813 if (bytes > 10000) bytes = 10000; 2814 2815 typedef enum _UICodeTokenType { 2816 UI_CODE_TOKEN_TYPE_DEFAULT, 2817 UI_CODE_TOKEN_TYPE_COMMENT, 2818 UI_CODE_TOKEN_TYPE_STRING, 2819 UI_CODE_TOKEN_TYPE_NUMBER, 2820 UI_CODE_TOKEN_TYPE_OPERATOR, 2821 UI_CODE_TOKEN_TYPE_PREPROCESSOR, 2822 } _UICodeTokenType; 2823 2824 uint32_t colors[] = { 2825 ui.theme.codeDefault, 2826 ui.theme.codeComment, 2827 ui.theme.codeString, 2828 ui.theme.codeNumber, 2829 ui.theme.codeOperator, 2830 ui.theme.codePreprocessor, 2831 }; 2832 2833 int lineHeight = UIMeasureStringHeight(); 2834 int x = lineBounds.l; 2835 int y = (lineBounds.t + lineBounds.b - lineHeight) / 2; 2836 int ti = 0; 2837 _UICodeTokenType tokenType = UI_CODE_TOKEN_TYPE_DEFAULT; 2838 bool inComment = false, inIdentifier = false, inChar = false, startedString = false, startedPreprocessor = false; 2839 uint32_t last = 0; 2840 int j = 0; 2841 2842 while (bytes) { 2843 #ifdef UI_UNICODE 2844 ptrdiff_t bytesConsumed; 2845 int c = Utf8GetCodePoint(string, bytes, &bytesConsumed); 2846 UI_ASSERT(bytesConsumed > 0); 2847 string += bytesConsumed; 2848 bytes -= bytesConsumed; 2849 #else 2850 char c = *string++; 2851 bytes--; 2852 #endif 2853 2854 last <<= 8; 2855 last |= c & 0xFF; 2856 2857 if (tokenType == UI_CODE_TOKEN_TYPE_PREPROCESSOR) { 2858 if (bytes && c == '/' && (*string == '/' || *string == '*')) { 2859 tokenType = UI_CODE_TOKEN_TYPE_DEFAULT; 2860 } 2861 } else if (tokenType == UI_CODE_TOKEN_TYPE_OPERATOR) { 2862 tokenType = UI_CODE_TOKEN_TYPE_DEFAULT; 2863 } else if (tokenType == UI_CODE_TOKEN_TYPE_COMMENT) { 2864 if ((last & 0xFF0000) == ('*' << 16) && (last & 0xFF00) == ('/' << 8) && inComment) { 2865 tokenType = startedPreprocessor ? UI_CODE_TOKEN_TYPE_PREPROCESSOR : UI_CODE_TOKEN_TYPE_DEFAULT; 2866 inComment = false; 2867 } 2868 } else if (tokenType == UI_CODE_TOKEN_TYPE_NUMBER) { 2869 if (!_UICharIsAlpha(c) && !_UICharIsDigit(c)) { 2870 tokenType = UI_CODE_TOKEN_TYPE_DEFAULT; 2871 } 2872 } else if (tokenType == UI_CODE_TOKEN_TYPE_STRING) { 2873 if (!startedString) { 2874 if (!inChar && ((last >> 8) & 0xFF) == '"' && ((last >> 16) & 0xFF) != '\\') { 2875 tokenType = UI_CODE_TOKEN_TYPE_DEFAULT; 2876 } else if (inChar && ((last >> 8) & 0xFF) == '\'' && ((last >> 16) & 0xFF) != '\\') { 2877 tokenType = UI_CODE_TOKEN_TYPE_DEFAULT; 2878 } 2879 } 2880 2881 startedString = false; 2882 } 2883 2884 if (tokenType == UI_CODE_TOKEN_TYPE_DEFAULT) { 2885 if (c == '#') { 2886 tokenType = UI_CODE_TOKEN_TYPE_PREPROCESSOR; 2887 startedPreprocessor = true; 2888 } else if (bytes && c == '/' && *string == '/') { 2889 tokenType = UI_CODE_TOKEN_TYPE_COMMENT; 2890 } else if (bytes && c == '/' && *string == '*') { 2891 tokenType = UI_CODE_TOKEN_TYPE_COMMENT, inComment = true; 2892 } else if (c == '"') { 2893 tokenType = UI_CODE_TOKEN_TYPE_STRING; 2894 inChar = false; 2895 startedString = true; 2896 } else if (c == '\'') { 2897 tokenType = UI_CODE_TOKEN_TYPE_STRING; 2898 inChar = true; 2899 startedString = true; 2900 } else if (_UICharIsDigit(c) && !inIdentifier) { 2901 tokenType = UI_CODE_TOKEN_TYPE_NUMBER; 2902 } else if (!_UICharIsAlpha(c) && !_UICharIsDigit(c)) { 2903 tokenType = UI_CODE_TOKEN_TYPE_OPERATOR; 2904 inIdentifier = false; 2905 } else { 2906 inIdentifier = true; 2907 } 2908 } 2909 2910 int oldX = x; 2911 2912 if (c == '\t') { 2913 x += ui.activeFont->glyphWidth, ti++; 2914 while (ti % tabSize) x += ui.activeFont->glyphWidth, ti++, j++; 2915 } else { 2916 UIDrawGlyph(painter, x, y, c, colors[tokenType]); 2917 x += ui.activeFont->glyphWidth, ti++; 2918 } 2919 2920 if (selection && j >= selection->carets[0] && j < selection->carets[1]) { 2921 UIDrawBlock(painter, UI_RECT_4(oldX, x, y, y + lineHeight), selection->colorBackground); 2922 if (c != '\t') UIDrawGlyph(painter, oldX, y, c, selection->colorText); 2923 } 2924 2925 if (selection && selection->carets[0] == j) { 2926 UIDrawInvert(painter, UI_RECT_4(oldX, oldX + 1, y, y + lineHeight)); 2927 } 2928 2929 j++; 2930 } 2931 2932 if (selection && selection->carets[0] == j) { 2933 UIDrawInvert(painter, UI_RECT_4(x, x + 1, y, y + lineHeight)); 2934 } 2935 2936 return x; 2937 } 2938 2939 void _UICodeUpdateSelection(UICode *code) { 2940 bool swap = code->selection[3].line < code->selection[2].line 2941 || (code->selection[3].line == code->selection[2].line && code->selection[3].offset < code->selection[2].offset); 2942 code->selection[1 - swap] = code->selection[3]; 2943 code->selection[0 + swap] = code->selection[2]; 2944 code->moveScrollToCaretNextLayout = true; 2945 UIElementRefresh(&code->e); 2946 } 2947 2948 void _UICodeSetVerticalMotionColumn(UICode *code, bool restore) { 2949 if (restore) { 2950 code->selection[3].offset = _UICodeColumnToByte(code, code->selection[3].line, code->verticalMotionColumn); 2951 } else if (!code->useVerticalMotionColumn) { 2952 code->useVerticalMotionColumn = true; 2953 code->verticalMotionColumn = _UICodeByteToColumn(code, code->selection[3].line, code->selection[3].offset); 2954 } 2955 } 2956 2957 void _UICodeCopyText(void *cp) { 2958 UICode *code = (UICode *) cp; 2959 2960 int from = code->lines[code->selection[0].line].offset + code->selection[0].offset; 2961 int to = code->lines[code->selection[1].line].offset + code->selection[1].offset; 2962 2963 if (from != to) { 2964 char *pasteText = (char *) UI_CALLOC(to - from + 2); 2965 for (int i = from; i < to; i++) pasteText[i - from] = code->content[i]; 2966 _UIClipboardWriteText(code->e.window, pasteText); 2967 } 2968 } 2969 2970 int _UICodeMessage(UIElement *element, UIMessage message, int di, void *dp) { 2971 UICode *code = (UICode *) element; 2972 2973 if (message == UI_MSG_LAYOUT) { 2974 UIFont *previousFont = UIFontActivate(code->font); 2975 int scrollBarSize = UI_SIZE_SCROLL_BAR * code->e.window->scale; 2976 code->vScroll->maximum = code->lineCount * UIMeasureStringHeight(); 2977 code->hScroll->maximum = code->columns * code->font->glyphWidth; // TODO This doesn't take into account tab sizes! 2978 int vSpace = code->vScroll->page = UI_RECT_HEIGHT(element->bounds); 2979 int hSpace = code->hScroll->page = UI_RECT_WIDTH(element->bounds); 2980 2981 if (code->moveScrollToCaretNextLayout) { 2982 int top = code->selection[3].line * UIMeasureStringHeight(); 2983 int bottom = top + UIMeasureStringHeight(); 2984 int context = UIMeasureStringHeight() * 2; 2985 if (bottom > code->vScroll->position + vSpace - context) code->vScroll->position = bottom - vSpace + context; 2986 if (top < code->vScroll->position + context) code->vScroll->position = top - context; 2987 code->moveScrollToCaretNextLayout = code->moveScrollToFocusNextLayout = false; 2988 // TODO Horizontal scrolling. 2989 } else if (code->moveScrollToFocusNextLayout) { 2990 int lineHeight = UIMeasureStringHeight(); 2991 int viewHeight = UI_RECT_HEIGHT(code->e.bounds); 2992 2993 int padding = lineHeight*5; 2994 int prevPos = code->vScroll->position; 2995 int newPos = (code->focused + 0.5) * lineHeight - viewHeight / 2; 2996 2997 if (!code->centerExecutionPointer) { 2998 if (newPos-prevPos > viewHeight/2 - padding) { 2999 newPos = newPos - (viewHeight/2 - padding); 3000 } else if (newPos-prevPos < -(viewHeight/2 - padding)) { 3001 newPos = newPos +(viewHeight/2 - padding); 3002 } else { 3003 newPos = prevPos; 3004 } 3005 } 3006 3007 code->vScroll->position = newPos; 3008 } 3009 3010 if (!(code->e.flags & UI_CODE_NO_MARGIN)) hSpace -= UI_SIZE_CODE_MARGIN + UI_SIZE_CODE_MARGIN_GAP; 3011 _UI_LAYOUT_SCROLL_BAR_PAIR(code); 3012 3013 UIFontActivate(previousFont); 3014 } else if (message == UI_MSG_PAINT) { 3015 UIFont *previousFont = UIFontActivate(code->font); 3016 3017 UIPainter *painter = (UIPainter *) dp; 3018 UIRectangle lineBounds = element->bounds; 3019 3020 lineBounds.r = code->vScroll->e.bounds.l; 3021 3022 if (~code->e.flags & UI_CODE_NO_MARGIN) { 3023 lineBounds.l += UI_SIZE_CODE_MARGIN + UI_SIZE_CODE_MARGIN_GAP; 3024 } 3025 3026 int lineHeight = UIMeasureStringHeight(); 3027 lineBounds.t -= (int64_t) code->vScroll->position % lineHeight; 3028 3029 UIDrawBlock(painter, element->bounds, ui.theme.codeBackground); 3030 3031 #ifdef __cplusplus 3032 UIStringSelection selection = {}; 3033 #else 3034 UIStringSelection selection = { 0 }; 3035 #endif 3036 selection.colorBackground = ui.theme.selected; 3037 selection.colorText = ui.theme.textSelected; 3038 3039 for (int i = code->vScroll->position / lineHeight; i < code->lineCount; i++) { 3040 if (lineBounds.t > element->clip.b) { 3041 break; 3042 } 3043 3044 lineBounds.b = lineBounds.t + lineHeight; 3045 3046 if (~code->e.flags & UI_CODE_NO_MARGIN) { 3047 char string[16]; 3048 int p = 16; 3049 int lineNumber = i + 1; 3050 3051 while (lineNumber) { 3052 string[--p] = (lineNumber % 10) + '0'; 3053 lineNumber /= 10; 3054 } 3055 3056 UIRectangle marginBounds = lineBounds; 3057 marginBounds.r = marginBounds.l - UI_SIZE_CODE_MARGIN_GAP; 3058 marginBounds.l -= UI_SIZE_CODE_MARGIN + UI_SIZE_CODE_MARGIN_GAP; 3059 3060 uint32_t marginColor = UIElementMessage(element, UI_MSG_CODE_GET_MARGIN_COLOR, i + 1, 0); 3061 3062 if (marginColor) { 3063 UIDrawBlock(painter, marginBounds, marginColor); 3064 } 3065 3066 UIDrawString(painter, marginBounds, string + p, 16 - p, 3067 marginColor ? ui.theme.codeDefault : ui.theme.codeComment, UI_ALIGN_RIGHT, NULL); 3068 } 3069 3070 if (code->focused == i) { 3071 UIDrawBlock(painter, lineBounds, ui.theme.codeFocused); 3072 } 3073 3074 UIRectangle oldClip = painter->clip; 3075 painter->clip = UIRectangleIntersection(oldClip, lineBounds); 3076 if (code->hScroll) lineBounds.l -= (int64_t) code->hScroll->position; 3077 selection.carets[0] = i == code->selection[0].line ? _UICodeByteToColumn(code, i, code->selection[0].offset) : 0; 3078 selection.carets[1] = i == code->selection[1].line ? _UICodeByteToColumn(code, i, code->selection[1].offset) : code->lines[i].bytes; 3079 int x = UIDrawStringHighlighted(painter, lineBounds, code->content + code->lines[i].offset, code->lines[i].bytes, code->tabSize, 3080 element->window->focused == element && i >= code->selection[0].line && i <= code->selection[1].line ? &selection : NULL); 3081 int y = (lineBounds.t + lineBounds.b - UIMeasureStringHeight()) / 2; 3082 3083 if (element->window->focused == element && i >= code->selection[0].line && i < code->selection[1].line) { 3084 UIDrawBlock(painter, UI_RECT_4PD(x, y, code->font->glyphWidth, code->font->glyphHeight), selection.colorBackground); 3085 } 3086 3087 if (code->hScroll) lineBounds.l += (int64_t) code->hScroll->position; 3088 painter->clip = oldClip; 3089 3090 UICodeDecorateLine m; 3091 m.x = x, m.y = y, m.bounds = lineBounds, m.index = i + 1, m.painter = painter; 3092 UIElementMessage(element, UI_MSG_CODE_DECORATE_LINE, 0, &m); 3093 3094 lineBounds.t += lineHeight; 3095 } 3096 3097 UIFontActivate(previousFont); 3098 } else if (message == UI_MSG_SCROLLED) { 3099 code->moveScrollToFocusNextLayout = false; 3100 UIElementRefresh(element); 3101 } else if (message == UI_MSG_MOUSE_WHEEL) { 3102 return UIElementMessage(&code->vScroll->e, message, di, dp); 3103 } else if (message == UI_MSG_GET_CURSOR) { 3104 if (UICodeHitTest(code, element->window->cursorX, element->window->cursorY) < 0) { 3105 return UI_CURSOR_FLIPPED_ARROW; 3106 } 3107 3108 if (element->flags & UI_CODE_SELECTABLE) { 3109 return UI_CURSOR_TEXT; 3110 } 3111 } else if (message == UI_MSG_LEFT_UP) { 3112 UIElementAnimate(element, true); 3113 } else if (message == UI_MSG_LEFT_DOWN && code->lineCount) { 3114 int hitTest = UICodeHitTest(code, element->window->cursorX, element->window->cursorY); 3115 code->leftDownInMargin = hitTest < 0; 3116 3117 if (hitTest > 0 && (element->flags & UI_CODE_SELECTABLE)) { 3118 UICodePositionToByte(code, element->window->cursorX, element->window->cursorY, &code->selection[2].line, &code->selection[2].offset); 3119 _UICodeMessage(element, UI_MSG_MOUSE_DRAG, di, dp); 3120 UIElementFocus(element); 3121 UIElementAnimate(element, false); 3122 code->lastAnimateTime = UI_CLOCK(); 3123 } 3124 } else if (message == UI_MSG_ANIMATE) { 3125 if (element->window->pressed == element && element->window->pressedButton == 1 && code->lineCount && !code->leftDownInMargin) { 3126 UI_CLOCK_T previous = code->lastAnimateTime; 3127 UI_CLOCK_T current = UI_CLOCK(); 3128 UI_CLOCK_T deltaTicks = current - previous; 3129 double deltaSeconds = (double) deltaTicks / UI_CLOCKS_PER_SECOND; 3130 if (deltaSeconds > 0.1) deltaSeconds = 0.1; 3131 int delta = deltaSeconds * 800; 3132 if (!delta) { return 0; } 3133 code->lastAnimateTime = current; 3134 3135 UIFont *previousFont = UIFontActivate(code->font); 3136 3137 if (element->window->cursorX < element->bounds.l + ((element->flags & UI_CODE_NO_MARGIN) 3138 ? UI_SIZE_CODE_MARGIN_GAP : (UI_SIZE_CODE_MARGIN + UI_SIZE_CODE_MARGIN_GAP * 2))) { 3139 code->hScroll->position -= delta; 3140 } else if (element->window->cursorX >= code->vScroll->e.bounds.l - UI_SIZE_CODE_MARGIN_GAP) { 3141 code->hScroll->position += delta; 3142 } 3143 3144 if (element->window->cursorY < element->bounds.t + UI_SIZE_CODE_MARGIN_GAP) { 3145 code->vScroll->position -= delta; 3146 } else if (element->window->cursorY >= code->hScroll->e.bounds.t - UI_SIZE_CODE_MARGIN_GAP) { 3147 code->vScroll->position += delta; 3148 } 3149 3150 code->moveScrollToFocusNextLayout = false; 3151 UIFontActivate(previousFont); 3152 _UICodeMessage(element, UI_MSG_MOUSE_DRAG, di, dp); 3153 UIElementRefresh(element); 3154 } 3155 } else if (message == UI_MSG_MOUSE_DRAG && element->window->pressedButton == 1 && code->lineCount && !code->leftDownInMargin) { 3156 // TODO Double-click and triple-click dragging for word and line granularity respectively. 3157 UICodePositionToByte(code, element->window->cursorX, element->window->cursorY, &code->selection[3].line, &code->selection[3].offset); 3158 _UICodeUpdateSelection(code); 3159 code->moveScrollToFocusNextLayout = code->moveScrollToCaretNextLayout = false; 3160 code->useVerticalMotionColumn = false; 3161 } else if (message == UI_MSG_KEY_TYPED && code->lineCount) { 3162 UIKeyTyped *m = (UIKeyTyped *) dp; 3163 3164 if ((m->code == UI_KEYCODE_LETTER('C') || m->code == UI_KEYCODE_LETTER('X') || m->code == UI_KEYCODE_INSERT) 3165 && element->window->ctrl && !element->window->alt && !element->window->shift) { 3166 _UICodeCopyText(code); 3167 } else if ((m->code == UI_KEYCODE_UP || m->code == UI_KEYCODE_DOWN || m->code == UI_KEYCODE_PAGE_UP || m->code == UI_KEYCODE_PAGE_DOWN) 3168 && !element->window->ctrl && !element->window->alt) { 3169 UIFont *previousFont = UIFontActivate(code->font); 3170 int lineHeight = UIMeasureStringHeight(); 3171 3172 if (element->window->shift) { 3173 if (m->code == UI_KEYCODE_UP) { 3174 if (code->selection[3].line - 1 >= 0) { 3175 _UICodeSetVerticalMotionColumn(code, false); 3176 code->selection[3].line--; 3177 _UICodeSetVerticalMotionColumn(code, true); 3178 } 3179 } else if (m->code == UI_KEYCODE_DOWN) { 3180 if (code->selection[3].line + 1 < code->lineCount) { 3181 _UICodeSetVerticalMotionColumn(code, false); 3182 code->selection[3].line++; 3183 _UICodeSetVerticalMotionColumn(code, true); 3184 } 3185 } else if (m->code == UI_KEYCODE_PAGE_UP || m->code == UI_KEYCODE_PAGE_DOWN) { 3186 _UICodeSetVerticalMotionColumn(code, false); 3187 int pageHeight = (element->bounds.t - code->hScroll->e.bounds.t) / lineHeight * 4 / 5; 3188 code->selection[3].line += m->code == UI_KEYCODE_PAGE_UP ? pageHeight : -pageHeight; 3189 if (code->selection[3].line < 0) code->selection[3].line = 0; 3190 if (code->selection[3].line >= code->lineCount) code->selection[3].line = code->lineCount - 1; 3191 _UICodeSetVerticalMotionColumn(code, true); 3192 } 3193 3194 _UICodeUpdateSelection(code); 3195 } else { 3196 code->moveScrollToFocusNextLayout = false; 3197 _UI_KEY_INPUT_VSCROLL(code, lineHeight, (element->bounds.t - code->hScroll->e.bounds.t) * 4 / 5 /* leave a few lines for context */); 3198 } 3199 3200 UIFontActivate(previousFont); 3201 } else if ((m->code == UI_KEYCODE_HOME || m->code == UI_KEYCODE_END) && !element->window->alt) { 3202 if (element->window->shift) { 3203 if (m->code == UI_KEYCODE_HOME) { 3204 if (element->window->ctrl) code->selection[3].line = 0; 3205 code->selection[3].offset = 0; 3206 code->useVerticalMotionColumn = false; 3207 } else { 3208 if (element->window->ctrl) code->selection[3].line = code->lineCount - 1; 3209 code->selection[3].offset = code->lines[code->selection[3].line].bytes; 3210 code->useVerticalMotionColumn = false; 3211 } 3212 3213 _UICodeUpdateSelection(code); 3214 } else { 3215 code->vScroll->position = m->code == UI_KEYCODE_HOME ? 0 : code->vScroll->maximum; 3216 code->moveScrollToFocusNextLayout = false; 3217 UIElementRefresh(&code->e); 3218 } 3219 } else if ((m->code == UI_KEYCODE_LEFT || m->code == UI_KEYCODE_RIGHT) && !element->window->alt) { 3220 if (element->window->shift) { 3221 UICodeMoveCaret(code, m->code == UI_KEYCODE_LEFT, element->window->ctrl); 3222 } else if (!element->window->ctrl) { 3223 code->hScroll->position += m->code == UI_KEYCODE_LEFT ? -ui.activeFont->glyphWidth : ui.activeFont->glyphWidth; 3224 UIElementRefresh(&code->e); 3225 } else { 3226 return 0; 3227 } 3228 } else { 3229 return 0; 3230 } 3231 3232 return 1; 3233 } else if (message == UI_MSG_RIGHT_DOWN) { 3234 int hitTest = UICodeHitTest(code, element->window->cursorX, element->window->cursorY); 3235 3236 if (hitTest > 0 && (element->flags & UI_CODE_SELECTABLE)) { 3237 UIElementFocus(element); 3238 UIMenu *menu = UIMenuCreate(&element->window->e, UI_MENU_NO_SCROLL); 3239 UIMenuAddItem(menu, (code->selection[0].line == code->selection[1].line 3240 && code->selection[0].offset == code->selection[1].offset) ? UI_ELEMENT_DISABLED : 0, "Copy", -1, _UICodeCopyText, code); 3241 UIMenuShow(menu); 3242 } 3243 } else if (message == UI_MSG_UPDATE) { 3244 UIElementRepaint(element, NULL); 3245 } else if (message == UI_MSG_DEALLOCATE) { 3246 UI_FREE(code->content); 3247 UI_FREE(code->lines); 3248 } 3249 3250 return 0; 3251 } 3252 3253 void UICodeMoveCaret(UICode *code, bool backward, bool word) { 3254 while (true) { 3255 if (backward) { 3256 if (code->selection[3].offset - 1 < 0) { 3257 if (code->selection[3].line > 0) { 3258 code->selection[3].line--; 3259 code->selection[3].offset = code->lines[code->selection[3].line].bytes; 3260 } else break; 3261 } else _UI_MOVE_CARET_BACKWARD(code->selection[3].offset, code->content, code->lines[code->selection[3].line].offset + code->selection[3].offset, code->lines[code->selection[3].line].offset); 3262 } else { 3263 if (code->selection[3].offset + 1 > code->lines[code->selection[3].line].bytes) { 3264 if (code->selection[3].line + 1 < code->lineCount) { 3265 code->selection[3].line++; 3266 code->selection[3].offset = 0; 3267 } else break; 3268 } else _UI_MOVE_CARET_FORWARD(code->selection[3].offset, code->content, code->contentBytes, code->lines[code->selection[3].line].offset + code->selection[3].offset); 3269 } 3270 3271 if (!word) break; 3272 3273 if (code->selection[3].offset != 0 && code->selection[3].offset != code->lines[code->selection[3].line].bytes) { 3274 _UI_MOVE_CARET_BY_WORD(code->content, code->contentBytes, code->lines[code->selection[3].line].offset + code->selection[3].offset); 3275 } 3276 } 3277 3278 code->useVerticalMotionColumn = false; 3279 _UICodeUpdateSelection(code); 3280 } 3281 3282 void UICodeFocusLine(UICode *code, int index) { 3283 code->focused = index - 1; 3284 code->moveScrollToFocusNextLayout = true; 3285 UIElementRefresh(&code->e); 3286 } 3287 3288 void UICodeInsertContent(UICode *code, const char *content, ptrdiff_t byteCount, bool replace) { 3289 code->useVerticalMotionColumn = false; 3290 3291 UIFont *previousFont = UIFontActivate(code->font); 3292 3293 if (byteCount == -1) { 3294 byteCount = _UIStringLength(content); 3295 } 3296 3297 if (byteCount > 1000000000) { 3298 byteCount = 1000000000; 3299 } 3300 3301 if (replace) { 3302 UI_FREE(code->content); 3303 UI_FREE(code->lines); 3304 code->content = NULL; 3305 code->lines = NULL; 3306 code->contentBytes = 0; 3307 code->lineCount = 0; 3308 code->columns = 0; 3309 code->selection[0].line = code->selection[1].line = 0; 3310 code->selection[0].offset = code->selection[1].offset = 0; 3311 } 3312 3313 code->content = (char *) UI_REALLOC(code->content, code->contentBytes + byteCount); 3314 3315 if (!byteCount) { 3316 return; 3317 } 3318 3319 int lineCount = content[byteCount - 1] != '\n'; 3320 3321 for (int i = 0; i < byteCount; i++) { 3322 code->content[i + code->contentBytes] = content[i]; 3323 3324 if (content[i] == '\n') { 3325 lineCount++; 3326 } 3327 } 3328 3329 code->lines = (UICodeLine *) UI_REALLOC(code->lines, sizeof(UICodeLine) * (code->lineCount + lineCount)); 3330 int offset = 0, lineIndex = 0; 3331 3332 for (intptr_t i = 0; i <= byteCount && lineIndex < lineCount; i++) { 3333 if (content[i] == '\n' || i == byteCount) { 3334 UICodeLine line = { 0 }; 3335 line.offset = offset + code->contentBytes; 3336 line.bytes = i - offset; 3337 if (line.bytes > code->columns) code->columns = line.bytes; 3338 code->lines[code->lineCount + lineIndex] = line; 3339 lineIndex++; 3340 offset = i + 1; 3341 } 3342 } 3343 3344 code->lineCount += lineCount; 3345 code->contentBytes += byteCount; 3346 3347 if (!replace) { 3348 code->vScroll->position = code->lineCount * UIMeasureStringHeight(); 3349 } 3350 3351 UIFontActivate(previousFont); 3352 UIElementRepaint(&code->e, NULL); 3353 } 3354 3355 UICode *UICodeCreate(UIElement *parent, uint32_t flags) { 3356 UICode *code = (UICode *) UIElementCreate(sizeof(UICode), parent, flags, _UICodeMessage, "Code"); 3357 code->font = ui.activeFont; 3358 code->vScroll = UIScrollBarCreate(&code->e, 0); 3359 code->hScroll = UIScrollBarCreate(&code->e, UI_SCROLL_BAR_HORIZONTAL); 3360 code->focused = -1; 3361 code->tabSize = 4; 3362 return code; 3363 } 3364 3365 ///////////////////////////////////////// 3366 // Gauges. 3367 ///////////////////////////////////////// 3368 3369 int _UIGaugeMessage(UIElement *element, UIMessage message, int di, void *dp) { 3370 UIGauge *gauge = (UIGauge *) element; 3371 3372 if (message == UI_MSG_GET_HEIGHT) { 3373 return UI_SIZE_GAUGE_HEIGHT * element->window->scale; 3374 } else if (message == UI_MSG_GET_WIDTH) { 3375 return UI_SIZE_GAUGE_WIDTH * element->window->scale; 3376 } else if (message == UI_MSG_PAINT) { 3377 UIDrawControl((UIPainter *) dp, element->bounds, UI_DRAW_CONTROL_GAUGE | UI_DRAW_CONTROL_STATE_FROM_ELEMENT(element), 3378 NULL, 0, gauge->position, element->window->scale); 3379 } 3380 3381 return 0; 3382 } 3383 3384 void UIGaugeSetPosition(UIGauge *gauge, float position) { 3385 if (position == gauge->position) return; 3386 gauge->position = position; 3387 UIElementRepaint(&gauge->e, NULL); 3388 } 3389 3390 UIGauge *UIGaugeCreate(UIElement *parent, uint32_t flags) { 3391 return (UIGauge *) UIElementCreate(sizeof(UIGauge), parent, flags, _UIGaugeMessage, "Gauge"); 3392 } 3393 3394 ///////////////////////////////////////// 3395 // Sliders. 3396 ///////////////////////////////////////// 3397 3398 int _UISliderMessage(UIElement *element, UIMessage message, int di, void *dp) { 3399 UISlider *slider = (UISlider *) element; 3400 3401 if (message == UI_MSG_GET_HEIGHT) { 3402 return UI_SIZE_SLIDER_HEIGHT * element->window->scale; 3403 } else if (message == UI_MSG_GET_WIDTH) { 3404 return UI_SIZE_SLIDER_WIDTH * element->window->scale; 3405 } else if (message == UI_MSG_PAINT) { 3406 UIDrawControl((UIPainter *) dp, element->bounds, UI_DRAW_CONTROL_SLIDER | UI_DRAW_CONTROL_STATE_FROM_ELEMENT(element), 3407 NULL, 0, slider->position, element->window->scale); 3408 } else if (message == UI_MSG_LEFT_DOWN || (message == UI_MSG_MOUSE_DRAG && element->window->pressedButton == 1)) { 3409 UIRectangle bounds = element->bounds; 3410 int thumbSize = UI_SIZE_SLIDER_THUMB * element->window->scale; 3411 slider->position = (double) (element->window->cursorX - thumbSize / 2 - bounds.l) / (UI_RECT_WIDTH(bounds) - thumbSize); 3412 if (slider->steps > 1) slider->position = (int) (slider->position * (slider->steps - 1) + 0.5f) / (double) (slider->steps - 1); 3413 if (slider->position < 0) slider->position = 0; 3414 if (slider->position > 1) slider->position = 1; 3415 UIElementMessage(element, UI_MSG_VALUE_CHANGED, 0, 0); 3416 UIElementRepaint(element, NULL); 3417 } else if (message == UI_MSG_UPDATE) { 3418 UIElementRepaint(element, NULL); 3419 } 3420 3421 return 0; 3422 } 3423 3424 UISlider *UISliderCreate(UIElement *parent, uint32_t flags) { 3425 return (UISlider *) UIElementCreate(sizeof(UISlider), parent, flags, _UISliderMessage, "Slider"); 3426 } 3427 3428 ///////////////////////////////////////// 3429 // Tables. 3430 ///////////////////////////////////////// 3431 3432 int UITableHitTest(UITable *table, int x, int y) { 3433 x -= table->e.bounds.l; 3434 3435 if (x < 0 || x >= table->vScroll->e.bounds.l) { 3436 return -1; 3437 } 3438 3439 y -= (table->e.bounds.t + UI_SIZE_TABLE_HEADER * table->e.window->scale) - table->vScroll->position; 3440 3441 int rowHeight = UI_SIZE_TABLE_ROW * table->e.window->scale; 3442 3443 if (y < 0 || y >= rowHeight * table->itemCount) { 3444 return -1; 3445 } 3446 3447 return y / rowHeight; 3448 } 3449 3450 int UITableHeaderHitTest(UITable *table, int x, int y) { 3451 if (!table->columnCount) return -1; 3452 UIRectangle header = table->e.bounds; 3453 header.b = header.t + UI_SIZE_TABLE_HEADER * table->e.window->scale; 3454 header.l += UI_SIZE_TABLE_COLUMN_GAP * table->e.window->scale; 3455 int position = 0, index = 0; 3456 3457 while (true) { 3458 int end = position; 3459 for (; table->columns[end] != '\t' && table->columns[end]; end++); 3460 header.r = header.l + table->columnWidths[index]; 3461 if (UIRectangleContains(header, x, y)) return index; 3462 header.l += table->columnWidths[index] + UI_SIZE_TABLE_COLUMN_GAP * table->e.window->scale; 3463 if (table->columns[end] != '\t') break; 3464 position = end + 1, index++; 3465 } 3466 3467 return -1; 3468 } 3469 3470 bool UITableEnsureVisible(UITable *table, int index) { 3471 int rowHeight = UI_SIZE_TABLE_ROW * table->e.window->scale; 3472 int y = index * rowHeight; 3473 y -= table->vScroll->position; 3474 int height = UI_RECT_HEIGHT(table->e.bounds) - UI_SIZE_TABLE_HEADER * table->e.window->scale - rowHeight; 3475 3476 if (y < 0) { 3477 table->vScroll->position += y; 3478 UIElementRefresh(&table->e); 3479 return true; 3480 } else if (y > height) { 3481 table->vScroll->position -= height - y; 3482 UIElementRefresh(&table->e); 3483 return true; 3484 } else { 3485 return false; 3486 } 3487 } 3488 3489 void UITableResizeColumns(UITable *table) { 3490 int position = 0; 3491 int count = 0; 3492 3493 while (true) { 3494 int end = position; 3495 for (; table->columns[end] != '\t' && table->columns[end]; end++); 3496 count++; 3497 if (table->columns[end] == '\t') position = end + 1; 3498 else break; 3499 } 3500 3501 UI_FREE(table->columnWidths); 3502 table->columnWidths = (int *) UI_MALLOC(count * sizeof(int)); 3503 table->columnCount = count; 3504 3505 position = 0; 3506 3507 char buffer[256]; 3508 UITableGetItem m = { 0 }; 3509 m.buffer = buffer; 3510 m.bufferBytes = sizeof(buffer); 3511 3512 while (true) { 3513 int end = position; 3514 for (; table->columns[end] != '\t' && table->columns[end]; end++); 3515 3516 int longest = UIMeasureStringWidth(table->columns + position, end - position); 3517 3518 for (int i = 0; i < table->itemCount; i++) { 3519 m.index = i; 3520 int bytes = UIElementMessage(&table->e, UI_MSG_TABLE_GET_ITEM, 0, &m); 3521 int width = UIMeasureStringWidth(buffer, bytes); 3522 3523 if (width > longest) { 3524 longest = width; 3525 } 3526 } 3527 3528 table->columnWidths[m.column] = longest; 3529 m.column++; 3530 if (table->columns[end] == '\t') position = end + 1; 3531 else break; 3532 } 3533 3534 UIElementRepaint(&table->e, NULL); 3535 } 3536 3537 int _UITableMessage(UIElement *element, UIMessage message, int di, void *dp) { 3538 UITable *table = (UITable *) element; 3539 3540 if (message == UI_MSG_PAINT) { 3541 UIPainter *painter = (UIPainter *) dp; 3542 UIRectangle bounds = element->bounds; 3543 bounds.r = table->vScroll->e.bounds.l; 3544 UIDrawControl(painter, element->bounds, UI_DRAW_CONTROL_TABLE_BACKGROUND | UI_DRAW_CONTROL_STATE_FROM_ELEMENT(element), NULL, 0, 0, element->window->scale); 3545 char buffer[256]; 3546 UIRectangle row = bounds; 3547 int rowHeight = UI_SIZE_TABLE_ROW * element->window->scale; 3548 UITableGetItem m = { 0 }; 3549 m.buffer = buffer; 3550 m.bufferBytes = sizeof(buffer); 3551 row.t += UI_SIZE_TABLE_HEADER * table->e.window->scale; 3552 row.t -= (int64_t) table->vScroll->position % rowHeight; 3553 int hovered = UITableHitTest(table, element->window->cursorX, element->window->cursorY); 3554 UIRectangle oldClip = painter->clip; 3555 painter->clip = UIRectangleIntersection(oldClip, UI_RECT_4(bounds.l, bounds.r, 3556 bounds.t + (int) (UI_SIZE_TABLE_HEADER * element->window->scale), bounds.b)); 3557 3558 for (int i = table->vScroll->position / rowHeight; i < table->itemCount; i++) { 3559 if (row.t > painter->clip.b) { 3560 break; 3561 } 3562 3563 row.b = row.t + rowHeight; 3564 m.index = i; 3565 m.isSelected = false; 3566 m.column = 0; 3567 int bytes = UIElementMessage(element, UI_MSG_TABLE_GET_ITEM, 0, &m); 3568 3569 uint32_t rowFlags = (m.isSelected ? UI_DRAW_CONTROL_STATE_SELECTED : 0) | (hovered == i ? UI_DRAW_CONTROL_STATE_HOVERED : 0); 3570 UIDrawControl(painter, row, UI_DRAW_CONTROL_TABLE_ROW | rowFlags, NULL, 0, 0, element->window->scale); 3571 3572 UIRectangle cell = row; 3573 cell.l += UI_SIZE_TABLE_COLUMN_GAP * table->e.window->scale - (int64_t) table->hScroll->position; 3574 3575 for (int j = 0; j < table->columnCount; j++) { 3576 if (j) { 3577 m.column = j; 3578 bytes = UIElementMessage(element, UI_MSG_TABLE_GET_ITEM, 0, &m); 3579 } 3580 3581 cell.r = cell.l + table->columnWidths[j]; 3582 if ((size_t) bytes > m.bufferBytes && bytes > 0) bytes = m.bufferBytes; 3583 UIDrawControl(painter, cell, UI_DRAW_CONTROL_TABLE_CELL | rowFlags, buffer, bytes, 0, element->window->scale); 3584 cell.l += table->columnWidths[j] + UI_SIZE_TABLE_COLUMN_GAP * table->e.window->scale; 3585 } 3586 3587 row.t += rowHeight; 3588 } 3589 3590 bounds = element->bounds; 3591 painter->clip = UIRectangleIntersection(oldClip, bounds); 3592 if (table->hScroll) bounds.l -= (int64_t) table->hScroll->position; 3593 3594 UIRectangle header = bounds; 3595 header.b = header.t + UI_SIZE_TABLE_HEADER * table->e.window->scale; 3596 header.l += UI_SIZE_TABLE_COLUMN_GAP * table->e.window->scale; 3597 3598 int position = 0; 3599 int index = 0; 3600 3601 if (table->columnCount) { 3602 while (true) { 3603 int end = position; 3604 for (; table->columns[end] != '\t' && table->columns[end]; end++); 3605 3606 header.r = header.l + table->columnWidths[index]; 3607 UIDrawControl(painter, header, UI_DRAW_CONTROL_TABLE_HEADER | (index == table->columnHighlight ? UI_DRAW_CONTROL_STATE_SELECTED : 0), 3608 table->columns + position, end - position, 0, element->window->scale); 3609 header.l += table->columnWidths[index] + UI_SIZE_TABLE_COLUMN_GAP * table->e.window->scale; 3610 3611 if (table->columns[end] == '\t') { 3612 position = end + 1; 3613 index++; 3614 } else { 3615 break; 3616 } 3617 } 3618 } 3619 } else if (message == UI_MSG_LAYOUT) { 3620 int scrollBarSize = UI_SIZE_SCROLL_BAR * table->e.window->scale; 3621 int columnGap = UI_SIZE_TABLE_COLUMN_GAP * table->e.window->scale; 3622 3623 table->vScroll->maximum = table->itemCount * UI_SIZE_TABLE_ROW * element->window->scale; 3624 table->hScroll->maximum = columnGap; 3625 for (int i = 0; i < table->columnCount; i++) { table->hScroll->maximum += table->columnWidths[i] + columnGap; } 3626 3627 int vSpace = table->vScroll->page = UI_RECT_HEIGHT(element->bounds) - UI_SIZE_TABLE_HEADER * element->window->scale; 3628 int hSpace = table->hScroll->page = UI_RECT_WIDTH(element->bounds); 3629 _UI_LAYOUT_SCROLL_BAR_PAIR(table); 3630 } else if (message == UI_MSG_MOUSE_MOVE || message == UI_MSG_UPDATE) { 3631 UIElementRepaint(element, NULL); 3632 } else if (message == UI_MSG_SCROLLED) { 3633 UIElementRefresh(element); 3634 } else if (message == UI_MSG_MOUSE_WHEEL) { 3635 return UIElementMessage(&table->vScroll->e, message, di, dp); 3636 } else if (message == UI_MSG_LEFT_DOWN) { 3637 UIElementFocus(element); 3638 } else if (message == UI_MSG_KEY_TYPED) { 3639 UIKeyTyped *m = (UIKeyTyped *) dp; 3640 3641 if ((m->code == UI_KEYCODE_UP || m->code == UI_KEYCODE_DOWN || m->code == UI_KEYCODE_PAGE_UP || m->code == UI_KEYCODE_PAGE_DOWN 3642 || m->code == UI_KEYCODE_HOME || m->code == UI_KEYCODE_END) 3643 && !element->window->ctrl && !element->window->alt && !element->window->shift) { 3644 _UI_KEY_INPUT_VSCROLL(table, UI_SIZE_TABLE_ROW * element->window->scale, 3645 (element->bounds.t - table->hScroll->e.bounds.t + UI_SIZE_TABLE_HEADER) * 4 / 5); 3646 return 1; 3647 } else if ((m->code == UI_KEYCODE_LEFT || m->code == UI_KEYCODE_RIGHT) 3648 && !element->window->ctrl && !element->window->alt && !element->window->shift) { 3649 table->hScroll->position += m->code == UI_KEYCODE_LEFT ? -ui.activeFont->glyphWidth : ui.activeFont->glyphWidth; 3650 UIElementRefresh(&table->e); 3651 return 1; 3652 } 3653 } else if (message == UI_MSG_DEALLOCATE) { 3654 UI_FREE(table->columns); 3655 UI_FREE(table->columnWidths); 3656 } 3657 3658 return 0; 3659 } 3660 3661 UITable *UITableCreate(UIElement *parent, uint32_t flags, const char *columns) { 3662 UITable *table = (UITable *) UIElementCreate(sizeof(UITable), parent, flags, _UITableMessage, "Table"); 3663 table->vScroll = UIScrollBarCreate(&table->e, 0); 3664 table->hScroll = UIScrollBarCreate(&table->e, UI_SCROLL_BAR_HORIZONTAL); 3665 table->columns = UIStringCopy(columns, -1); 3666 table->columnHighlight = -1; 3667 return table; 3668 } 3669 3670 ///////////////////////////////////////// 3671 // Textboxes. 3672 ///////////////////////////////////////// 3673 3674 int _UITextboxByteToColumn(const char *string, int byte, ptrdiff_t bytes) { 3675 return _UIByteToColumn(string, byte, bytes, 4); 3676 } 3677 3678 int _UITextboxColumnToByte(const char *string, int column, ptrdiff_t bytes) { 3679 return _UIColumnToByte(string, column, bytes, 4); 3680 } 3681 3682 char *UITextboxToCString(UITextbox *textbox) { 3683 char *buffer = (char *) UI_MALLOC(textbox->bytes + 1); 3684 3685 for (intptr_t i = 0; i < textbox->bytes; i++) { 3686 buffer[i] = textbox->string[i]; 3687 } 3688 3689 buffer[textbox->bytes] = 0; 3690 return buffer; 3691 } 3692 3693 void UITextboxReplace(UITextbox *textbox, const char *text, ptrdiff_t bytes, bool sendChangedMessage) { 3694 if (bytes == -1) bytes = _UIStringLength(text); 3695 int deleteFrom = textbox->carets[0], deleteTo = textbox->carets[1]; 3696 if (deleteFrom > deleteTo) UI_SWAP(int, deleteFrom, deleteTo); 3697 3698 UI_MEMMOVE(&textbox->string[deleteFrom], &textbox->string[deleteTo], textbox->bytes - deleteTo); 3699 textbox->bytes -= deleteTo - deleteFrom; 3700 textbox->string = (char *) UI_REALLOC(textbox->string, textbox->bytes + bytes); 3701 UI_MEMMOVE(&textbox->string[deleteFrom + bytes], &textbox->string[deleteFrom], textbox->bytes - deleteFrom); 3702 UI_MEMMOVE(&textbox->string[deleteFrom], &text[0], bytes); 3703 textbox->bytes += bytes; 3704 textbox->carets[0] = deleteFrom + bytes; 3705 textbox->carets[1] = textbox->carets[0]; 3706 3707 if (sendChangedMessage) UIElementMessage(&textbox->e, UI_MSG_VALUE_CHANGED, 0, 0); 3708 textbox->e.window->textboxModifiedFlag = true; 3709 UIElementRepaint(&textbox->e, NULL); 3710 } 3711 3712 void UITextboxClear(UITextbox *textbox, bool sendChangedMessage) { 3713 textbox->carets[1] = 0; 3714 textbox->carets[0] = textbox->bytes; 3715 UITextboxReplace(textbox, "", 0, sendChangedMessage); 3716 } 3717 3718 void UITextboxMoveCaret(UITextbox *textbox, bool backward, bool word) { 3719 while (true) { 3720 if (textbox->carets[0] > 0 && backward) { 3721 _UI_MOVE_CARET_BACKWARD(textbox->carets[0], textbox->string, textbox->carets[0], 0); 3722 } else if (textbox->carets[0] < textbox->bytes && !backward) { 3723 _UI_MOVE_CARET_FORWARD(textbox->carets[0], textbox->string, textbox->bytes, textbox->carets[0]); 3724 } else { 3725 return; 3726 } 3727 3728 if (!word) { 3729 return; 3730 } else if (textbox->carets[0] != textbox->bytes && textbox->carets[0] != 0) { 3731 _UI_MOVE_CARET_BY_WORD(textbox->string, textbox->bytes, textbox->carets[0]); 3732 } 3733 } 3734 3735 UIElementRepaint(&textbox->e, NULL); 3736 } 3737 3738 void _UITextboxCopyText(void *cp) { 3739 UITextbox *textbox = (UITextbox *) cp; 3740 3741 int to = textbox->carets[0] > textbox->carets[1] ? textbox->carets[0] : textbox->carets[1]; 3742 int from = textbox->carets[0] < textbox->carets[1] ? textbox->carets[0] : textbox->carets[1]; 3743 3744 if (from != to) { 3745 char *pasteText = (char *) UI_CALLOC(to - from + 1); 3746 for (int i = from; i < to; i++) pasteText[i - from] = textbox->string[i]; 3747 _UIClipboardWriteText(textbox->e.window, pasteText); 3748 } 3749 } 3750 3751 void _UITextboxPasteText(void *cp) { 3752 UITextbox *textbox = (UITextbox *) cp; 3753 size_t bytes; 3754 char *text = _UIClipboardReadTextStart(textbox->e.window, &bytes); 3755 3756 if (text) { 3757 for (size_t i = 0; i < bytes; i++) { 3758 if (text[i] == '\n') text[i] = ' '; 3759 } 3760 3761 UITextboxReplace(textbox, text, bytes, true); 3762 } 3763 3764 _UIClipboardReadTextEnd(textbox->e.window, text); 3765 } 3766 3767 int _UITextboxMessage(UIElement *element, UIMessage message, int di, void *dp) { 3768 UITextbox *textbox = (UITextbox *) element; 3769 3770 if (message == UI_MSG_GET_HEIGHT) { 3771 return UI_SIZE_TEXTBOX_HEIGHT * element->window->scale; 3772 } else if (message == UI_MSG_GET_WIDTH) { 3773 return UI_SIZE_TEXTBOX_WIDTH * element->window->scale; 3774 } else if (message == UI_MSG_PAINT) { 3775 UIDrawControl((UIPainter *) dp, element->bounds, UI_DRAW_CONTROL_TEXTBOX | UI_DRAW_CONTROL_STATE_FROM_ELEMENT(element), 3776 NULL, 0, 0, element->window->scale); 3777 3778 int scaledMargin = UI_SIZE_TEXTBOX_MARGIN * element->window->scale; 3779 int totalWidth = UIMeasureStringWidth(textbox->string, textbox->bytes) + scaledMargin * 2; 3780 UIRectangle textBounds = UIRectangleAdd(element->bounds, UI_RECT_1I(scaledMargin)); 3781 3782 if (textbox->scroll > totalWidth - UI_RECT_WIDTH(textBounds)) { 3783 textbox->scroll = totalWidth - UI_RECT_WIDTH(textBounds); 3784 } 3785 3786 if (textbox->scroll < 0) { 3787 textbox->scroll = 0; 3788 } 3789 3790 int caretX = UIMeasureStringWidth(textbox->string, textbox->carets[0]) - textbox->scroll; 3791 3792 if (caretX < 0) { 3793 textbox->scroll = caretX + textbox->scroll; 3794 } else if (caretX > UI_RECT_WIDTH(textBounds)) { 3795 textbox->scroll = caretX - UI_RECT_WIDTH(textBounds) + textbox->scroll + 1; 3796 } 3797 3798 #ifdef __cplusplus 3799 UIStringSelection selection = {}; 3800 #else 3801 UIStringSelection selection = { 0 }; 3802 #endif 3803 selection.carets[0] = _UITextboxByteToColumn(textbox->string, textbox->carets[0], textbox->bytes); 3804 selection.carets[1] = _UITextboxByteToColumn(textbox->string, textbox->carets[1], textbox->bytes); 3805 selection.colorBackground = ui.theme.selected; 3806 selection.colorText = ui.theme.textSelected; 3807 textBounds.l -= textbox->scroll; 3808 3809 UIDrawString((UIPainter *) dp, textBounds, textbox->string, textbox->bytes, 3810 (element->flags & UI_ELEMENT_DISABLED) ? ui.theme.textDisabled : ui.theme.text, UI_ALIGN_LEFT, 3811 element->window->focused == element ? &selection : NULL); 3812 } else if (message == UI_MSG_GET_CURSOR) { 3813 return UI_CURSOR_TEXT; 3814 } else if (message == UI_MSG_LEFT_DOWN) { 3815 int column = (element->window->cursorX - element->bounds.l + textbox->scroll - UI_SIZE_TEXTBOX_MARGIN * element->window->scale 3816 + ui.activeFont->glyphWidth / 2) / ui.activeFont->glyphWidth; 3817 textbox->carets[0] = textbox->carets[1] = column <= 0 ? 0 : _UITextboxColumnToByte(textbox->string, column, textbox->bytes); 3818 UIElementFocus(element); 3819 } else if (message == UI_MSG_UPDATE) { 3820 UIElementRepaint(element, NULL); 3821 } else if (message == UI_MSG_DEALLOCATE) { 3822 UI_FREE(textbox->string); 3823 } else if (message == UI_MSG_KEY_TYPED) { 3824 UIKeyTyped *m = (UIKeyTyped *) dp; 3825 bool handled = true; 3826 3827 if (textbox->rejectNextKey) { 3828 textbox->rejectNextKey = false; 3829 handled = false; 3830 } else if (m->code == UI_KEYCODE_BACKSPACE || m->code == UI_KEYCODE_DELETE) { 3831 if (textbox->carets[0] == textbox->carets[1]) { 3832 UITextboxMoveCaret(textbox, m->code == UI_KEYCODE_BACKSPACE, element->window->ctrl); 3833 } 3834 3835 UITextboxReplace(textbox, NULL, 0, true); 3836 } else if (m->code == UI_KEYCODE_LEFT || m->code == UI_KEYCODE_RIGHT) { 3837 if (textbox->carets[0] == textbox->carets[1] || element->window->shift) { 3838 UITextboxMoveCaret(textbox, m->code == UI_KEYCODE_LEFT, element->window->ctrl); 3839 if (!element->window->shift) textbox->carets[1] = textbox->carets[0]; 3840 } else { 3841 textbox->carets[1 - element->window->shift] = textbox->carets[element->window->shift]; 3842 } 3843 } else if (m->code == UI_KEYCODE_HOME || m->code == UI_KEYCODE_END) { 3844 if (m->code == UI_KEYCODE_HOME) { 3845 textbox->carets[0] = 0; 3846 } else { 3847 textbox->carets[0] = textbox->bytes; 3848 } 3849 3850 if (!element->window->shift) { 3851 textbox->carets[1] = textbox->carets[0]; 3852 } 3853 } else if (m->code == UI_KEYCODE_LETTER('A') && element->window->ctrl) { 3854 textbox->carets[1] = 0; 3855 textbox->carets[0] = textbox->bytes; 3856 } else if (m->textBytes && !element->window->alt && !element->window->ctrl && m->text[0] >= 0x20) { 3857 UITextboxReplace(textbox, m->text, m->textBytes, true); 3858 } else if ((m->code == UI_KEYCODE_LETTER('C') || m->code == UI_KEYCODE_LETTER('X') || m->code == UI_KEYCODE_INSERT) 3859 && element->window->ctrl && !element->window->alt && !element->window->shift) { 3860 _UITextboxCopyText(textbox); 3861 3862 if (m->code == UI_KEYCODE_LETTER('X')) { 3863 UITextboxReplace(textbox, NULL, 0, true); 3864 } 3865 } else if ((m->code == UI_KEYCODE_LETTER('V') && element->window->ctrl && !element->window->alt && !element->window->shift) 3866 || (m->code == UI_KEYCODE_INSERT && !element->window->ctrl && !element->window->alt && element->window->shift)) { 3867 _UITextboxPasteText(textbox); 3868 } else { 3869 handled = false; 3870 } 3871 3872 if (handled) { 3873 UIElementRepaint(element, NULL); 3874 return 1; 3875 } 3876 } else if (message == UI_MSG_RIGHT_DOWN) { 3877 int c0 = textbox->carets[0], c1 = textbox->carets[1]; 3878 _UITextboxMessage(element, UI_MSG_LEFT_DOWN, di, dp); 3879 3880 if (c0 < c1 ? (textbox->carets[0] >= c0 && textbox->carets[0] < c1) : (textbox->carets[0] >= c1 && textbox->carets[0] < c0)) { 3881 textbox->carets[0] = c0, textbox->carets[1] = c1; // Only move caret if clicking outside the existing selection. 3882 } 3883 3884 UIMenu *menu = UIMenuCreate(&element->window->e, UI_MENU_NO_SCROLL); 3885 UIMenuAddItem(menu, textbox->carets[0] == textbox->carets[1] ? UI_ELEMENT_DISABLED : 0, "Copy", -1, _UITextboxCopyText, textbox); 3886 size_t pasteBytes; 3887 char *paste = _UIClipboardReadTextStart(textbox->e.window, &pasteBytes); 3888 UIMenuAddItem(menu, !paste || !pasteBytes ? UI_ELEMENT_DISABLED : 0, "Paste", -1, _UITextboxPasteText, textbox); 3889 _UIClipboardReadTextEnd(textbox->e.window, paste); 3890 UIMenuShow(menu); 3891 } 3892 3893 return 0; 3894 } 3895 3896 UITextbox *UITextboxCreate(UIElement *parent, uint32_t flags) { 3897 return (UITextbox *) UIElementCreate(sizeof(UITextbox), parent, flags | UI_ELEMENT_TAB_STOP, _UITextboxMessage, "Textbox"); 3898 } 3899 3900 ///////////////////////////////////////// 3901 // MDI clients. 3902 ///////////////////////////////////////// 3903 3904 int _UIMDIChildHitTest(UIMDIChild *mdiChild, int x, int y) { 3905 UIElement *element = &mdiChild->e; 3906 UI_MDI_CHILD_CALCULATE_LAYOUT(element->bounds, element->window->scale); 3907 int cornerSize = UI_SIZE_MDI_CHILD_CORNER * element->window->scale; 3908 if (!UIRectangleContains(element->bounds, x, y) || UIRectangleContains(content, x, y)) return -1; 3909 else if (x < element->bounds.l + cornerSize && y < element->bounds.t + cornerSize) return 0b1010; 3910 else if (x > element->bounds.r - cornerSize && y < element->bounds.t + cornerSize) return 0b0110; 3911 else if (x < element->bounds.l + cornerSize && y > element->bounds.b - cornerSize) return 0b1001; 3912 else if (x > element->bounds.r - cornerSize && y > element->bounds.b - cornerSize) return 0b0101; 3913 else if (x < element->bounds.l + borderSize) return 0b1000; 3914 else if (x > element->bounds.r - borderSize) return 0b0100; 3915 else if (y < element->bounds.t + borderSize) return 0b0010; 3916 else if (y > element->bounds.b - borderSize) return 0b0001; 3917 else if (UIRectangleContains(title, x, y)) return 0b1111; 3918 else return -1; 3919 } 3920 3921 void _UIMDIChildCloseButton(void *_child) { 3922 UIElement *child = (UIElement *) _child; 3923 3924 if (!UIElementMessage(child, UI_MSG_WINDOW_CLOSE, 0, 0)) { 3925 UIElementDestroy(child); 3926 UIElementRefresh(child->parent); 3927 } 3928 } 3929 3930 int _UIMDIChildMessage(UIElement *element, UIMessage message, int di, void *dp) { 3931 UIMDIChild *mdiChild = (UIMDIChild *) element; 3932 3933 if (message == UI_MSG_PAINT) { 3934 UIDrawControl((UIPainter *) dp, element->bounds, UI_DRAW_CONTROL_MDI_CHILD, mdiChild->title, mdiChild->titleBytes, 0, element->window->scale); 3935 } else if (message == UI_MSG_GET_WIDTH) { 3936 UIElement *child = element->childCount ? element->children[element->childCount - 1] : NULL; 3937 int width = 2 * UI_SIZE_MDI_CHILD_BORDER; 3938 width += (child ? UIElementMessage(child, message, di ? (di - UI_SIZE_MDI_CHILD_TITLE + UI_SIZE_MDI_CHILD_BORDER) : 0, dp) : 0); 3939 if (width < UI_SIZE_MDI_CHILD_MINIMUM_WIDTH) width = UI_SIZE_MDI_CHILD_MINIMUM_WIDTH; 3940 return width; 3941 } else if (message == UI_MSG_GET_HEIGHT) { 3942 UIElement *child = element->childCount ? element->children[element->childCount - 1] : NULL; 3943 int height = UI_SIZE_MDI_CHILD_TITLE + UI_SIZE_MDI_CHILD_BORDER; 3944 height += (child ? UIElementMessage(child, message, di ? (di - 2 * UI_SIZE_MDI_CHILD_BORDER) : 0, dp) : 0); 3945 if (height < UI_SIZE_MDI_CHILD_MINIMUM_HEIGHT) height = UI_SIZE_MDI_CHILD_MINIMUM_HEIGHT; 3946 return height; 3947 } else if (message == UI_MSG_LAYOUT) { 3948 UI_MDI_CHILD_CALCULATE_LAYOUT(element->bounds, element->window->scale); 3949 3950 int position = title.r; 3951 3952 for (uint32_t i = 0; i < element->childCount - 1; i++) { 3953 UIElement *child = element->children[i]; 3954 int width = UIElementMessage(child, UI_MSG_GET_WIDTH, 0, 0); 3955 UIElementMove(child, UI_RECT_4(position - width, position, title.t, title.b), false); 3956 position -= width; 3957 } 3958 3959 UIElement *child = element->childCount ? element->children[element->childCount - 1] : NULL; 3960 3961 if (child) { 3962 UIElementMove(child, content, false); 3963 } 3964 } else if (message == UI_MSG_GET_CURSOR) { 3965 int hitTest = _UIMDIChildHitTest(mdiChild, element->window->cursorX, element->window->cursorY); 3966 if (hitTest == 0b1000) return UI_CURSOR_RESIZE_LEFT; 3967 if (hitTest == 0b0010) return UI_CURSOR_RESIZE_UP; 3968 if (hitTest == 0b0110) return UI_CURSOR_RESIZE_UP_RIGHT; 3969 if (hitTest == 0b1010) return UI_CURSOR_RESIZE_UP_LEFT; 3970 if (hitTest == 0b0100) return UI_CURSOR_RESIZE_RIGHT; 3971 if (hitTest == 0b0001) return UI_CURSOR_RESIZE_DOWN; 3972 if (hitTest == 0b1001) return UI_CURSOR_RESIZE_DOWN_LEFT; 3973 if (hitTest == 0b0101) return UI_CURSOR_RESIZE_DOWN_RIGHT; 3974 return UI_CURSOR_ARROW; 3975 } else if (message == UI_MSG_LEFT_DOWN) { 3976 mdiChild->dragHitTest = _UIMDIChildHitTest(mdiChild, element->window->cursorX, element->window->cursorY); 3977 mdiChild->dragOffset = UIRectangleAdd(element->bounds, UI_RECT_2(-element->window->cursorX, -element->window->cursorY)); 3978 } else if (message == UI_MSG_LEFT_UP) { 3979 if (mdiChild->bounds.l < 0) mdiChild->bounds.r -= mdiChild->bounds.l, mdiChild->bounds.l = 0; 3980 if (mdiChild->bounds.t < 0) mdiChild->bounds.b -= mdiChild->bounds.t, mdiChild->bounds.t = 0; 3981 UIElementRefresh(element->parent); 3982 } else if (message == UI_MSG_MOUSE_DRAG) { 3983 if (mdiChild->dragHitTest > 0) { 3984 #define _UI_MDI_CHILD_MOVE_EDGE(bit, edge, cursor, size, opposite, negate, minimum, offset) \ 3985 if (mdiChild->dragHitTest & bit) mdiChild->bounds.edge = mdiChild->dragOffset.edge + element->window->cursor - element->parent->bounds.offset; \ 3986 if ((mdiChild->dragHitTest & bit) && size(mdiChild->bounds) < minimum) mdiChild->bounds.edge = mdiChild->bounds.opposite negate minimum; 3987 _UI_MDI_CHILD_MOVE_EDGE(0b1000, l, cursorX, UI_RECT_WIDTH, r, -, UI_SIZE_MDI_CHILD_MINIMUM_WIDTH, l); 3988 _UI_MDI_CHILD_MOVE_EDGE(0b0100, r, cursorX, UI_RECT_WIDTH, l, +, UI_SIZE_MDI_CHILD_MINIMUM_WIDTH, l); 3989 _UI_MDI_CHILD_MOVE_EDGE(0b0010, t, cursorY, UI_RECT_HEIGHT, b, -, UI_SIZE_MDI_CHILD_MINIMUM_HEIGHT, t); 3990 _UI_MDI_CHILD_MOVE_EDGE(0b0001, b, cursorY, UI_RECT_HEIGHT, t, +, UI_SIZE_MDI_CHILD_MINIMUM_HEIGHT, t); 3991 UIElementRefresh(element->parent); 3992 } 3993 } else if (message == UI_MSG_DESTROY) { 3994 UIMDIClient *client = (UIMDIClient *) element->parent; 3995 3996 if (client->active == mdiChild) { 3997 client->active = (UIMDIChild *) (client->e.childCount == 1 ? NULL : client->e.children[client->e.childCount - 2]); 3998 } 3999 } else if (message == UI_MSG_DEALLOCATE) { 4000 UI_FREE(mdiChild->title); 4001 } 4002 4003 return 0; 4004 } 4005 4006 int _UIMDIClientMessage(UIElement *element, UIMessage message, int di, void *dp) { 4007 UIMDIClient *client = (UIMDIClient *) element; 4008 4009 if (message == UI_MSG_PAINT) { 4010 if (~element->flags & UI_MDI_CLIENT_TRANSPARENT) { 4011 UIDrawBlock((UIPainter *) dp, element->bounds, ui.theme.panel2); 4012 } 4013 } else if (message == UI_MSG_LAYOUT) { 4014 for (uint32_t i = 0; i < element->childCount; i++) { 4015 UIMDIChild *mdiChild = (UIMDIChild *) element->children[i]; 4016 UI_ASSERT(mdiChild->e.messageClass == _UIMDIChildMessage); 4017 4018 if (UIRectangleEquals(mdiChild->bounds, UI_RECT_1(0))) { 4019 int width = UIElementMessage(&mdiChild->e, UI_MSG_GET_WIDTH, 0, 0); 4020 int height = UIElementMessage(&mdiChild->e, UI_MSG_GET_HEIGHT, width, 0); 4021 if (client->cascade + width > element->bounds.r || client->cascade + height > element->bounds.b) client->cascade = 0; 4022 mdiChild->bounds = UI_RECT_4(client->cascade, client->cascade + width, client->cascade, client->cascade + height); 4023 client->cascade += UI_SIZE_MDI_CASCADE * element->window->scale; 4024 } 4025 4026 UIRectangle bounds = UIRectangleAdd(mdiChild->bounds, UI_RECT_2(element->bounds.l, element->bounds.t)); 4027 UIElementMove(&mdiChild->e, bounds, false); 4028 } 4029 } else if (message == UI_MSG_PRESSED_DESCENDENT) { 4030 UIMDIChild *child = (UIMDIChild *) dp; 4031 4032 if (child && child != client->active) { 4033 for (uint32_t i = 0; i < element->childCount; i++) { 4034 if (element->children[i] == &child->e) { 4035 UI_MEMMOVE(&element->children[i], &element->children[i + 1], sizeof(UIElement *) * (element->childCount - i - 1)); 4036 element->children[element->childCount - 1] = &child->e; 4037 break; 4038 } 4039 } 4040 4041 client->active = child; 4042 UIElementRefresh(element); 4043 } 4044 } 4045 4046 return 0; 4047 } 4048 4049 UIMDIChild *UIMDIChildCreate(UIElement *parent, uint32_t flags, UIRectangle initialBounds, const char *title, ptrdiff_t titleBytes) { 4050 UI_ASSERT(parent->messageClass == _UIMDIClientMessage); 4051 4052 UIMDIChild *mdiChild = (UIMDIChild *) UIElementCreate(sizeof(UIMDIChild), parent, flags, _UIMDIChildMessage, "MDIChild"); 4053 UIMDIClient *mdiClient = (UIMDIClient *) parent; 4054 4055 mdiChild->bounds = initialBounds; 4056 mdiChild->title = UIStringCopy(title, (mdiChild->titleBytes = titleBytes)); 4057 mdiClient->active = mdiChild; 4058 4059 if (flags & UI_MDI_CHILD_CLOSE_BUTTON) { 4060 UIButton *closeButton = UIButtonCreate(&mdiChild->e, UI_BUTTON_SMALL | UI_ELEMENT_NON_CLIENT, "X", 1); 4061 closeButton->invoke = _UIMDIChildCloseButton; 4062 closeButton->e.cp = mdiChild; 4063 } 4064 4065 return mdiChild; 4066 } 4067 4068 UIMDIClient *UIMDIClientCreate(UIElement *parent, uint32_t flags) { 4069 return (UIMDIClient *) UIElementCreate(sizeof(UIMDIClient), parent, flags, _UIMDIClientMessage, "MDIClient"); 4070 } 4071 4072 ///////////////////////////////////////// 4073 // Image displays. 4074 ///////////////////////////////////////// 4075 4076 void _UIImageDisplayUpdateViewport(UIImageDisplay *display) { 4077 UIRectangle bounds = display->e.bounds; 4078 bounds.r -= bounds.l, bounds.b -= bounds.t; 4079 4080 float minimumZoomX = 1, minimumZoomY = 1; 4081 if (display->width > bounds.r) minimumZoomX = (float) bounds.r / display->width; 4082 if (display->height > bounds.b) minimumZoomY = (float) bounds.b / display->height; 4083 float minimumZoom = minimumZoomX < minimumZoomY ? minimumZoomX : minimumZoomY; 4084 4085 if (display->zoom < minimumZoom || (display->e.flags & _UI_IMAGE_DISPLAY_ZOOM_FIT)) { 4086 display->zoom = minimumZoom; 4087 display->e.flags |= _UI_IMAGE_DISPLAY_ZOOM_FIT; 4088 } 4089 4090 if (display->panX < 0) display->panX = 0; 4091 if (display->panY < 0) display->panY = 0; 4092 if (display->panX > display->width - bounds.r / display->zoom) display->panX = display->width - bounds.r / display->zoom; 4093 if (display->panY > display->height - bounds.b / display->zoom) display->panY = display->height - bounds.b / display->zoom; 4094 4095 if (bounds.r && display->width * display->zoom <= bounds.r) display->panX = display->width / 2 - bounds.r / display->zoom / 2; 4096 if (bounds.b && display->height * display->zoom <= bounds.b) display->panY = display->height / 2 - bounds.b / display->zoom / 2; 4097 } 4098 4099 int _UIImageDisplayMessage(UIElement *element, UIMessage message, int di, void *dp) { 4100 UIImageDisplay *display = (UIImageDisplay *) element; 4101 4102 if (message == UI_MSG_GET_HEIGHT) { 4103 return display->height; 4104 } else if (message == UI_MSG_GET_WIDTH) { 4105 return display->width; 4106 } else if (message == UI_MSG_DEALLOCATE) { 4107 UI_FREE(display->bits); 4108 } else if (message == UI_MSG_PAINT) { 4109 UIPainter *painter = (UIPainter *) dp; 4110 4111 int w = UI_RECT_WIDTH(element->bounds), h = UI_RECT_HEIGHT(element->bounds); 4112 int x = _UILinearMap(0, display->panX, display->panX + w / display->zoom, 0, w) + element->bounds.l; 4113 int y = _UILinearMap(0, display->panY, display->panY + h / display->zoom, 0, h) + element->bounds.t; 4114 4115 UIRectangle image = UI_RECT_4(x, x + (int) (display->width * display->zoom), y, (int) (y + display->height * display->zoom)); 4116 UIRectangle bounds = UIRectangleIntersection(painter->clip, UIRectangleIntersection(display->e.bounds, image)); 4117 if (!UI_RECT_VALID(bounds)) return 0; 4118 4119 if (display->zoom == 1) { 4120 uint32_t *lineStart = (uint32_t *) painter->bits + bounds.t * painter->width + bounds.l; 4121 uint32_t *sourceLineStart = display->bits + (bounds.l - image.l) + display->width * (bounds.t - image.t); 4122 4123 for (int i = 0; i < bounds.b - bounds.t; i++, lineStart += painter->width, sourceLineStart += display->width) { 4124 uint32_t *destination = lineStart; 4125 uint32_t *source = sourceLineStart; 4126 int j = bounds.r - bounds.l; 4127 4128 do { 4129 *destination = *source; 4130 destination++; 4131 source++; 4132 } while (--j); 4133 } 4134 } else { 4135 float zr = 1.0f / display->zoom; 4136 uint32_t *destination = (uint32_t *) painter->bits; 4137 4138 for (int i = bounds.t; i < bounds.b; i++) { 4139 int ty = (i - image.t) * zr; 4140 4141 for (int j = bounds.l; j < bounds.r; j++) { 4142 int tx = (j - image.l) * zr; 4143 destination[i * painter->width + j] = display->bits[ty * display->width + tx]; 4144 } 4145 } 4146 } 4147 } else if (message == UI_MSG_MOUSE_WHEEL && (element->flags & UI_IMAGE_DISPLAY_INTERACTIVE)) { 4148 display->e.flags &= ~_UI_IMAGE_DISPLAY_ZOOM_FIT; 4149 int divisions = -di / 72; 4150 float factor = 1; 4151 float perDivision = element->window->ctrl ? 2.0f : element->window->alt ? 1.01f : 1.2f; 4152 while (divisions > 0) factor *= perDivision, divisions--; 4153 while (divisions < 0) factor /= perDivision, divisions++; 4154 if (display->zoom * factor > 64) factor = 64 / display->zoom; 4155 int mx = element->window->cursorX - element->bounds.l; 4156 int my = element->window->cursorY - element->bounds.t; 4157 display->zoom *= factor; 4158 display->panX -= mx / display->zoom * (1 - factor); 4159 display->panY -= my / display->zoom * (1 - factor); 4160 _UIImageDisplayUpdateViewport(display); 4161 UIElementRepaint(&display->e, NULL); 4162 } else if (message == UI_MSG_LAYOUT && (element->flags & UI_IMAGE_DISPLAY_INTERACTIVE)) { 4163 UIRectangle bounds = display->e.bounds; 4164 bounds.r -= bounds.l, bounds.b -= bounds.t; 4165 display->panX -= (bounds.r - display->previousWidth ) / 2 / display->zoom; 4166 display->panY -= (bounds.b - display->previousHeight) / 2 / display->zoom; 4167 display->previousWidth = bounds.r, display->previousHeight = bounds.b; 4168 _UIImageDisplayUpdateViewport(display); 4169 } else if (message == UI_MSG_GET_CURSOR && (element->flags & UI_IMAGE_DISPLAY_INTERACTIVE) 4170 && (UI_RECT_WIDTH(element->bounds) < display->width * display->zoom 4171 || UI_RECT_HEIGHT(element->bounds) < display->height * display->zoom)) { 4172 return UI_CURSOR_HAND; 4173 } else if (message == UI_MSG_MOUSE_DRAG) { 4174 display->panX -= (element->window->cursorX - display->previousPanPointX) / display->zoom; 4175 display->panY -= (element->window->cursorY - display->previousPanPointY) / display->zoom; 4176 _UIImageDisplayUpdateViewport(display); 4177 display->previousPanPointX = element->window->cursorX; 4178 display->previousPanPointY = element->window->cursorY; 4179 UIElementRepaint(element, NULL); 4180 } else if (message == UI_MSG_LEFT_DOWN) { 4181 display->e.flags &= ~_UI_IMAGE_DISPLAY_ZOOM_FIT; 4182 display->previousPanPointX = element->window->cursorX; 4183 display->previousPanPointY = element->window->cursorY; 4184 } 4185 4186 return 0; 4187 } 4188 4189 void UIImageDisplaySetContent(UIImageDisplay *display, uint32_t *bits, size_t width, size_t height, size_t stride) { 4190 UI_FREE(display->bits); 4191 4192 display->bits = (uint32_t *) UI_MALLOC(width * height * 4); 4193 display->width = width; 4194 display->height = height; 4195 4196 uint32_t *destination = display->bits; 4197 uint32_t *source = bits; 4198 4199 for (uintptr_t row = 0; row < height; row++, source += stride / 4) { 4200 for (uintptr_t i = 0; i < width; i++) { 4201 *destination++ = source[i]; 4202 } 4203 } 4204 4205 UIElementMeasurementsChanged(&display->e, 3); 4206 UIElementRepaint(&display->e, NULL); 4207 } 4208 4209 UIImageDisplay *UIImageDisplayCreate(UIElement *parent, uint32_t flags, uint32_t *bits, size_t width, size_t height, size_t stride) { 4210 UIImageDisplay *display = (UIImageDisplay *) UIElementCreate(sizeof(UIImageDisplay), parent, flags, _UIImageDisplayMessage, "ImageDisplay"); 4211 display->zoom = 1.0f; 4212 UIImageDisplaySetContent(display, bits, width, height, stride); 4213 return display; 4214 } 4215 4216 ///////////////////////////////////////// 4217 // Modal dialogs. 4218 ///////////////////////////////////////// 4219 4220 int _UIDialogWrapperMessage(UIElement *element, UIMessage message, int di, void *dp) { 4221 if (message == UI_MSG_LAYOUT) { 4222 int width = UIElementMessage(element->children[0], UI_MSG_GET_WIDTH, 0, 0); 4223 int height = UIElementMessage(element->children[0], UI_MSG_GET_HEIGHT, width, 0); 4224 int cx = (element->bounds.l + element->bounds.r) / 2; 4225 int cy = (element->bounds.t + element->bounds.b) / 2; 4226 UIRectangle bounds = UI_RECT_4(cx - (width + 1) / 2, cx + width / 2, cy - (height + 1) / 2, cy + height / 2); 4227 UIElementMove(element->children[0], bounds, false); 4228 UIElementRepaint(element, NULL); 4229 } else if (message == UI_MSG_PAINT) { 4230 UIDrawControl((UIPainter *) dp, element->children[0]->bounds, UI_DRAW_CONTROL_MODAL_POPUP, NULL, 0, 0, element->window->scale); 4231 } else if (message == UI_MSG_KEY_TYPED) { 4232 UIKeyTyped *typed = (UIKeyTyped *) dp; 4233 4234 if (element->window->ctrl) return 0; 4235 if (element->window->shift) return 0; 4236 4237 if (!ui.dialogCanExit) { 4238 } else if (!element->window->alt && typed->code == UI_KEYCODE_ESCAPE) { 4239 ui.dialogResult = "__C"; 4240 return 1; 4241 } else if (!element->window->alt && typed->code == UI_KEYCODE_ENTER) { 4242 ui.dialogResult = "__D"; 4243 return 1; 4244 } 4245 4246 char c0 = 0, c1 = 0; 4247 4248 if (typed->textBytes == 1 && typed->text[0] >= 'a' && typed->text[0] <= 'z') { 4249 c0 = typed->text[0], c1 = typed->text[0] - 'a' + 'A'; 4250 } else { 4251 return 0; 4252 } 4253 4254 UIElement *rowContainer = element->children[0]; 4255 UIElement *target = NULL; 4256 bool duplicate = false; 4257 4258 for (uint32_t i = 0; i < rowContainer->childCount; i++) { 4259 for (uint32_t j = 0; j < rowContainer->children[i]->childCount; j++) { 4260 UIElement *item = rowContainer->children[i]->children[j]; 4261 4262 if (item->messageClass == _UIButtonMessage) { 4263 UIButton *button = (UIButton *) item; 4264 4265 if (button->label && button->labelBytes && (button->label[0] == c0 || button->label[0] == c1)) { 4266 if (!target) { 4267 target = &button->e; 4268 } else { 4269 duplicate = true; 4270 } 4271 } 4272 } 4273 } 4274 } 4275 4276 if (target) { 4277 if (duplicate) { 4278 UIElementFocus(target); 4279 } else { 4280 UIElementMessage(target, UI_MSG_CLICKED, 0, 0); 4281 } 4282 4283 return 1; 4284 } 4285 } 4286 4287 return 0; 4288 } 4289 4290 void _UIDialogButtonInvoke(void *cp) { 4291 ui.dialogResult = (const char *) cp; 4292 } 4293 4294 int _UIDialogDefaultButtonMessage(UIElement *element, UIMessage message, int di, void *dp) { 4295 if (message == UI_MSG_PAINT && element->window->focused->messageClass != _UIButtonMessage) { 4296 element->flags |= UI_BUTTON_CHECKED; 4297 element->messageClass(element, message, di, dp); 4298 element->flags &= ~UI_BUTTON_CHECKED; 4299 return 1; 4300 } 4301 4302 return 0; 4303 } 4304 4305 int _UIDialogTextboxMessage(UIElement *element, UIMessage message, int di, void *dp) { 4306 UITextbox *textbox = (UITextbox *) element; 4307 4308 if (message == UI_MSG_VALUE_CHANGED) { 4309 char **buffer = (char **) element->cp; 4310 *buffer = (char *) UI_REALLOC(*buffer, textbox->bytes + 1); 4311 (*buffer)[textbox->bytes] = 0; 4312 4313 for (ptrdiff_t i = 0; i < textbox->bytes; i++) { 4314 (*buffer)[i] = textbox->string[i]; 4315 } 4316 } else if (message == UI_MSG_UPDATE && di == UI_UPDATE_FOCUSED && element->window->focused == element) { 4317 textbox->carets[1] = 0; 4318 textbox->carets[0] = textbox->bytes; 4319 UIElementRepaint(element, NULL); 4320 } 4321 4322 return 0; 4323 } 4324 4325 const char *UIDialogShow(UIWindow *window, uint32_t flags, const char *format, ...) { 4326 // Create the dialog wrapper and panel. 4327 4328 UI_ASSERT(!window->dialog); 4329 window->dialog = UIElementCreate(sizeof(UIElement), &window->e, 0, _UIDialogWrapperMessage, "DialogWrapper"); 4330 UIPanel *panel = UIPanelCreate(window->dialog, UI_PANEL_MEDIUM_SPACING | UI_PANEL_COLOR_1); 4331 panel->border = UI_RECT_1(UI_SIZE_PANE_MEDIUM_BORDER * 2); 4332 window->e.children[0]->flags |= UI_ELEMENT_DISABLED; 4333 4334 // Create the dialog contents. 4335 4336 va_list arguments; 4337 va_start(arguments, format); 4338 UIPanel *row = NULL; 4339 UIElement *focus = NULL; 4340 UIButton *defaultButton = NULL; 4341 UIButton *cancelButton = NULL; 4342 uint32_t buttonCount = 0; 4343 4344 for (int i = 0; format[i]; i++) { 4345 if (i == 0 || format[i - 1] == '\n') { 4346 row = UIPanelCreate(&panel->e, UI_PANEL_HORIZONTAL | UI_ELEMENT_H_FILL); 4347 row->gap = UI_SIZE_PANE_SMALL_GAP; 4348 } 4349 4350 if (format[i] == ' ' || format[i] == '\n') { 4351 } else if (format[i] == '%') { 4352 i++; 4353 4354 if (format[i] == 'b' /* button */ || format[i] == 'B' /* default button */ || format[i] == 'C' /* cancel button */) { 4355 const char *label = va_arg(arguments, const char *); 4356 UIButton *button = UIButtonCreate(&row->e, 0, label, -1); 4357 if (!focus) focus = &button->e; 4358 if (format[i] == 'B') defaultButton = button; 4359 if (format[i] == 'C') cancelButton = button; 4360 buttonCount++; 4361 button->invoke = _UIDialogButtonInvoke; 4362 if (format[i] == 'B') button->e.messageUser = _UIDialogDefaultButtonMessage; 4363 button->e.cp = (void *) label; 4364 } else if (format[i] == 's' /* label from string */) { 4365 const char *label = va_arg(arguments, const char *); 4366 UILabelCreate(&row->e, 0, label, -1); 4367 } else if (format[i] == 't' /* textbox */) { 4368 char **buffer = va_arg(arguments, char **); 4369 UITextbox *textbox = UITextboxCreate(&row->e, UI_ELEMENT_H_FILL); 4370 if (!focus) focus = &textbox->e; 4371 if (*buffer) UITextboxReplace(textbox, *buffer, _UIStringLength(*buffer), false); 4372 textbox->e.cp = buffer; 4373 textbox->e.messageUser = _UIDialogTextboxMessage; 4374 } else if (format[i] == 'f' /* horizontal fill */) { 4375 UISpacerCreate(&row->e, UI_ELEMENT_H_FILL, 0, 0); 4376 } else if (format[i] == 'l' /* horizontal line */) { 4377 UISpacerCreate(&row->e, UI_ELEMENT_BORDER | UI_ELEMENT_H_FILL, 0, 1); 4378 } else if (format[i] == 'u' /* user */) { 4379 UIDialogUserCallback callback = va_arg(arguments, UIDialogUserCallback); 4380 callback(&row->e); 4381 } 4382 } else { 4383 int j = i; 4384 while (format[j] && format[j] != '%' && format[j] != '\n') j++; 4385 UILabelCreate(&row->e, 0, format + i, j - i); 4386 i = j - 1; 4387 } 4388 } 4389 4390 va_end(arguments); 4391 4392 window->dialogOldFocus = window->focused; 4393 UIElementFocus(focus ? focus : window->dialog); 4394 4395 // Run the modal message loop. 4396 4397 int result; 4398 ui.dialogResult = NULL; 4399 ui.dialogCanExit = buttonCount != 0; 4400 for (int i = 1; i <= 3; i++) _UIWindowSetPressed(window, NULL, i); 4401 UIElementRefresh(&window->e); 4402 _UIUpdate(); 4403 while (!ui.dialogResult && _UIMessageLoopSingle(&result)); 4404 ui.quit = !ui.dialogResult; 4405 4406 // Check for cancel/default action. 4407 4408 if (buttonCount == 1 && defaultButton && !cancelButton) { 4409 cancelButton = defaultButton; 4410 } 4411 4412 if (!ui.dialogResult) { 4413 } else if (ui.dialogResult[0] == '_' && ui.dialogResult[1] == '_' && ui.dialogResult[2] == 'C' && ui.dialogResult[3] == 0 && cancelButton) { 4414 ui.dialogResult = (const char *) cancelButton->e.cp; 4415 } else if (ui.dialogResult[0] == '_' && ui.dialogResult[1] == '_' && ui.dialogResult[2] == 'D' && ui.dialogResult[3] == 0 && defaultButton) { 4416 ui.dialogResult = (const char *) defaultButton->e.cp; 4417 } 4418 4419 // Destroy the dialog. 4420 4421 window->e.children[0]->flags &= ~UI_ELEMENT_DISABLED; 4422 UIElementDestroy(window->dialog); 4423 window->dialog = NULL; 4424 UIElementRefresh(&window->e); 4425 if (window->dialogOldFocus) UIElementFocus(window->dialogOldFocus); 4426 return ui.dialogResult ? ui.dialogResult : ""; 4427 } 4428 4429 ///////////////////////////////////////// 4430 // Menus (common). 4431 ///////////////////////////////////////// 4432 4433 bool _UIMenusClose() { 4434 UIWindow *window = ui.windows; 4435 bool anyClosed = false; 4436 4437 while (window) { 4438 if (window->e.flags & UI_WINDOW_MENU) { 4439 UIElementDestroy(&window->e); 4440 anyClosed = true; 4441 } 4442 4443 window = window->next; 4444 } 4445 4446 return anyClosed; 4447 } 4448 4449 #if !defined(UI_ESSENCE) && !defined(UI_COCOA) 4450 int _UIMenuItemMessage(UIElement *element, UIMessage message, int di, void *dp) { 4451 if (message == UI_MSG_CLICKED) { 4452 _UIMenusClose(); 4453 } 4454 4455 return 0; 4456 } 4457 4458 int _UIMenuMessage(UIElement *element, UIMessage message, int di, void *dp) { 4459 UIMenu *menu = (UIMenu *) element; 4460 4461 if (message == UI_MSG_GET_WIDTH) { 4462 int width = 0; 4463 4464 for (uint32_t i = 0; i < element->childCount; i++) { 4465 UIElement *child = element->children[i]; 4466 4467 if (~child->flags & UI_ELEMENT_NON_CLIENT) { 4468 int w = UIElementMessage(child, UI_MSG_GET_WIDTH, 0, 0); 4469 if (w > width) width = w; 4470 } 4471 } 4472 4473 return width + 4 + UI_SIZE_SCROLL_BAR; 4474 } else if (message == UI_MSG_GET_HEIGHT) { 4475 int height = 0; 4476 4477 for (uint32_t i = 0; i < element->childCount; i++) { 4478 UIElement *child = element->children[i]; 4479 4480 if (~child->flags & UI_ELEMENT_NON_CLIENT) { 4481 height += UIElementMessage(child, UI_MSG_GET_HEIGHT, 0, 0); 4482 } 4483 } 4484 4485 return height + 4; 4486 } else if (message == UI_MSG_PAINT) { 4487 UIDrawControl((UIPainter *) dp, element->bounds, UI_DRAW_CONTROL_MENU, NULL, 0, 0, element->window->scale); 4488 } else if (message == UI_MSG_LAYOUT) { 4489 int position = element->bounds.t + 2 - menu->vScroll->position; 4490 int totalHeight = 0; 4491 int scrollBarSize = (menu->e.flags & UI_MENU_NO_SCROLL) ? 0 : UI_SIZE_SCROLL_BAR; 4492 4493 for (uint32_t i = 0; i < element->childCount; i++) { 4494 UIElement *child = element->children[i]; 4495 4496 if (~child->flags & UI_ELEMENT_NON_CLIENT) { 4497 int height = UIElementMessage(child, UI_MSG_GET_HEIGHT, 0, 0); 4498 UIElementMove(child, UI_RECT_4(element->bounds.l + 2, element->bounds.r - scrollBarSize - 2, 4499 position, position + height), false); 4500 position += height; 4501 totalHeight += height; 4502 } 4503 } 4504 4505 UIRectangle scrollBarBounds = element->bounds; 4506 scrollBarBounds.l = scrollBarBounds.r - scrollBarSize * element->window->scale; 4507 menu->vScroll->maximum = totalHeight; 4508 menu->vScroll->page = UI_RECT_HEIGHT(element->bounds); 4509 UIElementMove(&menu->vScroll->e, scrollBarBounds, true); 4510 } else if (message == UI_MSG_KEY_TYPED) { 4511 UIKeyTyped *m = (UIKeyTyped *) dp; 4512 4513 if (m->code == UI_KEYCODE_ESCAPE) { 4514 _UIMenusClose(); 4515 return 1; 4516 } 4517 } else if (message == UI_MSG_MOUSE_WHEEL) { 4518 return UIElementMessage(&menu->vScroll->e, message, di, dp); 4519 } else if (message == UI_MSG_SCROLLED) { 4520 UIElementRefresh(element); 4521 } 4522 4523 return 0; 4524 } 4525 4526 void UIMenuAddItem(UIMenu *menu, uint32_t flags, const char *label, ptrdiff_t labelBytes, void (*invoke)(void *cp), void *cp) { 4527 UIButton *button = UIButtonCreate(&menu->e, flags | UI_BUTTON_MENU_ITEM, label, labelBytes); 4528 button->invoke = invoke; 4529 button->e.messageUser = _UIMenuItemMessage; 4530 button->e.cp = cp; 4531 } 4532 4533 void _UIMenuPrepare(UIMenu *menu, int *width, int *height) { 4534 *width = UIElementMessage(&menu->e, UI_MSG_GET_WIDTH, 0, 0); 4535 *height = UIElementMessage(&menu->e, UI_MSG_GET_HEIGHT, 0, 0); 4536 4537 if (menu->e.flags & UI_MENU_PLACE_ABOVE) { 4538 menu->pointY -= *height; 4539 } 4540 } 4541 4542 UIMenu *UIMenuCreate(UIElement *parent, uint32_t flags) { 4543 UIWindow *window = UIWindowCreate(parent->window, UI_WINDOW_MENU, 0, 0, 0); 4544 UIMenu *menu = (UIMenu *) UIElementCreate(sizeof(UIMenu), &window->e, flags, _UIMenuMessage, "Menu"); 4545 menu->vScroll = UIScrollBarCreate(&menu->e, UI_ELEMENT_NON_CLIENT); 4546 menu->parentWindow = parent->window; 4547 4548 if (parent->parent) { 4549 UIRectangle screenBounds = UIElementScreenBounds(parent); 4550 menu->pointX = screenBounds.l; 4551 menu->pointY = (flags & UI_MENU_PLACE_ABOVE) ? (screenBounds.t + 1) : (screenBounds.b - 1); 4552 } else { 4553 int x = 0, y = 0; 4554 _UIWindowGetScreenPosition(parent->window, &x, &y); 4555 4556 menu->pointX = parent->window->cursorX + x; 4557 menu->pointY = parent->window->cursorY + y; 4558 } 4559 4560 return menu; 4561 } 4562 #endif 4563 4564 ///////////////////////////////////////// 4565 // Miscellaneous core functions. 4566 ///////////////////////////////////////// 4567 4568 UIRectangle UIElementScreenBounds(UIElement *element) { 4569 int x = 0, y = 0; 4570 _UIWindowGetScreenPosition(element->window, &x, &y); 4571 return UIRectangleAdd(element->bounds, UI_RECT_2(x, y)); 4572 } 4573 4574 void UIWindowRegisterShortcut(UIWindow *window, UIShortcut shortcut) { 4575 if (window->shortcutCount + 1 > window->shortcutAllocated) { 4576 window->shortcutAllocated = (window->shortcutCount + 1) * 2; 4577 window->shortcuts = (UIShortcut *) UI_REALLOC(window->shortcuts, window->shortcutAllocated * sizeof(UIShortcut)); 4578 } 4579 4580 window->shortcuts[window->shortcutCount++] = shortcut; 4581 } 4582 4583 void UIElementSetDisabled(UIElement *element, bool disabled) { 4584 if (element->window->focused == element && disabled) { 4585 UIElementFocus(&element->window->e); 4586 } 4587 4588 if ((element->flags & UI_ELEMENT_DISABLED) && disabled) return; 4589 if ((~element->flags & UI_ELEMENT_DISABLED) && !disabled) return; 4590 4591 if (disabled) element->flags |= UI_ELEMENT_DISABLED; 4592 else element->flags &= ~UI_ELEMENT_DISABLED; 4593 4594 UIElementMessage(element, UI_MSG_UPDATE, UI_UPDATE_DISABLED, 0); 4595 } 4596 4597 void UIElementFocus(UIElement *element) { 4598 UIElement *previous = element->window->focused; 4599 if (previous == element) return; 4600 element->window->focused = element; 4601 if (previous) UIElementMessage(previous, UI_MSG_UPDATE, UI_UPDATE_FOCUSED, 0); 4602 if (element) UIElementMessage(element, UI_MSG_UPDATE, UI_UPDATE_FOCUSED, 0); 4603 4604 #ifdef UI_DEBUG 4605 _UIInspectorRefresh(); 4606 #endif 4607 } 4608 4609 ///////////////////////////////////////// 4610 // Update cycles. 4611 ///////////////////////////////////////// 4612 4613 void UIElementRefresh(UIElement *element) { 4614 UIElementRelayout(element); 4615 UIElementRepaint(element, NULL); 4616 } 4617 4618 void UIElementRelayout(UIElement *element) { 4619 if (element->flags & UI_ELEMENT_RELAYOUT) { 4620 return; 4621 } 4622 4623 element->flags |= UI_ELEMENT_RELAYOUT; 4624 UIElement *ancestor = element->parent; 4625 4626 while (ancestor) { 4627 ancestor->flags |= UI_ELEMENT_RELAYOUT_DESCENDENT; 4628 ancestor = ancestor->parent; 4629 } 4630 } 4631 4632 void UIElementMeasurementsChanged(UIElement *element, int which) { 4633 if (!element->parent) { 4634 return; // This is the window element. 4635 } 4636 4637 while (true) { 4638 if (element->parent->flags & UI_ELEMENT_DESTROY) return; 4639 which &= ~UIElementMessage(element->parent, UI_MSG_GET_CHILD_STABILITY, which, element); 4640 if (!which) break; 4641 element->flags |= UI_ELEMENT_RELAYOUT; 4642 element = element->parent; 4643 } 4644 4645 UIElementRelayout(element); 4646 } 4647 4648 void UIElementRepaint(UIElement *element, UIRectangle *region) { 4649 if (!region) { 4650 region = &element->bounds; 4651 } 4652 4653 UIRectangle r = UIRectangleIntersection(*region, element->clip); 4654 4655 if (!UI_RECT_VALID(r)) { 4656 return; 4657 } 4658 4659 if (UI_RECT_VALID(element->window->updateRegion)) { 4660 element->window->updateRegion = UIRectangleBounding(element->window->updateRegion, r); 4661 } else { 4662 element->window->updateRegion = r; 4663 } 4664 } 4665 4666 void UIElementMove(UIElement *element, UIRectangle bounds, bool layout) { 4667 UIRectangle clip = element->parent? UIRectangleIntersection(element->parent->clip, bounds) : bounds; 4668 bool moved = !UIRectangleEquals(element->bounds, bounds) || !UIRectangleEquals(element->clip, clip); 4669 4670 if (moved) { 4671 layout = true; 4672 4673 UIElementRepaint(&element->window->e, &element->clip); 4674 UIElementRepaint(&element->window->e, &clip); 4675 4676 element->bounds = bounds; 4677 element->clip = clip; 4678 } 4679 4680 if (element->flags & UI_ELEMENT_RELAYOUT) { 4681 layout = true; 4682 } 4683 4684 if (layout) { 4685 UIElementMessage(element, UI_MSG_LAYOUT, 0, 0); 4686 } else if (element->flags & UI_ELEMENT_RELAYOUT_DESCENDENT) { 4687 for (uint32_t i = 0; i < element->childCount; i++) { 4688 UIElementMove(element->children[i], element->children[i]->bounds, false); 4689 } 4690 } 4691 4692 element->flags &= ~(UI_ELEMENT_RELAYOUT_DESCENDENT | UI_ELEMENT_RELAYOUT); 4693 } 4694 4695 void _UIElementPaint(UIElement *element, UIPainter *painter) { 4696 if (element->flags & UI_ELEMENT_HIDE) { 4697 return; 4698 } 4699 4700 // Clip painting to the element's clip. 4701 4702 painter->clip = UIRectangleIntersection(element->clip, painter->clip); 4703 4704 if (!UI_RECT_VALID(painter->clip)) { 4705 return; 4706 } 4707 4708 // Paint the element. 4709 4710 UIElementMessage(element, UI_MSG_PAINT, 0, painter); 4711 4712 // Paint its children. 4713 4714 UIRectangle previousClip = painter->clip; 4715 4716 for (uintptr_t i = 0; i < element->childCount; i++) { 4717 painter->clip = previousClip; 4718 _UIElementPaint(element->children[i], painter); 4719 } 4720 4721 // Draw the foreground and border. 4722 4723 painter->clip = previousClip; 4724 UIElementMessage(element, UI_MSG_PAINT_FOREGROUND, 0, painter); 4725 4726 if (element->flags & UI_ELEMENT_BORDER) { 4727 UIDrawBorder(painter, element->bounds, ui.theme.border, UI_RECT_1((int) element->window->scale)); 4728 } 4729 } 4730 4731 bool _UIDestroy(UIElement *element) { 4732 if (element->flags & UI_ELEMENT_DESTROY_DESCENDENT) { 4733 element->flags &= ~UI_ELEMENT_DESTROY_DESCENDENT; 4734 4735 for (uintptr_t i = 0; i < element->childCount; i++) { 4736 if (_UIDestroy(element->children[i])) { 4737 UI_MEMMOVE(&element->children[i], &element->children[i + 1], sizeof(UIElement *) * (element->childCount - i - 1)); 4738 element->childCount--, i--; 4739 } 4740 } 4741 } 4742 4743 if (element->flags & UI_ELEMENT_DESTROY) { 4744 UIElementMessage(element, UI_MSG_DEALLOCATE, 0, 0); 4745 4746 if (element->window->pressed == element) { 4747 _UIWindowSetPressed(element->window, NULL, 0); 4748 } 4749 4750 if (element->window->hovered == element) { 4751 element->window->hovered = &element->window->e; 4752 } 4753 4754 if (element->window->focused == element) { 4755 element->window->focused = NULL; 4756 } 4757 4758 if (element->window->dialogOldFocus == element) { 4759 element->window->dialogOldFocus = NULL; 4760 } 4761 4762 UIElementAnimate(element, true); 4763 UI_FREE(element->children); 4764 UI_FREE(element); 4765 return true; 4766 } else { 4767 return false; 4768 } 4769 } 4770 4771 void _UIUpdate() { 4772 UIWindow *window = ui.windows; 4773 UIWindow **link = &ui.windows; 4774 4775 while (window) { 4776 UIWindow *next = window->next; 4777 4778 UIElementMessage(&window->e, UI_MSG_WINDOW_UPDATE_START, 0, 0); 4779 UIElementMessage(&window->e, UI_MSG_WINDOW_UPDATE_BEFORE_DESTROY, 0, 0); 4780 4781 if (_UIDestroy(&window->e)) { 4782 *link = next; 4783 } else { 4784 link = &window->next; 4785 4786 UIElementMessage(&window->e, UI_MSG_WINDOW_UPDATE_BEFORE_LAYOUT, 0, 0); 4787 UIElementMove(&window->e, window->e.bounds, false); 4788 UIElementMessage(&window->e, UI_MSG_WINDOW_UPDATE_BEFORE_PAINT, 0, 0); 4789 4790 if (UI_RECT_VALID(window->updateRegion)) { 4791 #ifdef __cplusplus 4792 UIPainter painter = {}; 4793 #else 4794 UIPainter painter = { 0 }; 4795 #endif 4796 painter.bits = window->bits; 4797 painter.width = window->width; 4798 painter.height = window->height; 4799 painter.clip = UIRectangleIntersection(UI_RECT_2S(window->width, window->height), window->updateRegion); 4800 _UIElementPaint(&window->e, &painter); 4801 _UIWindowEndPaint(window, &painter); 4802 window->updateRegion = UI_RECT_1(0); 4803 4804 #ifdef UI_DEBUG 4805 window->lastFullFillCount = (float) painter.fillCount / (UI_RECT_WIDTH(window->updateRegion) * UI_RECT_HEIGHT(window->updateRegion)); 4806 #endif 4807 } 4808 4809 UIElementMessage(&window->e, UI_MSG_WINDOW_UPDATE_END, 0, 0); 4810 } 4811 4812 window = next; 4813 } 4814 } 4815 4816 ///////////////////////////////////////// 4817 // Input event handling. 4818 ///////////////////////////////////////// 4819 4820 void _UIWindowSetPressed(UIWindow *window, UIElement *element, int button) { 4821 UIElement *previous = window->pressed; 4822 window->pressed = element; 4823 window->pressedButton = button; 4824 if (previous) UIElementMessage(previous, UI_MSG_UPDATE, UI_UPDATE_PRESSED, 0); 4825 if (element) UIElementMessage(element, UI_MSG_UPDATE, UI_UPDATE_PRESSED, 0); 4826 4827 UIElement *ancestor = element; 4828 UIElement *child = NULL; 4829 4830 while (ancestor) { 4831 UIElementMessage(ancestor, UI_MSG_PRESSED_DESCENDENT, 0, child); 4832 child = ancestor; 4833 ancestor = ancestor->parent; 4834 } 4835 } 4836 4837 UIElement *UIElementFindByPoint(UIElement *element, int x, int y) { 4838 for (uint32_t i = element->childCount; i > 0; i--) { 4839 UIElement *child = element->children[i - 1]; 4840 4841 if ((~child->flags & UI_ELEMENT_HIDE) && UIRectangleContains(child->clip, x, y)) { 4842 return UIElementFindByPoint(child, x, y); 4843 } 4844 } 4845 4846 return element; 4847 } 4848 4849 bool UIMenusOpen() { 4850 UIWindow *window = ui.windows; 4851 4852 while (window) { 4853 if (window->e.flags & UI_WINDOW_MENU) { 4854 return true; 4855 } 4856 4857 window = window->next; 4858 } 4859 4860 return false; 4861 } 4862 4863 UIElement *_UIElementNextOrPreviousSibling(UIElement *element, bool previous) { 4864 if (!element->parent) { 4865 return NULL; 4866 } 4867 4868 for (uint32_t i = 0; i < element->parent->childCount; i++) { 4869 if (element->parent->children[i] == element) { 4870 if (previous) { 4871 return i > 0 ? element->parent->children[i - 1] : NULL; 4872 } else { 4873 return i < element->parent->childCount - 1 ? element->parent->children[i + 1] : NULL; 4874 } 4875 } 4876 } 4877 4878 UI_ASSERT(false); 4879 return NULL; 4880 } 4881 4882 bool _UIWindowInputEvent(UIWindow *window, UIMessage message, int di, void *dp) { 4883 bool handled = true; 4884 4885 if (window->pressed) { 4886 if (message == UI_MSG_MOUSE_MOVE) { 4887 UIElementMessage(window->pressed, UI_MSG_MOUSE_DRAG, di, dp); 4888 } else if (message == UI_MSG_LEFT_UP && window->pressedButton == 1) { 4889 if (window->hovered == window->pressed) { 4890 UIElementMessage(window->pressed, UI_MSG_CLICKED, di, dp); 4891 if (ui.quit || ui.dialogResult) goto end; 4892 } 4893 4894 if (window->pressed) { 4895 UIElementMessage(window->pressed, UI_MSG_LEFT_UP, di, dp); 4896 if (ui.quit || ui.dialogResult) goto end; 4897 _UIWindowSetPressed(window, NULL, 1); 4898 } 4899 } else if (message == UI_MSG_MIDDLE_UP && window->pressedButton == 2) { 4900 UIElementMessage(window->pressed, UI_MSG_MIDDLE_UP, di, dp); 4901 if (ui.quit || ui.dialogResult) goto end; 4902 _UIWindowSetPressed(window, NULL, 2); 4903 } else if (message == UI_MSG_RIGHT_UP && window->pressedButton == 3) { 4904 UIElementMessage(window->pressed, UI_MSG_RIGHT_UP, di, dp); 4905 if (ui.quit || ui.dialogResult) goto end; 4906 _UIWindowSetPressed(window, NULL, 3); 4907 } 4908 } 4909 4910 if (window->pressed) { 4911 bool inside = UIRectangleContains(window->pressed->clip, window->cursorX, window->cursorY); 4912 4913 if (inside && window->hovered == &window->e) { 4914 window->hovered = window->pressed; 4915 UIElementMessage(window->pressed, UI_MSG_UPDATE, UI_UPDATE_HOVERED, 0); 4916 } else if (!inside && window->hovered == window->pressed) { 4917 window->hovered = &window->e; 4918 UIElementMessage(window->pressed, UI_MSG_UPDATE, UI_UPDATE_HOVERED, 0); 4919 } 4920 4921 if (ui.quit || ui.dialogResult) goto end; 4922 } 4923 4924 if (!window->pressed) { 4925 UIElement *hovered = UIElementFindByPoint(&window->e, window->cursorX, window->cursorY); 4926 4927 if (message == UI_MSG_MOUSE_MOVE) { 4928 UIElementMessage(hovered, UI_MSG_MOUSE_MOVE, di, dp); 4929 4930 int cursor = UIElementMessage(window->hovered, UI_MSG_GET_CURSOR, di, dp); 4931 4932 if (cursor != window->cursorStyle) { 4933 window->cursorStyle = cursor; 4934 _UIWindowSetCursor(window, cursor); 4935 } 4936 } else if (message == UI_MSG_LEFT_DOWN) { 4937 if ((window->e.flags & UI_WINDOW_MENU) || !_UIMenusClose()) { 4938 _UIWindowSetPressed(window, hovered, 1); 4939 UIElementMessage(hovered, UI_MSG_LEFT_DOWN, di, dp); 4940 } 4941 } else if (message == UI_MSG_MIDDLE_DOWN) { 4942 if ((window->e.flags & UI_WINDOW_MENU) || !_UIMenusClose()) { 4943 _UIWindowSetPressed(window, hovered, 2); 4944 UIElementMessage(hovered, UI_MSG_MIDDLE_DOWN, di, dp); 4945 } 4946 } else if (message == UI_MSG_RIGHT_DOWN) { 4947 if ((window->e.flags & UI_WINDOW_MENU) || !_UIMenusClose()) { 4948 _UIWindowSetPressed(window, hovered, 3); 4949 UIElementMessage(hovered, UI_MSG_RIGHT_DOWN, di, dp); 4950 } 4951 } else if (message == UI_MSG_MOUSE_WHEEL) { 4952 UIElement *element = hovered; 4953 4954 while (element) { 4955 if (UIElementMessage(element, UI_MSG_MOUSE_WHEEL, di, dp)) { 4956 break; 4957 } 4958 4959 element = element->parent; 4960 } 4961 } else if (message == UI_MSG_KEY_TYPED || message == UI_MSG_KEY_RELEASED) { 4962 handled = false; 4963 4964 if (window->focused) { 4965 UIElement *element = window->focused; 4966 4967 while (element) { 4968 if (UIElementMessage(element, message, di, dp)) { 4969 handled = true; 4970 break; 4971 } 4972 4973 element = element->parent; 4974 } 4975 } else { 4976 if (UIElementMessage(&window->e, message, di, dp)) { 4977 handled = true; 4978 } 4979 } 4980 4981 if (!handled && !UIMenusOpen() && message == UI_MSG_KEY_TYPED) { 4982 UIKeyTyped *m = (UIKeyTyped *) dp; 4983 4984 if (m->code == UI_KEYCODE_TAB && !window->ctrl && !window->alt) { 4985 UIElement *start = window->focused ? window->focused : &window->e; 4986 UIElement *element = start; 4987 4988 do { 4989 if (element->childCount && !(element->flags & (UI_ELEMENT_HIDE | UI_ELEMENT_DISABLED))) { 4990 element = window->shift ? element->children[element->childCount - 1] : element->children[0]; 4991 continue; 4992 } 4993 4994 while (element) { 4995 UIElement *sibling = _UIElementNextOrPreviousSibling(element, window->shift); 4996 if (sibling) { element = sibling; break; } 4997 element = element->parent; 4998 } 4999 5000 if (!element) { 5001 element = &window->e; 5002 } 5003 } while (element != start && ((~element->flags & UI_ELEMENT_TAB_STOP) 5004 || (element->flags & (UI_ELEMENT_HIDE | UI_ELEMENT_DISABLED)))); 5005 5006 if (~element->flags & UI_ELEMENT_WINDOW) { 5007 UIElementFocus(element); 5008 } 5009 5010 handled = true; 5011 } else if (!window->dialog) { 5012 for (intptr_t i = window->shortcutCount - 1; i >= 0; i--) { 5013 UIShortcut *shortcut = window->shortcuts + i; 5014 5015 if (shortcut->code == m->code && shortcut->ctrl == window->ctrl 5016 && shortcut->shift == window->shift && shortcut->alt == window->alt) { 5017 shortcut->invoke(shortcut->cp); 5018 handled = true; 5019 break; 5020 } 5021 } 5022 } else if (window->dialog) { 5023 UIElementMessage(window->dialog, message, di, dp); 5024 } 5025 } 5026 } 5027 5028 if (ui.quit || ui.dialogResult) goto end; 5029 5030 if (hovered != window->hovered) { 5031 UIElement *previous = window->hovered; 5032 window->hovered = hovered; 5033 UIElementMessage(previous, UI_MSG_UPDATE, UI_UPDATE_HOVERED, 0); 5034 UIElementMessage(window->hovered, UI_MSG_UPDATE, UI_UPDATE_HOVERED, 0); 5035 } 5036 } 5037 5038 end: _UIUpdate(); 5039 return handled; 5040 } 5041 5042 ///////////////////////////////////////// 5043 // Font handling. 5044 ///////////////////////////////////////// 5045 5046 // Taken from https://commons.wikimedia.org/wiki/File:Codepage-437.png 5047 // Public domain. 5048 5049 const uint64_t _uiFont[] = { 5050 0x0000000000000000UL, 0x0000000000000000UL, 0xBD8181A5817E0000UL, 0x000000007E818199UL, 0xC3FFFFDBFF7E0000UL, 0x000000007EFFFFE7UL, 0x7F7F7F3600000000UL, 0x00000000081C3E7FUL, 5051 0x7F3E1C0800000000UL, 0x0000000000081C3EUL, 0xE7E73C3C18000000UL, 0x000000003C1818E7UL, 0xFFFF7E3C18000000UL, 0x000000003C18187EUL, 0x3C18000000000000UL, 0x000000000000183CUL, 5052 0xC3E7FFFFFFFFFFFFUL, 0xFFFFFFFFFFFFE7C3UL, 0x42663C0000000000UL, 0x00000000003C6642UL, 0xBD99C3FFFFFFFFFFUL, 0xFFFFFFFFFFC399BDUL, 0x331E4C5870780000UL, 0x000000001E333333UL, 5053 0x3C666666663C0000UL, 0x0000000018187E18UL, 0x0C0C0CFCCCFC0000UL, 0x00000000070F0E0CUL, 0xC6C6C6FEC6FE0000UL, 0x0000000367E7E6C6UL, 0xE73CDB1818000000UL, 0x000000001818DB3CUL, 5054 0x1F7F1F0F07030100UL, 0x000000000103070FUL, 0x7C7F7C7870604000UL, 0x0000000040607078UL, 0x1818187E3C180000UL, 0x0000000000183C7EUL, 0x6666666666660000UL, 0x0000000066660066UL, 5055 0xD8DEDBDBDBFE0000UL, 0x00000000D8D8D8D8UL, 0x6363361C06633E00UL, 0x0000003E63301C36UL, 0x0000000000000000UL, 0x000000007F7F7F7FUL, 0x1818187E3C180000UL, 0x000000007E183C7EUL, 5056 0x1818187E3C180000UL, 0x0000000018181818UL, 0x1818181818180000UL, 0x00000000183C7E18UL, 0x7F30180000000000UL, 0x0000000000001830UL, 0x7F060C0000000000UL, 0x0000000000000C06UL, 5057 0x0303000000000000UL, 0x0000000000007F03UL, 0xFF66240000000000UL, 0x0000000000002466UL, 0x3E1C1C0800000000UL, 0x00000000007F7F3EUL, 0x3E3E7F7F00000000UL, 0x0000000000081C1CUL, 5058 0x0000000000000000UL, 0x0000000000000000UL, 0x18183C3C3C180000UL, 0x0000000018180018UL, 0x0000002466666600UL, 0x0000000000000000UL, 0x36367F3636000000UL, 0x0000000036367F36UL, 5059 0x603E0343633E1818UL, 0x000018183E636160UL, 0x1830634300000000UL, 0x000000006163060CUL, 0x3B6E1C36361C0000UL, 0x000000006E333333UL, 0x000000060C0C0C00UL, 0x0000000000000000UL, 5060 0x0C0C0C0C18300000UL, 0x0000000030180C0CUL, 0x30303030180C0000UL, 0x000000000C183030UL, 0xFF3C660000000000UL, 0x000000000000663CUL, 0x7E18180000000000UL, 0x0000000000001818UL, 5061 0x0000000000000000UL, 0x0000000C18181800UL, 0x7F00000000000000UL, 0x0000000000000000UL, 0x0000000000000000UL, 0x0000000018180000UL, 0x1830604000000000UL, 0x000000000103060CUL, 5062 0xDBDBC3C3663C0000UL, 0x000000003C66C3C3UL, 0x1818181E1C180000UL, 0x000000007E181818UL, 0x0C183060633E0000UL, 0x000000007F630306UL, 0x603C6060633E0000UL, 0x000000003E636060UL, 5063 0x7F33363C38300000UL, 0x0000000078303030UL, 0x603F0303037F0000UL, 0x000000003E636060UL, 0x633F0303061C0000UL, 0x000000003E636363UL, 0x18306060637F0000UL, 0x000000000C0C0C0CUL, 5064 0x633E6363633E0000UL, 0x000000003E636363UL, 0x607E6363633E0000UL, 0x000000001E306060UL, 0x0000181800000000UL, 0x0000000000181800UL, 0x0000181800000000UL, 0x000000000C181800UL, 5065 0x060C183060000000UL, 0x000000006030180CUL, 0x00007E0000000000UL, 0x000000000000007EUL, 0x6030180C06000000UL, 0x00000000060C1830UL, 0x18183063633E0000UL, 0x0000000018180018UL, 5066 0x7B7B63633E000000UL, 0x000000003E033B7BUL, 0x7F6363361C080000UL, 0x0000000063636363UL, 0x663E6666663F0000UL, 0x000000003F666666UL, 0x03030343663C0000UL, 0x000000003C664303UL, 5067 0x66666666361F0000UL, 0x000000001F366666UL, 0x161E1646667F0000UL, 0x000000007F664606UL, 0x161E1646667F0000UL, 0x000000000F060606UL, 0x7B030343663C0000UL, 0x000000005C666363UL, 5068 0x637F636363630000UL, 0x0000000063636363UL, 0x18181818183C0000UL, 0x000000003C181818UL, 0x3030303030780000UL, 0x000000001E333333UL, 0x1E1E366666670000UL, 0x0000000067666636UL, 5069 0x06060606060F0000UL, 0x000000007F664606UL, 0xC3DBFFFFE7C30000UL, 0x00000000C3C3C3C3UL, 0x737B7F6F67630000UL, 0x0000000063636363UL, 0x63636363633E0000UL, 0x000000003E636363UL, 5070 0x063E6666663F0000UL, 0x000000000F060606UL, 0x63636363633E0000UL, 0x000070303E7B6B63UL, 0x363E6666663F0000UL, 0x0000000067666666UL, 0x301C0663633E0000UL, 0x000000003E636360UL, 5071 0x18181899DBFF0000UL, 0x000000003C181818UL, 0x6363636363630000UL, 0x000000003E636363UL, 0xC3C3C3C3C3C30000UL, 0x00000000183C66C3UL, 0xDBC3C3C3C3C30000UL, 0x000000006666FFDBUL, 5072 0x18183C66C3C30000UL, 0x00000000C3C3663CUL, 0x183C66C3C3C30000UL, 0x000000003C181818UL, 0x0C183061C3FF0000UL, 0x00000000FFC38306UL, 0x0C0C0C0C0C3C0000UL, 0x000000003C0C0C0CUL, 5073 0x1C0E070301000000UL, 0x0000000040607038UL, 0x30303030303C0000UL, 0x000000003C303030UL, 0x0000000063361C08UL, 0x0000000000000000UL, 0x0000000000000000UL, 0x0000FF0000000000UL, 5074 0x0000000000180C0CUL, 0x0000000000000000UL, 0x3E301E0000000000UL, 0x000000006E333333UL, 0x66361E0606070000UL, 0x000000003E666666UL, 0x03633E0000000000UL, 0x000000003E630303UL, 5075 0x33363C3030380000UL, 0x000000006E333333UL, 0x7F633E0000000000UL, 0x000000003E630303UL, 0x060F0626361C0000UL, 0x000000000F060606UL, 0x33336E0000000000UL, 0x001E33303E333333UL, 5076 0x666E360606070000UL, 0x0000000067666666UL, 0x18181C0018180000UL, 0x000000003C181818UL, 0x6060700060600000UL, 0x003C666660606060UL, 0x1E36660606070000UL, 0x000000006766361EUL, 5077 0x18181818181C0000UL, 0x000000003C181818UL, 0xDBFF670000000000UL, 0x00000000DBDBDBDBUL, 0x66663B0000000000UL, 0x0000000066666666UL, 0x63633E0000000000UL, 0x000000003E636363UL, 5078 0x66663B0000000000UL, 0x000F06063E666666UL, 0x33336E0000000000UL, 0x007830303E333333UL, 0x666E3B0000000000UL, 0x000000000F060606UL, 0x06633E0000000000UL, 0x000000003E63301CUL, 5079 0x0C0C3F0C0C080000UL, 0x00000000386C0C0CUL, 0x3333330000000000UL, 0x000000006E333333UL, 0xC3C3C30000000000UL, 0x00000000183C66C3UL, 0xC3C3C30000000000UL, 0x0000000066FFDBDBUL, 5080 0x3C66C30000000000UL, 0x00000000C3663C18UL, 0x6363630000000000UL, 0x001F30607E636363UL, 0x18337F0000000000UL, 0x000000007F63060CUL, 0x180E181818700000UL, 0x0000000070181818UL, 5081 0x1800181818180000UL, 0x0000000018181818UL, 0x18701818180E0000UL, 0x000000000E181818UL, 0x000000003B6E0000UL, 0x0000000000000000UL, 0x63361C0800000000UL, 0x00000000007F6363UL, 5082 }; 5083 5084 void UIDrawGlyph(UIPainter *painter, int x0, int y0, int c, uint32_t color) { 5085 #ifdef UI_FREETYPE 5086 UIFont *font = ui.activeFont; 5087 5088 if (font->isFreeType) { 5089 #ifdef UI_UNICODE 5090 if (c < 0) c = '?'; 5091 #else 5092 if (c < 0 || c > 127) c = '?'; 5093 #endif 5094 if (c == '\r') c = ' '; 5095 5096 if (!font->glyphsRendered[c]) { 5097 FT_Load_Char(font->font, c == 24 ? 0x2191 : c == 25 ? 0x2193 : c == 26 ? 0x2192 : c == 27 ? 0x2190 : c, FT_LOAD_DEFAULT); 5098 #ifdef UI_FREETYPE_SUBPIXEL 5099 FT_Render_Glyph(font->font->glyph, FT_RENDER_MODE_LCD); 5100 #else 5101 FT_Render_Glyph(font->font->glyph, FT_RENDER_MODE_NORMAL); 5102 #endif 5103 FT_Bitmap_Copy(ui.ft, &font->font->glyph->bitmap, &font->glyphs[c]); 5104 font->glyphOffsetsX[c] = font->font->glyph->bitmap_left; 5105 font->glyphOffsetsY[c] = font->font->size->metrics.ascender / 64 - font->font->glyph->bitmap_top; 5106 font->glyphsRendered[c] = true; 5107 } 5108 5109 FT_Bitmap *bitmap = &font->glyphs[c]; 5110 x0 += font->glyphOffsetsX[c], y0 += font->glyphOffsetsY[c]; 5111 5112 for (int y = 0; y < (int) bitmap->rows; y++) { 5113 if (y0 + y < painter->clip.t) continue; 5114 if (y0 + y >= painter->clip.b) break; 5115 5116 int width = bitmap->pixel_mode == FT_PIXEL_MODE_LCD ? bitmap->width / 3 : bitmap->width; 5117 5118 for (int x = 0; x < width; x++) { 5119 if (x0 + x < painter->clip.l) continue; 5120 if (x0 + x >= painter->clip.r) break; 5121 5122 uint32_t *destination = painter->bits + (x0 + x) + (y0 + y) * painter->width; 5123 uint32_t original = *destination, ra, ga, ba; 5124 5125 if (bitmap->pixel_mode == FT_PIXEL_MODE_LCD) { 5126 ra = ((uint8_t *) bitmap->buffer)[x * 3 + y * bitmap->pitch + 0]; 5127 ga = ((uint8_t *) bitmap->buffer)[x * 3 + y * bitmap->pitch + 1]; 5128 ba = ((uint8_t *) bitmap->buffer)[x * 3 + y * bitmap->pitch + 2]; 5129 ra += (ga - ra) / 2, ba += (ga - ba) / 2; // TODO Gamma correct blending! 5130 } else if (bitmap->pixel_mode == FT_PIXEL_MODE_MONO) { 5131 ra = (((uint8_t *) bitmap->buffer)[(x >> 3) + y * bitmap->pitch] & (0x80 >> (x & 7))) ? 0xFF : 0; 5132 ga = ra, ba = ra; 5133 } else if (bitmap->pixel_mode == FT_PIXEL_MODE_GRAY) { 5134 ra = ((uint8_t *) bitmap->buffer)[x + y * bitmap->pitch]; 5135 ga = ra, ba = ra; 5136 } else { 5137 ra = ga = ba = 0; 5138 } 5139 5140 uint32_t r2 = (255 - ra) * ((original & 0x000000FF) >> 0); 5141 uint32_t g2 = (255 - ga) * ((original & 0x0000FF00) >> 8); 5142 uint32_t b2 = (255 - ba) * ((original & 0x00FF0000) >> 16); 5143 uint32_t r1 = ra * ((color & 0x000000FF) >> 0); 5144 uint32_t g1 = ga * ((color & 0x0000FF00) >> 8); 5145 uint32_t b1 = ba * ((color & 0x00FF0000) >> 16); 5146 5147 uint32_t result = 0xFF000000 | (0x00FF0000 & ((b1 + b2) << 8)) 5148 | (0x0000FF00 & ((g1 + g2) << 0)) 5149 | (0x000000FF & ((r1 + r2) >> 8)); 5150 *destination = result; 5151 } 5152 } 5153 5154 return; 5155 } 5156 #endif 5157 5158 if (c < 0 || c > 127) c = '?'; 5159 5160 UIRectangle rectangle = UIRectangleIntersection(painter->clip, UI_RECT_4(x0, x0 + 8, y0, y0 + 16)); 5161 5162 const uint8_t *data = (const uint8_t *) _uiFont + c * 16; 5163 5164 for (int i = rectangle.t; i < rectangle.b; i++) { 5165 uint32_t *bits = painter->bits + i * painter->width + rectangle.l; 5166 uint8_t byte = data[i - y0]; 5167 5168 for (int j = rectangle.l; j < rectangle.r; j++) { 5169 if (byte & (1 << (j - x0))) { 5170 *bits = color; 5171 } 5172 5173 bits++; 5174 } 5175 } 5176 } 5177 5178 UIFont *UIFontCreate(const char *cPath, uint32_t size) { 5179 UIFont *font = (UIFont *) UI_CALLOC(sizeof(UIFont)); 5180 5181 #ifdef UI_FREETYPE 5182 #ifdef UI_UNICODE 5183 font->glyphs = (FT_Bitmap *) UI_CALLOC(sizeof(FT_Bitmap) * (_UNICODE_MAX_CODEPOINT + 1)); 5184 font->glyphsRendered = (bool *) UI_CALLOC(sizeof(bool) * (_UNICODE_MAX_CODEPOINT + 1)); 5185 font->glyphOffsetsX = (int *) UI_CALLOC(sizeof(int) * (_UNICODE_MAX_CODEPOINT + 1)); 5186 font->glyphOffsetsY = (int *) UI_CALLOC(sizeof(int) * (_UNICODE_MAX_CODEPOINT + 1)); 5187 #endif 5188 if (cPath) { 5189 int ret = FT_New_Face(ui.ft, cPath, 0, &font->font); 5190 if (ret == 0) { 5191 FT_Select_Charmap(font->font, FT_ENCODING_UNICODE); 5192 if (FT_HAS_FIXED_SIZES(font->font) && font->font->num_fixed_sizes) { 5193 // Look for the smallest strike that's at least `size`. 5194 int j = 0; 5195 5196 for (int i = 0; i < font->font->num_fixed_sizes; i++) { 5197 if ((uint32_t) font->font->available_sizes[i].height >= size 5198 && font->font->available_sizes[i].y_ppem < font->font->available_sizes[j].y_ppem) { 5199 j = i; 5200 } 5201 } 5202 5203 FT_Set_Pixel_Sizes(font->font, font->font->available_sizes[j].x_ppem / 64, font->font->available_sizes[j].y_ppem / 64); 5204 } else { 5205 FT_Set_Char_Size(font->font, 0, size * 64, 100, 100); 5206 } 5207 5208 FT_Load_Char(font->font, 'a', FT_LOAD_DEFAULT); 5209 font->glyphWidth = font->font->glyph->advance.x / 64; 5210 font->glyphHeight = (font->font->size->metrics.ascender - font->font->size->metrics.descender) / 64; 5211 font->isFreeType = true; 5212 return font; 5213 } else 5214 printf("Cannot load font %s : %d\n", cPath, ret); 5215 } 5216 #endif 5217 5218 font->glyphWidth = 9; 5219 font->glyphHeight = 16; 5220 return font; 5221 } 5222 5223 UIFont *UIFontActivate(UIFont *font) { 5224 UIFont *previous = ui.activeFont; 5225 ui.activeFont = font; 5226 return previous; 5227 } 5228 5229 ///////////////////////////////////////// 5230 // Debugging. 5231 ///////////////////////////////////////// 5232 5233 #ifdef UI_DEBUG 5234 5235 void UIInspectorLog(const char *cFormat, ...) { 5236 va_list arguments; 5237 va_start(arguments, cFormat); 5238 char buffer[4096]; 5239 vsnprintf(buffer, sizeof(buffer), cFormat, arguments); 5240 UICodeInsertContent(ui.inspectorLog, buffer, -1, false); 5241 va_end(arguments); 5242 UIElementRefresh(&ui.inspectorLog->e); 5243 } 5244 5245 UIElement *_UIInspectorFindNthElement(UIElement *element, int *index, int *depth) { 5246 if (*index == 0) { 5247 return element; 5248 } 5249 5250 *index = *index - 1; 5251 5252 for (uint32_t i = 0; i < element->childCount; i++) { 5253 UIElement *child = element->children[i]; 5254 5255 if (!(child->flags & (UI_ELEMENT_DESTROY | UI_ELEMENT_HIDE))) { 5256 UIElement *result = _UIInspectorFindNthElement(child, index, depth); 5257 5258 if (result) { 5259 if (depth) { 5260 *depth = *depth + 1; 5261 } 5262 5263 return result; 5264 } 5265 } 5266 } 5267 5268 return NULL; 5269 } 5270 5271 int _UIInspectorTableMessage(UIElement *element, UIMessage message, int di, void *dp) { 5272 if (!ui.inspectorTarget) { 5273 return 0; 5274 } 5275 5276 if (message == UI_MSG_TABLE_GET_ITEM) { 5277 UITableGetItem *m = (UITableGetItem *) dp; 5278 int index = m->index; 5279 int depth = 0; 5280 UIElement *element = _UIInspectorFindNthElement(&ui.inspectorTarget->e, &index, &depth); 5281 if (!element) return 0; 5282 5283 if (m->column == 0) { 5284 return snprintf(m->buffer, m->bufferBytes, "%.*s%s", depth * 2, " ", element->cClassName); 5285 } else if (m->column == 1) { 5286 return snprintf(m->buffer, m->bufferBytes, "%d:%d, %d:%d", UI_RECT_ALL(element->bounds)); 5287 } else if (m->column == 2) { 5288 return snprintf(m->buffer, m->bufferBytes, "%d%c", element->id, element->window->focused == element ? '*' : ' '); 5289 } 5290 } else if (message == UI_MSG_MOUSE_MOVE) { 5291 int index = UITableHitTest(ui.inspectorTable, element->window->cursorX, element->window->cursorY); 5292 UIElement *element = NULL; 5293 if (index >= 0) element = _UIInspectorFindNthElement(&ui.inspectorTarget->e, &index, NULL); 5294 UIWindow *window = ui.inspectorTarget; 5295 UIPainter painter = { 0 }; 5296 window->updateRegion = window->e.bounds; 5297 painter.bits = window->bits; 5298 painter.width = window->width; 5299 painter.height = window->height; 5300 painter.clip = UI_RECT_2S(window->width, window->height); 5301 5302 for (int i = 0; i < window->width * window->height; i++) { 5303 window->bits[i] = 0xFF00FF; 5304 } 5305 5306 _UIElementPaint(&window->e, &painter); 5307 painter.clip = UI_RECT_2S(window->width, window->height); 5308 5309 if (element) { 5310 UIDrawInvert(&painter, element->bounds); 5311 UIDrawInvert(&painter, UIRectangleAdd(element->bounds, UI_RECT_1I(4))); 5312 } 5313 5314 _UIWindowEndPaint(window, &painter); 5315 } 5316 5317 return 0; 5318 } 5319 5320 void _UIInspectorCreate() { 5321 ui.inspector = UIWindowCreate(0, UI_WINDOW_INSPECTOR, "Inspector", 0, 0); 5322 UISplitPane *splitPane = UISplitPaneCreate(&ui.inspector->e, 0, 0.5f); 5323 ui.inspectorTable = UITableCreate(&splitPane->e, 0, "Class\tBounds\tID"); 5324 ui.inspectorTable->e.messageUser = _UIInspectorTableMessage; 5325 ui.inspectorLog = UICodeCreate(&splitPane->e, 0); 5326 } 5327 5328 int _UIInspectorCountElements(UIElement *element) { 5329 int count = 1; 5330 5331 for (uint32_t i = 0; i < element->childCount; i++) { 5332 UIElement *child = element->children[i]; 5333 5334 if (!(child->flags & (UI_ELEMENT_DESTROY | UI_ELEMENT_HIDE))) { 5335 count += _UIInspectorCountElements(child); 5336 } 5337 } 5338 5339 return count; 5340 } 5341 5342 void _UIInspectorRefresh() { 5343 if (!ui.inspectorTarget || !ui.inspector || !ui.inspectorTable) return; 5344 ui.inspectorTable->itemCount = _UIInspectorCountElements(&ui.inspectorTarget->e); 5345 UITableResizeColumns(ui.inspectorTable); 5346 UIElementRefresh(&ui.inspectorTable->e); 5347 } 5348 5349 void _UIInspectorSetFocusedWindow(UIWindow *window) { 5350 if (!ui.inspector || !ui.inspectorTable) return; 5351 5352 if (window->e.flags & UI_WINDOW_INSPECTOR) { 5353 return; 5354 } 5355 5356 if (ui.inspectorTarget != window) { 5357 ui.inspectorTarget = window; 5358 _UIInspectorRefresh(); 5359 } 5360 } 5361 5362 #else 5363 5364 void _UIInspectorCreate() {} 5365 void _UIInspectorSetFocusedWindow(UIWindow *window) {} 5366 void _UIInspectorRefresh() {} 5367 5368 #endif 5369 5370 ///////////////////////////////////////// 5371 // Automation for tests. 5372 ///////////////////////////////////////// 5373 5374 #ifdef UI_AUTOMATION_TESTS 5375 5376 int UIAutomationRunTests(); 5377 5378 void UIAutomationProcessMessage() { 5379 int result; 5380 _UIMessageLoopSingle(&result); 5381 } 5382 5383 void UIAutomationKeyboardTypeSingle(intptr_t code, bool ctrl, bool shift, bool alt) { 5384 UIWindow *window = ui.windows; // TODO Get the focused window. 5385 UIKeyTyped m = { 0 }; 5386 m.code = code; 5387 window->ctrl = ctrl; 5388 window->alt = alt; 5389 window->shift = shift; 5390 _UIWindowInputEvent(window, UI_MSG_KEY_TYPED, 0, &m); 5391 window->ctrl = false; 5392 window->alt = false; 5393 window->shift = false; 5394 } 5395 5396 void UIAutomationKeyboardType(const char *string) { 5397 UIWindow *window = ui.windows; // TODO Get the focused window. 5398 5399 UIKeyTyped m = { 0 }; 5400 char c[2]; 5401 m.text = c; 5402 m.textBytes = 1; 5403 c[1] = 0; 5404 5405 for (int i = 0; string[i]; i++) { 5406 window->ctrl = false; 5407 window->alt = false; 5408 window->shift = (c[0] >= 'A' && c[0] <= 'Z'); 5409 c[0] = string[i]; 5410 m.code = (c[0] >= 'A' && c[0] <= 'Z') ? UI_KEYCODE_LETTER(c[0]) 5411 : c[0] == '\n' ? UI_KEYCODE_ENTER 5412 : c[0] == '\t' ? UI_KEYCODE_TAB 5413 : c[0] == ' ' ? UI_KEYCODE_SPACE 5414 : (c[0] >= '0' && c[0] <= '9') ? UI_KEYCODE_DIGIT(c[0]) : 0; 5415 _UIWindowInputEvent(window, UI_MSG_KEY_TYPED, 0, &m); 5416 } 5417 5418 window->ctrl = false; 5419 window->alt = false; 5420 window->shift = false; 5421 } 5422 5423 bool UIAutomationCheckCodeLineMatches(UICode *code, int lineIndex, const char *input) { 5424 if (lineIndex < 1 || lineIndex > code->lineCount) return false; 5425 int bytes = 0; 5426 for (int i = 0; input[i]; i++) bytes++; 5427 if (bytes != code->lines[lineIndex - 1].bytes) return false; 5428 for (int i = 0; input[i]; i++) if (code->content[code->lines[lineIndex - 1].offset + i] != input[i]) return false; 5429 return true; 5430 } 5431 5432 bool UIAutomationCheckTableItemMatches(UITable *table, int row, int column, const char *input) { 5433 int bytes = 0; 5434 for (int i = 0; input[i]; i++) bytes++; 5435 if (row < 0 || row >= table->itemCount) return false; 5436 if (column < 0 || column >= table->columnCount) return false; 5437 char *buffer = (char *) UI_MALLOC(bytes + 1); 5438 UITableGetItem m = { 0 }; 5439 m.buffer = buffer; 5440 m.bufferBytes = bytes + 1; 5441 m.column = column; 5442 m.index = row; 5443 int length = UIElementMessage(&table->e, UI_MSG_TABLE_GET_ITEM, 0, &m); 5444 if (length != bytes) return false; 5445 for (int i = 0; input[i]; i++) if (buffer[i] != input[i]) return false; 5446 return true; 5447 } 5448 5449 #endif 5450 5451 ///////////////////////////////////////// 5452 // Common platform layer functionality. 5453 ///////////////////////////////////////// 5454 5455 void _UIWindowDestroyCommon(UIWindow *window) { 5456 UI_FREE(window->bits); 5457 UI_FREE(window->shortcuts); 5458 } 5459 5460 void _UIInitialiseCommon() { 5461 ui.theme = uiThemeClassic; 5462 5463 #ifdef UI_FREETYPE 5464 FT_Init_FreeType(&ui.ft); 5465 #else 5466 UIFontActivate(UIFontCreate(0, 0)); 5467 #endif 5468 } 5469 5470 void _UIWindowAdd(UIWindow *window) { 5471 window->scale = 1.0f; 5472 window->e.window = window; 5473 window->hovered = &window->e; 5474 window->next = ui.windows; 5475 ui.windows = window; 5476 } 5477 5478 int _UIWindowMessageCommon(UIElement *element, UIMessage message, int di, void *dp) { 5479 if (message == UI_MSG_LAYOUT && element->childCount) { 5480 UIElementMove(element->children[0], element->bounds, false); 5481 if (element->window->dialog) UIElementMove(element->window->dialog, element->bounds, false); 5482 UIElementRepaint(element, NULL); 5483 } else if (message == UI_MSG_GET_CHILD_STABILITY) { 5484 return 3; // Both width and height of the child element are ignored. 5485 } 5486 5487 return 0; 5488 } 5489 5490 int UIMessageLoop() { 5491 _UIInspectorCreate(); 5492 _UIUpdate(); 5493 #ifdef UI_AUTOMATION_TESTS 5494 return UIAutomationRunTests(); 5495 #else 5496 int result = 0; 5497 while (!ui.quit && _UIMessageLoopSingle(&result)) ui.dialogResult = NULL; 5498 return result; 5499 #endif 5500 } 5501 5502 ///////////////////////////////////////// 5503 // Platform layers. 5504 ///////////////////////////////////////// 5505 5506 #ifdef UI_LINUX 5507 5508 const int UI_KEYCODE_A = XK_a; 5509 const int UI_KEYCODE_BACKSPACE = XK_BackSpace; 5510 const int UI_KEYCODE_DELETE = XK_Delete; 5511 const int UI_KEYCODE_DOWN = XK_Down; 5512 const int UI_KEYCODE_END = XK_End; 5513 const int UI_KEYCODE_ENTER = XK_Return; 5514 const int UI_KEYCODE_ESCAPE = XK_Escape; 5515 const int UI_KEYCODE_F1 = XK_F1; 5516 const int UI_KEYCODE_HOME = XK_Home; 5517 const int UI_KEYCODE_LEFT = XK_Left; 5518 const int UI_KEYCODE_RIGHT = XK_Right; 5519 const int UI_KEYCODE_SPACE = XK_space; 5520 const int UI_KEYCODE_TAB = XK_Tab; 5521 const int UI_KEYCODE_UP = XK_Up; 5522 const int UI_KEYCODE_INSERT = XK_Insert; 5523 const int UI_KEYCODE_0 = XK_0; 5524 const int UI_KEYCODE_BACKTICK = XK_grave; 5525 const int UI_KEYCODE_PAGE_DOWN = XK_Page_Down; 5526 const int UI_KEYCODE_PAGE_UP = XK_Page_Up; 5527 5528 int _UIWindowMessage(UIElement *element, UIMessage message, int di, void *dp) { 5529 if (message == UI_MSG_DEALLOCATE) { 5530 UIWindow *window = (UIWindow *) element; 5531 _UIWindowDestroyCommon(window); 5532 window->image->data = NULL; 5533 XDestroyImage(window->image); 5534 XDestroyIC(window->xic); 5535 XDestroyWindow(ui.display, ((UIWindow *) element)->window); 5536 } 5537 5538 return _UIWindowMessageCommon(element, message, di, dp); 5539 } 5540 5541 UIWindow *UIWindowCreate(UIWindow *owner, uint32_t flags, const char *cTitle, int _width, int _height) { 5542 _UIMenusClose(); 5543 5544 UIWindow *window = (UIWindow *) UIElementCreate(sizeof(UIWindow), NULL, flags | UI_ELEMENT_WINDOW, _UIWindowMessage, "Window"); 5545 _UIWindowAdd(window); 5546 if (owner) window->scale = owner->scale; 5547 5548 int width = (flags & UI_WINDOW_MENU) ? 1 : _width ? _width : 800; 5549 int height = (flags & UI_WINDOW_MENU) ? 1 : _height ? _height : 600; 5550 5551 XSetWindowAttributes attributes = {}; 5552 attributes.override_redirect = flags & UI_WINDOW_MENU; 5553 5554 char *xdg_current_desktop = getenv("XDG_CURRENT_DESKTOP"); 5555 if (xdg_current_desktop && strcmp(xdg_current_desktop, "Hyprland") == 0) { 5556 window->window = XCreateWindow(ui.display, DefaultRootWindow(ui.display), 0, 0, width, height, 1, 0, 5557 InputOutput, CopyFromParent, CWOverrideRedirect, &attributes); 5558 XSetWindowBorderWidth(ui.display, window->window, 0); 5559 } else { 5560 window->window = XCreateWindow(ui.display, DefaultRootWindow(ui.display), 0, 0, width, height, 0, 0, 5561 InputOutput, CopyFromParent, CWOverrideRedirect, &attributes); 5562 } 5563 if (cTitle) XStoreName(ui.display, window->window, cTitle); 5564 XSelectInput(ui.display, window->window, SubstructureNotifyMask | ExposureMask | PointerMotionMask 5565 | ButtonPressMask | ButtonReleaseMask | KeyPressMask | KeyReleaseMask | StructureNotifyMask 5566 | EnterWindowMask | LeaveWindowMask | ButtonMotionMask | KeymapStateMask | FocusChangeMask | PropertyChangeMask); 5567 5568 if (flags & UI_WINDOW_MAXIMIZE) { 5569 Atom atoms[2] = { XInternAtom(ui.display, "_NET_WM_STATE_MAXIMIZED_HORZ", 0), XInternAtom(ui.display, "_NET_WM_STATE_MAXIMIZED_VERT", 0) }; 5570 XChangeProperty(ui.display, window->window, XInternAtom(ui.display, "_NET_WM_STATE", 0), XA_ATOM, 32, PropModeReplace, (unsigned char *) atoms, 2); 5571 } 5572 5573 if (flags & UI_WINDOW_DIALOG) { 5574 Atom atoms[] = { 5575 XInternAtom(ui.display, "_NET_WM_STATE_MODAL", 0), 5576 XInternAtom(ui.display, "_NET_WM_STATE_SKIP_TASKBAR", 0) 5577 }; 5578 XChangeProperty(ui.display, window->window, XInternAtom(ui.display, "_NET_WM_STATE", 0), XA_ATOM, 32, PropModeAppend, (unsigned char *)atoms, sizeof(atoms)/sizeof(*atoms)); 5579 Atom atom1 = XInternAtom(ui.display, "_NET_WM_WINDOW_TYPE_DIALOG", 0); 5580 XChangeProperty(ui.display, window->window, XInternAtom(ui.display, "_NET_WM_WINDOW_TYPE", 0), XA_ATOM, 32, PropModeAppend, (unsigned char *)&atom1, 1); 5581 5582 XSetTransientForHint(ui.display, window->window, DefaultRootWindow(ui.display)); 5583 } 5584 5585 if (~flags & UI_WINDOW_MENU) { 5586 XMapRaised(ui.display, window->window); 5587 } 5588 5589 if (flags & UI_WINDOW_CENTER_IN_OWNER) { 5590 int x = 0, y = 0; 5591 _UIWindowGetScreenPosition(owner, &x, &y); 5592 XMoveResizeWindow(ui.display, window->window, x + owner->width / 2 - width / 2, y + owner->height / 2 - height / 2, width, height); 5593 } 5594 5595 XSetWMProtocols(ui.display, window->window, &ui.windowClosedID, 1); 5596 window->image = XCreateImage(ui.display, ui.visual, 24, ZPixmap, 0, NULL, 10, 10, 32, 0); 5597 5598 window->xic = XCreateIC(ui.xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, window->window, XNFocusWindow, window->window, NULL); 5599 5600 int dndVersion = 4; 5601 XChangeProperty(ui.display, window->window, ui.dndAwareID, XA_ATOM, 32 /* bits */, PropModeReplace, (uint8_t *) &dndVersion, 1); 5602 5603 return window; 5604 } 5605 5606 Display *_UIX11GetDisplay() { 5607 return ui.display; 5608 } 5609 5610 UIWindow *_UIFindWindow(Window window) { 5611 UIWindow *w = ui.windows; 5612 5613 while (w) { 5614 if (w->window == window) { 5615 return w; 5616 } 5617 5618 w = w->next; 5619 } 5620 5621 return NULL; 5622 } 5623 5624 void _UIClipboardWriteText(UIWindow *window, char *text) { 5625 UI_FREE(ui.pasteText); 5626 ui.pasteText = text; 5627 XSetSelectionOwner(ui.display, ui.clipboardID, window->window, 0); 5628 } 5629 5630 char *_UIClipboardReadTextStart(UIWindow *window, size_t *bytes) { 5631 Window clipboardOwner = XGetSelectionOwner(ui.display, ui.clipboardID); 5632 5633 if (clipboardOwner == None) { 5634 return NULL; 5635 } 5636 5637 if (_UIFindWindow(clipboardOwner)) { 5638 *bytes = strlen(ui.pasteText); 5639 char *copy = (char *) UI_MALLOC(*bytes); 5640 memcpy(copy, ui.pasteText, *bytes); 5641 return copy; 5642 } 5643 5644 XConvertSelection(ui.display, ui.clipboardID, XA_STRING, ui.xSelectionDataID, window->window, CurrentTime); 5645 XSync(ui.display, 0); 5646 XNextEvent(ui.display, &ui.copyEvent); 5647 5648 // Hack to get around the fact that PropertyNotify arrives before SelectionNotify. 5649 // We need PropertyNotify for incremental transfers. 5650 while (ui.copyEvent.type == PropertyNotify) { 5651 XNextEvent(ui.display, &ui.copyEvent); 5652 } 5653 5654 if (ui.copyEvent.type == SelectionNotify && ui.copyEvent.xselection.selection == ui.clipboardID && ui.copyEvent.xselection.property) { 5655 Atom target; 5656 // This `itemAmount` is actually `bytes_after_return` 5657 unsigned long size, itemAmount; 5658 char *data; 5659 int format; 5660 XGetWindowProperty(ui.copyEvent.xselection.display, ui.copyEvent.xselection.requestor, ui.copyEvent.xselection.property, 0L, ~0L, 0, 5661 AnyPropertyType, &target, &format, &size, &itemAmount, (unsigned char **) &data); 5662 5663 // We have to allocate for incremental transfers but we don't have to allocate for non-incremental transfers. 5664 // I'm allocating for both here to make _UIClipboardReadTextEnd work the same for both 5665 if (target != ui.incrID) { 5666 *bytes = size; 5667 char *copy = (char *) UI_MALLOC(*bytes); 5668 memcpy(copy, data, *bytes); 5669 XFree(data); 5670 XDeleteProperty(ui.copyEvent.xselection.display, ui.copyEvent.xselection.requestor, ui.copyEvent.xselection.property); 5671 return copy; 5672 } 5673 5674 XFree(data); 5675 XDeleteProperty(ui.display, ui.copyEvent.xselection.requestor, ui.copyEvent.xselection.property); 5676 XSync(ui.display, 0); 5677 5678 *bytes = 0; 5679 char *fullData = NULL; 5680 5681 while (true) { 5682 // TODO Timeout. 5683 XNextEvent(ui.display, &ui.copyEvent); 5684 5685 if (ui.copyEvent.type == PropertyNotify) { 5686 // The other case - PropertyDelete would be caused by us and can be ignored 5687 if (ui.copyEvent.xproperty.state == PropertyNewValue) { 5688 unsigned long chunkSize; 5689 5690 // Note that this call deletes the property. 5691 XGetWindowProperty(ui.display, ui.copyEvent.xproperty.window, ui.copyEvent.xproperty.atom, 0L, ~0L, 5692 True, AnyPropertyType, &target, &format, &chunkSize, &itemAmount, (unsigned char **) &data); 5693 5694 if (chunkSize == 0) { 5695 return fullData; 5696 } else { 5697 ptrdiff_t currentOffset = *bytes; 5698 *bytes += chunkSize; 5699 fullData = (char *) UI_REALLOC(fullData, *bytes); 5700 memcpy(fullData + currentOffset, data, chunkSize); 5701 } 5702 5703 XFree(data); 5704 } 5705 } 5706 } 5707 } else { 5708 // TODO What should happen in this case? Is the next event always going to be the selection event? 5709 return NULL; 5710 } 5711 } 5712 5713 void _UIClipboardReadTextEnd(UIWindow *window, char *text) { 5714 if (text) { 5715 UI_FREE(text); 5716 } 5717 } 5718 5719 void UIInitialise() { 5720 _UIInitialiseCommon(); 5721 5722 XInitThreads(); 5723 5724 ui.display = XOpenDisplay(NULL); 5725 ui.visual = XDefaultVisual(ui.display, 0); 5726 5727 ui.windowClosedID = XInternAtom(ui.display, "WM_DELETE_WINDOW", 0); 5728 ui.primaryID = XInternAtom(ui.display, "PRIMARY", 0); 5729 ui.dndEnterID = XInternAtom(ui.display, "XdndEnter", 0); 5730 ui.dndPositionID = XInternAtom(ui.display, "XdndPosition", 0); 5731 ui.dndStatusID = XInternAtom(ui.display, "XdndStatus", 0); 5732 ui.dndActionCopyID = XInternAtom(ui.display, "XdndActionCopy", 0); 5733 ui.dndDropID = XInternAtom(ui.display, "XdndDrop", 0); 5734 ui.dndSelectionID = XInternAtom(ui.display, "XdndSelection", 0); 5735 ui.dndFinishedID = XInternAtom(ui.display, "XdndFinished", 0); 5736 ui.dndAwareID = XInternAtom(ui.display, "XdndAware", 0); 5737 ui.uriListID = XInternAtom(ui.display, "text/uri-list", 0); 5738 ui.plainTextID = XInternAtom(ui.display, "text/plain", 0); 5739 ui.clipboardID = XInternAtom(ui.display, "CLIPBOARD", 0); 5740 ui.xSelectionDataID = XInternAtom(ui.display, "XSEL_DATA", 0); 5741 ui.textID = XInternAtom(ui.display, "TEXT", 0); 5742 ui.targetID = XInternAtom(ui.display, "TARGETS", 0); 5743 ui.incrID = XInternAtom(ui.display, "INCR", 0); 5744 5745 ui.cursors[UI_CURSOR_ARROW] = XCreateFontCursor(ui.display, XC_left_ptr); 5746 ui.cursors[UI_CURSOR_TEXT] = XCreateFontCursor(ui.display, XC_xterm); 5747 ui.cursors[UI_CURSOR_SPLIT_V] = XCreateFontCursor(ui.display, XC_sb_v_double_arrow); 5748 ui.cursors[UI_CURSOR_SPLIT_H] = XCreateFontCursor(ui.display, XC_sb_h_double_arrow); 5749 ui.cursors[UI_CURSOR_FLIPPED_ARROW] = XCreateFontCursor(ui.display, XC_right_ptr); 5750 ui.cursors[UI_CURSOR_CROSS_HAIR] = XCreateFontCursor(ui.display, XC_crosshair); 5751 ui.cursors[UI_CURSOR_HAND] = XCreateFontCursor(ui.display, XC_hand1); 5752 ui.cursors[UI_CURSOR_RESIZE_UP] = XCreateFontCursor(ui.display, XC_top_side); 5753 ui.cursors[UI_CURSOR_RESIZE_LEFT] = XCreateFontCursor(ui.display, XC_left_side); 5754 ui.cursors[UI_CURSOR_RESIZE_UP_RIGHT] = XCreateFontCursor(ui.display, XC_top_right_corner); 5755 ui.cursors[UI_CURSOR_RESIZE_UP_LEFT] = XCreateFontCursor(ui.display, XC_top_left_corner); 5756 ui.cursors[UI_CURSOR_RESIZE_DOWN] = XCreateFontCursor(ui.display, XC_bottom_side); 5757 ui.cursors[UI_CURSOR_RESIZE_RIGHT] = XCreateFontCursor(ui.display, XC_right_side); 5758 ui.cursors[UI_CURSOR_RESIZE_DOWN_LEFT] = XCreateFontCursor(ui.display, XC_bottom_left_corner); 5759 ui.cursors[UI_CURSOR_RESIZE_DOWN_RIGHT] = XCreateFontCursor(ui.display, XC_bottom_right_corner); 5760 5761 XSetLocaleModifiers(""); 5762 5763 ui.xim = XOpenIM(ui.display, 0, 0, 0); 5764 5765 if(!ui.xim){ 5766 XSetLocaleModifiers("@im=none"); 5767 ui.xim = XOpenIM(ui.display, 0, 0, 0); 5768 } 5769 } 5770 5771 void _UIWindowSetCursor(UIWindow *window, int cursor) { 5772 XDefineCursor(ui.display, window->window, ui.cursors[cursor]); 5773 } 5774 5775 void _UIX11ResetCursor(UIWindow *window) { 5776 XDefineCursor(ui.display, window->window, ui.cursors[UI_CURSOR_ARROW]); 5777 } 5778 5779 void _UIWindowEndPaint(UIWindow *window, UIPainter *painter) { 5780 (void) painter; 5781 5782 XPutImage(ui.display, window->window, DefaultGC(ui.display, 0), window->image, 5783 UI_RECT_TOP_LEFT(window->updateRegion), UI_RECT_TOP_LEFT(window->updateRegion), 5784 UI_RECT_SIZE(window->updateRegion)); 5785 } 5786 5787 void _UIWindowGetScreenPosition(UIWindow *window, int *_x, int *_y) { 5788 Window child; 5789 XTranslateCoordinates(ui.display, window->window, DefaultRootWindow(ui.display), 0, 0, _x, _y, &child); 5790 } 5791 5792 void UIMenuShow(UIMenu *menu) { 5793 Window child; 5794 5795 // Find the screen that contains the point the menu was created at. 5796 Screen *menuScreen = NULL; 5797 int screenX, screenY; 5798 5799 for (int i = 0; i < ScreenCount(ui.display); i++) { 5800 Screen *screen = ScreenOfDisplay(ui.display, i); 5801 int x, y; 5802 XTranslateCoordinates(ui.display, screen->root, DefaultRootWindow(ui.display), 0, 0, &x, &y, &child); 5803 5804 if (menu->pointX >= x && menu->pointX < x + screen->width && menu->pointY >= y && menu->pointY < y + screen->height) { 5805 menuScreen = screen; 5806 screenX = x, screenY = y; 5807 break; 5808 } 5809 } 5810 5811 int width, height; 5812 _UIMenuPrepare(menu, &width, &height); 5813 5814 { 5815 // Clamp the menu to the bounds of the window. 5816 // This step shouldn't be necessary with the screen clamping below, but there are some buggy X11 drivers that report screen sizes incorrectly. 5817 int wx, wy; 5818 UIWindow *parentWindow = menu->parentWindow; 5819 XTranslateCoordinates(ui.display, parentWindow->window, DefaultRootWindow(ui.display), 0, 0, &wx, &wy, &child); 5820 if (menu->pointX + width > wx + parentWindow->width) menu->pointX = wx + parentWindow->width - width; 5821 if (menu->pointY + height > wy + parentWindow->height) menu->pointY = wy + parentWindow->height - height; 5822 if (menu->pointX < wx) menu->pointX = wx; 5823 if (menu->pointY < wy) menu->pointY = wy; 5824 } 5825 5826 if (menuScreen) { 5827 // Clamp to the bounds of the screen. 5828 if (menu->pointX + width > screenX + menuScreen->width) menu->pointX = screenX + menuScreen->width - width; 5829 if (menu->pointY + height > screenY + menuScreen->height) menu->pointY = screenY + menuScreen->height - height; 5830 if (menu->pointX < screenX) menu->pointX = screenX; 5831 if (menu->pointY < screenY) menu->pointY = screenY; 5832 if (menu->pointX + width > screenX + menuScreen->width) width = screenX + menuScreen->width - menu->pointX; 5833 if (menu->pointY + height > screenY + menuScreen->height) height = screenY + menuScreen->height - menu->pointY; 5834 } 5835 5836 Atom properties[] = { 5837 XInternAtom(ui.display, "_NET_WM_WINDOW_TYPE", true), 5838 XInternAtom(ui.display, "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU", true), 5839 XInternAtom(ui.display, "_MOTIF_WM_HINTS", true), 5840 }; 5841 5842 XChangeProperty(ui.display, menu->e.window->window, properties[0], XA_ATOM, 32, PropModeReplace, (uint8_t *) properties, 2); 5843 XSetTransientForHint(ui.display, menu->e.window->window, DefaultRootWindow(ui.display)); 5844 5845 struct Hints { 5846 int flags; 5847 int functions; 5848 int decorations; 5849 int inputMode; 5850 int status; 5851 }; 5852 5853 struct Hints hints = { 0 }; 5854 hints.flags = 2; 5855 XChangeProperty(ui.display, menu->e.window->window, properties[2], properties[2], 32, PropModeReplace, (uint8_t *) &hints, 5); 5856 5857 XMapWindow(ui.display, menu->e.window->window); 5858 XMoveResizeWindow(ui.display, menu->e.window->window, menu->pointX, menu->pointY, width, height); 5859 } 5860 5861 void UIWindowPack(UIWindow *window, int _width) { 5862 int width = _width ? _width : UIElementMessage(window->e.children[0], UI_MSG_GET_WIDTH, 0, 0); 5863 int height = UIElementMessage(window->e.children[0], UI_MSG_GET_HEIGHT, width, 0); 5864 XResizeWindow(ui.display, window->window, width, height); 5865 } 5866 5867 bool _UIProcessEvent(XEvent *event) { 5868 if (event->type == ClientMessage && (Atom) event->xclient.data.l[0] == ui.windowClosedID) { 5869 UIWindow *window = _UIFindWindow(event->xclient.window); 5870 if (!window) return false; 5871 bool exit = !UIElementMessage(&window->e, UI_MSG_WINDOW_CLOSE, 0, 0); 5872 if (exit) return true; 5873 _UIUpdate(); 5874 return false; 5875 } else if (event->type == Expose) { 5876 UIWindow *window = _UIFindWindow(event->xexpose.window); 5877 if (!window) return false; 5878 XPutImage(ui.display, window->window, DefaultGC(ui.display, 0), window->image, 0, 0, 0, 0, window->width, window->height); 5879 } else if (event->type == ConfigureNotify) { 5880 UIWindow *window = _UIFindWindow(event->xconfigure.window); 5881 if (!window) return false; 5882 5883 if (window->width != event->xconfigure.width || window->height != event->xconfigure.height) { 5884 window->width = event->xconfigure.width; 5885 window->height = event->xconfigure.height; 5886 window->bits = (uint32_t *) UI_REALLOC(window->bits, window->width * window->height * 4); 5887 window->image->width = window->width; 5888 window->image->height = window->height; 5889 window->image->bytes_per_line = window->width * 4; 5890 window->image->data = (char *) window->bits; 5891 window->e.bounds = UI_RECT_2S(window->width, window->height); 5892 window->e.clip = UI_RECT_2S(window->width, window->height); 5893 #ifdef UI_DEBUG 5894 for (int i = 0; i < window->width * window->height; i++) window->bits[i] = 0xFF00FF; 5895 #endif 5896 UIElementRelayout(&window->e); 5897 _UIUpdate(); 5898 } 5899 } else if (event->type == MotionNotify) { 5900 UIWindow *window = _UIFindWindow(event->xmotion.window); 5901 if (!window) return false; 5902 window->cursorX = event->xmotion.x; 5903 window->cursorY = event->xmotion.y; 5904 _UIWindowInputEvent(window, UI_MSG_MOUSE_MOVE, 0, 0); 5905 } else if (event->type == LeaveNotify) { 5906 UIWindow *window = _UIFindWindow(event->xcrossing.window); 5907 if (!window) return false; 5908 5909 if (!window->pressed) { 5910 window->cursorX = -1; 5911 window->cursorY = -1; 5912 } 5913 5914 _UIWindowInputEvent(window, UI_MSG_MOUSE_MOVE, 0, 0); 5915 } else if (event->type == ButtonPress || event->type == ButtonRelease) { 5916 UIWindow *window = _UIFindWindow(event->xbutton.window); 5917 if (!window) return false; 5918 window->cursorX = event->xbutton.x; 5919 window->cursorY = event->xbutton.y; 5920 5921 if (event->xbutton.button >= 1 && event->xbutton.button <= 3) { 5922 _UIWindowInputEvent(window, (UIMessage) ((event->type == ButtonPress ? UI_MSG_LEFT_DOWN : UI_MSG_LEFT_UP) 5923 + event->xbutton.button * 2 - 2), 0, 0); 5924 } else if (event->xbutton.button == 4) { 5925 _UIWindowInputEvent(window, UI_MSG_MOUSE_WHEEL, -72, 0); 5926 } else if (event->xbutton.button == 5) { 5927 _UIWindowInputEvent(window, UI_MSG_MOUSE_WHEEL, 72, 0); 5928 } 5929 5930 _UIInspectorSetFocusedWindow(window); 5931 } else if (event->type == KeyPress) { 5932 UIWindow *window = _UIFindWindow(event->xkey.window); 5933 if (!window) return false; 5934 5935 if (event->xkey.x == 0x7123 && event->xkey.y == 0x7456) { 5936 // HACK! See UIWindowPostMessage. 5937 uintptr_t p = ((uintptr_t) (event->xkey.x_root & 0xFFFF) << 0) | ((uintptr_t) (event->xkey.y_root & 0xFFFF) << 16); 5938 #if INTPTR_MAX == INT64_MAX 5939 p |= (uintptr_t) (event->xkey.time & 0xFFFFFFFF) << 32; 5940 #endif 5941 UIElementMessage(&window->e, (UIMessage) event->xkey.state, 0, (void *) p); 5942 _UIUpdate(); 5943 } else { 5944 char text[32]; 5945 KeySym symbol = NoSymbol; 5946 Status status; 5947 // printf("%ld, %s\n", symbol, text); 5948 UIKeyTyped m = { 0 }; 5949 m.textBytes = Xutf8LookupString(window->xic, &event->xkey, text, sizeof(text) - 1, &symbol, &status); 5950 m.text = text; 5951 m.code = XLookupKeysym(&event->xkey, 0); 5952 5953 if (symbol == XK_Control_L || symbol == XK_Control_R) { 5954 window->ctrl = true; 5955 window->ctrlCode = event->xkey.keycode; 5956 _UIWindowInputEvent(window, UI_MSG_MOUSE_MOVE, 0, 0); 5957 } else if (symbol == XK_Shift_L || symbol == XK_Shift_R) { 5958 window->shift = true; 5959 window->shiftCode = event->xkey.keycode; 5960 _UIWindowInputEvent(window, UI_MSG_MOUSE_MOVE, 0, 0); 5961 } else if (symbol == XK_Alt_L || symbol == XK_Alt_R) { 5962 window->alt = true; 5963 window->altCode = event->xkey.keycode; 5964 _UIWindowInputEvent(window, UI_MSG_MOUSE_MOVE, 0, 0); 5965 } else if (symbol == XK_KP_Left) { 5966 m.code = UI_KEYCODE_LEFT; 5967 } else if (symbol == XK_KP_Right) { 5968 m.code = UI_KEYCODE_RIGHT; 5969 } else if (symbol == XK_KP_Up) { 5970 m.code = UI_KEYCODE_UP; 5971 } else if (symbol == XK_KP_Down) { 5972 m.code = UI_KEYCODE_DOWN; 5973 } else if (symbol == XK_KP_Home) { 5974 m.code = UI_KEYCODE_HOME; 5975 } else if (symbol == XK_KP_End) { 5976 m.code = UI_KEYCODE_END; 5977 } else if (symbol == XK_KP_Enter) { 5978 m.code = UI_KEYCODE_ENTER; 5979 } else if (symbol == XK_KP_Delete) { 5980 m.code = UI_KEYCODE_DELETE; 5981 } else if (symbol == XK_KP_Page_Up) { 5982 m.code = UI_KEYCODE_UP; 5983 } else if (symbol == XK_KP_Page_Down) { 5984 m.code = UI_KEYCODE_DOWN; 5985 } 5986 5987 _UIWindowInputEvent(window, UI_MSG_KEY_TYPED, 0, &m); 5988 } 5989 } else if (event->type == KeyRelease) { 5990 UIWindow *window = _UIFindWindow(event->xkey.window); 5991 if (!window) return false; 5992 5993 if (event->xkey.keycode == window->ctrlCode) { 5994 window->ctrl = false; 5995 _UIWindowInputEvent(window, UI_MSG_MOUSE_MOVE, 0, 0); 5996 } else if (event->xkey.keycode == window->shiftCode) { 5997 window->shift = false; 5998 _UIWindowInputEvent(window, UI_MSG_MOUSE_MOVE, 0, 0); 5999 } else if (event->xkey.keycode == window->altCode) { 6000 window->alt = false; 6001 _UIWindowInputEvent(window, UI_MSG_MOUSE_MOVE, 0, 0); 6002 } else { 6003 char text[32]; 6004 KeySym symbol = NoSymbol; 6005 Status status; 6006 UIKeyTyped m = { 0 }; 6007 m.textBytes = Xutf8LookupString(window->xic, &event->xkey, text, sizeof(text) - 1, &symbol, &status); 6008 m.text = text; 6009 m.code = XLookupKeysym(&event->xkey, 0); 6010 _UIWindowInputEvent(window, UI_MSG_KEY_RELEASED, 0, &m); 6011 } 6012 } else if (event->type == FocusIn) { 6013 UIWindow *window = _UIFindWindow(event->xfocus.window); 6014 if (!window) return false; 6015 window->ctrl = window->shift = window->alt = false; 6016 UIElementMessage(&window->e, UI_MSG_WINDOW_ACTIVATE, 0, 0); 6017 } else if (event->type == FocusOut || event->type == ResizeRequest) { 6018 _UIMenusClose(); 6019 _UIUpdate(); 6020 } else if (event->type == ClientMessage && event->xclient.message_type == ui.dndEnterID) { 6021 UIWindow *window = _UIFindWindow(event->xclient.window); 6022 if (!window) return false; 6023 window->dragSource = (Window) event->xclient.data.l[0]; 6024 } else if (event->type == ClientMessage && event->xclient.message_type == ui.dndPositionID) { 6025 UIWindow *window = _UIFindWindow(event->xclient.window); 6026 if (!window) return false; 6027 XClientMessageEvent m = { 0 }; 6028 m.type = ClientMessage; 6029 m.display = event->xclient.display; 6030 m.window = (Window) event->xclient.data.l[0]; 6031 m.message_type = ui.dndStatusID; 6032 m.format = 32; 6033 m.data.l[0] = window->window; 6034 m.data.l[1] = true; 6035 m.data.l[4] = ui.dndActionCopyID; 6036 XSendEvent(ui.display, m.window, False, NoEventMask, (XEvent *) &m); 6037 XFlush(ui.display); 6038 } else if (event->type == ClientMessage && event->xclient.message_type == ui.dndDropID) { 6039 UIWindow *window = _UIFindWindow(event->xclient.window); 6040 if (!window) return false; 6041 6042 // TODO Dropping text. 6043 6044 if (!XConvertSelection(ui.display, ui.dndSelectionID, ui.uriListID, ui.primaryID, window->window, event->xclient.data.l[2])) { 6045 XClientMessageEvent m = { 0 }; 6046 m.type = ClientMessage; 6047 m.display = ui.display; 6048 m.window = window->dragSource; 6049 m.message_type = ui.dndFinishedID; 6050 m.format = 32; 6051 m.data.l[0] = window->window; 6052 m.data.l[1] = 0; 6053 m.data.l[2] = ui.dndActionCopyID; 6054 XSendEvent(ui.display, m.window, False, NoEventMask, (XEvent *) &m); 6055 XFlush(ui.display); 6056 } 6057 } else if (event->type == SelectionNotify) { 6058 UIWindow *window = _UIFindWindow(event->xselection.requestor); 6059 if (!window) return false; 6060 if (!window->dragSource) return false; 6061 6062 Atom type = None; 6063 int format = 0; 6064 unsigned long count = 0, bytesLeft = 0; 6065 uint8_t *data = NULL; 6066 XGetWindowProperty(ui.display, window->window, ui.primaryID, 0, 65536, False, AnyPropertyType, &type, &format, &count, &bytesLeft, &data); 6067 6068 if (format == 8 /* bits per character */) { 6069 if (event->xselection.target == ui.uriListID) { 6070 char *copy = (char *) UI_MALLOC(count); 6071 int fileCount = 0; 6072 6073 for (int i = 0; i < (int) count; i++) { 6074 copy[i] = data[i]; 6075 6076 if (i && data[i - 1] == '\r' && data[i] == '\n') { 6077 fileCount++; 6078 } 6079 } 6080 6081 char **files = (char **) UI_MALLOC(sizeof(char *) * fileCount); 6082 fileCount = 0; 6083 6084 for (int i = 0; i < (int) count; i++) { 6085 char *s = copy + i; 6086 while (!(i && data[i - 1] == '\r' && data[i] == '\n' && i < (int) count)) i++; 6087 copy[i - 1] = 0; 6088 6089 for (int j = 0; s[j]; j++) { 6090 if (s[j] == '%' && s[j + 1] && s[j + 2]) { 6091 char n[3]; 6092 n[0] = s[j + 1], n[1] = s[j + 2], n[2] = 0; 6093 s[j] = strtol(n, NULL, 16); 6094 if (!s[j]) break; 6095 UI_MEMMOVE(s + j + 1, s + j + 3, strlen(s) - j - 2); 6096 } 6097 } 6098 6099 if (s[0] == 'f' && s[1] == 'i' && s[2] == 'l' && s[3] == 'e' && s[4] == ':' && s[5] == '/' && s[6] == '/') { 6100 files[fileCount++] = s + 7; 6101 } 6102 } 6103 6104 UIElementMessage(&window->e, UI_MSG_WINDOW_DROP_FILES, fileCount, files); 6105 6106 UI_FREE(files); 6107 UI_FREE(copy); 6108 } else if (event->xselection.target == ui.plainTextID) { 6109 // TODO. 6110 } 6111 } 6112 6113 XFree(data); 6114 6115 XClientMessageEvent m = { 0 }; 6116 m.type = ClientMessage; 6117 m.display = ui.display; 6118 m.window = window->dragSource; 6119 m.message_type = ui.dndFinishedID; 6120 m.format = 32; 6121 m.data.l[0] = window->window; 6122 m.data.l[1] = true; 6123 m.data.l[2] = ui.dndActionCopyID; 6124 XSendEvent(ui.display, m.window, False, NoEventMask, (XEvent *) &m); 6125 XFlush(ui.display); 6126 6127 window->dragSource = 0; // Drag complete. 6128 _UIUpdate(); 6129 } else if (event->type == SelectionRequest) { 6130 UIWindow *window = _UIFindWindow(event->xclient.window); 6131 if (!window) return false; 6132 6133 if ((XGetSelectionOwner(ui.display, ui.clipboardID) == window->window) 6134 && (event->xselectionrequest.selection == ui.clipboardID)) { 6135 XSelectionRequestEvent requestEvent = event->xselectionrequest; 6136 Atom utf8ID = XInternAtom(ui.display, "UTF8_STRING", 1); 6137 if (utf8ID == None) utf8ID = XA_STRING; 6138 6139 Atom type = requestEvent.target; 6140 type = (type == ui.textID) ? XA_STRING : type; 6141 int changePropertyResult = 0; 6142 6143 if(requestEvent.target == XA_STRING || requestEvent.target == ui.textID || requestEvent.target == utf8ID) { 6144 changePropertyResult = XChangeProperty(requestEvent.display, requestEvent.requestor, requestEvent.property, 6145 type, 8, PropModeReplace, (const unsigned char *) ui.pasteText, strlen(ui.pasteText)); 6146 } else if (requestEvent.target == ui.targetID) { 6147 changePropertyResult = XChangeProperty(requestEvent.display, requestEvent.requestor, requestEvent.property, 6148 XA_ATOM, 32, PropModeReplace, (unsigned char *) &utf8ID, 1); 6149 } 6150 6151 if(changePropertyResult == 0 || changePropertyResult == 1) { 6152 XSelectionEvent sendEvent = { 6153 .type = SelectionNotify, 6154 .serial = requestEvent.serial, 6155 .send_event = requestEvent.send_event, 6156 .display = requestEvent.display, 6157 .requestor = requestEvent.requestor, 6158 .selection = requestEvent.selection, 6159 .target = requestEvent.target, 6160 .property = requestEvent.property, 6161 .time = requestEvent.time 6162 }; 6163 6164 XSendEvent(ui.display, requestEvent.requestor, 0, 0, (XEvent *) &sendEvent); 6165 } 6166 } 6167 } 6168 6169 return false; 6170 } 6171 6172 bool _UIMessageLoopSingle(int *result) { 6173 XEvent events[64]; 6174 6175 if (ui.animatingCount) { 6176 if (XPending(ui.display)) { 6177 XNextEvent(ui.display, events + 0); 6178 } else { 6179 _UIProcessAnimations(); 6180 return true; 6181 } 6182 } else { 6183 XNextEvent(ui.display, events + 0); 6184 } 6185 6186 int p = 1; 6187 6188 int configureIndex = -1, motionIndex = -1, exposeIndex = -1; 6189 6190 while (p < 64 && XPending(ui.display)) { 6191 XNextEvent(ui.display, events + p); 6192 6193 #define _UI_MERGE_EVENTS(a, b) \ 6194 if (events[p].type == a) { \ 6195 if (b != -1) events[b].type = 0; \ 6196 b = p; \ 6197 } 6198 6199 _UI_MERGE_EVENTS(ConfigureNotify, configureIndex); 6200 _UI_MERGE_EVENTS(MotionNotify, motionIndex); 6201 _UI_MERGE_EVENTS(Expose, exposeIndex); 6202 6203 p++; 6204 } 6205 6206 for (int i = 0; i < p; i++) { 6207 if (!events[i].type) { 6208 continue; 6209 } 6210 6211 if (_UIProcessEvent(events + i)) { 6212 return false; 6213 } 6214 } 6215 6216 return true; 6217 } 6218 6219 void UIWindowPostMessage(UIWindow *window, UIMessage message, void *_dp) { 6220 // HACK! Xlib doesn't seem to have a nice way to do this, 6221 // so send a specially crafted key press event instead. 6222 // TODO Maybe ClientMessage is what this should use? 6223 uintptr_t dp = (uintptr_t) _dp; 6224 XKeyEvent event = { 0 }; 6225 event.display = ui.display; 6226 event.window = window->window; 6227 event.root = DefaultRootWindow(ui.display); 6228 event.subwindow = None; 6229 #if INTPTR_MAX == INT64_MAX 6230 event.time = dp >> 32; 6231 #endif 6232 event.x = 0x7123; 6233 event.y = 0x7456; 6234 event.x_root = (dp >> 0) & 0xFFFF; 6235 event.y_root = (dp >> 16) & 0xFFFF; 6236 event.same_screen = True; 6237 event.keycode = 1; 6238 event.state = message; 6239 event.type = KeyPress; 6240 XSendEvent(ui.display, window->window, True, KeyPressMask, (XEvent *) &event); 6241 XFlush(ui.display); 6242 } 6243 6244 #endif 6245 6246 #ifdef UI_WINDOWS 6247 6248 const int UI_KEYCODE_A = 'A'; 6249 const int UI_KEYCODE_0 = '0'; 6250 const int UI_KEYCODE_BACKSPACE = VK_BACK; 6251 const int UI_KEYCODE_DELETE = VK_DELETE; 6252 const int UI_KEYCODE_DOWN = VK_DOWN; 6253 const int UI_KEYCODE_END = VK_END; 6254 const int UI_KEYCODE_ENTER = VK_RETURN; 6255 const int UI_KEYCODE_ESCAPE = VK_ESCAPE; 6256 const int UI_KEYCODE_F1 = VK_F1; 6257 const int UI_KEYCODE_HOME = VK_HOME; 6258 const int UI_KEYCODE_LEFT = VK_LEFT; 6259 const int UI_KEYCODE_RIGHT = VK_RIGHT; 6260 const int UI_KEYCODE_SPACE = VK_SPACE; 6261 const int UI_KEYCODE_TAB = VK_TAB; 6262 const int UI_KEYCODE_UP = VK_UP; 6263 const int UI_KEYCODE_INSERT = VK_INSERT; 6264 const int UI_KEYCODE_PAGE_UP = VK_PRIOR; 6265 const int UI_KEYCODE_PAGE_DOWN = VK_NEXT; 6266 6267 int _UIWindowMessage(UIElement *element, UIMessage message, int di, void *dp) { 6268 if (message == UI_MSG_DEALLOCATE) { 6269 UIWindow *window = (UIWindow *) element; 6270 _UIWindowDestroyCommon(window); 6271 SetWindowLongPtr(window->hwnd, GWLP_USERDATA, 0); 6272 DestroyWindow(window->hwnd); 6273 } 6274 6275 return _UIWindowMessageCommon(element, message, di, dp); 6276 } 6277 6278 LRESULT CALLBACK _UIWindowProcedure(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { 6279 UIWindow *window = (UIWindow *) GetWindowLongPtr(hwnd, GWLP_USERDATA); 6280 6281 if (!window || ui.assertionFailure) { 6282 return DefWindowProc(hwnd, message, wParam, lParam); 6283 } 6284 6285 if (message == WM_CLOSE) { 6286 if (UIElementMessage(&window->e, UI_MSG_WINDOW_CLOSE, 0, 0)) { 6287 _UIUpdate(); 6288 return 0; 6289 } else { 6290 PostQuitMessage(0); 6291 } 6292 } else if (message == WM_SIZE) { 6293 RECT client; 6294 GetClientRect(hwnd, &client); 6295 window->width = client.right; 6296 window->height = client.bottom; 6297 window->bits = (uint32_t *) UI_REALLOC(window->bits, window->width * window->height * 4); 6298 window->e.bounds = UI_RECT_2S(window->width, window->height); 6299 window->e.clip = UI_RECT_2S(window->width, window->height); 6300 UIElementRelayout(&window->e); 6301 _UIUpdate(); 6302 } else if (message == WM_MOUSEMOVE) { 6303 if (!window->trackingLeave) { 6304 window->trackingLeave = true; 6305 TRACKMOUSEEVENT leave = { 0 }; 6306 leave.cbSize = sizeof(TRACKMOUSEEVENT); 6307 leave.dwFlags = TME_LEAVE; 6308 leave.hwndTrack = hwnd; 6309 TrackMouseEvent(&leave); 6310 } 6311 6312 POINT cursor; 6313 GetCursorPos(&cursor); 6314 ScreenToClient(hwnd, &cursor); 6315 window->cursorX = cursor.x; 6316 window->cursorY = cursor.y; 6317 _UIWindowInputEvent(window, UI_MSG_MOUSE_MOVE, 0, 0); 6318 } else if (message == WM_MOUSELEAVE) { 6319 window->trackingLeave = false; 6320 6321 if (!window->pressed) { 6322 window->cursorX = -1; 6323 window->cursorY = -1; 6324 } 6325 6326 _UIWindowInputEvent(window, UI_MSG_MOUSE_MOVE, 0, 0); 6327 } else if (message == WM_LBUTTONDOWN) { 6328 SetCapture(hwnd); 6329 _UIWindowInputEvent(window, UI_MSG_LEFT_DOWN, 0, 0); 6330 } else if (message == WM_LBUTTONUP) { 6331 if (window->pressedButton == 1) ReleaseCapture(); 6332 _UIWindowInputEvent(window, UI_MSG_LEFT_UP, 0, 0); 6333 } else if (message == WM_MBUTTONDOWN) { 6334 SetCapture(hwnd); 6335 _UIWindowInputEvent(window, UI_MSG_MIDDLE_DOWN, 0, 0); 6336 } else if (message == WM_MBUTTONUP) { 6337 if (window->pressedButton == 2) ReleaseCapture(); 6338 _UIWindowInputEvent(window, UI_MSG_MIDDLE_UP, 0, 0); 6339 } else if (message == WM_RBUTTONDOWN) { 6340 SetCapture(hwnd); 6341 _UIWindowInputEvent(window, UI_MSG_RIGHT_DOWN, 0, 0); 6342 } else if (message == WM_RBUTTONUP) { 6343 if (window->pressedButton == 3) ReleaseCapture(); 6344 _UIWindowInputEvent(window, UI_MSG_RIGHT_UP, 0, 0); 6345 } else if (message == WM_MOUSEWHEEL) { 6346 int delta = (int) wParam >> 16; 6347 _UIWindowInputEvent(window, UI_MSG_MOUSE_WHEEL, -delta, 0); 6348 } else if (message == WM_KEYDOWN) { 6349 window->ctrl = GetKeyState(VK_CONTROL) & 0x8000; 6350 window->shift = GetKeyState(VK_SHIFT) & 0x8000; 6351 window->alt = GetKeyState(VK_MENU) & 0x8000; 6352 6353 UIKeyTyped m = { 0 }; 6354 m.code = wParam; 6355 _UIWindowInputEvent(window, UI_MSG_KEY_TYPED, 0, &m); 6356 } else if (message == WM_CHAR) { 6357 UIKeyTyped m = { 0 }; 6358 char c = wParam; 6359 m.text = &c; 6360 m.textBytes = 1; 6361 _UIWindowInputEvent(window, UI_MSG_KEY_TYPED, 0, &m); 6362 } else if (message == WM_PAINT) { 6363 PAINTSTRUCT paint; 6364 HDC dc = BeginPaint(hwnd, &paint); 6365 BITMAPINFOHEADER info = { 0 }; 6366 info.biSize = sizeof(info); 6367 info.biWidth = window->width, info.biHeight = -window->height; 6368 info.biPlanes = 1, info.biBitCount = 32; 6369 StretchDIBits(dc, 0, 0, UI_RECT_SIZE(window->e.bounds), 0, 0, UI_RECT_SIZE(window->e.bounds), 6370 window->bits, (BITMAPINFO *) &info, DIB_RGB_COLORS, SRCCOPY); 6371 EndPaint(hwnd, &paint); 6372 } else if (message == WM_SETCURSOR && LOWORD(lParam) == HTCLIENT) { 6373 SetCursor(ui.cursors[window->cursorStyle]); 6374 return 1; 6375 } else if (message == WM_SETFOCUS || message == WM_KILLFOCUS) { 6376 _UIMenusClose(); 6377 6378 if (message == WM_SETFOCUS) { 6379 _UIInspectorSetFocusedWindow(window); 6380 UIElementMessage(&window->e, UI_MSG_WINDOW_ACTIVATE, 0, 0); 6381 } 6382 } else if (message == WM_MOUSEACTIVATE && (window->e.flags & UI_WINDOW_MENU)) { 6383 return MA_NOACTIVATE; 6384 } else if (message == WM_DROPFILES) { 6385 HDROP drop = (HDROP) wParam; 6386 int count = DragQueryFile(drop, 0xFFFFFFFF, NULL, 0); 6387 char **files = (char **) UI_MALLOC(sizeof(char *) * count); 6388 6389 for (int i = 0; i < count; i++) { 6390 int length = DragQueryFile(drop, i, NULL, 0); 6391 files[i] = (char *) UI_MALLOC(length + 1); 6392 files[i][length] = 0; 6393 DragQueryFile(drop, i, files[i], length + 1); 6394 } 6395 6396 UIElementMessage(&window->e, UI_MSG_WINDOW_DROP_FILES, count, files); 6397 for (int i = 0; i < count; i++) UI_FREE(files[i]); 6398 UI_FREE(files); 6399 DragFinish(drop); 6400 _UIUpdate(); 6401 } else if (message == WM_APP + 1) { 6402 UIElementMessage(&window->e, (UIMessage) wParam, 0, (void *) lParam); 6403 _UIUpdate(); 6404 } else { 6405 if (message == WM_NCLBUTTONDOWN || message == WM_NCMBUTTONDOWN || message == WM_NCRBUTTONDOWN) { 6406 if (~window->e.flags & UI_WINDOW_MENU) { 6407 _UIMenusClose(); 6408 _UIUpdate(); 6409 } 6410 } 6411 6412 return DefWindowProc(hwnd, message, wParam, lParam); 6413 } 6414 6415 return 0; 6416 } 6417 6418 void UIInitialise() { 6419 ui.heap = GetProcessHeap(); 6420 6421 _UIInitialiseCommon(); 6422 6423 ui.cursors[UI_CURSOR_ARROW] = LoadCursor(NULL, IDC_ARROW); 6424 ui.cursors[UI_CURSOR_TEXT] = LoadCursor(NULL, IDC_IBEAM); 6425 ui.cursors[UI_CURSOR_SPLIT_V] = LoadCursor(NULL, IDC_SIZENS); 6426 ui.cursors[UI_CURSOR_SPLIT_H] = LoadCursor(NULL, IDC_SIZEWE); 6427 ui.cursors[UI_CURSOR_FLIPPED_ARROW] = LoadCursor(NULL, IDC_ARROW); 6428 ui.cursors[UI_CURSOR_CROSS_HAIR] = LoadCursor(NULL, IDC_CROSS); 6429 ui.cursors[UI_CURSOR_HAND] = LoadCursor(NULL, IDC_HAND); 6430 ui.cursors[UI_CURSOR_RESIZE_UP] = LoadCursor(NULL, IDC_SIZENS); 6431 ui.cursors[UI_CURSOR_RESIZE_LEFT] = LoadCursor(NULL, IDC_SIZEWE); 6432 ui.cursors[UI_CURSOR_RESIZE_UP_RIGHT] = LoadCursor(NULL, IDC_SIZENESW); 6433 ui.cursors[UI_CURSOR_RESIZE_UP_LEFT] = LoadCursor(NULL, IDC_SIZENWSE); 6434 ui.cursors[UI_CURSOR_RESIZE_DOWN] = LoadCursor(NULL, IDC_SIZENS); 6435 ui.cursors[UI_CURSOR_RESIZE_RIGHT] = LoadCursor(NULL, IDC_SIZEWE); 6436 ui.cursors[UI_CURSOR_RESIZE_DOWN_LEFT] = LoadCursor(NULL, IDC_SIZENESW); 6437 ui.cursors[UI_CURSOR_RESIZE_DOWN_RIGHT] = LoadCursor(NULL, IDC_SIZENWSE); 6438 6439 WNDCLASS windowClass = { 0 }; 6440 windowClass.lpfnWndProc = _UIWindowProcedure; 6441 windowClass.lpszClassName = "normal"; 6442 RegisterClass(&windowClass); 6443 windowClass.style |= CS_DROPSHADOW; 6444 windowClass.lpszClassName = "shadow"; 6445 RegisterClass(&windowClass); 6446 } 6447 6448 bool _UIMessageLoopSingle(int *result) { 6449 MSG message = { 0 }; 6450 6451 if (ui.animating) { 6452 if (PeekMessage(&message, NULL, 0, 0, PM_REMOVE)) { 6453 if (message.message == WM_QUIT) { 6454 *result = message.wParam; 6455 return false; 6456 } 6457 6458 TranslateMessage(&message); 6459 DispatchMessage(&message); 6460 } else { 6461 _UIProcessAnimations(); 6462 } 6463 } else { 6464 if (!GetMessage(&message, NULL, 0, 0)) { 6465 *result = message.wParam; 6466 return false; 6467 } 6468 6469 TranslateMessage(&message); 6470 DispatchMessage(&message); 6471 } 6472 6473 return true; 6474 } 6475 6476 void UIMenuShow(UIMenu *menu) { 6477 int width, height; 6478 _UIMenuPrepare(menu, &width, &height); 6479 MoveWindow(menu->e.window->hwnd, menu->pointX, menu->pointY, width, height, FALSE); 6480 ShowWindow(menu->e.window->hwnd, SW_SHOWNOACTIVATE); 6481 } 6482 6483 UIWindow *UIWindowCreate(UIWindow *owner, uint32_t flags, const char *cTitle, int width, int height) { 6484 _UIMenusClose(); 6485 6486 UIWindow *window = (UIWindow *) UIElementCreate(sizeof(UIWindow), NULL, flags | UI_ELEMENT_WINDOW, _UIWindowMessage, "Window"); 6487 _UIWindowAdd(window); 6488 if (owner) window->scale = owner->scale; 6489 6490 if (flags & UI_WINDOW_MENU) { 6491 UI_ASSERT(owner); 6492 6493 window->hwnd = CreateWindowEx(WS_EX_TOPMOST | WS_EX_NOACTIVATE, "shadow", 0, WS_POPUP, 6494 0, 0, 0, 0, owner->hwnd, NULL, NULL, NULL); 6495 } else { 6496 window->hwnd = CreateWindowEx(WS_EX_ACCEPTFILES, "normal", cTitle, WS_OVERLAPPEDWINDOW, 6497 CW_USEDEFAULT, CW_USEDEFAULT, width ? width : CW_USEDEFAULT, height ? height : CW_USEDEFAULT, 6498 owner ? owner->hwnd : NULL, NULL, NULL, NULL); 6499 } 6500 6501 SetWindowLongPtr(window->hwnd, GWLP_USERDATA, (LONG_PTR) window); 6502 6503 if (~flags & UI_WINDOW_MENU) { 6504 ShowWindow(window->hwnd, SW_SHOW); 6505 PostMessage(window->hwnd, WM_SIZE, 0, 0); 6506 } 6507 6508 return window; 6509 } 6510 6511 void _UIWindowEndPaint(UIWindow *window, UIPainter *painter) { 6512 HDC dc = GetDC(window->hwnd); 6513 BITMAPINFOHEADER info = { 0 }; 6514 info.biSize = sizeof(info); 6515 info.biWidth = window->width, info.biHeight = window->height; 6516 info.biPlanes = 1, info.biBitCount = 32; 6517 StretchDIBits(dc, 6518 UI_RECT_TOP_LEFT(window->updateRegion), UI_RECT_SIZE(window->updateRegion), 6519 window->updateRegion.l, window->updateRegion.b + 1, 6520 UI_RECT_WIDTH(window->updateRegion), -UI_RECT_HEIGHT(window->updateRegion), 6521 window->bits, (BITMAPINFO *) &info, DIB_RGB_COLORS, SRCCOPY); 6522 ReleaseDC(window->hwnd, dc); 6523 } 6524 6525 void _UIWindowSetCursor(UIWindow *window, int cursor) { 6526 SetCursor(ui.cursors[cursor]); 6527 } 6528 6529 void _UIWindowGetScreenPosition(UIWindow *window, int *_x, int *_y) { 6530 POINT p; 6531 p.x = 0; 6532 p.y = 0; 6533 ClientToScreen(window->hwnd, &p); 6534 *_x = p.x; 6535 *_y = p.y; 6536 } 6537 6538 void UIWindowPostMessage(UIWindow *window, UIMessage message, void *_dp) { 6539 PostMessage(window->hwnd, WM_APP + 1, (WPARAM) message, (LPARAM) _dp); 6540 } 6541 6542 void *_UIHeapReAlloc(void *pointer, size_t size) { 6543 if (pointer) { 6544 if (size) { 6545 return HeapReAlloc(ui.heap, 0, pointer, size); 6546 } else { 6547 UI_FREE(pointer); 6548 return NULL; 6549 } 6550 } else { 6551 if (size) { 6552 return UI_MALLOC(size); 6553 } else { 6554 return NULL; 6555 } 6556 } 6557 } 6558 6559 void _UIClipboardWriteText(UIWindow *window, char *text) { 6560 if (OpenClipboard(window->hwnd)) { 6561 EmptyClipboard(); 6562 HGLOBAL memory = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, _UIStringLength(text) + 1); 6563 char *copy = (char *) GlobalLock(memory); 6564 for (uintptr_t i = 0; text[i]; i++) copy[i] = text[i]; 6565 GlobalUnlock(copy); 6566 SetClipboardData(CF_TEXT, memory); 6567 CloseClipboard(); 6568 } 6569 } 6570 6571 char *_UIClipboardReadTextStart(UIWindow *window, size_t *bytes) { 6572 if (!OpenClipboard(window->hwnd)) { 6573 return NULL; 6574 } 6575 6576 HANDLE memory = GetClipboardData(CF_TEXT); 6577 6578 if (!memory) { 6579 CloseClipboard(); 6580 return NULL; 6581 } 6582 6583 char *buffer = (char *) GlobalLock(memory); 6584 6585 if (!buffer) { 6586 CloseClipboard(); 6587 return NULL; 6588 } 6589 6590 size_t byteCount = GlobalSize(memory); 6591 6592 if (byteCount < 1) { 6593 GlobalUnlock(memory); 6594 CloseClipboard(); 6595 return NULL; 6596 } 6597 6598 char *copy = (char *) UI_MALLOC(byteCount + 1); 6599 for (uintptr_t i = 0; i < byteCount; i++) copy[i] = buffer[i]; 6600 copy[byteCount] = 0; // Just in case. 6601 6602 GlobalUnlock(memory); 6603 CloseClipboard(); 6604 6605 if (bytes) *bytes = _UIStringLength(copy); 6606 return copy; 6607 } 6608 6609 void _UIClipboardReadTextEnd(UIWindow *window, char *text) { 6610 UI_FREE(text); 6611 } 6612 6613 void *_UIMemmove(void *dest, const void *src, size_t n) { 6614 if ((uintptr_t) dest < (uintptr_t) src) { 6615 uint8_t *dest8 = (uint8_t *) dest; 6616 const uint8_t *src8 = (const uint8_t *) src; 6617 for (uintptr_t i = 0; i < n; i++) { 6618 dest8[i] = src8[i]; 6619 } 6620 return dest; 6621 } else { 6622 uint8_t *dest8 = (uint8_t *) dest; 6623 const uint8_t *src8 = (const uint8_t *) src; 6624 for (uintptr_t i = n; i; i--) { 6625 dest8[i - 1] = src8[i - 1]; 6626 } 6627 return dest; 6628 } 6629 } 6630 6631 #endif 6632 6633 #ifdef UI_ESSENCE 6634 6635 const int UI_KEYCODE_A = ES_SCANCODE_A; 6636 const int UI_KEYCODE_0 = ES_SCANCODE_0; 6637 const int UI_KEYCODE_BACKSPACE = ES_SCANCODE_BACKSPACE; 6638 const int UI_KEYCODE_DELETE = ES_SCANCODE_DELETE; 6639 const int UI_KEYCODE_DOWN = ES_SCANCODE_DOWN_ARROW; 6640 const int UI_KEYCODE_END = ES_SCANCODE_END; 6641 const int UI_KEYCODE_ENTER = ES_SCANCODE_ENTER; 6642 const int UI_KEYCODE_ESCAPE = ES_SCANCODE_ESCAPE; 6643 const int UI_KEYCODE_F1 = ES_SCANCODE_F1; 6644 const int UI_KEYCODE_HOME = ES_SCANCODE_HOME; 6645 const int UI_KEYCODE_LEFT = ES_SCANCODE_LEFT_ARROW; 6646 const int UI_KEYCODE_RIGHT = ES_SCANCODE_RIGHT_ARROW; 6647 const int UI_KEYCODE_SPACE = ES_SCANCODE_SPACE; 6648 const int UI_KEYCODE_TAB = ES_SCANCODE_TAB; 6649 const int UI_KEYCODE_UP = ES_SCANCODE_UP_ARROW; 6650 const int UI_KEYCODE_INSERT = ES_SCANCODE_INSERT; 6651 const int UI_KEYCODE_PAGE_UP = ES_SCANCODE_PAGE_UP; 6652 const int UI_KEYCODE_PAGE_DOWN = ES_SCANCODE_PAGE_DOWN; 6653 6654 int _UIWindowMessage(UIElement *element, UIMessage message, int di, void *dp) { 6655 if (message == UI_MSG_DEALLOCATE) { 6656 // TODO Non-main windows. 6657 element->window = NULL; 6658 EsInstanceCloseReference(ui.instance); 6659 } 6660 6661 return _UIWindowMessageCommon(element, message, di, dp); 6662 } 6663 6664 void UIInitialise() { 6665 _UIInitialiseCommon(); 6666 6667 while (true) { 6668 EsMessage *message = EsMessageReceive(); 6669 6670 if (message->type == ES_MSG_INSTANCE_CREATE) { 6671 ui.instance = EsInstanceCreate(message, NULL, 0); 6672 break; 6673 } 6674 } 6675 } 6676 6677 bool _UIMessageLoopSingle(int *result) { 6678 if (ui.animating) { 6679 // TODO. 6680 } else { 6681 _UIMessageProcess(EsMessageReceive()); 6682 } 6683 6684 return true; 6685 } 6686 6687 UIMenu *UIMenuCreate(UIElement *parent, uint32_t flags) { 6688 ui.menuIndex = 0; 6689 return EsMenuCreate(parent->window->window, ES_MENU_AT_CURSOR); 6690 } 6691 6692 void _UIMenuItemCallback(EsMenu *menu, EsGeneric context) { 6693 ((void (*)(void *)) ui.menuData[context.u * 2 + 0])(ui.menuData[context.u * 2 + 1]); 6694 } 6695 6696 void UIMenuAddItem(UIMenu *menu, uint32_t flags, const char *label, ptrdiff_t labelBytes, void (*invoke)(void *cp), void *cp) { 6697 EsAssert(ui.menuIndex < 128); 6698 ui.menuData[ui.menuIndex * 2 + 0] = (void *) invoke; 6699 ui.menuData[ui.menuIndex * 2 + 1] = cp; 6700 EsMenuAddItem(menu, (flags & UI_BUTTON_CHECKED) ? ES_MENU_ITEM_CHECKED : ES_FLAGS_DEFAULT, 6701 label, labelBytes, _UIMenuItemCallback, ui.menuIndex); 6702 ui.menuIndex++; 6703 } 6704 6705 void UIMenuShow(UIMenu *menu) { 6706 EsMenuShow(menu); 6707 } 6708 6709 int _UIWindowCanvasMessage(EsElement *element, EsMessage *message) { 6710 UIWindow *window = (UIWindow *) element->window->userData.p; 6711 6712 if (!window) { 6713 return 0; 6714 } else if (message->type == ES_MSG_PAINT) { 6715 EsRectangle bounds = ES_RECT_4PD(message->painter->offsetX, message->painter->offsetY, window->width, window->height); 6716 EsDrawBitmap(message->painter, bounds, window->bits, window->width * 4, 0xFFFF); 6717 } else if (message->type == ES_MSG_LAYOUT) { 6718 EsElementGetSize(element, &window->width, &window->height); 6719 window->bits = (uint32_t *) UI_REALLOC(window->bits, window->width * window->height * 4); 6720 window->e.bounds = UI_RECT_2S(window->width, window->height); 6721 window->e.clip = UI_RECT_2S(window->width, window->height); 6722 UIElementRelayout(&window->e); 6723 _UIUpdate(); 6724 } else if (message->type == ES_MSG_SCROLL_WHEEL) { 6725 _UIWindowInputEvent(window, UI_MSG_MOUSE_WHEEL, -message->scrollWheel.dy, 0); 6726 } else if (message->type == ES_MSG_MOUSE_MOVED || message->type == ES_MSG_HOVERED_END 6727 || message->type == ES_MSG_MOUSE_LEFT_DRAG || message->type == ES_MSG_MOUSE_RIGHT_DRAG || message->type == ES_MSG_MOUSE_MIDDLE_DRAG) { 6728 EsPoint point = EsMouseGetPosition(element); 6729 window->cursorX = point.x, window->cursorY = point.y; 6730 _UIWindowInputEvent(window, UI_MSG_MOUSE_MOVE, 0, 0); 6731 } else if (message->type == ES_MSG_KEY_UP) { 6732 window->ctrl = EsKeyboardIsCtrlHeld(); 6733 window->shift = EsKeyboardIsShiftHeld(); 6734 window->alt = EsKeyboardIsAltHeld(); 6735 } else if (message->type == ES_MSG_KEY_DOWN) { 6736 window->ctrl = EsKeyboardIsCtrlHeld(); 6737 window->shift = EsKeyboardIsShiftHeld(); 6738 window->alt = EsKeyboardIsAltHeld(); 6739 UIKeyTyped m = { 0 }; 6740 char c[64]; 6741 m.text = c; 6742 m.textBytes = EsMessageGetInputText(message, c); 6743 m.code = message->keyboard.scancode; 6744 return _UIWindowInputEvent(window, UI_MSG_KEY_TYPED, 0, &m) ? ES_HANDLED : 0; 6745 } else if (message->type == ES_MSG_MOUSE_LEFT_CLICK) { 6746 _UIInspectorSetFocusedWindow(window); 6747 } else if (message->type == ES_MSG_USER_START) { 6748 UIElementMessage(&window->e, (UIMessage) message->user.context1.u, 0, (void *) message->user.context2.p); 6749 _UIUpdate(); 6750 } else if (message->type == ES_MSG_GET_CURSOR) { 6751 message->cursorStyle = ES_CURSOR_NORMAL; 6752 if (window->cursor == UI_CURSOR_TEXT) message->cursorStyle = ES_CURSOR_TEXT; 6753 if (window->cursor == UI_CURSOR_SPLIT_V) message->cursorStyle = ES_CURSOR_SPLIT_VERTICAL; 6754 if (window->cursor == UI_CURSOR_SPLIT_H) message->cursorStyle = ES_CURSOR_SPLIT_HORIZONTAL; 6755 if (window->cursor == UI_CURSOR_FLIPPED_ARROW) message->cursorStyle = ES_CURSOR_SELECT_LINES; 6756 if (window->cursor == UI_CURSOR_CROSS_HAIR) message->cursorStyle = ES_CURSOR_CROSS_HAIR_PICK; 6757 if (window->cursor == UI_CURSOR_HAND) message->cursorStyle = ES_CURSOR_HAND_HOVER; 6758 if (window->cursor == UI_CURSOR_RESIZE_UP) message->cursorStyle = ES_CURSOR_RESIZE_VERTICAL; 6759 if (window->cursor == UI_CURSOR_RESIZE_LEFT) message->cursorStyle = ES_CURSOR_RESIZE_HORIZONTAL; 6760 if (window->cursor == UI_CURSOR_RESIZE_UP_RIGHT) message->cursorStyle = ES_CURSOR_RESIZE_DIAGONAL_1; 6761 if (window->cursor == UI_CURSOR_RESIZE_UP_LEFT) message->cursorStyle = ES_CURSOR_RESIZE_DIAGONAL_2; 6762 if (window->cursor == UI_CURSOR_RESIZE_DOWN) message->cursorStyle = ES_CURSOR_RESIZE_VERTICAL; 6763 if (window->cursor == UI_CURSOR_RESIZE_RIGHT) message->cursorStyle = ES_CURSOR_RESIZE_HORIZONTAL; 6764 if (window->cursor == UI_CURSOR_RESIZE_DOWN_RIGHT) message->cursorStyle = ES_CURSOR_RESIZE_DIAGONAL_1; 6765 if (window->cursor == UI_CURSOR_RESIZE_DOWN_LEFT) message->cursorStyle = ES_CURSOR_RESIZE_DIAGONAL_2; 6766 } 6767 6768 else if (message->type == ES_MSG_MOUSE_LEFT_DOWN) _UIWindowInputEvent(window, UI_MSG_LEFT_DOWN, 0, 0); 6769 else if (message->type == ES_MSG_MOUSE_LEFT_UP) _UIWindowInputEvent(window, UI_MSG_LEFT_UP, 0, 0); 6770 else if (message->type == ES_MSG_MOUSE_MIDDLE_DOWN) _UIWindowInputEvent(window, UI_MSG_MIDDLE_DOWN, 0, 0); 6771 else if (message->type == ES_MSG_MOUSE_MIDDLE_UP) _UIWindowInputEvent(window, UI_MSG_MIDDLE_UP, 0, 0); 6772 else if (message->type == ES_MSG_MOUSE_RIGHT_DOWN) _UIWindowInputEvent(window, UI_MSG_RIGHT_DOWN, 0, 0); 6773 else if (message->type == ES_MSG_MOUSE_RIGHT_UP) _UIWindowInputEvent(window, UI_MSG_RIGHT_UP, 0, 0); 6774 6775 else return 0; 6776 6777 return ES_HANDLED; 6778 } 6779 6780 UIWindow *UIWindowCreate(UIWindow *owner, uint32_t flags, const char *cTitle, int width, int height) { 6781 _UIMenusClose(); 6782 6783 UIWindow *window = (UIWindow *) UIElementCreate(sizeof(UIWindow), NULL, flags | UI_ELEMENT_WINDOW, _UIWindowMessage, "Window"); 6784 _UIWindowAdd(window); 6785 if (owner) window->scale = owner->scale; 6786 6787 if (flags & UI_WINDOW_MENU) { 6788 // TODO. 6789 } else { 6790 // TODO Non-main windows. 6791 window->window = ui.instance->window; 6792 window->window->userData = window; 6793 window->canvas = EsCustomElementCreate(window->window, ES_CELL_FILL | ES_ELEMENT_FOCUSABLE); 6794 window->canvas->messageUser = _UIWindowCanvasMessage; 6795 EsWindowSetTitle(window->window, cTitle, -1); 6796 EsElementFocus(window->canvas); 6797 } 6798 6799 return window; 6800 } 6801 6802 void _UIWindowEndPaint(UIWindow *window, UIPainter *painter) { 6803 EsElementRepaint(window->canvas, &window->updateRegion); 6804 } 6805 6806 void _UIWindowSetCursor(UIWindow *window, int cursor) { 6807 window->cursor = cursor; 6808 } 6809 6810 void _UIWindowGetScreenPosition(UIWindow *window, int *_x, int *_y) { 6811 EsRectangle r = EsElementGetScreenBounds(window->window); 6812 *_x = r.l, *_y = r.t; 6813 } 6814 6815 void UIWindowPostMessage(UIWindow *window, UIMessage message, void *_dp) { 6816 EsMessage m = {}; 6817 m.type = ES_MSG_USER_START; 6818 m.user.context1.u = message; 6819 m.user.context2.p = _dp; 6820 EsMessagePost(window->canvas, &m); 6821 } 6822 6823 void _UIClipboardWriteText(UIWindow *window, char *text) { 6824 EsClipboardAddText(ES_CLIPBOARD_PRIMARY, text, -1); 6825 UI_FREE(text); 6826 } 6827 6828 char *_UIClipboardReadTextStart(UIWindow *window, size_t *bytes) { 6829 return EsClipboardReadText(ES_CLIPBOARD_PRIMARY, bytes, NULL); 6830 } 6831 6832 void _UIClipboardReadTextEnd(UIWindow *window, char *text) { 6833 EsHeapFree(text); 6834 } 6835 6836 #endif 6837 6838 #ifdef UI_COCOA 6839 6840 // TODO Standard keyboard shortcuts (Command+Q, Command+W). 6841 6842 const int UI_KEYCODE_A = -100; // TODO Keyboard layout support. 6843 const int UI_KEYCODE_F1 = -70; 6844 const int UI_KEYCODE_0 = -50; 6845 const int UI_KEYCODE_INSERT = -30; 6846 6847 const int UI_KEYCODE_BACKSPACE = kVK_Delete; 6848 const int UI_KEYCODE_DELETE = kVK_ForwardDelete; 6849 const int UI_KEYCODE_DOWN = kVK_DownArrow; 6850 const int UI_KEYCODE_END = kVK_End; 6851 const int UI_KEYCODE_ENTER = kVK_Return; 6852 const int UI_KEYCODE_ESCAPE = kVK_Escape; 6853 const int UI_KEYCODE_HOME = kVK_Home; 6854 const int UI_KEYCODE_LEFT = kVK_LeftArrow; 6855 const int UI_KEYCODE_RIGHT = kVK_RightArrow; 6856 const int UI_KEYCODE_SPACE = kVK_Space; 6857 const int UI_KEYCODE_TAB = kVK_Tab; 6858 const int UI_KEYCODE_UP = kVK_UpArrow; 6859 const int UI_KEYCODE_BACKTICK = kVK_ANSI_Grave; // TODO Keyboard layout support. 6860 const int UI_KEYCODE_PAGE_UP = kVK_PageUp; 6861 const int UI_KEYCODE_PAGE_DOWN = kVK_PageDown; 6862 6863 int (*_cocoaAppMain)(int, char **); 6864 int _cocoaArgc; 6865 char **_cocoaArgv; 6866 6867 struct _UIPostedMessage { 6868 UIMessage message; 6869 void *dp; 6870 }; 6871 6872 char *_UIUTF8StringFromNSString(NSString *string) { 6873 NSUInteger size = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; 6874 char *buffer = (char *) UI_MALLOC(size + 1); 6875 buffer[size] = 0; 6876 [string getBytes:buffer maxLength:size usedLength:NULL encoding:NSUTF8StringEncoding options:0 range:NSMakeRange(0, [string length]) remainingRange:NULL]; 6877 return buffer; 6878 } 6879 6880 int _UICocoaRemapKey(int code) { 6881 if (code == kVK_ANSI_A) { return UI_KEYCODE_LETTER('A'); } 6882 if (code == kVK_ANSI_B) { return UI_KEYCODE_LETTER('B'); } 6883 if (code == kVK_ANSI_C) { return UI_KEYCODE_LETTER('C'); } 6884 if (code == kVK_ANSI_D) { return UI_KEYCODE_LETTER('D'); } 6885 if (code == kVK_ANSI_E) { return UI_KEYCODE_LETTER('E'); } 6886 if (code == kVK_ANSI_F) { return UI_KEYCODE_LETTER('F'); } 6887 if (code == kVK_ANSI_G) { return UI_KEYCODE_LETTER('G'); } 6888 if (code == kVK_ANSI_H) { return UI_KEYCODE_LETTER('H'); } 6889 if (code == kVK_ANSI_I) { return UI_KEYCODE_LETTER('I'); } 6890 if (code == kVK_ANSI_J) { return UI_KEYCODE_LETTER('J'); } 6891 if (code == kVK_ANSI_K) { return UI_KEYCODE_LETTER('K'); } 6892 if (code == kVK_ANSI_L) { return UI_KEYCODE_LETTER('L'); } 6893 if (code == kVK_ANSI_M) { return UI_KEYCODE_LETTER('M'); } 6894 if (code == kVK_ANSI_N) { return UI_KEYCODE_LETTER('N'); } 6895 if (code == kVK_ANSI_O) { return UI_KEYCODE_LETTER('O'); } 6896 if (code == kVK_ANSI_P) { return UI_KEYCODE_LETTER('P'); } 6897 if (code == kVK_ANSI_Q) { return UI_KEYCODE_LETTER('Q'); } 6898 if (code == kVK_ANSI_R) { return UI_KEYCODE_LETTER('R'); } 6899 if (code == kVK_ANSI_S) { return UI_KEYCODE_LETTER('S'); } 6900 if (code == kVK_ANSI_T) { return UI_KEYCODE_LETTER('T'); } 6901 if (code == kVK_ANSI_U) { return UI_KEYCODE_LETTER('U'); } 6902 if (code == kVK_ANSI_V) { return UI_KEYCODE_LETTER('V'); } 6903 if (code == kVK_ANSI_W) { return UI_KEYCODE_LETTER('W'); } 6904 if (code == kVK_ANSI_X) { return UI_KEYCODE_LETTER('X'); } 6905 if (code == kVK_ANSI_Y) { return UI_KEYCODE_LETTER('Y'); } 6906 if (code == kVK_ANSI_Z) { return UI_KEYCODE_LETTER('Z'); } 6907 6908 if (code == kVK_ANSI_0) { return UI_KEYCODE_DIGIT('0'); } 6909 if (code == kVK_ANSI_1) { return UI_KEYCODE_DIGIT('1'); } 6910 if (code == kVK_ANSI_2) { return UI_KEYCODE_DIGIT('2'); } 6911 if (code == kVK_ANSI_3) { return UI_KEYCODE_DIGIT('3'); } 6912 if (code == kVK_ANSI_4) { return UI_KEYCODE_DIGIT('4'); } 6913 if (code == kVK_ANSI_5) { return UI_KEYCODE_DIGIT('5'); } 6914 if (code == kVK_ANSI_6) { return UI_KEYCODE_DIGIT('6'); } 6915 if (code == kVK_ANSI_7) { return UI_KEYCODE_DIGIT('7'); } 6916 if (code == kVK_ANSI_8) { return UI_KEYCODE_DIGIT('8'); } 6917 if (code == kVK_ANSI_9) { return UI_KEYCODE_DIGIT('9'); } 6918 6919 if (code == kVK_F1) { return UI_KEYCODE_FKEY( 1); } 6920 if (code == kVK_F2) { return UI_KEYCODE_FKEY( 2); } 6921 if (code == kVK_F3) { return UI_KEYCODE_FKEY( 3); } 6922 if (code == kVK_F4) { return UI_KEYCODE_FKEY( 4); } 6923 if (code == kVK_F5) { return UI_KEYCODE_FKEY( 5); } 6924 if (code == kVK_F6) { return UI_KEYCODE_FKEY( 6); } 6925 if (code == kVK_F7) { return UI_KEYCODE_FKEY( 7); } 6926 if (code == kVK_F8) { return UI_KEYCODE_FKEY( 8); } 6927 if (code == kVK_F9) { return UI_KEYCODE_FKEY( 9); } 6928 if (code == kVK_F10) { return UI_KEYCODE_FKEY(10); } 6929 if (code == kVK_F11) { return UI_KEYCODE_FKEY(11); } 6930 if (code == kVK_F12) { return UI_KEYCODE_FKEY(12); } 6931 6932 return code; 6933 } 6934 6935 @interface UICocoaApplicationDelegate : NSObject<NSApplicationDelegate> 6936 @end 6937 6938 @interface UICocoaWindowDelegate : NSObject<NSWindowDelegate> 6939 @property (nonatomic) UIWindow *uiWindow; 6940 @end 6941 6942 @interface UICocoaMainView : NSView 6943 - (void)handlePostedMessage:(id)message; 6944 - (void)eventCommon:(NSEvent *)event; 6945 @property (nonatomic) UIWindow *uiWindow; 6946 @end 6947 6948 @implementation UICocoaApplicationDelegate 6949 - (void)applicationWillFinishLaunching:(NSNotification *)notification { 6950 int code = _cocoaAppMain(_cocoaArgc, _cocoaArgv); 6951 if (code) exit(code); 6952 } 6953 @end 6954 6955 @implementation UICocoaWindowDelegate 6956 - (void)windowDidBecomeKey:(NSNotification *)notification { 6957 UIElementMessage(&_uiWindow->e, UI_MSG_WINDOW_ACTIVATE, 0, 0); 6958 _UIUpdate(); 6959 } 6960 6961 - (void)windowDidResize:(NSNotification *)notification { 6962 _uiWindow->width = ((UICocoaMainView *) _uiWindow->view).frame.size.width; 6963 _uiWindow->height = ((UICocoaMainView *) _uiWindow->view).frame.size.height; 6964 _uiWindow->bits = (uint32_t *) UI_REALLOC(_uiWindow->bits, _uiWindow->width * _uiWindow->height * 4); 6965 _uiWindow->e.bounds = UI_RECT_2S(_uiWindow->width, _uiWindow->height); 6966 _uiWindow->e.clip = UI_RECT_2S(_uiWindow->width, _uiWindow->height); 6967 UIElementRelayout(&_uiWindow->e); 6968 _UIUpdate(); 6969 } 6970 @end 6971 6972 @implementation UICocoaMainView 6973 - (void)handlePostedMessage:(id)_message { 6974 _UIPostedMessage *message = (_UIPostedMessage *) _message; 6975 _UIWindowInputEvent(_uiWindow, message->message, 0, message->dp); 6976 UI_FREE(message); 6977 } 6978 6979 - (BOOL)acceptsFirstResponder { 6980 return YES; 6981 } 6982 6983 - (void)onMenuItemSelected:(NSMenuItem *)menuItem { 6984 ((void (*)(void *)) ui.menuData[menuItem.tag * 2 + 0])(ui.menuData[menuItem.tag * 2 + 1]); 6985 } 6986 6987 - (void)drawRect:(NSRect)dirtyRect { 6988 const unsigned char *data = (const unsigned char *) _uiWindow->bits; 6989 NSDrawBitmap(NSMakeRect(0, 0, _uiWindow->width, _uiWindow->height), _uiWindow->width, _uiWindow->height, 6990 8 /* bits per channel */, 4 /* channels per pixel */, 6991 32 /* bits per pixel */, 4 * _uiWindow->width /* bytes per row */, NO /* planar */, YES /* has alpha */, 6992 NSDeviceRGBColorSpace /* color space */, &data /* data */); 6993 } 6994 6995 - (void)eventCommon:(NSEvent *)event { 6996 NSPoint cursor = [self convertPoint:[event locationInWindow] fromView:nil]; 6997 _uiWindow->cursorX = cursor.x, _uiWindow->cursorY = _uiWindow->height - cursor.y - 1; 6998 _uiWindow->ctrl = event.modifierFlags & NSEventModifierFlagCommand; 6999 _uiWindow->shift = event.modifierFlags & NSEventModifierFlagShift; 7000 _uiWindow->alt = event.modifierFlags & NSEventModifierFlagOption; 7001 } 7002 7003 - (void)keyDown:(NSEvent *)event { 7004 [self eventCommon:event]; 7005 char *text = _UIUTF8StringFromNSString(event.characters); 7006 UIKeyTyped m = { .code = _UICocoaRemapKey(event.keyCode), .text = text, .textBytes = (int) strlen(text) }; 7007 _UIWindowInputEvent(_uiWindow, UI_MSG_KEY_TYPED, 0, &m); 7008 UI_FREE(text); 7009 } 7010 7011 - (void)keyUp:(NSEvent *)event { 7012 [self eventCommon:event]; 7013 UIKeyTyped m = { .code = _UICocoaRemapKey(event.keyCode) }; 7014 _UIWindowInputEvent(_uiWindow, UI_MSG_KEY_RELEASED, 0, &m); 7015 } 7016 7017 - (void)mouseMoved:(NSEvent *)event { 7018 [self eventCommon:event]; 7019 _UIWindowInputEvent(_uiWindow, UI_MSG_MOUSE_MOVE, 0, 0); 7020 } 7021 7022 - (void)mouseExited:(NSEvent *)event { [self mouseMoved:event]; } 7023 - (void)flagsChanged:(NSEvent *)event { [self mouseMoved:event]; } 7024 - (void)mouseDragged:(NSEvent *)event { [self mouseMoved:event]; } 7025 - (void)rightMouseDragged:(NSEvent *)event { [self mouseMoved:event]; } 7026 - (void)otherMouseDragged:(NSEvent *)event { [self mouseMoved:event]; } 7027 7028 - (void)mouseDown:(NSEvent *)event { 7029 [self eventCommon:event]; 7030 _UIWindowInputEvent(_uiWindow, UI_MSG_LEFT_DOWN, 0, 0); 7031 } 7032 7033 - (void)mouseUp:(NSEvent *)event { 7034 [self eventCommon:event]; 7035 _UIWindowInputEvent(_uiWindow, UI_MSG_LEFT_UP, 0, 0); 7036 } 7037 7038 - (void)rightMouseDown:(NSEvent *)event { 7039 [self eventCommon:event]; 7040 _UIWindowInputEvent(_uiWindow, UI_MSG_RIGHT_DOWN, 0, 0); 7041 } 7042 7043 - (void)rightMouseUp:(NSEvent *)event { 7044 [self eventCommon:event]; 7045 _UIWindowInputEvent(_uiWindow, UI_MSG_RIGHT_UP, 0, 0); 7046 } 7047 7048 - (void)otherMouseDown:(NSEvent *)event { 7049 [self eventCommon:event]; 7050 _UIWindowInputEvent(_uiWindow, UI_MSG_MIDDLE_DOWN, 0, 0); 7051 } 7052 7053 - (void)otherMouseUp:(NSEvent *)event { 7054 [self eventCommon:event]; 7055 _UIWindowInputEvent(_uiWindow, UI_MSG_MIDDLE_UP, 0, 0); 7056 } 7057 7058 - (void)scrollWheel:(NSEvent *)event { 7059 [self eventCommon:event]; 7060 _UIWindowInputEvent(_uiWindow, UI_MSG_MOUSE_WHEEL, -3 * event.deltaY, 0); 7061 _UIWindowInputEvent(_uiWindow, UI_MSG_MOUSE_MOVE, 0, 0); 7062 } 7063 7064 // TODO Animations. 7065 // TODO Drag and drop. 7066 // TODO Reporting window close. 7067 7068 @end 7069 7070 int _UIWindowMessage(UIElement *element, UIMessage message, int di, void *dp) { 7071 if (message == UI_MSG_DEALLOCATE) { 7072 UIWindow *window = (UIWindow *) element; 7073 _UIWindowDestroyCommon(window); 7074 [window->window close]; 7075 } 7076 7077 return _UIWindowMessageCommon(element, message, di, dp); 7078 } 7079 7080 UIWindow *UIWindowCreate(UIWindow *owner, uint32_t flags, const char *cTitle, int _width, int _height) { 7081 _UIMenusClose(); 7082 UIWindow *window = (UIWindow *) UIElementCreate(sizeof(UIWindow), NULL, flags | UI_ELEMENT_WINDOW, _UIWindowMessage, "Window"); 7083 _UIWindowAdd(window); 7084 if (owner) window->scale = owner->scale; 7085 7086 NSRect frame = NSMakeRect(0, 0, _width ?: 800, _height ?: 600); 7087 NSWindowStyleMask styleMask = NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable | NSWindowStyleMaskTitled; 7088 NSWindow *nsWindow = [[NSWindow alloc] initWithContentRect:frame styleMask:styleMask backing:NSBackingStoreBuffered defer:NO]; 7089 [nsWindow center]; 7090 [nsWindow setTitle:@(cTitle ?: "untitled")]; 7091 UICocoaWindowDelegate *delegate = [UICocoaWindowDelegate alloc]; 7092 [delegate setUiWindow:window]; 7093 nsWindow.delegate = delegate; 7094 UICocoaMainView *view = [UICocoaMainView alloc]; 7095 window->window = nsWindow; 7096 window->view = view; 7097 window->width = frame.size.width; 7098 window->height = frame.size.height; 7099 window->bits = (uint32_t *) UI_REALLOC(window->bits, window->width * window->height * 4); 7100 window->e.bounds = UI_RECT_2S(window->width, window->height); 7101 window->e.clip = UI_RECT_2S(window->width, window->height); 7102 [view setUiWindow:window]; 7103 [view initWithFrame:frame]; 7104 nsWindow.contentView = view; 7105 [view addTrackingArea:[[NSTrackingArea alloc] initWithRect:frame 7106 options:NSTrackingMouseMoved|NSTrackingActiveInKeyWindow|NSTrackingInVisibleRect owner:view userInfo:nil]]; 7107 [nsWindow setInitialFirstResponder:view]; 7108 [nsWindow makeKeyAndOrderFront:delegate]; 7109 7110 // TODO UI_WINDOW_MAXIMIZE. 7111 7112 return window; 7113 } 7114 7115 void _UIClipboardWriteText(UIWindow *window, char *text) { 7116 // TODO Clipboard support. 7117 } 7118 7119 char *_UIClipboardReadTextStart(UIWindow *window, size_t *bytes) { 7120 // TODO Clipboard support. 7121 return NULL; 7122 } 7123 7124 void _UIClipboardReadTextEnd(UIWindow *window, char *text) { 7125 UI_FREE(text); 7126 } 7127 7128 void UIInitialise() { 7129 _UIInitialiseCommon(); 7130 } 7131 7132 void _UIWindowSetCursor(UIWindow *window, int cursor) { 7133 if (cursor == UI_CURSOR_TEXT) [[NSCursor IBeamCursor] set]; 7134 else if (cursor == UI_CURSOR_SPLIT_V) [[NSCursor resizeUpDownCursor] set]; 7135 else if (cursor == UI_CURSOR_SPLIT_H) [[NSCursor resizeLeftRightCursor] set]; 7136 else if (cursor == UI_CURSOR_FLIPPED_ARROW) [[NSCursor pointingHandCursor] set]; 7137 else if (cursor == UI_CURSOR_CROSS_HAIR) [[NSCursor crosshairCursor] set]; 7138 else if (cursor == UI_CURSOR_HAND) [[NSCursor pointingHandCursor] set]; 7139 else [[NSCursor arrowCursor] set]; 7140 } 7141 7142 void _UIWindowEndPaint(UIWindow *window, UIPainter *painter) { 7143 for (int y = painter->clip.t; y < painter->clip.b; y++) { 7144 for (int x = painter->clip.l; x < painter->clip.r; x++) { 7145 uint32_t *p = &painter->bits[y * painter->width + x]; 7146 *p = 0xFF000000 | (*p & 0xFF00) | ((*p & 0xFF0000) >> 16) | ((*p & 0xFF) << 16); 7147 } 7148 } 7149 7150 [(UICocoaMainView *)window->view setNeedsDisplayInRect:((UICocoaMainView *)window->view).frame]; 7151 } 7152 7153 void _UIWindowGetScreenPosition(UIWindow *window, int *x, int *y) { 7154 NSPoint point = [window->window convertPointToScreen:NSMakePoint(0, 0)]; 7155 *x = point.x, *y = point.y; 7156 } 7157 7158 UIMenu *UIMenuCreate(UIElement *parent, uint32_t flags) { 7159 // TODO Fix the vertical position. 7160 7161 if (parent->parent) { 7162 UIRectangle screenBounds = UIElementScreenBounds(parent); 7163 ui.menuX = screenBounds.l; 7164 ui.menuY = screenBounds.b; 7165 } else { 7166 _UIWindowGetScreenPosition(parent->window, &ui.menuX, &ui.menuY); 7167 ui.menuX += parent->window->cursorX; 7168 ui.menuY += parent->window->cursorY; 7169 } 7170 7171 ui.menuIndex = 0; 7172 ui.menuWindow = parent->window; 7173 7174 NSMenu *menu = [[NSMenu alloc] init]; 7175 [menu setAutoenablesItems:NO]; 7176 return menu; 7177 } 7178 7179 void UIMenuAddItem(UIMenu *menu, uint32_t flags, const char *label, ptrdiff_t labelBytes, void (*invoke)(void *cp), void *cp) { 7180 if (ui.menuIndex == 128) return; 7181 ui.menuData[ui.menuIndex * 2 + 0] = (void *) invoke; 7182 ui.menuData[ui.menuIndex * 2 + 1] = cp; 7183 NSString *title = [[NSString alloc] initWithBytes:label length:(labelBytes == -1 ? strlen(label) : labelBytes) encoding:NSUTF8StringEncoding]; 7184 NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:title action:@selector(onMenuItemSelected:) keyEquivalent:@""]; 7185 item.tag = ui.menuIndex++; 7186 if (flags & UI_BUTTON_CHECKED) [item setState:NSControlStateValueOn]; 7187 [item setEnabled:((flags & UI_ELEMENT_DISABLED) ? NO : YES)]; 7188 [item setTarget:(UICocoaMainView *)ui.menuWindow->view]; 7189 [menu addItem:item]; 7190 [title release]; 7191 [item release]; 7192 } 7193 7194 void UIMenuShow(UIMenu *menu) { 7195 [menu popUpMenuPositioningItem:nil atLocation:NSMakePoint(ui.menuX, ui.menuY) inView:nil]; 7196 [menu release]; 7197 } 7198 7199 void UIWindowPack(UIWindow *window, int _width) { 7200 int width = _width ? _width : UIElementMessage(window->e.children[0], UI_MSG_GET_WIDTH, 0, 0); 7201 int height = UIElementMessage(window->e.children[0], UI_MSG_GET_HEIGHT, width, 0); 7202 [window->window setContentSize:NSMakeSize(width, height)]; 7203 } 7204 7205 bool _UIMessageLoopSingle(int *result) { 7206 // TODO Modal dialog support. 7207 return false; 7208 } 7209 7210 void UIWindowPostMessage(UIWindow *window, UIMessage _message, void *dp) { 7211 _UIPostedMessage *message = (_UIPostedMessage *) UI_MALLOC(sizeof(_UIPostedMessage)); 7212 message->message = _message; 7213 message->dp = dp; 7214 [(UICocoaMainView*)window->view performSelectorOnMainThread:@selector(handlePostedMessage:) withObject:(id)message waitUntilDone:NO]; 7215 } 7216 7217 int UICocoaMain(int argc, char **argv, int (*appMain)(int, char **)) { 7218 _cocoaArgc = argc, _cocoaArgv = argv, _cocoaAppMain = appMain; 7219 NSApplication *application = [NSApplication sharedApplication]; 7220 application.delegate = [[UICocoaApplicationDelegate alloc] init]; 7221 return NSApplicationMain(argc, (const char **) argv); 7222 } 7223 7224 #endif 7225 7226 #endif