diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-01-24 15:28:35 +0200 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-01-24 15:30:26 +0200 |
commit | b1813bc5b1a2fb68853c59c2576833435762c2ec (patch) | |
tree | fca54e8b00faddef785d9d364b8458fa77bb03d7 /src/ui | |
parent | bc7d3e49b5d9dfc82c4c2e78ac00b3e568975183 (diff) |
DocumentWidget: Fix/improve home row link navigation
Removed the second `U` from the set of home row keys for link navigation.
Added a new command bound to `.` (period) for switching to the next set of links for home row navigation. This makes it possible to access all visible links via keyboard no matter how many there are. `.` can also be used to activate home row navigation if it isn't active.
IssueID #111
Diffstat (limited to 'src/ui')
-rw-r--r-- | src/ui/documentwidget.c | 111 | ||||
-rw-r--r-- | src/ui/keys.c | 1 |
2 files changed, 76 insertions, 36 deletions
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 1ee83a85..c725a8f6 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c | |||
@@ -146,6 +146,7 @@ struct Impl_DocumentWidget { | |||
146 | iPersistentDocumentState mod; | 146 | iPersistentDocumentState mod; |
147 | int flags; | 147 | int flags; |
148 | enum iDocumentLinkOrdinalMode ordinalMode; | 148 | enum iDocumentLinkOrdinalMode ordinalMode; |
149 | size_t ordinalBase; | ||
149 | iString * titleUser; | 150 | iString * titleUser; |
150 | iGmRequest * request; | 151 | iGmRequest * request; |
151 | iAtomicInt isRequestUpdated; /* request has new content, need to parse it */ | 152 | iAtomicInt isRequestUpdated; /* request has new content, need to parse it */ |
@@ -213,6 +214,7 @@ void init_DocumentWidget(iDocumentWidget *d) { | |||
213 | d->media = new_ObjectList(); | 214 | d->media = new_ObjectList(); |
214 | d->doc = new_GmDocument(); | 215 | d->doc = new_GmDocument(); |
215 | d->redirectCount = 0; | 216 | d->redirectCount = 0; |
217 | d->ordinalBase = 0; | ||
216 | d->initNormScrollY = 0; | 218 | d->initNormScrollY = 0; |
217 | init_Anim(&d->scrollY, 0); | 219 | init_Anim(&d->scrollY, 0); |
218 | d->animWideRunId = 0; | 220 | d->animWideRunId = 0; |
@@ -380,6 +382,16 @@ static void addVisible_DocumentWidget_(void *context, const iGmRun *run) { | |||
380 | } | 382 | } |
381 | } | 383 | } |
382 | 384 | ||
385 | static const iGmRun *lastVisibleLink_DocumentWidget_(const iDocumentWidget *d) { | ||
386 | iReverseConstForEach(PtrArray, i, &d->visibleLinks) { | ||
387 | const iGmRun *run = i.ptr; | ||
388 | if (run->flags & decoration_GmRunFlag && run->linkId) { | ||
389 | return run; | ||
390 | } | ||
391 | } | ||
392 | return NULL; | ||
393 | } | ||
394 | |||
383 | static float normScrollPos_DocumentWidget_(const iDocumentWidget *d) { | 395 | static float normScrollPos_DocumentWidget_(const iDocumentWidget *d) { |
384 | const int docSize = size_GmDocument(d->doc).y; | 396 | const int docSize = size_GmDocument(d->doc).y; |
385 | if (docSize) { | 397 | if (docSize) { |
@@ -1489,6 +1501,34 @@ static void addAllLinks_(void *context, const iGmRun *run) { | |||
1489 | } | 1501 | } |
1490 | } | 1502 | } |
1491 | 1503 | ||
1504 | static size_t visibleLinkOrdinal_DocumentWidget_(const iDocumentWidget *d, iGmLinkId linkId) { | ||
1505 | size_t ord = 0; | ||
1506 | const iRangei visRange = visibleRange_DocumentWidget_(d); | ||
1507 | iConstForEach(PtrArray, i, &d->visibleLinks) { | ||
1508 | const iGmRun *run = i.ptr; | ||
1509 | if (top_Rect(run->visBounds) >= visRange.start + gap_UI * d->pageMargin * 4 / 5) { | ||
1510 | if (run->flags & decoration_GmRunFlag && run->linkId) { | ||
1511 | if (run->linkId == linkId) return ord; | ||
1512 | ord++; | ||
1513 | } | ||
1514 | } | ||
1515 | } | ||
1516 | return iInvalidPos; | ||
1517 | } | ||
1518 | |||
1519 | /* Sorted by proximity to F and J. */ | ||
1520 | static const int homeRowKeys_[] = { | ||
1521 | 'f', 'd', 's', 'a', | ||
1522 | 'j', 'k', 'l', | ||
1523 | 'r', 'e', 'w', 'q', | ||
1524 | 'u', 'i', 'o', 'p', | ||
1525 | 'v', 'c', 'x', 'z', | ||
1526 | 'm', 'n', | ||
1527 | 'g', 'h', | ||
1528 | 'b', | ||
1529 | 't', 'y', | ||
1530 | }; | ||
1531 | |||
1492 | static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) { | 1532 | static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) { |
1493 | iWidget *w = as_Widget(d); | 1533 | iWidget *w = as_Widget(d); |
1494 | if (equal_Command(cmd, "window.resized") || equal_Command(cmd, "font.changed")) { | 1534 | if (equal_Command(cmd, "window.resized") || equal_Command(cmd, "font.changed")) { |
@@ -1785,8 +1825,30 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
1785 | if (argLabel_Command(cmd, "release")) { | 1825 | if (argLabel_Command(cmd, "release")) { |
1786 | iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iFalse); | 1826 | iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iFalse); |
1787 | } | 1827 | } |
1828 | else if (argLabel_Command(cmd, "more")) { | ||
1829 | if (d->flags & showLinkNumbers_DocumentWidgetFlag && | ||
1830 | d->ordinalMode == homeRow_DocumentLinkOrdinalMode) { | ||
1831 | const size_t numKeys = iElemCount(homeRowKeys_); | ||
1832 | const iGmRun *last = lastVisibleLink_DocumentWidget_(d); | ||
1833 | if (!last) { | ||
1834 | d->ordinalBase = 0; | ||
1835 | } | ||
1836 | else { | ||
1837 | d->ordinalBase += numKeys; | ||
1838 | if (visibleLinkOrdinal_DocumentWidget_(d, last->linkId) < d->ordinalBase) { | ||
1839 | d->ordinalBase = 0; | ||
1840 | } | ||
1841 | } | ||
1842 | } | ||
1843 | else if (~d->flags & showLinkNumbers_DocumentWidgetFlag) { | ||
1844 | d->ordinalMode = homeRow_DocumentLinkOrdinalMode; | ||
1845 | d->ordinalBase = 0; | ||
1846 | iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iTrue); | ||
1847 | } | ||
1848 | } | ||
1788 | else { | 1849 | else { |
1789 | d->ordinalMode = arg_Command(cmd); | 1850 | d->ordinalMode = arg_Command(cmd); |
1851 | d->ordinalBase = 0; | ||
1790 | iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iTrue); | 1852 | iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iTrue); |
1791 | iChangeFlags(d->flags, setHoverViaKeys_DocumentWidgetFlag, | 1853 | iChangeFlags(d->flags, setHoverViaKeys_DocumentWidgetFlag, |
1792 | argLabel_Command(cmd, "hover") != 0); | 1854 | argLabel_Command(cmd, "hover") != 0); |
@@ -1980,25 +2042,12 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
1980 | return iFalse; | 2042 | return iFalse; |
1981 | } | 2043 | } |
1982 | 2044 | ||
2045 | #if 0 | ||
1983 | static int outlineHeight_DocumentWidget_(const iDocumentWidget *d) { | 2046 | static int outlineHeight_DocumentWidget_(const iDocumentWidget *d) { |
1984 | if (isEmpty_Array(&d->outline)) return 0; | 2047 | if (isEmpty_Array(&d->outline)) return 0; |
1985 | return bottom_Rect(((const iOutlineItem *) constBack_Array(&d->outline))->rect); | 2048 | return bottom_Rect(((const iOutlineItem *) constBack_Array(&d->outline))->rect); |
1986 | } | 2049 | } |
1987 | 2050 | #endif | |
1988 | static size_t visibleLinkOrdinal_DocumentWidget_(const iDocumentWidget *d, iGmLinkId linkId) { | ||
1989 | size_t ord = 0; | ||
1990 | const iRangei visRange = visibleRange_DocumentWidget_(d); | ||
1991 | iConstForEach(PtrArray, i, &d->visibleLinks) { | ||
1992 | const iGmRun *run = i.ptr; | ||
1993 | if (top_Rect(run->visBounds) >= visRange.start + gap_UI * d->pageMargin * 4 / 5) { | ||
1994 | if (run->flags & decoration_GmRunFlag && run->linkId) { | ||
1995 | if (run->linkId == linkId) return ord; | ||
1996 | ord++; | ||
1997 | } | ||
1998 | } | ||
1999 | } | ||
2000 | return iInvalidPos; | ||
2001 | } | ||
2002 | 2051 | ||
2003 | static iRect playerRect_DocumentWidget_(const iDocumentWidget *d, const iGmRun *run) { | 2052 | static iRect playerRect_DocumentWidget_(const iDocumentWidget *d, const iGmRun *run) { |
2004 | const iRect docBounds = documentBounds_DocumentWidget_(d); | 2053 | const iRect docBounds = documentBounds_DocumentWidget_(d); |
@@ -2110,19 +2159,6 @@ static iBool processPlayerEvents_DocumentWidget_(iDocumentWidget *d, const SDL_E | |||
2110 | return iFalse; | 2159 | return iFalse; |
2111 | } | 2160 | } |
2112 | 2161 | ||
2113 | /* Sorted by proximity to F and J. */ | ||
2114 | static const int homeRowKeys_[] = { | ||
2115 | 'f', 'd', 's', 'a', | ||
2116 | 'j', 'k', 'l', | ||
2117 | 'r', 'e', 'w', 'q', | ||
2118 | 'u', 'i', 'o', 'p', | ||
2119 | 'v', 'c', 'x', 'z', | ||
2120 | 'm', 'n', | ||
2121 | 'g', 'h', | ||
2122 | 'b', | ||
2123 | 't', 'y', 'u', | ||
2124 | }; | ||
2125 | |||
2126 | static size_t linkOrdinalFromKey_DocumentWidget_(const iDocumentWidget *d, int key) { | 2162 | static size_t linkOrdinalFromKey_DocumentWidget_(const iDocumentWidget *d, int key) { |
2127 | size_t ord = iInvalidPos; | 2163 | size_t ord = iInvalidPos; |
2128 | if (d->ordinalMode == numbersAndAlphabet_DocumentLinkOrdinalMode) { | 2164 | if (d->ordinalMode == numbersAndAlphabet_DocumentLinkOrdinalMode) { |
@@ -2195,7 +2231,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
2195 | const int key = ev->key.keysym.sym; | 2231 | const int key = ev->key.keysym.sym; |
2196 | if ((d->flags & showLinkNumbers_DocumentWidgetFlag) && | 2232 | if ((d->flags & showLinkNumbers_DocumentWidgetFlag) && |
2197 | ((key >= '1' && key <= '9') || (key >= 'a' && key <= 'z'))) { | 2233 | ((key >= '1' && key <= '9') || (key >= 'a' && key <= 'z'))) { |
2198 | const size_t ord = linkOrdinalFromKey_DocumentWidget_(d, key); | 2234 | const size_t ord = linkOrdinalFromKey_DocumentWidget_(d, key) + d->ordinalBase; |
2199 | iConstForEach(PtrArray, i, &d->visibleLinks) { | 2235 | iConstForEach(PtrArray, i, &d->visibleLinks) { |
2200 | if (ord == iInvalidPos) break; | 2236 | if (ord == iInvalidPos) break; |
2201 | const iGmRun *run = i.ptr; | 2237 | const iGmRun *run = i.ptr; |
@@ -2801,13 +2837,16 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { | |||
2801 | else { | 2837 | else { |
2802 | if (d->showLinkNumbers && run->linkId && run->flags & decoration_GmRunFlag) { | 2838 | if (d->showLinkNumbers && run->linkId && run->flags & decoration_GmRunFlag) { |
2803 | const size_t ord = visibleLinkOrdinal_DocumentWidget_(d->widget, run->linkId); | 2839 | const size_t ord = visibleLinkOrdinal_DocumentWidget_(d->widget, run->linkId); |
2804 | const iChar ordChar = linkOrdinalChar_DocumentWidget_(d->widget, ord); | 2840 | if (ord >= d->widget->ordinalBase) { |
2805 | if (ordChar) { | 2841 | const iChar ordChar = |
2806 | drawString_Text(run->font, | 2842 | linkOrdinalChar_DocumentWidget_(d->widget, ord - d->widget->ordinalBase); |
2807 | init_I2(d->viewPos.x - gap_UI / 3, visPos.y), | 2843 | if (ordChar) { |
2808 | tmQuote_ColorId, | 2844 | drawString_Text(run->font, |
2809 | collect_String(newUnicodeN_String(&ordChar, 1))); | 2845 | init_I2(d->viewPos.x - gap_UI / 3, visPos.y), |
2810 | goto runDrawn; | 2846 | tmQuote_ColorId, |
2847 | collect_String(newUnicodeN_String(&ordChar, 1))); | ||
2848 | goto runDrawn; | ||
2849 | } | ||
2811 | } | 2850 | } |
2812 | } | 2851 | } |
2813 | if (run->flags & quoteBorder_GmRunFlag) { | 2852 | if (run->flags & quoteBorder_GmRunFlag) { |
diff --git a/src/ui/keys.c b/src/ui/keys.c index 10855b8d..ec681b6f 100644 --- a/src/ui/keys.c +++ b/src/ui/keys.c | |||
@@ -79,6 +79,7 @@ static const struct { int id; iMenuItem bind; int flags; } defaultBindings_[] = | |||
79 | { 42, { "Open link via home row keys", 'f', 0, "document.linkkeys arg:1" }, 0 }, | 79 | { 42, { "Open link via home row keys", 'f', 0, "document.linkkeys arg:1" }, 0 }, |
80 | { 45, { "Open link in new tab via home row keys", 'f', KMOD_SHIFT, "document.linkkeys arg:1 newtab:1" }, 0 }, | 80 | { 45, { "Open link in new tab via home row keys", 'f', KMOD_SHIFT, "document.linkkeys arg:1 newtab:1" }, 0 }, |
81 | { 46, { "Hover on link via home row keys", 'h', 0, "document.linkkeys arg:1 hover:1" }, 0 }, | 81 | { 46, { "Hover on link via home row keys", 'h', 0, "document.linkkeys arg:1 hover:1" }, 0 }, |
82 | { 47, { "Next set of home row key links", '.', 0, "document.linkkeys more:1" }, 0 }, | ||
82 | { 70, { "Zoom in", SDLK_EQUALS, KMOD_PRIMARY, "zoom.delta arg:10" }, 0 }, | 83 | { 70, { "Zoom in", SDLK_EQUALS, KMOD_PRIMARY, "zoom.delta arg:10" }, 0 }, |
83 | { 71, { "Zoom out", SDLK_MINUS, KMOD_PRIMARY, "zoom.delta arg:-10" }, 0 }, | 84 | { 71, { "Zoom out", SDLK_MINUS, KMOD_PRIMARY, "zoom.delta arg:-10" }, 0 }, |
84 | { 72, { "Reset zoom", SDLK_0, KMOD_PRIMARY, "zoom.set arg:100" }, 0 }, | 85 | { 72, { "Reset zoom", SDLK_0, KMOD_PRIMARY, "zoom.set arg:100" }, 0 }, |