From f3d01757dfcbe9f9e63c6041a5b2152328c5fb2a Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Mon, 2 Aug 2021 11:44:02 +0300 Subject: DocumentWidget: Redirecting between gemini/titan schemes Automatic redirects between `gemini` and `titan` are allowed, because a reasonable response to a Titan request is a redirect to a Gemini URL. Gemini may likewise redirect to a Titan URL to initiate an upload. --- src/ui/documentwidget.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'src/ui/documentwidget.c') diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 9c8be416..e1be27c2 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -1939,13 +1939,19 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) { } else { /* Only accept redirects that use gemini scheme. */ - const iString *dstUrl = absoluteUrl_String(d->mod.url, &resp->meta); + const iString *dstUrl = absoluteUrl_String(d->mod.url, &resp->meta); + const iRangecc srcScheme = urlScheme_String(d->mod.url); + const iRangecc dstScheme = urlScheme_String(dstUrl); if (d->redirectCount >= 5) { showErrorPage_DocumentWidget_(d, tooManyRedirects_GmStatusCode, dstUrl); } - else if (equalCase_Rangecc(urlScheme_String(dstUrl), - cstr_Rangecc(urlScheme_String(d->mod.url)))) { - /* Redirects with the same scheme are automatic. */ + /* Redirects with the same scheme are automatic, and switching automatically + between "gemini" and "titan" is allowed. */ + else if (equalRangeCase_Rangecc(dstScheme, srcScheme) || + (equalCase_Rangecc(srcScheme, "titan") && + equalCase_Rangecc(dstScheme, "gemini")) || + (equalCase_Rangecc(srcScheme, "gemini") && + equalCase_Rangecc(dstScheme, "titan"))) { visitUrl_Visited(visited_App(), d->mod.url, transient_VisitedUrlFlag); postCommandf_Root(as_Widget(d)->root, "open doc:%p redirect:%d url:%s", d, d->redirectCount + 1, cstr_String(dstUrl)); -- cgit v1.2.3 From 7c7af3b7425f0496203d68d9bc42be0603048a9b Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Fri, 6 Aug 2021 19:01:12 +0300 Subject: GmDocument: Full-width images in narrow layout --- src/gmdocument.c | 15 ++++++++++++--- src/gmdocument.h | 4 ++-- src/ui/documentwidget.c | 7 +++++-- 3 files changed, 19 insertions(+), 7 deletions(-) (limited to 'src/ui/documentwidget.c') diff --git a/src/gmdocument.c b/src/gmdocument.c index b9832f38..6ed628de 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c @@ -82,6 +82,7 @@ struct Impl_GmDocument { iString url; /* for resolving relative links */ iString localHost; iInt2 size; + int outsideMargin; iArray layout; /* contents of source, laid out in document space */ iPtrArray links; enum iGmDocumentBanner bannerType; @@ -896,6 +897,12 @@ static void doLayout_GmDocument_(iGmDocument *d) { pos.y += margin; run.bounds.pos = pos; run.bounds.size.x = d->size.x; + /* Extend the image to full width, including outside margin, if the viewport + is narrow enough. */ + if (d->outsideMargin < 5 * gap_UI) { + run.bounds.size.x += d->outsideMargin * 2; + run.bounds.pos.x -= d->outsideMargin; + } const float aspect = (float) imgSize.y / (float) imgSize.x; run.bounds.size.y = d->size.x * aspect; run.visBounds = run.bounds; @@ -990,6 +997,7 @@ void init_GmDocument(iGmDocument *d) { init_String(&d->url); init_String(&d->localHost); d->bannerType = siteDomain_GmDocumentBanner; + d->outsideMargin = 0; d->size = zero_I2(); init_Array(&d->layout, sizeof(iGmRun)); init_PtrArray(&d->links); @@ -1543,8 +1551,9 @@ void setBanner_GmDocument(iGmDocument *d, enum iGmDocumentBanner type) { d->bannerType = type; } -void setWidth_GmDocument(iGmDocument *d, int width) { +void setWidth_GmDocument(iGmDocument *d, int width, int outsideMargin) { d->size.x = width; + d->outsideMargin = outsideMargin; /* distance to edge of the viewport */ doLayout_GmDocument_(d); /* TODO: just flag need-layout and do it later */ } @@ -1698,7 +1707,7 @@ void setUrl_GmDocument(iGmDocument *d, const iString *url) { updateIconBasedOnUrl_GmDocument_(d); } -void setSource_GmDocument(iGmDocument *d, const iString *source, int width, +void setSource_GmDocument(iGmDocument *d, const iString *source, int width, int outsideMargin, enum iGmDocumentUpdate updateType) { // printf("[GmDocument] source update (%zu bytes), width:%d, final:%d\n", // size_String(source), width, updateType == final_GmDocumentUpdate); @@ -1713,7 +1722,7 @@ void setSource_GmDocument(iGmDocument *d, const iString *source, int width, if (isNormalized_GmDocument_(d)) { normalize_GmDocument(d); } - setWidth_GmDocument(d, width); /* re-do layout */ + setWidth_GmDocument(d, width, outsideMargin); /* re-do layout */ } void foldPre_GmDocument(iGmDocument *d, uint16_t preId) { diff --git a/src/gmdocument.h b/src/gmdocument.h index 9a7a70df..9f8ee2ef 100644 --- a/src/gmdocument.h +++ b/src/gmdocument.h @@ -174,11 +174,11 @@ enum iGmDocumentUpdate { void setThemeSeed_GmDocument (iGmDocument *, const iBlock *seed); void setFormat_GmDocument (iGmDocument *, enum iSourceFormat format); void setBanner_GmDocument (iGmDocument *, enum iGmDocumentBanner type); -void setWidth_GmDocument (iGmDocument *, int width); +void setWidth_GmDocument (iGmDocument *, int width, int outsideMargin); void redoLayout_GmDocument (iGmDocument *); iBool updateOpenURLs_GmDocument(iGmDocument *); void setUrl_GmDocument (iGmDocument *, const iString *url); -void setSource_GmDocument (iGmDocument *, const iString *source, int width, +void setSource_GmDocument (iGmDocument *, const iString *source, int width, int outsideMargin, enum iGmDocumentUpdate updateType); void foldPre_GmDocument (iGmDocument *, uint16_t preId); diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index e1be27c2..8de1162f 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -1054,9 +1054,12 @@ static void documentWasChanged_DocumentWidget_(iDocumentWidget *d) { void setSource_DocumentWidget(iDocumentWidget *d, const iString *source) { setUrl_GmDocument(d->doc, d->mod.url); + const int docWidth = documentWidth_DocumentWidget_(d); + const int outsideMargin = (width_Widget(d) - docWidth) / 2; setSource_GmDocument(d->doc, source, - documentWidth_DocumentWidget_(d), + docWidth, + outsideMargin, isFinished_GmRequest(d->request) ? final_GmDocumentUpdate : partial_GmDocumentUpdate); documentWasChanged_DocumentWidget_(d); @@ -2249,7 +2252,7 @@ static iBool updateDocumentWidthRetainingScrollPosition_DocumentWidget_(iDocumen /* TODO: First *fully* visible run? */ voffset = visibleRange_DocumentWidget_(d).start - top_Rect(run->visBounds); } - setWidth_GmDocument(d->doc, newWidth); + setWidth_GmDocument(d->doc, newWidth, (width_Widget(d) - newWidth) / 2); documentRunsInvalidated_DocumentWidget_(d); if (runLoc && !keepCenter) { run = findRunAtLoc_GmDocument(d->doc, runLoc); -- cgit v1.2.3 From e9e496ffb64d83a55162d38ffc6d87a364bb6a95 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Fri, 20 Aug 2021 09:07:09 +0300 Subject: Added WebP decoding using libwebp --- CMakeLists.txt | 5 +++++ Depends.cmake | 1 + README.md | 1 + res/about/version.gmi | 4 ++++ src/gmdocument.c | 5 ++++- src/gmutil.c | 54 +++++++++++++++++++++++++++++-------------------- src/gmutil.h | 1 + src/media.c | 16 +++++++++++++-- src/ui/documentwidget.c | 30 ++++++++++++++++++++------- 9 files changed, 85 insertions(+), 32 deletions(-) (limited to 'src/ui/documentwidget.c') diff --git a/CMakeLists.txt b/CMakeLists.txt index 73e2b8d4..262bbcb9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,6 +48,7 @@ option (ENABLE_MPG123 "Use mpg123 for decoding MPEG audio" ON) option (ENABLE_RELATIVE_EMBED "Resources should always be found via relative path" OFF) option (ENABLE_RESIZE_DRAW "Force window to redraw during resizing" ${DEFAULT_RESIZE_DRAW}) option (ENABLE_RESOURCE_EMBED "Embed resources inside the executable" OFF) +option (ENABLE_WEBP "Use libwebp to decode .webp images (via pkg-config)" ON) option (ENABLE_WINDOWPOS_FIX "Set position after showing window (workaround for SDL bug)" OFF) option (ENABLE_X11_SWRENDER "Use software rendering under X11" OFF) @@ -315,6 +316,10 @@ if (ENABLE_MPG123 AND MPG123_FOUND) target_compile_definitions (app PUBLIC LAGRANGE_ENABLE_MPG123=1) target_link_libraries (app PUBLIC PkgConfig::MPG123) endif () +if (ENABLE_WEBP AND WEBP_FOUND) + target_compile_definitions (app PUBLIC LAGRANGE_ENABLE_WEBP=1) + target_link_libraries (app PUBLIC PkgConfig::WEBP) +endif () if (ENABLE_IDLE_SLEEP) target_compile_definitions (app PUBLIC LAGRANGE_ENABLE_IDLE_SLEEP=1) endif () diff --git a/Depends.cmake b/Depends.cmake index ef95c9f4..86a1b47d 100644 --- a/Depends.cmake +++ b/Depends.cmake @@ -149,3 +149,4 @@ endif () find_package (PkgConfig REQUIRED) pkg_check_modules (SDL2 REQUIRED sdl2) pkg_check_modules (MPG123 IMPORTED_TARGET libmpg123) +pkg_check_modules (WEBP IMPORTED_TARGET libwebp) diff --git a/README.md b/README.md index 3d9df1db..13b3f9d2 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,7 @@ Note that the `install` target also deploys an XDG .desktop file for launching t | `ENABLE_MPG123` | Use the mpg123 library for decoding MPEG audio files. | | `ENABLE_RELATIVE_EMBED` | Locate resources only in relation to the executable. Useful when any system/predefined directories are not supposed to be accessed, e.g., in the Windows portable build. | | `ENABLE_RESOURCE_EMBED` | Embed all resource files into the Lagrange executable instead of keeping them in a separate file that gets loaded at launch. Setting this **ON** makes it much slower to run CMake and to compile Lagrange. | +| `ENABLE_WEBP` | Use libwebp to decode .webp images, if `pkg-config` can find the library. | | `ENABLE_WINDOWPOS_FIX` | Set correct window position after the window has already been shown. This may be necessary on some platforms to prevent the window from being restored to the wrong position. | | `ENABLE_X11_SWRENDER` | Default to software rendering when running under X11. By default Lagrange attempts to use the GPU for rendering the user interface. You can also use the `--sw` option at launch to force software rendering. | diff --git a/res/about/version.gmi b/res/about/version.gmi index 89e81047..5141a4b1 100644 --- a/res/about/version.gmi +++ b/res/about/version.gmi @@ -6,6 +6,10 @@ ``` # Release notes +## 1.7 +* Added support for viewing WebP images. The libwebp library is an optional dependency and will be included in the build if found via pkg-config. +* Added a footer action to view `application/octet-stream` content depending on recognized file extensions. + ## 1.6.4 * Local files containing UTF-8 text can be viewed regardless of their file extension. * Fixed input field cursor positioning and insertion problems around Emoji variation selectors. diff --git a/src/gmdocument.c b/src/gmdocument.c index 6ed628de..75f6f06b 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c @@ -240,7 +240,10 @@ static iRangecc addLink_GmDocument_(iGmDocument *d, iRangecc line, iGmLinkId *li iString *path = newRange_String(parts.path); if (endsWithCase_String(path, ".gif") || endsWithCase_String(path, ".jpg") || endsWithCase_String(path, ".jpeg") || endsWithCase_String(path, ".png") || - endsWithCase_String(path, ".tga") || endsWithCase_String(path, ".psd") || + endsWithCase_String(path, ".tga") || endsWithCase_String(path, ".psd") || +#if defined (LAGRANGE_ENABLE_WEBP) + endsWithCase_String(path, ".webp") || +#endif endsWithCase_String(path, ".hdr") || endsWithCase_String(path, ".pic")) { link->flags |= imageFileExtension_GmLinkFlag; } diff --git a/src/gmutil.c b/src/gmutil.c index 9bd74ee0..d547d27d 100644 --- a/src/gmutil.c +++ b/src/gmutil.c @@ -511,54 +511,64 @@ const iString *findContainerArchive_Path(const iString *path) { return NULL; } -const char *mediaType_Path(const iString *path) { - if (endsWithCase_String(path, ".gmi") || endsWithCase_String(path, ".gemini")) { +const char *mediaTypeFromFileExtension_String(const iString *d) { + if (endsWithCase_String(d, ".gmi") || endsWithCase_String(d, ".gemini")) { return "text/gemini; charset=utf-8"; } - else if (endsWithCase_String(path, ".pem")) { + else if (endsWithCase_String(d, ".pem")) { return "application/x-pem-file"; } - else if (endsWithCase_String(path, ".zip")) { + else if (endsWithCase_String(d, ".zip")) { return "application/zip"; } - else if (endsWithCase_String(path, ".gpub")) { + else if (endsWithCase_String(d, ".gpub")) { return "application/gpub+zip"; } - else if (endsWithCase_String(path, ".xml")) { + else if (endsWithCase_String(d, ".xml")) { return "text/xml"; } - else if (endsWithCase_String(path, ".png")) { + else if (endsWithCase_String(d, ".png")) { return "image/png"; } - else if (endsWithCase_String(path, ".jpg") || endsWithCase_String(path, ".jpeg")) { + else if (endsWithCase_String(d, ".webp")) { + return "image/webp"; + } + else if (endsWithCase_String(d, ".jpg") || endsWithCase_String(d, ".jpeg")) { return "image/jpeg"; } - else if (endsWithCase_String(path, ".gif")) { + else if (endsWithCase_String(d, ".gif")) { return "image/gif"; } - else if (endsWithCase_String(path, ".wav")) { + else if (endsWithCase_String(d, ".wav")) { return "audio/wave"; } - else if (endsWithCase_String(path, ".ogg")) { + else if (endsWithCase_String(d, ".ogg")) { return "audio/ogg"; } - else if (endsWithCase_String(path, ".mp3")) { + else if (endsWithCase_String(d, ".mp3")) { return "audio/mpeg"; } - else if (endsWithCase_String(path, ".mid")) { + else if (endsWithCase_String(d, ".mid")) { return "audio/midi"; } - else if (endsWithCase_String(path, ".txt") || - endsWithCase_String(path, ".md") || - endsWithCase_String(path, ".c") || - endsWithCase_String(path, ".h") || - endsWithCase_String(path, ".cc") || - endsWithCase_String(path, ".hh") || - endsWithCase_String(path, ".cpp") || - endsWithCase_String(path, ".hpp")) { + else if (endsWithCase_String(d, ".txt") || + endsWithCase_String(d, ".md") || + endsWithCase_String(d, ".c") || + endsWithCase_String(d, ".h") || + endsWithCase_String(d, ".cc") || + endsWithCase_String(d, ".hh") || + endsWithCase_String(d, ".cpp") || + endsWithCase_String(d, ".hpp")) { return "text/plain"; } - const char *mtype = "application/octet-stream"; + return "application/octet-stream"; +} + +const char *mediaType_Path(const iString *path) { + const char *mtype = mediaTypeFromFileExtension_String(path); + if (iCmpStr(mtype, "application/octet-stream")) { + return mtype; /* extension recognized */ + } /* If the file is reasonably small and looks like UTF-8, we'll display it as text/plain. */ if (fileExists_FileInfo(path) && fileSize_FileInfo(path) <= 5000000) { iFile *f = new_File(path); diff --git a/src/gmutil.h b/src/gmutil.h index f8491781..3c10d45b 100644 --- a/src/gmutil.h +++ b/src/gmutil.h @@ -133,6 +133,7 @@ const iString * withSpacesEncoded_String(const iString *); const iString * canonicalUrl_String (const iString *); const char * mediaType_Path (const iString *path); +const char * mediaTypeFromFileExtension_String (const iString *); iRangecc mediaTypeWithoutParameters_Rangecc (iRangecc mime); const iString * findContainerArchive_Path (const iString *path); diff --git a/src/media.c b/src/media.c index eb4a8311..5240ab5d 100644 --- a/src/media.c +++ b/src/media.c @@ -30,6 +30,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "stb_image.h" #include "stb_image_resize.h" +#if defined (LAGRANGE_ENABLE_WEBP) +# include +#endif + #include #include #include @@ -86,8 +90,16 @@ void deinit_GmImage(iGmImage *d) { void makeTexture_GmImage(iGmImage *d) { iBlock *data = &d->partialData; d->numBytes = size_Block(data); - uint8_t *imgData = stbi_load_from_memory( - constData_Block(data), size_Block(data), &d->size.x, &d->size.y, NULL, 4); + uint8_t *imgData = NULL; + if (cmp_String(&d->props.mime, "image/webp") == 0) { +#if defined (LAGRANGE_ENABLE_WEBP) + imgData = WebPDecodeRGBA(constData_Block(data), size_Block(data), &d->size.x, &d->size.y); +#endif + } + else { + imgData = stbi_load_from_memory( + constData_Block(data), size_Block(data), &d->size.x, &d->size.y, NULL, 4); + } if (!imgData) { d->size = zero_I2(); d->texture = NULL; diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 3f655db5..83f38dee 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -1168,13 +1168,25 @@ static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode iString *key = collectNew_String(); toString_Sym(SDLK_s, KMOD_PRIMARY, key); appendFormat_String(src, "\n```\n%s\n```\n", cstr_String(meta)); - makeFooterButtons_DocumentWidget_( - d, - (iMenuItem[]){ { translateCStr_Lang(download_Icon " " saveToDownloads_Label), - 0, - 0, - "document.save" } }, - 1); + const char *mtype = mediaTypeFromFileExtension_String(d->mod.url); + iArray items; + init_Array(&items, sizeof(iMenuItem)); + if (iCmpStr(mtype, "application/octet-stream")) { + pushBack_Array( + &items, + &(iMenuItem){ translateCStr_Lang(format_CStr("View as \"%s\"", mtype)), + SDLK_RETURN, + 0, + format_CStr("document.setmediatype mime:%s", mtype) }); + } + pushBack_Array( + &items, + &(iMenuItem){ translateCStr_Lang(download_Icon " " saveToDownloads_Label), + 0, + 0, + "document.save" }); + makeFooterButtons_DocumentWidget_(d, data_Array(&items), size_Array(&items)); + deinit_Array(&items); break; } default: @@ -3176,6 +3188,10 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) document_App() == d) { return handleSwipe_DocumentWidget_(d, cmd); } + else if (equal_Command(cmd, "document.setmediatype") && document_App() == d) { + setUrlAndSource_DocumentWidget(d, d->mod.url, string_Command(cmd, "mime"), &d->sourceContent); + return iTrue; + } return iFalse; } -- cgit v1.2.3 From c3e5a0c2d4168515804dc0823c93d0522982108c Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Sat, 4 Sep 2021 19:30:36 +0300 Subject: iOS: Minor fixes The SDL text input rectangle function applies some sort of offset that is inappropriate on iOS. --- CMakeLists.txt | 2 +- src/ui/documentwidget.c | 6 ++++++ src/ui/inputwidget.c | 4 +++- src/ui/root.c | 8 ++++++++ 4 files changed, 18 insertions(+), 2 deletions(-) (limited to 'src/ui/documentwidget.c') diff --git a/CMakeLists.txt b/CMakeLists.txt index c3793eea..78b78562 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,7 +25,7 @@ project (Lagrange set (COPYRIGHT_YEAR 2021) if (IOS) set (PROJECT_VERSION 1.4) # pinned for TestFlight - set (IOS_BUNDLE_VERSION 21.9.4) + set (IOS_BUNDLE_VERSION 21.9.5) endif () # Default that depend on environment. diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 76e843d5..e280bc84 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -3676,10 +3676,16 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e { "---", 0, 0, NULL }, { book_Icon " ${menu.page.import}", 0, 0, "bookmark.links confirm:1" }, { globe_Icon " ${menu.page.translate}", 0, 0, "document.translate" }, +#if defined (iPlatformMobile) + { "---", 0, 0, NULL }, + { "${menu.page.copyurl}", 0, 0, "document.copylink" } }, + 14); +#else { upload_Icon " ${menu.page.upload}", 0, 0, "document.upload" }, { "---", 0, 0, NULL }, { "${menu.page.copyurl}", 0, 0, "document.copylink" } }, 15); +#endif if (isEmpty_Range(&d->selectMark)) { pushBackN_Array( &items, diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c index 690107a2..f1b21922 100644 --- a/src/ui/inputwidget.c +++ b/src/ui/inputwidget.c @@ -542,8 +542,10 @@ static int contentHeight_InputWidget_(const iInputWidget *d) { } static void updateTextInputRect_InputWidget_(const iInputWidget *d) { +#if !defined (iPlatformAppleMobile) const iRect bounds = bounds_Widget(constAs_Widget(d)); - SDL_SetTextInputRect(&(SDL_Rect){ bounds.pos.x, bounds.pos.y, bounds.size.x, bounds.size.y }); + SDL_SetTextInputRect(&(SDL_Rect){ bounds.pos.x, bounds.pos.y, bounds.size.x, bounds.size.y }); +#endif } static void updateMetrics_InputWidget_(iInputWidget *d) { diff --git a/src/ui/root.c b/src/ui/root.c index a72f002c..0b55d250 100644 --- a/src/ui/root.c +++ b/src/ui/root.c @@ -1163,12 +1163,20 @@ void createUserInterface_Root(iRoot *d) { { 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" }, +#if defined (iPlatformMobile) + { "---", 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" } }, + 11); +#else { upload_Icon " ${menu.page.upload}", 0, 0, "document.upload" }, { "---", 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); +#endif setId_Widget(as_Widget(pageMenuButton), "pagemenubutton"); setFont_LabelWidget(pageMenuButton, uiContentBold_FontId); setAlignVisually_LabelWidget(pageMenuButton, iTrue); -- cgit v1.2.3 From 4ce9a07bbd4a4a82ff0b310d9c9e0768febe8d61 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Tue, 7 Sep 2021 22:10:29 +0300 Subject: Mobile: Redoing Preferences Contents of the Preferences split panel view are created based on arrays of MenuItems. This removes the confusing indirection of trying to modify the desktop widget tree to fit mobile. --- src/gmdocument.h | 1 + src/ui/documentwidget.c | 3 + src/ui/labelwidget.c | 38 ++++++-- src/ui/labelwidget.h | 4 +- src/ui/mobile.c | 205 +++++++++++++++++++++++++++++++++++++++- src/ui/mobile.h | 11 ++- src/ui/util.c | 247 ++++++++++++++++++++++++++++++++++-------------- src/ui/util.h | 5 +- src/ui/widget.c | 1 + src/ui/window.c | 3 + 10 files changed, 426 insertions(+), 92 deletions(-) (limited to 'src/ui/documentwidget.c') diff --git a/src/gmdocument.h b/src/gmdocument.h index 9f8ee2ef..332c3e00 100644 --- a/src/gmdocument.h +++ b/src/gmdocument.h @@ -59,6 +59,7 @@ enum iGmDocumentTheme { white_GmDocumentTheme, sepia_GmDocumentTheme, highContrast_GmDocumentTheme, + max_GmDocumentTheme }; iBool isDark_GmDocumentTheme(enum iGmDocumentTheme); diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index e280bc84..6ca4fd8d 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -1010,6 +1010,9 @@ static void documentRunsInvalidated_DocumentWidget_(iDocumentWidget *d) { } iBool isPinned_DocumentWidget_(const iDocumentWidget *d) { + if (deviceType_App() == phone_AppDeviceType) { + return iFalse; + } if (d->flags & otherRootByDefault_DocumentWidgetFlag) { return iTrue; } diff --git a/src/ui/labelwidget.c b/src/ui/labelwidget.c index 3bfa600b..b84e17f4 100644 --- a/src/ui/labelwidget.c +++ b/src/ui/labelwidget.c @@ -44,11 +44,13 @@ struct Impl_LabelWidget { iString command; iClick click; struct { - uint8_t alignVisual : 1; /* align according to visible bounds, not font metrics */ - uint8_t noAutoMinHeight : 1; /* minimum height is not set automatically */ - uint8_t drawAsOutline : 1; /* draw as outline, filled with background color */ - uint8_t noTopFrame : 1; - uint8_t wrap : 1; + uint8_t alignVisual : 1; /* align according to visible bounds, not font metrics */ + uint8_t noAutoMinHeight : 1; /* minimum height is not set automatically */ + uint8_t drawAsOutline : 1; /* draw as outline, filled with background color */ + uint8_t noTopFrame : 1; + uint8_t wrap : 1; + uint8_t allCaps : 1; + uint8_t removeTrailingColon : 1; } flags; }; @@ -442,11 +444,18 @@ void updateSize_LabelWidget(iLabelWidget *d) { static void replaceVariables_LabelWidget_(iLabelWidget *d) { translate_Lang(&d->label); + if (d->flags.allCaps) { + set_String(&d->label, collect_String(upper_String(&d->label))); + } + if (d->flags.removeTrailingColon && endsWith_String(&d->label, ":")) { + removeEnd_String(&d->label, 1); + } } void init_LabelWidget(iLabelWidget *d, const char *label, const char *cmd) { iWidget *w = &d->widget; init_Widget(w); + iZap(d->flags); d->font = uiLabel_FontId; d->forceFg = none_ColorId; d->icon = 0; @@ -464,11 +473,6 @@ void init_LabelWidget(iLabelWidget *d, const char *label, const char *cmd) { d->kmods = 0; init_Click(&d->click, d, !isEmpty_String(&d->command) ? SDL_BUTTON_LEFT : 0); setFlags_Widget(w, hover_WidgetFlag, d->click.button != 0); - d->flags.alignVisual = iFalse; - d->flags.noAutoMinHeight = iFalse; - d->flags.drawAsOutline = iFalse; - d->flags.noTopFrame = iFalse; - d->flags.wrap = iFalse; updateSize_LabelWidget(d); updateKey_LabelWidget_(d); /* could be bound to another key */ } @@ -525,6 +529,20 @@ void setOutline_LabelWidget(iLabelWidget *d, iBool drawAsOutline) { } } +void setAllCaps_LabelWidget(iLabelWidget *d, iBool allCaps) { + if (d) { + d->flags.allCaps = allCaps; + replaceVariables_LabelWidget_(d); + } +} + +void setRemoveTrailingColon_LabelWidget(iLabelWidget *d, iBool removeTrailingColon) { + if (d) { + d->flags.removeTrailingColon = removeTrailingColon; + replaceVariables_LabelWidget_(d); + } +} + void updateText_LabelWidget(iLabelWidget *d, const iString *text) { set_String(&d->label, text); set_String(&d->srcLabel, text); diff --git a/src/ui/labelwidget.h b/src/ui/labelwidget.h index b8b6fd87..6275d2c8 100644 --- a/src/ui/labelwidget.h +++ b/src/ui/labelwidget.h @@ -30,10 +30,12 @@ iDeclareWidgetClass(LabelWidget) iDeclareObjectConstructionArgs(LabelWidget, const char *label, const char *command) void setAlignVisually_LabelWidget(iLabelWidget *, iBool alignVisual); -void setNoAutoMinHeight_LabelWidget(iLabelWidget *, iBool noAutoMinHeight); +void setNoAutoMinHeight_LabelWidget (iLabelWidget *, iBool noAutoMinHeight); void setNoTopFrame_LabelWidget (iLabelWidget *, iBool noTopFrame); void setWrap_LabelWidget (iLabelWidget *, iBool wrap); void setOutline_LabelWidget (iLabelWidget *, iBool drawAsOutline); +void setAllCaps_LabelWidget (iLabelWidget *, iBool allCaps); +void setRemoveTrailingColon_LabelWidget (iLabelWidget *, iBool removeTrailingColon); void setFont_LabelWidget (iLabelWidget *, int fontId); void setTextColor_LabelWidget (iLabelWidget *, int color); void setText_LabelWidget (iLabelWidget *, const iString *text); /* resizes widget */ diff --git a/src/ui/mobile.c b/src/ui/mobile.c index 168a92b8..4ccbb0cb 100644 --- a/src/ui/mobile.c +++ b/src/ui/mobile.c @@ -57,7 +57,6 @@ static enum iFontId labelBoldFont_(void) { static void updatePanelSheetMetrics_(iWidget *sheet) { iWidget *navi = findChild_Widget(sheet, "panel.navi"); -// iWidget *naviPad = child_Widget(navi, 0); int naviHeight = lineHeight_Text(labelFont_()) + 4 * gap_UI; #if defined (iPlatformMobile) float left = 0.0f, right = 0.0f, top = 0.0f, bottom = 0.0f; @@ -339,7 +338,7 @@ static iWidget *makeValuePaddingWithHeading_(iLabelWidget *heading, iWidget *val addChildFlags_Widget(div, iClob(new_Widget()), expand_WidgetFlag); addChild_Widget(div, iClob(value)); } - printTree_Widget(div); +// printTree_Widget(div); return div; } @@ -369,6 +368,205 @@ static iWidget *addChildPanel_(iWidget *parent, iLabelWidget *panelButton, } void finalizeSheet_Mobile(iWidget *sheet) { + arrange_Widget(sheet); +// postRefresh_App(); +} + +static size_t countItems_(const iMenuItem *itemsNullTerminated) { + size_t num = 0; + for (; itemsNullTerminated->label; num++, itemsNullTerminated++) {} + return num; +} + +void makePanelItem_Mobile(iWidget *panel, const iMenuItem *item) { + const char * spec = item->label; + const char * id = cstr_Rangecc(range_Command(spec, "id")); + const char * label = format_CStr("${%s}", id); + iWidget * widget = NULL; + iLabelWidget *heading = NULL; + if (hasLabel_Command(spec, "device") && deviceType_App() != argLabel_Command(spec, "device")) { + return; + } + if (equal_Command(spec, "title")) { + iLabelWidget *title = addChildFlags_Widget(panel, + iClob(new_LabelWidget(label, NULL)), + alignLeft_WidgetFlag | frameless_WidgetFlag); + setFont_LabelWidget(title, uiLabelLargeBold_FontId); + setTextColor_LabelWidget(title, uiHeading_ColorId); + setAllCaps_LabelWidget(title, iTrue); + } + else if (equal_Command(spec, "heading")) { + addChild_Widget(panel, iClob(makePadding_Widget(lineHeight_Text(labelFont_())))); + heading = makeHeading_Widget(label); + setAllCaps_LabelWidget(heading, iTrue); + setRemoveTrailingColon_LabelWidget(heading, iTrue); + addChild_Widget(panel, iClob(heading)); + } + else if (equal_Command(spec, "toggle")) { + iLabelWidget *toggle = (iLabelWidget *) makeToggle_Widget(id); + setFont_LabelWidget(toggle, labelFont_()); + widget = makeValuePaddingWithHeading_(heading = makeHeading_Widget(label), + as_Widget(toggle)); + } + else if (equal_Command(spec, "dropdown")) { + const iMenuItem *dropItems = item->data; + iLabelWidget *drop = makeMenuButton_LabelWidget("", dropItems, countItems_(dropItems)); + setFont_LabelWidget(drop, labelFont_()); + setFlags_Widget(as_Widget(drop), + alignRight_WidgetFlag | noBackground_WidgetFlag | + frameless_WidgetFlag, iTrue); + setId_Widget(as_Widget(drop), id); + widget = makeValuePaddingWithHeading_(heading = makeHeading_Widget(label), as_Widget(drop)); + } + else if (equal_Command(spec, "radio")) { + addChild_Widget(panel, iClob(makePadding_Widget(lineHeight_Text(labelFont_())))); + iLabelWidget *head = makeHeading_Widget(label); + setAllCaps_LabelWidget(head, iTrue); + setRemoveTrailingColon_LabelWidget(head, iTrue); + addChild_Widget(panel, iClob(head)); + widget = new_Widget(); + setBackgroundColor_Widget(widget, uiBackgroundSidebar_ColorId); + setPadding_Widget(widget, 4 * gap_UI, 2 * gap_UI, 4 * gap_UI, 2 * gap_UI); +// setFlags_Widget(widget, arrangeWidth_WidgetFlag, iFalse); + setFlags_Widget(widget, + borderBottom_WidgetFlag | + arrangeHorizontal_WidgetFlag | + arrangeHeight_WidgetFlag | + resizeToParentWidth_WidgetFlag | + resizeWidthOfChildren_WidgetFlag, + iTrue); + setId_Widget(widget, id); + for (const iMenuItem *radioItem = item->data; radioItem->label; radioItem++) { + const char * radId = cstr_Rangecc(range_Command(radioItem->label, "id")); + const char * radLabel = hasLabel_Command(radioItem->label, "label") + ? format_CStr("${%s}", cstr_Rangecc(range_Command(radioItem->label, "label"))) + : suffixPtr_Command(radioItem->label, "text"); + iLabelWidget *radButton = new_LabelWidget(radLabel, radioItem->command); + setId_Widget(as_Widget(radButton), radId); + setFont_LabelWidget(radButton, defaultMedium_FontId); + addChildFlags_Widget(widget, iClob(radButton), radio_WidgetFlag | noBackground_WidgetFlag); + } + } + else if (equal_Command(spec, "input")) { + iInputWidget *input = new_InputWidget(argU32Label_Command(spec, "maxlen")); + setId_Widget(as_Widget(input), id); + setFont_InputWidget(input, labelFont_()); + setContentPadding_InputWidget(input, 3 * gap_UI, 0); + setUrlContent_InputWidget(input, argLabel_Command(spec, "url")); + widget = makeValuePaddingWithHeading_(heading = makeHeading_Widget(label), + as_Widget(input)); + } + else if (equal_Command(spec, "padding")) { + widget = makePadding_Widget(lineHeight_Text(labelFont_()) * 1.5f); + } + if (heading) { + setRemoveTrailingColon_LabelWidget(heading, iTrue); + const iChar icon = toInt_String(string_Command(item->label, "icon")); + if (icon) { + setIcon_LabelWidget(heading, icon); + } + } + if (widget) { + addChild_Widget(panel, iClob(widget)); + } +} + +void makePanelItems_Mobile(iWidget *panel, const iMenuItem *itemsNullTerminated) { + for (const iMenuItem *item = itemsNullTerminated; item->label; item++) { + makePanelItem_Mobile(panel, item); + } +} + +iWidget *makeSplitMultiPanel_Mobile(const iMenuItem *itemsNullTerminated) { + /* A multipanel widget has a top panel and one or more detail panels. In a horizontal layout, + the detail panels slide in from the right and cover the top panel. In a landscape layout, + the detail panels are always visible on the side. */ + iWidget *sheet = new_Widget(); + setBackgroundColor_Widget(sheet, uiBackground_ColorId); + setFlags_Widget(sheet, + resizeToParentWidth_WidgetFlag | + resizeToParentHeight_WidgetFlag | + frameless_WidgetFlag | focusRoot_WidgetFlag | commandOnClick_WidgetFlag | + overflowScrollable_WidgetFlag | leftEdgeDraggable_WidgetFlag, + iTrue); + /* The top-level split between main and detail panels. */ + iWidget *mainDetailSplit = makeHDiv_Widget(); { + setCommandHandler_Widget(mainDetailSplit, mainDetailSplitHandler_); + setFlags_Widget(mainDetailSplit, resizeHeightOfChildren_WidgetFlag, iFalse); + setId_Widget(mainDetailSplit, "mdsplit"); + addChild_Widget(sheet, iClob(mainDetailSplit)); + } + /* The panel roots. */ + 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)); + setId_Widget(addChild_Widget(topPanel, iClob(makePadding_Widget(0))), "panel.toppad"); + } + iWidget *detailStack = new_Widget(); { + setId_Widget(detailStack, "detailstack"); + setFlags_Widget(detailStack, collapse_WidgetFlag | resizeWidthOfChildren_WidgetFlag, iTrue); + addChild_Widget(mainDetailSplit, iClob(detailStack)); + } + addChild_Widget(topPanel, iClob(makePadding_Widget(lineHeight_Text(labelFont_())))); + /* Slide top panel with detail panels. */ { + setFlags_Widget(topPanel, refChildrenOffset_WidgetFlag, iTrue); + topPanel->offsetRef = detailStack; + } + /* Navigation bar at the top. */ + 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, labelFont_()); + addChildFlags_Widget(sheet, iClob(navi), + drawBackgroundToVerticalSafeArea_WidgetFlag | + arrangeHeight_WidgetFlag | resizeWidthOfChildren_WidgetFlag | + resizeToParentWidth_WidgetFlag | arrangeVertical_WidgetFlag); + } + /* Create panel contents based on provided items. */ + for (size_t i = 0; itemsNullTerminated[i].label; i++) { + const iMenuItem *item = &itemsNullTerminated[i]; + if (equal_Command(item->label, "panel")) { + const char *id = cstr_Rangecc(range_Command(item->label, "id")); + const iString *label = collectNewFormat_String("${%s}", id); + iLabelWidget *button = + addChildFlags_Widget(topPanel, + iClob(makePanelButton_(cstr_String(label), "panel.open")), + chevron_WidgetFlag | borderTop_WidgetFlag); + const iChar icon = toInt_String(string_Command(item->label, "icon")); + if (icon) { + setIcon_LabelWidget(button, icon); + } + iWidget *panel = addChildPanel_(detailStack, button, NULL); + makePanelItems_Mobile(panel, item->data); + } + else { + makePanelItem_Mobile(topPanel, item); + } + } + /* Finalize the layout. */ + addChild_Widget(sheet->root->widget, iClob(sheet)); + mainDetailSplitHandler_(mainDetailSplit, "window.resized"); /* make it resize the split */ + updatePanelSheetMetrics_(sheet); + arrange_Widget(sheet); + postCommand_App("widget.overflow"); /* with the correct dimensions */ + return sheet; +} + +#if 0 /* 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 @@ -399,7 +597,7 @@ void finalizeSheet_Mobile(iWidget *sheet) { │ │ └┤ ││ │ │└┤ ││ │ │ └───────────────────┘│ │ │ └──────┘ └─────────┴───────────────────────┘ └─────────┴ ─ ─ ─ ─ ┘ - offscreen + underneath */ /* Modify the top sheet to act as a fullscreen background. */ setPadding1_Widget(sheet, 0); @@ -774,6 +972,7 @@ void finalizeSheet_Mobile(iWidget *sheet) { } postRefresh_App(); } +#endif void setupMenuTransition_Mobile(iWidget *sheet, iBool isIncoming) { if (!useMobileSheetLayout_()) { diff --git a/src/ui/mobile.h b/src/ui/mobile.h index 44134389..50b89e32 100644 --- a/src/ui/mobile.h +++ b/src/ui/mobile.h @@ -22,11 +22,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once -#include +#include iDeclareType(Widget) - -void setupMenuTransition_Mobile (iWidget *menu, iBool isIncoming); -void setupSheetTransition_Mobile (iWidget *sheet, iBool isIncoming); +iDeclareType(MenuItem) + +iWidget * makeSplitMultiPanel_Mobile (const iMenuItem *itemsNullTerminated); + +void setupMenuTransition_Mobile (iWidget *menu, iBool isIncoming); +void setupSheetTransition_Mobile (iWidget *sheet, iBool isIncoming); void finalizeSheet_Mobile (iWidget *sheet); diff --git a/src/ui/util.c b/src/ui/util.c index 906d30ae..995b730e 100644 --- a/src/ui/util.c +++ b/src/ui/util.c @@ -707,6 +707,9 @@ iWidget *makeMenu_Widget(iWidget *parent, const iMenuItem *items, size_t n) { iBool haveIcons = iFalse; for (size_t i = 0; i < n; ++i) { const iMenuItem *item = &items[i]; + if (!item->label) { + break; + } if (equal_CStr(item->label, "---")) { addChild_Widget(menu, iClob(makeMenuSeparator_())); } @@ -1506,7 +1509,7 @@ static void addRadioButton_(iWidget *parent, const char *id, const char *label, id); } -static void addFontButtons_(iWidget *parent, const char *id) { +static const iArray *makeFontItems_(const char *id) { const struct { const char * name; enum iTextFont cfgId; @@ -1518,7 +1521,7 @@ static void addFontButtons_(iWidget *parent, const char *id) { { "Tinos", tinos_TextFont }, { "---", -1 }, { "Iosevka", iosevka_TextFont } }; - iArray *items = new_Array(sizeof(iMenuItem)); + iArray *items = collectNew_Array(sizeof(iMenuItem)); iForIndices(i, fonts) { pushBack_Array(items, &(iMenuItem){ fonts[i].name, @@ -1528,11 +1531,18 @@ static void addFontButtons_(iWidget *parent, const char *id) { ? format_CStr("!%s.set arg:%d", id, fonts[i].cfgId) : NULL }); } - iLabelWidget *button = makeMenuButton_LabelWidget("Source Sans 3", data_Array(items), size_Array(items)); - setBackgroundColor_Widget(findChild_Widget(as_Widget(button), "menu"), uiBackgroundMenu_ColorId); + pushBack_Array(items, &(iMenuItem){ NULL }); /* terminator */ + return items; +} + +static void addFontButtons_(iWidget *parent, const char *id) { + const iArray *items = makeFontItems_(id); + iLabelWidget *button = makeMenuButton_LabelWidget("Source Sans 3", + constData_Array(items), size_Array(items)); + setBackgroundColor_Widget(findChild_Widget(as_Widget(button), "menu"), + uiBackgroundMenu_ColorId); setId_Widget(as_Widget(button), format_CStr("prefs.%s", id)); addChildFlags_Widget(parent, iClob(button), alignLeft_WidgetFlag); - delete_Array(items); } #if 0 @@ -1615,7 +1625,7 @@ static void addPrefsInputWithHeading_(iWidget *headings, iWidget *values, static size_t findWidestItemLabel_(const iMenuItem *items, size_t num) { int widest = 0; size_t widestPos = iInvalidPos; - for (size_t i = 0; i < num; i++) { + for (size_t i = 0; i < num && items[i].label; i++) { const int width = measure_Text(uiLabel_FontId, translateCStr_Lang(items[i].label)) @@ -1629,6 +1639,153 @@ static size_t findWidestItemLabel_(const iMenuItem *items, size_t num) { } iWidget *makePreferences_Widget(void) { + /* Common items. */ + const iMenuItem langItems[] = { { "${lang.de} - de", 0, 0, "uilang id:de" }, + { "${lang.en} - en", 0, 0, "uilang id:en" }, + { "${lang.es} - es", 0, 0, "uilang id:es" }, + { "${lang.fi} - fi", 0, 0, "uilang id:fi" }, + { "${lang.fr} - fr", 0, 0, "uilang id:fr" }, + { "${lang.ia} - ia", 0, 0, "uilang id:ia" }, + { "${lang.ie} - ie", 0, 0, "uilang id:ie" }, + { "${lang.pl} - pl", 0, 0, "uilang id:pl" }, + { "${lang.ru} - ru", 0, 0, "uilang id:ru" }, + { "${lang.sr} - sr", 0, 0, "uilang id:sr" }, + { "${lang.tok} - tok", 0, 0, "uilang id:tok" }, + { "${lang.zh.hans} - zh", 0, 0, "uilang id:zh_Hans" }, + { "${lang.zh.hant} - zh", 0, 0, "uilang id:zh_Hant" }, + { NULL } }; + const iMenuItem returnKeyBehaviors[] = { + { "${prefs.returnkey.linebreak} " uiTextAction_ColorEscape shift_Icon return_Icon + restore_ColorEscape + " ${prefs.returnkey.accept} " uiTextAction_ColorEscape return_Icon, + 0, + 0, + format_CStr("returnkey.set arg:%d", default_ReturnKeyBehavior) }, + { "${prefs.returnkey.linebreak} " uiTextAction_ColorEscape return_Icon restore_ColorEscape + " ${prefs.returnkey.accept} " uiTextAction_ColorEscape shift_Icon return_Icon, + 0, + 0, + format_CStr("returnkey.set arg:%d", acceptWithShift_ReturnKeyBehavior) }, + { "${prefs.returnkey.linebreak} " uiTextAction_ColorEscape return_Icon restore_ColorEscape + " ${prefs.returnkey.accept} " uiTextAction_ColorEscape +#if defined (iPlatformApple) + "\u2318" return_Icon, +#else + "Ctrl" return_Icon, +#endif + 0, + 0, + format_CStr("returnkey.set arg:%d", acceptWithPrimaryMod_ReturnKeyBehavior) }, + { NULL } + }; + iMenuItem docThemes[2][max_GmDocumentTheme + 1]; + for (int i = 0; i < 2; ++i) { + const iBool isDark = (i == 0); + const char *mode = isDark ? "dark" : "light"; + const iMenuItem items[max_GmDocumentTheme + 1] = { + { "${prefs.doctheme.name.colorfuldark}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, colorfulDark_GmDocumentTheme) }, + { "${prefs.doctheme.name.colorfullight}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, colorfulLight_GmDocumentTheme) }, + { "${prefs.doctheme.name.black}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, black_GmDocumentTheme) }, + { "${prefs.doctheme.name.gray}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, gray_GmDocumentTheme) }, + { "${prefs.doctheme.name.white}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, white_GmDocumentTheme) }, + { "${prefs.doctheme.name.sepia}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, sepia_GmDocumentTheme) }, + { "${prefs.doctheme.name.highcontrast}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, highContrast_GmDocumentTheme) }, + { NULL } + }; + memcpy(docThemes[i], items, sizeof(items)); + } + const iMenuItem imgStyles[] = { + { "${prefs.imagestyle.original}", 0, 0, format_CStr("imagestyle.set arg:%d", original_ImageStyle) }, + { "${prefs.imagestyle.grayscale}", 0, 0, format_CStr("imagestyle.set arg:%d", grayscale_ImageStyle) }, + { "${prefs.imagestyle.bgfg}", 0, 0, format_CStr("imagestyle.set arg:%d", bgFg_ImageStyle) }, + { "${prefs.imagestyle.text}", 0, 0, format_CStr("imagestyle.set arg:%d", textColorized_ImageStyle) }, + { "${prefs.imagestyle.preformat}", 0, 0, format_CStr("imagestyle.set arg:%d", preformatColorized_ImageStyle) }, + { NULL } + }; + /* Create the Preferences UI. */ + if (deviceType_App() != desktop_AppDeviceType) { + const iMenuItem pinSplit[] = { + { "button id:prefs.pinsplit.0 label:prefs.pinsplit.none", 0, 0, "pinsplit.set arg:0" }, + { "button id:prefs.pinsplit.1 label:prefs.pinsplit.left", 0, 0, "pinsplit.set arg:1" }, + { "button id:prefs.pinsplit.2 label:prefs.pinsplit.right", 0, 0, "pinsplit.set arg:2" }, + { NULL } + }; + const iMenuItem themeItems[] = { + { "button id:prefs.theme.0 label:prefs.theme.black", 0, 0, "theme.set arg:0" }, + { "button id:prefs.theme.1 label:prefs.theme.dark", 0, 0, "theme.set arg:1" }, + { "button id:prefs.theme.2 label:prefs.theme.light", 0, 0, "theme.set arg:2" }, + { "button id:prefs.theme.3 label:prefs.theme.white", 0, 0, "theme.set arg:3" }, + { NULL } + }; + const iMenuItem accentItems[] = { + { "button id:prefs.accent.0 label:prefs.accent.teal", 0, 0, "accent.set arg:0" }, + { "button id:prefs.accent.1 label:prefs.accent.orange", 0, 0, "accent.set arg:1" }, + { NULL } + }; + const iMenuItem satItems[] = { + { "button id:prefs.saturation.3 text:100 %", 0, 0, "saturation.set arg:100" }, + { "button id:prefs.saturation.2 text:66 %", 0, 0, "saturation.set arg:66" }, + { "button id:prefs.saturation.1 text:33 %", 0, 0, "saturation.set arg:33" }, + { "button id:prefs.saturation.0 text:0 %", 0, 0, "saturation.set arg:0" }, + { NULL } + }; + const iMenuItem generalItems[] = { + { "title id:heading.prefs.general", 0, 0, NULL }, + { "input id:prefs.searchurl url:1", 0, 0, NULL }, + { "padding", 0, 0, NULL }, + { "toggle id:prefs.hoverlink", 0, 0, NULL }, + { "toggle id:prefs.archive.openindex", 0, 0, NULL }, + { "radio device:1 id:prefs.pinsplit", 0, 0, (const void *) pinSplit }, + { "padding", 0, 0, NULL }, + { "dropdown id:prefs.uilang", 0, 0, (const void *) langItems }, + { NULL } + }; + const iMenuItem uiItems[] = { + { "title id:heading.prefs.interface", 0, 0, NULL }, + { "dropdown device:1 id:prefs.returnkey", 0, 0, (const void *) returnKeyBehaviors }, + { "padding device:1", 0, 0, NULL }, + { "toggle device:2 id:prefs.hidetoolbarscroll", 0, 0, NULL }, + { "heading id:heading.prefs.sizing", 0, 0, NULL }, + { "input id:prefs.uiscale maxlen:8", 0, 0, NULL }, + { NULL } + }; + const iMenuItem colorItems[] = { + { "title id:heading.prefs.colors", 0, 0, NULL }, + { "heading id:heading.prefs.uitheme", 0, 0, NULL }, + { "toggle id:prefs.ostheme", 0, 0, NULL }, + { "radio id:prefs.theme", 0, 0, (const void *) themeItems }, + { "radio id:prefs.accent", 0, 0, (const void *) accentItems }, + { "heading id:heading.prefs.pagecontent", 0, 0, NULL }, + { "dropdown id:prefs.doctheme.dark", 0, 0, (const void *) docThemes[0] }, + { "dropdown id:prefs.doctheme.light", 0, 0, (const void *) docThemes[1] }, + { "radio id:prefs.saturation", 0, 0, (const void *) satItems }, + { "padding", 0, 0, NULL }, + { "dropdown id:prefs.imagestyle", 0, 0, (const void *) imgStyles }, + { NULL } + }; + const iMenuItem fontItems[] = { + { "title id:heading.prefs.fonts", 0, 0, NULL }, + { "dropdown id:prefs.headingfont", 0, 0, (const void *) constData_Array(makeFontItems_("headingfont")) }, + { "dropdown id:prefs.font", 0, 0, (const void *) constData_Array(makeFontItems_("font")) }, + { NULL } + }; + const iMenuItem items[] = { { "panel icon:0x2699 id:heading.prefs.general", + '1', 0, + (const void *) generalItems }, + { "panel icon:0x1f4f1 id:heading.prefs.interface", + '2', 0, + (const void *) uiItems }, + { "panel icon:0x1f3a8 id:heading.prefs.colors", + '3', 0, + (const void *) colorItems }, + { "panel icon:0x1f5da id:heading.prefs.fonts", + '4', 0, + (const void *) fontItems }, + { NULL } }; + iWidget *dlg = makeSplitMultiPanel_Mobile(items); + setupSheetTransition_Mobile(dlg, iTrue); + return dlg; + } iWidget *dlg = makeSheet_Widget("prefs"); addChildFlags_Widget(dlg, iClob(new_LabelWidget(uiHeading_ColorEscape "${heading.prefs}", NULL)), @@ -1666,22 +1823,7 @@ iWidget *makePreferences_Widget(void) { addChild_Widget(values, iClob(makePadding_Widget(bigGap))); /* UI languages. */ { iArray *uiLangs = collectNew_Array(sizeof(iMenuItem)); - const iMenuItem langItems[] = { - { "${lang.de} - de", 0, 0, "uilang id:de" }, - { "${lang.en} - en", 0, 0, "uilang id:en" }, - { "${lang.es} - es", 0, 0, "uilang id:es" }, - { "${lang.fi} - fi", 0, 0, "uilang id:fi" }, - { "${lang.fr} - fr", 0, 0, "uilang id:fr" }, - { "${lang.ia} - ia", 0, 0, "uilang id:ia" }, - { "${lang.ie} - ie", 0, 0, "uilang id:ie" }, - { "${lang.pl} - pl", 0, 0, "uilang id:pl" }, - { "${lang.ru} - ru", 0, 0, "uilang id:ru" }, - { "${lang.sr} - sr", 0, 0, "uilang id:sr" }, - { "${lang.tok} - tok", 0, 0, "uilang id:tok" }, - { "${lang.zh.hans} - zh", 0, 0, "uilang id:zh_Hans" }, - { "${lang.zh.hant} - zh", 0, 0, "uilang id:zh_Hant" }, - }; - pushBackN_Array(uiLangs, langItems, iElemCount(langItems)); + pushBackN_Array(uiLangs, langItems, iElemCount(langItems) - 1); /* TODO: Add an arrange flag for resizing parent to widest child. */ size_t widestPos = findWidestItemLabel_(data_Array(uiLangs), size_Array(uiLangs)); addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.uilang}"))); @@ -1702,39 +1844,12 @@ iWidget *makePreferences_Widget(void) { #endif addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.returnkey}"))); /* Return key behaviors. */ { - const iMenuItem returnKeyBehaviors[] = { - { "${prefs.returnkey.linebreak} " - uiTextAction_ColorEscape shift_Icon return_Icon restore_ColorEscape - " ${prefs.returnkey.accept} " - uiTextAction_ColorEscape return_Icon, - 0, - 0, - format_CStr("returnkey.set arg:%d", default_ReturnKeyBehavior) }, - { "${prefs.returnkey.linebreak} " - uiTextAction_ColorEscape return_Icon restore_ColorEscape - " ${prefs.returnkey.accept} " - uiTextAction_ColorEscape shift_Icon return_Icon, - 0, - 0, - format_CStr("returnkey.set arg:%d", acceptWithShift_ReturnKeyBehavior) }, - { "${prefs.returnkey.linebreak} " - uiTextAction_ColorEscape return_Icon restore_ColorEscape - " ${prefs.returnkey.accept} " uiTextAction_ColorEscape -#if defined (iPlatformApple) - "\u2318" return_Icon, -#else - "Ctrl" return_Icon, -#endif - 0, - 0, - format_CStr("returnkey.set arg:%d", acceptWithPrimaryMod_ReturnKeyBehavior) }, - }; iLabelWidget *returnKey = makeMenuButton_LabelWidget( returnKeyBehaviors[findWidestItemLabel_(returnKeyBehaviors, - iElemCount(returnKeyBehaviors))] + iElemCount(returnKeyBehaviors) - 1)] .label, returnKeyBehaviors, - iElemCount(returnKeyBehaviors)); + iElemCount(returnKeyBehaviors) - 1); setBackgroundColor_Widget(findChild_Widget(as_Widget(returnKey), "menu"), uiBackgroundMenu_ColorId); setId_Widget(addChildFlags_Widget(values, iClob(returnKey), alignLeft_WidgetFlag), @@ -1804,20 +1919,13 @@ iWidget *makePreferences_Widget(void) { for (int i = 0; i < 2; ++i) { const iBool isDark = (i == 0); const char *mode = isDark ? "dark" : "light"; - const iMenuItem themes[] = { - { "${prefs.doctheme.name.colorfuldark}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, colorfulDark_GmDocumentTheme) }, - { "${prefs.doctheme.name.colorfullight}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, colorfulLight_GmDocumentTheme) }, - { "${prefs.doctheme.name.black}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, black_GmDocumentTheme) }, - { "${prefs.doctheme.name.gray}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, gray_GmDocumentTheme) }, - { "${prefs.doctheme.name.white}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, white_GmDocumentTheme) }, - { "${prefs.doctheme.name.sepia}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, sepia_GmDocumentTheme) }, - { "${prefs.doctheme.name.highcontrast}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, highContrast_GmDocumentTheme) }, - }; addChild_Widget(headings, iClob(makeHeading_Widget(isDark ? "${prefs.doctheme.dark}" : "${prefs.doctheme.light}"))); - iLabelWidget *button = - makeMenuButton_LabelWidget(themes[1].label, themes, iElemCount(themes)); -// setFrameColor_Widget(findChild_Widget(as_Widget(button), "menu"), -// uiBackgroundSelected_ColorId); + iLabelWidget *button = makeMenuButton_LabelWidget( + docThemes[i][findWidestItemLabel_(docThemes[i], max_GmDocumentTheme)].label, + docThemes[i], + max_GmDocumentTheme); + // setFrameColor_Widget(findChild_Widget(as_Widget(button), "menu"), + // uiBackgroundSelected_ColorId); setBackgroundColor_Widget(findChild_Widget(as_Widget(button), "menu"), uiBackgroundMenu_ColorId); setId_Widget(addChildFlags_Widget(values, iClob(button), alignLeft_WidgetFlag), format_CStr("prefs.doctheme.%s", mode)); @@ -1833,17 +1941,10 @@ iWidget *makePreferences_Widget(void) { addChildFlags_Widget(values, iClob(sats), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); /* Colorize images. */ { addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.imagestyle}"))); - const iMenuItem imgStyles[] = { - { "${prefs.imagestyle.original}", 0, 0, format_CStr("imagestyle.set arg:%d", original_ImageStyle) }, - { "${prefs.imagestyle.grayscale}", 0, 0, format_CStr("imagestyle.set arg:%d", grayscale_ImageStyle) }, - { "${prefs.imagestyle.bgfg}", 0, 0, format_CStr("imagestyle.set arg:%d", bgFg_ImageStyle) }, - { "${prefs.imagestyle.text}", 0, 0, format_CStr("imagestyle.set arg:%d", textColorized_ImageStyle) }, - { "${prefs.imagestyle.preformat}", 0, 0, format_CStr("imagestyle.set arg:%d", preformatColorized_ImageStyle) }, - }; iLabelWidget *button = makeMenuButton_LabelWidget( - imgStyles[findWidestItemLabel_(imgStyles, iElemCount(imgStyles))].label, + imgStyles[findWidestItemLabel_(imgStyles, iElemCount(imgStyles) - 1)].label, imgStyles, - iElemCount(imgStyles)); + iElemCount(imgStyles) - 1); setBackgroundColor_Widget(findChild_Widget(as_Widget(button), "menu"), uiBackgroundMenu_ColorId); setId_Widget(addChildFlags_Widget(values, iClob(button), alignLeft_WidgetFlag), diff --git a/src/ui/util.h b/src/ui/util.h index 2423f834..87b72394 100644 --- a/src/ui/util.h +++ b/src/ui/util.h @@ -220,7 +220,10 @@ struct Impl_MenuItem { const char *label; int key; int kmods; - const char *command; + union { + const char *command; + const void *data; + }; }; iWidget * makeMenu_Widget (iWidget *parent, const iMenuItem *items, size_t n); /* returns no ref */ diff --git a/src/ui/widget.c b/src/ui/widget.c index 4f567989..0d6787ce 100644 --- a/src/ui/widget.c +++ b/src/ui/widget.c @@ -1402,6 +1402,7 @@ iAny *hitChild_Widget(const iWidget *d, iInt2 coord) { } iAny *findChild_Widget(const iWidget *d, const char *id) { + if (!d) return NULL; if (cmp_String(id_Widget(d), id) == 0) { return iConstCast(iAny *, d); } diff --git a/src/ui/window.c b/src/ui/window.c index 3ac02495..096853cc 100644 --- a/src/ui/window.c +++ b/src/ui/window.c @@ -1136,6 +1136,9 @@ void setTitle_Window(iWindow *d, const iString *title) { } void setUiScale_Window(iWindow *d, float uiScale) { + if (uiScale <= 0.0f) { + uiScale = 1.0f; + } uiScale = iClamp(uiScale, 0.5f, 4.0f); if (d) { if (iAbs(d->uiScale - uiScale) > 0.0001f) { -- cgit v1.2.3 From b85471a5b84f0837611fb47be35ee713139f702f Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Thu, 9 Sep 2021 20:22:33 +0300 Subject: Mobile: Working on dialogs The new panels maker offers a declarative solution for creating consistent UIs. --- src/app.c | 42 ++---- src/ui/certimportwidget.c | 2 +- src/ui/documentwidget.c | 24 ++-- src/ui/mobile.c | 106 ++++++++++++-- src/ui/mobile.h | 5 +- src/ui/root.c | 48 +++---- src/ui/uploadwidget.c | 2 +- src/ui/util.c | 342 +++++++++++++++++++++++++++++----------------- src/ui/util.h | 3 +- 9 files changed, 368 insertions(+), 206 deletions(-) (limited to 'src/ui/documentwidget.c') diff --git a/src/app.c b/src/app.c index fa8cc105..0d0f2ffd 100644 --- a/src/app.c +++ b/src/app.c @@ -1653,34 +1653,20 @@ static void updateScrollSpeedButtons_(iWidget *d, enum iScrollType type, const i } } -static void updateDropdownSelection_(iLabelWidget *dropButton, const char *selectedCommand) { - iWidget *menu = findChild_Widget(as_Widget(dropButton), "menu"); - iForEach(ObjectList, i, children_Widget(menu)) { - if (isInstance_Object(i.object, &Class_LabelWidget)) { - iLabelWidget *item = i.object; - const iBool isSelected = endsWith_String(command_LabelWidget(item), selectedCommand); - setFlags_Widget(as_Widget(item), selected_WidgetFlag, isSelected); - if (isSelected) { - updateText_LabelWidget(dropButton, sourceText_LabelWidget(item)); - } - } - } -} - static void updateColorThemeButton_(iLabelWidget *button, int theme) { /* TODO: These three functions are all the same? Cleanup? */ if (!button) return; - updateDropdownSelection_(button, format_CStr(".set arg:%d", theme)); + updateDropdownSelection_LabelWidget(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)); + updateDropdownSelection_LabelWidget(button, format_CStr(".set arg:%d", font)); } static void updateImageStyleButton_(iLabelWidget *button, int style) { if (!button) return; - updateDropdownSelection_(button, format_CStr(".set arg:%d", style)); + updateDropdownSelection_LabelWidget(button, format_CStr(".set arg:%d", style)); } static iBool handlePrefsCommands_(iWidget *d, const char *cmd) { @@ -1733,8 +1719,8 @@ static iBool handlePrefsCommands_(iWidget *d, const char *cmd) { return iTrue; } else if (equal_Command(cmd, "uilang")) { - updateDropdownSelection_(findChild_Widget(d, "prefs.uilang"), - cstr_String(string_Command(cmd, "id"))); + updateDropdownSelection_LabelWidget(findChild_Widget(d, "prefs.uilang"), + cstr_String(string_Command(cmd, "id"))); return iFalse; } else if (equal_Command(cmd, "quoteicon.set")) { @@ -1744,8 +1730,8 @@ static iBool handlePrefsCommands_(iWidget *d, const char *cmd) { return iFalse; } else if (equal_Command(cmd, "returnkey.set")) { - updateDropdownSelection_(findChild_Widget(d, "prefs.returnkey"), - format_CStr("returnkey.set arg:%d", arg_Command(cmd))); + updateDropdownSelection_LabelWidget(findChild_Widget(d, "prefs.returnkey"), + format_CStr("returnkey.set arg:%d", arg_Command(cmd))); return iFalse; } else if (equal_Command(cmd, "pinsplit.set")) { @@ -1849,7 +1835,8 @@ iDocumentWidget *newTab_App(const iDocumentWidget *duplicateOf, iBool switchToNe static iBool handleIdentityCreationCommands_(iWidget *dlg, const char *cmd) { iApp *d = &app_; if (equal_Command(cmd, "ident.showmore")) { - iForEach(ObjectList, i, children_Widget(findChild_Widget(dlg, "headings"))) { + iForEach(ObjectList, i, + children_Widget(findChild_Widget(dlg, isUsingPanelLayout_Mobile() ? "panel.top" : "headings"))) { if (flags_Widget(i.object) & collapse_WidgetFlag) { setFlags_Widget(i.object, hidden_WidgetFlag, iFalse); } @@ -1859,8 +1846,7 @@ static iBool handleIdentityCreationCommands_(iWidget *dlg, const char *cmd) { setFlags_Widget(j.object, hidden_WidgetFlag, iFalse); } } - setFlags_Widget(child_Widget(findChild_Widget(dlg, "dialogbuttons"), 0), disabled_WidgetFlag, - iTrue); + setFlags_Widget(pointer_Command(cmd), disabled_WidgetFlag, iTrue); arrange_Widget(dlg); refresh_Widget(dlg); return iTrue; @@ -1870,6 +1856,7 @@ static iBool handleIdentityCreationCommands_(iWidget *dlg, const char *cmd) { setText_LabelWidget(scope, text_LabelWidget(child_Widget( findChild_Widget(as_Widget(scope), "menu"), arg_Command(cmd)))); + arrange_Widget(findWidget_App("ident")); return iTrue; } if (equal_Command(cmd, "ident.temp.changed")) { @@ -2596,9 +2583,10 @@ iBool handleCommand_App(const char *cmd) { updatePrefsPinSplitButtons_(dlg, d->prefs.pinSplit); updateScrollSpeedButtons_(dlg, mouse_ScrollType, d->prefs.smoothScrollSpeed[mouse_ScrollType]); updateScrollSpeedButtons_(dlg, keyboard_ScrollType, d->prefs.smoothScrollSpeed[keyboard_ScrollType]); - updateDropdownSelection_(findChild_Widget(dlg, "prefs.uilang"), cstr_String(&d->prefs.uiLanguage)); - updateDropdownSelection_(findChild_Widget(dlg, "prefs.returnkey"), - format_CStr("returnkey.set arg:%d", d->prefs.returnKey)); + updateDropdownSelection_LabelWidget(findChild_Widget(dlg, "prefs.uilang"), cstr_String(&d->prefs.uiLanguage)); + updateDropdownSelection_LabelWidget( + findChild_Widget(dlg, "prefs.returnkey"), + format_CStr("returnkey.set arg:%d", d->prefs.returnKey)); setToggle_Widget(findChild_Widget(dlg, "prefs.retainwindow"), d->prefs.retainWindowSize); setText_InputWidget(findChild_Widget(dlg, "prefs.uiscale"), collectNewFormat_String("%g", uiScale_Window(d->window))); diff --git a/src/ui/certimportwidget.c b/src/ui/certimportwidget.c index a8346e19..2e60c71f 100644 --- a/src/ui/certimportwidget.c +++ b/src/ui/certimportwidget.c @@ -152,7 +152,7 @@ void init_CertImportWidget(iCertImportWidget *d) { /* Buttons. */ addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); iWidget *buttons = makeDialogButtons_Widget( - (iMenuItem[]){ { "${cancel}", 0, 0, NULL }, + (iMenuItem[]){ { "${cancel}" }, { uiTextAction_ColorEscape "${dlg.certimport.import}", SDLK_RETURN, KMOD_PRIMARY, diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 6ca4fd8d..83f2ea6a 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -3127,7 +3127,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) makeQuestion_Widget( uiHeading_ColorEscape "${heading.import.bookmarks}", formatCStrs_Lang("dlg.import.found.n", count), - (iMenuItem[]){ { "${cancel}", 0, 0, NULL }, + (iMenuItem[]){ { "${cancel}" }, { format_CStr(cstrCount_Lang("dlg.import.add.n", (int) count), uiTextAction_ColorEscape, count), @@ -3299,7 +3299,7 @@ static iBool processMediaEvents_DocumentWidget_(iDocumentWidget *d, const SDL_Ev d->playerMenu = makeMenu_Widget( as_Widget(d), (iMenuItem[]){ - { cstrCollect_String(metadataLabel_Player(plr)), 0, 0, NULL }, + { cstrCollect_String(metadataLabel_Player(plr)) }, }, 1); openMenu_Widget(d->playerMenu, bottomLeft_Rect(ui.menuRect)); @@ -3604,7 +3604,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e pushBackN_Array( &items, (iMenuItem[]){ - { "---", 0, 0, NULL }, + { "---" }, { isGemini ? "${link.noproxy}" : openExt_Icon " ${link.browser}", 0, 0, @@ -3615,7 +3615,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e linkLabel_GmDocument(d->doc, d->contextLink->linkId)); urlEncodeSpaces_String(linkLabel); pushBackN_Array(&items, - (iMenuItem[]){ { "---", 0, 0, NULL }, + (iMenuItem[]){ { "---" }, { "${link.copy}", 0, 0, "document.copylink" }, { bookmark_Icon " ${link.bookmark}", 0, @@ -3627,7 +3627,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e 3); if (isNative && d->contextLink->mediaType != download_GmRunMediaType) { pushBackN_Array(&items, (iMenuItem[]){ - { "---", 0, 0, NULL }, + { "---" }, { download_Icon " ${link.download}", 0, 0, "document.downloadlink" }, }, 2); } @@ -3670,22 +3670,22 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e { "${menu.forward}", navigateForward_KeyShortcut, "navigate.forward" }, { upArrow_Icon " ${menu.parent}", navigateParent_KeyShortcut, "navigate.parent" }, { upArrowBar_Icon " ${menu.root}", navigateRoot_KeyShortcut, "navigate.root" }, - { "---", 0, 0, NULL }, + { "---" }, { reload_Icon " ${menu.reload}", reload_KeyShortcut, "navigate.reload" }, { 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" }, - { "---", 0, 0, NULL }, + { "---" }, { book_Icon " ${menu.page.import}", 0, 0, "bookmark.links confirm:1" }, { globe_Icon " ${menu.page.translate}", 0, 0, "document.translate" }, #if defined (iPlatformMobile) - { "---", 0, 0, NULL }, + { "---" }, { "${menu.page.copyurl}", 0, 0, "document.copylink" } }, 14); #else { upload_Icon " ${menu.page.upload}", 0, 0, "document.upload" }, - { "---", 0, 0, NULL }, + { "---" }, { "${menu.page.copyurl}", 0, 0, "document.copylink" } }, 15); #endif @@ -3862,7 +3862,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e } d->copyMenu = makeMenu_Widget(w, (iMenuItem[]){ { clipCopy_Icon " ${menu.copy}", 0, 0, "copy" }, - { "---", 0, 0, NULL }, + { "---" }, { close_Icon " ${menu.select.clear}", 0, 0, "document.select arg:0" }, }, 3); setFlags_Widget(d->copyMenu, noFadeBackground_WidgetFlag, iTrue); @@ -3955,7 +3955,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e uiTextAction_ColorEscape, cstr_String(url)), (iMenuItem[]){ - { "${cancel}", 0, 0, NULL }, + { "${cancel}" }, { uiTextCaution_ColorEscape "${dlg.openlink}", 0, 0, format_CStr("!open default:1 url:%s", cstr_String(url)) } }, 2); diff --git a/src/ui/mobile.c b/src/ui/mobile.c index 7e359a84..f3e23e06 100644 --- a/src/ui/mobile.c +++ b/src/ui/mobile.c @@ -36,7 +36,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ # include "ios.h" #endif -static iBool useMobileSheetLayout_(void) { +iBool isUsingPanelLayout_Mobile(void) { return deviceType_App() != desktop_AppDeviceType; } @@ -381,6 +381,7 @@ static size_t countItems_(const iMenuItem *itemsNullTerminated) { void makePanelItem_Mobile(iWidget *panel, const iMenuItem *item) { iWidget * widget = NULL; iLabelWidget *heading = NULL; + iWidget * value = NULL; const char * spec = item->label; const char * id = cstr_Rangecc(range_Command(spec, "id")); const char * label = hasLabel_Command(spec, "text") @@ -396,6 +397,7 @@ void makePanelItem_Mobile(iWidget *panel, const iMenuItem *item) { setFont_LabelWidget(title, uiLabelLargeBold_FontId); setTextColor_LabelWidget(title, uiHeading_ColorId); setAllCaps_LabelWidget(title, iTrue); + setId_Widget(as_Widget(title), id); } else if (equal_Command(spec, "heading")) { addChild_Widget(panel, iClob(makePadding_Widget(lineHeight_Text(labelFont_())))); @@ -403,6 +405,7 @@ void makePanelItem_Mobile(iWidget *panel, const iMenuItem *item) { setAllCaps_LabelWidget(heading, iTrue); setRemoveTrailingColon_LabelWidget(heading, iTrue); addChild_Widget(panel, iClob(heading)); + setId_Widget(as_Widget(heading), id); } else if (equal_Command(spec, "toggle")) { iLabelWidget *toggle = (iLabelWidget *) makeToggle_Widget(id); @@ -412,7 +415,9 @@ void makePanelItem_Mobile(iWidget *panel, const iMenuItem *item) { } else if (equal_Command(spec, "dropdown")) { const iMenuItem *dropItems = item->data; - iLabelWidget *drop = makeMenuButton_LabelWidget("", dropItems, countItems_(dropItems)); + iLabelWidget *drop = makeMenuButton_LabelWidget(dropItems[0].label, + dropItems, countItems_(dropItems)); + value = as_Widget(drop); setFont_LabelWidget(drop, labelFont_()); setFlags_Widget(as_Widget(drop), alignRight_WidgetFlag | noBackground_WidgetFlag | @@ -465,6 +470,9 @@ void makePanelItem_Mobile(iWidget *panel, const iMenuItem *item) { } else if (equal_Command(spec, "input")) { iInputWidget *input = new_InputWidget(argU32Label_Command(spec, "maxlen")); + if (hasLabel_Command(spec, "hint")) { + setHint_InputWidget(input, cstr_Lang(cstr_Rangecc(range_Command(spec, "hint")))); + } setId_Widget(as_Widget(input), id); setUrlContent_InputWidget(input, argLabel_Command(spec, "url")); setSelectAllOnFocus_InputWidget(input, argLabel_Command(spec, "selectall")); @@ -491,6 +499,12 @@ void makePanelItem_Mobile(iWidget *panel, const iMenuItem *item) { else if (equal_Command(spec, "button")) { widget = as_Widget(heading = makePanelButton_(label, item->command)); } + else if (equal_Command(spec, "label")) { + iLabelWidget *lab = new_LabelWidget(label, NULL); + widget = as_Widget(lab); + setWrap_LabelWidget(lab, iTrue); + setFlags_Widget(widget, frameless_WidgetFlag, iTrue); + } else if (equal_Command(spec, "padding")) { widget = makePadding_Widget(lineHeight_Text(labelFont_()) * 1.5f); } @@ -500,8 +514,14 @@ void makePanelItem_Mobile(iWidget *panel, const iMenuItem *item) { if (icon) { setIcon_LabelWidget(heading, icon); } + if (value && as_Widget(heading) != value) { + as_Widget(heading)->sizeRef = value; /* heading height matches value widget */ + } } if (widget) { + setFlags_Widget(widget, + collapse_WidgetFlag | hidden_WidgetFlag, + argLabel_Command(spec, "collapse") != 0); addChild_Widget(panel, iClob(widget)); } } @@ -512,11 +532,26 @@ void makePanelItems_Mobile(iWidget *panel, const iMenuItem *itemsNullTerminated) } } -iWidget *makePanels_Mobile(const iMenuItem *itemsNullTerminated) { +static const iMenuItem *findDialogCancelAction_(const iMenuItem *items, size_t n) { + if (n <= 1) { + return NULL; + } + for (size_t i = 0; i < n - 1; i++) { + if (!iCmpStr(items[i].label, "${cancel}")) { + return &items[i]; + } + } + return NULL; +} + +iWidget *makePanels_Mobile(const char *id, + const iMenuItem *itemsNullTerminated, + const iMenuItem *actions, size_t numActions) { /* A multipanel widget has a top panel and one or more detail panels. In a horizontal layout, the detail panels slide in from the right and cover the top panel. In a landscape layout, the detail panels are always visible on the side. */ iWidget *sheet = new_Widget(); + setId_Widget(sheet, id); setBackgroundColor_Widget(sheet, uiBackground_ColorId); setFlags_Widget(sheet, resizeToParentWidth_WidgetFlag | resizeToParentHeight_WidgetFlag | @@ -553,18 +588,21 @@ iWidget *makePanels_Mobile(const iMenuItem *itemsNullTerminated) { topPanel->offsetRef = detailStack; } /* Navigation bar at the top. */ + iLabelWidget *naviBack; 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( + naviBack = addChildFlags_Widget( navi, - iClob(new_LabelWidget(leftAngle_Icon " ${panel.back}", "panel.close")), + iClob(newKeyMods_LabelWidget(leftAngle_Icon " ${panel.back}", + SDLK_ESCAPE, 0, + "panel.close")), noBackground_WidgetFlag | frameless_WidgetFlag | alignLeft_WidgetFlag | extraPadding_WidgetFlag); - checkIcon_LabelWidget(back); - setId_Widget(as_Widget(back), "panel.back"); - setFont_LabelWidget(back, labelFont_()); + checkIcon_LabelWidget(naviBack); + setId_Widget(as_Widget(naviBack), "panel.back"); + setFont_LabelWidget(naviBack, labelFont_()); addChildFlags_Widget(sheet, iClob(navi), drawBackgroundToVerticalSafeArea_WidgetFlag | arrangeHeight_WidgetFlag | resizeWidthOfChildren_WidgetFlag | @@ -593,6 +631,54 @@ iWidget *makePanels_Mobile(const iMenuItem *itemsNullTerminated) { makePanelItem_Mobile(topPanel, item); } } + /* Actions. */ + if (numActions) { + /* Some actions go in the navigation bar and some go on the top panel. */ + const iMenuItem *cancelItem = findDialogCancelAction_(actions, numActions); + const iMenuItem *defaultItem = &actions[numActions - 1]; + iAssert(defaultItem); + if (!cancelItem) { + updateTextCStr_LabelWidget(naviBack, defaultItem->label); + setCommand_LabelWidget(naviBack, collectNewCStr_String(defaultItem->command)); + setFlags_Widget(as_Widget(naviBack), alignLeft_WidgetFlag, iFalse); + setFlags_Widget(as_Widget(naviBack), alignRight_WidgetFlag, iTrue); + setIcon_LabelWidget(naviBack, 0); + setFont_LabelWidget(naviBack, labelBoldFont_()); + } + else { + updateTextCStr_LabelWidget(naviBack, cancelItem->label); + setCommand_LabelWidget(naviBack, collectNewCStr_String(cancelItem->command + ? cancelItem->command + : "cancel")); + iLabelWidget *defaultButton = new_LabelWidget(defaultItem->label, defaultItem->command); + setFont_LabelWidget(defaultButton, labelBoldFont_()); + setFlags_Widget(as_Widget(defaultButton), + frameless_WidgetFlag | extraPadding_WidgetFlag | + noBackground_WidgetFlag, + iTrue); + addChildFlags_Widget(as_Widget(naviBack), iClob(defaultButton), + moveToParentRightEdge_WidgetFlag); + updateSize_LabelWidget(defaultButton); + } + /* All other actions are added as buttons. */ + iBool needPadding = iTrue; + for (size_t i = 0; i < numActions; i++) { + const iMenuItem *act = &actions[i]; + if (act == cancelItem || act == defaultItem) { + continue; + } + if (!iCmpStr(act->label, "---")) { + continue; + } + if (needPadding) { + makePanelItem_Mobile(topPanel, &(iMenuItem){ "padding" }); + needPadding = iFalse; + } + makePanelItem_Mobile( + topPanel, + &(iMenuItem){ format_CStr("button text:%s", act->label), 0, 0, act->command }); + } + } /* Finalize the layout. */ addChild_Widget(sheet->root->widget, iClob(sheet)); mainDetailSplitHandler_(mainDetailSplit, "window.resized"); /* make it resize the split */ @@ -1011,7 +1097,7 @@ iWidget *makePanels_Mobile(const iMenuItem *itemsNullTerminated) { #endif void setupMenuTransition_Mobile(iWidget *sheet, iBool isIncoming) { - if (!useMobileSheetLayout_()) { + if (!isUsingPanelLayout_Mobile()) { return; } const iBool isSlidePanel = (flags_Widget(sheet) & horizontalOffset_WidgetFlag) != 0; @@ -1032,7 +1118,7 @@ void setupMenuTransition_Mobile(iWidget *sheet, iBool isIncoming) { } void setupSheetTransition_Mobile(iWidget *sheet, iBool isIncoming) { - if (!useMobileSheetLayout_()) { + if (!isUsingPanelLayout_Mobile()) { if (prefs_App()->uiAnimations) { setFlags_Widget(sheet, horizontalOffset_WidgetFlag, iFalse); if (isIncoming) { diff --git a/src/ui/mobile.h b/src/ui/mobile.h index 5e2d8957..4d742a0a 100644 --- a/src/ui/mobile.h +++ b/src/ui/mobile.h @@ -27,7 +27,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ iDeclareType(Widget) iDeclareType(MenuItem) -iWidget * makePanels_Mobile (const iMenuItem *itemsNullTerminated); +iBool isUsingPanelLayout_Mobile (void); +iWidget * makePanels_Mobile (const char *id, + const iMenuItem *itemsNullTerminated, + const iMenuItem *actions, size_t numActions); void setupMenuTransition_Mobile (iWidget *menu, iBool isIncoming); void setupSheetTransition_Mobile (iWidget *sheet, iBool isIncoming); diff --git a/src/ui/root.c b/src/ui/root.c index 0b55d250..eae8e4bb 100644 --- a/src/ui/root.c +++ b/src/ui/root.c @@ -57,28 +57,28 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ static const iMenuItem navMenuItems_[] = { { add_Icon " ${menu.newtab}", 't', KMOD_PRIMARY, "tabs.new" }, { "${menu.openlocation}", SDLK_l, KMOD_PRIMARY, "navigate.focus" }, - { "---", 0, 0, NULL }, + { "---" }, { download_Icon " " saveToDownloads_Label, SDLK_s, KMOD_PRIMARY, "document.save" }, { "${menu.page.copysource}", SDLK_c, KMOD_PRIMARY, "copy" }, - { "---", 0, 0, NULL }, + { "---" }, { leftHalf_Icon " ${menu.sidebar.left}", SDLK_l, KMOD_PRIMARY | KMOD_SHIFT, "sidebar.toggle" }, { rightHalf_Icon " ${menu.sidebar.right}", SDLK_p, KMOD_PRIMARY | KMOD_SHIFT, "sidebar2.toggle" }, { "${menu.view.split}", SDLK_j, KMOD_PRIMARY, "splitmenu.open" }, { "${menu.zoom.in}", SDLK_EQUALS, KMOD_PRIMARY, "zoom.delta arg:10" }, { "${menu.zoom.out}", SDLK_MINUS, KMOD_PRIMARY, "zoom.delta arg:-10" }, { "${menu.zoom.reset}", SDLK_0, KMOD_PRIMARY, "zoom.set arg:100" }, - { "---", 0, 0, NULL }, + { "---" }, { book_Icon " ${menu.bookmarks.list}", 0, 0, "!open url:about:bookmarks" }, { "${menu.bookmarks.bytag}", 0, 0, "!open url:about:bookmarks?tags" }, { "${menu.bookmarks.bytime}", 0, 0, "!open url:about:bookmarks?created" }, - { "---", 0, 0, NULL }, + { "---" }, { "${menu.downloads}", 0, 0, "downloads.open" }, { "${menu.feeds.entrylist}", 0, 0, "!open url:about:feeds" }, - { "---", 0, 0, NULL }, + { "---" }, { gear_Icon " ${menu.preferences}", SDLK_COMMA, KMOD_PRIMARY, "preferences" }, { "${menu.help}", SDLK_F1, 0, "!open url:about:help" }, { "${menu.releasenotes}", 0, 0, "!open url:about:version" }, - { "---", 0, 0, NULL }, + { "---" }, { "${menu.quit}", 'q', KMOD_PRIMARY, "quit" } }; #endif @@ -89,17 +89,17 @@ static const iMenuItem tabletNavMenuItems_[] = { { folder_Icon " ${menu.openfile}", SDLK_o, KMOD_PRIMARY, "file.open" }, { add_Icon " ${menu.newtab}", 't', KMOD_PRIMARY, "tabs.new" }, { close_Icon " ${menu.closetab}", 'w', KMOD_PRIMARY, "tabs.close" }, - { "---", 0, 0, NULL }, + { "---" }, { magnifyingGlass_Icon " ${menu.find}", 0, 0, "focus.set id:find.input" }, { leftHalf_Icon " ${menu.sidebar.left}", SDLK_l, KMOD_PRIMARY | KMOD_SHIFT, "sidebar.toggle" }, { rightHalf_Icon " ${menu.sidebar.right}", SDLK_p, KMOD_PRIMARY | KMOD_SHIFT, "sidebar2.toggle" }, { "${menu.view.split}", SDLK_j, KMOD_PRIMARY, "splitmenu.open" }, - { "---", 0, 0, NULL }, + { "---" }, { book_Icon " ${menu.bookmarks.list}", 0, 0, "!open url:about:bookmarks" }, { "${menu.bookmarks.bytag}", 0, 0, "!open url:about:bookmarks?tags" }, { "${menu.feeds.entrylist}", 0, 0, "!open url:about:feeds" }, { "${menu.downloads}", 0, 0, "downloads.open" }, - { "---", 0, 0, NULL }, + { "---" }, { gear_Icon " ${menu.preferences}", SDLK_COMMA, KMOD_PRIMARY, "preferences" }, { "${menu.help}", SDLK_F1, 0, "!open url:about:help" }, { "${menu.releasenotes}", 0, 0, "!open url:about:version" }, @@ -110,14 +110,14 @@ static const iMenuItem phoneNavMenuItems_[] = { { folder_Icon " ${menu.openfile}", SDLK_o, KMOD_PRIMARY, "file.open" }, { add_Icon " ${menu.newtab}", 't', KMOD_PRIMARY, "tabs.new" }, { close_Icon " ${menu.closetab}", 'w', KMOD_PRIMARY, "tabs.close" }, - { "---", 0, 0, NULL }, + { "---" }, { magnifyingGlass_Icon " ${menu.find}", 0, 0, "focus.set id:find.input" }, { leftHalf_Icon " ${menu.sidebar}", SDLK_l, KMOD_PRIMARY | KMOD_SHIFT, "sidebar.toggle" }, - { "---", 0, 0, NULL }, + { "---" }, { book_Icon " ${menu.bookmarks.list}", 0, 0, "!open url:about:bookmarks" }, { "${menu.downloads}", 0, 0, "downloads.open" }, { "${menu.feeds.entrylist}", 0, 0, "!open url:about:feeds" }, - { "---", 0, 0, NULL }, + { "---" }, { gear_Icon " Settings...", SDLK_COMMA, KMOD_PRIMARY, "preferences" }, }; #endif /* Mobile */ @@ -125,24 +125,24 @@ static const iMenuItem phoneNavMenuItems_[] = { #if defined (iPlatformMobile) static const iMenuItem identityButtonMenuItems_[] = { { "${menu.identity.notactive}", 0, 0, "ident.showactive" }, - { "---", 0, 0, NULL }, + { "---" }, { add_Icon " ${menu.identity.new}", newIdentity_KeyShortcut, "ident.new" }, { "${menu.identity.import}", SDLK_i, KMOD_PRIMARY | KMOD_SHIFT, "ident.import" }, - { "---", 0, 0, NULL }, + { "---" }, { person_Icon " ${menu.show.identities}", 0, 0, "toolbar.showident" }, }; #else /* desktop */ static const iMenuItem identityButtonMenuItems_[] = { { "${menu.identity.notactive}", 0, 0, "ident.showactive" }, - { "---", 0, 0, NULL }, + { "---" }, # if !defined (iPlatformAppleDesktop) { add_Icon " ${menu.identity.new}", newIdentity_KeyShortcut, "ident.new" }, { "${menu.identity.import}", SDLK_i, KMOD_PRIMARY | KMOD_SHIFT, "ident.import" }, - { "---", 0, 0, NULL }, + { "---" }, { person_Icon " ${menu.show.identities}", '4', KMOD_PRIMARY, "sidebar.mode arg:3 show:1" }, # else { add_Icon " ${menu.identity.new}", 0, 0, "ident.new" }, - { "---", 0, 0, NULL }, + { "---" }, { person_Icon " ${menu.show.identities}", 0, 0, "sidebar.mode arg:3 show:1" }, # endif }; @@ -1158,20 +1158,20 @@ void createUserInterface_Root(iRoot *d) { { 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" }, #if defined (iPlatformMobile) - { "---", 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" } }, 11); #else { upload_Icon " ${menu.page.upload}", 0, 0, "document.upload" }, - { "---", 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" } }, @@ -1355,7 +1355,7 @@ void createUserInterface_Root(iRoot *d) { (iMenuItem[]){ { close_Icon " ${menu.closetab}", 0, 0, "tabs.close" }, { copy_Icon " ${menu.duptab}", 0, 0, "tabs.new duplicate:1" }, - { "---", 0, 0, NULL }, + { "---" }, { "${menu.closetab.other}", 0, 0, "tabs.close toleft:1 toright:1" }, { barLeftArrow_Icon " ${menu.closetab.left}", 0, 0, "tabs.close toleft:1" }, { barRightArrow_Icon " ${menu.closetab.right}", 0, 0, "tabs.close toright:1" }, @@ -1372,18 +1372,18 @@ void createUserInterface_Root(iRoot *d) { (iMenuItem[]){ { scissor_Icon " ${menu.cut}", 0, 0, "input.copy cut:1" }, { clipCopy_Icon " ${menu.copy}", 0, 0, "input.copy" }, - { "---", 0, 0, NULL }, + { "---" }, { clipboard_Icon " ${menu.paste}", 0, 0, "input.paste" }, }, 4); iWidget *splitMenu = makeMenu_Widget(root, (iMenuItem[]){ { "${menu.split.merge}", '1', 0, "ui.split arg:0" }, { "${menu.split.swap}", SDLK_x, 0, "ui.split swap:1" }, - { "---", 0, 0, NULL }, + { "---" }, { "${menu.split.horizontal}", '3', 0, "ui.split arg:3 axis:0" }, { "${menu.split.horizontal} 1:2", SDLK_d, 0, "ui.split arg:1 axis:0" }, { "${menu.split.horizontal} 2:1", SDLK_e, 0, "ui.split arg:2 axis:0" }, - { "---", 0, 0, NULL }, + { "---" }, { "${menu.split.vertical}", '2', 0, "ui.split arg:3 axis:1" }, { "${menu.split.vertical} 1:2", SDLK_f, 0, "ui.split arg:1 axis:1" }, { "${menu.split.vertical} 2:1", SDLK_r, 0, "ui.split arg:2 axis:1" }, diff --git a/src/ui/uploadwidget.c b/src/ui/uploadwidget.c index 5e1ee493..4c72c60a 100644 --- a/src/ui/uploadwidget.c +++ b/src/ui/uploadwidget.c @@ -148,7 +148,7 @@ void init_UploadWidget(iUploadWidget *d) { addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); iWidget *buttons = makeDialogButtons_Widget((iMenuItem[]){ { "${upload.port}", 0, 0, "upload.setport" }, - { "---", 0, 0, NULL }, + { "---" }, { "${close}", SDLK_ESCAPE, 0, "upload.cancel" }, { uiTextAction_ColorEscape "${dlg.upload.send}", SDLK_RETURN, diff --git a/src/ui/util.c b/src/ui/util.c index 22d8bce4..abe6f22e 100644 --- a/src/ui/util.c +++ b/src/ui/util.c @@ -907,6 +907,20 @@ iLabelWidget *makeMenuButton_LabelWidget(const char *label, const iMenuItem *ite return button; } +void updateDropdownSelection_LabelWidget(iLabelWidget *dropButton, const char *selectedCommand) { + iWidget *menu = findChild_Widget(as_Widget(dropButton), "menu"); + iForEach(ObjectList, i, children_Widget(menu)) { + if (isInstance_Object(i.object, &Class_LabelWidget)) { + iLabelWidget *item = i.object; + const iBool isSelected = endsWith_String(command_LabelWidget(item), selectedCommand); + setFlags_Widget(as_Widget(item), selected_WidgetFlag, isSelected); + if (isSelected) { + updateText_LabelWidget(dropButton, sourceText_LabelWidget(item)); + } + } + } +} + /*-----------------------------------------------------------------------------------------------*/ static iBool isTabPage_Widget_(const iWidget *tabs, const iWidget *page) { @@ -1650,6 +1664,19 @@ static size_t findWidestItemLabel_(const iMenuItem *items, size_t num) { return widestPos; } +iWidget *makeDialog_Widget(const char *id, + const iMenuItem *itemsNullTerminated, + const iMenuItem *actions, size_t numActions) { + iWidget *dlg = makeSheet_Widget(id); + /* TODO: Construct desktop dialogs using NULL-terminated item arrays, like mobile panels. */ + addChild_Widget(dlg, iClob(makePadding_Widget(gap_UI))); + addChild_Widget(dlg, iClob(makeDialogButtons_Widget(actions, numActions))); + addChild_Widget(dlg->root->widget, iClob(dlg)); + arrange_Widget(dlg); + setupSheetTransition_Mobile(dlg, iTrue); + return dlg; +} + iWidget *makePreferences_Widget(void) { /* Common items. */ const iMenuItem langItems[] = { { "${lang.de} - de", 0, 0, "uilang id:de" }, @@ -1715,7 +1742,7 @@ iWidget *makePreferences_Widget(void) { { NULL } }; /* Create the Preferences UI. */ - if (deviceType_App() != desktop_AppDeviceType) { + if (isUsingPanelLayout_Mobile()) { const iMenuItem pinSplitItems[] = { { "button id:prefs.pinsplit.0 label:prefs.pinsplit.none", 0, 0, "pinsplit.set arg:0" }, { "button id:prefs.pinsplit.1 label:prefs.pinsplit.left", 0, 0, "pinsplit.set arg:1" }, @@ -1767,7 +1794,7 @@ iWidget *makePreferences_Widget(void) { }; const iMenuItem generalPanelItems[] = { { "title id:heading.prefs.general" }, - { "heading id:prefs.searchurl" }, + { "heading text:${prefs.searchurl}" }, { "input id:prefs.searchurl url:1 noheading:1" }, { "padding" }, { "toggle id:prefs.archive.openindex" }, @@ -1828,11 +1855,11 @@ iWidget *makePreferences_Widget(void) { { "padding" }, { "input id:prefs.cachesize maxlen:4 selectall:1 unit:mb" }, { "input id:prefs.memorysize maxlen:4 selectall:1 unit:mb" }, - { "heading id:prefs.proxy.gemini" }, + { "heading text:${prefs.proxy.gemini}" }, { "input id:prefs.proxy.gemini noheading:1" }, - { "heading id:prefs.proxy.gemini" }, + { "heading text:${prefs.proxy.gopher}" }, { "input id:prefs.proxy.gopher noheading:1" }, - { "heading id:prefs.proxy.gemini" }, + { "heading text:${prefs.proxy.http}" }, { "input id:prefs.proxy.http noheading:1" }, { NULL } }; @@ -1855,8 +1882,8 @@ iWidget *makePreferences_Widget(void) { { "button text:" bug_Icon " ${menu.debug}", 0, 0, "!open url:about:debug" }, { NULL } }; - iWidget *dlg = makePanels_Mobile((iMenuItem[]){ - { "panel icon:0x2699 id:heading.prefs.general", 0, 0, (const void *) generalPanelItems }, + iWidget *dlg = makePanels_Mobile("prefs", (iMenuItem[]){ + { "panel text:" gear_Icon " ${heading.prefs.general}", 0, 0, (const void *) generalPanelItems }, { "panel icon:0x1f5a7 id:heading.prefs.network", 0, 0, (const void *) networkPanelItems }, { "panel text:" person_Icon " ${sidebar.identities}", 0, 0, (const void *) identityPanelItems }, { "padding" }, @@ -1869,7 +1896,7 @@ iWidget *makePreferences_Widget(void) { { "padding" }, { "panel text:" planet_Icon " ${menu.about}", 0, 0, (const void *) aboutPanelItems }, { NULL } - }); + }, NULL, 0); setupSheetTransition_Mobile(dlg, iTrue); return dlg; } @@ -2153,6 +2180,29 @@ iWidget *makePreferences_Widget(void) { } iWidget *makeBookmarkEditor_Widget(void) { + const iMenuItem actions[] = { + { "${cancel}" }, + { uiTextCaution_ColorEscape "${dlg.bookmark.save}", SDLK_RETURN, KMOD_PRIMARY, "bmed.accept" } + }; + if (isUsingPanelLayout_Mobile()) { + const iMenuItem items[] = { + { "title id:bmed.heading text:${heading.bookmark.edit}" }, + { "heading id:dlg.bookmark.url" }, + { "input id:bmed.url url:1 noheading:1" }, + { "padding" }, + { "input id:bmed.title text:${dlg.bookmark.title}" }, + { "input id:bmed.tags text:${dlg.bookmark.tags}" }, + { "input id:bmed.icon maxlen:1 text:${dlg.bookmark.icon}" }, + { "heading text:${heading.bookmark.tags}" }, + { "toggle id:bmed.tag.home text:${bookmark.tag.home}" }, + { "toggle id:bmed.tag.remote text:${bookmark.tag.remote}" }, + { "toggle id:bmed.tag.linksplit text:${bookmark.tag.linksplit}" }, + { NULL } + }; + iWidget *dlg = makePanels_Mobile("bmed", items, actions, iElemCount(actions)); + setupSheetTransition_Mobile(dlg, iTrue); + return dlg; + } iWidget *dlg = makeSheet_Widget("bmed"); setId_Widget(addChildFlags_Widget( dlg, @@ -2179,14 +2229,7 @@ iWidget *makeBookmarkEditor_Widget(void) { as_Widget(inputs[i])->rect.size.x = 100 * gap_UI - headings->rect.size.x; } addChild_Widget(dlg, iClob(makePadding_Widget(gap_UI))); - addChild_Widget( - dlg, - iClob(makeDialogButtons_Widget((iMenuItem[]){ { "${cancel}" }, - { uiTextCaution_ColorEscape "${dlg.bookmark.save}", - SDLK_RETURN, - KMOD_PRIMARY, - "bmed.accept" } }, - 2))); + addChild_Widget(dlg, iClob(makeDialogButtons_Widget(actions, iElemCount(actions)))); addChild_Widget(get_Root()->widget, iClob(dlg)); finalizeSheet_Mobile(dlg); return dlg; @@ -2242,7 +2285,6 @@ iWidget *makeBookmarkCreation_Widget(const iString *url, const iString *title, i return dlg; } - static iBool handleFeedSettingCommands_(iWidget *dlg, const char *cmd) { if (equal_Command(cmd, "cancel")) { setupSheetTransition_Mobile(dlg, iFalse); @@ -2288,46 +2330,59 @@ static iBool handleFeedSettingCommands_(iWidget *dlg, const char *cmd) { } iWidget *makeFeedSettings_Widget(uint32_t bookmarkId) { - iWidget *dlg = makeSheet_Widget("feedcfg"); - setId_Widget(addChildFlags_Widget( - dlg, - iClob(new_LabelWidget(bookmarkId ? uiHeading_ColorEscape "${heading.feedcfg}" - : uiHeading_ColorEscape "${heading.subscribe}", - NULL)), - frameless_WidgetFlag), - "feedcfg.heading"); - iWidget *headings, *values; - addChild_Widget(dlg, iClob(makeTwoColumns_Widget(&headings, &values))); - iInputWidget *input = new_InputWidget(0); - addDialogInputWithHeading_(headings, values, "${dlg.feed.title}", "feedcfg.title", iClob(input)); - addChild_Widget(headings, iClob(makeHeading_Widget("${dlg.feed.entrytype}"))); - iWidget *types = new_Widget(); { - addRadioButton_(types, "feedcfg.type.gemini", "${dlg.feed.type.gemini}", "feedcfg.type arg:0"); - addRadioButton_(types, "feedcfg.type.headings", "${dlg.feed.type.headings}", "feedcfg.type arg:1"); - } - addChildFlags_Widget(values, iClob(types), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); - iWidget *buttons = - addChild_Widget(dlg, - iClob(makeDialogButtons_Widget( - (iMenuItem[]){ { "${cancel}" }, - { bookmarkId ? uiTextCaution_ColorEscape "${dlg.feed.save}" - : uiTextCaution_ColorEscape "${dlg.feed.sub}", - SDLK_RETURN, - KMOD_PRIMARY, - format_CStr("feedcfg.accept bmid:%d", bookmarkId) } }, - 2))); - setId_Widget(child_Widget(buttons, childCount_Widget(buttons) - 1), "feedcfg.save"); - arrange_Widget(dlg); - as_Widget(input)->rect.size.x = 100 * gap_UI - headings->rect.size.x; - addChild_Widget(get_Root()->widget, iClob(dlg)); - finalizeSheet_Mobile(dlg); + const char *headingText = bookmarkId ? uiHeading_ColorEscape "${heading.feedcfg}" + : uiHeading_ColorEscape "${heading.subscribe}"; + const iMenuItem actions[] = { { "${cancel}" }, + { bookmarkId ? uiTextCaution_ColorEscape "${dlg.feed.save}" + : uiTextCaution_ColorEscape "${dlg.feed.sub}", + SDLK_RETURN, + KMOD_PRIMARY, + format_CStr("feedcfg.accept bmid:%d", bookmarkId) } }; + iWidget *dlg; + if (isUsingPanelLayout_Mobile()) { + const iMenuItem typeItems[] = { + { "button id:feedcfg.type.gemini label:dlg.feed.type.gemini", 0, 0, "feedcfg.type arg:0" }, + { "button id:feedcfg.type.headings label:dlg.feed.type.headings", 0, 0, "feedcfg.type arg:1" }, + { NULL } + }; + dlg = makePanels_Mobile("feedcfg", (iMenuItem[]){ + { format_CStr("title id:feedcfg.heading text:%s", headingText) }, + { "input id:feedcfg.title text:${dlg.feed.title}" }, + { "radio id:dlg.feed.entrytype", 0, 0, (const void *) typeItems }, + { NULL } + }, actions, iElemCount(actions)); + } + else { + dlg = makeSheet_Widget("feedcfg"); + setId_Widget( + addChildFlags_Widget(dlg, iClob(new_LabelWidget(headingText, NULL)), frameless_WidgetFlag), + "feedcfg.heading"); + iWidget *headings, *values; + addChild_Widget(dlg, iClob(makeTwoColumns_Widget(&headings, &values))); + iInputWidget *input = new_InputWidget(0); + addDialogInputWithHeading_(headings, values, "${dlg.feed.title}", "feedcfg.title", iClob(input)); + addChild_Widget(headings, iClob(makeHeading_Widget("${dlg.feed.entrytype}"))); + iWidget *types = new_Widget(); { + addRadioButton_(types, "feedcfg.type.gemini", "${dlg.feed.type.gemini}", "feedcfg.type arg:0"); + addRadioButton_(types, "feedcfg.type.headings", "${dlg.feed.type.headings}", "feedcfg.type arg:1"); + } + addChildFlags_Widget(values, iClob(types), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); + iWidget *buttons = + addChild_Widget(dlg, iClob(makeDialogButtons_Widget(actions, iElemCount(actions)))); + setId_Widget(child_Widget(buttons, childCount_Widget(buttons) - 1), "feedcfg.save"); + arrange_Widget(dlg); + as_Widget(input)->rect.size.x = 100 * gap_UI - headings->rect.size.x; + addChild_Widget(get_Root()->widget, iClob(dlg)); + finalizeSheet_Mobile(dlg); + } /* Initialize. */ { const iBookmark *bm = bookmarkId ? get_Bookmarks(bookmarks_App(), bookmarkId) : NULL; setText_InputWidget(findChild_Widget(dlg, "feedcfg.title"), bm ? &bm->title : feedTitle_DocumentWidget(document_App())); setFlags_Widget(findChild_Widget(dlg, - hasTag_Bookmark(bm, headings_BookmarkTag) ? "feedcfg.type.headings" - : "feedcfg.type.gemini"), + hasTag_Bookmark(bm, headings_BookmarkTag) + ? "feedcfg.type.headings" + : "feedcfg.type.gemini"), selected_WidgetFlag, iTrue); setCommandHandler_Widget(dlg, handleFeedSettingCommands_); @@ -2336,84 +2391,113 @@ iWidget *makeFeedSettings_Widget(uint32_t bookmarkId) { } iWidget *makeIdentityCreation_Widget(void) { - iWidget *dlg = makeSheet_Widget("ident"); - setId_Widget(addChildFlags_Widget( - dlg, - iClob(new_LabelWidget(uiHeading_ColorEscape "${heading.newident}", NULL)), - frameless_WidgetFlag), - "ident.heading"); - iWidget *page = new_Widget(); - addChildFlags_Widget( - dlg, iClob(new_LabelWidget("${dlg.newident.rsa.selfsign}", NULL)), frameless_WidgetFlag); - /* TODO: Use makeTwoColumnWidget_? */ - addChild_Widget(dlg, iClob(page)); - setFlags_Widget(page, arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag, iTrue); - iWidget *headings = addChildFlags_Widget( - page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag); - iWidget *values = addChildFlags_Widget( - page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag); - setId_Widget(headings, "headings"); - setId_Widget(values, "values"); - iInputWidget *inputs[6]; - /* Where will the new identity be active on? */ { - addChild_Widget(headings, iClob(makeHeading_Widget("${dlg.newident.scope}"))); - const iMenuItem items[] = { - { "${dlg.newident.scope.domain}", 0, 0, "ident.scope arg:0" }, - { "${dlg.newident.scope.page}", 0, 0, "ident.scope arg:1" }, - { "${dlg.newident.scope.none}", 0, 0, "ident.scope arg:2" }, - }; - setId_Widget(addChild_Widget(values, - iClob(makeMenuButton_LabelWidget( - items[0].label, items, iElemCount(items)))), - "ident.scope"); - } - addDialogInputWithHeading_(headings, - values, - "${dlg.newident.until}", - "ident.until", - iClob(newHint_InputWidget(19, "${hint.newident.date}"))); - addDialogInputWithHeading_(headings, - values, - "${dlg.newident.commonname}", - "ident.common", - iClob(inputs[0] = new_InputWidget(0))); - /* Temporary? */ { - addChild_Widget(headings, iClob(makeHeading_Widget("${dlg.newident.temp}"))); - iWidget *tmpGroup = new_Widget(); - setFlags_Widget(tmpGroup, arrangeSize_WidgetFlag | arrangeHorizontal_WidgetFlag, iTrue); - addChild_Widget(tmpGroup, iClob(makeToggle_Widget("ident.temp"))); - setId_Widget( - addChildFlags_Widget(tmpGroup, - iClob(new_LabelWidget(uiTextCaution_ColorEscape warning_Icon - " ${dlg.newident.notsaved}", - NULL)), - hidden_WidgetFlag | frameless_WidgetFlag), - "ident.temp.note"); - addChild_Widget(values, iClob(tmpGroup)); - } - addChildFlags_Widget(headings, iClob(makePadding_Widget(gap_UI)), collapse_WidgetFlag | hidden_WidgetFlag); - addChildFlags_Widget(values, iClob(makePadding_Widget(gap_UI)), collapse_WidgetFlag | hidden_WidgetFlag); - addDialogInputWithHeadingAndFlags_(headings, values, "${dlg.newident.email}", "ident.email", iClob(inputs[1] = newHint_InputWidget(0, "${hint.newident.optional}")), collapse_WidgetFlag | hidden_WidgetFlag); - addDialogInputWithHeadingAndFlags_(headings, values, "${dlg.newident.userid}", "ident.userid", iClob(inputs[2] = newHint_InputWidget(0, "${hint.newident.optional}")), collapse_WidgetFlag | hidden_WidgetFlag); - addDialogInputWithHeadingAndFlags_(headings, values, "${dlg.newident.domain}", "ident.domain", iClob(inputs[3] = newHint_InputWidget(0, "${hint.newident.optional}")), collapse_WidgetFlag | hidden_WidgetFlag); - addDialogInputWithHeadingAndFlags_(headings, values, "${dlg.newident.org}", "ident.org", iClob(inputs[4] = newHint_InputWidget(0, "${hint.newident.optional}")), collapse_WidgetFlag | hidden_WidgetFlag); - addDialogInputWithHeadingAndFlags_(headings, values, "${dlg.newident.country}", "ident.country", iClob(inputs[5] = newHint_InputWidget(0, "${hint.newident.optional}")), collapse_WidgetFlag | hidden_WidgetFlag); - arrange_Widget(dlg); - for (size_t i = 0; i < iElemCount(inputs); ++i) { - as_Widget(inputs[i])->rect.size.x = 100 * gap_UI - headings->rect.size.x; + const iMenuItem actions[] = { { "${dlg.newident.more}", 0, 0, "ident.showmore" }, + { "---" }, + { "${cancel}", SDLK_ESCAPE, 0, "ident.cancel" }, + { uiTextAction_ColorEscape "${dlg.newident.create}", + SDLK_RETURN, + KMOD_PRIMARY, + "ident.accept" } }; + iUrl url; + init_Url(&url, url_DocumentWidget(document_App())); + const iMenuItem scopeItems[] = { + { format_CStr("${dlg.newident.scope.domain}:\n%s", cstr_Rangecc(url.host)), 0, 0, "ident.scope arg:0" }, + { format_CStr("${dlg.newident.scope.page}:\n%s", cstr_Rangecc(url.path)), 0, 0, "ident.scope arg:1" }, + { "${dlg.newident.scope.none}", 0, 0, "ident.scope arg:2" }, + { NULL } + }; + iWidget *dlg; + if (isUsingPanelLayout_Mobile()) { + dlg = makePanels_Mobile("ident", + (iMenuItem[]){ { "title id:ident.heading text:${heading.newident}" }, + { "label text:${dlg.newident.rsa.selfsign}" }, + { "dropdown id:ident.scope text:${dlg.newident.scope}", 0, 0, + (const void *) scopeItems }, + { "input id:ident.until hint:hint.newident.date maxlen:19 text:${dlg.newident.until}" }, + //{ "padding" }, + //{ "toggle id:ident.temp text:${dlg.newident.temp}" }, + //{ "label text:${help.ident.temp}" }, + { "heading id:dlg.newident.commonname" }, + { "input id:ident.common noheading:1" }, + { "padding collapse:1" }, + { "input collapse:1 id:ident.email hint:hint.newident.optional text:${dlg.newident.email}" }, + { "input collapse:1 id:ident.userid hint:hint.newident.optional text:${dlg.newident.userid}" }, + { "input collapse:1 id:ident.domain hint:hint.newident.optional text:${dlg.newident.domain}" }, + { "input collapse:1 id:ident.org hint:hint.newident.optional text:${dlg.newident.org}" }, + { "input collapse:1 id:ident.country hint:hint.newident.optional text:${dlg.newident.country}" }, + { NULL } + }, actions, iElemCount(actions)); + setupSheetTransition_Mobile(dlg, iTrue); + } + else { + dlg = makeSheet_Widget("ident"); + setId_Widget(addChildFlags_Widget( + dlg, + iClob(new_LabelWidget(uiHeading_ColorEscape "${heading.newident}", NULL)), + frameless_WidgetFlag), + "ident.heading"); + iWidget *page = new_Widget(); + addChildFlags_Widget( + dlg, iClob(new_LabelWidget("${dlg.newident.rsa.selfsign}", NULL)), frameless_WidgetFlag); + /* TODO: Use makeTwoColumnWidget_? */ + addChild_Widget(dlg, iClob(page)); + setFlags_Widget(page, arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag, iTrue); + iWidget *headings = addChildFlags_Widget( + page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag); + iWidget *values = addChildFlags_Widget( + page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag); + setId_Widget(headings, "headings"); + setId_Widget(values, "values"); + iInputWidget *inputs[6]; + /* Where will the new identity be active on? */ { + iWidget *head = addChild_Widget(headings, iClob(makeHeading_Widget("${dlg.newident.scope}"))); + iWidget *val; + setId_Widget( + addChild_Widget(values, + val = iClob(makeMenuButton_LabelWidget( + scopeItems[0].label, scopeItems, iElemCount(scopeItems)))), + "ident.scope"); + head->sizeRef = val; + } + addDialogInputWithHeading_(headings, + values, + "${dlg.newident.until}", + "ident.until", + iClob(newHint_InputWidget(19, "${hint.newident.date}"))); + addDialogInputWithHeading_(headings, + values, + "${dlg.newident.commonname}", + "ident.common", + iClob(inputs[0] = new_InputWidget(0))); + /* Temporary? */ { + addChild_Widget(headings, iClob(makeHeading_Widget("${dlg.newident.temp}"))); + iWidget *tmpGroup = new_Widget(); + setFlags_Widget(tmpGroup, arrangeSize_WidgetFlag | arrangeHorizontal_WidgetFlag, iTrue); + addChild_Widget(tmpGroup, iClob(makeToggle_Widget("ident.temp"))); + setId_Widget( + addChildFlags_Widget(tmpGroup, + iClob(new_LabelWidget(uiTextCaution_ColorEscape warning_Icon + " ${dlg.newident.notsaved}", + NULL)), + hidden_WidgetFlag | frameless_WidgetFlag), + "ident.temp.note"); + addChild_Widget(values, iClob(tmpGroup)); + } + addChildFlags_Widget(headings, iClob(makePadding_Widget(gap_UI)), collapse_WidgetFlag | hidden_WidgetFlag); + addChildFlags_Widget(values, iClob(makePadding_Widget(gap_UI)), collapse_WidgetFlag | hidden_WidgetFlag); + addDialogInputWithHeadingAndFlags_(headings, values, "${dlg.newident.email}", "ident.email", iClob(inputs[1] = newHint_InputWidget(0, "${hint.newident.optional}")), collapse_WidgetFlag | hidden_WidgetFlag); + addDialogInputWithHeadingAndFlags_(headings, values, "${dlg.newident.userid}", "ident.userid", iClob(inputs[2] = newHint_InputWidget(0, "${hint.newident.optional}")), collapse_WidgetFlag | hidden_WidgetFlag); + addDialogInputWithHeadingAndFlags_(headings, values, "${dlg.newident.domain}", "ident.domain", iClob(inputs[3] = newHint_InputWidget(0, "${hint.newident.optional}")), collapse_WidgetFlag | hidden_WidgetFlag); + addDialogInputWithHeadingAndFlags_(headings, values, "${dlg.newident.org}", "ident.org", iClob(inputs[4] = newHint_InputWidget(0, "${hint.newident.optional}")), collapse_WidgetFlag | hidden_WidgetFlag); + addDialogInputWithHeadingAndFlags_(headings, values, "${dlg.newident.country}", "ident.country", iClob(inputs[5] = newHint_InputWidget(0, "${hint.newident.optional}")), collapse_WidgetFlag | hidden_WidgetFlag); + arrange_Widget(dlg); + for (size_t i = 0; i < iElemCount(inputs); ++i) { + as_Widget(inputs[i])->rect.size.x = 100 * gap_UI - headings->rect.size.x; + } + addChild_Widget(dlg, iClob(makeDialogButtons_Widget(actions, iElemCount(actions)))); + addChild_Widget(get_Root()->widget, iClob(dlg)); + finalizeSheet_Mobile(dlg); } - addChild_Widget(dlg, - iClob(makeDialogButtons_Widget( - (iMenuItem[]){ { "${dlg.newident.more}", 0, 0, "ident.showmore" }, - { "---" }, - { "${cancel}", SDLK_ESCAPE, 0, "ident.cancel" }, - { uiTextAction_ColorEscape "${dlg.newident.create}", - SDLK_RETURN, - KMOD_PRIMARY, - "ident.accept" } }, - 4))); - addChild_Widget(get_Root()->widget, iClob(dlg)); - finalizeSheet_Mobile(dlg); return dlg; } diff --git a/src/ui/util.h b/src/ui/util.h index 87b72394..0dff8978 100644 --- a/src/ui/util.h +++ b/src/ui/util.h @@ -242,7 +242,8 @@ int checkContextMenu_Widget (iWidget *, const SDL_Event *ev); /* see mac break; \ } -iLabelWidget * makeMenuButton_LabelWidget (const char *label, const iMenuItem *items, size_t n); +iLabelWidget * makeMenuButton_LabelWidget (const char *label, const iMenuItem *items, size_t n); +void updateDropdownSelection_LabelWidget (iLabelWidget *dropButton, const char *selectedCommand); /*-----------------------------------------------------------------------------------------------*/ -- cgit v1.2.3 From 1410bbde7779efe3a20f603523547c8b8f55b6a1 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Sat, 11 Sep 2021 07:43:57 +0300 Subject: Mobile: Many UI improvements; Upload UI --- po/en.po | 22 +++++- res/lang/de.bin | Bin 24008 -> 24162 bytes res/lang/en.bin | Bin 22609 -> 22763 bytes res/lang/es.bin | Bin 25135 -> 25286 bytes res/lang/fi.bin | Bin 25140 -> 25291 bytes res/lang/fr.bin | Bin 26109 -> 26260 bytes res/lang/ia.bin | Bin 24736 -> 24890 bytes res/lang/ie.bin | Bin 24494 -> 24645 bytes res/lang/pl.bin | Bin 25669 -> 25823 bytes res/lang/ru.bin | Bin 37391 -> 37542 bytes res/lang/sr.bin | Bin 37048 -> 37199 bytes res/lang/tok.bin | Bin 22930 -> 23081 bytes res/lang/zh_Hans.bin | Bin 21688 -> 21842 bytes res/lang/zh_Hant.bin | Bin 21873 -> 22027 bytes src/app.c | 7 +- src/ui/certimportwidget.c | 120 +++++++++++++++++------------ src/ui/documentwidget.c | 9 +-- src/ui/labelwidget.c | 17 +++-- src/ui/mobile.c | 134 +++++++++++++++++++++++++-------- src/ui/mobile.h | 19 ++++- src/ui/root.c | 10 +-- src/ui/uploadwidget.c | 188 +++++++++++++++++++++++++++------------------- src/ui/util.c | 15 ++-- src/ui/widget.c | 4 +- 24 files changed, 349 insertions(+), 196 deletions(-) (limited to 'src/ui/documentwidget.c') diff --git a/po/en.po b/po/en.po index dd3af388..546a9489 100644 --- a/po/en.po +++ b/po/en.po @@ -251,6 +251,10 @@ msgstr "Show Feed Entries" msgid "menu.preferences" msgstr "Preferences…" +# used for Preferences on mobile +msgid "menu.settings" +msgstr "Settings…" + msgid "menu.help" msgstr "Help" @@ -906,7 +910,7 @@ msgid "heading.lookup.other" msgstr "OTHER" msgid "menu.page.upload" -msgstr "Upload Page with Titan" +msgstr "Upload Page with Titan…" msgid "heading.upload" msgstr "UPLOAD WITH TITAN" @@ -953,6 +957,18 @@ msgstr "Set the Titan server port to use for this URL.\nThe port is saved in the msgid "dlg.uploadport.set" msgstr "Set Port" +# used on mobile +msgid "dlg.upload.text" +msgstr "Upload Plain Text" + +# used on mobile +msgid "dlg.upload.file" +msgstr "Upload a File" + +# used on mobile +msgid "dlg.upload.pickfile" +msgstr "Select File…" + msgid "heading.translate" msgstr "TRANSLATE PAGE" @@ -1151,6 +1167,10 @@ msgstr "SPECIAL TAGS" msgid "heading.prefs" msgstr "PREFERENCES" +# used on mobile +msgid "heading.settings" +msgstr "SETTINGS" + msgid "heading.prefs.certs" msgstr "CERTIFICATES" diff --git a/res/lang/de.bin b/res/lang/de.bin index 9d87657f..ba87d002 100644 Binary files a/res/lang/de.bin and b/res/lang/de.bin differ diff --git a/res/lang/en.bin b/res/lang/en.bin index 9fe4cfb3..a7dfb866 100644 Binary files a/res/lang/en.bin and b/res/lang/en.bin differ diff --git a/res/lang/es.bin b/res/lang/es.bin index 1effaf3d..7e7398d6 100644 Binary files a/res/lang/es.bin and b/res/lang/es.bin differ diff --git a/res/lang/fi.bin b/res/lang/fi.bin index fcaa8cc6..607e52fd 100644 Binary files a/res/lang/fi.bin and b/res/lang/fi.bin differ diff --git a/res/lang/fr.bin b/res/lang/fr.bin index 32104f44..955695ed 100644 Binary files a/res/lang/fr.bin and b/res/lang/fr.bin differ diff --git a/res/lang/ia.bin b/res/lang/ia.bin index 5b64a182..61a18efc 100644 Binary files a/res/lang/ia.bin and b/res/lang/ia.bin differ diff --git a/res/lang/ie.bin b/res/lang/ie.bin index 6d568972..06ea7979 100644 Binary files a/res/lang/ie.bin and b/res/lang/ie.bin differ diff --git a/res/lang/pl.bin b/res/lang/pl.bin index 69ad06f8..5fc5e24a 100644 Binary files a/res/lang/pl.bin and b/res/lang/pl.bin differ diff --git a/res/lang/ru.bin b/res/lang/ru.bin index 67babbcd..1718f647 100644 Binary files a/res/lang/ru.bin and b/res/lang/ru.bin differ diff --git a/res/lang/sr.bin b/res/lang/sr.bin index a7f7639b..60b7b600 100644 Binary files a/res/lang/sr.bin and b/res/lang/sr.bin differ diff --git a/res/lang/tok.bin b/res/lang/tok.bin index b7476101..3298f0e8 100644 Binary files a/res/lang/tok.bin and b/res/lang/tok.bin differ diff --git a/res/lang/zh_Hans.bin b/res/lang/zh_Hans.bin index 03eb8b43..8c32a0c5 100644 Binary files a/res/lang/zh_Hans.bin and b/res/lang/zh_Hans.bin differ diff --git a/res/lang/zh_Hant.bin b/res/lang/zh_Hant.bin index c0378db8..68f7d3bc 100644 Binary files a/res/lang/zh_Hant.bin and b/res/lang/zh_Hant.bin differ diff --git a/src/app.c b/src/app.c index a52552c0..e597edbe 100644 --- a/src/app.c +++ b/src/app.c @@ -2382,7 +2382,8 @@ iBool handleCommand_App(const char *cmd) { setUrl_UploadWidget(upload, url); setResponseViewer_UploadWidget(upload, document_App()); addChild_Widget(get_Root()->widget, iClob(upload)); - finalizeSheet_Mobile(as_Widget(upload)); +// finalizeSheet_Mobile(as_Widget(upload)); + setupSheetTransition_Mobile(as_Widget(upload), iTrue); postRefresh_App(); return iTrue; } @@ -2761,7 +2762,9 @@ 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_Mobile(as_Widget(imp)); +// finalizeSheet_Mobile(as_Widget(imp)); + arrange_Widget(as_Widget(imp)); + setupSheetTransition_Mobile(as_Widget(imp), iTrue); postRefresh_App(); return iTrue; } diff --git a/src/ui/certimportwidget.c b/src/ui/certimportwidget.c index 2e60c71f..65cb6654 100644 --- a/src/ui/certimportwidget.c +++ b/src/ui/certimportwidget.c @@ -104,61 +104,83 @@ static iBool tryImport_CertImportWidget_(iCertImportWidget *d, const iBlock *dat void init_CertImportWidget(iCertImportWidget *d) { iWidget *w = as_Widget(d); + const iMenuItem actions[] = { +#if defined (iPlatformAppleMobile) + { "${dlg.certimport.pickfile}", 0, 0, "certimport.pickfile" }, + { "---" }, +#endif + { "${cancel}" }, + { uiTextAction_ColorEscape "${dlg.certimport.import}", + SDLK_RETURN, KMOD_PRIMARY, + "certimport.accept" } + }; init_Widget(w); setId_Widget(w, "certimport"); d->cert = NULL; - /* This should behave similar to sheets. */ - useSheetStyle_Widget(w); - addChildFlags_Widget( - w, - iClob(new_LabelWidget(uiHeading_ColorEscape "${heading.certimport}", NULL)), - frameless_WidgetFlag); - d->info = addChildFlags_Widget(w, iClob(new_LabelWidget(infoText_, NULL)), frameless_WidgetFlag); - addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); - d->crtLabel = new_LabelWidget("", NULL); { - setFont_LabelWidget(d->crtLabel, uiContent_FontId); - addChildFlags_Widget(w, iClob(d->crtLabel), 0); - setFrameColor_Widget(as_Widget(d->crtLabel), uiTextCaution_ColorId); + if (isUsingPanelLayout_Mobile()) { + initPanels_Mobile(w, NULL, (iMenuItem[]){ + { "title id:heading.certimport" }, + { format_CStr("label id:certimport.info text:%s", infoText_) }, + //{ "padding" }, + { "label id:certimport.crt nowrap:1 frame:1" }, + { "padding arg:0.25" }, + { "label id:certimport.key nowrap:1 frame:1" }, + { "heading text:${dlg.certimport.notes}" }, + { "input id:certimport.notes hint:hint.certimport.description noheading:1" }, + { NULL } + }, actions, iElemCount(actions)); + d->info = findChild_Widget(w, "certimport.info"); + d->crtLabel = findChild_Widget(w, "certimport.crt"); + d->keyLabel = findChild_Widget(w, "certimport.key"); + d->notes = findChild_Widget(w, "certimport.notes"); + setFixedSize_Widget(as_Widget(d->crtLabel), init_I2(-1, gap_UI * 12)); + setFixedSize_Widget(as_Widget(d->keyLabel), init_I2(-1, gap_UI * 12)); } - d->keyLabel = new_LabelWidget("", NULL); { - setFont_LabelWidget(d->keyLabel, uiContent_FontId); + else { + /* This should behave similar to sheets. */ + useSheetStyle_Widget(w); + addChildFlags_Widget( + w, + iClob(new_LabelWidget(uiHeading_ColorEscape "${heading.certimport}", NULL)), + frameless_WidgetFlag); + d->info = addChildFlags_Widget(w, iClob(new_LabelWidget(infoText_, NULL)), frameless_WidgetFlag); addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); - addChildFlags_Widget(w, iClob(d->keyLabel), 0); - setFrameColor_Widget(as_Widget(d->keyLabel), uiTextCaution_ColorId); - } - addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); - /* TODO: Use makeTwoColumnWidget_() */ - iWidget *page = new_Widget(); { - setFlags_Widget(page, arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag, iTrue); - iWidget *headings = addChildFlags_Widget( - page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag); - iWidget *values = addChildFlags_Widget( - page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag); -// addChild_Widget(headings, iClob(makeHeading_Widget("${dlg.certimport.notes}"))); -// addChild_Widget(values, iClob(d->notes = new_InputWidget(0))); -// setHint_InputWidget(d->notes, "${hint.certimport.description}"); - addTwoColumnDialogInputField_Widget( - headings, - values, - "${dlg.certimport.notes}", - "", - iClob(d->notes = newHint_InputWidget(0, "${hint.certimport.description}"))); - as_Widget(d->notes)->rect.size.x = gap_UI * 70; + d->crtLabel = new_LabelWidget("", NULL); { + setFont_LabelWidget(d->crtLabel, uiContent_FontId); + addChildFlags_Widget(w, iClob(d->crtLabel), 0); + } + d->keyLabel = new_LabelWidget("", NULL); { + setFont_LabelWidget(d->keyLabel, uiContent_FontId); + addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); + addChildFlags_Widget(w, iClob(d->keyLabel), 0); + } + addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); + /* TODO: Use makeTwoColumnWidget_() */ + iWidget *page = new_Widget(); { + setFlags_Widget(page, arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag, iTrue); + iWidget *headings = addChildFlags_Widget( + page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag); + iWidget *values = addChildFlags_Widget( + page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag); + addTwoColumnDialogInputField_Widget( + headings, + values, + "${dlg.certimport.notes}", + "", + iClob(d->notes = newHint_InputWidget(0, "${hint.certimport.description}"))); + as_Widget(d->notes)->rect.size.x = gap_UI * 70; + } + addChild_Widget(w, iClob(page)); + arrange_Widget(w); + setFixedSize_Widget(as_Widget(d->crtLabel), init_I2(width_Widget(w) - 6.5 * gap_UI, gap_UI * 12)); + setFixedSize_Widget(as_Widget(d->keyLabel), init_I2(width_Widget(w) - 6.5 * gap_UI, gap_UI * 12)); + /* Buttons. */ + addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); + iWidget *buttons = makeDialogButtons_Widget(actions, iElemCount(actions)); + addChild_Widget(w, iClob(buttons)); } - addChild_Widget(w, iClob(page)); - arrange_Widget(w); - setFixedSize_Widget(as_Widget(d->crtLabel), init_I2(width_Widget(w) - 6.5 * gap_UI, gap_UI * 12)); - setFixedSize_Widget(as_Widget(d->keyLabel), init_I2(width_Widget(w) - 6.5 * gap_UI, gap_UI * 12)); - /* Buttons. */ - addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); - iWidget *buttons = makeDialogButtons_Widget( - (iMenuItem[]){ { "${cancel}" }, - { uiTextAction_ColorEscape "${dlg.certimport.import}", - SDLK_RETURN, - KMOD_PRIMARY, - "certimport.accept" } }, - 2); - addChild_Widget(w, iClob(buttons)); + setFrameColor_Widget(as_Widget(d->crtLabel), uiTextCaution_ColorId); + setFrameColor_Widget(as_Widget(d->keyLabel), uiTextCaution_ColorId); if (deviceType_App() != desktop_AppDeviceType) { /* Try auto-pasting. */ postCommand_App("certimport.paste"); diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 83f2ea6a..4b3c2db0 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -2852,7 +2852,8 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) setUrl_UploadWidget(upload, d->mod.url); setResponseViewer_UploadWidget(upload, d); addChild_Widget(get_Root()->widget, iClob(upload)); - finalizeSheet_Mobile(as_Widget(upload)); +// finalizeSheet_Mobile(as_Widget(upload)); + setupSheetTransition_Mobile(as_Widget(upload), iTrue); postRefresh_App(); } return iTrue; @@ -3679,16 +3680,10 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e { "---" }, { book_Icon " ${menu.page.import}", 0, 0, "bookmark.links confirm:1" }, { globe_Icon " ${menu.page.translate}", 0, 0, "document.translate" }, -#if defined (iPlatformMobile) - { "---" }, - { "${menu.page.copyurl}", 0, 0, "document.copylink" } }, - 14); -#else { upload_Icon " ${menu.page.upload}", 0, 0, "document.upload" }, { "---" }, { "${menu.page.copyurl}", 0, 0, "document.copylink" } }, 15); -#endif if (isEmpty_Range(&d->selectMark)) { pushBackN_Array( &items, diff --git a/src/ui/labelwidget.c b/src/ui/labelwidget.c index 03595d1a..ec324d02 100644 --- a/src/ui/labelwidget.c +++ b/src/ui/labelwidget.c @@ -244,6 +244,9 @@ static void getColors_LabelWidget_(const iLabelWidget *d, int *bg, int *fg, int } } } + if (d->forceFg >= 0) { + *fg = d->forceFg; + } if (isPress) { *bg = uiBackgroundPressed_ColorId | permanent_ColorId; if (isButton) { @@ -257,9 +260,6 @@ static void getColors_LabelWidget_(const iLabelWidget *d, int *bg, int *fg, int *fg = isDark_ColorTheme(colorTheme_App()) ? white_ColorId : black_ColorId; } } - if (d->forceFg >= 0) { - *fg = d->forceFg; - } } iLocalDef int iconPadding_LabelWidget_(const iLabelWidget *d) { @@ -318,6 +318,10 @@ static void draw_LabelWidget_(const iLabelWidget *d) { } setClip_Paint(&p, rect); const int iconPad = iconPadding_LabelWidget_(d); + const int iconColor = isCaution ? uiTextCaution_ColorId + : flags & (disabled_WidgetFlag | pressed_WidgetFlag) ? fg + : isHover ? uiIconHover_ColorId + : uiIcon_ColorId; if (d->icon && d->icon != 0x20) { /* no need to draw an empty icon */ iString str; initUnicodeN_String(&str, &d->icon, 1); @@ -331,10 +335,7 @@ static void draw_LabelWidget_(const iLabelWidget *d) { -gap_UI / 8)), init_I2(iconPad, lineHeight_Text(d->font)) }, iTrue, - isCaution ? uiTextCaution_ColorId - : flags & (disabled_WidgetFlag | pressed_WidgetFlag) ? fg - : isHover ? uiIconHover_ColorId - : uiIcon_ColorId, + iconColor, "%s", cstr_String(&str)); deinit_String(&str); @@ -387,7 +388,7 @@ static void draw_LabelWidget_(const iLabelWidget *d) { drawCentered_Text(d->font, (iRect){ addX_I2(topRight_Rect(chRect), -iconPad), init_I2(chSize, height_Rect(chRect)) }, - iTrue, uiSeparator_ColorId, rightAngle_Icon); + iTrue, iconColor /*uiSeparator_ColorId*/, rightAngle_Icon); } unsetClip_Paint(&p); } diff --git a/src/ui/mobile.c b/src/ui/mobile.c index daa1fa1a..6ea672e6 100644 --- a/src/ui/mobile.c +++ b/src/ui/mobile.c @@ -90,15 +90,15 @@ static void unselectAllPanelButtons_(iWidget *topPanel) { static iBool mainDetailSplitHandler_(iWidget *mainDetailSplit, const char *cmd) { if (equal_Command(cmd, "window.resized")) { - const iBool isPortrait = (deviceType_App() == phone_AppDeviceType && isPortrait_App()); - const iRect safeRoot = safeRect_Root(mainDetailSplit->root); - setPos_Widget(mainDetailSplit, topLeft_Rect(safeRoot)); - setFixedSize_Widget(mainDetailSplit, safeRoot.size); + const iBool isPortrait = (deviceType_App() == phone_AppDeviceType && isPortrait_App()); + const iRect safeRoot = safeRect_Root(mainDetailSplit->root); iWidget * sheet = parent_Widget(mainDetailSplit); iWidget * navi = findChild_Widget(sheet, "panel.navi"); iWidget * detailStack = findChild_Widget(mainDetailSplit, "detailstack"); const size_t numPanels = childCount_Widget(detailStack); const iBool isSideBySide = isSideBySideLayout_() && numPanels > 0; + setPos_Widget(mainDetailSplit, topLeft_Rect(safeRoot)); + setFixedSize_Widget(mainDetailSplit, safeRoot.size); setFlags_Widget(mainDetailSplit, arrangeHorizontal_WidgetFlag, isSideBySide); setFlags_Widget(detailStack, expand_WidgetFlag, isSideBySide); setFlags_Widget(detailStack, hidden_WidgetFlag, numPanels == 0); @@ -172,7 +172,16 @@ static iBool topPanelHandler_(iWidget *topPanel, const char *cmd) { } unselectAllPanelButtons_(topPanel); if (!wasClosed) { - postCommand_App("prefs.dismiss"); + /* TODO: Should come up with a more general-purpose approach here. */ + if (findWidget_App("prefs")) { + postCommand_App("prefs.dismiss"); + } + else if (findWidget_App("upload")) { + postCommand_App("upload.cancel"); + } + else { + postCommand_App("cancel"); + } } return iTrue; } @@ -503,11 +512,19 @@ void makePanelItem_Mobile(iWidget *panel, const iMenuItem *item) { else if (equal_Command(spec, "label")) { iLabelWidget *lab = new_LabelWidget(label, NULL); widget = as_Widget(lab); - setWrap_LabelWidget(lab, iTrue); - setFlags_Widget(widget, fixedHeight_WidgetFlag | frameless_WidgetFlag, iTrue); + setId_Widget(widget, id); + setWrap_LabelWidget(lab, !argLabel_Command(spec, "nowrap")); + setFlags_Widget(widget, + fixedHeight_WidgetFlag | + (!argLabel_Command(spec, "frame") ? frameless_WidgetFlag : 0), + iTrue); } else if (equal_Command(spec, "padding")) { - widget = makePadding_Widget(lineHeight_Text(labelFont_()) * 1.5f); + float height = 1.5f; + if (hasLabel_Command(spec, "arg")) { + height *= argfLabel_Command(spec, "arg"); + } + widget = makePadding_Widget(lineHeight_Text(labelFont_()) * height); } /* Apply common styling to the heading. */ if (heading) { @@ -539,7 +556,7 @@ static const iMenuItem *findDialogCancelAction_(const iMenuItem *items, size_t n return NULL; } for (size_t i = 0; i < n; i++) { - if (!iCmpStr(items[i].label, "${cancel}")) { + if (!iCmpStr(items[i].label, "${cancel}") || !iCmpStr(items[i].label, "${close}")) { return &items[i]; } } @@ -556,13 +573,20 @@ iWidget *makePanelsParent_Mobile(iWidget *parentWidget, const char *id, const iMenuItem *itemsNullTerminated, const iMenuItem *actions, size_t numActions) { + iWidget *panels = new_Widget(); + setId_Widget(panels, id); + initPanels_Mobile(panels, parentWidget, itemsNullTerminated, actions, numActions); + return panels; +} + +void initPanels_Mobile(iWidget *panels, iWidget *parentWidget, + const iMenuItem *itemsNullTerminated, + const iMenuItem *actions, size_t numActions) { /* A multipanel widget has a top panel and one or more detail panels. In a horizontal layout, the detail panels slide in from the right and cover the top panel. In a landscape layout, the detail panels are always visible on the side. */ - iWidget *sheet = new_Widget(); - setId_Widget(sheet, id); - setBackgroundColor_Widget(sheet, uiBackground_ColorId); - setFlags_Widget(sheet, + setBackgroundColor_Widget(panels, uiBackground_ColorId); + setFlags_Widget(panels, resizeToParentWidth_WidgetFlag | resizeToParentHeight_WidgetFlag | frameless_WidgetFlag | focusRoot_WidgetFlag | commandOnClick_WidgetFlag | overflowScrollable_WidgetFlag | leftEdgeDraggable_WidgetFlag, @@ -572,7 +596,7 @@ iWidget *makePanelsParent_Mobile(iWidget *parentWidget, setCommandHandler_Widget(mainDetailSplit, mainDetailSplitHandler_); setFlags_Widget(mainDetailSplit, resizeHeightOfChildren_WidgetFlag, iFalse); setId_Widget(mainDetailSplit, "mdsplit"); - addChild_Widget(sheet, iClob(mainDetailSplit)); + addChild_Widget(panels, iClob(mainDetailSplit)); } /* The panel roots. */ iWidget *topPanel = new_Widget(); { @@ -591,7 +615,6 @@ iWidget *makePanelsParent_Mobile(iWidget *parentWidget, setFlags_Widget(detailStack, collapse_WidgetFlag | resizeWidthOfChildren_WidgetFlag, iTrue); addChild_Widget(mainDetailSplit, iClob(detailStack)); } - addChild_Widget(topPanel, iClob(makePadding_Widget(lineHeight_Text(labelFont_())))); /* Slide top panel with detail panels. */ { setFlags_Widget(topPanel, refChildrenOffset_WidgetFlag, iTrue); topPanel->offsetRef = detailStack; @@ -612,15 +635,17 @@ iWidget *makePanelsParent_Mobile(iWidget *parentWidget, checkIcon_LabelWidget(naviBack); setId_Widget(as_Widget(naviBack), "panel.back"); setFont_LabelWidget(naviBack, labelFont_()); - addChildFlags_Widget(sheet, iClob(navi), + addChildFlags_Widget(panels, iClob(navi), drawBackgroundToVerticalSafeArea_WidgetFlag | arrangeHeight_WidgetFlag | resizeWidthOfChildren_WidgetFlag | resizeToParentWidth_WidgetFlag | arrangeVertical_WidgetFlag); } + iBool haveDetailPanels = iFalse; /* Create panel contents based on provided items. */ for (size_t i = 0; itemsNullTerminated[i].label; i++) { const iMenuItem *item = &itemsNullTerminated[i]; if (equal_Command(item->label, "panel")) { + haveDetailPanels = iTrue; const char *id = cstr_Rangecc(range_Command(item->label, "id")); const iString *label = hasLabel_Command(item->label, "text") ? collect_String(suffix_Command(item->label, "text")) @@ -655,10 +680,12 @@ iWidget *makePanelsParent_Mobile(iWidget *parentWidget, setFont_LabelWidget(naviBack, labelBoldFont_()); } else if (defaultItem && defaultItem != cancelItem) { - updateTextCStr_LabelWidget(naviBack, cancelItem->label); - setCommand_LabelWidget(naviBack, collectNewCStr_String(cancelItem->command - ? cancelItem->command - : "cancel")); + if (!haveDetailPanels) { + updateTextCStr_LabelWidget(naviBack, cancelItem->label); + setCommand_LabelWidget(naviBack, collectNewCStr_String(cancelItem->command + ? cancelItem->command + : "cancel")); + } iLabelWidget *defaultButton = new_LabelWidget(defaultItem->label, defaultItem->command); setFont_LabelWidget(defaultButton, labelBoldFont_()); setFlags_Widget(as_Widget(defaultButton), @@ -689,16 +716,21 @@ iWidget *makePanelsParent_Mobile(iWidget *parentWidget, } makePanelItem_Mobile( topPanel, - &(iMenuItem){ format_CStr("button text:%s", act->label), 0, 0, act->command }); + &(iMenuItem){ format_CStr("button text:" uiTextAction_ColorEscape "%s", act->label), + 0, + 0, + act->command }); } } /* Finalize the layout. */ - addChild_Widget(parentWidget, iClob(sheet)); + if (parentWidget) { + addChild_Widget(parentWidget, iClob(panels)); + } mainDetailSplitHandler_(mainDetailSplit, "window.resized"); /* make it resize the split */ - updatePanelSheetMetrics_(sheet); - arrange_Widget(sheet); + updatePanelSheetMetrics_(panels); + arrange_Widget(panels); postCommand_App("widget.overflow"); /* with the correct dimensions */ - return sheet; + printTree_Widget(panels); } #if 0 @@ -1130,7 +1162,9 @@ void setupMenuTransition_Mobile(iWidget *sheet, iBool isIncoming) { } } -void setupSheetTransition_Mobile(iWidget *sheet, iBool isIncoming) { +void setupSheetTransition_Mobile(iWidget *sheet, int flags) { + const iBool isIncoming = (flags & incoming_TransitionFlag) != 0; + const int dir = flags & dirMask_TransitionFlag; if (!isUsingPanelLayout_Mobile()) { if (prefs_App()->uiAnimations) { setFlags_Widget(sheet, horizontalOffset_WidgetFlag, iFalse); @@ -1144,17 +1178,51 @@ void setupSheetTransition_Mobile(iWidget *sheet, iBool isIncoming) { } return; } - if(isSideBySideLayout_()) { + if (isSideBySideLayout_()) { + /* TODO: Landscape transitions? */ return; } - setFlags_Widget(sheet, horizontalOffset_WidgetFlag, iTrue); + setFlags_Widget(sheet, + horizontalOffset_WidgetFlag, + dir == right_TransitionDir || dir == left_TransitionDir); if (isIncoming) { - setVisualOffset_Widget(sheet, size_Root(sheet->root).x, 0, 0); + switch (dir) { + case right_TransitionDir: + setVisualOffset_Widget(sheet, size_Root(sheet->root).x, 0, 0); + break; + case left_TransitionDir: + setVisualOffset_Widget(sheet, -size_Root(sheet->root).x, 0, 0); + break; + case top_TransitionDir: + setVisualOffset_Widget( + sheet, -bottom_Rect(boundsWithoutVisualOffset_Widget(sheet)), 0, 0); + break; + case bottom_TransitionDir: + setVisualOffset_Widget(sheet, height_Widget(sheet), 0, 0); + break; + } 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); + switch (dir) { + case right_TransitionDir: { + const iBool wasDragged = iAbs(value_Anim(&sheet->visualOffset)) > 0; + setVisualOffset_Widget(sheet, size_Root(sheet->root).x, wasDragged ? 100 : 200, + wasDragged ? 0 : easeIn_AnimFlag); + break; + } + case left_TransitionDir: + setVisualOffset_Widget(sheet, -size_Root(sheet->root).x, 200, easeIn_AnimFlag); + break; + case top_TransitionDir: + setVisualOffset_Widget(sheet, + -bottom_Rect(boundsWithoutVisualOffset_Widget(sheet)), + 200, + easeIn_AnimFlag); + break; + case bottom_TransitionDir: + setVisualOffset_Widget(sheet, height_Widget(sheet), 200, easeIn_AnimFlag); + break; + } } } diff --git a/src/ui/mobile.h b/src/ui/mobile.h index 30679c7c..e1131953 100644 --- a/src/ui/mobile.h +++ b/src/ui/mobile.h @@ -35,8 +35,23 @@ iWidget * makePanelsParent_Mobile (iWidget *parent, const char *id, const iMenuItem *itemsNullTerminated, const iMenuItem *actions, size_t numActions); +void initPanels_Mobile (iWidget *panels, iWidget *parentWidget, + const iMenuItem *itemsNullTerminated, + const iMenuItem *actions, size_t numActions); + +enum iTransitionFlags { + incoming_TransitionFlag = iBit(1), + dirMask_TransitionFlag = iBit(2) | iBit(3), +}; + +enum iTransitionDir { + right_TransitionDir = 0, + bottom_TransitionDir = 2, + left_TransitionDir = 4, + top_TransitionDir = 6, +}; -void setupMenuTransition_Mobile (iWidget *menu, iBool isIncoming); -void setupSheetTransition_Mobile (iWidget *sheet, iBool isIncoming); +void setupMenuTransition_Mobile (iWidget *menu, iBool isIncoming); +void setupSheetTransition_Mobile (iWidget *sheet, int flags); void finalizeSheet_Mobile (iWidget *sheet); diff --git a/src/ui/root.c b/src/ui/root.c index eae8e4bb..a792e93d 100644 --- a/src/ui/root.c +++ b/src/ui/root.c @@ -118,7 +118,7 @@ static const iMenuItem phoneNavMenuItems_[] = { { "${menu.downloads}", 0, 0, "downloads.open" }, { "${menu.feeds.entrylist}", 0, 0, "!open url:about:feeds" }, { "---" }, - { gear_Icon " Settings...", SDLK_COMMA, KMOD_PRIMARY, "preferences" }, + { gear_Icon " ${menu.settings}", SDLK_COMMA, KMOD_PRIMARY, "preferences" }, }; #endif /* Mobile */ @@ -1163,20 +1163,12 @@ void createUserInterface_Root(iRoot *d) { { 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" }, -#if defined (iPlatformMobile) - { "---" }, - { "${menu.page.copyurl}", 0, 0, "document.copylink" }, - { "${menu.page.copysource}", 'c', KMOD_PRIMARY, "copy" }, - { download_Icon " " saveToDownloads_Label, SDLK_s, KMOD_PRIMARY, "document.save" } }, - 11); -#else { upload_Icon " ${menu.page.upload}", 0, 0, "document.upload" }, { "---" }, { "${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); -#endif setId_Widget(as_Widget(pageMenuButton), "pagemenubutton"); setFont_LabelWidget(pageMenuButton, uiContentBold_FontId); setAlignVisually_LabelWidget(pageMenuButton, iTrue); diff --git a/src/ui/uploadwidget.c b/src/ui/uploadwidget.c index 4c72c60a..fb8aaf0a 100644 --- a/src/ui/uploadwidget.c +++ b/src/ui/uploadwidget.c @@ -81,91 +81,122 @@ void init_UploadWidget(iUploadWidget *d) { iWidget *w = as_Widget(d); init_Widget(w); setId_Widget(w, "upload"); - useSheetStyle_Widget(w); init_String(&d->originalUrl); init_String(&d->url); d->viewer = NULL; d->request = NULL; init_String(&d->filePath); d->fileSize = 0; - addChildFlags_Widget(w, - iClob(new_LabelWidget(uiHeading_ColorEscape "${heading.upload}", NULL)), - frameless_WidgetFlag); - d->info = addChildFlags_Widget(w, iClob(new_LabelWidget("", NULL)), - frameless_WidgetFlag | resizeToParentWidth_WidgetFlag | - fixedHeight_WidgetFlag); - setWrap_LabelWidget(d->info, iTrue); - /* Tabs for input data. */ - iWidget *tabs = makeTabs_Widget(w); - /* Make the tabs support vertical expansion based on content. */ { - setFlags_Widget(tabs, resizeHeightOfChildren_WidgetFlag, iFalse); - setFlags_Widget(tabs, arrangeHeight_WidgetFlag, iTrue); - iWidget *tabPages = findChild_Widget(tabs, "tabs.pages"); - setFlags_Widget(tabPages, resizeHeightOfChildren_WidgetFlag, iFalse); - setFlags_Widget(tabPages, arrangeHeight_WidgetFlag, iTrue); + const iMenuItem actions[] = { + { "${upload.port}", 0, 0, "upload.setport" }, + { "---" }, + { "${close}", SDLK_ESCAPE, 0, "upload.cancel" }, + { uiTextAction_ColorEscape "${dlg.upload.send}", SDLK_RETURN, KMOD_PRIMARY, "upload.accept" } + }; + if (isUsingPanelLayout_Mobile()) { + const iMenuItem textItems[] = { + { "title id:heading.upload.text" }, + { "input id:upload.text noheading:1" }, + { NULL } + }; + const iMenuItem fileItems[] = { + { "title id:heading.upload.file" }, + { "button text:" uiTextAction_ColorEscape "${dlg.upload.pickfile}", 0, 0, "upload.pickfile" }, + { "heading id:upload.file.name" }, + { "label id:upload.filepathlabel" }, + { "heading id:upload.file.size" }, + { "label id:upload.filesizelabel" }, + { "padding" }, + { "input id:upload.mime" }, + { "label id:upload.counter text:" }, + { NULL } + }; + initPanels_Mobile(w, NULL, (iMenuItem[]){ + { "title id:heading.upload" }, + { "label id:upload.info" }, +// { "padding" }, + { "panel id:dlg.upload.text icon:0x1f5b9", 0, 0, (const void *) textItems }, + { "panel id:dlg.upload.file icon:0x1f4c1", 0, 0, (const void *) fileItems }, + { "padding" }, + { "input id:upload.token hint:hint.upload.token" }, + { NULL } + }, actions, iElemCount(actions)); + d->info = findChild_Widget(w, "upload.info"); + d->input = findChild_Widget(w, "upload.text"); + d->filePathLabel = findChild_Widget(w, "upload.file.name"); + d->fileSizeLabel = findChild_Widget(w, "upload.file.size"); + d->mime = findChild_Widget(w, "upload.mime"); + d->token = findChild_Widget(w, "upload.token"); + d->counter = findChild_Widget(w, "upload.counter"); } - iWidget *headings, *values; - setBackgroundColor_Widget(findChild_Widget(tabs, "tabs.buttons"), uiBackgroundSidebar_ColorId); - setId_Widget(tabs, "upload.tabs"); -// const int bigGap = lineHeight_Text(uiLabel_FontId) * 3 / 4; - /* Text input. */ { - //appendTwoColumnTabPage_Widget(tabs, "${heading.upload.text}", '1', &headings, &values); - iWidget *page = new_Widget(); - setFlags_Widget(page, arrangeSize_WidgetFlag, iTrue); - d->input = new_InputWidget(0); - setId_Widget(as_Widget(d->input), "upload.text"); - setFont_InputWidget(d->input, monospace_FontId); - setLineLimits_InputWidget(d->input, 7, 20); - setUseReturnKeyBehavior_InputWidget(d->input, iFalse); /* traditional text editor */ - setHint_InputWidget(d->input, "${hint.upload.text}"); - setFixedSize_Widget(as_Widget(d->input), init_I2(120 * gap_UI, -1)); - addChild_Widget(page, iClob(d->input)); - appendFramelessTabPage_Widget(tabs, iClob(page), "${heading.upload.text}", '1', 0); - } - /* File content. */ { - appendTwoColumnTabPage_Widget(tabs, "${heading.upload.file}", '2', &headings, &values); -// iWidget *pad = addChild_Widget(headings, iClob(makePadding_Widget(0))); -// iWidget *hint = addChild_Widget(values, iClob(new_LabelWidget("${upload.file.drophint}", NULL))); -// pad->sizeRef = hint; - addChildFlags_Widget(headings, iClob(new_LabelWidget("${upload.file.name}", NULL)), frameless_WidgetFlag); - d->filePathLabel = addChildFlags_Widget(values, iClob(new_LabelWidget(uiTextAction_ColorEscape "${upload.file.drophere}", NULL)), frameless_WidgetFlag); - addChildFlags_Widget(headings, iClob(new_LabelWidget("${upload.file.size}", NULL)), frameless_WidgetFlag); - d->fileSizeLabel = addChildFlags_Widget(values, iClob(new_LabelWidget("\u2014", NULL)), frameless_WidgetFlag); - d->mime = new_InputWidget(0); - setFixedSize_Widget(as_Widget(d->mime), init_I2(70 * gap_UI, -1)); - addTwoColumnDialogInputField_Widget(headings, values, "${upload.mime}", "upload.mime", iClob(d->mime)); - } - /* Token. */ { - addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); - iWidget *page = makeTwoColumns_Widget(&headings, &values); - d->token = addTwoColumnDialogInputField_Widget( - headings, values, "${upload.token}", "upload.token", iClob(new_InputWidget(0))); - setHint_InputWidget(d->token, "${hint.upload.token}"); - setFixedSize_Widget(as_Widget(d->token), init_I2(50 * gap_UI, -1)); - addChild_Widget(w, iClob(page)); - } - /* Buttons. */ { - addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); - iWidget *buttons = - makeDialogButtons_Widget((iMenuItem[]){ { "${upload.port}", 0, 0, "upload.setport" }, - { "---" }, - { "${close}", SDLK_ESCAPE, 0, "upload.cancel" }, - { uiTextAction_ColorEscape "${dlg.upload.send}", - SDLK_RETURN, - KMOD_PRIMARY, - "upload.accept" } }, - 4); - setId_Widget(insertChildAfterFlags_Widget(buttons, - iClob(d->counter = new_LabelWidget("", NULL)), - 0, frameless_WidgetFlag), - "upload.counter"); - addChild_Widget(w, iClob(buttons)); + else { + useSheetStyle_Widget(w); + addChildFlags_Widget(w, + iClob(new_LabelWidget(uiHeading_ColorEscape "${heading.upload}", NULL)), + frameless_WidgetFlag); + d->info = addChildFlags_Widget(w, iClob(new_LabelWidget("", NULL)), + frameless_WidgetFlag | resizeToParentWidth_WidgetFlag | + fixedHeight_WidgetFlag); + setWrap_LabelWidget(d->info, iTrue); + /* Tabs for input data. */ + iWidget *tabs = makeTabs_Widget(w); + /* Make the tabs support vertical expansion based on content. */ { + setFlags_Widget(tabs, resizeHeightOfChildren_WidgetFlag, iFalse); + setFlags_Widget(tabs, arrangeHeight_WidgetFlag, iTrue); + iWidget *tabPages = findChild_Widget(tabs, "tabs.pages"); + setFlags_Widget(tabPages, resizeHeightOfChildren_WidgetFlag, iFalse); + setFlags_Widget(tabPages, arrangeHeight_WidgetFlag, iTrue); + } + iWidget *headings, *values; + setBackgroundColor_Widget(findChild_Widget(tabs, "tabs.buttons"), uiBackgroundSidebar_ColorId); + setId_Widget(tabs, "upload.tabs"); + /* Text input. */ { + iWidget *page = new_Widget(); + setFlags_Widget(page, arrangeSize_WidgetFlag, iTrue); + d->input = new_InputWidget(0); + setId_Widget(as_Widget(d->input), "upload.text"); + setFixedSize_Widget(as_Widget(d->input), init_I2(120 * gap_UI, -1)); + addChild_Widget(page, iClob(d->input)); + appendFramelessTabPage_Widget(tabs, iClob(page), "${heading.upload.text}", '1', 0); + } + /* File content. */ { + appendTwoColumnTabPage_Widget(tabs, "${heading.upload.file}", '2', &headings, &values); + addChildFlags_Widget(headings, iClob(new_LabelWidget("${upload.file.name}", NULL)), frameless_WidgetFlag); + d->filePathLabel = addChildFlags_Widget(values, iClob(new_LabelWidget(uiTextAction_ColorEscape "${upload.file.drophere}", NULL)), frameless_WidgetFlag); + addChildFlags_Widget(headings, iClob(new_LabelWidget("${upload.file.size}", NULL)), frameless_WidgetFlag); + d->fileSizeLabel = addChildFlags_Widget(values, iClob(new_LabelWidget("\u2014", NULL)), frameless_WidgetFlag); + d->mime = new_InputWidget(0); + setFixedSize_Widget(as_Widget(d->mime), init_I2(70 * gap_UI, -1)); + addTwoColumnDialogInputField_Widget(headings, values, "${upload.mime}", "upload.mime", iClob(d->mime)); + } + /* Token. */ { + addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); + iWidget *page = makeTwoColumns_Widget(&headings, &values); + d->token = addTwoColumnDialogInputField_Widget( + headings, values, "${upload.token}", "upload.token", iClob(new_InputWidget(0))); + setHint_InputWidget(d->token, "${hint.upload.token}"); + setFixedSize_Widget(as_Widget(d->token), init_I2(50 * gap_UI, -1)); + addChild_Widget(w, iClob(page)); + } + /* Buttons. */ { + addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); + iWidget *buttons = makeDialogButtons_Widget(actions, iElemCount(actions)); + setId_Widget(insertChildAfterFlags_Widget(buttons, + iClob(d->counter = new_LabelWidget("", NULL)), + 0, frameless_WidgetFlag), + "upload.counter"); + addChild_Widget(w, iClob(buttons)); + } + resizeToLargestPage_Widget(tabs); + arrange_Widget(w); + setFixedSize_Widget(as_Widget(d->token), init_I2(width_Widget(tabs) - left_Rect(parent_Widget(d->token)->rect), -1)); + setFlags_Widget(as_Widget(d->token), expand_WidgetFlag, iTrue); + setFocus_Widget(as_Widget(d->input)); } - resizeToLargestPage_Widget(tabs); - arrange_Widget(w); - setFixedSize_Widget(as_Widget(d->token), init_I2(width_Widget(tabs) - left_Rect(parent_Widget(d->token)->rect), -1)); - setFlags_Widget(as_Widget(d->token), expand_WidgetFlag, iTrue); - setFocus_Widget(as_Widget(d->input)); + setFont_InputWidget(d->input, monospace_FontId); + setUseReturnKeyBehavior_InputWidget(d->input, iFalse); /* traditional text editor */ + setLineLimits_InputWidget(d->input, 7, 20); + setHint_InputWidget(d->input, "${hint.upload.text}"); setBackupFileName_InputWidget(d->input, "uploadbackup.txt"); updateInputMaxHeight_UploadWidget_(d); } @@ -201,6 +232,7 @@ static void setUrlPort_UploadWidget_(iUploadWidget *d, const iString *url, uint1 appendFormat_String(&d->url, ":%u", overridePort ? overridePort : titanPortForUrl_(url)); appendRange_String(&d->url, (iRangecc){ parts.path.start, constEnd_String(url) }); setText_LabelWidget(d->info, &d->url); + arrange_Widget(as_Widget(d)); } void setUrl_UploadWidget(iUploadWidget *d, const iString *url) { @@ -233,7 +265,7 @@ static iBool processEvent_UploadWidget_(iUploadWidget *d, const SDL_Event *ev) { if (isResize_UserEvent(ev)) { updateInputMaxHeight_UploadWidget_(d); } - if (isCommand_Widget(w, ev, "upload.cancel")) { + if (equal_Command(cmd, "upload.cancel")) { setupSheetTransition_Mobile(w, iFalse); destroy_Widget(w); return iTrue; diff --git a/src/ui/util.c b/src/ui/util.c index b875e260..6069e800 100644 --- a/src/ui/util.c +++ b/src/ui/util.c @@ -1061,6 +1061,7 @@ iWidget *removeTabPage_Widget(iWidget *tabs, size_t index) { } void resizeToLargestPage_Widget(iWidget *tabs) { + if (!tabs) return; // puts("RESIZE TO LARGEST PAGE ..."); iWidget *pages = findChild_Widget(tabs, "tabs.pages"); iForEach(ObjectList, i, children_Widget(pages)) { @@ -1216,7 +1217,7 @@ iBool valueInputHandler_(iWidget *dlg, const char *cmd) { postCommandf_App("valueinput.cancelled id:%s", cstr_String(id_Widget(dlg))); setId_Widget(dlg, ""); /* no further commands to emit */ } - setupSheetTransition_Mobile(dlg, iFalse); + setupSheetTransition_Mobile(dlg, top_TransitionDir); destroy_Widget(dlg); return iTrue; } @@ -1225,13 +1226,13 @@ iBool valueInputHandler_(iWidget *dlg, const char *cmd) { else if (equal_Command(cmd, "valueinput.cancel")) { postCommandf_App("valueinput.cancelled id:%s", cstr_String(id_Widget(dlg))); setId_Widget(dlg, ""); /* no further commands to emit */ - setupSheetTransition_Mobile(dlg, iFalse); + setupSheetTransition_Mobile(dlg, top_TransitionDir); destroy_Widget(dlg); return iTrue; } else if (equal_Command(cmd, "valueinput.accept")) { acceptValueInput_(dlg); - setupSheetTransition_Mobile(dlg, iFalse); + setupSheetTransition_Mobile(dlg, top_TransitionDir); destroy_Widget(dlg); return iTrue; } @@ -1345,7 +1346,9 @@ iWidget *makeValueInput_Widget(iWidget *parent, const iString *initialValue, con acceptKeyMod_ReturnKeyBehavior(prefs_App()->returnKey), "valueinput.accept" } }, 2))); - finalizeSheet_Mobile(dlg); +// finalizeSheet_Mobile(dlg); + arrange_Widget(dlg); + setupSheetTransition_Mobile(dlg, incoming_TransitionFlag | top_TransitionDir); if (parent) { setFocus_Widget(as_Widget(input)); } @@ -1915,6 +1918,7 @@ iWidget *makePreferences_Widget(void) { { NULL } }; iWidget *dlg = makePanels_Mobile("prefs", (iMenuItem[]){ + { "title id:heading.settings" }, { "panel text:" gear_Icon " ${heading.prefs.general}", 0, 0, (const void *) generalPanelItems }, { "panel icon:0x1f5a7 id:heading.prefs.network", 0, 0, (const void *) networkPanelItems }, { "panel text:" person_Icon " ${sidebar.identities}", 0, 0, (const void *) identityPanelItems }, @@ -2405,7 +2409,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_Mobile(dlg); +// finalizeSheet_Mobile(dlg); } /* Initialize. */ { const iBookmark *bm = bookmarkId ? get_Bookmarks(bookmarks_App(), bookmarkId) : NULL; @@ -2419,6 +2423,7 @@ iWidget *makeFeedSettings_Widget(uint32_t bookmarkId) { iTrue); setCommandHandler_Widget(dlg, handleFeedSettingCommands_); } + setupSheetTransition_Mobile(dlg, incoming_TransitionFlag); return dlg; } diff --git a/src/ui/widget.c b/src/ui/widget.c index 4fd8f066..659a00cc 100644 --- a/src/ui/widget.c +++ b/src/ui/widget.c @@ -452,7 +452,7 @@ static void arrange_Widget_(iWidget *d) { else if (d->flags & centerHorizontal_WidgetFlag) { centerHorizontal_Widget_(d); } - if (d->flags & resizeToParentWidth_WidgetFlag) { + if (d->flags & resizeToParentWidth_WidgetFlag && d->parent) { iRect childBounds = zero_Rect(); if (flags_Widget(d->parent) & arrangeWidth_WidgetFlag) { /* Can't go narrower than what the children require, though. */ @@ -462,7 +462,7 @@ static void arrange_Widget_(iWidget *d) { setWidth_Widget_(d, iMaxi(width_Rect(innerRect_Widget_(d->parent)), width_Rect(childBounds))); } - if (d->flags & resizeToParentHeight_WidgetFlag) { + if (d->flags & resizeToParentHeight_WidgetFlag && d->parent) { TRACE(d, "resize to parent height"); setHeight_Widget_(d, height_Rect(innerRect_Widget_(d->parent))); } -- cgit v1.2.3 From 4cf52f29b926a924d838a3158d5c78b3337ee0ee Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Mon, 13 Sep 2021 19:52:21 +0300 Subject: Mobile: New selection logic for InputWidget Touch-based interaction requires a different kind of selection and copy/paste behavior. This isn't done yet; especially multi-line text still needs work. --- po/en.po | 12 ++ res/lang/de.bin | Bin 24162 -> 24222 bytes res/lang/en.bin | Bin 22763 -> 22823 bytes res/lang/es.bin | Bin 25286 -> 25346 bytes res/lang/fi.bin | Bin 25291 -> 25351 bytes res/lang/fr.bin | Bin 26260 -> 26320 bytes res/lang/ia.bin | Bin 24890 -> 24950 bytes res/lang/ie.bin | Bin 24645 -> 24705 bytes res/lang/pl.bin | Bin 25823 -> 25883 bytes res/lang/ru.bin | Bin 37542 -> 37602 bytes res/lang/sr.bin | Bin 37199 -> 37259 bytes res/lang/tok.bin | Bin 23081 -> 23141 bytes res/lang/zh_Hans.bin | Bin 21842 -> 21902 bytes res/lang/zh_Hant.bin | Bin 22027 -> 22087 bytes src/app.c | 16 +- src/defs.h | 2 + src/ui/color.h | 1 + src/ui/documentwidget.c | 21 +- src/ui/inputwidget.c | 563 +++++++++++++++++++++++++++++++++++++----------- src/ui/paint.c | 19 +- src/ui/paint.h | 2 + src/ui/root.c | 33 ++- src/ui/touch.c | 9 +- src/ui/util.c | 70 +++--- src/ui/widget.c | 40 ++-- src/ui/window.c | 10 +- src/ui/window.h | 2 +- 27 files changed, 591 insertions(+), 209 deletions(-) (limited to 'src/ui/documentwidget.c') diff --git a/po/en.po b/po/en.po index 546a9489..96026528 100644 --- a/po/en.po +++ b/po/en.po @@ -273,6 +273,18 @@ msgstr "Copy" msgid "menu.paste" msgstr "Paste" +# keep this short (3x1 horiz layout) +msgid "menu.selectall" +msgstr "Select All" + +# keep this short (3x1 horiz layout) +msgid "menu.delete" +msgstr "Delete" + +# keep this short (3x1 horiz layout) +msgid "menu.undo" +msgstr "Undo" + msgid "menu.select.clear" msgstr "Clear Selection" diff --git a/res/lang/de.bin b/res/lang/de.bin index ba87d002..f5a6b07e 100644 Binary files a/res/lang/de.bin and b/res/lang/de.bin differ diff --git a/res/lang/en.bin b/res/lang/en.bin index a7dfb866..df3c4025 100644 Binary files a/res/lang/en.bin and b/res/lang/en.bin differ diff --git a/res/lang/es.bin b/res/lang/es.bin index 7e7398d6..9f5a169f 100644 Binary files a/res/lang/es.bin and b/res/lang/es.bin differ diff --git a/res/lang/fi.bin b/res/lang/fi.bin index 607e52fd..ac3b99ef 100644 Binary files a/res/lang/fi.bin and b/res/lang/fi.bin differ diff --git a/res/lang/fr.bin b/res/lang/fr.bin index 955695ed..7d40e32c 100644 Binary files a/res/lang/fr.bin and b/res/lang/fr.bin differ diff --git a/res/lang/ia.bin b/res/lang/ia.bin index 61a18efc..d10ab85e 100644 Binary files a/res/lang/ia.bin and b/res/lang/ia.bin differ diff --git a/res/lang/ie.bin b/res/lang/ie.bin index 06ea7979..14349637 100644 Binary files a/res/lang/ie.bin and b/res/lang/ie.bin differ diff --git a/res/lang/pl.bin b/res/lang/pl.bin index 5fc5e24a..9ddd137f 100644 Binary files a/res/lang/pl.bin and b/res/lang/pl.bin differ diff --git a/res/lang/ru.bin b/res/lang/ru.bin index 1718f647..366c6ee5 100644 Binary files a/res/lang/ru.bin and b/res/lang/ru.bin differ diff --git a/res/lang/sr.bin b/res/lang/sr.bin index 60b7b600..870b0950 100644 Binary files a/res/lang/sr.bin and b/res/lang/sr.bin differ diff --git a/res/lang/tok.bin b/res/lang/tok.bin index 3298f0e8..dac5fb33 100644 Binary files a/res/lang/tok.bin and b/res/lang/tok.bin differ diff --git a/res/lang/zh_Hans.bin b/res/lang/zh_Hans.bin index 8c32a0c5..3407c485 100644 Binary files a/res/lang/zh_Hans.bin and b/res/lang/zh_Hans.bin differ diff --git a/res/lang/zh_Hant.bin b/res/lang/zh_Hant.bin index 68f7d3bc..b9001e2d 100644 Binary files a/res/lang/zh_Hant.bin and b/res/lang/zh_Hant.bin differ diff --git a/src/app.c b/src/app.c index fa601ac3..37f9c804 100644 --- a/src/app.c +++ b/src/app.c @@ -1069,11 +1069,11 @@ iLocalDef iBool isWaitingAllowed_App_(iApp *d) { return iFalse; } #endif -#if defined (iPlatformMobile) - if (!isFinished_Anim(&d->window->rootOffset)) { - return iFalse; - } -#endif +//#if defined (iPlatformMobile) +// if (!isFinished_Anim(&d->window->rootOffset)) { +// return iFalse; +// } +//#endif return !value_Atomic(&d->pendingRefresh) && isEmpty_SortedArray(&d->tickers); } @@ -1318,7 +1318,7 @@ void processEvents_App(enum iAppEventMode eventMode) { } } #if defined (LAGRANGE_ENABLE_IDLE_SLEEP) - if (d->isIdling && !gotEvents && isFinished_Anim(&d->window->rootOffset)) { + if (d->isIdling && !gotEvents /*&& isFinished_Anim(&d->window->rootOffset)*/) { /* This is where we spend most of our time when idle. 60 Hz still quite a lot but we can't wait too long after the user tries to interact again with the app. In any case, on macOS SDL_WaitEvent() seems to use 10x more CPU time than sleeping. */ @@ -1411,9 +1411,9 @@ void refresh_App(void) { #endif if (!exchange_Atomic(&d->pendingRefresh, iFalse)) { /* Refreshing wasn't pending. */ - if (isFinished_Anim(&d->window->rootOffset)) { +// if (isFinished_Anim(&d->window->rootOffset)) { return; - } +// } } // iTime draw; // initCurrent_Time(&draw); diff --git a/src/defs.h b/src/defs.h index 6b12c86c..c3a23596 100644 --- a/src/defs.h +++ b/src/defs.h @@ -151,6 +151,8 @@ iLocalDef int acceptKeyMod_ReturnKeyBehavior(int behavior) { #define magnifyingGlass_Icon "\U0001f50d" #define midEllipsis_Icon "\u00b7\u00b7\u00b7" #define return_Icon "\u23ce" +#define undo_Icon "\u23ea" +#define select_Icon "\u2b1a" #if defined (iPlatformApple) # define shift_Icon "\u21e7" diff --git a/src/ui/color.h b/src/ui/color.h index 37ec49eb..a1d863dc 100644 --- a/src/ui/color.h +++ b/src/ui/color.h @@ -183,6 +183,7 @@ iLocalDef iBool isRegularText_ColorId(enum iColorId d) { #define mask_ColorId 0x7f #define permanent_ColorId 0x80 /* cannot be changed via escapes */ #define fillBackground_ColorId 0x100 /* fill background with same color, but alpha 0 */ +#define opaque_ColorId 0x200 #define asciiBase_ColorEscape 33 #define asciiExtended_ColorEscape (128 - asciiBase_ColorEscape) diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 4b3c2db0..8ea695d5 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -4597,23 +4597,6 @@ static void drawMedia_DocumentWidget_(const iDocumentWidget *d, iPaint *p) { } } -static void drawPin_(iPaint *p, iRect rangeRect, int dir) { - const int pinColor = tmQuote_ColorId; - const int height = height_Rect(rangeRect); - iRect pin; - if (dir == 0) { - pin = (iRect){ add_I2(topLeft_Rect(rangeRect), init_I2(-gap_UI / 4, -gap_UI)), - init_I2(gap_UI / 2, height + gap_UI) }; - } - else { - pin = (iRect){ addX_I2(topRight_Rect(rangeRect), -gap_UI / 4), - init_I2(gap_UI / 2, height + gap_UI) }; - } - fillRect_Paint(p, pin, pinColor); - fillRect_Paint(p, initCentered_Rect(dir == 0 ? topMid_Rect(pin) : bottomMid_Rect(pin), - init1_I2(gap_UI * 2)), pinColor); -} - static void extend_GmRunRange_(iGmRunRange *runs) { if (runs->start) { runs->start--; @@ -4857,8 +4840,8 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) { SDL_SetRenderDrawBlendMode(render, SDL_BLENDMODE_NONE); /* Selection range pins. */ if (isTouchSelecting) { - drawPin_(&ctx.paint, ctx.firstMarkRect, 0); - drawPin_(&ctx.paint, ctx.lastMarkRect, 1); + drawPin_Paint(&ctx.paint, ctx.firstMarkRect, 0, tmQuote_ColorId); + drawPin_Paint(&ctx.paint, ctx.lastMarkRect, 1, tmQuote_ColorId); } } drawMedia_DocumentWidget_(d, &ctx.paint); diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c index ad630223..12eb490d 100644 --- a/src/ui/inputwidget.c +++ b/src/ui/inputwidget.c @@ -179,19 +179,23 @@ static void deinit_InputUndo_(iInputUndo *d) { } enum iInputWidgetFlag { - isSensitive_InputWidgetFlag = iBit(1), - isUrl_InputWidgetFlag = iBit(2), /* affected by decoding preference */ - enterPressed_InputWidgetFlag = iBit(3), - selectAllOnFocus_InputWidgetFlag = iBit(4), - notifyEdits_InputWidgetFlag = iBit(5), - eatEscape_InputWidgetFlag = iBit(6), - isMarking_InputWidgetFlag = iBit(7), - markWords_InputWidgetFlag = iBit(8), - needUpdateBuffer_InputWidgetFlag = iBit(9), - enterKeyEnabled_InputWidgetFlag = iBit(10), - lineBreaksEnabled_InputWidgetFlag= iBit(11), - needBackup_InputWidgetFlag = iBit(12), + isSensitive_InputWidgetFlag = iBit(1), + isUrl_InputWidgetFlag = iBit(2), /* affected by decoding preference */ + enterPressed_InputWidgetFlag = iBit(3), + selectAllOnFocus_InputWidgetFlag = iBit(4), + notifyEdits_InputWidgetFlag = iBit(5), + eatEscape_InputWidgetFlag = iBit(6), + isMarking_InputWidgetFlag = iBit(7), + markWords_InputWidgetFlag = iBit(8), + needUpdateBuffer_InputWidgetFlag = iBit(9), + enterKeyEnabled_InputWidgetFlag = iBit(10), + lineBreaksEnabled_InputWidgetFlag = iBit(11), + needBackup_InputWidgetFlag = iBit(12), useReturnKeyBehavior_InputWidgetFlag = iBit(13), + //touchBehavior_InputWidgetFlag = iBit(14), /* different behavior depending on interaction method */ + dragCursor_InputWidgetFlag = iBit(14), + dragMarkerStart_InputWidgetFlag = iBit(15), + dragMarkerEnd_InputWidgetFlag = iBit(16), }; /*----------------------------------------------------------------------------------------------*/ @@ -217,6 +221,10 @@ struct Impl_InputWidget { iArray undoStack; int font; iClick click; + uint32_t tapStartTime; + uint32_t lastTapTime; + iInt2 lastTapPos; + int tapCount; int wheelAccum; int cursorVis; uint32_t timer; @@ -460,14 +468,18 @@ static iWrapText wrap_InputWidget_(const iInputWidget *d, int y) { }; } -static iInt2 relativeCursorCoord_InputWidget_(const iInputWidget *d) { +static iInt2 relativeCoord_InputWidget_(const iInputWidget *d, iInt2 pos) { /* Relative to the start of the line on which the cursor is. */ - iWrapText wt = wrap_InputWidget_(d, d->cursor.y); - wt.hitChar = wt.text.start + d->cursor.x; + iWrapText wt = wrap_InputWidget_(d, pos.y); + wt.hitChar = wt.text.start + pos.x; measure_WrapText(&wt, d->font); return wt.hitAdvance_out; } +static iInt2 relativeCursorCoord_InputWidget_(const iInputWidget *d) { + return relativeCoord_InputWidget_(d, d->cursor); +} + static void updateVisible_InputWidget_(iInputWidget *d) { const int totalWraps = numWrapLines_InputWidget_(d); const int visWraps = iClamp(totalWraps, d->minWrapLines, d->maxWrapLines); @@ -632,7 +644,7 @@ void init_InputWidget(iInputWidget *d, size_t maxLen) { init_Widget(w); d->validator = NULL; d->validatorContext = NULL; - setFlags_Widget(w, focusable_WidgetFlag | hover_WidgetFlag | touchDrag_WidgetFlag, iTrue); + setFlags_Widget(w, focusable_WidgetFlag | hover_WidgetFlag, iTrue); #if defined (iPlatformMobile) setFlags_Widget(w, extraPadding_WidgetFlag, iTrue); #endif @@ -662,6 +674,8 @@ void init_InputWidget(iInputWidget *d, size_t maxLen) { splitToLines_(&iStringLiteral(""), &d->lines); setFlags_Widget(w, fixedHeight_WidgetFlag, iTrue); /* resizes its own height */ init_Click(&d->click, d, SDL_BUTTON_LEFT); + d->lastTapTime = 0; + d->tapCount = 0; d->wheelAccum = 0; d->timer = 0; d->cursorVis = 0; @@ -993,7 +1007,7 @@ void begin_InputWidget(iInputWidget *d) { d->mark = (iRanges){ 0, lastLine_InputWidget_(d)->range.end }; d->cursor = cursorMax_InputWidget_(d); } - else { + else if (~d->inFlags & isMarking_InputWidgetFlag) { iZap(d->mark); } enableEditorKeysInMenus_(iFalse); @@ -1013,9 +1027,10 @@ void end_InputWidget(iInputWidget *d, iBool accept) { splitToLines_(&d->oldText, &d->lines); } d->inFlags |= needUpdateBuffer_InputWidgetFlag; + d->inFlags &= ~isMarking_InputWidgetFlag; startOrStopCursorTimer_InputWidget_(d, iFalse); SDL_StopTextInput(); - setFlags_Widget(w, selected_WidgetFlag | keepOnTop_WidgetFlag, iFalse); + setFlags_Widget(w, selected_WidgetFlag | keepOnTop_WidgetFlag | touchDrag_WidgetFlag, iFalse); const char *id = cstr_String(id_Widget(as_Widget(d))); if (!*id) id = "_"; refresh_Widget(w); @@ -1445,88 +1460,31 @@ static iBool checkAcceptMods_InputWidget_(const iInputWidget *d, int mods) { return mods == 0; } -static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { - iWidget *w = as_Widget(d); - /* Resize according to width immediately. */ - if (d->lastUpdateWidth != w->rect.size.x) { - d->inFlags |= needUpdateBuffer_InputWidgetFlag; - if (d->inFlags & isUrl_InputWidgetFlag) { - /* Restore/omit the default scheme if necessary. */ - setText_InputWidget(d, text_InputWidget(d)); - } - updateAllLinesAndResizeHeight_InputWidget_(d); - d->lastUpdateWidth = w->rect.size.x; - } - if (isCommand_Widget(w, ev, "focus.gained")) { - begin_InputWidget(d); - return iFalse; - } - else if (isEditing_InputWidget_(d) && (isCommand_UserEvent(ev, "window.focus.lost") || - isCommand_UserEvent(ev, "window.focus.gained"))) { - startOrStopCursorTimer_InputWidget_(d, isCommand_UserEvent(ev, "window.focus.gained")); - d->cursorVis = 1; - refresh_Widget(d); - return iFalse; - } - else if (isCommand_UserEvent(ev, "keyroot.changed")) { - d->inFlags |= needUpdateBuffer_InputWidgetFlag; - } - else if (isCommand_UserEvent(ev, "lang.changed")) { - set_String(&d->hint, &d->srcHint); - translate_Lang(&d->hint); - return iFalse; - } - else if (isCommand_Widget(w, ev, "focus.lost")) { - end_InputWidget(d, iTrue); - return iFalse; - } - else if ((isCommand_UserEvent(ev, "copy") || isCommand_UserEvent(ev, "input.copy")) && - isEditing_InputWidget_(d)) { - copy_InputWidget_(d, argLabel_Command(command_UserEvent(ev), "cut")); - return iTrue; - } - else if (isCommand_UserEvent(ev, "input.paste") && isEditing_InputWidget_(d)) { - paste_InputWidget_(d); - return iTrue; - } - else if (isCommand_UserEvent(ev, "theme.changed")) { - if (d->buffered) { - d->inFlags |= needUpdateBuffer_InputWidgetFlag; - } - return iFalse; - } - else if (isCommand_UserEvent(ev, "keyboard.changed")) { - if (isFocused_Widget(d) && arg_Command(command_UserEvent(ev))) { - iRect rect = bounds_Widget(w); - rect.pos.y -= value_Anim(&get_Window()->rootOffset); - const iInt2 visRoot = visibleSize_Root(w->root); - if (bottom_Rect(rect) > visRoot.y) { - setValue_Anim(&get_Window()->rootOffset, -(bottom_Rect(rect) - visRoot.y), 250); - } - } - return iFalse; - } - else if (isCommand_UserEvent(ev, "text.insert")) { - pushUndo_InputWidget_(d); - deleteMarked_InputWidget_(d); - insertChar_InputWidget_(d, arg_Command(command_UserEvent(ev))); - contentsWereChanged_InputWidget_(d); - return iTrue; - } - else if (isCommand_Widget(w, ev, "input.backup")) { - if (d->inFlags & needBackup_InputWidgetFlag) { - saveBackup_InputWidget_(d); - } - return iTrue; - } - else if (isMetricsChange_UserEvent(ev)) { - updateMetrics_InputWidget_(d); - // updateLinesAndResize_InputWidget_(d); +enum iEventResult { + ignored_EventResult = 0, /* event was not processed */ + false_EventResult = 1, /* event was processed but other widgets can still process it, too*/ + true_EventResult = 2, /* event was processed and should not be passed on */ +}; + +static void markWordAtCursor_InputWidget_(iInputWidget *d) { + d->mark.start = d->mark.end = cursorToIndex_InputWidget_(d, d->cursor); + extendRange_InputWidget_(d, &d->mark.start, -1); + extendRange_InputWidget_(d, &d->mark.end, +1); + d->initialMark = d->mark; +} + +static void showClipMenu_(iInt2 coord) { + iWidget *clipMenu = findWidget_App("clipmenu"); + if (isVisible_Widget(clipMenu)) { + closeMenu_Widget(clipMenu); } - else if (isFocused_Widget(d) && isCommand_UserEvent(ev, "copy")) { - copy_InputWidget_(d, iFalse); - return iTrue; + else { + openMenuFlags_Widget(clipMenu, coord, iFalse); } +} + +static enum iEventResult processPointerEvents_InputWidget_(iInputWidget *d, const SDL_Event *ev) { + iWidget *w = as_Widget(d); if (ev->type == SDL_MOUSEMOTION && (isHover_Widget(d) || flags_Widget(w) & keepOnTop_WidgetFlag)) { const iInt2 coord = init_I2(ev->motion.x, ev->motion.y); const iInt2 inner = windowToInner_Widget(w, coord); @@ -1559,10 +1517,15 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { d->visWrapLines.start += lineDelta; d->visWrapLines.end += lineDelta; d->inFlags |= needUpdateBuffer_InputWidgetFlag; - refresh_Widget(d); - return iTrue; + refresh_Widget(d); + return true_EventResult; } - return iFalse; + return false_EventResult; + } + if (ev->type == SDL_MOUSEBUTTONDOWN && ev->button.button == SDL_BUTTON_RIGHT && + contains_Widget(w, init_I2(ev->button.x, ev->button.y))) { + showClipMenu_(mouseCoord_Window(get_Window(), ev->button.which)); + return iTrue; } switch (processEvent_Click(&d->click, ev)) { case none_ClickResult: @@ -1584,10 +1547,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { d->inFlags &= ~(isMarking_InputWidgetFlag | markWords_InputWidgetFlag); if (d->click.count == 2) { d->inFlags |= isMarking_InputWidgetFlag | markWords_InputWidgetFlag; - d->mark.start = d->mark.end = cursorToIndex_InputWidget_(d, d->cursor); - extendRange_InputWidget_(d, &d->mark.start, -1); - extendRange_InputWidget_(d, &d->mark.end, +1); - d->initialMark = d->mark; + markWordAtCursor_InputWidget_(d); refresh_Widget(w); } if (d->click.count == 3) { @@ -1595,11 +1555,11 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { } } refresh_Widget(d); - return iTrue; + return true_EventResult; } case aborted_ClickResult: d->inFlags &= ~isMarking_InputWidgetFlag; - return iTrue; + return true_EventResult; case drag_ClickResult: d->cursor = coordCursor_InputWidget_(d, pos_Click(&d->click)); showCursor_InputWidget_(d); @@ -1614,30 +1574,374 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { d->mark.start = isFwd ? d->initialMark.start : d->initialMark.end; } refresh_Widget(w); - return iTrue; + return true_EventResult; case finished_ClickResult: d->inFlags &= ~isMarking_InputWidgetFlag; - return iTrue; + return true_EventResult; } if (ev->type == SDL_MOUSEMOTION && flags_Widget(w) & keepOnTop_WidgetFlag) { const iInt2 coord = init_I2(ev->motion.x, ev->motion.y); if (contains_Click(&d->click, coord)) { - return iTrue; + return true_EventResult; } } - if (ev->type == SDL_MOUSEBUTTONDOWN && ev->button.button == SDL_BUTTON_RIGHT && - contains_Widget(w, init_I2(ev->button.x, ev->button.y))) { - iWidget *clipMenu = findWidget_App("clipmenu"); - if (isVisible_Widget(clipMenu)) { - closeMenu_Widget(clipMenu); + return ignored_EventResult; +} + +static iInt2 touchCoordCursor_InputWidget_(const iInputWidget *d, iInt2 coord) { + /* Clamp to the bounds so the cursor doesn't wrap at the ends. */ + iRect bounds = shrunk_Rect(contentBounds_InputWidget_(d), one_I2()); + bounds.size.y = iMini(numWrapLines_InputWidget_(d), d->maxWrapLines) * lineHeight_Text(d->font) - 2; + return coordCursor_InputWidget_(d, min_I2(bottomRight_Rect(bounds), + max_I2(coord, topLeft_Rect(bounds)))); +} + +static iBool isInsideMark_InputWidget_(const iInputWidget *d, size_t pos) { + const iRanges mark = mark_InputWidget_(d); + return contains_Range(&mark, pos); +} + +static int distanceToPos_InputWidget_(const iInputWidget *d, iInt2 uiCoord, iInt2 textPos) { + const iInt2 a = addY_I2(relativeCoord_InputWidget_(d, textPos), lineHeight_Text(d->font) / 2); + const iInt2 b = sub_I2(uiCoord, topLeft_Rect(contentBounds_InputWidget_(d))); + return dist_I2(a, b); +} + +static enum iEventResult processTouchEvents_InputWidget_(iInputWidget *d, const SDL_Event *ev) { + iWidget *w = as_Widget(d); + /* + + first tap to focus & select all/place cursor + + focused tap to place cursor + - drag cursor to move it + - double-click to select a word + - drag to move selection handles + - long-press for context menu: copy, paste, delete, select all, deselect + - double-click and hold to select words + - triple-click to select all + - drag/wheel elsewhere to scroll (contents or overflow), no change in focus + */ +// if (ev->type != SDL_MOUSEBUTTONUP && ev->type != SDL_MOUSEBUTTONDOWN && +// ev->type != SDL_MOUSEWHEEL && ev->type != SDL_MOUSEMOTION && +// !(ev->type == SDL_USEREVENT && ev->user.code == widgetTapBegins_UserEventCode) && +// !(ev->type == SDL_USEREVENT && ev->user.code == widgetTouchEnds_UserEventCode)) { +// return ignored_EventResult; +// } + if (isFocused_Widget(w)) { + if (ev->type == SDL_USEREVENT && ev->user.code == widgetTapBegins_UserEventCode) { + d->lastTapTime = d->tapStartTime; + d->tapStartTime = SDL_GetTicks(); + const int tapDist = dist_I2(latestPosition_Touch(), d->lastTapPos); + d->lastTapPos = latestPosition_Touch(); + printf("[%p] tap start time: %u (%u) %d\n", w, d->tapStartTime, d->tapStartTime - d->lastTapTime, tapDist); + if (d->tapStartTime - d->lastTapTime < 400 && tapDist < gap_UI * 4) { + d->tapCount++; + printf("[%p] >> tap count: %d\n", w, d->tapCount); + } + else { + d->tapCount = 0; + } + if (!isEmpty_Range(&d->mark)) { + const int dist[2] = { + distanceToPos_InputWidget_(d, latestPosition_Touch(), + indexToCursor_InputWidget_(d, d->mark.start)), + distanceToPos_InputWidget_(d, latestPosition_Touch(), + indexToCursor_InputWidget_(d, d->mark.end)) + }; + if (dist[0] < dist[1]) { + printf("[%p] begin marker start drag\n", w); + d->inFlags |= dragMarkerStart_InputWidgetFlag; + } + else { + printf("[%p] begin marker end drag\n", w); + d->inFlags |= dragMarkerEnd_InputWidgetFlag; + } + d->inFlags |= isMarking_InputWidgetFlag; + setFlags_Widget(w, touchDrag_WidgetFlag, iTrue); + } + else { + const int dist = distanceToPos_InputWidget_(d, latestPosition_Touch(), d->cursor); + printf("[%p] tap dist: %d\n", w, dist); + if (dist < gap_UI * 10) { + printf("[%p] begin cursor drag\n", w); + setFlags_Widget(w, touchDrag_WidgetFlag, iTrue); + d->inFlags |= dragCursor_InputWidgetFlag; +// d->inFlags |= touchBehavior_InputWidgetFlag; +// setMouseGrab_Widget(w); +// return iTrue; + } + } +// if (~d->inFlags & selectAllOnFocus_InputWidgetFlag) { +// d->cursor = coordCursor_InputWidget_(d, pos_Click(&d->click)); +// showCursor_InputWidget_(d); +// } + return true_EventResult; + } + } +#if 0 + else if (isFocused_Widget(w)) { + if (ev->type == SDL_MOUSEMOTION) { + if (~d->inFlags & touchBehavior_InputWidgetFlag) { + const iInt2 curPos = relativeCursorCoord_InputWidget_(d); + const iInt2 relClick = sub_I2(pos_Click(&d->click), + topLeft_Rect(contentBounds_InputWidget_(d))); + if (dist_I2(curPos, relClick) < gap_UI * 8) { + printf("tap on cursor!\n"); + setFlags_Widget(w, touchDrag_WidgetFlag, iTrue); + d->inFlags |= touchBehavior_InputWidgetFlag; + printf("[Input] begin cursor drag\n"); + setMouseGrab_Widget(w); + return iTrue; + } + } + else if (ev->motion.x > 0 && ev->motion.y > 0) { + printf("[Input] cursor being dragged\n"); + iRect bounds = shrunk_Rect(contentBounds_InputWidget_(d), one_I2()); + bounds.size.y = iMini(numWrapLines_InputWidget_(d), d->maxWrapLines) * lineHeight_Text(d->font) - 2; + iInt2 mpos = init_I2(ev->motion.x, ev->motion.y); + mpos = min_I2(bottomRight_Rect(bounds), max_I2(mpos, topLeft_Rect(bounds))); + d->cursor = coordCursor_InputWidget_(d, mpos); + showCursor_InputWidget_(d); + refresh_Widget(w); + return iTrue; + } } - else { - openMenuFlags_Widget(clipMenu, - mouseCoord_Window(get_Window(), ev->button.which), - iFalse); + if (d->inFlags & touchBehavior_InputWidgetFlag) { + if (ev->type == SDL_MOUSEBUTTONUP || + (ev->type == SDL_USEREVENT && ev->user.code == widgetTouchEnds_UserEventCode)) { + d->inFlags &= ~touchBehavior_InputWidgetFlag; + setFlags_Widget(w, touchDrag_WidgetFlag, iFalse); + setMouseGrab_Widget(NULL); + printf("[Input] touch ends\n"); + return iFalse; + } + } + } +#endif +#if 1 + if ((ev->type == SDL_MOUSEBUTTONDOWN || ev->type == SDL_MOUSEBUTTONUP) && + ev->button.button == SDL_BUTTON_RIGHT && contains_Widget(w, latestPosition_Touch())) { + if (ev->type == SDL_MOUSEBUTTONDOWN) { + /*if (isFocused_Widget(w)) { + d->inFlags |= isMarking_InputWidgetFlag; + d->cursor = touchCoordCursor_InputWidget_(d, latestPosition_Touch()); + markWordAtCursor_InputWidget_(d); + refresh_Widget(d); + return true_EventResult; + }*/ + setFocus_Widget(w); + d->inFlags |= isMarking_InputWidgetFlag; + d->cursor = touchCoordCursor_InputWidget_(d, latestPosition_Touch()); + markWordAtCursor_InputWidget_(d); + d->cursor = indexToCursor_InputWidget_(d, d->mark.end); + refresh_Widget(d); + } + return true_EventResult; + } + switch (processEvent_Click(&d->click, ev)) { + case none_ClickResult: + break; + case started_ClickResult: { + printf("[%p] started\n", w); + /* + const iInt2 curPos = relativeCursorCoord_InputWidget_(d); + const iInt2 relClick = sub_I2(pos_Click(&d->click), + topLeft_Rect(contentBounds_InputWidget_(d))); + if (dist_I2(curPos, relClick) < gap_UI * 8) { + printf("tap on cursor!\n"); + setFlags_Widget(w, touchDrag_WidgetFlag, iTrue); + } + else { + printf("tap elsewhere\n"); + }*/ + return true_EventResult; + } + case drag_ClickResult: + printf("[%p] drag %d,%d\n", w, pos_Click(&d->click).x, pos_Click(&d->click).y); + if (d->inFlags & dragCursor_InputWidgetFlag) { + iZap(d->mark); + d->cursor = touchCoordCursor_InputWidget_(d, pos_Click(&d->click)); + showCursor_InputWidget_(d); + refresh_Widget(w); + } + else if (d->inFlags & dragMarkerStart_InputWidgetFlag) { + d->mark.start = cursorToIndex_InputWidget_(d, touchCoordCursor_InputWidget_(d, pos_Click(&d->click))); + refresh_Widget(w); + } + else if (d->inFlags & dragMarkerEnd_InputWidgetFlag) { + d->mark.end = cursorToIndex_InputWidget_(d, touchCoordCursor_InputWidget_(d, pos_Click(&d->click))); + refresh_Widget(w); + } + return true_EventResult; + // printf("[%p] aborted\n", w); +// d->inFlags &= ~touchBehavior_InputWidgetFlag; +// setFlags_Widget(w, touchDrag_WidgetFlag, iFalse); +// return true_EventResult; + case finished_ClickResult: + case aborted_ClickResult: + printf("[%p] ended\n", w); + uint32_t tapElapsed = SDL_GetTicks() - d->tapStartTime; + printf("tapElapsed: %u\n", tapElapsed); + if (!isFocused_Widget(w)) { + setFocus_Widget(w); + d->lastTapPos = latestPosition_Touch(); + d->tapStartTime = SDL_GetTicks(); + d->tapCount = 0; + d->cursor = touchCoordCursor_InputWidget_(d, pos_Click(&d->click)); + showCursor_InputWidget_(d); + } + else if (!isEmpty_Range(&d->mark) && !isMoved_Click(&d->click)) { + if (isInsideMark_InputWidget_(d, cursorToIndex_InputWidget_(d, touchCoordCursor_InputWidget_(d, latestPosition_Touch())))) { + showClipMenu_(latestPosition_Touch()); + } + else { + iZap(d->mark); + d->cursor = touchCoordCursor_InputWidget_(d, pos_Click(&d->click)); + } + } + else if (SDL_GetTicks() - d->lastTapTime > 1000 && + d->tapCount == 0 && isEmpty_Range(&d->mark) && !isMoved_Click(&d->click) && + distanceToPos_InputWidget_(d, latestPosition_Touch(), d->cursor) < gap_UI * 5) { + showClipMenu_(latestPosition_Touch()); + } + else { + if (~d->inFlags & isMarking_InputWidgetFlag) { + iZap(d->mark); + d->cursor = touchCoordCursor_InputWidget_(d, pos_Click(&d->click)); + } + } + if (d->inFlags & (dragCursor_InputWidgetFlag | dragMarkerStart_InputWidgetFlag | + dragMarkerEnd_InputWidgetFlag)) { + printf("[%p] finished cursor/marker drag\n", w); + d->inFlags &= ~(dragCursor_InputWidgetFlag | + dragMarkerStart_InputWidgetFlag | + dragMarkerEnd_InputWidgetFlag); + setFlags_Widget(w, touchDrag_WidgetFlag, iFalse); + } + d->inFlags &= ~isMarking_InputWidgetFlag; + showCursor_InputWidget_(d); + refresh_Widget(w); +#if 0 + d->inFlags &= ~touchBehavior_InputWidgetFlag; + if (flags_Widget(w) & touchDrag_WidgetFlag) { + setFlags_Widget(w, touchDrag_WidgetFlag, iFalse); + return true_EventResult; + } + if (!isMoved_Click(&d->click)) { + if (!isFocused_Widget(w)) { + setFocus_Widget(w); + if (~d->inFlags & selectAllOnFocus_InputWidgetFlag) { + d->cursor = coordCursor_InputWidget_(d, pos_Click(&d->click)); + showCursor_InputWidget_(d); + } + } + else { + iZap(d->mark); + d->cursor = coordCursor_InputWidget_(d, pos_Click(&d->click)); + showCursor_InputWidget_(d); + } + } +#endif + return true_EventResult; + } +#endif +// if ((ev->type == SDL_MOUSEBUTTONDOWN || ev->type == SDL_MOUSEBUTTONUP) && +// contains_Widget(w, init_I2(ev->button.x, ev->button.y))) { +// /* Eat all mouse clicks on the widget. */ +// return true_EventResult; +// } + return ignored_EventResult; +} + +static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { + iWidget *w = as_Widget(d); + /* Resize according to width immediately. */ + if (d->lastUpdateWidth != w->rect.size.x) { + d->inFlags |= needUpdateBuffer_InputWidgetFlag; + if (d->inFlags & isUrl_InputWidgetFlag) { + /* Restore/omit the default scheme if necessary. */ + setText_InputWidget(d, text_InputWidget(d)); } + updateAllLinesAndResizeHeight_InputWidget_(d); + d->lastUpdateWidth = w->rect.size.x; + } + if (isCommand_Widget(w, ev, "focus.gained")) { + begin_InputWidget(d); + return iFalse; + } + else if (isEditing_InputWidget_(d) && (isCommand_UserEvent(ev, "window.focus.lost") || + isCommand_UserEvent(ev, "window.focus.gained"))) { + startOrStopCursorTimer_InputWidget_(d, isCommand_UserEvent(ev, "window.focus.gained")); + d->cursorVis = 1; + refresh_Widget(d); + return iFalse; + } + else if (isCommand_UserEvent(ev, "keyroot.changed")) { + d->inFlags |= needUpdateBuffer_InputWidgetFlag; + } + else if (isCommand_UserEvent(ev, "lang.changed")) { + set_String(&d->hint, &d->srcHint); + translate_Lang(&d->hint); + return iFalse; + } + else if (isCommand_Widget(w, ev, "focus.lost")) { + end_InputWidget(d, iTrue); + return iFalse; + } + else if ((isCommand_UserEvent(ev, "copy") || isCommand_UserEvent(ev, "input.copy")) && + isEditing_InputWidget_(d)) { + copy_InputWidget_(d, argLabel_Command(command_UserEvent(ev), "cut")); + return iTrue; + } + else if (isCommand_UserEvent(ev, "input.paste") && isEditing_InputWidget_(d)) { + paste_InputWidget_(d); return iTrue; } + else if (isCommand_UserEvent(ev, "theme.changed")) { + if (d->buffered) { + d->inFlags |= needUpdateBuffer_InputWidgetFlag; + } + return iFalse; + } +// else if (isCommand_UserEvent(ev, "keyboard.changed")) { +// if (isFocused_Widget(d) && arg_Command(command_UserEvent(ev))) { +// iRect rect = bounds_Widget(w); +// rect.pos.y -= value_Anim(&get_Window()->rootOffset); +// const iInt2 visRoot = visibleSize_Root(w->root); +// if (bottom_Rect(rect) > visRoot.y) { +// setValue_Anim(&get_Window()->rootOffset, -(bottom_Rect(rect) - visRoot.y), 250); +// } +// } +// return iFalse; +// } + else if (isCommand_UserEvent(ev, "text.insert")) { + pushUndo_InputWidget_(d); + deleteMarked_InputWidget_(d); + insertChar_InputWidget_(d, arg_Command(command_UserEvent(ev))); + contentsWereChanged_InputWidget_(d); + return iTrue; + } + else if (isCommand_Widget(w, ev, "input.backup")) { + if (d->inFlags & needBackup_InputWidgetFlag) { + saveBackup_InputWidget_(d); + } + return iTrue; + } + else if (isMetricsChange_UserEvent(ev)) { + updateMetrics_InputWidget_(d); + // updateLinesAndResize_InputWidget_(d); + } + else if (isFocused_Widget(d) && isCommand_UserEvent(ev, "copy")) { + copy_InputWidget_(d, iFalse); + return iTrue; + } + /* Click behavior depends on device type. */ { + const int mbResult = (deviceType_App() == desktop_AppDeviceType + ? processPointerEvents_InputWidget_(d, ev) + : processTouchEvents_InputWidget_(d, ev)); + if (mbResult) { + return mbResult >> 1; + } + } if (ev->type == SDL_KEYUP && isFocused_Widget(w)) { return iTrue; } @@ -1884,6 +2188,8 @@ struct Impl_MarkPainter { const iInputLine * line; iInt2 pos; iRanges mark; + iRect firstMarkRect; + iRect lastMarkRect; }; static iBool draw_MarkPainter_(iWrapText *wrapText, iRangecc wrappedText, int origin, int advance, @@ -1922,7 +2228,11 @@ static iBool draw_MarkPainter_(iWrapText *wrapText, iRangecc wrappedText, int or } rect.size.x = iMax(gap_UI / 3, rect.size.x); mp->pos.y += lineHeight_Text(mp->d->font); - fillRect_Paint(mp->paint, rect, uiMarked_ColorId); + fillRect_Paint(mp->paint, rect, uiMarked_ColorId | opaque_ColorId); + if (deviceType_App() != desktop_AppDeviceType) { + if (isEmpty_Rect(mp->firstMarkRect)) mp->firstMarkRect = rect; + mp->lastMarkRect = rect; + } return iTrue; } @@ -1962,6 +2272,7 @@ static void draw_InputWidget_(const iInputWidget *d) { }; const iRangei visLines = visibleLineRange_InputWidget_(d); const int visLineOffsetY = visLineOffsetY_InputWidget_(d); + iRect markerRects[2]; /* If buffered, just draw the buffered copy. */ if (d->buffered && !isFocused) { /* Most input widgets will use this, since only one is focused at a time. */ @@ -1977,7 +2288,7 @@ static void draw_InputWidget_(const iInputWidget *d) { .paint = &p, .d = d, .contentBounds = contentBounds, - .mark = mark_InputWidget_(d) + .mark = mark_InputWidget_(d), }; wrapText.context = ▮ wrapText.wrapFunc = isFocused ? draw_MarkPainter_ : NULL; /* mark is drawn under each line of text */ @@ -1988,11 +2299,14 @@ static void draw_InputWidget_(const iInputWidget *d) { marker.pos = drawPos; addv_I2(&drawPos, draw_WrapText(&wrapText, d->font, drawPos, fg).advance); /* lines end with \n */ } + markerRects[0] = marker.firstMarkRect; + markerRects[1] = marker.lastMarkRect; wrapText.wrapFunc = NULL; wrapText.context = NULL; } /* Draw the insertion point. */ - if (isFocused && d->cursorVis && contains_Range(&visLines, d->cursor.y)) { + if (isFocused && d->cursorVis && contains_Range(&visLines, d->cursor.y) && + isEmpty_Range(&d->mark)) { iInt2 curSize; iRangecc cursorChar = iNullRange; int visWrapsAbove = 0; @@ -2040,6 +2354,11 @@ static void draw_InputWidget_(const iInputWidget *d) { } } unsetClip_Paint(&p); + if (!isEmpty_Rect(markerRects[0])) { + for (int i = 0; i < 2; ++i) { + drawPin_Paint(&p, markerRects[i], i, uiTextCaution_ColorId); + } + } drawChildren_Widget(w); } diff --git a/src/ui/paint.c b/src/ui/paint.c index 71ebb81d..89de47d4 100644 --- a/src/ui/paint.c +++ b/src/ui/paint.c @@ -33,7 +33,8 @@ iLocalDef SDL_Renderer *renderer_Paint_(const iPaint *d) { static void setColor_Paint_(const iPaint *d, int color) { const iColor clr = get_Color(color & mask_ColorId); - SDL_SetRenderDrawColor(renderer_Paint_(d), clr.r, clr.g, clr.b, clr.a * d->alpha / 255); + SDL_SetRenderDrawColor(renderer_Paint_(d), clr.r, clr.g, clr.b, + (color & opaque_ColorId ? 255 : clr.a) * d->alpha / 255); } void init_Paint(iPaint *d) { @@ -186,6 +187,22 @@ void drawLines_Paint(const iPaint *d, const iInt2 *points, size_t n, int color) free(offsetPoints); } +void drawPin_Paint(iPaint *d, iRect rangeRect, int dir, int pinColor) { + const int height = height_Rect(rangeRect); + iRect pin; + if (dir == 0) { + pin = (iRect){ add_I2(topLeft_Rect(rangeRect), init_I2(-gap_UI / 4, -gap_UI)), + init_I2(gap_UI / 2, height + gap_UI) }; + } + else { + pin = (iRect){ addX_I2(topRight_Rect(rangeRect), -gap_UI / 4), + init_I2(gap_UI / 2, height + gap_UI) }; + } + fillRect_Paint(d, pin, pinColor); + fillRect_Paint(d, initCentered_Rect(dir == 0 ? topMid_Rect(pin) : bottomMid_Rect(pin), + init1_I2(gap_UI * 2)), pinColor); +} + iInt2 size_SDLTexture(SDL_Texture *d) { iInt2 size; SDL_QueryTexture(d, NULL, NULL, &size.x, &size.y); diff --git a/src/ui/paint.h b/src/ui/paint.h index e6701635..e894b62f 100644 --- a/src/ui/paint.h +++ b/src/ui/paint.h @@ -63,4 +63,6 @@ iLocalDef void drawVLine_Paint(const iPaint *d, iInt2 pos, int len, int color) { drawLine_Paint(d, pos, addY_I2(pos, len), color); } +void drawPin_Paint (iPaint *, iRect rangeRect, int dir, int pinColor); + iInt2 size_SDLTexture (SDL_Texture *); diff --git a/src/ui/root.c b/src/ui/root.c index 59f98aa4..91f9fbb3 100644 --- a/src/ui/root.c +++ b/src/ui/root.c @@ -1401,23 +1401,38 @@ void createUserInterface_Root(iRoot *d) { { "${menu.closetab.other}", 0, 0, "tabs.close toleft:1 toright:1" }, { barLeftArrow_Icon " ${menu.closetab.left}", 0, 0, "tabs.close toleft:1" }, { barRightArrow_Icon " ${menu.closetab.right}", 0, 0, "tabs.close toright:1" }, - }, + }, 6); iWidget *barMenu = makeMenu_Widget(root, (iMenuItem[]){ { leftHalf_Icon " ${menu.sidebar.left}", 0, 0, "sidebar.toggle" }, { rightHalf_Icon " ${menu.sidebar.right}", 0, 0, "sidebar2.toggle" }, - }, + }, deviceType_App() == phone_AppDeviceType ? 1 : 2); iWidget *clipMenu = makeMenu_Widget(root, - (iMenuItem[]){ - { scissor_Icon " ${menu.cut}", 0, 0, "input.copy cut:1" }, - { clipCopy_Icon " ${menu.copy}", 0, 0, "input.copy" }, - { "---" }, - { clipboard_Icon " ${menu.paste}", 0, 0, "input.paste" }, - }, - 4); +#if defined (iPlatformMobile) + (iMenuItem[]){ + { ">>>" scissor_Icon " ${menu.cut}", 0, 0, "input.copy cut:1" }, + { ">>>" clipCopy_Icon " ${menu.copy}", 0, 0, "input.copy" }, + { ">>>" clipboard_Icon " ${menu.paste}", 0, 0, "input.paste" }, + { "---" }, + { ">>>" delete_Icon " " uiTextCaution_ColorEscape "${menu.delete}", 0, 0, "input.delete" }, + { ">>>" select_Icon " ${menu.selectall}", 0, 0, "input.selectall" }, + { ">>>" undo_Icon " ${menu.undo}", 0, 0, "input.undo" }, + }, 7); +#else + (iMenuItem[]){ + { scissor_Icon " ${menu.cut}", 0, 0, "input.copy cut:1" }, + { clipCopy_Icon " ${menu.copy}", 0, 0, "input.copy" }, + { clipboard_Icon " ${menu.paste}", 0, 0, "input.paste" }, + { "---" }, + { delete_Icon " " uiTextCaution_ColorEscape "${menu.delete}", 0, 0, "input.delete" }, + { undo_Icon " ${menu.undo}", 0, 0, "input.undo" }, + { "---" }, + { select_Icon " ${menu.selectall}", 0, 0, "input.selectall" }, + }, 8); +#endif iWidget *splitMenu = makeMenu_Widget(root, (iMenuItem[]){ { "${menu.split.merge}", '1', 0, "ui.split arg:0" }, { "${menu.split.swap}", SDLK_x, 0, "ui.split swap:1" }, diff --git a/src/ui/touch.c b/src/ui/touch.c index 5fc8f245..61882739 100644 --- a/src/ui/touch.c +++ b/src/ui/touch.c @@ -293,6 +293,7 @@ static void update_TouchState_(void *ptr) { } if (elapsed > 50 && !touch->isTapBegun) { /* Looks like a possible tap. */ + touchState_()->currentTouchPos = initF3_I2(touch->pos[0]); dispatchNotification_Touch_(touch, widgetTapBegins_UserEventCode); dispatchMotion_Touch_(touch->pos[0], 0); refresh_Widget(touch->affinity); @@ -471,13 +472,13 @@ iBool processEvent_Touch(const SDL_Event *ev) { } iTouchState *d = touchState_(); iWindow *window = get_Window(); - if (!isFinished_Anim(&window->rootOffset)) { - return iFalse; - } +// if (!isFinished_Anim(&window->rootOffset)) { +// return iFalse; +// } const iInt2 rootSize = size_Window(window); const SDL_TouchFingerEvent *fing = &ev->tfinger; const iFloat3 pos = add_F3(init_F3(fing->x * rootSize.x, fing->y * rootSize.y, 0), /* pixels */ - init_F3(0, -value_Anim(&window->rootOffset), 0)); + init_F3(0, 0 /*-value_Anim(&window->rootOffset)*/, 0)); const uint32_t nowTime = SDL_GetTicks(); if (ev->type == SDL_FINGERDOWN) { /* Register the new touch. */ diff --git a/src/ui/util.c b/src/ui/util.c index 48ed41a6..cfa8152c 100644 --- a/src/ui/util.c +++ b/src/ui/util.c @@ -706,23 +706,36 @@ iWidget *makeMenu_Widget(iWidget *parent, const iMenuItem *items, size_t n) { setFrameColor_Widget(menu, uiSeparator_ColorId); } iBool haveIcons = iFalse; + iWidget *horizGroup = NULL; for (size_t i = 0; i < n; ++i) { const iMenuItem *item = &items[i]; if (!item->label) { break; } - if (equal_CStr(item->label, "---")) { + const char *labelText = item->label; + if (!startsWith_CStr(labelText, ">>>")) { + horizGroup = NULL; + } + if (equal_CStr(labelText, "---")) { addChild_Widget(menu, iClob(makeMenuSeparator_())); } else { iBool isInfo = iFalse; - const char *labelText = item->label; + if (startsWith_CStr(labelText, ">>>")) { + labelText += 3; + if (!horizGroup) { + horizGroup = makeHDiv_Widget(); + setFlags_Widget(horizGroup, resizeHeightOfChildren_WidgetFlag, iFalse); + setFlags_Widget(horizGroup, arrangeHeight_WidgetFlag, iTrue); + addChild_Widget(menu, iClob(horizGroup)); + } + } if (startsWith_CStr(labelText, "```")) { labelText += 3; isInfo = iTrue; } iLabelWidget *label = addChildFlags_Widget( - menu, + horizGroup ? horizGroup : menu, iClob(newKeyMods_LabelWidget(labelText, item->key, item->kmods, item->command)), noBackground_WidgetFlag | frameless_WidgetFlag | alignLeft_WidgetFlag | drawKey_WidgetFlag | itemFlags); @@ -766,6 +779,34 @@ void openMenu_Widget(iWidget *d, iInt2 windowCoord) { openMenuFlags_Widget(d, windowCoord, iTrue); } +static void updateMenuItemFonts_Widget_(iWidget *d) { + const iBool isPortraitPhone = (deviceType_App() == phone_AppDeviceType && isPortrait_App()); + const iBool isSlidePanel = (flags_Widget(d) & horizontalOffset_WidgetFlag) != 0; + iForEach(ObjectList, i, children_Widget(d)) { + if (isInstance_Object(i.object, &Class_LabelWidget)) { + iLabelWidget *label = i.object; + const iBool isCaution = startsWith_String(text_LabelWidget(label), uiTextCaution_ColorEscape); + if (isWrapped_LabelWidget(label)) { + continue; + } + if (deviceType_App() == desktop_AppDeviceType) { + setFont_LabelWidget(label, isCaution ? uiLabelBold_FontId : uiLabel_FontId); + } + else if (isPortraitPhone) { + if (!isSlidePanel) { + setFont_LabelWidget(label, isCaution ? defaultBigBold_FontId : defaultBig_FontId); + } + } + else { + setFont_LabelWidget(label, isCaution ? uiContentBold_FontId : uiContent_FontId); + } + } + else if (childCount_Widget(i.object)) { + updateMenuItemFonts_Widget_(i.object); + } + } +} + void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) { const iRect rootRect = rect_Root(d->root); const iInt2 rootSize = rootRect.size; @@ -788,28 +829,7 @@ void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) { } d->rect.size.x = rootSize.x; } - /* Update item fonts. */ { - iForEach(ObjectList, i, children_Widget(d)) { - if (isInstance_Object(i.object, &Class_LabelWidget)) { - iLabelWidget *label = i.object; - const iBool isCaution = startsWith_String(text_LabelWidget(label), uiTextCaution_ColorEscape); - if (isWrapped_LabelWidget(label)) { - continue; - } - if (deviceType_App() == desktop_AppDeviceType) { - setFont_LabelWidget(label, isCaution ? uiLabelBold_FontId : uiLabel_FontId); - } - else if (isPortraitPhone) { - if (!isSlidePanel) { - setFont_LabelWidget(label, isCaution ? defaultBigBold_FontId : defaultBig_FontId); - } - } - else { - setFont_LabelWidget(label, isCaution ? uiContentBold_FontId : uiContent_FontId); - } - } - } - } + updateMenuItemFonts_Widget_(d); arrange_Widget(d); if (isPortraitPhone) { if (isSlidePanel) { diff --git a/src/ui/widget.c b/src/ui/widget.c index 0765bf9f..1c0fb271 100644 --- a/src/ui/widget.c +++ b/src/ui/widget.c @@ -880,9 +880,9 @@ iInt2 localToWindow_Widget(const iWidget *d, iInt2 localCoord) { applyVisualOffset_Widget_(w, &pos); addv_I2(&window, pos); } -#if defined (iPlatformMobile) - window.y += value_Anim(&get_Window()->rootOffset); -#endif +//#if defined (iPlatformMobile) +// window.y += value_Anim(&get_Window()->rootOffset); +//#endif return window; } @@ -1072,23 +1072,33 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) { } iBool scrollOverflow_Widget(iWidget *d, int delta) { - iRect bounds = boundsWithoutVisualOffset_Widget(d); - const iInt2 rootSize = size_Root(d->root); - const iRect winRect = safeRect_Root(d->root); - const int yTop = top_Rect(winRect); - const int yBottom = bottom_Rect(winRect); + iRect bounds = boundsWithoutVisualOffset_Widget(d); +// const iInt2 rootSize = size_Root(d->root); + const iRect winRect = adjusted_Rect(safeRect_Root(d->root), + zero_I2(), + init_I2(0, -get_Window()->keyboardHeight)); + const int yTop = top_Rect(winRect); + const int yBottom = bottom_Rect(winRect); if (top_Rect(bounds) >= yTop && bottom_Rect(bounds) < yBottom) { return iFalse; /* fits inside just fine */ } //const int safeBottom = rootSize.y - yBottom; - bounds.pos.y += delta; - const iRangei range = { bottom_Rect(winRect) - height_Rect(bounds), yTop }; + iRangei validPosRange = { bottom_Rect(winRect) - height_Rect(bounds), yTop }; + if (validPosRange.start > validPosRange.end) { + validPosRange.start = validPosRange.end; /* no room to scroll */ + } + if (delta) { + if (delta < 0 && bounds.pos.y < validPosRange.start) { + delta = 0; + } + if (delta > 0 && bounds.pos.y > validPosRange.end) { + delta = 0; + } + bounds.pos.y += delta; // printf("range: %d ... %d\n", range.start, range.end); - if (range.start >= range.end) { - bounds.pos.y = range.end; } else { - bounds.pos.y = iClamp(bounds.pos.y, range.start, range.end); + bounds.pos.y = iClamp(bounds.pos.y, validPosRange.start, validPosRange.end); } // if (delta >= 0) { // bounds.pos.y = iMin(bounds.pos.y, yTop); @@ -1454,7 +1464,7 @@ void setDrawBufferEnabled_Widget(iWidget *d, iBool enable) { static void beginBufferDraw_Widget_(const iWidget *d) { if (d->drawBuf) { - printf("[%p] drawbuffer update %d\n", d, d->drawBuf->isValid); +// printf("[%p] drawbuffer update %d\n", d, d->drawBuf->isValid); if (d->drawBuf->isValid) { iAssert(!isEqual_I2(d->drawBuf->size, boundsForDraw_Widget_(d).size)); // printf(" drawBuf:%dx%d boundsForDraw:%dx%d\n", @@ -1503,7 +1513,7 @@ void draw_Widget(const iWidget *d) { endBufferDraw_Widget_(d); } if (d->drawBuf) { - iAssert(d->drawBuf->isValid); + //iAssert(d->drawBuf->isValid); const iRect bounds = bounds_Widget(d); SDL_RenderCopy(renderer_Window(get_Window()), d->drawBuf->texture, NULL, &(SDL_Rect){ bounds.pos.x, bounds.pos.y, diff --git a/src/ui/window.c b/src/ui/window.c index 8034d858..ed2ec024 100644 --- a/src/ui/window.c +++ b/src/ui/window.c @@ -421,7 +421,7 @@ void init_Window(iWindow *d, iRect rect) { d->ignoreClick = iFalse; d->focusGainedAt = 0; d->keyboardHeight = 0; - init_Anim(&d->rootOffset, 0.0f); +// init_Anim(&d->rootOffset, 0.0f); uint32_t flags = 0; #if defined (iPlatformAppleDesktop) SDL_SetHint(SDL_HINT_RENDER_DRIVER, shouldDefaultToMetalRenderer_MacOS() ? "metal" : "opengl"); @@ -1215,10 +1215,10 @@ iBool isOpenGLRenderer_Window(void) { void setKeyboardHeight_Window(iWindow *d, int height) { if (d->keyboardHeight != height) { d->keyboardHeight = height; - if (height == 0) { - setFlags_Anim(&d->rootOffset, easeBoth_AnimFlag, iTrue); - setValue_Anim(&d->rootOffset, 0, 250); - } +// if (height == 0) { +// setFlags_Anim(&d->rootOffset, easeBoth_AnimFlag, iTrue); +// setValue_Anim(&d->rootOffset, 0, 250); +// } postCommandf_App("keyboard.changed arg:%d", height); postRefresh_App(); } diff --git a/src/ui/window.h b/src/ui/window.h index 63f7e5f2..a5b8f137 100644 --- a/src/ui/window.h +++ b/src/ui/window.h @@ -98,7 +98,7 @@ struct Impl_Window { SDL_Cursor * cursors[SDL_NUM_SYSTEM_CURSORS]; SDL_Cursor * pendingCursor; int loadAnimTimer; - iAnim rootOffset; +// iAnim rootOffset; int keyboardHeight; /* mobile software keyboards */ }; -- cgit v1.2.3 From 81007a2debffebc97c230cd810fb1bf10760180f Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Sun, 19 Sep 2021 13:28:58 +0300 Subject: Refactoring Window to split off MainWindow MainWindow represents (one of) the main windows of the app, while the basic Window will be a used for popups. Only MainWindow supports split view modes. --- src/app.c | 122 +++++------ src/ui/documentwidget.c | 2 +- src/ui/paint.c | 3 + src/ui/root.c | 10 +- src/ui/uploadwidget.c | 2 +- src/ui/util.c | 2 +- src/ui/widget.c | 4 +- src/ui/window.c | 557 +++++++++++++++++++++++++----------------------- src/ui/window.h | 135 ++++++++---- 9 files changed, 462 insertions(+), 375 deletions(-) (limited to 'src/ui/documentwidget.c') diff --git a/src/app.c b/src/app.c index e06a32ce..91b3a06d 100644 --- a/src/app.c +++ b/src/app.c @@ -117,7 +117,7 @@ struct Impl_App { iGmCerts * certs; iVisited * visited; iBookmarks * bookmarks; - iWindow * window; + iMainWindow *window; iSortedArray tickers; /* per-frame callbacks, used for animations */ uint32_t lastTickerTime; uint32_t elapsedSinceLastTicker; @@ -188,7 +188,7 @@ static iString *serializePrefs_App_(const iApp *d) { /* On macOS, maximization should be applied at creation time or the window will take a moment to animate to its maximized size. */ #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) - if (snap_Window(d->window)) { + if (snap_MainWindow(d->window)) { if (~SDL_GetWindowFlags(d->window->win) & SDL_WINDOW_MINIMIZED) { /* Save the actual visible window position, too, because snapped windows may still be resized/moved without affecting normalRect. */ @@ -196,17 +196,17 @@ static iString *serializePrefs_App_(const iApp *d) { SDL_GetWindowSize(d->window->win, &w, &h); appendFormat_String( str, "~window.setrect snap:%d width:%d height:%d coord:%d %d\n", - snap_Window(d->window), w, h, x, y); + snap_MainWindow(d->window), w, h, x, y); } } #elif !defined (iPlatformApple) - if (snap_Window(d->window) == maximized_WindowSnap) { + if (snap_MainWindow(d->window) == maximized_WindowSnap) { appendFormat_String(str, "~window.maximize\n"); } #endif } appendFormat_String(str, "uilang id:%s\n", cstr_String(&d->prefs.uiLanguage)); - appendFormat_String(str, "uiscale arg:%f\n", uiScale_Window(d->window)); + appendFormat_String(str, "uiscale arg:%f\n", uiScale_Window(as_Window(d->window))); appendFormat_String(str, "prefs.dialogtab arg:%d\n", d->prefs.dialogTab); appendFormat_String(str, "font.set arg:%d\n", d->prefs.font); appendFormat_String(str, "font.user path:%s\n", cstr_String(&d->prefs.symbolFontPath)); @@ -414,8 +414,8 @@ static iBool loadState_App_(iApp *d) { const int splitMode = read32_File(f); const int keyRoot = read32_File(f); d->window->pendingSplitMode = splitMode; - setSplitMode_Window(d->window, splitMode | noEvents_WindowSplit); - d->window->keyRoot = d->window->roots[keyRoot]; + setSplitMode_MainWindow(d->window, splitMode | noEvents_WindowSplit); + d->window->base.keyRoot = d->window->base.roots[keyRoot]; } else if (!memcmp(magic, magicSidebar_App_, 4)) { const uint16_t bits = readU16_File(f); @@ -426,7 +426,7 @@ static iBool loadState_App_(iApp *d) { }; const uint8_t rootIndex = bits & 0xff; const uint8_t flags = bits >> 8; - iRoot *root = d->window->roots[rootIndex]; + iRoot *root = d->window->base.roots[rootIndex]; if (root) { iSidebarWidget *sidebar = findChild_Widget(root->widget, "sidebar"); iSidebarWidget *sidebar2 = findChild_Widget(root->widget, "sidebar2"); @@ -443,10 +443,10 @@ static iBool loadState_App_(iApp *d) { else if (!memcmp(magic, magicTabDocument_App_, 4)) { const int8_t flags = read8_File(f); int rootIndex = flags & rootIndex1_DocumentStateFlag ? 1 : 0; - if (rootIndex > numRoots_Window(d->window) - 1) { + if (rootIndex > numRoots_Window(as_Window(d->window)) - 1) { rootIndex = 0; } - setCurrent_Root(d->window->roots[rootIndex]); + setCurrent_Root(d->window->base.roots[rootIndex]); if (isFirstTab[rootIndex]) { isFirstTab[rootIndex] = iFalse; /* There is one pre-created tab in each root. */ @@ -469,7 +469,7 @@ static iBool loadState_App_(iApp *d) { } if (d->window->splitMode) { /* Update root placement. */ - resize_Window(d->window, -1, -1); + resize_MainWindow(d->window, -1, -1); } iForIndices(i, current) { postCommandf_Root(NULL, "tabs.switch page:%p", current[i]); @@ -483,7 +483,7 @@ static iBool loadState_App_(iApp *d) { static void saveState_App_(const iApp *d) { iUnused(d); trimCache_App(); - iWindow *win = d->window; + iMainWindow *win = d->window; /* UI state is saved in binary because it is quite complex (e.g., navigation history, cached content) and depends closely on the widget tree. The data is largely not reorderable and should not be modified @@ -495,11 +495,11 @@ static void saveState_App_(const iApp *d) { /* Begin with window state. */ { writeData_File(f, magicWindow_App_, 4); writeU32_File(f, win->splitMode); - writeU32_File(f, win->keyRoot == win->roots[0] ? 0 : 1); + writeU32_File(f, win->base.keyRoot == win->base.roots[0] ? 0 : 1); } /* State of UI elements. */ { - iForIndices(i, win->roots) { - const iRoot *root = win->roots[i]; + iForIndices(i, win->base.roots) { + const iRoot *root = win->base.roots[i]; if (root) { writeData_File(f, magicSidebar_App_, 4); const iSidebarWidget *sidebar = findChild_Widget(root->widget, "sidebar"); @@ -520,7 +520,7 @@ static void saveState_App_(const iApp *d) { const iWidget *widget = constAs_Widget(i.object); writeData_File(f, magicTabDocument_App_, 4); int8_t flags = (document_Root(widget->root) == i.object ? current_DocumentStateFlag : 0); - if (widget->root == win->roots[1]) { + if (widget->root == win->base.roots[1]) { flags |= rootIndex1_DocumentStateFlag; } write8_File(f, flags); @@ -801,7 +801,7 @@ static void init_App_(iApp *d, int argc, char **argv) { d->initialWindowRect.size.y = toInt_String(value_CommandLineArg(arg, 0)); } } - d->window = new_Window(d->initialWindowRect); + d->window = new_MainWindow(d->initialWindowRect); load_Visited(d->visited, dataDir_App_()); load_Bookmarks(d->bookmarks, dataDir_App_()); load_MimeHooks(d->mimehooks, dataDir_App_()); @@ -848,7 +848,7 @@ static void init_App_(iApp *d, int argc, char **argv) { fetchRemote_Bookmarks(d->bookmarks); if (deviceType_App() != desktop_AppDeviceType) { /* HACK: Force a resize so widgets update their state. */ - resize_Window(d->window, -1, -1); + resize_MainWindow(d->window, -1, -1); } } @@ -871,7 +871,7 @@ static void deinit_App(iApp *d) { delete_GmCerts(d->certs); save_MimeHooks(d->mimehooks); delete_MimeHooks(d->mimehooks); - delete_Window(d->window); + delete_MainWindow(d->window); d->window = NULL; deinit_CommandLine(&d->args); iRelease(d->launchCommands); @@ -1110,7 +1110,7 @@ void processEvents_App(enum iAppEventMode eventMode) { clearCache_App_(); break; case SDL_APP_WILLENTERFOREGROUND: - invalidate_Window(d->window); + invalidate_Window(as_Window(d->window)); break; case SDL_APP_DIDENTERFOREGROUND: gotEvents = iTrue; @@ -1125,17 +1125,17 @@ void processEvents_App(enum iAppEventMode eventMode) { #if defined (iPlatformAppleMobile) updateNowPlayingInfo_iOS(); #endif - setFreezeDraw_Window(d->window, iTrue); + setFreezeDraw_Window(as_Window(d), iTrue); savePrefs_App_(d); saveState_App_(d); break; case SDL_APP_TERMINATING: - setFreezeDraw_Window(d->window, iTrue); + setFreezeDraw_Window(as_Window(d), iTrue); savePrefs_App_(d); saveState_App_(d); break; case SDL_DROPFILE: { - iBool wasUsed = processEvent_Window(d->window, &ev); + iBool wasUsed = processEvent_MainWindow(d->window, &ev); if (!wasUsed) { iBool newTab = iFalse; if (elapsedSeconds_Time(&d->lastDropTime) < 0.1) { @@ -1177,10 +1177,10 @@ void processEvents_App(enum iAppEventMode eventMode) { #endif if (ev.type == SDL_USEREVENT && ev.user.code == arrange_UserEventCode) { printf("[App] rearrange\n"); - resize_Window(d->window, -1, -1); - iForIndices(i, d->window->roots) { - if (d->window->roots[i]) { - d->window->roots[i]->pendingArrange = iFalse; + resize_MainWindow(d->window, -1, -1); + iForIndices(i, d->window->base.roots) { + if (d->window->base.roots[i]) { + d->window->base.roots[i]->pendingArrange = iFalse; } } // if (ev.user.data2 == d->window->roots[0]) { @@ -1209,8 +1209,8 @@ void processEvents_App(enum iAppEventMode eventMode) { if (ev.wheel.which == 0) { /* Trackpad with precise scrolling w/inertia (points). */ setPerPixel_MouseWheelEvent(&ev.wheel, iTrue); - ev.wheel.x *= -d->window->pixelRatio; - ev.wheel.y *= d->window->pixelRatio; + ev.wheel.x *= -d->window->base.pixelRatio; + ev.wheel.y *= d->window->base.pixelRatio; /* Only scroll on one axis at a time. */ if (iAbs(ev.wheel.x) > iAbs(ev.wheel.y)) { ev.wheel.y = 0; @@ -1268,8 +1268,8 @@ void processEvents_App(enum iAppEventMode eventMode) { } } #endif - d->window->lastHover = d->window->hover; - iBool wasUsed = processEvent_Window(d->window, &ev); + d->window->base.lastHover = d->window->base.hover; + iBool wasUsed = processEvent_MainWindow(d->window, &ev); if (!wasUsed) { /* There may be a key bindings for this. */ wasUsed = processEvent_Keys(&ev); @@ -1289,8 +1289,8 @@ void processEvents_App(enum iAppEventMode eventMode) { handleCommand_MacOS(command_UserEvent(&ev)); #endif if (isMetricsChange_UserEvent(&ev)) { - iForIndices(i, d->window->roots) { - iRoot *root = d->window->roots[i]; + iForIndices(i, d->window->base.roots) { + iRoot *root = d->window->base.roots[i]; if (root) { arrange_Widget(root->widget); } @@ -1304,9 +1304,9 @@ void processEvents_App(enum iAppEventMode eventMode) { free(ev.user.data1); } /* Update when hover has changed. */ - if (d->window->lastHover != d->window->hover) { - refresh_Widget(d->window->lastHover); - refresh_Widget(d->window->hover); + if (d->window->base.lastHover != d->window->base.hover) { + refresh_Widget(d->window->base.lastHover); + refresh_Widget(d->window->base.hover); } break; } @@ -1362,15 +1362,16 @@ static int resizeWatcher_(void *user, SDL_Event *event) { dispatchEvent_Window(d->window, &u); } #endif - drawWhileResizing_Window(d->window, winev->data1, winev->data2); + drawWhileResizing_MainWindow(d->window, winev->data1, winev->data2); } return 0; } static int run_App_(iApp *d) { - iForIndices(i, d->window->roots) { - if (d->window->roots[i]) { - arrange_Widget(d->window->roots[i]->widget); + /* Initial arrangement. */ + iForIndices(i, d->window->base.roots) { + if (d->window->base.roots[i]) { + arrange_Widget(d->window->base.roots[i]->widget); } } d->isRunning = iTrue; @@ -1384,7 +1385,7 @@ static int run_App_(iApp *d) { runTickers_App_(d); refresh_App(); /* Change the widget tree while we are not iterating through it. */ - checkPendingSplit_Window(d->window); + checkPendingSplit_MainWindow(d->window); recycle_Garbage(); } SDL_DelEventWatch(resizeWatcher_, d); @@ -1393,8 +1394,8 @@ static int run_App_(iApp *d) { void refresh_App(void) { iApp *d = &app_; - iForIndices(i, d->window->roots) { - iRoot *root = d->window->roots[i]; + iForIndices(i, d->window->base.roots) { + iRoot *root = d->window->base.roots[i]; if (root) { destroyPending_Root(root); } @@ -1409,7 +1410,7 @@ void refresh_App(void) { } // iTime draw; // initCurrent_Time(&draw); - draw_Window(d->window); + draw_MainWindow(d->window); // printf("draw: %lld \u03bcs\n", (long long) (elapsedSeconds_Time(&draw) * 1000000)); // fflush(stdout); if (d->warmupFrames > 0) { @@ -1545,7 +1546,7 @@ void postCommandf_App(const char *command, ...) { } void rootOrder_App(iRoot *roots[2]) { - const iWindow *win = app_.window; + const iWindow *win = as_Window(app_.window); roots[0] = win->keyRoot; roots[1] = (roots[0] == win->roots[0] ? win->roots[1] : win->roots[0]); } @@ -1979,7 +1980,7 @@ const iString *searchQueryUrl_App(const iString *queryStringUnescaped) { iBool handleCommand_App(const char *cmd) { iApp *d = &app_; - const iBool isFrozen = !d->window || d->window->isDrawFrozen; + const iBool isFrozen = !d->window || d->window->base.isDrawFrozen; if (equal_Command(cmd, "config.error")) { makeSimpleMessage_Widget(uiTextCaution_ColorEscape "CONFIG ERROR", format_CStr("Error in config file: %s\n" @@ -2011,13 +2012,13 @@ iBool handleCommand_App(const char *cmd) { } else if (equal_Command(cmd, "ui.split")) { if (argLabel_Command(cmd, "swap")) { - swapRoots_Window(d->window); + swapRoots_MainWindow(d->window); return iTrue; } d->window->pendingSplitMode = (argLabel_Command(cmd, "axis") ? vertical_WindowSplit : 0) | (arg_Command(cmd) << 1); const char *url = suffixPtr_Command(cmd, "url"); - setCStr_String(get_Window()->pendingSplitUrl, url ? url : ""); + setCStr_String(d->window->pendingSplitUrl, url ? url : ""); postRefresh_App(); return iTrue; } @@ -2031,17 +2032,17 @@ iBool handleCommand_App(const char *cmd) { } else if (equal_Command(cmd, "window.maximize")) { if (!argLabel_Command(cmd, "toggle")) { - setSnap_Window(d->window, maximized_WindowSnap); + setSnap_MainWindow(d->window, maximized_WindowSnap); } else { - setSnap_Window(d->window, snap_Window(d->window) == maximized_WindowSnap ? 0 : + setSnap_MainWindow(d->window, snap_MainWindow(d->window) == maximized_WindowSnap ? 0 : maximized_WindowSnap); } return iTrue; } else if (equal_Command(cmd, "window.fullscreen")) { - const iBool wasFull = snap_Window(d->window) == fullscreen_WindowSnap; - setSnap_Window(d->window, wasFull ? 0 : fullscreen_WindowSnap); + const iBool wasFull = snap_MainWindow(d->window) == fullscreen_WindowSnap; + setSnap_MainWindow(d->window, wasFull ? 0 : fullscreen_WindowSnap); postCommandf_App("window.fullscreen.changed arg:%d", !wasFull); return iTrue; } @@ -2210,7 +2211,7 @@ iBool handleCommand_App(const char *cmd) { equal_Command(cmd, "prefs.mono.gopher.changed")) { const iBool isSet = (arg_Command(cmd) != 0); if (!isFrozen) { - setFreezeDraw_Window(d->window, iTrue); + setFreezeDraw_Window(as_Window(d->window), iTrue); } if (startsWith_CStr(cmd, "prefs.mono.gemini")) { d->prefs.monospaceGemini = isSet; @@ -2405,8 +2406,8 @@ iBool handleCommand_App(const char *cmd) { iRoot *root = get_Root(); iRoot *oldRoot = root; if (newTab & otherRoot_OpenTabFlag) { - root = otherRoot_Window(d->window, root); - setKeyRoot_Window(d->window, root); + root = otherRoot_Window(as_Window(d->window), root); + setKeyRoot_Window(as_Window(d->window), root); setCurrent_Root(root); /* need to change for widget creation */ } iDocumentWidget *doc = document_Command(cmd); @@ -2560,7 +2561,8 @@ iBool handleCommand_App(const char *cmd) { return iTrue; } else if (equal_Command(cmd, "keyroot.next")) { - if (setKeyRoot_Window(d->window, otherRoot_Window(d->window, d->window->keyRoot))) { + if (setKeyRoot_Window(as_Window(d->window), + otherRoot_Window(as_Window(d->window), d->window->base.keyRoot))) { setFocus_Widget(NULL); } return iTrue; @@ -2592,7 +2594,7 @@ iBool handleCommand_App(const char *cmd) { format_CStr("returnkey.set arg:%d", d->prefs.returnKey)); setToggle_Widget(findChild_Widget(dlg, "prefs.retainwindow"), d->prefs.retainWindowSize); setText_InputWidget(findChild_Widget(dlg, "prefs.uiscale"), - collectNewFormat_String("%g", uiScale_Window(d->window))); + collectNewFormat_String("%g", uiScale_Window(as_Window(d->window)))); setFlags_Widget(findChild_Widget(dlg, format_CStr("prefs.font.%d", d->prefs.font)), selected_WidgetFlag, iTrue); @@ -2749,7 +2751,7 @@ iBool handleCommand_App(const char *cmd) { /* Set of open tabs has changed. */ postCommand_App("document.openurls.changed"); if (deviceType_App() == phone_AppDeviceType) { - showToolbar_Root(d->window->roots[0], iTrue); + showToolbar_Root(d->window->base.roots[0], iTrue); } return iFalse; } @@ -2825,8 +2827,8 @@ iBool handleCommand_App(const char *cmd) { } else if (equal_Command(cmd, "ipc.signal")) { if (argLabel_Command(cmd, "raise")) { - if (d->window && d->window->win) { - SDL_RaiseWindow(d->window->win); + if (d->window && d->window->base.win) { + SDL_RaiseWindow(d->window->base.win); } } signal_Ipc(arg_Command(cmd)); diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 8ea695d5..ed9e41d6 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -934,7 +934,7 @@ static void updateWindowTitle_DocumentWidget_(const iDocumentWidget *d) { iString *text = collect_String(joinCStr_StringArray(title, " \u2014 ")); if (setWindow) { /* Longest version for the window title, and omit the icon. */ - setTitle_Window(get_Window(), text); + setTitle_MainWindow(get_MainWindow(), text); setWindow = iFalse; } const iChar siteIcon = siteIcon_GmDocument(d->doc); diff --git a/src/ui/paint.c b/src/ui/paint.c index a3ee32c6..5506f845 100644 --- a/src/ui/paint.c +++ b/src/ui/paint.c @@ -152,6 +152,9 @@ void drawSoftShadow_Paint(const iPaint *d, iRect inner, int thickness, int color addv_I2(&inner.pos, origin_Paint); SDL_Renderer *render = renderer_Paint_(d); SDL_Texture *shadow = get_Window()->borderShadow; + if (!shadow) { + return; + } const iInt2 size = size_SDLTexture(shadow); const iRect outer = expanded_Rect(inner, init1_I2(thickness)); const iColor clr = get_Color(color); diff --git a/src/ui/root.c b/src/ui/root.c index 6ea5f521..52a08eca 100644 --- a/src/ui/root.c +++ b/src/ui/root.c @@ -383,18 +383,18 @@ static iBool handleRootCommands_(iWidget *root, const char *cmd) { else if (equal_Command(cmd, "window.setrect")) { const int snap = argLabel_Command(cmd, "snap"); if (snap) { - iWindow *window = get_Window(); + iMainWindow *window = get_MainWindow(); iInt2 coord = coord_Command(cmd); iInt2 size = init_I2(argLabel_Command(cmd, "width"), argLabel_Command(cmd, "height")); - SDL_SetWindowPosition(window->win, coord.x, coord.y); - SDL_SetWindowSize(window->win, size.x, size.y); + SDL_SetWindowPosition(window->base.win, coord.x, coord.y); + SDL_SetWindowSize(window->base.win, size.x, size.y); window->place.snap = snap; return iTrue; } } else if (equal_Command(cmd, "window.restore")) { - setSnap_Window(get_Window(), none_WindowSnap); + setSnap_MainWindow(get_MainWindow(), none_WindowSnap); return iTrue; } else if (equal_Command(cmd, "window.minimize")) { @@ -1525,5 +1525,5 @@ iRect safeRect_Root(const iRoot *d) { } iInt2 visibleSize_Root(const iRoot *d) { - return addY_I2(size_Root(d), -get_Window()->keyboardHeight); + return addY_I2(size_Root(d), -get_MainWindow()->keyboardHeight); } diff --git a/src/ui/uploadwidget.c b/src/ui/uploadwidget.c index 72608851..ba7545fd 100644 --- a/src/ui/uploadwidget.c +++ b/src/ui/uploadwidget.c @@ -97,7 +97,7 @@ static void updateInputMaxHeight_UploadWidget_(iUploadWidget *d) { height_Widget(findChild_Widget(w, "dialogbuttons")) + 12 * gap_UI); const int avail = bottom_Rect(safeRect_Root(w->root)) - footerHeight - - get_Window()->keyboardHeight; + get_MainWindow()->keyboardHeight; setLineLimits_InputWidget(d->input, minLines_InputWidget(d->input), iMaxi(minLines_InputWidget(d->input), diff --git a/src/ui/util.c b/src/ui/util.c index 01939e66..721aed2d 100644 --- a/src/ui/util.c +++ b/src/ui/util.c @@ -1222,7 +1222,7 @@ static void updateValueInputWidth_(iWidget *dlg) { iMin(rootSize.x, iMaxi(iMaxi(100 * gap_UI, title->rect.size.x), prompt->rect.size.x)); } /* Adjust the maximum number of visible lines. */ - int footer = 6 * gap_UI + get_Window()->keyboardHeight; + int footer = 6 * gap_UI + get_MainWindow()->keyboardHeight; iWidget *buttons = findChild_Widget(dlg, "dialogbuttons"); if (buttons) { footer += height_Widget(buttons); diff --git a/src/ui/widget.c b/src/ui/widget.c index c1fb9e95..23c19315 100644 --- a/src/ui/widget.c +++ b/src/ui/widget.c @@ -1088,7 +1088,7 @@ void scrollInfo_Widget(const iWidget *d, iWidgetScrollInfo *info) { iRect bounds = boundsWithoutVisualOffset_Widget(d); const iRect winRect = adjusted_Rect(safeRect_Root(d->root), zero_I2(), - init_I2(0, -get_Window()->keyboardHeight)); + init_I2(0, -get_MainWindow()->keyboardHeight)); info->height = bounds.size.y; info->avail = height_Rect(winRect); if (info->avail >= info->height) { @@ -1109,7 +1109,7 @@ iBool scrollOverflow_Widget(iWidget *d, int delta) { iRect bounds = boundsWithoutVisualOffset_Widget(d); const iRect winRect = adjusted_Rect(safeRect_Root(d->root), zero_I2(), - init_I2(0, -get_Window()->keyboardHeight)); + init_I2(0, -get_MainWindow()->keyboardHeight)); const int yTop = top_Rect(winRect); const int yBottom = bottom_Rect(winRect); if (top_Rect(bounds) >= yTop && bottom_Rect(bounds) < yBottom) { diff --git a/src/ui/window.c b/src/ui/window.c index b8198636..92125d81 100644 --- a/src/ui/window.c +++ b/src/ui/window.c @@ -67,7 +67,7 @@ static float initialUiScale_ = 1.1f; static iBool isOpenGLRenderer_; -iDefineTypeConstructionArgs(Window, (iRect rect), rect) +iDefineTypeConstructionArgs(MainWindow, (iRect rect), rect) /* TODO: Define menus per platform. */ @@ -169,17 +169,17 @@ int numRoots_Window(const iWindow *d) { return num; } -static void windowSizeChanged_Window_(iWindow *d) { - const int numRoots = numRoots_Window(d); - const iInt2 rootSize = d->size; +static void windowSizeChanged_MainWindow_(iMainWindow *d) { + const int numRoots = numRoots_Window(as_Window(d)); + const iInt2 rootSize = d->base.size; const int weights[2] = { - d->roots[0] ? (d->splitMode & twoToOne_WindowSplit ? 2 : 1) : 0, - d->roots[1] ? (d->splitMode & oneToTwo_WindowSplit ? 2 : 1) : 0, + d->base.roots[0] ? (d->splitMode & twoToOne_WindowSplit ? 2 : 1) : 0, + d->base.roots[1] ? (d->splitMode & oneToTwo_WindowSplit ? 2 : 1) : 0, }; const int totalWeight = weights[0] + weights[1]; int w = 0; - iForIndices(i, d->roots) { - iRoot *root = d->roots[i]; + iForIndices(i, d->base.roots) { + iRoot *root = d->base.roots[i]; if (root) { iRect *rect = &root->widget->rect; /* Horizontal split frame. */ @@ -199,26 +199,26 @@ static void windowSizeChanged_Window_(iWindow *d) { } } -static void setupUserInterface_Window(iWindow *d) { +static void setupUserInterface_MainWindow(iMainWindow *d) { #if defined (iHaveNativeMenus) insertMacMenus_(); #endif /* One root is created by default. */ - d->roots[0] = new_Root(); - setCurrent_Root(d->roots[0]); - createUserInterface_Root(d->roots[0]); + d->base.roots[0] = new_Root(); + setCurrent_Root(d->base.roots[0]); + createUserInterface_Root(d->base.roots[0]); setCurrent_Root(NULL); /* One of the roots always has keyboard input focus. */ - d->keyRoot = d->roots[0]; + d->base.keyRoot = d->base.roots[0]; } -static void updateSize_Window_(iWindow *d, iBool notifyAlways) { - iInt2 *size = &d->size; +static void updateSize_MainWindow_(iMainWindow *d, iBool notifyAlways) { + iInt2 *size = &d->base.size; const iInt2 oldSize = *size; - SDL_GetRendererOutputSize(d->render, &size->x, &size->y); + SDL_GetRendererOutputSize(d->base.render, &size->x, &size->y); size->y -= d->keyboardHeight; if (notifyAlways || !isEqual_I2(oldSize, *size)) { - windowSizeChanged_Window_(d); + windowSizeChanged_MainWindow_(d); if (!isEqual_I2(*size, d->place.lastNotifiedSize)) { const iBool isHoriz = (d->place.lastNotifiedSize.x != size->x); const iBool isVert = (d->place.lastNotifiedSize.y != size->y); @@ -234,8 +234,8 @@ static void updateSize_Window_(iWindow *d, iBool notifyAlways) { } } -void drawWhileResizing_Window(iWindow *d, int w, int h) { - draw_Window(d); +void drawWhileResizing_MainWindow(iMainWindow *d, int w, int h) { + draw_MainWindow(d); } static float pixelRatio_Window_(const iWindow *d) { @@ -308,7 +308,7 @@ static iRoot *rootAt_Window_(const iWindow *d, iInt2 coord) { } #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) -static SDL_HitTestResult hitTest_Window_(SDL_Window *win, const SDL_Point *pos, void *data) { +static SDL_HitTestResult hitTest_MainWindow_(SDL_Window *win, const SDL_Point *pos, void *data) { iWindow *d = data; iAssert(d->win == win); if (SDL_GetWindowFlags(d->win) & (SDL_WINDOW_MOUSE_CAPTURE | SDL_WINDOW_FULLSCREEN_DESKTOP)) { @@ -361,19 +361,22 @@ SDL_HitTestResult hitTest_Window(const iWindow *d, iInt2 pos) { #endif iBool create_Window_(iWindow *d, iRect rect, uint32_t flags) { - flags |= SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN; + flags |= SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN; + if (d->type == main_WindowType) { + flags |= SDL_WINDOW_RESIZABLE; #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) - if (prefs_App()->customFrame) { - /* We are drawing a custom frame so hide the default one. */ - flags |= SDL_WINDOW_BORDERLESS; - } + if (prefs_App()->customFrame) { + /* We are drawing a custom frame so hide the default one. */ + flags |= SDL_WINDOW_BORDERLESS; + } #endif + } if (SDL_CreateWindowAndRenderer( width_Rect(rect), height_Rect(rect), flags, &d->win, &d->render)) { return iFalse; } #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) - if (prefs_App()->customFrame) { + if (type_Window(d) == main_WindowType && prefs_App()->customFrame) { /* Register a handler for window hit testing (drag, resize). */ SDL_SetWindowHitTest(d->win, hitTest_Window_, d); SDL_SetWindowResizable(d->win, SDL_TRUE); @@ -397,40 +400,28 @@ static SDL_Surface *loadImage_(const iBlock *data, int resized) { pixels, w, h, 8 * num, w * num, SDL_PIXELFORMAT_RGBA32); } -void init_Window(iWindow *d, iRect rect) { - theWindow_ = d; - d->win = NULL; - d->size = zero_I2(); /* will be updated below */ - iZap(d->roots); - d->splitMode = d->pendingSplitMode = 0; - d->pendingSplitUrl = new_String(); - d->hover = NULL; - d->lastHover = NULL; - d->mouseGrab = NULL; - d->focus = NULL; - iZap(d->cursors); - d->place.initialPos = rect.pos; - d->place.normalRect = rect; - d->place.lastNotifiedSize = zero_I2(); - d->place.snap = 0; +void init_Window(iWindow *d, enum iWindowType type, iRect rect, uint32_t flags) { + d->type = type; + d->win = NULL; + d->size = zero_I2(); /* will be updated below */ + d->hover = NULL; + d->lastHover = NULL; + d->mouseGrab = NULL; + d->focus = NULL; d->pendingCursor = NULL; - d->isDrawFrozen = iTrue; - d->isExposed = iFalse; - d->isMinimized = iFalse; + d->isDrawFrozen = iTrue; + d->isExposed = iFalse; + d->isMinimized = iFalse; d->isInvalidated = iFalse; /* set when posting event, to avoid repeated events */ - d->isMouseInside = iTrue; - d->ignoreClick = iFalse; + d->isMouseInside = iTrue; + d->ignoreClick = iFalse; d->focusGainedAt = 0; - d->keyboardHeight = 0; - uint32_t flags = 0; -#if defined (iPlatformAppleDesktop) - SDL_SetHint(SDL_HINT_RENDER_DRIVER, shouldDefaultToMetalRenderer_MacOS() ? "metal" : "opengl"); -#elif defined (iPlatformAppleMobile) - SDL_SetHint(SDL_HINT_RENDER_DRIVER, "metal"); -#else - flags |= SDL_WINDOW_OPENGL; -#endif - SDL_SetHint(SDL_HINT_RENDER_VSYNC, "1"); + d->presentTime = 0.0; + d->frameTime = SDL_GetTicks(); + d->keyRoot = NULL; + d->borderShadow = NULL; + iZap(d->roots); + iZap(d->cursors); /* First try SDL's default renderer that should be the best option. */ if (forceSoftwareRender_App() || !create_Window_(d, rect, flags)) { /* No luck, maybe software only? This should always work as long as there is a display. */ @@ -443,40 +434,74 @@ void init_Window(iWindow *d, iRect rect) { if (left_Rect(rect) >= 0 || top_Rect(rect) >= 0) { SDL_SetWindowPosition(d->win, left_Rect(rect), top_Rect(rect)); } -#if defined (iPlatformMobile) + SDL_GetRendererOutputSize(d->render, &d->size.x, &d->size.y); + drawBlank_Window_(d); + d->pixelRatio = pixelRatio_Window_(d); /* point/pixel conversion */ + d->displayScale = displayScale_Window_(d); + d->uiScale = initialUiScale_; + /* TODO: Ratios, scales, and metrics must be window-specific, not global. */ + setScale_Metrics(d->pixelRatio * d->displayScale * d->uiScale); +} + +void deinit_Window(iWindow *d) { + SDL_DestroyRenderer(d->render); + SDL_DestroyWindow(d->win); + iForIndices(i, d->cursors) { + if (d->cursors[i]) { + SDL_FreeCursor(d->cursors[i]); + } + } +} + +void init_MainWindow(iMainWindow *d, iRect rect) { + theWindow_ = &d->base; + uint32_t flags = 0; +#if defined (iPlatformAppleDesktop) + SDL_SetHint(SDL_HINT_RENDER_DRIVER, shouldDefaultToMetalRenderer_MacOS() ? "metal" : "opengl"); +#elif defined (iPlatformAppleMobile) + SDL_SetHint(SDL_HINT_RENDER_DRIVER, "metal"); +#else + flags |= SDL_WINDOW_OPENGL; +#endif + SDL_SetHint(SDL_HINT_RENDER_VSYNC, "1"); + init_Window(&d->base, main_WindowType, rect, flags); + d->splitMode = d->pendingSplitMode = 0; + d->pendingSplitUrl = new_String(); + d->place.initialPos = rect.pos; + d->place.normalRect = rect; + d->place.lastNotifiedSize = zero_I2(); + d->place.snap = 0; + d->keyboardHeight = 0; +#if defined(iPlatformMobile) const iInt2 minSize = zero_I2(); /* windows aren't independently resizable */ #else const iInt2 minSize = init_I2(425, 325); #endif - SDL_SetWindowMinimumSize(d->win, minSize.x, minSize.y); - SDL_SetWindowTitle(d->win, "Lagrange"); + SDL_SetWindowMinimumSize(d->base.win, minSize.x, minSize.y); + SDL_SetWindowTitle(d->base.win, "Lagrange"); /* Some info. */ { SDL_RendererInfo info; - SDL_GetRendererInfo(d->render, &info); + SDL_GetRendererInfo(d->base.render, &info); isOpenGLRenderer_ = !iCmpStr(info.name, "opengl"); - printf("[window] renderer: %s%s\n", info.name, + printf("[window] renderer: %s%s\n", + info.name, info.flags & SDL_RENDERER_ACCELERATED ? " (accelerated)" : ""); -#if !defined (NDEBUG) +#if !defined(NDEBUG) printf("[window] max texture size: %d x %d\n", info.max_texture_width, info.max_texture_height); for (size_t i = 0; i < info.num_texture_formats; ++i) { - printf("[window] supported texture format: %s\n", SDL_GetPixelFormatName( - info.texture_formats[i])); + printf("[window] supported texture format: %s\n", + SDL_GetPixelFormatName(info.texture_formats[i])); } #endif } - drawBlank_Window_(d); - d->pixelRatio = pixelRatio_Window_(d); /* point/pixel conversion */ - d->displayScale = displayScale_Window_(d); - d->uiScale = initialUiScale_; - setScale_Metrics(d->pixelRatio * d->displayScale * d->uiScale); -#if defined (iPlatformMsys) - SDL_SetWindowMinimumSize(d->win, minSize.x * d->displayScale, minSize.y * d->displayScale); - useExecutableIconResource_SDLWindow(d->win); +#if defined(iPlatformMsys) + SDL_SetWindowMinimumSize(d->win, minSize.x * d->base.displayScale, minSize.y * d->base.displayScale); + useExecutableIconResource_SDLWindow(d->base.win); #endif #if defined (iPlatformLinux) - SDL_SetWindowMinimumSize(d->win, minSize.x * d->pixelRatio, minSize.y * d->pixelRatio); + SDL_SetWindowMinimumSize(d->win, minSize.x * d->base.pixelRatio, minSize.y * d->base.pixelRatio); /* Load the window icon. */ { SDL_Surface *surf = loadImage_(&imageLagrange64_Embedded, 0); SDL_SetWindowIcon(d->win, surf); @@ -487,18 +512,14 @@ void init_Window(iWindow *d, iRect rect) { #if defined (iPlatformAppleMobile) setupWindow_iOS(d); #endif - d->presentTime = 0.0; - d->frameTime = SDL_GetTicks(); - d->loadAnimTimer = 0; - init_Text(d->render); - SDL_GetRendererOutputSize(d->render, &d->size.x, &d->size.y); - setupUserInterface_Window(d); + init_Text(d->base.render); + SDL_GetRendererOutputSize(d->base.render, &d->base.size.x, &d->base.size.y); + setupUserInterface_MainWindow(d); postCommand_App("~bindings.changed"); /* update from bindings */ - //updateSize_Window_(d, iFalse); /* Load the border shadow texture. */ { SDL_Surface *surf = loadImage_(&imageShadow_Embedded, 0); - d->borderShadow = SDL_CreateTextureFromSurface(d->render, surf); - SDL_SetTextureBlendMode(d->borderShadow, SDL_BLENDMODE_BLEND); + d->base.borderShadow = SDL_CreateTextureFromSurface(d->base.render, surf); + SDL_SetTextureBlendMode(d->base.borderShadow, SDL_BLENDMODE_BLEND); free(surf->pixels); SDL_FreeSurface(surf); } @@ -508,7 +529,7 @@ void init_Window(iWindow *d, iRect rect) { if (prefs_App()->customFrame) { SDL_Surface *surf = loadImage_(&imageLagrange64_Embedded, appIconSize_Root()); SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); - d->appIcon = SDL_CreateTextureFromSurface(d->render, surf); + d->appIcon = SDL_CreateTextureFromSurface(d->base.render, surf); free(surf->pixels); SDL_FreeSurface(surf); /* We need to observe non-client-area events. */ @@ -517,7 +538,7 @@ void init_Window(iWindow *d, iRect rect) { #endif } -void deinit_Window(iWindow *d) { +static void deinitRoots_Window_(iWindow *d) { iRecycle(); iForIndices(i, d->roots) { if (d->roots[i]) { @@ -525,19 +546,17 @@ void deinit_Window(iWindow *d) { deinit_Root(d->roots[i]); } } - if (theWindow_ == d) { + setCurrent_Root(NULL); +} + +void deinit_MainWindow(iMainWindow *d) { + deinitRoots_Window_(as_Window(d)); + if (theWindow_ == as_Window(d)) { theWindow_ = NULL; } - setCurrent_Root(NULL); delete_String(d->pendingSplitUrl); deinit_Text(); - SDL_DestroyRenderer(d->render); - SDL_DestroyWindow(d->win); - iForIndices(i, d->cursors) { - if (d->cursors[i]) { - SDL_FreeCursor(d->cursors[i]); - } - } + deinit_Window(&d->base); } SDL_Renderer *renderer_Window(const iWindow *d) { @@ -550,8 +569,8 @@ iInt2 maxTextureSize_Window(const iWindow *d) { return init_I2(info.max_texture_width, info.max_texture_height); } -iBool isFullscreen_Window(const iWindow *d) { - return snap_Window(d) == fullscreen_WindowSnap; +iBool isFullscreen_MainWindow(const iMainWindow *d) { + return snap_MainWindow(d) == fullscreen_WindowSnap; } iRoot *findRoot_Window(const iWindow *d, const iWidget *widget) { @@ -570,36 +589,41 @@ iRoot *otherRoot_Window(const iWindow *d, iRoot *root) { return root == d->roots[0] && d->roots[1] ? d->roots[1] : d->roots[0]; } -static void invalidate_Window_(iWindow *d, iBool forced) { - if (d && (!d->isInvalidated || forced)) { - d->isInvalidated = iTrue; +static void invalidate_MainWindow_(iMainWindow *d, iBool forced) { + if (d && (!d->base.isInvalidated || forced)) { + d->base.isInvalidated = iTrue; resetFonts_Text(); postCommand_App("theme.changed auto:1"); /* forces UI invalidation */ } } -void invalidate_Window(iWindow *d) { - invalidate_Window_(d, iFalse); +void invalidate_Window(iAnyWindow *d) { + if (type_Window(d) == main_WindowType) { + invalidate_MainWindow_(as_MainWindow(d), iFalse); + } + else { + iAssert(type_Window(d) == main_WindowType); + } } -static iBool isNormalPlacement_Window_(const iWindow *d) { - if (d->isDrawFrozen) return iFalse; +static iBool isNormalPlacement_MainWindow_(const iMainWindow *d) { + if (d->base.isDrawFrozen) return iFalse; #if defined (iPlatformApple) /* Maximized mode is not special on macOS. */ - if (snap_Window(d) == maximized_WindowSnap) { + if (snap_MainWindow(d) == maximized_WindowSnap) { return iTrue; } #endif - if (snap_Window(d)) return iFalse; - return !(SDL_GetWindowFlags(d->win) & SDL_WINDOW_MINIMIZED); + if (snap_MainWindow(d)) return iFalse; + return !(SDL_GetWindowFlags(d->base.win) & SDL_WINDOW_MINIMIZED); } -static iBool unsnap_Window_(iWindow *d, const iInt2 *newPos) { +static iBool unsnap_MainWindow_(iMainWindow *d, const iInt2 *newPos) { #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) if (!prefs_App()->customFrame) { return iFalse; } - const int snap = snap_Window(d); + const int snap = snap_MainWindow(d); if (snap == yMaximized_WindowSnap || snap == left_WindowSnap || snap == right_WindowSnap) { if (!newPos || (d->place.lastHit == SDL_HITTEST_RESIZE_LEFT || d->place.lastHit == SDL_HITTEST_RESIZE_RIGHT)) { @@ -607,21 +631,21 @@ static iBool unsnap_Window_(iWindow *d, const iInt2 *newPos) { } if (newPos) { SDL_Rect usable; - SDL_GetDisplayUsableBounds(SDL_GetWindowDisplayIndex(d->win), &usable); + SDL_GetDisplayUsableBounds(SDL_GetWindowDisplayIndex(d->base.win), &usable); /* Snap to top. */ if (snap == yMaximized_WindowSnap && iAbs(newPos->y - usable.y) < lineHeight_Text(uiContent_FontId) * 2) { - setSnap_Window(d, redo_WindowSnap | yMaximized_WindowSnap); + setSnap_MainWindow(d, redo_WindowSnap | yMaximized_WindowSnap); return iFalse; } } } if (snap && snap != fullscreen_WindowSnap) { - if (snap_Window(d) == yMaximized_WindowSnap && newPos) { + if (snap_MainWindow(d) == yMaximized_WindowSnap && newPos) { d->place.normalRect.pos = *newPos; } //printf("unsnap\n"); fflush(stdout); - setSnap_Window(d, none_WindowSnap); + setSnap_MainWindow(d, none_WindowSnap); return iTrue; } #endif @@ -652,109 +676,110 @@ static void checkPixelRatioChange_Window_(iWindow *d) { } } -static iBool handleWindowEvent_Window_(iWindow *d, const SDL_WindowEvent *ev) { +static iBool handleWindowEvent_MainWindow_(iMainWindow *d, const SDL_WindowEvent *ev) { switch (ev->event) { -#if defined (iPlatformDesktop) +#if defined(iPlatformDesktop) case SDL_WINDOWEVENT_EXPOSED: - if (!d->isExposed) { - drawBlank_Window_(d); /* avoid showing system-provided contents */ - d->isExposed = iTrue; + if (!d->base.isExposed) { + drawBlank_Window_(as_Window(d)); /* avoid showing system-provided contents */ + d->base.isExposed = iTrue; } /* Since we are manually controlling when to redraw the window, we are responsible - for ensuring that window contents get redrawn after expose events. Under certain - circumstances (e.g., under openbox), not doing this would mean that the window - is missing contents until other events trigger a refresh. */ + for ensuring that window contents get redrawn after expose events. Under certain + circumstances (e.g., under openbox), not doing this would mean that the window + is missing contents until other events trigger a refresh. */ postRefresh_App(); -#if defined (LAGRANGE_ENABLE_WINDOWPOS_FIX) +#if defined(LAGRANGE_ENABLE_WINDOWPOS_FIX) if (d->place.initialPos.x >= 0) { int bx, by; SDL_GetWindowBordersSize(d->win, &by, &bx, NULL, NULL); - SDL_SetWindowPosition(d->win, d->place.initialPos.x + bx, d->place.initialPos.y + by); + SDL_SetWindowPosition( + d->win, d->place.initialPos.x + bx, d->place.initialPos.y + by); d->place.initialPos = init1_I2(-1); } #endif return iFalse; case SDL_WINDOWEVENT_MOVED: { - if (d->isMinimized) { + if (d->base.isMinimized) { return iFalse; } - checkPixelRatioChange_Window_(d); + checkPixelRatioChange_Window_(as_Window(d)); const iInt2 newPos = init_I2(ev->data1, ev->data2); if (isEqual_I2(newPos, init1_I2(-32000))) { /* magic! */ /* Maybe minimized? Seems like a Windows constant of some kind. */ - d->isMinimized = iTrue; + d->base.isMinimized = iTrue; return iFalse; } -#if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) +#if defined(LAGRANGE_ENABLE_CUSTOM_FRAME) /* Set the snap position depending on where the mouse cursor is. */ if (prefs_App()->customFrame) { SDL_Rect usable; - iInt2 mouse = cursor_Win32(); /* SDL is unaware of the current cursor pos */ - SDL_GetDisplayUsableBounds(SDL_GetWindowDisplayIndex(d->win), &usable); - const iBool isTop = iAbs(mouse.y - usable.y) < gap_UI * 20; + iInt2 mouse = cursor_Win32(); /* SDL is unaware of the current cursor pos */ + SDL_GetDisplayUsableBounds(SDL_GetWindowDisplayIndex(d->base.win), &usable); + const iBool isTop = iAbs(mouse.y - usable.y) < gap_UI * 20; const iBool isBottom = iAbs(usable.y + usable.h - mouse.y) < gap_UI * 20; if (iAbs(mouse.x - usable.x) < gap_UI) { - setSnap_Window(d, - redo_WindowSnap | left_WindowSnap | - (isTop ? topBit_WindowSnap : 0) | - (isBottom ? bottomBit_WindowSnap : 0)); + setSnap_MainWindow(d, + redo_WindowSnap | left_WindowSnap | + (isTop ? topBit_WindowSnap : 0) | + (isBottom ? bottomBit_WindowSnap : 0)); return iTrue; } if (iAbs(mouse.x - usable.x - usable.w) < gap_UI) { - setSnap_Window(d, - redo_WindowSnap | right_WindowSnap | - (isTop ? topBit_WindowSnap : 0) | - (isBottom ? bottomBit_WindowSnap : 0)); + setSnap_MainWindow(d, + redo_WindowSnap | right_WindowSnap | + (isTop ? topBit_WindowSnap : 0) | + (isBottom ? bottomBit_WindowSnap : 0)); return iTrue; } if (iAbs(mouse.y - usable.y) < 2) { - setSnap_Window(d, - redo_WindowSnap | (d->place.lastHit == SDL_HITTEST_RESIZE_TOP - ? yMaximized_WindowSnap - : maximized_WindowSnap)); + setSnap_MainWindow(d, + redo_WindowSnap | (d->place.lastHit == SDL_HITTEST_RESIZE_TOP + ? yMaximized_WindowSnap + : maximized_WindowSnap)); return iTrue; } } #endif /* defined LAGRANGE_ENABLE_CUSTOM_FRAME */ - if (unsnap_Window_(d, &newPos)) { + if (unsnap_MainWindow_(d, &newPos)) { return iTrue; } - if (isNormalPlacement_Window_(d)) { + if (isNormalPlacement_MainWindow_(d)) { d->place.normalRect.pos = newPos; - //printf("normal rect set (move)\n"); fflush(stdout); + // printf("normal rect set (move)\n"); fflush(stdout); iInt2 border = zero_I2(); -#if !defined (iPlatformApple) +#if !defined(iPlatformApple) SDL_GetWindowBordersSize(d->win, &border.y, &border.x, NULL, NULL); #endif - d->place.normalRect.pos = max_I2(zero_I2(), sub_I2(d->place.normalRect.pos, border)); + d->place.normalRect.pos = + max_I2(zero_I2(), sub_I2(d->place.normalRect.pos, border)); } return iTrue; } case SDL_WINDOWEVENT_RESIZED: - if (d->isMinimized) { - //updateSize_Window_(d, iTrue); + if (d->base.isMinimized) { + // updateSize_Window_(d, iTrue); return iTrue; } - if (unsnap_Window_(d, NULL)) { + if (unsnap_MainWindow_(d, NULL)) { return iTrue; } - if (isNormalPlacement_Window_(d)) { + if (isNormalPlacement_MainWindow_(d)) { d->place.normalRect.size = init_I2(ev->data1, ev->data2); - //printf("normal rect set (resize)\n"); fflush(stdout); + // printf("normal rect set (resize)\n"); fflush(stdout); } - checkPixelRatioChange_Window_(d); - //updateSize_Window_(d, iTrue /* we were already redrawing during the resize */); + checkPixelRatioChange_Window_(as_Window(d)); postRefresh_App(); return iTrue; case SDL_WINDOWEVENT_RESTORED: case SDL_WINDOWEVENT_SHOWN: - updateSize_Window_(d, iTrue); - invalidate_Window_(d, iTrue); - d->isMinimized = iFalse; + updateSize_MainWindow_(d, iTrue); + invalidate_MainWindow_(d, iTrue); + d->base.isMinimized = iFalse; postRefresh_App(); return iTrue; case SDL_WINDOWEVENT_MINIMIZED: - d->isMinimized = iTrue; + d->base.isMinimized = iTrue; return iTrue; #else /* if defined (!iPlatformDesktop) */ case SDL_WINDOWEVENT_RESIZED: @@ -765,19 +790,19 @@ static iBool handleWindowEvent_Window_(iWindow *d, const SDL_WindowEvent *ev) { #endif case SDL_WINDOWEVENT_LEAVE: unhover_Widget(); - d->isMouseInside = iFalse; + d->base.isMouseInside = iFalse; postCommand_App("window.mouse.exited"); return iTrue; case SDL_WINDOWEVENT_ENTER: - d->isMouseInside = iTrue; + d->base.isMouseInside = iTrue; postCommand_App("window.mouse.entered"); return iTrue; case SDL_WINDOWEVENT_FOCUS_GAINED: - d->focusGainedAt = SDL_GetTicks(); + d->base.focusGainedAt = SDL_GetTicks(); setCapsLockDown_Keys(iFalse); postCommand_App("window.focus.gained"); - d->isExposed = iTrue; -#if !defined (iPlatformDesktop) + d->base.isExposed = iTrue; +#if !defined(iPlatformDesktop) /* Returned to foreground, may have lost buffered content. */ invalidate_Window_(d, iTrue); postCommand_App("window.unfreeze"); @@ -785,12 +810,12 @@ static iBool handleWindowEvent_Window_(iWindow *d, const SDL_WindowEvent *ev) { return iFalse; case SDL_WINDOWEVENT_FOCUS_LOST: postCommand_App("window.focus.lost"); -#if !defined (iPlatformDesktop) +#if !defined(iPlatformDesktop) setFreezeDraw_Window(d, iTrue); #endif return iFalse; case SDL_WINDOWEVENT_TAKE_FOCUS: - SDL_SetWindowInputFocus(d->win); + SDL_SetWindowInputFocus(d->base.win); postRefresh_App(); return iTrue; default: @@ -806,7 +831,8 @@ static void applyCursor_Window_(iWindow *d) { } } -iBool processEvent_Window(iWindow *d, const SDL_Event *ev) { +iBool processEvent_MainWindow(iMainWindow *d, const SDL_Event *ev) { + iWindow *w = as_Window(d); switch (ev->type) { #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) case SDL_SYSWMEVENT: { @@ -819,19 +845,19 @@ iBool processEvent_Window(iWindow *d, const SDL_Event *ev) { } #endif case SDL_WINDOWEVENT: { - return handleWindowEvent_Window_(d, &ev->window); + return handleWindowEvent_MainWindow_(d, &ev->window); } case SDL_RENDER_TARGETS_RESET: case SDL_RENDER_DEVICE_RESET: { - invalidate_Window_(d, iTrue /* force full reset */); + invalidate_MainWindow_(d, iTrue /* force full reset */); break; } default: { SDL_Event event = *ev; if (event.type == SDL_USEREVENT && isCommand_UserEvent(ev, "window.unfreeze")) { - d->isDrawFrozen = iFalse; - if (SDL_GetWindowFlags(d->win) & SDL_WINDOW_HIDDEN) { - SDL_ShowWindow(d->win); + d->base.isDrawFrozen = iFalse; + if (SDL_GetWindowFlags(w->win) & SDL_WINDOW_HIDDEN) { + SDL_ShowWindow(w->win); } postRefresh_App(); postCommand_App("media.player.update"); /* in case a player needs updating */ @@ -840,35 +866,35 @@ iBool processEvent_Window(iWindow *d, const SDL_Event *ev) { if (processEvent_Touch(&event)) { return iTrue; } - if (event.type == SDL_KEYDOWN && SDL_GetTicks() - d->focusGainedAt < 10) { + if (event.type == SDL_KEYDOWN && SDL_GetTicks() - d->base.focusGainedAt < 10) { /* Suspiciously close to when input focus was received. For example under openbox, closing xterm with Ctrl+D will cause the keydown event to "spill" over to us. As a workaround, ignore these events. */ return iTrue; /* won't go to bindings, either */ } - if (event.type == SDL_MOUSEBUTTONDOWN && d->ignoreClick) { - d->ignoreClick = iFalse; + if (event.type == SDL_MOUSEBUTTONDOWN && d->base.ignoreClick) { + d->base.ignoreClick = iFalse; return iTrue; } /* Map mouse pointer coordinate to our coordinate system. */ if (event.type == SDL_MOUSEMOTION) { - setCursor_Window(d, SDL_SYSTEM_CURSOR_ARROW); /* default cursor */ - const iInt2 pos = coord_Window(d, event.motion.x, event.motion.y); + setCursor_Window(w, SDL_SYSTEM_CURSOR_ARROW); /* default cursor */ + const iInt2 pos = coord_Window(w, event.motion.x, event.motion.y); event.motion.x = pos.x; event.motion.y = pos.y; } else if (event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEBUTTONDOWN) { - const iInt2 pos = coord_Window(d, event.button.x, event.button.y); + const iInt2 pos = coord_Window(w, event.button.x, event.button.y); event.button.x = pos.x; event.button.y = pos.y; if (event.type == SDL_MOUSEBUTTONDOWN) { /* Button clicks will change keyroot. */ - if (numRoots_Window(d) > 1) { + if (numRoots_Window(w) > 1) { const iInt2 click = init_I2(event.button.x, event.button.y); - iForIndices(i, d->roots) { - iRoot *root = d->roots[i]; - if (root != d->keyRoot && contains_Rect(rect_Root(root), click)) { - setKeyRoot_Window(d, root); + iForIndices(i, w->roots) { + iRoot *root = w->roots[i]; + if (root != w->keyRoot && contains_Rect(rect_Root(root), click)) { + setKeyRoot_Window(w, root); break; } } @@ -883,13 +909,13 @@ iBool processEvent_Window(iWindow *d, const SDL_Event *ev) { event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEBUTTONDOWN) { if (mouseGrab_Widget()) { iWidget *grabbed = mouseGrab_Widget(); - setCurrent_Root(findRoot_Window(d, grabbed)); + setCurrent_Root(findRoot_Window(w, grabbed)); wasUsed = dispatchEvent_Widget(grabbed, &event); } } /* Dispatch the event to the tree of widgets. */ if (!wasUsed) { - wasUsed = dispatchEvent_Window(d, &event); + wasUsed = dispatchEvent_Window(w, &event); } if (!wasUsed) { /* As a special case, clicking the middle mouse button can be used for pasting @@ -902,17 +928,17 @@ iBool processEvent_Window(iWindow *d, const SDL_Event *ev) { paste.key.keysym.mod = KMOD_PRIMARY; paste.key.state = SDL_PRESSED; paste.key.timestamp = SDL_GetTicks(); - wasUsed = dispatchEvent_Window(d, &paste); + wasUsed = dispatchEvent_Window(w, &paste); } if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_RIGHT) { - if (postContextClick_Window(d, &event.button)) { + if (postContextClick_Window(w, &event.button)) { wasUsed = iTrue; } } } if (isMetricsChange_UserEvent(&event)) { - iForIndices(i, d->roots) { - updateMetrics_Root(d->roots[i]); + iForIndices(i, w->roots) { + updateMetrics_Root(w->roots[i]); } } if (isCommand_UserEvent(&event, "lang.changed")) { @@ -921,16 +947,16 @@ iBool processEvent_Window(iWindow *d, const SDL_Event *ev) { removeMacMenus_(); insertMacMenus_(); #endif - invalidate_Window(d); - iForIndices(i, d->roots) { - if (d->roots[i]) { - updatePreferencesLayout_Widget(findChild_Widget(d->roots[i]->widget, "prefs")); - arrange_Widget(d->roots[i]->widget); + invalidate_Window(w); + iForIndices(i, w->roots) { + if (w->roots[i]) { + updatePreferencesLayout_Widget(findChild_Widget(w->roots[i]->widget, "prefs")); + arrange_Widget(w->roots[i]->widget); } } } if (event.type == SDL_MOUSEMOTION) { - applyCursor_Window_(d); + applyCursor_Window_(w); } return wasUsed; } @@ -1018,25 +1044,26 @@ iBool postContextClick_Window(iWindow *d, const SDL_MouseButtonEvent *ev) { return iFalse; } -void draw_Window(iWindow *d) { - if (d->isDrawFrozen) { +void draw_MainWindow(iMainWindow *d) { + iWindow *w = as_Window(d); + if (w->isDrawFrozen) { return; } /* Check if root needs resizing. */ { iInt2 renderSize; - SDL_GetRendererOutputSize(d->render, &renderSize.x, &renderSize.y); - if (!isEqual_I2(renderSize, d->size)) { - updateSize_Window_(d, iTrue); + SDL_GetRendererOutputSize(w->render, &renderSize.x, &renderSize.y); + if (!isEqual_I2(renderSize, w->size)) { + updateSize_MainWindow_(d, iTrue); processEvents_App(postedEventsOnly_AppEventMode); } } - const int winFlags = SDL_GetWindowFlags(d->win); + const int winFlags = SDL_GetWindowFlags(d->base.win); const iBool gotFocus = (winFlags & SDL_WINDOW_INPUT_FOCUS) != 0; iPaint p; init_Paint(&p); /* Clear the window. The clear color is visible as a border around the window when the custom frame is being used. */ { - setCurrent_Root(d->roots[0]); + setCurrent_Root(w->roots[0]); #if defined (iPlatformMobile) iColor back = get_Color(uiBackground_ColorId); if (deviceType_App() == phone_AppDeviceType) { @@ -1050,16 +1077,16 @@ void draw_Window(iWindow *d) { : uiSeparator_ColorId); #endif unsetClip_Paint(&p); /* update clip to full window */ - SDL_SetRenderDrawColor(d->render, back.r, back.g, back.b, 255); - SDL_RenderClear(d->render); + SDL_SetRenderDrawColor(w->render, back.r, back.g, back.b, 255); + SDL_RenderClear(w->render); } /* Draw widgets. */ - d->frameTime = SDL_GetTicks(); - if (isExposed_Window(d)) { - d->isInvalidated = iFalse; + w->frameTime = SDL_GetTicks(); + if (isExposed_Window(w)) { + w->isInvalidated = iFalse; extern int drawCount_; - iForIndices(i, d->roots) { - iRoot *root = d->roots[i]; + iForIndices(i, w->roots) { + iRoot *root = w->roots[i]; if (root) { setCurrent_Root(root); unsetClip_Paint(&p); /* update clip to current root */ @@ -1076,14 +1103,14 @@ void draw_Window(iWindow *d) { SDL_SetTextureColorMod(d->appIcon, iconColor.r, iconColor.g, iconColor.b); SDL_SetTextureAlphaMod(d->appIcon, gotFocus || !isLight ? 255 : 92); SDL_RenderCopy( - d->render, + w->render, d->appIcon, NULL, &(SDL_Rect){ left_Rect(rect) + gap_UI * 1.25f, mid.y - size / 2, size, size }); } #endif /* Root separator and keyboard focus indicator. */ - if (numRoots_Window(d) > 1){ + if (numRoots_Window(w) > 1){ const iRect bounds = bounds_Widget(root->widget); if (i == 1) { fillRect_Paint(&p, (iRect){ @@ -1091,7 +1118,7 @@ void draw_Window(iWindow *d) { init_I2(gap_UI / 4, height_Rect(bounds)) }, uiSeparator_ColorId); } - if (root == d->keyRoot) { + if (root == w->keyRoot) { const iBool isDark = isDark_ColorTheme(colorTheme_App()); fillRect_Paint(&p, (iRect){ topLeft_Rect(bounds), @@ -1104,7 +1131,7 @@ void draw_Window(iWindow *d) { } setCurrent_Root(NULL); #if !defined (NDEBUG) - draw_Text(defaultBold_FontId, safeRect_Root(d->roots[0]).pos, red_ColorId, "%d", drawCount_); + draw_Text(defaultBold_FontId, safeRect_Root(w->roots[0]).pos, red_ColorId, "%d", drawCount_); drawCount_ = 0; #endif } @@ -1116,21 +1143,21 @@ void draw_Window(iWindow *d) { SDL_RenderCopy(d->render, glyphCache_Text(), NULL, &rect); } #endif - SDL_RenderPresent(d->render); + SDL_RenderPresent(w->render); } -void resize_Window(iWindow *d, int w, int h) { +void resize_MainWindow(iMainWindow *d, int w, int h) { if (w > 0 && h > 0) { - SDL_SetWindowSize(d->win, w, h); - updateSize_Window_(d, iFalse); + SDL_SetWindowSize(d->base.win, w, h); + updateSize_MainWindow_(d, iFalse); } else { - updateSize_Window_(d, iTrue); /* notify always */ + updateSize_MainWindow_(d, iTrue); /* notify always */ } } -void setTitle_Window(iWindow *d, const iString *title) { - SDL_SetWindowTitle(d->win, cstr_String(title)); +void setTitle_MainWindow(iMainWindow *d, const iString *title) { + SDL_SetWindowTitle(d->base.win, cstr_String(title)); iLabelWidget *bar = findChild_Widget(get_Root()->widget, "winbar.title"); if (bar) { updateText_LabelWidget(bar, title); @@ -1204,11 +1231,15 @@ iWindow *get_Window(void) { return theWindow_; } +iMainWindow *get_MainWindow(void) { + return as_MainWindow(theWindow_); +} + iBool isOpenGLRenderer_Window(void) { return isOpenGLRenderer_; } -void setKeyboardHeight_Window(iWindow *d, int height) { +void setKeyboardHeight_MainWindow(iMainWindow *d, int height) { if (d->keyboardHeight != height) { d->keyboardHeight = height; postCommandf_App("keyboard.changed arg:%d", height); @@ -1216,47 +1247,49 @@ void setKeyboardHeight_Window(iWindow *d, int height) { } } -void checkPendingSplit_Window(iWindow *d) { +void checkPendingSplit_MainWindow(iMainWindow *d) { if (d->splitMode != d->pendingSplitMode) { - setSplitMode_Window(d, d->pendingSplitMode); + setSplitMode_MainWindow(d, d->pendingSplitMode); } } -void swapRoots_Window(iWindow *d) { - if (numRoots_Window(d) == 2) { - iSwap(iRoot *, d->roots[0], d->roots[1]); - updateSize_Window_(d, iTrue); +void swapRoots_MainWindow(iMainWindow *d) { + iWindow *w = as_Window(d); + if (numRoots_Window(w) == 2) { + iSwap(iRoot *, w->roots[0], w->roots[1]); + updateSize_MainWindow_(d, iTrue); } } -void setSplitMode_Window(iWindow *d, int splitFlags) { +void setSplitMode_MainWindow(iMainWindow *d, int splitFlags) { const int splitMode = splitFlags & mode_WindowSplit; if (deviceType_App() == phone_AppDeviceType) { /* There isn't enough room on the phone. */ /* TODO: Maybe in landscape only? */ return; } + iWindow *w = as_Window(d); iAssert(current_Root() == NULL); if (d->splitMode != splitMode) { - int oldCount = numRoots_Window(d); - setFreezeDraw_Window(d, iTrue); + int oldCount = numRoots_Window(w); + setFreezeDraw_Window(w, iTrue); if (oldCount == 2 && splitMode == 0) { /* Keep references to the tabs of the second root. */ - const iDocumentWidget *curPage = document_Root(d->keyRoot); + const iDocumentWidget *curPage = document_Root(w->keyRoot); if (!curPage) { /* All tabs closed on that side. */ - curPage = document_Root(otherRoot_Window(d, d->keyRoot)); + curPage = document_Root(otherRoot_Window(w, w->keyRoot)); } - iObjectList *tabs = listDocuments_App(d->roots[1]); + iObjectList *tabs = listDocuments_App(w->roots[1]); iForEach(ObjectList, i, tabs) { - setRoot_Widget(i.object, d->roots[0]); + setRoot_Widget(i.object, w->roots[0]); } setFocus_Widget(NULL); - delete_Root(d->roots[1]); - d->roots[1] = NULL; - d->keyRoot = d->roots[0]; + delete_Root(w->roots[1]); + w->roots[1] = NULL; + w->keyRoot = w->roots[0]; /* Move the deleted root's tabs to the first root. */ - setCurrent_Root(d->roots[0]); + setCurrent_Root(w->roots[0]); iWidget *docTabs = findWidget_Root("doctabs"); iForEach(ObjectList, j, tabs) { appendTabPage_Widget(docTabs, j.object, "", 0, 0); @@ -1268,38 +1301,38 @@ void setSplitMode_Window(iWindow *d, int splitFlags) { } else if (splitMode && oldCount == 1) { /* Add a second root. */ - iDocumentWidget *moved = document_Root(d->roots[0]); - iAssert(d->roots[1] == NULL); + iDocumentWidget *moved = document_Root(w->roots[0]); + iAssert(w->roots[1] == NULL); const iBool addToLeft = (prefs_App()->pinSplit == 2); size_t newRootIndex = 1; if (addToLeft) { - iSwap(iRoot *, d->roots[0], d->roots[1]); + iSwap(iRoot *, w->roots[0], w->roots[1]); newRootIndex = 0; } - d->roots[newRootIndex] = new_Root(); - d->keyRoot = d->roots[newRootIndex]; - setCurrent_Root(d->roots[newRootIndex]); - createUserInterface_Root(d->roots[newRootIndex]); + w->roots[newRootIndex] = new_Root(); + w->keyRoot = w->roots[newRootIndex]; + setCurrent_Root(w->roots[newRootIndex]); + createUserInterface_Root(w->roots[newRootIndex]); if (!isEmpty_String(d->pendingSplitUrl)) { - postCommandf_Root(d->roots[newRootIndex], "open url:%s", + postCommandf_Root(w->roots[newRootIndex], "open url:%s", cstr_String(d->pendingSplitUrl)); clear_String(d->pendingSplitUrl); } else if (~splitFlags & noEvents_WindowSplit) { - iWidget *docTabs0 = findChild_Widget(d->roots[newRootIndex ^ 1]->widget, "doctabs"); - iWidget *docTabs1 = findChild_Widget(d->roots[newRootIndex]->widget, "doctabs"); + iWidget *docTabs0 = findChild_Widget(w->roots[newRootIndex ^ 1]->widget, "doctabs"); + iWidget *docTabs1 = findChild_Widget(w->roots[newRootIndex]->widget, "doctabs"); /* If the old root has multiple tabs, move the current one to the new split. */ if (tabCount_Widget(docTabs0) >= 2) { int movedIndex = tabPageIndex_Widget(docTabs0, moved); removeTabPage_Widget(docTabs0, movedIndex); showTabPage_Widget(docTabs0, tabPage_Widget(docTabs0, iMax(movedIndex - 1, 0))); iRelease(removeTabPage_Widget(docTabs1, 0)); /* delete the default tab */ - setRoot_Widget(as_Widget(moved), d->roots[newRootIndex]); + setRoot_Widget(as_Widget(moved), w->roots[newRootIndex]); prependTabPage_Widget(docTabs1, iClob(moved), "", 0, 0); postCommandf_App("tabs.switch page:%p", moved); } else { - postCommand_Root(d->roots[newRootIndex], "navigate.home"); + postCommand_Root(w->roots[newRootIndex], "navigate.home"); } } setCurrent_Root(NULL); @@ -1328,26 +1361,26 @@ void setSplitMode_Window(iWindow *d, int splitFlags) { } #endif if (~splitFlags & noEvents_WindowSplit) { - updateSize_Window_(d, iTrue); + updateSize_MainWindow_(d, iTrue); postCommand_App("window.unfreeze"); } } } -void setSnap_Window(iWindow *d, int snapMode) { +void setSnap_MainWindow(iMainWindow *d, int snapMode) { if (!prefs_App()->customFrame) { if (snapMode == maximized_WindowSnap) { - SDL_MaximizeWindow(d->win); + SDL_MaximizeWindow(d->base.win); } else if (snapMode == fullscreen_WindowSnap) { - SDL_SetWindowFullscreen(d->win, SDL_WINDOW_FULLSCREEN_DESKTOP); + SDL_SetWindowFullscreen(d->base.win, SDL_WINDOW_FULLSCREEN_DESKTOP); } else { - if (snap_Window(d) == fullscreen_WindowSnap) { - SDL_SetWindowFullscreen(d->win, 0); + if (snap_MainWindow(d) == fullscreen_WindowSnap) { + SDL_SetWindowFullscreen(d->base.win, 0); } else { - SDL_RestoreWindow(d->win); + SDL_RestoreWindow(d->base.win); } } return; @@ -1359,9 +1392,9 @@ void setSnap_Window(iWindow *d, int snapMode) { const int snapDist = gap_UI * 4; iRect newRect = zero_Rect(); SDL_Rect usable; - SDL_GetDisplayUsableBounds(SDL_GetWindowDisplayIndex(d->win), &usable); + SDL_GetDisplayUsableBounds(SDL_GetWindowDisplayIndex(d->base.win), &usable); if (d->place.snap == fullscreen_WindowSnap) { - SDL_SetWindowFullscreen(d->win, 0); + SDL_SetWindowFullscreen(d->base.win, 0); } d->place.snap = snapMode & ~redo_WindowSnap; switch (snapMode & mask_WindowSnap) { @@ -1382,8 +1415,8 @@ void setSnap_Window(iWindow *d, int snapMode) { case yMaximized_WindowSnap: newRect.pos.y = 0; newRect.size.y = usable.h; - SDL_GetWindowSize(d->win, &newRect.size.x, NULL); - SDL_GetWindowPosition(d->win, &newRect.pos.x, NULL); + SDL_GetWindowSize(d->base.win, &newRect.size.x, NULL); + SDL_GetWindowPosition(d->base.win, &newRect.pos.x, NULL); /* Snap the window to left/right edges, if close by. */ if (iAbs(right_Rect(newRect) - (usable.x + usable.w)) < snapDist) { newRect.pos.x = usable.x + usable.w - width_Rect(newRect); @@ -1393,7 +1426,7 @@ void setSnap_Window(iWindow *d, int snapMode) { } break; case fullscreen_WindowSnap: - SDL_SetWindowFullscreen(d->win, SDL_WINDOW_FULLSCREEN_DESKTOP); + SDL_SetWindowFullscreen(d->base.win, SDL_WINDOW_FULLSCREEN_DESKTOP); break; } if (snapMode & (topBit_WindowSnap | bottomBit_WindowSnap)) { @@ -1425,9 +1458,9 @@ void setSnap_Window(iWindow *d, int snapMode) { #endif /* defined (LAGRANGE_ENABLE_CUSTOM_FRAME) */ } -int snap_Window(const iWindow *d) { +int snap_MainWindow(const iMainWindow *d) { if (!prefs_App()->customFrame) { - const int flags = SDL_GetWindowFlags(d->win); + const int flags = SDL_GetWindowFlags(d->base.win); if (flags & SDL_WINDOW_FULLSCREEN_DESKTOP) { return fullscreen_WindowSnap; } diff --git a/src/ui/window.h b/src/ui/window.h index 282e1682..73e92391 100644 --- a/src/ui/window.h +++ b/src/ui/window.h @@ -29,8 +29,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include +iDeclareType(MainWindow) iDeclareType(Window) -iDeclareTypeConstructionArgs(Window, iRect rect) +iDeclareTypeConstructionArgs(MainWindow, iRect rect) + +typedef iAny iAnyWindow; enum iWindowSnap { none_WindowSnap = 0, @@ -68,9 +71,14 @@ enum iWindowSplit { noEvents_WindowSplit = iBit(11), }; +enum iWindowType { + main_WindowType, + popup_WindowType, +}; + struct Impl_Window { + enum iWindowType type; SDL_Window * win; - iWindowPlacement place; iBool isDrawFrozen; /* avoids premature draws while restoring window state */ iBool isExposed; iBool isMinimized; @@ -80,11 +88,6 @@ struct Impl_Window { uint32_t focusGainedAt; SDL_Renderer *render; iInt2 size; - int splitMode; - int pendingSplitMode; - iString * pendingSplitUrl; /* URL to open in a newly opened split */ - iRoot * roots[2]; /* root widget and UI state; second one is for split mode */ - iRoot * keyRoot; /* root that has the current keyboard input focus */ iWidget * hover; iWidget * lastHover; /* cleared if deleted */ iWidget * mouseGrab; @@ -94,56 +97,102 @@ struct Impl_Window { float uiScale; uint32_t frameTime; double presentTime; - SDL_Texture * appIcon; - SDL_Texture * borderShadow; SDL_Cursor * cursors[SDL_NUM_SYSTEM_CURSORS]; SDL_Cursor * pendingCursor; - int loadAnimTimer; - int keyboardHeight; /* mobile software keyboards */ + iRoot * roots[2]; /* root widget and UI state; second one is for split mode */ + iRoot * keyRoot; /* root that has the current keyboard input focus */ + SDL_Texture * borderShadow; +}; + +struct Impl_MainWindow { + iWindow base; + iWindowPlacement place; + int splitMode; + int pendingSplitMode; + iString * pendingSplitUrl; /* URL to open in a newly opened split */ + SDL_Texture * appIcon; + int keyboardHeight; /* mobile software keyboards */ }; -iBool processEvent_Window (iWindow *, const SDL_Event *); +iLocalDef enum iWindowType type_Window(const iAnyWindow *d) { + return ((const iWindow *) d)->type; +} + +uint32_t id_Window (const iWindow *); +iInt2 size_Window (const iWindow *); +iInt2 maxTextureSize_Window (const iWindow *); +float uiScale_Window (const iWindow *); +iInt2 coord_Window (const iWindow *, int x, int y); +iInt2 mouseCoord_Window (const iWindow *, int whichDevice); +iAnyObject * hitChild_Window (const iWindow *, iInt2 coord); +uint32_t frameTime_Window (const iWindow *); +SDL_Renderer * renderer_Window (const iWindow *); +int numRoots_Window (const iWindow *); +iRoot * findRoot_Window (const iWindow *, const iWidget *widget); +iRoot * otherRoot_Window (const iWindow *, iRoot *root); + iBool dispatchEvent_Window (iWindow *, const SDL_Event *); -void invalidate_Window (iWindow *); /* discard all cached graphics */ +void invalidate_Window (iAnyWindow *); /* discard all cached graphics */ void draw_Window (iWindow *); -void drawWhileResizing_Window(iWindow *d, int w, int h); /* workaround for SDL bug */ -void resize_Window (iWindow *, int w, int h); -void setTitle_Window (iWindow *, const iString *title); void setUiScale_Window (iWindow *, float uiScale); void setFreezeDraw_Window (iWindow *, iBool freezeDraw); -iBool setKeyRoot_Window (iWindow *, iRoot *root); void setCursor_Window (iWindow *, int cursor); -void setSnap_Window (iWindow *, int snapMode); -void setKeyboardHeight_Window(iWindow *, int height); -void setSplitMode_Window (iWindow *, int splitMode); -void showToolbars_Window (iWindow *, iBool show); +iBool setKeyRoot_Window (iWindow *, iRoot *root); iBool postContextClick_Window (iWindow *, const SDL_MouseButtonEvent *); -void checkPendingSplit_Window(iWindow *); -void swapRoots_Window (iWindow *); - -uint32_t id_Window (const iWindow *); -iInt2 size_Window (const iWindow *); -iInt2 maxTextureSize_Window (const iWindow *); -float uiScale_Window (const iWindow *); -iInt2 coord_Window (const iWindow *, int x, int y); -iInt2 mouseCoord_Window (const iWindow *, int whichDevice); -iAnyObject *hitChild_Window (const iWindow *, iInt2 coord); -uint32_t frameTime_Window (const iWindow *); -SDL_Renderer *renderer_Window (const iWindow *); -int snap_Window (const iWindow *); -iBool isFullscreen_Window (const iWindow *); -int numRoots_Window (const iWindow *); -iRoot * findRoot_Window (const iWindow *, const iWidget *widget); -iRoot * otherRoot_Window (const iWindow *, iRoot *root); -iWindow * get_Window (void); +iWindow * get_Window (void); iBool isOpenGLRenderer_Window (void); -#if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) -SDL_HitTestResult hitTest_Window(const iWindow *d, iInt2 pos); -#endif - iLocalDef iBool isExposed_Window(const iWindow *d) { iAssert(d); return d->isExposed; } + +iLocalDef iWindow *as_Window(iAnyWindow *d) { + iAssert(type_Window(d) == main_WindowType || type_Window(d) == popup_WindowType); + return (iWindow *) d; +} + +iLocalDef const iWindow *constAs_Window(const iAnyWindow *d) { + iAssert(type_Window(d) == main_WindowType || type_Window(d) == popup_WindowType); + return (const iWindow *) d; +} + +/*----------------------------------------------------------------------------------------------*/ + +iLocalDef iWindow *asWindow_MainWindow(iMainWindow *d) { + iAssert(type_Window(d) == main_WindowType); + return &d->base; +} + +void setTitle_MainWindow (iMainWindow *, const iString *title); +void setSnap_MainWindow (iMainWindow *, int snapMode); +void setKeyboardHeight_MainWindow (iMainWindow *, int height); +void setSplitMode_MainWindow (iMainWindow *, int splitMode); +void checkPendingSplit_MainWindow (iMainWindow *); +void swapRoots_MainWindow (iMainWindow *); +void showToolbars_MainWindow (iMainWindow *, iBool show); +void resize_MainWindow (iMainWindow *, int w, int h); + +iBool processEvent_MainWindow (iMainWindow *, const SDL_Event *); +void draw_MainWindow (iMainWindow *); +void drawWhileResizing_MainWindow (iMainWindow *, int w, int h); /* workaround for SDL bug */ + +int snap_MainWindow (const iMainWindow *); +iBool isFullscreen_Window (const iMainWindow *); + +#if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) +SDL_HitTestResult hitTest_MainWindow(const iMainWindow *d, iInt2 pos); +#endif + +iMainWindow * get_MainWindow (void); + +iLocalDef iMainWindow *as_MainWindow(iAnyWindow *d) { + iAssert(type_Window(d) == main_WindowType); + return (iMainWindow *) d; +} + +iLocalDef const iMainWindow *constAs_MainWindow(const iAnyWindow *d) { + iAssert(type_Window(d) == main_WindowType); + return (const iMainWindow *) d; +} -- cgit v1.2.3 From 2d81addf78d6a8b0fb2f2959b04a385c4adffdf2 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Mon, 20 Sep 2021 11:37:23 +0300 Subject: Experimenting with independent popup windows Toe dipping into multiple window support by allowing popup menu widgets to be displayed in independent windows. This is not a 100% replacement for native menus, but it gets pretty close. --- src/app.c | 186 +++++++++++++++++++++++------------ src/app.h | 10 +- src/ios.m | 10 +- src/macos.h | 3 + src/macos.m | 17 +++- src/ui/documentwidget.c | 2 +- src/ui/inputwidget.c | 2 +- src/ui/root.c | 10 -- src/ui/root.h | 2 +- src/ui/text.c | 93 +++++++++--------- src/ui/text.h | 17 ++-- src/ui/text_simple.c | 16 +-- src/ui/util.c | 42 +++++++- src/ui/widget.c | 38 +++++-- src/ui/widget.h | 4 +- src/ui/window.c | 257 ++++++++++++++++++++++++++++++++++++------------ src/ui/window.h | 34 +++++-- 17 files changed, 511 insertions(+), 232 deletions(-) (limited to 'src/ui/documentwidget.c') diff --git a/src/app.c b/src/app.c index 91b3a06d..c1c1da27 100644 --- a/src/app.c +++ b/src/app.c @@ -118,6 +118,7 @@ struct Impl_App { iVisited * visited; iBookmarks * bookmarks; iMainWindow *window; + iPtrArray popupWindows; iSortedArray tickers; /* per-frame callbacks, used for animations */ uint32_t lastTickerTime; uint32_t elapsedSinceLastTicker; @@ -801,6 +802,7 @@ static void init_App_(iApp *d, int argc, char **argv) { d->initialWindowRect.size.y = toInt_String(value_CommandLineArg(arg, 0)); } } + init_PtrArray(&d->popupWindows); d->window = new_MainWindow(d->initialWindowRect); load_Visited(d->visited, dataDir_App_()); load_Bookmarks(d->bookmarks, dataDir_App_()); @@ -853,6 +855,11 @@ static void init_App_(iApp *d, int argc, char **argv) { } static void deinit_App(iApp *d) { + iReverseForEach(PtrArray, i, &d->popupWindows) { + delete_Window(i.ptr); + } + iAssert(isEmpty_PtrArray(&d->popupWindows)); + deinit_PtrArray(&d->popupWindows); #if defined (LAGRANGE_ENABLE_IDLE_SLEEP) SDL_RemoveTimer(d->sleepTimer); #endif @@ -1086,6 +1093,15 @@ static iBool nextEvent_App_(iApp *d, enum iAppEventMode eventMode, SDL_Event *ev return SDL_PollEvent(event); } +static const iPtrArray *listWindows_App_(const iApp *d) { + iPtrArray *list = collectNew_PtrArray(); + iReverseConstForEach(PtrArray, i, &d->popupWindows) { + pushBack_PtrArray(list, i.ptr); + } + pushBack_PtrArray(list, d->window); + return list; +} + void processEvents_App(enum iAppEventMode eventMode) { iApp *d = &app_; iRoot *oldCurrentRoot = current_Root(); /* restored afterwards */ @@ -1125,17 +1141,17 @@ void processEvents_App(enum iAppEventMode eventMode) { #if defined (iPlatformAppleMobile) updateNowPlayingInfo_iOS(); #endif - setFreezeDraw_Window(as_Window(d), iTrue); + setFreezeDraw_MainWindow(d->window, iTrue); savePrefs_App_(d); saveState_App_(d); break; case SDL_APP_TERMINATING: - setFreezeDraw_Window(as_Window(d), iTrue); + setFreezeDraw_MainWindow(d->window, iTrue); savePrefs_App_(d); saveState_App_(d); break; case SDL_DROPFILE: { - iBool wasUsed = processEvent_MainWindow(d->window, &ev); + iBool wasUsed = processEvent_Window(as_Window(d->window), &ev); if (!wasUsed) { iBool newTab = iFalse; if (elapsedSeconds_Time(&d->lastDropTime) < 0.1) { @@ -1175,23 +1191,6 @@ void processEvents_App(enum iAppEventMode eventMode) { } d->isIdling = iFalse; #endif - if (ev.type == SDL_USEREVENT && ev.user.code == arrange_UserEventCode) { - printf("[App] rearrange\n"); - resize_MainWindow(d->window, -1, -1); - iForIndices(i, d->window->base.roots) { - if (d->window->base.roots[i]) { - d->window->base.roots[i]->pendingArrange = iFalse; - } - } -// if (ev.user.data2 == d->window->roots[0]) { -// arrange_Widget(d->window->roots[0]->widget); -// } -// else if (d->window->roots[1]) { -// arrange_Widget(d->window->roots[1]->widget); -// } -// postRefresh_App(); - continue; - } gotEvents = iTrue; /* Keyboard modifier mapping. */ if (ev.type == SDL_KEYDOWN || ev.type == SDL_KEYUP) { @@ -1268,10 +1267,22 @@ void processEvents_App(enum iAppEventMode eventMode) { } } #endif - d->window->base.lastHover = d->window->base.hover; - iBool wasUsed = processEvent_MainWindow(d->window, &ev); + /* Per-window processing. */ + iBool wasUsed = iFalse; + const iPtrArray *windows = listWindows_App_(d); + iConstForEach(PtrArray, iter, windows) { + iWindow *window = iter.ptr; + setCurrent_Window(window); + window->lastHover = window->hover; + wasUsed = processEvent_Window(window, &ev); + if (ev.type == SDL_MOUSEMOTION) { + break; + } + if (wasUsed) break; + } + setCurrent_Window(d->window); if (!wasUsed) { - /* There may be a key bindings for this. */ + /* There may be a key binding for this. */ wasUsed = processEvent_Keys(&ev); } if (!wasUsed) { @@ -1289,24 +1300,32 @@ void processEvents_App(enum iAppEventMode eventMode) { handleCommand_MacOS(command_UserEvent(&ev)); #endif if (isMetricsChange_UserEvent(&ev)) { - iForIndices(i, d->window->base.roots) { - iRoot *root = d->window->base.roots[i]; - if (root) { - arrange_Widget(root->widget); - } + iConstForEach(PtrArray, iter, windows) { + iWindow *window = iter.ptr; + iForIndices(i, window->roots) { + iRoot *root = window->roots[i]; + if (root) { + arrange_Widget(root->widget); + } + } } } if (!wasUsed) { /* No widget handled the command, so we'll do it. */ + setCurrent_Window(d->window); handleCommand_App(ev.user.data1); } /* Allocated by postCommand_Apps(). */ free(ev.user.data1); } - /* Update when hover has changed. */ - if (d->window->base.lastHover != d->window->base.hover) { - refresh_Widget(d->window->base.lastHover); - refresh_Widget(d->window->base.hover); + /* Refresh after hover changes. */ { + iConstForEach(PtrArray, iter, windows) { + iWindow *window = iter.ptr; + if (window->lastHover != window->hover) { + refresh_Widget(window->lastHover); + refresh_Widget(window->hover); + } + } } break; } @@ -1394,25 +1413,46 @@ static int run_App_(iApp *d) { void refresh_App(void) { iApp *d = &app_; - iForIndices(i, d->window->base.roots) { - iRoot *root = d->window->base.roots[i]; - if (root) { - destroyPending_Root(root); - } - } #if defined (LAGRANGE_ENABLE_IDLE_SLEEP) if (d->warmupFrames == 0 && d->isIdling) { return; } #endif + const iPtrArray *windows = listWindows_App_(d); + /* Destroy pending widgets. */ { + iConstForEach(PtrArray, j, windows) { + iWindow *win = j.ptr; + setCurrent_Window(win); + iForIndices(i, win->roots) { + iRoot *root = win->roots[i]; + if (root) { + destroyPending_Root(root); + } + } + } + } + /* TODO: Pending refresh is window-specific. */ if (!exchange_Atomic(&d->pendingRefresh, iFalse)) { return; } -// iTime draw; -// initCurrent_Time(&draw); - draw_MainWindow(d->window); -// printf("draw: %lld \u03bcs\n", (long long) (elapsedSeconds_Time(&draw) * 1000000)); -// fflush(stdout); + /* Draw each window. */ { + iConstForEach(PtrArray, j, windows) { + iWindow *win = j.ptr; + setCurrent_Window(win); + switch (win->type) { + case main_WindowType: + // iTime draw; + // initCurrent_Time(&draw); + draw_MainWindow(as_MainWindow(win)); + // printf("draw: %lld \u03bcs\n", (long long) (elapsedSeconds_Time(&draw) * 1000000)); + // fflush(stdout); + break; + default: + draw_Window(win); + break; + } + } + } if (d->warmupFrames > 0) { d->warmupFrames--; } @@ -1485,12 +1525,6 @@ void postRefresh_App(void) { } } -void postImmediateRefresh_App(void) { - SDL_Event ev = { .type = SDL_USEREVENT }; - ev.user.code = immediateRefresh_UserEventCode; - SDL_PushEvent(&ev); -} - void postCommand_Root(iRoot *d, const char *command) { iAssert(command); if (strlen(command) == 0) { @@ -1546,7 +1580,7 @@ void postCommandf_App(const char *command, ...) { } void rootOrder_App(iRoot *roots[2]) { - const iWindow *win = as_Window(app_.window); + const iWindow *win = get_Window(); roots[0] = win->keyRoot; roots[1] = (roots[0] == win->roots[0] ? win->roots[1] : win->roots[0]); } @@ -1583,6 +1617,16 @@ void removeTicker_App(iTickerFunc ticker, iAny *context) { remove_SortedArray(&d->tickers, &(iTicker){ context, NULL, ticker }); } +void addPopup_App(iWindow *popup) { + iApp *d = &app_; + pushBack_PtrArray(&d->popupWindows, popup); +} + +void removePopup_App(iWindow *popup) { + iApp *d = &app_; + removeOne_PtrArray(&d->popupWindows, popup); +} + iMimeHooks *mimeHooks_App(void) { return app_.mimehooks; } @@ -1836,8 +1880,10 @@ iDocumentWidget *newTab_App(const iDocumentWidget *duplicateOf, iBool switchToNe static iBool handleIdentityCreationCommands_(iWidget *dlg, const char *cmd) { iApp *d = &app_; if (equal_Command(cmd, "ident.showmore")) { - iForEach(ObjectList, i, - children_Widget(findChild_Widget(dlg, isUsingPanelLayout_Mobile() ? "panel.top" : "headings"))) { + iForEach(ObjectList, + i, + children_Widget(findChild_Widget( + dlg, isUsingPanelLayout_Mobile() ? "panel.top" : "headings"))) { if (flags_Widget(i.object) & collapse_WidgetFlag) { setFlags_Widget(i.object, hidden_WidgetFlag, iFalse); } @@ -1978,9 +2024,15 @@ const iString *searchQueryUrl_App(const iString *queryStringUnescaped) { return collectNewFormat_String("%s?%s", cstr_String(&d->prefs.searchUrl), cstr_String(escaped)); } +static void resetFonts_App_(iApp *d) { + iConstForEach(PtrArray, win, listWindows_App_(d)) { + resetFonts_Text(text_Window(win.ptr)); + } +} + iBool handleCommand_App(const char *cmd) { iApp *d = &app_; - const iBool isFrozen = !d->window || d->window->base.isDrawFrozen; + const iBool isFrozen = !d->window || d->window->isDrawFrozen; if (equal_Command(cmd, "config.error")) { makeSimpleMessage_Widget(uiTextCaution_ColorEscape "CONFIG ERROR", format_CStr("Error in config file: %s\n" @@ -2047,18 +2099,18 @@ iBool handleCommand_App(const char *cmd) { return iTrue; } else if (equal_Command(cmd, "font.reset")) { - resetFonts_Text(); + resetFonts_App_(d); return iTrue; } else if (equal_Command(cmd, "font.user")) { const char *path = suffixPtr_Command(cmd, "path"); if (cmp_String(&d->prefs.symbolFontPath, path)) { if (!isFrozen) { - setFreezeDraw_Window(get_Window(), iTrue); + setFreezeDraw_MainWindow(get_MainWindow(), iTrue); } setCStr_String(&d->prefs.symbolFontPath, path); loadUserFonts_Text(); - resetFonts_Text(); + resetFonts_App_(d); if (!isFrozen) { postCommand_App("font.changed"); postCommand_App("window.unfreeze"); @@ -2068,10 +2120,10 @@ iBool handleCommand_App(const char *cmd) { } else if (equal_Command(cmd, "font.set")) { if (!isFrozen) { - setFreezeDraw_Window(get_Window(), iTrue); + setFreezeDraw_MainWindow(get_MainWindow(), iTrue); } d->prefs.font = arg_Command(cmd); - setContentFont_Text(d->prefs.font); + setContentFont_Text(text_Window(d->window), d->prefs.font); if (!isFrozen) { postCommand_App("font.changed"); postCommand_App("window.unfreeze"); @@ -2080,10 +2132,10 @@ iBool handleCommand_App(const char *cmd) { } else if (equal_Command(cmd, "headingfont.set")) { if (!isFrozen) { - setFreezeDraw_Window(get_Window(), iTrue); + setFreezeDraw_MainWindow(get_MainWindow(), iTrue); } d->prefs.headingFont = arg_Command(cmd); - setHeadingFont_Text(d->prefs.headingFont); + setHeadingFont_Text(text_Window(d->window), d->prefs.headingFont); if (!isFrozen) { postCommand_App("font.changed"); postCommand_App("window.unfreeze"); @@ -2092,10 +2144,10 @@ iBool handleCommand_App(const char *cmd) { } else if (equal_Command(cmd, "zoom.set")) { if (!isFrozen) { - setFreezeDraw_Window(get_Window(), iTrue); /* no intermediate draws before docs updated */ + setFreezeDraw_MainWindow(get_MainWindow(), iTrue); /* no intermediate draws before docs updated */ } d->prefs.zoomPercent = arg_Command(cmd); - setContentFontSize_Text((float) d->prefs.zoomPercent / 100.0f); + setContentFontSize_Text(text_Window(d->window), (float) d->prefs.zoomPercent / 100.0f); if (!isFrozen) { postCommand_App("font.changed"); postCommand_App("window.unfreeze"); @@ -2104,14 +2156,14 @@ iBool handleCommand_App(const char *cmd) { } else if (equal_Command(cmd, "zoom.delta")) { if (!isFrozen) { - setFreezeDraw_Window(get_Window(), iTrue); /* no intermediate draws before docs updated */ + setFreezeDraw_MainWindow(get_MainWindow(), iTrue); /* no intermediate draws before docs updated */ } int delta = arg_Command(cmd); if (d->prefs.zoomPercent < 100 || (delta < 0 && d->prefs.zoomPercent == 100)) { delta /= 2; } d->prefs.zoomPercent = iClamp(d->prefs.zoomPercent + delta, 50, 200); - setContentFontSize_Text((float) d->prefs.zoomPercent / 100.0f); + setContentFontSize_Text(text_Window(d->window), (float) d->prefs.zoomPercent / 100.0f); if (!isFrozen) { postCommand_App("font.changed"); postCommand_App("window.unfreeze"); @@ -2211,7 +2263,7 @@ iBool handleCommand_App(const char *cmd) { equal_Command(cmd, "prefs.mono.gopher.changed")) { const iBool isSet = (arg_Command(cmd) != 0); if (!isFrozen) { - setFreezeDraw_Window(as_Window(d->window), iTrue); + setFreezeDraw_MainWindow(get_MainWindow(), iTrue); } if (startsWith_CStr(cmd, "prefs.mono.gemini")) { d->prefs.monospaceGemini = isSet; @@ -2936,3 +2988,7 @@ iStringSet *listOpenURLs_App(void) { iRelease(docs); return set; } + +iMainWindow *mainWindow_App(void) { + return app_.window; +} diff --git a/src/app.h b/src/app.h index 08589000..8966e8c7 100644 --- a/src/app.h +++ b/src/app.h @@ -22,8 +22,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once -/* Application core: event loop, base event processing, audio synth. */ - #include #include #include @@ -35,6 +33,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ iDeclareType(Bookmarks) iDeclareType(DocumentWidget) iDeclareType(GmCerts) +iDeclareType(MainWindow) iDeclareType(MimeHooks) iDeclareType(Periodic) iDeclareType(Root) @@ -61,14 +60,12 @@ enum iAppEventMode { enum iUserEventCode { command_UserEventCode = 1, refresh_UserEventCode, - arrange_UserEventCode, asleep_UserEventCode, /* The start of a potential touch tap event is notified via a custom event because sending SDL_MOUSEBUTTONDOWN would be premature: we don't know how long the tap will take, it could turn into a tap-and-hold for example. */ widgetTapBegins_UserEventCode, widgetTouchEnds_UserEventCode, /* finger lifted, but momentum may continue */ - immediateRefresh_UserEventCode, /* refresh even though more events are pending */ }; const iString *execPath_App (void); @@ -119,8 +116,9 @@ iAny * findWidget_App (const char *id); void addTicker_App (iTickerFunc ticker, iAny *context); void addTickerRoot_App (iTickerFunc ticker, iRoot *root, iAny *context); void removeTicker_App (iTickerFunc ticker, iAny *context); +void addPopup_App (iWindow *popup); +void removePopup_App (iWindow *popup); void postRefresh_App (void); -void postImmediateRefresh_App(void); void postCommand_Root (iRoot *, const char *command); void postCommandf_Root (iRoot *, const char *command, ...); void postCommandf_App (const char *command, ...); @@ -138,3 +136,5 @@ iDocumentWidget * document_Command (const char *cmd); void openInDefaultBrowser_App (const iString *url); void revealPath_App (const iString *path); + +iMainWindow *mainWindow_App(void); diff --git a/src/ios.m b/src/ios.m index 3fb0af48..b46fb8dc 100644 --- a/src/ios.m +++ b/src/ios.m @@ -247,14 +247,14 @@ didPickDocumentsAtURLs:(NSArray *)urls { UIView *view = [viewController_(get_Window()) view]; CGRect keyboardFrame = [view convertRect:rawFrame fromView:nil]; // NSLog(@"keyboardFrame: %@", NSStringFromCGRect(keyboardFrame)); - iWindow *window = get_Window(); - const iInt2 rootSize = size_Root(window->roots[0]); - const int keyTop = keyboardFrame.origin.y * window->pixelRatio; - setKeyboardHeight_Window(window, rootSize.y - keyTop); + iMainWindow *window = get_MainWindow(); + const iInt2 rootSize = size_Root(window->base.roots[0]); + const int keyTop = keyboardFrame.origin.y * window->base.pixelRatio; + setKeyboardHeight_MainWindow(window, rootSize.y - keyTop); } -(void)keyboardOffScreen:(NSNotification *)notification { - setKeyboardHeight_Window(get_Window(), 0); + setKeyboardHeight_MainWindow(get_MainWindow(), 0); } @end diff --git a/src/macos.h b/src/macos.h index 0d3f097a..20b95943 100644 --- a/src/macos.h +++ b/src/macos.h @@ -24,6 +24,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "ui/util.h" +iDeclareType(Window) + /* Platform-specific functionality for macOS */ iBool shouldDefaultToMetalRenderer_MacOS (void); @@ -31,6 +33,7 @@ iBool shouldDefaultToMetalRenderer_MacOS (void); void enableMomentumScroll_MacOS (void); void registerURLHandler_MacOS (void); void setupApplication_MacOS (void); +void hideTitleBar_MacOS (iWindow *window); void insertMenuItems_MacOS (const char *menuLabel, int atIndex, const iMenuItem *items, size_t count); void removeMenu_MacOS (int atIndex); void enableMenu_MacOS (const char *menuLabel, iBool enable); diff --git a/src/macos.m b/src/macos.m index d588fa4a..298db0f8 100644 --- a/src/macos.m +++ b/src/macos.m @@ -30,6 +30,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "ui/window.h" #include +#include #import @@ -51,6 +52,16 @@ static iInt2 macVer_(void) { return init_I2(10, 10); } +static NSWindow *nsWindow_(SDL_Window *window) { + SDL_SysWMinfo wm; + SDL_VERSION(&wm.version); + if (SDL_GetWindowWMInfo(window, &wm)) { + return wm.info.cocoa.window; + } + iAssert(false); + return nil; +} + static NSString *currentSystemAppearance_(void) { /* This API does not exist on 10.13. */ if ([NSApp respondsToSelector:@selector(effectiveAppearance)]) { @@ -370,6 +381,11 @@ void setupApplication_MacOS(void) { windowCloseItem.action = @selector(closeTab); } +void hideTitleBar_MacOS(iWindow *window) { + NSWindow *w = nsWindow_(window->win); + w.styleMask = 0; /* borderless */ +} + void enableMenu_MacOS(const char *menuLabel, iBool enable) { menuLabel = translateCStr_Lang(menuLabel); NSApplication *app = [NSApplication sharedApplication]; @@ -377,7 +393,6 @@ void enableMenu_MacOS(const char *menuLabel, iBool enable) { NSString *label = [NSString stringWithUTF8String:menuLabel]; NSMenuItem *menuItem = [appMenu itemAtIndex:[appMenu indexOfItemWithTitle:label]]; [menuItem setEnabled:enable]; - [label release]; } void enableMenuItem_MacOS(const char *menuItemCommand, iBool enable) { diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index ed9e41d6..6f9824de 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -750,7 +750,7 @@ static uint32_t mediaUpdateInterval_DocumentWidget_(const iDocumentWidget *d) { if (document_App() != d) { return 0; } - if (get_Window()->isDrawFrozen) { + if (as_MainWindow(window_Widget(d))->isDrawFrozen) { return 0; } static const uint32_t invalidInterval_ = ~0u; diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c index a561d5bd..f02bf408 100644 --- a/src/ui/inputwidget.c +++ b/src/ui/inputwidget.c @@ -2352,7 +2352,7 @@ static void draw_InputWidget_(const iInputWidget *d) { } /* Draw the insertion point. */ if (isFocused && d->cursorVis && contains_Range(&visLines, d->cursor.y) && - isEmpty_Range(&d->mark)) { + (deviceType_App() == desktop_AppDeviceType || isEmpty_Range(&d->mark))) { iInt2 curSize; iRangecc cursorChar = iNullRange; int visWrapsAbove = 0; diff --git a/src/ui/root.c b/src/ui/root.c index 52a08eca..9e290b05 100644 --- a/src/ui/root.c +++ b/src/ui/root.c @@ -298,16 +298,6 @@ void destroyPending_Root(iRoot *d) { setCurrent_Root(oldRoot); } -void postArrange_Root(iRoot *d) { - if (!d->pendingArrange) { - d->pendingArrange = iTrue; - SDL_Event ev = { .type = SDL_USEREVENT }; - ev.user.code = arrange_UserEventCode; - ev.user.data2 = d; - SDL_PushEvent(&ev); - } -} - iPtrArray *onTop_Root(iRoot *d) { if (!d->onTop) { d->onTop = new_PtrArray(); diff --git a/src/ui/root.h b/src/ui/root.h index 740e97c9..04dd5e16 100644 --- a/src/ui/root.h +++ b/src/ui/root.h @@ -9,6 +9,7 @@ iDeclareType(Root) struct Impl_Root { iWidget * widget; + iWindow * window; iPtrArray *onTop; /* order is important; last one is topmost */ iPtrSet * pendingDestruction; iBool pendingArrange; @@ -29,7 +30,6 @@ iAnyObject *findWidget_Root (const char *id); /* under curre iPtrArray * onTop_Root (iRoot *); void destroyPending_Root (iRoot *); -void postArrange_Root (iRoot *); void updateMetrics_Root (iRoot *); void updatePadding_Root (iRoot *); /* TODO: is part of metrics? */ diff --git a/src/ui/text.c b/src/ui/text.c index f7fff4bc..bf71b0e9 100644 --- a/src/ui/text.c +++ b/src/ui/text.c @@ -290,7 +290,9 @@ struct Impl_Text { iRegExp * ansiEscape; }; -static iText text_; +iDefineTypeConstructionArgs(Text, (SDL_Renderer *render), render) + +static iText *activeText_; static iBlock *userFont_; static void initFonts_Text_(iText *d) { @@ -501,8 +503,7 @@ void loadUserFonts_Text(void) { } } -void init_Text(SDL_Renderer *render) { - iText *d = &text_; +void init_Text(iText *d, SDL_Renderer *render) { loadUserFonts_Text(); d->contentFont = nunito_TextFont; d->headingFont = nunito_TextFont; @@ -521,8 +522,7 @@ void init_Text(SDL_Renderer *render) { initFonts_Text_(d); } -void deinit_Text(void) { - iText *d = &text_; +void deinit_Text(iText *d) { SDL_FreePalette(d->grayscale); deinitFonts_Text_(d); deinitCache_Text_(d); @@ -530,30 +530,34 @@ void deinit_Text(void) { iRelease(d->ansiEscape); } +void setCurrent_Text(iText *d) { + activeText_ = d; +} + void setOpacity_Text(float opacity) { - SDL_SetTextureAlphaMod(text_.cache, iClamp(opacity, 0.0f, 1.0f) * 255 + 0.5f); + SDL_SetTextureAlphaMod(activeText_->cache, iClamp(opacity, 0.0f, 1.0f) * 255 + 0.5f); } -void setContentFont_Text(enum iTextFont font) { - if (text_.contentFont != font) { - text_.contentFont = font; - resetFonts_Text(); +void setContentFont_Text(iText *d, enum iTextFont font) { + if (d->contentFont != font) { + d->contentFont = font; + resetFonts_Text(d); } } -void setHeadingFont_Text(enum iTextFont font) { - if (text_.headingFont != font) { - text_.headingFont = font; - resetFonts_Text(); +void setHeadingFont_Text(iText *d, enum iTextFont font) { + if (d->headingFont != font) { + d->headingFont = font; + resetFonts_Text(d); } } -void setContentFontSize_Text(float fontSizeFactor) { +void setContentFontSize_Text(iText *d, float fontSizeFactor) { fontSizeFactor *= contentScale_Text_; iAssert(fontSizeFactor > 0); - if (iAbs(text_.contentFontSize - fontSizeFactor) > 0.001f) { - text_.contentFontSize = fontSizeFactor; - resetFonts_Text(); + if (iAbs(d->contentFontSize - fontSizeFactor) > 0.001f) { + d->contentFontSize = fontSizeFactor; + resetFonts_Text(d); } } @@ -565,8 +569,7 @@ static void resetCache_Text_(iText *d) { initCache_Text_(d); } -void resetFonts_Text(void) { - iText *d = &text_; +void resetFonts_Text(iText *d) { deinitFonts_Text_(d); deinitCache_Text_(d); initCache_Text_(d); @@ -574,7 +577,7 @@ void resetFonts_Text(void) { } iLocalDef iFont *font_Text_(enum iFontId id) { - return &text_.fonts[id & mask_FontId]; + return &activeText_->fonts[id & mask_FontId]; } static SDL_Surface *rasterizeGlyph_Font_(const iFont *d, uint32_t glyphIndex, float xShift) { @@ -584,7 +587,7 @@ static SDL_Surface *rasterizeGlyph_Font_(const iFont *d, uint32_t glyphIndex, fl SDL_Surface *surface8 = SDL_CreateRGBSurfaceWithFormatFrom(bmp, w, h, 8, w, SDL_PIXELFORMAT_INDEX8); SDL_SetSurfaceBlendMode(surface8, SDL_BLENDMODE_NONE); - SDL_SetSurfacePalette(surface8, text_.grayscale); + SDL_SetSurfacePalette(surface8, activeText_->grayscale); #if LAGRANGE_RASTER_DEPTH != 8 /* Convert to the cache format. */ SDL_Surface *surf = SDL_ConvertSurfaceFormat(surface8, LAGRANGE_RASTER_FORMAT, 0); @@ -631,7 +634,7 @@ static void allocate_Font_(iFont *d, iGlyph *glyph, int hoff) { &d->font, index_Glyph_(glyph), d->xScale, d->yScale, hoff * 0.5f, 0.0f, &x0, &y0, &x1, &y1); glRect->size = init_I2(x1 - x0, y1 - y0); /* Determine placement in the glyph cache texture, advancing in rows. */ - glRect->pos = assignCachePos_Text_(&text_, glRect->size); + glRect->pos = assignCachePos_Text_(activeText_, glRect->size); glyph->d[hoff] = init_I2(x0, y0); glyph->d[hoff].y += d->vertOffset; if (hoff == 0) { /* hoff==1 uses same metrics as `glyph` */ @@ -737,11 +740,11 @@ static iGlyph *glyphByIndex_Font_(iFont *d, uint32_t glyphIndex) { } else { /* If the cache is running out of space, clear it and we'll recache what's needed currently. */ - if (text_.cacheBottom > text_.cacheSize.y - maxGlyphHeight_Text_(&text_)) { + if (activeText_->cacheBottom > activeText_->cacheSize.y - maxGlyphHeight_Text_(activeText_)) { #if !defined (NDEBUG) printf("[Text] glyph cache is full, clearing!\n"); fflush(stdout); #endif - resetCache_Text_(&text_); + resetCache_Text_(activeText_); } glyph = new_Glyph(glyphIndex); glyph->font = d; @@ -858,7 +861,7 @@ static void finishRun_AttributedText_(iAttributedText *d, iAttributedRun *run, i } static enum iFontId fontId_Text_(const iFont *font) { - return (enum iFontId) (font - text_.fonts); + return (enum iFontId) (font - activeText_->fonts); } static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iChar overrideChar) { @@ -949,7 +952,7 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh /* Do a regexp match in the source text. */ iRegExpMatch m; init_RegExpMatch(&m); - if (match_RegExp(text_.ansiEscape, srcPos, d->source.end - srcPos, &m)) { + if (match_RegExp(activeText_->ansiEscape, srcPos, d->source.end - srcPos, &m)) { finishRun_AttributedText_(d, &run, pos - 1); run.fgColor = ansiForeground_Color(capturedRange_RegExpMatch(&m, 1), tmParagraph_ColorId); @@ -1082,9 +1085,9 @@ static void cacheGlyphs_Font_(iFont *d, const iArray *glyphIndices) { while (index < size_Array(glyphIndices)) { for (; index < size_Array(glyphIndices); index++) { const uint32_t glyphIndex = constValue_Array(glyphIndices, index, uint32_t); - const int lastCacheBottom = text_.cacheBottom; + const int lastCacheBottom = activeText_->cacheBottom; iGlyph *glyph = glyphByIndex_Font_(d, glyphIndex); - if (text_.cacheBottom < lastCacheBottom) { + if (activeText_->cacheBottom < lastCacheBottom) { /* The cache was reset due to running out of space. We need to restart from the beginning! */ bufX = 0; @@ -1103,7 +1106,7 @@ static void cacheGlyphs_Font_(iFont *d, const iArray *glyphIndices) { LAGRANGE_RASTER_DEPTH, LAGRANGE_RASTER_FORMAT); SDL_SetSurfaceBlendMode(buf, SDL_BLENDMODE_NONE); - SDL_SetSurfacePalette(buf, text_.grayscale); + SDL_SetSurfacePalette(buf, activeText_->grayscale); } SDL_Surface *surfaces[2] = { !isRasterized_Glyph_(glyph, 0) ? @@ -1147,19 +1150,19 @@ static void cacheGlyphs_Font_(iFont *d, const iArray *glyphIndices) { } /* Finished or the buffer is full, copy the glyphs to the cache texture. */ if (!isEmpty_Array(rasters)) { - SDL_Texture *bufTex = SDL_CreateTextureFromSurface(text_.render, buf); + SDL_Texture *bufTex = SDL_CreateTextureFromSurface(activeText_->render, buf); SDL_SetTextureBlendMode(bufTex, SDL_BLENDMODE_NONE); if (!isTargetChanged) { isTargetChanged = iTrue; - oldTarget = SDL_GetRenderTarget(text_.render); - SDL_SetRenderTarget(text_.render, text_.cache); + oldTarget = SDL_GetRenderTarget(activeText_->render); + SDL_SetRenderTarget(activeText_->render, activeText_->cache); } // printf("copying %zu rasters from %p\n", size_Array(rasters), bufTex); fflush(stdout); iConstForEach(Array, i, rasters) { const iRasterGlyph *rg = i.value; // iAssert(isEqual_I2(rg->rect.size, rg->glyph->rect[rg->hoff].size)); const iRect *glRect = &rg->glyph->rect[rg->hoff]; - SDL_RenderCopy(text_.render, + SDL_RenderCopy(activeText_->render, bufTex, (const SDL_Rect *) &rg->rect, (const SDL_Rect *) glRect); @@ -1179,7 +1182,7 @@ static void cacheGlyphs_Font_(iFont *d, const iArray *glyphIndices) { SDL_FreeSurface(buf); } if (isTargetChanged) { - SDL_SetRenderTarget(text_.render, oldTarget); + SDL_SetRenderTarget(activeText_->render, oldTarget); } } @@ -1706,9 +1709,9 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { } if (~mode & permanentColorFlag_RunMode) { const iColor clr = run->fgColor; - SDL_SetTextureColorMod(text_.cache, clr.r, clr.g, clr.b); + SDL_SetTextureColorMod(activeText_->cache, clr.r, clr.g, clr.b); if (args->mode & fillBackground_RunMode) { - SDL_SetRenderDrawColor(text_.render, clr.r, clr.g, clr.b, 0); + SDL_SetRenderDrawColor(activeText_->render, clr.r, clr.g, clr.b, 0); } } SDL_Rect src; @@ -1719,9 +1722,9 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { /* Alpha blending looks much better if the RGB components don't change in the partially transparent pixels. */ /* TODO: Backgrounds of all glyphs should be cleared before drawing anything else. */ - SDL_RenderFillRect(text_.render, &dst); + SDL_RenderFillRect(activeText_->render, &dst); } - SDL_RenderCopy(text_.render, text_.cache, &src, &dst); + SDL_RenderCopy(activeText_->render, activeText_->cache, &src, &dst); #if 0 /* Show spaces and direction. */ if (logicalText[logPos] == 0x20) { @@ -1863,7 +1866,7 @@ iTextMetrics measureN_Text(int fontId, const char *text, size_t n) { } static void drawBoundedN_Text_(int fontId, iInt2 pos, int xposBound, int color, iRangecc text, size_t maxLen) { - iText * d = &text_; + iText * d = activeText_; iFont * font = font_Text_(fontId); const iColor clr = get_Color(color & mask_ColorId); SDL_SetTextureColorMod(d->cache, clr.r, clr.g, clr.b); @@ -2057,7 +2060,7 @@ iTextMetrics draw_WrapText(iWrapText *d, int fontId, iInt2 pos, int color) { } SDL_Texture *glyphCache_Text(void) { - return text_.cache; + return activeText_->cache; } static void freeBitmap_(void *ptr) { @@ -2170,7 +2173,7 @@ iString *renderBlockChars_Text(const iBlock *fontData, int height, enum iTextBlo iDefineTypeConstructionArgs(TextBuf, (iWrapText *wrapText, int font, int color), wrapText, font, color) void init_TextBuf(iTextBuf *d, iWrapText *wrapText, int font, int color) { - SDL_Renderer *render = text_.render; + SDL_Renderer *render = activeText_->render; d->size = measure_WrapText(wrapText, font).bounds.size; SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); if (d->size.x * d->size.y) { @@ -2191,9 +2194,9 @@ void init_TextBuf(iTextBuf *d, iWrapText *wrapText, int font, int color) { SDL_SetRenderDrawBlendMode(render, SDL_BLENDMODE_NONE); SDL_SetRenderDrawColor(render, 255, 255, 255, 0); SDL_RenderClear(render); - SDL_SetTextureBlendMode(text_.cache, SDL_BLENDMODE_NONE); /* blended when TextBuf is drawn */ + SDL_SetTextureBlendMode(activeText_->cache, SDL_BLENDMODE_NONE); /* blended when TextBuf is drawn */ draw_WrapText(wrapText, font, zero_I2(), color | fillBackground_ColorId); - SDL_SetTextureBlendMode(text_.cache, SDL_BLENDMODE_BLEND); + SDL_SetTextureBlendMode(activeText_->cache, SDL_BLENDMODE_BLEND); SDL_SetRenderTarget(render, oldTarget); origin_Paint = oldOrigin; SDL_SetTextureBlendMode(d->texture, SDL_BLENDMODE_BLEND); @@ -2212,7 +2215,7 @@ void draw_TextBuf(const iTextBuf *d, iInt2 pos, int color) { addv_I2(&pos, origin_Paint); const iColor clr = get_Color(color); SDL_SetTextureColorMod(d->texture, clr.r, clr.g, clr.b); - SDL_RenderCopy(text_.render, + SDL_RenderCopy(activeText_->render, d->texture, &(SDL_Rect){ 0, 0, d->size.x, d->size.y }, &(SDL_Rect){ pos.x, pos.y, d->size.x, d->size.y }); diff --git a/src/ui/text.h b/src/ui/text.h index ac6cc1c1..1da43818 100644 --- a/src/ui/text.h +++ b/src/ui/text.h @@ -139,15 +139,20 @@ enum iTextFont { extern int gap_Text; /* affected by content font size */ -void init_Text (SDL_Renderer *); -void deinit_Text (void); +iDeclareType(Text) +iDeclareTypeConstructionArgs(Text, SDL_Renderer *) + +void init_Text (iText *, SDL_Renderer *); +void deinit_Text (iText *); + +void setCurrent_Text (iText *); void loadUserFonts_Text (void); /* based on Prefs */ -void setContentFont_Text (enum iTextFont font); -void setHeadingFont_Text (enum iTextFont font); -void setContentFontSize_Text (float fontSizeFactor); /* affects all except `default*` fonts */ -void resetFonts_Text (void); +void setContentFont_Text (iText *, enum iTextFont font); +void setHeadingFont_Text (iText *, enum iTextFont font); +void setContentFontSize_Text (iText *, float fontSizeFactor); /* affects all except `default*` fonts */ +void resetFonts_Text (iText *); int lineHeight_Text (int fontId); iRect visualBounds_Text (int fontId, iRangecc text); diff --git a/src/ui/text_simple.c b/src/ui/text_simple.c index bf33b4be..8b1de64a 100644 --- a/src/ui/text_simple.c +++ b/src/ui/text_simple.c @@ -92,7 +92,7 @@ static iRect runSimple_Font_(iFont *d, const iRunArgs *args) { } if (args->mode & fillBackground_RunMode) { const iColor initial = get_Color(args->color); - SDL_SetRenderDrawColor(text_.render, initial.r, initial.g, initial.b, 0); + SDL_SetRenderDrawColor(activeText_->render, initial.r, initial.g, initial.b, 0); } /* Text rendering is not very straightforward! Let's dive in... */ iChar prevCh = 0; @@ -114,14 +114,14 @@ static iRect runSimple_Font_(iFont *d, const iRunArgs *args) { chPos++; iRegExpMatch m; init_RegExpMatch(&m); - if (match_RegExp(text_.ansiEscape, chPos, args->text.end - chPos, &m)) { + if (match_RegExp(activeText_->ansiEscape, chPos, args->text.end - chPos, &m)) { if (mode & draw_RunMode && ~mode & permanentColorFlag_RunMode) { /* Change the color. */ const iColor clr = ansiForeground_Color(capturedRange_RegExpMatch(&m, 1), tmParagraph_ColorId); - SDL_SetTextureColorMod(text_.cache, clr.r, clr.g, clr.b); + SDL_SetTextureColorMod(activeText_->cache, clr.r, clr.g, clr.b); if (args->mode & fillBackground_RunMode) { - SDL_SetRenderDrawColor(text_.render, clr.r, clr.g, clr.b, 0); + SDL_SetRenderDrawColor(activeText_->render, clr.r, clr.g, clr.b, 0); } } chPos = end_RegExpMatch(&m); @@ -205,9 +205,9 @@ static iRect runSimple_Font_(iFont *d, const iRunArgs *args) { } if (mode & draw_RunMode && ~mode & permanentColorFlag_RunMode) { const iColor clr = get_Color(colorNum); - SDL_SetTextureColorMod(text_.cache, clr.r, clr.g, clr.b); + SDL_SetTextureColorMod(activeText_->cache, clr.r, clr.g, clr.b); if (args->mode & fillBackground_RunMode) { - SDL_SetRenderDrawColor(text_.render, clr.r, clr.g, clr.b, 0); + SDL_SetRenderDrawColor(activeText_->render, clr.r, clr.g, clr.b, 0); } } prevCh = 0; @@ -311,9 +311,9 @@ static iRect runSimple_Font_(iFont *d, const iRunArgs *args) { if (args->mode & fillBackground_RunMode) { /* Alpha blending looks much better if the RGB components don't change in the partially transparent pixels. */ - SDL_RenderFillRect(text_.render, &dst); + SDL_RenderFillRect(activeText_->render, &dst); } - SDL_RenderCopy(text_.render, text_.cache, &src, &dst); + SDL_RenderCopy(activeText_->render, activeText_->cache, &src, &dst); } xpos += advance; if (!isSpace_Char(ch)) { diff --git a/src/ui/util.c b/src/ui/util.c index 721aed2d..38977b96 100644 --- a/src/ui/util.c +++ b/src/ui/util.c @@ -613,6 +613,8 @@ iBool isAction_Widget(const iWidget *d) { /*-----------------------------------------------------------------------------------------------*/ static iBool isCommandIgnoredByMenus_(const char *cmd) { + if (equal_Command(cmd, "window.focus.lost") || + equal_Command(cmd, "window.focus.gained")) return iTrue; /* TODO: Perhaps a common way of indicating which commands are notifications and should not be reacted to by menus? */ return equal_Command(cmd, "media.updated") || @@ -810,6 +812,10 @@ static void updateMenuItemFonts_Widget_(iWidget *d) { } } +iLocalDef iBool isUsingMenuPopupWindows_(void) { + return deviceType_App() == desktop_AppDeviceType; +} + void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) { const iRect rootRect = rect_Root(d->root); const iInt2 rootSize = rootRect.size; @@ -822,6 +828,26 @@ void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) { processEvents_App(postedEventsOnly_AppEventMode); setFlags_Widget(d, hidden_WidgetFlag, iFalse); setFlags_Widget(d, commandOnMouseMiss_WidgetFlag, iTrue); + if (isUsingMenuPopupWindows_()) { + if (postCommands) { + postCommand_Widget(d, "menu.opened"); + } + updateMenuItemFonts_Widget_(d); + iRoot *oldRoot = current_Root(); + setFlags_Widget(d, keepOnTop_WidgetFlag, iFalse); + setUserData_Object(d, parent_Widget(d)); + removeChild_Widget(parent_Widget(d), d); /* we'll borrow the widget for a while */ + iInt2 mousePos; + SDL_GetGlobalMouseState(&mousePos.x, &mousePos.y); + iWindow *win = newPopup_Window(sub_I2(mousePos, divi_I2(gap2_UI, 2)), d); + SDL_SetWindowTitle(win->win, "Menu"); + addPopup_App(win); /* window takes the widget */ + SDL_ShowWindow(win->win); + draw_Window(win); + setCurrent_Window(mainWindow_App()); + setCurrent_Root(oldRoot); + return; + } raise_Widget(d); setFlags_Widget(findChild_Widget(d, "menu.cancel"), disabled_WidgetFlag, iFalse); if (isPortraitPhone) { @@ -836,7 +862,7 @@ void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) { arrange_Widget(d); if (isPortraitPhone) { if (isSlidePanel) { - d->rect.pos = zero_I2(); //neg_I2(bounds_Widget(parent_Widget(d)).pos); + d->rect.pos = zero_I2(); } else { d->rect.pos = init_I2(0, rootSize.y); @@ -856,7 +882,7 @@ void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) { float l, t, r, b; safeAreaInsets_iOS(&l, &t, &r, &b); topExcess += t; - bottomExcess += iMax(b, get_Window()->keyboardHeight); + bottomExcess += iMax(b, get_MainWindow()->keyboardHeight); leftExcess += l; rightExcess += r; } @@ -884,6 +910,18 @@ void closeMenu_Widget(iWidget *d) { if (d == NULL || flags_Widget(d) & hidden_WidgetFlag) { return; /* Already closed. */ } + if (isUsingMenuPopupWindows_()) { + iWindow *win = window_Widget(d); + iAssert(type_Window(win) == popup_WindowType); + iWidget *originalParent = userData_Object(d); + setUserData_Object(d, NULL); + win->roots[0]->widget = NULL; + setRoot_Widget(d, originalParent->root); + addChild_Widget(originalParent, d); + setFlags_Widget(d, keepOnTop_WidgetFlag, iTrue); + SDL_HideWindow(win->win); + collect_Garbage(win, (iDeleteFunc) delete_Window); /* get rid of it after event processing */ + } setFlags_Widget(d, hidden_WidgetFlag, iTrue); setFlags_Widget(findChild_Widget(d, "menu.cancel"), disabled_WidgetFlag, iTrue); postRefresh_App(); diff --git a/src/ui/widget.c b/src/ui/widget.c index 23c19315..7b33a752 100644 --- a/src/ui/widget.c +++ b/src/ui/widget.c @@ -271,6 +271,10 @@ iWidget *root_Widget(const iWidget *d) { return d ? d->root->widget : NULL; } +iWindow *window_Widget(const iAnyObject *d) { + return constAs_Widget(d)->root->window; +} + void showCollapsed_Widget(iWidget *d, iBool show) { const iBool isVisible = !(d->flags & hidden_WidgetFlag); if ((isVisible && !show) || (!isVisible && show)) { @@ -979,11 +983,10 @@ void unhover_Widget(void) { } iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) { - //iAssert(d->root == get_Root()); if (!d->parent) { - if (get_Window()->focus && get_Window()->focus->root == d->root && isKeyboardEvent_(ev)) { + if (window_Widget(d)->focus && window_Widget(d)->focus->root == d->root && isKeyboardEvent_(ev)) { /* Root dispatches keyboard events directly to the focused widget. */ - if (dispatchEvent_Widget(get_Window()->focus, ev)) { + if (dispatchEvent_Widget(window_Widget(d)->focus, ev)) { return iTrue; } } @@ -1012,7 +1015,8 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) { } } else if (ev->type == SDL_MOUSEMOTION && - (!get_Window()->hover || hasParent_Widget(d, get_Window()->hover)) && + ev->motion.windowID == SDL_GetWindowID(window_Widget(d)->win) && + (!window_Widget(d)->hover || hasParent_Widget(d, window_Widget(d)->hover)) && flags_Widget(d) & hover_WidgetFlag && ~flags_Widget(d) & hidden_WidgetFlag && ~flags_Widget(d) & disabled_WidgetFlag) { if (contains_Widget(d, init_I2(ev->motion.x, ev->motion.y))) { @@ -1031,11 +1035,11 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) { iReverseForEach(ObjectList, i, d->children) { iWidget *child = as_Widget(i.object); //iAssert(child->root == d->root); - if (child == get_Window()->focus && isKeyboardEvent_(ev)) { + if (child == window_Widget(d)->focus && isKeyboardEvent_(ev)) { continue; /* Already dispatched. */ } if (isVisible_Widget(child) && child->flags & keepOnTop_WidgetFlag) { - /* Already dispatched. */ + /* Already dispatched. */ continue; } if (dispatchEvent_Widget(child, ev)) { @@ -1050,7 +1054,7 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) { #endif #if 0 if (ev->type == SDL_MOUSEMOTION) { - printf("[%p] %s:'%s' (on top) ate the motion\n", + printf("[%p] %s:'%s' ate the motion\n", child, class_Widget(child)->name, cstr_String(id_Widget(child))); fflush(stdout); @@ -1246,7 +1250,7 @@ iBool processEvent_Widget(iWidget *d, const SDL_Event *ev) { ev->button.x, ev->button.y); } - setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_ARROW); + setCursor_Window(window_Widget(d), SDL_SYSTEM_CURSOR_ARROW); return iTrue; } return iFalse; @@ -1270,6 +1274,7 @@ iLocalDef iBool isDrawn_Widget_(const iWidget *d) { void drawLayerEffects_Widget(const iWidget *d) { /* Layered effects are not buffered, so they are drawn here separately. */ iAssert(isDrawn_Widget_(d)); + iAssert(window_Widget(d) == get_Window()); iBool shadowBorder = (d->flags & keepOnTop_WidgetFlag && ~d->flags & mouseModal_WidgetFlag) != 0; iBool fadeBackground = (d->bgColor >= 0 || d->frameColor >= 0) && d->flags & mouseModal_WidgetFlag; if (deviceType_App() == phone_AppDeviceType) { @@ -1539,6 +1544,7 @@ static void endBufferDraw_Widget_(const iWidget *d) { } void draw_Widget(const iWidget *d) { + iAssert(window_Widget(d) == get_Window()); if (!isDrawn_Widget_(d)) { if (d->drawBuf) { // printf("[%p] drawBuffer released\n", d); @@ -1820,7 +1826,17 @@ iBool equalWidget_Command(const char *cmd, const iWidget *widget, const char *ch if (equal_Command(cmd, checkCommand)) { const iWidget *src = pointer_Command(cmd); iAssert(!src || strstr(cmd, " ptr:")); - return src == widget || hasParent_Widget(src, widget); + if (src == widget || hasParent_Widget(src, widget)) { + return iTrue; + } +// if (src && type_Window(window_Widget(src)) == popup_WindowType) { +// /* Special case: command was emitted from a popup widget. The popup root widget actually +// belongs to someone else. */ +// iWidget *realParent = userData_Object(src->root->widget); +// iAssert(realParent); +// iAssert(isInstance_Object(realParent, &Class_Widget)); +// return realParent == widget || hasParent_Widget(realParent, widget); +// } } return iFalse; } @@ -1962,6 +1978,10 @@ void postCommand_Widget(const iAnyObject *d, const char *cmd, ...) { } if (!isGlobal) { iAssert(isInstance_Object(d, &Class_Widget)); + if (type_Window(window_Widget(d)) == popup_WindowType) { + postCommandf_Root(((const iWidget *) d)->root, "cancel popup:1 ptr:%p", d); + d = userData_Object(root_Widget(d)); + } appendFormat_String(&str, " ptr:%p", d); } postCommandString_Root(((const iWidget *) d)->root, &str); diff --git a/src/ui/widget.h b/src/ui/widget.h index 7491cb79..0eab69c1 100644 --- a/src/ui/widget.h +++ b/src/ui/widget.h @@ -34,7 +34,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include -iDeclareType(Root) /* each widget is associated with a Root */ +iDeclareType(Root) /* each widget is associated with a Root */ +iDeclareType(Window) /* each Root is inside a Window */ #define iDeclareWidgetClass(className) \ iDeclareType(className); \ @@ -185,6 +186,7 @@ void releaseChildren_Widget (iWidget *); - inner: 0,0 is at the top left corner of the widget */ iWidget * root_Widget (const iWidget *); +iWindow * window_Widget (const iAnyObject *); const iString * id_Widget (const iWidget *); int64_t flags_Widget (const iWidget *); iRect bounds_Widget (const iWidget *); /* outer bounds */ diff --git a/src/ui/window.c b/src/ui/window.c index 92125d81..e9a34ace 100644 --- a/src/ui/window.c +++ b/src/ui/window.c @@ -57,7 +57,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "stb_image.h" #include "stb_image_resize.h" -static iWindow *theWindow_ = NULL; +static iWindow * theWindow_; +static iMainWindow *theMainWindow_; #if defined (iPlatformApple) || defined (iPlatformLinux) || defined (iPlatformOther) static float initialUiScale_ = 1.0f; @@ -67,6 +68,9 @@ static float initialUiScale_ = 1.1f; static iBool isOpenGLRenderer_; +iDefineTypeConstructionArgs(Window, + (enum iWindowType type, iRect rect, uint32_t flags), + type, rect, flags) iDefineTypeConstructionArgs(MainWindow, (iRect rect), rect) /* TODO: Define menus per platform. */ @@ -205,6 +209,7 @@ static void setupUserInterface_MainWindow(iMainWindow *d) { #endif /* One root is created by default. */ d->base.roots[0] = new_Root(); + d->base.roots[0]->window = as_Window(d); setCurrent_Root(d->base.roots[0]); createUserInterface_Root(d->base.roots[0]); setCurrent_Root(NULL); @@ -409,7 +414,6 @@ void init_Window(iWindow *d, enum iWindowType type, iRect rect, uint32_t flags) d->mouseGrab = NULL; d->focus = NULL; d->pendingCursor = NULL; - d->isDrawFrozen = iTrue; d->isExposed = iFalse; d->isMinimized = iFalse; d->isInvalidated = iFalse; /* set when posting event, to avoid repeated events */ @@ -441,9 +445,27 @@ void init_Window(iWindow *d, enum iWindowType type, iRect rect, uint32_t flags) d->uiScale = initialUiScale_; /* TODO: Ratios, scales, and metrics must be window-specific, not global. */ setScale_Metrics(d->pixelRatio * d->displayScale * d->uiScale); + d->text = new_Text(d->render); +} + +static void deinitRoots_Window_(iWindow *d) { + iRecycle(); + iForIndices(i, d->roots) { + if (d->roots[i]) { + setCurrent_Root(d->roots[i]); + delete_Root(d->roots[i]); + d->roots[i] = NULL; + } + } + setCurrent_Root(NULL); } void deinit_Window(iWindow *d) { + if (d->type == popup_WindowType) { + removePopup_App(d); + } + deinitRoots_Window_(d); + delete_Text(d->text); SDL_DestroyRenderer(d->render); SDL_DestroyWindow(d->win); iForIndices(i, d->cursors) { @@ -455,6 +477,7 @@ void deinit_Window(iWindow *d) { void init_MainWindow(iMainWindow *d, iRect rect) { theWindow_ = &d->base; + theMainWindow_ = d; uint32_t flags = 0; #if defined (iPlatformAppleDesktop) SDL_SetHint(SDL_HINT_RENDER_DRIVER, shouldDefaultToMetalRenderer_MacOS() ? "metal" : "opengl"); @@ -465,13 +488,15 @@ void init_MainWindow(iMainWindow *d, iRect rect) { #endif SDL_SetHint(SDL_HINT_RENDER_VSYNC, "1"); init_Window(&d->base, main_WindowType, rect, flags); - d->splitMode = d->pendingSplitMode = 0; - d->pendingSplitUrl = new_String(); - d->place.initialPos = rect.pos; - d->place.normalRect = rect; + d->isDrawFrozen = iTrue; + d->splitMode = 0; + d->pendingSplitMode = 0; + d->pendingSplitUrl = new_String(); + d->place.initialPos = rect.pos; + d->place.normalRect = rect; d->place.lastNotifiedSize = zero_I2(); - d->place.snap = 0; - d->keyboardHeight = 0; + d->place.snap = 0; + d->keyboardHeight = 0; #if defined(iPlatformMobile) const iInt2 minSize = zero_I2(); /* windows aren't independently resizable */ #else @@ -510,9 +535,9 @@ void init_MainWindow(iMainWindow *d, iRect rect) { } #endif #if defined (iPlatformAppleMobile) - setupWindow_iOS(d); + setupWindow_iOS(as_Window(d)); #endif - init_Text(d->base.render); + setCurrent_Text(d->base.text); SDL_GetRendererOutputSize(d->base.render, &d->base.size.x, &d->base.size.y); setupUserInterface_MainWindow(d); postCommand_App("~bindings.changed"); /* update from bindings */ @@ -538,24 +563,15 @@ void init_MainWindow(iMainWindow *d, iRect rect) { #endif } -static void deinitRoots_Window_(iWindow *d) { - iRecycle(); - iForIndices(i, d->roots) { - if (d->roots[i]) { - setCurrent_Root(d->roots[i]); - deinit_Root(d->roots[i]); - } - } - setCurrent_Root(NULL); -} - void deinit_MainWindow(iMainWindow *d) { deinitRoots_Window_(as_Window(d)); if (theWindow_ == as_Window(d)) { theWindow_ = NULL; } + if (theMainWindow_ == d) { + theMainWindow_ = NULL; + } delete_String(d->pendingSplitUrl); - deinit_Text(); deinit_Window(&d->base); } @@ -592,7 +608,7 @@ iRoot *otherRoot_Window(const iWindow *d, iRoot *root) { static void invalidate_MainWindow_(iMainWindow *d, iBool forced) { if (d && (!d->base.isInvalidated || forced)) { d->base.isInvalidated = iTrue; - resetFonts_Text(); + resetFonts_Text(text_Window(d)); postCommand_App("theme.changed auto:1"); /* forces UI invalidation */ } } @@ -607,7 +623,7 @@ void invalidate_Window(iAnyWindow *d) { } static iBool isNormalPlacement_MainWindow_(const iMainWindow *d) { - if (d->base.isDrawFrozen) return iFalse; + if (d->isDrawFrozen) return iFalse; #if defined (iPlatformApple) /* Maximized mode is not special on macOS. */ if (snap_MainWindow(d) == maximized_WindowSnap) { @@ -655,7 +671,7 @@ static iBool unsnap_MainWindow_(iMainWindow *d, const iInt2 *newPos) { static void notifyMetricsChange_Window_(const iWindow *d) { /* Dynamic UI metrics change. Widgets need to update themselves. */ setScale_Metrics(d->pixelRatio * d->displayScale * d->uiScale); - resetFonts_Text(); + resetFonts_Text(d->text); postCommand_App("metrics.changed"); } @@ -676,6 +692,41 @@ static void checkPixelRatioChange_Window_(iWindow *d) { } } +static iBool handleWindowEvent_Window_(iWindow *d, const SDL_WindowEvent *ev) { + if (ev->windowID != SDL_GetWindowID(d->win)) { + return iFalse; + } + switch (ev->event) { + case SDL_WINDOWEVENT_EXPOSED: + d->isExposed = iTrue; + postRefresh_App(); + return iTrue; + case SDL_WINDOWEVENT_RESTORED: + case SDL_WINDOWEVENT_SHOWN: + postRefresh_App(); + return iTrue; + case SDL_WINDOWEVENT_FOCUS_LOST: + /* Popup windows are currently only used for menus. */ + closeMenu_Widget(d->roots[0]->widget); + return iTrue; + case SDL_WINDOWEVENT_LEAVE: + unhover_Widget(); + d->isMouseInside = iFalse; + //postCommand_App("window.mouse.exited"); +// SDL_SetWindowInputFocus(mainWindow_App()->base.win); + printf("mouse leaves popup\n"); fflush(stdout); + //SDL_RaiseWindow(mainWindow_App()->base.win); + postRefresh_App(); + return iTrue; + case SDL_WINDOWEVENT_ENTER: + d->isMouseInside = iTrue; + //postCommand_App("window.mouse.entered"); + printf("mouse enters popup\n"); fflush(stdout); + return iTrue; + } + return iFalse; +} + static iBool handleWindowEvent_MainWindow_(iMainWindow *d, const SDL_WindowEvent *ev) { switch (ev->event) { #if defined(iPlatformDesktop) @@ -795,6 +846,7 @@ static iBool handleWindowEvent_MainWindow_(iMainWindow *d, const SDL_WindowEvent return iTrue; case SDL_WINDOWEVENT_ENTER: d->base.isMouseInside = iTrue; + SDL_SetWindowInputFocus(d->base.win); postCommand_App("window.mouse.entered"); return iTrue; case SDL_WINDOWEVENT_FOCUS_GAINED: @@ -802,16 +854,16 @@ static iBool handleWindowEvent_MainWindow_(iMainWindow *d, const SDL_WindowEvent setCapsLockDown_Keys(iFalse); postCommand_App("window.focus.gained"); d->base.isExposed = iTrue; -#if !defined(iPlatformDesktop) +#if !defined (iPlatformDesktop) /* Returned to foreground, may have lost buffered content. */ - invalidate_Window_(d, iTrue); + invalidate_MainWindow_(d, iTrue); postCommand_App("window.unfreeze"); #endif return iFalse; case SDL_WINDOWEVENT_FOCUS_LOST: postCommand_App("window.focus.lost"); -#if !defined(iPlatformDesktop) - setFreezeDraw_Window(d, iTrue); +#if !defined (iPlatformDesktop) + setFreezeDraw_MainWindow(d, iTrue); #endif return iFalse; case SDL_WINDOWEVENT_TAKE_FOCUS: @@ -831,8 +883,8 @@ static void applyCursor_Window_(iWindow *d) { } } -iBool processEvent_MainWindow(iMainWindow *d, const SDL_Event *ev) { - iWindow *w = as_Window(d); +iBool processEvent_Window(iWindow *d, const SDL_Event *ev) { + iMainWindow *mw = (type_Window(d) == main_WindowType ? as_MainWindow(d) : NULL); switch (ev->type) { #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) case SDL_SYSWMEVENT: { @@ -845,19 +897,26 @@ iBool processEvent_MainWindow(iMainWindow *d, const SDL_Event *ev) { } #endif case SDL_WINDOWEVENT: { - return handleWindowEvent_MainWindow_(d, &ev->window); + if (mw) { + return handleWindowEvent_MainWindow_(mw, &ev->window); + } + else { + return handleWindowEvent_Window_(d, &ev->window); + } } case SDL_RENDER_TARGETS_RESET: case SDL_RENDER_DEVICE_RESET: { - invalidate_MainWindow_(d, iTrue /* force full reset */); + if (mw) { + invalidate_MainWindow_(mw, iTrue /* force full reset */); + } break; } default: { SDL_Event event = *ev; if (event.type == SDL_USEREVENT && isCommand_UserEvent(ev, "window.unfreeze")) { - d->base.isDrawFrozen = iFalse; - if (SDL_GetWindowFlags(w->win) & SDL_WINDOW_HIDDEN) { - SDL_ShowWindow(w->win); + mw->isDrawFrozen = iFalse; + if (SDL_GetWindowFlags(d->win) & SDL_WINDOW_HIDDEN) { + SDL_ShowWindow(d->win); } postRefresh_App(); postCommand_App("media.player.update"); /* in case a player needs updating */ @@ -866,35 +925,35 @@ iBool processEvent_MainWindow(iMainWindow *d, const SDL_Event *ev) { if (processEvent_Touch(&event)) { return iTrue; } - if (event.type == SDL_KEYDOWN && SDL_GetTicks() - d->base.focusGainedAt < 10) { + if (event.type == SDL_KEYDOWN && SDL_GetTicks() - d->focusGainedAt < 10) { /* Suspiciously close to when input focus was received. For example under openbox, closing xterm with Ctrl+D will cause the keydown event to "spill" over to us. As a workaround, ignore these events. */ return iTrue; /* won't go to bindings, either */ } - if (event.type == SDL_MOUSEBUTTONDOWN && d->base.ignoreClick) { - d->base.ignoreClick = iFalse; + if (event.type == SDL_MOUSEBUTTONDOWN && d->ignoreClick) { + d->ignoreClick = iFalse; return iTrue; } /* Map mouse pointer coordinate to our coordinate system. */ if (event.type == SDL_MOUSEMOTION) { - setCursor_Window(w, SDL_SYSTEM_CURSOR_ARROW); /* default cursor */ - const iInt2 pos = coord_Window(w, event.motion.x, event.motion.y); + setCursor_Window(d, SDL_SYSTEM_CURSOR_ARROW); /* default cursor */ + const iInt2 pos = coord_Window(d, event.motion.x, event.motion.y); event.motion.x = pos.x; event.motion.y = pos.y; } else if (event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEBUTTONDOWN) { - const iInt2 pos = coord_Window(w, event.button.x, event.button.y); + const iInt2 pos = coord_Window(d, event.button.x, event.button.y); event.button.x = pos.x; event.button.y = pos.y; if (event.type == SDL_MOUSEBUTTONDOWN) { /* Button clicks will change keyroot. */ - if (numRoots_Window(w) > 1) { + if (numRoots_Window(d) > 1) { const iInt2 click = init_I2(event.button.x, event.button.y); - iForIndices(i, w->roots) { - iRoot *root = w->roots[i]; - if (root != w->keyRoot && contains_Rect(rect_Root(root), click)) { - setKeyRoot_Window(w, root); + iForIndices(i, d->roots) { + iRoot *root = d->roots[i]; + if (root != d->keyRoot && contains_Rect(rect_Root(root), click)) { + setKeyRoot_Window(d, root); break; } } @@ -909,13 +968,13 @@ iBool processEvent_MainWindow(iMainWindow *d, const SDL_Event *ev) { event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEBUTTONDOWN) { if (mouseGrab_Widget()) { iWidget *grabbed = mouseGrab_Widget(); - setCurrent_Root(findRoot_Window(w, grabbed)); + setCurrent_Root(findRoot_Window(d, grabbed)); wasUsed = dispatchEvent_Widget(grabbed, &event); } } /* Dispatch the event to the tree of widgets. */ if (!wasUsed) { - wasUsed = dispatchEvent_Window(w, &event); + wasUsed = dispatchEvent_Window(d, &event); } if (!wasUsed) { /* As a special case, clicking the middle mouse button can be used for pasting @@ -928,35 +987,35 @@ iBool processEvent_MainWindow(iMainWindow *d, const SDL_Event *ev) { paste.key.keysym.mod = KMOD_PRIMARY; paste.key.state = SDL_PRESSED; paste.key.timestamp = SDL_GetTicks(); - wasUsed = dispatchEvent_Window(w, &paste); + wasUsed = dispatchEvent_Window(d, &paste); } if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_RIGHT) { - if (postContextClick_Window(w, &event.button)) { + if (postContextClick_Window(d, &event.button)) { wasUsed = iTrue; } } } if (isMetricsChange_UserEvent(&event)) { - iForIndices(i, w->roots) { - updateMetrics_Root(w->roots[i]); + iForIndices(i, d->roots) { + updateMetrics_Root(d->roots[i]); } } - if (isCommand_UserEvent(&event, "lang.changed")) { + if (isCommand_UserEvent(&event, "lang.changed") && mw) { #if defined (iHaveNativeMenus) /* Retranslate the menus. */ removeMacMenus_(); insertMacMenus_(); #endif - invalidate_Window(w); - iForIndices(i, w->roots) { - if (w->roots[i]) { - updatePreferencesLayout_Widget(findChild_Widget(w->roots[i]->widget, "prefs")); - arrange_Widget(w->roots[i]->widget); + invalidate_Window(d); + iForIndices(i, d->roots) { + if (d->roots[i]) { + updatePreferencesLayout_Widget(findChild_Widget(d->roots[i]->widget, "prefs")); + arrange_Widget(d->roots[i]->widget); } } } if (event.type == SDL_MOUSEMOTION) { - applyCursor_Window_(w); + applyCursor_Window_(d); } return wasUsed; } @@ -1003,6 +1062,9 @@ iBool dispatchEvent_Window(iWindow *d, const SDL_Event *ev) { coord_MouseWheelEvent(&ev->wheel))) { continue; /* Only process the event in the relevant split. */ } + if (!root->widget) { + continue; + } setCurrent_Root(root); const iBool wasUsed = dispatchEvent_Widget(root->widget, ev); if (wasUsed) { @@ -1044,11 +1106,40 @@ iBool postContextClick_Window(iWindow *d, const SDL_MouseButtonEvent *ev) { return iFalse; } +void draw_Window(iWindow *d) { + if (SDL_GetWindowFlags(d->win) & SDL_WINDOW_HIDDEN) { + return; + } + iPaint p; + init_Paint(&p); + iRoot *root = d->roots[0]; + setCurrent_Root(root); + unsetClip_Paint(&p); /* update clip to full window */ + const iColor back = get_Color(uiBackground_ColorId); + SDL_SetRenderDrawColor(d->render, back.r, back.g, back.b, 255); + SDL_RenderClear(d->render); + d->frameTime = SDL_GetTicks(); + if (isExposed_Window(d)) { + d->isInvalidated = iFalse; + extern int drawCount_; + drawRoot_Widget(root->widget); +#if !defined (NDEBUG) + draw_Text(defaultBold_FontId, safeRect_Root(root).pos, red_ColorId, "%d", drawCount_); + drawCount_ = 0; +#endif + } +// drawRectThickness_Paint(&p, (iRect){ zero_I2(), sub_I2(d->size, one_I2()) }, gap_UI / 4, uiSeparator_ColorId); + setCurrent_Root(NULL); + SDL_RenderPresent(d->render); +} + void draw_MainWindow(iMainWindow *d) { + /* TODO: Try to make this a specialization of `draw_Window`? */ iWindow *w = as_Window(d); - if (w->isDrawFrozen) { + if (d->isDrawFrozen) { return; } + setCurrent_Text(d->base.text); /* Check if root needs resizing. */ { iInt2 renderSize; SDL_GetRendererOutputSize(w->render, &renderSize.x, &renderSize.y); @@ -1180,7 +1271,7 @@ void setUiScale_Window(iWindow *d, float uiScale) { } } -void setFreezeDraw_Window(iWindow *d, iBool freezeDraw) { +void setFreezeDraw_MainWindow(iMainWindow *d, iBool freezeDraw) { d->isDrawFrozen = freezeDraw; } @@ -1231,8 +1322,23 @@ iWindow *get_Window(void) { return theWindow_; } +void setCurrent_Window(iAnyWindow *d) { + theWindow_ = d; + if (type_Window(d) == main_WindowType) { + theMainWindow_ = d; + } + if (d) { + setCurrent_Text(theWindow_->text); + setCurrent_Root(theWindow_->keyRoot); + } + else { + setCurrent_Text(NULL); + setCurrent_Root(NULL); + } +} + iMainWindow *get_MainWindow(void) { - return as_MainWindow(theWindow_); + return theMainWindow_; } iBool isOpenGLRenderer_Window(void) { @@ -1272,7 +1378,7 @@ void setSplitMode_MainWindow(iMainWindow *d, int splitFlags) { iAssert(current_Root() == NULL); if (d->splitMode != splitMode) { int oldCount = numRoots_Window(w); - setFreezeDraw_Window(w, iTrue); + setFreezeDraw_MainWindow(d, iTrue); if (oldCount == 2 && splitMode == 0) { /* Keep references to the tabs of the second root. */ const iDocumentWidget *curPage = document_Root(w->keyRoot); @@ -1311,6 +1417,7 @@ void setSplitMode_MainWindow(iMainWindow *d, int splitFlags) { } w->roots[newRootIndex] = new_Root(); w->keyRoot = w->roots[newRootIndex]; + w->keyRoot->window = w; setCurrent_Root(w->roots[newRootIndex]); createUserInterface_Root(w->roots[newRootIndex]); if (!isEmpty_String(d->pendingSplitUrl)) { @@ -1471,3 +1578,25 @@ int snap_MainWindow(const iMainWindow *d) { } return d->place.snap; } + +/*----------------------------------------------------------------------------------------------*/ + +iWindow *newPopup_Window(iInt2 screenPos, iWidget *rootWidget) { + arrange_Widget(rootWidget); + iWindow *win = + new_Window(popup_WindowType, + (iRect){ screenPos, divf_I2(rootWidget->rect.size, get_Window()->pixelRatio) }, + SDL_WINDOW_ALWAYS_ON_TOP | + SDL_WINDOW_POPUP_MENU | + SDL_WINDOW_SKIP_TASKBAR); +#if defined (iPlatformAppleDesktop) + hideTitleBar_MacOS(win); /* make it a borderless window */ +#endif + iRoot *root = new_Root(); + win->roots[0] = root; + win->keyRoot = root; + root->widget = rootWidget; + root->window = win; + setRoot_Widget(rootWidget, root); + return win; +} diff --git a/src/ui/window.h b/src/ui/window.h index 73e92391..f1827931 100644 --- a/src/ui/window.h +++ b/src/ui/window.h @@ -29,8 +29,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include +enum iWindowType { + main_WindowType, + popup_WindowType, +}; + iDeclareType(MainWindow) +iDeclareType(Text) iDeclareType(Window) + +iDeclareTypeConstructionArgs(Window, enum iWindowType type, iRect rect, uint32_t flags) iDeclareTypeConstructionArgs(MainWindow, iRect rect) typedef iAny iAnyWindow; @@ -71,15 +79,9 @@ enum iWindowSplit { noEvents_WindowSplit = iBit(11), }; -enum iWindowType { - main_WindowType, - popup_WindowType, -}; - struct Impl_Window { enum iWindowType type; SDL_Window * win; - iBool isDrawFrozen; /* avoids premature draws while restoring window state */ iBool isExposed; iBool isMinimized; iBool isMouseInside; @@ -102,11 +104,13 @@ struct Impl_Window { iRoot * roots[2]; /* root widget and UI state; second one is for split mode */ iRoot * keyRoot; /* root that has the current keyboard input focus */ SDL_Texture * borderShadow; + iText * text; }; struct Impl_MainWindow { iWindow base; iWindowPlacement place; + iBool isDrawFrozen; /* avoids premature draws while restoring window state */ int splitMode; int pendingSplitMode; iString * pendingSplitUrl; /* URL to open in a newly opened split */ @@ -115,7 +119,10 @@ struct Impl_MainWindow { }; iLocalDef enum iWindowType type_Window(const iAnyWindow *d) { - return ((const iWindow *) d)->type; + if (d) { + return ((const iWindow *) d)->type; + } + return main_WindowType; } uint32_t id_Window (const iWindow *); @@ -131,11 +138,11 @@ int numRoots_Window (const iWindow *); iRoot * findRoot_Window (const iWindow *, const iWidget *widget); iRoot * otherRoot_Window (const iWindow *, iRoot *root); +iBool processEvent_Window (iWindow *, const SDL_Event *); iBool dispatchEvent_Window (iWindow *, const SDL_Event *); void invalidate_Window (iAnyWindow *); /* discard all cached graphics */ void draw_Window (iWindow *); void setUiScale_Window (iWindow *, float uiScale); -void setFreezeDraw_Window (iWindow *, iBool freezeDraw); void setCursor_Window (iWindow *, int cursor); iBool setKeyRoot_Window (iWindow *, iRoot *root); iBool postContextClick_Window (iWindow *, const SDL_MouseButtonEvent *); @@ -143,6 +150,8 @@ iBool postContextClick_Window (iWindow *, const SDL_MouseButtonEvent *); iWindow * get_Window (void); iBool isOpenGLRenderer_Window (void); +void setCurrent_Window (iAnyWindow *); + iLocalDef iBool isExposed_Window(const iWindow *d) { iAssert(d); return d->isExposed; @@ -158,6 +167,10 @@ iLocalDef const iWindow *constAs_Window(const iAnyWindow *d) { return (const iWindow *) d; } +iLocalDef iText *text_Window(const iAnyWindow *d) { + return constAs_Window(d)->text; +} + /*----------------------------------------------------------------------------------------------*/ iLocalDef iWindow *asWindow_MainWindow(iMainWindow *d) { @@ -167,6 +180,7 @@ iLocalDef iWindow *asWindow_MainWindow(iMainWindow *d) { void setTitle_MainWindow (iMainWindow *, const iString *title); void setSnap_MainWindow (iMainWindow *, int snapMode); +void setFreezeDraw_MainWindow (iMainWindow *, iBool freezeDraw); void setKeyboardHeight_MainWindow (iMainWindow *, int height); void setSplitMode_MainWindow (iMainWindow *, int splitMode); void checkPendingSplit_MainWindow (iMainWindow *); @@ -196,3 +210,7 @@ iLocalDef const iMainWindow *constAs_MainWindow(const iAnyWindow *d) { iAssert(type_Window(d) == main_WindowType); return (const iMainWindow *) d; } + +/*----------------------------------------------------------------------------------------------*/ + +iWindow * newPopup_Window (iInt2 screenPos, iWidget *rootWidget); -- cgit v1.2.3