From 859fad2c6d5013ace7fcb749b591468dd0b65612 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Sat, 24 Oct 2020 08:18:46 +0300 Subject: DocumentWidget: Page load progress indicator Show clearly that something is happening, even though we don't know the exact duration of the operation. --- CMakeLists.txt | 2 + src/ui/documentwidget.c | 4 ++ src/ui/indicatorwidget.c | 158 +++++++++++++++++++++++++++++++++++++++++++++++ src/ui/indicatorwidget.h | 28 +++++++++ src/ui/util.c | 90 ++++++++++++++------------- src/ui/widget.h | 7 +++ 6 files changed, 247 insertions(+), 42 deletions(-) create mode 100644 src/ui/indicatorwidget.c create mode 100644 src/ui/indicatorwidget.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b71be6ab..ed064730 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -129,6 +129,8 @@ set (SOURCES src/ui/command.h src/ui/documentwidget.c src/ui/documentwidget.h + src/ui/indicatorwidget.c + src/ui/indicatorwidget.h src/ui/listwidget.c src/ui/listwidget.h src/ui/lookupwidget.c diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 1f84aed6..bdf7d282 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -30,6 +30,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "gmrequest.h" #include "gmutil.h" #include "history.h" +#include "indicatorwidget.h" #include "inputwidget.h" #include "keys.h" #include "labelwidget.h" @@ -247,6 +248,9 @@ void init_DocumentWidget(iDocumentWidget *d) { addChild_Widget(w, iClob(d->scroll = new_ScrollWidget())); d->menu = NULL; /* created when clicking */ d->playerMenu = NULL; + addChildFlags_Widget(w, + iClob(new_IndicatorWidget()), + resizeToParentWidth_WidgetFlag | resizeToParentHeight_WidgetFlag); #if !defined (iPlatformApple) /* in system menu */ addAction_Widget(w, reload_KeyShortcut, "navigate.reload"); addAction_Widget(w, SDLK_w, KMOD_PRIMARY, "tabs.close"); diff --git a/src/ui/indicatorwidget.c b/src/ui/indicatorwidget.c new file mode 100644 index 00000000..d43e23d9 --- /dev/null +++ b/src/ui/indicatorwidget.c @@ -0,0 +1,158 @@ +/* Copyright 2020 Jaakko Keränen + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + +#include "indicatorwidget.h" +#include "paint.h" +#include "util.h" +#include "app.h" +#include "command.h" + +#include + +static int timerId_; /* common timer for all indicators */ +static int animCount_; /* number of animating indicators */ + +static uint32_t postRefresh_(uint32_t interval, void *context) { + iUnused(context); + postRefresh_App(); + return interval; +} + +static void startTimer_(void) { + animCount_++; + if (!timerId_) { + timerId_ = SDL_AddTimer(1000 / 60, postRefresh_, NULL); + } +} + +static void stopTimer_(void) { + iAssert(animCount_ > 0); + if (--animCount_ == 0) { + iAssert(timerId_); + SDL_RemoveTimer(timerId_); + timerId_ = 0; + } +} + +struct Impl_IndicatorWidget{ + iWidget widget; + iAnim pos; +}; + +iDefineObjectConstruction(IndicatorWidget) + +iLocalDef iBool isActive_IndicatorWidget_(const iIndicatorWidget *d) { + return isSelected_Widget(d); +} + +static void setActive_IndicatorWidget_(iIndicatorWidget *d, iBool set) { + setFlags_Widget(as_Widget(d), selected_WidgetFlag, set); +} + +void init_IndicatorWidget(iIndicatorWidget *d) { + iWidget *w = &d->widget; + init_Widget(w); + init_Anim(&d->pos, 0); +} + +static void startTimer_IndicatorWidget_(iIndicatorWidget *d) { + if (!isActive_IndicatorWidget_(d)) { + startTimer_(); + setActive_IndicatorWidget_(d, iTrue); + } +} + +static void stopTimer_IndicatorWidget_(iIndicatorWidget *d) { + if (isActive_IndicatorWidget_(d)) { + stopTimer_(); + setActive_IndicatorWidget_(d, iFalse); + } +} + +void deinit_IndicatorWidget(iIndicatorWidget *d) { + stopTimer_IndicatorWidget_(d); +} + +static iBool isCompleted_IndicatorWidget_(const iIndicatorWidget *d) { + return targetValue_Anim(&d->pos) == 1.0f; +} + +void draw_IndicatorWidget_(const iIndicatorWidget *d) { + const float pos = value_Anim(&d->pos); + if (pos > 0.0f && pos < 1.0f) { + const iWidget *w = &d->widget; + const iRect rect = innerBounds_Widget(w); + iPaint p; + init_Paint(&p); + drawHLine_Paint(&p, + topLeft_Rect(rect), + pos * width_Rect(rect), + isCompleted_IndicatorWidget_(d) ? uiTextAction_ColorId + : uiTextCaution_ColorId); + } +} + +iBool processEvent_IndicatorWidget_(iIndicatorWidget *d, const SDL_Event *ev) { + iWidget *w = &d->widget; + if (ev->type == SDL_USEREVENT && ev->user.code == refresh_UserEventCode) { + if (isFinished_Anim(&d->pos)) { + stopTimer_IndicatorWidget_(d); + } + } + else if (isCommand_SDLEvent(ev)) { + const char *cmd = command_UserEvent(ev); + if (startsWith_CStr(cmd, "document.request.")) { + if (pointerLabel_Command(cmd, "doc") == parent_Widget(w)) { + cmd += 17; + if (equal_Command(cmd, "started")) { + setValue_Anim(&d->pos, 0, 0); + setValue_Anim(&d->pos, 0.75f, 4000); + setFlags_Anim(&d->pos, easeOut_AnimFlag, iTrue); + startTimer_IndicatorWidget_(d); + } + else if (equal_Command(cmd, "finished")) { + if (value_Anim(&d->pos) > 0.01f) { + setValue_Anim(&d->pos, 1.0f, 250); + setFlags_Anim(&d->pos, easeOut_AnimFlag, iFalse); + startTimer_IndicatorWidget_(d); + } + else { + setValue_Anim(&d->pos, 0, 0); + stopTimer_IndicatorWidget_(d); + refresh_Widget(d); + } + } + else if (equal_Command(cmd, "cancelled")) { + setValue_Anim(&d->pos, 0, 0); + stopTimer_IndicatorWidget_(d); + refresh_Widget(d); + } + } + } + } + return iFalse; +} + +iBeginDefineSubclass(IndicatorWidget, Widget) + .draw = (iAny *) draw_IndicatorWidget_, + .processEvent = (iAny *) processEvent_IndicatorWidget_, +iEndDefineSubclass(IndicatorWidget) diff --git a/src/ui/indicatorwidget.h b/src/ui/indicatorwidget.h new file mode 100644 index 00000000..a3d9af39 --- /dev/null +++ b/src/ui/indicatorwidget.h @@ -0,0 +1,28 @@ +/* Copyright 2020 Jaakko Keränen + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + +#pragma once + +#include "widget.h" + +iDeclareWidgetClass(IndicatorWidget) +iDeclareObjectConstruction(IndicatorWidget) diff --git a/src/ui/util.c b/src/ui/util.c index 38124b22..27950c5e 100644 --- a/src/ui/util.c +++ b/src/ui/util.c @@ -135,54 +135,15 @@ iBool isFinished_Anim(const iAnim *d) { } void init_Anim(iAnim *d, float value) { - d->due = d->when = SDL_GetTicks(); // frameTime_Window(get_Window()); + d->due = d->when = SDL_GetTicks(); d->from = d->to = value; d->flags = 0; } -void setValue_Anim(iAnim *d, float to, uint32_t span) { - if (fabsf(to - d->to) > 0.00001f) { - const uint32_t now = SDL_GetTicks(); - d->from = value_Anim(d); - d->to = to; - d->when = now; - d->due = now + span; - } -} - iLocalDef float pos_Anim_(const iAnim *d, uint32_t now) { return (float) (now - d->when) / (float) (d->due - d->when); } -void setValueEased_Anim(iAnim *d, float to, uint32_t span) { - if (fabsf(to - d->to) <= 0.00001f) { - d->to = to; /* Pretty much unchanged. */ - return; - } - const uint32_t now = SDL_GetTicks(); - if (isFinished_Anim(d)) { - d->from = d->to; - d->when = now; - d->flags = easeBoth_AnimFlag; - } - else { - d->from = value_Anim(d); - d->when = frameTime_Window(get_Window()); /* to match the timing of value_Anim */ - d->flags = easeOut_AnimFlag; - } - d->to = to; - d->due = now + span; -} - -void setFlags_Anim(iAnim *d, int flags, iBool set) { - iChangeFlags(d->flags, flags, set); -} - -void stop_Anim(iAnim *d) { - d->from = d->to = value_Anim(d); - d->when = d->due = SDL_GetTicks(); -} - iLocalDef float easeIn_(float t) { return t * t; } @@ -198,8 +159,7 @@ iLocalDef float easeBoth_(float t) { return 0.5f + easeOut_((t - 0.5f) * 2.0f) * 0.5f; } -float value_Anim(const iAnim *d) { - const uint32_t now = frameTime_Window(get_Window()); +static float valueAt_Anim_(const iAnim *d, const uint32_t now) { if (now >= d->due) { return d->to; } @@ -219,6 +179,52 @@ float value_Anim(const iAnim *d) { return d->from * (1.0f - t) + d->to * t; } +void setValue_Anim(iAnim *d, float to, uint32_t span) { + if (span == 0) { + d->from = d->to = to; + d->when = d->due = SDL_GetTicks(); + } + else if (fabsf(to - d->to) > 0.00001f) { + const uint32_t now = SDL_GetTicks(); + d->from = valueAt_Anim_(d, now); + d->to = to; + d->when = now; + d->due = now + span; + } +} + +void setValueEased_Anim(iAnim *d, float to, uint32_t span) { + if (fabsf(to - d->to) <= 0.00001f) { + d->to = to; /* Pretty much unchanged. */ + return; + } + const uint32_t now = SDL_GetTicks(); + if (isFinished_Anim(d)) { + d->from = d->to; + d->flags = easeBoth_AnimFlag; + } + else { + d->from = valueAt_Anim_(d, now); + d->flags = easeOut_AnimFlag; + } + d->to = to; + d->when = now; + d->due = now + span; +} + +void setFlags_Anim(iAnim *d, int flags, iBool set) { + iChangeFlags(d->flags, flags, set); +} + +void stop_Anim(iAnim *d) { + d->from = d->to = value_Anim(d); + d->when = d->due = SDL_GetTicks(); +} + +float value_Anim(const iAnim *d) { + return valueAt_Anim_(d, frameTime_Window(get_Window())); +} + /*-----------------------------------------------------------------------------------------------*/ void init_Click(iClick *d, iAnyObject *widget, int button) { diff --git a/src/ui/widget.h b/src/ui/widget.h index f39612ed..a1a38f28 100644 --- a/src/ui/widget.h +++ b/src/ui/widget.h @@ -160,6 +160,13 @@ iLocalDef iObjectList *children_Widget(iAnyObject *d) { iAssert(isInstance_Object(d, &Class_Widget)); return ((iWidget *) d)->children; } +iLocalDef iWidget *parent_Widget(const iAnyObject *d) { + if (d) { + iAssert(isInstance_Object(d, &Class_Widget)); + return ((iWidget *) d)->parent; + } + return NULL; +} iBool isVisible_Widget (const iAnyObject *); iBool isDisabled_Widget (const iAnyObject *); -- cgit v1.2.3