summaryrefslogtreecommitdiff
path: root/src/ui
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-09-07 22:10:29 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-09-07 22:10:29 +0300
commit4ce9a07bbd4a4a82ff0b310d9c9e0768febe8d61 (patch)
treeca8c41fa1d792fdc764c787cb343ddbb6cfbef5a /src/ui
parentc10fc7a058c05edcedeb23bdcc9faae635f1780d (diff)
Mobile: Redoing Preferences
Contents of the Preferences split panel view are created based on arrays of MenuItems. This removes the confusing indirection of trying to modify the desktop widget tree to fit mobile.
Diffstat (limited to 'src/ui')
-rw-r--r--src/ui/documentwidget.c3
-rw-r--r--src/ui/labelwidget.c38
-rw-r--r--src/ui/labelwidget.h4
-rw-r--r--src/ui/mobile.c205
-rw-r--r--src/ui/mobile.h11
-rw-r--r--src/ui/util.c247
-rw-r--r--src/ui/util.h5
-rw-r--r--src/ui/widget.c1
-rw-r--r--src/ui/window.c3
9 files changed, 425 insertions, 92 deletions
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index e280bc84..6ca4fd8d 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -1010,6 +1010,9 @@ static void documentRunsInvalidated_DocumentWidget_(iDocumentWidget *d) {
1010} 1010}
1011 1011
1012iBool isPinned_DocumentWidget_(const iDocumentWidget *d) { 1012iBool isPinned_DocumentWidget_(const iDocumentWidget *d) {
1013 if (deviceType_App() == phone_AppDeviceType) {
1014 return iFalse;
1015 }
1013 if (d->flags & otherRootByDefault_DocumentWidgetFlag) { 1016 if (d->flags & otherRootByDefault_DocumentWidgetFlag) {
1014 return iTrue; 1017 return iTrue;
1015 } 1018 }
diff --git a/src/ui/labelwidget.c b/src/ui/labelwidget.c
index 3bfa600b..b84e17f4 100644
--- a/src/ui/labelwidget.c
+++ b/src/ui/labelwidget.c
@@ -44,11 +44,13 @@ struct Impl_LabelWidget {
44 iString command; 44 iString command;
45 iClick click; 45 iClick click;
46 struct { 46 struct {
47 uint8_t alignVisual : 1; /* align according to visible bounds, not font metrics */ 47 uint8_t alignVisual : 1; /* align according to visible bounds, not font metrics */
48 uint8_t noAutoMinHeight : 1; /* minimum height is not set automatically */ 48 uint8_t noAutoMinHeight : 1; /* minimum height is not set automatically */
49 uint8_t drawAsOutline : 1; /* draw as outline, filled with background color */ 49 uint8_t drawAsOutline : 1; /* draw as outline, filled with background color */
50 uint8_t noTopFrame : 1; 50 uint8_t noTopFrame : 1;
51 uint8_t wrap : 1; 51 uint8_t wrap : 1;
52 uint8_t allCaps : 1;
53 uint8_t removeTrailingColon : 1;
52 } flags; 54 } flags;
53}; 55};
54 56
@@ -442,11 +444,18 @@ void updateSize_LabelWidget(iLabelWidget *d) {
442 444
443static void replaceVariables_LabelWidget_(iLabelWidget *d) { 445static void replaceVariables_LabelWidget_(iLabelWidget *d) {
444 translate_Lang(&d->label); 446 translate_Lang(&d->label);
447 if (d->flags.allCaps) {
448 set_String(&d->label, collect_String(upper_String(&d->label)));
449 }
450 if (d->flags.removeTrailingColon && endsWith_String(&d->label, ":")) {
451 removeEnd_String(&d->label, 1);
452 }
445} 453}
446 454
447void init_LabelWidget(iLabelWidget *d, const char *label, const char *cmd) { 455void init_LabelWidget(iLabelWidget *d, const char *label, const char *cmd) {
448 iWidget *w = &d->widget; 456 iWidget *w = &d->widget;
449 init_Widget(w); 457 init_Widget(w);
458 iZap(d->flags);
450 d->font = uiLabel_FontId; 459 d->font = uiLabel_FontId;
451 d->forceFg = none_ColorId; 460 d->forceFg = none_ColorId;
452 d->icon = 0; 461 d->icon = 0;
@@ -464,11 +473,6 @@ void init_LabelWidget(iLabelWidget *d, const char *label, const char *cmd) {
464 d->kmods = 0; 473 d->kmods = 0;
465 init_Click(&d->click, d, !isEmpty_String(&d->command) ? SDL_BUTTON_LEFT : 0); 474 init_Click(&d->click, d, !isEmpty_String(&d->command) ? SDL_BUTTON_LEFT : 0);
466 setFlags_Widget(w, hover_WidgetFlag, d->click.button != 0); 475 setFlags_Widget(w, hover_WidgetFlag, d->click.button != 0);
467 d->flags.alignVisual = iFalse;
468 d->flags.noAutoMinHeight = iFalse;
469 d->flags.drawAsOutline = iFalse;
470 d->flags.noTopFrame = iFalse;
471 d->flags.wrap = iFalse;
472 updateSize_LabelWidget(d); 476 updateSize_LabelWidget(d);
473 updateKey_LabelWidget_(d); /* could be bound to another key */ 477 updateKey_LabelWidget_(d); /* could be bound to another key */
474} 478}
@@ -525,6 +529,20 @@ void setOutline_LabelWidget(iLabelWidget *d, iBool drawAsOutline) {
525 } 529 }
526} 530}
527 531
532void setAllCaps_LabelWidget(iLabelWidget *d, iBool allCaps) {
533 if (d) {
534 d->flags.allCaps = allCaps;
535 replaceVariables_LabelWidget_(d);
536 }
537}
538
539void setRemoveTrailingColon_LabelWidget(iLabelWidget *d, iBool removeTrailingColon) {
540 if (d) {
541 d->flags.removeTrailingColon = removeTrailingColon;
542 replaceVariables_LabelWidget_(d);
543 }
544}
545
528void updateText_LabelWidget(iLabelWidget *d, const iString *text) { 546void updateText_LabelWidget(iLabelWidget *d, const iString *text) {
529 set_String(&d->label, text); 547 set_String(&d->label, text);
530 set_String(&d->srcLabel, text); 548 set_String(&d->srcLabel, text);
diff --git a/src/ui/labelwidget.h b/src/ui/labelwidget.h
index b8b6fd87..6275d2c8 100644
--- a/src/ui/labelwidget.h
+++ b/src/ui/labelwidget.h
@@ -30,10 +30,12 @@ iDeclareWidgetClass(LabelWidget)
30iDeclareObjectConstructionArgs(LabelWidget, const char *label, const char *command) 30iDeclareObjectConstructionArgs(LabelWidget, const char *label, const char *command)
31 31
32void setAlignVisually_LabelWidget(iLabelWidget *, iBool alignVisual); 32void setAlignVisually_LabelWidget(iLabelWidget *, iBool alignVisual);
33void setNoAutoMinHeight_LabelWidget(iLabelWidget *, iBool noAutoMinHeight); 33void setNoAutoMinHeight_LabelWidget (iLabelWidget *, iBool noAutoMinHeight);
34void setNoTopFrame_LabelWidget (iLabelWidget *, iBool noTopFrame); 34void setNoTopFrame_LabelWidget (iLabelWidget *, iBool noTopFrame);
35void setWrap_LabelWidget (iLabelWidget *, iBool wrap); 35void setWrap_LabelWidget (iLabelWidget *, iBool wrap);
36void setOutline_LabelWidget (iLabelWidget *, iBool drawAsOutline); 36void setOutline_LabelWidget (iLabelWidget *, iBool drawAsOutline);
37void setAllCaps_LabelWidget (iLabelWidget *, iBool allCaps);
38void setRemoveTrailingColon_LabelWidget (iLabelWidget *, iBool removeTrailingColon);
37void setFont_LabelWidget (iLabelWidget *, int fontId); 39void setFont_LabelWidget (iLabelWidget *, int fontId);
38void setTextColor_LabelWidget (iLabelWidget *, int color); 40void setTextColor_LabelWidget (iLabelWidget *, int color);
39void setText_LabelWidget (iLabelWidget *, const iString *text); /* resizes widget */ 41void setText_LabelWidget (iLabelWidget *, const iString *text); /* resizes widget */
diff --git a/src/ui/mobile.c b/src/ui/mobile.c
index 168a92b8..4ccbb0cb 100644
--- a/src/ui/mobile.c
+++ b/src/ui/mobile.c
@@ -57,7 +57,6 @@ static enum iFontId labelBoldFont_(void) {
57 57
58static void updatePanelSheetMetrics_(iWidget *sheet) { 58static void updatePanelSheetMetrics_(iWidget *sheet) {
59 iWidget *navi = findChild_Widget(sheet, "panel.navi"); 59 iWidget *navi = findChild_Widget(sheet, "panel.navi");
60// iWidget *naviPad = child_Widget(navi, 0);
61 int naviHeight = lineHeight_Text(labelFont_()) + 4 * gap_UI; 60 int naviHeight = lineHeight_Text(labelFont_()) + 4 * gap_UI;
62#if defined (iPlatformMobile) 61#if defined (iPlatformMobile)
63 float left = 0.0f, right = 0.0f, top = 0.0f, bottom = 0.0f; 62 float left = 0.0f, right = 0.0f, top = 0.0f, bottom = 0.0f;
@@ -339,7 +338,7 @@ static iWidget *makeValuePaddingWithHeading_(iLabelWidget *heading, iWidget *val
339 addChildFlags_Widget(div, iClob(new_Widget()), expand_WidgetFlag); 338 addChildFlags_Widget(div, iClob(new_Widget()), expand_WidgetFlag);
340 addChild_Widget(div, iClob(value)); 339 addChild_Widget(div, iClob(value));
341 } 340 }
342 printTree_Widget(div); 341// printTree_Widget(div);
343 return div; 342 return div;
344} 343}
345 344
@@ -369,6 +368,205 @@ static iWidget *addChildPanel_(iWidget *parent, iLabelWidget *panelButton,
369} 368}
370 369
371void finalizeSheet_Mobile(iWidget *sheet) { 370void finalizeSheet_Mobile(iWidget *sheet) {
371 arrange_Widget(sheet);
372// postRefresh_App();
373}
374
375static size_t countItems_(const iMenuItem *itemsNullTerminated) {
376 size_t num = 0;
377 for (; itemsNullTerminated->label; num++, itemsNullTerminated++) {}
378 return num;
379}
380
381void makePanelItem_Mobile(iWidget *panel, const iMenuItem *item) {
382 const char * spec = item->label;
383 const char * id = cstr_Rangecc(range_Command(spec, "id"));
384 const char * label = format_CStr("${%s}", id);
385 iWidget * widget = NULL;
386 iLabelWidget *heading = NULL;
387 if (hasLabel_Command(spec, "device") && deviceType_App() != argLabel_Command(spec, "device")) {
388 return;
389 }
390 if (equal_Command(spec, "title")) {
391 iLabelWidget *title = addChildFlags_Widget(panel,
392 iClob(new_LabelWidget(label, NULL)),
393 alignLeft_WidgetFlag | frameless_WidgetFlag);
394 setFont_LabelWidget(title, uiLabelLargeBold_FontId);
395 setTextColor_LabelWidget(title, uiHeading_ColorId);
396 setAllCaps_LabelWidget(title, iTrue);
397 }
398 else if (equal_Command(spec, "heading")) {
399 addChild_Widget(panel, iClob(makePadding_Widget(lineHeight_Text(labelFont_()))));
400 heading = makeHeading_Widget(label);
401 setAllCaps_LabelWidget(heading, iTrue);
402 setRemoveTrailingColon_LabelWidget(heading, iTrue);
403 addChild_Widget(panel, iClob(heading));
404 }
405 else if (equal_Command(spec, "toggle")) {
406 iLabelWidget *toggle = (iLabelWidget *) makeToggle_Widget(id);
407 setFont_LabelWidget(toggle, labelFont_());
408 widget = makeValuePaddingWithHeading_(heading = makeHeading_Widget(label),
409 as_Widget(toggle));
410 }
411 else if (equal_Command(spec, "dropdown")) {
412 const iMenuItem *dropItems = item->data;
413 iLabelWidget *drop = makeMenuButton_LabelWidget("", dropItems, countItems_(dropItems));
414 setFont_LabelWidget(drop, labelFont_());
415 setFlags_Widget(as_Widget(drop),
416 alignRight_WidgetFlag | noBackground_WidgetFlag |
417 frameless_WidgetFlag, iTrue);
418 setId_Widget(as_Widget(drop), id);
419 widget = makeValuePaddingWithHeading_(heading = makeHeading_Widget(label), as_Widget(drop));
420 }
421 else if (equal_Command(spec, "radio")) {
422 addChild_Widget(panel, iClob(makePadding_Widget(lineHeight_Text(labelFont_()))));
423 iLabelWidget *head = makeHeading_Widget(label);
424 setAllCaps_LabelWidget(head, iTrue);
425 setRemoveTrailingColon_LabelWidget(head, iTrue);
426 addChild_Widget(panel, iClob(head));
427 widget = new_Widget();
428 setBackgroundColor_Widget(widget, uiBackgroundSidebar_ColorId);
429 setPadding_Widget(widget, 4 * gap_UI, 2 * gap_UI, 4 * gap_UI, 2 * gap_UI);
430// setFlags_Widget(widget, arrangeWidth_WidgetFlag, iFalse);
431 setFlags_Widget(widget,
432 borderBottom_WidgetFlag |
433 arrangeHorizontal_WidgetFlag |
434 arrangeHeight_WidgetFlag |
435 resizeToParentWidth_WidgetFlag |
436 resizeWidthOfChildren_WidgetFlag,
437 iTrue);
438 setId_Widget(widget, id);
439 for (const iMenuItem *radioItem = item->data; radioItem->label; radioItem++) {
440 const char * radId = cstr_Rangecc(range_Command(radioItem->label, "id"));
441 const char * radLabel = hasLabel_Command(radioItem->label, "label")
442 ? format_CStr("${%s}", cstr_Rangecc(range_Command(radioItem->label, "label")))
443 : suffixPtr_Command(radioItem->label, "text");
444 iLabelWidget *radButton = new_LabelWidget(radLabel, radioItem->command);
445 setId_Widget(as_Widget(radButton), radId);
446 setFont_LabelWidget(radButton, defaultMedium_FontId);
447 addChildFlags_Widget(widget, iClob(radButton), radio_WidgetFlag | noBackground_WidgetFlag);
448 }
449 }
450 else if (equal_Command(spec, "input")) {
451 iInputWidget *input = new_InputWidget(argU32Label_Command(spec, "maxlen"));
452 setId_Widget(as_Widget(input), id);
453 setFont_InputWidget(input, labelFont_());
454 setContentPadding_InputWidget(input, 3 * gap_UI, 0);
455 setUrlContent_InputWidget(input, argLabel_Command(spec, "url"));
456 widget = makeValuePaddingWithHeading_(heading = makeHeading_Widget(label),
457 as_Widget(input));
458 }
459 else if (equal_Command(spec, "padding")) {
460 widget = makePadding_Widget(lineHeight_Text(labelFont_()) * 1.5f);
461 }
462 if (heading) {
463 setRemoveTrailingColon_LabelWidget(heading, iTrue);
464 const iChar icon = toInt_String(string_Command(item->label, "icon"));
465 if (icon) {
466 setIcon_LabelWidget(heading, icon);
467 }
468 }
469 if (widget) {
470 addChild_Widget(panel, iClob(widget));
471 }
472}
473
474void makePanelItems_Mobile(iWidget *panel, const iMenuItem *itemsNullTerminated) {
475 for (const iMenuItem *item = itemsNullTerminated; item->label; item++) {
476 makePanelItem_Mobile(panel, item);
477 }
478}
479
480iWidget *makeSplitMultiPanel_Mobile(const iMenuItem *itemsNullTerminated) {
481 /* A multipanel widget has a top panel and one or more detail panels. In a horizontal layout,
482 the detail panels slide in from the right and cover the top panel. In a landscape layout,
483 the detail panels are always visible on the side. */
484 iWidget *sheet = new_Widget();
485 setBackgroundColor_Widget(sheet, uiBackground_ColorId);
486 setFlags_Widget(sheet,
487 resizeToParentWidth_WidgetFlag |
488 resizeToParentHeight_WidgetFlag |
489 frameless_WidgetFlag | focusRoot_WidgetFlag | commandOnClick_WidgetFlag |
490 overflowScrollable_WidgetFlag | leftEdgeDraggable_WidgetFlag,
491 iTrue);
492 /* The top-level split between main and detail panels. */
493 iWidget *mainDetailSplit = makeHDiv_Widget(); {
494 setCommandHandler_Widget(mainDetailSplit, mainDetailSplitHandler_);
495 setFlags_Widget(mainDetailSplit, resizeHeightOfChildren_WidgetFlag, iFalse);
496 setId_Widget(mainDetailSplit, "mdsplit");
497 addChild_Widget(sheet, iClob(mainDetailSplit));
498 }
499 /* The panel roots. */
500 iWidget *topPanel = new_Widget(); {
501 setId_Widget(topPanel, "panel.top");
502 setCommandHandler_Widget(topPanel, topPanelHandler_);
503 setFlags_Widget(topPanel,
504 arrangeVertical_WidgetFlag | resizeWidthOfChildren_WidgetFlag |
505 arrangeHeight_WidgetFlag | overflowScrollable_WidgetFlag |
506 commandOnClick_WidgetFlag,
507 iTrue);
508 addChild_Widget(mainDetailSplit, iClob(topPanel));
509 setId_Widget(addChild_Widget(topPanel, iClob(makePadding_Widget(0))), "panel.toppad");
510 }
511 iWidget *detailStack = new_Widget(); {
512 setId_Widget(detailStack, "detailstack");
513 setFlags_Widget(detailStack, collapse_WidgetFlag | resizeWidthOfChildren_WidgetFlag, iTrue);
514 addChild_Widget(mainDetailSplit, iClob(detailStack));
515 }
516 addChild_Widget(topPanel, iClob(makePadding_Widget(lineHeight_Text(labelFont_()))));
517 /* Slide top panel with detail panels. */ {
518 setFlags_Widget(topPanel, refChildrenOffset_WidgetFlag, iTrue);
519 topPanel->offsetRef = detailStack;
520 }
521 /* Navigation bar at the top. */
522 iWidget *navi = new_Widget(); {
523 setId_Widget(navi, "panel.navi");
524 setBackgroundColor_Widget(navi, uiBackground_ColorId);
525 addChild_Widget(navi, iClob(makePadding_Widget(0)));
526 iLabelWidget *back = addChildFlags_Widget(
527 navi,
528 iClob(new_LabelWidget(leftAngle_Icon " ${panel.back}", "panel.close")),
529 noBackground_WidgetFlag | frameless_WidgetFlag | alignLeft_WidgetFlag |
530 extraPadding_WidgetFlag);
531 checkIcon_LabelWidget(back);
532 setId_Widget(as_Widget(back), "panel.back");
533 setFont_LabelWidget(back, labelFont_());
534 addChildFlags_Widget(sheet, iClob(navi),
535 drawBackgroundToVerticalSafeArea_WidgetFlag |
536 arrangeHeight_WidgetFlag | resizeWidthOfChildren_WidgetFlag |
537 resizeToParentWidth_WidgetFlag | arrangeVertical_WidgetFlag);
538 }
539 /* Create panel contents based on provided items. */
540 for (size_t i = 0; itemsNullTerminated[i].label; i++) {
541 const iMenuItem *item = &itemsNullTerminated[i];
542 if (equal_Command(item->label, "panel")) {
543 const char *id = cstr_Rangecc(range_Command(item->label, "id"));
544 const iString *label = collectNewFormat_String("${%s}", id);
545 iLabelWidget *button =
546 addChildFlags_Widget(topPanel,
547 iClob(makePanelButton_(cstr_String(label), "panel.open")),
548 chevron_WidgetFlag | borderTop_WidgetFlag);
549 const iChar icon = toInt_String(string_Command(item->label, "icon"));
550 if (icon) {
551 setIcon_LabelWidget(button, icon);
552 }
553 iWidget *panel = addChildPanel_(detailStack, button, NULL);
554 makePanelItems_Mobile(panel, item->data);
555 }
556 else {
557 makePanelItem_Mobile(topPanel, item);
558 }
559 }
560 /* Finalize the layout. */
561 addChild_Widget(sheet->root->widget, iClob(sheet));
562 mainDetailSplitHandler_(mainDetailSplit, "window.resized"); /* make it resize the split */
563 updatePanelSheetMetrics_(sheet);
564 arrange_Widget(sheet);
565 postCommand_App("widget.overflow"); /* with the correct dimensions */
566 return sheet;
567}
568
569#if 0
372 /* The sheet contents are completely rearranged and restyled on a phone. 570 /* The sheet contents are completely rearranged and restyled on a phone.
373 We'll set up a linear fullscreen arrangement of the widgets. Sheets are already 571 We'll set up a linear fullscreen arrangement of the widgets. Sheets are already
374 scrollable so they can be taller than the display. In hindsight, it may have been 572 scrollable so they can be taller than the display. In hindsight, it may have been
@@ -399,7 +597,7 @@ void finalizeSheet_Mobile(iWidget *sheet) {
399 │ │ └┤ ││ │ │└┤ ││ 597 │ │ └┤ ││ │ │└┤ ││
400 │ │ └───────────────────┘│ │ │ └──────┘ 598 │ │ └───────────────────┘│ │ │ └──────┘
401 └─────────┴───────────────────────┘ └─────────┴ ─ ─ ─ ─ ┘ 599 └─────────┴───────────────────────┘ └─────────┴ ─ ─ ─ ─ ┘
402 offscreen 600 underneath
403 */ 601 */
404 /* Modify the top sheet to act as a fullscreen background. */ 602 /* Modify the top sheet to act as a fullscreen background. */
405 setPadding1_Widget(sheet, 0); 603 setPadding1_Widget(sheet, 0);
@@ -774,6 +972,7 @@ void finalizeSheet_Mobile(iWidget *sheet) {
774 } 972 }
775 postRefresh_App(); 973 postRefresh_App();
776} 974}
975#endif
777 976
778void setupMenuTransition_Mobile(iWidget *sheet, iBool isIncoming) { 977void setupMenuTransition_Mobile(iWidget *sheet, iBool isIncoming) {
779 if (!useMobileSheetLayout_()) { 978 if (!useMobileSheetLayout_()) {
diff --git a/src/ui/mobile.h b/src/ui/mobile.h
index 44134389..50b89e32 100644
--- a/src/ui/mobile.h
+++ b/src/ui/mobile.h
@@ -22,11 +22,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22 22
23#pragma once 23#pragma once
24 24
25#include <the_Foundation/defs.h> 25#include <the_Foundation/rect.h>
26 26
27iDeclareType(Widget) 27iDeclareType(Widget)
28 28iDeclareType(MenuItem)
29void setupMenuTransition_Mobile (iWidget *menu, iBool isIncoming); 29
30void setupSheetTransition_Mobile (iWidget *sheet, iBool isIncoming); 30iWidget * makeSplitMultiPanel_Mobile (const iMenuItem *itemsNullTerminated);
31
32void setupMenuTransition_Mobile (iWidget *menu, iBool isIncoming);
33void setupSheetTransition_Mobile (iWidget *sheet, iBool isIncoming);
31 34
32void finalizeSheet_Mobile (iWidget *sheet); 35void finalizeSheet_Mobile (iWidget *sheet);
diff --git a/src/ui/util.c b/src/ui/util.c
index 906d30ae..995b730e 100644
--- a/src/ui/util.c
+++ b/src/ui/util.c
@@ -707,6 +707,9 @@ iWidget *makeMenu_Widget(iWidget *parent, const iMenuItem *items, size_t n) {
707 iBool haveIcons = iFalse; 707 iBool haveIcons = iFalse;
708 for (size_t i = 0; i < n; ++i) { 708 for (size_t i = 0; i < n; ++i) {
709 const iMenuItem *item = &items[i]; 709 const iMenuItem *item = &items[i];
710 if (!item->label) {
711 break;
712 }
710 if (equal_CStr(item->label, "---")) { 713 if (equal_CStr(item->label, "---")) {
711 addChild_Widget(menu, iClob(makeMenuSeparator_())); 714 addChild_Widget(menu, iClob(makeMenuSeparator_()));
712 } 715 }
@@ -1506,7 +1509,7 @@ static void addRadioButton_(iWidget *parent, const char *id, const char *label,
1506 id); 1509 id);
1507} 1510}
1508 1511
1509static void addFontButtons_(iWidget *parent, const char *id) { 1512static const iArray *makeFontItems_(const char *id) {
1510 const struct { 1513 const struct {
1511 const char * name; 1514 const char * name;
1512 enum iTextFont cfgId; 1515 enum iTextFont cfgId;
@@ -1518,7 +1521,7 @@ static void addFontButtons_(iWidget *parent, const char *id) {
1518 { "Tinos", tinos_TextFont }, 1521 { "Tinos", tinos_TextFont },
1519 { "---", -1 }, 1522 { "---", -1 },
1520 { "Iosevka", iosevka_TextFont } }; 1523 { "Iosevka", iosevka_TextFont } };
1521 iArray *items = new_Array(sizeof(iMenuItem)); 1524 iArray *items = collectNew_Array(sizeof(iMenuItem));
1522 iForIndices(i, fonts) { 1525 iForIndices(i, fonts) {
1523 pushBack_Array(items, 1526 pushBack_Array(items,
1524 &(iMenuItem){ fonts[i].name, 1527 &(iMenuItem){ fonts[i].name,
@@ -1528,11 +1531,18 @@ static void addFontButtons_(iWidget *parent, const char *id) {
1528 ? format_CStr("!%s.set arg:%d", id, fonts[i].cfgId) 1531 ? format_CStr("!%s.set arg:%d", id, fonts[i].cfgId)
1529 : NULL }); 1532 : NULL });
1530 } 1533 }
1531 iLabelWidget *button = makeMenuButton_LabelWidget("Source Sans 3", data_Array(items), size_Array(items)); 1534 pushBack_Array(items, &(iMenuItem){ NULL }); /* terminator */
1532 setBackgroundColor_Widget(findChild_Widget(as_Widget(button), "menu"), uiBackgroundMenu_ColorId); 1535 return items;
1536}
1537
1538static void addFontButtons_(iWidget *parent, const char *id) {
1539 const iArray *items = makeFontItems_(id);
1540 iLabelWidget *button = makeMenuButton_LabelWidget("Source Sans 3",
1541 constData_Array(items), size_Array(items));
1542 setBackgroundColor_Widget(findChild_Widget(as_Widget(button), "menu"),
1543 uiBackgroundMenu_ColorId);
1533 setId_Widget(as_Widget(button), format_CStr("prefs.%s", id)); 1544 setId_Widget(as_Widget(button), format_CStr("prefs.%s", id));
1534 addChildFlags_Widget(parent, iClob(button), alignLeft_WidgetFlag); 1545 addChildFlags_Widget(parent, iClob(button), alignLeft_WidgetFlag);
1535 delete_Array(items);
1536} 1546}
1537 1547
1538#if 0 1548#if 0
@@ -1615,7 +1625,7 @@ static void addPrefsInputWithHeading_(iWidget *headings, iWidget *values,
1615static size_t findWidestItemLabel_(const iMenuItem *items, size_t num) { 1625static size_t findWidestItemLabel_(const iMenuItem *items, size_t num) {
1616 int widest = 0; 1626 int widest = 0;
1617 size_t widestPos = iInvalidPos; 1627 size_t widestPos = iInvalidPos;
1618 for (size_t i = 0; i < num; i++) { 1628 for (size_t i = 0; i < num && items[i].label; i++) {
1619 const int width = 1629 const int width =
1620 measure_Text(uiLabel_FontId, 1630 measure_Text(uiLabel_FontId,
1621 translateCStr_Lang(items[i].label)) 1631 translateCStr_Lang(items[i].label))
@@ -1629,6 +1639,153 @@ static size_t findWidestItemLabel_(const iMenuItem *items, size_t num) {
1629} 1639}
1630 1640
1631iWidget *makePreferences_Widget(void) { 1641iWidget *makePreferences_Widget(void) {
1642 /* Common items. */
1643 const iMenuItem langItems[] = { { "${lang.de} - de", 0, 0, "uilang id:de" },
1644 { "${lang.en} - en", 0, 0, "uilang id:en" },
1645 { "${lang.es} - es", 0, 0, "uilang id:es" },
1646 { "${lang.fi} - fi", 0, 0, "uilang id:fi" },
1647 { "${lang.fr} - fr", 0, 0, "uilang id:fr" },
1648 { "${lang.ia} - ia", 0, 0, "uilang id:ia" },
1649 { "${lang.ie} - ie", 0, 0, "uilang id:ie" },
1650 { "${lang.pl} - pl", 0, 0, "uilang id:pl" },
1651 { "${lang.ru} - ru", 0, 0, "uilang id:ru" },
1652 { "${lang.sr} - sr", 0, 0, "uilang id:sr" },
1653 { "${lang.tok} - tok", 0, 0, "uilang id:tok" },
1654 { "${lang.zh.hans} - zh", 0, 0, "uilang id:zh_Hans" },
1655 { "${lang.zh.hant} - zh", 0, 0, "uilang id:zh_Hant" },
1656 { NULL } };
1657 const iMenuItem returnKeyBehaviors[] = {
1658 { "${prefs.returnkey.linebreak} " uiTextAction_ColorEscape shift_Icon return_Icon
1659 restore_ColorEscape
1660 " ${prefs.returnkey.accept} " uiTextAction_ColorEscape return_Icon,
1661 0,
1662 0,
1663 format_CStr("returnkey.set arg:%d", default_ReturnKeyBehavior) },
1664 { "${prefs.returnkey.linebreak} " uiTextAction_ColorEscape return_Icon restore_ColorEscape
1665 " ${prefs.returnkey.accept} " uiTextAction_ColorEscape shift_Icon return_Icon,
1666 0,
1667 0,
1668 format_CStr("returnkey.set arg:%d", acceptWithShift_ReturnKeyBehavior) },
1669 { "${prefs.returnkey.linebreak} " uiTextAction_ColorEscape return_Icon restore_ColorEscape
1670 " ${prefs.returnkey.accept} " uiTextAction_ColorEscape
1671#if defined (iPlatformApple)
1672 "\u2318" return_Icon,
1673#else
1674 "Ctrl" return_Icon,
1675#endif
1676 0,
1677 0,
1678 format_CStr("returnkey.set arg:%d", acceptWithPrimaryMod_ReturnKeyBehavior) },
1679 { NULL }
1680 };
1681 iMenuItem docThemes[2][max_GmDocumentTheme + 1];
1682 for (int i = 0; i < 2; ++i) {
1683 const iBool isDark = (i == 0);
1684 const char *mode = isDark ? "dark" : "light";
1685 const iMenuItem items[max_GmDocumentTheme + 1] = {
1686 { "${prefs.doctheme.name.colorfuldark}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, colorfulDark_GmDocumentTheme) },
1687 { "${prefs.doctheme.name.colorfullight}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, colorfulLight_GmDocumentTheme) },
1688 { "${prefs.doctheme.name.black}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, black_GmDocumentTheme) },
1689 { "${prefs.doctheme.name.gray}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, gray_GmDocumentTheme) },
1690 { "${prefs.doctheme.name.white}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, white_GmDocumentTheme) },
1691 { "${prefs.doctheme.name.sepia}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, sepia_GmDocumentTheme) },
1692 { "${prefs.doctheme.name.highcontrast}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, highContrast_GmDocumentTheme) },
1693 { NULL }
1694 };
1695 memcpy(docThemes[i], items, sizeof(items));
1696 }
1697 const iMenuItem imgStyles[] = {
1698 { "${prefs.imagestyle.original}", 0, 0, format_CStr("imagestyle.set arg:%d", original_ImageStyle) },
1699 { "${prefs.imagestyle.grayscale}", 0, 0, format_CStr("imagestyle.set arg:%d", grayscale_ImageStyle) },
1700 { "${prefs.imagestyle.bgfg}", 0, 0, format_CStr("imagestyle.set arg:%d", bgFg_ImageStyle) },
1701 { "${prefs.imagestyle.text}", 0, 0, format_CStr("imagestyle.set arg:%d", textColorized_ImageStyle) },
1702 { "${prefs.imagestyle.preformat}", 0, 0, format_CStr("imagestyle.set arg:%d", preformatColorized_ImageStyle) },
1703 { NULL }
1704 };
1705 /* Create the Preferences UI. */
1706 if (deviceType_App() != desktop_AppDeviceType) {
1707 const iMenuItem pinSplit[] = {
1708 { "button id:prefs.pinsplit.0 label:prefs.pinsplit.none", 0, 0, "pinsplit.set arg:0" },
1709 { "button id:prefs.pinsplit.1 label:prefs.pinsplit.left", 0, 0, "pinsplit.set arg:1" },
1710 { "button id:prefs.pinsplit.2 label:prefs.pinsplit.right", 0, 0, "pinsplit.set arg:2" },
1711 { NULL }
1712 };
1713 const iMenuItem themeItems[] = {
1714 { "button id:prefs.theme.0 label:prefs.theme.black", 0, 0, "theme.set arg:0" },
1715 { "button id:prefs.theme.1 label:prefs.theme.dark", 0, 0, "theme.set arg:1" },
1716 { "button id:prefs.theme.2 label:prefs.theme.light", 0, 0, "theme.set arg:2" },
1717 { "button id:prefs.theme.3 label:prefs.theme.white", 0, 0, "theme.set arg:3" },
1718 { NULL }
1719 };
1720 const iMenuItem accentItems[] = {
1721 { "button id:prefs.accent.0 label:prefs.accent.teal", 0, 0, "accent.set arg:0" },
1722 { "button id:prefs.accent.1 label:prefs.accent.orange", 0, 0, "accent.set arg:1" },
1723 { NULL }
1724 };
1725 const iMenuItem satItems[] = {
1726 { "button id:prefs.saturation.3 text:100 %", 0, 0, "saturation.set arg:100" },
1727 { "button id:prefs.saturation.2 text:66 %", 0, 0, "saturation.set arg:66" },
1728 { "button id:prefs.saturation.1 text:33 %", 0, 0, "saturation.set arg:33" },
1729 { "button id:prefs.saturation.0 text:0 %", 0, 0, "saturation.set arg:0" },
1730 { NULL }
1731 };
1732 const iMenuItem generalItems[] = {
1733 { "title id:heading.prefs.general", 0, 0, NULL },
1734 { "input id:prefs.searchurl url:1", 0, 0, NULL },
1735 { "padding", 0, 0, NULL },
1736 { "toggle id:prefs.hoverlink", 0, 0, NULL },
1737 { "toggle id:prefs.archive.openindex", 0, 0, NULL },
1738 { "radio device:1 id:prefs.pinsplit", 0, 0, (const void *) pinSplit },
1739 { "padding", 0, 0, NULL },
1740 { "dropdown id:prefs.uilang", 0, 0, (const void *) langItems },
1741 { NULL }
1742 };
1743 const iMenuItem uiItems[] = {
1744 { "title id:heading.prefs.interface", 0, 0, NULL },
1745 { "dropdown device:1 id:prefs.returnkey", 0, 0, (const void *) returnKeyBehaviors },
1746 { "padding device:1", 0, 0, NULL },
1747 { "toggle device:2 id:prefs.hidetoolbarscroll", 0, 0, NULL },
1748 { "heading id:heading.prefs.sizing", 0, 0, NULL },
1749 { "input id:prefs.uiscale maxlen:8", 0, 0, NULL },
1750 { NULL }
1751 };
1752 const iMenuItem colorItems[] = {
1753 { "title id:heading.prefs.colors", 0, 0, NULL },
1754 { "heading id:heading.prefs.uitheme", 0, 0, NULL },
1755 { "toggle id:prefs.ostheme", 0, 0, NULL },
1756 { "radio id:prefs.theme", 0, 0, (const void *) themeItems },
1757 { "radio id:prefs.accent", 0, 0, (const void *) accentItems },
1758 { "heading id:heading.prefs.pagecontent", 0, 0, NULL },
1759 { "dropdown id:prefs.doctheme.dark", 0, 0, (const void *) docThemes[0] },
1760 { "dropdown id:prefs.doctheme.light", 0, 0, (const void *) docThemes[1] },
1761 { "radio id:prefs.saturation", 0, 0, (const void *) satItems },
1762 { "padding", 0, 0, NULL },
1763 { "dropdown id:prefs.imagestyle", 0, 0, (const void *) imgStyles },
1764 { NULL }
1765 };
1766 const iMenuItem fontItems[] = {
1767 { "title id:heading.prefs.fonts", 0, 0, NULL },
1768 { "dropdown id:prefs.headingfont", 0, 0, (const void *) constData_Array(makeFontItems_("headingfont")) },
1769 { "dropdown id:prefs.font", 0, 0, (const void *) constData_Array(makeFontItems_("font")) },
1770 { NULL }
1771 };
1772 const iMenuItem items[] = { { "panel icon:0x2699 id:heading.prefs.general",
1773 '1', 0,
1774 (const void *) generalItems },
1775 { "panel icon:0x1f4f1 id:heading.prefs.interface",
1776 '2', 0,
1777 (const void *) uiItems },
1778 { "panel icon:0x1f3a8 id:heading.prefs.colors",
1779 '3', 0,
1780 (const void *) colorItems },
1781 { "panel icon:0x1f5da id:heading.prefs.fonts",
1782 '4', 0,
1783 (const void *) fontItems },
1784 { NULL } };
1785 iWidget *dlg = makeSplitMultiPanel_Mobile(items);
1786 setupSheetTransition_Mobile(dlg, iTrue);
1787 return dlg;
1788 }
1632 iWidget *dlg = makeSheet_Widget("prefs"); 1789 iWidget *dlg = makeSheet_Widget("prefs");
1633 addChildFlags_Widget(dlg, 1790 addChildFlags_Widget(dlg,
1634 iClob(new_LabelWidget(uiHeading_ColorEscape "${heading.prefs}", NULL)), 1791 iClob(new_LabelWidget(uiHeading_ColorEscape "${heading.prefs}", NULL)),
@@ -1666,22 +1823,7 @@ iWidget *makePreferences_Widget(void) {
1666 addChild_Widget(values, iClob(makePadding_Widget(bigGap))); 1823 addChild_Widget(values, iClob(makePadding_Widget(bigGap)));
1667 /* UI languages. */ { 1824 /* UI languages. */ {
1668 iArray *uiLangs = collectNew_Array(sizeof(iMenuItem)); 1825 iArray *uiLangs = collectNew_Array(sizeof(iMenuItem));
1669 const iMenuItem langItems[] = { 1826 pushBackN_Array(uiLangs, langItems, iElemCount(langItems) - 1);
1670 { "${lang.de} - de", 0, 0, "uilang id:de" },
1671 { "${lang.en} - en", 0, 0, "uilang id:en" },
1672 { "${lang.es} - es", 0, 0, "uilang id:es" },
1673 { "${lang.fi} - fi", 0, 0, "uilang id:fi" },
1674 { "${lang.fr} - fr", 0, 0, "uilang id:fr" },
1675 { "${lang.ia} - ia", 0, 0, "uilang id:ia" },
1676 { "${lang.ie} - ie", 0, 0, "uilang id:ie" },
1677 { "${lang.pl} - pl", 0, 0, "uilang id:pl" },
1678 { "${lang.ru} - ru", 0, 0, "uilang id:ru" },
1679 { "${lang.sr} - sr", 0, 0, "uilang id:sr" },
1680 { "${lang.tok} - tok", 0, 0, "uilang id:tok" },
1681 { "${lang.zh.hans} - zh", 0, 0, "uilang id:zh_Hans" },
1682 { "${lang.zh.hant} - zh", 0, 0, "uilang id:zh_Hant" },
1683 };
1684 pushBackN_Array(uiLangs, langItems, iElemCount(langItems));
1685 /* TODO: Add an arrange flag for resizing parent to widest child. */ 1827 /* TODO: Add an arrange flag for resizing parent to widest child. */
1686 size_t widestPos = findWidestItemLabel_(data_Array(uiLangs), size_Array(uiLangs)); 1828 size_t widestPos = findWidestItemLabel_(data_Array(uiLangs), size_Array(uiLangs));
1687 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.uilang}"))); 1829 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.uilang}")));
@@ -1702,39 +1844,12 @@ iWidget *makePreferences_Widget(void) {
1702#endif 1844#endif
1703 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.returnkey}"))); 1845 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.returnkey}")));
1704 /* Return key behaviors. */ { 1846 /* Return key behaviors. */ {
1705 const iMenuItem returnKeyBehaviors[] = {
1706 { "${prefs.returnkey.linebreak} "
1707 uiTextAction_ColorEscape shift_Icon return_Icon restore_ColorEscape
1708 " ${prefs.returnkey.accept} "
1709 uiTextAction_ColorEscape return_Icon,
1710 0,
1711 0,
1712 format_CStr("returnkey.set arg:%d", default_ReturnKeyBehavior) },
1713 { "${prefs.returnkey.linebreak} "
1714 uiTextAction_ColorEscape return_Icon restore_ColorEscape
1715 " ${prefs.returnkey.accept} "
1716 uiTextAction_ColorEscape shift_Icon return_Icon,
1717 0,
1718 0,
1719 format_CStr("returnkey.set arg:%d", acceptWithShift_ReturnKeyBehavior) },
1720 { "${prefs.returnkey.linebreak} "
1721 uiTextAction_ColorEscape return_Icon restore_ColorEscape
1722 " ${prefs.returnkey.accept} " uiTextAction_ColorEscape
1723#if defined (iPlatformApple)
1724 "\u2318" return_Icon,
1725#else
1726 "Ctrl" return_Icon,
1727#endif
1728 0,
1729 0,
1730 format_CStr("returnkey.set arg:%d", acceptWithPrimaryMod_ReturnKeyBehavior) },
1731 };
1732 iLabelWidget *returnKey = makeMenuButton_LabelWidget( 1847 iLabelWidget *returnKey = makeMenuButton_LabelWidget(
1733 returnKeyBehaviors[findWidestItemLabel_(returnKeyBehaviors, 1848 returnKeyBehaviors[findWidestItemLabel_(returnKeyBehaviors,
1734 iElemCount(returnKeyBehaviors))] 1849 iElemCount(returnKeyBehaviors) - 1)]
1735 .label, 1850 .label,
1736 returnKeyBehaviors, 1851 returnKeyBehaviors,
1737 iElemCount(returnKeyBehaviors)); 1852 iElemCount(returnKeyBehaviors) - 1);
1738 setBackgroundColor_Widget(findChild_Widget(as_Widget(returnKey), "menu"), 1853 setBackgroundColor_Widget(findChild_Widget(as_Widget(returnKey), "menu"),
1739 uiBackgroundMenu_ColorId); 1854 uiBackgroundMenu_ColorId);
1740 setId_Widget(addChildFlags_Widget(values, iClob(returnKey), alignLeft_WidgetFlag), 1855 setId_Widget(addChildFlags_Widget(values, iClob(returnKey), alignLeft_WidgetFlag),
@@ -1804,20 +1919,13 @@ iWidget *makePreferences_Widget(void) {
1804 for (int i = 0; i < 2; ++i) { 1919 for (int i = 0; i < 2; ++i) {
1805 const iBool isDark = (i == 0); 1920 const iBool isDark = (i == 0);
1806 const char *mode = isDark ? "dark" : "light"; 1921 const char *mode = isDark ? "dark" : "light";
1807 const iMenuItem themes[] = {
1808 { "${prefs.doctheme.name.colorfuldark}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, colorfulDark_GmDocumentTheme) },
1809 { "${prefs.doctheme.name.colorfullight}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, colorfulLight_GmDocumentTheme) },
1810 { "${prefs.doctheme.name.black}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, black_GmDocumentTheme) },
1811 { "${prefs.doctheme.name.gray}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, gray_GmDocumentTheme) },
1812 { "${prefs.doctheme.name.white}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, white_GmDocumentTheme) },
1813 { "${prefs.doctheme.name.sepia}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, sepia_GmDocumentTheme) },
1814 { "${prefs.doctheme.name.highcontrast}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, highContrast_GmDocumentTheme) },
1815 };
1816 addChild_Widget(headings, iClob(makeHeading_Widget(isDark ? "${prefs.doctheme.dark}" : "${prefs.doctheme.light}"))); 1922 addChild_Widget(headings, iClob(makeHeading_Widget(isDark ? "${prefs.doctheme.dark}" : "${prefs.doctheme.light}")));
1817 iLabelWidget *button = 1923 iLabelWidget *button = makeMenuButton_LabelWidget(
1818 makeMenuButton_LabelWidget(themes[1].label, themes, iElemCount(themes)); 1924 docThemes[i][findWidestItemLabel_(docThemes[i], max_GmDocumentTheme)].label,
1819// setFrameColor_Widget(findChild_Widget(as_Widget(button), "menu"), 1925 docThemes[i],
1820// uiBackgroundSelected_ColorId); 1926 max_GmDocumentTheme);
1927 // setFrameColor_Widget(findChild_Widget(as_Widget(button), "menu"),
1928 // uiBackgroundSelected_ColorId);
1821 setBackgroundColor_Widget(findChild_Widget(as_Widget(button), "menu"), uiBackgroundMenu_ColorId); 1929 setBackgroundColor_Widget(findChild_Widget(as_Widget(button), "menu"), uiBackgroundMenu_ColorId);
1822 setId_Widget(addChildFlags_Widget(values, iClob(button), alignLeft_WidgetFlag), 1930 setId_Widget(addChildFlags_Widget(values, iClob(button), alignLeft_WidgetFlag),
1823 format_CStr("prefs.doctheme.%s", mode)); 1931 format_CStr("prefs.doctheme.%s", mode));
@@ -1833,17 +1941,10 @@ iWidget *makePreferences_Widget(void) {
1833 addChildFlags_Widget(values, iClob(sats), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); 1941 addChildFlags_Widget(values, iClob(sats), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);
1834 /* Colorize images. */ { 1942 /* Colorize images. */ {
1835 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.imagestyle}"))); 1943 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.imagestyle}")));
1836 const iMenuItem imgStyles[] = {
1837 { "${prefs.imagestyle.original}", 0, 0, format_CStr("imagestyle.set arg:%d", original_ImageStyle) },
1838 { "${prefs.imagestyle.grayscale}", 0, 0, format_CStr("imagestyle.set arg:%d", grayscale_ImageStyle) },
1839 { "${prefs.imagestyle.bgfg}", 0, 0, format_CStr("imagestyle.set arg:%d", bgFg_ImageStyle) },
1840 { "${prefs.imagestyle.text}", 0, 0, format_CStr("imagestyle.set arg:%d", textColorized_ImageStyle) },
1841 { "${prefs.imagestyle.preformat}", 0, 0, format_CStr("imagestyle.set arg:%d", preformatColorized_ImageStyle) },
1842 };
1843 iLabelWidget *button = makeMenuButton_LabelWidget( 1944 iLabelWidget *button = makeMenuButton_LabelWidget(
1844 imgStyles[findWidestItemLabel_(imgStyles, iElemCount(imgStyles))].label, 1945 imgStyles[findWidestItemLabel_(imgStyles, iElemCount(imgStyles) - 1)].label,
1845 imgStyles, 1946 imgStyles,
1846 iElemCount(imgStyles)); 1947 iElemCount(imgStyles) - 1);
1847 setBackgroundColor_Widget(findChild_Widget(as_Widget(button), "menu"), 1948 setBackgroundColor_Widget(findChild_Widget(as_Widget(button), "menu"),
1848 uiBackgroundMenu_ColorId); 1949 uiBackgroundMenu_ColorId);
1849 setId_Widget(addChildFlags_Widget(values, iClob(button), alignLeft_WidgetFlag), 1950 setId_Widget(addChildFlags_Widget(values, iClob(button), alignLeft_WidgetFlag),
diff --git a/src/ui/util.h b/src/ui/util.h
index 2423f834..87b72394 100644
--- a/src/ui/util.h
+++ b/src/ui/util.h
@@ -220,7 +220,10 @@ struct Impl_MenuItem {
220 const char *label; 220 const char *label;
221 int key; 221 int key;
222 int kmods; 222 int kmods;
223 const char *command; 223 union {
224 const char *command;
225 const void *data;
226 };
224}; 227};
225 228
226iWidget * makeMenu_Widget (iWidget *parent, const iMenuItem *items, size_t n); /* returns no ref */ 229iWidget * makeMenu_Widget (iWidget *parent, const iMenuItem *items, size_t n); /* returns no ref */
diff --git a/src/ui/widget.c b/src/ui/widget.c
index 4f567989..0d6787ce 100644
--- a/src/ui/widget.c
+++ b/src/ui/widget.c
@@ -1402,6 +1402,7 @@ iAny *hitChild_Widget(const iWidget *d, iInt2 coord) {
1402} 1402}
1403 1403
1404iAny *findChild_Widget(const iWidget *d, const char *id) { 1404iAny *findChild_Widget(const iWidget *d, const char *id) {
1405 if (!d) return NULL;
1405 if (cmp_String(id_Widget(d), id) == 0) { 1406 if (cmp_String(id_Widget(d), id) == 0) {
1406 return iConstCast(iAny *, d); 1407 return iConstCast(iAny *, d);
1407 } 1408 }
diff --git a/src/ui/window.c b/src/ui/window.c
index 3ac02495..096853cc 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -1136,6 +1136,9 @@ void setTitle_Window(iWindow *d, const iString *title) {
1136} 1136}
1137 1137
1138void setUiScale_Window(iWindow *d, float uiScale) { 1138void setUiScale_Window(iWindow *d, float uiScale) {
1139 if (uiScale <= 0.0f) {
1140 uiScale = 1.0f;
1141 }
1139 uiScale = iClamp(uiScale, 0.5f, 4.0f); 1142 uiScale = iClamp(uiScale, 0.5f, 4.0f);
1140 if (d) { 1143 if (d) {
1141 if (iAbs(d->uiScale - uiScale) > 0.0001f) { 1144 if (iAbs(d->uiScale - uiScale) > 0.0001f) {