summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-03-09 22:44:45 +0200
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-03-09 22:44:45 +0200
commit2002543dbedcc1666d02d11fbde55f794d692bb7 (patch)
tree853412e7593d4a1ca3f0ccdc5ca31466a3400463
parent5ccb0e0096dff6c2bbdfa959610620a14fdc918d (diff)
Mobile: Revising phone-style dialogs
Sliding panels and left-edge swipes.
-rw-r--r--src/app.c2
-rw-r--r--src/defs.h2
-rw-r--r--src/ui/labelwidget.c15
-rw-r--r--src/ui/touch.c7
-rw-r--r--src/ui/util.c207
-rw-r--r--src/ui/widget.c92
-rw-r--r--src/ui/widget.h2
-rw-r--r--src/ui/window.c10
-rw-r--r--src/ui/window.h1
9 files changed, 287 insertions, 51 deletions
diff --git a/src/app.c b/src/app.c
index 9f5eef09..32255675 100644
--- a/src/app.c
+++ b/src/app.c
@@ -1218,12 +1218,14 @@ static void updateDropdownSelection_(iLabelWidget *dropButton, const char *selec
1218} 1218}
1219 1219
1220static void updateColorThemeButton_(iLabelWidget *button, int theme) { 1220static void updateColorThemeButton_(iLabelWidget *button, int theme) {
1221 if (!button) return;
1221// const char *mode = strstr(cstr_String(id_Widget(as_Widget(button))), ".dark") 1222// const char *mode = strstr(cstr_String(id_Widget(as_Widget(button))), ".dark")
1222// ? "dark" : "light"; 1223// ? "dark" : "light";
1223 updateDropdownSelection_(button, format_CStr(".set arg:%d", theme)); 1224 updateDropdownSelection_(button, format_CStr(".set arg:%d", theme));
1224} 1225}
1225 1226
1226static void updateFontButton_(iLabelWidget *button, int font) { 1227static void updateFontButton_(iLabelWidget *button, int font) {
1228 if (!button) return;
1227 updateDropdownSelection_(button, format_CStr(".set arg:%d", font)); 1229 updateDropdownSelection_(button, format_CStr(".set arg:%d", font));
1228} 1230}
1229 1231
diff --git a/src/defs.h b/src/defs.h
index 80951997..6cfd5733 100644
--- a/src/defs.h
+++ b/src/defs.h
@@ -70,3 +70,5 @@ enum iFileVersion {
70#define circleWhite_Icon "\u25cb" 70#define circleWhite_Icon "\u25cb"
71#define gear_Icon "\u2699" 71#define gear_Icon "\u2699"
72#define explosion_Icon "\U0001f4a5" 72#define explosion_Icon "\U0001f4a5"
73#define leftAngle_Icon "\U0001fba4"
74#define rightAngle_Icon "\U0001fba5"
diff --git a/src/ui/labelwidget.c b/src/ui/labelwidget.c
index b628ac23..9af0e2e3 100644
--- a/src/ui/labelwidget.c
+++ b/src/ui/labelwidget.c
@@ -22,6 +22,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22 22
23#include "labelwidget.h" 23#include "labelwidget.h"
24#include "text.h" 24#include "text.h"
25#include "defs.h"
25#include "color.h" 26#include "color.h"
26#include "paint.h" 27#include "paint.h"
27#include "app.h" 28#include "app.h"
@@ -147,7 +148,9 @@ static void getColors_LabelWidget_(const iLabelWidget *d, int *bg, int *fg, int
147 const iBool isFrameless = (flags & frameless_WidgetFlag) != 0; 148 const iBool isFrameless = (flags & frameless_WidgetFlag) != 0;
148 const iBool isButton = d->click.button != 0; 149 const iBool isButton = d->click.button != 0;
149 /* Default color state. */ 150 /* Default color state. */
150 *bg = isButton && ~flags & noBackground_WidgetFlag ? uiBackground_ColorId : none_ColorId; 151 *bg = isButton && ~flags & noBackground_WidgetFlag ? (d->widget.bgColor != none_ColorId ?
152 d->widget.bgColor : uiBackground_ColorId)
153 : none_ColorId;
151 *fg = uiText_ColorId; 154 *fg = uiText_ColorId;
152 *frame1 = isButton ? uiEmboss1_ColorId : d->widget.frameColor; 155 *frame1 = isButton ? uiEmboss1_ColorId : d->widget.frameColor;
153 *frame2 = isButton ? uiEmboss2_ColorId : *frame1; 156 *frame2 = isButton ? uiEmboss2_ColorId : *frame1;
@@ -248,6 +251,7 @@ static void draw_LabelWidget_(const iLabelWidget *d) {
248 drawCentered_Text( 251 drawCentered_Text(
249 d->font, 252 d->font,
250 (iRect){ 253 (iRect){
254 /* The icon position is fine-tuned; c.f. high baseline of Source Sans Pro. */
251 add_I2(add_I2(bounds.pos, padding_(flags)), 255 add_I2(add_I2(bounds.pos, padding_(flags)),
252 init_I2((flags & extraPadding_WidgetFlag ? -2 : -1.20f) * gap_UI, -gap_UI / 8)), 256 init_I2((flags & extraPadding_WidgetFlag ? -2 : -1.20f) * gap_UI, -gap_UI / 8)),
253 init_I2(iconPad, lineHeight_Text(d->font)) }, 257 init_I2(iconPad, lineHeight_Text(d->font)) },
@@ -294,6 +298,14 @@ static void draw_LabelWidget_(const iLabelWidget *d) {
294 fg, 298 fg,
295 cstr_String(&d->label)); 299 cstr_String(&d->label));
296 } 300 }
301 if (flags & chevron_WidgetFlag) {
302 const iRect chRect = rect;
303 const int chSize = lineHeight_Text(d->font);
304 drawCentered_Text(d->font,
305 (iRect){ addX_I2(topRight_Rect(chRect), -iconPad),
306 init_I2(chSize, height_Rect(chRect)) },
307 iTrue, fg, rightAngle_Icon);
308 }
297 unsetClip_Paint(&p); 309 unsetClip_Paint(&p);
298} 310}
299 311
@@ -436,6 +448,7 @@ iChar icon_LabelWidget(const iLabelWidget *d) {
436} 448}
437 449
438const iString *text_LabelWidget(const iLabelWidget *d) { 450const iString *text_LabelWidget(const iLabelWidget *d) {
451 if (!d) return collectNew_String();
439 return &d->label; 452 return &d->label;
440} 453}
441 454
diff --git a/src/ui/touch.c b/src/ui/touch.c
index 4b22b8fb..14e5fe48 100644
--- a/src/ui/touch.c
+++ b/src/ui/touch.c
@@ -130,7 +130,7 @@ static void dispatchMotion_Touch_(iFloat3 pos, int buttonState) {
130 }); 130 });
131} 131}
132 132
133static void dispatchClick_Touch_(const iTouch *d, int button) { 133static iBool dispatchClick_Touch_(const iTouch *d, int button) {
134 const iFloat3 tapPos = d->pos[0]; 134 const iFloat3 tapPos = d->pos[0];
135 SDL_MouseButtonEvent btn = { 135 SDL_MouseButtonEvent btn = {
136 .type = SDL_MOUSEBUTTONDOWN, 136 .type = SDL_MOUSEBUTTONDOWN,
@@ -142,7 +142,7 @@ static void dispatchClick_Touch_(const iTouch *d, int button) {
142 .x = x_F3(tapPos), 142 .x = x_F3(tapPos),
143 .y = y_F3(tapPos) 143 .y = y_F3(tapPos)
144 }; 144 };
145 dispatchEvent_Widget(get_Window()->root, (SDL_Event *) &btn); 145 iBool wasUsed = dispatchEvent_Widget(get_Window()->root, (SDL_Event *) &btn);
146 /* Immediately released, too. */ 146 /* Immediately released, too. */
147 btn.type = SDL_MOUSEBUTTONUP; 147 btn.type = SDL_MOUSEBUTTONUP;
148 btn.state = SDL_RELEASED; 148 btn.state = SDL_RELEASED;
@@ -150,6 +150,7 @@ static void dispatchClick_Touch_(const iTouch *d, int button) {
150 dispatchEvent_Widget(get_Window()->root, (SDL_Event *) &btn); 150 dispatchEvent_Widget(get_Window()->root, (SDL_Event *) &btn);
151 //dispatchMotion_Touch_(zero_F3(), 0); 151 //dispatchMotion_Touch_(zero_F3(), 0);
152 setHover_Widget(NULL); /* FIXME: this doesn't seem to do anything? */ 152 setHover_Widget(NULL); /* FIXME: this doesn't seem to do anything? */
153 return wasUsed;
153} 154}
154 155
155static void clearWidgetMomentum_TouchState_(iTouchState *d, iWidget *widget) { 156static void clearWidgetMomentum_TouchState_(iTouchState *d, iWidget *widget) {
@@ -305,7 +306,7 @@ iBool processEvent_Touch(const SDL_Event *ev) {
305 iWidget *aff = hitChild_Widget(window->root, init_I2(iRound(x), iRound(y_F3(pos)))); 306 iWidget *aff = hitChild_Widget(window->root, init_I2(iRound(x), iRound(y_F3(pos))));
306 /* TODO: We must retain a reference to the affinity widget, or otherwise it might 307 /* TODO: We must retain a reference to the affinity widget, or otherwise it might
307 be destroyed during the gesture. */ 308 be destroyed during the gesture. */
308// printf("aff:%p (%s)\n", aff, aff ? class_Widget(aff)->name : "-"); 309 printf("aff:%p (%s)\n", aff, aff ? class_Widget(aff)->name : "-");
309 if (flags_Widget(aff) & touchDrag_WidgetFlag) { 310 if (flags_Widget(aff) & touchDrag_WidgetFlag) {
310 dispatchEvent_Widget(window->root, (SDL_Event *) &(SDL_MouseButtonEvent){ 311 dispatchEvent_Widget(window->root, (SDL_Event *) &(SDL_MouseButtonEvent){
311 .type = SDL_MOUSEBUTTONDOWN, 312 .type = SDL_MOUSEBUTTONDOWN,
diff --git a/src/ui/util.c b/src/ui/util.c
index 10fa66b0..c6e2fd1a 100644
--- a/src/ui/util.c
+++ b/src/ui/util.c
@@ -26,6 +26,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
26#include "bookmarks.h" 26#include "bookmarks.h"
27#include "color.h" 27#include "color.h"
28#include "command.h" 28#include "command.h"
29#include "defs.h"
29#include "documentwidget.h" 30#include "documentwidget.h"
30#include "gmutil.h" 31#include "gmutil.h"
31#include "feeds.h" 32#include "feeds.h"
@@ -517,6 +518,7 @@ iWidget *makeMenu_Widget(iWidget *parent, const iMenuItem *items, size_t n) {
517void openMenu_Widget(iWidget *d, iInt2 coord) { 518void openMenu_Widget(iWidget *d, iInt2 coord) {
518 const iInt2 rootSize = rootSize_Window(get_Window()); 519 const iInt2 rootSize = rootSize_Window(get_Window());
519 const iBool isPortraitPhone = (deviceType_App() == phone_AppDeviceType && isPortrait_App()); 520 const iBool isPortraitPhone = (deviceType_App() == phone_AppDeviceType && isPortrait_App());
521 const iBool isSlidePanel = (flags_Widget(d) & horizontalOffset_WidgetFlag) != 0;
520 /* Menu closes when commands are emitted, so handle any pending ones beforehand. */ 522 /* Menu closes when commands are emitted, so handle any pending ones beforehand. */
521 postCommand_App("cancel"); /* dismiss any other menus */ 523 postCommand_App("cancel"); /* dismiss any other menus */
522 processEvents_App(postedEventsOnly_AppEventMode); 524 processEvents_App(postedEventsOnly_AppEventMode);
@@ -526,6 +528,9 @@ void openMenu_Widget(iWidget *d, iInt2 coord) {
526 if (isPortraitPhone) { 528 if (isPortraitPhone) {
527 setFlags_Widget(d, arrangeWidth_WidgetFlag | resizeChildrenToWidestChild_WidgetFlag, iFalse); 529 setFlags_Widget(d, arrangeWidth_WidgetFlag | resizeChildrenToWidestChild_WidgetFlag, iFalse);
528 setFlags_Widget(d, resizeWidthOfChildren_WidgetFlag, iTrue); 530 setFlags_Widget(d, resizeWidthOfChildren_WidgetFlag, iTrue);
531 if (!isSlidePanel) {
532 setFlags_Widget(d, borderTop_WidgetFlag, iTrue);
533 }
529 d->rect.size.x = rootSize_Window(get_Window()).x; 534 d->rect.size.x = rootSize_Window(get_Window()).x;
530 } 535 }
531 /* Update item fonts. */ { 536 /* Update item fonts. */ {
@@ -537,7 +542,9 @@ void openMenu_Widget(iWidget *d, iInt2 coord) {
537 setFont_LabelWidget(label, isCaution ? uiLabelBold_FontId : uiLabel_FontId); 542 setFont_LabelWidget(label, isCaution ? uiLabelBold_FontId : uiLabel_FontId);
538 } 543 }
539 else if (isPortraitPhone) { 544 else if (isPortraitPhone) {
540 setFont_LabelWidget(label, isCaution ? defaultBigBold_FontId : defaultBig_FontId); 545 if (!isSlidePanel) {
546 setFont_LabelWidget(label, isCaution ? defaultBigBold_FontId : defaultBig_FontId);
547 }
541 } 548 }
542 else { 549 else {
543 setFont_LabelWidget(label, isCaution ? uiContentBold_FontId : uiContent_FontId); 550 setFont_LabelWidget(label, isCaution ? uiContentBold_FontId : uiContent_FontId);
@@ -547,7 +554,12 @@ void openMenu_Widget(iWidget *d, iInt2 coord) {
547 } 554 }
548 arrange_Widget(d); 555 arrange_Widget(d);
549 if (isPortraitPhone) { 556 if (isPortraitPhone) {
550 d->rect.pos = init_I2(0, rootSize.y); 557 if (isSlidePanel) {
558 d->rect.pos = zero_I2(); //neg_I2(bounds_Widget(parent_Widget(d)).pos);
559 }
560 else {
561 d->rect.pos = init_I2(0, rootSize.y);
562 }
551 } 563 }
552 else { 564 else {
553 d->rect.pos = coord; 565 d->rect.pos = coord;
@@ -568,7 +580,7 @@ void openMenu_Widget(iWidget *d, iInt2 coord) {
568 rightExcess += r; 580 rightExcess += r;
569 } 581 }
570#endif 582#endif
571 if (bottomExcess > 0) { 583 if (bottomExcess > 0 && (!isPortraitPhone || !isSlidePanel)) {
572 d->rect.pos.y -= bottomExcess; 584 d->rect.pos.y -= bottomExcess;
573 } 585 }
574 if (topExcess > 0) { 586 if (topExcess > 0) {
@@ -583,7 +595,7 @@ void openMenu_Widget(iWidget *d, iInt2 coord) {
583 postRefresh_App(); 595 postRefresh_App();
584 postCommand_Widget(d, "menu.opened"); 596 postCommand_Widget(d, "menu.opened");
585 if (isPortraitPhone) { 597 if (isPortraitPhone) {
586 setVisualOffset_Widget(d, height_Widget(d), 0, 0); 598 setVisualOffset_Widget(d, isSlidePanel ? width_Widget(d) : height_Widget(d), 0, 0);
587 setVisualOffset_Widget(d, 0, 330, easeOut_AnimFlag | softer_AnimFlag); 599 setVisualOffset_Widget(d, 0, 330, easeOut_AnimFlag | softer_AnimFlag);
588 } 600 }
589} 601}
@@ -594,7 +606,11 @@ void closeMenu_Widget(iWidget *d) {
594 postRefresh_App(); 606 postRefresh_App();
595 postCommand_Widget(d, "menu.closed"); 607 postCommand_Widget(d, "menu.closed");
596 if (isPortrait_App() && deviceType_App() == phone_AppDeviceType) { 608 if (isPortrait_App() && deviceType_App() == phone_AppDeviceType) {
597 setVisualOffset_Widget(d, height_Widget(d), 200, easeIn_AnimFlag | softer_AnimFlag); 609 setVisualOffset_Widget(d,
610 flags_Widget(d) & horizontalOffset_WidgetFlag ?
611 width_Widget(d) : height_Widget(d),
612 200,
613 easeIn_AnimFlag | softer_AnimFlag);
598 } 614 }
599} 615}
600 616
@@ -882,7 +898,43 @@ iWidget *makeSheet_Widget(const char *id) {
882 return sheet; 898 return sheet;
883} 899}
884 900
901static iBool slidePanelHandler_(iWidget *d, const char *cmd) {
902 if (equal_Command(cmd, "panel.open")) {
903 iWidget *button = pointer_Command(cmd);
904 iWidget *panel = userData_Object(button);
905 openMenu_Widget(panel, zero_I2());
906 updateTextCStr_LabelWidget(findWidget_App("panel.back"), "Settings");
907 return iTrue;
908 }
909 if (equal_Command(cmd, "mouse.clicked") && arg_Command(cmd) &&
910 argLabel_Command(cmd, "button") == SDL_BUTTON_X1) {
911 postCommand_App("panel.close");
912 return iTrue;
913 }
914 if (equal_Command(cmd, "panel.close")) {
915 iBool wasClosed = iFalse;
916 iForEach(ObjectList, i, children_Widget(parent_Widget(d))) {
917 iWidget *child = i.object;
918 if (!cmp_String(id_Widget(child), "panel") && isVisible_Widget(child)) {
919 closeMenu_Widget(child);
920 setFocus_Widget(NULL);
921 updateTextCStr_LabelWidget(findWidget_App("panel.back"), "Back");
922 wasClosed = iTrue;
923 }
924 }
925 if (!wasClosed) {
926 postCommand_App("prefs.dismiss");
927 }
928 return iTrue;
929 }
930 return iFalse;
931}
932
885static iBool isTwoColumnPage_(iWidget *d) { 933static iBool isTwoColumnPage_(iWidget *d) {
934 if (cmp_String(id_Widget(d), "dialogbuttons") == 0 ||
935 cmp_String(id_Widget(d), "prefs.tabs") == 0) {
936 return iFalse;
937 }
886 if (class_Widget(d) == &Class_Widget && childCount_Widget(d) == 2) { 938 if (class_Widget(d) == &Class_Widget && childCount_Widget(d) == 2) {
887 return class_Widget(child_Widget(d, 0)) == &Class_Widget && 939 return class_Widget(child_Widget(d, 0)) == &Class_Widget &&
888 class_Widget(child_Widget(d, 1)) == &Class_Widget; 940 class_Widget(child_Widget(d, 1)) == &Class_Widget;
@@ -894,46 +946,129 @@ void finalizeSheet_Widget(iWidget *sheet) {
894 if (deviceType_App() == phone_AppDeviceType) { 946 if (deviceType_App() == phone_AppDeviceType) {
895 /* The sheet contents are completely rearranged on a phone. We'll set up a linear 947 /* The sheet contents are completely rearranged on a phone. We'll set up a linear
896 fullscreen arrangement of the widgets. Sheets are already scrollable so they 948 fullscreen arrangement of the widgets. Sheets are already scrollable so they
897 can be taller than the display. */ 949 can be taller than the display. In hindsight, it may have been easier to
950 create phone versions of each dialog, but at least this works with any future
951 changes to the UI (..."works"). */
952 int topSafe = 0;
953 int navBarHeight = lineHeight_Text(defaultBig_FontId) + 4 * gap_UI;
898#if defined (iPlatformAppleMobile) 954#if defined (iPlatformAppleMobile)
899 /* Safe area insets. */ { 955 /* Safe area insets. */ {
900 /* TODO: Must be updated when orientation changes; use a widget flag? */ 956 /* TODO: Must be updated when orientation changes; use a widget flag? */
901 float l, t, r, b; 957 float l, t, r, b;
902 safeAreaInsets_iOS(&l, &t, &r, &b); 958 safeAreaInsets_iOS(&l, &t, &r, &b);
903 setPadding_Widget(sheet, l, t, r, b); 959// setPadding_Widget(sheet, l, t, r, b);
960 setPadding1_Widget(sheet, 0);
961 topSafe = t;
962 navBarHeight += t;
904 } 963 }
905#endif 964#endif
906 setFlags_Widget(sheet, 965 setFlags_Widget(sheet,
907 parentCannotResize_WidgetFlag | arrangeWidth_WidgetFlag | 966 keepOnTop_WidgetFlag |
908 centerHorizontal_WidgetFlag, 967 parentCannotResize_WidgetFlag |
968 arrangeSize_WidgetFlag |
969 centerHorizontal_WidgetFlag |
970 arrangeVertical_WidgetFlag |
971 arrangeHorizontal_WidgetFlag |
972 overflowScrollable_WidgetFlag,
909 iFalse); 973 iFalse);
910 setFlags_Widget(sheet, frameless_WidgetFlag | resizeWidthOfChildren_WidgetFlag, iTrue); 974 setFlags_Widget(sheet,
975 commandOnClick_WidgetFlag |
976 frameless_WidgetFlag |
977 resizeWidthOfChildren_WidgetFlag,
978 iTrue);
979 setBackgroundColor_Widget(sheet, green_ColorId);
911 iPtrArray *contents = collect_PtrArray(new_PtrArray()); /* two-column pages */ 980 iPtrArray *contents = collect_PtrArray(new_PtrArray()); /* two-column pages */
981 iPtrArray *panelButtons = collect_PtrArray(new_PtrArray());
912 iWidget *tabs = findChild_Widget(sheet, "prefs.tabs"); 982 iWidget *tabs = findChild_Widget(sheet, "prefs.tabs");
983 iWidget *topPanel = new_Widget();
984 setId_Widget(topPanel, "panel.top");
985 //setBackgroundColor_Widget(topPanel, red_ColorId);
913 if (tabs) { 986 if (tabs) {
914 /* Pull out the pages and make them sequential. */ 987 iRelease(removeChild_Widget(sheet, child_Widget(sheet, 0))); /* heading */
988 iRelease(removeChild_Widget(sheet, findChild_Widget(sheet, "dialogbuttons")));
989 /* Pull out the pages and make them panels. */
915 iWidget *pages = findChild_Widget(tabs, "tabs.pages"); 990 iWidget *pages = findChild_Widget(tabs, "tabs.pages");
916 size_t pageCount = tabCount_Widget(tabs); 991 size_t pageCount = tabCount_Widget(tabs);
917 for (size_t i = 0; i < pageCount; i++) { 992 for (size_t i = 0; i < pageCount; i++) {
993 iString *text = copy_String(text_LabelWidget(tabPageButton_Widget(tabs, tabPage_Widget(tabs, 0))));
918 iWidget *page = removeTabPage_Widget(tabs, 0); 994 iWidget *page = removeTabPage_Widget(tabs, 0);
919 iWidget *pageContent = child_Widget(page, 1); /* surrounded by padding widgets */ 995 iWidget *pageContent = child_Widget(page, 1); /* surrounded by padding widgets */
920 pushBack_PtrArray(contents, ref_Object(pageContent)); 996 pushBack_PtrArray(contents, ref_Object(pageContent));
997 iLabelWidget *panelButton;
998 pushBack_PtrArray(panelButtons,
999 addChildFlags_Widget(topPanel,
1000 iClob(panelButton = new_LabelWidget
1001 (i == 1 ? "User Interface" : cstr_String(text),
1002 "panel.open")),
1003 alignLeft_WidgetFlag | frameless_WidgetFlag |
1004 borderBottom_WidgetFlag | extraPadding_WidgetFlag |
1005 chevron_WidgetFlag));
1006 const iChar icons[] = {
1007 0x2699, /* gear */
1008 0x1f4f1, /* mobile phone */
1009 0x1f3a8, /* palette */
1010 0x1f523,
1011 0x1f5a7, /* computer network */
1012 };
1013 setIcon_LabelWidget(panelButton, icons[i]);
1014 setFont_LabelWidget(panelButton, defaultBig_FontId);
1015 setBackgroundColor_Widget(as_Widget(panelButton), uiBackgroundSidebar_ColorId);
921 iRelease(page); 1016 iRelease(page);
1017 delete_String(text);
922 } 1018 }
923 destroy_Widget(tabs); 1019 destroy_Widget(tabs);
924 } 1020 }
925 else { 1021 iForEach(ObjectList, i, children_Widget(sheet)) {
926 iForEach(ObjectList, i, children_Widget(sheet)) { 1022 iWidget *child = i.object;
927 iWidget *child = i.object; 1023 if (isTwoColumnPage_(child)) {
928 if (isTwoColumnPage_(child)) { 1024 printf("Non-tabbed two-column page:\n");
929 pushBack_PtrArray(contents, removeChild_Widget(sheet, child)); 1025 printTree_Widget(child);
930 } 1026 pushBack_PtrArray(contents, removeChild_Widget(sheet, child));
1027 }
1028 else {
1029 removeChild_Widget(sheet, child);
1030 addChild_Widget(topPanel, child);
1031 iRelease(child);
931 } 1032 }
932 } 1033 }
1034 iAssert(size_PtrArray(contents) == size_PtrArray(panelButtons));
1035 topPanel->rect.pos = init_I2(0, navBarHeight);
1036 addChildFlags_Widget(sheet, iClob(topPanel),
1037 arrangeVertical_WidgetFlag |
1038 resizeWidthOfChildren_WidgetFlag | arrangeHeight_WidgetFlag |
1039 overflowScrollable_WidgetFlag |
1040 commandOnClick_WidgetFlag);
1041 setCommandHandler_Widget(topPanel, slidePanelHandler_);
933 iForEach(PtrArray, j, contents) { 1042 iForEach(PtrArray, j, contents) {
1043 iWidget *owner = topPanel;
1044 if (!isEmpty_PtrArray(panelButtons)) {
1045 iLabelWidget *button = at_PtrArray(panelButtons, index_PtrArrayIterator(&j));
1046 owner = new_Widget();
1047 setId_Widget(owner, "panel");
1048 setUserData_Object(button, owner);
1049 setBackgroundColor_Widget(owner, uiBackground_ColorId);
1050 addChild_Widget(owner, iClob(makePadding_Widget(navBarHeight - topSafe)));
1051 iLabelWidget *title = addChildFlags_Widget(owner,
1052 iClob(new_LabelWidget(cstr_String(text_LabelWidget(button)), NULL)), alignLeft_WidgetFlag | frameless_WidgetFlag);
1053 setFont_LabelWidget(title, uiLabelLargeBold_FontId);
1054 setTextColor_LabelWidget(title, uiHeading_ColorId);
1055 addChildFlags_Widget(sheet,
1056 iClob(owner),
1057 focusRoot_WidgetFlag |
1058 //mouseModal_WidgetFlag |
1059 hidden_WidgetFlag |
1060 disabled_WidgetFlag |
1061 arrangeVertical_WidgetFlag |
1062 resizeWidthOfChildren_WidgetFlag |
1063 arrangeHeight_WidgetFlag |
1064 overflowScrollable_WidgetFlag |
1065 horizontalOffset_WidgetFlag |
1066 commandOnClick_WidgetFlag);
1067 }
934 iWidget *pageContent = j.ptr; 1068 iWidget *pageContent = j.ptr;
935 iWidget *headings = child_Widget(pageContent, 0); 1069 iWidget *headings = child_Widget(pageContent, 0);
936 iWidget *values = child_Widget(pageContent, 1); 1070 iWidget *values = child_Widget(pageContent, 1);
1071 iBool isFirst = iTrue;
937 while (!isEmpty_ObjectList(children_Widget(headings))) { 1072 while (!isEmpty_ObjectList(children_Widget(headings))) {
938 iWidget *heading = child_Widget(headings, 0); 1073 iWidget *heading = child_Widget(headings, 0);
939 iWidget *value = child_Widget(values, 0); 1074 iWidget *value = child_Widget(values, 0);
@@ -968,7 +1103,7 @@ void finalizeSheet_Widget(iWidget *sheet) {
968 setFont_LabelWidget((iLabelWidget *) heading, defaultBig_FontId); 1103 setFont_LabelWidget((iLabelWidget *) heading, defaultBig_FontId);
969 addChildFlags_Widget(div, iClob(new_Widget()), expand_WidgetFlag); 1104 addChildFlags_Widget(div, iClob(new_Widget()), expand_WidgetFlag);
970 addChild_Widget(div, iClob(value)); 1105 addChild_Widget(div, iClob(value));
971 addChildFlags_Widget(sheet, 1106 addChildFlags_Widget(owner,
972 iClob(div), 1107 iClob(div),
973 borderBottom_WidgetFlag | arrangeHeight_WidgetFlag | 1108 borderBottom_WidgetFlag | arrangeHeight_WidgetFlag |
974 resizeWidthOfChildren_WidgetFlag | 1109 resizeWidthOfChildren_WidgetFlag |
@@ -977,12 +1112,17 @@ void finalizeSheet_Widget(iWidget *sheet) {
977 else { 1112 else {
978 if (valueLabel && isEmpty_String(text_LabelWidget(valueLabel))) { 1113 if (valueLabel && isEmpty_String(text_LabelWidget(valueLabel))) {
979 /* Subheading padding goes above. */ 1114 /* Subheading padding goes above. */
980 addChild_Widget(sheet, iClob(value)); 1115// if (!isFirst) {
981 addChildFlags_Widget(sheet, iClob(heading), 0); 1116// addChild_Widget(owner, iClob(value));
1117// }
1118// else {
1119 iRelease(value);
1120// }
1121 addChildFlags_Widget(owner, iClob(heading), 0);
982 setFont_LabelWidget(headingLabel, uiLabelBold_FontId); 1122 setFont_LabelWidget(headingLabel, uiLabelBold_FontId);
983 } 1123 }
984 else { 1124 else {
985 addChildFlags_Widget(sheet, iClob(heading), borderBottom_WidgetFlag); 1125 addChildFlags_Widget(owner, iClob(heading), borderBottom_WidgetFlag);
986 if (headingLabel) { 1126 if (headingLabel) {
987 setTextColor_LabelWidget(headingLabel, uiSubheading_ColorId); 1127 setTextColor_LabelWidget(headingLabel, uiSubheading_ColorId);
988 setText_LabelWidget(headingLabel, 1128 setText_LabelWidget(headingLabel,
@@ -999,14 +1139,14 @@ void finalizeSheet_Widget(iWidget *sheet) {
999 setBackgroundColor_Widget(pad, uiBackgroundSidebar_ColorId); 1139 setBackgroundColor_Widget(pad, uiBackgroundSidebar_ColorId);
1000 setPadding_Widget(pad, 0, 1 * gap_UI, 0, 1 * gap_UI); 1140 setPadding_Widget(pad, 0, 1 * gap_UI, 0, 1 * gap_UI);
1001 addChild_Widget(pad, iClob(value)); 1141 addChild_Widget(pad, iClob(value));
1002 addChildFlags_Widget(sheet, iClob(pad), borderBottom_WidgetFlag | 1142 addChildFlags_Widget(owner, iClob(pad), borderBottom_WidgetFlag |
1003 arrangeVertical_WidgetFlag | 1143 arrangeVertical_WidgetFlag |
1004 resizeToParentWidth_WidgetFlag | 1144 resizeToParentWidth_WidgetFlag |
1005 resizeWidthOfChildren_WidgetFlag | 1145 resizeWidthOfChildren_WidgetFlag |
1006 arrangeHeight_WidgetFlag); 1146 arrangeHeight_WidgetFlag);
1007 } 1147 }
1008 else { 1148 else {
1009 addChild_Widget(sheet, iClob(value)); 1149 addChild_Widget(owner, iClob(value));
1010 } 1150 }
1011 /* Align radio buttons to the right. */ 1151 /* Align radio buttons to the right. */
1012 if (childCount_Widget(value) >= 2) { 1152 if (childCount_Widget(value) >= 2) {
@@ -1021,15 +1161,35 @@ void finalizeSheet_Widget(iWidget *sheet) {
1021 if (isInstance_Object(sub.object, &Class_LabelWidget)) { 1161 if (isInstance_Object(sub.object, &Class_LabelWidget)) {
1022 iLabelWidget *opt = sub.object; 1162 iLabelWidget *opt = sub.object;
1023 setFont_LabelWidget(opt, defaultMedium_FontId); 1163 setFont_LabelWidget(opt, defaultMedium_FontId);
1164 setFlags_Widget(as_Widget(opt), noBackground_WidgetFlag, iTrue);
1024 } 1165 }
1025 } 1166 }
1026 } 1167 }
1027 } 1168 }
1028 } 1169 }
1170 isFirst = iFalse;
1029 } 1171 }
1030 destroy_Widget(pageContent); 1172 destroy_Widget(pageContent);
1173 addChildFlags_Widget(owner, iClob(new_Widget()), expand_WidgetFlag);
1031 } 1174 }
1032 destroyPending_Widget(); 1175 destroyPending_Widget();
1176 /* Navbar. */ {
1177 iWidget *navi = new_Widget();
1178 setSize_Widget(navi, init_I2(-1, navBarHeight));
1179 setBackgroundColor_Widget(navi, uiBackground_ColorId);
1180 addChild_Widget(navi, iClob(makePadding_Widget(topSafe)));
1181 iLabelWidget *back = addChildFlags_Widget(navi,
1182 iClob(new_LabelWidget(leftAngle_Icon " Back", "panel.close")),
1183 noBackground_WidgetFlag | frameless_WidgetFlag |
1184 alignLeft_WidgetFlag | extraPadding_WidgetFlag);
1185 setId_Widget(as_Widget(back), "panel.back");
1186 checkIcon_LabelWidget(back);
1187 setFont_LabelWidget(back, defaultBig_FontId);
1188 addChildFlags_Widget(sheet, iClob(navi),
1189 arrangeHeight_WidgetFlag | resizeWidthOfChildren_WidgetFlag |
1190 resizeToParentWidth_WidgetFlag | arrangeVertical_WidgetFlag |
1191 borderBottom_WidgetFlag);
1192 }
1033 arrange_Widget(sheet->parent); 1193 arrange_Widget(sheet->parent);
1034 printTree_Widget(sheet); 1194 printTree_Widget(sheet);
1035 } 1195 }
@@ -1125,6 +1285,7 @@ iBool valueInputHandler_(iWidget *dlg, const char *cmd) {
1125 1285
1126iWidget *makeDialogButtons_Widget(const iMenuItem *actions, size_t numActions) { 1286iWidget *makeDialogButtons_Widget(const iMenuItem *actions, size_t numActions) {
1127 iWidget *div = new_Widget(); 1287 iWidget *div = new_Widget();
1288 setId_Widget(div, "dialogbuttons");
1128 setFlags_Widget(div, 1289 setFlags_Widget(div,
1129 arrangeHorizontal_WidgetFlag | arrangeHeight_WidgetFlag | 1290 arrangeHorizontal_WidgetFlag | arrangeHeight_WidgetFlag |
1130 resizeToParentWidth_WidgetFlag | 1291 resizeToParentWidth_WidgetFlag |
diff --git a/src/ui/widget.c b/src/ui/widget.c
index d7543615..ec229b86 100644
--- a/src/ui/widget.c
+++ b/src/ui/widget.c
@@ -153,11 +153,13 @@ void setFlags_Widget(iWidget *d, int64_t flags, iBool set) {
153 } 153 }
154 iChangeFlags(d->flags, flags, set); 154 iChangeFlags(d->flags, flags, set);
155 if (flags & keepOnTop_WidgetFlag) { 155 if (flags & keepOnTop_WidgetFlag) {
156 iPtrArray *onTop = onTop_RootData_();
156 if (set) { 157 if (set) {
157 pushBack_PtrArray(onTop_RootData_(), d); 158 iAssert(indexOf_PtrArray(onTop, d) == iInvalidPos);
159 pushBack_PtrArray(onTop, d);
158 } 160 }
159 else { 161 else {
160 removeOne_PtrArray(onTop_RootData_(), d); 162 removeOne_PtrArray(onTop, d);
161 } 163 }
162 } 164 }
163 } 165 }
@@ -169,10 +171,17 @@ void setPos_Widget(iWidget *d, iInt2 pos) {
169} 171}
170 172
171void setSize_Widget(iWidget *d, iInt2 size) { 173void setSize_Widget(iWidget *d, iInt2 size) {
172 if (size.x < 0) size.x = d->rect.size.x; 174 int flags = fixedSize_WidgetFlag;
173 if (size.y < 0) size.y = d->rect.size.y; 175 if (size.x < 0) {
176 size.x = d->rect.size.x;
177 flags &= ~fixedWidth_WidgetFlag;
178 }
179 if (size.y < 0) {
180 size.y = d->rect.size.y;
181 flags &= ~fixedHeight_WidgetFlag;
182 }
174 d->rect.size = size; 183 d->rect.size = size;
175 setFlags_Widget(d, fixedSize_WidgetFlag, iTrue); 184 setFlags_Widget(d, flags, iTrue);
176} 185}
177 186
178void setPadding_Widget(iWidget *d, int left, int top, int right, int bottom) { 187void setPadding_Widget(iWidget *d, int left, int top, int right, int bottom) {
@@ -498,16 +507,24 @@ void arrange_Widget(iWidget *d) {
498 } 507 }
499} 508}
500 509
501iRect bounds_Widget(const iWidget *d) { 510static void applyVisualOffset_Widget_(const iWidget *d, iInt2 *pos) {
502 iRect bounds = d->rect;
503 if (d->flags & visualOffset_WidgetFlag) { 511 if (d->flags & visualOffset_WidgetFlag) {
504 bounds.pos.y += iRound(value_Anim(&d->visualOffset)); 512 const int off = iRound(value_Anim(&d->visualOffset));
513 if (d->flags & horizontalOffset_WidgetFlag) {
514 pos->x += off;
515 }
516 else {
517 pos->y += off;
518 }
505 } 519 }
520}
521
522iRect bounds_Widget(const iWidget *d) {
523 iRect bounds = d->rect;
524 applyVisualOffset_Widget_(d, &bounds.pos);
506 for (const iWidget *w = d->parent; w; w = w->parent) { 525 for (const iWidget *w = d->parent; w; w = w->parent) {
507 iInt2 pos = w->rect.pos; 526 iInt2 pos = w->rect.pos;
508 if (w->flags & visualOffset_WidgetFlag) { 527 applyVisualOffset_Widget_(w, &pos);
509 pos.y += iRound(value_Anim(&w->visualOffset));
510 }
511 addv_I2(&bounds.pos, pos); 528 addv_I2(&bounds.pos, pos);
512 } 529 }
513#if defined (iPlatformMobile) 530#if defined (iPlatformMobile)
@@ -595,6 +612,14 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {
595 if (dispatchEvent_Widget(child, ev)) { 612 if (dispatchEvent_Widget(child, ev)) {
596#if 0 613#if 0
597 if (ev->type == SDL_MOUSEBUTTONDOWN) { 614 if (ev->type == SDL_MOUSEBUTTONDOWN) {
615 printf("[%p] %s:'%s' ate the button %d\n",
616 child, class_Widget(child)->name,
617 cstr_String(id_Widget(child)), ev->button.button);
618 fflush(stdout);
619 }
620#endif
621#if 0
622 if (ev->type == SDL_MOUSEBUTTONDOWN) {
598 printf("widget %p ('%s' class:%s) ate the mouse down\n", 623 printf("widget %p ('%s' class:%s) ate the mouse down\n",
599 child, cstr_String(id_Widget(child)), 624 child, cstr_String(id_Widget(child)),
600 class_Widget(child)->name); 625 class_Widget(child)->name);
@@ -637,20 +662,25 @@ iBool processEvent_Widget(iWidget *d, const SDL_Event *ev) {
637 postCommand_Widget(d, "mouse.moved coord:%d %d", ev->motion.x, ev->motion.y); 662 postCommand_Widget(d, "mouse.moved coord:%d %d", ev->motion.x, ev->motion.y);
638 return iTrue; 663 return iTrue;
639 } 664 }
640 else if (d->flags & overflowScrollable_WidgetFlag && ev->type == SDL_MOUSEWHEEL) { 665 else if (d->flags & overflowScrollable_WidgetFlag && ev->type == SDL_MOUSEWHEEL &&
666 ~d->flags & visualOffset_WidgetFlag) {
641 iRect bounds = bounds_Widget(d); 667 iRect bounds = bounds_Widget(d);
642 const iInt2 winSize = rootSize_Window(get_Window()); 668 const iInt2 rootSize = rootSize_Window(get_Window());
643 if (height_Rect(bounds) > winSize.y) { 669 const iRect winRect = safeRootRect_Window(get_Window());
670 const int yTop = top_Rect(winRect);
671 const int yBottom = bottom_Rect(winRect);
672 const int safeBottom = rootSize.y - yBottom;
673 if (height_Rect(bounds) > height_Rect(winRect)) {
644 int step = ev->wheel.y; 674 int step = ev->wheel.y;
645#if !defined (iPlatformApple) 675 if (!isPerPixel_MouseWheelEvent(&ev->wheel)) {
646 step *= lineHeight_Text(uiLabel_FontId); 676 step *= lineHeight_Text(uiLabel_FontId);
647#endif 677 }
648 bounds.pos.y += step; 678 bounds.pos.y += step;
649 if (step > 0) { 679 if (step > 0) {
650 bounds.pos.y = iMin(bounds.pos.y, 0); 680 bounds.pos.y = iMin(bounds.pos.y, yTop);
651 } 681 }
652 else { 682 else {
653 bounds.pos.y = iMax(bounds.pos.y, winSize.y - height_Rect(bounds)); 683 bounds.pos.y = iMax(bounds.pos.y, rootSize.y + safeBottom - height_Rect(bounds));
654 } 684 }
655 d->rect.pos = localCoord_Widget(d->parent, bounds.pos); 685 d->rect.pos = localCoord_Widget(d->parent, bounds.pos);
656 refresh_Widget(d); 686 refresh_Widget(d);
@@ -676,6 +706,15 @@ iBool processEvent_Widget(iWidget *d, const SDL_Event *ev) {
676 ev->button.y); 706 ev->button.y);
677 } 707 }
678 if (d->flags & mouseModal_WidgetFlag && isMouseEvent_(ev)) { 708 if (d->flags & mouseModal_WidgetFlag && isMouseEvent_(ev)) {
709 if ((ev->type == SDL_MOUSEBUTTONDOWN || ev->type == SDL_MOUSEBUTTONUP) &&
710 d->flags & commandOnClick_WidgetFlag) {
711 postCommand_Widget(d,
712 "mouse.clicked arg:%d button:%d coord:%d %d",
713 ev->type == SDL_MOUSEBUTTONDOWN ? 1 : 0,
714 ev->button.button,
715 ev->button.x,
716 ev->button.y);
717 }
679 setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_ARROW); 718 setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_ARROW);
680 return iTrue; 719 return iTrue;
681 } 720 }
@@ -801,14 +840,15 @@ void drawChildren_Widget(const iWidget *d) {
801 /* Root draws the on-top widgets on top of everything else. */ 840 /* Root draws the on-top widgets on top of everything else. */
802 if (!d->parent) { 841 if (!d->parent) {
803 iConstForEach(PtrArray, i, onTop_RootData_()) { 842 iConstForEach(PtrArray, i, onTop_RootData_()) {
804 draw_Widget(*i.value); 843 const iWidget *top = *i.value;
844 draw_Widget(top);
805 } 845 }
806 } 846 }
807} 847}
808 848
809void draw_Widget(const iWidget *d) { 849void draw_Widget(const iWidget *d) {
810 drawBackground_Widget(d); 850 drawBackground_Widget(d);
811 drawChildren_Widget(d); 851 drawChildren_Widget(d);
812} 852}
813 853
814iAny *addChild_Widget(iWidget *d, iAnyObject *child) { 854iAny *addChild_Widget(iWidget *d, iAnyObject *child) {
@@ -903,7 +943,7 @@ iAny *hitChild_Widget(const iWidget *d, iInt2 coord) {
903 } 943 }
904 /* Check for on-top widgets first. */ 944 /* Check for on-top widgets first. */
905 if (!d->parent) { 945 if (!d->parent) {
906 iForEach(PtrArray, i, onTop_RootData_()) { 946 iReverseForEach(PtrArray, i, onTop_RootData_()) {
907 iWidget *child = i.ptr; 947 iWidget *child = i.ptr;
908// printf("ontop: %s (%s) hidden:%d hittable:%d\n", cstr_String(id_Widget(child)), 948// printf("ontop: %s (%s) hidden:%d hittable:%d\n", cstr_String(id_Widget(child)),
909// class_Widget(child)->name, 949// class_Widget(child)->name,
@@ -920,8 +960,8 @@ iAny *hitChild_Widget(const iWidget *d, iInt2 coord) {
920 if (found) return found; 960 if (found) return found;
921 } 961 }
922 } 962 }
923 if ((d->flags & overflowScrollable_WidgetFlag || class_Widget(d) != &Class_Widget) && 963 if ((d->flags & overflowScrollable_WidgetFlag || class_Widget(d) != &Class_Widget ||
924 ~d->flags & unhittable_WidgetFlag && 964 d->flags & mouseModal_WidgetFlag) && ~d->flags & unhittable_WidgetFlag &&
925 contains_Widget(d, coord)) { 965 contains_Widget(d, coord)) {
926 return iConstCast(iWidget *, d); 966 return iConstCast(iWidget *, d);
927 } 967 }
@@ -1168,6 +1208,10 @@ iBool hasVisibleChildOnTop_Widget(const iWidget *parent) {
1168} 1208}
1169 1209
1170void printTree_Widget(const iWidget *d) { 1210void printTree_Widget(const iWidget *d) {
1211 if (!d) {
1212 printf("[NULL]\n");
1213 return;
1214 }
1171 printTree_Widget_(d, 0); 1215 printTree_Widget_(d, 0);
1172} 1216}
1173 1217
diff --git a/src/ui/widget.h b/src/ui/widget.h
index 1229ba7f..4b1f42d3 100644
--- a/src/ui/widget.h
+++ b/src/ui/widget.h
@@ -103,6 +103,8 @@ enum iWidgetFlag {
103#define unpadded_WidgetFlag iBit64(48) /* ignore parent's padding */ 103#define unpadded_WidgetFlag iBit64(48) /* ignore parent's padding */
104#define extraPadding_WidgetFlag iBit64(49) 104#define extraPadding_WidgetFlag iBit64(49)
105#define borderBottom_WidgetFlag iBit64(50) 105#define borderBottom_WidgetFlag iBit64(50)
106#define horizontalOffset_WidgetFlag iBit64(51) /* default is vertical offset */
107#define chevron_WidgetFlag iBit64(52)
106 108
107enum iWidgetAddPos { 109enum iWidgetAddPos {
108 back_WidgetAddPos, 110 back_WidgetAddPos,
diff --git a/src/ui/window.c b/src/ui/window.c
index 3c362986..af9b20ba 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -1808,6 +1808,16 @@ iInt2 rootSize_Window(const iWindow *d) {
1808 return d ? d->root->rect.size : zero_I2(); 1808 return d ? d->root->rect.size : zero_I2();
1809} 1809}
1810 1810
1811iRect safeRootRect_Window(const iWindow *d) {
1812 iRect rect = { zero_I2(), d->root->rect.size };
1813#if defined (iPlatformAppleMobile)
1814 float left, top, right, bottom;
1815 safeAreaInsets_iOS(&left, &top, &right, &bottom);
1816 adjustEdges_Rect(&rect, top, right, bottom, left);
1817#endif
1818 return rect;
1819}
1820
1811iInt2 visibleRootSize_Window(const iWindow *d) { 1821iInt2 visibleRootSize_Window(const iWindow *d) {
1812 return addY_I2(rootSize_Window(d), -d->keyboardHeight); 1822 return addY_I2(rootSize_Window(d), -d->keyboardHeight);
1813} 1823}
diff --git a/src/ui/window.h b/src/ui/window.h
index 9a70fdec..dc865277 100644
--- a/src/ui/window.h
+++ b/src/ui/window.h
@@ -92,6 +92,7 @@ void setKeyboardHeight_Window(iWindow *, int height);
92 92
93uint32_t id_Window (const iWindow *); 93uint32_t id_Window (const iWindow *);
94iInt2 rootSize_Window (const iWindow *); 94iInt2 rootSize_Window (const iWindow *);
95iRect safeRootRect_Window (const iWindow *);
95iInt2 visibleRootSize_Window (const iWindow *); /* may be obstructed by software keyboard */ 96iInt2 visibleRootSize_Window (const iWindow *); /* may be obstructed by software keyboard */
96iInt2 maxTextureSize_Window (const iWindow *); 97iInt2 maxTextureSize_Window (const iWindow *);
97float uiScale_Window (const iWindow *); 98float uiScale_Window (const iWindow *);