From 97f9a1c9bf49ca67fe1f99c57aa70e0bdf64a466 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Sun, 16 May 2021 13:21:36 +0300 Subject: Cleanup: Moved mobile UI code to its own file The mobile UI related code has grown large enough to warrant a separate file. Also, work-in-progress redo of the Preferences layout so it can be used with landscape as well. --- CMakeLists.txt | 2 + src/app.c | 4 +- src/ui/documentwidget.c | 4 +- src/ui/mobile.c | 704 +++++++++++++++++++++++++++++++++++++++++++++++ src/ui/mobile.h | 32 +++ src/ui/root.c | 106 +++---- src/ui/util.c | 719 +----------------------------------------------- src/ui/util.h | 4 +- 8 files changed, 813 insertions(+), 762 deletions(-) create mode 100644 src/ui/mobile.c create mode 100644 src/ui/mobile.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 98fd29fa..a1037b0c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -180,6 +180,8 @@ set (SOURCES src/ui/root.h src/ui/mediaui.c src/ui/mediaui.h + src/ui/mobile.c + src/ui/mobile.h src/ui/scrollwidget.c src/ui/scrollwidget.h src/ui/sidebarwidget.c diff --git a/src/app.c b/src/app.c index 64348930..27ce93ce 100644 --- a/src/app.c +++ b/src/app.c @@ -1508,7 +1508,7 @@ static void updateFontButton_(iLabelWidget *button, int font) { static iBool handlePrefsCommands_(iWidget *d, const char *cmd) { if (equal_Command(cmd, "prefs.dismiss") || equal_Command(cmd, "preferences")) { - setupSheetTransition_Widget(d, iFalse); + setupSheetTransition_Mobile(d, iFalse); setUiScale_Window(get_Window(), toFloat_String(text_InputWidget(findChild_Widget(d, "prefs.uiscale")))); #if defined (LAGRANGE_ENABLE_DOWNLOAD_EDIT) @@ -2383,7 +2383,7 @@ iBool handleCommand_App(const char *cmd) { iCertImportWidget *imp = new_CertImportWidget(); setPageContent_CertImportWidget(imp, sourceContent_DocumentWidget(document_App())); addChild_Widget(get_Root()->widget, iClob(imp)); - finalizeSheet_Widget(as_Widget(imp)); + finalizeSheet_Mobile(as_Widget(imp)); postRefresh_App(); return iTrue; } diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 32cbaca9..9d60f86f 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -1651,7 +1651,7 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) { break; } case categorySuccess_GmStatusCode: - reset_SmoothScroll(&d->scrollY); + //reset_SmoothScroll(&d->scrollY); reset_GmDocument(d->doc); /* new content incoming */ delete_Gempub(d->sourceGempub); d->sourceGempub = NULL; @@ -2307,7 +2307,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) } updateFetchProgress_DocumentWidget_(d); checkResponse_DocumentWidget_(d); - if (category_GmStatusCode(status_GmRequest(d->request)) == success_GmStatusCode) { + if (category_GmStatusCode(status_GmRequest(d->request)) == categorySuccess_GmStatusCode) { init_Anim(&d->scrollY.pos, d->initNormScrollY * size_GmDocument(d->doc).y); /* TODO: unless user already scrolled! */ } d->state = ready_RequestState; diff --git a/src/ui/mobile.c b/src/ui/mobile.c new file mode 100644 index 00000000..f93d4352 --- /dev/null +++ b/src/ui/mobile.c @@ -0,0 +1,704 @@ +/* Copyright 2021 Jaakko Keränen + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + +#include "mobile.h" + +#include "app.h" +#include "command.h" +#include "defs.h" +#include "inputwidget.h" +#include "labelwidget.h" +#include "root.h" +#include "text.h" +#include "widget.h" + +#if defined (iPlatformAppleMobile) +# include "ios.h" +#endif + +static void updatePanelSheetMetrics_(iWidget *sheet) { + iWidget *navi = findChild_Widget(sheet, "panel.navi"); + iWidget *naviPad = child_Widget(navi, 0); + int naviHeight = lineHeight_Text(defaultBig_FontId) + 4 * gap_UI; +#if defined (iPlatformAppleMobile) + float left, right, top, bottom; + safeAreaInsets_iOS(&left, &top, &right, &bottom); + setPadding_Widget(sheet, left, 0, right, 0); + navi->rect.pos = init_I2(left, top); + iConstForEach(PtrArray, i, findChildren_Widget(sheet, "panel.toppad")) { + iWidget *pad = *i.value; + setFixedSize_Widget(pad, init1_I2(naviHeight)); + } +#endif + setFixedSize_Widget(navi, init_I2(-1, naviHeight)); +} + +static iWidget *findDetailStack_(iWidget *topPanel) { + return findChild_Widget(parent_Widget(topPanel), "detailstack"); +} + +static iBool topPanelHandler_(iWidget *topPanel, const char *cmd) { + if (equal_Command(cmd, "panel.open")) { + iWidget *button = pointer_Command(cmd); + iWidget *panel = userData_Object(button); +// openMenu_Widget(panel, innerToWindow_Widget(panel, zero_I2())); +// setFlags_Widget(panel, hidden_WidgetFlag, iFalse); + iForEach(ObjectList, i, children_Widget(findDetailStack_(topPanel))) { + iWidget *child = i.object; + setFlags_Widget(child, hidden_WidgetFlag | disabled_WidgetFlag, child != panel); + } + 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; + if (isPortrait_App()) { + iForEach(ObjectList, i, children_Widget(parent_Widget(topPanel))) { + iWidget *child = i.object; + if (!cmp_String(id_Widget(child), "panel") && isVisible_Widget(child)) { + // closeMenu_Widget(child); + setFlags_Widget(child, hidden_WidgetFlag | disabled_WidgetFlag, iTrue); + setFocus_Widget(NULL); + updateTextCStr_LabelWidget(findWidget_App("panel.back"), "Back"); + wasClosed = iTrue; + } + } + } + if (!wasClosed) { + postCommand_App("prefs.dismiss"); + } + return iTrue; + } + if (equal_Command(cmd, "document.changed")) { + postCommand_App("prefs.dismiss"); + return iFalse; + } + if (equal_Command(cmd, "window.resized")) { + // sheet > mdsplit > panel.top + updatePanelSheetMetrics_(parent_Widget(parent_Widget(topPanel))); + } + 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; + } + return iFalse; +} + +static iBool isOmittedPref_(const iString *id) { + static const char *omittedPrefs[] = { + "prefs.smoothscroll", + "prefs.imageloadscroll", + "prefs.pinsplit", + "prefs.retainwindow", + "prefs.ca.file", + "prefs.ca.path", + }; + iForIndices(i, omittedPrefs) { + if (cmp_String(id, omittedPrefs[i]) == 0) { + return iTrue; + } + } + return iFalse; +} + +enum iPrefsElement { + panelTitle_PrefsElement, + heading_PrefsElement, + toggle_PrefsElement, + dropdown_PrefsElement, + radioButton_PrefsElement, + textInput_PrefsElement, +}; + +static iAnyObject *addPanelChild_(iWidget *panel, iAnyObject *child, int64_t flags, + enum iPrefsElement elementType, + enum iPrefsElement precedingElementType) { + /* Erase redundant/unused headings. */ + if (precedingElementType == heading_PrefsElement && + (!child || (elementType == heading_PrefsElement || elementType == radioButton_PrefsElement))) { + iRelease(removeChild_Widget(panel, lastChild_Widget(panel))); + if (!cmp_String(id_Widget(constAs_Widget(lastChild_Widget(panel))), "padding")) { + iRelease(removeChild_Widget(panel, lastChild_Widget(panel))); + } + } + if (child) { + /* Insert padding between different element types. */ + if (precedingElementType != panelTitle_PrefsElement) { + if (elementType == heading_PrefsElement || + (elementType == toggle_PrefsElement && + precedingElementType != toggle_PrefsElement && + precedingElementType != heading_PrefsElement) || + (elementType == dropdown_PrefsElement && + precedingElementType != dropdown_PrefsElement && + precedingElementType != heading_PrefsElement) || + (elementType == textInput_PrefsElement && + precedingElementType != textInput_PrefsElement && + precedingElementType != heading_PrefsElement)) { + addChild_Widget(panel, iClob(makePadding_Widget(lineHeight_Text(defaultBig_FontId)))); + } + } + if ((elementType == toggle_PrefsElement && precedingElementType != toggle_PrefsElement) || + (elementType == textInput_PrefsElement && precedingElementType != textInput_PrefsElement)) { + flags |= borderTop_WidgetFlag; + } + return addChildFlags_Widget(panel, child, flags); + } + return NULL; +} + +static void stripTrailingColon_(iLabelWidget *label) { + const iString *text = text_LabelWidget(label); + if (endsWith_String(text, ":")) { + iString *mod = copy_String(text); + removeEnd_String(mod, 1); + updateText_LabelWidget(label, mod); + delete_String(mod); + } +} + +static iLabelWidget *makePanelButton_(const char *text, const char *command) { + iLabelWidget *btn = new_LabelWidget(text, command); + setFlags_Widget(as_Widget(btn), + borderBottom_WidgetFlag | alignLeft_WidgetFlag | + frameless_WidgetFlag | extraPadding_WidgetFlag, + iTrue); + checkIcon_LabelWidget(btn); + setFont_LabelWidget(btn, defaultBig_FontId); + setTextColor_LabelWidget(btn, uiTextStrong_ColorId); + setBackgroundColor_Widget(as_Widget(btn), uiBackgroundSidebar_ColorId); + return btn; +} + +static iWidget *makeValuePadding_(iWidget *value) { + iInputWidget *input = isInstance_Object(value, &Class_InputWidget) ? (iInputWidget *) value : NULL; + if (input) { + setFont_InputWidget(input, defaultBig_FontId); + setContentPadding_InputWidget(input, 3 * gap_UI, 3 * gap_UI); + } + iWidget *pad = new_Widget(); + setBackgroundColor_Widget(pad, uiBackgroundSidebar_ColorId); + setPadding_Widget(pad, 0, 1 * gap_UI, 0, 1 * gap_UI); + addChild_Widget(pad, iClob(value)); + setFlags_Widget(pad, + borderBottom_WidgetFlag | + arrangeVertical_WidgetFlag | + resizeToParentWidth_WidgetFlag | + resizeWidthOfChildren_WidgetFlag | + arrangeHeight_WidgetFlag, + iTrue); + return pad; +} + +static iWidget *makeValuePaddingWithHeading_(iLabelWidget *heading, iWidget *value) { + iWidget *div = new_Widget(); + setFlags_Widget(div, + borderBottom_WidgetFlag | arrangeHeight_WidgetFlag | + resizeWidthOfChildren_WidgetFlag | + arrangeHorizontal_WidgetFlag, iTrue); + setBackgroundColor_Widget(div, uiBackgroundSidebar_ColorId); + setPadding_Widget(div, gap_UI, gap_UI, 4 * gap_UI, gap_UI); + addChildFlags_Widget(div, iClob(heading), 0); + //setFixedSize_Widget(as_Widget(heading), init_I2(-1, height_Widget(value))); + setFont_LabelWidget(heading, defaultBig_FontId); + setTextColor_LabelWidget(heading, uiTextStrong_ColorId); + if (isInstance_Object(value, &Class_InputWidget)) { + addChildFlags_Widget(div, iClob(value), expand_WidgetFlag); + } + else if (isInstance_Object(value, &Class_LabelWidget) && + cmp_String(command_LabelWidget((iLabelWidget *) value), "toggle")) { + addChildFlags_Widget(div, iClob(value), expand_WidgetFlag); + /* TODO: This doesn't work? */ +// setCommand_LabelWidget(heading, +// collectNewFormat_String("!%s ptr:%p", +// cstr_String(command_LabelWidget((iLabelWidget *) value)), +// value)); + } + else { + addChildFlags_Widget(div, iClob(new_Widget()), expand_WidgetFlag); + addChild_Widget(div, iClob(value)); + } + return div; +} + +static iWidget *addChildPanel_(iWidget *parent, iLabelWidget *panelButton, + const iString *titleText) { + iWidget *panel = new_Widget(); + setId_Widget(panel, "panel"); + setUserData_Object(panelButton, panel); + setBackgroundColor_Widget(panel, uiBackground_ColorId); + setId_Widget(addChild_Widget(panel, iClob(makePadding_Widget(0))), "panel.toppad"); + if (titleText) { + iLabelWidget *title = + addChildFlags_Widget(panel, + iClob(new_LabelWidget(cstr_String(titleText), NULL)), + alignLeft_WidgetFlag | frameless_WidgetFlag); + setFont_LabelWidget(title, uiLabelLargeBold_FontId); + setTextColor_LabelWidget(title, uiHeading_ColorId); + } + addChildFlags_Widget(parent, + iClob(panel), + focusRoot_WidgetFlag | hidden_WidgetFlag | disabled_WidgetFlag | + arrangeVertical_WidgetFlag | resizeWidthOfChildren_WidgetFlag | + arrangeHeight_WidgetFlag | overflowScrollable_WidgetFlag | + //horizontalOffset_WidgetFlag | edgeDraggable_WidgetFlag | + commandOnClick_WidgetFlag); + return panel; +} + +void finalizeSheet_Mobile(iWidget *sheet) { + /* The sheet contents are completely rearranged and restyled 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. 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"). At least this way it is possible to enforce + a consistent styling. */ + if (deviceType_App() == phone_AppDeviceType && parent_Widget(sheet) == root_Widget(sheet)) { + if (~flags_Widget(sheet) & keepOnTop_WidgetFlag) { + /* Already finalized. */ + arrange_Widget(sheet); + postRefresh_App(); + return; + } + /* Landscape Layout Portrait Layout + + ┌─────────┬──────Detail─Stack─────┐ ┌─────────┬ ─ ─ ─ ─ ┐ + │ │┌───────────────────┐ │ │ │Detail + │ ││┌──────────────────┴┐ │ │ │Stack │ + │ │││┌──────────────────┴┐│ │ │┌──────┐ + │ ││││ ││ │ ││┌─────┴┐│ + │ ││││ ││ │ │││ │ + │Top Panel││││ ││ │Top Panel│││ ││ + │ ││││ Panels ││ │ │││Panels│ + │ ││││ ││ │ │││ ││ + │ │└┤│ ││ │ │││ │ + │ │ └┤ ││ │ │└┤ ││ + │ │ └───────────────────┘│ │ │ └──────┘ + └─────────┴───────────────────────┘ └─────────┴ ─ ─ ─ ─ ┘ + offscreen + */ + /* Modify the top sheet to act as a fullscreen background. */ + setPadding1_Widget(sheet, 0); + setBackgroundColor_Widget(sheet, uiBackground_ColorId); + setFlags_Widget(sheet, + keepOnTop_WidgetFlag | + parentCannotResize_WidgetFlag | + arrangeSize_WidgetFlag | + centerHorizontal_WidgetFlag | + arrangeVertical_WidgetFlag | + arrangeHorizontal_WidgetFlag | + overflowScrollable_WidgetFlag, + iFalse); + setFlags_Widget(sheet, + frameless_WidgetFlag | + resizeWidthOfChildren_WidgetFlag | + edgeDraggable_WidgetFlag | + commandOnClick_WidgetFlag, + iTrue); + iPtrArray * contents = collect_PtrArray(new_PtrArray()); /* two-column pages */ + iPtrArray * panelButtons = collect_PtrArray(new_PtrArray()); + iWidget * prefsTabs = findChild_Widget(sheet, "prefs.tabs"); + iWidget * dialogHeading = (prefsTabs ? NULL : child_Widget(sheet, 0)); + const iBool isPrefs = (prefsTabs != NULL); + const int64_t panelButtonFlags = borderBottom_WidgetFlag | alignLeft_WidgetFlag | + frameless_WidgetFlag | extraPadding_WidgetFlag; + iWidget *mainDetailSplit = makeHDiv_Widget(); + setFlags_Widget(mainDetailSplit, resizeHeightOfChildren_WidgetFlag, iFalse); + setId_Widget(mainDetailSplit, "mdsplit"); + iWidget *topPanel = new_Widget(); { + setId_Widget(topPanel, "panel.top"); + setCommandHandler_Widget(topPanel, topPanelHandler_); + setFlags_Widget(topPanel, + arrangeVertical_WidgetFlag | + resizeWidthOfChildren_WidgetFlag | + arrangeHeight_WidgetFlag | + overflowScrollable_WidgetFlag | + commandOnClick_WidgetFlag, + iTrue); + addChild_Widget(mainDetailSplit, iClob(topPanel)); + } + iWidget *detailStack = new_Widget(); { + setId_Widget(detailStack, "detailstack"); + setFlags_Widget(detailStack, resizeWidthOfChildren_WidgetFlag, iTrue); + addChild_Widget(mainDetailSplit, iClob(detailStack)); + } + //setFlags_Widget(topPanel, topPanelOffset_WidgetFlag, iTrue); /* slide with children */ + addChild_Widget(topPanel, iClob(makePadding_Widget(lineHeight_Text(defaultBig_FontId)))); + if (prefsTabs) { + 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(prefsTabs, "tabs.pages"); + size_t pageCount = tabCount_Widget(prefsTabs); + for (size_t i = 0; i < pageCount; i++) { + iString *text = copy_String(text_LabelWidget(tabPageButton_Widget(prefsTabs, tabPage_Widget(prefsTabs, 0)))); + iWidget *page = removeTabPage_Widget(prefsTabs, 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 = makePanelButton_( + i == 1 ? "${heading.prefs.userinterface}" : cstr_String(text), + "panel.open")), + (i == 0 ? borderTop_WidgetFlag : 0) | + chevron_WidgetFlag)); + const iChar icons[] = { + 0x02699, /* 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(prefsTabs); + } + iForEach(ObjectList, i, children_Widget(sheet)) { + iWidget *child = i.object; + if (isTwoColumnPage_(child)) { + pushBack_PtrArray(contents, removeChild_Widget(sheet, child)); + } + else { + removeChild_Widget(sheet, child); + addChild_Widget(topPanel, child); + iRelease(child); + } + } + const iBool useSlidePanels = (size_PtrArray(contents) == size_PtrArray(panelButtons)); + addChild_Widget(sheet, iClob(mainDetailSplit)); + iForEach(PtrArray, j, contents) { + iWidget *owner = topPanel; + if (useSlidePanels) { + /* Create a new child panel. */ + iLabelWidget *button = at_PtrArray(panelButtons, index_PtrArrayIterator(&j)); + owner = addChildPanel_(detailStack, button, + collect_String(upper_String(text_LabelWidget(button)))); + } + iWidget *pageContent = j.ptr; + iWidget *headings = child_Widget(pageContent, 0); + iWidget *values = child_Widget(pageContent, 1); + enum iPrefsElement prevElement = panelTitle_PrefsElement; + /* Identify the types of controls in the dialog and restyle/organize them. */ + while (!isEmpty_ObjectList(children_Widget(headings))) { + iWidget *heading = child_Widget(headings, 0); + iWidget *value = child_Widget(values, 0); + removeChild_Widget(headings, heading); + removeChild_Widget(values, value); + /* Can we ignore these widgets? */ + if (isOmittedPref_(id_Widget(value)) || + (class_Widget(heading) == &Class_Widget && + class_Widget(value) == &Class_Widget) /* just padding */) { + iRelease(heading); + iRelease(value); + continue; + } + enum iPrefsElement element = toggle_PrefsElement; + iLabelWidget *headingLabel = NULL; + iLabelWidget *valueLabel = NULL; + iInputWidget *valueInput = NULL; + const iBool isMenuButton = findChild_Widget(value, "menu") != NULL; + if (isInstance_Object(heading, &Class_LabelWidget)) { + headingLabel = (iLabelWidget *) heading; + stripTrailingColon_(headingLabel); + } + if (isInstance_Object(value, &Class_LabelWidget)) { + valueLabel = (iLabelWidget *) value; + setFont_LabelWidget(valueLabel, defaultBig_FontId); + } + if (isInstance_Object(value, &Class_InputWidget)) { + valueInput = (iInputWidget *) value; + setFlags_Widget(value, borderBottom_WidgetFlag, iFalse); + element = textInput_PrefsElement; + } + if (childCount_Widget(value) >= 2) { + if (isInstance_Object(child_Widget(value, 0), &Class_InputWidget)) { + element = textInput_PrefsElement; + setPadding_Widget(value, 0, 0, gap_UI, 0); + valueInput = child_Widget(value, 0); + } + } + if (valueInput) { + setFont_InputWidget(valueInput, defaultBig_FontId); + setContentPadding_InputWidget(valueInput, 3 * gap_UI, 0); + } + /* Toggles have the button on the right. */ + if (valueLabel && cmp_String(command_LabelWidget(valueLabel), "toggle") == 0) { + element = toggle_PrefsElement; + addPanelChild_(owner, + iClob(makeValuePaddingWithHeading_(headingLabel, value)), + 0, + element, + prevElement); + } + else if (valueLabel && isEmpty_String(text_LabelWidget(valueLabel))) { + element = heading_PrefsElement; + iRelease(value); + addPanelChild_(owner, iClob(heading), 0, element, prevElement); + setFont_LabelWidget(headingLabel, uiLabel_FontId); + } + else if (isMenuButton) { + element = dropdown_PrefsElement; + setFlags_Widget(value, + alignRight_WidgetFlag | noBackground_WidgetFlag | + frameless_WidgetFlag, iTrue); + setFlags_Widget(value, alignLeft_WidgetFlag, iFalse); + iWidget *pad = addPanelChild_(owner, iClob(makeValuePaddingWithHeading_(headingLabel, value)), 0, + element, prevElement); + pad->padding[2] = gap_UI; + } + else if (valueInput) { + addPanelChild_(owner, iClob(makeValuePaddingWithHeading_(headingLabel, value)), 0, + element, prevElement); + } + else { + if (childCount_Widget(value) >= 2) { + element = radioButton_PrefsElement; + /* Always padding before radio buttons. */ + addChild_Widget(owner, iClob(makePadding_Widget(lineHeight_Text(defaultBig_FontId)))); + } + addChildFlags_Widget(owner, iClob(heading), borderBottom_WidgetFlag); + if (headingLabel) { + setTextColor_LabelWidget(headingLabel, uiSubheading_ColorId); + setText_LabelWidget(headingLabel, + collect_String(upper_String(text_LabelWidget(headingLabel)))); + } + addPanelChild_(owner, iClob(value), 0, element, prevElement); + /* Radio buttons expand to fill the space. */ + if (element == radioButton_PrefsElement) { + setBackgroundColor_Widget(value, uiBackgroundSidebar_ColorId); + setPadding_Widget(value, 4 * gap_UI, 2 * gap_UI, 4 * gap_UI, 2 * gap_UI); + setFlags_Widget(value, arrangeWidth_WidgetFlag, iFalse); + setFlags_Widget(value, + borderBottom_WidgetFlag | + resizeToParentWidth_WidgetFlag | + resizeWidthOfChildren_WidgetFlag, + iTrue); + iForEach(ObjectList, sub, children_Widget(value)) { + if (isInstance_Object(sub.object, &Class_LabelWidget)) { + iLabelWidget *opt = sub.object; + setFont_LabelWidget(opt, defaultMedium_FontId); + setFlags_Widget(as_Widget(opt), noBackground_WidgetFlag, iTrue); + } + } + } + } + prevElement = element; + } + addPanelChild_(owner, NULL, 0, 0, prevElement); + destroy_Widget(pageContent); + setFlags_Widget(owner, drawBackgroundToBottom_WidgetFlag, iTrue); + } + destroyPending_Root(sheet->root); + /* Additional elements for preferences. */ + if (isPrefs) { + addChild_Widget(topPanel, iClob(makePadding_Widget(lineHeight_Text(defaultBig_FontId)))); + addChildFlags_Widget(topPanel, + iClob(makePanelButton_(info_Icon " ${menu.help}", "!open url:about:help")), + borderTop_WidgetFlag); + iLabelWidget *aboutButton = addChildFlags_Widget(topPanel, + iClob(makePanelButton_(planet_Icon " ${menu.about}", "panel.open")), + chevron_WidgetFlag); + /* The About panel. */ { + iWidget *panel = addChildPanel_(detailStack, aboutButton, NULL); + iString *msg = collectNew_String(); + setCStr_String(msg, "Lagrange " LAGRANGE_APP_VERSION); +#if defined (iPlatformAppleMobile) + appendCStr_String(msg, " (" LAGRANGE_IOS_VERSION ")"); +#endif + addChildFlags_Widget(panel, iClob(new_LabelWidget(cstr_String(msg), NULL)), + frameless_WidgetFlag); + addChildFlags_Widget(panel, + iClob(makePanelButton_(globe_Icon " By @jk@skyjake.fi", + "!open url:https://skyjake.fi/@jk")), + borderTop_WidgetFlag); + addChildFlags_Widget(panel, + iClob(makePanelButton_(clock_Icon " ${menu.releasenotes}", + "!open url:about:version")), + 0); + addChildFlags_Widget(panel, + iClob(makePanelButton_(info_Icon " ${menu.aboutpages}", + "!open url:about:about")), + 0); + addChildFlags_Widget(panel, + iClob(makePanelButton_(bug_Icon " ${menu.debug}", + "!open url:about:debug")), + 0); + } + } + else { + setFlags_Widget(topPanel, overflowScrollable_WidgetFlag, iTrue); + /* Update heading style. */ + setFont_LabelWidget((iLabelWidget *) dialogHeading, uiLabelLargeBold_FontId); + setFlags_Widget(dialogHeading, alignLeft_WidgetFlag, iTrue); + } + if (findChild_Widget(sheet, "valueinput.prompt")) { + iWidget *prompt = findChild_Widget(sheet, "valueinput.prompt"); + setFlags_Widget(prompt, alignLeft_WidgetFlag, iTrue); + iInputWidget *input = findChild_Widget(sheet, "input"); + removeChild_Widget(parent_Widget(input), input); + addChild_Widget(topPanel, iClob(makeValuePadding_(as_Widget(input)))); + } + /* Top padding for each panel, to account for the overlaid navbar. */ { + setId_Widget(addChildPos_Widget(topPanel, + iClob(makePadding_Widget(0)), front_WidgetAddPos), + "panel.toppad"); + } + /* Navbar. */ { + iWidget *navi = new_Widget(); + setId_Widget(navi, "panel.navi"); + setBackgroundColor_Widget(navi, uiBackground_ColorId); + addChild_Widget(navi, iClob(makePadding_Widget(0))); + iLabelWidget *back = addChildFlags_Widget(navi, + iClob(new_LabelWidget(leftAngle_Icon " ${panel.back}", "panel.close")), + noBackground_WidgetFlag | frameless_WidgetFlag | + alignLeft_WidgetFlag | extraPadding_WidgetFlag); + checkIcon_LabelWidget(back); + setId_Widget(as_Widget(back), "panel.back"); + setFont_LabelWidget(back, defaultBig_FontId); + if (!isPrefs) { + /* Pick up the dialog buttons for the navbar. */ + iWidget *buttons = findChild_Widget(sheet, "dialogbuttons"); + iLabelWidget *cancel = findMenuItem_Widget(buttons, "cancel"); +// if (!cancel) { +// cancel = findMenuItem_Widget(buttons, "translation.cancel"); +// } + if (cancel) { + updateText_LabelWidget(back, text_LabelWidget(cancel)); + setCommand_LabelWidget(back, command_LabelWidget(cancel)); + } + iLabelWidget *def = (iLabelWidget *) lastChild_Widget(buttons); + if (def && !cancel) { + updateText_LabelWidget(back, text_LabelWidget(def)); + setCommand_LabelWidget(back, command_LabelWidget(def)); + setFlags_Widget(as_Widget(back), alignLeft_WidgetFlag, iFalse); + setFlags_Widget(as_Widget(back), alignRight_WidgetFlag, iTrue); + setIcon_LabelWidget(back, 0); + setFont_LabelWidget(back, defaultBigBold_FontId); + } + else if (def != cancel) { + removeChild_Widget(buttons, def); + setFont_LabelWidget(def, defaultBigBold_FontId); + setFlags_Widget(as_Widget(def), + frameless_WidgetFlag | extraPadding_WidgetFlag | + noBackground_WidgetFlag, iTrue); + addChildFlags_Widget(as_Widget(back), iClob(def), moveToParentRightEdge_WidgetFlag); + updateSize_LabelWidget(def); + } + /* Action buttons are added in the bottom as extra buttons. */ { + iBool isFirstAction = iTrue; + iForEach(ObjectList, i, children_Widget(buttons)) { + if (isInstance_Object(i.object, &Class_LabelWidget) && + i.object != cancel && i.object != def) { + iLabelWidget *item = i.object; + setBackgroundColor_Widget(i.object, uiBackgroundSidebar_ColorId); + setFont_LabelWidget(item, defaultBig_FontId); + removeChild_Widget(buttons, item); + addChildFlags_Widget(topPanel, iClob(item), panelButtonFlags | + (isFirstAction ? borderTop_WidgetFlag : 0)); + updateSize_LabelWidget(item); + isFirstAction = iFalse; + } + } + } + iRelease(removeChild_Widget(parent_Widget(buttons), buttons)); + /* Styling for remaining elements. */ + iForEach(ObjectList, i, children_Widget(topPanel)) { + if (isInstance_Object(i.object, &Class_LabelWidget) && + isEmpty_String(command_LabelWidget(i.object)) && + isEmpty_String(id_Widget(i.object))) { + setFlags_Widget(i.object, alignLeft_WidgetFlag, iTrue); + if (font_LabelWidget(i.object) == uiLabel_FontId) { + setFont_LabelWidget(i.object, uiContent_FontId); + } + } + } + } + addChildFlags_Widget(sheet, iClob(navi), + drawBackgroundToVerticalSafeArea_WidgetFlag | + arrangeHeight_WidgetFlag | resizeWidthOfChildren_WidgetFlag | + resizeToParentWidth_WidgetFlag | arrangeVertical_WidgetFlag); + } + updatePanelSheetMetrics_(sheet); + iAssert(sheet->parent); + arrange_Widget(sheet->parent); + postCommand_App("widget.overflow"); /* with the correct dimensions */ + //puts("---- MOBILE LAYOUT ----"); + //printTree_Widget(sheet); + } + else { + arrange_Widget(sheet); + } + postRefresh_App(); +} + +void setupMenuTransition_Mobile(iWidget *sheet, iBool isIncoming) { + if (deviceType_App() != phone_AppDeviceType) { + return; + } + const iBool isSlidePanel = (flags_Widget(sheet) & horizontalOffset_WidgetFlag) != 0; + if (isSlidePanel && isLandscape_App()) { + return; + } + if (isIncoming) { + setVisualOffset_Widget(sheet, isSlidePanel ? width_Widget(sheet) : height_Widget(sheet), 0, 0); + setVisualOffset_Widget(sheet, 0, 330, easeOut_AnimFlag | softer_AnimFlag); + } + else { + const iBool wasDragged = iAbs(value_Anim(&sheet->visualOffset) - 0) > 1; + setVisualOffset_Widget(sheet, + isSlidePanel ? width_Widget(sheet) : height_Widget(sheet), + wasDragged ? 100 : 200, + wasDragged ? 0 : easeIn_AnimFlag | softer_AnimFlag); + } +} + +void setupSheetTransition_Mobile(iWidget *sheet, iBool isIncoming) { + if (deviceType_App() != phone_AppDeviceType || isLandscape_App()) { + return; + } + if (isIncoming) { + setFlags_Widget(sheet, horizontalOffset_WidgetFlag, iTrue); + setVisualOffset_Widget(sheet, size_Root(sheet->root).x, 0, 0); + setVisualOffset_Widget(sheet, 0, 200, easeOut_AnimFlag); + } + else { + const iBool wasDragged = iAbs(value_Anim(&sheet->visualOffset)) > 0; + setVisualOffset_Widget(sheet, size_Root(sheet->root).x, wasDragged ? 100 : 200, + wasDragged ? 0 : easeIn_AnimFlag); + } +} diff --git a/src/ui/mobile.h b/src/ui/mobile.h new file mode 100644 index 00000000..92b2280a --- /dev/null +++ b/src/ui/mobile.h @@ -0,0 +1,32 @@ +/* Copyright 2021 Jaakko Keränen + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + +#pragma once + +#include + +iDeclareType(Widget) + +void setupMenuTransition_Mobile (iWidget *menu, iBool isIncoming); + +void finalizeSheet_Mobile (iWidget *sheet); +void setupSheetTransition_Mobile (iWidget *sheet, iBool isIncoming); diff --git a/src/ui/root.c b/src/ui/root.c index f37d274d..6cf3f424 100644 --- a/src/ui/root.c +++ b/src/ui/root.c @@ -469,11 +469,11 @@ static void setReloadLabel_Root_(iRoot *d, iBool animating) { const iBool isMobile = deviceType_App() != desktop_AppDeviceType; iLabelWidget *label = findChild_Widget(d->widget, "reload"); updateTextCStr_LabelWidget( - label, animating ? loadAnimationCStr_() : (isMobile ? pageMenuCStr_ : reloadCStr_)); - if (isMobile) { - setCommand_LabelWidget(label, - collectNewCStr_String(animating ? "navigate.reload" : "menu.open")); - } + label, animating ? loadAnimationCStr_() : (/*isMobile ? pageMenuCStr_ :*/ reloadCStr_)); +// if (isMobile) { +// setCommand_LabelWidget(label, +// collectNewCStr_String(animating ? "navigate.reload" : "menu.open")); +// } } static void checkLoadAnimation_Root_(iRoot *d) { @@ -539,9 +539,12 @@ static iBool willPerformSearchQuery_(const iString *userInput) { static void updateUrlInputContentPadding_(iWidget *navBar) { iInputWidget *url = findChild_Widget(navBar, "url"); - const iWidget *indicators = findChild_Widget(navBar, "url.rightembed"); - setContentPadding_InputWidget(url, -1, - width_Widget(indicators)); + const int lockWidth = width_Widget(findChild_Widget(navBar, "navbar.lock")); + const int indicatorsWidth = width_Widget(findChild_Widget(navBar, "url.rightembed")); + /* The indicators widget has a padding that covers the urlButtons area. */ + setContentPadding_InputWidget(url, + lockWidth - 2 * gap_UI, // * 0.75f, + indicatorsWidth); } static void showSearchQueryIndicator_(iBool show) { @@ -588,12 +591,12 @@ static void updateNavBarSize_(iWidget *navBar) { updateSize_LabelWidget(label); } } + updateUrlInputContentPadding_(navBar); /* Note that InputWidget uses the `tight` flag to adjust its inner padding. */ - /* TODO: Is this redundant? See `updateMetrics_Window_()`. */ - const int embedButtonWidth = width_Widget(findChild_Widget(navBar, "navbar.lock")); - setContentPadding_InputWidget(findChild_Widget(navBar, "url"), - embedButtonWidth * 0.75f, - embedButtonWidth * 0.75f); +// const int embedButtonWidth = width_Widget(findChild_Widget(navBar, "navbar.lock")); +// setContentPadding_InputWidget(findChild_Widget(navBar, "url"), +// embedButtonWidth * 0.75f, +// embedButtonWidth * 0.75f); } if (isPhone) { static const char *buttons[] = { "navbar.back", "navbar.forward", "navbar.sidebar", @@ -904,15 +907,19 @@ void updateMetrics_Root(iRoot *d) { iWidget *url = findChild_Widget(d->widget, "url"); iWidget *rightEmbed = findChild_Widget(navBar, "url.rightembed"); iWidget *embedPad = findChild_Widget(navBar, "url.embedpad"); + iWidget *urlButtons = findChild_Widget(navBar, "url.buttons"); setPadding_Widget(as_Widget(url), 0, gap_UI, 0, gap_UI); navBar->rect.size.y = 0; /* recalculate height based on children (FIXME: shouldn't be needed) */ - updateSize_LabelWidget((iLabelWidget *) lock); - setFixedSize_Widget(embedPad, init_I2(width_Widget(lock) + gap_UI / 2, 1)); - setContentPadding_InputWidget((iInputWidget *) url, width_Widget(lock) * 0.75, - width_Widget(lock) * 0.75); +// updateSize_LabelWidget((iLabelWidget *) lock); +// updateSize_LabelWidget((iLabelWidget *) findChild_Widget(navBar, "reload")); +// arrange_Widget(urlButtons); + setFixedSize_Widget(embedPad, init_I2(width_Widget(urlButtons) + gap_UI / 2, 1)); +// setContentPadding_InputWidget((iInputWidget *) url, width_Widget(lock) * 0.75, +// width_Widget(lock) * 0.75); rightEmbed->rect.pos.y = gap_UI; updatePadding_Root(d); arrange_Widget(d->widget); + updateUrlInputContentPadding_(navBar); postRefresh_App(); } @@ -1060,7 +1067,7 @@ void createUserInterface_Root(iRoot *d) { setNoAutoMinHeight_LabelWidget(fprog, iTrue); addChildFlags_Widget(rightEmbed, iClob(fprog), - collapse_WidgetFlag | frameless_WidgetFlag | hidden_WidgetFlag); + collapse_WidgetFlag | hidden_WidgetFlag | frameless_WidgetFlag); } /* Download progress indicator is also inside the input field, but hidden normally. */ { iLabelWidget *progress = new_LabelWidget(uiTextCaution_ColorEscape "00.000 ${mb}", NULL); @@ -1069,7 +1076,7 @@ void createUserInterface_Root(iRoot *d) { setAlignVisually_LabelWidget(progress, iTrue); setNoAutoMinHeight_LabelWidget(progress, iTrue); addChildFlags_Widget( - rightEmbed, iClob(progress), collapse_WidgetFlag); + rightEmbed, iClob(progress), collapse_WidgetFlag | hidden_WidgetFlag); } /* Pinning indicator. */ { iLabelWidget *pin = new_LabelWidget(uiTextAction_ColorEscape leftHalf_Icon, NULL); @@ -1079,39 +1086,44 @@ void createUserInterface_Root(iRoot *d) { setNoAutoMinHeight_LabelWidget(pin, iTrue); addChildFlags_Widget(rightEmbed, iClob(pin), - collapse_WidgetFlag | tight_WidgetFlag | frameless_WidgetFlag); + collapse_WidgetFlag | hidden_WidgetFlag | tight_WidgetFlag | frameless_WidgetFlag); + } + iWidget *urlButtons = new_Widget(); + setId_Widget(urlButtons, "url.buttons"); + setFlags_Widget(urlButtons, embedFlags | arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag, iTrue); + /* Mobile page menu. */ + if (deviceType_App() != desktop_AppDeviceType) { + iLabelWidget *pageMenuButton; + /* In a mobile layout, the reload button is replaced with the Page/Ellipsis menu. */ + pageMenuButton = makeMenuButton_LabelWidget(pageMenuCStr_, + (iMenuItem[]){ + { upArrow_Icon " ${menu.parent}", navigateParent_KeyShortcut, "navigate.parent" }, + { upArrowBar_Icon " ${menu.root}", navigateRoot_KeyShortcut, "navigate.root" }, + { timer_Icon " ${menu.autoreload}", 0, 0, "document.autoreload.menu" }, + { "---", 0, 0, NULL }, + { bookmark_Icon " ${menu.page.bookmark}", SDLK_d, KMOD_PRIMARY, "bookmark.add" }, + { star_Icon " ${menu.page.subscribe}", subscribeToPage_KeyModifier, "feeds.subscribe" }, + { book_Icon " ${menu.page.import}", 0, 0, "bookmark.links confirm:1" }, + { globe_Icon " ${menu.page.translate}", 0, 0, "document.translate" }, + { "---", 0, 0, NULL }, + { "${menu.page.copyurl}", 0, 0, "document.copylink" }, + { "${menu.page.copysource}", 'c', KMOD_PRIMARY, "copy" }, + { download_Icon " " saveToDownloads_Label, SDLK_s, KMOD_PRIMARY, "document.save" } }, + 12); + setId_Widget(as_Widget(pageMenuButton), "pagemenubutton"); + setFont_LabelWidget(pageMenuButton, uiContentBold_FontId); + setAlignVisually_LabelWidget(pageMenuButton, iTrue); + addChildFlags_Widget(urlButtons, iClob(pageMenuButton), embedFlags); + updateSize_LabelWidget(pageMenuButton); } /* Reload button. */ { - iLabelWidget *reload; - if (deviceType_App() == desktop_AppDeviceType) { - reload = newIcon_LabelWidget(reloadCStr_, 0, 0, "navigate.reload"); - } - else { - /* In a mobile layout, the reload button is replaced with the Page/Ellipsis menu. */ - reload = makeMenuButton_LabelWidget(pageMenuCStr_, - (iMenuItem[]){ - { reload_Icon " ${menu.reload}", reload_KeyShortcut, "navigate.reload" }, - { timer_Icon " ${menu.autoreload}", 0, 0, "document.autoreload.menu" }, - { "---", 0, 0, NULL }, - { upArrow_Icon " ${menu.parent}", navigateParent_KeyShortcut, "navigate.parent" }, - { upArrowBar_Icon " ${menu.root}", navigateRoot_KeyShortcut, "navigate.root" }, - { "---", 0, 0, NULL }, - { pin_Icon " ${menu.page.bookmark}", SDLK_d, KMOD_PRIMARY, "bookmark.add" }, - { star_Icon " ${menu.page.subscribe}", subscribeToPage_KeyModifier, "feeds.subscribe" }, - { book_Icon " ${menu.page.import}", 0, 0, "bookmark.links confirm:1" }, - { globe_Icon " ${menu.page.translate}", 0, 0, "document.translate" }, - { "---", 0, 0, NULL }, - { "${menu.page.copyurl}", 0, 0, "document.copylink" }, - { "${menu.page.copysource}", 'c', KMOD_PRIMARY, "copy" }, - { download_Icon " " saveToDownloads_Label, SDLK_s, KMOD_PRIMARY, "document.save" } }, - 14); - setFont_LabelWidget((iLabelWidget *) reload, uiContentBold_FontId); - setAlignVisually_LabelWidget((iLabelWidget *) reload, iTrue); - } + iLabelWidget *reload = newIcon_LabelWidget(reloadCStr_, 0, 0, "navigate.reload"); setId_Widget(as_Widget(reload), "reload"); - addChildFlags_Widget(as_Widget(url), iClob(reload), embedFlags | moveToParentRightEdge_WidgetFlag); + addChildFlags_Widget(urlButtons, iClob(reload), embedFlags); updateSize_LabelWidget(reload); } + addChildFlags_Widget(as_Widget(url), iClob(urlButtons), moveToParentRightEdge_WidgetFlag); + arrange_Widget(urlButtons); setId_Widget(addChild_Widget(rightEmbed, iClob(makePadding_Widget(0))), "url.embedpad"); } if (deviceType_App() != desktop_AppDeviceType) { diff --git a/src/ui/util.c b/src/ui/util.c index 82047479..b4c43951 100644 --- a/src/ui/util.c +++ b/src/ui/util.c @@ -837,10 +837,7 @@ void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) { if (postCommands) { postCommand_Widget(d, "menu.opened"); } - if (deviceType_App() == phone_AppDeviceType) { - setVisualOffset_Widget(d, isSlidePanel ? width_Widget(d) : height_Widget(d), 0, 0); - setVisualOffset_Widget(d, 0, 330, easeOut_AnimFlag | softer_AnimFlag); - } + setupMenuTransition_Mobile(d, iTrue); } void closeMenu_Widget(iWidget *d) { @@ -851,14 +848,7 @@ void closeMenu_Widget(iWidget *d) { setFlags_Widget(findChild_Widget(d, "menu.cancel"), disabled_WidgetFlag, iTrue); postRefresh_App(); postCommand_Widget(d, "menu.closed"); - if (deviceType_App() == phone_AppDeviceType) { - const iBool wasDragged = iAbs(value_Anim(&d->visualOffset) - 0) > 1; - setVisualOffset_Widget(d, - flags_Widget(d) & horizontalOffset_WidgetFlag ? - width_Widget(d) : height_Widget(d), - wasDragged ? 100 : 200, - wasDragged ? 0 : easeIn_AnimFlag | softer_AnimFlag); - } + setupMenuTransition_Mobile(d, iFalse); } iLabelWidget *findMenuItem_Widget(iWidget *menu, const char *command) { @@ -1127,46 +1117,6 @@ size_t tabCount_Widget(const iWidget *tabs) { /*-----------------------------------------------------------------------------------------------*/ -static void acceptFilePath_(iWidget *dlg) { - iInputWidget *input = findChild_Widget(dlg, "input"); - iString *path = makeAbsolute_Path(text_InputWidget(input)); - postCommandf_App("%s path:%s", cstr_String(id_Widget(dlg)), cstr_String(path)); - destroy_Widget(dlg); - delete_String(path); -} - -iBool filePathHandler_(iWidget *dlg, const char *cmd) { - iWidget *ptr = as_Widget(pointer_Command(cmd)); - if (equal_Command(cmd, "input.ended")) { - if (hasParent_Widget(ptr, dlg)) { - if (arg_Command(cmd)) { - acceptFilePath_(dlg); - } - else { - destroy_Widget(dlg); - } - return iTrue; - } - return iFalse; - } - else if (ptr && !hasParent_Widget(ptr, dlg)) { - /* Command from outside the dialog, so dismiss the dialog. */ - if (!equal_Command(cmd, "focus.lost")) { - destroy_Widget(dlg); - } - return iFalse; - } - else if (equal_Command(cmd, "filepath.cancel")) { - end_InputWidget(findChild_Widget(dlg, "input"), iFalse); - destroy_Widget(dlg); - return iTrue; - } - else if (equal_Command(cmd, "filepath.accept")) { - acceptFilePath_(dlg); - return iTrue; - } - return iFalse; -} iWidget *makeSheet_Widget(const char *id) { iWidget *sheet = new_Widget(); @@ -1183,638 +1133,6 @@ iWidget *makeSheet_Widget(const char *id) { return sheet; } -static void updateSheetPanelMetrics_(iWidget *sheet) { - iWidget *navi = findChild_Widget(sheet, "panel.navi"); - iWidget *naviPad = child_Widget(navi, 0); - int naviHeight = lineHeight_Text(defaultBig_FontId) + 4 * gap_UI; -#if defined (iPlatformAppleMobile) - float left, right, top, bottom; - safeAreaInsets_iOS(&left, &top, &right, &bottom); - setPadding_Widget(sheet, left, 0, right, 0); - navi->rect.pos = init_I2(left, top); - iConstForEach(PtrArray, i, findChildren_Widget(sheet, "panel.toppad")) { - iWidget *pad = *i.value; - setFixedSize_Widget(pad, init1_I2(naviHeight)); - } -#endif - setFixedSize_Widget(navi, init_I2(-1, naviHeight)); -} - -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, innerToWindow_Widget(panel, zero_I2())); - setFlags_Widget(panel, disabled_WidgetFlag, iFalse); - /* - if (deviceType_App() == phone_AppDeviceType && isPortrait_App()) { - setFlags_Widget(d, visualOffset_WidgetFlag | horizontalOffset_WidgetFlag, iTrue); - d->visualOffset = panel->visualOffset; - d->visualOffset.to = -d->visualOffset.from / 3; - d->visualOffset.from = 0; - }*/ - 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); - /* - if (deviceType_App() == phone_AppDeviceType && isPortrait_App()) { - setFlags_Widget(d, visualOffset_WidgetFlag | horizontalOffset_WidgetFlag, iTrue); - d->visualOffset = child->visualOffset; - d->visualOffset.from = -d->visualOffset.to / 3; - d->visualOffset.to = 0; - }*/ - setFlags_Widget(child, disabled_WidgetFlag, iTrue); - setFocus_Widget(NULL); - updateTextCStr_LabelWidget(findWidget_App("panel.back"), "Back"); - wasClosed = iTrue; - } - } - if (!wasClosed) { - postCommand_App("prefs.dismiss"); - } - return iTrue; - } - if (equal_Command(cmd, "document.changed")) { - postCommand_App("prefs.dismiss"); - return iFalse; - } - if (equal_Command(cmd, "window.resized")) { - updateSheetPanelMetrics_(parent_Widget(d)); - } - 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; - } - return iFalse; -} - -static iBool isOmittedPref_(const iString *id) { - static const char *omittedPrefs[] = { - "prefs.smoothscroll", - "prefs.imageloadscroll", - "prefs.pinsplit", - "prefs.retainwindow", - "prefs.ca.file", - "prefs.ca.path", - }; - iForIndices(i, omittedPrefs) { - if (cmp_String(id, omittedPrefs[i]) == 0) { - return iTrue; - } - } - return iFalse; -} - -enum iPrefsElement { - panelTitle_PrefsElement, - heading_PrefsElement, - toggle_PrefsElement, - dropdown_PrefsElement, - radioButton_PrefsElement, - textInput_PrefsElement, -}; - -static iAnyObject *addPanelChild_(iWidget *panel, iAnyObject *child, int64_t flags, - enum iPrefsElement elementType, - enum iPrefsElement precedingElementType) { - /* Erase redundant/unused headings. */ - if (precedingElementType == heading_PrefsElement && - (!child || (elementType == heading_PrefsElement || elementType == radioButton_PrefsElement))) { - iRelease(removeChild_Widget(panel, lastChild_Widget(panel))); - if (!cmp_String(id_Widget(constAs_Widget(lastChild_Widget(panel))), "padding")) { - iRelease(removeChild_Widget(panel, lastChild_Widget(panel))); - } - } - if (child) { - /* Insert padding between different element types. */ - if (precedingElementType != panelTitle_PrefsElement) { - if (elementType == heading_PrefsElement || - (elementType == toggle_PrefsElement && - precedingElementType != toggle_PrefsElement && - precedingElementType != heading_PrefsElement) || - (elementType == dropdown_PrefsElement && - precedingElementType != dropdown_PrefsElement && - precedingElementType != heading_PrefsElement) || - (elementType == textInput_PrefsElement && - precedingElementType != textInput_PrefsElement && - precedingElementType != heading_PrefsElement)) { - addChild_Widget(panel, iClob(makePadding_Widget(lineHeight_Text(defaultBig_FontId)))); - } - } - if ((elementType == toggle_PrefsElement && precedingElementType != toggle_PrefsElement) || - (elementType == textInput_PrefsElement && precedingElementType != textInput_PrefsElement)) { - flags |= borderTop_WidgetFlag; - } - return addChildFlags_Widget(panel, child, flags); - } - return NULL; -} - -static void stripTrailingColon_(iLabelWidget *label) { - const iString *text = text_LabelWidget(label); - if (endsWith_String(text, ":")) { - iString *mod = copy_String(text); - removeEnd_String(mod, 1); - updateText_LabelWidget(label, mod); - delete_String(mod); - } -} - -static iLabelWidget *makePanelButton_(const char *text, const char *command) { - iLabelWidget *btn = new_LabelWidget(text, command); - setFlags_Widget(as_Widget(btn), - borderBottom_WidgetFlag | alignLeft_WidgetFlag | - frameless_WidgetFlag | extraPadding_WidgetFlag, - iTrue); - checkIcon_LabelWidget(btn); - setFont_LabelWidget(btn, defaultBig_FontId); - setTextColor_LabelWidget(btn, uiTextStrong_ColorId); - setBackgroundColor_Widget(as_Widget(btn), uiBackgroundSidebar_ColorId); - return btn; -} - -static iWidget *makeValuePadding_(iWidget *value) { - iInputWidget *input = isInstance_Object(value, &Class_InputWidget) ? (iInputWidget *) value : NULL; - if (input) { - setFont_InputWidget(input, defaultBig_FontId); - setContentPadding_InputWidget(input, 3 * gap_UI, 3 * gap_UI); - } - iWidget *pad = new_Widget(); - setBackgroundColor_Widget(pad, uiBackgroundSidebar_ColorId); - setPadding_Widget(pad, 0, 1 * gap_UI, 0, 1 * gap_UI); - addChild_Widget(pad, iClob(value)); - setFlags_Widget(pad, - borderBottom_WidgetFlag | - arrangeVertical_WidgetFlag | - resizeToParentWidth_WidgetFlag | - resizeWidthOfChildren_WidgetFlag | - arrangeHeight_WidgetFlag, - iTrue); - return pad; -} - -static iWidget *makeValuePaddingWithHeading_(iLabelWidget *heading, iWidget *value) { - iWidget *div = new_Widget(); - setFlags_Widget(div, - borderBottom_WidgetFlag | arrangeHeight_WidgetFlag | - resizeWidthOfChildren_WidgetFlag | - arrangeHorizontal_WidgetFlag, iTrue); - setBackgroundColor_Widget(div, uiBackgroundSidebar_ColorId); - setPadding_Widget(div, gap_UI, gap_UI, 4 * gap_UI, gap_UI); - addChildFlags_Widget(div, iClob(heading), 0); - //setFixedSize_Widget(as_Widget(heading), init_I2(-1, height_Widget(value))); - setFont_LabelWidget(heading, defaultBig_FontId); - setTextColor_LabelWidget(heading, uiTextStrong_ColorId); - if (isInstance_Object(value, &Class_InputWidget)) { - addChildFlags_Widget(div, iClob(value), expand_WidgetFlag); - } - else if (isInstance_Object(value, &Class_LabelWidget) && - cmp_String(command_LabelWidget((iLabelWidget *) value), "toggle")) { - addChildFlags_Widget(div, iClob(value), expand_WidgetFlag); - /* TODO: This doesn't work? */ -// setCommand_LabelWidget(heading, -// collectNewFormat_String("!%s ptr:%p", -// cstr_String(command_LabelWidget((iLabelWidget *) value)), -// value)); - } - else { - addChildFlags_Widget(div, iClob(new_Widget()), expand_WidgetFlag); - addChild_Widget(div, iClob(value)); - } - return div; -} - -static iWidget *addChildPanel_(iWidget *sheet, iLabelWidget *panelButton, - const iString *titleText) { - iWidget *owner = new_Widget(); - setId_Widget(owner, "panel"); - setUserData_Object(panelButton, owner); - setBackgroundColor_Widget(owner, uiBackground_ColorId); - setId_Widget(addChild_Widget(owner, iClob(makePadding_Widget(0))), "panel.toppad"); - if (titleText) { - iLabelWidget *title = - addChildFlags_Widget(owner, - iClob(new_LabelWidget(cstr_String(titleText), NULL)), - alignLeft_WidgetFlag | frameless_WidgetFlag); - setFont_LabelWidget(title, uiLabelLargeBold_FontId); - setTextColor_LabelWidget(title, uiHeading_ColorId); - } - addChildFlags_Widget(sheet, - iClob(owner), - focusRoot_WidgetFlag | hidden_WidgetFlag | disabled_WidgetFlag | - arrangeVertical_WidgetFlag | resizeWidthOfChildren_WidgetFlag | - arrangeHeight_WidgetFlag | overflowScrollable_WidgetFlag | - horizontalOffset_WidgetFlag | edgeDraggable_WidgetFlag | - commandOnClick_WidgetFlag); - return owner; -} - -void finalizeSheet_Widget(iWidget *sheet) { - /* The sheet contents are completely rearranged and restyled 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. 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"). At least this way it is possible to enforce - a consistent styling. */ - if (deviceType_App() == phone_AppDeviceType && parent_Widget(sheet) == root_Widget(sheet)) { - if (~flags_Widget(sheet) & keepOnTop_WidgetFlag) { - /* Already finalized. */ - arrange_Widget(sheet); - postRefresh_App(); - return; - } - /* Modify the top sheet to act as a fullscreen background. */ - setPadding1_Widget(sheet, 0); - setBackgroundColor_Widget(sheet, uiBackground_ColorId); - setFlags_Widget(sheet, - keepOnTop_WidgetFlag | - parentCannotResize_WidgetFlag | - arrangeSize_WidgetFlag | - centerHorizontal_WidgetFlag | - arrangeVertical_WidgetFlag | - arrangeHorizontal_WidgetFlag | - overflowScrollable_WidgetFlag, - iFalse); - setFlags_Widget(sheet, - commandOnClick_WidgetFlag | - frameless_WidgetFlag | - resizeWidthOfChildren_WidgetFlag | - edgeDraggable_WidgetFlag, - iTrue); - iPtrArray * contents = collect_PtrArray(new_PtrArray()); /* two-column pages */ - iPtrArray * panelButtons = collect_PtrArray(new_PtrArray()); - iWidget * prefsTabs = findChild_Widget(sheet, "prefs.tabs"); - iWidget * dialogHeading = (prefsTabs ? NULL : child_Widget(sheet, 0)); - const iBool isPrefs = (prefsTabs != NULL); - const int64_t panelButtonFlags = borderBottom_WidgetFlag | alignLeft_WidgetFlag | - frameless_WidgetFlag | extraPadding_WidgetFlag; - iWidget *topPanel = new_Widget(); - setFlags_Widget(topPanel, topPanelOffset_WidgetFlag, iTrue); /* slide with children */ - setId_Widget(topPanel, "panel.top"); - addChild_Widget(topPanel, iClob(makePadding_Widget(lineHeight_Text(defaultBig_FontId)))); - if (prefsTabs) { - 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(prefsTabs, "tabs.pages"); - size_t pageCount = tabCount_Widget(prefsTabs); - for (size_t i = 0; i < pageCount; i++) { - iString *text = copy_String(text_LabelWidget(tabPageButton_Widget(prefsTabs, tabPage_Widget(prefsTabs, 0)))); - iWidget *page = removeTabPage_Widget(prefsTabs, 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 = makePanelButton_( - i == 1 ? "${heading.prefs.userinterface}" : cstr_String(text), - "panel.open")), - (i == 0 ? borderTop_WidgetFlag : 0) | - chevron_WidgetFlag)); - const iChar icons[] = { - 0x02699, /* 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(prefsTabs); - } - iForEach(ObjectList, i, children_Widget(sheet)) { - iWidget *child = i.object; - if (isTwoColumnPage_(child)) { - pushBack_PtrArray(contents, removeChild_Widget(sheet, child)); - } - else { - removeChild_Widget(sheet, child); - addChild_Widget(topPanel, child); - iRelease(child); - } - } - const iBool useSlidePanels = (size_PtrArray(contents) == size_PtrArray(panelButtons)); - 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 (useSlidePanels) { - /* Create a new child panel. */ - iLabelWidget *button = at_PtrArray(panelButtons, index_PtrArrayIterator(&j)); - owner = addChildPanel_(sheet, button, - collect_String(upper_String(text_LabelWidget(button)))); - } - iWidget *pageContent = j.ptr; - iWidget *headings = child_Widget(pageContent, 0); - iWidget *values = child_Widget(pageContent, 1); - enum iPrefsElement prevElement = panelTitle_PrefsElement; - /* Identify the types of controls in the dialog and restyle/organize them. */ - while (!isEmpty_ObjectList(children_Widget(headings))) { - iWidget *heading = child_Widget(headings, 0); - iWidget *value = child_Widget(values, 0); - removeChild_Widget(headings, heading); - removeChild_Widget(values, value); - /* Can we ignore these widgets? */ - if (isOmittedPref_(id_Widget(value)) || - (class_Widget(heading) == &Class_Widget && - class_Widget(value) == &Class_Widget) /* just padding */) { - iRelease(heading); - iRelease(value); - continue; - } - enum iPrefsElement element = toggle_PrefsElement; - iLabelWidget *headingLabel = NULL; - iLabelWidget *valueLabel = NULL; - iInputWidget *valueInput = NULL; - const iBool isMenuButton = findChild_Widget(value, "menu") != NULL; - if (isInstance_Object(heading, &Class_LabelWidget)) { - headingLabel = (iLabelWidget *) heading; - stripTrailingColon_(headingLabel); - } - if (isInstance_Object(value, &Class_LabelWidget)) { - valueLabel = (iLabelWidget *) value; - setFont_LabelWidget(valueLabel, defaultBig_FontId); - } - if (isInstance_Object(value, &Class_InputWidget)) { - valueInput = (iInputWidget *) value; - setFlags_Widget(value, borderBottom_WidgetFlag, iFalse); - element = textInput_PrefsElement; - } - if (childCount_Widget(value) >= 2) { - if (isInstance_Object(child_Widget(value, 0), &Class_InputWidget)) { - element = textInput_PrefsElement; - setPadding_Widget(value, 0, 0, gap_UI, 0); - valueInput = child_Widget(value, 0); - } - } - if (valueInput) { - setFont_InputWidget(valueInput, defaultBig_FontId); - setContentPadding_InputWidget(valueInput, 3 * gap_UI, 0); - } - /* Toggles have the button on the right. */ - if (valueLabel && cmp_String(command_LabelWidget(valueLabel), "toggle") == 0) { - element = toggle_PrefsElement; - addPanelChild_(owner, - iClob(makeValuePaddingWithHeading_(headingLabel, value)), - 0, - element, - prevElement); - } - else if (valueLabel && isEmpty_String(text_LabelWidget(valueLabel))) { - element = heading_PrefsElement; - iRelease(value); - addPanelChild_(owner, iClob(heading), 0, element, prevElement); - setFont_LabelWidget(headingLabel, uiLabel_FontId); - } - else if (isMenuButton) { - element = dropdown_PrefsElement; - setFlags_Widget(value, - alignRight_WidgetFlag | noBackground_WidgetFlag | - frameless_WidgetFlag, iTrue); - setFlags_Widget(value, alignLeft_WidgetFlag, iFalse); - iWidget *pad = addPanelChild_(owner, iClob(makeValuePaddingWithHeading_(headingLabel, value)), 0, - element, prevElement); - pad->padding[2] = gap_UI; - } - else if (valueInput) { - addPanelChild_(owner, iClob(makeValuePaddingWithHeading_(headingLabel, value)), 0, - element, prevElement); - } - else { - if (childCount_Widget(value) >= 2) { - element = radioButton_PrefsElement; - /* Always padding before radio buttons. */ - addChild_Widget(owner, iClob(makePadding_Widget(lineHeight_Text(defaultBig_FontId)))); - } - addChildFlags_Widget(owner, iClob(heading), borderBottom_WidgetFlag); - if (headingLabel) { - setTextColor_LabelWidget(headingLabel, uiSubheading_ColorId); - setText_LabelWidget(headingLabel, - collect_String(upper_String(text_LabelWidget(headingLabel)))); - } - addPanelChild_(owner, iClob(value), 0, element, prevElement); - /* Radio buttons expand to fill the space. */ - if (element == radioButton_PrefsElement) { - setBackgroundColor_Widget(value, uiBackgroundSidebar_ColorId); - setPadding_Widget(value, 4 * gap_UI, 2 * gap_UI, 4 * gap_UI, 2 * gap_UI); - setFlags_Widget(value, arrangeWidth_WidgetFlag, iFalse); - setFlags_Widget(value, - borderBottom_WidgetFlag | - resizeToParentWidth_WidgetFlag | - resizeWidthOfChildren_WidgetFlag, - iTrue); - iForEach(ObjectList, sub, children_Widget(value)) { - if (isInstance_Object(sub.object, &Class_LabelWidget)) { - iLabelWidget *opt = sub.object; - setFont_LabelWidget(opt, defaultMedium_FontId); - setFlags_Widget(as_Widget(opt), noBackground_WidgetFlag, iTrue); - } - } - } - } - prevElement = element; - } - addPanelChild_(owner, NULL, 0, 0, prevElement); - destroy_Widget(pageContent); - setFlags_Widget(owner, drawBackgroundToBottom_WidgetFlag, iTrue); - } - destroyPending_Root(sheet->root); - /* Additional elements for preferences. */ - if (isPrefs) { - addChild_Widget(topPanel, iClob(makePadding_Widget(lineHeight_Text(defaultBig_FontId)))); - addChildFlags_Widget(topPanel, - iClob(makePanelButton_(info_Icon " ${menu.help}", "!open url:about:help")), - borderTop_WidgetFlag); - iLabelWidget *aboutButton = addChildFlags_Widget(topPanel, - iClob(makePanelButton_(planet_Icon " ${menu.about}", "panel.open")), - chevron_WidgetFlag); - /* The About panel. */ { - iWidget *panel = addChildPanel_(sheet, aboutButton, NULL); - iString *msg = collectNew_String(); - setCStr_String(msg, "Lagrange " LAGRANGE_APP_VERSION); -#if defined (iPlatformAppleMobile) - appendCStr_String(msg, " (" LAGRANGE_IOS_VERSION ")"); -#endif - addChildFlags_Widget(panel, iClob(new_LabelWidget(cstr_String(msg), NULL)), - frameless_WidgetFlag); - addChildFlags_Widget(panel, - iClob(makePanelButton_(globe_Icon " By @jk@skyjake.fi", - "!open url:https://skyjake.fi/@jk")), - borderTop_WidgetFlag); - addChildFlags_Widget(panel, - iClob(makePanelButton_(clock_Icon " ${menu.releasenotes}", - "!open url:about:version")), - 0); - addChildFlags_Widget(panel, - iClob(makePanelButton_(info_Icon " ${menu.aboutpages}", - "!open url:about:about")), - 0); - addChildFlags_Widget(panel, - iClob(makePanelButton_(bug_Icon " ${menu.debug}", - "!open url:about:debug")), - 0); - } - } - else { - setFlags_Widget(topPanel, overflowScrollable_WidgetFlag, iTrue); - /* Update heading style. */ - setFont_LabelWidget((iLabelWidget *) dialogHeading, uiLabelLargeBold_FontId); - setFlags_Widget(dialogHeading, alignLeft_WidgetFlag, iTrue); - } - if (findChild_Widget(sheet, "valueinput.prompt")) { - iWidget *prompt = findChild_Widget(sheet, "valueinput.prompt"); - setFlags_Widget(prompt, alignLeft_WidgetFlag, iTrue); - iInputWidget *input = findChild_Widget(sheet, "input"); - removeChild_Widget(parent_Widget(input), input); - addChild_Widget(topPanel, iClob(makeValuePadding_(as_Widget(input)))); - } - /* Top padding for each panel, to account for the overlaid navbar. */ { - setId_Widget(addChildPos_Widget(topPanel, - iClob(makePadding_Widget(0)), front_WidgetAddPos), - "panel.toppad"); - } - /* Navbar. */ { - iWidget *navi = new_Widget(); - setId_Widget(navi, "panel.navi"); - setBackgroundColor_Widget(navi, uiBackground_ColorId); - addChild_Widget(navi, iClob(makePadding_Widget(0))); - iLabelWidget *back = addChildFlags_Widget(navi, - iClob(new_LabelWidget(leftAngle_Icon " ${panel.back}", "panel.close")), - noBackground_WidgetFlag | frameless_WidgetFlag | - alignLeft_WidgetFlag | extraPadding_WidgetFlag); - checkIcon_LabelWidget(back); - setId_Widget(as_Widget(back), "panel.back"); - setFont_LabelWidget(back, defaultBig_FontId); - if (!isPrefs) { - /* Pick up the dialog buttons for the navbar. */ - iWidget *buttons = findChild_Widget(sheet, "dialogbuttons"); - iLabelWidget *cancel = findMenuItem_Widget(buttons, "cancel"); -// if (!cancel) { -// cancel = findMenuItem_Widget(buttons, "translation.cancel"); -// } - if (cancel) { - updateText_LabelWidget(back, text_LabelWidget(cancel)); - setCommand_LabelWidget(back, command_LabelWidget(cancel)); - } - iLabelWidget *def = (iLabelWidget *) lastChild_Widget(buttons); - if (def && !cancel) { - updateText_LabelWidget(back, text_LabelWidget(def)); - setCommand_LabelWidget(back, command_LabelWidget(def)); - setFlags_Widget(as_Widget(back), alignLeft_WidgetFlag, iFalse); - setFlags_Widget(as_Widget(back), alignRight_WidgetFlag, iTrue); - setIcon_LabelWidget(back, 0); - setFont_LabelWidget(back, defaultBigBold_FontId); - } - else if (def != cancel) { - removeChild_Widget(buttons, def); - setFont_LabelWidget(def, defaultBigBold_FontId); - setFlags_Widget(as_Widget(def), - frameless_WidgetFlag | extraPadding_WidgetFlag | - noBackground_WidgetFlag, iTrue); - addChildFlags_Widget(as_Widget(back), iClob(def), moveToParentRightEdge_WidgetFlag); - updateSize_LabelWidget(def); - } - /* Action buttons are added in the bottom as extra buttons. */ { - iBool isFirstAction = iTrue; - iForEach(ObjectList, i, children_Widget(buttons)) { - if (isInstance_Object(i.object, &Class_LabelWidget) && - i.object != cancel && i.object != def) { - iLabelWidget *item = i.object; - setBackgroundColor_Widget(i.object, uiBackgroundSidebar_ColorId); - setFont_LabelWidget(item, defaultBig_FontId); - removeChild_Widget(buttons, item); - addChildFlags_Widget(topPanel, iClob(item), panelButtonFlags | - (isFirstAction ? borderTop_WidgetFlag : 0)); - updateSize_LabelWidget(item); - isFirstAction = iFalse; - } - } - } - iRelease(removeChild_Widget(parent_Widget(buttons), buttons)); - /* Styling for remaining elements. */ - iForEach(ObjectList, i, children_Widget(topPanel)) { - if (isInstance_Object(i.object, &Class_LabelWidget) && - isEmpty_String(command_LabelWidget(i.object)) && - isEmpty_String(id_Widget(i.object))) { - setFlags_Widget(i.object, alignLeft_WidgetFlag, iTrue); - if (font_LabelWidget(i.object) == uiLabel_FontId) { - setFont_LabelWidget(i.object, uiContent_FontId); - } - } - } - } - addChildFlags_Widget(sheet, iClob(navi), - drawBackgroundToVerticalSafeArea_WidgetFlag | - arrangeHeight_WidgetFlag | resizeWidthOfChildren_WidgetFlag | - resizeToParentWidth_WidgetFlag | arrangeVertical_WidgetFlag); - } - updateSheetPanelMetrics_(sheet); - iAssert(sheet->parent); - arrange_Widget(sheet->parent); - postCommand_App("widget.overflow"); /* with the correct dimensions */ -// printTree_Widget(sheet); - } - else { - arrange_Widget(sheet); - } - postRefresh_App(); -} - -void makeFilePath_Widget(iWidget * parent, - const iString *initialPath, - const char * title, - const char * acceptLabel, - const char * command) { - setFocus_Widget(NULL); -// processEvents_App(postedEventsOnly_AppEventMode); - iWidget *dlg = makeSheet_Widget(command); - setCommandHandler_Widget(dlg, filePathHandler_); - addChild_Widget(parent, iClob(dlg)); - addChildFlags_Widget(dlg, iClob(new_LabelWidget(title, NULL)), frameless_WidgetFlag); - iInputWidget *input = addChild_Widget(dlg, iClob(new_InputWidget(0))); - if (initialPath) { - setText_InputWidget(input, collect_String(makeRelative_Path(initialPath))); - } - setId_Widget(as_Widget(input), "input"); - as_Widget(input)->rect.size.x = dlg->rect.size.x; - addChild_Widget(dlg, iClob(makePadding_Widget(gap_UI))); - iWidget *div = new_Widget(); { - setFlags_Widget(div, arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag, iTrue); - addChild_Widget(div, iClob(newKeyMods_LabelWidget("${cancel}", SDLK_ESCAPE, 0, "filepath.cancel"))); - addChild_Widget(div, iClob(newKeyMods_LabelWidget(acceptLabel, SDLK_RETURN, 0, "filepath.accept"))); - } - addChild_Widget(dlg, iClob(div)); - finalizeSheet_Widget(dlg); - setFocus_Widget(as_Widget(input)); -} - static void acceptValueInput_(iWidget *dlg) { const iInputWidget *input = findChild_Widget(dlg, "input"); if (!isEmpty_String(id_Widget(dlg))) { @@ -1981,7 +1299,7 @@ iWidget *makeValueInput_Widget(iWidget *parent, const iString *initialValue, con iClob(makeDialogButtons_Widget( (iMenuItem[]){ { "${cancel}", 0, 0, NULL }, { acceptLabel, 0, 0, "valueinput.accept" } }, 2))); - finalizeSheet_Widget(dlg); + finalizeSheet_Mobile(dlg); if (parent) { setFocus_Widget(as_Widget(input)); } @@ -2056,7 +1374,7 @@ iWidget *makeQuestion_Widget(const char *title, const char *msg, addChild_Widget(dlg->root->widget, iClob(dlg)); arrange_Widget(dlg); /* BUG: This extra arrange shouldn't be needed but the dialog won't be arranged correctly unless it's here. */ - finalizeSheet_Widget(dlg); + finalizeSheet_Mobile(dlg); return dlg; } @@ -2256,23 +1574,6 @@ static void addPrefsInputWithHeading_(iWidget *headings, iWidget *values, addDialogInputWithHeading_(headings, values, format_CStr("${%s}", id), id, input); } -void setupSheetTransition_Widget(iWidget *sheet, iBool isIncoming) { - if (deviceType_App() == phone_AppDeviceType && isPortrait_App()) { - identify_Widget(sheet); - /* View transition. */ - if (isIncoming) { - setFlags_Widget(sheet, horizontalOffset_WidgetFlag, iTrue); - setVisualOffset_Widget(sheet, size_Root(sheet->root).x, 0, 0); - setVisualOffset_Widget(sheet, 0, 200, easeOut_AnimFlag); - } - else { - const iBool wasDragged = iAbs(value_Anim(&sheet->visualOffset)) > 0; - setVisualOffset_Widget(sheet, size_Root(sheet->root).x, wasDragged ? 100 : 200, - wasDragged ? 0 : easeIn_AnimFlag); - } - } -} - iWidget *makePreferences_Widget(void) { iWidget *dlg = makeSheet_Widget("prefs"); addChildFlags_Widget(dlg, @@ -2535,8 +1836,8 @@ iWidget *makePreferences_Widget(void) { iClob(makeDialogButtons_Widget( (iMenuItem[]){ { "${dismiss}", SDLK_ESCAPE, 0, "prefs.dismiss" } }, 1))); addChild_Widget(dlg->root->widget, iClob(dlg)); - finalizeSheet_Widget(dlg); - setupSheetTransition_Widget(dlg, iTrue); + finalizeSheet_Mobile(dlg); + setupSheetTransition_Mobile(dlg, iTrue); // printTree_Widget(dlg); return dlg; } @@ -2580,7 +1881,7 @@ iWidget *makeBookmarkEditor_Widget(void) { "bmed.accept" } }, 2))); addChild_Widget(get_Root()->widget, iClob(dlg)); - finalizeSheet_Widget(dlg); + finalizeSheet_Mobile(dlg); return dlg; } @@ -2709,7 +2010,7 @@ iWidget *makeFeedSettings_Widget(uint32_t bookmarkId) { arrange_Widget(dlg); as_Widget(input)->rect.size.x = 100 * gap_UI - headings->rect.size.x; addChild_Widget(get_Root()->widget, iClob(dlg)); - finalizeSheet_Widget(dlg); + finalizeSheet_Mobile(dlg); /* Initialize. */ { const iBookmark *bm = bookmarkId ? get_Bookmarks(bookmarks_App(), bookmarkId) : NULL; setText_InputWidget(findChild_Widget(dlg, "feedcfg.title"), @@ -2785,7 +2086,7 @@ iWidget *makeIdentityCreation_Widget(void) { "ident.accept" } }, 2))); addChild_Widget(get_Root()->widget, iClob(dlg)); - finalizeSheet_Widget(dlg); + finalizeSheet_Mobile(dlg); return dlg; } @@ -2882,6 +2183,6 @@ iWidget *makeTranslation_Widget(iWidget *parent) { 2))); addChild_Widget(parent, iClob(dlg)); arrange_Widget(dlg); - finalizeSheet_Widget(dlg); + finalizeSheet_Mobile(dlg); return dlg; } diff --git a/src/ui/util.h b/src/ui/util.h index 6185945f..43aeb172 100644 --- a/src/ui/util.h +++ b/src/ui/util.h @@ -22,6 +22,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once +#include "mobile.h" + #include #include #include @@ -256,8 +258,6 @@ size_t tabCount_Widget (const iWidget *tabs); /*-----------------------------------------------------------------------------------------------*/ iWidget * makeSheet_Widget (const char *id); -void finalizeSheet_Widget (iWidget *sheet); -void setupSheetTransition_Widget (iWidget *sheet, iBool isIncoming); iWidget * makeDialogButtons_Widget (const iMenuItem *actions, size_t numActions); iInputWidget *addTwoColumnDialogInputField_Widget(iWidget *headings, iWidget *values, -- cgit v1.2.3