diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-03-18 11:26:11 +0200 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-03-18 11:26:11 +0200 |
commit | fa174461abdc5c33de16428109c7d46b4f150093 (patch) | |
tree | 21cf16a2426f8a6b76195e1816ceea69778fbd70 | |
parent | 21f6248a3dc906a0c296b937dd1aa697784e6ea3 (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.txt | 2 | ||||
-rw-r--r-- | src/app.c | 29 | ||||
-rw-r--r-- | src/app.h | 2 | ||||
-rw-r--r-- | src/periodic.c | 107 | ||||
-rw-r--r-- | src/periodic.h | 39 | ||||
-rw-r--r-- | src/ui/listwidget.c | 10 | ||||
-rw-r--r-- | src/ui/scrollwidget.c | 85 | ||||
-rw-r--r-- | src/ui/sidebarwidget.c | 11 | ||||
-rw-r--r-- | src/ui/util.c | 3 | ||||
-rw-r--r-- | src/ui/util.h | 1 | ||||
-rw-r--r-- | src/ui/widget.c | 19 | ||||
-rw-r--r-- | src/ui/widget.h | 31 | ||||
-rw-r--r-- | src/ui/window.c | 2 |
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 |
@@ -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 | ||
133 | static iApp app_; | 135 | static iApp app_; |
134 | 136 | ||
137 | /*----------------------------------------------------------------------------------------------*/ | ||
138 | |||
135 | iDeclareType(Ticker) | 139 | iDeclareType(Ticker) |
136 | 140 | ||
137 | struct Impl_Ticker { | 141 | struct 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 | |||
147 | const iString *dateStr_(const iDate *date) { | 153 | const 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 | ||
670 | static void deinit_App(iApp *d) { | 677 | static 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 | ||
1051 | iBool isRefreshPending_App(void) { | 1064 | iBool isRefreshPending_App(void) { |
@@ -1175,6 +1188,10 @@ iMimeHooks *mimeHooks_App(void) { | |||
1175 | return app_.mimehooks; | 1188 | return app_.mimehooks; |
1176 | } | 1189 | } |
1177 | 1190 | ||
1191 | iPeriodic *periodic_App(void) { | ||
1192 | return &app_.periodic; | ||
1193 | } | ||
1194 | |||
1178 | iBool isLandscape_App(void) { | 1195 | iBool 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; |
@@ -35,6 +35,7 @@ iDeclareType(Bookmarks) | |||
35 | iDeclareType(DocumentWidget) | 35 | iDeclareType(DocumentWidget) |
36 | iDeclareType(GmCerts) | 36 | iDeclareType(GmCerts) |
37 | iDeclareType(MimeHooks) | 37 | iDeclareType(MimeHooks) |
38 | iDeclareType(Periodic) | ||
38 | iDeclareType(Visited) | 39 | iDeclareType(Visited) |
39 | iDeclareType(Window) | 40 | iDeclareType(Window) |
40 | 41 | ||
@@ -78,6 +79,7 @@ iGmCerts * certs_App (void); | |||
78 | iVisited * visited_App (void); | 79 | iVisited * visited_App (void); |
79 | iBookmarks * bookmarks_App (void); | 80 | iBookmarks * bookmarks_App (void); |
80 | iMimeHooks * mimeHooks_App (void); | 81 | iMimeHooks * mimeHooks_App (void); |
82 | iPeriodic * periodic_App (void); | ||
81 | iDocumentWidget * document_App (void); | 83 | iDocumentWidget * document_App (void); |
82 | iObjectList * listDocuments_App (void); | 84 | iObjectList * listDocuments_App (void); |
83 | iDocumentWidget * newTab_App (const iDocumentWidget *duplicateOf, iBool switchToNew); | 85 | iDocumentWidget * 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 | |||
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 "periodic.h" | ||
24 | #include "app.h" | ||
25 | |||
26 | #include <the_Foundation/string.h> | ||
27 | #include <SDL_timer.h> | ||
28 | |||
29 | iDeclareType(PeriodicCommand) | ||
30 | |||
31 | struct Impl_PeriodicCommand { | ||
32 | iAny * context; | ||
33 | iString command; | ||
34 | }; | ||
35 | |||
36 | static void init_PeriodicCommand(iPeriodicCommand *d, iAny *context, const char *command) { | ||
37 | d->context = context; | ||
38 | initCStr_String(&d->command, command); | ||
39 | } | ||
40 | |||
41 | static void deinit_PeriodicCommand(iPeriodicCommand *d) { | ||
42 | deinit_String(&d->command); | ||
43 | } | ||
44 | |||
45 | static 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 | |||
50 | iDefineTypeConstructionArgs(PeriodicCommand, (iAny *ctx, const char *cmd), ctx, cmd) | ||
51 | |||
52 | /*----------------------------------------------------------------------------------------------*/ | ||
53 | |||
54 | static 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 | |||
64 | void 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 | |||
70 | void 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 | |||
81 | void 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 | |||
97 | void 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 | |||
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 <the_Foundation/mutex.h> | ||
24 | #include <the_Foundation/sortedarray.h> | ||
25 | |||
26 | iDeclareType(Periodic) | ||
27 | |||
28 | /* Animation utility. Not per frame but several times per second. */ | ||
29 | struct Impl_Periodic { | ||
30 | int timer; | ||
31 | iMutex * mutex; | ||
32 | iSortedArray commands; | ||
33 | }; | ||
34 | |||
35 | void init_Periodic (iPeriodic *); | ||
36 | void deinit_Periodic (iPeriodic *); | ||
37 | |||
38 | void add_Periodic (iPeriodic *, iAny *context, const char *command); | ||
39 | void 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 | ||
27 | iDefineObjectConstruction(ScrollWidget) | 31 | iDefineObjectConstruction(ScrollWidget) |
28 | 32 | ||
33 | static 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 | |||
29 | struct Impl_ScrollWidget { | 43 | struct 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 | ||
38 | static void updateMetrics_ScrollWidget_(iScrollWidget *d) { | 55 | static 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 | ||
59 | static 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 | |||
42 | void init_ScrollWidget(iScrollWidget *d) { | 67 | void 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 | ||
54 | void deinit_ScrollWidget(iScrollWidget *d) { | 81 | void deinit_ScrollWidget(iScrollWidget *d) { |
55 | iUnused(d); | 82 | remove_Periodic(periodic_App(), d); |
83 | removeTicker_App(animateOpacity_ScrollWidget_, d); | ||
56 | } | 84 | } |
57 | 85 | ||
58 | static int thumbSize_ScrollWidget_(const iScrollWidget *d) { | 86 | static 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 | ||
105 | static 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 | |||
77 | static void checkVisible_ScrollWidget_(iScrollWidget *d) { | 118 | static 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 | ||
83 | void setRange_ScrollWidget(iScrollWidget *d, iRangei range) { | 127 | void setRange_ScrollWidget(iScrollWidget *d, iRangei range) { |
@@ -87,9 +131,13 @@ void setRange_ScrollWidget(iScrollWidget *d, iRangei range) { | |||
87 | } | 131 | } |
88 | 132 | ||
89 | void setThumb_ScrollWidget(iScrollWidget *d, int thumb, int thumbSize) { | 133 | void 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 | ||
95 | static iBool processEvent_ScrollWidget_(iScrollWidget *d, const SDL_Event *ev) { | 143 | static 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 | ||
1635 | static iBool messageHandler_(iWidget *msg, const char *cmd) { | 1636 | static 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 | ||
103 | void init_Anim (iAnim *, float value); | 103 | void init_Anim (iAnim *, float value); |
104 | void setValue_Anim (iAnim *, float to, uint32_t span); | 104 | void setValue_Anim (iAnim *, float to, uint32_t span); |
105 | void setValueLinear_Anim (iAnim *, float to, uint32_t span); | ||
106 | void setValueEased_Anim (iAnim *, float to, uint32_t span); | 105 | void setValueEased_Anim (iAnim *, float to, uint32_t span); |
107 | void setFlags_Anim (iAnim *, int flags, iBool set); | 106 | void setFlags_Anim (iAnim *, int flags, iBool set); |
108 | void stop_Anim (iAnim *); | 107 | 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) { | |||
582 | } | 582 | } |
583 | 583 | ||
584 | iBool contains_Widget(const iWidget *d, iInt2 coord) { | 584 | iBool 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)); | 588 | iBool 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 | ||
591 | iLocalDef iBool isKeyboardEvent_(const SDL_Event *ev) { | 598 | iLocalDef 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 | |||
161 | void destroyPending_Widget (void); | 161 | void destroyPending_Widget (void); |
162 | void releaseChildren_Widget (iWidget *); | 162 | void releaseChildren_Widget (iWidget *); |
163 | 163 | ||
164 | const iString *id_Widget (const iWidget *); | 164 | const iString *id_Widget (const iWidget *); |
165 | int64_t flags_Widget (const iWidget *); | 165 | int64_t flags_Widget (const iWidget *); |
166 | iRect bounds_Widget (const iWidget *); /* outer bounds */ | 166 | iRect bounds_Widget (const iWidget *); /* outer bounds */ |
167 | iRect innerBounds_Widget (const iWidget *); | 167 | iRect innerBounds_Widget (const iWidget *); |
168 | iInt2 localCoord_Widget (const iWidget *, iInt2 coord); | 168 | iInt2 localCoord_Widget (const iWidget *, iInt2 coord); |
169 | iBool contains_Widget (const iWidget *, iInt2 coord); | 169 | iBool contains_Widget (const iWidget *, iInt2 coord); |
170 | iAny * hitChild_Widget (const iWidget *, iInt2 coord); | 170 | iBool containsExpanded_Widget (const iWidget *, iInt2 coord, int expand); |
171 | iAny * findChild_Widget (const iWidget *, const char *id); | 171 | iAny * hitChild_Widget (const iWidget *, iInt2 coord); |
172 | const iPtrArray *findChildren_Widget (const iWidget *, const char *id); | 172 | iAny * findChild_Widget (const iWidget *, const char *id); |
173 | iAny * findParentClass_Widget(const iWidget *, const iAnyClass *class); | 173 | const iPtrArray *findChildren_Widget (const iWidget *, const char *id); |
174 | iAny * findFocusable_Widget(const iWidget *startFrom, enum iWidgetFocusDir focusDir); | 174 | iAny * findParentClass_Widget (const iWidget *, const iAnyClass *class); |
175 | size_t childCount_Widget (const iWidget *); | 175 | iAny * findFocusable_Widget (const iWidget *startFrom, enum iWidgetFocusDir focusDir); |
176 | void draw_Widget (const iWidget *); | 176 | size_t childCount_Widget (const iWidget *); |
177 | void drawBackground_Widget(const iWidget *); | 177 | void draw_Widget (const iWidget *); |
178 | void drawChildren_Widget (const iWidget *); | 178 | void drawBackground_Widget (const iWidget *); |
179 | void drawChildren_Widget (const iWidget *); | ||
179 | 180 | ||
180 | iLocalDef int width_Widget(const iAnyObject *d) { | 181 | iLocalDef 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. */ { |