summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-03-18 11:26:11 +0200
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-03-18 11:26:11 +0200
commitfa174461abdc5c33de16428109c7d46b4f150093 (patch)
tree21cf16a2426f8a6b76195e1816ceea69778fbd70
parent21f6248a3dc906a0c296b937dd1aa697784e6ea3 (diff)
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.
-rw-r--r--CMakeLists.txt2
-rw-r--r--src/app.c29
-rw-r--r--src/app.h2
-rw-r--r--src/periodic.c107
-rw-r--r--src/periodic.h39
-rw-r--r--src/ui/listwidget.c10
-rw-r--r--src/ui/scrollwidget.c85
-rw-r--r--src/ui/sidebarwidget.c11
-rw-r--r--src/ui/util.c3
-rw-r--r--src/ui/util.h1
-rw-r--r--src/ui/widget.c19
-rw-r--r--src/ui/widget.h31
-rw-r--r--src/ui/window.c2
13 files changed, 301 insertions, 40 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 51cad4b2..8c248738 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -112,6 +112,8 @@ set (SOURCES
112 src/media.h 112 src/media.h
113 src/mimehooks.c 113 src/mimehooks.c
114 src/mimehooks.h 114 src/mimehooks.h
115 src/periodic.c
116 src/periodic.h
115 src/prefs.c 117 src/prefs.c
116 src/prefs.h 118 src/prefs.h
117 src/stb_image.h 119 src/stb_image.h
diff --git a/src/app.c b/src/app.c
index 383b9e2a..c5ddc454 100644
--- a/src/app.c
+++ b/src/app.c
@@ -31,6 +31,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
31#include "gmutil.h" 31#include "gmutil.h"
32#include "history.h" 32#include "history.h"
33#include "ipc.h" 33#include "ipc.h"
34#include "periodic.h"
34#include "ui/certimportwidget.h" 35#include "ui/certimportwidget.h"
35#include "ui/color.h" 36#include "ui/color.h"
36#include "ui/command.h" 37#include "ui/command.h"
@@ -108,7 +109,7 @@ struct Impl_App {
108 iVisited * visited; 109 iVisited * visited;
109 iBookmarks * bookmarks; 110 iBookmarks * bookmarks;
110 iWindow * window; 111 iWindow * window;
111 iSortedArray tickers; 112 iSortedArray tickers; /* per-frame callbacks, used for animations */
112 uint32_t lastTickerTime; 113 uint32_t lastTickerTime;
113 uint32_t elapsedSinceLastTicker; 114 uint32_t elapsedSinceLastTicker;
114 iBool isRunning; 115 iBool isRunning;
@@ -123,6 +124,7 @@ struct Impl_App {
123 iBool isFinishedLaunching; 124 iBool isFinishedLaunching;
124 iTime lastDropTime; /* for detecting drops of multiple items */ 125 iTime lastDropTime; /* for detecting drops of multiple items */
125 int autoReloadTimer; 126 int autoReloadTimer;
127 iPeriodic periodic;
126 /* Preferences: */ 128 /* Preferences: */
127 iBool commandEcho; /* --echo */ 129 iBool commandEcho; /* --echo */
128 iBool forceSoftwareRender; /* --sw */ 130 iBool forceSoftwareRender; /* --sw */
@@ -132,6 +134,8 @@ struct Impl_App {
132 134
133static iApp app_; 135static iApp app_;
134 136
137/*----------------------------------------------------------------------------------------------*/
138
135iDeclareType(Ticker) 139iDeclareType(Ticker)
136 140
137struct Impl_Ticker { 141struct Impl_Ticker {
@@ -144,6 +148,8 @@ static int cmp_Ticker_(const void *a, const void *b) {
144 return iCmp(elems[0]->context, elems[1]->context); 148 return iCmp(elems[0]->context, elems[1]->context);
145} 149}
146 150
151/*----------------------------------------------------------------------------------------------*/
152
147const iString *dateStr_(const iDate *date) { 153const iString *dateStr_(const iDate *date) {
148 return collectNewFormat_String("%d-%02d-%02d %02d:%02d:%02d", 154 return collectNewFormat_String("%d-%02d-%02d %02d:%02d:%02d",
149 date->year, 155 date->year,
@@ -611,6 +617,7 @@ static void init_App_(iApp *d, int argc, char **argv) {
611 d->visited = new_Visited(); 617 d->visited = new_Visited();
612 d->bookmarks = new_Bookmarks(); 618 d->bookmarks = new_Bookmarks();
613 d->tabEnum = 0; /* generates unique IDs for tab pages */ 619 d->tabEnum = 0; /* generates unique IDs for tab pages */
620 init_Periodic(&d->periodic);
614 setThemePalette_Color(d->prefs.theme); 621 setThemePalette_Color(d->prefs.theme);
615#if defined (iPlatformAppleDesktop) 622#if defined (iPlatformAppleDesktop)
616 setupApplication_MacOS(); 623 setupApplication_MacOS();
@@ -668,6 +675,10 @@ static void init_App_(iApp *d, int argc, char **argv) {
668} 675}
669 676
670static void deinit_App(iApp *d) { 677static void deinit_App(iApp *d) {
678#if defined (LAGRANGE_IDLE_SLEEP)
679 SDL_RemoveTimer(d->sleepTimer);
680#endif
681 SDL_RemoveTimer(d->autoReloadTimer);
671 saveState_App_(d); 682 saveState_App_(d);
672 deinit_Feeds(); 683 deinit_Feeds();
673 save_Keys(dataDir_App_()); 684 save_Keys(dataDir_App_());
@@ -681,13 +692,14 @@ static void deinit_App(iApp *d) {
681 delete_GmCerts(d->certs); 692 delete_GmCerts(d->certs);
682 save_MimeHooks(d->mimehooks); 693 save_MimeHooks(d->mimehooks);
683 delete_MimeHooks(d->mimehooks); 694 delete_MimeHooks(d->mimehooks);
684 deinit_SortedArray(&d->tickers);
685 delete_Window(d->window); 695 delete_Window(d->window);
686 d->window = NULL; 696 d->window = NULL;
687 deinit_CommandLine(&d->args); 697 deinit_CommandLine(&d->args);
688 iRelease(d->launchCommands); 698 iRelease(d->launchCommands);
689 delete_String(d->execPath); 699 delete_String(d->execPath);
690 deinit_Ipc(); 700 deinit_Ipc();
701 deinit_SortedArray(&d->tickers);
702 deinit_Periodic(&d->periodic);
691 iRecycle(); 703 iRecycle();
692} 704}
693 705
@@ -892,10 +904,11 @@ void processEvents_App(enum iAppEventMode eventMode) {
892 default: { 904 default: {
893#if defined (LAGRANGE_IDLE_SLEEP) 905#if defined (LAGRANGE_IDLE_SLEEP)
894 if (ev.type == SDL_USEREVENT && ev.user.code == asleep_UserEventCode) { 906 if (ev.type == SDL_USEREVENT && ev.user.code == asleep_UserEventCode) {
895 if (SDL_GetTicks() - d->lastEventTime > idleThreshold_App_) { 907 if (SDL_GetTicks() - d->lastEventTime > idleThreshold_App_ &&
908 isEmpty_SortedArray(&d->tickers)) {
896 if (!d->isIdling) { 909 if (!d->isIdling) {
897// printf("[App] idling...\n"); 910// printf("[App] idling...\n");
898 fflush(stdout); 911// fflush(stdout);
899 } 912 }
900 d->isIdling = iTrue; 913 d->isIdling = iTrue;
901 } 914 }
@@ -904,7 +917,7 @@ void processEvents_App(enum iAppEventMode eventMode) {
904 d->lastEventTime = SDL_GetTicks(); 917 d->lastEventTime = SDL_GetTicks();
905 if (d->isIdling) { 918 if (d->isIdling) {
906// printf("[App] ...woke up\n"); 919// printf("[App] ...woke up\n");
907 fflush(stdout); 920// fflush(stdout);
908 } 921 }
909 d->isIdling = iFalse; 922 d->isIdling = iFalse;
910#endif 923#endif
@@ -1043,9 +1056,9 @@ void refresh_App(void) {
1043#if defined (LAGRANGE_IDLE_SLEEP) 1056#if defined (LAGRANGE_IDLE_SLEEP)
1044 if (d->isIdling) return; 1057 if (d->isIdling) return;
1045#endif 1058#endif
1059 set_Atomic(&d->pendingRefresh, iFalse);
1046 destroyPending_Widget(); 1060 destroyPending_Widget();
1047 draw_Window(d->window); 1061 draw_Window(d->window);
1048 set_Atomic(&d->pendingRefresh, iFalse);
1049} 1062}
1050 1063
1051iBool isRefreshPending_App(void) { 1064iBool isRefreshPending_App(void) {
@@ -1175,6 +1188,10 @@ iMimeHooks *mimeHooks_App(void) {
1175 return app_.mimehooks; 1188 return app_.mimehooks;
1176} 1189}
1177 1190
1191iPeriodic *periodic_App(void) {
1192 return &app_.periodic;
1193}
1194
1178iBool isLandscape_App(void) { 1195iBool isLandscape_App(void) {
1179 const iInt2 size = rootSize_Window(get_Window()); 1196 const iInt2 size = rootSize_Window(get_Window());
1180 return size.x > size.y; 1197 return size.x > size.y;
diff --git a/src/app.h b/src/app.h
index bdb0e22f..4d09f5da 100644
--- a/src/app.h
+++ b/src/app.h
@@ -35,6 +35,7 @@ iDeclareType(Bookmarks)
35iDeclareType(DocumentWidget) 35iDeclareType(DocumentWidget)
36iDeclareType(GmCerts) 36iDeclareType(GmCerts)
37iDeclareType(MimeHooks) 37iDeclareType(MimeHooks)
38iDeclareType(Periodic)
38iDeclareType(Visited) 39iDeclareType(Visited)
39iDeclareType(Window) 40iDeclareType(Window)
40 41
@@ -78,6 +79,7 @@ iGmCerts * certs_App (void);
78iVisited * visited_App (void); 79iVisited * visited_App (void);
79iBookmarks * bookmarks_App (void); 80iBookmarks * bookmarks_App (void);
80iMimeHooks * mimeHooks_App (void); 81iMimeHooks * mimeHooks_App (void);
82iPeriodic * periodic_App (void);
81iDocumentWidget * document_App (void); 83iDocumentWidget * document_App (void);
82iObjectList * listDocuments_App (void); 84iObjectList * listDocuments_App (void);
83iDocumentWidget * newTab_App (const iDocumentWidget *duplicateOf, iBool switchToNew); 85iDocumentWidget * newTab_App (const iDocumentWidget *duplicateOf, iBool switchToNew);
diff --git a/src/periodic.c b/src/periodic.c
new file mode 100644
index 00000000..c840e63c
--- /dev/null
+++ b/src/periodic.c
@@ -0,0 +1,107 @@
1/* Copyright 2021 Jaakko Keränen <jaakko.keranen@iki.fi>
2
3Redistribution and use in source and binary forms, with or without
4modification, are permitted provided that the following conditions are met:
5
61. Redistributions of source code must retain the above copyright notice, this
7 list of conditions and the following disclaimer.
82. 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
12THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
13ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
14WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
15DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
16ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
17(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
18LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
19ANY 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
21SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22
23#include "periodic.h"
24#include "app.h"
25
26#include <the_Foundation/string.h>
27#include <SDL_timer.h>
28
29iDeclareType(PeriodicCommand)
30
31struct Impl_PeriodicCommand {
32 iAny * context;
33 iString command;
34};
35
36static void init_PeriodicCommand(iPeriodicCommand *d, iAny *context, const char *command) {
37 d->context = context;
38 initCStr_String(&d->command, command);
39}
40
41static void deinit_PeriodicCommand(iPeriodicCommand *d) {
42 deinit_String(&d->command);
43}
44
45static int cmp_PeriodicCommand_(const void *a, const void *b) {
46 const iPeriodicCommand *elems[2] = { a, b };
47 return iCmp(elems[0]->context, elems[1]->context);
48}
49
50iDefineTypeConstructionArgs(PeriodicCommand, (iAny *ctx, const char *cmd), ctx, cmd)
51
52/*----------------------------------------------------------------------------------------------*/
53
54static uint32_t postCommands_Periodic_(uint32_t interval, void *param) {
55 iPeriodic *d = param;
56 lock_Mutex(d->mutex);
57 iConstForEach(Array, i, &d->commands.values) {
58 postCommandString_App(&((const iPeriodicCommand *) i.value)->command);
59 }
60 unlock_Mutex(d->mutex);
61 return interval;
62}
63
64void init_Periodic(iPeriodic *d) {
65 d->mutex = new_Mutex();
66 init_SortedArray(&d->commands, sizeof(iPeriodicCommand), cmp_PeriodicCommand_);
67 d->timer = SDL_AddTimer(500, postCommands_Periodic_, d);
68}
69
70void deinit_Periodic(iPeriodic *d) {
71 SDL_RemoveTimer(d->timer);
72 iGuardMutex(d->mutex, {
73 iForEach(Array, i, &d->commands.values) {
74 deinit_PeriodicCommand(i.value);
75 }
76 deinit_SortedArray(&d->commands);
77 });
78 delete_Mutex(d->mutex);
79}
80
81void add_Periodic(iPeriodic *d, iAny *context, const char *command) {
82 lock_Mutex(d->mutex);
83 size_t pos;
84 iPeriodicCommand key = { .context = context };
85 if (locate_SortedArray(&d->commands, &key, &pos)) {
86 iPeriodicCommand *pc = at_SortedArray(&d->commands, pos);
87 setCStr_String(&pc->command, command);
88 }
89 else {
90 iPeriodicCommand pc;
91 init_PeriodicCommand(&pc, context, command);
92 insert_SortedArray(&d->commands, &pc);
93 }
94 unlock_Mutex(d->mutex);
95}
96
97void remove_Periodic(iPeriodic *d, iAny *context) {
98 lock_Mutex(d->mutex);
99 size_t pos;
100 iPeriodicCommand key = { .context = context };
101 if (locate_SortedArray(&d->commands, &key, &pos)) {
102 iPeriodicCommand *pc = at_SortedArray(&d->commands, pos);
103 deinit_PeriodicCommand(pc);
104 remove_Array(&d->commands.values, pos);
105 }
106 unlock_Mutex(d->mutex);
107}
diff --git a/src/periodic.h b/src/periodic.h
new file mode 100644
index 00000000..acb00aa2
--- /dev/null
+++ b/src/periodic.h
@@ -0,0 +1,39 @@
1/* Copyright 2021 Jaakko Keränen <jaakko.keranen@iki.fi>
2
3Redistribution and use in source and binary forms, with or without
4modification, are permitted provided that the following conditions are met:
5
61. Redistributions of source code must retain the above copyright notice, this
7 list of conditions and the following disclaimer.
82. 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
12THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
13ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
14WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
15DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
16ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
17(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
18LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
19ANY 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
21SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22
23#include <the_Foundation/mutex.h>
24#include <the_Foundation/sortedarray.h>
25
26iDeclareType(Periodic)
27
28/* Animation utility. Not per frame but several times per second. */
29struct Impl_Periodic {
30 int timer;
31 iMutex * mutex;
32 iSortedArray commands;
33};
34
35void init_Periodic (iPeriodic *);
36void deinit_Periodic (iPeriodic *);
37
38void add_Periodic (iPeriodic *, iAny *context, const char *command);
39void remove_Periodic (iPeriodic *, iAny *context);
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) {
379 beginTarget_Paint(&p, buf->texture); 379 beginTarget_Paint(&p, buf->texture);
380 fillRect_Paint(&p, (iRect){ zero_I2(), d->visBuf->texSize }, bg[i]); 380 fillRect_Paint(&p, (iRect){ zero_I2(), d->visBuf->texSize }, bg[i]);
381 } 381 }
382 const iRect sbBlankRect = 382#if defined (iPlatformApple)
383 { init_I2(d->visBuf->texSize.x - scrollBarWidth_ListWidget(d), 0), 383 const int blankWidth = 0; /* scrollbars fade away */
384 init_I2(scrollBarWidth_ListWidget(d), d->itemHeight) }; 384#else
385 const int blankWidth = scrollBarWidth_ListWidget(d);
386#endif
387 const iRect sbBlankRect = { init_I2(d->visBuf->texSize.x - blankWidth, 0),
388 init_I2(blankWidth, d->itemHeight) };
385 iConstForEach(IntSet, v, &d->invalidItems) { 389 iConstForEach(IntSet, v, &d->invalidItems) {
386 const size_t index = *v.value; 390 const size_t index = *v.value;
387 if (contains_Range(&drawItems, index)) { 391 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. */
23#include "scrollwidget.h" 23#include "scrollwidget.h"
24#include "paint.h" 24#include "paint.h"
25#include "util.h" 25#include "util.h"
26#include "periodic.h"
27#include "app.h"
28
29#include <SDL_timer.h>
26 30
27iDefineObjectConstruction(ScrollWidget) 31iDefineObjectConstruction(ScrollWidget)
28 32
33static float minOpacity_(void) {
34#if !defined (iPlatformApple)
35 if (deviceType_App() == desktop_AppDeviceType) {
36 /* Don't fade the scrollbars completely. */
37 return 0.333f;
38 }
39#endif
40 return 0.0f;
41}
42
29struct Impl_ScrollWidget { 43struct Impl_ScrollWidget {
30 iWidget widget; 44 iWidget widget;
31 iRangei range; 45 iRangei range;
@@ -33,12 +47,23 @@ struct Impl_ScrollWidget {
33 int thumbSize; 47 int thumbSize;
34 iClick click; 48 iClick click;
35 int startThumb; 49 int startThumb;
50 iAnim opacity;
51 uint32_t fadeStart;
52 iBool willCheckFade;
36}; 53};
37 54
38static void updateMetrics_ScrollWidget_(iScrollWidget *d) { 55static void updateMetrics_ScrollWidget_(iScrollWidget *d) {
39 as_Widget(d)->rect.size.x = gap_UI * 3; 56 as_Widget(d)->rect.size.x = gap_UI * 3;
40} 57}
41 58
59static void animateOpacity_ScrollWidget_(void *ptr) {
60 iScrollWidget *d = ptr;
61 if (!isFinished_Anim(&d->opacity)) {
62 addTicker_App(animateOpacity_ScrollWidget_, ptr);
63 }
64 refresh_Widget(ptr);
65}
66
42void init_ScrollWidget(iScrollWidget *d) { 67void init_ScrollWidget(iScrollWidget *d) {
43 iWidget *w = as_Widget(d); 68 iWidget *w = as_Widget(d);
44 init_Widget(w); 69 init_Widget(w);
@@ -49,10 +74,13 @@ void init_ScrollWidget(iScrollWidget *d) {
49 iTrue); 74 iTrue);
50 updateMetrics_ScrollWidget_(d); 75 updateMetrics_ScrollWidget_(d);
51 init_Click(&d->click, d, SDL_BUTTON_LEFT); 76 init_Click(&d->click, d, SDL_BUTTON_LEFT);
77 init_Anim(&d->opacity, minOpacity_());
78 d->willCheckFade = iFalse;
52} 79}
53 80
54void deinit_ScrollWidget(iScrollWidget *d) { 81void deinit_ScrollWidget(iScrollWidget *d) {
55 iUnused(d); 82 remove_Periodic(periodic_App(), d);
83 removeTicker_App(animateOpacity_ScrollWidget_, d);
56} 84}
57 85
58static int thumbSize_ScrollWidget_(const iScrollWidget *d) { 86static int thumbSize_ScrollWidget_(const iScrollWidget *d) {
@@ -74,10 +102,26 @@ static iRect thumbRect_ScrollWidget_(const iScrollWidget *d) {
74 return rect; 102 return rect;
75} 103}
76 104
105static void unfade_ScrollWidget_(iScrollWidget *d, float opacity) {
106 d->fadeStart = SDL_GetTicks() + 1000;
107 if (targetValue_Anim(&d->opacity) < opacity) {
108 setValue_Anim(&d->opacity, opacity, 66);
109 addTicker_App(animateOpacity_ScrollWidget_, d);
110 }
111 if (!d->willCheckFade) {
112 d->willCheckFade = iTrue;
113 add_Periodic(periodic_App(), d, "scrollbar.fade");
114 }
115 refresh_Widget(d);
116}
117
77static void checkVisible_ScrollWidget_(iScrollWidget *d) { 118static void checkVisible_ScrollWidget_(iScrollWidget *d) {
78 setFlags_Widget(as_Widget(d), 119 const iBool wasHidden = isVisible_Widget(d);
79 hidden_WidgetFlag, 120 const iBool isHidden = d->thumbSize != 0 ? height_Rect(thumbRect_ScrollWidget_(d)) == 0 : iTrue;
80 d->thumbSize != 0 ? height_Rect(thumbRect_ScrollWidget_(d)) == 0 : iTrue); 121 setFlags_Widget(as_Widget(d), hidden_WidgetFlag, isHidden);
122 if (wasHidden && !isHidden) {
123 unfade_ScrollWidget_(d, 1.0f);
124 }
81} 125}
82 126
83void setRange_ScrollWidget(iScrollWidget *d, iRangei range) { 127void setRange_ScrollWidget(iScrollWidget *d, iRangei range) {
@@ -87,9 +131,13 @@ void setRange_ScrollWidget(iScrollWidget *d, iRangei range) {
87} 131}
88 132
89void setThumb_ScrollWidget(iScrollWidget *d, int thumb, int thumbSize) { 133void setThumb_ScrollWidget(iScrollWidget *d, int thumb, int thumbSize) {
134 const int oldThumb = d->thumb;
90 d->thumb = thumb; 135 d->thumb = thumb;
91 d->thumbSize = thumbSize; 136 d->thumbSize = thumbSize;
92 checkVisible_ScrollWidget_(d); 137 checkVisible_ScrollWidget_(d);
138 if (oldThumb != d->thumb && thumbSize) {
139 unfade_ScrollWidget_(d, 1.0f);
140 }
93} 141}
94 142
95static iBool processEvent_ScrollWidget_(iScrollWidget *d, const SDL_Event *ev) { 143static iBool processEvent_ScrollWidget_(iScrollWidget *d, const SDL_Event *ev) {
@@ -97,6 +145,25 @@ static iBool processEvent_ScrollWidget_(iScrollWidget *d, const SDL_Event *ev) {
97 if (isMetricsChange_UserEvent(ev)) { 145 if (isMetricsChange_UserEvent(ev)) {
98 updateMetrics_ScrollWidget_(d); 146 updateMetrics_ScrollWidget_(d);
99 } 147 }
148 if (ev->type == SDL_MOUSEMOTION) {
149 const iInt2 mouse = init_I2(ev->motion.x, ev->motion.y);
150 const iBool isNearby = containsExpanded_Widget(&d->widget, mouse, 4 * gap_UI);
151 const iBool isOver = isNearby && contains_Rect(thumbRect_ScrollWidget_(d), mouse);
152 if (isNearby) {
153 unfade_ScrollWidget_(d, isOver ? 1.0f : 0.4f);
154 }
155 }
156 if (isCommand_UserEvent(ev, "scrollbar.fade")) {
157 if (d->willCheckFade && SDL_GetTicks() > d->fadeStart) {
158 setValue_Anim(&d->opacity, minOpacity_(), 200);
159 remove_Periodic(periodic_App(), d);
160 d->willCheckFade = iFalse;
161 if (!isFinished_Anim(&d->opacity)) {
162 addTicker_App(animateOpacity_ScrollWidget_, d);
163 }
164 }
165 return iFalse;
166 }
100 switch (processEvent_Click(&d->click, ev)) { 167 switch (processEvent_Click(&d->click, ev)) {
101 case started_ClickResult: 168 case started_ClickResult:
102 setFlags_Widget(w, pressed_WidgetFlag, iTrue); 169 setFlags_Widget(w, pressed_WidgetFlag, iTrue);
@@ -146,10 +213,18 @@ static void draw_ScrollWidget_(const iScrollWidget *d) {
146 if (bounds.size.x > 0) { 213 if (bounds.size.x > 0) {
147 iPaint p; 214 iPaint p;
148 init_Paint(&p); 215 init_Paint(&p);
149 //drawVLine_Paint(&p, topLeft_Rect(bounds), height_Rect(bounds), uiSeparator_ColorId); 216 /* Blend if opacity is not at maximum. */
217 p.alpha = 255 * value_Anim(&d->opacity);
218 SDL_Renderer *render = renderer_Window(get_Window());
219 if (p.alpha < 255) {
220 SDL_SetRenderDrawBlendMode(render, SDL_BLENDMODE_BLEND);
221 }
150 const iRect thumbRect = shrunk_Rect( 222 const iRect thumbRect = shrunk_Rect(
151 thumbRect_ScrollWidget_(d), init_I2(isPressed ? gap_UI : (gap_UI * 4 / 3), gap_UI / 2)); 223 thumbRect_ScrollWidget_(d), init_I2(isPressed ? gap_UI : (gap_UI * 4 / 3), gap_UI / 2));
152 fillRect_Paint(&p, thumbRect, isPressed ? uiBackgroundPressed_ColorId : tmQuote_ColorId); 224 fillRect_Paint(&p, thumbRect, isPressed ? uiBackgroundPressed_ColorId : tmQuote_ColorId);
225 if (p.alpha < 255) {
226 SDL_SetRenderDrawBlendMode(render, SDL_BLENDMODE_NONE);
227 }
153 } 228 }
154} 229}
155 230
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) {
143 iDate on; 143 iDate on;
144 initCurrent_Time(&now); 144 initCurrent_Time(&now);
145 init_Date(&on, &now); 145 init_Date(&on, &now);
146 const int thisYear = on.year; 146 const iDate today = on;
147 iZap(on); 147 iZap(on);
148 size_t numItems = 0; 148 size_t numItems = 0;
149 iConstForEach(PtrArray, i, listEntries_Feeds()) { 149 iConstForEach(PtrArray, i, listEntries_Feeds()) {
@@ -1306,6 +1306,11 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect,
1306 const iBool isHover = isHover_Widget(constAs_Widget(list)) && 1306 const iBool isHover = isHover_Widget(constAs_Widget(list)) &&
1307 constHoverItem_ListWidget(list) == d; 1307 constHoverItem_ListWidget(list) == d;
1308 const int scrollBarWidth = scrollBarWidth_ListWidget(list); 1308 const int scrollBarWidth = scrollBarWidth_ListWidget(list);
1309#if defined (iPlatformApple)
1310 const int blankWidth = 0;
1311#else
1312 const int blankWidth = scrollBarWidth;
1313#endif
1309 const int itemHeight = height_Rect(itemRect); 1314 const int itemHeight = height_Rect(itemRect);
1310 const int iconColor = isHover ? (isPressing ? uiTextPressed_ColorId : uiIconHover_ColorId) 1315 const int iconColor = isHover ? (isPressing ? uiTextPressed_ColorId : uiIconHover_ColorId)
1311 : uiIcon_ColorId; 1316 : uiIcon_ColorId;
@@ -1345,7 +1350,7 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect,
1345 if (d != constItem_ListWidget(list, 0)) { 1350 if (d != constItem_ListWidget(list, 0)) {
1346 drawHLine_Paint(p, 1351 drawHLine_Paint(p,
1347 addY_I2(pos, 2 * gap_UI), 1352 addY_I2(pos, 2 * gap_UI),
1348 width_Rect(itemRect) - scrollBarWidth, 1353 width_Rect(itemRect) - blankWidth,
1349 uiSeparator_ColorId); 1354 uiSeparator_ColorId);
1350 } 1355 }
1351 drawRange_Text( 1356 drawRange_Text(
@@ -1463,7 +1468,7 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect,
1463 iInt2 drawPos = addY_I2(topLeft_Rect(itemRect), d->id); 1468 iInt2 drawPos = addY_I2(topLeft_Rect(itemRect), d->id);
1464 drawHLine_Paint(p, 1469 drawHLine_Paint(p,
1465 addY_I2(drawPos, -gap_UI), 1470 addY_I2(drawPos, -gap_UI),
1466 width_Rect(itemRect) - scrollBarWidth, 1471 width_Rect(itemRect) - blankWidth,
1467 uiSeparator_ColorId); 1472 uiSeparator_ColorId);
1468 drawRange_Text( 1473 drawRange_Text(
1469 uiLabelLargeBold_FontId, 1474 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) {
401 equal_Command(cmd, "document.request.updated") || 401 equal_Command(cmd, "document.request.updated") ||
402 equal_Command(cmd, "document.request.finished") || 402 equal_Command(cmd, "document.request.finished") ||
403 equal_Command(cmd, "document.changed") || 403 equal_Command(cmd, "document.changed") ||
404 equal_Command(cmd, "scrollbar.fade") ||
404 equal_Command(cmd, "visited.changed") || 405 equal_Command(cmd, "visited.changed") ||
405 (deviceType_App() == desktop_AppDeviceType && equal_Command(cmd, "window.resized")) || 406 (deviceType_App() == desktop_AppDeviceType && equal_Command(cmd, "window.resized")) ||
406 equal_Command(cmd, "widget.overflow") || 407 equal_Command(cmd, "widget.overflow") ||
@@ -1634,12 +1635,14 @@ void updateValueInput_Widget(iWidget *d, const char *title, const char *prompt)
1634 1635
1635static iBool messageHandler_(iWidget *msg, const char *cmd) { 1636static iBool messageHandler_(iWidget *msg, const char *cmd) {
1636 /* Almost any command dismisses the sheet. */ 1637 /* Almost any command dismisses the sheet. */
1638 /* TODO: Use a "notification" prefix (like `) to ignore all types of commands line this? */
1637 if (!(equal_Command(cmd, "media.updated") || 1639 if (!(equal_Command(cmd, "media.updated") ||
1638 equal_Command(cmd, "media.player.update") || 1640 equal_Command(cmd, "media.player.update") ||
1639 equal_Command(cmd, "bookmarks.request.finished") || 1641 equal_Command(cmd, "bookmarks.request.finished") ||
1640 equal_Command(cmd, "document.autoreload") || 1642 equal_Command(cmd, "document.autoreload") ||
1641 equal_Command(cmd, "document.reload") || 1643 equal_Command(cmd, "document.reload") ||
1642 equal_Command(cmd, "document.request.updated") || 1644 equal_Command(cmd, "document.request.updated") ||
1645 equal_Command(cmd, "scrollbar.fade") ||
1643 equal_Command(cmd, "widget.overflow") || 1646 equal_Command(cmd, "widget.overflow") ||
1644 startsWith_CStr(cmd, "window."))) { 1647 startsWith_CStr(cmd, "window."))) {
1645 destroy_Widget(msg); 1648 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 {
102 102
103void init_Anim (iAnim *, float value); 103void init_Anim (iAnim *, float value);
104void setValue_Anim (iAnim *, float to, uint32_t span); 104void setValue_Anim (iAnim *, float to, uint32_t span);
105void setValueLinear_Anim (iAnim *, float to, uint32_t span);
106void setValueEased_Anim (iAnim *, float to, uint32_t span); 105void setValueEased_Anim (iAnim *, float to, uint32_t span);
107void setFlags_Anim (iAnim *, int flags, iBool set); 106void setFlags_Anim (iAnim *, int flags, iBool set);
108void stop_Anim (iAnim *); 107void 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) {
582} 582}
583 583
584iBool contains_Widget(const iWidget *d, iInt2 coord) { 584iBool contains_Widget(const iWidget *d, iInt2 coord) {
585 const iRect bounds = { zero_I2(), addY_I2(d->rect.size, 585 return containsExpanded_Widget(d, coord, 0);
586 d->flags & drawBackgroundToBottom_WidgetFlag ? 586}
587 rootSize_Window(get_Window()).y : 0) }; 587
588 return contains_Rect(bounds, localCoord_Widget(d, coord)); 588iBool containsExpanded_Widget(const iWidget *d, iInt2 coord, int expand) {
589 const iRect bounds = {
590 zero_I2(),
591 addY_I2(d->rect.size,
592 d->flags & drawBackgroundToBottom_WidgetFlag ? rootSize_Window(get_Window()).y : 0)
593 };
594 return contains_Rect(expand ? expanded_Rect(bounds, init1_I2(expand)) : bounds,
595 localCoord_Widget(d, coord));
589} 596}
590 597
591iLocalDef iBool isKeyboardEvent_(const SDL_Event *ev) { 598iLocalDef iBool isKeyboardEvent_(const SDL_Event *ev) {
@@ -854,8 +861,8 @@ void drawBackground_Widget(const iWidget *d) {
854 const iBool isLight = isLight_ColorTheme(colorTheme_App()); 861 const iBool isLight = isLight_ColorTheme(colorTheme_App());
855 p.alpha = isLight ? 0xc : 0x20; 862 p.alpha = isLight ? 0xc : 0x20;
856 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND); 863 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND);
857 iRect shadowRect = expanded_Rect(bounds_Widget(d), mulf_I2(gap2_UI, 1)); 864 iRect shadowRect = expanded_Rect(bounds_Widget(d), mulf_I2(gap2_UI, 8));
858 shadowRect.pos.y += gap_UI / 4; 865// shadowRect.pos.y += gap_UI * 4;
859 fillRect_Paint(&p, shadowRect, /*isLight ? white_ColorId :*/ black_ColorId); 866 fillRect_Paint(&p, shadowRect, /*isLight ? white_ColorId :*/ black_ColorId);
860 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE); 867 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE);
861 } 868 }
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
161void destroyPending_Widget (void); 161void destroyPending_Widget (void);
162void releaseChildren_Widget (iWidget *); 162void releaseChildren_Widget (iWidget *);
163 163
164const iString *id_Widget (const iWidget *); 164const iString *id_Widget (const iWidget *);
165int64_t flags_Widget (const iWidget *); 165int64_t flags_Widget (const iWidget *);
166iRect bounds_Widget (const iWidget *); /* outer bounds */ 166iRect bounds_Widget (const iWidget *); /* outer bounds */
167iRect innerBounds_Widget (const iWidget *); 167iRect innerBounds_Widget (const iWidget *);
168iInt2 localCoord_Widget (const iWidget *, iInt2 coord); 168iInt2 localCoord_Widget (const iWidget *, iInt2 coord);
169iBool contains_Widget (const iWidget *, iInt2 coord); 169iBool contains_Widget (const iWidget *, iInt2 coord);
170iAny * hitChild_Widget (const iWidget *, iInt2 coord); 170iBool containsExpanded_Widget (const iWidget *, iInt2 coord, int expand);
171iAny * findChild_Widget (const iWidget *, const char *id); 171iAny * hitChild_Widget (const iWidget *, iInt2 coord);
172const iPtrArray *findChildren_Widget (const iWidget *, const char *id); 172iAny * findChild_Widget (const iWidget *, const char *id);
173iAny * findParentClass_Widget(const iWidget *, const iAnyClass *class); 173const iPtrArray *findChildren_Widget (const iWidget *, const char *id);
174iAny * findFocusable_Widget(const iWidget *startFrom, enum iWidgetFocusDir focusDir); 174iAny * findParentClass_Widget (const iWidget *, const iAnyClass *class);
175size_t childCount_Widget (const iWidget *); 175iAny * findFocusable_Widget (const iWidget *startFrom, enum iWidgetFocusDir focusDir);
176void draw_Widget (const iWidget *); 176size_t childCount_Widget (const iWidget *);
177void drawBackground_Widget(const iWidget *); 177void draw_Widget (const iWidget *);
178void drawChildren_Widget (const iWidget *); 178void drawBackground_Widget (const iWidget *);
179void drawChildren_Widget (const iWidget *);
179 180
180iLocalDef int width_Widget(const iAnyObject *d) { 181iLocalDef int width_Widget(const iAnyObject *d) {
181 if (d) { 182 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) {
1827 } 1827 }
1828 } 1828 }
1829#endif 1829#endif
1830 const int winFlags = SDL_GetWindowFlags(d->win); 1830 const int winFlags = SDL_GetWindowFlags(d->win);
1831 const iBool gotFocus = (winFlags & SDL_WINDOW_INPUT_FOCUS) != 0; 1831 const iBool gotFocus = (winFlags & SDL_WINDOW_INPUT_FOCUS) != 0;
1832 /* Clear the window. The clear color is visible as a border around the window 1832 /* Clear the window. The clear color is visible as a border around the window
1833 when the custom frame is being used. */ { 1833 when the custom frame is being used. */ {