diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-10-24 08:18:46 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-10-24 08:18:46 +0300 |
commit | 859fad2c6d5013ace7fcb749b591468dd0b65612 (patch) | |
tree | 7a743d1bc638ec014fd5006af5b0c2aa6c8124bf | |
parent | 57cc6baba013965b1e9aed244ed40ee604e8a872 (diff) |
DocumentWidget: Page load progress indicator
Show clearly that something is happening, even though we don't know the exact duration of the operation.
-rw-r--r-- | CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 4 | ||||
-rw-r--r-- | src/ui/indicatorwidget.c | 158 | ||||
-rw-r--r-- | src/ui/indicatorwidget.h | 28 | ||||
-rw-r--r-- | src/ui/util.c | 90 | ||||
-rw-r--r-- | src/ui/widget.h | 7 |
6 files changed, 247 insertions, 42 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index b71be6ab..ed064730 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt | |||
@@ -129,6 +129,8 @@ set (SOURCES | |||
129 | src/ui/command.h | 129 | src/ui/command.h |
130 | src/ui/documentwidget.c | 130 | src/ui/documentwidget.c |
131 | src/ui/documentwidget.h | 131 | src/ui/documentwidget.h |
132 | src/ui/indicatorwidget.c | ||
133 | src/ui/indicatorwidget.h | ||
132 | src/ui/listwidget.c | 134 | src/ui/listwidget.c |
133 | src/ui/listwidget.h | 135 | src/ui/listwidget.h |
134 | src/ui/lookupwidget.c | 136 | 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. */ | |||
30 | #include "gmrequest.h" | 30 | #include "gmrequest.h" |
31 | #include "gmutil.h" | 31 | #include "gmutil.h" |
32 | #include "history.h" | 32 | #include "history.h" |
33 | #include "indicatorwidget.h" | ||
33 | #include "inputwidget.h" | 34 | #include "inputwidget.h" |
34 | #include "keys.h" | 35 | #include "keys.h" |
35 | #include "labelwidget.h" | 36 | #include "labelwidget.h" |
@@ -247,6 +248,9 @@ void init_DocumentWidget(iDocumentWidget *d) { | |||
247 | addChild_Widget(w, iClob(d->scroll = new_ScrollWidget())); | 248 | addChild_Widget(w, iClob(d->scroll = new_ScrollWidget())); |
248 | d->menu = NULL; /* created when clicking */ | 249 | d->menu = NULL; /* created when clicking */ |
249 | d->playerMenu = NULL; | 250 | d->playerMenu = NULL; |
251 | addChildFlags_Widget(w, | ||
252 | iClob(new_IndicatorWidget()), | ||
253 | resizeToParentWidth_WidgetFlag | resizeToParentHeight_WidgetFlag); | ||
250 | #if !defined (iPlatformApple) /* in system menu */ | 254 | #if !defined (iPlatformApple) /* in system menu */ |
251 | addAction_Widget(w, reload_KeyShortcut, "navigate.reload"); | 255 | addAction_Widget(w, reload_KeyShortcut, "navigate.reload"); |
252 | addAction_Widget(w, SDLK_w, KMOD_PRIMARY, "tabs.close"); | 256 | 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 @@ | |||
1 | /* Copyright 2020 Jaakko Keränen <jaakko.keranen@iki.fi> | ||
2 | |||
3 | Redistribution and use in source and binary forms, with or without | ||
4 | modification, are permitted provided that the following conditions are met: | ||
5 | |||
6 | 1. Redistributions of source code must retain the above copyright notice, this | ||
7 | list of conditions and the following disclaimer. | ||
8 | 2. Redistributions in binary form must reproduce the above copyright notice, | ||
9 | this list of conditions and the following disclaimer in the documentation | ||
10 | and/or other materials provided with the distribution. | ||
11 | |||
12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||
13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
14 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
15 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | ||
16 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
17 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
18 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | ||
19 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
20 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | ||
22 | |||
23 | #include "indicatorwidget.h" | ||
24 | #include "paint.h" | ||
25 | #include "util.h" | ||
26 | #include "app.h" | ||
27 | #include "command.h" | ||
28 | |||
29 | #include <SDL_timer.h> | ||
30 | |||
31 | static int timerId_; /* common timer for all indicators */ | ||
32 | static int animCount_; /* number of animating indicators */ | ||
33 | |||
34 | static uint32_t postRefresh_(uint32_t interval, void *context) { | ||
35 | iUnused(context); | ||
36 | postRefresh_App(); | ||
37 | return interval; | ||
38 | } | ||
39 | |||
40 | static void startTimer_(void) { | ||
41 | animCount_++; | ||
42 | if (!timerId_) { | ||
43 | timerId_ = SDL_AddTimer(1000 / 60, postRefresh_, NULL); | ||
44 | } | ||
45 | } | ||
46 | |||
47 | static void stopTimer_(void) { | ||
48 | iAssert(animCount_ > 0); | ||
49 | if (--animCount_ == 0) { | ||
50 | iAssert(timerId_); | ||
51 | SDL_RemoveTimer(timerId_); | ||
52 | timerId_ = 0; | ||
53 | } | ||
54 | } | ||
55 | |||
56 | struct Impl_IndicatorWidget{ | ||
57 | iWidget widget; | ||
58 | iAnim pos; | ||
59 | }; | ||
60 | |||
61 | iDefineObjectConstruction(IndicatorWidget) | ||
62 | |||
63 | iLocalDef iBool isActive_IndicatorWidget_(const iIndicatorWidget *d) { | ||
64 | return isSelected_Widget(d); | ||
65 | } | ||
66 | |||
67 | static void setActive_IndicatorWidget_(iIndicatorWidget *d, iBool set) { | ||
68 | setFlags_Widget(as_Widget(d), selected_WidgetFlag, set); | ||
69 | } | ||
70 | |||
71 | void init_IndicatorWidget(iIndicatorWidget *d) { | ||
72 | iWidget *w = &d->widget; | ||
73 | init_Widget(w); | ||
74 | init_Anim(&d->pos, 0); | ||
75 | } | ||
76 | |||
77 | static void startTimer_IndicatorWidget_(iIndicatorWidget *d) { | ||
78 | if (!isActive_IndicatorWidget_(d)) { | ||
79 | startTimer_(); | ||
80 | setActive_IndicatorWidget_(d, iTrue); | ||
81 | } | ||
82 | } | ||
83 | |||
84 | static void stopTimer_IndicatorWidget_(iIndicatorWidget *d) { | ||
85 | if (isActive_IndicatorWidget_(d)) { | ||
86 | stopTimer_(); | ||
87 | setActive_IndicatorWidget_(d, iFalse); | ||
88 | } | ||
89 | } | ||
90 | |||
91 | void deinit_IndicatorWidget(iIndicatorWidget *d) { | ||
92 | stopTimer_IndicatorWidget_(d); | ||
93 | } | ||
94 | |||
95 | static iBool isCompleted_IndicatorWidget_(const iIndicatorWidget *d) { | ||
96 | return targetValue_Anim(&d->pos) == 1.0f; | ||
97 | } | ||
98 | |||
99 | void draw_IndicatorWidget_(const iIndicatorWidget *d) { | ||
100 | const float pos = value_Anim(&d->pos); | ||
101 | if (pos > 0.0f && pos < 1.0f) { | ||
102 | const iWidget *w = &d->widget; | ||
103 | const iRect rect = innerBounds_Widget(w); | ||
104 | iPaint p; | ||
105 | init_Paint(&p); | ||
106 | drawHLine_Paint(&p, | ||
107 | topLeft_Rect(rect), | ||
108 | pos * width_Rect(rect), | ||
109 | isCompleted_IndicatorWidget_(d) ? uiTextAction_ColorId | ||
110 | : uiTextCaution_ColorId); | ||
111 | } | ||
112 | } | ||
113 | |||
114 | iBool processEvent_IndicatorWidget_(iIndicatorWidget *d, const SDL_Event *ev) { | ||
115 | iWidget *w = &d->widget; | ||
116 | if (ev->type == SDL_USEREVENT && ev->user.code == refresh_UserEventCode) { | ||
117 | if (isFinished_Anim(&d->pos)) { | ||
118 | stopTimer_IndicatorWidget_(d); | ||
119 | } | ||
120 | } | ||
121 | else if (isCommand_SDLEvent(ev)) { | ||
122 | const char *cmd = command_UserEvent(ev); | ||
123 | if (startsWith_CStr(cmd, "document.request.")) { | ||
124 | if (pointerLabel_Command(cmd, "doc") == parent_Widget(w)) { | ||
125 | cmd += 17; | ||
126 | if (equal_Command(cmd, "started")) { | ||
127 | setValue_Anim(&d->pos, 0, 0); | ||
128 | setValue_Anim(&d->pos, 0.75f, 4000); | ||
129 | setFlags_Anim(&d->pos, easeOut_AnimFlag, iTrue); | ||
130 | startTimer_IndicatorWidget_(d); | ||
131 | } | ||
132 | else if (equal_Command(cmd, "finished")) { | ||
133 | if (value_Anim(&d->pos) > 0.01f) { | ||
134 | setValue_Anim(&d->pos, 1.0f, 250); | ||
135 | setFlags_Anim(&d->pos, easeOut_AnimFlag, iFalse); | ||
136 | startTimer_IndicatorWidget_(d); | ||
137 | } | ||
138 | else { | ||
139 | setValue_Anim(&d->pos, 0, 0); | ||
140 | stopTimer_IndicatorWidget_(d); | ||
141 | refresh_Widget(d); | ||
142 | } | ||
143 | } | ||
144 | else if (equal_Command(cmd, "cancelled")) { | ||
145 | setValue_Anim(&d->pos, 0, 0); | ||
146 | stopTimer_IndicatorWidget_(d); | ||
147 | refresh_Widget(d); | ||
148 | } | ||
149 | } | ||
150 | } | ||
151 | } | ||
152 | return iFalse; | ||
153 | } | ||
154 | |||
155 | iBeginDefineSubclass(IndicatorWidget, Widget) | ||
156 | .draw = (iAny *) draw_IndicatorWidget_, | ||
157 | .processEvent = (iAny *) processEvent_IndicatorWidget_, | ||
158 | 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 @@ | |||
1 | /* Copyright 2020 Jaakko Keränen <jaakko.keranen@iki.fi> | ||
2 | |||
3 | Redistribution and use in source and binary forms, with or without | ||
4 | modification, are permitted provided that the following conditions are met: | ||
5 | |||
6 | 1. Redistributions of source code must retain the above copyright notice, this | ||
7 | list of conditions and the following disclaimer. | ||
8 | 2. Redistributions in binary form must reproduce the above copyright notice, | ||
9 | this list of conditions and the following disclaimer in the documentation | ||
10 | and/or other materials provided with the distribution. | ||
11 | |||
12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||
13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
14 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
15 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | ||
16 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
17 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
18 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | ||
19 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
20 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | ||
22 | |||
23 | #pragma once | ||
24 | |||
25 | #include "widget.h" | ||
26 | |||
27 | iDeclareWidgetClass(IndicatorWidget) | ||
28 | 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) { | |||
135 | } | 135 | } |
136 | 136 | ||
137 | void init_Anim(iAnim *d, float value) { | 137 | void init_Anim(iAnim *d, float value) { |
138 | d->due = d->when = SDL_GetTicks(); // frameTime_Window(get_Window()); | 138 | d->due = d->when = SDL_GetTicks(); |
139 | d->from = d->to = value; | 139 | d->from = d->to = value; |
140 | d->flags = 0; | 140 | d->flags = 0; |
141 | } | 141 | } |
142 | 142 | ||
143 | void setValue_Anim(iAnim *d, float to, uint32_t span) { | ||
144 | if (fabsf(to - d->to) > 0.00001f) { | ||
145 | const uint32_t now = SDL_GetTicks(); | ||
146 | d->from = value_Anim(d); | ||
147 | d->to = to; | ||
148 | d->when = now; | ||
149 | d->due = now + span; | ||
150 | } | ||
151 | } | ||
152 | |||
153 | iLocalDef float pos_Anim_(const iAnim *d, uint32_t now) { | 143 | iLocalDef float pos_Anim_(const iAnim *d, uint32_t now) { |
154 | return (float) (now - d->when) / (float) (d->due - d->when); | 144 | return (float) (now - d->when) / (float) (d->due - d->when); |
155 | } | 145 | } |
156 | 146 | ||
157 | void setValueEased_Anim(iAnim *d, float to, uint32_t span) { | ||
158 | if (fabsf(to - d->to) <= 0.00001f) { | ||
159 | d->to = to; /* Pretty much unchanged. */ | ||
160 | return; | ||
161 | } | ||
162 | const uint32_t now = SDL_GetTicks(); | ||
163 | if (isFinished_Anim(d)) { | ||
164 | d->from = d->to; | ||
165 | d->when = now; | ||
166 | d->flags = easeBoth_AnimFlag; | ||
167 | } | ||
168 | else { | ||
169 | d->from = value_Anim(d); | ||
170 | d->when = frameTime_Window(get_Window()); /* to match the timing of value_Anim */ | ||
171 | d->flags = easeOut_AnimFlag; | ||
172 | } | ||
173 | d->to = to; | ||
174 | d->due = now + span; | ||
175 | } | ||
176 | |||
177 | void setFlags_Anim(iAnim *d, int flags, iBool set) { | ||
178 | iChangeFlags(d->flags, flags, set); | ||
179 | } | ||
180 | |||
181 | void stop_Anim(iAnim *d) { | ||
182 | d->from = d->to = value_Anim(d); | ||
183 | d->when = d->due = SDL_GetTicks(); | ||
184 | } | ||
185 | |||
186 | iLocalDef float easeIn_(float t) { | 147 | iLocalDef float easeIn_(float t) { |
187 | return t * t; | 148 | return t * t; |
188 | } | 149 | } |
@@ -198,8 +159,7 @@ iLocalDef float easeBoth_(float t) { | |||
198 | return 0.5f + easeOut_((t - 0.5f) * 2.0f) * 0.5f; | 159 | return 0.5f + easeOut_((t - 0.5f) * 2.0f) * 0.5f; |
199 | } | 160 | } |
200 | 161 | ||
201 | float value_Anim(const iAnim *d) { | 162 | static float valueAt_Anim_(const iAnim *d, const uint32_t now) { |
202 | const uint32_t now = frameTime_Window(get_Window()); | ||
203 | if (now >= d->due) { | 163 | if (now >= d->due) { |
204 | return d->to; | 164 | return d->to; |
205 | } | 165 | } |
@@ -219,6 +179,52 @@ float value_Anim(const iAnim *d) { | |||
219 | return d->from * (1.0f - t) + d->to * t; | 179 | return d->from * (1.0f - t) + d->to * t; |
220 | } | 180 | } |
221 | 181 | ||
182 | void setValue_Anim(iAnim *d, float to, uint32_t span) { | ||
183 | if (span == 0) { | ||
184 | d->from = d->to = to; | ||
185 | d->when = d->due = SDL_GetTicks(); | ||
186 | } | ||
187 | else if (fabsf(to - d->to) > 0.00001f) { | ||
188 | const uint32_t now = SDL_GetTicks(); | ||
189 | d->from = valueAt_Anim_(d, now); | ||
190 | d->to = to; | ||
191 | d->when = now; | ||
192 | d->due = now + span; | ||
193 | } | ||
194 | } | ||
195 | |||
196 | void setValueEased_Anim(iAnim *d, float to, uint32_t span) { | ||
197 | if (fabsf(to - d->to) <= 0.00001f) { | ||
198 | d->to = to; /* Pretty much unchanged. */ | ||
199 | return; | ||
200 | } | ||
201 | const uint32_t now = SDL_GetTicks(); | ||
202 | if (isFinished_Anim(d)) { | ||
203 | d->from = d->to; | ||
204 | d->flags = easeBoth_AnimFlag; | ||
205 | } | ||
206 | else { | ||
207 | d->from = valueAt_Anim_(d, now); | ||
208 | d->flags = easeOut_AnimFlag; | ||
209 | } | ||
210 | d->to = to; | ||
211 | d->when = now; | ||
212 | d->due = now + span; | ||
213 | } | ||
214 | |||
215 | void setFlags_Anim(iAnim *d, int flags, iBool set) { | ||
216 | iChangeFlags(d->flags, flags, set); | ||
217 | } | ||
218 | |||
219 | void stop_Anim(iAnim *d) { | ||
220 | d->from = d->to = value_Anim(d); | ||
221 | d->when = d->due = SDL_GetTicks(); | ||
222 | } | ||
223 | |||
224 | float value_Anim(const iAnim *d) { | ||
225 | return valueAt_Anim_(d, frameTime_Window(get_Window())); | ||
226 | } | ||
227 | |||
222 | /*-----------------------------------------------------------------------------------------------*/ | 228 | /*-----------------------------------------------------------------------------------------------*/ |
223 | 229 | ||
224 | void init_Click(iClick *d, iAnyObject *widget, int button) { | 230 | 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) { | |||
160 | iAssert(isInstance_Object(d, &Class_Widget)); | 160 | iAssert(isInstance_Object(d, &Class_Widget)); |
161 | return ((iWidget *) d)->children; | 161 | return ((iWidget *) d)->children; |
162 | } | 162 | } |
163 | iLocalDef iWidget *parent_Widget(const iAnyObject *d) { | ||
164 | if (d) { | ||
165 | iAssert(isInstance_Object(d, &Class_Widget)); | ||
166 | return ((iWidget *) d)->parent; | ||
167 | } | ||
168 | return NULL; | ||
169 | } | ||
163 | 170 | ||
164 | iBool isVisible_Widget (const iAnyObject *); | 171 | iBool isVisible_Widget (const iAnyObject *); |
165 | iBool isDisabled_Widget (const iAnyObject *); | 172 | iBool isDisabled_Widget (const iAnyObject *); |