From 168ded2245101126e7954887ddc04b80f211e917 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Fri, 10 Sep 2021 09:29:46 +0300 Subject: Mobile: Revised more dialogs --- src/ui/translation.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) (limited to 'src/ui/translation.c') diff --git a/src/ui/translation.c b/src/ui/translation.c index 3ffa961b..cef68dce 100644 --- a/src/ui/translation.c +++ b/src/ui/translation.c @@ -424,19 +424,18 @@ static iBool processResult_Translation_(iTranslation *d) { } static iLabelWidget *acceptButton_Translation_(const iTranslation *d) { - iWidget *buttonParent = findChild_Widget(d->dlg, "dialogbuttons"); -// if (!buttonParent) { -// buttonParent = findChild_Widget(d->dlg, "panel.back"); -// } - return (iLabelWidget *) lastChild_Widget(buttonParent); + return dialogAcceptButton_Widget(d->dlg); } iBool handleCommand_Translation(iTranslation *d, const char *cmd) { iWidget *w = as_Widget(d->doc); if (equalWidget_Command(cmd, w, "translation.submit")) { if (status_TlsRequest(d->request) == initialized_TlsRequestStatus) { - iWidget *langs = findChild_Widget(d->dlg, "xlt.langs"); - setFlags_Widget(langs, hidden_WidgetFlag, iTrue); + iWidget *langs = findChild_Widget(d->dlg, "xlt.langs"); +// setFlags_Widget(langs, hidden_WidgetFlag, iTrue); + setFlags_Widget(findChild_Widget(d->dlg, "xlt.from"), hidden_WidgetFlag, iTrue); + setFlags_Widget(findChild_Widget(d->dlg, "xlt.to"), hidden_WidgetFlag, iTrue); + if (!langs) langs = d->dlg; iLabelWidget *acceptButton = acceptButton_Translation_(d); updateTextCStr_LabelWidget(acceptButton, "00:00"); setFlags_Widget(as_Widget(acceptButton), disabled_WidgetFlag, iTrue); -- cgit v1.2.3 From 33620846cca5678fbd662ea1a48fad302727dae7 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Sun, 12 Sep 2021 13:49:38 +0300 Subject: Mobile: Draw optimizations; focus handling Widgets can now be marked for buffering their contents, which is useful if their contents change seldom but they are drawn often. For example, the navbar is always visible but doesn't change very often, and during animations menu contents are static but there is a moving animation so everything gets drawn 60 FPS. Focus handling was also improved so the lookup results can be scrolled while entering text, and one can tap outside an input field to unfocus it. --- src/ui/inputwidget.c | 5 + src/ui/mobile.c | 4 +- src/ui/paint.c | 18 +++- src/ui/paint.h | 2 + src/ui/root.c | 54 ++++++++++- src/ui/sidebarwidget.c | 3 +- src/ui/text.c | 7 ++ src/ui/text_simple.c | 2 + src/ui/touch.c | 2 +- src/ui/translation.c | 3 +- src/ui/uploadwidget.c | 8 +- src/ui/util.c | 1 + src/ui/widget.c | 241 ++++++++++++++++++++++++++++++++++++++++++++----- src/ui/widget.h | 9 ++ src/ui/window.c | 7 +- 15 files changed, 327 insertions(+), 39 deletions(-) (limited to 'src/ui/translation.c') diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c index 6e9ef6c2..802a2d6c 100644 --- a/src/ui/inputwidget.c +++ b/src/ui/inputwidget.c @@ -1565,6 +1565,11 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { } switch (processEvent_Click(&d->click, ev)) { case none_ClickResult: + if (ev->type == SDL_MOUSEBUTTONUP && + deviceType_App() != desktop_AppDeviceType && isFocused_Widget(d)) { + setFocus_Widget(NULL); + return iTrue; + } break; case started_ClickResult: { setFocus_Widget(w); diff --git a/src/ui/mobile.c b/src/ui/mobile.c index 6ea672e6..9e2dc4f7 100644 --- a/src/ui/mobile.c +++ b/src/ui/mobile.c @@ -357,6 +357,7 @@ static iWidget *addChildPanel_(iWidget *parent, iLabelWidget *panelButton, setId_Widget(panel, "panel"); setUserData_Object(panelButton, panel); setBackgroundColor_Widget(panel, uiBackground_ColorId); + setDrawBufferEnabled_Widget(panel, iTrue); setId_Widget(addChild_Widget(panel, iClob(makePadding_Widget(0))), "panel.toppad"); if (titleText) { iLabelWidget *title = @@ -601,6 +602,7 @@ void initPanels_Mobile(iWidget *panels, iWidget *parentWidget, /* The panel roots. */ iWidget *topPanel = new_Widget(); { setId_Widget(topPanel, "panel.top"); + setDrawBufferEnabled_Widget(topPanel, iTrue); setCommandHandler_Widget(topPanel, topPanelHandler_); setFlags_Widget(topPanel, arrangeVertical_WidgetFlag | resizeWidthOfChildren_WidgetFlag | @@ -730,7 +732,7 @@ void initPanels_Mobile(iWidget *panels, iWidget *parentWidget, updatePanelSheetMetrics_(panels); arrange_Widget(panels); postCommand_App("widget.overflow"); /* with the correct dimensions */ - printTree_Widget(panels); +// printTree_Widget(panels); } #if 0 diff --git a/src/ui/paint.c b/src/ui/paint.c index 79adb7d1..af62f908 100644 --- a/src/ui/paint.c +++ b/src/ui/paint.c @@ -24,6 +24,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include +iInt2 origin_Paint; + iLocalDef SDL_Renderer *renderer_Paint_(const iPaint *d) { iAssert(d->dst); return d->dst->render; @@ -62,10 +64,11 @@ void endTarget_Paint(iPaint *d) { } void setClip_Paint(iPaint *d, iRect rect) { - rect = intersect_Rect(rect, rect_Root(get_Root())); + //rect = intersect_Rect(rect, rect_Root(get_Root())); if (isEmpty_Rect(rect)) { rect = init_Rect(0, 0, 1, 1); } + addv_I2(&rect.pos, origin_Paint); SDL_RenderSetClipRect(renderer_Paint_(d), (const SDL_Rect *) &rect); } @@ -85,6 +88,7 @@ void unsetClip_Paint(iPaint *d) { } void drawRect_Paint(const iPaint *d, iRect rect, int color) { + addv_I2(&rect.pos, origin_Paint); iInt2 br = bottomRight_Rect(rect); /* Keep the right/bottom edge visible in the window. */ if (br.x == d->dst->size.x) br.x--; @@ -115,11 +119,14 @@ void drawRectThickness_Paint(const iPaint *d, iRect rect, int thickness, int col } void fillRect_Paint(const iPaint *d, iRect rect, int color) { + addv_I2(&rect.pos, origin_Paint); setColor_Paint_(d, color); +// printf("fillRect_Paint: %d,%d %dx%d\n", rect.pos.x, rect.pos.y, rect.size.x, rect.size.y); SDL_RenderFillRect(renderer_Paint_(d), (SDL_Rect *) &rect); } void drawSoftShadow_Paint(const iPaint *d, iRect inner, int thickness, int color, int alpha) { + addv_I2(&inner.pos, origin_Paint); SDL_Renderer *render = renderer_Paint_(d); SDL_Texture *shadow = get_Window()->borderShadow; const iInt2 size = size_SDLTexture(shadow); @@ -146,9 +153,14 @@ void drawSoftShadow_Paint(const iPaint *d, iRect inner, int thickness, int color &(SDL_Rect){ outer.pos.x, inner.pos.y, thickness, inner.size.y }); } -void drawLines_Paint(const iPaint *d, const iInt2 *points, size_t count, int color) { +void drawLines_Paint(const iPaint *d, const iInt2 *points, size_t n, int color) { setColor_Paint_(d, color); - SDL_RenderDrawLines(renderer_Paint_(d), (const SDL_Point *) points, count); + iInt2 *offsetPoints = malloc(sizeof(iInt2) * n); + for (size_t i = 0; i < n; i++) { + offsetPoints[i] = add_I2(points[i], origin_Paint); + } + SDL_RenderDrawLines(renderer_Paint_(d), (const SDL_Point *) offsetPoints, n); + free(offsetPoints); } iInt2 size_SDLTexture(SDL_Texture *d) { diff --git a/src/ui/paint.h b/src/ui/paint.h index 90cc2aef..e6701635 100644 --- a/src/ui/paint.h +++ b/src/ui/paint.h @@ -36,6 +36,8 @@ struct Impl_Paint { uint8_t alpha; }; +extern iInt2 origin_Paint; /* add this to all drawn positions so buffered graphics are correctly offset */ + void init_Paint (iPaint *); void beginTarget_Paint (iPaint *, SDL_Texture *target); diff --git a/src/ui/root.c b/src/ui/root.c index a792e93d..7b2b5b15 100644 --- a/src/ui/root.c +++ b/src/ui/root.c @@ -685,6 +685,34 @@ static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) { } return iTrue; } + else if (deviceType_App() != desktop_AppDeviceType && + (equal_Command(cmd, "focus.gained") || equal_Command(cmd, "focus.lost"))) { + iInputWidget *url = findChild_Widget(navBar, "url"); + if (pointer_Command(cmd) == url) { + const iBool isFocused = equal_Command(cmd, "focus.gained"); + setFlags_Widget(findChild_Widget(navBar, "navbar.clear"), hidden_WidgetFlag, !isFocused); + showCollapsed_Widget(findChild_Widget(navBar, "navbar.cancel"), isFocused); + showCollapsed_Widget(findChild_Widget(navBar, "pagemenubutton"), !isFocused); + showCollapsed_Widget(findChild_Widget(navBar, "reload"), !isFocused); + } + return iFalse; + } + else if (equal_Command(cmd, "navbar.clear")) { + iInputWidget *url = findChild_Widget(navBar, "url"); + selectAll_InputWidget(url); + /* Emulate a Backspace keypress. */ + class_InputWidget(url)->processEvent( + as_Widget(url), + (SDL_Event *) &(SDL_KeyboardEvent){ .type = SDL_KEYDOWN, + .timestamp = SDL_GetTicks(), + .state = SDL_PRESSED, + .keysym = { .sym = SDLK_BACKSPACE } }); + return iTrue; + } + else if (equal_Command(cmd, "navbar.cancel")) { + setFocus_Widget(NULL); + return iTrue; + } else if (equal_Command(cmd, "input.edited")) { iAnyObject * url = findChild_Widget(navBar, "url"); const iString *text = text_InputWidget(url); @@ -941,7 +969,7 @@ void updateMetrics_Root(iRoot *d) { setFixedSize_Widget(appIcon, init_I2(appIconSize_Root(), appMin->rect.size.y)); } iWidget *navBar = findChild_Widget(d->widget, "navbar"); - iWidget *lock = findChild_Widget(navBar, "navbar.lock"); +// iWidget *lock = findChild_Widget(navBar, "navbar.lock"); iWidget *url = findChild_Widget(d->widget, "url"); iWidget *rightEmbed = findChild_Widget(navBar, "url.rightembed"); iWidget *embedPad = findChild_Widget(navBar, "url.embedpad"); @@ -1044,6 +1072,7 @@ void createUserInterface_Root(iRoot *d) { /* Navigation bar. */ { navBar = new_Widget(); setId_Widget(navBar, "navbar"); + setDrawBufferEnabled_Widget(navBar, iTrue); setFlags_Widget(navBar, hittable_WidgetFlag | /* context menu */ arrangeHeight_WidgetFlag | @@ -1095,6 +1124,16 @@ void createUserInterface_Root(iRoot *d) { setFont_LabelWidget(lock, symbols_FontId + uiNormal_FontSize); updateTextCStr_LabelWidget(lock, "\U0001f512"); } + /* Button for clearing the URL bar contents. */ { + iLabelWidget *clear = addChildFlags_Widget( + as_Widget(url), + iClob(newIcon_LabelWidget(delete_Icon, 0, 0, "navbar.clear")), + hidden_WidgetFlag | embedFlags | moveToParentLeftEdge_WidgetFlag); + setId_Widget(as_Widget(clear), "navbar.clear"); + setFont_LabelWidget(clear, symbols2_FontId + uiNormal_FontSize); + setFlags_Widget(as_Widget(clear), noBackground_WidgetFlag, iFalse); + setBackgroundColor_Widget(as_Widget(clear), uiBackground_ColorId); + } iWidget *rightEmbed = new_Widget(); setId_Widget(rightEmbed, "url.rightembed"); addChildFlags_Widget(as_Widget(url), @@ -1151,6 +1190,13 @@ void createUserInterface_Root(iRoot *d) { setFlags_Widget(urlButtons, embedFlags | arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag, iTrue); /* Mobile page menu. */ if (deviceType_App() != desktop_AppDeviceType) { + iLabelWidget *navCancel = new_LabelWidget("${cancel}", "navbar.cancel"); + addChildFlags_Widget(urlButtons, iClob(navCancel), + (embedFlags | tight_WidgetFlag | hidden_WidgetFlag | + collapse_WidgetFlag) & ~noBackground_WidgetFlag); + as_Widget(navCancel)->sizeRef = as_Widget(url); +// setFont_LabelWidget(navCancel, defaultBold_FontId); + setId_Widget(as_Widget(navCancel), "navbar.cancel"); iLabelWidget *pageMenuButton; /* In a mobile layout, the reload button is replaced with the Page/Ellipsis menu. */ pageMenuButton = makeMenuButton_LabelWidget(pageMenuCStr_, @@ -1172,13 +1218,14 @@ void createUserInterface_Root(iRoot *d) { setId_Widget(as_Widget(pageMenuButton), "pagemenubutton"); setFont_LabelWidget(pageMenuButton, uiContentBold_FontId); setAlignVisually_LabelWidget(pageMenuButton, iTrue); - addChildFlags_Widget(urlButtons, iClob(pageMenuButton), embedFlags | tight_WidgetFlag); + addChildFlags_Widget(urlButtons, iClob(pageMenuButton), + embedFlags | tight_WidgetFlag | collapse_WidgetFlag); updateSize_LabelWidget(pageMenuButton); } /* Reload button. */ { iLabelWidget *reload = newIcon_LabelWidget(reloadCStr_, 0, 0, "navigate.reload"); setId_Widget(as_Widget(reload), "reload"); - addChildFlags_Widget(urlButtons, iClob(reload), embedFlags); + addChildFlags_Widget(urlButtons, iClob(reload), embedFlags | collapse_WidgetFlag); updateSize_LabelWidget(reload); } addChildFlags_Widget(as_Widget(url), iClob(urlButtons), moveToParentRightEdge_WidgetFlag); @@ -1287,6 +1334,7 @@ void createUserInterface_Root(iRoot *d) { iWidget *toolBar = new_Widget(); addChild_Widget(root, iClob(toolBar)); setId_Widget(toolBar, "toolbar"); + setDrawBufferEnabled_Widget(toolBar, iTrue); setCommandHandler_Widget(toolBar, handleToolBarCommands_); setFlags_Widget(toolBar, moveToParentBottomEdge_WidgetFlag | parentCannotResizeHeight_WidgetFlag | diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c index b816b572..eb129424 100644 --- a/src/ui/sidebarwidget.c +++ b/src/ui/sidebarwidget.c @@ -663,8 +663,9 @@ void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) { /* On a phone, the right sidebar is used exclusively for Identities. */ const iBool isPhone = deviceType_App() == phone_AppDeviceType; if (!isPhone || d->side == left_SidebarSide) { - iWidget *buttons = new_Widget(); + iWidget *buttons = new_Widget(); setId_Widget(buttons, "buttons"); + setDrawBufferEnabled_Widget(buttons, iTrue); for (int i = 0; i < max_SidebarMode; i++) { if (deviceType_App() == phone_AppDeviceType && i == identities_SidebarMode) { continue; diff --git a/src/ui/text.c b/src/ui/text.c index 231281eb..f7fff4bc 100644 --- a/src/ui/text.c +++ b/src/ui/text.c @@ -25,6 +25,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "metrics.h" #include "embedded.h" #include "window.h" +#include "paint.h" #include "app.h" #define STB_TRUETYPE_IMPLEMENTATION @@ -1712,6 +1713,8 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { } SDL_Rect src; memcpy(&src, &glyph->rect[hoff], sizeof(SDL_Rect)); + dst.x += origin_Paint.x; + dst.y += origin_Paint.y; if (args->mode & fillBackground_RunMode) { /* Alpha blending looks much better if the RGB components don't change in the partially transparent pixels. */ @@ -2182,6 +2185,8 @@ void init_TextBuf(iTextBuf *d, iWrapText *wrapText, int font, int color) { } if (d->texture) { SDL_Texture *oldTarget = SDL_GetRenderTarget(render); + const iInt2 oldOrigin = origin_Paint; + origin_Paint = zero_I2(); SDL_SetRenderTarget(render, d->texture); SDL_SetRenderDrawBlendMode(render, SDL_BLENDMODE_NONE); SDL_SetRenderDrawColor(render, 255, 255, 255, 0); @@ -2190,6 +2195,7 @@ void init_TextBuf(iTextBuf *d, iWrapText *wrapText, int font, int color) { draw_WrapText(wrapText, font, zero_I2(), color | fillBackground_ColorId); SDL_SetTextureBlendMode(text_.cache, SDL_BLENDMODE_BLEND); SDL_SetRenderTarget(render, oldTarget); + origin_Paint = oldOrigin; SDL_SetTextureBlendMode(d->texture, SDL_BLENDMODE_BLEND); } } @@ -2203,6 +2209,7 @@ iTextBuf *newRange_TextBuf(int font, int color, iRangecc text) { } 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, diff --git a/src/ui/text_simple.c b/src/ui/text_simple.c index e88b09a8..bf33b4be 100644 --- a/src/ui/text_simple.c +++ b/src/ui/text_simple.c @@ -306,6 +306,8 @@ static iRect runSimple_Font_(iFont *d, const iRunArgs *args) { src.y += over; src.h -= over; } + dst.x += origin_Paint.x; + dst.y += origin_Paint.y; if (args->mode & fillBackground_RunMode) { /* Alpha blending looks much better if the RGB components don't change in the partially transparent pixels. */ diff --git a/src/ui/touch.c b/src/ui/touch.c index dac1152e..f0456acb 100644 --- a/src/ui/touch.c +++ b/src/ui/touch.c @@ -614,7 +614,7 @@ iBool processEvent_Touch(const SDL_Event *ev) { // pixels.y, y_F3(amount), y_F3(touch->accum), // touch->edge); if (pixels.x || pixels.y) { - setFocus_Widget(NULL); + //setFocus_Widget(NULL); dispatchMotion_Touch_(touch->pos[0], 0); setCurrent_Root(touch->affinity->root); dispatchEvent_Widget(touch->affinity, (SDL_Event *) &(SDL_MouseWheelEvent){ diff --git a/src/ui/translation.c b/src/ui/translation.c index cef68dce..b86e6e52 100644 --- a/src/ui/translation.c +++ b/src/ui/translation.c @@ -136,7 +136,8 @@ static void draw_TranslationProgressWidget_(const iTranslationProgressWidget *d) get_Color(palette[palCur]), get_Color(palette[palNext]), palPos - (int) palPos); SDL_SetRenderDrawColor(renderer_Window(get_Window()), back.r, back.g, back.b, p.alpha); SDL_RenderFillRect(renderer_Window(get_Window()), - &(SDL_Rect){ pos.x, pos.y, spr->size.x, spr->size.y }); + &(SDL_Rect){ pos.x + origin_Paint.x, pos.y + origin_Paint.y, + spr->size.x, spr->size.y }); if (fg >= 0) { setOpacity_Text(opacity * 2); drawRange_Text(d->font, addX_I2(pos, spr->xoff), fg, range_String(&spr->text)); diff --git a/src/ui/uploadwidget.c b/src/ui/uploadwidget.c index fb8aaf0a..78a1196a 100644 --- a/src/ui/uploadwidget.c +++ b/src/ui/uploadwidget.c @@ -376,11 +376,11 @@ static iBool processEvent_UploadWidget_(iUploadWidget *d, const SDL_Event *ev) { return processEvent_Widget(w, ev); } -static void draw_UploadWidget_(const iUploadWidget *d) { - draw_Widget(constAs_Widget(d)); -} +//static void draw_UploadWidget_(const iUploadWidget *d) { +// draw_Widget(constAs_Widget(d)); +//} iBeginDefineSubclass(UploadWidget, Widget) .processEvent = (iAny *) processEvent_UploadWidget_, - .draw = (iAny *) draw_UploadWidget_, + .draw = draw_Widget, iEndDefineSubclass(UploadWidget) diff --git a/src/ui/util.c b/src/ui/util.c index 6069e800..05d39c01 100644 --- a/src/ui/util.c +++ b/src/ui/util.c @@ -685,6 +685,7 @@ static iWidget *makeMenuSeparator_(void) { iWidget *makeMenu_Widget(iWidget *parent, const iMenuItem *items, size_t n) { iWidget *menu = new_Widget(); + setDrawBufferEnabled_Widget(menu, iTrue); setBackgroundColor_Widget(menu, uiBackgroundMenu_ColorId); if (deviceType_App() != desktop_AppDeviceType) { setPadding1_Widget(menu, 2 * gap_UI); diff --git a/src/ui/widget.c b/src/ui/widget.c index 659a00cc..66cd0e7b 100644 --- a/src/ui/widget.c +++ b/src/ui/widget.c @@ -40,6 +40,67 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ # include "../ios.h" #endif +struct Impl_WidgetDrawBuffer { + SDL_Texture *texture; + iInt2 size; + iBool isValid; + SDL_Texture *oldTarget; + iInt2 oldOrigin; +}; + +static void init_WidgetDrawBuffer(iWidgetDrawBuffer *d) { + d->texture = NULL; + d->size = zero_I2(); + d->isValid = iFalse; + d->oldTarget = NULL; +} + +static void deinit_WidgetDrawBuffer(iWidgetDrawBuffer *d) { + SDL_DestroyTexture(d->texture); +} + +iDefineTypeConstruction(WidgetDrawBuffer) + +static void realloc_WidgetDrawBuffer(iWidgetDrawBuffer *d, SDL_Renderer *render, iInt2 size) { + if (!isEqual_I2(d->size, size)) { + d->size = size; + if (d->texture) { + SDL_DestroyTexture(d->texture); + } + d->texture = SDL_CreateTexture(render, + SDL_PIXELFORMAT_RGBA8888, + SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET, + size.x, + size.y); + SDL_SetTextureBlendMode(d->texture, SDL_BLENDMODE_BLEND); + d->isValid = iFalse; + } +} + +static void release_WidgetDrawBuffer(iWidgetDrawBuffer *d) { + if (d->texture) { + SDL_DestroyTexture(d->texture); + d->texture = NULL; + } + d->size = zero_I2(); + d->isValid = iFalse; +} + +static iRect boundsForDraw_Widget_(const iWidget *d) { + iRect bounds = bounds_Widget(d); + if (d->flags & drawBackgroundToBottom_WidgetFlag) { + bounds.size.y = iMaxi(bounds.size.y, size_Root(d->root).y - top_Rect(bounds)); + } + return bounds; +} + +static iBool checkDrawBuffer_Widget_(const iWidget *d) { + return d->drawBuf && d->drawBuf->isValid && + isEqual_I2(d->drawBuf->size, boundsForDraw_Widget_(d).size); +} + +/*----------------------------------------------------------------------------------------------*/ + static void printInfo_Widget_(const iWidget *); void releaseChildren_Widget(iWidget *d) { @@ -66,6 +127,7 @@ void init_Widget(iWidget *d) { d->children = NULL; d->parent = NULL; d->commandHandler = NULL; + d->drawBuf = NULL; iZap(d->padding); } @@ -82,6 +144,7 @@ static void visualOffsetAnimation_Widget_(void *ptr) { void deinit_Widget(iWidget *d) { releaseChildren_Widget(d); + delete_WidgetDrawBuffer(d->drawBuf); #if 0 && !defined (NDEBUG) printf("widget %p (%s) deleted (on top:%d)\n", d, cstr_String(&d->id), d->flags & keepOnTop_WidgetFlag ? 1 : 0); @@ -1036,7 +1099,8 @@ iBool scrollOverflow_Widget(iWidget *d, int delta) { const iInt2 newPos = windowToInner_Widget(d->parent, bounds.pos); if (!isEqual_I2(newPos, d->rect.pos)) { d->rect.pos = newPos; - refresh_Widget(d); +// refresh_Widget(d); + postRefresh_App(); } return height_Rect(bounds) > height_Rect(winRect); } @@ -1077,6 +1141,9 @@ iBool processEvent_Widget(iWidget *d, const SDL_Event *ev) { } if (ev->user.code == command_UserEventCode) { const char *cmd = command_UserEvent(ev); + if (d->drawBuf && equal_Command(cmd, "theme.changed")) { + d->drawBuf->isValid = iFalse; + } if (d->flags & (leftEdgeDraggable_WidgetFlag | rightEdgeDraggable_WidgetFlag) && isVisible_Widget(d) && ~d->flags & disabled_WidgetFlag && equal_Command(cmd, "edgeswipe.moved")) { @@ -1147,14 +1214,13 @@ int backgroundFadeColor_Widget(void) { } } -void drawBackground_Widget(const iWidget *d) { - if (d->flags & noBackground_WidgetFlag) { - return; - } - if (d->flags & hidden_WidgetFlag && ~d->flags & visualOffset_WidgetFlag) { - return; - } - /* Popup menus have a shadowed border. */ +iLocalDef iBool isDrawn_Widget_(const iWidget *d) { + return ~d->flags & hidden_WidgetFlag || d->flags & visualOffset_WidgetFlag; +} + +static void drawLayerEffects_Widget_(const iWidget *d) { + /* Layered effects are not buffered, so they are drawn here separately. */ + iAssert(isDrawn_Widget_(d)); 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) { @@ -1163,13 +1229,12 @@ void drawBackground_Widget(const iWidget *d) { shadowBorder = iFalse; } } + const iBool isFaded = fadeBackground && ~d->flags & noFadeBackground_WidgetFlag; if (shadowBorder && ~d->flags & noShadowBorder_WidgetFlag) { iPaint p; init_Paint(&p); drawSoftShadow_Paint(&p, bounds_Widget(d), 12 * gap_UI, black_ColorId, 30); } - const iBool isFaded = fadeBackground && - ~d->flags & noFadeBackground_WidgetFlag; if (isFaded) { iPaint p; init_Paint(&p); @@ -1183,10 +1248,20 @@ void drawBackground_Widget(const iWidget *d) { fillRect_Paint(&p, rect_Root(d->root), backgroundFadeColor_Widget()); SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE); } +} + +void drawBackground_Widget(const iWidget *d) { + if (d->flags & noBackground_WidgetFlag) { + return; + } + if (!isDrawn_Widget_(d)) { + return; + } + /* Popup menus have a shadowed border. */ if (d->bgColor >= 0 || d->frameColor >= 0) { iRect rect = bounds_Widget(d); if (d->flags & drawBackgroundToBottom_WidgetFlag) { - rect.size.y = size_Root(d->root).y - top_Rect(rect); + rect.size.y = iMax(rect.size.y, size_Root(d->root).y - top_Rect(rect)); } iPaint p; init_Paint(&p); @@ -1242,8 +1317,54 @@ void drawBackground_Widget(const iWidget *d) { } } -iLocalDef iBool isDrawn_Widget_(const iWidget *d) { - return ~d->flags & hidden_WidgetFlag || d->flags & visualOffset_WidgetFlag; +int drawCount_; + +static iBool isRoot_Widget_(const iWidget *d) { + return d == d->root->widget; +} + +iLocalDef iBool isFullyContainedByOther_Rect(const iRect d, const iRect other) { + if (isEmpty_Rect(other)) { + /* Nothing is contained by empty. */ + return iFalse; + } + if (isEmpty_Rect(d)) { + /* Empty is fully contained by anything. */ + return iTrue; + } + return equal_Rect(intersect_Rect(d, other), d); +} + +static void addToPotentiallyVisible_Widget_(const iWidget *d, iPtrArray *pvs, iRect *fullyMasked) { + if (isDrawn_Widget_(d)) { + iRect bounds = bounds_Widget(d); + if (d->flags & drawBackgroundToBottom_WidgetFlag) { + bounds.size.y = size_Root(d->root).y - top_Rect(bounds); + } + if (isFullyContainedByOther_Rect(bounds, *fullyMasked)) { + return; /* can't be seen */ + } + pushBack_PtrArray(pvs, d); + if (d->bgColor >= 0 && ~d->flags & noBackground_WidgetFlag && + isFullyContainedByOther_Rect(*fullyMasked, bounds)) { + *fullyMasked = bounds; + } + } +} + +static void findPotentiallyVisible_Widget_(const iWidget *d, iPtrArray *pvs) { + iRect fullyMasked = zero_Rect(); + if (isRoot_Widget_(d)) { + iReverseConstForEach(PtrArray, i, onTop_Root(d->root)) { + addToPotentiallyVisible_Widget_(i.ptr, pvs, &fullyMasked); + } + } + iReverseConstForEach(ObjectList, i, d->children) { + const iWidget *child = i.object; + if (~child->flags & keepOnTop_WidgetFlag) { + addToPotentiallyVisible_Widget_(child, pvs, &fullyMasked); + } + } } void drawChildren_Widget(const iWidget *d) { @@ -1253,21 +1374,85 @@ void drawChildren_Widget(const iWidget *d) { iConstForEach(ObjectList, i, d->children) { const iWidget *child = constAs_Widget(i.object); if (~child->flags & keepOnTop_WidgetFlag && isDrawn_Widget_(child)) { + drawCount_++; class_Widget(child)->draw(child); } } +} + +void drawRoot_Widget(const iWidget *d) { + iAssert(d == d->root->widget); /* Root draws the on-top widgets on top of everything else. */ - if (d == d->root->widget) { - iConstForEach(PtrArray, i, onTop_Root(d->root)) { - const iWidget *top = *i.value; - class_Widget(top)->draw(top); - } - } + iPtrArray pvs; + init_PtrArray(&pvs); + findPotentiallyVisible_Widget_(d, &pvs); + iReverseConstForEach(PtrArray, i, &pvs) { + drawCount_++; + class_Widget(i.ptr)->draw(i.ptr); + } + deinit_PtrArray(&pvs); +} + +void setDrawBufferEnabled_Widget(iWidget *d, iBool enable) { + if (enable && !d->drawBuf) { + d->drawBuf = new_WidgetDrawBuffer(); + } + else if (!enable && d->drawBuf) { + delete_WidgetDrawBuffer(d->drawBuf); + d->drawBuf = NULL; + } +} + +static void beginBufferDraw_Widget_(const iWidget *d) { + if (d->drawBuf) { +// printf("[%p] drawbuffer update %d\n", d, d->drawBuf->isValid); + const iRect bounds = bounds_Widget(d); + SDL_Renderer *render = renderer_Window(get_Window()); + d->drawBuf->oldTarget = SDL_GetRenderTarget(render); + d->drawBuf->oldOrigin = origin_Paint; + realloc_WidgetDrawBuffer(d->drawBuf, render, boundsForDraw_Widget_(d).size); + SDL_SetRenderTarget(render, d->drawBuf->texture); + //SDL_SetRenderDrawColor(render, 255, 0, 0, 128); + SDL_SetRenderDrawColor(render, 0, 0, 0, 0); + SDL_RenderClear(render); + origin_Paint = neg_I2(bounds.pos); /* with current visual offset */ +// printf("beginBufferDraw: origin %d,%d\n", origin_Paint.x, origin_Paint.y); +// fflush(stdout); + } +} + +static void endBufferDraw_Widget_(const iWidget *d) { + if (d->drawBuf) { + d->drawBuf->isValid = iTrue; + SDL_SetRenderTarget(renderer_Window(get_Window()), d->drawBuf->oldTarget); + origin_Paint = d->drawBuf->oldOrigin; +// printf("endBufferDraw: origin %d,%d\n", origin_Paint.x, origin_Paint.y); +// fflush(stdout); + } } void draw_Widget(const iWidget *d) { - drawBackground_Widget(d); - drawChildren_Widget(d); + if (!isDrawn_Widget_(d)) { + if (d->drawBuf) { +// printf("[%p] drawBuffer released\n", d); + release_WidgetDrawBuffer(d->drawBuf); + } + return; + } + drawLayerEffects_Widget_(d); + if (!d->drawBuf || !checkDrawBuffer_Widget_(d)) { + beginBufferDraw_Widget_(d); + drawBackground_Widget(d); + drawChildren_Widget(d); + endBufferDraw_Widget_(d); + } + if (d->drawBuf) { + 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, + d->drawBuf->size.x, d->drawBuf->size.y }); + } } iAny *addChild_Widget(iWidget *d, iAnyObject *child) { @@ -1659,12 +1844,20 @@ void postCommand_Widget(const iAnyObject *d, const char *cmd, ...) { deinit_String(&str); } -void refresh_Widget(const iAnyObject *d) { +void refresh_Widget(const iAnyObject *d) { /* TODO: Could be widget specific, if parts of the tree are cached. */ /* TODO: The visbuffer in DocumentWidget and ListWidget could be moved to be a general purpose feature of Widget. */ iAssert(isInstance_Object(d, &Class_Widget)); - iUnused(d); + /* Mark draw buffers invalid. */ + for (const iWidget *w = d; w; w = w->parent) { + if (w->drawBuf) { +// if (w->drawBuf->isValid) { +// printf("[%p] drawbuffer invalidated by %p\n", w, d); fflush(stdout); +// } + w->drawBuf->isValid = iFalse; + } + } postRefresh_App(); } diff --git a/src/ui/widget.h b/src/ui/widget.h index 1a944c0a..fd4d8898 100644 --- a/src/ui/widget.h +++ b/src/ui/widget.h @@ -131,6 +131,8 @@ enum iWidgetFocusDir { backward_WidgetFocusDir, }; +iDeclareType(WidgetDrawBuffer) + struct Impl_Widget { iObject object; iString id; @@ -148,6 +150,7 @@ struct Impl_Widget { iWidget * parent; iBool (*commandHandler)(iWidget *, const char *); iRoot * root; + iWidgetDrawBuffer *drawBuf; }; iDeclareObjectConstruction(Widget) @@ -203,6 +206,12 @@ size_t childCount_Widget (const iWidget *); void draw_Widget (const iWidget *); void drawBackground_Widget (const iWidget *); void drawChildren_Widget (const iWidget *); +void drawRoot_Widget (const iWidget *); /* root only */ +void setDrawBufferEnabled_Widget (iWidget *, iBool enable); + +iLocalDef iBool isDrawBufferEnabled_Widget(const iWidget *d) { + return d && d->drawBuf; +} iLocalDef int width_Widget(const iAnyObject *d) { if (d) { diff --git a/src/ui/window.c b/src/ui/window.c index 096853cc..3385f436 100644 --- a/src/ui/window.c +++ b/src/ui/window.c @@ -1060,12 +1060,13 @@ void draw_Window(iWindow *d) { d->frameTime = SDL_GetTicks(); if (isExposed_Window(d)) { d->isInvalidated = iFalse; + extern int drawCount_; iForIndices(i, d->roots) { iRoot *root = d->roots[i]; if (root) { setCurrent_Root(root); unsetClip_Paint(&p); /* update clip to current root */ - draw_Widget(root->widget); + drawRoot_Widget(root->widget); #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) /* App icon. */ const iWidget *appIcon = findChild_Widget(root->widget, "winbar.icon"); @@ -1105,6 +1106,10 @@ void draw_Window(iWindow *d) { } } setCurrent_Root(NULL); +#if !defined (NDEBUG) + draw_Text(defaultBold_FontId, zero_I2(), red_ColorId, "%d", drawCount_); + drawCount_ = 0; +#endif } #if 0 /* Text cache debugging. */ { -- cgit v1.2.3