diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-11-04 08:24:09 +0200 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-11-04 08:24:09 +0200 |
commit | 37825eff3a026656014cc06cf17fcb1669c3ef8f (patch) | |
tree | f4ae54c7130ae1b51f05152248962d2b2eb6eccd | |
parent | 8913f836cba97fdd509314456eca9fb0df7aa035 (diff) |
Widget: Scroll overflow widgets via mouse hover
Useful for tall menus and dialogs on the desktop.
-rw-r--r-- | src/ui/widget.c | 96 |
1 files changed, 77 insertions, 19 deletions
diff --git a/src/ui/widget.c b/src/ui/widget.c index 8a7127a2..b509cbe2 100644 --- a/src/ui/widget.c +++ b/src/ui/widget.c | |||
@@ -34,6 +34,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
34 | #include <the_Foundation/ptrarray.h> | 34 | #include <the_Foundation/ptrarray.h> |
35 | #include <the_Foundation/ptrset.h> | 35 | #include <the_Foundation/ptrset.h> |
36 | #include <SDL_mouse.h> | 36 | #include <SDL_mouse.h> |
37 | #include <SDL_timer.h> | ||
37 | #include <stdarg.h> | 38 | #include <stdarg.h> |
38 | 39 | ||
39 | #if defined (iPlatformAppleMobile) | 40 | #if defined (iPlatformAppleMobile) |
@@ -1136,18 +1137,36 @@ void scrollInfo_Widget(const iWidget *d, iWidgetScrollInfo *info) { | |||
1136 | } | 1137 | } |
1137 | } | 1138 | } |
1138 | 1139 | ||
1139 | iBool scrollOverflow_Widget(iWidget *d, int delta) { | 1140 | static iBool isOverflowScrollPossible_Widget_(const iWidget *d, int delta) { |
1141 | if (~d->flags & overflowScrollable_WidgetFlag) { | ||
1142 | return iFalse; | ||
1143 | } | ||
1140 | iRect bounds = boundsWithoutVisualOffset_Widget(d); | 1144 | iRect bounds = boundsWithoutVisualOffset_Widget(d); |
1141 | const iRect winRect = adjusted_Rect(safeRect_Root(d->root), | 1145 | const iRect winRect = adjusted_Rect(safeRect_Root(d->root), |
1142 | zero_I2(), | 1146 | zero_I2(), |
1143 | init_I2(0, -get_MainWindow()->keyboardHeight)); | 1147 | init_I2(0, -get_MainWindow()->keyboardHeight)); |
1144 | const int yTop = top_Rect(winRect); | 1148 | const int yTop = top_Rect(winRect); |
1145 | const int yBottom = bottom_Rect(winRect); | 1149 | const int yBottom = bottom_Rect(winRect); |
1146 | if (top_Rect(bounds) >= yTop && bottom_Rect(bounds) < yBottom) { | 1150 | if (delta == 0) { |
1147 | return iFalse; /* fits inside just fine */ | 1151 | if (top_Rect(bounds) >= yTop && bottom_Rect(bounds) <= yBottom) { |
1152 | return iFalse; /* fits inside just fine */ | ||
1153 | } | ||
1154 | } | ||
1155 | else if (delta > 0) { | ||
1156 | return top_Rect(bounds) < yTop; | ||
1157 | } | ||
1158 | return bottom_Rect(bounds) > yBottom; | ||
1159 | } | ||
1160 | |||
1161 | iBool scrollOverflow_Widget(iWidget *d, int delta) { | ||
1162 | if (!isOverflowScrollPossible_Widget_(d, delta)) { | ||
1163 | return iFalse; | ||
1148 | } | 1164 | } |
1149 | //const int safeBottom = rootSize.y - yBottom; | 1165 | iRect bounds = boundsWithoutVisualOffset_Widget(d); |
1150 | iRangei validPosRange = { bottom_Rect(winRect) - height_Rect(bounds), yTop }; | 1166 | const iRect winRect = adjusted_Rect(safeRect_Root(d->root), |
1167 | zero_I2(), | ||
1168 | init_I2(0, -get_MainWindow()->keyboardHeight)); | ||
1169 | iRangei validPosRange = { bottom_Rect(winRect) - height_Rect(bounds), top_Rect(winRect) }; | ||
1151 | if (validPosRange.start > validPosRange.end) { | 1170 | if (validPosRange.start > validPosRange.end) { |
1152 | validPosRange.start = validPosRange.end; /* no room to scroll */ | 1171 | validPosRange.start = validPosRange.end; /* no room to scroll */ |
1153 | } | 1172 | } |
@@ -1170,21 +1189,29 @@ iBool scrollOverflow_Widget(iWidget *d, int delta) { | |||
1170 | else { | 1189 | else { |
1171 | bounds.pos.y = iClamp(bounds.pos.y, validPosRange.start, validPosRange.end); | 1190 | bounds.pos.y = iClamp(bounds.pos.y, validPosRange.start, validPosRange.end); |
1172 | } | 1191 | } |
1173 | // if (delta >= 0) { | ||
1174 | // bounds.pos.y = iMin(bounds.pos.y, yTop); | ||
1175 | // } | ||
1176 | // else { | ||
1177 | // bounds.pos.y = iMax(bounds.pos.y, ); | ||
1178 | // } | ||
1179 | const iInt2 newPos = windowToInner_Widget(d->parent, bounds.pos); | 1192 | const iInt2 newPos = windowToInner_Widget(d->parent, bounds.pos); |
1180 | if (!isEqual_I2(newPos, d->rect.pos)) { | 1193 | if (!isEqual_I2(newPos, d->rect.pos)) { |
1181 | d->rect.pos = newPos; | 1194 | d->rect.pos = newPos; |
1182 | // refresh_Widget(d); | ||
1183 | postRefresh_App(); | 1195 | postRefresh_App(); |
1184 | } | 1196 | } |
1185 | return height_Rect(bounds) > height_Rect(winRect); | 1197 | return height_Rect(bounds) > height_Rect(winRect); |
1186 | } | 1198 | } |
1187 | 1199 | ||
1200 | static uint32_t lastHoverOverflowMotionTime_; | ||
1201 | |||
1202 | static void overflowHoverAnimation_(iAny *widget) { | ||
1203 | iWindow *win = window_Widget(widget); | ||
1204 | iInt2 coord = mouseCoord_Window(win, 0); | ||
1205 | /* A motion event will cause an overflow window to scroll. */ | ||
1206 | SDL_MouseMotionEvent ev = { | ||
1207 | .type = SDL_MOUSEMOTION, | ||
1208 | .windowID = SDL_GetWindowID(win->win), | ||
1209 | .x = coord.x / win->pixelRatio, | ||
1210 | .y = coord.y / win->pixelRatio, | ||
1211 | }; | ||
1212 | SDL_PushEvent((SDL_Event *) &ev); | ||
1213 | } | ||
1214 | |||
1188 | iBool processEvent_Widget(iWidget *d, const SDL_Event *ev) { | 1215 | iBool processEvent_Widget(iWidget *d, const SDL_Event *ev) { |
1189 | if (d->flags & commandOnClick_WidgetFlag && | 1216 | if (d->flags & commandOnClick_WidgetFlag && |
1190 | (ev->type == SDL_MOUSEBUTTONDOWN || ev->type == SDL_MOUSEBUTTONUP) && | 1217 | (ev->type == SDL_MOUSEBUTTONDOWN || ev->type == SDL_MOUSEBUTTONUP) && |
@@ -1202,14 +1229,45 @@ iBool processEvent_Widget(iWidget *d, const SDL_Event *ev) { | |||
1202 | postCommand_Widget(d, "mouse.moved coord:%d %d", ev->motion.x, ev->motion.y); | 1229 | postCommand_Widget(d, "mouse.moved coord:%d %d", ev->motion.x, ev->motion.y); |
1203 | return iTrue; | 1230 | return iTrue; |
1204 | } | 1231 | } |
1205 | else if (d->flags & overflowScrollable_WidgetFlag && ev->type == SDL_MOUSEWHEEL && | 1232 | else if (d->flags & overflowScrollable_WidgetFlag && ~d->flags & visualOffset_WidgetFlag) { |
1206 | ~d->flags & visualOffset_WidgetFlag) { | 1233 | if (ev->type == SDL_MOUSEWHEEL) { |
1207 | int step = ev->wheel.y; | 1234 | int step = ev->wheel.y; |
1208 | if (!isPerPixel_MouseWheelEvent(&ev->wheel)) { | 1235 | if (!isPerPixel_MouseWheelEvent(&ev->wheel)) { |
1209 | step *= lineHeight_Text(uiLabel_FontId); | 1236 | step *= lineHeight_Text(uiLabel_FontId); |
1237 | } | ||
1238 | if (scrollOverflow_Widget(d, step)) { | ||
1239 | return iTrue; | ||
1240 | } | ||
1210 | } | 1241 | } |
1211 | if (scrollOverflow_Widget(d, step)) { | 1242 | else if (ev->type == SDL_MOUSEMOTION && ev->motion.which != SDL_TOUCH_MOUSEID && |
1212 | return iTrue; | 1243 | ev->motion.y >= 0) { |
1244 | /* TODO: Motion events occur frequently. Maybe it would help if these were handled | ||
1245 | via audiences that specifically register to listen for motion, to minimize the | ||
1246 | number of widgets that need to process them. */ | ||
1247 | const int hoverScrollLimit = 2 * lineHeight_Text(default_FontId); | ||
1248 | float speed = 0.0f; | ||
1249 | if (ev->motion.y < hoverScrollLimit) { | ||
1250 | speed = (hoverScrollLimit - ev->motion.y) / (float) hoverScrollLimit; | ||
1251 | } | ||
1252 | else { | ||
1253 | const int bottomLimit = bottom_Rect(rect_Root(d->root)) - hoverScrollLimit; | ||
1254 | if (ev->motion.y > bottomLimit ) { | ||
1255 | speed = -(ev->motion.y - bottomLimit) / (float) hoverScrollLimit; | ||
1256 | } | ||
1257 | } | ||
1258 | if (speed != 0.0f && isOverflowScrollPossible_Widget_(d, speed > 0 ? 1 : -1)) { | ||
1259 | const uint32_t nowTime = SDL_GetTicks(); | ||
1260 | uint32_t elapsed = nowTime - lastHoverOverflowMotionTime_; | ||
1261 | if (elapsed > 100) { | ||
1262 | elapsed = 16; | ||
1263 | } | ||
1264 | int step = elapsed * gap_UI / 16 * iClamp(speed, -1.0f, 1.0f); | ||
1265 | if (step != 0) { | ||
1266 | lastHoverOverflowMotionTime_ = nowTime; | ||
1267 | scrollOverflow_Widget(d, step); | ||
1268 | } | ||
1269 | addTicker_App(overflowHoverAnimation_, d); | ||
1270 | } | ||
1213 | } | 1271 | } |
1214 | } | 1272 | } |
1215 | switch (ev->type) { | 1273 | switch (ev->type) { |