diff options
-rw-r--r-- | CMakeLists.txt | 4 | ||||
-rw-r--r-- | src/defs.h | 1 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 202 | ||||
-rw-r--r-- | src/ui/scrollwidget.c | 11 | ||||
-rw-r--r-- | src/ui/scrollwidget.h | 1 | ||||
-rw-r--r-- | src/ui/touch.c | 27 | ||||
-rw-r--r-- | src/ui/util.c | 26 | ||||
-rw-r--r-- | src/ui/util.h | 4 | ||||
-rw-r--r-- | src/ui/widget.c | 5 | ||||
-rw-r--r-- | src/ui/widget.h | 1 | ||||
-rw-r--r-- | src/ui/window.c | 45 |
11 files changed, 253 insertions, 74 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index b6093888..bb937fa0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt | |||
@@ -18,11 +18,11 @@ | |||
18 | cmake_minimum_required (VERSION 3.9) | 18 | cmake_minimum_required (VERSION 3.9) |
19 | 19 | ||
20 | project (Lagrange | 20 | project (Lagrange |
21 | VERSION 1.3.2 | 21 | VERSION 1.4.0 |
22 | DESCRIPTION "A Beautiful Gemini Client" | 22 | DESCRIPTION "A Beautiful Gemini Client" |
23 | LANGUAGES C | 23 | LANGUAGES C |
24 | ) | 24 | ) |
25 | set (IOS_BUNDLE_VERSION 9) | 25 | set (IOS_BUNDLE_VERSION 1) |
26 | set (COPYRIGHT_YEAR 2021) | 26 | set (COPYRIGHT_YEAR 2021) |
27 | 27 | ||
28 | # Build configuration. | 28 | # Build configuration. |
@@ -84,6 +84,7 @@ enum iFileVersion { | |||
84 | #define unhappy_Icon "\U0001f641" | 84 | #define unhappy_Icon "\U0001f641" |
85 | #define globe_Icon "\U0001f310" | 85 | #define globe_Icon "\U0001f310" |
86 | #define magnifyingGlass_Icon "\U0001f50d" | 86 | #define magnifyingGlass_Icon "\U0001f50d" |
87 | #define midEllipsis_Icon "\u22ef" | ||
87 | 88 | ||
88 | /* UI labels that depend on the platform */ | 89 | /* UI labels that depend on the platform */ |
89 | 90 | ||
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index e18d5283..105a7158 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c | |||
@@ -200,6 +200,8 @@ enum iDocumentWidgetFlag { | |||
200 | selectWords_DocumentWidgetFlag = iBit(7), | 200 | selectWords_DocumentWidgetFlag = iBit(7), |
201 | selectLines_DocumentWidgetFlag = iBit(8), | 201 | selectLines_DocumentWidgetFlag = iBit(8), |
202 | pinchZoom_DocumentWidgetFlag = iBit(9), | 202 | pinchZoom_DocumentWidgetFlag = iBit(9), |
203 | movingSelectMarkStart_DocumentWidgetFlag = iBit(10), | ||
204 | movingSelectMarkEnd_DocumentWidgetFlag = iBit(11), | ||
203 | }; | 205 | }; |
204 | 206 | ||
205 | enum iDocumentLinkOrdinalMode { | 207 | enum iDocumentLinkOrdinalMode { |
@@ -251,6 +253,7 @@ struct Impl_DocumentWidget { | |||
251 | const iGmRun * firstVisibleRun; | 253 | const iGmRun * firstVisibleRun; |
252 | const iGmRun * lastVisibleRun; | 254 | const iGmRun * lastVisibleRun; |
253 | iClick click; | 255 | iClick click; |
256 | iInt2 contextPos; /* coordinates of latest right click */ | ||
254 | iString pendingGotoHeading; | 257 | iString pendingGotoHeading; |
255 | float initNormScrollY; | 258 | float initNormScrollY; |
256 | iAnim scrollY; | 259 | iAnim scrollY; |
@@ -259,6 +262,7 @@ struct Impl_DocumentWidget { | |||
259 | iScrollWidget *scroll; | 262 | iScrollWidget *scroll; |
260 | iWidget * menu; | 263 | iWidget * menu; |
261 | iWidget * playerMenu; | 264 | iWidget * playerMenu; |
265 | iWidget * copyMenu; | ||
262 | iVisBuf * visBuf; | 266 | iVisBuf * visBuf; |
263 | iPtrSet * invalidRuns; | 267 | iPtrSet * invalidRuns; |
264 | iDrawBufs * drawBufs; /* dynamic state for drawing */ | 268 | iDrawBufs * drawBufs; /* dynamic state for drawing */ |
@@ -324,6 +328,7 @@ void init_DocumentWidget(iDocumentWidget *d) { | |||
324 | addChild_Widget(w, iClob(d->scroll = new_ScrollWidget())); | 328 | addChild_Widget(w, iClob(d->scroll = new_ScrollWidget())); |
325 | d->menu = NULL; /* created when clicking */ | 329 | d->menu = NULL; /* created when clicking */ |
326 | d->playerMenu = NULL; | 330 | d->playerMenu = NULL; |
331 | d->copyMenu = NULL; | ||
327 | d->drawBufs = new_DrawBufs(); | 332 | d->drawBufs = new_DrawBufs(); |
328 | d->translation = NULL; | 333 | d->translation = NULL; |
329 | addChildFlags_Widget(w, | 334 | addChildFlags_Widget(w, |
@@ -367,6 +372,15 @@ void deinit_DocumentWidget(iDocumentWidget *d) { | |||
367 | deinit_PersistentDocumentState(&d->mod); | 372 | deinit_PersistentDocumentState(&d->mod); |
368 | } | 373 | } |
369 | 374 | ||
375 | static iRangecc selectMark_DocumentWidget_(const iDocumentWidget *d) { | ||
376 | /* Normalize so start < end. */ | ||
377 | iRangecc norm = d->selectMark; | ||
378 | if (norm.start > norm.end) { | ||
379 | iSwap(const char *, norm.start, norm.end); | ||
380 | } | ||
381 | return norm; | ||
382 | } | ||
383 | |||
370 | static void enableActions_DocumentWidget_(iDocumentWidget *d, iBool enable) { | 384 | static void enableActions_DocumentWidget_(iDocumentWidget *d, iBool enable) { |
371 | /* Actions are invisible child widgets of the DocumentWidget. */ | 385 | /* Actions are invisible child widgets of the DocumentWidget. */ |
372 | iForEach(ObjectList, i, children_Widget(d)) { | 386 | iForEach(ObjectList, i, children_Widget(d)) { |
@@ -1747,6 +1761,26 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
1747 | updateWindowTitle_DocumentWidget_(d); | 1761 | updateWindowTitle_DocumentWidget_(d); |
1748 | return iFalse; | 1762 | return iFalse; |
1749 | } | 1763 | } |
1764 | else if (equal_Command(cmd, "document.select") && d == document_App()) { | ||
1765 | /* Touch selection mode. */ | ||
1766 | if (!arg_Command(cmd)) { | ||
1767 | d->selectMark = iNullRange; | ||
1768 | setFlags_Widget(w, touchDrag_WidgetFlag, iFalse); | ||
1769 | setFadeEnabled_ScrollWidget(d->scroll, iTrue); | ||
1770 | } | ||
1771 | else { | ||
1772 | setFlags_Widget(w, touchDrag_WidgetFlag, iTrue); | ||
1773 | d->flags |= movingSelectMarkEnd_DocumentWidgetFlag | | ||
1774 | selectWords_DocumentWidgetFlag; /* finger-based selection is imprecise */ | ||
1775 | d->flags &= ~selectLines_DocumentWidgetFlag; | ||
1776 | setFadeEnabled_ScrollWidget(d->scroll, iFalse); | ||
1777 | d->selectMark = sourceLoc_DocumentWidget_(d, d->contextPos); | ||
1778 | extendRange_Rangecc(&d->selectMark, range_String(source_GmDocument(d->doc)), | ||
1779 | word_RangeExtension | bothStartAndEnd_RangeExtension); | ||
1780 | d->initialSelectMark = d->selectMark; | ||
1781 | } | ||
1782 | return iTrue; | ||
1783 | } | ||
1750 | else if (equal_Command(cmd, "document.info") && d == document_App()) { | 1784 | else if (equal_Command(cmd, "document.info") && d == document_App()) { |
1751 | const char *unchecked = red_ColorEscape "\u2610"; | 1785 | const char *unchecked = red_ColorEscape "\u2610"; |
1752 | const char *checked = green_ColorEscape "\u2611"; | 1786 | const char *checked = green_ColorEscape "\u2611"; |
@@ -1870,6 +1904,9 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
1870 | } | 1904 | } |
1871 | SDL_SetClipboardText(cstr_String(copied)); | 1905 | SDL_SetClipboardText(cstr_String(copied)); |
1872 | delete_String(copied); | 1906 | delete_String(copied); |
1907 | if (flags_Widget(w) & touchDrag_WidgetFlag) { | ||
1908 | postCommand_App("document.select arg:0"); | ||
1909 | } | ||
1873 | return iTrue; | 1910 | return iTrue; |
1874 | } | 1911 | } |
1875 | else if (equal_Command(cmd, "document.copylink") && document_App() == d) { | 1912 | else if (equal_Command(cmd, "document.copylink") && document_App() == d) { |
@@ -1960,7 +1997,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
1960 | cacheDocumentGlyphs_DocumentWidget_(d); | 1997 | cacheDocumentGlyphs_DocumentWidget_(d); |
1961 | return iFalse; | 1998 | return iFalse; |
1962 | } | 1999 | } |
1963 | else if (equalWidget_Command(cmd, w, "document.translate")) { | 2000 | else if (equal_Command(cmd, "document.translate") && d == document_App()) { |
1964 | if (!d->translation) { | 2001 | if (!d->translation) { |
1965 | d->translation = new_Translation(d); | 2002 | d->translation = new_Translation(d); |
1966 | } | 2003 | } |
@@ -2623,10 +2660,12 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
2623 | } | 2660 | } |
2624 | if (ev->button.button == SDL_BUTTON_RIGHT && | 2661 | if (ev->button.button == SDL_BUTTON_RIGHT && |
2625 | contains_Widget(w, init_I2(ev->button.x, ev->button.y))) { | 2662 | contains_Widget(w, init_I2(ev->button.x, ev->button.y))) { |
2626 | if (!d->menu || !isVisible_Widget(d->menu)) { | 2663 | if (!isVisible_Widget(d->menu)) { |
2627 | d->contextLink = d->hoverLink; | 2664 | d->contextLink = d->hoverLink; |
2665 | d->contextPos = init_I2(ev->button.x, ev->button.y); | ||
2628 | if (d->menu) { | 2666 | if (d->menu) { |
2629 | destroy_Widget(d->menu); | 2667 | destroy_Widget(d->menu); |
2668 | d->menu = NULL; | ||
2630 | } | 2669 | } |
2631 | setFocus_Widget(NULL); | 2670 | setFocus_Widget(NULL); |
2632 | iArray items; | 2671 | iArray items; |
@@ -2637,6 +2676,12 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
2637 | const iRangecc scheme = urlScheme_String(linkUrl); | 2676 | const iRangecc scheme = urlScheme_String(linkUrl); |
2638 | const iBool isGemini = equalCase_Rangecc(scheme, "gemini"); | 2677 | const iBool isGemini = equalCase_Rangecc(scheme, "gemini"); |
2639 | iBool isNative = iFalse; | 2678 | iBool isNative = iFalse; |
2679 | if (deviceType_App() != desktop_AppDeviceType) { | ||
2680 | /* Show the link as the first, non-interactive item. */ | ||
2681 | pushBack_Array(&items, &(iMenuItem){ | ||
2682 | format_CStr("```%s", cstr_String(linkUrl)), | ||
2683 | 0, 0, NULL }); | ||
2684 | } | ||
2640 | if (willUseProxy_App(scheme) || isGemini || | 2685 | if (willUseProxy_App(scheme) || isGemini || |
2641 | equalCase_Rangecc(scheme, "finger") || | 2686 | equalCase_Rangecc(scheme, "finger") || |
2642 | equalCase_Rangecc(scheme, "gopher")) { | 2687 | equalCase_Rangecc(scheme, "gopher")) { |
@@ -2707,24 +2752,18 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
2707 | } | 2752 | } |
2708 | } | 2753 | } |
2709 | } | 2754 | } |
2710 | else { | 2755 | else if (deviceType_App() == desktop_AppDeviceType) { |
2711 | if (!isEmpty_Range(&d->selectMark)) { | 2756 | if (!isEmpty_Range(&d->selectMark)) { |
2712 | pushBackN_Array( | 2757 | pushBackN_Array(&items, |
2713 | &items, | 2758 | (iMenuItem[]){ { "${menu.copy}", 0, 0, "copy" }, |
2714 | (iMenuItem[]){ { "${menu.copy}", 0, 0, "copy" }, { "---", 0, 0, NULL } }, | 2759 | { "---", 0, 0, NULL } }, |
2715 | 2); | 2760 | 2); |
2716 | } | ||
2717 | if (deviceType_App() == desktop_AppDeviceType) { | ||
2718 | pushBackN_Array( | ||
2719 | &items, | ||
2720 | (iMenuItem[]){ | ||
2721 | { "${menu.back}", navigateBack_KeyShortcut, "navigate.back" }, | ||
2722 | { "${menu.forward}", navigateForward_KeyShortcut, "navigate.forward" } }, | ||
2723 | 2); | ||
2724 | } | 2761 | } |
2725 | pushBackN_Array( | 2762 | pushBackN_Array( |
2726 | &items, | 2763 | &items, |
2727 | (iMenuItem[]){ | 2764 | (iMenuItem[]){ |
2765 | { "${menu.back}", navigateBack_KeyShortcut, "navigate.back" }, | ||
2766 | { "${menu.forward}", navigateForward_KeyShortcut, "navigate.forward" }, | ||
2728 | { upArrow_Icon " ${menu.parent}", navigateParent_KeyShortcut, "navigate.parent" }, | 2767 | { upArrow_Icon " ${menu.parent}", navigateParent_KeyShortcut, "navigate.parent" }, |
2729 | { upArrowBar_Icon " ${menu.root}", navigateRoot_KeyShortcut, "navigate.root" }, | 2768 | { upArrowBar_Icon " ${menu.root}", navigateRoot_KeyShortcut, "navigate.root" }, |
2730 | { "---", 0, 0, NULL }, | 2769 | { "---", 0, 0, NULL }, |
@@ -2738,7 +2777,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
2738 | { globe_Icon " ${menu.page.translate}", 0, 0, "document.translate" }, | 2777 | { globe_Icon " ${menu.page.translate}", 0, 0, "document.translate" }, |
2739 | { "---", 0, 0, NULL }, | 2778 | { "---", 0, 0, NULL }, |
2740 | { "${menu.page.copyurl}", 0, 0, "document.copylink" } }, | 2779 | { "${menu.page.copyurl}", 0, 0, "document.copylink" } }, |
2741 | 12); | 2780 | 15); |
2742 | if (isEmpty_Range(&d->selectMark)) { | 2781 | if (isEmpty_Range(&d->selectMark)) { |
2743 | pushBackN_Array( | 2782 | pushBackN_Array( |
2744 | &items, | 2783 | &items, |
@@ -2748,6 +2787,21 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
2748 | 2); | 2787 | 2); |
2749 | } | 2788 | } |
2750 | } | 2789 | } |
2790 | else { | ||
2791 | /* Mobile text selection menu. */ | ||
2792 | #if 0 | ||
2793 | pushBackN_Array( | ||
2794 | &items, | ||
2795 | (iMenuItem[]){ | ||
2796 | { "${menu.select}", 0, 0, "document.select arg:1" }, | ||
2797 | { "${menu.select.word}", 0, 0, "document.select arg:2" }, | ||
2798 | { "${menu.select.par}", 0, 0, "document.select arg:3" }, | ||
2799 | }, | ||
2800 | 3); | ||
2801 | #endif | ||
2802 | postCommand_App("document.select arg:1"); | ||
2803 | return iTrue; | ||
2804 | } | ||
2751 | d->menu = makeMenu_Widget(w, data_Array(&items), size_Array(&items)); | 2805 | d->menu = makeMenu_Widget(w, data_Array(&items), size_Array(&items)); |
2752 | deinit_Array(&items); | 2806 | deinit_Array(&items); |
2753 | } | 2807 | } |
@@ -2763,26 +2817,29 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
2763 | if (d->grabbedPlayer) { | 2817 | if (d->grabbedPlayer) { |
2764 | return iTrue; | 2818 | return iTrue; |
2765 | } | 2819 | } |
2820 | /* Enable hover state now that scrolling has surely finished. */ | ||
2766 | if (d->flags & noHoverWhileScrolling_DocumentWidgetFlag) { | 2821 | if (d->flags & noHoverWhileScrolling_DocumentWidgetFlag) { |
2767 | d->flags &= ~noHoverWhileScrolling_DocumentWidgetFlag; | 2822 | d->flags &= ~noHoverWhileScrolling_DocumentWidgetFlag; |
2768 | updateHover_DocumentWidget_(d, mouseCoord_Window(get_Window())); | 2823 | updateHover_DocumentWidget_(d, mouseCoord_Window(get_Window())); |
2769 | } | 2824 | } |
2770 | iChangeFlags(d->flags, selecting_DocumentWidgetFlag, iFalse); | 2825 | if (~flags_Widget(w) & touchDrag_WidgetFlag) { |
2771 | iChangeFlags(d->flags, selectWords_DocumentWidgetFlag, d->click.count == 2); | 2826 | iChangeFlags(d->flags, selecting_DocumentWidgetFlag, iFalse); |
2772 | iChangeFlags(d->flags, selectLines_DocumentWidgetFlag, d->click.count >= 3); | 2827 | iChangeFlags(d->flags, selectWords_DocumentWidgetFlag, d->click.count == 2); |
2773 | /* Double/triple clicks marks the selection immediately. */ | 2828 | iChangeFlags(d->flags, selectLines_DocumentWidgetFlag, d->click.count >= 3); |
2774 | if (d->click.count >= 2) { | 2829 | /* Double/triple clicks marks the selection immediately. */ |
2775 | beginMarkingSelection_DocumentWidget_(d, d->click.startPos); | 2830 | if (d->click.count >= 2) { |
2776 | extendRange_Rangecc( | 2831 | beginMarkingSelection_DocumentWidget_(d, d->click.startPos); |
2777 | &d->selectMark, | 2832 | extendRange_Rangecc( |
2778 | range_String(source_GmDocument(d->doc)), | 2833 | &d->selectMark, |
2779 | bothStartAndEnd_RangeExtension | | 2834 | range_String(source_GmDocument(d->doc)), |
2780 | (d->click.count == 2 ? word_RangeExtension : line_RangeExtension)); | 2835 | bothStartAndEnd_RangeExtension | |
2781 | d->initialSelectMark = d->selectMark; | 2836 | (d->click.count == 2 ? word_RangeExtension : line_RangeExtension)); |
2782 | refresh_Widget(w); | 2837 | d->initialSelectMark = d->selectMark; |
2783 | } | 2838 | refresh_Widget(w); |
2784 | else { | 2839 | } |
2785 | d->initialSelectMark = iNullRange; | 2840 | else { |
2841 | d->initialSelectMark = iNullRange; | ||
2842 | } | ||
2786 | } | 2843 | } |
2787 | return iTrue; | 2844 | return iTrue; |
2788 | case drag_ClickResult: { | 2845 | case drag_ClickResult: { |
@@ -2796,29 +2853,59 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
2796 | refresh_Widget(w); | 2853 | refresh_Widget(w); |
2797 | return iTrue; | 2854 | return iTrue; |
2798 | } | 2855 | } |
2799 | /* Begin selecting a range of text. */ | 2856 | /* Fold/unfold a preformatted block. */ |
2800 | if (~d->flags & selecting_DocumentWidgetFlag && d->hoverPre && | 2857 | if (~d->flags & selecting_DocumentWidgetFlag && d->hoverPre && |
2801 | preIsFolded_GmDocument(d->doc, d->hoverPre->preId)) { | 2858 | preIsFolded_GmDocument(d->doc, d->hoverPre->preId)) { |
2802 | return iTrue; | 2859 | return iTrue; |
2803 | } | 2860 | } |
2861 | /* Begin selecting a range of text. */ | ||
2804 | if (~d->flags & selecting_DocumentWidgetFlag) { | 2862 | if (~d->flags & selecting_DocumentWidgetFlag) { |
2805 | beginMarkingSelection_DocumentWidget_(d, d->click.startPos); | 2863 | beginMarkingSelection_DocumentWidget_(d, d->click.startPos); |
2806 | } | 2864 | } |
2807 | iRangecc loc = sourceLoc_DocumentWidget_(d, pos_Click(&d->click)); | 2865 | iRangecc loc = sourceLoc_DocumentWidget_(d, pos_Click(&d->click)); |
2808 | if (!d->selectMark.start) { | 2866 | if (d->selectMark.start == NULL) { |
2809 | d->selectMark = loc; | 2867 | d->selectMark = loc; |
2810 | } | 2868 | } |
2811 | else if (loc.end) { | 2869 | else if (loc.end) { |
2812 | d->selectMark.end = (d->selectMark.end > d->selectMark.start ? loc.end : loc.start); | 2870 | if (flags_Widget(w) & touchDrag_WidgetFlag) { |
2871 | /* Choose which end to move. */ | ||
2872 | if (!(d->flags & (movingSelectMarkStart_DocumentWidgetFlag | | ||
2873 | movingSelectMarkEnd_DocumentWidgetFlag))) { | ||
2874 | const iRangecc mark = selectMark_DocumentWidget_(d); | ||
2875 | const char * midMark = mark.start + size_Range(&mark) / 2; | ||
2876 | const iRangecc loc = sourceLoc_DocumentWidget_(d, pos_Click(&d->click)); | ||
2877 | const iBool isCloserToStart = d->selectMark.start > d->selectMark.end ? | ||
2878 | (loc.start > midMark) : (loc.start < midMark); | ||
2879 | iChangeFlags(d->flags, movingSelectMarkStart_DocumentWidgetFlag, isCloserToStart); | ||
2880 | iChangeFlags(d->flags, movingSelectMarkEnd_DocumentWidgetFlag, !isCloserToStart); | ||
2881 | } | ||
2882 | /* Move the start or the end depending on which is nearer. */ | ||
2883 | if (d->flags & movingSelectMarkStart_DocumentWidgetFlag) { | ||
2884 | d->selectMark.start = loc.start; | ||
2885 | } | ||
2886 | else { | ||
2887 | d->selectMark.end = (d->selectMark.end > d->selectMark.start ? loc.end : loc.start); | ||
2888 | } | ||
2889 | } | ||
2890 | else { | ||
2891 | d->selectMark.end = (d->selectMark.end > d->selectMark.start ? loc.end : loc.start); | ||
2892 | } | ||
2813 | } | 2893 | } |
2814 | iAssert((!d->selectMark.start && !d->selectMark.end) || | 2894 | iAssert((!d->selectMark.start && !d->selectMark.end) || |
2815 | ( d->selectMark.start && d->selectMark.end)); | 2895 | ( d->selectMark.start && d->selectMark.end)); |
2816 | /* Extend the selection when double/triple clicking. */ | 2896 | /* Extend to full words/paragraphs. */ |
2817 | if (d->flags & (selectWords_DocumentWidgetFlag | selectLines_DocumentWidgetFlag)) { | 2897 | if (d->flags & (selectWords_DocumentWidgetFlag | selectLines_DocumentWidgetFlag)) { |
2818 | extendRange_Rangecc( | 2898 | extendRange_Rangecc( |
2819 | &d->selectMark, | 2899 | &d->selectMark, |
2820 | range_String(source_GmDocument(d->doc)), | 2900 | range_String(source_GmDocument(d->doc)), |
2821 | d->click.count == 2 ? word_RangeExtension : line_RangeExtension); | 2901 | (d->flags & movingSelectMarkStart_DocumentWidgetFlag ? moveStart_RangeExtension |
2902 | : moveEnd_RangeExtension) | | ||
2903 | (d->flags & selectWords_DocumentWidgetFlag ? word_RangeExtension | ||
2904 | : line_RangeExtension)); | ||
2905 | if (d->flags & movingSelectMarkStart_DocumentWidgetFlag) { | ||
2906 | d->initialSelectMark.start = | ||
2907 | d->initialSelectMark.end = d->selectMark.start; | ||
2908 | } | ||
2822 | if (!isEmpty_Range(&d->initialSelectMark)) { | 2909 | if (!isEmpty_Range(&d->initialSelectMark)) { |
2823 | if (d->selectMark.end > d->selectMark.start) { | 2910 | if (d->selectMark.end > d->selectMark.start) { |
2824 | d->selectMark.start = d->initialSelectMark.start; | 2911 | d->selectMark.start = d->initialSelectMark.start; |
@@ -2842,13 +2929,42 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
2842 | if (isVisible_Widget(d->menu)) { | 2929 | if (isVisible_Widget(d->menu)) { |
2843 | closeMenu_Widget(d->menu); | 2930 | closeMenu_Widget(d->menu); |
2844 | } | 2931 | } |
2932 | d->flags &= ~(movingSelectMarkStart_DocumentWidgetFlag | | ||
2933 | movingSelectMarkEnd_DocumentWidgetFlag); | ||
2845 | if (!isMoved_Click(&d->click)) { | 2934 | if (!isMoved_Click(&d->click)) { |
2846 | setFocus_Widget(NULL); | 2935 | setFocus_Widget(NULL); |
2936 | /* Tap in tap selection mode. */ | ||
2937 | if (flags_Widget(w) & touchDrag_WidgetFlag) { | ||
2938 | const iRangecc tapLoc = sourceLoc_DocumentWidget_(d, pos_Click(&d->click)); | ||
2939 | /* Tapping on the selection will show a menu. */ | ||
2940 | const iRangecc mark = selectMark_DocumentWidget_(d); | ||
2941 | if (tapLoc.start >= mark.start && tapLoc.end <= mark.end) { | ||
2942 | if (d->copyMenu) { | ||
2943 | closeMenu_Widget(d->copyMenu); | ||
2944 | destroy_Widget(d->copyMenu); | ||
2945 | d->copyMenu = NULL; | ||
2946 | } | ||
2947 | d->copyMenu = makeMenu_Widget(w, (iMenuItem[]){ | ||
2948 | { clipCopy_Icon " ${menu.copy}", 0, 0, "copy" }, | ||
2949 | { "---", 0, 0, NULL }, | ||
2950 | { close_Icon " Clear Selection", 0, 0, "document.select arg:0" }, | ||
2951 | }, 3); | ||
2952 | setFlags_Widget(d->copyMenu, noFadeBackground_WidgetFlag, iTrue); | ||
2953 | openMenu_Widget(d->copyMenu, pos_Click(&d->click)); | ||
2954 | return iTrue; | ||
2955 | } | ||
2956 | else { | ||
2957 | /* Tapping elsewhere exits selection mode. */ | ||
2958 | postCommand_Widget(d, "document.select arg:0"); | ||
2959 | return iTrue; | ||
2960 | } | ||
2961 | } | ||
2847 | if (d->hoverPre) { | 2962 | if (d->hoverPre) { |
2848 | togglePreFold_DocumentWidget_(d, d->hoverPre->preId); | 2963 | togglePreFold_DocumentWidget_(d, d->hoverPre->preId); |
2849 | return iTrue; | 2964 | return iTrue; |
2850 | } | 2965 | } |
2851 | if (d->hoverLink) { | 2966 | if (d->hoverLink) { |
2967 | /* TODO: Move this to a method. */ | ||
2852 | const iGmLinkId linkId = d->hoverLink->linkId; | 2968 | const iGmLinkId linkId = d->hoverLink->linkId; |
2853 | const int linkFlags = linkFlags_GmDocument(d->doc, linkId); | 2969 | const int linkFlags = linkFlags_GmDocument(d->doc, linkId); |
2854 | iAssert(linkId); | 2970 | iAssert(linkId); |
@@ -3636,6 +3752,18 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) { | |||
3636 | drawCentered_Text(font, bounds, iFalse, uiBackground_ColorId, "%d %%", | 3752 | drawCentered_Text(font, bounds, iFalse, uiBackground_ColorId, "%d %%", |
3637 | d->pinchZoomPosted); | 3753 | d->pinchZoomPosted); |
3638 | } | 3754 | } |
3755 | /* Touch selection indicator. */ | ||
3756 | if (flags_Widget(w) & touchDrag_WidgetFlag) { | ||
3757 | iString msg; | ||
3758 | init_String(&msg); | ||
3759 | format_String(&msg, "Selecting: drag and tap"); | ||
3760 | fillRect_Paint(&ctx.paint, (iRect){ topLeft_Rect(bounds), | ||
3761 | init_I2(width_Rect(bounds), lineHeight_Text(uiLabelBold_FontId))}, | ||
3762 | uiTextAction_ColorId); | ||
3763 | drawRange_Text(uiLabelBold_FontId, addX_I2(topLeft_Rect(bounds), 3 * gap_UI), | ||
3764 | uiBackground_ColorId, range_String(&msg)); | ||
3765 | deinit_String(&msg); | ||
3766 | } | ||
3639 | } | 3767 | } |
3640 | 3768 | ||
3641 | /*----------------------------------------------------------------------------------------------*/ | 3769 | /*----------------------------------------------------------------------------------------------*/ |
diff --git a/src/ui/scrollwidget.c b/src/ui/scrollwidget.c index 55ede426..32b57c69 100644 --- a/src/ui/scrollwidget.c +++ b/src/ui/scrollwidget.c | |||
@@ -48,6 +48,7 @@ struct Impl_ScrollWidget { | |||
48 | iClick click; | 48 | iClick click; |
49 | int startThumb; | 49 | int startThumb; |
50 | iAnim opacity; | 50 | iAnim opacity; |
51 | iBool fadeEnabled; | ||
51 | uint32_t fadeStart; | 52 | uint32_t fadeStart; |
52 | iBool willCheckFade; | 53 | iBool willCheckFade; |
53 | }; | 54 | }; |
@@ -76,6 +77,7 @@ void init_ScrollWidget(iScrollWidget *d) { | |||
76 | init_Click(&d->click, d, SDL_BUTTON_LEFT); | 77 | init_Click(&d->click, d, SDL_BUTTON_LEFT); |
77 | init_Anim(&d->opacity, minOpacity_()); | 78 | init_Anim(&d->opacity, minOpacity_()); |
78 | d->willCheckFade = iFalse; | 79 | d->willCheckFade = iFalse; |
80 | d->fadeEnabled = iTrue; | ||
79 | } | 81 | } |
80 | 82 | ||
81 | void deinit_ScrollWidget(iScrollWidget *d) { | 83 | void deinit_ScrollWidget(iScrollWidget *d) { |
@@ -108,7 +110,7 @@ static void unfade_ScrollWidget_(iScrollWidget *d, float opacity) { | |||
108 | setValue_Anim(&d->opacity, opacity, 66); | 110 | setValue_Anim(&d->opacity, opacity, 66); |
109 | addTicker_App(animateOpacity_ScrollWidget_, d); | 111 | addTicker_App(animateOpacity_ScrollWidget_, d); |
110 | } | 112 | } |
111 | if (!d->willCheckFade) { | 113 | if (!d->willCheckFade && d->fadeEnabled) { |
112 | d->willCheckFade = iTrue; | 114 | d->willCheckFade = iTrue; |
113 | /* TODO: This causes an inexplicable refresh issue on macOS: the drawing of one frame | 115 | /* TODO: This causes an inexplicable refresh issue on macOS: the drawing of one frame |
114 | takes 100ms for some reason (not the current frame but some time after). */ | 116 | takes 100ms for some reason (not the current frame but some time after). */ |
@@ -142,6 +144,11 @@ void setThumb_ScrollWidget(iScrollWidget *d, int thumb, int thumbSize) { | |||
142 | } | 144 | } |
143 | } | 145 | } |
144 | 146 | ||
147 | void setFadeEnabled_ScrollWidget(iScrollWidget *d, iBool fadeEnabled) { | ||
148 | d->fadeEnabled = fadeEnabled; | ||
149 | unfade_ScrollWidget_(d, 1.0f); | ||
150 | } | ||
151 | |||
145 | static iBool processEvent_ScrollWidget_(iScrollWidget *d, const SDL_Event *ev) { | 152 | static iBool processEvent_ScrollWidget_(iScrollWidget *d, const SDL_Event *ev) { |
146 | iWidget *w = as_Widget(d); | 153 | iWidget *w = as_Widget(d); |
147 | if (isMetricsChange_UserEvent(ev)) { | 154 | if (isMetricsChange_UserEvent(ev)) { |
@@ -156,7 +163,7 @@ static iBool processEvent_ScrollWidget_(iScrollWidget *d, const SDL_Event *ev) { | |||
156 | } | 163 | } |
157 | } | 164 | } |
158 | if (isCommand_UserEvent(ev, "scrollbar.fade")) { | 165 | if (isCommand_UserEvent(ev, "scrollbar.fade")) { |
159 | if (d->willCheckFade && SDL_GetTicks() > d->fadeStart) { | 166 | if (d->fadeEnabled && d->willCheckFade && SDL_GetTicks() > d->fadeStart) { |
160 | setValue_Anim(&d->opacity, minOpacity_(), 200); | 167 | setValue_Anim(&d->opacity, minOpacity_(), 200); |
161 | remove_Periodic(periodic_App(), d); | 168 | remove_Periodic(periodic_App(), d); |
162 | d->willCheckFade = iFalse; | 169 | d->willCheckFade = iFalse; |
diff --git a/src/ui/scrollwidget.h b/src/ui/scrollwidget.h index e6cda03d..e3f0a51f 100644 --- a/src/ui/scrollwidget.h +++ b/src/ui/scrollwidget.h | |||
@@ -31,3 +31,4 @@ iDeclareObjectConstruction(ScrollWidget) | |||
31 | void setRange_ScrollWidget (iScrollWidget *, iRangei range); | 31 | void setRange_ScrollWidget (iScrollWidget *, iRangei range); |
32 | void setThumb_ScrollWidget (iScrollWidget *, int thumb, int thumbSize); | 32 | void setThumb_ScrollWidget (iScrollWidget *, int thumb, int thumbSize); |
33 | 33 | ||
34 | void setFadeEnabled_ScrollWidget (iScrollWidget *, iBool fadeEnabled); | ||
diff --git a/src/ui/touch.c b/src/ui/touch.c index 87bb7478..0f23e948 100644 --- a/src/ui/touch.c +++ b/src/ui/touch.c | |||
@@ -59,6 +59,7 @@ struct Impl_Touch { | |||
59 | iBool hasMoved; | 59 | iBool hasMoved; |
60 | iBool isTapBegun; | 60 | iBool isTapBegun; |
61 | iBool isTouchDrag; | 61 | iBool isTouchDrag; |
62 | iBool didConvertToTouchDrag; | ||
62 | iBool isTapAndHold; | 63 | iBool isTapAndHold; |
63 | int pinchId; | 64 | int pinchId; |
64 | enum iTouchEdge edge; | 65 | enum iTouchEdge edge; |
@@ -226,7 +227,7 @@ static void update_TouchState_(void *ptr) { | |||
226 | /* Check for long presses to simulate right clicks. */ | 227 | /* Check for long presses to simulate right clicks. */ |
227 | iForEach(Array, i, d->touches) { | 228 | iForEach(Array, i, d->touches) { |
228 | iTouch *touch = i.value; | 229 | iTouch *touch = i.value; |
229 | if (touch->pinchId) { | 230 | if (touch->pinchId || touch->isTouchDrag) { |
230 | continue; | 231 | continue; |
231 | } | 232 | } |
232 | /* Holding a touch will reset previous momentum for this widget. */ | 233 | /* Holding a touch will reset previous momentum for this widget. */ |
@@ -253,6 +254,14 @@ static void update_TouchState_(void *ptr) { | |||
253 | #endif | 254 | #endif |
254 | dispatchMotion_Touch_(init_F3(-100, -100, 0), 0); | 255 | dispatchMotion_Touch_(init_F3(-100, -100, 0), 0); |
255 | } | 256 | } |
257 | if (touch->isTapAndHold && touch->affinity && | ||
258 | flags_Widget(touch->affinity) & touchDrag_WidgetFlag) { | ||
259 | /* Convert to touch drag. */ | ||
260 | touch->isTapAndHold = iFalse; | ||
261 | touch->isTouchDrag = iTrue; | ||
262 | touch->didConvertToTouchDrag = iTrue; | ||
263 | dispatchButtonDown_Touch_(touch->pos[0]); | ||
264 | } | ||
256 | } | 265 | } |
257 | } | 266 | } |
258 | /* Update/cancel momentum scrolling. */ { | 267 | /* Update/cancel momentum scrolling. */ { |
@@ -486,16 +495,7 @@ iBool processEvent_Touch(const SDL_Event *ev) { | |||
486 | touch->edge = none_TouchEdge; | 495 | touch->edge = none_TouchEdge; |
487 | pushPos_Touch_(touch, pos, fing->timestamp); | 496 | pushPos_Touch_(touch, pos, fing->timestamp); |
488 | dispatchMotion_Touch_(touch->startPos, 0); | 497 | dispatchMotion_Touch_(touch->startPos, 0); |
489 | dispatchEvent_Widget(window->root, (SDL_Event *) &(SDL_MouseButtonEvent){ | 498 | dispatchButtonDown_Touch_(touch->startPos); |
490 | .type = SDL_MOUSEBUTTONDOWN, | ||
491 | .timestamp = fing->timestamp, | ||
492 | .clicks = 1, | ||
493 | .state = SDL_PRESSED, | ||
494 | .which = SDL_TOUCH_MOUSEID, | ||
495 | .button = SDL_BUTTON_LEFT, | ||
496 | .x = x_F3(touch->startPos), | ||
497 | .y = y_F3(touch->startPos) | ||
498 | }); | ||
499 | dispatchMotion_Touch_(pos, SDL_BUTTON_LMASK); | 499 | dispatchMotion_Touch_(pos, SDL_BUTTON_LMASK); |
500 | return iTrue; | 500 | return iTrue; |
501 | } | 501 | } |
@@ -603,11 +603,10 @@ iBool processEvent_Touch(const SDL_Event *ev) { | |||
603 | continue; | 603 | continue; |
604 | } | 604 | } |
605 | if (flags_Widget(touch->affinity) & touchDrag_WidgetFlag) { | 605 | if (flags_Widget(touch->affinity) & touchDrag_WidgetFlag) { |
606 | if (!touch->isTouchDrag) { | 606 | if (!touch->isTouchDrag && !touch->didConvertToTouchDrag) { |
607 | dispatchButtonDown_Touch_(touch->startPos); | 607 | dispatchButtonDown_Touch_(touch->startPos); |
608 | } | 608 | } |
609 | dispatchButtonUp_Touch_(pos); | 609 | dispatchButtonUp_Touch_(pos); |
610 | // setHover_Widget(NULL); | ||
611 | remove_ArrayIterator(&i); | 610 | remove_ArrayIterator(&i); |
612 | continue; | 611 | continue; |
613 | } | 612 | } |
@@ -678,7 +677,7 @@ void widgetDestroyed_Touch(iWidget *widget) { | |||
678 | } | 677 | } |
679 | } | 678 | } |
680 | iForEach(Array, p, d->pinches) { | 679 | iForEach(Array, p, d->pinches) { |
681 | iPinch *pinch = i.value; | 680 | iPinch *pinch = p.value; |
682 | if (pinch->affinity == widget) { | 681 | if (pinch->affinity == widget) { |
683 | remove_ArrayIterator(&p); | 682 | remove_ArrayIterator(&p); |
684 | } | 683 | } |
diff --git a/src/ui/util.c b/src/ui/util.c index e6edb119..047fa7af 100644 --- a/src/ui/util.c +++ b/src/ui/util.c | |||
@@ -220,20 +220,18 @@ static const char *moveForward_(const char *pos, iRangecc bounds, int mode) { | |||
220 | void extendRange_Rangecc(iRangecc *d, iRangecc bounds, int mode) { | 220 | void extendRange_Rangecc(iRangecc *d, iRangecc bounds, int mode) { |
221 | if (!d->start) return; | 221 | if (!d->start) return; |
222 | if (d->end >= d->start) { | 222 | if (d->end >= d->start) { |
223 | if (mode & bothStartAndEnd_RangeExtension) { | 223 | if (mode & moveStart_RangeExtension) { |
224 | d->start = moveBackward_(d->start, bounds, mode); | 224 | d->start = moveBackward_(d->start, bounds, mode); |
225 | d->end = moveForward_(d->end, bounds, mode); | ||
226 | } | 225 | } |
227 | else { | 226 | if (mode & moveEnd_RangeExtension) { |
228 | d->end = moveForward_(d->end, bounds, mode); | 227 | d->end = moveForward_(d->end, bounds, mode); |
229 | } | 228 | } |
230 | } | 229 | } |
231 | else { | 230 | else { |
232 | if (mode & bothStartAndEnd_RangeExtension) { | 231 | if (mode & moveStart_RangeExtension) { |
233 | d->start = moveForward_(d->start, bounds, mode); | 232 | d->start = moveForward_(d->start, bounds, mode); |
234 | d->end = moveBackward_(d->end, bounds, mode); | ||
235 | } | 233 | } |
236 | else { | 234 | if (mode & moveEnd_RangeExtension) { |
237 | d->end = moveBackward_(d->end, bounds, mode); | 235 | d->end = moveBackward_(d->end, bounds, mode); |
238 | } | 236 | } |
239 | } | 237 | } |
@@ -573,13 +571,22 @@ iWidget *makeMenu_Widget(iWidget *parent, const iMenuItem *items, size_t n) { | |||
573 | addChild_Widget(menu, iClob(makeMenuSeparator_())); | 571 | addChild_Widget(menu, iClob(makeMenuSeparator_())); |
574 | } | 572 | } |
575 | else { | 573 | else { |
574 | iBool isInfo = iFalse; | ||
575 | const char *labelText = item->label; | ||
576 | if (startsWith_CStr(labelText, "```")) { | ||
577 | labelText += 3; | ||
578 | isInfo = iTrue; | ||
579 | } | ||
576 | iLabelWidget *label = addChildFlags_Widget( | 580 | iLabelWidget *label = addChildFlags_Widget( |
577 | menu, | 581 | menu, |
578 | iClob(newKeyMods_LabelWidget(item->label, item->key, item->kmods, item->command)), | 582 | iClob(newKeyMods_LabelWidget(labelText, item->key, item->kmods, item->command)), |
579 | noBackground_WidgetFlag | frameless_WidgetFlag | alignLeft_WidgetFlag | | 583 | noBackground_WidgetFlag | frameless_WidgetFlag | alignLeft_WidgetFlag | |
580 | drawKey_WidgetFlag | itemFlags); | 584 | drawKey_WidgetFlag | (isInfo ? wrapText_WidgetFlag : 0) | itemFlags); |
581 | haveIcons |= checkIcon_LabelWidget(label); | 585 | haveIcons |= checkIcon_LabelWidget(label); |
582 | updateSize_LabelWidget(label); /* drawKey was set */ | 586 | updateSize_LabelWidget(label); /* drawKey was set */ |
587 | if (isInfo) { | ||
588 | setTextColor_LabelWidget(label, uiTextAction_ColorId); | ||
589 | } | ||
583 | } | 590 | } |
584 | } | 591 | } |
585 | if (deviceType_App() == phone_AppDeviceType) { | 592 | if (deviceType_App() == phone_AppDeviceType) { |
@@ -639,6 +646,9 @@ void openMenuFlags_Widget(iWidget *d, iInt2 coord, iBool postCommands) { | |||
639 | if (isInstance_Object(i.object, &Class_LabelWidget)) { | 646 | if (isInstance_Object(i.object, &Class_LabelWidget)) { |
640 | iLabelWidget *label = i.object; | 647 | iLabelWidget *label = i.object; |
641 | const iBool isCaution = startsWith_String(text_LabelWidget(label), uiTextCaution_ColorEscape); | 648 | const iBool isCaution = startsWith_String(text_LabelWidget(label), uiTextCaution_ColorEscape); |
649 | if (flags_Widget(as_Widget(label)) & wrapText_WidgetFlag) { | ||
650 | continue; | ||
651 | } | ||
642 | if (deviceType_App() == desktop_AppDeviceType) { | 652 | if (deviceType_App() == desktop_AppDeviceType) { |
643 | setFont_LabelWidget(label, isCaution ? uiLabelBold_FontId : uiLabel_FontId); | 653 | setFont_LabelWidget(label, isCaution ? uiLabelBold_FontId : uiLabel_FontId); |
644 | } | 654 | } |
diff --git a/src/ui/util.h b/src/ui/util.h index 42dad6e0..d39a00fa 100644 --- a/src/ui/util.h +++ b/src/ui/util.h | |||
@@ -86,7 +86,9 @@ iLocalDef iBool isOverlapping_Rangei(iRangei a, iRangei b) { | |||
86 | enum iRangeExtension { | 86 | enum iRangeExtension { |
87 | word_RangeExtension = iBit(1), | 87 | word_RangeExtension = iBit(1), |
88 | line_RangeExtension = iBit(2), | 88 | line_RangeExtension = iBit(2), |
89 | bothStartAndEnd_RangeExtension = iBit(3), | 89 | moveStart_RangeExtension = iBit(3), |
90 | moveEnd_RangeExtension = iBit(4), | ||
91 | bothStartAndEnd_RangeExtension = moveStart_RangeExtension | moveEnd_RangeExtension, | ||
90 | }; | 92 | }; |
91 | 93 | ||
92 | void extendRange_Rangecc (iRangecc *, iRangecc bounds, int mode); | 94 | void extendRange_Rangecc (iRangecc *, iRangecc bounds, int mode); |
diff --git a/src/ui/widget.c b/src/ui/widget.c index 3eea761e..78a8a8bf 100644 --- a/src/ui/widget.c +++ b/src/ui/widget.c | |||
@@ -1004,8 +1004,7 @@ void drawBackground_Widget(const iWidget *d) { | |||
1004 | } | 1004 | } |
1005 | /* Popup menus have a shadowed border. */ | 1005 | /* Popup menus have a shadowed border. */ |
1006 | iBool shadowBorder = (d->flags & keepOnTop_WidgetFlag && ~d->flags & mouseModal_WidgetFlag) != 0; | 1006 | iBool shadowBorder = (d->flags & keepOnTop_WidgetFlag && ~d->flags & mouseModal_WidgetFlag) != 0; |
1007 | iBool fadeBackground = (d->bgColor >= 0 || d->frameColor >= 0) && | 1007 | iBool fadeBackground = (d->bgColor >= 0 || d->frameColor >= 0) && d->flags & mouseModal_WidgetFlag; |
1008 | d->flags & mouseModal_WidgetFlag; | ||
1009 | if (deviceType_App() == phone_AppDeviceType) { | 1008 | if (deviceType_App() == phone_AppDeviceType) { |
1010 | if (shadowBorder) { | 1009 | if (shadowBorder) { |
1011 | fadeBackground = iTrue; | 1010 | fadeBackground = iTrue; |
@@ -1017,7 +1016,7 @@ void drawBackground_Widget(const iWidget *d) { | |||
1017 | init_Paint(&p); | 1016 | init_Paint(&p); |
1018 | drawSoftShadow_Paint(&p, bounds_Widget(d), 12 * gap_UI, black_ColorId, 30); | 1017 | drawSoftShadow_Paint(&p, bounds_Widget(d), 12 * gap_UI, black_ColorId, 30); |
1019 | } | 1018 | } |
1020 | if (fadeBackground) { | 1019 | if (fadeBackground && ~d->flags & noFadeBackground_WidgetFlag) { |
1021 | iPaint p; | 1020 | iPaint p; |
1022 | init_Paint(&p); | 1021 | init_Paint(&p); |
1023 | p.alpha = 0x50; | 1022 | p.alpha = 0x50; |
diff --git a/src/ui/widget.h b/src/ui/widget.h index 27295149..2116e0d8 100644 --- a/src/ui/widget.h +++ b/src/ui/widget.h | |||
@@ -113,6 +113,7 @@ enum iWidgetFlag { | |||
113 | #define moveToParentBottomEdge_WidgetFlag iBit64(57) | 113 | #define moveToParentBottomEdge_WidgetFlag iBit64(57) |
114 | #define parentCannotResizeHeight_WidgetFlag iBit64(58) | 114 | #define parentCannotResizeHeight_WidgetFlag iBit64(58) |
115 | #define ignoreForParentWidth_WidgetFlag iBit64(59) | 115 | #define ignoreForParentWidth_WidgetFlag iBit64(59) |
116 | #define noFadeBackground_WidgetFlag iBit64(60) | ||
116 | 117 | ||
117 | enum iWidgetAddPos { | 118 | enum iWidgetAddPos { |
118 | back_WidgetAddPos, | 119 | back_WidgetAddPos, |
diff --git a/src/ui/window.c b/src/ui/window.c index d00d19a7..0a5bab6f 100644 --- a/src/ui/window.c +++ b/src/ui/window.c | |||
@@ -362,7 +362,8 @@ static const iMenuItem identityButtonMenuItems_[] = { | |||
362 | }; | 362 | }; |
363 | #endif | 363 | #endif |
364 | 364 | ||
365 | static const char *reloadCStr_ = reload_Icon; | 365 | static const char *reloadCStr_ = reload_Icon; |
366 | static const char *pageMenuCStr_ = midEllipsis_Icon; | ||
366 | 367 | ||
367 | /* TODO: A preference for these, maybe? */ | 368 | /* TODO: A preference for these, maybe? */ |
368 | static const char *stopSeqCStr_[] = { | 369 | static const char *stopSeqCStr_[] = { |
@@ -476,8 +477,14 @@ static uint32_t updateReloadAnimation_Window_(uint32_t interval, void *window) { | |||
476 | } | 477 | } |
477 | 478 | ||
478 | static void setReloadLabel_Window_(iWindow *d, iBool animating) { | 479 | static void setReloadLabel_Window_(iWindow *d, iBool animating) { |
480 | const iBool isMobile = deviceType_App() != desktop_AppDeviceType; | ||
479 | iLabelWidget *label = findChild_Widget(d->root, "reload"); | 481 | iLabelWidget *label = findChild_Widget(d->root, "reload"); |
480 | updateTextCStr_LabelWidget(label, animating ? loadAnimationCStr_() : reloadCStr_); | 482 | updateTextCStr_LabelWidget(label, animating ? loadAnimationCStr_() : |
483 | (isMobile ? pageMenuCStr_ : reloadCStr_)); | ||
484 | if (isMobile) { | ||
485 | setCommand_LabelWidget(label, | ||
486 | collectNewCStr_String(animating ? "navigate.reload" : "menu.open")); | ||
487 | } | ||
481 | } | 488 | } |
482 | 489 | ||
483 | static void checkLoadAnimation_Window_(iWindow *d) { | 490 | static void checkLoadAnimation_Window_(iWindow *d) { |
@@ -1047,11 +1054,35 @@ static void setupUserInterface_Window(iWindow *d) { | |||
1047 | addChildFlags_Widget( | 1054 | addChildFlags_Widget( |
1048 | rightEmbed, iClob(progress), collapse_WidgetFlag); | 1055 | rightEmbed, iClob(progress), collapse_WidgetFlag); |
1049 | } | 1056 | } |
1050 | /* Reload button. */ | 1057 | /* Reload button. */ { |
1051 | iLabelWidget *reload = newIcon_LabelWidget(reloadCStr_, 0, 0, "navigate.reload"); | 1058 | iLabelWidget *reload; |
1052 | setId_Widget(as_Widget(reload), "reload"); | 1059 | if (deviceType_App() == desktop_AppDeviceType) { |
1053 | addChildFlags_Widget(as_Widget(url), iClob(reload), embedFlags | moveToParentRightEdge_WidgetFlag); | 1060 | reload = newIcon_LabelWidget(reloadCStr_, 0, 0, "navigate.reload"); |
1054 | updateSize_LabelWidget(reload); | 1061 | } |
1062 | else { | ||
1063 | /* In a mobile layout, the reload button is replaced with the Page/Ellipsis menu. */ | ||
1064 | reload = makeMenuButton_LabelWidget(pageMenuCStr_, | ||
1065 | (iMenuItem[]){ | ||
1066 | { reload_Icon " ${menu.reload}", reload_KeyShortcut, "navigate.reload" }, | ||
1067 | { timer_Icon " ${menu.autoreload}", 0, 0, "document.autoreload.menu" }, | ||
1068 | { "---", 0, 0, NULL }, | ||
1069 | { upArrow_Icon " ${menu.parent}", navigateParent_KeyShortcut, "navigate.parent" }, | ||
1070 | { upArrowBar_Icon " ${menu.root}", navigateRoot_KeyShortcut, "navigate.root" }, | ||
1071 | { "---", 0, 0, NULL }, | ||
1072 | { pin_Icon " ${menu.page.bookmark}", SDLK_d, KMOD_PRIMARY, "bookmark.add" }, | ||
1073 | { star_Icon " ${menu.page.subscribe}", subscribeToPage_KeyModifier, "feeds.subscribe" }, | ||
1074 | { book_Icon " ${menu.page.import}", 0, 0, "bookmark.links confirm:1" }, | ||
1075 | { globe_Icon " ${menu.page.translate}", 0, 0, "document.translate" }, | ||
1076 | { "---", 0, 0, NULL }, | ||
1077 | { "${menu.page.copyurl}", 0, 0, "document.copylink" }, | ||
1078 | { "${menu.page.copysource}", 'c', KMOD_PRIMARY, "copy" }, | ||
1079 | { download_Icon " " saveToDownloads_Label, SDLK_s, KMOD_PRIMARY, "document.save" } }, | ||
1080 | 14); | ||
1081 | } | ||
1082 | setId_Widget(as_Widget(reload), "reload"); | ||
1083 | addChildFlags_Widget(as_Widget(url), iClob(reload), embedFlags | moveToParentRightEdge_WidgetFlag); | ||
1084 | updateSize_LabelWidget(reload); | ||
1085 | } | ||
1055 | setId_Widget(addChild_Widget(rightEmbed, iClob(makePadding_Widget(0))), "url.embedpad"); | 1086 | setId_Widget(addChild_Widget(rightEmbed, iClob(makePadding_Widget(0))), "url.embedpad"); |
1056 | } | 1087 | } |
1057 | if (deviceType_App() != desktop_AppDeviceType) { | 1088 | if (deviceType_App() != desktop_AppDeviceType) { |