From e39721581f082a20f1ef3a6f81b83c5e489cf7c7 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Mon, 13 Dec 2021 10:58:59 +0200 Subject: Android: Various fixes to get things up and running Resource paths, runtime data, ignore mouse events. Assume that the Java side tells us the display pixel density via a command line argument. --- src/app.c | 39 ++++++++++++++++++++++++---- src/resources.c | 20 +++++++++++++-- src/ui/documentwidget.c | 68 ++++++++++++++++++++++++------------------------- src/ui/touch.c | 4 +++ src/ui/window.c | 10 +++++++- 5 files changed, 99 insertions(+), 42 deletions(-) diff --git a/src/app.c b/src/app.c index eea3689f..3ee2345c 100644 --- a/src/app.c +++ b/src/app.c @@ -92,7 +92,10 @@ static const char *defaultDataDir_App_ = "~/Library/Application Support"; #define EMB_BIN "../resources.lgr" static const char *defaultDataDir_App_ = "~/AppData/Roaming/fi.skyjake.Lagrange"; #endif -#if defined (iPlatformLinux) || defined (iPlatformOther) +#if defined (iPlatformAndroidMobile) +#define EMB_BIN "resources.lgr" /* loaded from assets with SDL_rwops */ +static const char *defaultDataDir_App_ = NULL; /* will ask SDL */ +#elif defined (iPlatformLinux) || defined (iPlatformOther) #define EMB_BIN "../../share/lagrange/resources.lgr" #define EMB_BIN2 "../../../share/lagrange/resources.lgr" static const char *defaultDataDir_App_ = "~/.config/lagrange"; @@ -139,6 +142,9 @@ struct Impl_App { int autoReloadTimer; iPeriodic periodic; int warmupFrames; /* forced refresh just after resuming from background; FIXME: shouldn't be needed */ +#if defined (iPlatformAndroidMobile) + float displayDensity; +#endif /* Preferences: */ iBool commandEcho; /* --echo */ iBool forceSoftwareRender; /* --sw */ @@ -307,7 +313,10 @@ static const char *dataDir_App_(void) { return userDir; } #endif - return defaultDataDir_App_; + if (defaultDataDir_App_) { + return defaultDataDir_App_; + } + return SDL_GetPrefPath("Jaakko Keränen", "fi.skyjake.lagrange"); } static const char *downloadDir_App_(void) { @@ -715,7 +724,7 @@ static iBool hasCommandLineOpenableScheme_(const iRangecc uri) { } static void init_App_(iApp *d, int argc, char **argv) { -#if defined (iPlatformLinux) +#if defined (iPlatformLinux) && !defined (iPlatformAndroid) d->isRunningUnderWindowSystem = !iCmpStr(SDL_GetCurrentVideoDriver(), "x11") || !iCmpStr(SDL_GetCurrentVideoDriver(), "wayland"); #else @@ -763,6 +772,8 @@ static void init_App_(iApp *d, int argc, char **argv) { } } init_Lang(); + iStringList *openCmds = new_StringList(); +#if !defined (iPlatformAndroidMobile) /* Configure the valid command line options. */ { defineValues_CommandLine(&d->args, "close-tab", 0); defineValues_CommandLine(&d->args, "echo;E", 0); @@ -777,7 +788,6 @@ static void init_App_(iApp *d, int argc, char **argv) { defineValues_CommandLine(&d->args, "sw", 0); defineValues_CommandLine(&d->args, "version;V", 0); } - iStringList *openCmds = new_StringList(); /* Handle command line options. */ { if (contains_CommandLine(&d->args, "help")) { puts(cstr_Block(&blobArghelp_Resources)); @@ -826,6 +836,7 @@ static void init_App_(iApp *d, int argc, char **argv) { } } } +#endif #if defined (LAGRANGE_ENABLE_IPC) /* Only one instance is allowed to run at a time; the runtime files (bookmarks, etc.) are not shareable. */ { @@ -860,7 +871,7 @@ static void init_App_(iApp *d, int argc, char **argv) { /* Must scale by UI scaling factor. */ mulfv_I2(&d->initialWindowRect.size, desktopDPI_Win32()); #endif -#if defined (iPlatformLinux) +#if defined (iPlatformLinux) && !defined (iPlatformAndroid) /* Scale by the primary (?) monitor DPI. */ if (isRunningUnderWindowSystem_App()) { float vdpi; @@ -1325,6 +1336,15 @@ void processEvents_App(enum iAppEventMode eventMode) { } ev.key.keysym.mod = mapMods_Keys(ev.key.keysym.mod & ~KMOD_CAPS); } +#if defined (iPlatformAndroidMobile) + /* Ignore all mouse events; just use touch. */ + if (ev.type == SDL_MOUSEBUTTONDOWN || + ev.type == SDL_MOUSEBUTTONUP || + ev.type == SDL_MOUSEMOTION || + ev.type == SDL_MOUSEWHEEL) { + continue; + } +#endif /* Scroll events may be per-pixel or mouse wheel steps. */ if (ev.type == SDL_MOUSEWHEEL) { #if defined (iPlatformMsys) @@ -1773,6 +1793,8 @@ enum iAppDeviceType deviceType_App(void) { return tablet_AppDeviceType; #elif defined (iPlatformAppleMobile) return isPhone_iOS() ? phone_AppDeviceType : tablet_AppDeviceType; +#elif defined (iPlatformAndroidMobile) + return phone_AppDeviceType; /* TODO: Java side could tell us via cmdline if this is a tablet. */ #else return desktop_AppDeviceType; #endif @@ -3408,3 +3430,10 @@ void closePopups_App(void) { } } } + +#if defined (iPlatformAndroidMobile) +float displayDensity_Android(void) { + iApp *d = &app_; + return toFloat_String(at_CommandLine(&d->args, 1)); +} +#endif diff --git a/src/resources.c b/src/resources.c index bb601cca..e3d92946 100644 --- a/src/resources.c +++ b/src/resources.c @@ -26,7 +26,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include static iArchive *archive_; - + iBlock blobAbout_Resources; iBlock blobHelp_Resources; iBlock blobLagrange_Resources; @@ -101,7 +101,23 @@ static struct { iBool init_Resources(const char *path) { archive_ = new_Archive(); - if (openFile_Archive(archive_, collectNewCStr_String(path))) { + iBool ok = iFalse; +#if defined (iPlatformAndroidMobile) + /* Resources are bundled as assets so they cannot be loaded as a regular file. + Fortunately, SDL implements a file wrapper. */ + SDL_RWops *io = SDL_RWFromFile(path, "rb"); + if (io) { + iBlock buf; + init_Block(&buf, (size_t) SDL_RWsize(io)); + SDL_RWread(io, data_Block(&buf), size_Block(&buf), 1); + SDL_RWclose(io); + ok = openData_Archive(archive_, &buf); + deinit_Block(&buf); + } +#else + ok = openFile_Archive(archive_, collectNewCStr_String(path)); +#endif + if (ok) { iVersion appVer; init_Version(&appVer, range_CStr(LAGRANGE_APP_VERSION)); iVersion resVer; diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index e93fb586..b20ae672 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -225,7 +225,7 @@ enum iDocumentWidgetFlag { animationPlaceholder_DocumentWidgetFlag = iBit(16), /* avoid slow operations */ invalidationPending_DocumentWidgetFlag = iBit(17), /* invalidate as soon as convenient */ leftWheelSwipe_DocumentWidgetFlag = iBit(18), /* swipe state flags are used on desktop */ - rightWheelSwipe_DocumentWidgetFlag = iBit(19), + rightWheelSwipe_DocumentWidgetFlag = iBit(19), eitherWheelSwipe_DocumentWidgetFlag = leftWheelSwipe_DocumentWidgetFlag | rightWheelSwipe_DocumentWidgetFlag, }; @@ -242,8 +242,8 @@ enum iWheelSwipeState { /* TODO: DocumentView is supposed to be useful on its own; move to a separate source file. */ iDeclareType(DocumentView) - -struct Impl_DocumentView { + +struct Impl_DocumentView { iDocumentWidget *owner; /* TODO: Convert to an abstract provider of metrics? */ iGmDocument * doc; int pageMargin; @@ -253,7 +253,7 @@ struct Impl_DocumentView { iGmRunRange visibleRuns; iPtrArray visibleLinks; iPtrArray visiblePre; - iPtrArray visibleMedia; /* currently playing audio / ongoing downloads */ + iPtrArray visibleMedia; /* currently playing audio / ongoing downloads */ iPtrArray visibleWideRuns; /* scrollable blocks; TODO: merge into `visiblePre` */ const iGmRun * hoverPre; /* for clicking */ const iGmRun * hoverAltPre; /* for drawing alt text */ @@ -263,7 +263,7 @@ struct Impl_DocumentView { uint16_t animWideRunId; iGmRunRange animWideRunRange; iDrawBufs * drawBufs; /* dynamic state for drawing */ - iVisBuf * visBuf; + iVisBuf * visBuf; iVisBufMeta * visBufMeta; iGmRunRange renderRuns; iPtrSet * invalidRuns; @@ -272,7 +272,7 @@ struct Impl_DocumentView { struct Impl_DocumentWidget { iWidget widget; int flags; /* internal behavior, see enum iDocumentWidgetFlag */ - + /* User interface: */ enum iDocumentLinkOrdinalMode ordinalMode; size_t ordinalBase; @@ -293,7 +293,7 @@ struct Impl_DocumentWidget { enum iWheelSwipeState wheelSwipeState; iString pendingGotoHeading; iString linePrecedingLink; - + /* Network request: */ enum iRequestState state; iGmRequest * request; @@ -304,7 +304,7 @@ struct Impl_DocumentWidget { iString * certSubject; int redirectCount; iObjectList * media; /* inline media requests */ - + /* Document: */ iPersistentDocumentState mod; iString * titleUser; @@ -316,12 +316,12 @@ struct Impl_DocumentWidget { iGempub * sourceGempub; /* NULL unless the page is Gempub content */ iBanner * banner; float initNormScrollY; - + /* Rendering: */ iDocumentView view; iLinkInfo * linkInfo; - - /* Widget structure: */ + + /* Widget structure: */ iScrollWidget *scroll; iWidget * footerButtons; iWidget * menu; @@ -332,7 +332,7 @@ struct Impl_DocumentWidget { }; iDefineObjectConstruction(DocumentWidget) - + /* Sorted by proximity to F and J. */ static const int homeRowKeys_[] = { 'f', 'd', 's', 'a', @@ -344,7 +344,7 @@ static const int homeRowKeys_[] = { 'g', 'h', 'b', 't', 'y', -}; +}; static int docEnum_ = 0; static void animate_DocumentWidget_ (void *ticker); @@ -909,7 +909,7 @@ static void updateTimestampBuf_DocumentView_(const iDocumentView *d) { static void invalidate_DocumentView_(iDocumentView *d) { invalidate_VisBuf(d->visBuf); - clear_PtrSet(d->invalidRuns); + clear_PtrSet(d->invalidRuns); } static void documentRunsInvalidated_DocumentView_(iDocumentView *d) { @@ -928,11 +928,11 @@ static void resetScroll_DocumentView_(iDocumentView *d) { } static void updateWidth_DocumentView_(iDocumentView *d) { - updateWidth_GmDocument(d->doc, documentWidth_DocumentView_(d), width_Widget(d->owner)); + updateWidth_GmDocument(d->doc, documentWidth_DocumentView_(d), width_Widget(d->owner)); } static void updateWidthAndRedoLayout_DocumentView_(iDocumentView *d) { - setWidth_GmDocument(d->doc, documentWidth_DocumentView_(d), width_Widget(d->owner)); + setWidth_GmDocument(d->doc, documentWidth_DocumentView_(d), width_Widget(d->owner)); } static void clampScroll_DocumentView_(iDocumentView *d) { @@ -1025,7 +1025,7 @@ static iRangecc sourceLoc_DocumentView_(const iDocumentView *d, iInt2 pos) { } iDeclareType(MiddleRunParams) - + struct Impl_MiddleRunParams { int midY; const iGmRun *closest; @@ -1126,7 +1126,7 @@ static iRect runRect_DocumentView_(const iDocumentView *d, const iGmRun *run) { } iDeclareType(DrawContext) - + struct Impl_DrawContext { const iDocumentView *view; iRect widgetBounds; @@ -1260,7 +1260,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { isInlineImageCaption = iFalse; } #endif - /* While this is consistent, it's a bit excessive to indicate that an inlined image + /* While this is consistent, it's a bit excessive to indicate that an inlined image is open: the image itself is the indication. */ const iBool isInlineImageCaption = iFalse; if (run->linkId && (linkFlags & isOpen_GmLinkFlag || isInlineImageCaption)) { @@ -1285,7 +1285,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { } fillRect_Paint(&d->paint, wideRect, bg); } - else { + else { /* Normal background for other runs. There are cases when runs get drawn multiple times, e.g., at the buffer boundary, and there are slightly overlapping characters in monospace blocks. Clearing the background here ensures a cleaner visual appearance @@ -2095,7 +2095,7 @@ static void invalidate_DocumentWidget_(iDocumentWidget *d) { } static iRangecc siteText_DocumentWidget_(const iDocumentWidget *d) { - return isEmpty_String(d->titleUser) ? urlHost_String(d->mod.url) + return isEmpty_String(d->titleUser) ? urlHost_String(d->mod.url) : range_String(d->titleUser); } @@ -2161,7 +2161,7 @@ static void updateBanner_DocumentWidget_(iDocumentWidget *d) { static void updateTheme_DocumentWidget_(iDocumentWidget *d) { if (document_App() != d || category_GmStatusCode(d->sourceStatus) == categoryInput_GmStatusCode) { return; - } + } d->view.drawBufs->flags |= updateTimestampBuf_DrawBufsFlag; updateBanner_DocumentWidget_(d); } @@ -2620,7 +2620,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, appendFormat_String(&str, cstr_Lang("doc.archive"), cstr_Rangecc(baseName_Path(d->mod.url))); - appendCStr_String(&str, "\n"); + appendCStr_String(&str, "\n"); } appendCStr_String(&str, "\n"); iString *localPath = localFilePathFromUrl_String(d->mod.url); @@ -2768,7 +2768,7 @@ static void updateTrust_DocumentWidget_(iDocumentWidget *d, const iGmResponse *r } else if (~d->certFlags & timeVerified_GmCertFlag) { updateTextCStr_LabelWidget(lock, isDarkMode ? orange_ColorEscape warning_Icon - : black_ColorEscape warning_Icon); + : black_ColorEscape warning_Icon); } else { updateTextCStr_LabelWidget(lock, green_ColorEscape closedLock_Icon); @@ -3067,7 +3067,7 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) { it is only displayed as an input dialog. */ visitUrl_Visited(visited_App(), d->mod.url, transient_VisitedUrlFlag); iUrl parts; - init_Url(&parts, d->mod.url); + init_Url(&parts, d->mod.url); iWidget *dlg = makeValueInput_Widget( as_Widget(d), NULL, @@ -3132,7 +3132,7 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) { setFont_LabelWidget(menu, font_LabelWidget((iLabelWidget *) lastChild_Widget(buttons))); setTextColor_LabelWidget(menu, uiTextAction_ColorId); } - } + } setValidator_InputWidget(findChild_Widget(dlg, "input"), inputQueryValidator_, d); setSensitiveContent_InputWidget(findChild_Widget(dlg, "input"), statusCode == sensitiveInput_GmStatusCode); @@ -3491,7 +3491,7 @@ static iBool handleSwipe_DocumentWidget_(iDocumentWidget *d, const char *cmd) { } /* The temporary "swipein" will display the previous page until the finger is lifted. */ iDocumentWidget *swipeIn = findChild_Widget(swipeParent, "swipein"); - if (!swipeIn) { + if (!swipeIn) { swipeIn = new_DocumentWidget(); swipeIn->flags |= animationPlaceholder_DocumentWidgetFlag; setId_Widget(as_Widget(swipeIn), "swipein"); @@ -3531,7 +3531,7 @@ static iBool handleSwipe_DocumentWidget_(iDocumentWidget *d, const char *cmd) { iWidget *swipeParent = swipeParent_DocumentWidget_(d); if (findChild_Widget(swipeParent, "swipeout")) { return iTrue; /* too fast, previous animation hasn't finished */ - } + } /* Setup the drag. `d` will be moving with the finger. */ animSpan = 0; postCommand_Widget(d, "navigate.forward"); @@ -3694,7 +3694,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) else if (equal_Command(cmd, "window.resized") || equal_Command(cmd, "font.changed") || equal_Command(cmd, "keyroot.changed")) { if (equal_Command(cmd, "font.changed")) { - invalidateCachedLayout_History(d->mod.history); + invalidateCachedLayout_History(d->mod.history); } /* Alt/Option key may be involved in window size changes. */ setLinkNumberMode_DocumentWidget_(d, iFalse); @@ -4056,7 +4056,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) } return wasHandled; } - else if (equal_Command(cmd, "document.upload") && d == document_App()) { + else if (equal_Command(cmd, "document.upload") && d == document_App()) { if (findChild_Widget(root_Widget(w), "upload")) { return iTrue; /* already open */ } @@ -4124,7 +4124,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) if (equalCase_Rangecc(urlScheme_String(d->mod.url), "titan")) { /* Reopen so the Upload dialog gets shown. */ postCommandf_App("open url:%s", cstr_String(d->mod.url)); - return iTrue; + return iTrue; } fetch_DocumentWidget_(d); return iTrue; @@ -4416,7 +4416,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) if (argLabel_Command(cmd, "ttf")) { iAssert(!cmp_String(&d->sourceMime, "font/ttf")); installFontFile_Fonts(collect_String(suffix_Command(cmd, "name")), &d->sourceContent); - postCommand_App("open url:about:fonts"); + postCommand_App("open url:about:fonts"); } else { const iString *id = idFromUrl_FontPack(d->mod.url); @@ -5435,7 +5435,7 @@ void init_DocumentWidget(iDocumentWidget *d) { init_Widget(w); setId_Widget(w, format_CStr("document%03d", ++docEnum_)); setFlags_Widget(w, hover_WidgetFlag | noBackground_WidgetFlag, iTrue); -#if defined (iPlatformAppleDesktop) +#if defined (iPlatformAppleDesktop) iBool enableSwipeNavigation = iTrue; /* swipes on the trackpad */ #else iBool enableSwipeNavigation = (deviceType_App() != desktop_AppDeviceType); @@ -5671,7 +5671,7 @@ iBool isRequestOngoing_DocumentWidget(const iDocumentWidget *d) { void takeRequest_DocumentWidget(iDocumentWidget *d, iGmRequest *finishedRequest) { cancelRequest_DocumentWidget_(d, iFalse /* don't post anything */); const iString *url = url_GmRequest(finishedRequest); - + add_History(d->mod.history, url); setUrl_DocumentWidget_(d, url); d->state = fetching_RequestState; diff --git a/src/ui/touch.c b/src/ui/touch.c index 0749bc7c..3d318dfb 100644 --- a/src/ui/touch.c +++ b/src/ui/touch.c @@ -42,7 +42,11 @@ iDeclareType(TouchState) static const uint32_t longPressSpanMs_ = 500; static const uint32_t shortPressSpanMs_ = 250; +#if defined (iPlatformAndroidMobile) +static const int tapRadiusPt_ = 30; /* inaccurate sensors? */ +#else static const int tapRadiusPt_ = 10; +#endif enum iTouchEdge { none_TouchEdge, diff --git a/src/ui/window.c b/src/ui/window.c index 0e13a57f..7f3371c8 100644 --- a/src/ui/window.c +++ b/src/ui/window.c @@ -263,6 +263,10 @@ static float pixelRatio_Window_(const iWindow *d) { # define baseDPI_Window 96.0f #endif +#if defined (iPlatformAndroidMobile) +float displayDensity_Android(void); +#endif + static float displayScale_Window_(const iWindow *d) { /* The environment variable LAGRANGE_OVERRIDE_DPI can be used to override the automatic display DPI detection. If not set, or is an empty string, ignore it. @@ -289,6 +293,8 @@ static float displayScale_Window_(const iWindow *d) { #elif defined (iPlatformMsys) iUnused(d); return desktopDPI_Win32(); +#elif defined (iPlatformAndroidMobile) + return displayDensity_Android(); #else if (isRunningUnderWindowSystem_App()) { float vdpi = 0.0f; @@ -457,7 +463,7 @@ void init_Window(iWindow *d, enum iWindowType type, iRect rect, uint32_t flags) d->mouseGrab = NULL; d->focus = NULL; d->pendingCursor = NULL; - d->isExposed = iFalse; + d->isExposed = (deviceType_App() != desktop_AppDeviceType); d->isMinimized = iFalse; d->isInvalidated = iFalse; /* set when posting event, to avoid repeated events */ d->isMouseInside = iTrue; @@ -541,6 +547,8 @@ void init_MainWindow(iMainWindow *d, iRect rect) { SDL_SetHint(SDL_HINT_RENDER_DRIVER, "metal"); flags |= SDL_WINDOW_METAL; d->base.isExposed = iTrue; +#elif defined (iPlatformAndroidMobile) + d->base.isExposed = iTrue; #else if (!forceSoftwareRender_App()) { flags |= SDL_WINDOW_OPENGL; -- cgit v1.2.3