summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2020-08-25 16:50:30 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2020-08-25 16:50:30 +0300
commit327e2fd4229bf14ef61b0aeace9c8503b183f7e9 (patch)
tree616510056dc1c91a21fe8c45204110e59fdc94a6
parent37d93f50a1e4c2c1d95246e2110c7bafb5ff5c89 (diff)
DocumentWidget: Smooth scrolling for keyboard
-rw-r--r--src/app.c16
-rw-r--r--src/app.h11
-rw-r--r--src/ui/color.c2
-rw-r--r--src/ui/documentwidget.c95
4 files changed, 112 insertions, 12 deletions
diff --git a/src/app.c b/src/app.c
index 76298ec5..1e37030f 100644
--- a/src/app.c
+++ b/src/app.c
@@ -47,6 +47,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
47#include <the_Foundation/time.h> 47#include <the_Foundation/time.h>
48#include <SDL_events.h> 48#include <SDL_events.h>
49#include <SDL_render.h> 49#include <SDL_render.h>
50#include <SDL_timer.h>
50#include <SDL_video.h> 51#include <SDL_video.h>
51 52
52#include <stdio.h> 53#include <stdio.h>
@@ -82,6 +83,8 @@ struct Impl_App {
82 iBookmarks * bookmarks; 83 iBookmarks * bookmarks;
83 iWindow * window; 84 iWindow * window;
84 iSortedArray tickers; 85 iSortedArray tickers;
86 uint32_t lastTickerTime;
87 uint32_t elapsedSinceLastTicker;
85 iBool running; 88 iBool running;
86 iBool pendingRefresh; 89 iBool pendingRefresh;
87 int tabEnum; 90 int tabEnum;
@@ -255,6 +258,8 @@ static void saveState_App_(const iApp *d) {
255static void init_App_(iApp *d, int argc, char **argv) { 258static void init_App_(iApp *d, int argc, char **argv) {
256 init_CommandLine(&d->args, argc, argv); 259 init_CommandLine(&d->args, argc, argv);
257 init_SortedArray(&d->tickers, sizeof(iTicker), cmp_Ticker_); 260 init_SortedArray(&d->tickers, sizeof(iTicker), cmp_Ticker_);
261 d->lastTickerTime = SDL_GetTicks();
262 d->elapsedSinceLastTicker = 0;
258 d->commandEcho = checkArgument_CommandLine(&d->args, "echo") != NULL; 263 d->commandEcho = checkArgument_CommandLine(&d->args, "echo") != NULL;
259 d->initialWindowRect = init_Rect(-1, -1, 800, 500); 264 d->initialWindowRect = init_Rect(-1, -1, 800, 500);
260 d->theme = dark_ColorTheme; 265 d->theme = dark_ColorTheme;
@@ -350,6 +355,9 @@ backToMainLoop:;
350} 355}
351 356
352static void runTickers_App_(iApp *d) { 357static void runTickers_App_(iApp *d) {
358 const uint32_t now = SDL_GetTicks();
359 d->elapsedSinceLastTicker = (d->lastTickerTime ? now - d->lastTickerTime : 0);
360 d->lastTickerTime = now;
353 /* Tickers may add themselves again, so we'll run off a copy. */ 361 /* Tickers may add themselves again, so we'll run off a copy. */
354 iSortedArray *pending = copy_SortedArray(&d->tickers); 362 iSortedArray *pending = copy_SortedArray(&d->tickers);
355 clear_SortedArray(&d->tickers); 363 clear_SortedArray(&d->tickers);
@@ -389,6 +397,10 @@ iBool isRefreshPending_App(void) {
389 return app_.pendingRefresh; 397 return app_.pendingRefresh;
390} 398}
391 399
400uint32_t elapsedSinceLastTicker_App(void) {
401 return app_.elapsedSinceLastTicker;
402}
403
392int zoom_App(void) { 404int zoom_App(void) {
393 return app_.zoomPercent; 405 return app_.zoomPercent;
394} 406}
@@ -407,6 +419,9 @@ int run_App(int argc, char **argv) {
407void postRefresh_App(void) { 419void postRefresh_App(void) {
408 iApp *d = &app_; 420 iApp *d = &app_;
409 if (!d->pendingRefresh) { 421 if (!d->pendingRefresh) {
422 if (!isEmpty_SortedArray(&d->tickers)) {
423 d->lastTickerTime = 0; /* tickers had been paused */
424 }
410 d->pendingRefresh = iTrue; 425 d->pendingRefresh = iTrue;
411 SDL_Event ev; 426 SDL_Event ev;
412 ev.user.type = SDL_USEREVENT; 427 ev.user.type = SDL_USEREVENT;
@@ -454,6 +469,7 @@ iAny *findWidget_App(const char *id) {
454void addTicker_App(void (*ticker)(iAny *), iAny *context) { 469void addTicker_App(void (*ticker)(iAny *), iAny *context) {
455 iApp *d = &app_; 470 iApp *d = &app_;
456 insert_SortedArray(&d->tickers, &(iTicker){ context, ticker }); 471 insert_SortedArray(&d->tickers, &(iTicker){ context, ticker });
472 postRefresh_App();
457} 473}
458 474
459iGmCerts *certs_App(void) { 475iGmCerts *certs_App(void) {
diff --git a/src/app.h b/src/app.h
index 75fc4331..1a6b7fe5 100644
--- a/src/app.h
+++ b/src/app.h
@@ -48,11 +48,12 @@ enum iUserEventCode {
48const iString *execPath_App (void); 48const iString *execPath_App (void);
49const iString *dataDir_App (void); 49const iString *dataDir_App (void);
50 50
51int run_App (int argc, char **argv); 51int run_App (int argc, char **argv);
52void processEvents_App (enum iAppEventMode mode); 52void processEvents_App (enum iAppEventMode mode);
53iBool handleCommand_App (const char *cmd); 53iBool handleCommand_App (const char *cmd);
54void refresh_App (void); 54void refresh_App (void);
55iBool isRefreshPending_App(void); 55iBool isRefreshPending_App (void);
56uint32_t elapsedSinceLastTicker_App (void); /* milliseconds */
56 57
57int zoom_App (void); 58int zoom_App (void);
58enum iColorTheme colorTheme_App (void); 59enum iColorTheme colorTheme_App (void);
diff --git a/src/ui/color.c b/src/ui/color.c
index dcdc5788..441db18d 100644
--- a/src/ui/color.c
+++ b/src/ui/color.c
@@ -187,7 +187,7 @@ void setThemePalette_Color(enum iColorTheme theme) {
187 copy_(uiInputBackgroundFocused_ColorId, white_ColorId); 187 copy_(uiInputBackgroundFocused_ColorId, white_ColorId);
188 copy_(uiInputText_ColorId, gray25_ColorId); 188 copy_(uiInputText_ColorId, gray25_ColorId);
189 copy_(uiInputTextFocused_ColorId, black_ColorId); 189 copy_(uiInputTextFocused_ColorId, black_ColorId);
190 copy_(uiInputFrame_ColorId, gray25_ColorId); 190 copy_(uiInputFrame_ColorId, gray50_ColorId);
191 copy_(uiInputFrameHover_ColorId, brown_ColorId); 191 copy_(uiInputFrameHover_ColorId, brown_ColorId);
192 copy_(uiInputFrameFocused_ColorId, teal_ColorId); 192 copy_(uiInputFrameFocused_ColorId, teal_ColorId);
193 copy_(uiInputCursor_ColorId, teal_ColorId); 193 copy_(uiInputCursor_ColorId, teal_ColorId);
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index a36fe761..5292d3f5 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -162,6 +162,8 @@ iDefineTypeConstruction(VisBuffer)
162 162
163/*----------------------------------------------------------------------------------------------*/ 163/*----------------------------------------------------------------------------------------------*/
164 164
165static const int smoothSpeed_DocumentWidget_ = 150; /* unit: gap_Text per second */
166
165enum iRequestState { 167enum iRequestState {
166 blank_RequestState, 168 blank_RequestState,
167 fetching_RequestState, 169 fetching_RequestState,
@@ -193,6 +195,10 @@ struct Impl_DocumentWidget {
193 iClick click; 195 iClick click;
194 float initNormScrollY; 196 float initNormScrollY;
195 int scrollY; 197 int scrollY;
198 int smoothScroll;
199 int smoothSpeed;
200 int lastSmoothOffset;
201 iBool keepScrolling;
196 iScrollWidget *scroll; 202 iScrollWidget *scroll;
197 iWidget * menu; 203 iWidget * menu;
198 iVisBuffer * visBuffer; 204 iVisBuffer * visBuffer;
@@ -217,6 +223,10 @@ void init_DocumentWidget(iDocumentWidget *d) {
217 d->doc = new_GmDocument(); 223 d->doc = new_GmDocument();
218 d->initNormScrollY = 0; 224 d->initNormScrollY = 0;
219 d->scrollY = 0; 225 d->scrollY = 0;
226 d->smoothScroll = 0;
227 d->smoothSpeed = 0;
228 d->lastSmoothOffset = 0;
229 d->keepScrolling = iFalse;
220 d->selecting = iFalse; 230 d->selecting = iFalse;
221 d->selectMark = iNullRange; 231 d->selectMark = iNullRange;
222 d->foundMark = iNullRange; 232 d->foundMark = iNullRange;
@@ -254,6 +264,13 @@ void deinit_DocumentWidget(iDocumentWidget *d) {
254 deinit_Model(&d->mod); 264 deinit_Model(&d->mod);
255} 265}
256 266
267static void resetSmoothScroll_DocumentWidget_(iDocumentWidget *d) {
268 d->smoothSpeed = 0;
269 d->smoothScroll = 0;
270 d->lastSmoothOffset = 0;
271 d->keepScrolling = iFalse;
272}
273
257static int documentWidth_DocumentWidget_(const iDocumentWidget *d) { 274static int documentWidth_DocumentWidget_(const iDocumentWidget *d) {
258 const iWidget *w = constAs_Widget(d); 275 const iWidget *w = constAs_Widget(d);
259 const iRect bounds = bounds_Widget(w); 276 const iRect bounds = bounds_Widget(w);
@@ -287,9 +304,11 @@ iLocalDef int documentToWindowY_DocumentWidget_(const iDocumentWidget *d, int do
287 return docY - d->scrollY + documentBounds_DocumentWidget_(d).pos.y; 304 return docY - d->scrollY + documentBounds_DocumentWidget_(d).pos.y;
288} 305}
289 306
307#if 0
290iLocalDef int windowToDocumentY_DocumentWidget_(const iDocumentWidget *d, int localY) { 308iLocalDef int windowToDocumentY_DocumentWidget_(const iDocumentWidget *d, int localY) {
291 return localY + d->scrollY - documentBounds_DocumentWidget_(d).pos.y; 309 return localY + d->scrollY - documentBounds_DocumentWidget_(d).pos.y;
292} 310}
311#endif
293 312
294static iInt2 documentPos_DocumentWidget_(const iDocumentWidget *d, iInt2 pos) { 313static iInt2 documentPos_DocumentWidget_(const iDocumentWidget *d, iInt2 pos) {
295 return addY_I2(sub_I2(pos, topLeft_Rect(documentBounds_DocumentWidget_(d))), d->scrollY); 314 return addY_I2(sub_I2(pos, topLeft_Rect(documentBounds_DocumentWidget_(d))), d->scrollY);
@@ -500,6 +519,7 @@ static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode
500 break; 519 break;
501 } 520 }
502 setSource_DocumentWidget_(d, src); 521 setSource_DocumentWidget_(d, src);
522 resetSmoothScroll_DocumentWidget_(d);
503 d->scrollY = 0; 523 d->scrollY = 0;
504 d->state = ready_RequestState; 524 d->state = ready_RequestState;
505} 525}
@@ -743,6 +763,37 @@ static void scroll_DocumentWidget_(iDocumentWidget *d, int offset) {
743 refresh_Widget(as_Widget(d)); 763 refresh_Widget(as_Widget(d));
744} 764}
745 765
766static void doScroll_DocumentWidget_(iAny *ptr) {
767 iDocumentWidget *d = ptr;
768 if (!d->smoothScroll) return; /* was cancelled */
769 const double elapsed = (double) elapsedSinceLastTicker_App() / 1000.0;
770 int delta = d->smoothSpeed * elapsed * iSign(d->smoothScroll);
771 if (iAbs(d->smoothScroll) <= iAbs(delta)) {
772 if (d->keepScrolling) {
773 d->smoothScroll += d->lastSmoothOffset;
774 }
775 else {
776 delta = d->smoothScroll;
777 }
778 }
779 scroll_DocumentWidget_(d, delta);
780 d->smoothScroll -= delta;
781 if (d->smoothScroll != 0) {
782 addTicker_App(doScroll_DocumentWidget_, d);
783 }
784}
785
786static void smoothScroll_DocumentWidget_(iDocumentWidget *d, int offset, int speed) {
787 if (speed == 0) {
788 scroll_DocumentWidget_(d, offset);
789 return;
790 }
791 d->smoothSpeed = speed;
792 d->smoothScroll += offset;
793 d->lastSmoothOffset = offset;
794 addTicker_App(doScroll_DocumentWidget_, d);
795}
796
746static void scrollTo_DocumentWidget_(iDocumentWidget *d, int documentY, iBool centered) { 797static void scrollTo_DocumentWidget_(iDocumentWidget *d, int documentY, iBool centered) {
747 d->scrollY = documentY - (centered ? documentBounds_DocumentWidget_(d).size.y / 2 : 798 d->scrollY = documentY - (centered ? documentBounds_DocumentWidget_(d).size.y / 2 :
748 lineHeight_Text(paragraph_FontId)); 799 lineHeight_Text(paragraph_FontId));
@@ -780,6 +831,7 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
780 } 831 }
781 case categorySuccess_GmStatusCode: 832 case categorySuccess_GmStatusCode:
782 d->scrollY = 0; 833 d->scrollY = 0;
834 resetSmoothScroll_DocumentWidget_(d);
783 reset_GmDocument(d->doc); /* new content incoming */ 835 reset_GmDocument(d->doc); /* new content incoming */
784 updateDocument_DocumentWidget_(d, response_GmRequest(d->request)); 836 updateDocument_DocumentWidget_(d, response_GmRequest(d->request));
785 break; 837 break;
@@ -1076,6 +1128,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
1076 else if (equalWidget_Command(cmd, w, "document.request.finished") && 1128 else if (equalWidget_Command(cmd, w, "document.request.finished") &&
1077 pointerLabel_Command(cmd, "request") == d->request) { 1129 pointerLabel_Command(cmd, "request") == d->request) {
1078 checkResponse_DocumentWidget_(d); 1130 checkResponse_DocumentWidget_(d);
1131 resetSmoothScroll_DocumentWidget_(d);
1079 d->scrollY = d->initNormScrollY * size_GmDocument(d->doc).y; 1132 d->scrollY = d->initNormScrollY * size_GmDocument(d->doc).y;
1080 d->state = ready_RequestState; 1133 d->state = ready_RequestState;
1081 /* The response may be cached. */ { 1134 /* The response may be cached. */ {
@@ -1123,12 +1176,24 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
1123 } 1176 }
1124 else if (equalWidget_Command(cmd, w, "scroll.moved")) { 1177 else if (equalWidget_Command(cmd, w, "scroll.moved")) {
1125 d->scrollY = arg_Command(cmd); 1178 d->scrollY = arg_Command(cmd);
1179 resetSmoothScroll_DocumentWidget_(d);
1126 updateVisible_DocumentWidget_(d); 1180 updateVisible_DocumentWidget_(d);
1127 return iTrue; 1181 return iTrue;
1128 } 1182 }
1129 else if (equalWidget_Command(cmd, w, "scroll.page")) { 1183 else if (equalWidget_Command(cmd, w, "scroll.page")) {
1130 scroll_DocumentWidget_(d, 1184 if (argLabel_Command(cmd, "repeat")) {
1131 arg_Command(cmd) * height_Rect(documentBounds_DocumentWidget_(d))); 1185 if (!d->keepScrolling) {
1186 d->keepScrolling = iTrue;
1187 }
1188 else {
1189 return iTrue;
1190 }
1191 }
1192 smoothScroll_DocumentWidget_(d,
1193 arg_Command(cmd) *
1194 (0.5f * height_Rect(documentBounds_DocumentWidget_(d)) -
1195 0 * lineHeight_Text(paragraph_FontId)),
1196 15 * smoothSpeed_DocumentWidget_);
1132 return iTrue; 1197 return iTrue;
1133 } 1198 }
1134 else if (equal_Command(cmd, "document.goto") && document_App() == d) { 1199 else if (equal_Command(cmd, "document.goto") && document_App() == d) {
@@ -1211,6 +1276,13 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
1211 refresh_Widget(w); 1276 refresh_Widget(w);
1212 } 1277 }
1213 break; 1278 break;
1279 case SDLK_PAGEUP:
1280 case SDLK_PAGEDOWN:
1281 case SDLK_SPACE:
1282 case SDLK_UP:
1283 case SDLK_DOWN:
1284 d->keepScrolling = iFalse;
1285 break;
1214 } 1286 }
1215 } 1287 }
1216 if (ev->type == SDL_KEYDOWN) { 1288 if (ev->type == SDL_KEYDOWN) {
@@ -1241,12 +1313,14 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
1241 break; 1313 break;
1242 case SDLK_HOME: 1314 case SDLK_HOME:
1243 d->scrollY = 0; 1315 d->scrollY = 0;
1316 resetSmoothScroll_DocumentWidget_(d);
1244 scroll_DocumentWidget_(d, 0); 1317 scroll_DocumentWidget_(d, 0);
1245 updateVisible_DocumentWidget_(d); 1318 updateVisible_DocumentWidget_(d);
1246 refresh_Widget(w); 1319 refresh_Widget(w);
1247 return iTrue; 1320 return iTrue;
1248 case SDLK_END: 1321 case SDLK_END:
1249 d->scrollY = scrollMax_DocumentWidget_(d); 1322 d->scrollY = scrollMax_DocumentWidget_(d);
1323 resetSmoothScroll_DocumentWidget_(d);
1250 scroll_DocumentWidget_(d, 0); 1324 scroll_DocumentWidget_(d, 0);
1251 updateVisible_DocumentWidget_(d); 1325 updateVisible_DocumentWidget_(d);
1252 refresh_Widget(w); 1326 refresh_Widget(w);
@@ -1254,8 +1328,16 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
1254 case SDLK_UP: 1328 case SDLK_UP:
1255 case SDLK_DOWN: 1329 case SDLK_DOWN:
1256 if (mods == 0) { 1330 if (mods == 0) {
1257 scroll_DocumentWidget_(d, 2 * lineHeight_Text(default_FontId) * 1331 if (ev->key.repeat) {
1258 (key == SDLK_UP ? -1 : 1)); 1332 if (!d->keepScrolling) {
1333 d->keepScrolling = iTrue;
1334 }
1335 else return iTrue;
1336 }
1337 smoothScroll_DocumentWidget_(d,
1338 3 * lineHeight_Text(paragraph_FontId) *
1339 (key == SDLK_UP ? -1 : 1),
1340 gap_Text * smoothSpeed_DocumentWidget_);
1259 return iTrue; 1341 return iTrue;
1260 } 1342 }
1261 break; 1343 break;
@@ -1264,8 +1346,9 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
1264 case SDLK_SPACE: 1346 case SDLK_SPACE:
1265 postCommand_Widget( 1347 postCommand_Widget(
1266 w, 1348 w,
1267 "scroll.page arg:%d", 1349 "scroll.page arg:%d repeat:%d",
1268 (key == SDLK_SPACE && mods & KMOD_SHIFT) || key == SDLK_PAGEUP ? -1 : +1); 1350 (key == SDLK_SPACE && mods & KMOD_SHIFT) || key == SDLK_PAGEUP ? -1 : +1,
1351 ev->key.repeat != 0);
1269 return iTrue; 1352 return iTrue;
1270#if 1 1353#if 1
1271 case SDLK_KP_1: { 1354 case SDLK_KP_1: {