From 2002543dbedcc1666d02d11fbde55f794d692bb7 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Tue, 9 Mar 2021 22:44:45 +0200 Subject: Mobile: Revising phone-style dialogs Sliding panels and left-edge swipes. --- src/app.c | 2 + src/defs.h | 2 + src/ui/labelwidget.c | 15 +++- src/ui/touch.c | 7 +- src/ui/util.c | 207 +++++++++++++++++++++++++++++++++++++++++++++------ src/ui/widget.c | 92 +++++++++++++++++------ src/ui/widget.h | 2 + src/ui/window.c | 10 +++ src/ui/window.h | 1 + 9 files changed, 287 insertions(+), 51 deletions(-) (limited to 'src') 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 } static void updateColorThemeButton_(iLabelWidget *button, int theme) { + if (!button) return; // const char *mode = strstr(cstr_String(id_Widget(as_Widget(button))), ".dark") // ? "dark" : "light"; updateDropdownSelection_(button, format_CStr(".set arg:%d", theme)); } static void updateFontButton_(iLabelWidget *button, int font) { + if (!button) return; updateDropdownSelection_(button, format_CStr(".set arg:%d", font)); } 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 { #define circleWhite_Icon "\u25cb" #define gear_Icon "\u2699" #define explosion_Icon "\U0001f4a5" +#define leftAngle_Icon "\U0001fba4" +#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. */ #include "labelwidget.h" #include "text.h" +#include "defs.h" #include "color.h" #include "paint.h" #include "app.h" @@ -147,7 +148,9 @@ static void getColors_LabelWidget_(const iLabelWidget *d, int *bg, int *fg, int const iBool isFrameless = (flags & frameless_WidgetFlag) != 0; const iBool isButton = d->click.button != 0; /* Default color state. */ - *bg = isButton && ~flags & noBackground_WidgetFlag ? uiBackground_ColorId : none_ColorId; + *bg = isButton && ~flags & noBackground_WidgetFlag ? (d->widget.bgColor != none_ColorId ? + d->widget.bgColor : uiBackground_ColorId) + : none_ColorId; *fg = uiText_ColorId; *frame1 = isButton ? uiEmboss1_ColorId : d->widget.frameColor; *frame2 = isButton ? uiEmboss2_ColorId : *frame1; @@ -248,6 +251,7 @@ static void draw_LabelWidget_(const iLabelWidget *d) { drawCentered_Text( d->font, (iRect){ + /* The icon position is fine-tuned; c.f. high baseline of Source Sans Pro. */ add_I2(add_I2(bounds.pos, padding_(flags)), init_I2((flags & extraPadding_WidgetFlag ? -2 : -1.20f) * gap_UI, -gap_UI / 8)), init_I2(iconPad, lineHeight_Text(d->font)) }, @@ -294,6 +298,14 @@ static void draw_LabelWidget_(const iLabelWidget *d) { fg, cstr_String(&d->label)); } + if (flags & chevron_WidgetFlag) { + const iRect chRect = rect; + const int chSize = lineHeight_Text(d->font); + drawCentered_Text(d->font, + (iRect){ addX_I2(topRight_Rect(chRect), -iconPad), + init_I2(chSize, height_Rect(chRect)) }, + iTrue, fg, rightAngle_Icon); + } unsetClip_Paint(&p); } @@ -436,6 +448,7 @@ iChar icon_LabelWidget(const iLabelWidget *d) { } const iString *text_LabelWidget(const iLabelWidget *d) { + if (!d) return collectNew_String(); return &d->label; } 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) { }); } -static void dispatchClick_Touch_(const iTouch *d, int button) { +static iBool dispatchClick_Touch_(const iTouch *d, int button) { const iFloat3 tapPos = d->pos[0]; SDL_MouseButtonEvent btn = { .type = SDL_MOUSEBUTTONDOWN, @@ -142,7 +142,7 @@ static void dispatchClick_Touch_(const iTouch *d, int button) { .x = x_F3(tapPos), .y = y_F3(tapPos) }; - dispatchEvent_Widget(get_Window()->root, (SDL_Event *) &btn); + iBool wasUsed = dispatchEvent_Widget(get_Window()->root, (SDL_Event *) &btn); /* Immediately released, too. */ btn.type = SDL_MOUSEBUTTONUP; btn.state = SDL_RELEASED; @@ -150,6 +150,7 @@ static void dispatchClick_Touch_(const iTouch *d, int button) { dispatchEvent_Widget(get_Window()->root, (SDL_Event *) &btn); //dispatchMotion_Touch_(zero_F3(), 0); setHover_Widget(NULL); /* FIXME: this doesn't seem to do anything? */ + return wasUsed; } static void clearWidgetMomentum_TouchState_(iTouchState *d, iWidget *widget) { @@ -305,7 +306,7 @@ iBool processEvent_Touch(const SDL_Event *ev) { iWidget *aff = hitChild_Widget(window->root, init_I2(iRound(x), iRound(y_F3(pos)))); /* TODO: We must retain a reference to the affinity widget, or otherwise it might be destroyed during the gesture. */ -// printf("aff:%p (%s)\n", aff, aff ? class_Widget(aff)->name : "-"); + printf("aff:%p (%s)\n", aff, aff ? class_Widget(aff)->name : "-"); if (flags_Widget(aff) & touchDrag_WidgetFlag) { dispatchEvent_Widget(window->root, (SDL_Event *) &(SDL_MouseButtonEvent){ .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. */ #include "bookmarks.h" #include "color.h" #include "command.h" +#include "defs.h" #include "documentwidget.h" #include "gmutil.h" #include "feeds.h" @@ -517,6 +518,7 @@ iWidget *makeMenu_Widget(iWidget *parent, const iMenuItem *items, size_t n) { void openMenu_Widget(iWidget *d, iInt2 coord) { const iInt2 rootSize = rootSize_Window(get_Window()); const iBool isPortraitPhone = (deviceType_App() == phone_AppDeviceType && isPortrait_App()); + const iBool isSlidePanel = (flags_Widget(d) & horizontalOffset_WidgetFlag) != 0; /* Menu closes when commands are emitted, so handle any pending ones beforehand. */ postCommand_App("cancel"); /* dismiss any other menus */ processEvents_App(postedEventsOnly_AppEventMode); @@ -526,6 +528,9 @@ void openMenu_Widget(iWidget *d, iInt2 coord) { if (isPortraitPhone) { setFlags_Widget(d, arrangeWidth_WidgetFlag | resizeChildrenToWidestChild_WidgetFlag, iFalse); setFlags_Widget(d, resizeWidthOfChildren_WidgetFlag, iTrue); + if (!isSlidePanel) { + setFlags_Widget(d, borderTop_WidgetFlag, iTrue); + } d->rect.size.x = rootSize_Window(get_Window()).x; } /* Update item fonts. */ { @@ -537,7 +542,9 @@ void openMenu_Widget(iWidget *d, iInt2 coord) { setFont_LabelWidget(label, isCaution ? uiLabelBold_FontId : uiLabel_FontId); } else if (isPortraitPhone) { - setFont_LabelWidget(label, isCaution ? defaultBigBold_FontId : defaultBig_FontId); + if (!isSlidePanel) { + setFont_LabelWidget(label, isCaution ? defaultBigBold_FontId : defaultBig_FontId); + } } else { setFont_LabelWidget(label, isCaution ? uiContentBold_FontId : uiContent_FontId); @@ -547,7 +554,12 @@ void openMenu_Widget(iWidget *d, iInt2 coord) { } arrange_Widget(d); if (isPortraitPhone) { - d->rect.pos = init_I2(0, rootSize.y); + if (isSlidePanel) { + d->rect.pos = zero_I2(); //neg_I2(bounds_Widget(parent_Widget(d)).pos); + } + else { + d->rect.pos = init_I2(0, rootSize.y); + } } else { d->rect.pos = coord; @@ -568,7 +580,7 @@ void openMenu_Widget(iWidget *d, iInt2 coord) { rightExcess += r; } #endif - if (bottomExcess > 0) { + if (bottomExcess > 0 && (!isPortraitPhone || !isSlidePanel)) { d->rect.pos.y -= bottomExcess; } if (topExcess > 0) { @@ -583,7 +595,7 @@ void openMenu_Widget(iWidget *d, iInt2 coord) { postRefresh_App(); postCommand_Widget(d, "menu.opened"); if (isPortraitPhone) { - setVisualOffset_Widget(d, height_Widget(d), 0, 0); + setVisualOffset_Widget(d, isSlidePanel ? width_Widget(d) : height_Widget(d), 0, 0); setVisualOffset_Widget(d, 0, 330, easeOut_AnimFlag | softer_AnimFlag); } } @@ -594,7 +606,11 @@ void closeMenu_Widget(iWidget *d) { postRefresh_App(); postCommand_Widget(d, "menu.closed"); if (isPortrait_App() && deviceType_App() == phone_AppDeviceType) { - setVisualOffset_Widget(d, height_Widget(d), 200, easeIn_AnimFlag | softer_AnimFlag); + setVisualOffset_Widget(d, + flags_Widget(d) & horizontalOffset_WidgetFlag ? + width_Widget(d) : height_Widget(d), + 200, + easeIn_AnimFlag | softer_AnimFlag); } } @@ -882,7 +898,43 @@ iWidget *makeSheet_Widget(const char *id) { return sheet; } +static iBool slidePanelHandler_(iWidget *d, const char *cmd) { + if (equal_Command(cmd, "panel.open")) { + iWidget *button = pointer_Command(cmd); + iWidget *panel = userData_Object(button); + openMenu_Widget(panel, zero_I2()); + updateTextCStr_LabelWidget(findWidget_App("panel.back"), "Settings"); + return iTrue; + } + if (equal_Command(cmd, "mouse.clicked") && arg_Command(cmd) && + argLabel_Command(cmd, "button") == SDL_BUTTON_X1) { + postCommand_App("panel.close"); + return iTrue; + } + if (equal_Command(cmd, "panel.close")) { + iBool wasClosed = iFalse; + iForEach(ObjectList, i, children_Widget(parent_Widget(d))) { + iWidget *child = i.object; + if (!cmp_String(id_Widget(child), "panel") && isVisible_Widget(child)) { + closeMenu_Widget(child); + setFocus_Widget(NULL); + updateTextCStr_LabelWidget(findWidget_App("panel.back"), "Back"); + wasClosed = iTrue; + } + } + if (!wasClosed) { + postCommand_App("prefs.dismiss"); + } + return iTrue; + } + return iFalse; +} + static iBool isTwoColumnPage_(iWidget *d) { + if (cmp_String(id_Widget(d), "dialogbuttons") == 0 || + cmp_String(id_Widget(d), "prefs.tabs") == 0) { + return iFalse; + } if (class_Widget(d) == &Class_Widget && childCount_Widget(d) == 2) { return class_Widget(child_Widget(d, 0)) == &Class_Widget && class_Widget(child_Widget(d, 1)) == &Class_Widget; @@ -894,46 +946,129 @@ void finalizeSheet_Widget(iWidget *sheet) { if (deviceType_App() == phone_AppDeviceType) { /* The sheet contents are completely rearranged on a phone. We'll set up a linear fullscreen arrangement of the widgets. Sheets are already scrollable so they - can be taller than the display. */ + can be taller than the display. In hindsight, it may have been easier to + create phone versions of each dialog, but at least this works with any future + changes to the UI (..."works"). */ + int topSafe = 0; + int navBarHeight = lineHeight_Text(defaultBig_FontId) + 4 * gap_UI; #if defined (iPlatformAppleMobile) /* Safe area insets. */ { /* TODO: Must be updated when orientation changes; use a widget flag? */ float l, t, r, b; safeAreaInsets_iOS(&l, &t, &r, &b); - setPadding_Widget(sheet, l, t, r, b); +// setPadding_Widget(sheet, l, t, r, b); + setPadding1_Widget(sheet, 0); + topSafe = t; + navBarHeight += t; } #endif setFlags_Widget(sheet, - parentCannotResize_WidgetFlag | arrangeWidth_WidgetFlag | - centerHorizontal_WidgetFlag, + keepOnTop_WidgetFlag | + parentCannotResize_WidgetFlag | + arrangeSize_WidgetFlag | + centerHorizontal_WidgetFlag | + arrangeVertical_WidgetFlag | + arrangeHorizontal_WidgetFlag | + overflowScrollable_WidgetFlag, iFalse); - setFlags_Widget(sheet, frameless_WidgetFlag | resizeWidthOfChildren_WidgetFlag, iTrue); + setFlags_Widget(sheet, + commandOnClick_WidgetFlag | + frameless_WidgetFlag | + resizeWidthOfChildren_WidgetFlag, + iTrue); + setBackgroundColor_Widget(sheet, green_ColorId); iPtrArray *contents = collect_PtrArray(new_PtrArray()); /* two-column pages */ + iPtrArray *panelButtons = collect_PtrArray(new_PtrArray()); iWidget *tabs = findChild_Widget(sheet, "prefs.tabs"); + iWidget *topPanel = new_Widget(); + setId_Widget(topPanel, "panel.top"); + //setBackgroundColor_Widget(topPanel, red_ColorId); if (tabs) { - /* Pull out the pages and make them sequential. */ + iRelease(removeChild_Widget(sheet, child_Widget(sheet, 0))); /* heading */ + iRelease(removeChild_Widget(sheet, findChild_Widget(sheet, "dialogbuttons"))); + /* Pull out the pages and make them panels. */ iWidget *pages = findChild_Widget(tabs, "tabs.pages"); size_t pageCount = tabCount_Widget(tabs); for (size_t i = 0; i < pageCount; i++) { + iString *text = copy_String(text_LabelWidget(tabPageButton_Widget(tabs, tabPage_Widget(tabs, 0)))); iWidget *page = removeTabPage_Widget(tabs, 0); iWidget *pageContent = child_Widget(page, 1); /* surrounded by padding widgets */ pushBack_PtrArray(contents, ref_Object(pageContent)); + iLabelWidget *panelButton; + pushBack_PtrArray(panelButtons, + addChildFlags_Widget(topPanel, + iClob(panelButton = new_LabelWidget + (i == 1 ? "User Interface" : cstr_String(text), + "panel.open")), + alignLeft_WidgetFlag | frameless_WidgetFlag | + borderBottom_WidgetFlag | extraPadding_WidgetFlag | + chevron_WidgetFlag)); + const iChar icons[] = { + 0x2699, /* gear */ + 0x1f4f1, /* mobile phone */ + 0x1f3a8, /* palette */ + 0x1f523, + 0x1f5a7, /* computer network */ + }; + setIcon_LabelWidget(panelButton, icons[i]); + setFont_LabelWidget(panelButton, defaultBig_FontId); + setBackgroundColor_Widget(as_Widget(panelButton), uiBackgroundSidebar_ColorId); iRelease(page); + delete_String(text); } destroy_Widget(tabs); } - else { - iForEach(ObjectList, i, children_Widget(sheet)) { - iWidget *child = i.object; - if (isTwoColumnPage_(child)) { - pushBack_PtrArray(contents, removeChild_Widget(sheet, child)); - } + iForEach(ObjectList, i, children_Widget(sheet)) { + iWidget *child = i.object; + if (isTwoColumnPage_(child)) { + printf("Non-tabbed two-column page:\n"); + printTree_Widget(child); + pushBack_PtrArray(contents, removeChild_Widget(sheet, child)); + } + else { + removeChild_Widget(sheet, child); + addChild_Widget(topPanel, child); + iRelease(child); } } + iAssert(size_PtrArray(contents) == size_PtrArray(panelButtons)); + topPanel->rect.pos = init_I2(0, navBarHeight); + addChildFlags_Widget(sheet, iClob(topPanel), + arrangeVertical_WidgetFlag | + resizeWidthOfChildren_WidgetFlag | arrangeHeight_WidgetFlag | + overflowScrollable_WidgetFlag | + commandOnClick_WidgetFlag); + setCommandHandler_Widget(topPanel, slidePanelHandler_); iForEach(PtrArray, j, contents) { + iWidget *owner = topPanel; + if (!isEmpty_PtrArray(panelButtons)) { + iLabelWidget *button = at_PtrArray(panelButtons, index_PtrArrayIterator(&j)); + owner = new_Widget(); + setId_Widget(owner, "panel"); + setUserData_Object(button, owner); + setBackgroundColor_Widget(owner, uiBackground_ColorId); + addChild_Widget(owner, iClob(makePadding_Widget(navBarHeight - topSafe))); + iLabelWidget *title = addChildFlags_Widget(owner, + iClob(new_LabelWidget(cstr_String(text_LabelWidget(button)), NULL)), alignLeft_WidgetFlag | frameless_WidgetFlag); + setFont_LabelWidget(title, uiLabelLargeBold_FontId); + setTextColor_LabelWidget(title, uiHeading_ColorId); + addChildFlags_Widget(sheet, + iClob(owner), + focusRoot_WidgetFlag | + //mouseModal_WidgetFlag | + hidden_WidgetFlag | + disabled_WidgetFlag | + arrangeVertical_WidgetFlag | + resizeWidthOfChildren_WidgetFlag | + arrangeHeight_WidgetFlag | + overflowScrollable_WidgetFlag | + horizontalOffset_WidgetFlag | + commandOnClick_WidgetFlag); + } iWidget *pageContent = j.ptr; iWidget *headings = child_Widget(pageContent, 0); iWidget *values = child_Widget(pageContent, 1); + iBool isFirst = iTrue; while (!isEmpty_ObjectList(children_Widget(headings))) { iWidget *heading = child_Widget(headings, 0); iWidget *value = child_Widget(values, 0); @@ -968,7 +1103,7 @@ void finalizeSheet_Widget(iWidget *sheet) { setFont_LabelWidget((iLabelWidget *) heading, defaultBig_FontId); addChildFlags_Widget(div, iClob(new_Widget()), expand_WidgetFlag); addChild_Widget(div, iClob(value)); - addChildFlags_Widget(sheet, + addChildFlags_Widget(owner, iClob(div), borderBottom_WidgetFlag | arrangeHeight_WidgetFlag | resizeWidthOfChildren_WidgetFlag | @@ -977,12 +1112,17 @@ void finalizeSheet_Widget(iWidget *sheet) { else { if (valueLabel && isEmpty_String(text_LabelWidget(valueLabel))) { /* Subheading padding goes above. */ - addChild_Widget(sheet, iClob(value)); - addChildFlags_Widget(sheet, iClob(heading), 0); +// if (!isFirst) { +// addChild_Widget(owner, iClob(value)); +// } +// else { + iRelease(value); +// } + addChildFlags_Widget(owner, iClob(heading), 0); setFont_LabelWidget(headingLabel, uiLabelBold_FontId); } else { - addChildFlags_Widget(sheet, iClob(heading), borderBottom_WidgetFlag); + addChildFlags_Widget(owner, iClob(heading), borderBottom_WidgetFlag); if (headingLabel) { setTextColor_LabelWidget(headingLabel, uiSubheading_ColorId); setText_LabelWidget(headingLabel, @@ -999,14 +1139,14 @@ void finalizeSheet_Widget(iWidget *sheet) { setBackgroundColor_Widget(pad, uiBackgroundSidebar_ColorId); setPadding_Widget(pad, 0, 1 * gap_UI, 0, 1 * gap_UI); addChild_Widget(pad, iClob(value)); - addChildFlags_Widget(sheet, iClob(pad), borderBottom_WidgetFlag | + addChildFlags_Widget(owner, iClob(pad), borderBottom_WidgetFlag | arrangeVertical_WidgetFlag | resizeToParentWidth_WidgetFlag | resizeWidthOfChildren_WidgetFlag | arrangeHeight_WidgetFlag); } else { - addChild_Widget(sheet, iClob(value)); + addChild_Widget(owner, iClob(value)); } /* Align radio buttons to the right. */ if (childCount_Widget(value) >= 2) { @@ -1021,15 +1161,35 @@ void finalizeSheet_Widget(iWidget *sheet) { if (isInstance_Object(sub.object, &Class_LabelWidget)) { iLabelWidget *opt = sub.object; setFont_LabelWidget(opt, defaultMedium_FontId); + setFlags_Widget(as_Widget(opt), noBackground_WidgetFlag, iTrue); } } } } } + isFirst = iFalse; } destroy_Widget(pageContent); + addChildFlags_Widget(owner, iClob(new_Widget()), expand_WidgetFlag); } destroyPending_Widget(); + /* Navbar. */ { + iWidget *navi = new_Widget(); + setSize_Widget(navi, init_I2(-1, navBarHeight)); + setBackgroundColor_Widget(navi, uiBackground_ColorId); + addChild_Widget(navi, iClob(makePadding_Widget(topSafe))); + iLabelWidget *back = addChildFlags_Widget(navi, + iClob(new_LabelWidget(leftAngle_Icon " Back", "panel.close")), + noBackground_WidgetFlag | frameless_WidgetFlag | + alignLeft_WidgetFlag | extraPadding_WidgetFlag); + setId_Widget(as_Widget(back), "panel.back"); + checkIcon_LabelWidget(back); + setFont_LabelWidget(back, defaultBig_FontId); + addChildFlags_Widget(sheet, iClob(navi), + arrangeHeight_WidgetFlag | resizeWidthOfChildren_WidgetFlag | + resizeToParentWidth_WidgetFlag | arrangeVertical_WidgetFlag | + borderBottom_WidgetFlag); + } arrange_Widget(sheet->parent); printTree_Widget(sheet); } @@ -1125,6 +1285,7 @@ iBool valueInputHandler_(iWidget *dlg, const char *cmd) { iWidget *makeDialogButtons_Widget(const iMenuItem *actions, size_t numActions) { iWidget *div = new_Widget(); + setId_Widget(div, "dialogbuttons"); setFlags_Widget(div, arrangeHorizontal_WidgetFlag | arrangeHeight_WidgetFlag | 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) { } iChangeFlags(d->flags, flags, set); if (flags & keepOnTop_WidgetFlag) { + iPtrArray *onTop = onTop_RootData_(); if (set) { - pushBack_PtrArray(onTop_RootData_(), d); + iAssert(indexOf_PtrArray(onTop, d) == iInvalidPos); + pushBack_PtrArray(onTop, d); } else { - removeOne_PtrArray(onTop_RootData_(), d); + removeOne_PtrArray(onTop, d); } } } @@ -169,10 +171,17 @@ void setPos_Widget(iWidget *d, iInt2 pos) { } void setSize_Widget(iWidget *d, iInt2 size) { - if (size.x < 0) size.x = d->rect.size.x; - if (size.y < 0) size.y = d->rect.size.y; + int flags = fixedSize_WidgetFlag; + if (size.x < 0) { + size.x = d->rect.size.x; + flags &= ~fixedWidth_WidgetFlag; + } + if (size.y < 0) { + size.y = d->rect.size.y; + flags &= ~fixedHeight_WidgetFlag; + } d->rect.size = size; - setFlags_Widget(d, fixedSize_WidgetFlag, iTrue); + setFlags_Widget(d, flags, iTrue); } void setPadding_Widget(iWidget *d, int left, int top, int right, int bottom) { @@ -498,16 +507,24 @@ void arrange_Widget(iWidget *d) { } } -iRect bounds_Widget(const iWidget *d) { - iRect bounds = d->rect; +static void applyVisualOffset_Widget_(const iWidget *d, iInt2 *pos) { if (d->flags & visualOffset_WidgetFlag) { - bounds.pos.y += iRound(value_Anim(&d->visualOffset)); + const int off = iRound(value_Anim(&d->visualOffset)); + if (d->flags & horizontalOffset_WidgetFlag) { + pos->x += off; + } + else { + pos->y += off; + } } +} + +iRect bounds_Widget(const iWidget *d) { + iRect bounds = d->rect; + applyVisualOffset_Widget_(d, &bounds.pos); for (const iWidget *w = d->parent; w; w = w->parent) { iInt2 pos = w->rect.pos; - if (w->flags & visualOffset_WidgetFlag) { - pos.y += iRound(value_Anim(&w->visualOffset)); - } + applyVisualOffset_Widget_(w, &pos); addv_I2(&bounds.pos, pos); } #if defined (iPlatformMobile) @@ -593,6 +610,14 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) { continue; } if (dispatchEvent_Widget(child, ev)) { +#if 0 + if (ev->type == SDL_MOUSEBUTTONDOWN) { + printf("[%p] %s:'%s' ate the button %d\n", + child, class_Widget(child)->name, + cstr_String(id_Widget(child)), ev->button.button); + fflush(stdout); + } +#endif #if 0 if (ev->type == SDL_MOUSEBUTTONDOWN) { printf("widget %p ('%s' class:%s) ate the mouse down\n", @@ -637,20 +662,25 @@ iBool processEvent_Widget(iWidget *d, const SDL_Event *ev) { postCommand_Widget(d, "mouse.moved coord:%d %d", ev->motion.x, ev->motion.y); return iTrue; } - else if (d->flags & overflowScrollable_WidgetFlag && ev->type == SDL_MOUSEWHEEL) { + else if (d->flags & overflowScrollable_WidgetFlag && ev->type == SDL_MOUSEWHEEL && + ~d->flags & visualOffset_WidgetFlag) { iRect bounds = bounds_Widget(d); - const iInt2 winSize = rootSize_Window(get_Window()); - if (height_Rect(bounds) > winSize.y) { + const iInt2 rootSize = rootSize_Window(get_Window()); + const iRect winRect = safeRootRect_Window(get_Window()); + const int yTop = top_Rect(winRect); + const int yBottom = bottom_Rect(winRect); + const int safeBottom = rootSize.y - yBottom; + if (height_Rect(bounds) > height_Rect(winRect)) { int step = ev->wheel.y; -#if !defined (iPlatformApple) - step *= lineHeight_Text(uiLabel_FontId); -#endif + if (!isPerPixel_MouseWheelEvent(&ev->wheel)) { + step *= lineHeight_Text(uiLabel_FontId); + } bounds.pos.y += step; if (step > 0) { - bounds.pos.y = iMin(bounds.pos.y, 0); + bounds.pos.y = iMin(bounds.pos.y, yTop); } else { - bounds.pos.y = iMax(bounds.pos.y, winSize.y - height_Rect(bounds)); + bounds.pos.y = iMax(bounds.pos.y, rootSize.y + safeBottom - height_Rect(bounds)); } d->rect.pos = localCoord_Widget(d->parent, bounds.pos); refresh_Widget(d); @@ -676,6 +706,15 @@ iBool processEvent_Widget(iWidget *d, const SDL_Event *ev) { ev->button.y); } if (d->flags & mouseModal_WidgetFlag && isMouseEvent_(ev)) { + if ((ev->type == SDL_MOUSEBUTTONDOWN || ev->type == SDL_MOUSEBUTTONUP) && + d->flags & commandOnClick_WidgetFlag) { + postCommand_Widget(d, + "mouse.clicked arg:%d button:%d coord:%d %d", + ev->type == SDL_MOUSEBUTTONDOWN ? 1 : 0, + ev->button.button, + ev->button.x, + ev->button.y); + } setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_ARROW); return iTrue; } @@ -801,14 +840,15 @@ void drawChildren_Widget(const iWidget *d) { /* Root draws the on-top widgets on top of everything else. */ if (!d->parent) { iConstForEach(PtrArray, i, onTop_RootData_()) { - draw_Widget(*i.value); + const iWidget *top = *i.value; + draw_Widget(top); } } } void draw_Widget(const iWidget *d) { drawBackground_Widget(d); - drawChildren_Widget(d); + drawChildren_Widget(d); } iAny *addChild_Widget(iWidget *d, iAnyObject *child) { @@ -903,7 +943,7 @@ iAny *hitChild_Widget(const iWidget *d, iInt2 coord) { } /* Check for on-top widgets first. */ if (!d->parent) { - iForEach(PtrArray, i, onTop_RootData_()) { + iReverseForEach(PtrArray, i, onTop_RootData_()) { iWidget *child = i.ptr; // printf("ontop: %s (%s) hidden:%d hittable:%d\n", cstr_String(id_Widget(child)), // class_Widget(child)->name, @@ -920,8 +960,8 @@ iAny *hitChild_Widget(const iWidget *d, iInt2 coord) { if (found) return found; } } - if ((d->flags & overflowScrollable_WidgetFlag || class_Widget(d) != &Class_Widget) && - ~d->flags & unhittable_WidgetFlag && + if ((d->flags & overflowScrollable_WidgetFlag || class_Widget(d) != &Class_Widget || + d->flags & mouseModal_WidgetFlag) && ~d->flags & unhittable_WidgetFlag && contains_Widget(d, coord)) { return iConstCast(iWidget *, d); } @@ -1168,6 +1208,10 @@ iBool hasVisibleChildOnTop_Widget(const iWidget *parent) { } void printTree_Widget(const iWidget *d) { + if (!d) { + printf("[NULL]\n"); + return; + } printTree_Widget_(d, 0); } 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 { #define unpadded_WidgetFlag iBit64(48) /* ignore parent's padding */ #define extraPadding_WidgetFlag iBit64(49) #define borderBottom_WidgetFlag iBit64(50) +#define horizontalOffset_WidgetFlag iBit64(51) /* default is vertical offset */ +#define chevron_WidgetFlag iBit64(52) enum iWidgetAddPos { 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) { return d ? d->root->rect.size : zero_I2(); } +iRect safeRootRect_Window(const iWindow *d) { + iRect rect = { zero_I2(), d->root->rect.size }; +#if defined (iPlatformAppleMobile) + float left, top, right, bottom; + safeAreaInsets_iOS(&left, &top, &right, &bottom); + adjustEdges_Rect(&rect, top, right, bottom, left); +#endif + return rect; +} + iInt2 visibleRootSize_Window(const iWindow *d) { return addY_I2(rootSize_Window(d), -d->keyboardHeight); } 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); uint32_t id_Window (const iWindow *); iInt2 rootSize_Window (const iWindow *); +iRect safeRootRect_Window (const iWindow *); iInt2 visibleRootSize_Window (const iWindow *); /* may be obstructed by software keyboard */ iInt2 maxTextureSize_Window (const iWindow *); float uiScale_Window (const iWindow *); -- cgit v1.2.3