From fa174461abdc5c33de16428109c7d46b4f150093 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Thu, 18 Mar 2021 11:26:11 +0200 Subject: Scrollbar fading and periodic commands Added a new mechanism to issue periodic but not per-frame commands. This is used for main-thread operations like checking if it's time to fade away the scrollbars. Scrollbars are faded away completely on Apple platforms. Adjusted list right margins accordingly. --- src/ui/listwidget.c | 10 ++++-- src/ui/scrollwidget.c | 85 +++++++++++++++++++++++++++++++++++++++++++++++--- src/ui/sidebarwidget.c | 11 +++++-- src/ui/util.c | 3 ++ src/ui/util.h | 1 - src/ui/widget.c | 19 +++++++---- src/ui/widget.h | 31 +++++++++--------- src/ui/window.c | 2 +- 8 files changed, 128 insertions(+), 34 deletions(-) (limited to 'src/ui') diff --git a/src/ui/listwidget.c b/src/ui/listwidget.c index 237562ca..f351c3b3 100644 --- a/src/ui/listwidget.c +++ b/src/ui/listwidget.c @@ -379,9 +379,13 @@ static void draw_ListWidget_(const iListWidget *d) { beginTarget_Paint(&p, buf->texture); fillRect_Paint(&p, (iRect){ zero_I2(), d->visBuf->texSize }, bg[i]); } - const iRect sbBlankRect = - { init_I2(d->visBuf->texSize.x - scrollBarWidth_ListWidget(d), 0), - init_I2(scrollBarWidth_ListWidget(d), d->itemHeight) }; +#if defined (iPlatformApple) + const int blankWidth = 0; /* scrollbars fade away */ +#else + const int blankWidth = scrollBarWidth_ListWidget(d); +#endif + const iRect sbBlankRect = { init_I2(d->visBuf->texSize.x - blankWidth, 0), + init_I2(blankWidth, d->itemHeight) }; iConstForEach(IntSet, v, &d->invalidItems) { const size_t index = *v.value; if (contains_Range(&drawItems, index)) { diff --git a/src/ui/scrollwidget.c b/src/ui/scrollwidget.c index e887ddbf..a08b58d7 100644 --- a/src/ui/scrollwidget.c +++ b/src/ui/scrollwidget.c @@ -23,9 +23,23 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "scrollwidget.h" #include "paint.h" #include "util.h" +#include "periodic.h" +#include "app.h" + +#include iDefineObjectConstruction(ScrollWidget) +static float minOpacity_(void) { +#if !defined (iPlatformApple) + if (deviceType_App() == desktop_AppDeviceType) { + /* Don't fade the scrollbars completely. */ + return 0.333f; + } +#endif + return 0.0f; +} + struct Impl_ScrollWidget { iWidget widget; iRangei range; @@ -33,12 +47,23 @@ struct Impl_ScrollWidget { int thumbSize; iClick click; int startThumb; + iAnim opacity; + uint32_t fadeStart; + iBool willCheckFade; }; static void updateMetrics_ScrollWidget_(iScrollWidget *d) { as_Widget(d)->rect.size.x = gap_UI * 3; } +static void animateOpacity_ScrollWidget_(void *ptr) { + iScrollWidget *d = ptr; + if (!isFinished_Anim(&d->opacity)) { + addTicker_App(animateOpacity_ScrollWidget_, ptr); + } + refresh_Widget(ptr); +} + void init_ScrollWidget(iScrollWidget *d) { iWidget *w = as_Widget(d); init_Widget(w); @@ -49,10 +74,13 @@ void init_ScrollWidget(iScrollWidget *d) { iTrue); updateMetrics_ScrollWidget_(d); init_Click(&d->click, d, SDL_BUTTON_LEFT); + init_Anim(&d->opacity, minOpacity_()); + d->willCheckFade = iFalse; } void deinit_ScrollWidget(iScrollWidget *d) { - iUnused(d); + remove_Periodic(periodic_App(), d); + removeTicker_App(animateOpacity_ScrollWidget_, d); } static int thumbSize_ScrollWidget_(const iScrollWidget *d) { @@ -74,10 +102,26 @@ static iRect thumbRect_ScrollWidget_(const iScrollWidget *d) { return rect; } +static void unfade_ScrollWidget_(iScrollWidget *d, float opacity) { + d->fadeStart = SDL_GetTicks() + 1000; + if (targetValue_Anim(&d->opacity) < opacity) { + setValue_Anim(&d->opacity, opacity, 66); + addTicker_App(animateOpacity_ScrollWidget_, d); + } + if (!d->willCheckFade) { + d->willCheckFade = iTrue; + add_Periodic(periodic_App(), d, "scrollbar.fade"); + } + refresh_Widget(d); +} + static void checkVisible_ScrollWidget_(iScrollWidget *d) { - setFlags_Widget(as_Widget(d), - hidden_WidgetFlag, - d->thumbSize != 0 ? height_Rect(thumbRect_ScrollWidget_(d)) == 0 : iTrue); + const iBool wasHidden = isVisible_Widget(d); + const iBool isHidden = d->thumbSize != 0 ? height_Rect(thumbRect_ScrollWidget_(d)) == 0 : iTrue; + setFlags_Widget(as_Widget(d), hidden_WidgetFlag, isHidden); + if (wasHidden && !isHidden) { + unfade_ScrollWidget_(d, 1.0f); + } } void setRange_ScrollWidget(iScrollWidget *d, iRangei range) { @@ -87,9 +131,13 @@ void setRange_ScrollWidget(iScrollWidget *d, iRangei range) { } void setThumb_ScrollWidget(iScrollWidget *d, int thumb, int thumbSize) { + const int oldThumb = d->thumb; d->thumb = thumb; d->thumbSize = thumbSize; checkVisible_ScrollWidget_(d); + if (oldThumb != d->thumb && thumbSize) { + unfade_ScrollWidget_(d, 1.0f); + } } static iBool processEvent_ScrollWidget_(iScrollWidget *d, const SDL_Event *ev) { @@ -97,6 +145,25 @@ static iBool processEvent_ScrollWidget_(iScrollWidget *d, const SDL_Event *ev) { if (isMetricsChange_UserEvent(ev)) { updateMetrics_ScrollWidget_(d); } + if (ev->type == SDL_MOUSEMOTION) { + const iInt2 mouse = init_I2(ev->motion.x, ev->motion.y); + const iBool isNearby = containsExpanded_Widget(&d->widget, mouse, 4 * gap_UI); + const iBool isOver = isNearby && contains_Rect(thumbRect_ScrollWidget_(d), mouse); + if (isNearby) { + unfade_ScrollWidget_(d, isOver ? 1.0f : 0.4f); + } + } + if (isCommand_UserEvent(ev, "scrollbar.fade")) { + if (d->willCheckFade && SDL_GetTicks() > d->fadeStart) { + setValue_Anim(&d->opacity, minOpacity_(), 200); + remove_Periodic(periodic_App(), d); + d->willCheckFade = iFalse; + if (!isFinished_Anim(&d->opacity)) { + addTicker_App(animateOpacity_ScrollWidget_, d); + } + } + return iFalse; + } switch (processEvent_Click(&d->click, ev)) { case started_ClickResult: setFlags_Widget(w, pressed_WidgetFlag, iTrue); @@ -146,10 +213,18 @@ static void draw_ScrollWidget_(const iScrollWidget *d) { if (bounds.size.x > 0) { iPaint p; init_Paint(&p); - //drawVLine_Paint(&p, topLeft_Rect(bounds), height_Rect(bounds), uiSeparator_ColorId); + /* Blend if opacity is not at maximum. */ + p.alpha = 255 * value_Anim(&d->opacity); + SDL_Renderer *render = renderer_Window(get_Window()); + if (p.alpha < 255) { + SDL_SetRenderDrawBlendMode(render, SDL_BLENDMODE_BLEND); + } const iRect thumbRect = shrunk_Rect( thumbRect_ScrollWidget_(d), init_I2(isPressed ? gap_UI : (gap_UI * 4 / 3), gap_UI / 2)); fillRect_Paint(&p, thumbRect, isPressed ? uiBackgroundPressed_ColorId : tmQuote_ColorId); + if (p.alpha < 255) { + SDL_SetRenderDrawBlendMode(render, SDL_BLENDMODE_NONE); + } } } diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c index 802669c7..ac119575 100644 --- a/src/ui/sidebarwidget.c +++ b/src/ui/sidebarwidget.c @@ -143,7 +143,7 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) { iDate on; initCurrent_Time(&now); init_Date(&on, &now); - const int thisYear = on.year; + const iDate today = on; iZap(on); size_t numItems = 0; iConstForEach(PtrArray, i, listEntries_Feeds()) { @@ -1306,6 +1306,11 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect, const iBool isHover = isHover_Widget(constAs_Widget(list)) && constHoverItem_ListWidget(list) == d; const int scrollBarWidth = scrollBarWidth_ListWidget(list); +#if defined (iPlatformApple) + const int blankWidth = 0; +#else + const int blankWidth = scrollBarWidth; +#endif const int itemHeight = height_Rect(itemRect); const int iconColor = isHover ? (isPressing ? uiTextPressed_ColorId : uiIconHover_ColorId) : uiIcon_ColorId; @@ -1345,7 +1350,7 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect, if (d != constItem_ListWidget(list, 0)) { drawHLine_Paint(p, addY_I2(pos, 2 * gap_UI), - width_Rect(itemRect) - scrollBarWidth, + width_Rect(itemRect) - blankWidth, uiSeparator_ColorId); } drawRange_Text( @@ -1463,7 +1468,7 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect, iInt2 drawPos = addY_I2(topLeft_Rect(itemRect), d->id); drawHLine_Paint(p, addY_I2(drawPos, -gap_UI), - width_Rect(itemRect) - scrollBarWidth, + width_Rect(itemRect) - blankWidth, uiSeparator_ColorId); drawRange_Text( uiLabelLargeBold_FontId, diff --git a/src/ui/util.c b/src/ui/util.c index e07e036a..13d1bf78 100644 --- a/src/ui/util.c +++ b/src/ui/util.c @@ -401,6 +401,7 @@ static iBool isCommandIgnoredByMenus_(const char *cmd) { equal_Command(cmd, "document.request.updated") || equal_Command(cmd, "document.request.finished") || equal_Command(cmd, "document.changed") || + equal_Command(cmd, "scrollbar.fade") || equal_Command(cmd, "visited.changed") || (deviceType_App() == desktop_AppDeviceType && equal_Command(cmd, "window.resized")) || equal_Command(cmd, "widget.overflow") || @@ -1634,12 +1635,14 @@ void updateValueInput_Widget(iWidget *d, const char *title, const char *prompt) static iBool messageHandler_(iWidget *msg, const char *cmd) { /* Almost any command dismisses the sheet. */ + /* TODO: Use a "notification" prefix (like `) to ignore all types of commands line this? */ if (!(equal_Command(cmd, "media.updated") || equal_Command(cmd, "media.player.update") || equal_Command(cmd, "bookmarks.request.finished") || equal_Command(cmd, "document.autoreload") || equal_Command(cmd, "document.reload") || equal_Command(cmd, "document.request.updated") || + equal_Command(cmd, "scrollbar.fade") || equal_Command(cmd, "widget.overflow") || startsWith_CStr(cmd, "window."))) { destroy_Widget(msg); diff --git a/src/ui/util.h b/src/ui/util.h index 35e10c6f..09e52a4d 100644 --- a/src/ui/util.h +++ b/src/ui/util.h @@ -102,7 +102,6 @@ struct Impl_Anim { void init_Anim (iAnim *, float value); void setValue_Anim (iAnim *, float to, uint32_t span); -void setValueLinear_Anim (iAnim *, float to, uint32_t span); void setValueEased_Anim (iAnim *, float to, uint32_t span); void setFlags_Anim (iAnim *, int flags, iBool set); void stop_Anim (iAnim *); diff --git a/src/ui/widget.c b/src/ui/widget.c index d5e639fd..4f5d174c 100644 --- a/src/ui/widget.c +++ b/src/ui/widget.c @@ -582,10 +582,17 @@ iInt2 localCoord_Widget(const iWidget *d, iInt2 coord) { } iBool contains_Widget(const iWidget *d, iInt2 coord) { - const iRect bounds = { zero_I2(), addY_I2(d->rect.size, - d->flags & drawBackgroundToBottom_WidgetFlag ? - rootSize_Window(get_Window()).y : 0) }; - return contains_Rect(bounds, localCoord_Widget(d, coord)); + return containsExpanded_Widget(d, coord, 0); +} + +iBool containsExpanded_Widget(const iWidget *d, iInt2 coord, int expand) { + const iRect bounds = { + zero_I2(), + addY_I2(d->rect.size, + d->flags & drawBackgroundToBottom_WidgetFlag ? rootSize_Window(get_Window()).y : 0) + }; + return contains_Rect(expand ? expanded_Rect(bounds, init1_I2(expand)) : bounds, + localCoord_Widget(d, coord)); } iLocalDef iBool isKeyboardEvent_(const SDL_Event *ev) { @@ -854,8 +861,8 @@ void drawBackground_Widget(const iWidget *d) { const iBool isLight = isLight_ColorTheme(colorTheme_App()); p.alpha = isLight ? 0xc : 0x20; SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND); - iRect shadowRect = expanded_Rect(bounds_Widget(d), mulf_I2(gap2_UI, 1)); - shadowRect.pos.y += gap_UI / 4; + iRect shadowRect = expanded_Rect(bounds_Widget(d), mulf_I2(gap2_UI, 8)); +// shadowRect.pos.y += gap_UI * 4; fillRect_Paint(&p, shadowRect, /*isLight ? white_ColorId :*/ black_ColorId); SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE); } diff --git a/src/ui/widget.h b/src/ui/widget.h index 88d75b62..90ccac8e 100644 --- a/src/ui/widget.h +++ b/src/ui/widget.h @@ -161,21 +161,22 @@ void destroy_Widget (iWidget *); /* widget removed and deleted later void destroyPending_Widget (void); void releaseChildren_Widget (iWidget *); -const iString *id_Widget (const iWidget *); -int64_t flags_Widget (const iWidget *); -iRect bounds_Widget (const iWidget *); /* outer bounds */ -iRect innerBounds_Widget (const iWidget *); -iInt2 localCoord_Widget (const iWidget *, iInt2 coord); -iBool contains_Widget (const iWidget *, iInt2 coord); -iAny * hitChild_Widget (const iWidget *, iInt2 coord); -iAny * findChild_Widget (const iWidget *, const char *id); -const iPtrArray *findChildren_Widget (const iWidget *, const char *id); -iAny * findParentClass_Widget(const iWidget *, const iAnyClass *class); -iAny * findFocusable_Widget(const iWidget *startFrom, enum iWidgetFocusDir focusDir); -size_t childCount_Widget (const iWidget *); -void draw_Widget (const iWidget *); -void drawBackground_Widget(const iWidget *); -void drawChildren_Widget (const iWidget *); +const iString *id_Widget (const iWidget *); +int64_t flags_Widget (const iWidget *); +iRect bounds_Widget (const iWidget *); /* outer bounds */ +iRect innerBounds_Widget (const iWidget *); +iInt2 localCoord_Widget (const iWidget *, iInt2 coord); +iBool contains_Widget (const iWidget *, iInt2 coord); +iBool containsExpanded_Widget (const iWidget *, iInt2 coord, int expand); +iAny * hitChild_Widget (const iWidget *, iInt2 coord); +iAny * findChild_Widget (const iWidget *, const char *id); +const iPtrArray *findChildren_Widget (const iWidget *, const char *id); +iAny * findParentClass_Widget (const iWidget *, const iAnyClass *class); +iAny * findFocusable_Widget (const iWidget *startFrom, enum iWidgetFocusDir focusDir); +size_t childCount_Widget (const iWidget *); +void draw_Widget (const iWidget *); +void drawBackground_Widget (const iWidget *); +void drawChildren_Widget (const iWidget *); iLocalDef int width_Widget(const iAnyObject *d) { if (d) { diff --git a/src/ui/window.c b/src/ui/window.c index 1c0c0394..e7945c62 100644 --- a/src/ui/window.c +++ b/src/ui/window.c @@ -1827,7 +1827,7 @@ void draw_Window(iWindow *d) { } } #endif - const int winFlags = SDL_GetWindowFlags(d->win); + const int winFlags = SDL_GetWindowFlags(d->win); const iBool gotFocus = (winFlags & SDL_WINDOW_INPUT_FOCUS) != 0; /* Clear the window. The clear color is visible as a border around the window when the custom frame is being used. */ { -- cgit v1.2.3