diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-05-18 07:04:27 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-05-18 07:04:27 +0300 |
commit | facc3abb7faafb323cb87e26ddba8dac01af02e9 (patch) | |
tree | 387aa23e03b029f516ecca6880131fd6523e1c3f | |
parent | ced855c338b78e05c66d38618373728ef946ebaa (diff) | |
parent | 5d517c2f790a38d7fe3c3cc59a1b39fd49e20280 (diff) |
Merge branch 'dev' into work/typesetter
# Conflicts:
# src/ui/documentwidget.c
# src/ui/inputwidget.c
-rw-r--r-- | CMakeLists.txt | 8 | ||||
-rwxr-xr-x | po/compile.py | 13 | ||||
-rw-r--r-- | po/en.po | 6 | ||||
-rw-r--r-- | po/fi.po | 5 | ||||
-rw-r--r-- | po/isv.po | 193 | ||||
-rw-r--r-- | po/pl.po | 1629 | ||||
-rw-r--r-- | po/tok.po | 18 | ||||
-rw-r--r-- | res/about/version.gmi | 5 | ||||
-rw-r--r-- | res/fi.skyjake.Lagrange.appdata.xml | 18 | ||||
-rw-r--r-- | res/lang/de.bin | bin | 20259 -> 20305 bytes | |||
-rw-r--r-- | res/lang/en.bin | bin | 19008 -> 19054 bytes | |||
-rw-r--r-- | res/lang/es.bin | bin | 20975 -> 21021 bytes | |||
-rw-r--r-- | res/lang/fi.bin | bin | 20950 -> 20995 bytes | |||
-rw-r--r-- | res/lang/fr.bin | bin | 21465 -> 21511 bytes | |||
-rw-r--r-- | res/lang/ia.bin | bin | 21166 -> 21212 bytes | |||
-rw-r--r-- | res/lang/ie.bin | bin | 20337 -> 20383 bytes | |||
-rw-r--r-- | res/lang/pl.bin | bin | 0 -> 22016 bytes | |||
-rw-r--r-- | res/lang/ru.bin | bin | 32048 -> 32094 bytes | |||
-rw-r--r-- | res/lang/sr.bin | bin | 30359 -> 30673 bytes | |||
-rw-r--r-- | res/lang/tok.bin | bin | 19372 -> 19417 bytes | |||
-rw-r--r-- | res/lang/zh_Hans.bin | bin | 18130 -> 18176 bytes | |||
-rw-r--r-- | res/lang/zh_Hant.bin | bin | 18204 -> 18250 bytes | |||
-rw-r--r-- | src/app.c | 48 | ||||
-rw-r--r-- | src/app.h | 1 | ||||
-rw-r--r-- | src/defs.h | 11 | ||||
-rw-r--r-- | src/gmdocument.c | 19 | ||||
-rw-r--r-- | src/ios.m | 20 | ||||
-rw-r--r-- | src/lang.c | 9 | ||||
-rw-r--r-- | src/ui/color.c | 2 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 34 | ||||
-rw-r--r-- | src/ui/inputwidget.c | 267 | ||||
-rw-r--r-- | src/ui/labelwidget.c | 14 | ||||
-rw-r--r-- | src/ui/listwidget.c | 2 | ||||
-rw-r--r-- | src/ui/lookupwidget.c | 2 | ||||
-rw-r--r-- | src/ui/mobile.c | 795 | ||||
-rw-r--r-- | src/ui/mobile.h | 32 | ||||
-rw-r--r-- | src/ui/paint.c | 17 | ||||
-rw-r--r-- | src/ui/root.c | 132 | ||||
-rw-r--r-- | src/ui/scrollwidget.c | 2 | ||||
-rw-r--r-- | src/ui/sidebarwidget.c | 97 | ||||
-rw-r--r-- | src/ui/text.c | 60 | ||||
-rw-r--r-- | src/ui/text.h | 7 | ||||
-rw-r--r-- | src/ui/touch.c | 54 | ||||
-rw-r--r-- | src/ui/touch.h | 1 | ||||
-rw-r--r-- | src/ui/util.c | 728 | ||||
-rw-r--r-- | src/ui/util.h | 7 | ||||
-rw-r--r-- | src/ui/widget.c | 133 | ||||
-rw-r--r-- | src/ui/widget.h | 8 | ||||
-rw-r--r-- | src/ui/window.c | 45 |
49 files changed, 3438 insertions, 1004 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 17fded2b..a1037b0c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt | |||
@@ -22,8 +22,11 @@ project (Lagrange | |||
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 6) | ||
26 | set (COPYRIGHT_YEAR 2021) | 25 | set (COPYRIGHT_YEAR 2021) |
26 | if (IOS) | ||
27 | set (PROJECT_VERSION 1.4) # pinned for TestFlight | ||
28 | set (IOS_BUNDLE_VERSION 7) # just increment this | ||
29 | endif () | ||
27 | 30 | ||
28 | # Build configuration. | 31 | # Build configuration. |
29 | option (ENABLE_IPC "Use IPC to communicate between running instances" ON) | 32 | option (ENABLE_IPC "Use IPC to communicate between running instances" ON) |
@@ -87,6 +90,7 @@ set (EMBED_RESOURCES | |||
87 | res/lang/fr.bin | 90 | res/lang/fr.bin |
88 | res/lang/ia.bin | 91 | res/lang/ia.bin |
89 | res/lang/ie.bin | 92 | res/lang/ie.bin |
93 | res/lang/pl.bin | ||
90 | res/lang/ru.bin | 94 | res/lang/ru.bin |
91 | res/lang/sr.bin | 95 | res/lang/sr.bin |
92 | res/lang/tok.bin | 96 | res/lang/tok.bin |
@@ -176,6 +180,8 @@ set (SOURCES | |||
176 | src/ui/root.h | 180 | src/ui/root.h |
177 | src/ui/mediaui.c | 181 | src/ui/mediaui.c |
178 | src/ui/mediaui.h | 182 | src/ui/mediaui.h |
183 | src/ui/mobile.c | ||
184 | src/ui/mobile.h | ||
179 | src/ui/scrollwidget.c | 185 | src/ui/scrollwidget.c |
180 | src/ui/scrollwidget.h | 186 | src/ui/scrollwidget.h |
181 | src/ui/sidebarwidget.c | 187 | src/ui/sidebarwidget.c |
diff --git a/po/compile.py b/po/compile.py index 5cf403db..257f9b6f 100755 --- a/po/compile.py +++ b/po/compile.py | |||
@@ -6,7 +6,18 @@ import os, sys | |||
6 | 6 | ||
7 | BUILD_LANGS = [ | 7 | BUILD_LANGS = [ |
8 | 'en', # base strings | 8 | 'en', # base strings |
9 | 'de', 'es', 'fi', 'fr', 'ia', 'ie', 'ru', 'sr', 'tok', 'zh_Hans', 'zh_Hant' | 9 | 'de', |
10 | 'es', | ||
11 | 'fi', | ||
12 | 'fr', | ||
13 | 'ia', | ||
14 | 'ie', | ||
15 | 'pl', | ||
16 | 'ru', | ||
17 | 'sr', | ||
18 | 'tok', | ||
19 | 'zh_Hans', | ||
20 | 'zh_Hant' | ||
10 | ] | 21 | ] |
11 | MODE = 'compile' | 22 | MODE = 'compile' |
12 | ESCAPES = { | 23 | ESCAPES = { |
@@ -640,6 +640,9 @@ msgstr "Copy Fingerprint" | |||
640 | msgid "dlg.input.prompt" | 640 | msgid "dlg.input.prompt" |
641 | msgstr "Please enter input for %s:" | 641 | msgstr "Please enter input for %s:" |
642 | 642 | ||
643 | msgid "dlg.input.linebreak" | ||
644 | msgstr "Line break" | ||
645 | |||
643 | msgid "dlg.input.send" | 646 | msgid "dlg.input.send" |
644 | msgstr "Send" | 647 | msgstr "Send" |
645 | 648 | ||
@@ -885,6 +888,9 @@ msgstr "Italian" | |||
885 | msgid "lang.ja" | 888 | msgid "lang.ja" |
886 | msgstr "Japanese" | 889 | msgstr "Japanese" |
887 | 890 | ||
891 | msgid "lang.pl" | ||
892 | msgstr "Polish" | ||
893 | |||
888 | msgid "lang.pt" | 894 | msgid "lang.pt" |
889 | msgstr "Portuguese" | 895 | msgstr "Portuguese" |
890 | 896 | ||
@@ -3,7 +3,7 @@ msgstr "" | |||
3 | "Project-Id-Version: PACKAGE VERSION\n" | 3 | "Project-Id-Version: PACKAGE VERSION\n" |
4 | "Report-Msgid-Bugs-To: jaakko.keranen@iki.fi\n" | 4 | "Report-Msgid-Bugs-To: jaakko.keranen@iki.fi\n" |
5 | "POT-Creation-Date: 2021-03-23 09:09+0000\n" | 5 | "POT-Creation-Date: 2021-03-23 09:09+0000\n" |
6 | "PO-Revision-Date: 2021-05-06 09:39+0000\n" | 6 | "PO-Revision-Date: 2021-05-13 05:38+0000\n" |
7 | "Last-Translator: Jaakko Keränen <jaakko.keranen@iki.fi>\n" | 7 | "Last-Translator: Jaakko Keränen <jaakko.keranen@iki.fi>\n" |
8 | "Language-Team: Finnish <http://weblate.skyjake.fi/projects/lagrange/ui/fi/>\n" | 8 | "Language-Team: Finnish <http://weblate.skyjake.fi/projects/lagrange/ui/fi/>\n" |
9 | "Language: fi\n" | 9 | "Language: fi\n" |
@@ -1576,3 +1576,6 @@ msgstr "Aseta näkymän jakaus" | |||
1576 | 1576 | ||
1577 | msgid "keys.split.next" | 1577 | msgid "keys.split.next" |
1578 | msgstr "Aktivoi seuraava jakaus" | 1578 | msgstr "Aktivoi seuraava jakaus" |
1579 | |||
1580 | msgid "lang.pl" | ||
1581 | msgstr "Puola" | ||
diff --git a/po/isv.po b/po/isv.po new file mode 100644 index 00000000..b1ffdc84 --- /dev/null +++ b/po/isv.po | |||
@@ -0,0 +1,193 @@ | |||
1 | msgid "" | ||
2 | msgstr "" | ||
3 | "Report-Msgid-Bugs-To: jaakko.keranen@iki.fi\n" | ||
4 | "PO-Revision-Date: 2021-05-13 04:58+0000\n" | ||
5 | "Last-Translator: Waterrail <maksymiliankrol03@gmail.com>\n" | ||
6 | "Language-Team: Interslavic <http://weblate.skyjake.fi/projects/lagrange/ui/" | ||
7 | "isv/>\n" | ||
8 | "Language: isv\n" | ||
9 | "MIME-Version: 1.0\n" | ||
10 | "Content-Type: text/plain; charset=UTF-8\n" | ||
11 | "Content-Transfer-Encoding: 8bit\n" | ||
12 | "Plural-Forms: nplurals=3; plural=n==1? 0 : n==2 ? 1 : 2;\n" | ||
13 | "X-Generator: Weblate 4.5.1\n" | ||
14 | |||
15 | msgid "doc.pre.nocaption" | ||
16 | msgstr "Vvodno sformatovany napis bez opisa" | ||
17 | |||
18 | # Inline download status message. | ||
19 | msgid "media.download.warnclose" | ||
20 | msgstr "Dostava bude odrěčeny pri zamknutju tutoj karticy." | ||
21 | |||
22 | #, c-format | ||
23 | msgid "feeds.list.counts" | ||
24 | msgid_plural "feeds.list.counts.n" | ||
25 | msgstr[0] "Jeste podpisyvani na %zu tasmě sodrživajučej %%s.\n" | ||
26 | msgstr[1] "Jeste podpisyvani na %zu tasmu sodrživajuču %%s.\n" | ||
27 | msgstr[2] "Jeste podpisyvani na %zu tasmah sodrživajučih %%s.\n" | ||
28 | |||
29 | # Link download progress message. | ||
30 | msgid "doc.fetching" | ||
31 | msgstr "Dostavanje" | ||
32 | |||
33 | #, c-format | ||
34 | msgid "doc.archive" | ||
35 | msgstr "%s je spakovany arhivom." | ||
36 | |||
37 | msgid "doc.archive.view" | ||
38 | msgstr "Prěgleděti sodržanje arhiva" | ||
39 | |||
40 | # Inline download status message. | ||
41 | msgid "media.download.complete" | ||
42 | msgstr "Dostava ukončena." | ||
43 | |||
44 | # Used in inline audio player metadata popup. | ||
45 | msgid "audio.meta.title" | ||
46 | msgstr "Nazva" | ||
47 | |||
48 | # Used in inline audio player metadata popup. | ||
49 | msgid "audio.meta.artist" | ||
50 | msgstr "Artist" | ||
51 | |||
52 | # Used in inline audio player metadata popup. | ||
53 | msgid "audio.meta.date" | ||
54 | msgstr "Data" | ||
55 | |||
56 | # used as adjective, n is 8, 16, 24, 32, or 64 | ||
57 | #, c-format | ||
58 | msgid "n.bit" | ||
59 | msgstr "%d-bitny" | ||
60 | |||
61 | msgid "numbertype.integer" | ||
62 | msgstr "cělo" | ||
63 | |||
64 | msgid "numbertype.float" | ||
65 | msgstr "s plavajučeju komoju" | ||
66 | |||
67 | # Hertz, unit for frequency values | ||
68 | msgid "hz" | ||
69 | msgstr "Hz" | ||
70 | |||
71 | # Used in about:feeds. | ||
72 | msgid "feeds.list.title" | ||
73 | msgstr "Vpisy v tasmě" | ||
74 | |||
75 | msgid "archive.exit" | ||
76 | msgstr "Izhod iz arhiva" | ||
77 | |||
78 | msgid "bookmark.delete" | ||
79 | msgstr "Dodati zakladku" | ||
80 | |||
81 | msgid "bookmark.tag.linksplit" | ||
82 | msgstr "Linky otvarjane v děljenom oknom" | ||
83 | |||
84 | msgid "bookmarks.reload" | ||
85 | msgstr "Izsinhronizovati s žrlom zakladok" | ||
86 | |||
87 | msgid "dir.empty" | ||
88 | msgstr "Toj katalog je pusty." | ||
89 | |||
90 | #, c-format | ||
91 | msgid "dir.summary" | ||
92 | msgid_plural "dir.summary.n" | ||
93 | msgstr[0] "Toj katalog sodrživa %zu element." | ||
94 | msgstr[1] "Toj katalog sodrživa %zu elementa." | ||
95 | msgstr[2] "Toj katalog sodrživa %zu elementy." | ||
96 | |||
97 | msgid "dlg.autoreload" | ||
98 | msgstr "Izberite interval automatičnogo ponovnogo nakladanja toj karticy." | ||
99 | |||
100 | msgid "dlg.bookmark.icon" | ||
101 | msgstr "Ikona:" | ||
102 | |||
103 | msgid "dlg.bookmark.save" | ||
104 | msgstr "Zapisati zakladku" | ||
105 | |||
106 | msgid "dlg.certimport.help" | ||
107 | msgstr "" | ||
108 | "Vstavite privatny ključ i/abo svědočstvo v formatu PEM,\n" | ||
109 | "abo spustite fajl .crt/.key na oblast okna." | ||
110 | |||
111 | # Alt-text of the preformatted logo. | ||
112 | msgid "about.logo" | ||
113 | msgstr "ASCII-art: rěč \"Lagrange\" natipkana velikym fontom" | ||
114 | |||
115 | msgid "about.powered" | ||
116 | msgstr "Ustanovjeny na SDL 2, OpenSSL i ☕️" | ||
117 | |||
118 | msgid "about.tagline" | ||
119 | msgstr "Krasny klient Gemini" | ||
120 | |||
121 | msgid "about.version" | ||
122 | msgstr "Versija" | ||
123 | |||
124 | # Used in inline audio player metadata popup. | ||
125 | msgid "audio.meta.genre" | ||
126 | msgstr "Žanr" | ||
127 | |||
128 | msgid "cancel" | ||
129 | msgstr "Anulovati" | ||
130 | |||
131 | #, c-format | ||
132 | msgid "days.ago" | ||
133 | msgid_plural "days.ago.n" | ||
134 | msgstr[0] "%d dni nazad" | ||
135 | msgstr[1] "%d dnja nazad" | ||
136 | msgstr[2] "%d denj nazad" | ||
137 | |||
138 | msgid "dismiss" | ||
139 | msgstr "Zamknuti" | ||
140 | |||
141 | msgid "bookmark.tag.home" | ||
142 | msgstr "Upotrěbiti kako glavnu stranicu" | ||
143 | |||
144 | msgid "bookmark.tag.remote" | ||
145 | msgstr "Upotrěbiti kako žrlo zakladok" | ||
146 | |||
147 | msgid "bookmark.tag.sub" | ||
148 | msgstr "Podpiš tasmu" | ||
149 | |||
150 | msgid "bookmark.untag.home" | ||
151 | msgstr "Odstraniti glavnu stranicu" | ||
152 | |||
153 | msgid "bookmark.untag.remote" | ||
154 | msgstr "Odstraniti žrlo zakladok" | ||
155 | |||
156 | msgid "bookmark.untag.sub" | ||
157 | msgstr "Odrěči podpisku tasmy" | ||
158 | |||
159 | msgid "dlg.cert.fingerprint" | ||
160 | msgstr "Kopirovati odtisk prsta" | ||
161 | |||
162 | msgid "dlg.cert.trust" | ||
163 | msgstr "Dověrjati" | ||
164 | |||
165 | msgid "dlg.certimport.import" | ||
166 | msgstr "Importovati" | ||
167 | |||
168 | msgid "dlg.certimport.nocert" | ||
169 | msgstr "Nemaje svědočstva" | ||
170 | |||
171 | msgid "dlg.certimport.nokey" | ||
172 | msgstr "Nemaje privatnogo ključa" | ||
173 | |||
174 | msgid "dlg.certimport.notes" | ||
175 | msgstr "Zapisky:" | ||
176 | |||
177 | msgid "dlg.certimport.notfound" | ||
178 | msgstr "Svědočstvo abo privatny ključ ne najdeny." | ||
179 | |||
180 | msgid "dlg.certimport.notfound.page" | ||
181 | msgstr "Nemaje svědočstva/ključa na sejčasnoj stranici." | ||
182 | |||
183 | msgid "bookmark.title.blank" | ||
184 | msgstr "Pusta stranica" | ||
185 | |||
186 | msgid "dlg.bookmark.title" | ||
187 | msgstr "Nazva:" | ||
188 | |||
189 | msgid "dlg.bookmark.tags" | ||
190 | msgstr "Etikety:" | ||
191 | |||
192 | msgid "dlg.bookmark.url" | ||
193 | msgstr "URL:" | ||
diff --git a/po/pl.po b/po/pl.po new file mode 100644 index 00000000..01e295b8 --- /dev/null +++ b/po/pl.po | |||
@@ -0,0 +1,1629 @@ | |||
1 | msgid "" | ||
2 | msgstr "" | ||
3 | "Report-Msgid-Bugs-To: jaakko.keranen@iki.fi\n" | ||
4 | "PO-Revision-Date: 2021-05-13 04:58+0000\n" | ||
5 | "Last-Translator: Waterrail <maksymiliankrol03@gmail.com>\n" | ||
6 | "Language-Team: Polish <http://weblate.skyjake.fi/projects/lagrange/ui/pl/>\n" | ||
7 | "Language: pl\n" | ||
8 | "MIME-Version: 1.0\n" | ||
9 | "Content-Type: text/plain; charset=UTF-8\n" | ||
10 | "Content-Transfer-Encoding: 8bit\n" | ||
11 | "Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " | ||
12 | "|| n%100>=20) ? 1 : 2;\n" | ||
13 | "X-Generator: Weblate 4.5.1\n" | ||
14 | |||
15 | msgid "error.proxyrefusal.msg" | ||
16 | msgstr "" | ||
17 | "Żądanie dotyczyło zasobu w domenie, która nie jest obsługiwana przez serwer, " | ||
18 | "a serwer nie akceptuje żądań proxy." | ||
19 | |||
20 | msgid "gempub.cover.viewlocal" | ||
21 | msgstr "Aby wyświetlić tę książkę Gempub, najpierw musisz ją pobrać." | ||
22 | |||
23 | msgid "gempub.meta.revdate" | ||
24 | msgstr "Data korekty" | ||
25 | |||
26 | # Link download progress message. | ||
27 | msgid "doc.fetching" | ||
28 | msgstr "Pobieram" | ||
29 | |||
30 | #, c-format | ||
31 | msgid "doc.archive" | ||
32 | msgstr "%s jest archiwum skompresowanym." | ||
33 | |||
34 | # Inline download status message. | ||
35 | msgid "media.download.complete" | ||
36 | msgstr "Ukończono pobieranie." | ||
37 | |||
38 | # Used in inline audio player metadata popup. | ||
39 | msgid "audio.meta.title" | ||
40 | msgstr "Tytuł" | ||
41 | |||
42 | # Used in inline audio player metadata popup. | ||
43 | msgid "audio.meta.artist" | ||
44 | msgstr "Artysta" | ||
45 | |||
46 | # Used in inline audio player metadata popup. | ||
47 | msgid "audio.meta.genre" | ||
48 | msgstr "Gatunek" | ||
49 | |||
50 | # used as adjective, n is 8, 16, 24, 32, or 64 | ||
51 | #, c-format | ||
52 | msgid "n.bit" | ||
53 | msgstr "%d-bitowy" | ||
54 | |||
55 | msgid "numbertype.integer" | ||
56 | msgstr "całkowita" | ||
57 | |||
58 | msgid "numbertype.float" | ||
59 | msgstr "zmiennoprzecinkowa" | ||
60 | |||
61 | # Hertz, unit for frequency values | ||
62 | msgid "hz" | ||
63 | msgstr "Hz" | ||
64 | |||
65 | # Used in about:feeds. | ||
66 | msgid "feeds.list.title" | ||
67 | msgstr "Wpisy kanału" | ||
68 | |||
69 | #, c-format | ||
70 | msgid "feeds.list.entrycount" | ||
71 | msgid_plural "feeds.list.entrycount.n" | ||
72 | msgstr[0] "łącznie %zu wpis" | ||
73 | msgstr[1] "łącznie %zu wpisy" | ||
74 | msgstr[2] "łącznie %zu wpisów" | ||
75 | |||
76 | msgid "feeds.list.refreshtime.now" | ||
77 | msgstr "Ostatnie odświeżenie miało miejsce przed chwilą." | ||
78 | |||
79 | #, c-format | ||
80 | msgid "feeds.list.refreshtime" | ||
81 | msgstr "Ostatnie odświeżenie miało miejsce %s." | ||
82 | |||
83 | #, c-format | ||
84 | msgid "minutes.ago" | ||
85 | msgid_plural "minutes.ago.n" | ||
86 | msgstr[0] "minutę temu" | ||
87 | msgstr[1] "%d minuty temu" | ||
88 | msgstr[2] "%d minut temu" | ||
89 | |||
90 | msgid "about.tagline" | ||
91 | msgstr "Piękny klient Gemini" | ||
92 | |||
93 | msgid "about.version" | ||
94 | msgstr "Wersja" | ||
95 | |||
96 | msgid "about.powered" | ||
97 | msgstr "Oparty na SDL 2, OpenSSL oraz ☕️" | ||
98 | |||
99 | msgid "cancel" | ||
100 | msgstr "Anuluj" | ||
101 | |||
102 | msgid "menu.title.edit" | ||
103 | msgstr "Edytuj" | ||
104 | |||
105 | msgid "menu.title.view" | ||
106 | msgstr "Podgląd" | ||
107 | |||
108 | msgid "menu.title.bookmarks" | ||
109 | msgstr "Zakładki" | ||
110 | |||
111 | msgid "menu.title.identity" | ||
112 | msgstr "Tożsamość" | ||
113 | |||
114 | msgid "dismiss" | ||
115 | msgstr "Zamknij" | ||
116 | |||
117 | msgid "dlg.message.ok" | ||
118 | msgstr "Kontynuuj" | ||
119 | |||
120 | msgid "dlg.default" | ||
121 | msgstr " OK " | ||
122 | |||
123 | msgid "toggle.yes" | ||
124 | msgstr "TAK" | ||
125 | |||
126 | msgid "toggle.no" | ||
127 | msgstr "NIE" | ||
128 | |||
129 | msgid "menu.newtab" | ||
130 | msgstr "Nowa karta" | ||
131 | |||
132 | msgid "menu.closetab" | ||
133 | msgstr "Zamknij kartę" | ||
134 | |||
135 | msgid "menu.closetab.left" | ||
136 | msgstr "Zamknij karty po lewej" | ||
137 | |||
138 | msgid "menu.closetab.right" | ||
139 | msgstr "Zamknij karty po prawej" | ||
140 | |||
141 | msgid "menu.duptab" | ||
142 | msgstr "Powiel kartę" | ||
143 | |||
144 | msgid "menu.split.merge" | ||
145 | msgstr "Scal karty" | ||
146 | |||
147 | msgid "menu.split.swap" | ||
148 | msgstr "Zamień karty miejscami" | ||
149 | |||
150 | msgid "menu.split.horizontal" | ||
151 | msgstr "Poziomo" | ||
152 | |||
153 | msgid "menu.openlocation" | ||
154 | msgstr "Otwórz lokalizację…" | ||
155 | |||
156 | msgid "menu.downloads" | ||
157 | msgstr "Pokaż folder pobranych" | ||
158 | |||
159 | msgid "menu.find" | ||
160 | msgstr "Znajdź na stronie" | ||
161 | |||
162 | msgid "macos.menu.find" | ||
163 | msgstr "Znajdź" | ||
164 | |||
165 | # Used on desktop operating systems. "Downloads" refers to the user's configured downloads directory. | ||
166 | msgid "menu.save.downloads" | ||
167 | msgstr "Zapisz w Pobranych" | ||
168 | |||
169 | msgid "menu.sidebar" | ||
170 | msgstr "Przełącz pasek boczny" | ||
171 | |||
172 | msgid "menu.sidebar.left" | ||
173 | msgstr "Przełącz lewy panel boczny" | ||
174 | |||
175 | msgid "menu.sidebar.right" | ||
176 | msgstr "Przełącz prawy panel boczny" | ||
177 | |||
178 | msgid "menu.zoom.in" | ||
179 | msgstr "Powiększ" | ||
180 | |||
181 | msgid "menu.zoom.out" | ||
182 | msgstr "Pomniejsz" | ||
183 | |||
184 | msgid "menu.zoom.reset" | ||
185 | msgstr "Resetuj powiększenie" | ||
186 | |||
187 | msgid "macos.menu.bookmarks.list" | ||
188 | msgstr "Pokaż wszystkie" | ||
189 | |||
190 | msgid "menu.view.split" | ||
191 | msgstr "Widok dzielony…" | ||
192 | |||
193 | msgid "macos.menu.bookmarks.bytag" | ||
194 | msgstr "Sortuj według tagu" | ||
195 | |||
196 | msgid "macos.menu.bookmarks.bytime" | ||
197 | msgstr "Sortuj według daty utworzenia" | ||
198 | |||
199 | msgid "menu.feeds.entrylist" | ||
200 | msgstr "Pokaż wpisy kanału" | ||
201 | |||
202 | msgid "menu.help" | ||
203 | msgstr "Pomoc" | ||
204 | |||
205 | msgid "menu.releasenotes" | ||
206 | msgstr "Informacje o wydaniu" | ||
207 | |||
208 | msgid "menu.quit" | ||
209 | msgstr "Wyjdź z Lagrange" | ||
210 | |||
211 | msgid "menu.preferences" | ||
212 | msgstr "Opcje…" | ||
213 | |||
214 | msgid "menu.copy" | ||
215 | msgstr "Kopiuj" | ||
216 | |||
217 | msgid "menu.paste" | ||
218 | msgstr "Wklej" | ||
219 | |||
220 | msgid "menu.select.clear" | ||
221 | msgstr "Wyczyść zaznaczenie" | ||
222 | |||
223 | # Used in the View menu on macOS. Shows sidebar and switches sidebar tab. | ||
224 | msgid "menu.show.bookmarks" | ||
225 | msgstr "Pokaż zakładki" | ||
226 | |||
227 | # Used in the View menu on macOS. Shows sidebar and switches sidebar tab. | ||
228 | msgid "menu.show.feeds" | ||
229 | msgstr "Pokaż kanały" | ||
230 | |||
231 | # Used in the View menu on macOS. Shows sidebar and switches sidebar tab. | ||
232 | msgid "menu.show.history" | ||
233 | msgstr "Pokaż historię" | ||
234 | |||
235 | # Used in the View menu on macOS. Shows sidebar and switches sidebar tab. | ||
236 | msgid "menu.show.identities" | ||
237 | msgstr "Pokaż tożsamości" | ||
238 | |||
239 | msgid "menu.back" | ||
240 | msgstr "Wstecz" | ||
241 | |||
242 | msgid "menu.forward" | ||
243 | msgstr "Dalej" | ||
244 | |||
245 | msgid "menu.parent" | ||
246 | msgstr "Przejdź do katalogu nadrzędnego" | ||
247 | |||
248 | msgid "menu.root" | ||
249 | msgstr "Przejdź do katalogu głównego" | ||
250 | |||
251 | msgid "menu.reload" | ||
252 | msgstr "Odśwież stronę" | ||
253 | |||
254 | msgid "menu.autoreload" | ||
255 | msgstr "Ustaw autoodświeżanie…" | ||
256 | |||
257 | msgid "menu.page.bookmark" | ||
258 | msgstr "Dodaj stronę do zakładek…" | ||
259 | |||
260 | msgid "menu.aboutpages" | ||
261 | msgstr "Strony z informacjami" | ||
262 | |||
263 | msgid "menu.about" | ||
264 | msgstr "O Lagrange" | ||
265 | |||
266 | msgid "panel.back" | ||
267 | msgstr "Wstecz" | ||
268 | |||
269 | msgid "menu.debug" | ||
270 | msgstr "Informacje debugowania" | ||
271 | |||
272 | msgid "menu.page.import" | ||
273 | msgstr "Importuj linki jako zakładki…" | ||
274 | |||
275 | msgid "menu.page.translate" | ||
276 | msgstr "Przetłumacz…" | ||
277 | |||
278 | msgid "menu.page.copyurl" | ||
279 | msgstr "Kopiuj adres strony" | ||
280 | |||
281 | msgid "menu.page.copysource" | ||
282 | msgstr "Kopiuj źródło strony" | ||
283 | |||
284 | msgid "menu.feeds.refresh" | ||
285 | msgstr "Odśwież kanały" | ||
286 | |||
287 | msgid "menu.identity.new" | ||
288 | msgstr "Nowa tożsamość…" | ||
289 | |||
290 | msgid "menu.identity.import" | ||
291 | msgstr "Importuj…" | ||
292 | |||
293 | msgid "sidebar.bookmarks" | ||
294 | msgstr "Zakładki" | ||
295 | |||
296 | msgid "sidebar.history" | ||
297 | msgstr "Historia" | ||
298 | |||
299 | msgid "sidebar.identities" | ||
300 | msgstr "Tożsamości" | ||
301 | |||
302 | msgid "sidebar.outline" | ||
303 | msgstr "Konspekt" | ||
304 | |||
305 | msgid "sidebar.action.feeds.showunread" | ||
306 | msgstr "Nieprzeczytane" | ||
307 | |||
308 | msgid "sidebar.action.ident.new" | ||
309 | msgstr "Nowa…" | ||
310 | |||
311 | msgid "toolbar.outline" | ||
312 | msgstr "Konspekt strony" | ||
313 | |||
314 | msgid "hint.findtext" | ||
315 | msgstr "Znajdź tekst na stronie" | ||
316 | |||
317 | msgid "status.query" | ||
318 | msgstr "Zapytanie wyszukiwania" | ||
319 | |||
320 | msgid "status.feeds" | ||
321 | msgstr "Aktualizuję kanały" | ||
322 | |||
323 | # megabytes, used as the unit after a number | ||
324 | msgid "mb" | ||
325 | msgstr "MB" | ||
326 | |||
327 | msgid "mb.per.sec" | ||
328 | msgstr "MB/s" | ||
329 | |||
330 | # kilobytes, used as the unit after a number | ||
331 | msgid "kb" | ||
332 | msgstr "KB" | ||
333 | |||
334 | msgid "num.bytes" | ||
335 | msgid_plural "num.bytes.n" | ||
336 | msgstr[0] "%zu bajt" | ||
337 | msgstr[1] "%zu bajty" | ||
338 | msgstr[2] "%zu bajtów" | ||
339 | |||
340 | # strftime() formatted, split on two lines | ||
341 | #, c-format | ||
342 | msgid "page.timestamp" | ||
343 | msgstr "" | ||
344 | "Otrzymano %d.%m.%Y\n" | ||
345 | "o %H:%M" | ||
346 | |||
347 | # strftime() formatted | ||
348 | #, c-format | ||
349 | msgid "sidebar.date.thisyear" | ||
350 | msgstr "%d %B" | ||
351 | |||
352 | # strftime() formatted | ||
353 | #, c-format | ||
354 | msgid "sidebar.date.otheryear" | ||
355 | msgstr "%d.%m.%Y" | ||
356 | |||
357 | msgid "feeds.today" | ||
358 | msgstr "Dziś" | ||
359 | |||
360 | msgid "feeds.entry.newtab" | ||
361 | msgstr "Otwórz wpis w nowej karcie" | ||
362 | |||
363 | msgid "feeds.entry.markread" | ||
364 | msgstr "Oznacz jako przeczytane" | ||
365 | |||
366 | msgid "sidebar.entry.bookmark" | ||
367 | msgstr "Dodaj zakładkę…" | ||
368 | |||
369 | msgid "feeds.entry.bookmark" | ||
370 | msgstr "Dodaj zakładkę…" | ||
371 | |||
372 | msgid "feeds.unsubscribe" | ||
373 | msgstr "Anuluj subskrypcję…" | ||
374 | |||
375 | msgid "feeds.markallread" | ||
376 | msgstr "Oznacz wszystko jako przeczytane" | ||
377 | |||
378 | msgid "feeds.refresh" | ||
379 | msgstr "Odśwież kanały" | ||
380 | |||
381 | msgid "menu.opentab" | ||
382 | msgstr "Otwórz w nowej karcie" | ||
383 | |||
384 | msgid "menu.opentab.background" | ||
385 | msgstr "Otwórz w nowej karcie w tle" | ||
386 | |||
387 | msgid "menu.edit" | ||
388 | msgstr "Edytuj…" | ||
389 | |||
390 | msgid "menu.dup" | ||
391 | msgstr "Powiel…" | ||
392 | |||
393 | msgid "menu.forgeturl" | ||
394 | msgstr "Zapomnij adres URL" | ||
395 | |||
396 | msgid "history.clear" | ||
397 | msgstr "Wyczyść historię…" | ||
398 | |||
399 | msgid "heading.history.clear" | ||
400 | msgstr "CZYSZCZENIE HISTORII" | ||
401 | |||
402 | msgid "dlg.history.clear" | ||
403 | msgstr "Wyczyść historię" | ||
404 | |||
405 | msgid "bookmark.tag.home" | ||
406 | msgstr "Użyj jako strony głównej" | ||
407 | |||
408 | msgid "bookmark.tag.sub" | ||
409 | msgstr "Subskrybuj kanał" | ||
410 | |||
411 | msgid "bookmark.tag.remote" | ||
412 | msgstr "Użyj jako źródła zakładek" | ||
413 | |||
414 | msgid "bookmark.untag.home" | ||
415 | msgstr "Usuń stronę główną" | ||
416 | |||
417 | msgid "bookmark.untag.sub" | ||
418 | msgstr "Anuluj subskrypcję kanału" | ||
419 | |||
420 | msgid "bookmark.tag.linksplit" | ||
421 | msgstr "Linki otwierane w podzielonym oknie" | ||
422 | |||
423 | msgid "bookmark.delete" | ||
424 | msgstr "Usuń zakładkę" | ||
425 | |||
426 | msgid "bookmarks.reload" | ||
427 | msgstr "Odśwież zewnętrzne zakładki" | ||
428 | |||
429 | msgid "ident.notused" | ||
430 | msgstr "Nieużywana" | ||
431 | |||
432 | #, c-format | ||
433 | msgid "ident.usedonurls" | ||
434 | msgid_plural "ident.usedonurls.n" | ||
435 | msgstr[0] "Używana w %zu adresie URL" | ||
436 | msgstr[1] "Używana w %zu adresach URL" | ||
437 | msgstr[2] "Używana w %zu adresach URL" | ||
438 | |||
439 | msgid "ident.temporary" | ||
440 | msgstr "Tymczasowa" | ||
441 | |||
442 | msgid "ident.stopuse.all" | ||
443 | msgstr "Nie używaj nigdzie" | ||
444 | |||
445 | msgid "ident.use" | ||
446 | msgstr "Używaj na tej stronie" | ||
447 | |||
448 | msgid "ident.stopuse" | ||
449 | msgstr "Nie używaj na tej stronie" | ||
450 | |||
451 | msgid "ident.showuse" | ||
452 | msgstr "Pokaż użycie" | ||
453 | |||
454 | msgid "heading.ident.use" | ||
455 | msgstr "UŻYCIE TOŻSAMOŚCI" | ||
456 | |||
457 | msgid "menu.edit.notes" | ||
458 | msgstr "Edytuj adnotacje…" | ||
459 | |||
460 | msgid "heading.ident.notes" | ||
461 | msgstr "ADNOTACJE TOŻSAMOŚCI" | ||
462 | |||
463 | # %s refers to name of an identity. | ||
464 | #, c-format | ||
465 | msgid "dlg.ident.notes" | ||
466 | msgstr "Adnotacje na temat tożsamości %s:" | ||
467 | |||
468 | msgid "ident.fingerprint" | ||
469 | msgstr "Kopiuj odcisk palca" | ||
470 | |||
471 | msgid "ident.delete" | ||
472 | msgstr "Usuń tożsamość…" | ||
473 | |||
474 | msgid "heading.ident.delete" | ||
475 | msgstr "USUWANIE TOŻSAMOŚCI" | ||
476 | |||
477 | msgid "dlg.ident.delete" | ||
478 | msgstr "Usuń tożsamość i pliki" | ||
479 | |||
480 | #, c-format | ||
481 | msgid "dlg.confirm.unsub" | ||
482 | msgstr "" | ||
483 | "Czy na pewno chcesz anulować subskrypcję kanału\n" | ||
484 | "„%s”?" | ||
485 | |||
486 | msgid "dlg.unsub" | ||
487 | msgstr "Anuluj subskrypcję" | ||
488 | |||
489 | #, c-format | ||
490 | msgid "error.unsupported.suggestsave" | ||
491 | msgstr "" | ||
492 | "Możesz zapisać go jako plik w folderze Pobrane: naciśnij %s lub wybierz „%s” " | ||
493 | "z menu." | ||
494 | |||
495 | msgid "heading.pageinfo" | ||
496 | msgstr "INFORMACJE NA TEMAT STRONY" | ||
497 | |||
498 | msgid "pageinfo.header.cached" | ||
499 | msgstr "(zawartość pamięci podręcznej)" | ||
500 | |||
501 | msgid "heading.unsub" | ||
502 | msgstr "ANULOWANIE SUBSKRYPCJI" | ||
503 | |||
504 | msgid "pageinfo.cert.ca.unverified" | ||
505 | msgstr "Niezweryfikowany przez CA" | ||
506 | |||
507 | msgid "pageinfo.cert.notexpired" | ||
508 | msgstr "Niewygasły" | ||
509 | |||
510 | msgid "pageinfo.cert.expired" | ||
511 | msgstr "Wygasły" | ||
512 | |||
513 | msgid "pageinfo.cert.trusted" | ||
514 | msgstr "Zaufany" | ||
515 | |||
516 | msgid "pageinfo.cert.untrusted" | ||
517 | msgstr "Niezaufany" | ||
518 | |||
519 | msgid "pageinfo.domain.match" | ||
520 | msgstr "Pasująca nazwa domeny" | ||
521 | |||
522 | msgid "pageinfo.domain.mismatch" | ||
523 | msgstr "Niepasująca nazwa domeny" | ||
524 | |||
525 | msgid "dlg.cert.trust" | ||
526 | msgstr "Ufaj" | ||
527 | |||
528 | msgid "dlg.cert.fingerprint" | ||
529 | msgstr "Kopiuj odcisk palca" | ||
530 | |||
531 | #, c-format | ||
532 | msgid "dlg.input.prompt" | ||
533 | msgstr "Wprowadź dane dla %s:" | ||
534 | |||
535 | msgid "dlg.input.send" | ||
536 | msgstr "Wyślij" | ||
537 | |||
538 | msgid "heading.save" | ||
539 | msgstr "ZAPISANO PLIK" | ||
540 | |||
541 | msgid "heading.save.incomplete" | ||
542 | msgstr "STRONA NIEKOMPLETNA" | ||
543 | |||
544 | msgid "dlg.save.incomplete" | ||
545 | msgstr "Zawartość strony jest nadal pobierana." | ||
546 | |||
547 | msgid "dlg.save.size" | ||
548 | msgstr "Rozmiar:" | ||
549 | |||
550 | msgid "dlg.save.opendownload" | ||
551 | msgstr "Otwórz pobrany plik" | ||
552 | |||
553 | msgid "heading.save.error" | ||
554 | msgstr "BŁĄD ZAPISYWANIA PLIKU" | ||
555 | |||
556 | msgid "heading.import.bookmarks" | ||
557 | msgstr "IMPORTUJ ZAKŁADKI" | ||
558 | |||
559 | #, c-format | ||
560 | msgid "dlg.import.add" | ||
561 | msgid_plural "dlg.import.add.n" | ||
562 | msgstr[0] "%sDodaj zakładkę" | ||
563 | msgstr[1] "%sDodaj %d zakładki" | ||
564 | msgstr[2] "%sDodaj %d zakładek" | ||
565 | |||
566 | msgid "dlg.import.notnew" | ||
567 | msgstr "Wszystkie linki na tej stronie są już oznaczone zakładką." | ||
568 | |||
569 | msgid "heading.autoreload" | ||
570 | msgstr "AUTOODŚWIEŻANIE" | ||
571 | |||
572 | msgid "dlg.autoreload" | ||
573 | msgstr "Wybierz interwał autoodświeżania tej karty." | ||
574 | |||
575 | msgid "reload.never" | ||
576 | msgstr "Nigdy" | ||
577 | |||
578 | msgid "reload.onceperday" | ||
579 | msgstr "Raz dziennie" | ||
580 | |||
581 | #, c-format | ||
582 | msgid "num.hours" | ||
583 | msgid_plural "num.hours.n" | ||
584 | msgstr[0] "%d godzina" | ||
585 | msgstr[1] "%d godziny" | ||
586 | msgstr[2] "%d godzin" | ||
587 | |||
588 | msgid "link.newtab" | ||
589 | msgstr "Otwórz link w nowej karcie" | ||
590 | |||
591 | msgid "link.browser" | ||
592 | msgstr "Otwórz link w domyślnej przeglądarce" | ||
593 | |||
594 | msgid "link.noproxy" | ||
595 | msgstr "Otwórz bez użycia proxy" | ||
596 | |||
597 | msgid "link.copy" | ||
598 | msgstr "Kopiuj link" | ||
599 | |||
600 | msgid "link.bookmark" | ||
601 | msgstr "Dodaj link do zakładek…" | ||
602 | |||
603 | msgid "link.side" | ||
604 | msgstr "Otwórz link w innym oknie" | ||
605 | |||
606 | msgid "link.side.newtab" | ||
607 | msgstr "Otwórz link w nowym oknie" | ||
608 | |||
609 | msgid "heading.openlink" | ||
610 | msgstr "OTWARCIE LINKU" | ||
611 | |||
612 | #, c-format | ||
613 | msgid "dlg.openlink.confirm" | ||
614 | msgstr "" | ||
615 | "Otworzyć link w przeglądarce domyślnej?\n" | ||
616 | "%s%s" | ||
617 | |||
618 | msgid "dlg.openlink" | ||
619 | msgstr "Otwórz link" | ||
620 | |||
621 | msgid "heading.certwarn" | ||
622 | msgstr "NIEZAUFANY CERTYFIKAT" | ||
623 | |||
624 | msgid "dlg.certwarn.different" | ||
625 | msgstr "" | ||
626 | "Otrzymany certyfikat jest ważny, ale różni się od tego zaufanego przez nas." | ||
627 | |||
628 | msgid "dlg.certwarn.domain.expired" | ||
629 | msgstr "Otrzymany certyfikat zarówno wygasł, jak i dotyczy niewłaściwej domeny." | ||
630 | |||
631 | msgid "heading.certimport" | ||
632 | msgstr "IMPORT TOŻSAMOŚCI" | ||
633 | |||
634 | msgid "dlg.certimport.help" | ||
635 | msgstr "" | ||
636 | "Wklej klucz prywatny i/lub certyfikat w formacie PEM,\n" | ||
637 | "albo upuść plik .crt/.key na obszar okna." | ||
638 | |||
639 | msgid "heading.certimport.pasted" | ||
640 | msgstr "WKLEJONO ZE SCHOWKA" | ||
641 | |||
642 | msgid "heading.certimport.dropped" | ||
643 | msgstr "UPUSZCZONO PLIK" | ||
644 | |||
645 | msgid "dlg.certimport.import" | ||
646 | msgstr "Importuj" | ||
647 | |||
648 | msgid "dlg.certimport.notes" | ||
649 | msgstr "Adnotacje:" | ||
650 | |||
651 | msgid "hint.certimport.description" | ||
652 | msgstr "opis" | ||
653 | |||
654 | msgid "dlg.certimport.nokey" | ||
655 | msgstr "Brak prywatnego klucza" | ||
656 | |||
657 | msgid "link.hint.audio" | ||
658 | msgstr "Odtwórz dźwięk" | ||
659 | |||
660 | msgid "link.hint.image" | ||
661 | msgstr "Wyświetl obraz" | ||
662 | |||
663 | msgid "bookmark.title.blank" | ||
664 | msgstr "Pusta strona" | ||
665 | |||
666 | # Interpret as "Results from bookmarks..." | ||
667 | msgid "heading.lookup.bookmarks" | ||
668 | msgstr "ZAKŁADKI" | ||
669 | |||
670 | # Interpret as "Results from feeds..." | ||
671 | msgid "heading.lookup.feeds" | ||
672 | msgstr "KANAŁY" | ||
673 | |||
674 | # Interpret as "Results from history..." | ||
675 | msgid "heading.lookup.history" | ||
676 | msgstr "HISTORIA" | ||
677 | |||
678 | # Interpret as "Results from page content..." | ||
679 | msgid "heading.lookup.pagecontent" | ||
680 | msgstr "ZAWARTOŚĆ STRONY" | ||
681 | |||
682 | msgid "heading.translate" | ||
683 | msgstr "TŁUMACZENIE STRONY" | ||
684 | |||
685 | msgid "dlg.translate.unavail" | ||
686 | msgstr "Usługa niedostępna" | ||
687 | |||
688 | msgid "dlg.translate" | ||
689 | msgstr "Tłumacz" | ||
690 | |||
691 | msgid "lang.ar" | ||
692 | msgstr "arabski" | ||
693 | |||
694 | msgid "lang.zh" | ||
695 | msgstr "chiński" | ||
696 | |||
697 | msgid "lang.zh.hans" | ||
698 | msgstr "chiński (uproszczony)" | ||
699 | |||
700 | msgid "dlg.translate.from" | ||
701 | msgstr "Język strony:" | ||
702 | |||
703 | msgid "dlg.translate.to" | ||
704 | msgstr "Język docelowy:" | ||
705 | |||
706 | msgid "lang.zh.hant" | ||
707 | msgstr "chiński (tradycyjny)" | ||
708 | |||
709 | msgid "lang.de" | ||
710 | msgstr "niemiecki" | ||
711 | |||
712 | msgid "lang.en" | ||
713 | msgstr "angielski" | ||
714 | |||
715 | msgid "lang.ia" | ||
716 | msgstr "interlingua" | ||
717 | |||
718 | msgid "lang.ie" | ||
719 | msgstr "interlingue" | ||
720 | |||
721 | msgid "lang.fi" | ||
722 | msgstr "fiński" | ||
723 | |||
724 | msgid "lang.fr" | ||
725 | msgstr "francuski" | ||
726 | |||
727 | msgid "lang.hi" | ||
728 | msgstr "hindi" | ||
729 | |||
730 | msgid "lang.it" | ||
731 | msgstr "włoski" | ||
732 | |||
733 | msgid "lang.ja" | ||
734 | msgstr "japoński" | ||
735 | |||
736 | msgid "lang.ru" | ||
737 | msgstr "rosyjski" | ||
738 | |||
739 | msgid "lang.es" | ||
740 | msgstr "hiszpański" | ||
741 | |||
742 | msgid "lang.sr" | ||
743 | msgstr "serbski" | ||
744 | |||
745 | msgid "lang.tok" | ||
746 | msgstr "toki pona" | ||
747 | |||
748 | msgid "heading.newident" | ||
749 | msgstr "NOWA TOŻSAMOŚĆ" | ||
750 | |||
751 | msgid "dlg.newident.until" | ||
752 | msgstr "Data ważności:" | ||
753 | |||
754 | msgid "hint.newident.date" | ||
755 | msgstr "RRRR-MM-DD GG:MM:SS" | ||
756 | |||
757 | msgid "dlg.newident.commonname" | ||
758 | msgstr "Nazwa zwyczajowa:" | ||
759 | |||
760 | msgid "dlg.newident.temp" | ||
761 | msgstr "Tymczasowo:" | ||
762 | |||
763 | msgid "dlg.newident.notsaved" | ||
764 | msgstr "nie zapisano na dysku" | ||
765 | |||
766 | msgid "hint.newident.optional" | ||
767 | msgstr "pole opcjonalne" | ||
768 | |||
769 | msgid "dlg.newident.userid" | ||
770 | msgstr "Identyfikator użytkownika:" | ||
771 | |||
772 | msgid "dlg.newident.domain" | ||
773 | msgstr "Domena:" | ||
774 | |||
775 | msgid "dlg.newident.org" | ||
776 | msgstr "Organizacja:" | ||
777 | |||
778 | msgid "dlg.newident.country" | ||
779 | msgstr "Kraj:" | ||
780 | |||
781 | msgid "heading.newident.missing" | ||
782 | msgstr "BRAKUJĄCE DANE" | ||
783 | |||
784 | msgid "dlg.newindent.missing.commonname" | ||
785 | msgstr "Podaj nazwę zwyczajową." | ||
786 | |||
787 | msgid "heading.newident.date.bad" | ||
788 | msgstr "NIEWŁAŚCIWA DATA" | ||
789 | |||
790 | msgid "dlg.newident.date.past" | ||
791 | msgstr "Data wygaśnięcia musi przypadać w przyszłości." | ||
792 | |||
793 | msgid "heading.feedcfg" | ||
794 | msgstr "USTAWIENIA KANAŁU" | ||
795 | |||
796 | msgid "heading.subscribe" | ||
797 | msgstr "SUBSKRYBOWANIE STRONY" | ||
798 | |||
799 | msgid "dlg.feed.title" | ||
800 | msgstr "Tytuł:" | ||
801 | |||
802 | msgid "dlg.feed.type.gemini" | ||
803 | msgstr "Linki RRRR-MM-DD" | ||
804 | |||
805 | msgid "dlg.feed.type.headings" | ||
806 | msgstr "Nowe nagłówki" | ||
807 | |||
808 | msgid "dlg.feed.save" | ||
809 | msgstr "Zapisz ustawienia" | ||
810 | |||
811 | msgid "dlg.feed.sub" | ||
812 | msgstr "Subskrybuj" | ||
813 | |||
814 | msgid "heading.bookmark.edit" | ||
815 | msgstr "EDYCJA ZAKŁADKI" | ||
816 | |||
817 | msgid "dlg.bookmark.save" | ||
818 | msgstr "Zapisz zakładkę" | ||
819 | |||
820 | msgid "dlg.bookmark.title" | ||
821 | msgstr "Tytuł:" | ||
822 | |||
823 | msgid "dlg.bookmark.url" | ||
824 | msgstr "Adres:" | ||
825 | |||
826 | msgid "dlg.bookmark.icon" | ||
827 | msgstr "Ikona:" | ||
828 | |||
829 | msgid "heading.prefs" | ||
830 | msgstr "OPCJE" | ||
831 | |||
832 | msgid "heading.prefs.certs" | ||
833 | msgstr "CERTYFIKATY" | ||
834 | |||
835 | msgid "heading.prefs.fonts" | ||
836 | msgstr "CZCIONKI" | ||
837 | |||
838 | # tab button | ||
839 | msgid "heading.prefs.interface" | ||
840 | msgstr "Interfejs" | ||
841 | |||
842 | # tab button | ||
843 | msgid "heading.prefs.keys" | ||
844 | msgstr "Klawisze" | ||
845 | |||
846 | # tab button | ||
847 | msgid "heading.prefs.network" | ||
848 | msgstr "Sieć" | ||
849 | |||
850 | msgid "heading.prefs.paragraph" | ||
851 | msgstr "PARAGRAF" | ||
852 | |||
853 | msgid "heading.prefs.pagecontent" | ||
854 | msgstr "ZAWARTOŚĆ STRON" | ||
855 | |||
856 | msgid "heading.prefs.proxies" | ||
857 | msgstr "PROXY" | ||
858 | |||
859 | msgid "heading.prefs.scrolling" | ||
860 | msgstr "PRZEWIJANIE" | ||
861 | |||
862 | msgid "heading.prefs.sizing" | ||
863 | msgstr "ROZMIAR" | ||
864 | |||
865 | msgid "heading.prefs.widelayout" | ||
866 | msgstr "SZEROKI UKŁAD" | ||
867 | |||
868 | # tab button | ||
869 | msgid "heading.prefs.style" | ||
870 | msgstr "Styl" | ||
871 | |||
872 | # tab button | ||
873 | msgid "heading.prefs.userinterface" | ||
874 | msgstr "Interfejs użytkownika" | ||
875 | |||
876 | msgid "prefs.downloads" | ||
877 | msgstr "Folder pobranych plików:" | ||
878 | |||
879 | msgid "prefs.hoverlink" | ||
880 | msgstr "Pokazuj adres po najechaniu kursorem:" | ||
881 | |||
882 | msgid "prefs.centershort" | ||
883 | msgstr "Wyrównanie w pionie:" | ||
884 | |||
885 | # User preference that controls whether index.gmi pages get automatically opened when browsing the contents of a directory inside a compressed archive. | ||
886 | msgid "prefs.archive.openindex" | ||
887 | msgstr "Otwieraj indeksy archiwum:" | ||
888 | |||
889 | msgid "prefs.pinsplit" | ||
890 | msgstr "Otwieraj linki w widoku dzielonym:" | ||
891 | |||
892 | msgid "prefs.pinsplit.none" | ||
893 | msgstr "W tym samym oknie" | ||
894 | |||
895 | msgid "prefs.pinsplit.left" | ||
896 | msgstr "w oknie z prawej" | ||
897 | |||
898 | msgid "prefs.pinsplit.right" | ||
899 | msgstr "w oknie z lewej" | ||
900 | |||
901 | msgid "prefs.smoothscroll" | ||
902 | msgstr "Gładkie przewijanie:" | ||
903 | |||
904 | msgid "prefs.hidetoolbarscroll" | ||
905 | msgstr "Ukryj pasek narzędzi przy przewijaniu:" | ||
906 | |||
907 | msgid "prefs.theme" | ||
908 | msgstr "Motyw:" | ||
909 | |||
910 | msgid "prefs.theme.black" | ||
911 | msgstr "Czarny" | ||
912 | |||
913 | msgid "prefs.theme.dark" | ||
914 | msgstr "Ciemny" | ||
915 | |||
916 | msgid "prefs.theme.light" | ||
917 | msgstr "Jasny" | ||
918 | |||
919 | msgid "prefs.theme.white" | ||
920 | msgstr "Biały" | ||
921 | |||
922 | msgid "prefs.uilang" | ||
923 | msgstr "Język:" | ||
924 | |||
925 | msgid "prefs.uiscale" | ||
926 | msgstr "Współczynnik skalowania interfejsu:" | ||
927 | |||
928 | msgid "prefs.customframe" | ||
929 | msgstr "Niestandardowa ramka okna:" | ||
930 | |||
931 | msgid "prefs.retainwindow" | ||
932 | msgstr "Utrzymuj położenie:" | ||
933 | |||
934 | msgid "prefs.sideicon" | ||
935 | msgstr "Ikona kapsułki:" | ||
936 | |||
937 | msgid "prefs.doctheme.light" | ||
938 | msgstr "Jasny motyw:" | ||
939 | |||
940 | msgid "prefs.doctheme.name.colorfuldark" | ||
941 | msgstr "Kolorowy ciemny" | ||
942 | |||
943 | msgid "prefs.doctheme.name.black" | ||
944 | msgstr "Czarny" | ||
945 | |||
946 | msgid "prefs.doctheme.name.gray" | ||
947 | msgstr "Szary" | ||
948 | |||
949 | msgid "prefs.doctheme.name.white" | ||
950 | msgstr "Biały" | ||
951 | |||
952 | msgid "prefs.doctheme.name.sepia" | ||
953 | msgstr "Sepia" | ||
954 | |||
955 | msgid "prefs.doctheme.name.highcontrast" | ||
956 | msgstr "Wysoki kontrast" | ||
957 | |||
958 | msgid "prefs.font" | ||
959 | msgstr "Czcionka treści:" | ||
960 | |||
961 | msgid "prefs.mono" | ||
962 | msgstr "Tekst o stałej szerokości:" | ||
963 | |||
964 | msgid "prefs.mono.gemini" | ||
965 | msgstr "Gemini" | ||
966 | |||
967 | msgid "prefs.mono.gopher" | ||
968 | msgstr "Gopher" | ||
969 | |||
970 | msgid "prefs.boldlink" | ||
971 | msgstr "Pogrubione linki:" | ||
972 | |||
973 | # Interpretation: (Bold links) on light (background). | ||
974 | msgid "prefs.boldlink.light" | ||
975 | msgstr "Na jasnym" | ||
976 | |||
977 | msgid "prefs.linewidth" | ||
978 | msgstr "Szerokość linii:" | ||
979 | |||
980 | msgid "prefs.linewidth.normal" | ||
981 | msgstr "Normalna" | ||
982 | |||
983 | msgid "prefs.linewidth.fill" | ||
984 | msgstr "Pełna" | ||
985 | |||
986 | msgid "prefs.quoteicon" | ||
987 | msgstr "Wskaźnik cytatu:" | ||
988 | |||
989 | msgid "prefs.quoteicon.icon" | ||
990 | msgstr "Ikona \"" | ||
991 | |||
992 | msgid "prefs.quoteicon.line" | ||
993 | msgstr "Linia" | ||
994 | |||
995 | msgid "prefs.biglede" | ||
996 | msgstr "Duży 1. paragraf:" | ||
997 | |||
998 | msgid "prefs.decodeurls" | ||
999 | msgstr "Dekoduj adresy URL:" | ||
1000 | |||
1001 | msgid "prefs.cachesize" | ||
1002 | msgstr "Rozmiar buforu:" | ||
1003 | |||
1004 | msgid "prefs.ca.file" | ||
1005 | msgstr "Plik CA:" | ||
1006 | |||
1007 | msgid "archive.exit" | ||
1008 | msgstr "Wyjdź z archiwum" | ||
1009 | |||
1010 | msgid "dir.empty" | ||
1011 | msgstr "Ten katalog jest pusty." | ||
1012 | |||
1013 | msgid "prefs.proxy.gemini" | ||
1014 | msgstr "Proxy Gemini:" | ||
1015 | |||
1016 | msgid "prefs.proxy.gopher" | ||
1017 | msgstr "Proxy Gopher:" | ||
1018 | |||
1019 | msgid "prefs.proxy.http" | ||
1020 | msgstr "Proxy HTTP:" | ||
1021 | |||
1022 | #, c-format | ||
1023 | msgid "dir.summary" | ||
1024 | msgid_plural "dir.summary.n" | ||
1025 | msgstr[0] "Ten katalog zawiera %zu element." | ||
1026 | msgstr[1] "Ten katalog zawiera %zu elementy." | ||
1027 | msgstr[2] "Ten katalog zawiera %zu elementów." | ||
1028 | |||
1029 | msgid "menu.binding.reset" | ||
1030 | msgstr "Przywróć domyślne" | ||
1031 | |||
1032 | msgid "keys.top" | ||
1033 | msgstr "Przejdź na szczyt" | ||
1034 | |||
1035 | msgid "keys.bottom" | ||
1036 | msgstr "Przejdź na spód" | ||
1037 | |||
1038 | msgid "keys.scroll.up" | ||
1039 | msgstr "Przewiń w górę" | ||
1040 | |||
1041 | msgid "keys.scroll.down" | ||
1042 | msgstr "Przewiń w dół" | ||
1043 | |||
1044 | msgid "keys.scroll.page.down" | ||
1045 | msgstr "Przewiń o całą stronę w dół" | ||
1046 | |||
1047 | msgid "keys.scroll.page.up" | ||
1048 | msgstr "Przewiń o całą stronę w górę" | ||
1049 | |||
1050 | msgid "keys.scroll.halfpage.up" | ||
1051 | msgstr "Przewiń o pół strony w górę" | ||
1052 | |||
1053 | msgid "keys.back" | ||
1054 | msgstr "Wstecz" | ||
1055 | |||
1056 | msgid "keys.forward" | ||
1057 | msgstr "Dalej" | ||
1058 | |||
1059 | msgid "keys.root" | ||
1060 | msgstr "Przejdź do korzenia kapsułki" | ||
1061 | |||
1062 | msgid "keys.reload" | ||
1063 | msgstr "Odśwież stronę" | ||
1064 | |||
1065 | msgid "keys.link.homerow" | ||
1066 | msgstr "Otwórz link za pomocą klawisza wiersza głównego" | ||
1067 | |||
1068 | msgid "keys.link.homerow.hover" | ||
1069 | msgstr "Najedź kursorem na link za pomocą klawisza wiersza głównego" | ||
1070 | |||
1071 | msgid "keys.link.homerow.next" | ||
1072 | msgstr "Następny zestaw linków klawiszy wiersza głównego" | ||
1073 | |||
1074 | msgid "keys.bookmark.add" | ||
1075 | msgstr "Dodaj zakładkę" | ||
1076 | |||
1077 | msgid "keys.findtext" | ||
1078 | msgstr "Znajdź tekst na stronie" | ||
1079 | |||
1080 | msgid "keys.zoom.in" | ||
1081 | msgstr "Powiększ" | ||
1082 | |||
1083 | msgid "keys.zoom.out" | ||
1084 | msgstr "Pomniejsz" | ||
1085 | |||
1086 | msgid "keys.zoom.reset" | ||
1087 | msgstr "Resetuj powiększenie" | ||
1088 | |||
1089 | msgid "keys.tab.new" | ||
1090 | msgstr "Nowa karta" | ||
1091 | |||
1092 | msgid "keys.tab.close" | ||
1093 | msgstr "Zamknij kartę" | ||
1094 | |||
1095 | msgid "error.badstatus" | ||
1096 | msgstr "Nieznany kod statusu" | ||
1097 | |||
1098 | msgid "keys.tab.close.other" | ||
1099 | msgstr "Zamknij resztę kart" | ||
1100 | |||
1101 | msgid "keys.tab.prev" | ||
1102 | msgstr "Poprzednia karta" | ||
1103 | |||
1104 | msgid "keys.tab.next" | ||
1105 | msgstr "Następna karta" | ||
1106 | |||
1107 | msgid "keys.split.menu" | ||
1108 | msgstr "Dostosuj tryb widoku dzielonego" | ||
1109 | |||
1110 | msgid "keys.split.next" | ||
1111 | msgstr "Skoncentruj się na następnym oknie" | ||
1112 | |||
1113 | msgid "keys.hoverurl" | ||
1114 | msgstr "Pokaż adres URL po najechaniu kursorem" | ||
1115 | |||
1116 | msgid "error.openfile" | ||
1117 | msgstr "Nie można otworzyć pliku" | ||
1118 | |||
1119 | msgid "error.openfile.msg" | ||
1120 | msgstr "Żądany plik nie istnieje bądź jest niedostępny. Sprawdź ścieżkę pliku." | ||
1121 | |||
1122 | msgid "error.unsupported.media" | ||
1123 | msgstr "Nieobsługiwany rodzaj treści" | ||
1124 | |||
1125 | msgid "error.badredirect" | ||
1126 | msgstr "Nieprawidłowe przekierowanie" | ||
1127 | |||
1128 | msgid "error.unsupported.media.msg" | ||
1129 | msgstr "Nie można wyświetlić otrzymanej treści za pomocą tej aplikacji." | ||
1130 | |||
1131 | msgid "error.unsupported.protocol" | ||
1132 | msgstr "Nieobsługiwany protokół" | ||
1133 | |||
1134 | msgid "error.unsupported.protocol.msg" | ||
1135 | msgstr "Żądany protokół jest nieobsługiwany przez tę aplikację." | ||
1136 | |||
1137 | msgid "error.manyredirects" | ||
1138 | msgstr "Zbyt wiele przekierowań" | ||
1139 | |||
1140 | msgid "error.schemeredirect" | ||
1141 | msgstr "Przekierowanie ze zmianą schematu" | ||
1142 | |||
1143 | msgid "error.tls" | ||
1144 | msgstr "Błąd sieci/TLS" | ||
1145 | |||
1146 | msgid "error.tls.msg" | ||
1147 | msgstr "Nie można nawiązać połączenia z serwerem. Oto treść błędu:" | ||
1148 | |||
1149 | msgid "error.temporary" | ||
1150 | msgstr "Tymczasowy błąd" | ||
1151 | |||
1152 | msgid "error.temporary.msg" | ||
1153 | msgstr "" | ||
1154 | "Żądanie nie powiodło się, ale może się powieść, jeśli spróbujesz ponownie w " | ||
1155 | "przyszłości." | ||
1156 | |||
1157 | msgid "error.unavail" | ||
1158 | msgstr "Serwer niedostępny" | ||
1159 | |||
1160 | msgid "error.proxy" | ||
1161 | msgstr "Błąd proxy" | ||
1162 | |||
1163 | msgid "error.slowdown" | ||
1164 | msgstr "Zwolnij" | ||
1165 | |||
1166 | msgid "error.slowdown.msg" | ||
1167 | msgstr "Serwer ogranicza liczbę żądań. Proszę czekać…" | ||
1168 | |||
1169 | msgid "error.gone" | ||
1170 | msgstr "Usunięto" | ||
1171 | |||
1172 | msgid "error.notfound" | ||
1173 | msgstr "Nie znaleziono" | ||
1174 | |||
1175 | msgid "error.permanent" | ||
1176 | msgstr "Błąd permanentny" | ||
1177 | |||
1178 | msgid "error.permanent.msg" | ||
1179 | msgstr "" | ||
1180 | "Twoje żądanie nie powiodło się i nie powiedzie się również w przyszłości, " | ||
1181 | "jeśli zostanie powtórzone." | ||
1182 | |||
1183 | msgid "error.proxyrefusal" | ||
1184 | msgstr "Żądanie odrzucone przez serwer proxy" | ||
1185 | |||
1186 | msgid "error.cert.auth" | ||
1187 | msgstr "Nieautoryzowany certyfikat" | ||
1188 | |||
1189 | msgid "error.cert.invalid" | ||
1190 | msgstr "Nieprawidłowy certyfikat" | ||
1191 | |||
1192 | msgid "error.cert.invalid.msg" | ||
1193 | msgstr "Podany certyfikat klienta stracił ważność lub jest nieprawidłowy." | ||
1194 | |||
1195 | msgid "error.cert.needed" | ||
1196 | msgstr "Wymagany certyfikat" | ||
1197 | |||
1198 | msgid "error.cert.needed.msg" | ||
1199 | msgstr "" | ||
1200 | "Dostęp do żądanego zasobu wymaga identyfikacji za pomocą certyfikatu klienta." | ||
1201 | |||
1202 | msgid "gempub.cover.untitled" | ||
1203 | msgstr "Książka bez tytułu" | ||
1204 | |||
1205 | msgid "gempub.cover.aboutbook" | ||
1206 | msgstr "O tej książce" | ||
1207 | |||
1208 | msgid "gempub.cover.view" | ||
1209 | msgstr "Wyświetl zawartość Gempub" | ||
1210 | |||
1211 | msgid "gempub.cover.image" | ||
1212 | msgstr "Grafika okładki" | ||
1213 | |||
1214 | msgid "gempub.meta.author" | ||
1215 | msgstr "Autor" | ||
1216 | |||
1217 | msgid "gempub.meta.version" | ||
1218 | msgstr "Wersja" | ||
1219 | |||
1220 | msgid "gempub.meta.pub" | ||
1221 | msgstr "Rok publikacji" | ||
1222 | |||
1223 | msgid "gempub.meta.pubdate" | ||
1224 | msgstr "Data publikacji" | ||
1225 | |||
1226 | msgid "gempub.meta.lang" | ||
1227 | msgstr "Język" | ||
1228 | |||
1229 | msgid "gempub.meta.license" | ||
1230 | msgstr "Licencja" | ||
1231 | |||
1232 | msgid "doc.pre.nocaption" | ||
1233 | msgstr "Wstępnie sformatowany tekst bez opisu" | ||
1234 | |||
1235 | msgid "doc.archive.view" | ||
1236 | msgstr "Wyświetl zawartość archiwum" | ||
1237 | |||
1238 | # Inline download status message. | ||
1239 | msgid "media.download.warnclose" | ||
1240 | msgstr "Pobieranie zostanie anulowane po zamknięciu tej karty." | ||
1241 | |||
1242 | # Used in inline audio player metadata popup. | ||
1243 | msgid "audio.meta.date" | ||
1244 | msgstr "Data" | ||
1245 | |||
1246 | #, c-format | ||
1247 | msgid "feeds.list.counts" | ||
1248 | msgid_plural "feeds.list.counts.n" | ||
1249 | msgstr[0] "Subskrybujesz %zu kanał zawierający %%s.\n" | ||
1250 | msgstr[1] "Subskrybujesz %zu kanały zawierające %%s.\n" | ||
1251 | msgstr[2] "Subskrybujesz %zu kanałów zawierających %%s.\n" | ||
1252 | |||
1253 | msgid "menu.title.file" | ||
1254 | msgstr "Plik" | ||
1255 | |||
1256 | msgid "menu.title.help" | ||
1257 | msgstr "Pomoc" | ||
1258 | |||
1259 | msgid "menu.closetab.other" | ||
1260 | msgstr "Zamknij resztę kart" | ||
1261 | |||
1262 | msgid "menu.split.vertical" | ||
1263 | msgstr "Pionowo" | ||
1264 | |||
1265 | # Used on iOS. "Files" refers to Apple's iOS app where you can pick an iCloud folder. | ||
1266 | msgid "menu.save.files" | ||
1267 | msgstr "Zapisz w Plikach" | ||
1268 | |||
1269 | msgid "menu.bookmarks.list" | ||
1270 | msgstr "Pokaż wszystkie zakładki" | ||
1271 | |||
1272 | msgid "menu.bookmarks.bytag" | ||
1273 | msgstr "Sortuj zakładki według tagu" | ||
1274 | |||
1275 | msgid "menu.bookmarks.bytime" | ||
1276 | msgstr "Sortuj zakładki według daty utworzenia" | ||
1277 | |||
1278 | msgid "menu.cut" | ||
1279 | msgstr "Wytnij" | ||
1280 | |||
1281 | # Used in the Edit menu on macOS. Note: could be replaced with menu.page.copyurl. | ||
1282 | msgid "menu.copy.pagelink" | ||
1283 | msgstr "Kopiuj link do strony" | ||
1284 | |||
1285 | # Used in the View menu on macOS. Shows sidebar and switches sidebar tab. | ||
1286 | msgid "menu.show.outline" | ||
1287 | msgstr "Pokaż konspekt strony" | ||
1288 | |||
1289 | msgid "menu.page.subscribe" | ||
1290 | msgstr "Zasubskrybuj stronę…" | ||
1291 | |||
1292 | msgid "menu.import.links" | ||
1293 | msgstr "Importuj wszystkie linki ze strony…" | ||
1294 | |||
1295 | msgid "menu.bookmarks.refresh" | ||
1296 | msgstr "Odśwież zewnętrzne zakładki" | ||
1297 | |||
1298 | msgid "menu.identity.notactive" | ||
1299 | msgstr "Brak aktywnych tożsamości" | ||
1300 | |||
1301 | msgid "sidebar.feeds" | ||
1302 | msgstr "Kanały" | ||
1303 | |||
1304 | msgid "sidebar.action.feeds.showall" | ||
1305 | msgstr "Wszystkie" | ||
1306 | |||
1307 | msgid "sidebar.action.ident.import" | ||
1308 | msgstr "Importuj…" | ||
1309 | |||
1310 | # Usage: "(count) Unread" in the sidebar tab title, referring to feed entries. | ||
1311 | msgid "sidebar.unread" | ||
1312 | msgid_plural "sidebar.unread.n" | ||
1313 | msgstr[0] "nieprzeczytany" | ||
1314 | msgstr[1] "nieprzeczytane" | ||
1315 | msgstr[2] "nieprzeczytanych" | ||
1316 | |||
1317 | msgid "feeds.entry.markunread" | ||
1318 | msgstr "Oznacz jako nieprzeczytane" | ||
1319 | |||
1320 | msgid "feeds.entry.openfeed" | ||
1321 | msgstr "Otwórz stronę kanału" | ||
1322 | |||
1323 | msgid "feeds.edit" | ||
1324 | msgstr "Edytuj kanał…" | ||
1325 | |||
1326 | msgid "menu.copyurl" | ||
1327 | msgstr "Kopiuj adres URL" | ||
1328 | |||
1329 | msgid "dlg.confirm.history.clear" | ||
1330 | msgstr "Czy na pewno chcesz wyczyścić historię wszystkich odwiedzonych witryn?" | ||
1331 | |||
1332 | #, c-format | ||
1333 | msgid "hours.ago" | ||
1334 | msgid_plural "hours.ago.n" | ||
1335 | msgstr[0] "godzinę temu" | ||
1336 | msgstr[1] "%d godziny temu" | ||
1337 | msgstr[2] "%d godzin temu" | ||
1338 | |||
1339 | #, c-format | ||
1340 | msgid "days.ago" | ||
1341 | msgid_plural "days.ago.n" | ||
1342 | msgstr[0] "dzień temu" | ||
1343 | msgstr[1] "%d dni temu" | ||
1344 | msgstr[2] "%d dni temu" | ||
1345 | |||
1346 | # Alt-text of the preformatted logo. | ||
1347 | msgid "about.logo" | ||
1348 | msgstr "ASCII-Art: wyraz „Lagrange” napisany dużą czcionką" | ||
1349 | |||
1350 | msgid "bookmark.untag.remote" | ||
1351 | msgstr "Usuń źródło zakładki" | ||
1352 | |||
1353 | msgid "ident.using" | ||
1354 | msgstr "Używana na tej stronie" | ||
1355 | |||
1356 | # strftime() formatted | ||
1357 | #, c-format | ||
1358 | msgid "ident.expiry" | ||
1359 | msgstr "Wygasa %d.%m.%Y" | ||
1360 | |||
1361 | #, c-format | ||
1362 | msgid "dlg.confirm.ident.delete" | ||
1363 | msgstr "" | ||
1364 | "Czy na pewno chcesz usunąć tożsamość\n" | ||
1365 | "%s%s%s\n" | ||
1366 | "wraz z jej certyfikatem i plikami kluczy prywatnych?" | ||
1367 | |||
1368 | msgid "sidebar.empty.idents" | ||
1369 | msgstr "Brak tożsamości" | ||
1370 | |||
1371 | # The %s format characters are used to highlight the word "Help" and must be used in the translation in same way as here. | ||
1372 | #, c-format | ||
1373 | msgid "ident.gotohelp" | ||
1374 | msgstr "" | ||
1375 | "Więcej informacji na temat certyfikatów klienta TLS można uzyskać w dziale " | ||
1376 | "%sPomoc%s." | ||
1377 | |||
1378 | msgid "pageinfo.cert.status" | ||
1379 | msgstr "Status certyfikatu:" | ||
1380 | |||
1381 | msgid "pageinfo.cert.ca.verified" | ||
1382 | msgstr "Zweryfikowany przez CA" | ||
1383 | |||
1384 | #, c-format | ||
1385 | msgid "dlg.import.found" | ||
1386 | msgid_plural "dlg.import.found.n" | ||
1387 | msgstr[0] "Znaleziono nowy link na tej stronie." | ||
1388 | msgstr[1] "Znaleziono %d nowe linki na tej stronie." | ||
1389 | msgstr[2] "Znaleziono %d nowych linków na tej stronie." | ||
1390 | |||
1391 | #, c-format | ||
1392 | msgid "num.minutes" | ||
1393 | msgid_plural "num.minutes.n" | ||
1394 | msgstr[0] "%d minuta" | ||
1395 | msgstr[1] "%d minuty" | ||
1396 | msgstr[2] "%d minut" | ||
1397 | |||
1398 | msgid "link.newtab.background" | ||
1399 | msgstr "Otwórz link w karcie w tle" | ||
1400 | |||
1401 | msgid "link.download" | ||
1402 | msgstr "Pobierz plik" | ||
1403 | |||
1404 | #, c-format | ||
1405 | msgid "dlg.certwarn.mayberenewed" | ||
1406 | msgid_plural "dlg.certwarn.mayberenewed.n" | ||
1407 | msgstr[0] "" | ||
1408 | "Otrzymany certyfikat mógł zostać niedawno odnowiony — dotyczy właściwej " | ||
1409 | "domeny i jeszcze nie wygasł. Aktualny zaufany certyfikat wygaśnie %s, w " | ||
1410 | "ciągu jednego dnia." | ||
1411 | msgstr[1] "" | ||
1412 | "Otrzymany certyfikat mógł zostać niedawno odnowiony — dotyczy właściwej " | ||
1413 | "domeny i jeszcze nie wygasł. Aktualny zaufany certyfikat wygaśnie %s, za %-d " | ||
1414 | "dni." | ||
1415 | msgstr[2] "" | ||
1416 | "Otrzymany certyfikat mógł zostać niedawno odnowiony — dotyczy właściwej " | ||
1417 | "domeny i jeszcze nie wygasł. Aktualny zaufany certyfikat wygaśnie %s, za %-d " | ||
1418 | "dni." | ||
1419 | |||
1420 | #, c-format | ||
1421 | msgid "dlg.certwarn.expired" | ||
1422 | msgstr "Otrzymany certyfikat wygasł %s." | ||
1423 | |||
1424 | msgid "dlg.certwarn.domain" | ||
1425 | msgstr "" | ||
1426 | "Otrzymany certyfikat dotyczy niewłaściwej domeny (%s). Problem może wynikać " | ||
1427 | "z konfiguracji serwera." | ||
1428 | |||
1429 | msgid "dlg.certimport.notfound" | ||
1430 | msgstr "Nie znaleziono certyfikatu lub klucza prywatnego." | ||
1431 | |||
1432 | msgid "dlg.certimport.notfound.page" | ||
1433 | msgstr "Nie znaleziono certyfikatu/klucza na obecnej stronie." | ||
1434 | |||
1435 | msgid "dlg.certimport.nocert" | ||
1436 | msgstr "Brak certyfikatu" | ||
1437 | |||
1438 | # Interpret as "Results from identitites..." | ||
1439 | msgid "heading.lookup.identities" | ||
1440 | msgstr "TOŻSAMOŚCI" | ||
1441 | |||
1442 | # Interpret as "Other results..." | ||
1443 | msgid "heading.lookup.other" | ||
1444 | msgstr "INNY" | ||
1445 | |||
1446 | msgid "dlg.translate.fail" | ||
1447 | msgstr "Żądanie nie powiodło się" | ||
1448 | |||
1449 | msgid "lang.pt" | ||
1450 | msgstr "portugalski" | ||
1451 | |||
1452 | msgid "dlg.newident.rsa.selfsign" | ||
1453 | msgstr "Tworzenie samodzielnie podpisanego 2048-bitowego certyfikatu RSA." | ||
1454 | |||
1455 | msgid "dlg.newident.email" | ||
1456 | msgstr "Adres e-mail:" | ||
1457 | |||
1458 | msgid "dlg.newident.create" | ||
1459 | msgstr "Utwórz tożsamość" | ||
1460 | |||
1461 | msgid "dlg.newident.date.example" | ||
1462 | msgstr "" | ||
1463 | "Sprawdź datę wygaśnięcia. Przykładowo:\n" | ||
1464 | "• 2030\n" | ||
1465 | "• 2025-06-30\n" | ||
1466 | "• 2021-12-31 23:59:59" | ||
1467 | |||
1468 | msgid "menu.binding.clear" | ||
1469 | msgstr "Usuń" | ||
1470 | |||
1471 | #, c-format | ||
1472 | msgid "archive.summary" | ||
1473 | msgid_plural "archive.summary.n" | ||
1474 | msgstr[0] "" | ||
1475 | "To archiwum zawiera %zu element, a jego rozmiar po kompresji wynosi %.1f MB." | ||
1476 | msgstr[1] "" | ||
1477 | "To archiwum zawiera %zu elementy, a jego rozmiar po kompresji wynosi %.1f MB." | ||
1478 | msgstr[2] "" | ||
1479 | "To archiwum zawiera %zu elementów, a jego rozmiar po kompresji wynosi %.1f " | ||
1480 | "MB." | ||
1481 | |||
1482 | msgid "dlg.feed.entrytype" | ||
1483 | msgstr "Rodzaj wpisu:" | ||
1484 | |||
1485 | msgid "heading.bookmark.add" | ||
1486 | msgstr "DODAWANIE ZAKŁADKI" | ||
1487 | |||
1488 | msgid "dlg.bookmark.tags" | ||
1489 | msgstr "Etykiety:" | ||
1490 | |||
1491 | # tab button | ||
1492 | msgid "heading.prefs.colors" | ||
1493 | msgstr "Kolorystyka" | ||
1494 | |||
1495 | # tab button | ||
1496 | msgid "heading.prefs.general" | ||
1497 | msgstr "Ogólne" | ||
1498 | |||
1499 | msgid "prefs.searchurl" | ||
1500 | msgstr "URL wyszukiwania:" | ||
1501 | |||
1502 | msgid "prefs.collapsepreonload" | ||
1503 | msgstr "Zwijaj wstępnie sformatowany tekst:" | ||
1504 | |||
1505 | msgid "prefs.imageloadscroll" | ||
1506 | msgstr "Ładuj obraz przy przewijaniu:" | ||
1507 | |||
1508 | msgid "prefs.ostheme" | ||
1509 | msgstr "Używaj motywu systemu:" | ||
1510 | |||
1511 | msgid "prefs.accent" | ||
1512 | msgstr "Kolor akcentu:" | ||
1513 | |||
1514 | msgid "prefs.accent.teal" | ||
1515 | msgstr "Turkusowy" | ||
1516 | |||
1517 | msgid "prefs.accent.orange" | ||
1518 | msgstr "Pomarańczowy" | ||
1519 | |||
1520 | msgid "prefs.plaintext.wrap" | ||
1521 | msgstr "Zawijaj zwykły tekst:" | ||
1522 | |||
1523 | msgid "prefs.saturation" | ||
1524 | msgstr "Nasycenie:" | ||
1525 | |||
1526 | msgid "error.badheader" | ||
1527 | msgstr "Niewłaściwy nagłówek" | ||
1528 | |||
1529 | msgid "error.badheader.msg" | ||
1530 | msgstr "" | ||
1531 | "Otrzymany nagłówek nie jest zgodny ze specyfikacją protokołu Gemini. Może to " | ||
1532 | "być wynikiem usterki serwera, bądź próby skontaktowania się z serwerem innym " | ||
1533 | "niż Gemini." | ||
1534 | |||
1535 | msgid "error.badredirect.msg" | ||
1536 | msgstr "" | ||
1537 | "Serwer odpowiedział przekierowaniem, ale nie podał poprawnego docelowego " | ||
1538 | "adresu URL. Może to być wynikiem usterki serwera." | ||
1539 | |||
1540 | msgid "error.badrequest" | ||
1541 | msgstr "Nieprawidłowe żądanie" | ||
1542 | |||
1543 | msgid "error.badrequest.msg" | ||
1544 | msgstr "Serwer nie zrozumiał Twojego żądania." | ||
1545 | |||
1546 | msgid "error.badresource" | ||
1547 | msgstr "Nieprawidłowy zasób" | ||
1548 | |||
1549 | msgid "error.badresource.msg" | ||
1550 | msgstr "Żądany zasób nie istnieje." | ||
1551 | |||
1552 | msgid "error.badstatus.msg" | ||
1553 | msgstr "" | ||
1554 | "Serwer odpowiedział kodem statusu, którego nie ma w specyfikacji protokołu " | ||
1555 | "Gemini. Może serwer pochodzi z przyszłości? Albo po prostu uległ awarii." | ||
1556 | |||
1557 | msgid "error.cert.auth.msg" | ||
1558 | msgstr "" | ||
1559 | "Podany certyfikat klienta jest ważny, ale nie ma uprawnień dostępu do " | ||
1560 | "żądanego zasobu." | ||
1561 | |||
1562 | msgid "error.cgi" | ||
1563 | msgstr "Błąd CGI" | ||
1564 | |||
1565 | msgid "error.cgi.msg" | ||
1566 | msgstr "" | ||
1567 | "Awaria podczas generowania dynamicznej zawartości na serwerze. Może to być " | ||
1568 | "spowodowane nieprawidłowym działaniem oprogramowania po stronie serwera." | ||
1569 | |||
1570 | msgid "error.gone.msg" | ||
1571 | msgstr "Żądany zasób nie jest już dostępny." | ||
1572 | |||
1573 | msgid "error.manyredirects.msg" | ||
1574 | msgstr "" | ||
1575 | "Być może znajdujesz się w pętli przekierowań. Następny przekierowany adres " | ||
1576 | "URL znajduje się poniżej, jeśli chcesz kontynuować ręcznie." | ||
1577 | |||
1578 | msgid "error.notfound.msg" | ||
1579 | msgstr "Żądany zasób nie może być znaleziony w tym czasie." | ||
1580 | |||
1581 | msgid "prefs.doctheme.dark" | ||
1582 | msgstr "Ciemny motyw:" | ||
1583 | |||
1584 | msgid "prefs.doctheme.name.colorfullight" | ||
1585 | msgstr "Kolorowy jasny" | ||
1586 | |||
1587 | msgid "prefs.headingfont" | ||
1588 | msgstr "Czcionka nagłówka:" | ||
1589 | |||
1590 | # Interpretation: (Bold links) on dark (background). | ||
1591 | msgid "prefs.boldlink.dark" | ||
1592 | msgstr "Na ciemnym" | ||
1593 | |||
1594 | msgid "prefs.ca.path" | ||
1595 | msgstr "Ścieżka CA:" | ||
1596 | |||
1597 | msgid "keys.scroll.halfpage.down" | ||
1598 | msgstr "Przewiń o pół strony w dół" | ||
1599 | |||
1600 | msgid "keys.parent" | ||
1601 | msgstr "Przejdź do katalogu nadrzędnego" | ||
1602 | |||
1603 | msgid "keys.link.modkey" | ||
1604 | msgstr "Otwórz link za pomocą klucza modyfikującego" | ||
1605 | |||
1606 | msgid "keys.link.homerow.newtab" | ||
1607 | msgstr "Otwórz link w nowej karcie za pomocą klawisza wiersza głównego" | ||
1608 | |||
1609 | msgid "keys.subscribe" | ||
1610 | msgstr "Subskrybuj stronę" | ||
1611 | |||
1612 | msgid "keys.fullscreen" | ||
1613 | msgstr "Przełącz tryb pełnoekranowy" | ||
1614 | |||
1615 | msgid "error.schemeredirect.msg" | ||
1616 | msgstr "" | ||
1617 | "Serwer próbował przekierować nas na adres URL o innym schemacie niż " | ||
1618 | "pierwotny adres URL. Oto link, za pomocą którego możesz go ręcznie otworzyć." | ||
1619 | |||
1620 | msgid "error.unavail.msg" | ||
1621 | msgstr "" | ||
1622 | "Serwer jest niedostępny z powodu przeciążenia lub konserwacji. Sprawdź " | ||
1623 | "ponownie później." | ||
1624 | |||
1625 | msgid "error.proxy.msg" | ||
1626 | msgstr "" | ||
1627 | "Żądanie proxy nie powiodło się, ponieważ serwer nie był w stanie pomyślnie " | ||
1628 | "zakończyć transakcji ze zdalnym hostem. Mogły wystąpić problemy z łącznością " | ||
1629 | "sieciową." | ||
@@ -1,7 +1,7 @@ | |||
1 | msgid "" | 1 | msgid "" |
2 | msgstr "" | 2 | msgstr "" |
3 | "Report-Msgid-Bugs-To: jaakko.keranen@iki.fi\n" | 3 | "Report-Msgid-Bugs-To: jaakko.keranen@iki.fi\n" |
4 | "PO-Revision-Date: 2021-05-06 09:39+0000\n" | 4 | "PO-Revision-Date: 2021-05-10 06:26+0000\n" |
5 | "Last-Translator: jan Anja <cyber@sysrq.in>\n" | 5 | "Last-Translator: jan Anja <cyber@sysrq.in>\n" |
6 | "Language-Team: Toki Pona <http://weblate.skyjake.fi/projects/lagrange/ui/tok/" | 6 | "Language-Team: Toki Pona <http://weblate.skyjake.fi/projects/lagrange/ui/tok/" |
7 | ">\n" | 7 | ">\n" |
@@ -119,7 +119,7 @@ msgid "menu.closetab.other" | |||
119 | msgstr "o pini e poki ante" | 119 | msgstr "o pini e poki ante" |
120 | 120 | ||
121 | msgid "menu.closetab.right" | 121 | msgid "menu.closetab.right" |
122 | msgstr "o pini e poki sinpin" | 122 | msgstr "o pini e poki lon sinpin" |
123 | 123 | ||
124 | msgid "menu.duptab" | 124 | msgid "menu.duptab" |
125 | msgstr "o pali e poki sama" | 125 | msgstr "o pali e poki sama" |
@@ -147,7 +147,7 @@ msgid "menu.sidebar.right" | |||
147 | msgstr "o len ala len e poki poka tu?" | 147 | msgstr "o len ala len e poki poka tu?" |
148 | 148 | ||
149 | msgid "menu.zoom.in" | 149 | msgid "menu.zoom.in" |
150 | msgstr "o mute" | 150 | msgstr "o suli" |
151 | 151 | ||
152 | msgid "menu.zoom.out" | 152 | msgid "menu.zoom.out" |
153 | msgstr "o lili" | 153 | msgstr "o lili" |
@@ -789,7 +789,7 @@ msgid "prefs.searchurl" | |||
789 | msgstr "nimi tawa tan lukin e nimi:" | 789 | msgstr "nimi tawa tan lukin e nimi:" |
790 | 790 | ||
791 | msgid "prefs.hoverlink" | 791 | msgid "prefs.hoverlink" |
792 | msgstr "tawa la sina lukin e nimi tawa:" | 792 | msgstr "tawa la o lukin e nimi tawa:" |
793 | 793 | ||
794 | msgid "prefs.collapsepreonload" | 794 | msgid "prefs.collapsepreonload" |
795 | msgstr "o len e sitelen namako:" | 795 | msgstr "o len e sitelen namako:" |
@@ -798,7 +798,7 @@ msgid "prefs.smoothscroll" | |||
798 | msgstr "tawa suwi:" | 798 | msgstr "tawa suwi:" |
799 | 799 | ||
800 | msgid "prefs.imageloadscroll" | 800 | msgid "prefs.imageloadscroll" |
801 | msgstr "tawa la sina lukin e sitelen:" | 801 | msgstr "tawa la o lukin e sitelen:" |
802 | 802 | ||
803 | msgid "prefs.hidetoolbarscroll" | 803 | msgid "prefs.hidetoolbarscroll" |
804 | msgstr "tawa la mi len e ijo sewi:" | 804 | msgstr "tawa la mi len e ijo sewi:" |
@@ -1186,7 +1186,7 @@ msgid "prefs.linewidth.normal" | |||
1186 | msgstr "nasa ala" | 1186 | msgstr "nasa ala" |
1187 | 1187 | ||
1188 | msgid "prefs.decodeurls" | 1188 | msgid "prefs.decodeurls" |
1189 | msgstr "sina lukin pona e nimi tawa:" | 1189 | msgstr "o pona lukin e nimi tawa:" |
1190 | 1190 | ||
1191 | msgid "prefs.ca.file" | 1191 | msgid "prefs.ca.file" |
1192 | msgstr "ijo tawa ilo CA:" | 1192 | msgstr "ijo tawa ilo CA:" |
@@ -1211,7 +1211,7 @@ msgid "dlg.message.ok" | |||
1211 | msgstr "o awen pali" | 1211 | msgstr "o awen pali" |
1212 | 1212 | ||
1213 | msgid "menu.closetab.left" | 1213 | msgid "menu.closetab.left" |
1214 | msgstr "o pini e poki monsi" | 1214 | msgstr "o pini e poki lon monsi" |
1215 | 1215 | ||
1216 | msgid "macos.menu.find" | 1216 | msgid "macos.menu.find" |
1217 | msgstr "o lukin e nimi" | 1217 | msgstr "o lukin e nimi" |
@@ -1444,7 +1444,7 @@ msgid "menu.view.split" | |||
1444 | msgstr "o tu e poki..." | 1444 | msgstr "o tu e poki..." |
1445 | 1445 | ||
1446 | msgid "menu.split.vertical" | 1446 | msgid "menu.split.vertical" |
1447 | msgstr "poka" | 1447 | msgstr "sewi en anpa" |
1448 | 1448 | ||
1449 | msgid "menu.split.merge" | 1449 | msgid "menu.split.merge" |
1450 | msgstr "o wan e poki" | 1450 | msgstr "o wan e poki" |
@@ -1465,7 +1465,7 @@ msgid "gempub.cover.viewlocal" | |||
1465 | msgstr "sina awen e lipu tawa ilo sona la sina ken lukin e lipu Kempu." | 1465 | msgstr "sina awen e lipu tawa ilo sona la sina ken lukin e lipu Kempu." |
1466 | 1466 | ||
1467 | msgid "menu.split.horizontal" | 1467 | msgid "menu.split.horizontal" |
1468 | msgstr "sewi en anpa" | 1468 | msgstr "poka" |
1469 | 1469 | ||
1470 | msgid "dlg.newident.date.example" | 1470 | msgid "dlg.newident.date.example" |
1471 | msgstr "" | 1471 | msgstr "" |
diff --git a/res/about/version.gmi b/res/about/version.gmi index e261cb25..23eeebef 100644 --- a/res/about/version.gmi +++ b/res/about/version.gmi | |||
@@ -7,10 +7,11 @@ | |||
7 | # Release notes | 7 | # Release notes |
8 | 8 | ||
9 | ## 1.4.1 | 9 | ## 1.4.1 |
10 | * Fixed removing the left side split by closing all its tabs. The URL input field got confused about which tab was currently open and the wrong theme was active. | 10 | * Fixed removing the left side split by closing all its tabs. The URL input field got confused about which tab was currently open, and the wrong theme was active. |
11 | * Fixed tab merging when unsplitting the window: keep the currently active tab open. | 11 | * Fixed tab merging when unsplitting the window: keep the currently active tab open. |
12 | * Fixed issue with sidebars sometimes becoming unresponsive. | ||
12 | * Fixed font used for visited monospace Gopher links. | 13 | * Fixed font used for visited monospace Gopher links. |
13 | * Fixed incorrectly shown pinning indicator (◧) for the current tab. | 14 | * Fixed incorrectly shown/hidden ◧ indicator. |
14 | * Fixed scrollbar in Preferences > Keys being hidden until the list is scrolled. | 15 | * Fixed scrollbar in Preferences > Keys being hidden until the list is scrolled. |
15 | 16 | ||
16 | ## 1.4 | 17 | ## 1.4 |
diff --git a/res/fi.skyjake.Lagrange.appdata.xml b/res/fi.skyjake.Lagrange.appdata.xml index 308a2164..e3b75771 100644 --- a/res/fi.skyjake.Lagrange.appdata.xml +++ b/res/fi.skyjake.Lagrange.appdata.xml | |||
@@ -45,6 +45,24 @@ | |||
45 | <update_contact>jaakko.keranen@iki.fi</update_contact> | 45 | <update_contact>jaakko.keranen@iki.fi</update_contact> |
46 | 46 | ||
47 | <releases> | 47 | <releases> |
48 | <release version="1.4.1" date="2021-05-13"> | ||
49 | <description> | ||
50 | <p>Bug fixes:</p> | ||
51 | <ul> | ||
52 | <li>Removing the left side split by closing all its tabs. The URL | ||
53 | input field got confused about which tab was currently open, and | ||
54 | the wrong theme was active.</li> | ||
55 | <li>Tab merging when unsplitting the window: keep the currently | ||
56 | active tab open.</li> | ||
57 | <li>Sidebars sometimes become unresponsive.</li> | ||
58 | <li>Incorrect font was used for visited monospace Gopher links.</li> | ||
59 | <li>Incorrectly shown/hidden tab pinning indicator.</li> | ||
60 | <li>Scrollbar in Preferences > Keys was hidden until the list | ||
61 | was scrolled.</li> | ||
62 | </ul> | ||
63 | </description> | ||
64 | <url>https://github.com/skyjake/lagrange/releases/tag/v1.4.1</url> | ||
65 | </release> | ||
48 | <release version="1.4" date="2021-05-07"> | 66 | <release version="1.4" date="2021-05-07"> |
49 | <description> | 67 | <description> |
50 | <p>This release introduces a split view mode, support for Gempub | 68 | <p>This release introduces a split view mode, support for Gempub |
diff --git a/res/lang/de.bin b/res/lang/de.bin index 61769bdb..45c01abd 100644 --- a/res/lang/de.bin +++ b/res/lang/de.bin | |||
Binary files differ | |||
diff --git a/res/lang/en.bin b/res/lang/en.bin index f6ccec4b..0a9a3c0f 100644 --- a/res/lang/en.bin +++ b/res/lang/en.bin | |||
Binary files differ | |||
diff --git a/res/lang/es.bin b/res/lang/es.bin index 5842d4c4..0d3e80c7 100644 --- a/res/lang/es.bin +++ b/res/lang/es.bin | |||
Binary files differ | |||
diff --git a/res/lang/fi.bin b/res/lang/fi.bin index 6fada58c..00039ebd 100644 --- a/res/lang/fi.bin +++ b/res/lang/fi.bin | |||
Binary files differ | |||
diff --git a/res/lang/fr.bin b/res/lang/fr.bin index d4734f50..139f8e51 100644 --- a/res/lang/fr.bin +++ b/res/lang/fr.bin | |||
Binary files differ | |||
diff --git a/res/lang/ia.bin b/res/lang/ia.bin index 3f1f65e4..e7607899 100644 --- a/res/lang/ia.bin +++ b/res/lang/ia.bin | |||
Binary files differ | |||
diff --git a/res/lang/ie.bin b/res/lang/ie.bin index 4741ff24..79b517df 100644 --- a/res/lang/ie.bin +++ b/res/lang/ie.bin | |||
Binary files differ | |||
diff --git a/res/lang/pl.bin b/res/lang/pl.bin new file mode 100644 index 00000000..e208f3ba --- /dev/null +++ b/res/lang/pl.bin | |||
Binary files differ | |||
diff --git a/res/lang/ru.bin b/res/lang/ru.bin index f8676f61..4e775c94 100644 --- a/res/lang/ru.bin +++ b/res/lang/ru.bin | |||
Binary files differ | |||
diff --git a/res/lang/sr.bin b/res/lang/sr.bin index 13159229..19408d1a 100644 --- a/res/lang/sr.bin +++ b/res/lang/sr.bin | |||
Binary files differ | |||
diff --git a/res/lang/tok.bin b/res/lang/tok.bin index 76304d49..2d8017cb 100644 --- a/res/lang/tok.bin +++ b/res/lang/tok.bin | |||
Binary files differ | |||
diff --git a/res/lang/zh_Hans.bin b/res/lang/zh_Hans.bin index 75245b7b..171bd2a8 100644 --- a/res/lang/zh_Hans.bin +++ b/res/lang/zh_Hans.bin | |||
Binary files differ | |||
diff --git a/res/lang/zh_Hant.bin b/res/lang/zh_Hant.bin index 8a48e087..6518c7bf 100644 --- a/res/lang/zh_Hant.bin +++ b/res/lang/zh_Hant.bin | |||
Binary files differ | |||
@@ -146,6 +146,7 @@ iDeclareType(Ticker) | |||
146 | 146 | ||
147 | struct Impl_Ticker { | 147 | struct Impl_Ticker { |
148 | iAny *context; | 148 | iAny *context; |
149 | iRoot *root; | ||
149 | void (*callback)(iAny *); | 150 | void (*callback)(iAny *); |
150 | }; | 151 | }; |
151 | 152 | ||
@@ -411,15 +412,17 @@ static iBool loadState_App_(iApp *d) { | |||
411 | const uint8_t rootIndex = bits & 0xff; | 412 | const uint8_t rootIndex = bits & 0xff; |
412 | const uint8_t flags = bits >> 8; | 413 | const uint8_t flags = bits >> 8; |
413 | iRoot *root = d->window->roots[rootIndex]; | 414 | iRoot *root = d->window->roots[rootIndex]; |
414 | if (root && deviceType_App() != phone_AppDeviceType) { | 415 | if (root) { |
415 | iSidebarWidget *sidebar = findChild_Widget(root->widget, "sidebar"); | 416 | iSidebarWidget *sidebar = findChild_Widget(root->widget, "sidebar"); |
416 | iSidebarWidget *sidebar2 = findChild_Widget(root->widget, "sidebar2"); | 417 | iSidebarWidget *sidebar2 = findChild_Widget(root->widget, "sidebar2"); |
417 | setWidth_SidebarWidget(sidebar, widths[0]); | ||
418 | setWidth_SidebarWidget(sidebar2, widths[1]); | ||
419 | postCommandf_Root(root, "sidebar.mode arg:%u", modes & 0xf); | 418 | postCommandf_Root(root, "sidebar.mode arg:%u", modes & 0xf); |
420 | postCommandf_Root(root, "sidebar2.mode arg:%u", modes >> 4); | 419 | postCommandf_Root(root, "sidebar2.mode arg:%u", modes >> 4); |
421 | if (flags & 1) postCommand_Root(root, "sidebar.toggle"); | 420 | if (deviceType_App() != phone_AppDeviceType) { |
422 | if (flags & 2) postCommand_Root(root, "sidebar2.toggle"); | 421 | setWidth_SidebarWidget(sidebar, widths[0]); |
422 | setWidth_SidebarWidget(sidebar2, widths[1]); | ||
423 | if (flags & 1) postCommand_Root(root, "sidebar.toggle noanim:1"); | ||
424 | if (flags & 2) postCommand_Root(root, "sidebar2.toggle noanim:1"); | ||
425 | } | ||
423 | } | 426 | } |
424 | } | 427 | } |
425 | else if (!memcmp(magic, magicTabDocument_App_, 4)) { | 428 | else if (!memcmp(magic, magicTabDocument_App_, 4)) { |
@@ -776,6 +779,10 @@ static void init_App_(iApp *d, int argc, char **argv) { | |||
776 | iRelease(openCmds); | 779 | iRelease(openCmds); |
777 | } | 780 | } |
778 | fetchRemote_Bookmarks(d->bookmarks); | 781 | fetchRemote_Bookmarks(d->bookmarks); |
782 | if (deviceType_App() != desktop_AppDeviceType) { | ||
783 | /* HACK: Force a resize so widgets update their state. */ | ||
784 | resize_Window(d->window, -1, -1); | ||
785 | } | ||
779 | } | 786 | } |
780 | 787 | ||
781 | static void deinit_App(iApp *d) { | 788 | static void deinit_App(iApp *d) { |
@@ -1061,7 +1068,14 @@ void processEvents_App(enum iAppEventMode eventMode) { | |||
1061 | } | 1068 | } |
1062 | continue; | 1069 | continue; |
1063 | } | 1070 | } |
1064 | else if (ev.type == SDL_USEREVENT && ev.user.code == arrange_UserEventCode) { | 1071 | d->lastEventTime = SDL_GetTicks(); |
1072 | if (d->isIdling) { | ||
1073 | // printf("[App] ...woke up\n"); | ||
1074 | // fflush(stdout); | ||
1075 | } | ||
1076 | d->isIdling = iFalse; | ||
1077 | #endif | ||
1078 | if (ev.type == SDL_USEREVENT && ev.user.code == arrange_UserEventCode) { | ||
1065 | printf("[App] rearrange\n"); | 1079 | printf("[App] rearrange\n"); |
1066 | resize_Window(d->window, -1, -1); | 1080 | resize_Window(d->window, -1, -1); |
1067 | iForIndices(i, d->window->roots) { | 1081 | iForIndices(i, d->window->roots) { |
@@ -1078,13 +1092,6 @@ void processEvents_App(enum iAppEventMode eventMode) { | |||
1078 | // postRefresh_App(); | 1092 | // postRefresh_App(); |
1079 | continue; | 1093 | continue; |
1080 | } | 1094 | } |
1081 | d->lastEventTime = SDL_GetTicks(); | ||
1082 | if (d->isIdling) { | ||
1083 | // printf("[App] ...woke up\n"); | ||
1084 | // fflush(stdout); | ||
1085 | } | ||
1086 | d->isIdling = iFalse; | ||
1087 | #endif | ||
1088 | gotEvents = iTrue; | 1095 | gotEvents = iTrue; |
1089 | /* Keyboard modifier mapping. */ | 1096 | /* Keyboard modifier mapping. */ |
1090 | if (ev.type == SDL_KEYDOWN || ev.type == SDL_KEYUP) { | 1097 | if (ev.type == SDL_KEYDOWN || ev.type == SDL_KEYUP) { |
@@ -1187,7 +1194,7 @@ static void runTickers_App_(iApp *d) { | |||
1187 | iConstForEach(Array, i, &pending->values) { | 1194 | iConstForEach(Array, i, &pending->values) { |
1188 | const iTicker *ticker = i.value; | 1195 | const iTicker *ticker = i.value; |
1189 | if (ticker->callback) { | 1196 | if (ticker->callback) { |
1190 | setCurrent_Root(findRoot_Window(d->window, ticker->context)); /* root might be NULL */ | 1197 | setCurrent_Root(ticker->root); /* root might be NULL */ |
1191 | ticker->callback(ticker->context); | 1198 | ticker->callback(ticker->context); |
1192 | } | 1199 | } |
1193 | } | 1200 | } |
@@ -1418,13 +1425,19 @@ iAny *findWidget_App(const char *id) { | |||
1418 | 1425 | ||
1419 | void addTicker_App(iTickerFunc ticker, iAny *context) { | 1426 | void addTicker_App(iTickerFunc ticker, iAny *context) { |
1420 | iApp *d = &app_; | 1427 | iApp *d = &app_; |
1421 | insert_SortedArray(&d->tickers, &(iTicker){ context, ticker }); | 1428 | insert_SortedArray(&d->tickers, &(iTicker){ context, get_Root(), ticker }); |
1429 | postRefresh_App(); | ||
1430 | } | ||
1431 | |||
1432 | void addTickerRoot_App(iTickerFunc ticker, iRoot *root, iAny *context) { | ||
1433 | iApp *d = &app_; | ||
1434 | insert_SortedArray(&d->tickers, &(iTicker){ context, root, ticker }); | ||
1422 | postRefresh_App(); | 1435 | postRefresh_App(); |
1423 | } | 1436 | } |
1424 | 1437 | ||
1425 | void removeTicker_App(iTickerFunc ticker, iAny *context) { | 1438 | void removeTicker_App(iTickerFunc ticker, iAny *context) { |
1426 | iApp *d = &app_; | 1439 | iApp *d = &app_; |
1427 | remove_SortedArray(&d->tickers, &(iTicker){ context, ticker }); | 1440 | remove_SortedArray(&d->tickers, &(iTicker){ context, NULL, ticker }); |
1428 | } | 1441 | } |
1429 | 1442 | ||
1430 | iMimeHooks *mimeHooks_App(void) { | 1443 | iMimeHooks *mimeHooks_App(void) { |
@@ -1507,6 +1520,7 @@ static void updateFontButton_(iLabelWidget *button, int font) { | |||
1507 | 1520 | ||
1508 | static iBool handlePrefsCommands_(iWidget *d, const char *cmd) { | 1521 | static iBool handlePrefsCommands_(iWidget *d, const char *cmd) { |
1509 | if (equal_Command(cmd, "prefs.dismiss") || equal_Command(cmd, "preferences")) { | 1522 | if (equal_Command(cmd, "prefs.dismiss") || equal_Command(cmd, "preferences")) { |
1523 | setupSheetTransition_Mobile(d, iFalse); | ||
1510 | setUiScale_Window(get_Window(), | 1524 | setUiScale_Window(get_Window(), |
1511 | toFloat_String(text_InputWidget(findChild_Widget(d, "prefs.uiscale")))); | 1525 | toFloat_String(text_InputWidget(findChild_Widget(d, "prefs.uiscale")))); |
1512 | #if defined (LAGRANGE_ENABLE_DOWNLOAD_EDIT) | 1526 | #if defined (LAGRANGE_ENABLE_DOWNLOAD_EDIT) |
@@ -2381,7 +2395,7 @@ iBool handleCommand_App(const char *cmd) { | |||
2381 | iCertImportWidget *imp = new_CertImportWidget(); | 2395 | iCertImportWidget *imp = new_CertImportWidget(); |
2382 | setPageContent_CertImportWidget(imp, sourceContent_DocumentWidget(document_App())); | 2396 | setPageContent_CertImportWidget(imp, sourceContent_DocumentWidget(document_App())); |
2383 | addChild_Widget(get_Root()->widget, iClob(imp)); | 2397 | addChild_Widget(get_Root()->widget, iClob(imp)); |
2384 | finalizeSheet_Widget(as_Widget(imp)); | 2398 | finalizeSheet_Mobile(as_Widget(imp)); |
2385 | postRefresh_App(); | 2399 | postRefresh_App(); |
2386 | return iTrue; | 2400 | return iTrue; |
2387 | } | 2401 | } |
@@ -110,6 +110,7 @@ typedef void (*iTickerFunc)(iAny *); | |||
110 | 110 | ||
111 | iAny * findWidget_App (const char *id); | 111 | iAny * findWidget_App (const char *id); |
112 | void addTicker_App (iTickerFunc ticker, iAny *context); | 112 | void addTicker_App (iTickerFunc ticker, iAny *context); |
113 | void addTickerRoot_App (iTickerFunc ticker, iRoot *root, iAny *context); | ||
113 | void removeTicker_App (iTickerFunc ticker, iAny *context); | 114 | void removeTicker_App (iTickerFunc ticker, iAny *context); |
114 | void postRefresh_App (void); | 115 | void postRefresh_App (void); |
115 | void postImmediateRefresh_App(void); | 116 | void postImmediateRefresh_App(void); |
@@ -66,6 +66,7 @@ enum iFileVersion { | |||
66 | #define ballotCheck_Icon "\U0001f5f9" | 66 | #define ballotCheck_Icon "\U0001f5f9" |
67 | #define inbox_Icon "\U0001f4e5" | 67 | #define inbox_Icon "\U0001f4e5" |
68 | #define book_Icon "\U0001f56e" | 68 | #define book_Icon "\U0001f56e" |
69 | #define bookmark_Icon "\U0001f516" | ||
69 | #define folder_Icon "\U0001f4c1" | 70 | #define folder_Icon "\U0001f4c1" |
70 | #define openTab_Icon "\u2750" | 71 | #define openTab_Icon "\u2750" |
71 | #define openTabBg_Icon "\u2b1a" | 72 | #define openTabBg_Icon "\u2b1a" |
@@ -90,6 +91,16 @@ enum iFileVersion { | |||
90 | #define globe_Icon "\U0001f310" | 91 | #define globe_Icon "\U0001f310" |
91 | #define magnifyingGlass_Icon "\U0001f50d" | 92 | #define magnifyingGlass_Icon "\U0001f50d" |
92 | #define midEllipsis_Icon "\u00b7\u00b7\u00b7" | 93 | #define midEllipsis_Icon "\u00b7\u00b7\u00b7" |
94 | #define return_Icon "\u21a9" | ||
95 | |||
96 | #if defined (iPlatformApple) | ||
97 | # define shift_Icon "\u21e7" | ||
98 | # define shiftReturn_Icon shift_Icon return_Icon | ||
99 | #else | ||
100 | # define shift_Icon "Shift" | ||
101 | # define shiftReturn_Icon shift_Icon " " return_Icon | ||
102 | #endif | ||
103 | |||
93 | 104 | ||
94 | /* UI labels that depend on the platform */ | 105 | /* UI labels that depend on the platform */ |
95 | 106 | ||
diff --git a/src/gmdocument.c b/src/gmdocument.c index 3daa4714..67adb9cc 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c | |||
@@ -716,12 +716,25 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
716 | meta->flags |= topLeft_GmPreMetaFlag; | 716 | meta->flags |= topLeft_GmPreMetaFlag; |
717 | } | 717 | } |
718 | } | 718 | } |
719 | /* Visited links are never bold. */ | 719 | float lineHeightReduction = 0.0f; |
720 | if (!isMono && run.linkId && linkFlags_GmDocument(d, run.linkId) & visited_GmLinkFlag) { | 720 | if (!isMono) { |
721 | run.font = paragraph_FontId; | 721 | /* Upper-level headings are typeset a bit tighter. */ |
722 | if (type == heading1_GmLineType) { | ||
723 | lineHeightReduction = 0.10f; | ||
724 | } | ||
725 | else if (type == heading2_GmLineType) { | ||
726 | lineHeightReduction = 0.05f; | ||
727 | } | ||
728 | /* Visited links are never bold. */ | ||
729 | if (run.linkId && linkFlags_GmDocument(d, run.linkId) & visited_GmLinkFlag) { | ||
730 | run.font = paragraph_FontId; | ||
731 | } | ||
722 | } | 732 | } |
723 | iAssert(!isEmpty_Range(&runLine)); /* must have something at this point */ | 733 | iAssert(!isEmpty_Range(&runLine)); /* must have something at this point */ |
724 | while (!isEmpty_Range(&runLine)) { | 734 | while (!isEmpty_Range(&runLine)) { |
735 | if (~run.flags & startOfLine_GmRunFlag && lineHeightReduction > 0.0f) { | ||
736 | pos.y -= lineHeightReduction * lineHeight_Text(run.font); | ||
737 | } | ||
725 | run.bounds.pos = addX_I2(pos, indent * gap_Text); | 738 | run.bounds.pos = addX_I2(pos, indent * gap_Text); |
726 | const int wrapAvail = d->size.x - run.bounds.pos.x - rightMargin * gap_Text; | 739 | const int wrapAvail = d->size.x - run.bounds.pos.x - rightMargin * gap_Text; |
727 | const int avail = isWordWrapped ? wrapAvail : 0; | 740 | const int avail = isWordWrapped ? wrapAvail : 0; |
@@ -196,7 +196,7 @@ didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls { | |||
196 | CGRect keyboardFrame = [view convertRect:rawFrame fromView:nil]; | 196 | CGRect keyboardFrame = [view convertRect:rawFrame fromView:nil]; |
197 | // NSLog(@"keyboardFrame: %@", NSStringFromCGRect(keyboardFrame)); | 197 | // NSLog(@"keyboardFrame: %@", NSStringFromCGRect(keyboardFrame)); |
198 | iWindow *window = get_Window(); | 198 | iWindow *window = get_Window(); |
199 | const iInt2 rootSize = rootSize_Window(window); | 199 | const iInt2 rootSize = size_Root(window->roots[0]); |
200 | const int keyTop = keyboardFrame.origin.y * window->pixelRatio; | 200 | const int keyTop = keyboardFrame.origin.y * window->pixelRatio; |
201 | setKeyboardHeight_Window(window, rootSize.y - keyTop); | 201 | setKeyboardHeight_Window(window, rootSize.y - keyTop); |
202 | } | 202 | } |
@@ -302,6 +302,24 @@ iBool processEvent_iOS(const SDL_Event *ev) { | |||
302 | postCommandf_App("os.theme.changed dark:%d contrast:1", isSystemDarkMode_ ? 1 : 0); | 302 | postCommandf_App("os.theme.changed dark:%d contrast:1", isSystemDarkMode_ ? 1 : 0); |
303 | } | 303 | } |
304 | } | 304 | } |
305 | else if (equal_Command(cmd, "theme.changed")) { | ||
306 | if (@available(iOS 13.0, *)) { | ||
307 | /* SDL doesn't expose this as a setting, so we'll rely on a hack. | ||
308 | Adding an SDL hint for this would be a cleaner solution than calling | ||
309 | a private method. */ | ||
310 | UIViewController *vc = viewController_(get_Window()); | ||
311 | SEL sel = NSSelectorFromString(@"setStatusStyle:"); /* custom method */ | ||
312 | if ([vc respondsToSelector:sel]) { | ||
313 | NSInvocation *call = [NSInvocation invocationWithMethodSignature: | ||
314 | [NSMethodSignature signatureWithObjCTypes:"v@:i"]]; | ||
315 | [call setSelector:sel]; | ||
316 | int style = isDark_ColorTheme(colorTheme_App()) ? | ||
317 | UIStatusBarStyleLightContent : UIStatusBarStyleDarkContent; | ||
318 | [call setArgument:&style atIndex:2]; | ||
319 | [call invokeWithTarget:vc]; | ||
320 | } | ||
321 | } | ||
322 | } | ||
305 | } | 323 | } |
306 | return iFalse; /* allow normal processing */ | 324 | return iFalse; /* allow normal processing */ |
307 | } | 325 | } |
@@ -44,6 +44,7 @@ int cmp_MsgStr_(const void *e1, const void *e2) { | |||
44 | enum iPluralType { | 44 | enum iPluralType { |
45 | none_PluralType, | 45 | none_PluralType, |
46 | notEqualToOne_PluralType, | 46 | notEqualToOne_PluralType, |
47 | polish_PluralType, | ||
47 | slavic_PluralType, | 48 | slavic_PluralType, |
48 | }; | 49 | }; |
49 | 50 | ||
@@ -58,6 +59,10 @@ static size_t pluralIndex_Lang_(const iLang *d, int n) { | |||
58 | switch (d->pluralType) { | 59 | switch (d->pluralType) { |
59 | case notEqualToOne_PluralType: | 60 | case notEqualToOne_PluralType: |
60 | return n != 1; | 61 | return n != 1; |
62 | case polish_PluralType: | ||
63 | return n == 1 ? 0 | ||
64 | : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 | ||
65 | : 2; | ||
61 | case slavic_PluralType: | 66 | case slavic_PluralType: |
62 | return n % 10 == 1 && n % 100 != 11 ? 0 | 67 | return n % 10 == 1 && n % 100 != 11 ? 0 |
63 | : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 | 68 | : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 |
@@ -81,6 +86,7 @@ static void load_Lang_(iLang *d, const char *id) { | |||
81 | : equal_CStr(id, "de") ? &blobDe_Embedded | 86 | : equal_CStr(id, "de") ? &blobDe_Embedded |
82 | : equal_CStr(id, "ia") ? &blobIa_Embedded | 87 | : equal_CStr(id, "ia") ? &blobIa_Embedded |
83 | : equal_CStr(id, "ie") ? &blobIe_Embedded | 88 | : equal_CStr(id, "ie") ? &blobIe_Embedded |
89 | : equal_CStr(id, "pl") ? &blobPl_Embedded | ||
84 | : equal_CStr(id, "sr") ? &blobSr_Embedded | 90 | : equal_CStr(id, "sr") ? &blobSr_Embedded |
85 | : equal_CStr(id, "tok") ? &blobTok_Embedded | 91 | : equal_CStr(id, "tok") ? &blobTok_Embedded |
86 | : equal_CStr(id, "zh_Hans") ? &blobZh_Hans_Embedded | 92 | : equal_CStr(id, "zh_Hans") ? &blobZh_Hans_Embedded |
@@ -89,6 +95,9 @@ static void load_Lang_(iLang *d, const char *id) { | |||
89 | if (data == &blobRu_Embedded || data == &blobSr_Embedded) { | 95 | if (data == &blobRu_Embedded || data == &blobSr_Embedded) { |
90 | d->pluralType = slavic_PluralType; | 96 | d->pluralType = slavic_PluralType; |
91 | } | 97 | } |
98 | else if (data == &blobPl_Embedded) { | ||
99 | d->pluralType = polish_PluralType; | ||
100 | } | ||
92 | else if (data == &blobZh_Hans_Embedded || data == &blobZh_Hant_Embedded || | 101 | else if (data == &blobZh_Hans_Embedded || data == &blobZh_Hant_Embedded || |
93 | data == &blobTok_Embedded) { | 102 | data == &blobTok_Embedded) { |
94 | d->pluralType = none_PluralType; | 103 | d->pluralType = none_PluralType; |
diff --git a/src/ui/color.c b/src/ui/color.c index 0a177a8a..6c51bc06 100644 --- a/src/ui/color.c +++ b/src/ui/color.c | |||
@@ -305,7 +305,7 @@ void setThemePalette_Color(enum iColorTheme theme) { | |||
305 | 0.4f)); | 305 | 0.4f)); |
306 | uiPalette_[uiMarked_ColorId ].a = 128; | 306 | uiPalette_[uiMarked_ColorId ].a = 128; |
307 | uiPalette_[uiMatching_ColorId].a = 128; | 307 | uiPalette_[uiMatching_ColorId].a = 128; |
308 | if (deviceType_App() == phone_AppDeviceType) { | 308 | if (deviceType_App() != desktop_AppDeviceType) { |
309 | copy_(uiInputBackground_ColorId, uiBackgroundSidebar_ColorId); | 309 | copy_(uiInputBackground_ColorId, uiBackgroundSidebar_ColorId); |
310 | copy_(uiInputFrame_ColorId, uiBackgroundSidebar_ColorId); | 310 | copy_(uiInputFrame_ColorId, uiBackgroundSidebar_ColorId); |
311 | copy_(uiInputFrameFocused_ColorId, uiBackgroundSidebar_ColorId); | 311 | copy_(uiInputFrameFocused_ColorId, uiBackgroundSidebar_ColorId); |
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index d23e95a6..5dcdace6 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c | |||
@@ -1466,7 +1466,8 @@ static void scrollBegan_DocumentWidget_(iAnyObject *any, int offset, uint32_t du | |||
1466 | } | 1466 | } |
1467 | /* Show and hide toolbar on scroll. */ | 1467 | /* Show and hide toolbar on scroll. */ |
1468 | if (deviceType_App() == phone_AppDeviceType) { | 1468 | if (deviceType_App() == phone_AppDeviceType) { |
1469 | if (prefs_App()->hideToolbarOnScroll && iAbs(offset) > 5) { | 1469 | const float normPos = normScrollPos_DocumentWidget_(d); |
1470 | if (prefs_App()->hideToolbarOnScroll && iAbs(offset) > 5 && normPos >= 0) { | ||
1470 | showToolbars_Root(as_Widget(d)->root, offset < 0); | 1471 | showToolbars_Root(as_Widget(d)->root, offset < 0); |
1471 | } | 1472 | } |
1472 | } | 1473 | } |
@@ -1606,6 +1607,7 @@ static void inputQueryValidator_(iInputWidget *input, void *context) { | |||
1606 | avail < 128 ? uiTextStrong_ColorId | 1607 | avail < 128 ? uiTextStrong_ColorId |
1607 | : uiTextDim_ColorId); | 1608 | : uiTextDim_ColorId); |
1608 | delete_String(url); | 1609 | delete_String(url); |
1610 | arrange_Widget(findChild_Widget(dlg, "dialogbuttons")); | ||
1609 | } | 1611 | } |
1610 | 1612 | ||
1611 | static void checkResponse_DocumentWidget_(iDocumentWidget *d) { | 1613 | static void checkResponse_DocumentWidget_(iDocumentWidget *d) { |
@@ -1637,10 +1639,28 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) { | |||
1637 | : cstr_String(&resp->meta), | 1639 | : cstr_String(&resp->meta), |
1638 | uiTextCaution_ColorEscape "${dlg.input.send}", | 1640 | uiTextCaution_ColorEscape "${dlg.input.send}", |
1639 | format_CStr("!document.input.submit doc:%p", d)); | 1641 | format_CStr("!document.input.submit doc:%p", d)); |
1640 | setId_Widget(addChildPosFlags_Widget(findChild_Widget(dlg, "dialogbuttons"), | 1642 | iWidget *buttons = findChild_Widget(dlg, "dialogbuttons"); |
1643 | iLabelWidget *lineBreak; | ||
1644 | /* The line break and URL length counters are positioned differently on mobile. */ | ||
1645 | if (deviceType_App() == desktop_AppDeviceType) { | ||
1646 | lineBreak = new_LabelWidget("${dlg.input.linebreak}" | ||
1647 | uiTextAction_ColorEscape | ||
1648 | " " shiftReturn_Icon, | ||
1649 | NULL); | ||
1650 | insertChildAfter_Widget(buttons, iClob(lineBreak), 0); | ||
1651 | } | ||
1652 | else { | ||
1653 | lineBreak = new_LabelWidget("${dlg.input.linebreak}", "text.insert arg:10"); | ||
1654 | } | ||
1655 | setFlags_Widget(as_Widget(lineBreak), frameless_WidgetFlag, iTrue); | ||
1656 | setTextColor_LabelWidget(lineBreak, uiTextDim_ColorId); | ||
1657 | setId_Widget(addChildPosFlags_Widget(buttons, | ||
1641 | iClob(new_LabelWidget("", NULL)), | 1658 | iClob(new_LabelWidget("", NULL)), |
1642 | front_WidgetAddPos, frameless_WidgetFlag), | 1659 | front_WidgetAddPos, frameless_WidgetFlag), |
1643 | "valueinput.counter"); | 1660 | "valueinput.counter"); |
1661 | if (deviceType_App() != desktop_AppDeviceType) { | ||
1662 | addChildPos_Widget(buttons, iClob(lineBreak), front_WidgetAddPos); | ||
1663 | } | ||
1644 | setValidator_InputWidget(findChild_Widget(dlg, "input"), inputQueryValidator_, d); | 1664 | setValidator_InputWidget(findChild_Widget(dlg, "input"), inputQueryValidator_, d); |
1645 | setSensitiveContent_InputWidget(findChild_Widget(dlg, "input"), | 1665 | setSensitiveContent_InputWidget(findChild_Widget(dlg, "input"), |
1646 | statusCode == sensitiveInput_GmStatusCode); | 1666 | statusCode == sensitiveInput_GmStatusCode); |
@@ -1653,7 +1673,7 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) { | |||
1653 | break; | 1673 | break; |
1654 | } | 1674 | } |
1655 | case categorySuccess_GmStatusCode: | 1675 | case categorySuccess_GmStatusCode: |
1656 | reset_SmoothScroll(&d->scrollY); | 1676 | //reset_SmoothScroll(&d->scrollY); |
1657 | iRelease(d->doc); /* new content incoming */ | 1677 | iRelease(d->doc); /* new content incoming */ |
1658 | d->doc = new_GmDocument(); | 1678 | d->doc = new_GmDocument(); |
1659 | delete_Gempub(d->sourceGempub); | 1679 | delete_Gempub(d->sourceGempub); |
@@ -2310,7 +2330,9 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
2310 | } | 2330 | } |
2311 | updateFetchProgress_DocumentWidget_(d); | 2331 | updateFetchProgress_DocumentWidget_(d); |
2312 | checkResponse_DocumentWidget_(d); | 2332 | checkResponse_DocumentWidget_(d); |
2313 | init_Anim(&d->scrollY.pos, d->initNormScrollY * size_GmDocument(d->doc).y); /* TODO: unless user already scrolled! */ | 2333 | if (category_GmStatusCode(status_GmRequest(d->request)) == categorySuccess_GmStatusCode) { |
2334 | init_Anim(&d->scrollY.pos, d->initNormScrollY * size_GmDocument(d->doc).y); /* TODO: unless user already scrolled! */ | ||
2335 | } | ||
2314 | d->state = ready_RequestState; | 2336 | d->state = ready_RequestState; |
2315 | postProcessRequestContent_DocumentWidget_(d, iFalse); | 2337 | postProcessRequestContent_DocumentWidget_(d, iFalse); |
2316 | /* The response may be cached. */ | 2338 | /* The response may be cached. */ |
@@ -3081,7 +3103,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
3081 | pushBackN_Array(&items, | 3103 | pushBackN_Array(&items, |
3082 | (iMenuItem[]){ { "---", 0, 0, NULL }, | 3104 | (iMenuItem[]){ { "---", 0, 0, NULL }, |
3083 | { "${link.copy}", 0, 0, "document.copylink" }, | 3105 | { "${link.copy}", 0, 0, "document.copylink" }, |
3084 | { pin_Icon " ${link.bookmark}", | 3106 | { bookmark_Icon " ${link.bookmark}", |
3085 | 0, | 3107 | 0, |
3086 | 0, | 3108 | 0, |
3087 | format_CStr("!bookmark.add title:%s url:%s", | 3109 | format_CStr("!bookmark.add title:%s url:%s", |
@@ -3126,7 +3148,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
3126 | { reload_Icon " ${menu.reload}", reload_KeyShortcut, "navigate.reload" }, | 3148 | { reload_Icon " ${menu.reload}", reload_KeyShortcut, "navigate.reload" }, |
3127 | { timer_Icon " ${menu.autoreload}", 0, 0, "document.autoreload.menu" }, | 3149 | { timer_Icon " ${menu.autoreload}", 0, 0, "document.autoreload.menu" }, |
3128 | { "---", 0, 0, NULL }, | 3150 | { "---", 0, 0, NULL }, |
3129 | { pin_Icon " ${menu.page.bookmark}", SDLK_d, KMOD_PRIMARY, "bookmark.add" }, | 3151 | { bookmark_Icon " ${menu.page.bookmark}", SDLK_d, KMOD_PRIMARY, "bookmark.add" }, |
3130 | { star_Icon " ${menu.page.subscribe}", subscribeToPage_KeyModifier, "feeds.subscribe" }, | 3152 | { star_Icon " ${menu.page.subscribe}", subscribeToPage_KeyModifier, "feeds.subscribe" }, |
3131 | { "---", 0, 0, NULL }, | 3153 | { "---", 0, 0, NULL }, |
3132 | { book_Icon " ${menu.page.import}", 0, 0, "bookmark.links confirm:1" }, | 3154 | { book_Icon " ${menu.page.import}", 0, 0, "bookmark.links confirm:1" }, |
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c index 6719fb40..784fabdd 100644 --- a/src/ui/inputwidget.c +++ b/src/ui/inputwidget.c | |||
@@ -42,8 +42,12 @@ static const size_t maxUndo_InputWidget_ = 64; | |||
42 | 42 | ||
43 | static void enableEditorKeysInMenus_(iBool enable) { | 43 | static void enableEditorKeysInMenus_(iBool enable) { |
44 | #if defined (iPlatformAppleDesktop) | 44 | #if defined (iPlatformAppleDesktop) |
45 | enableMenuItemsByKey_MacOS(SDLK_LEFT, KMOD_PRIMARY, enable); | 45 | enableMenuItemsByKey_MacOS(SDLK_LEFT, KMOD_PRIMARY, enable); |
46 | enableMenuItemsByKey_MacOS(SDLK_RIGHT, KMOD_PRIMARY, enable); | 46 | enableMenuItemsByKey_MacOS(SDLK_RIGHT, KMOD_PRIMARY, enable); |
47 | enableMenuItemsByKey_MacOS(SDLK_UP, KMOD_PRIMARY, enable); | ||
48 | enableMenuItemsByKey_MacOS(SDLK_DOWN, KMOD_PRIMARY, enable); | ||
49 | enableMenuItemsByKey_MacOS(SDLK_UP, KMOD_PRIMARY | KMOD_SHIFT, enable); | ||
50 | enableMenuItemsByKey_MacOS(SDLK_DOWN, KMOD_PRIMARY | KMOD_SHIFT, enable); | ||
47 | #else | 51 | #else |
48 | iUnused(enable); | 52 | iUnused(enable); |
49 | #endif | 53 | #endif |
@@ -84,6 +88,7 @@ iDeclareType(InputLine) | |||
84 | 88 | ||
85 | struct Impl_InputLine { | 89 | struct Impl_InputLine { |
86 | size_t offset; /* character position from the beginning */ | 90 | size_t offset; /* character position from the beginning */ |
91 | size_t len; /* length as characters */ | ||
87 | iString text; /* UTF-8 */ | 92 | iString text; /* UTF-8 */ |
88 | }; | 93 | }; |
89 | 94 | ||
@@ -109,6 +114,7 @@ struct Impl_InputWidget { | |||
109 | iArray text; /* iChar[] */ | 114 | iArray text; /* iChar[] */ |
110 | iArray oldText; /* iChar[] */ | 115 | iArray oldText; /* iChar[] */ |
111 | iArray lines; | 116 | iArray lines; |
117 | int lastUpdateWidth; | ||
112 | iString hint; | 118 | iString hint; |
113 | iString srcHint; | 119 | iString srcHint; |
114 | int leftPadding; | 120 | int leftPadding; |
@@ -138,7 +144,26 @@ static void clearUndo_InputWidget_(iInputWidget *d) { | |||
138 | clear_Array(&d->undoStack); | 144 | clear_Array(&d->undoStack); |
139 | } | 145 | } |
140 | 146 | ||
147 | iLocalDef iInt2 padding_(void) { | ||
148 | return init_I2(gap_UI / 2, gap_UI / 2); | ||
149 | } | ||
150 | |||
151 | static iRect contentBounds_InputWidget_(const iInputWidget *d) { | ||
152 | const iWidget *w = constAs_Widget(d); | ||
153 | // const iRect widgetBounds = bounds_Widget(w); | ||
154 | iRect bounds = adjusted_Rect(bounds_Widget(w), | ||
155 | addX_I2(padding_(), d->leftPadding), | ||
156 | neg_I2(addX_I2(padding_(), d->rightPadding))); | ||
157 | shrink_Rect(&bounds, init_I2(gap_UI * (flags_Widget(w) & tight_WidgetFlag ? 1 : 2), 0)); | ||
158 | bounds.pos.y += padding_().y / 2; | ||
159 | if (flags_Widget(w) & extraPadding_WidgetFlag) { | ||
160 | bounds.pos.y += gap_UI; | ||
161 | } | ||
162 | return bounds; | ||
163 | } | ||
164 | |||
141 | static void updateCursorLine_InputWidget_(iInputWidget *d) { | 165 | static void updateCursorLine_InputWidget_(iInputWidget *d) { |
166 | iWidget *w = as_Widget(d); | ||
142 | d->cursorLine = 0; | 167 | d->cursorLine = 0; |
143 | iConstForEach(Array, i, &d->lines) { | 168 | iConstForEach(Array, i, &d->lines) { |
144 | const iInputLine *line = i.value; | 169 | const iInputLine *line = i.value; |
@@ -147,6 +172,20 @@ static void updateCursorLine_InputWidget_(iInputWidget *d) { | |||
147 | } | 172 | } |
148 | d->cursorLine = index_ArrayConstIterator(&i); | 173 | d->cursorLine = index_ArrayConstIterator(&i); |
149 | } | 174 | } |
175 | /* May need to scroll to keep the cursor visible. */ | ||
176 | iWidget *flow = findOverflowScrollable_Widget(w); | ||
177 | if (flow) { | ||
178 | const iRect rootRect = { rect_Root(w->root).pos, visibleSize_Root(w->root) }; | ||
179 | int yCursor = contentBounds_InputWidget_(d).pos.y + | ||
180 | lineHeight_Text(d->font) * (int) d->cursorLine; | ||
181 | const int margin = lineHeight_Text(d->font) * 3; | ||
182 | if (yCursor < top_Rect(rootRect) + margin) { | ||
183 | scrollOverflow_Widget(flow, top_Rect(rootRect) + margin - yCursor); | ||
184 | } | ||
185 | else if (yCursor > bottom_Rect(rootRect) - margin * 3 / 2) { | ||
186 | scrollOverflow_Widget(flow, bottom_Rect(rootRect) - margin * 3 / 2 - yCursor); | ||
187 | } | ||
188 | } | ||
150 | } | 189 | } |
151 | 190 | ||
152 | static void showCursor_InputWidget_(iInputWidget *d) { | 191 | static void showCursor_InputWidget_(iInputWidget *d) { |
@@ -161,21 +200,6 @@ static void invalidateBuffered_InputWidget_(iInputWidget *d) { | |||
161 | } | 200 | } |
162 | } | 201 | } |
163 | 202 | ||
164 | iLocalDef iInt2 padding_(void) { | ||
165 | return init_I2(gap_UI / 2, gap_UI / 2); | ||
166 | } | ||
167 | |||
168 | static iRect contentBounds_InputWidget_(const iInputWidget *d) { | ||
169 | const iWidget *w = constAs_Widget(d); | ||
170 | const iRect widgetBounds = bounds_Widget(w); | ||
171 | iRect bounds = adjusted_Rect(bounds_Widget(w), | ||
172 | addX_I2(padding_(), d->leftPadding), | ||
173 | neg_I2(addX_I2(padding_(), d->rightPadding))); | ||
174 | shrink_Rect(&bounds, init_I2(gap_UI * (flags_Widget(w) & tight_WidgetFlag ? 1 : 2), 0)); | ||
175 | bounds.pos.y += padding_().y / 2; | ||
176 | return bounds; | ||
177 | } | ||
178 | |||
179 | static void updateSizeForFixedLength_InputWidget_(iInputWidget *d) { | 203 | static void updateSizeForFixedLength_InputWidget_(iInputWidget *d) { |
180 | if (d->maxLen) { | 204 | if (d->maxLen) { |
181 | /* Set a fixed size based on maximum possible width of the text. */ | 205 | /* Set a fixed size based on maximum possible width of the text. */ |
@@ -219,6 +243,7 @@ static void clearLines_InputWidget_(iInputWidget *d) { | |||
219 | } | 243 | } |
220 | 244 | ||
221 | static void updateLines_InputWidget_(iInputWidget *d) { | 245 | static void updateLines_InputWidget_(iInputWidget *d) { |
246 | d->lastUpdateWidth = d->widget.rect.size.x; | ||
222 | clearLines_InputWidget_(d); | 247 | clearLines_InputWidget_(d); |
223 | if (d->maxLen) { | 248 | if (d->maxLen) { |
224 | /* Everything on a single line. */ | 249 | /* Everything on a single line. */ |
@@ -226,6 +251,7 @@ static void updateLines_InputWidget_(iInputWidget *d) { | |||
226 | init_InputLine(&line); | 251 | init_InputLine(&line); |
227 | iString *u8 = visText_InputWidget_(d); | 252 | iString *u8 = visText_InputWidget_(d); |
228 | set_String(&line.text, u8); | 253 | set_String(&line.text, u8); |
254 | line.len = length_String(u8); | ||
229 | delete_String(u8); | 255 | delete_String(u8); |
230 | pushBack_Array(&d->lines, &line); | 256 | pushBack_Array(&d->lines, &line); |
231 | updateCursorLine_InputWidget_(d); | 257 | updateCursorLine_InputWidget_(d); |
@@ -249,14 +275,16 @@ static void updateLines_InputWidget_(iInputWidget *d) { | |||
249 | init_InputLine(&line); | 275 | init_InputLine(&line); |
250 | setRange_String(&line.text, part); | 276 | setRange_String(&line.text, part); |
251 | line.offset = charPos; | 277 | line.offset = charPos; |
278 | line.len = length_String(&line.text); | ||
252 | pushBack_Array(&d->lines, &line); | 279 | pushBack_Array(&d->lines, &line); |
253 | charPos += length_String(&line.text); | 280 | charPos += line.len; |
254 | content.start = endPos; | 281 | content.start = endPos; |
255 | } | 282 | } |
256 | if (isEmpty_Array(&d->lines) || endsWith_String(u8, "\n")) { | 283 | if (isEmpty_Array(&d->lines) || endsWith_String(u8, "\n")) { |
257 | /* Always at least one empty line. */ | 284 | /* Always at least one empty line. */ |
258 | iInputLine line; | 285 | iInputLine line; |
259 | init_InputLine(&line); | 286 | init_InputLine(&line); |
287 | line.offset = charPos; | ||
260 | pushBack_Array(&d->lines, &line); | 288 | pushBack_Array(&d->lines, &line); |
261 | } | 289 | } |
262 | else { | 290 | else { |
@@ -278,7 +306,7 @@ static void updateMetrics_InputWidget_(iInputWidget *d) { | |||
278 | iWidget *w = as_Widget(d); | 306 | iWidget *w = as_Widget(d); |
279 | updateSizeForFixedLength_InputWidget_(d); | 307 | updateSizeForFixedLength_InputWidget_(d); |
280 | /* Caller must arrange the width, but the height is fixed. */ | 308 | /* Caller must arrange the width, but the height is fixed. */ |
281 | w->rect.size.y = contentHeight_InputWidget_(d, iTrue) + 3 * padding_().y; /* TODO: Why 3x? */ | 309 | w->rect.size.y = contentHeight_InputWidget_(d, iTrue) + 3.0f * padding_().y; /* TODO: Why 3x? */ |
282 | if (flags_Widget(w) & extraPadding_WidgetFlag) { | 310 | if (flags_Widget(w) & extraPadding_WidgetFlag) { |
283 | w->rect.size.y += 2 * gap_UI; | 311 | w->rect.size.y += 2 * gap_UI; |
284 | } | 312 | } |
@@ -316,6 +344,7 @@ void init_InputWidget(iInputWidget *d, size_t maxLen) { | |||
316 | d->cursor = 0; | 344 | d->cursor = 0; |
317 | d->lastCursor = 0; | 345 | d->lastCursor = 0; |
318 | d->cursorLine = 0; | 346 | d->cursorLine = 0; |
347 | d->lastUpdateWidth = 0; | ||
319 | d->verticalMoveX = -1; /* TODO: Use this. */ | 348 | d->verticalMoveX = -1; /* TODO: Use this. */ |
320 | d->inFlags = eatEscape_InputWidgetFlag | enterKeyEnabled_InputWidgetFlag; | 349 | d->inFlags = eatEscape_InputWidgetFlag | enterKeyEnabled_InputWidgetFlag; |
321 | iZap(d->mark); | 350 | iZap(d->mark); |
@@ -455,31 +484,46 @@ void setContentPadding_InputWidget(iInputWidget *d, int left, int right) { | |||
455 | refresh_Widget(d); | 484 | refresh_Widget(d); |
456 | } | 485 | } |
457 | 486 | ||
487 | static iBool isHintVisible_InputWidget_(const iInputWidget *d) { | ||
488 | return !isEmpty_String(&d->hint) && size_Array(&d->lines) == 1 && | ||
489 | isEmpty_String(&line_InputWidget_(d, 0)->text); | ||
490 | } | ||
491 | |||
458 | static void updateBuffered_InputWidget_(iInputWidget *d) { | 492 | static void updateBuffered_InputWidget_(iInputWidget *d) { |
459 | invalidateBuffered_InputWidget_(d); | 493 | invalidateBuffered_InputWidget_(d); |
460 | iString *bufText = NULL; | 494 | if (isHintVisible_InputWidget_(d)) { |
495 | d->buffered = new_TextBuf(d->font, uiAnnotation_ColorId, cstr_String(&d->hint)); | ||
496 | } | ||
497 | else { | ||
498 | iString *bufText = NULL; | ||
461 | #if 0 | 499 | #if 0 |
462 | if (d->inFlags & isUrl_InputWidgetFlag && as_Widget(d)->root == win->keyRoot) { | 500 | if (d->inFlags & isUrl_InputWidgetFlag && as_Widget(d)->root == win->keyRoot) { |
463 | /* TODO: Move this omitting to `updateLines_`? */ | 501 | /* TODO: Move this omitting to `updateLines_`? */ |
464 | /* Highlight the host name. */ | 502 | /* Highlight the host name. */ |
465 | iUrl parts; | 503 | iUrl parts; |
466 | const iString *text = collect_String(utf32toUtf8_InputWidget_(d)); | 504 | const iString *text = collect_String(utf32toUtf8_InputWidget_(d)); |
467 | init_Url(&parts, text); | 505 | init_Url(&parts, text); |
468 | if (!isEmpty_Range(&parts.host)) { | 506 | if (!isEmpty_Range(&parts.host)) { |
469 | bufText = new_String(); | 507 | bufText = new_String(); |
470 | appendRange_String(bufText, (iRangecc){ constBegin_String(text), parts.host.start }); | 508 | appendRange_String(bufText, (iRangecc){ constBegin_String(text), parts.host.start }); |
471 | appendCStr_String(bufText, uiTextStrong_ColorEscape); | 509 | appendCStr_String(bufText, uiTextStrong_ColorEscape); |
472 | appendRange_String(bufText, parts.host); | 510 | appendRange_String(bufText, parts.host); |
473 | appendCStr_String(bufText, restore_ColorEscape); | 511 | appendCStr_String(bufText, restore_ColorEscape); |
474 | appendRange_String(bufText, (iRangecc){ parts.host.end, constEnd_String(text) }); | 512 | appendRange_String(bufText, (iRangecc){ parts.host.end, constEnd_String(text) }); |
513 | } | ||
475 | } | 514 | } |
476 | } | ||
477 | #endif | 515 | #endif |
478 | if (!bufText) { | 516 | if (!bufText) { |
479 | bufText = visText_InputWidget_(d); | 517 | bufText = visText_InputWidget_(d); |
518 | } | ||
519 | const int maxWidth = contentBounds_InputWidget_(d).size.x; | ||
520 | const int fg = uiInputText_ColorId; | ||
521 | const char *text = cstr_String(bufText); | ||
522 | d->buffered = | ||
523 | (d->inFlags & isUrl_InputWidgetFlag ? newBound_TextBuf(d->font, fg, maxWidth, text) | ||
524 | : newWrap_TextBuf (d->font, fg, maxWidth, text)); | ||
525 | delete_String(bufText); | ||
480 | } | 526 | } |
481 | d->buffered = new_TextBuf(d->font, uiInputText_ColorId, cstr_String(bufText)); | ||
482 | delete_String(bufText); | ||
483 | d->inFlags &= ~needUpdateBuffer_InputWidgetFlag; | 527 | d->inFlags &= ~needUpdateBuffer_InputWidgetFlag; |
484 | } | 528 | } |
485 | 529 | ||
@@ -563,7 +607,11 @@ void begin_InputWidget(iInputWidget *d) { | |||
563 | } | 607 | } |
564 | updateCursorLine_InputWidget_(d); | 608 | updateCursorLine_InputWidget_(d); |
565 | SDL_StartTextInput(); | 609 | SDL_StartTextInput(); |
566 | setFlags_Widget(w, selected_WidgetFlag | keepOnTop_WidgetFlag, iTrue); | 610 | setFlags_Widget(w, selected_WidgetFlag, iTrue); |
611 | if (d->maxLayoutLines != iInvalidSize) { | ||
612 | /* This will extend beyond the arranged region. */ | ||
613 | setFlags_Widget(w, keepOnTop_WidgetFlag, iTrue); | ||
614 | } | ||
567 | showCursor_InputWidget_(d); | 615 | showCursor_InputWidget_(d); |
568 | refresh_Widget(w); | 616 | refresh_Widget(w); |
569 | d->timer = SDL_AddTimer(refreshInterval_InputWidget_, cursorTimer_, d); | 617 | d->timer = SDL_AddTimer(refreshInterval_InputWidget_, cursorTimer_, d); |
@@ -657,6 +705,10 @@ void setCursor_InputWidget(iInputWidget *d, size_t pos) { | |||
657 | showCursor_InputWidget_(d); | 705 | showCursor_InputWidget_(d); |
658 | } | 706 | } |
659 | 707 | ||
708 | iLocalDef iBool isLastLine_InputWidget_(const iInputWidget *d, const iInputLine *line) { | ||
709 | return (const void *) line == constAt_Array(&d->lines, size_Array(&d->lines) - 1); | ||
710 | } | ||
711 | |||
660 | static size_t indexForRelativeX_InputWidget_(const iInputWidget *d, int x, const iInputLine *line) { | 712 | static size_t indexForRelativeX_InputWidget_(const iInputWidget *d, int x, const iInputLine *line) { |
661 | size_t index = line->offset; | 713 | size_t index = line->offset; |
662 | if (x <= 0) { | 714 | if (x <= 0) { |
@@ -665,7 +717,7 @@ static size_t indexForRelativeX_InputWidget_(const iInputWidget *d, int x, const | |||
665 | const char *endPos; | 717 | const char *endPos; |
666 | tryAdvanceNoWrap_Text(d->font, range_String(&line->text), x, &endPos); | 718 | tryAdvanceNoWrap_Text(d->font, range_String(&line->text), x, &endPos); |
667 | if (endPos == constEnd_String(&line->text)) { | 719 | if (endPos == constEnd_String(&line->text)) { |
668 | index += length_String(&line->text); | 720 | index += line->len; |
669 | } | 721 | } |
670 | else { | 722 | else { |
671 | /* Need to know the actual character index. */ | 723 | /* Need to know the actual character index. */ |
@@ -675,6 +727,9 @@ static size_t indexForRelativeX_InputWidget_(const iInputWidget *d, int x, const | |||
675 | index++; | 727 | index++; |
676 | } | 728 | } |
677 | } | 729 | } |
730 | if (!isLastLine_InputWidget_(d, line) && index == line->offset + line->len) { | ||
731 | index = iMax(index - 1, line->offset); | ||
732 | } | ||
678 | return index; | 733 | return index; |
679 | } | 734 | } |
680 | 735 | ||
@@ -690,11 +745,6 @@ static iBool moveCursorByLine_InputWidget_(iInputWidget *d, int dir) { | |||
690 | newCursor = indexForRelativeX_InputWidget_(d, xPos, ++line); | 745 | newCursor = indexForRelativeX_InputWidget_(d, xPos, ++line); |
691 | } | 746 | } |
692 | if (newCursor != iInvalidPos) { | 747 | if (newCursor != iInvalidPos) { |
693 | /* Clamp it to the current line. */ | ||
694 | newCursor = iMin(newCursor, line->offset + length_String(&line->text) - | ||
695 | /* last line is allowed to go to the cursorMax */ | ||
696 | ((const void *) line < constAt_Array(&d->lines, numLines - 1) ? 1 : 0)); | ||
697 | newCursor = iMax(newCursor, line->offset); | ||
698 | setCursor_InputWidget(d, newCursor); | 748 | setCursor_InputWidget(d, newCursor); |
699 | return iTrue; | 749 | return iTrue; |
700 | } | 750 | } |
@@ -707,6 +757,7 @@ void setSensitiveContent_InputWidget(iInputWidget *d, iBool isSensitive) { | |||
707 | 757 | ||
708 | void setUrlContent_InputWidget(iInputWidget *d, iBool isUrl) { | 758 | void setUrlContent_InputWidget(iInputWidget *d, iBool isUrl) { |
709 | iChangeFlags(d->inFlags, isUrl_InputWidgetFlag, isUrl); | 759 | iChangeFlags(d->inFlags, isUrl_InputWidgetFlag, isUrl); |
760 | d->inFlags |= needUpdateBuffer_InputWidgetFlag; | ||
710 | } | 761 | } |
711 | 762 | ||
712 | void setSelectAllOnFocus_InputWidget(iInputWidget *d, iBool selectAllOnFocus) { | 763 | void setSelectAllOnFocus_InputWidget(iInputWidget *d, iBool selectAllOnFocus) { |
@@ -826,7 +877,8 @@ static iInt2 textOrigin_InputWidget_(const iInputWidget *d) { //}, const char *v | |||
826 | 877 | ||
827 | static size_t coordIndex_InputWidget_(const iInputWidget *d, iInt2 coord) { | 878 | static size_t coordIndex_InputWidget_(const iInputWidget *d, iInt2 coord) { |
828 | const iInt2 pos = sub_I2(coord, contentBounds_InputWidget_(d).pos); | 879 | const iInt2 pos = sub_I2(coord, contentBounds_InputWidget_(d).pos); |
829 | const size_t lineNumber = iMin(pos.y / lineHeight_Text(d->font), (int) size_Array(&d->lines) - 1); | 880 | const size_t lineNumber = iMin(iMax(0, pos.y) / lineHeight_Text(d->font), |
881 | (int) size_Array(&d->lines) - 1); | ||
830 | const iInputLine *line = line_InputWidget_(d, lineNumber); | 882 | const iInputLine *line = line_InputWidget_(d, lineNumber); |
831 | const char *endPos; | 883 | const char *endPos; |
832 | tryAdvanceNoWrap_Text(d->font, range_String(&line->text), pos.x, &endPos); | 884 | tryAdvanceNoWrap_Text(d->font, range_String(&line->text), pos.x, &endPos); |
@@ -880,7 +932,7 @@ static iRanges lineRange_InputWidget_(const iInputWidget *d) { | |||
880 | return (iRanges){ 0, 0 }; | 932 | return (iRanges){ 0, 0 }; |
881 | } | 933 | } |
882 | const iInputLine *line = line_InputWidget_(d, d->cursorLine); | 934 | const iInputLine *line = line_InputWidget_(d, d->cursorLine); |
883 | return (iRanges){ line->offset, line->offset + length_String(&line->text) }; | 935 | return (iRanges){ line->offset, line->offset + line->len }; |
884 | } | 936 | } |
885 | 937 | ||
886 | static void extendRange_InputWidget_(iInputWidget *d, size_t *pos, int dir) { | 938 | static void extendRange_InputWidget_(iInputWidget *d, size_t *pos, int dir) { |
@@ -907,6 +959,9 @@ static iRect bounds_InputWidget_(const iInputWidget *d) { | |||
907 | return bounds; | 959 | return bounds; |
908 | } | 960 | } |
909 | bounds.size.y = contentHeight_InputWidget_(d, iFalse) + 3 * padding_().y; | 961 | bounds.size.y = contentHeight_InputWidget_(d, iFalse) + 3 * padding_().y; |
962 | if (w->flags & extraPadding_WidgetFlag) { | ||
963 | bounds.size.y += 2 * gap_UI; | ||
964 | } | ||
910 | return bounds; | 965 | return bounds; |
911 | } | 966 | } |
912 | 967 | ||
@@ -958,11 +1013,19 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
958 | } | 1013 | } |
959 | return iFalse; | 1014 | return iFalse; |
960 | } | 1015 | } |
1016 | else if (isCommand_UserEvent(ev, "text.insert")) { | ||
1017 | pushUndo_InputWidget_(d); | ||
1018 | deleteMarked_InputWidget_(d); | ||
1019 | insertChar_InputWidget_(d, arg_Command(command_UserEvent(ev))); | ||
1020 | contentsWereChanged_InputWidget_(d); | ||
1021 | return iTrue; | ||
1022 | } | ||
961 | else if (isMetricsChange_UserEvent(ev)) { | 1023 | else if (isMetricsChange_UserEvent(ev)) { |
962 | updateMetrics_InputWidget_(d); | 1024 | updateMetrics_InputWidget_(d); |
963 | updateLinesAndResize_InputWidget_(d); | 1025 | updateLinesAndResize_InputWidget_(d); |
964 | } | 1026 | } |
965 | else if (isResize_UserEvent(ev)) { | 1027 | else if (isResize_UserEvent(ev) || d->lastUpdateWidth != w->rect.size.x) { |
1028 | d->inFlags |= needUpdateBuffer_InputWidgetFlag; | ||
966 | if (d->inFlags & isUrl_InputWidgetFlag) { | 1029 | if (d->inFlags & isUrl_InputWidgetFlag) { |
967 | /* Restore/omit the default scheme if necessary. */ | 1030 | /* Restore/omit the default scheme if necessary. */ |
968 | setText_InputWidget(d, text_InputWidget(d)); | 1031 | setText_InputWidget(d, text_InputWidget(d)); |
@@ -1077,6 +1140,17 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
1077 | return iTrue; | 1140 | return iTrue; |
1078 | } | 1141 | } |
1079 | } | 1142 | } |
1143 | #if defined (iPlatformApple) | ||
1144 | if (mods == KMOD_PRIMARY || mods == (KMOD_PRIMARY | KMOD_SHIFT)) { | ||
1145 | switch (key) { | ||
1146 | case SDLK_UP: | ||
1147 | case SDLK_DOWN: | ||
1148 | setCursor_InputWidget(d, key == SDLK_UP ? 0 : curMax); | ||
1149 | refresh_Widget(d); | ||
1150 | return iTrue; | ||
1151 | } | ||
1152 | } | ||
1153 | #endif | ||
1080 | d->lastCursor = d->cursor; | 1154 | d->lastCursor = d->cursor; |
1081 | switch (key) { | 1155 | switch (key) { |
1082 | case SDLK_INSERT: | 1156 | case SDLK_INSERT: |
@@ -1086,7 +1160,8 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
1086 | return iTrue; | 1160 | return iTrue; |
1087 | case SDLK_RETURN: | 1161 | case SDLK_RETURN: |
1088 | case SDLK_KP_ENTER: | 1162 | case SDLK_KP_ENTER: |
1089 | if (mods == KMOD_SHIFT) { | 1163 | if (mods == KMOD_SHIFT || (~d->inFlags & isUrl_InputWidgetFlag && |
1164 | deviceType_App() != desktop_AppDeviceType)) { | ||
1090 | pushUndo_InputWidget_(d); | 1165 | pushUndo_InputWidget_(d); |
1091 | deleteMarked_InputWidget_(d); | 1166 | deleteMarked_InputWidget_(d); |
1092 | insertChar_InputWidget_(d, '\n'); | 1167 | insertChar_InputWidget_(d, '\n'); |
@@ -1170,7 +1245,12 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
1170 | break; | 1245 | break; |
1171 | case SDLK_HOME: | 1246 | case SDLK_HOME: |
1172 | case SDLK_END: | 1247 | case SDLK_END: |
1173 | setCursor_InputWidget(d, key == SDLK_HOME ? lineFirst : lineLast); | 1248 | if (mods == KMOD_PRIMARY || mods == (KMOD_PRIMARY | KMOD_SHIFT)) { |
1249 | setCursor_InputWidget(d, key == SDLK_HOME ? 0 : curMax); | ||
1250 | } | ||
1251 | else { | ||
1252 | setCursor_InputWidget(d, key == SDLK_HOME ? lineFirst : lineLast); | ||
1253 | } | ||
1174 | refresh_Widget(w); | 1254 | refresh_Widget(w); |
1175 | return iTrue; | 1255 | return iTrue; |
1176 | case SDLK_a: | 1256 | case SDLK_a: |
@@ -1216,19 +1296,20 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
1216 | /* Allow focus switching. */ | 1296 | /* Allow focus switching. */ |
1217 | return processEvent_Widget(as_Widget(d), ev); | 1297 | return processEvent_Widget(as_Widget(d), ev); |
1218 | case SDLK_UP: | 1298 | case SDLK_UP: |
1219 | if (moveCursorByLine_InputWidget_(d, -1)) { | ||
1220 | refresh_Widget(d); | ||
1221 | return iTrue; | ||
1222 | } | ||
1223 | /* For moving to lookup from url entry. */ | ||
1224 | return processEvent_Widget(as_Widget(d), ev); | ||
1225 | case SDLK_DOWN: | 1299 | case SDLK_DOWN: |
1226 | if (moveCursorByLine_InputWidget_(d, +1)) { | 1300 | if (moveCursorByLine_InputWidget_(d, key == SDLK_UP ? -1 : +1)) { |
1227 | refresh_Widget(d); | 1301 | refresh_Widget(d); |
1228 | return iTrue; | 1302 | return iTrue; |
1229 | } | 1303 | } |
1230 | /* For moving to lookup from url entry. */ | 1304 | /* For moving to lookup from url entry. */ |
1231 | return processEvent_Widget(as_Widget(d), ev); | 1305 | return processEvent_Widget(as_Widget(d), ev); |
1306 | case SDLK_PAGEUP: | ||
1307 | case SDLK_PAGEDOWN: | ||
1308 | for (int count = 0; count < 5; count++) { | ||
1309 | moveCursorByLine_InputWidget_(d, key == SDLK_PAGEUP ? -1 : +1); | ||
1310 | } | ||
1311 | refresh_Widget(d); | ||
1312 | return iTrue; | ||
1232 | } | 1313 | } |
1233 | if (mods & (KMOD_PRIMARY | KMOD_SECONDARY)) { | 1314 | if (mods & (KMOD_PRIMARY | KMOD_SECONDARY)) { |
1234 | return iFalse; | 1315 | return iFalse; |
@@ -1248,6 +1329,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
1248 | return processEvent_Widget(w, ev); | 1329 | return processEvent_Widget(w, ev); |
1249 | } | 1330 | } |
1250 | 1331 | ||
1332 | #if 0 | ||
1251 | static iBool isWhite_(const iString *str) { | 1333 | static iBool isWhite_(const iString *str) { |
1252 | iConstForEach(String, i, str) { | 1334 | iConstForEach(String, i, str) { |
1253 | if (!isSpace_Char(i.value)) { | 1335 | if (!isSpace_Char(i.value)) { |
@@ -1256,11 +1338,12 @@ static iBool isWhite_(const iString *str) { | |||
1256 | } | 1338 | } |
1257 | return iTrue; | 1339 | return iTrue; |
1258 | } | 1340 | } |
1341 | #endif | ||
1259 | 1342 | ||
1260 | static void draw_InputWidget_(const iInputWidget *d) { | 1343 | static void draw_InputWidget_(const iInputWidget *d) { |
1261 | const iWidget *w = constAs_Widget(d); | 1344 | const iWidget *w = constAs_Widget(d); |
1262 | iRect bounds = adjusted_Rect(bounds_InputWidget_(d), padding_(), neg_I2(padding_())); | 1345 | iRect bounds = adjusted_Rect(bounds_InputWidget_(d), padding_(), neg_I2(padding_())); |
1263 | iBool isHint = iFalse; | 1346 | iBool isHint = isHintVisible_InputWidget_(d); |
1264 | const iBool isFocused = isFocused_Widget(w); | 1347 | const iBool isFocused = isFocused_Widget(w); |
1265 | const iBool isHover = isHover_Widget(w) && | 1348 | const iBool isHover = isHover_Widget(w) && |
1266 | contains_InputWidget_(d, mouseCoord_Window(get_Window())); | 1349 | contains_InputWidget_(d, mouseCoord_Window(get_Window())); |
@@ -1283,40 +1366,52 @@ static void draw_InputWidget_(const iInputWidget *d) { | |||
1283 | isFocused ? gap_UI / 4 : 1, | 1366 | isFocused ? gap_UI / 4 : 1, |
1284 | isFocused ? uiInputFrameFocused_ColorId | 1367 | isFocused ? uiInputFrameFocused_ColorId |
1285 | : isHover ? uiInputFrameHover_ColorId : uiInputFrame_ColorId); | 1368 | : isHover ? uiInputFrameHover_ColorId : uiInputFrame_ColorId); |
1286 | setClip_Paint(&p, adjusted_Rect(bounds, init_I2(d->leftPadding, 0), init_I2(-d->rightPadding, 0))); | 1369 | setClip_Paint(&p, adjusted_Rect(bounds, init_I2(d->leftPadding, 0), |
1370 | init_I2(-d->rightPadding, w->flags & extraPadding_WidgetFlag ? -gap_UI / 2 : 0))); | ||
1287 | const iRect contentBounds = contentBounds_InputWidget_(d); | 1371 | const iRect contentBounds = contentBounds_InputWidget_(d); |
1288 | // const iInt2 textOrigin = textOrigin_InputWidget_(d); //, cstr_String(text)); | 1372 | // const iInt2 textOrigin = textOrigin_InputWidget_(d); //, cstr_String(text)); |
1289 | iInt2 drawPos = topLeft_Rect(contentBounds); | 1373 | iInt2 drawPos = topLeft_Rect(contentBounds); |
1290 | const int fg = isHint ? uiAnnotation_ColorId | 1374 | const int fg = isHint ? uiAnnotation_ColorId |
1291 | : isFocused && !isEmpty_Array(&d->text) ? uiInputTextFocused_ColorId | 1375 | : isFocused && !isEmpty_Array(&d->text) ? uiInputTextFocused_ColorId |
1292 | : uiInputText_ColorId; | 1376 | : uiInputText_ColorId; |
1293 | /* TODO: If buffered, just draw the buffered copy. */ | 1377 | /* If buffered, just draw the buffered copy. */ |
1294 | iConstForEach(Array, i, &d->lines) { | 1378 | if (d->buffered && !isFocused) { //&& !isFocused/* && !isHint*/) { |
1295 | const iInputLine *line = i.value; | 1379 | /* Most input widgets will use this, since only one is focused at a time. */ |
1296 | const iBool isLast = index_ArrayConstIterator(&i) == size_Array(&d->lines) - 1; | 1380 | draw_TextBuf(d->buffered, topLeft_Rect(contentBounds), white_ColorId); |
1297 | const iInputLine *nextLine = isLast ? NULL : (line + 1); | 1381 | } |
1298 | const iRanges lineRange = { line->offset, | 1382 | else if (isHint) { |
1299 | nextLine ? nextLine->offset : size_Array(&d->text) }; | 1383 | drawRange_Text(d->font, topLeft_Rect(contentBounds), uiAnnotation_ColorId, |
1300 | if (isFocused && !isEmpty_Range(&d->mark)) { | 1384 | range_String(&d->hint)); |
1301 | /* Draw the selected range. */ | 1385 | } |
1302 | const iRanges mark = mark_InputWidget_(d); | 1386 | else { |
1303 | if (mark.start < lineRange.end && mark.end > lineRange.start) { | 1387 | iConstForEach(Array, i, &d->lines) { |
1304 | const int m1 = advanceN_Text(d->font, | 1388 | const iInputLine *line = i.value; |
1305 | cstr_String(&line->text), | 1389 | const iBool isLast = index_ArrayConstIterator(&i) == size_Array(&d->lines) - 1; |
1306 | iMax(lineRange.start, mark.start) - line->offset) | 1390 | const iInputLine *nextLine = isLast ? NULL : (line + 1); |
1307 | .x; | 1391 | const iRanges lineRange = { line->offset, |
1308 | const int m2 = advanceN_Text(d->font, | 1392 | nextLine ? nextLine->offset : size_Array(&d->text) }; |
1309 | cstr_String(&line->text), | 1393 | if (isFocused && !isEmpty_Range(&d->mark)) { |
1310 | iMin(lineRange.end, mark.end) - line->offset) | 1394 | /* Draw the selected range. */ |
1311 | .x; | 1395 | const iRanges mark = mark_InputWidget_(d); |
1312 | fillRect_Paint(&p, | 1396 | if (mark.start < lineRange.end && mark.end > lineRange.start) { |
1313 | (iRect){ addX_I2(drawPos, iMin(m1, m2)), | 1397 | const int m1 = advanceN_Text(d->font, |
1314 | init_I2(iMax(gap_UI / 3, iAbs(m2 - m1)), lineHeight_Text(d->font)) }, | 1398 | cstr_String(&line->text), |
1315 | uiMarked_ColorId); | 1399 | iMax(lineRange.start, mark.start) - line->offset) |
1400 | .x; | ||
1401 | const int m2 = advanceN_Text(d->font, | ||
1402 | cstr_String(&line->text), | ||
1403 | iMin(lineRange.end, mark.end) - line->offset) | ||
1404 | .x; | ||
1405 | fillRect_Paint(&p, | ||
1406 | (iRect){ addX_I2(drawPos, iMin(m1, m2)), | ||
1407 | init_I2(iMax(gap_UI / 3, iAbs(m2 - m1)), | ||
1408 | lineHeight_Text(d->font)) }, | ||
1409 | uiMarked_ColorId); | ||
1410 | } | ||
1316 | } | 1411 | } |
1412 | drawRange_Text(d->font, drawPos, fg, range_String(&line->text)); | ||
1413 | drawPos.y += lineHeight_Text(d->font); | ||
1317 | } | 1414 | } |
1318 | drawRange_Text(d->font, drawPos, fg, range_String(&line->text)); | ||
1319 | drawPos.y += lineHeight_Text(d->font); | ||
1320 | } | 1415 | } |
1321 | // if (d->buffered && !isFocused && !isHint) { | 1416 | // if (d->buffered && !isFocused && !isHint) { |
1322 | // /* Most input widgets will use this, since only one is focused at a time. */ | 1417 | // /* Most input widgets will use this, since only one is focused at a time. */ |
@@ -1378,13 +1473,7 @@ static void draw_InputWidget_(const iInputWidget *d) { | |||
1378 | drawChildren_Widget(w); | 1473 | drawChildren_Widget(w); |
1379 | } | 1474 | } |
1380 | 1475 | ||
1381 | //static void sizeChanged_InputWidget_(iInputWidget *d) { | ||
1382 | // printf("[InputWidget] %p: size changed, updating layout\n", d); | ||
1383 | // updateLinesAndResize_InputWidget_(d, iFalse); | ||
1384 | //} | ||
1385 | |||
1386 | iBeginDefineSubclass(InputWidget, Widget) | 1476 | iBeginDefineSubclass(InputWidget, Widget) |
1387 | .processEvent = (iAny *) processEvent_InputWidget_, | 1477 | .processEvent = (iAny *) processEvent_InputWidget_, |
1388 | .draw = (iAny *) draw_InputWidget_, | 1478 | .draw = (iAny *) draw_InputWidget_, |
1389 | // .sizeChanged = (iAny *) sizeChanged_InputWidget_, | ||
1390 | iEndDefineSubclass(InputWidget) | 1479 | iEndDefineSubclass(InputWidget) |
diff --git a/src/ui/labelwidget.c b/src/ui/labelwidget.c index a7aa6391..a940b0cb 100644 --- a/src/ui/labelwidget.c +++ b/src/ui/labelwidget.c | |||
@@ -28,6 +28,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
28 | #include "app.h" | 28 | #include "app.h" |
29 | #include "util.h" | 29 | #include "util.h" |
30 | #include "keys.h" | 30 | #include "keys.h" |
31 | #include "touch.h" | ||
31 | 32 | ||
32 | struct Impl_LabelWidget { | 33 | struct Impl_LabelWidget { |
33 | iWidget widget; | 34 | iWidget widget; |
@@ -46,6 +47,15 @@ struct Impl_LabelWidget { | |||
46 | } flags; | 47 | } flags; |
47 | }; | 48 | }; |
48 | 49 | ||
50 | static iBool isHover_LabelWidget_(const iLabelWidget *d) { | ||
51 | #if defined (iPlatformMobile) | ||
52 | if (!isHovering_Touch()) { | ||
53 | return iFalse; | ||
54 | } | ||
55 | #endif | ||
56 | return isHover_Widget(d); | ||
57 | } | ||
58 | |||
49 | static iInt2 padding_LabelWidget_(const iLabelWidget *d, int corner) { | 59 | static iInt2 padding_LabelWidget_(const iLabelWidget *d, int corner) { |
50 | const iWidget *w = constAs_Widget(d); | 60 | const iWidget *w = constAs_Widget(d); |
51 | const int64_t flags = flags_Widget(w); | 61 | const int64_t flags = flags_Widget(w); |
@@ -199,7 +209,7 @@ static void getColors_LabelWidget_(const iLabelWidget *d, int *bg, int *fg, int | |||
199 | if (startsWith_String(&d->label, "\v")) { | 209 | if (startsWith_String(&d->label, "\v")) { |
200 | colorEscape = cstr_String(&d->label)[1] - asciiBase_ColorEscape; /* TODO: can be two bytes long */ | 210 | colorEscape = cstr_String(&d->label)[1] - asciiBase_ColorEscape; /* TODO: can be two bytes long */ |
201 | } | 211 | } |
202 | if (isHover_Widget(w)) { | 212 | if (isHover_LabelWidget_(d)) { |
203 | if (isFrameless) { | 213 | if (isFrameless) { |
204 | *bg = uiBackgroundFramelessHover_ColorId; | 214 | *bg = uiBackgroundFramelessHover_ColorId; |
205 | *fg = uiTextFramelessHover_ColorId; | 215 | *fg = uiTextFramelessHover_ColorId; |
@@ -257,7 +267,7 @@ static void draw_LabelWidget_(const iLabelWidget *d) { | |||
257 | const int64_t flags = flags_Widget(w); | 267 | const int64_t flags = flags_Widget(w); |
258 | const iRect bounds = bounds_Widget(w); | 268 | const iRect bounds = bounds_Widget(w); |
259 | iRect rect = bounds; | 269 | iRect rect = bounds; |
260 | const iBool isHover = isHover_Widget(w); | 270 | const iBool isHover = isHover_LabelWidget_(d); |
261 | if (isButton) { | 271 | if (isButton) { |
262 | shrink_Rect(&rect, divi_I2(gap2_UI, 4)); | 272 | shrink_Rect(&rect, divi_I2(gap2_UI, 4)); |
263 | adjustEdges_Rect(&rect, gap_UI / 8, 0, -gap_UI / 8, 0); | 273 | adjustEdges_Rect(&rect, gap_UI / 8, 0, -gap_UI / 8, 0); |
diff --git a/src/ui/listwidget.c b/src/ui/listwidget.c index 95be44a0..a3406d48 100644 --- a/src/ui/listwidget.c +++ b/src/ui/listwidget.c | |||
@@ -414,7 +414,7 @@ static void draw_ListWidget_(const iListWidget *d) { | |||
414 | init_I2(blankWidth, d->itemHeight) }; | 414 | init_I2(blankWidth, d->itemHeight) }; |
415 | iConstForEach(IntSet, v, &d->invalidItems) { | 415 | iConstForEach(IntSet, v, &d->invalidItems) { |
416 | const size_t index = *v.value; | 416 | const size_t index = *v.value; |
417 | if (contains_Range(&drawItems, index)) { | 417 | if (contains_Range(&drawItems, index) && index < size_PtrArray(&d->items)) { |
418 | const iListItem *item = constAt_PtrArray(&d->items, index); | 418 | const iListItem *item = constAt_PtrArray(&d->items, index); |
419 | const iRect itemRect = { init_I2(0, index * d->itemHeight - buf->origin), | 419 | const iRect itemRect = { init_I2(0, index * d->itemHeight - buf->origin), |
420 | init_I2(d->visBuf->texSize.x, d->itemHeight) }; | 420 | init_I2(d->visBuf->texSize.x, d->itemHeight) }; |
diff --git a/src/ui/lookupwidget.c b/src/ui/lookupwidget.c index b06523f9..3eafd4bd 100644 --- a/src/ui/lookupwidget.c +++ b/src/ui/lookupwidget.c | |||
@@ -662,7 +662,7 @@ static iBool processEvent_LookupWidget_(iLookupWidget *d, const SDL_Event *ev) { | |||
662 | setPos_Widget(w, windowToLocal_Widget(w, bottomLeft_Rect(bounds_Widget(url)))); | 662 | setPos_Widget(w, windowToLocal_Widget(w, bottomLeft_Rect(bounds_Widget(url)))); |
663 | #if defined (iPlatformAppleMobile) | 663 | #if defined (iPlatformAppleMobile) |
664 | /* Adjust height based on keyboard size. */ { | 664 | /* Adjust height based on keyboard size. */ { |
665 | w->rect.size.y = visibleRootSize_Window(window).y - top_Rect(bounds_Widget(w)); | 665 | w->rect.size.y = visibleSize_Root(root).y - top_Rect(bounds_Widget(w)); |
666 | if (deviceType_App() == phone_AppDeviceType) { | 666 | if (deviceType_App() == phone_AppDeviceType) { |
667 | float l, r; | 667 | float l, r; |
668 | safeAreaInsets_iOS(&l, NULL, &r, NULL); | 668 | safeAreaInsets_iOS(&l, NULL, &r, NULL); |
diff --git a/src/ui/mobile.c b/src/ui/mobile.c new file mode 100644 index 00000000..1f5e9758 --- /dev/null +++ b/src/ui/mobile.c | |||
@@ -0,0 +1,795 @@ | |||
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 "mobile.h" | ||
24 | |||
25 | #include "app.h" | ||
26 | #include "command.h" | ||
27 | #include "defs.h" | ||
28 | #include "inputwidget.h" | ||
29 | #include "labelwidget.h" | ||
30 | #include "root.h" | ||
31 | #include "text.h" | ||
32 | #include "widget.h" | ||
33 | #include "window.h" | ||
34 | |||
35 | #if defined (iPlatformAppleMobile) | ||
36 | # include "ios.h" | ||
37 | #endif | ||
38 | |||
39 | static iBool useMobileSheetLayout_(void) { | ||
40 | return deviceType_App() != desktop_AppDeviceType; | ||
41 | } | ||
42 | |||
43 | static iBool isSideBySideLayout_(void) { | ||
44 | if (deviceType_App() == phone_AppDeviceType) { | ||
45 | return isLandscape_App(); | ||
46 | } | ||
47 | return numRoots_Window(get_Window()) == 1; | ||
48 | } | ||
49 | |||
50 | static enum iFontId labelFont_(void) { | ||
51 | return deviceType_App() == phone_AppDeviceType ? defaultBig_FontId : defaultMedium_FontId; | ||
52 | } | ||
53 | |||
54 | static enum iFontId labelBoldFont_(void) { | ||
55 | return deviceType_App() == phone_AppDeviceType ? defaultBigBold_FontId : defaultMediumBold_FontId; | ||
56 | } | ||
57 | |||
58 | static void updatePanelSheetMetrics_(iWidget *sheet) { | ||
59 | iWidget *navi = findChild_Widget(sheet, "panel.navi"); | ||
60 | iWidget *naviPad = child_Widget(navi, 0); | ||
61 | int naviHeight = lineHeight_Text(labelFont_()) + 4 * gap_UI; | ||
62 | #if defined (iPlatformAppleMobile) | ||
63 | float left, right, top, bottom; | ||
64 | safeAreaInsets_iOS(&left, &top, &right, &bottom); | ||
65 | setPadding_Widget(sheet, left, 0, right, 0); | ||
66 | navi->rect.pos = init_I2(left, top); | ||
67 | iConstForEach(PtrArray, i, findChildren_Widget(sheet, "panel.toppad")) { | ||
68 | iWidget *pad = *i.value; | ||
69 | setFixedSize_Widget(pad, init1_I2(naviHeight)); | ||
70 | } | ||
71 | #endif | ||
72 | setFixedSize_Widget(navi, init_I2(-1, naviHeight)); | ||
73 | } | ||
74 | |||
75 | static iWidget *findDetailStack_(iWidget *topPanel) { | ||
76 | return findChild_Widget(parent_Widget(topPanel), "detailstack"); | ||
77 | } | ||
78 | |||
79 | static void unselectAllPanelButtons_(iWidget *topPanel) { | ||
80 | iForEach(ObjectList, i, children_Widget(topPanel)) { | ||
81 | if (isInstance_Object(i.object, &Class_LabelWidget)) { | ||
82 | iLabelWidget *label = i.object; | ||
83 | if (!cmp_String(command_LabelWidget(label), "panel.open")) { | ||
84 | setFlags_Widget(i.object, selected_WidgetFlag, iFalse); | ||
85 | } | ||
86 | } | ||
87 | } | ||
88 | } | ||
89 | |||
90 | static iBool mainDetailSplitHandler_(iWidget *mainDetailSplit, const char *cmd) { | ||
91 | if (equal_Command(cmd, "window.resized")) { | ||
92 | const iBool isPortrait = (deviceType_App() == phone_AppDeviceType && isPortrait_App()); | ||
93 | const iRect safeRoot = safeRect_Root(mainDetailSplit->root); | ||
94 | setPos_Widget(mainDetailSplit, topLeft_Rect(safeRoot)); | ||
95 | setFixedSize_Widget(mainDetailSplit, safeRoot.size); | ||
96 | iWidget * sheet = parent_Widget(mainDetailSplit); | ||
97 | iWidget * navi = findChild_Widget(sheet, "panel.navi"); | ||
98 | iWidget * detailStack = findChild_Widget(mainDetailSplit, "detailstack"); | ||
99 | const size_t numPanels = childCount_Widget(detailStack); | ||
100 | const iBool isSideBySide = isSideBySideLayout_() && numPanels > 0; | ||
101 | setFlags_Widget(mainDetailSplit, arrangeHorizontal_WidgetFlag, isSideBySide); | ||
102 | setFlags_Widget(detailStack, expand_WidgetFlag, isSideBySide); | ||
103 | setFlags_Widget(detailStack, hidden_WidgetFlag, numPanels == 0); | ||
104 | iWidget *topPanel = findChild_Widget(mainDetailSplit, "panel.top"); | ||
105 | const int pad = isPortrait ? 0 : 3 * gap_UI; | ||
106 | if (isSideBySide) { | ||
107 | iAssert(topPanel); | ||
108 | topPanel->rect.size.x = (deviceType_App() == phone_AppDeviceType ? | ||
109 | safeRoot.size.x * 2 / 5 : (safeRoot.size.x / 3)); | ||
110 | } | ||
111 | if (deviceType_App() == tablet_AppDeviceType) { | ||
112 | setPadding_Widget(topPanel, pad, 0, pad, pad); | ||
113 | if (numPanels == 0) { | ||
114 | setFlags_Widget(sheet, centerHorizontal_WidgetFlag, iTrue); | ||
115 | const int sheetWidth = iMin(safeRoot.size.x, safeRoot.size.y); | ||
116 | mainDetailSplit->rect.size.x = sheetWidth; | ||
117 | setFixedSize_Widget(sheet, init_I2(sheetWidth, -1)); | ||
118 | setFixedSize_Widget(navi, init_I2(sheetWidth, -1)); | ||
119 | } | ||
120 | } | ||
121 | iForEach(ObjectList, i, children_Widget(detailStack)) { | ||
122 | iWidget *panel = i.object; | ||
123 | setFlags_Widget(panel, edgeDraggable_WidgetFlag, !isSideBySide); | ||
124 | if (isSideBySide) { | ||
125 | setVisualOffset_Widget(panel, 0, 0, 0); | ||
126 | } | ||
127 | setPadding_Widget(panel, pad, 0, pad, pad); | ||
128 | } | ||
129 | arrange_Widget(mainDetailSplit); | ||
130 | } | ||
131 | return iFalse; | ||
132 | } | ||
133 | |||
134 | static iBool topPanelHandler_(iWidget *topPanel, const char *cmd) { | ||
135 | const iBool isPortrait = !isSideBySideLayout_(); | ||
136 | if (equal_Command(cmd, "panel.open")) { | ||
137 | iWidget *button = pointer_Command(cmd); | ||
138 | iWidget *panel = userData_Object(button); | ||
139 | // openMenu_Widget(panel, innerToWindow_Widget(panel, zero_I2())); | ||
140 | // setFlags_Widget(panel, hidden_WidgetFlag, iFalse); | ||
141 | unselectAllPanelButtons_(topPanel); | ||
142 | iForEach(ObjectList, i, children_Widget(findDetailStack_(topPanel))) { | ||
143 | iWidget *child = i.object; | ||
144 | setFlags_Widget(child, hidden_WidgetFlag | disabled_WidgetFlag, child != panel); | ||
145 | /* Animate the current panel in. */ | ||
146 | if (child == panel && isPortrait) { | ||
147 | setupSheetTransition_Mobile(panel, iTrue); | ||
148 | } | ||
149 | } | ||
150 | setFlags_Widget(button, selected_WidgetFlag, iTrue); | ||
151 | return iTrue; | ||
152 | } | ||
153 | if (equal_Command(cmd, "mouse.clicked") && arg_Command(cmd) && | ||
154 | argLabel_Command(cmd, "button") == SDL_BUTTON_X1) { | ||
155 | postCommand_App("panel.close"); | ||
156 | return iTrue; | ||
157 | } | ||
158 | if (equal_Command(cmd, "panel.close")) { | ||
159 | iBool wasClosed = iFalse; | ||
160 | if (isPortrait) { | ||
161 | iForEach(ObjectList, i, children_Widget(findDetailStack_(topPanel))) { | ||
162 | iWidget *child = i.object; | ||
163 | if (!cmp_String(id_Widget(child), "panel") && isVisible_Widget(child)) { | ||
164 | // closeMenu_Widget(child); | ||
165 | setupSheetTransition_Mobile(child, iFalse); | ||
166 | setFlags_Widget(child, hidden_WidgetFlag | disabled_WidgetFlag, iTrue); | ||
167 | setFocus_Widget(NULL); | ||
168 | updateTextCStr_LabelWidget(findWidget_App("panel.back"), "Back"); | ||
169 | wasClosed = iTrue; | ||
170 | } | ||
171 | } | ||
172 | } | ||
173 | unselectAllPanelButtons_(topPanel); | ||
174 | if (!wasClosed) { | ||
175 | postCommand_App("prefs.dismiss"); | ||
176 | } | ||
177 | return iTrue; | ||
178 | } | ||
179 | if (equal_Command(cmd, "document.changed")) { | ||
180 | postCommand_App("prefs.dismiss"); | ||
181 | return iFalse; | ||
182 | } | ||
183 | if (equal_Command(cmd, "window.resized")) { | ||
184 | // sheet > mdsplit > panel.top | ||
185 | updatePanelSheetMetrics_(parent_Widget(parent_Widget(topPanel))); | ||
186 | } | ||
187 | return iFalse; | ||
188 | } | ||
189 | |||
190 | static iBool isTwoColumnPage_(iWidget *d) { | ||
191 | if (cmp_String(id_Widget(d), "dialogbuttons") == 0 || | ||
192 | cmp_String(id_Widget(d), "prefs.tabs") == 0) { | ||
193 | return iFalse; | ||
194 | } | ||
195 | if (class_Widget(d) == &Class_Widget && childCount_Widget(d) == 2) { | ||
196 | return class_Widget(child_Widget(d, 0)) == &Class_Widget && | ||
197 | class_Widget(child_Widget(d, 1)) == &Class_Widget; | ||
198 | } | ||
199 | return iFalse; | ||
200 | } | ||
201 | |||
202 | static iBool isOmittedPref_(const iString *id) { | ||
203 | static const char *omittedPrefs[] = { | ||
204 | "prefs.smoothscroll", | ||
205 | "prefs.imageloadscroll", | ||
206 | "prefs.pinsplit", | ||
207 | "prefs.retainwindow", | ||
208 | "prefs.ca.file", | ||
209 | "prefs.ca.path", | ||
210 | }; | ||
211 | iForIndices(i, omittedPrefs) { | ||
212 | if (cmp_String(id, omittedPrefs[i]) == 0) { | ||
213 | return iTrue; | ||
214 | } | ||
215 | } | ||
216 | return iFalse; | ||
217 | } | ||
218 | |||
219 | enum iPrefsElement { | ||
220 | panelTitle_PrefsElement, | ||
221 | heading_PrefsElement, | ||
222 | toggle_PrefsElement, | ||
223 | dropdown_PrefsElement, | ||
224 | radioButton_PrefsElement, | ||
225 | textInput_PrefsElement, | ||
226 | }; | ||
227 | |||
228 | static iAnyObject *addPanelChild_(iWidget *panel, iAnyObject *child, int64_t flags, | ||
229 | enum iPrefsElement elementType, | ||
230 | enum iPrefsElement precedingElementType) { | ||
231 | /* Erase redundant/unused headings. */ | ||
232 | if (precedingElementType == heading_PrefsElement && | ||
233 | (!child || (elementType == heading_PrefsElement || elementType == radioButton_PrefsElement))) { | ||
234 | iRelease(removeChild_Widget(panel, lastChild_Widget(panel))); | ||
235 | if (!cmp_String(id_Widget(constAs_Widget(lastChild_Widget(panel))), "padding")) { | ||
236 | iRelease(removeChild_Widget(panel, lastChild_Widget(panel))); | ||
237 | } | ||
238 | } | ||
239 | if (child) { | ||
240 | /* Insert padding between different element types. */ | ||
241 | if (precedingElementType != panelTitle_PrefsElement) { | ||
242 | if (elementType == heading_PrefsElement || | ||
243 | (elementType == toggle_PrefsElement && | ||
244 | precedingElementType != toggle_PrefsElement && | ||
245 | precedingElementType != heading_PrefsElement) || | ||
246 | (elementType == dropdown_PrefsElement && | ||
247 | precedingElementType != dropdown_PrefsElement && | ||
248 | precedingElementType != heading_PrefsElement) || | ||
249 | (elementType == textInput_PrefsElement && | ||
250 | precedingElementType != textInput_PrefsElement && | ||
251 | precedingElementType != heading_PrefsElement)) { | ||
252 | addChild_Widget(panel, iClob(makePadding_Widget(lineHeight_Text(labelFont_())))); | ||
253 | } | ||
254 | } | ||
255 | if ((elementType == toggle_PrefsElement && precedingElementType != toggle_PrefsElement) || | ||
256 | (elementType == textInput_PrefsElement && precedingElementType != textInput_PrefsElement)) { | ||
257 | flags |= borderTop_WidgetFlag; | ||
258 | } | ||
259 | return addChildFlags_Widget(panel, child, flags); | ||
260 | } | ||
261 | return NULL; | ||
262 | } | ||
263 | |||
264 | static void stripTrailingColon_(iLabelWidget *label) { | ||
265 | const iString *text = text_LabelWidget(label); | ||
266 | if (endsWith_String(text, ":")) { | ||
267 | iString *mod = copy_String(text); | ||
268 | removeEnd_String(mod, 1); | ||
269 | updateText_LabelWidget(label, mod); | ||
270 | delete_String(mod); | ||
271 | } | ||
272 | } | ||
273 | |||
274 | static iLabelWidget *makePanelButton_(const char *text, const char *command) { | ||
275 | iLabelWidget *btn = new_LabelWidget(text, command); | ||
276 | setFlags_Widget(as_Widget(btn), | ||
277 | borderBottom_WidgetFlag | alignLeft_WidgetFlag | | ||
278 | frameless_WidgetFlag | extraPadding_WidgetFlag, | ||
279 | iTrue); | ||
280 | checkIcon_LabelWidget(btn); | ||
281 | setFont_LabelWidget(btn, labelFont_()); | ||
282 | setTextColor_LabelWidget(btn, uiTextStrong_ColorId); | ||
283 | setBackgroundColor_Widget(as_Widget(btn), uiBackgroundSidebar_ColorId); | ||
284 | return btn; | ||
285 | } | ||
286 | |||
287 | static iWidget *makeValuePadding_(iWidget *value) { | ||
288 | iInputWidget *input = isInstance_Object(value, &Class_InputWidget) ? (iInputWidget *) value : NULL; | ||
289 | if (input) { | ||
290 | setFont_InputWidget(input, labelFont_()); | ||
291 | setContentPadding_InputWidget(input, 3 * gap_UI, 3 * gap_UI); | ||
292 | } | ||
293 | iWidget *pad = new_Widget(); | ||
294 | setBackgroundColor_Widget(pad, uiBackgroundSidebar_ColorId); | ||
295 | setPadding_Widget(pad, 0, 1 * gap_UI, 0, 1 * gap_UI); | ||
296 | addChild_Widget(pad, iClob(value)); | ||
297 | setFlags_Widget(pad, | ||
298 | borderBottom_WidgetFlag | | ||
299 | arrangeVertical_WidgetFlag | | ||
300 | resizeToParentWidth_WidgetFlag | | ||
301 | resizeWidthOfChildren_WidgetFlag | | ||
302 | arrangeHeight_WidgetFlag, | ||
303 | iTrue); | ||
304 | return pad; | ||
305 | } | ||
306 | |||
307 | static iWidget *makeValuePaddingWithHeading_(iLabelWidget *heading, iWidget *value) { | ||
308 | iWidget *div = new_Widget(); | ||
309 | setFlags_Widget(div, | ||
310 | borderBottom_WidgetFlag | arrangeHeight_WidgetFlag | | ||
311 | resizeWidthOfChildren_WidgetFlag | | ||
312 | arrangeHorizontal_WidgetFlag, iTrue); | ||
313 | setBackgroundColor_Widget(div, uiBackgroundSidebar_ColorId); | ||
314 | setPadding_Widget(div, gap_UI, gap_UI, 4 * gap_UI, gap_UI); | ||
315 | addChildFlags_Widget(div, iClob(heading), 0); | ||
316 | //setFixedSize_Widget(as_Widget(heading), init_I2(-1, height_Widget(value))); | ||
317 | setFont_LabelWidget(heading, labelFont_()); | ||
318 | setTextColor_LabelWidget(heading, uiTextStrong_ColorId); | ||
319 | if (isInstance_Object(value, &Class_InputWidget)) { | ||
320 | addChildFlags_Widget(div, iClob(value), expand_WidgetFlag); | ||
321 | } | ||
322 | else if (isInstance_Object(value, &Class_LabelWidget) && | ||
323 | cmp_String(command_LabelWidget((iLabelWidget *) value), "toggle")) { | ||
324 | addChildFlags_Widget(div, iClob(value), expand_WidgetFlag); | ||
325 | /* TODO: This doesn't work? */ | ||
326 | // setCommand_LabelWidget(heading, | ||
327 | // collectNewFormat_String("!%s ptr:%p", | ||
328 | // cstr_String(command_LabelWidget((iLabelWidget *) value)), | ||
329 | // value)); | ||
330 | } | ||
331 | else { | ||
332 | addChildFlags_Widget(div, iClob(new_Widget()), expand_WidgetFlag); | ||
333 | addChild_Widget(div, iClob(value)); | ||
334 | } | ||
335 | return div; | ||
336 | } | ||
337 | |||
338 | static iWidget *addChildPanel_(iWidget *parent, iLabelWidget *panelButton, | ||
339 | const iString *titleText) { | ||
340 | iWidget *panel = new_Widget(); | ||
341 | setId_Widget(panel, "panel"); | ||
342 | setUserData_Object(panelButton, panel); | ||
343 | setBackgroundColor_Widget(panel, uiBackground_ColorId); | ||
344 | setId_Widget(addChild_Widget(panel, iClob(makePadding_Widget(0))), "panel.toppad"); | ||
345 | if (titleText) { | ||
346 | iLabelWidget *title = | ||
347 | addChildFlags_Widget(panel, | ||
348 | iClob(new_LabelWidget(cstr_String(titleText), NULL)), | ||
349 | alignLeft_WidgetFlag | frameless_WidgetFlag); | ||
350 | setFont_LabelWidget(title, uiLabelLargeBold_FontId); | ||
351 | setTextColor_LabelWidget(title, uiHeading_ColorId); | ||
352 | } | ||
353 | addChildFlags_Widget(parent, | ||
354 | iClob(panel), | ||
355 | focusRoot_WidgetFlag | hidden_WidgetFlag | disabled_WidgetFlag | | ||
356 | arrangeVertical_WidgetFlag | resizeWidthOfChildren_WidgetFlag | | ||
357 | arrangeHeight_WidgetFlag | overflowScrollable_WidgetFlag | | ||
358 | drawBackgroundToBottom_WidgetFlag | | ||
359 | horizontalOffset_WidgetFlag | commandOnClick_WidgetFlag); | ||
360 | return panel; | ||
361 | } | ||
362 | |||
363 | void finalizeSheet_Mobile(iWidget *sheet) { | ||
364 | /* The sheet contents are completely rearranged and restyled on a phone. | ||
365 | We'll set up a linear fullscreen arrangement of the widgets. Sheets are already | ||
366 | scrollable so they can be taller than the display. In hindsight, it may have been | ||
367 | easier to create phone versions of each dialog, but at least this works with any | ||
368 | future changes to the UI (..."works"). At least this way it is possible to enforce | ||
369 | a consistent styling. */ | ||
370 | if (useMobileSheetLayout_() && parent_Widget(sheet) == root_Widget(sheet)) { | ||
371 | if (~flags_Widget(sheet) & keepOnTop_WidgetFlag) { | ||
372 | /* Already finalized. */ | ||
373 | arrange_Widget(sheet); | ||
374 | postRefresh_App(); | ||
375 | return; | ||
376 | } | ||
377 | /* Landscape Layout Portrait Layout | ||
378 | |||
379 | ┌─────────┬──────Detail─Stack─────┐ ┌─────────┬ ─ ─ ─ ─ ┐ | ||
380 | │ │┌───────────────────┐ │ │ │Detail | ||
381 | │ ││┌──────────────────┴┐ │ │ │Stack │ | ||
382 | │ │││┌──────────────────┴┐│ │ │┌──────┐ | ||
383 | │ ││││ ││ │ ││┌─────┴┐│ | ||
384 | │ ││││ ││ │ │││ │ | ||
385 | │Top Panel││││ ││ │Top Panel│││ ││ | ||
386 | │ ││││ Panels ││ │ │││Panels│ | ||
387 | │ ││││ ││ │ │││ ││ | ||
388 | │ │└┤│ ││ │ │││ │ | ||
389 | │ │ └┤ ││ │ │└┤ ││ | ||
390 | │ │ └───────────────────┘│ │ │ └──────┘ | ||
391 | └─────────┴───────────────────────┘ └─────────┴ ─ ─ ─ ─ ┘ | ||
392 | offscreen | ||
393 | */ | ||
394 | /* Modify the top sheet to act as a fullscreen background. */ | ||
395 | setPadding1_Widget(sheet, 0); | ||
396 | setBackgroundColor_Widget(sheet, uiBackground_ColorId); | ||
397 | setFlags_Widget(sheet, | ||
398 | keepOnTop_WidgetFlag | | ||
399 | parentCannotResize_WidgetFlag | | ||
400 | arrangeSize_WidgetFlag | | ||
401 | centerHorizontal_WidgetFlag | | ||
402 | arrangeVertical_WidgetFlag | | ||
403 | arrangeHorizontal_WidgetFlag | | ||
404 | overflowScrollable_WidgetFlag, | ||
405 | iFalse); | ||
406 | setFlags_Widget(sheet, | ||
407 | frameless_WidgetFlag | | ||
408 | //resizeWidthOfChildren_WidgetFlag | | ||
409 | edgeDraggable_WidgetFlag | | ||
410 | commandOnClick_WidgetFlag, | ||
411 | iTrue); | ||
412 | iPtrArray * contents = collect_PtrArray(new_PtrArray()); /* two-column pages */ | ||
413 | iPtrArray * panelButtons = collect_PtrArray(new_PtrArray()); | ||
414 | iWidget * prefsTabs = findChild_Widget(sheet, "prefs.tabs"); | ||
415 | iWidget * dialogHeading = (prefsTabs ? NULL : child_Widget(sheet, 0)); | ||
416 | const iBool isPrefs = (prefsTabs != NULL); | ||
417 | const int64_t panelButtonFlags = borderBottom_WidgetFlag | alignLeft_WidgetFlag | | ||
418 | frameless_WidgetFlag | extraPadding_WidgetFlag; | ||
419 | iWidget *mainDetailSplit = makeHDiv_Widget(); | ||
420 | setCommandHandler_Widget(mainDetailSplit, mainDetailSplitHandler_); | ||
421 | setFlags_Widget(mainDetailSplit, resizeHeightOfChildren_WidgetFlag, iFalse); | ||
422 | setId_Widget(mainDetailSplit, "mdsplit"); | ||
423 | iWidget *topPanel = new_Widget(); { | ||
424 | setId_Widget(topPanel, "panel.top"); | ||
425 | setCommandHandler_Widget(topPanel, topPanelHandler_); | ||
426 | setFlags_Widget(topPanel, | ||
427 | arrangeVertical_WidgetFlag | | ||
428 | resizeWidthOfChildren_WidgetFlag | | ||
429 | arrangeHeight_WidgetFlag | | ||
430 | overflowScrollable_WidgetFlag | | ||
431 | commandOnClick_WidgetFlag, | ||
432 | iTrue); | ||
433 | addChild_Widget(mainDetailSplit, iClob(topPanel)); | ||
434 | } | ||
435 | iWidget *detailStack = new_Widget(); { | ||
436 | setId_Widget(detailStack, "detailstack"); | ||
437 | setFlags_Widget(detailStack, collapse_WidgetFlag | resizeWidthOfChildren_WidgetFlag, iTrue); | ||
438 | addChild_Widget(mainDetailSplit, iClob(detailStack)); | ||
439 | } | ||
440 | addChild_Widget(topPanel, iClob(makePadding_Widget(lineHeight_Text(labelFont_())))); | ||
441 | /* Slide top panel with detail panels. */ { | ||
442 | setFlags_Widget(topPanel, refChildrenOffset_WidgetFlag, iTrue); | ||
443 | topPanel->offsetRef = detailStack; | ||
444 | } | ||
445 | if (prefsTabs) { | ||
446 | iRelease(removeChild_Widget(sheet, child_Widget(sheet, 0))); /* heading */ | ||
447 | iRelease(removeChild_Widget(sheet, findChild_Widget(sheet, "dialogbuttons"))); | ||
448 | /* Pull out the pages and make them panels. */ | ||
449 | iWidget *pages = findChild_Widget(prefsTabs, "tabs.pages"); | ||
450 | size_t pageCount = tabCount_Widget(prefsTabs); | ||
451 | for (size_t i = 0; i < pageCount; i++) { | ||
452 | iString *text = copy_String(text_LabelWidget(tabPageButton_Widget(prefsTabs, tabPage_Widget(prefsTabs, 0)))); | ||
453 | iWidget *page = removeTabPage_Widget(prefsTabs, 0); | ||
454 | iWidget *pageContent = child_Widget(page, 1); /* surrounded by padding widgets */ | ||
455 | pushBack_PtrArray(contents, ref_Object(pageContent)); | ||
456 | iLabelWidget *panelButton; | ||
457 | pushBack_PtrArray(panelButtons, | ||
458 | addChildFlags_Widget(topPanel, | ||
459 | iClob(panelButton = makePanelButton_( | ||
460 | i == 1 ? "${heading.prefs.userinterface}" : cstr_String(text), | ||
461 | "panel.open")), | ||
462 | (i == 0 ? borderTop_WidgetFlag : 0) | | ||
463 | chevron_WidgetFlag)); | ||
464 | const iChar icons[] = { | ||
465 | 0x02699, /* gear */ | ||
466 | 0x1f4f1, /* mobile phone */ | ||
467 | 0x1f3a8, /* palette */ | ||
468 | 0x1f523, | ||
469 | 0x1f5a7, /* computer network */ | ||
470 | }; | ||
471 | setIcon_LabelWidget(panelButton, icons[i]); | ||
472 | // setFont_LabelWidget(panelButton, labelFont_()); | ||
473 | // setBackgroundColor_Widget(as_Widget(panelButton), uiBackgroundSidebar_ColorId); | ||
474 | iRelease(page); | ||
475 | delete_String(text); | ||
476 | } | ||
477 | destroy_Widget(prefsTabs); | ||
478 | } | ||
479 | iForEach(ObjectList, i, children_Widget(sheet)) { | ||
480 | iWidget *child = i.object; | ||
481 | if (isTwoColumnPage_(child)) { | ||
482 | pushBack_PtrArray(contents, removeChild_Widget(sheet, child)); | ||
483 | } | ||
484 | else { | ||
485 | removeChild_Widget(sheet, child); | ||
486 | addChild_Widget(topPanel, child); | ||
487 | iRelease(child); | ||
488 | } | ||
489 | } | ||
490 | const iBool useSlidePanels = (size_PtrArray(contents) == size_PtrArray(panelButtons)); | ||
491 | addChild_Widget(sheet, iClob(mainDetailSplit)); | ||
492 | iForEach(PtrArray, j, contents) { | ||
493 | iWidget *owner = topPanel; | ||
494 | if (useSlidePanels) { | ||
495 | /* Create a new child panel. */ | ||
496 | iLabelWidget *button = at_PtrArray(panelButtons, index_PtrArrayIterator(&j)); | ||
497 | owner = addChildPanel_(detailStack, button, | ||
498 | collect_String(upper_String(text_LabelWidget(button)))); | ||
499 | } | ||
500 | iWidget *pageContent = j.ptr; | ||
501 | iWidget *headings = child_Widget(pageContent, 0); | ||
502 | iWidget *values = child_Widget(pageContent, 1); | ||
503 | enum iPrefsElement prevElement = panelTitle_PrefsElement; | ||
504 | /* Identify the types of controls in the dialog and restyle/organize them. */ | ||
505 | while (!isEmpty_ObjectList(children_Widget(headings))) { | ||
506 | iWidget *heading = child_Widget(headings, 0); | ||
507 | iWidget *value = child_Widget(values, 0); | ||
508 | removeChild_Widget(headings, heading); | ||
509 | removeChild_Widget(values, value); | ||
510 | /* Can we ignore these widgets? */ | ||
511 | if (isOmittedPref_(id_Widget(value)) || | ||
512 | (class_Widget(heading) == &Class_Widget && | ||
513 | class_Widget(value) == &Class_Widget) /* just padding */) { | ||
514 | iRelease(heading); | ||
515 | iRelease(value); | ||
516 | continue; | ||
517 | } | ||
518 | enum iPrefsElement element = toggle_PrefsElement; | ||
519 | iLabelWidget *headingLabel = NULL; | ||
520 | iLabelWidget *valueLabel = NULL; | ||
521 | iInputWidget *valueInput = NULL; | ||
522 | const iBool isMenuButton = findChild_Widget(value, "menu") != NULL; | ||
523 | if (isInstance_Object(heading, &Class_LabelWidget)) { | ||
524 | headingLabel = (iLabelWidget *) heading; | ||
525 | stripTrailingColon_(headingLabel); | ||
526 | } | ||
527 | if (isInstance_Object(value, &Class_LabelWidget)) { | ||
528 | valueLabel = (iLabelWidget *) value; | ||
529 | setFont_LabelWidget(valueLabel, labelFont_()); | ||
530 | } | ||
531 | if (isInstance_Object(value, &Class_InputWidget)) { | ||
532 | valueInput = (iInputWidget *) value; | ||
533 | setFlags_Widget(value, borderBottom_WidgetFlag, iFalse); | ||
534 | element = textInput_PrefsElement; | ||
535 | } | ||
536 | if (childCount_Widget(value) >= 2) { | ||
537 | if (isInstance_Object(child_Widget(value, 0), &Class_InputWidget)) { | ||
538 | element = textInput_PrefsElement; | ||
539 | setPadding_Widget(value, 0, 0, gap_UI, 0); | ||
540 | valueInput = child_Widget(value, 0); | ||
541 | } | ||
542 | } | ||
543 | if (valueInput) { | ||
544 | setFont_InputWidget(valueInput, labelFont_()); | ||
545 | setContentPadding_InputWidget(valueInput, 3 * gap_UI, 0); | ||
546 | } | ||
547 | /* Toggles have the button on the right. */ | ||
548 | if (valueLabel && cmp_String(command_LabelWidget(valueLabel), "toggle") == 0) { | ||
549 | element = toggle_PrefsElement; | ||
550 | addPanelChild_(owner, | ||
551 | iClob(makeValuePaddingWithHeading_(headingLabel, value)), | ||
552 | 0, | ||
553 | element, | ||
554 | prevElement); | ||
555 | } | ||
556 | else if (valueLabel && isEmpty_String(text_LabelWidget(valueLabel))) { | ||
557 | element = heading_PrefsElement; | ||
558 | iRelease(value); | ||
559 | addPanelChild_(owner, iClob(heading), 0, element, prevElement); | ||
560 | setFont_LabelWidget(headingLabel, uiLabel_FontId); | ||
561 | } | ||
562 | else if (isMenuButton) { | ||
563 | element = dropdown_PrefsElement; | ||
564 | setFlags_Widget(value, | ||
565 | alignRight_WidgetFlag | noBackground_WidgetFlag | | ||
566 | frameless_WidgetFlag, iTrue); | ||
567 | setFlags_Widget(value, alignLeft_WidgetFlag, iFalse); | ||
568 | iWidget *pad = addPanelChild_(owner, iClob(makeValuePaddingWithHeading_(headingLabel, value)), 0, | ||
569 | element, prevElement); | ||
570 | pad->padding[2] = gap_UI; | ||
571 | } | ||
572 | else if (valueInput) { | ||
573 | addPanelChild_(owner, iClob(makeValuePaddingWithHeading_(headingLabel, value)), 0, | ||
574 | element, prevElement); | ||
575 | } | ||
576 | else { | ||
577 | if (childCount_Widget(value) >= 2) { | ||
578 | element = radioButton_PrefsElement; | ||
579 | /* Always padding before radio buttons. */ | ||
580 | addChild_Widget(owner, iClob(makePadding_Widget(lineHeight_Text(labelFont_())))); | ||
581 | } | ||
582 | addChildFlags_Widget(owner, iClob(heading), borderBottom_WidgetFlag); | ||
583 | if (headingLabel) { | ||
584 | setTextColor_LabelWidget(headingLabel, uiSubheading_ColorId); | ||
585 | setText_LabelWidget(headingLabel, | ||
586 | collect_String(upper_String(text_LabelWidget(headingLabel)))); | ||
587 | } | ||
588 | addPanelChild_(owner, iClob(value), 0, element, prevElement); | ||
589 | /* Radio buttons expand to fill the space. */ | ||
590 | if (element == radioButton_PrefsElement) { | ||
591 | setBackgroundColor_Widget(value, uiBackgroundSidebar_ColorId); | ||
592 | setPadding_Widget(value, 4 * gap_UI, 2 * gap_UI, 4 * gap_UI, 2 * gap_UI); | ||
593 | setFlags_Widget(value, arrangeWidth_WidgetFlag, iFalse); | ||
594 | setFlags_Widget(value, | ||
595 | borderBottom_WidgetFlag | | ||
596 | resizeToParentWidth_WidgetFlag | | ||
597 | resizeWidthOfChildren_WidgetFlag, | ||
598 | iTrue); | ||
599 | iForEach(ObjectList, sub, children_Widget(value)) { | ||
600 | if (isInstance_Object(sub.object, &Class_LabelWidget)) { | ||
601 | iLabelWidget *opt = sub.object; | ||
602 | setFont_LabelWidget(opt, defaultMedium_FontId); | ||
603 | setFlags_Widget(as_Widget(opt), noBackground_WidgetFlag, iTrue); | ||
604 | } | ||
605 | } | ||
606 | } | ||
607 | } | ||
608 | prevElement = element; | ||
609 | } | ||
610 | addPanelChild_(owner, NULL, 0, 0, prevElement); | ||
611 | destroy_Widget(pageContent); | ||
612 | } | ||
613 | destroyPending_Root(sheet->root); | ||
614 | /* Additional elements for preferences. */ | ||
615 | if (isPrefs) { | ||
616 | addChild_Widget(topPanel, iClob(makePadding_Widget(lineHeight_Text(labelFont_())))); | ||
617 | iLabelWidget *aboutButton = addChildFlags_Widget(topPanel, | ||
618 | iClob(makePanelButton_(planet_Icon " ${menu.about}", "panel.open")), | ||
619 | chevron_WidgetFlag); | ||
620 | addChildFlags_Widget(topPanel, | ||
621 | iClob(makePanelButton_(info_Icon " ${menu.help}", "!open url:about:help")), | ||
622 | borderTop_WidgetFlag); | ||
623 | /* The About panel. */ { | ||
624 | iWidget *panel = addChildPanel_(detailStack, aboutButton, NULL); | ||
625 | iString *msg = collectNew_String(); | ||
626 | setCStr_String(msg, "Lagrange " LAGRANGE_APP_VERSION); | ||
627 | #if defined (iPlatformAppleMobile) | ||
628 | appendCStr_String(msg, " (" LAGRANGE_IOS_VERSION ")"); | ||
629 | #endif | ||
630 | addChildFlags_Widget(panel, iClob(new_LabelWidget(cstr_String(msg), NULL)), | ||
631 | frameless_WidgetFlag); | ||
632 | addChildFlags_Widget(panel, | ||
633 | iClob(makePanelButton_(globe_Icon " By @jk@skyjake.fi", | ||
634 | "!open url:https://skyjake.fi/@jk")), | ||
635 | borderTop_WidgetFlag); | ||
636 | addChildFlags_Widget(panel, | ||
637 | iClob(makePanelButton_(clock_Icon " ${menu.releasenotes}", | ||
638 | "!open url:about:version")), | ||
639 | 0); | ||
640 | addChildFlags_Widget(panel, | ||
641 | iClob(makePanelButton_(info_Icon " ${menu.aboutpages}", | ||
642 | "!open url:about:about")), | ||
643 | 0); | ||
644 | addChildFlags_Widget(panel, | ||
645 | iClob(makePanelButton_(bug_Icon " ${menu.debug}", | ||
646 | "!open url:about:debug")), | ||
647 | 0); | ||
648 | } | ||
649 | } | ||
650 | else { | ||
651 | setFlags_Widget(topPanel, overflowScrollable_WidgetFlag, iTrue); | ||
652 | /* Update heading style. */ | ||
653 | setFont_LabelWidget((iLabelWidget *) dialogHeading, uiLabelLargeBold_FontId); | ||
654 | setFlags_Widget(dialogHeading, alignLeft_WidgetFlag, iTrue); | ||
655 | } | ||
656 | if (findChild_Widget(sheet, "valueinput.prompt")) { | ||
657 | iWidget *prompt = findChild_Widget(sheet, "valueinput.prompt"); | ||
658 | setFlags_Widget(prompt, alignLeft_WidgetFlag, iTrue); | ||
659 | iInputWidget *input = findChild_Widget(sheet, "input"); | ||
660 | removeChild_Widget(parent_Widget(input), input); | ||
661 | addChild_Widget(topPanel, iClob(makeValuePadding_(as_Widget(input)))); | ||
662 | } | ||
663 | /* Top padding for each panel, to account for the overlaid navbar. */ { | ||
664 | setId_Widget(addChildPos_Widget(topPanel, | ||
665 | iClob(makePadding_Widget(0)), front_WidgetAddPos), | ||
666 | "panel.toppad"); | ||
667 | } | ||
668 | /* Navbar. */ { | ||
669 | iWidget *navi = new_Widget(); | ||
670 | setId_Widget(navi, "panel.navi"); | ||
671 | setBackgroundColor_Widget(navi, uiBackground_ColorId); | ||
672 | addChild_Widget(navi, iClob(makePadding_Widget(0))); | ||
673 | iLabelWidget *back = addChildFlags_Widget(navi, | ||
674 | iClob(new_LabelWidget(leftAngle_Icon " ${panel.back}", "panel.close")), | ||
675 | noBackground_WidgetFlag | frameless_WidgetFlag | | ||
676 | alignLeft_WidgetFlag | extraPadding_WidgetFlag); | ||
677 | checkIcon_LabelWidget(back); | ||
678 | setId_Widget(as_Widget(back), "panel.back"); | ||
679 | setFont_LabelWidget(back, labelFont_()); | ||
680 | if (!isPrefs) { | ||
681 | /* Pick up the dialog buttons for the navbar. */ | ||
682 | iWidget *buttons = findChild_Widget(sheet, "dialogbuttons"); | ||
683 | iLabelWidget *cancel = findMenuItem_Widget(buttons, "cancel"); | ||
684 | // if (!cancel) { | ||
685 | // cancel = findMenuItem_Widget(buttons, "translation.cancel"); | ||
686 | // } | ||
687 | if (cancel) { | ||
688 | updateText_LabelWidget(back, text_LabelWidget(cancel)); | ||
689 | setCommand_LabelWidget(back, command_LabelWidget(cancel)); | ||
690 | } | ||
691 | iLabelWidget *def = (iLabelWidget *) lastChild_Widget(buttons); | ||
692 | if (def && !cancel) { | ||
693 | updateText_LabelWidget(back, text_LabelWidget(def)); | ||
694 | setCommand_LabelWidget(back, command_LabelWidget(def)); | ||
695 | setFlags_Widget(as_Widget(back), alignLeft_WidgetFlag, iFalse); | ||
696 | setFlags_Widget(as_Widget(back), alignRight_WidgetFlag, iTrue); | ||
697 | setIcon_LabelWidget(back, 0); | ||
698 | setFont_LabelWidget(back, labelBoldFont_()); | ||
699 | } | ||
700 | else if (def != cancel) { | ||
701 | removeChild_Widget(buttons, def); | ||
702 | setFont_LabelWidget(def, labelBoldFont_()); | ||
703 | setFlags_Widget(as_Widget(def), | ||
704 | frameless_WidgetFlag | extraPadding_WidgetFlag | | ||
705 | noBackground_WidgetFlag, iTrue); | ||
706 | addChildFlags_Widget(as_Widget(back), iClob(def), moveToParentRightEdge_WidgetFlag); | ||
707 | updateSize_LabelWidget(def); | ||
708 | } | ||
709 | /* Action buttons are added in the bottom as extra buttons. */ { | ||
710 | iBool isFirstAction = iTrue; | ||
711 | iForEach(ObjectList, i, children_Widget(buttons)) { | ||
712 | if (isInstance_Object(i.object, &Class_LabelWidget) && | ||
713 | i.object != cancel && i.object != def) { | ||
714 | iLabelWidget *item = i.object; | ||
715 | setBackgroundColor_Widget(i.object, uiBackgroundSidebar_ColorId); | ||
716 | setFont_LabelWidget(item, labelFont_()); | ||
717 | removeChild_Widget(buttons, item); | ||
718 | addChildFlags_Widget(topPanel, iClob(item), panelButtonFlags | | ||
719 | (isFirstAction ? borderTop_WidgetFlag : 0)); | ||
720 | updateSize_LabelWidget(item); | ||
721 | isFirstAction = iFalse; | ||
722 | } | ||
723 | } | ||
724 | } | ||
725 | iRelease(removeChild_Widget(parent_Widget(buttons), buttons)); | ||
726 | /* Styling for remaining elements. */ | ||
727 | iForEach(ObjectList, i, children_Widget(topPanel)) { | ||
728 | if (isInstance_Object(i.object, &Class_LabelWidget) && | ||
729 | isEmpty_String(command_LabelWidget(i.object)) && | ||
730 | isEmpty_String(id_Widget(i.object))) { | ||
731 | setFlags_Widget(i.object, alignLeft_WidgetFlag, iTrue); | ||
732 | if (font_LabelWidget(i.object) == uiLabel_FontId) { | ||
733 | setFont_LabelWidget(i.object, uiContent_FontId); | ||
734 | } | ||
735 | } | ||
736 | } | ||
737 | } | ||
738 | addChildFlags_Widget(sheet, iClob(navi), | ||
739 | drawBackgroundToVerticalSafeArea_WidgetFlag | | ||
740 | arrangeHeight_WidgetFlag | resizeWidthOfChildren_WidgetFlag | | ||
741 | resizeToParentWidth_WidgetFlag | arrangeVertical_WidgetFlag); | ||
742 | } | ||
743 | if (isPrefs && isSideBySideLayout_()) { | ||
744 | /* Show the General panel. */ | ||
745 | postCommand_Widget(at_PtrArray(panelButtons, 0), "panel.open"); | ||
746 | } | ||
747 | mainDetailSplitHandler_(mainDetailSplit, "window.resized"); /* make it resize the split */ | ||
748 | updatePanelSheetMetrics_(sheet); | ||
749 | iAssert(sheet->parent); | ||
750 | arrange_Widget(sheet->parent); | ||
751 | postCommand_App("widget.overflow"); /* with the correct dimensions */ | ||
752 | } | ||
753 | else { | ||
754 | arrange_Widget(sheet); | ||
755 | } | ||
756 | postRefresh_App(); | ||
757 | } | ||
758 | |||
759 | void setupMenuTransition_Mobile(iWidget *sheet, iBool isIncoming) { | ||
760 | if (!useMobileSheetLayout_()) { | ||
761 | return; | ||
762 | } | ||
763 | const iBool isSlidePanel = (flags_Widget(sheet) & horizontalOffset_WidgetFlag) != 0; | ||
764 | if (isSlidePanel && isLandscape_App()) { | ||
765 | return; | ||
766 | } | ||
767 | if (isIncoming) { | ||
768 | setVisualOffset_Widget(sheet, isSlidePanel ? width_Widget(sheet) : height_Widget(sheet), 0, 0); | ||
769 | setVisualOffset_Widget(sheet, 0, 330, easeOut_AnimFlag | softer_AnimFlag); | ||
770 | } | ||
771 | else { | ||
772 | const iBool wasDragged = iAbs(value_Anim(&sheet->visualOffset) - 0) > 1; | ||
773 | setVisualOffset_Widget(sheet, | ||
774 | isSlidePanel ? width_Widget(sheet) : height_Widget(sheet), | ||
775 | wasDragged ? 100 : 200, | ||
776 | wasDragged ? 0 : easeIn_AnimFlag | softer_AnimFlag); | ||
777 | } | ||
778 | } | ||
779 | |||
780 | void setupSheetTransition_Mobile(iWidget *sheet, iBool isIncoming) { | ||
781 | if (isSideBySideLayout_()) { | ||
782 | return; | ||
783 | } | ||
784 | if (isIncoming) { | ||
785 | setFlags_Widget(sheet, horizontalOffset_WidgetFlag, iTrue); | ||
786 | setVisualOffset_Widget(sheet, size_Root(sheet->root).x, 0, 0); | ||
787 | setVisualOffset_Widget(sheet, 0, 200, easeOut_AnimFlag); | ||
788 | } | ||
789 | else { | ||
790 | setFlags_Widget(sheet, horizontalOffset_WidgetFlag, iTrue); | ||
791 | const iBool wasDragged = iAbs(value_Anim(&sheet->visualOffset)) > 0; | ||
792 | setVisualOffset_Widget(sheet, size_Root(sheet->root).x, wasDragged ? 100 : 200, | ||
793 | wasDragged ? 0 : easeIn_AnimFlag); | ||
794 | } | ||
795 | } | ||
diff --git a/src/ui/mobile.h b/src/ui/mobile.h new file mode 100644 index 00000000..44134389 --- /dev/null +++ b/src/ui/mobile.h | |||
@@ -0,0 +1,32 @@ | |||
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 | #pragma once | ||
24 | |||
25 | #include <the_Foundation/defs.h> | ||
26 | |||
27 | iDeclareType(Widget) | ||
28 | |||
29 | void setupMenuTransition_Mobile (iWidget *menu, iBool isIncoming); | ||
30 | void setupSheetTransition_Mobile (iWidget *sheet, iBool isIncoming); | ||
31 | |||
32 | void finalizeSheet_Mobile (iWidget *sheet); | ||
diff --git a/src/ui/paint.c b/src/ui/paint.c index faaf403d..c575d5fc 100644 --- a/src/ui/paint.c +++ b/src/ui/paint.c | |||
@@ -62,20 +62,19 @@ void endTarget_Paint(iPaint *d) { | |||
62 | } | 62 | } |
63 | 63 | ||
64 | void setClip_Paint(iPaint *d, iRect rect) { | 64 | void setClip_Paint(iPaint *d, iRect rect) { |
65 | if (rect.pos.y < 0) { | 65 | rect = intersect_Rect(rect, rect_Root(get_Root())); |
66 | const int off = rect.pos.y; | 66 | if (isEmpty_Rect(rect)) { |
67 | rect.pos.y -= off; | 67 | rect = init_Rect(0, 0, 1, 1); |
68 | rect.size.y = iMax(0, rect.size.y + off); | ||
69 | } | ||
70 | if (rect.pos.x < 0) { | ||
71 | const int off = rect.pos.x; | ||
72 | rect.pos.x -= off; | ||
73 | rect.size.x = iMax(0, rect.size.x + off); | ||
74 | } | 68 | } |
75 | SDL_RenderSetClipRect(renderer_Paint_(d), (const SDL_Rect *) &rect); | 69 | SDL_RenderSetClipRect(renderer_Paint_(d), (const SDL_Rect *) &rect); |
76 | } | 70 | } |
77 | 71 | ||
78 | void unsetClip_Paint(iPaint *d) { | 72 | void unsetClip_Paint(iPaint *d) { |
73 | if (numRoots_Window(get_Window()) > 1) { | ||
74 | const iRect rect = rect_Root(get_Root()); | ||
75 | SDL_RenderSetClipRect(renderer_Paint_(d), (const SDL_Rect *) &rect); | ||
76 | return; | ||
77 | } | ||
79 | #if SDL_VERSION_ATLEAST(2, 0, 12) | 78 | #if SDL_VERSION_ATLEAST(2, 0, 12) |
80 | SDL_RenderSetClipRect(renderer_Paint_(d), NULL); | 79 | SDL_RenderSetClipRect(renderer_Paint_(d), NULL); |
81 | #else | 80 | #else |
diff --git a/src/ui/root.c b/src/ui/root.c index 76ef05c4..6cf3f424 100644 --- a/src/ui/root.c +++ b/src/ui/root.c | |||
@@ -269,6 +269,7 @@ iAnyObject *findWidget_Root(const char *id) { | |||
269 | } | 269 | } |
270 | 270 | ||
271 | void destroyPending_Root(iRoot *d) { | 271 | void destroyPending_Root(iRoot *d) { |
272 | iRoot *oldRoot = current_Root(); | ||
272 | setCurrent_Root(d); | 273 | setCurrent_Root(d); |
273 | iForEach(PtrSet, i, d->pendingDestruction) { | 274 | iForEach(PtrSet, i, d->pendingDestruction) { |
274 | iWidget *widget = *i.value; | 275 | iWidget *widget = *i.value; |
@@ -282,7 +283,7 @@ void destroyPending_Root(iRoot *d) { | |||
282 | iRelease(widget); | 283 | iRelease(widget); |
283 | remove_PtrSetIterator(&i); | 284 | remove_PtrSetIterator(&i); |
284 | } | 285 | } |
285 | setCurrent_Root(NULL); | 286 | setCurrent_Root(oldRoot); |
286 | } | 287 | } |
287 | 288 | ||
288 | void postArrange_Root(iRoot *d) { | 289 | void postArrange_Root(iRoot *d) { |
@@ -349,6 +350,7 @@ static iBool handleRootCommands_(iWidget *root, const char *cmd) { | |||
349 | } | 350 | } |
350 | else if (equal_Command(cmd, "input.resized")) { | 351 | else if (equal_Command(cmd, "input.resized")) { |
351 | /* No parent handled this, so do a full rearrangement. */ | 352 | /* No parent handled this, so do a full rearrangement. */ |
353 | /* TODO: Defer this and do a single rearrangement later. */ | ||
352 | arrange_Widget(root); | 354 | arrange_Widget(root); |
353 | postRefresh_App(); | 355 | postRefresh_App(); |
354 | return iTrue; | 356 | return iTrue; |
@@ -414,6 +416,7 @@ static iBool handleRootCommands_(iWidget *root, const char *cmd) { | |||
414 | else { | 416 | else { |
415 | addChildPos_Widget(findChild_Widget(root, "stack"), iClob(sidebar), back_WidgetAddPos); | 417 | addChildPos_Widget(findChild_Widget(root, "stack"), iClob(sidebar), back_WidgetAddPos); |
416 | setWidth_SidebarWidget(sidebar, (float) width_Widget(root) / (float) gap_UI); | 418 | setWidth_SidebarWidget(sidebar, (float) width_Widget(root) / (float) gap_UI); |
419 | setWidth_SidebarWidget(sidebar2, (float) width_Widget(root) / (float) gap_UI); | ||
417 | } | 420 | } |
418 | return iFalse; | 421 | return iFalse; |
419 | } | 422 | } |
@@ -466,11 +469,11 @@ static void setReloadLabel_Root_(iRoot *d, iBool animating) { | |||
466 | const iBool isMobile = deviceType_App() != desktop_AppDeviceType; | 469 | const iBool isMobile = deviceType_App() != desktop_AppDeviceType; |
467 | iLabelWidget *label = findChild_Widget(d->widget, "reload"); | 470 | iLabelWidget *label = findChild_Widget(d->widget, "reload"); |
468 | updateTextCStr_LabelWidget( | 471 | updateTextCStr_LabelWidget( |
469 | label, animating ? loadAnimationCStr_() : (isMobile ? pageMenuCStr_ : reloadCStr_)); | 472 | label, animating ? loadAnimationCStr_() : (/*isMobile ? pageMenuCStr_ :*/ reloadCStr_)); |
470 | if (isMobile) { | 473 | // if (isMobile) { |
471 | setCommand_LabelWidget(label, | 474 | // setCommand_LabelWidget(label, |
472 | collectNewCStr_String(animating ? "navigate.reload" : "menu.open")); | 475 | // collectNewCStr_String(animating ? "navigate.reload" : "menu.open")); |
473 | } | 476 | // } |
474 | } | 477 | } |
475 | 478 | ||
476 | static void checkLoadAnimation_Root_(iRoot *d) { | 479 | static void checkLoadAnimation_Root_(iRoot *d) { |
@@ -536,9 +539,12 @@ static iBool willPerformSearchQuery_(const iString *userInput) { | |||
536 | 539 | ||
537 | static void updateUrlInputContentPadding_(iWidget *navBar) { | 540 | static void updateUrlInputContentPadding_(iWidget *navBar) { |
538 | iInputWidget *url = findChild_Widget(navBar, "url"); | 541 | iInputWidget *url = findChild_Widget(navBar, "url"); |
539 | const iWidget *indicators = findChild_Widget(navBar, "url.rightembed"); | 542 | const int lockWidth = width_Widget(findChild_Widget(navBar, "navbar.lock")); |
540 | setContentPadding_InputWidget(url, -1, | 543 | const int indicatorsWidth = width_Widget(findChild_Widget(navBar, "url.rightembed")); |
541 | width_Widget(indicators)); | 544 | /* The indicators widget has a padding that covers the urlButtons area. */ |
545 | setContentPadding_InputWidget(url, | ||
546 | lockWidth - 2 * gap_UI, // * 0.75f, | ||
547 | indicatorsWidth); | ||
542 | } | 548 | } |
543 | 549 | ||
544 | static void showSearchQueryIndicator_(iBool show) { | 550 | static void showSearchQueryIndicator_(iBool show) { |
@@ -585,17 +591,17 @@ static void updateNavBarSize_(iWidget *navBar) { | |||
585 | updateSize_LabelWidget(label); | 591 | updateSize_LabelWidget(label); |
586 | } | 592 | } |
587 | } | 593 | } |
594 | updateUrlInputContentPadding_(navBar); | ||
588 | /* Note that InputWidget uses the `tight` flag to adjust its inner padding. */ | 595 | /* Note that InputWidget uses the `tight` flag to adjust its inner padding. */ |
589 | /* TODO: Is this redundant? See `updateMetrics_Window_()`. */ | 596 | // const int embedButtonWidth = width_Widget(findChild_Widget(navBar, "navbar.lock")); |
590 | const int embedButtonWidth = width_Widget(findChild_Widget(navBar, "navbar.lock")); | 597 | // setContentPadding_InputWidget(findChild_Widget(navBar, "url"), |
591 | setContentPadding_InputWidget(findChild_Widget(navBar, "url"), | 598 | // embedButtonWidth * 0.75f, |
592 | embedButtonWidth * 0.75f, | 599 | // embedButtonWidth * 0.75f); |
593 | embedButtonWidth * 0.75f); | ||
594 | } | 600 | } |
595 | if (isPhone) { | 601 | if (isPhone) { |
596 | static const char *buttons[] = { "navbar.back", "navbar.forward", "navbar.sidebar", | 602 | static const char *buttons[] = { "navbar.back", "navbar.forward", "navbar.sidebar", |
597 | "navbar.ident", "navbar.home", "navbar.menu" }; | 603 | "navbar.ident", "navbar.home", "navbar.menu" }; |
598 | iWidget *toolBar = findWidget_App("toolbar"); | 604 | iWidget *toolBar = findWidget_Root("toolbar"); |
599 | setVisualOffset_Widget(toolBar, 0, 0, 0); | 605 | setVisualOffset_Widget(toolBar, 0, 0, 0); |
600 | setFlags_Widget(toolBar, hidden_WidgetFlag, isLandscape_App()); | 606 | setFlags_Widget(toolBar, hidden_WidgetFlag, isLandscape_App()); |
601 | iForIndices(i, buttons) { | 607 | iForIndices(i, buttons) { |
@@ -901,15 +907,19 @@ void updateMetrics_Root(iRoot *d) { | |||
901 | iWidget *url = findChild_Widget(d->widget, "url"); | 907 | iWidget *url = findChild_Widget(d->widget, "url"); |
902 | iWidget *rightEmbed = findChild_Widget(navBar, "url.rightembed"); | 908 | iWidget *rightEmbed = findChild_Widget(navBar, "url.rightembed"); |
903 | iWidget *embedPad = findChild_Widget(navBar, "url.embedpad"); | 909 | iWidget *embedPad = findChild_Widget(navBar, "url.embedpad"); |
910 | iWidget *urlButtons = findChild_Widget(navBar, "url.buttons"); | ||
904 | setPadding_Widget(as_Widget(url), 0, gap_UI, 0, gap_UI); | 911 | setPadding_Widget(as_Widget(url), 0, gap_UI, 0, gap_UI); |
905 | navBar->rect.size.y = 0; /* recalculate height based on children (FIXME: shouldn't be needed) */ | 912 | navBar->rect.size.y = 0; /* recalculate height based on children (FIXME: shouldn't be needed) */ |
906 | updateSize_LabelWidget((iLabelWidget *) lock); | 913 | // updateSize_LabelWidget((iLabelWidget *) lock); |
907 | setFixedSize_Widget(embedPad, init_I2(width_Widget(lock) + gap_UI / 2, 1)); | 914 | // updateSize_LabelWidget((iLabelWidget *) findChild_Widget(navBar, "reload")); |
908 | setContentPadding_InputWidget((iInputWidget *) url, width_Widget(lock) * 0.75, | 915 | // arrange_Widget(urlButtons); |
909 | width_Widget(lock) * 0.75); | 916 | setFixedSize_Widget(embedPad, init_I2(width_Widget(urlButtons) + gap_UI / 2, 1)); |
917 | // setContentPadding_InputWidget((iInputWidget *) url, width_Widget(lock) * 0.75, | ||
918 | // width_Widget(lock) * 0.75); | ||
910 | rightEmbed->rect.pos.y = gap_UI; | 919 | rightEmbed->rect.pos.y = gap_UI; |
911 | updatePadding_Root(d); | 920 | updatePadding_Root(d); |
912 | arrange_Widget(d->widget); | 921 | arrange_Widget(d->widget); |
922 | updateUrlInputContentPadding_(navBar); | ||
913 | postRefresh_App(); | 923 | postRefresh_App(); |
914 | } | 924 | } |
915 | 925 | ||
@@ -1057,7 +1067,7 @@ void createUserInterface_Root(iRoot *d) { | |||
1057 | setNoAutoMinHeight_LabelWidget(fprog, iTrue); | 1067 | setNoAutoMinHeight_LabelWidget(fprog, iTrue); |
1058 | addChildFlags_Widget(rightEmbed, | 1068 | addChildFlags_Widget(rightEmbed, |
1059 | iClob(fprog), | 1069 | iClob(fprog), |
1060 | collapse_WidgetFlag | frameless_WidgetFlag | hidden_WidgetFlag); | 1070 | collapse_WidgetFlag | hidden_WidgetFlag | frameless_WidgetFlag); |
1061 | } | 1071 | } |
1062 | /* Download progress indicator is also inside the input field, but hidden normally. */ { | 1072 | /* Download progress indicator is also inside the input field, but hidden normally. */ { |
1063 | iLabelWidget *progress = new_LabelWidget(uiTextCaution_ColorEscape "00.000 ${mb}", NULL); | 1073 | iLabelWidget *progress = new_LabelWidget(uiTextCaution_ColorEscape "00.000 ${mb}", NULL); |
@@ -1066,7 +1076,7 @@ void createUserInterface_Root(iRoot *d) { | |||
1066 | setAlignVisually_LabelWidget(progress, iTrue); | 1076 | setAlignVisually_LabelWidget(progress, iTrue); |
1067 | setNoAutoMinHeight_LabelWidget(progress, iTrue); | 1077 | setNoAutoMinHeight_LabelWidget(progress, iTrue); |
1068 | addChildFlags_Widget( | 1078 | addChildFlags_Widget( |
1069 | rightEmbed, iClob(progress), collapse_WidgetFlag); | 1079 | rightEmbed, iClob(progress), collapse_WidgetFlag | hidden_WidgetFlag); |
1070 | } | 1080 | } |
1071 | /* Pinning indicator. */ { | 1081 | /* Pinning indicator. */ { |
1072 | iLabelWidget *pin = new_LabelWidget(uiTextAction_ColorEscape leftHalf_Icon, NULL); | 1082 | iLabelWidget *pin = new_LabelWidget(uiTextAction_ColorEscape leftHalf_Icon, NULL); |
@@ -1076,39 +1086,44 @@ void createUserInterface_Root(iRoot *d) { | |||
1076 | setNoAutoMinHeight_LabelWidget(pin, iTrue); | 1086 | setNoAutoMinHeight_LabelWidget(pin, iTrue); |
1077 | addChildFlags_Widget(rightEmbed, | 1087 | addChildFlags_Widget(rightEmbed, |
1078 | iClob(pin), | 1088 | iClob(pin), |
1079 | collapse_WidgetFlag | tight_WidgetFlag | frameless_WidgetFlag); | 1089 | collapse_WidgetFlag | hidden_WidgetFlag | tight_WidgetFlag | frameless_WidgetFlag); |
1090 | } | ||
1091 | iWidget *urlButtons = new_Widget(); | ||
1092 | setId_Widget(urlButtons, "url.buttons"); | ||
1093 | setFlags_Widget(urlButtons, embedFlags | arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag, iTrue); | ||
1094 | /* Mobile page menu. */ | ||
1095 | if (deviceType_App() != desktop_AppDeviceType) { | ||
1096 | iLabelWidget *pageMenuButton; | ||
1097 | /* In a mobile layout, the reload button is replaced with the Page/Ellipsis menu. */ | ||
1098 | pageMenuButton = makeMenuButton_LabelWidget(pageMenuCStr_, | ||
1099 | (iMenuItem[]){ | ||
1100 | { upArrow_Icon " ${menu.parent}", navigateParent_KeyShortcut, "navigate.parent" }, | ||
1101 | { upArrowBar_Icon " ${menu.root}", navigateRoot_KeyShortcut, "navigate.root" }, | ||
1102 | { timer_Icon " ${menu.autoreload}", 0, 0, "document.autoreload.menu" }, | ||
1103 | { "---", 0, 0, NULL }, | ||
1104 | { bookmark_Icon " ${menu.page.bookmark}", SDLK_d, KMOD_PRIMARY, "bookmark.add" }, | ||
1105 | { star_Icon " ${menu.page.subscribe}", subscribeToPage_KeyModifier, "feeds.subscribe" }, | ||
1106 | { book_Icon " ${menu.page.import}", 0, 0, "bookmark.links confirm:1" }, | ||
1107 | { globe_Icon " ${menu.page.translate}", 0, 0, "document.translate" }, | ||
1108 | { "---", 0, 0, NULL }, | ||
1109 | { "${menu.page.copyurl}", 0, 0, "document.copylink" }, | ||
1110 | { "${menu.page.copysource}", 'c', KMOD_PRIMARY, "copy" }, | ||
1111 | { download_Icon " " saveToDownloads_Label, SDLK_s, KMOD_PRIMARY, "document.save" } }, | ||
1112 | 12); | ||
1113 | setId_Widget(as_Widget(pageMenuButton), "pagemenubutton"); | ||
1114 | setFont_LabelWidget(pageMenuButton, uiContentBold_FontId); | ||
1115 | setAlignVisually_LabelWidget(pageMenuButton, iTrue); | ||
1116 | addChildFlags_Widget(urlButtons, iClob(pageMenuButton), embedFlags); | ||
1117 | updateSize_LabelWidget(pageMenuButton); | ||
1080 | } | 1118 | } |
1081 | /* Reload button. */ { | 1119 | /* Reload button. */ { |
1082 | iLabelWidget *reload; | 1120 | iLabelWidget *reload = newIcon_LabelWidget(reloadCStr_, 0, 0, "navigate.reload"); |
1083 | if (deviceType_App() == desktop_AppDeviceType) { | ||
1084 | reload = newIcon_LabelWidget(reloadCStr_, 0, 0, "navigate.reload"); | ||
1085 | } | ||
1086 | else { | ||
1087 | /* In a mobile layout, the reload button is replaced with the Page/Ellipsis menu. */ | ||
1088 | reload = makeMenuButton_LabelWidget(pageMenuCStr_, | ||
1089 | (iMenuItem[]){ | ||
1090 | { reload_Icon " ${menu.reload}", reload_KeyShortcut, "navigate.reload" }, | ||
1091 | { timer_Icon " ${menu.autoreload}", 0, 0, "document.autoreload.menu" }, | ||
1092 | { "---", 0, 0, NULL }, | ||
1093 | { upArrow_Icon " ${menu.parent}", navigateParent_KeyShortcut, "navigate.parent" }, | ||
1094 | { upArrowBar_Icon " ${menu.root}", navigateRoot_KeyShortcut, "navigate.root" }, | ||
1095 | { "---", 0, 0, NULL }, | ||
1096 | { pin_Icon " ${menu.page.bookmark}", SDLK_d, KMOD_PRIMARY, "bookmark.add" }, | ||
1097 | { star_Icon " ${menu.page.subscribe}", subscribeToPage_KeyModifier, "feeds.subscribe" }, | ||
1098 | { book_Icon " ${menu.page.import}", 0, 0, "bookmark.links confirm:1" }, | ||
1099 | { globe_Icon " ${menu.page.translate}", 0, 0, "document.translate" }, | ||
1100 | { "---", 0, 0, NULL }, | ||
1101 | { "${menu.page.copyurl}", 0, 0, "document.copylink" }, | ||
1102 | { "${menu.page.copysource}", 'c', KMOD_PRIMARY, "copy" }, | ||
1103 | { download_Icon " " saveToDownloads_Label, SDLK_s, KMOD_PRIMARY, "document.save" } }, | ||
1104 | 14); | ||
1105 | setFont_LabelWidget((iLabelWidget *) reload, uiContentBold_FontId); | ||
1106 | setAlignVisually_LabelWidget((iLabelWidget *) reload, iTrue); | ||
1107 | } | ||
1108 | setId_Widget(as_Widget(reload), "reload"); | 1121 | setId_Widget(as_Widget(reload), "reload"); |
1109 | addChildFlags_Widget(as_Widget(url), iClob(reload), embedFlags | moveToParentRightEdge_WidgetFlag); | 1122 | addChildFlags_Widget(urlButtons, iClob(reload), embedFlags); |
1110 | updateSize_LabelWidget(reload); | 1123 | updateSize_LabelWidget(reload); |
1111 | } | 1124 | } |
1125 | addChildFlags_Widget(as_Widget(url), iClob(urlButtons), moveToParentRightEdge_WidgetFlag); | ||
1126 | arrange_Widget(urlButtons); | ||
1112 | setId_Widget(addChild_Widget(rightEmbed, iClob(makePadding_Widget(0))), "url.embedpad"); | 1127 | setId_Widget(addChild_Widget(rightEmbed, iClob(makePadding_Widget(0))), "url.embedpad"); |
1113 | } | 1128 | } |
1114 | if (deviceType_App() != desktop_AppDeviceType) { | 1129 | if (deviceType_App() != desktop_AppDeviceType) { |
@@ -1216,7 +1231,9 @@ void createUserInterface_Root(iRoot *d) { | |||
1216 | setFlags_Widget(toolBar, moveToParentBottomEdge_WidgetFlag | | 1231 | setFlags_Widget(toolBar, moveToParentBottomEdge_WidgetFlag | |
1217 | parentCannotResizeHeight_WidgetFlag | | 1232 | parentCannotResizeHeight_WidgetFlag | |
1218 | resizeWidthOfChildren_WidgetFlag | | 1233 | resizeWidthOfChildren_WidgetFlag | |
1219 | arrangeHeight_WidgetFlag | arrangeHorizontal_WidgetFlag, iTrue); | 1234 | arrangeHeight_WidgetFlag | arrangeHorizontal_WidgetFlag | |
1235 | commandOnClick_WidgetFlag | | ||
1236 | drawBackgroundToBottom_WidgetFlag, iTrue); | ||
1220 | setBackgroundColor_Widget(toolBar, tmBannerBackground_ColorId); | 1237 | setBackgroundColor_Widget(toolBar, tmBannerBackground_ColorId); |
1221 | setId_Widget(addChildFlags_Widget(toolBar, | 1238 | setId_Widget(addChildFlags_Widget(toolBar, |
1222 | iClob(newLargeIcon_LabelWidget("\U0001f870", "navigate.back")), | 1239 | iClob(newLargeIcon_LabelWidget("\U0001f870", "navigate.back")), |
@@ -1231,7 +1248,7 @@ void createUserInterface_Root(iRoot *d) { | |||
1231 | frameless_WidgetFlag), | 1248 | frameless_WidgetFlag), |
1232 | "toolbar.ident"); | 1249 | "toolbar.ident"); |
1233 | setId_Widget(addChildFlags_Widget(toolBar, | 1250 | setId_Widget(addChildFlags_Widget(toolBar, |
1234 | iClob(newLargeIcon_LabelWidget("\U0001f588", "toolbar.showview arg:-1")), | 1251 | iClob(newLargeIcon_LabelWidget(book_Icon, "toolbar.showview arg:-1")), |
1235 | frameless_WidgetFlag | commandOnClick_WidgetFlag), | 1252 | frameless_WidgetFlag | commandOnClick_WidgetFlag), |
1236 | "toolbar.view"); | 1253 | "toolbar.view"); |
1237 | iLabelWidget *menuButton = makeMenuButton_LabelWidget("\U0001d362", phoneNavMenuItems_, | 1254 | iLabelWidget *menuButton = makeMenuButton_LabelWidget("\U0001d362", phoneNavMenuItems_, |
@@ -1244,17 +1261,17 @@ void createUserInterface_Root(iRoot *d) { | |||
1244 | setFlags_Widget(i.object, noBackground_WidgetFlag, iTrue); | 1261 | setFlags_Widget(i.object, noBackground_WidgetFlag, iTrue); |
1245 | setTextColor_LabelWidget(i.object, tmBannerIcon_ColorId); | 1262 | setTextColor_LabelWidget(i.object, tmBannerIcon_ColorId); |
1246 | // setBackgroundColor_Widget(i.object, tmBannerSideTitle_ColorId); | 1263 | // setBackgroundColor_Widget(i.object, tmBannerSideTitle_ColorId); |
1247 | } | 1264 | } |
1248 | const iMenuItem items[] = { | 1265 | const iMenuItem items[] = { |
1249 | { pin_Icon " ${sidebar.bookmarks}", 0, 0, "toolbar.showview arg:0" }, | 1266 | { book_Icon " ${sidebar.bookmarks}", 0, 0, "toolbar.showview arg:0" }, |
1250 | { star_Icon " ${sidebar.feeds}", 0, 0, "toolbar.showview arg:1" }, | 1267 | { star_Icon " ${sidebar.feeds}", 0, 0, "toolbar.showview arg:1" }, |
1251 | { clock_Icon " ${sidebar.history}", 0, 0, "toolbar.showview arg:2" }, | 1268 | { clock_Icon " ${sidebar.history}", 0, 0, "toolbar.showview arg:2" }, |
1252 | { page_Icon " ${toolbar.outline}", 0, 0, "toolbar.showview arg:4" }, | 1269 | { page_Icon " ${toolbar.outline}", 0, 0, "toolbar.showview arg:4" }, |
1253 | }; | 1270 | }; |
1254 | iWidget *menu = makeMenu_Widget(findChild_Widget(toolBar, "toolbar.view"), | 1271 | iWidget *menu = makeMenu_Widget(findChild_Widget(toolBar, "toolbar.view"), |
1255 | items, iElemCount(items)); | 1272 | items, iElemCount(items)); |
1256 | setId_Widget(menu, "toolbar.menu"); /* view menu */ | 1273 | setId_Widget(menu, "toolbar.menu"); /* view menu */ |
1257 | } | 1274 | } |
1258 | #endif | 1275 | #endif |
1259 | updatePadding_Root(d); | 1276 | updatePadding_Root(d); |
1260 | /* Global context menus. */ { | 1277 | /* Global context menus. */ { |
@@ -1319,6 +1336,11 @@ void createUserInterface_Root(iRoot *d) { | |||
1319 | } | 1336 | } |
1320 | updateMetrics_Root(d); | 1337 | updateMetrics_Root(d); |
1321 | updateNavBarSize_(navBar); | 1338 | updateNavBarSize_(navBar); |
1339 | if (deviceType_App() == phone_AppDeviceType) { | ||
1340 | const float sidebarWidth = width_Widget(root) / (float) gap_UI; | ||
1341 | setWidth_SidebarWidget(findChild_Widget(root, "sidebar"), sidebarWidth); | ||
1342 | setWidth_SidebarWidget(findChild_Widget(root, "sidebar2"), sidebarWidth); | ||
1343 | } | ||
1322 | } | 1344 | } |
1323 | 1345 | ||
1324 | void showToolbars_Root(iRoot *d, iBool show) { | 1346 | void showToolbars_Root(iRoot *d, iBool show) { |
diff --git a/src/ui/scrollwidget.c b/src/ui/scrollwidget.c index 32b57c69..ff5144b2 100644 --- a/src/ui/scrollwidget.c +++ b/src/ui/scrollwidget.c | |||
@@ -108,7 +108,7 @@ static void unfade_ScrollWidget_(iScrollWidget *d, float opacity) { | |||
108 | d->fadeStart = SDL_GetTicks() + 1000; | 108 | d->fadeStart = SDL_GetTicks() + 1000; |
109 | if (targetValue_Anim(&d->opacity) < opacity) { | 109 | if (targetValue_Anim(&d->opacity) < opacity) { |
110 | setValue_Anim(&d->opacity, opacity, 66); | 110 | setValue_Anim(&d->opacity, opacity, 66); |
111 | addTicker_App(animateOpacity_ScrollWidget_, d); | 111 | addTickerRoot_App(animateOpacity_ScrollWidget_, as_Widget(d)->root, d); |
112 | } | 112 | } |
113 | if (!d->willCheckFade && d->fadeEnabled) { | 113 | if (!d->willCheckFade && d->fadeEnabled) { |
114 | d->willCheckFade = iTrue; | 114 | d->willCheckFade = iTrue; |
diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c index eae0432f..cb6dab65 100644 --- a/src/ui/sidebarwidget.c +++ b/src/ui/sidebarwidget.c | |||
@@ -106,7 +106,8 @@ struct Impl_SidebarWidget { | |||
106 | size_t numUnreadEntries; | 106 | size_t numUnreadEntries; |
107 | iWidget * resizer; | 107 | iWidget * resizer; |
108 | iWidget * menu; | 108 | iWidget * menu; |
109 | iSidebarItem * contextItem; /* list item accessed in the context menu */ | 109 | iSidebarItem * contextItem; /* list item accessed in the context menu */ |
110 | size_t contextIndex; /* index of list item accessed in the context menu */ | ||
110 | }; | 111 | }; |
111 | 112 | ||
112 | iDefineObjectConstructionArgs(SidebarWidget, (enum iSidebarSide side), side) | 113 | iDefineObjectConstructionArgs(SidebarWidget, (enum iSidebarSide side), side) |
@@ -239,7 +240,7 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) { | |||
239 | as_Widget(d), | 240 | as_Widget(d), |
240 | (iMenuItem[]){ { openTab_Icon " ${feeds.entry.newtab}", 0, 0, "feed.entry.opentab" }, | 241 | (iMenuItem[]){ { openTab_Icon " ${feeds.entry.newtab}", 0, 0, "feed.entry.opentab" }, |
241 | { circle_Icon " ${feeds.entry.markread}", 0, 0, "feed.entry.toggleread" }, | 242 | { circle_Icon " ${feeds.entry.markread}", 0, 0, "feed.entry.toggleread" }, |
242 | { pin_Icon " ${feeds.entry.bookmark}", 0, 0, "feed.entry.bookmark" }, | 243 | { bookmark_Icon " ${feeds.entry.bookmark}", 0, 0, "feed.entry.bookmark" }, |
243 | { "---", 0, 0, NULL }, | 244 | { "---", 0, 0, NULL }, |
244 | { page_Icon " ${feeds.entry.openfeed}", 0, 0, "feed.entry.openfeed" }, | 245 | { page_Icon " ${feeds.entry.openfeed}", 0, 0, "feed.entry.openfeed" }, |
245 | { edit_Icon " ${feeds.edit}", 0, 0, "feed.entry.edit" }, | 246 | { edit_Icon " ${feeds.edit}", 0, 0, "feed.entry.edit" }, |
@@ -364,7 +365,7 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) { | |||
364 | as_Widget(d), | 365 | as_Widget(d), |
365 | (iMenuItem[]){ | 366 | (iMenuItem[]){ |
366 | { "${menu.copyurl}", 0, 0, "history.copy" }, | 367 | { "${menu.copyurl}", 0, 0, "history.copy" }, |
367 | { pin_Icon " ${sidebar.entry.bookmark}", 0, 0, "history.addbookmark" }, | 368 | { bookmark_Icon " ${sidebar.entry.bookmark}", 0, 0, "history.addbookmark" }, |
368 | { "---", 0, 0, NULL }, | 369 | { "---", 0, 0, NULL }, |
369 | { close_Icon " ${menu.forgeturl}", 0, 0, "history.delete" }, | 370 | { close_Icon " ${menu.forgeturl}", 0, 0, "history.delete" }, |
370 | { "---", 0, 0, NULL }, | 371 | { "---", 0, 0, NULL }, |
@@ -373,10 +374,6 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) { | |||
373 | break; | 374 | break; |
374 | } | 375 | } |
375 | case identities_SidebarMode: { | 376 | case identities_SidebarMode: { |
376 | /* Actions. */ { | ||
377 | addActionButton_SidebarWidget_(d, add_Icon " ${sidebar.action.ident.new}", "ident.new", 0); | ||
378 | addActionButton_SidebarWidget_(d, "${sidebar.action.ident.import}", "ident.import", 0); | ||
379 | } | ||
380 | const iString *tabUrl = url_DocumentWidget(document_App()); | 377 | const iString *tabUrl = url_DocumentWidget(document_App()); |
381 | isEmpty = iTrue; | 378 | isEmpty = iTrue; |
382 | iConstForEach(PtrArray, i, identities_GmCerts(certs_App())) { | 379 | iConstForEach(PtrArray, i, identities_GmCerts(certs_App())) { |
@@ -413,6 +410,11 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) { | |||
413 | iRelease(item); | 410 | iRelease(item); |
414 | isEmpty = iFalse; | 411 | isEmpty = iFalse; |
415 | } | 412 | } |
413 | /* Actions. */ | ||
414 | if (!isEmpty) { | ||
415 | addActionButton_SidebarWidget_(d, add_Icon " ${sidebar.action.ident.new}", "ident.new", 0); | ||
416 | addActionButton_SidebarWidget_(d, "${sidebar.action.ident.import}", "ident.import", 0); | ||
417 | } | ||
416 | const iMenuItem menuItems[] = { | 418 | const iMenuItem menuItems[] = { |
417 | { person_Icon " ${ident.use}", 0, 0, "ident.use arg:1" }, | 419 | { person_Icon " ${ident.use}", 0, 0, "ident.use arg:1" }, |
418 | { close_Icon " ${ident.stopuse}", 0, 0, "ident.use arg:0" }, | 420 | { close_Icon " ${ident.stopuse}", 0, 0, "ident.use arg:0" }, |
@@ -520,15 +522,15 @@ float width_SidebarWidget(const iSidebarWidget *d) { | |||
520 | } | 522 | } |
521 | 523 | ||
522 | static const char *normalModeLabels_[max_SidebarMode] = { | 524 | static const char *normalModeLabels_[max_SidebarMode] = { |
523 | pin_Icon " ${sidebar.bookmarks}", | 525 | book_Icon " ${sidebar.bookmarks}", |
524 | star_Icon " ${sidebar.feeds}", | 526 | star_Icon " ${sidebar.feeds}", |
525 | clock_Icon " ${sidebar.history}", | 527 | clock_Icon " ${sidebar.history}", |
526 | person_Icon " ${sidebar.identities}", | 528 | person_Icon " ${sidebar.identities}", |
527 | page_Icon " ${sidebar.outline}", | 529 | page_Icon " ${sidebar.outline}", |
528 | }; | 530 | }; |
529 | 531 | ||
530 | static const char *tightModeLabels_[max_SidebarMode] = { | 532 | static const char *tightModeLabels_[max_SidebarMode] = { |
531 | pin_Icon, | 533 | book_Icon, |
532 | star_Icon, | 534 | star_Icon, |
533 | clock_Icon, | 535 | clock_Icon, |
534 | person_Icon, | 536 | person_Icon, |
@@ -576,11 +578,11 @@ void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) { | |||
576 | d->itemFonts[0] = uiContent_FontId; | 578 | d->itemFonts[0] = uiContent_FontId; |
577 | d->itemFonts[1] = uiContentBold_FontId; | 579 | d->itemFonts[1] = uiContentBold_FontId; |
578 | #if defined (iPlatformAppleMobile) | 580 | #if defined (iPlatformAppleMobile) |
579 | d->widthAsGaps = 73; | ||
580 | if (deviceType_App() == phone_AppDeviceType) { | 581 | if (deviceType_App() == phone_AppDeviceType) { |
581 | d->itemFonts[0] = defaultBig_FontId; | 582 | d->itemFonts[0] = defaultBig_FontId; |
582 | d->itemFonts[1] = defaultBigBold_FontId; | 583 | d->itemFonts[1] = defaultBigBold_FontId; |
583 | } | 584 | } |
585 | d->widthAsGaps = 73; | ||
584 | #else | 586 | #else |
585 | d->widthAsGaps = 60; | 587 | d->widthAsGaps = 60; |
586 | #endif | 588 | #endif |
@@ -644,6 +646,7 @@ void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) { | |||
644 | "actions"); | 646 | "actions"); |
645 | setBackgroundColor_Widget(d->actions, uiBackgroundSidebar_ColorId); | 647 | setBackgroundColor_Widget(d->actions, uiBackgroundSidebar_ColorId); |
646 | d->contextItem = NULL; | 648 | d->contextItem = NULL; |
649 | d->contextIndex = iInvalidPos; | ||
647 | d->blank = new_Widget(); | 650 | d->blank = new_Widget(); |
648 | addChildFlags_Widget(content, iClob(d->blank), resizeChildren_WidgetFlag); | 651 | addChildFlags_Widget(content, iClob(d->blank), resizeChildren_WidgetFlag); |
649 | addChildFlags_Widget(vdiv, iClob(content), expand_WidgetFlag); | 652 | addChildFlags_Widget(vdiv, iClob(content), expand_WidgetFlag); |
@@ -770,6 +773,7 @@ static void checkModeButtonLayout_SidebarWidget_(iSidebarWidget *d) { | |||
770 | for (int i = 0; i < max_SidebarMode; i++) { | 773 | for (int i = 0; i < max_SidebarMode; i++) { |
771 | iLabelWidget *button = d->modeButtons[i]; | 774 | iLabelWidget *button = d->modeButtons[i]; |
772 | if (!button) continue; | 775 | if (!button) continue; |
776 | setAlignVisually_LabelWidget(button, isTight); | ||
773 | setFlags_Widget(as_Widget(button), tight_WidgetFlag, isTight); | 777 | setFlags_Widget(as_Widget(button), tight_WidgetFlag, isTight); |
774 | if (i == feeds_SidebarMode && d->numUnreadEntries) { | 778 | if (i == feeds_SidebarMode && d->numUnreadEntries) { |
775 | updateText_LabelWidget( | 779 | updateText_LabelWidget( |
@@ -803,7 +807,7 @@ void setWidth_SidebarWidget(iSidebarWidget *d, float widthAsGaps) { | |||
803 | if (isVisible_Widget(w)) { | 807 | if (isVisible_Widget(w)) { |
804 | w->rect.size.x = width; | 808 | w->rect.size.x = width; |
805 | } | 809 | } |
806 | arrange_Widget(findWidget_App("stack")); | 810 | arrange_Widget(findWidget_Root("stack")); |
807 | checkModeButtonLayout_SidebarWidget_(d); | 811 | checkModeButtonLayout_SidebarWidget_(d); |
808 | updateItemHeight_SidebarWidget_(d); | 812 | updateItemHeight_SidebarWidget_(d); |
809 | if (!isFixedWidth && !isRefreshPending_App()) { | 813 | if (!isFixedWidth && !isRefreshPending_App()) { |
@@ -844,7 +848,6 @@ iBool handleBookmarkEditorCommands_SidebarWidget_(iWidget *editor, const char *c | |||
844 | isSelected_Widget(findChild_Widget(editor, "bmed.tag.linksplit"))); | 848 | isSelected_Widget(findChild_Widget(editor, "bmed.tag.linksplit"))); |
845 | postCommand_App("bookmarks.changed"); | 849 | postCommand_App("bookmarks.changed"); |
846 | } | 850 | } |
847 | setFlags_Widget(as_Widget(d), disabled_WidgetFlag, iFalse); | ||
848 | destroy_Widget(editor); | 851 | destroy_Widget(editor); |
849 | return iTrue; | 852 | return iTrue; |
850 | } | 853 | } |
@@ -875,10 +878,33 @@ static iBool handleSidebarCommand_SidebarWidget_(iSidebarWidget *d, const char * | |||
875 | if (arg_Command(cmd) && isVisible_Widget(w)) { | 878 | if (arg_Command(cmd) && isVisible_Widget(w)) { |
876 | return iTrue; | 879 | return iTrue; |
877 | } | 880 | } |
881 | const iBool isAnimated = argLabel_Command(cmd, "noanim") == 0 && | ||
882 | (deviceType_App() != phone_AppDeviceType); | ||
883 | int visX = 0; | ||
884 | if (isVisible_Widget(w)) { | ||
885 | visX = left_Rect(bounds_Widget(w)) - left_Rect(w->root->widget->rect); | ||
886 | } | ||
878 | setFlags_Widget(w, hidden_WidgetFlag, isVisible_Widget(w)); | 887 | setFlags_Widget(w, hidden_WidgetFlag, isVisible_Widget(w)); |
879 | if (isVisible_Widget(w)) { | 888 | if (isVisible_Widget(w)) { |
889 | setFlags_Widget(w, keepOnTop_WidgetFlag, iFalse); | ||
880 | w->rect.size.x = d->widthAsGaps * gap_UI; | 890 | w->rect.size.x = d->widthAsGaps * gap_UI; |
881 | invalidate_ListWidget(d->list); | 891 | invalidate_ListWidget(d->list); |
892 | if (isAnimated) { | ||
893 | setFlags_Widget(w, horizontalOffset_WidgetFlag, iTrue); | ||
894 | setVisualOffset_Widget(w, (d->side == left_SideBarSide ? -1 : 1) * w->rect.size.x, 0, 0); | ||
895 | setVisualOffset_Widget(w, 0, 300, easeOut_AnimFlag | softer_AnimFlag); | ||
896 | } | ||
897 | } | ||
898 | else if (isAnimated) { | ||
899 | setFlags_Widget(w, horizontalOffset_WidgetFlag, iTrue); | ||
900 | if (d->side == right_SideBarSide) { | ||
901 | setVisualOffset_Widget(w, visX, 0, 0); | ||
902 | setVisualOffset_Widget(w, visX + w->rect.size.x, 300, easeOut_AnimFlag | softer_AnimFlag); | ||
903 | } | ||
904 | else { | ||
905 | setFlags_Widget(w, keepOnTop_WidgetFlag, iTrue); | ||
906 | setVisualOffset_Widget(w, -w->rect.size.x, 300, easeOut_AnimFlag | softer_AnimFlag); | ||
907 | } | ||
882 | } | 908 | } |
883 | arrange_Widget(w->parent); | 909 | arrange_Widget(w->parent); |
884 | /* BUG: Rearranging because the arrange above didn't fully resolve the height. */ | 910 | /* BUG: Rearranging because the arrange above didn't fully resolve the height. */ |
@@ -985,11 +1011,8 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
985 | itemClicked_SidebarWidget_(d, pointerLabel_Command(cmd, "item")); | 1011 | itemClicked_SidebarWidget_(d, pointerLabel_Command(cmd, "item")); |
986 | return iTrue; | 1012 | return iTrue; |
987 | } | 1013 | } |
988 | else if (isCommand_Widget(w, ev, "menu.opened")) { | ||
989 | setFlags_Widget(as_Widget(d->list), disabled_WidgetFlag, iTrue); | ||
990 | } | ||
991 | else if (isCommand_Widget(w, ev, "menu.closed")) { | 1014 | else if (isCommand_Widget(w, ev, "menu.closed")) { |
992 | setFlags_Widget(as_Widget(d->list), disabled_WidgetFlag, iFalse); | 1015 | // invalidateItem_ListWidget(d->list, d->contextIndex); |
993 | } | 1016 | } |
994 | else if (isCommand_Widget(w, ev, "bookmark.open")) { | 1017 | else if (isCommand_Widget(w, ev, "bookmark.open")) { |
995 | const iSidebarItem *item = d->contextItem; | 1018 | const iSidebarItem *item = d->contextItem; |
@@ -1010,7 +1033,6 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
1010 | else if (isCommand_Widget(w, ev, "bookmark.edit")) { | 1033 | else if (isCommand_Widget(w, ev, "bookmark.edit")) { |
1011 | const iSidebarItem *item = d->contextItem; | 1034 | const iSidebarItem *item = d->contextItem; |
1012 | if (d->mode == bookmarks_SidebarMode && item) { | 1035 | if (d->mode == bookmarks_SidebarMode && item) { |
1013 | setFlags_Widget(w, disabled_WidgetFlag, iTrue); | ||
1014 | iWidget *dlg = makeBookmarkEditor_Widget(); | 1036 | iWidget *dlg = makeBookmarkEditor_Widget(); |
1015 | setId_Widget(dlg, format_CStr("bmed.%s", cstr_String(id_Widget(w)))); | 1037 | setId_Widget(dlg, format_CStr("bmed.%s", cstr_String(id_Widget(w)))); |
1016 | iBookmark *bm = get_Bookmarks(bookmarks_App(), item->id); | 1038 | iBookmark *bm = get_Bookmarks(bookmarks_App(), item->id); |
@@ -1038,7 +1060,6 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
1038 | else if (isCommand_Widget(w, ev, "bookmark.dup")) { | 1060 | else if (isCommand_Widget(w, ev, "bookmark.dup")) { |
1039 | const iSidebarItem *item = d->contextItem; | 1061 | const iSidebarItem *item = d->contextItem; |
1040 | if (d->mode == bookmarks_SidebarMode && item) { | 1062 | if (d->mode == bookmarks_SidebarMode && item) { |
1041 | setFlags_Widget(w, disabled_WidgetFlag, iTrue); | ||
1042 | iBookmark *bm = get_Bookmarks(bookmarks_App(), item->id); | 1063 | iBookmark *bm = get_Bookmarks(bookmarks_App(), item->id); |
1043 | const iBool isRemote = hasTag_Bookmark(bm, remote_BookmarkTag); | 1064 | const iBool isRemote = hasTag_Bookmark(bm, remote_BookmarkTag); |
1044 | iChar icon = isRemote ? 0x1f588 : bm->icon; | 1065 | iChar icon = isRemote ? 0x1f588 : bm->icon; |
@@ -1131,7 +1152,6 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
1131 | return iTrue; | 1152 | return iTrue; |
1132 | } | 1153 | } |
1133 | if (isCommand_Widget(w, ev, "feed.entry.edit")) { | 1154 | if (isCommand_Widget(w, ev, "feed.entry.edit")) { |
1134 | setFlags_Widget(w, disabled_WidgetFlag, iTrue); | ||
1135 | makeFeedSettings_Widget(id_Bookmark(feedBookmark)); | 1155 | makeFeedSettings_Widget(id_Bookmark(feedBookmark)); |
1136 | return iTrue; | 1156 | return iTrue; |
1137 | } | 1157 | } |
@@ -1307,6 +1327,10 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
1307 | setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_ARROW); | 1327 | setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_ARROW); |
1308 | } | 1328 | } |
1309 | } | 1329 | } |
1330 | if (d->contextIndex != iInvalidPos) { | ||
1331 | invalidateItem_ListWidget(d->list, d->contextIndex); | ||
1332 | d->contextIndex = iInvalidPos; | ||
1333 | } | ||
1310 | } | 1334 | } |
1311 | if (d->menu && ev->type == SDL_MOUSEBUTTONDOWN) { | 1335 | if (d->menu && ev->type == SDL_MOUSEBUTTONDOWN) { |
1312 | if (ev->button.button == SDL_BUTTON_RIGHT) { | 1336 | if (ev->button.button == SDL_BUTTON_RIGHT) { |
@@ -1315,7 +1339,12 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
1315 | updateMouseHover_ListWidget(d->list); | 1339 | updateMouseHover_ListWidget(d->list); |
1316 | } | 1340 | } |
1317 | if (constHoverItem_ListWidget(d->list) || isVisible_Widget(d->menu)) { | 1341 | if (constHoverItem_ListWidget(d->list) || isVisible_Widget(d->menu)) { |
1318 | d->contextItem = hoverItem_ListWidget(d->list); | 1342 | d->contextItem = hoverItem_ListWidget(d->list); |
1343 | /* Context is drawn in hover state. */ | ||
1344 | if (d->contextIndex != iInvalidPos) { | ||
1345 | invalidateItem_ListWidget(d->list, d->contextIndex); | ||
1346 | } | ||
1347 | d->contextIndex = hoverItemIndex_ListWidget(d->list); | ||
1319 | /* Update menu items. */ | 1348 | /* Update menu items. */ |
1320 | /* TODO: Some callback-based mechanism would be nice for updating menus right | 1349 | /* TODO: Some callback-based mechanism would be nice for updating menus right |
1321 | before they open? */ | 1350 | before they open? */ |
@@ -1427,18 +1456,30 @@ static void draw_SidebarWidget_(const iSidebarWidget *d) { | |||
1427 | const iRect bounds = bounds_Widget(w); | 1456 | const iRect bounds = bounds_Widget(w); |
1428 | iPaint p; | 1457 | iPaint p; |
1429 | init_Paint(&p); | 1458 | init_Paint(&p); |
1459 | if (flags_Widget(w) & visualOffset_WidgetFlag && isVisible_Widget(w)) { | ||
1460 | fillRect_Paint(&p, boundsWithoutVisualOffset_Widget(w), tmBackground_ColorId); | ||
1461 | } | ||
1430 | draw_Widget(w); | 1462 | draw_Widget(w); |
1431 | drawVLine_Paint( | 1463 | if (isVisible_Widget(w)) { |
1432 | &p, addX_I2(topRight_Rect(bounds), -1), height_Rect(bounds), uiSeparator_ColorId); | 1464 | drawVLine_Paint( |
1465 | &p, | ||
1466 | addX_I2(d->side == left_SideBarSide ? topRight_Rect(bounds) : topLeft_Rect(bounds), -1), | ||
1467 | height_Rect(bounds), | ||
1468 | uiSeparator_ColorId); | ||
1469 | } | ||
1433 | } | 1470 | } |
1434 | 1471 | ||
1435 | static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect, | 1472 | static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect, |
1436 | const iListWidget *list) { | 1473 | const iListWidget *list) { |
1437 | const iSidebarWidget *sidebar = findParentClass_Widget(constAs_Widget(list), | 1474 | const iSidebarWidget *sidebar = findParentClass_Widget(constAs_Widget(list), |
1438 | &Class_SidebarWidget); | 1475 | &Class_SidebarWidget); |
1476 | const iBool isMenuVisible = isVisible_Widget(sidebar->menu); | ||
1439 | const iBool isPressing = isMouseDown_ListWidget(list); | 1477 | const iBool isPressing = isMouseDown_ListWidget(list); |
1440 | const iBool isHover = isHover_Widget(constAs_Widget(list)) && | 1478 | const iBool isHover = |
1441 | constHoverItem_ListWidget(list) == d; | 1479 | (!isMenuVisible && |
1480 | isHover_Widget(constAs_Widget(list)) && | ||
1481 | constHoverItem_ListWidget(list) == d) || | ||
1482 | (isMenuVisible && sidebar->contextItem == d); | ||
1442 | const int scrollBarWidth = scrollBarWidth_ListWidget(list); | 1483 | const int scrollBarWidth = scrollBarWidth_ListWidget(list); |
1443 | #if defined (iPlatformApple) | 1484 | #if defined (iPlatformApple) |
1444 | const int blankWidth = 0; | 1485 | const int blankWidth = 0; |
@@ -1652,7 +1693,7 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect, | |||
1652 | drawRange_Text( | 1693 | drawRange_Text( |
1653 | font, cPos, d->listItem.isSelected ? iconColor : metaFg, range_String(&icon)); | 1694 | font, cPos, d->listItem.isSelected ? iconColor : metaFg, range_String(&icon)); |
1654 | deinit_String(&icon); | 1695 | deinit_String(&icon); |
1655 | drawRange_Text(d->listItem.isSelected ? uiContentBold_FontId : font, | 1696 | drawRange_Text(d->listItem.isSelected ? sidebar->itemFonts[1] : font, |
1656 | add_I2(cPos, init_I2(indent, 0)), | 1697 | add_I2(cPos, init_I2(indent, 0)), |
1657 | fg, | 1698 | fg, |
1658 | range_String(&d->label)); | 1699 | range_String(&d->label)); |
diff --git a/src/ui/text.c b/src/ui/text.c index 9838fb00..889aa2e4 100644 --- a/src/ui/text.c +++ b/src/ui/text.c | |||
@@ -781,6 +781,7 @@ enum iRunMode { | |||
781 | permanentColorFlag_RunMode = iBit(11), | 781 | permanentColorFlag_RunMode = iBit(11), |
782 | alwaysVariableWidthFlag_RunMode = iBit(12), | 782 | alwaysVariableWidthFlag_RunMode = iBit(12), |
783 | fillBackground_RunMode = iBit(13), | 783 | fillBackground_RunMode = iBit(13), |
784 | stopAtNewline_RunMode = iBit(14), /* don't advance past \n, consider it a wrap pos */ | ||
784 | }; | 785 | }; |
785 | 786 | ||
786 | static enum iFontId fontId_Text_(const iFont *font) { | 787 | static enum iFontId fontId_Text_(const iFont *font) { |
@@ -923,7 +924,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
923 | } | 924 | } |
924 | /* TODO: Check out if `uc_wordbreak_property()` from libunistring can be used here. */ | 925 | /* TODO: Check out if `uc_wordbreak_property()` from libunistring can be used here. */ |
925 | if (ch == '\n') { | 926 | if (ch == '\n') { |
926 | if (args->xposLimit > 0 && ~mode & noWrapFlag_RunMode) { | 927 | if (args->xposLimit > 0 && mode & stopAtNewline_RunMode) { |
927 | /* Stop the line here, this is a hard warp. */ | 928 | /* Stop the line here, this is a hard warp. */ |
928 | if (args->continueFrom_out) { | 929 | if (args->continueFrom_out) { |
929 | *args->continueFrom_out = chPos; | 930 | *args->continueFrom_out = chPos; |
@@ -1148,7 +1149,8 @@ iInt2 advanceRange_Text(int fontId, iRangecc text) { | |||
1148 | iInt2 tryAdvance_Text(int fontId, iRangecc text, int width, const char **endPos) { | 1149 | iInt2 tryAdvance_Text(int fontId, iRangecc text, int width, const char **endPos) { |
1149 | int advance; | 1150 | int advance; |
1150 | const int height = run_Font_(font_Text_(fontId), | 1151 | const int height = run_Font_(font_Text_(fontId), |
1151 | &(iRunArgs){ .mode = measure_RunMode | runFlagsFromId_(fontId), | 1152 | &(iRunArgs){ .mode = measure_RunMode | stopAtNewline_RunMode | |
1153 | runFlagsFromId_(fontId), | ||
1152 | .text = text, | 1154 | .text = text, |
1153 | .xposLimit = width, | 1155 | .xposLimit = width, |
1154 | .continueFrom_out = endPos, | 1156 | .continueFrom_out = endPos, |
@@ -1161,6 +1163,7 @@ iInt2 tryAdvanceNoWrap_Text(int fontId, iRangecc text, int width, const char **e | |||
1161 | int advance; | 1163 | int advance; |
1162 | const int height = run_Font_(font_Text_(fontId), | 1164 | const int height = run_Font_(font_Text_(fontId), |
1163 | &(iRunArgs){ .mode = measure_RunMode | noWrapFlag_RunMode | | 1165 | &(iRunArgs){ .mode = measure_RunMode | noWrapFlag_RunMode | |
1166 | stopAtNewline_RunMode | | ||
1164 | runFlagsFromId_(fontId), | 1167 | runFlagsFromId_(fontId), |
1165 | .text = text, | 1168 | .text = text, |
1166 | .xposLimit = width, | 1169 | .xposLimit = width, |
@@ -1269,7 +1272,7 @@ int drawWrapRange_Text(int fontId, iInt2 pos, int maxWidth, int color, iRangecc | |||
1269 | const iInt2 adv = tryAdvance_Text(fontId, text, maxWidth, &endp); | 1272 | const iInt2 adv = tryAdvance_Text(fontId, text, maxWidth, &endp); |
1270 | drawRange_Text(fontId, pos, color, (iRangecc){ text.start, endp }); | 1273 | drawRange_Text(fontId, pos, color, (iRangecc){ text.start, endp }); |
1271 | text.start = endp; | 1274 | text.start = endp; |
1272 | pos.y += adv.y; | 1275 | pos.y += iMax(adv.y, lineHeight_Text(fontId)); |
1273 | } | 1276 | } |
1274 | return pos.y; | 1277 | return pos.y; |
1275 | } | 1278 | } |
@@ -1404,9 +1407,21 @@ iString *renderBlockChars_Text(const iBlock *fontData, int height, enum iTextBlo | |||
1404 | 1407 | ||
1405 | iDefineTypeConstructionArgs(TextBuf, (int font, int color, const char *text), font, color, text) | 1408 | iDefineTypeConstructionArgs(TextBuf, (int font, int color, const char *text), font, color, text) |
1406 | 1409 | ||
1407 | void init_TextBuf(iTextBuf *d, int font, int color, const char *text) { | 1410 | static void initWrap_TextBuf_(iTextBuf *d, int font, int color, int maxWidth, iBool doWrap, const char *text) { |
1408 | SDL_Renderer *render = text_.render; | 1411 | SDL_Renderer *render = text_.render; |
1409 | d->size = advance_Text(font, text); | 1412 | if (maxWidth == 0) { |
1413 | d->size = advance_Text(font, text); | ||
1414 | } | ||
1415 | else { | ||
1416 | d->size = zero_I2(); | ||
1417 | iRangecc content = range_CStr(text); | ||
1418 | while (!isEmpty_Range(&content)) { | ||
1419 | const iInt2 size = (doWrap ? tryAdvance_Text(font, content, maxWidth, &content.start) | ||
1420 | : tryAdvanceNoWrap_Text(font, content, maxWidth, &content.start)); | ||
1421 | d->size.x = iMax(d->size.x, size.x); | ||
1422 | d->size.y += iMax(size.y, lineHeight_Text(font)); | ||
1423 | } | ||
1424 | } | ||
1410 | SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); | 1425 | SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); |
1411 | if (d->size.x * d->size.y) { | 1426 | if (d->size.x * d->size.y) { |
1412 | d->texture = SDL_CreateTexture(render, | 1427 | d->texture = SDL_CreateTexture(render, |
@@ -1424,17 +1439,50 @@ void init_TextBuf(iTextBuf *d, int font, int color, const char *text) { | |||
1424 | SDL_SetTextureBlendMode(text_.cache, SDL_BLENDMODE_NONE); /* blended when TextBuf is drawn */ | 1439 | SDL_SetTextureBlendMode(text_.cache, SDL_BLENDMODE_NONE); /* blended when TextBuf is drawn */ |
1425 | SDL_SetRenderDrawColor(text_.render, 0, 0, 0, 0); | 1440 | SDL_SetRenderDrawColor(text_.render, 0, 0, 0, 0); |
1426 | SDL_RenderClear(text_.render); | 1441 | SDL_RenderClear(text_.render); |
1427 | draw_Text_(font, zero_I2(), color | fillBackground_ColorId, range_CStr(text)); | 1442 | const int fg = color | fillBackground_ColorId; |
1443 | iRangecc range = range_CStr(text); | ||
1444 | if (maxWidth == 0) { | ||
1445 | draw_Text_(font, zero_I2(), fg, range); | ||
1446 | } | ||
1447 | else if (doWrap) { | ||
1448 | drawWrapRange_Text(font, zero_I2(), maxWidth, fg, range); | ||
1449 | } | ||
1450 | else { | ||
1451 | iInt2 pos = zero_I2(); | ||
1452 | while (!isEmpty_Range(&range)) { | ||
1453 | const char *endp; | ||
1454 | tryAdvanceNoWrap_Text(font, range, maxWidth, &endp); | ||
1455 | draw_Text_(font, pos, fg, (iRangecc){ range.start, endp }); | ||
1456 | range.start = endp; | ||
1457 | pos.y += lineHeight_Text(font); | ||
1458 | } | ||
1459 | } | ||
1428 | SDL_SetTextureBlendMode(text_.cache, SDL_BLENDMODE_BLEND); | 1460 | SDL_SetTextureBlendMode(text_.cache, SDL_BLENDMODE_BLEND); |
1429 | SDL_SetRenderTarget(render, oldTarget); | 1461 | SDL_SetRenderTarget(render, oldTarget); |
1430 | SDL_SetTextureBlendMode(d->texture, SDL_BLENDMODE_BLEND); | 1462 | SDL_SetTextureBlendMode(d->texture, SDL_BLENDMODE_BLEND); |
1431 | } | 1463 | } |
1432 | } | 1464 | } |
1433 | 1465 | ||
1466 | void init_TextBuf(iTextBuf *d, int font, int color, const char *text) { | ||
1467 | initWrap_TextBuf_(d, font, color, 0, iFalse, text); | ||
1468 | } | ||
1469 | |||
1434 | void deinit_TextBuf(iTextBuf *d) { | 1470 | void deinit_TextBuf(iTextBuf *d) { |
1435 | SDL_DestroyTexture(d->texture); | 1471 | SDL_DestroyTexture(d->texture); |
1436 | } | 1472 | } |
1437 | 1473 | ||
1474 | iTextBuf *newBound_TextBuf(int font, int color, int boundWidth, const char *text) { | ||
1475 | iTextBuf *d = iMalloc(TextBuf); | ||
1476 | initWrap_TextBuf_(d, font, color, boundWidth, iFalse, text); | ||
1477 | return d; | ||
1478 | } | ||
1479 | |||
1480 | iTextBuf *newWrap_TextBuf(int font, int color, int wrapWidth, const char *text) { | ||
1481 | iTextBuf *d = iMalloc(TextBuf); | ||
1482 | initWrap_TextBuf_(d, font, color, wrapWidth, iTrue, text); | ||
1483 | return d; | ||
1484 | } | ||
1485 | |||
1438 | void draw_TextBuf(const iTextBuf *d, iInt2 pos, int color) { | 1486 | void draw_TextBuf(const iTextBuf *d, iInt2 pos, int color) { |
1439 | const iColor clr = get_Color(color); | 1487 | const iColor clr = get_Color(color); |
1440 | SDL_SetTextureColorMod(d->texture, clr.r, clr.g, clr.b); | 1488 | SDL_SetTextureColorMod(d->texture, clr.r, clr.g, clr.b); |
diff --git a/src/ui/text.h b/src/ui/text.h index c6091599..044ddd32 100644 --- a/src/ui/text.h +++ b/src/ui/text.h | |||
@@ -170,10 +170,13 @@ iString * renderBlockChars_Text (const iBlock *fontData, int height, enum iT | |||
170 | 170 | ||
171 | iDeclareType(TextBuf) | 171 | iDeclareType(TextBuf) |
172 | iDeclareTypeConstructionArgs(TextBuf, int font, int color, const char *text) | 172 | iDeclareTypeConstructionArgs(TextBuf, int font, int color, const char *text) |
173 | 173 | ||
174 | struct Impl_TextBuf { | 174 | struct Impl_TextBuf { |
175 | SDL_Texture *texture; | 175 | SDL_Texture *texture; |
176 | iInt2 size; | 176 | iInt2 size; |
177 | }; | 177 | }; |
178 | 178 | ||
179 | void draw_TextBuf (const iTextBuf *, iInt2 pos, int color); | 179 | iTextBuf * newBound_TextBuf(int font, int color, int boundWidth, const char *text); /* does not word wrap */ |
180 | iTextBuf * newWrap_TextBuf (int font, int color, int wrapWidth, const char *text); | ||
181 | |||
182 | void draw_TextBuf (const iTextBuf *, iInt2 pos, int color); | ||
diff --git a/src/ui/touch.c b/src/ui/touch.c index b2c52526..74a22baf 100644 --- a/src/ui/touch.c +++ b/src/ui/touch.c | |||
@@ -227,7 +227,7 @@ static void dispatchButtonUp_Touch_(iFloat3 pos) { | |||
227 | 227 | ||
228 | static void dispatchNotification_Touch_(const iTouch *d, int code) { | 228 | static void dispatchNotification_Touch_(const iTouch *d, int code) { |
229 | if (d->affinity) { | 229 | if (d->affinity) { |
230 | iRoot *oldRoot = get_Root(); | 230 | iRoot *oldRoot = current_Root(); |
231 | setCurrent_Root(d->affinity->root); | 231 | setCurrent_Root(d->affinity->root); |
232 | dispatchEvent_Widget(d->affinity, (SDL_Event *) &(SDL_UserEvent){ | 232 | dispatchEvent_Widget(d->affinity, (SDL_Event *) &(SDL_UserEvent){ |
233 | .type = SDL_USEREVENT, | 233 | .type = SDL_USEREVENT, |
@@ -318,6 +318,8 @@ static void update_TouchState_(void *ptr) { | |||
318 | if (pixels.x || pixels.y) { | 318 | if (pixels.x || pixels.y) { |
319 | subv_F3(&mom->accum, initI2_F3(pixels)); | 319 | subv_F3(&mom->accum, initI2_F3(pixels)); |
320 | dispatchMotion_Touch_(mom->pos, 0); | 320 | dispatchMotion_Touch_(mom->pos, 0); |
321 | iAssert(mom->affinity); | ||
322 | setCurrent_Root(mom->affinity->root); | ||
321 | dispatchEvent_Widget(mom->affinity, (SDL_Event *) &(SDL_MouseWheelEvent){ | 323 | dispatchEvent_Widget(mom->affinity, (SDL_Event *) &(SDL_MouseWheelEvent){ |
322 | .type = SDL_MOUSEWHEEL, | 324 | .type = SDL_MOUSEWHEEL, |
323 | .timestamp = nowTime, | 325 | .timestamp = nowTime, |
@@ -334,26 +336,13 @@ static void update_TouchState_(void *ptr) { | |||
334 | } | 336 | } |
335 | /* Keep updating if interaction is still ongoing. */ | 337 | /* Keep updating if interaction is still ongoing. */ |
336 | if (!isEmpty_Array(d->touches) || !isEmpty_Array(d->moms)) { | 338 | if (!isEmpty_Array(d->touches) || !isEmpty_Array(d->moms)) { |
337 | addTicker_App(update_TouchState_, ptr); | 339 | addTickerRoot_App(update_TouchState_, NULL, ptr); |
338 | } | 340 | } |
339 | } | 341 | } |
340 | 342 | ||
341 | static iWidget *findOverflowScrollable_Widget_(iWidget *d) { | ||
342 | const iInt2 rootSize = size_Root(d->root); | ||
343 | for (iWidget *w = d; w; w = parent_Widget(w)) { | ||
344 | if (flags_Widget(w) & overflowScrollable_WidgetFlag) { | ||
345 | if (height_Widget(w) > rootSize.y && !hasVisibleChildOnTop_Widget(w)) { | ||
346 | return w; | ||
347 | } | ||
348 | return NULL; | ||
349 | } | ||
350 | } | ||
351 | return NULL; | ||
352 | } | ||
353 | |||
354 | static iWidget *findSlidePanel_Widget_(iWidget *d) { | 343 | static iWidget *findSlidePanel_Widget_(iWidget *d) { |
355 | for (iWidget *w = d; w; w = parent_Widget(w)) { | 344 | for (iWidget *w = d; w; w = parent_Widget(w)) { |
356 | if (isVisible_Widget(w) && flags_Widget(w) & horizontalOffset_WidgetFlag) { | 345 | if (isVisible_Widget(w) && flags_Widget(w) & edgeDraggable_WidgetFlag) { |
357 | return w; | 346 | return w; |
358 | } | 347 | } |
359 | } | 348 | } |
@@ -466,6 +455,8 @@ iBool processEvent_Touch(const SDL_Event *ev) { | |||
466 | if (edge == left_TouchEdge) { | 455 | if (edge == left_TouchEdge) { |
467 | dragging = findSlidePanel_Widget_(aff); | 456 | dragging = findSlidePanel_Widget_(aff); |
468 | if (dragging) { | 457 | if (dragging) { |
458 | // printf("Selected for dragging: "); | ||
459 | // identify_Widget(dragging); | ||
469 | setFlags_Widget(dragging, dragged_WidgetFlag, iTrue); | 460 | setFlags_Widget(dragging, dragged_WidgetFlag, iTrue); |
470 | } | 461 | } |
471 | } | 462 | } |
@@ -492,7 +483,7 @@ iBool processEvent_Touch(const SDL_Event *ev) { | |||
492 | } | 483 | } |
493 | /* This may begin a pinch. */ | 484 | /* This may begin a pinch. */ |
494 | checkNewPinch_TouchState_(d, back_Array(d->touches)); | 485 | checkNewPinch_TouchState_(d, back_Array(d->touches)); |
495 | addTicker_App(update_TouchState_, d); | 486 | addTickerRoot_App(update_TouchState_, NULL, d); |
496 | } | 487 | } |
497 | else if (ev->type == SDL_FINGERMOTION) { | 488 | else if (ev->type == SDL_FINGERMOTION) { |
498 | iTouch *touch = find_TouchState_(d, fing->fingerId); | 489 | iTouch *touch = find_TouchState_(d, fing->fingerId); |
@@ -544,7 +535,7 @@ iBool processEvent_Touch(const SDL_Event *ev) { | |||
544 | divvf_F3(&touch->accum, 6); | 535 | divvf_F3(&touch->accum, 6); |
545 | divfv_I2(&pixels, 6); | 536 | divfv_I2(&pixels, 6); |
546 | /* Allow scrolling a scrollable widget. */ | 537 | /* Allow scrolling a scrollable widget. */ |
547 | iWidget *flow = findOverflowScrollable_Widget_(touch->affinity); | 538 | iWidget *flow = findOverflowScrollable_Widget(touch->affinity); |
548 | if (flow) { | 539 | if (flow) { |
549 | touch->affinity = flow; | 540 | touch->affinity = flow; |
550 | } | 541 | } |
@@ -567,7 +558,7 @@ iBool processEvent_Touch(const SDL_Event *ev) { | |||
567 | } | 558 | } |
568 | /* Edge swipe aborted? */ | 559 | /* Edge swipe aborted? */ |
569 | if (touch->edge == left_TouchEdge) { | 560 | if (touch->edge == left_TouchEdge) { |
570 | if (fing->dx < 0) { | 561 | if (fing->dx < 0 && x_F3(touch->pos[0]) < tapRadiusPt_ * window->pixelRatio) { |
571 | touch->edge = none_TouchEdge; | 562 | touch->edge = none_TouchEdge; |
572 | if (touch->edgeDragging) { | 563 | if (touch->edgeDragging) { |
573 | setFlags_Widget(touch->edgeDragging, dragged_WidgetFlag, iFalse); | 564 | setFlags_Widget(touch->edgeDragging, dragged_WidgetFlag, iFalse); |
@@ -598,6 +589,7 @@ iBool processEvent_Touch(const SDL_Event *ev) { | |||
598 | if (pixels.x || pixels.y) { | 589 | if (pixels.x || pixels.y) { |
599 | setFocus_Widget(NULL); | 590 | setFocus_Widget(NULL); |
600 | dispatchMotion_Touch_(touch->pos[0], 0); | 591 | dispatchMotion_Touch_(touch->pos[0], 0); |
592 | setCurrent_Root(touch->affinity->root); | ||
601 | dispatchEvent_Widget(touch->affinity, (SDL_Event *) &(SDL_MouseWheelEvent){ | 593 | dispatchEvent_Widget(touch->affinity, (SDL_Event *) &(SDL_MouseWheelEvent){ |
602 | .type = SDL_MOUSEWHEEL, | 594 | .type = SDL_MOUSEWHEEL, |
603 | .timestamp = SDL_GetTicks(), | 595 | .timestamp = SDL_GetTicks(), |
@@ -642,17 +634,19 @@ iBool processEvent_Touch(const SDL_Event *ev) { | |||
642 | continue; | 634 | continue; |
643 | } | 635 | } |
644 | /* Edge swipes do not generate momentum. */ | 636 | /* Edge swipes do not generate momentum. */ |
637 | const size_t lastIndex = iMin(touch->posCount - 1, lastIndex_Touch_); | ||
645 | const uint32_t duration = nowTime - touch->startTime; | 638 | const uint32_t duration = nowTime - touch->startTime; |
646 | const iFloat3 gestureVector = sub_F3(pos, touch->startPos); | 639 | const iFloat3 gestureVector = sub_F3(pos, touch->pos[lastIndex]); |
647 | iFloat3 velocity = zero_F3(); | 640 | iFloat3 velocity = zero_F3(); |
648 | if (touch->edge && fabsf(2 * x_F3(gestureVector)) > fabsf(y_F3(gestureVector)) && | 641 | if (touch->edge && fabsf(2 * x_F3(gestureVector)) > fabsf(y_F3(gestureVector)) && |
649 | !isStationary_Touch_(touch)) { | 642 | !isStationary_Touch_(touch)) { |
650 | dispatchClick_Touch_(touch, touch->edge == left_TouchEdge ? SDL_BUTTON_X1 | 643 | const int swipeDir = x_F3(gestureVector) > 0 ? +1 : -1; |
651 | : SDL_BUTTON_X2); | 644 | dispatchClick_Touch_(touch, |
645 | touch->edge == left_TouchEdge && swipeDir > 0 ? SDL_BUTTON_X1 : | ||
646 | touch->edge == right_TouchEdge && swipeDir < 0 ? SDL_BUTTON_X2 : 0); | ||
652 | setHover_Widget(NULL); | 647 | setHover_Widget(NULL); |
653 | } | 648 | } |
654 | else { | 649 | else { |
655 | const size_t lastIndex = iMin(touch->posCount - 1, lastIndex_Touch_); | ||
656 | const uint32_t elapsed = fing->timestamp - touch->posTime[lastIndex]; | 650 | const uint32_t elapsed = fing->timestamp - touch->posTime[lastIndex]; |
657 | const float minVelocity = 400.0f; | 651 | const float minVelocity = 400.0f; |
658 | if (elapsed < 150) { | 652 | if (elapsed < 150) { |
@@ -758,6 +752,20 @@ iInt2 latestPosition_Touch(void) { | |||
758 | return touchState_()->currentTouchPos; | 752 | return touchState_()->currentTouchPos; |
759 | } | 753 | } |
760 | 754 | ||
755 | iBool isHovering_Touch(void) { | ||
756 | iTouchState *d = touchState_(); | ||
757 | if (numFingers_Touch() == 1) { | ||
758 | const iTouch *touch = constFront_Array(d->touches); | ||
759 | if (touch->isTapBegun && isStationary_Touch_(touch)) { | ||
760 | return iTrue; | ||
761 | } | ||
762 | if (touch->isTapAndHold) { | ||
763 | return iTrue; | ||
764 | } | ||
765 | } | ||
766 | return iFalse; | ||
767 | } | ||
768 | |||
761 | size_t numFingers_Touch(void) { | 769 | size_t numFingers_Touch(void) { |
762 | return size_Array(touchState_()->touches); | 770 | return size_Array(touchState_()->touches); |
763 | } | 771 | } |
diff --git a/src/ui/touch.h b/src/ui/touch.h index 1a6fb350..e048224a 100644 --- a/src/ui/touch.h +++ b/src/ui/touch.h | |||
@@ -41,4 +41,5 @@ enum iWidgetTouchMode widgetMode_Touch (const iWidget *widget); | |||
41 | void widgetDestroyed_Touch (iWidget *widget); | 41 | void widgetDestroyed_Touch (iWidget *widget); |
42 | 42 | ||
43 | iInt2 latestPosition_Touch (void); /* valid during processing of current event */ | 43 | iInt2 latestPosition_Touch (void); /* valid during processing of current event */ |
44 | iBool isHovering_Touch (void); /* stationary touch or a long-press drag ongoing */ | ||
44 | size_t numFingers_Touch (void); | 45 | size_t numFingers_Touch (void); |
diff --git a/src/ui/util.c b/src/ui/util.c index 04cdf27f..4b35f8f7 100644 --- a/src/ui/util.c +++ b/src/ui/util.c | |||
@@ -80,7 +80,7 @@ void toString_Sym(int key, int kmods, iString *str) { | |||
80 | appendChar_String(str, 0x2325); | 80 | appendChar_String(str, 0x2325); |
81 | } | 81 | } |
82 | if (kmods & KMOD_SHIFT) { | 82 | if (kmods & KMOD_SHIFT) { |
83 | appendChar_String(str, 0x21e7); | 83 | appendCStr_String(str, shift_Icon); |
84 | } | 84 | } |
85 | if (kmods & KMOD_GUI) { | 85 | if (kmods & KMOD_GUI) { |
86 | appendChar_String(str, 0x2318); | 86 | appendChar_String(str, 0x2318); |
@@ -93,7 +93,7 @@ void toString_Sym(int key, int kmods, iString *str) { | |||
93 | appendCStr_String(str, "Alt+"); | 93 | appendCStr_String(str, "Alt+"); |
94 | } | 94 | } |
95 | if (kmods & KMOD_SHIFT) { | 95 | if (kmods & KMOD_SHIFT) { |
96 | appendCStr_String(str, "Shift+"); | 96 | appendCStr_String(str, shift_Icon "+"); |
97 | } | 97 | } |
98 | if (kmods & KMOD_GUI) { | 98 | if (kmods & KMOD_GUI) { |
99 | appendCStr_String(str, "Meta+"); | 99 | appendCStr_String(str, "Meta+"); |
@@ -138,7 +138,7 @@ void toString_Sym(int key, int kmods, iString *str) { | |||
138 | } | 138 | } |
139 | else if (key == SDLK_RETURN) { | 139 | else if (key == SDLK_RETURN) { |
140 | removePlus_(str); | 140 | removePlus_(str); |
141 | appendChar_String(str, 0x21a9); /* Leftwards arrow with a hook */ | 141 | appendCStr_String(str, return_Icon); /* Leftwards arrow with a hook */ |
142 | } | 142 | } |
143 | else { | 143 | else { |
144 | appendCStr_String(str, SDL_GetKeyName(key)); | 144 | appendCStr_String(str, SDL_GetKeyName(key)); |
@@ -837,10 +837,7 @@ void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) { | |||
837 | if (postCommands) { | 837 | if (postCommands) { |
838 | postCommand_Widget(d, "menu.opened"); | 838 | postCommand_Widget(d, "menu.opened"); |
839 | } | 839 | } |
840 | if (isPortraitPhone) { | 840 | setupMenuTransition_Mobile(d, iTrue); |
841 | setVisualOffset_Widget(d, isSlidePanel ? width_Widget(d) : height_Widget(d), 0, 0); | ||
842 | setVisualOffset_Widget(d, 0, 330, easeOut_AnimFlag | softer_AnimFlag); | ||
843 | } | ||
844 | } | 841 | } |
845 | 842 | ||
846 | void closeMenu_Widget(iWidget *d) { | 843 | void closeMenu_Widget(iWidget *d) { |
@@ -851,14 +848,7 @@ void closeMenu_Widget(iWidget *d) { | |||
851 | setFlags_Widget(findChild_Widget(d, "menu.cancel"), disabled_WidgetFlag, iTrue); | 848 | setFlags_Widget(findChild_Widget(d, "menu.cancel"), disabled_WidgetFlag, iTrue); |
852 | postRefresh_App(); | 849 | postRefresh_App(); |
853 | postCommand_Widget(d, "menu.closed"); | 850 | postCommand_Widget(d, "menu.closed"); |
854 | if (isPortrait_App() && deviceType_App() == phone_AppDeviceType) { | 851 | setupMenuTransition_Mobile(d, iFalse); |
855 | const iBool wasDragged = iAbs(value_Anim(&d->visualOffset) - 0) > 1; | ||
856 | setVisualOffset_Widget(d, | ||
857 | flags_Widget(d) & horizontalOffset_WidgetFlag ? | ||
858 | width_Widget(d) : height_Widget(d), | ||
859 | wasDragged ? 100 : 200, | ||
860 | wasDragged ? 0 : easeIn_AnimFlag | softer_AnimFlag); | ||
861 | } | ||
862 | } | 852 | } |
863 | 853 | ||
864 | iLabelWidget *findMenuItem_Widget(iWidget *menu, const char *command) { | 854 | iLabelWidget *findMenuItem_Widget(iWidget *menu, const char *command) { |
@@ -904,7 +894,7 @@ static iBool isTabPage_Widget_(const iWidget *tabs, const iWidget *page) { | |||
904 | static void unfocusFocusInsideTabPage_(const iWidget *page) { | 894 | static void unfocusFocusInsideTabPage_(const iWidget *page) { |
905 | iWidget *focus = focus_Widget(); | 895 | iWidget *focus = focus_Widget(); |
906 | if (page && focus && hasParent_Widget(focus, page)) { | 896 | if (page && focus && hasParent_Widget(focus, page)) { |
907 | printf("unfocus inside page: %p\n", focus); | 897 | // printf("unfocus inside page: %p\n", focus); |
908 | setFocus_Widget(NULL); | 898 | setFocus_Widget(NULL); |
909 | } | 899 | } |
910 | } | 900 | } |
@@ -1127,46 +1117,6 @@ size_t tabCount_Widget(const iWidget *tabs) { | |||
1127 | 1117 | ||
1128 | /*-----------------------------------------------------------------------------------------------*/ | 1118 | /*-----------------------------------------------------------------------------------------------*/ |
1129 | 1119 | ||
1130 | static void acceptFilePath_(iWidget *dlg) { | ||
1131 | iInputWidget *input = findChild_Widget(dlg, "input"); | ||
1132 | iString *path = makeAbsolute_Path(text_InputWidget(input)); | ||
1133 | postCommandf_App("%s path:%s", cstr_String(id_Widget(dlg)), cstr_String(path)); | ||
1134 | destroy_Widget(dlg); | ||
1135 | delete_String(path); | ||
1136 | } | ||
1137 | |||
1138 | iBool filePathHandler_(iWidget *dlg, const char *cmd) { | ||
1139 | iWidget *ptr = as_Widget(pointer_Command(cmd)); | ||
1140 | if (equal_Command(cmd, "input.ended")) { | ||
1141 | if (hasParent_Widget(ptr, dlg)) { | ||
1142 | if (arg_Command(cmd)) { | ||
1143 | acceptFilePath_(dlg); | ||
1144 | } | ||
1145 | else { | ||
1146 | destroy_Widget(dlg); | ||
1147 | } | ||
1148 | return iTrue; | ||
1149 | } | ||
1150 | return iFalse; | ||
1151 | } | ||
1152 | else if (ptr && !hasParent_Widget(ptr, dlg)) { | ||
1153 | /* Command from outside the dialog, so dismiss the dialog. */ | ||
1154 | if (!equal_Command(cmd, "focus.lost")) { | ||
1155 | destroy_Widget(dlg); | ||
1156 | } | ||
1157 | return iFalse; | ||
1158 | } | ||
1159 | else if (equal_Command(cmd, "filepath.cancel")) { | ||
1160 | end_InputWidget(findChild_Widget(dlg, "input"), iFalse); | ||
1161 | destroy_Widget(dlg); | ||
1162 | return iTrue; | ||
1163 | } | ||
1164 | else if (equal_Command(cmd, "filepath.accept")) { | ||
1165 | acceptFilePath_(dlg); | ||
1166 | return iTrue; | ||
1167 | } | ||
1168 | return iFalse; | ||
1169 | } | ||
1170 | 1120 | ||
1171 | iWidget *makeSheet_Widget(const char *id) { | 1121 | iWidget *makeSheet_Widget(const char *id) { |
1172 | iWidget *sheet = new_Widget(); | 1122 | iWidget *sheet = new_Widget(); |
@@ -1183,609 +1133,6 @@ iWidget *makeSheet_Widget(const char *id) { | |||
1183 | return sheet; | 1133 | return sheet; |
1184 | } | 1134 | } |
1185 | 1135 | ||
1186 | static void updateSheetPanelMetrics_(iWidget *sheet) { | ||
1187 | iWidget *navi = findChild_Widget(sheet, "panel.navi"); | ||
1188 | iWidget *naviPad = child_Widget(navi, 0); | ||
1189 | int naviHeight = lineHeight_Text(defaultBig_FontId) + 4 * gap_UI; | ||
1190 | #if defined (iPlatformAppleMobile) | ||
1191 | float left, right, top, bottom; | ||
1192 | safeAreaInsets_iOS(&left, &top, &right, &bottom); | ||
1193 | setPadding_Widget(sheet, left, 0, right, 0); | ||
1194 | navi->rect.pos = init_I2(left, top); | ||
1195 | iConstForEach(PtrArray, i, findChildren_Widget(sheet, "panel.toppad")) { | ||
1196 | iWidget *pad = *i.value; | ||
1197 | setFixedSize_Widget(pad, init1_I2(naviHeight)); | ||
1198 | } | ||
1199 | #endif | ||
1200 | setFixedSize_Widget(navi, init_I2(-1, naviHeight)); | ||
1201 | } | ||
1202 | |||
1203 | static iBool slidePanelHandler_(iWidget *d, const char *cmd) { | ||
1204 | if (equal_Command(cmd, "panel.open")) { | ||
1205 | iWidget *button = pointer_Command(cmd); | ||
1206 | iWidget *panel = userData_Object(button); | ||
1207 | openMenu_Widget(panel, innerToWindow_Widget(panel, zero_I2())); | ||
1208 | setFlags_Widget(panel, disabled_WidgetFlag, iFalse); | ||
1209 | // updateTextCStr_LabelWidget(findWidget_App("panel.back"), ); | ||
1210 | return iTrue; | ||
1211 | } | ||
1212 | if (equal_Command(cmd, "mouse.clicked") && arg_Command(cmd) && | ||
1213 | argLabel_Command(cmd, "button") == SDL_BUTTON_X1) { | ||
1214 | postCommand_App("panel.close"); | ||
1215 | return iTrue; | ||
1216 | } | ||
1217 | if (equal_Command(cmd, "panel.close")) { | ||
1218 | iBool wasClosed = iFalse; | ||
1219 | iForEach(ObjectList, i, children_Widget(parent_Widget(d))) { | ||
1220 | iWidget *child = i.object; | ||
1221 | if (!cmp_String(id_Widget(child), "panel") && isVisible_Widget(child)) { | ||
1222 | closeMenu_Widget(child); | ||
1223 | setFlags_Widget(child, disabled_WidgetFlag, iTrue); | ||
1224 | setFocus_Widget(NULL); | ||
1225 | updateTextCStr_LabelWidget(findWidget_App("panel.back"), "Back"); | ||
1226 | wasClosed = iTrue; | ||
1227 | } | ||
1228 | } | ||
1229 | if (!wasClosed) { | ||
1230 | postCommand_App("prefs.dismiss"); | ||
1231 | } | ||
1232 | return iTrue; | ||
1233 | } | ||
1234 | if (equal_Command(cmd, "document.changed")) { | ||
1235 | postCommand_App("prefs.dismiss"); | ||
1236 | return iFalse; | ||
1237 | } | ||
1238 | if (equal_Command(cmd, "window.resized")) { | ||
1239 | updateSheetPanelMetrics_(parent_Widget(d)); | ||
1240 | } | ||
1241 | return iFalse; | ||
1242 | } | ||
1243 | |||
1244 | static iBool isTwoColumnPage_(iWidget *d) { | ||
1245 | if (cmp_String(id_Widget(d), "dialogbuttons") == 0 || | ||
1246 | cmp_String(id_Widget(d), "prefs.tabs") == 0) { | ||
1247 | return iFalse; | ||
1248 | } | ||
1249 | if (class_Widget(d) == &Class_Widget && childCount_Widget(d) == 2) { | ||
1250 | return class_Widget(child_Widget(d, 0)) == &Class_Widget && | ||
1251 | class_Widget(child_Widget(d, 1)) == &Class_Widget; | ||
1252 | } | ||
1253 | return iFalse; | ||
1254 | } | ||
1255 | |||
1256 | static iBool isOmittedPref_(const iString *id) { | ||
1257 | static const char *omittedPrefs[] = { | ||
1258 | "prefs.smoothscroll", | ||
1259 | "prefs.imageloadscroll", | ||
1260 | "prefs.retainwindow", | ||
1261 | "prefs.ca.file", | ||
1262 | "prefs.ca.path", | ||
1263 | }; | ||
1264 | iForIndices(i, omittedPrefs) { | ||
1265 | if (cmp_String(id, omittedPrefs[i]) == 0) { | ||
1266 | return iTrue; | ||
1267 | } | ||
1268 | } | ||
1269 | return iFalse; | ||
1270 | } | ||
1271 | |||
1272 | enum iPrefsElement { | ||
1273 | panelTitle_PrefsElement, | ||
1274 | heading_PrefsElement, | ||
1275 | toggle_PrefsElement, | ||
1276 | dropdown_PrefsElement, | ||
1277 | radioButton_PrefsElement, | ||
1278 | textInput_PrefsElement, | ||
1279 | }; | ||
1280 | |||
1281 | static iAnyObject *addPanelChild_(iWidget *panel, iAnyObject *child, int64_t flags, | ||
1282 | enum iPrefsElement elementType, | ||
1283 | enum iPrefsElement precedingElementType) { | ||
1284 | /* Erase redundant/unused headings. */ | ||
1285 | if (precedingElementType == heading_PrefsElement && | ||
1286 | (!child || (elementType == heading_PrefsElement || elementType == radioButton_PrefsElement))) { | ||
1287 | iRelease(removeChild_Widget(panel, lastChild_Widget(panel))); | ||
1288 | if (!cmp_String(id_Widget(constAs_Widget(lastChild_Widget(panel))), "padding")) { | ||
1289 | iRelease(removeChild_Widget(panel, lastChild_Widget(panel))); | ||
1290 | } | ||
1291 | } | ||
1292 | if (child) { | ||
1293 | /* Insert padding between different element types. */ | ||
1294 | if (precedingElementType != panelTitle_PrefsElement) { | ||
1295 | if (elementType == heading_PrefsElement || | ||
1296 | (elementType == toggle_PrefsElement && | ||
1297 | precedingElementType != toggle_PrefsElement && | ||
1298 | precedingElementType != heading_PrefsElement) || | ||
1299 | (elementType == dropdown_PrefsElement && | ||
1300 | precedingElementType != dropdown_PrefsElement && | ||
1301 | precedingElementType != heading_PrefsElement) || | ||
1302 | (elementType == textInput_PrefsElement && | ||
1303 | precedingElementType != textInput_PrefsElement && | ||
1304 | precedingElementType != heading_PrefsElement)) { | ||
1305 | addChild_Widget(panel, iClob(makePadding_Widget(lineHeight_Text(defaultBig_FontId)))); | ||
1306 | } | ||
1307 | } | ||
1308 | if ((elementType == toggle_PrefsElement && precedingElementType != toggle_PrefsElement) || | ||
1309 | (elementType == textInput_PrefsElement && precedingElementType != textInput_PrefsElement)) { | ||
1310 | flags |= borderTop_WidgetFlag; | ||
1311 | } | ||
1312 | return addChildFlags_Widget(panel, child, flags); | ||
1313 | } | ||
1314 | return NULL; | ||
1315 | } | ||
1316 | |||
1317 | static void stripTrailingColon_(iLabelWidget *label) { | ||
1318 | const iString *text = text_LabelWidget(label); | ||
1319 | if (endsWith_String(text, ":")) { | ||
1320 | iString *mod = copy_String(text); | ||
1321 | removeEnd_String(mod, 1); | ||
1322 | updateText_LabelWidget(label, mod); | ||
1323 | delete_String(mod); | ||
1324 | } | ||
1325 | } | ||
1326 | |||
1327 | static iLabelWidget *makePanelButton_(const char *text, const char *command) { | ||
1328 | iLabelWidget *btn = new_LabelWidget(text, command); | ||
1329 | setFlags_Widget(as_Widget(btn), | ||
1330 | borderBottom_WidgetFlag | alignLeft_WidgetFlag | | ||
1331 | frameless_WidgetFlag | extraPadding_WidgetFlag, | ||
1332 | iTrue); | ||
1333 | checkIcon_LabelWidget(btn); | ||
1334 | setFont_LabelWidget(btn, defaultBig_FontId); | ||
1335 | setTextColor_LabelWidget(btn, uiTextStrong_ColorId); | ||
1336 | setBackgroundColor_Widget(as_Widget(btn), uiBackgroundSidebar_ColorId); | ||
1337 | return btn; | ||
1338 | } | ||
1339 | |||
1340 | static iWidget *makeValuePadding_(iWidget *value) { | ||
1341 | iInputWidget *input = isInstance_Object(value, &Class_InputWidget) ? (iInputWidget *) value : NULL; | ||
1342 | if (input) { | ||
1343 | setFont_InputWidget(input, defaultBig_FontId); | ||
1344 | setContentPadding_InputWidget(input, 3 * gap_UI, 3 * gap_UI); | ||
1345 | } | ||
1346 | iWidget *pad = new_Widget(); | ||
1347 | setBackgroundColor_Widget(pad, uiBackgroundSidebar_ColorId); | ||
1348 | setPadding_Widget(pad, 0, 1 * gap_UI, 0, 1 * gap_UI); | ||
1349 | addChild_Widget(pad, iClob(value)); | ||
1350 | setFlags_Widget(pad, | ||
1351 | borderBottom_WidgetFlag | | ||
1352 | arrangeVertical_WidgetFlag | | ||
1353 | resizeToParentWidth_WidgetFlag | | ||
1354 | resizeWidthOfChildren_WidgetFlag | | ||
1355 | arrangeHeight_WidgetFlag, | ||
1356 | iTrue); | ||
1357 | return pad; | ||
1358 | } | ||
1359 | |||
1360 | static iWidget *makeValuePaddingWithHeading_(iLabelWidget *heading, iWidget *value) { | ||
1361 | iWidget *div = new_Widget(); | ||
1362 | setFlags_Widget(div, | ||
1363 | borderBottom_WidgetFlag | arrangeHeight_WidgetFlag | | ||
1364 | resizeWidthOfChildren_WidgetFlag | | ||
1365 | arrangeHorizontal_WidgetFlag, iTrue); | ||
1366 | setBackgroundColor_Widget(div, uiBackgroundSidebar_ColorId); | ||
1367 | setPadding_Widget(div, gap_UI, gap_UI, 4 * gap_UI, gap_UI); | ||
1368 | addChildFlags_Widget(div, iClob(heading), 0); | ||
1369 | //setFixedSize_Widget(as_Widget(heading), init_I2(-1, height_Widget(value))); | ||
1370 | setFont_LabelWidget(heading, defaultBig_FontId); | ||
1371 | setTextColor_LabelWidget(heading, uiTextStrong_ColorId); | ||
1372 | if (isInstance_Object(value, &Class_InputWidget)) { | ||
1373 | addChildFlags_Widget(div, iClob(value), expand_WidgetFlag); | ||
1374 | } | ||
1375 | else { | ||
1376 | addChildFlags_Widget(div, iClob(new_Widget()), expand_WidgetFlag); | ||
1377 | addChild_Widget(div, iClob(value)); | ||
1378 | } | ||
1379 | return div; | ||
1380 | } | ||
1381 | |||
1382 | static iWidget *addChildPanel_(iWidget *sheet, iLabelWidget *panelButton, | ||
1383 | const iString *titleText) { | ||
1384 | iWidget *owner = new_Widget(); | ||
1385 | setId_Widget(owner, "panel"); | ||
1386 | setUserData_Object(panelButton, owner); | ||
1387 | setBackgroundColor_Widget(owner, uiBackground_ColorId); | ||
1388 | setId_Widget(addChild_Widget(owner, iClob(makePadding_Widget(0))), "panel.toppad"); | ||
1389 | if (titleText) { | ||
1390 | iLabelWidget *title = | ||
1391 | addChildFlags_Widget(owner, | ||
1392 | iClob(new_LabelWidget(cstr_String(titleText), NULL)), | ||
1393 | alignLeft_WidgetFlag | frameless_WidgetFlag); | ||
1394 | setFont_LabelWidget(title, uiLabelLargeBold_FontId); | ||
1395 | setTextColor_LabelWidget(title, uiHeading_ColorId); | ||
1396 | } | ||
1397 | addChildFlags_Widget(sheet, | ||
1398 | iClob(owner), | ||
1399 | focusRoot_WidgetFlag | hidden_WidgetFlag | disabled_WidgetFlag | | ||
1400 | arrangeVertical_WidgetFlag | resizeWidthOfChildren_WidgetFlag | | ||
1401 | arrangeHeight_WidgetFlag | overflowScrollable_WidgetFlag | | ||
1402 | horizontalOffset_WidgetFlag | commandOnClick_WidgetFlag); | ||
1403 | return owner; | ||
1404 | } | ||
1405 | |||
1406 | void finalizeSheet_Widget(iWidget *sheet) { | ||
1407 | /* The sheet contents are completely rearranged and restyled on a phone. | ||
1408 | We'll set up a linear fullscreen arrangement of the widgets. Sheets are already | ||
1409 | scrollable so they can be taller than the display. In hindsight, it may have been | ||
1410 | easier to create phone versions of each dialog, but at least this works with any | ||
1411 | future changes to the UI (..."works"). At least this way it is possible to enforce | ||
1412 | a consistent styling. */ | ||
1413 | if (deviceType_App() == phone_AppDeviceType && parent_Widget(sheet) == root_Widget(sheet)) { | ||
1414 | if (~flags_Widget(sheet) & keepOnTop_WidgetFlag) { | ||
1415 | /* Already finalized. */ | ||
1416 | arrange_Widget(sheet); | ||
1417 | postRefresh_App(); | ||
1418 | return; | ||
1419 | } | ||
1420 | /* Modify the top sheet to act as a fullscreen background. */ | ||
1421 | setPadding1_Widget(sheet, 0); | ||
1422 | setBackgroundColor_Widget(sheet, uiBackground_ColorId); | ||
1423 | setFlags_Widget(sheet, | ||
1424 | keepOnTop_WidgetFlag | | ||
1425 | parentCannotResize_WidgetFlag | | ||
1426 | arrangeSize_WidgetFlag | | ||
1427 | centerHorizontal_WidgetFlag | | ||
1428 | arrangeVertical_WidgetFlag | | ||
1429 | arrangeHorizontal_WidgetFlag | | ||
1430 | overflowScrollable_WidgetFlag, | ||
1431 | iFalse); | ||
1432 | setFlags_Widget(sheet, | ||
1433 | commandOnClick_WidgetFlag | | ||
1434 | frameless_WidgetFlag | | ||
1435 | resizeWidthOfChildren_WidgetFlag, | ||
1436 | iTrue); | ||
1437 | iPtrArray * contents = collect_PtrArray(new_PtrArray()); /* two-column pages */ | ||
1438 | iPtrArray * panelButtons = collect_PtrArray(new_PtrArray()); | ||
1439 | iWidget * prefsTabs = findChild_Widget(sheet, "prefs.tabs"); | ||
1440 | iWidget * dialogHeading = (prefsTabs ? NULL : child_Widget(sheet, 0)); | ||
1441 | const iBool isPrefs = (prefsTabs != NULL); | ||
1442 | const int64_t panelButtonFlags = borderBottom_WidgetFlag | alignLeft_WidgetFlag | | ||
1443 | frameless_WidgetFlag | extraPadding_WidgetFlag; | ||
1444 | iWidget *topPanel = new_Widget(); | ||
1445 | setId_Widget(topPanel, "panel.top"); | ||
1446 | addChild_Widget(topPanel, iClob(makePadding_Widget(lineHeight_Text(defaultBig_FontId)))); | ||
1447 | if (prefsTabs) { | ||
1448 | iRelease(removeChild_Widget(sheet, child_Widget(sheet, 0))); /* heading */ | ||
1449 | iRelease(removeChild_Widget(sheet, findChild_Widget(sheet, "dialogbuttons"))); | ||
1450 | /* Pull out the pages and make them panels. */ | ||
1451 | iWidget *pages = findChild_Widget(prefsTabs, "tabs.pages"); | ||
1452 | size_t pageCount = tabCount_Widget(prefsTabs); | ||
1453 | for (size_t i = 0; i < pageCount; i++) { | ||
1454 | iString *text = copy_String(text_LabelWidget(tabPageButton_Widget(prefsTabs, tabPage_Widget(prefsTabs, 0)))); | ||
1455 | iWidget *page = removeTabPage_Widget(prefsTabs, 0); | ||
1456 | iWidget *pageContent = child_Widget(page, 1); /* surrounded by padding widgets */ | ||
1457 | pushBack_PtrArray(contents, ref_Object(pageContent)); | ||
1458 | iLabelWidget *panelButton; | ||
1459 | pushBack_PtrArray(panelButtons, | ||
1460 | addChildFlags_Widget(topPanel, | ||
1461 | iClob(panelButton = makePanelButton_( | ||
1462 | i == 1 ? "${heading.prefs.userinterface}" : cstr_String(text), | ||
1463 | "panel.open")), | ||
1464 | (i == 0 ? borderTop_WidgetFlag : 0) | | ||
1465 | chevron_WidgetFlag)); | ||
1466 | const iChar icons[] = { | ||
1467 | 0x02699, /* gear */ | ||
1468 | 0x1f4f1, /* mobile phone */ | ||
1469 | 0x1f3a8, /* palette */ | ||
1470 | 0x1f523, | ||
1471 | 0x1f5a7, /* computer network */ | ||
1472 | }; | ||
1473 | setIcon_LabelWidget(panelButton, icons[i]); | ||
1474 | // setFont_LabelWidget(panelButton, defaultBig_FontId); | ||
1475 | // setBackgroundColor_Widget(as_Widget(panelButton), uiBackgroundSidebar_ColorId); | ||
1476 | iRelease(page); | ||
1477 | delete_String(text); | ||
1478 | } | ||
1479 | destroy_Widget(prefsTabs); | ||
1480 | } | ||
1481 | iForEach(ObjectList, i, children_Widget(sheet)) { | ||
1482 | iWidget *child = i.object; | ||
1483 | if (isTwoColumnPage_(child)) { | ||
1484 | pushBack_PtrArray(contents, removeChild_Widget(sheet, child)); | ||
1485 | } | ||
1486 | else { | ||
1487 | removeChild_Widget(sheet, child); | ||
1488 | addChild_Widget(topPanel, child); | ||
1489 | iRelease(child); | ||
1490 | } | ||
1491 | } | ||
1492 | const iBool useSlidePanels = (size_PtrArray(contents) == size_PtrArray(panelButtons)); | ||
1493 | addChildFlags_Widget(sheet, iClob(topPanel), | ||
1494 | arrangeVertical_WidgetFlag | | ||
1495 | resizeWidthOfChildren_WidgetFlag | arrangeHeight_WidgetFlag | | ||
1496 | overflowScrollable_WidgetFlag | | ||
1497 | commandOnClick_WidgetFlag); | ||
1498 | setCommandHandler_Widget(topPanel, slidePanelHandler_); | ||
1499 | iForEach(PtrArray, j, contents) { | ||
1500 | iWidget *owner = topPanel; | ||
1501 | if (useSlidePanels) { | ||
1502 | /* Create a new child panel. */ | ||
1503 | iLabelWidget *button = at_PtrArray(panelButtons, index_PtrArrayIterator(&j)); | ||
1504 | owner = addChildPanel_(sheet, button, | ||
1505 | collect_String(upper_String(text_LabelWidget(button)))); | ||
1506 | } | ||
1507 | iWidget *pageContent = j.ptr; | ||
1508 | iWidget *headings = child_Widget(pageContent, 0); | ||
1509 | iWidget *values = child_Widget(pageContent, 1); | ||
1510 | enum iPrefsElement prevElement = panelTitle_PrefsElement; | ||
1511 | /* Identify the types of controls in the dialog and restyle/organize them. */ | ||
1512 | while (!isEmpty_ObjectList(children_Widget(headings))) { | ||
1513 | iWidget *heading = child_Widget(headings, 0); | ||
1514 | iWidget *value = child_Widget(values, 0); | ||
1515 | removeChild_Widget(headings, heading); | ||
1516 | removeChild_Widget(values, value); | ||
1517 | /* Can we ignore these widgets? */ | ||
1518 | if (isOmittedPref_(id_Widget(value)) || | ||
1519 | (class_Widget(heading) == &Class_Widget && | ||
1520 | class_Widget(value) == &Class_Widget) /* just padding */) { | ||
1521 | iRelease(heading); | ||
1522 | iRelease(value); | ||
1523 | continue; | ||
1524 | } | ||
1525 | enum iPrefsElement element = toggle_PrefsElement; | ||
1526 | iLabelWidget *headingLabel = NULL; | ||
1527 | iLabelWidget *valueLabel = NULL; | ||
1528 | iInputWidget *valueInput = NULL; | ||
1529 | const iBool isMenuButton = findChild_Widget(value, "menu") != NULL; | ||
1530 | if (isInstance_Object(heading, &Class_LabelWidget)) { | ||
1531 | headingLabel = (iLabelWidget *) heading; | ||
1532 | stripTrailingColon_(headingLabel); | ||
1533 | } | ||
1534 | if (isInstance_Object(value, &Class_LabelWidget)) { | ||
1535 | valueLabel = (iLabelWidget *) value; | ||
1536 | setFont_LabelWidget(valueLabel, defaultBig_FontId); | ||
1537 | } | ||
1538 | if (isInstance_Object(value, &Class_InputWidget)) { | ||
1539 | valueInput = (iInputWidget *) value; | ||
1540 | setFlags_Widget(value, borderBottom_WidgetFlag, iFalse); | ||
1541 | element = textInput_PrefsElement; | ||
1542 | } | ||
1543 | if (childCount_Widget(value) >= 2) { | ||
1544 | if (isInstance_Object(child_Widget(value, 0), &Class_InputWidget)) { | ||
1545 | element = textInput_PrefsElement; | ||
1546 | setPadding_Widget(value, 0, 0, gap_UI, 0); | ||
1547 | valueInput = child_Widget(value, 0); | ||
1548 | } | ||
1549 | } | ||
1550 | if (valueInput) { | ||
1551 | setFont_InputWidget(valueInput, defaultBig_FontId); | ||
1552 | setContentPadding_InputWidget(valueInput, 3 * gap_UI, 0); | ||
1553 | } | ||
1554 | /* Toggles have the button on the right. */ | ||
1555 | if (valueLabel && cmp_String(command_LabelWidget(valueLabel), "toggle") == 0) { | ||
1556 | element = toggle_PrefsElement; | ||
1557 | addPanelChild_(owner, | ||
1558 | iClob(makeValuePaddingWithHeading_(headingLabel, value)), | ||
1559 | 0, | ||
1560 | element, | ||
1561 | prevElement); | ||
1562 | } | ||
1563 | else if (valueLabel && isEmpty_String(text_LabelWidget(valueLabel))) { | ||
1564 | element = heading_PrefsElement; | ||
1565 | iRelease(value); | ||
1566 | addPanelChild_(owner, iClob(heading), 0, element, prevElement); | ||
1567 | setFont_LabelWidget(headingLabel, uiLabel_FontId); | ||
1568 | } | ||
1569 | else if (isMenuButton) { | ||
1570 | element = dropdown_PrefsElement; | ||
1571 | setFlags_Widget(value, | ||
1572 | alignRight_WidgetFlag | noBackground_WidgetFlag | | ||
1573 | frameless_WidgetFlag, iTrue); | ||
1574 | setFlags_Widget(value, alignLeft_WidgetFlag, iFalse); | ||
1575 | iWidget *pad = addPanelChild_(owner, iClob(makeValuePaddingWithHeading_(headingLabel, value)), 0, | ||
1576 | element, prevElement); | ||
1577 | pad->padding[2] = gap_UI; | ||
1578 | } | ||
1579 | else if (valueInput) { | ||
1580 | addPanelChild_(owner, iClob(makeValuePaddingWithHeading_(headingLabel, value)), 0, | ||
1581 | element, prevElement); | ||
1582 | } | ||
1583 | else { | ||
1584 | if (childCount_Widget(value) >= 2) { | ||
1585 | element = radioButton_PrefsElement; | ||
1586 | /* Always padding before radio buttons. */ | ||
1587 | addChild_Widget(owner, iClob(makePadding_Widget(lineHeight_Text(defaultBig_FontId)))); | ||
1588 | } | ||
1589 | addChildFlags_Widget(owner, iClob(heading), borderBottom_WidgetFlag); | ||
1590 | if (headingLabel) { | ||
1591 | setTextColor_LabelWidget(headingLabel, uiSubheading_ColorId); | ||
1592 | setText_LabelWidget(headingLabel, | ||
1593 | collect_String(upper_String(text_LabelWidget(headingLabel)))); | ||
1594 | } | ||
1595 | addPanelChild_(owner, iClob(value), 0, element, prevElement); | ||
1596 | /* Radio buttons expand to fill the space. */ | ||
1597 | if (element == radioButton_PrefsElement) { | ||
1598 | setBackgroundColor_Widget(value, uiBackgroundSidebar_ColorId); | ||
1599 | setPadding_Widget(value, 4 * gap_UI, 2 * gap_UI, 4 * gap_UI, 2 * gap_UI); | ||
1600 | setFlags_Widget(value, | ||
1601 | borderBottom_WidgetFlag | | ||
1602 | resizeToParentWidth_WidgetFlag | | ||
1603 | resizeWidthOfChildren_WidgetFlag, | ||
1604 | iTrue); | ||
1605 | iForEach(ObjectList, sub, children_Widget(value)) { | ||
1606 | if (isInstance_Object(sub.object, &Class_LabelWidget)) { | ||
1607 | iLabelWidget *opt = sub.object; | ||
1608 | setFont_LabelWidget(opt, defaultMedium_FontId); | ||
1609 | setFlags_Widget(as_Widget(opt), noBackground_WidgetFlag, iTrue); | ||
1610 | } | ||
1611 | } | ||
1612 | } | ||
1613 | } | ||
1614 | prevElement = element; | ||
1615 | } | ||
1616 | addPanelChild_(owner, NULL, 0, 0, prevElement); | ||
1617 | destroy_Widget(pageContent); | ||
1618 | setFlags_Widget(owner, drawBackgroundToBottom_WidgetFlag, iTrue); | ||
1619 | } | ||
1620 | destroyPending_Root(sheet->root); | ||
1621 | /* Additional elements for preferences. */ | ||
1622 | if (isPrefs) { | ||
1623 | addChild_Widget(topPanel, iClob(makePadding_Widget(lineHeight_Text(defaultBig_FontId)))); | ||
1624 | addChildFlags_Widget(topPanel, | ||
1625 | iClob(makePanelButton_(info_Icon " ${menu.help}", "!open url:about:help")), | ||
1626 | borderTop_WidgetFlag); | ||
1627 | iLabelWidget *aboutButton = addChildFlags_Widget(topPanel, | ||
1628 | iClob(makePanelButton_(planet_Icon " ${menu.about}", "panel.open")), | ||
1629 | chevron_WidgetFlag); | ||
1630 | /* The About panel. */ { | ||
1631 | iWidget *panel = addChildPanel_(sheet, aboutButton, NULL); | ||
1632 | iString *msg = collectNew_String(); | ||
1633 | setCStr_String(msg, "Lagrange " LAGRANGE_APP_VERSION); | ||
1634 | #if defined (iPlatformAppleMobile) | ||
1635 | appendCStr_String(msg, " (" LAGRANGE_IOS_VERSION ")"); | ||
1636 | #endif | ||
1637 | addChild_Widget(panel, iClob(new_LabelWidget(cstr_String(msg), NULL))); | ||
1638 | addChildFlags_Widget(panel, | ||
1639 | iClob(makePanelButton_(globe_Icon " By @jk@skyjake.fi", | ||
1640 | "!open url:https://skyjake.fi/@jk")), | ||
1641 | borderTop_WidgetFlag); | ||
1642 | addChildFlags_Widget(panel, | ||
1643 | iClob(makePanelButton_(clock_Icon " ${menu.releasenotes}", | ||
1644 | "!open url:about:version")), | ||
1645 | 0); | ||
1646 | addChildFlags_Widget(panel, | ||
1647 | iClob(makePanelButton_(info_Icon " ${menu.aboutpages}", | ||
1648 | "!open url:about:about")), | ||
1649 | 0); | ||
1650 | addChildFlags_Widget(panel, | ||
1651 | iClob(makePanelButton_(bug_Icon " ${menu.debug}", | ||
1652 | "!open url:about:debug")), | ||
1653 | 0); | ||
1654 | } | ||
1655 | } | ||
1656 | else { | ||
1657 | setFlags_Widget(topPanel, overflowScrollable_WidgetFlag, iTrue); | ||
1658 | /* Update heading style. */ | ||
1659 | setFont_LabelWidget((iLabelWidget *) dialogHeading, uiLabelLargeBold_FontId); | ||
1660 | setFlags_Widget(dialogHeading, alignLeft_WidgetFlag, iTrue); | ||
1661 | } | ||
1662 | if (findChild_Widget(sheet, "valueinput.prompt")) { | ||
1663 | iWidget *prompt = findChild_Widget(sheet, "valueinput.prompt"); | ||
1664 | setFlags_Widget(prompt, alignLeft_WidgetFlag, iTrue); | ||
1665 | iInputWidget *input = findChild_Widget(sheet, "input"); | ||
1666 | removeChild_Widget(parent_Widget(input), input); | ||
1667 | addChild_Widget(topPanel, iClob(makeValuePadding_(as_Widget(input)))); | ||
1668 | } | ||
1669 | /* Top padding for each panel, to account for the overlaid navbar. */ { | ||
1670 | setId_Widget(addChildPos_Widget(topPanel, | ||
1671 | iClob(makePadding_Widget(0)), front_WidgetAddPos), | ||
1672 | "panel.toppad"); | ||
1673 | } | ||
1674 | /* Navbar. */ { | ||
1675 | iWidget *navi = new_Widget(); | ||
1676 | setId_Widget(navi, "panel.navi"); | ||
1677 | setBackgroundColor_Widget(navi, uiBackground_ColorId); | ||
1678 | addChild_Widget(navi, iClob(makePadding_Widget(0))); | ||
1679 | iLabelWidget *back = addChildFlags_Widget(navi, | ||
1680 | iClob(new_LabelWidget(leftAngle_Icon " ${panel.back}", "panel.close")), | ||
1681 | noBackground_WidgetFlag | frameless_WidgetFlag | | ||
1682 | alignLeft_WidgetFlag | extraPadding_WidgetFlag); | ||
1683 | checkIcon_LabelWidget(back); | ||
1684 | setId_Widget(as_Widget(back), "panel.back"); | ||
1685 | setFont_LabelWidget(back, defaultBig_FontId); | ||
1686 | if (!isPrefs) { | ||
1687 | /* Pick up the dialog buttons for the navbar. */ | ||
1688 | iWidget *buttons = findChild_Widget(sheet, "dialogbuttons"); | ||
1689 | iLabelWidget *cancel = findMenuItem_Widget(buttons, "cancel"); | ||
1690 | // if (!cancel) { | ||
1691 | // cancel = findMenuItem_Widget(buttons, "translation.cancel"); | ||
1692 | // } | ||
1693 | if (cancel) { | ||
1694 | updateText_LabelWidget(back, text_LabelWidget(cancel)); | ||
1695 | setCommand_LabelWidget(back, command_LabelWidget(cancel)); | ||
1696 | } | ||
1697 | iLabelWidget *def = (iLabelWidget *) lastChild_Widget(buttons); | ||
1698 | if (def && !cancel) { | ||
1699 | updateText_LabelWidget(back, text_LabelWidget(def)); | ||
1700 | setCommand_LabelWidget(back, command_LabelWidget(def)); | ||
1701 | setFlags_Widget(as_Widget(back), alignLeft_WidgetFlag, iFalse); | ||
1702 | setFlags_Widget(as_Widget(back), alignRight_WidgetFlag, iTrue); | ||
1703 | setIcon_LabelWidget(back, 0); | ||
1704 | setFont_LabelWidget(back, defaultBigBold_FontId); | ||
1705 | } | ||
1706 | else if (def != cancel) { | ||
1707 | removeChild_Widget(buttons, def); | ||
1708 | setFont_LabelWidget(def, defaultBigBold_FontId); | ||
1709 | setFlags_Widget(as_Widget(def), | ||
1710 | frameless_WidgetFlag | extraPadding_WidgetFlag | | ||
1711 | noBackground_WidgetFlag, iTrue); | ||
1712 | addChildFlags_Widget(as_Widget(back), iClob(def), moveToParentRightEdge_WidgetFlag); | ||
1713 | updateSize_LabelWidget(def); | ||
1714 | } | ||
1715 | /* Action buttons are added in the bottom as extra buttons. */ { | ||
1716 | iBool isFirstAction = iTrue; | ||
1717 | iForEach(ObjectList, i, children_Widget(buttons)) { | ||
1718 | if (isInstance_Object(i.object, &Class_LabelWidget) && | ||
1719 | i.object != cancel && i.object != def) { | ||
1720 | iLabelWidget *item = i.object; | ||
1721 | setBackgroundColor_Widget(i.object, uiBackgroundSidebar_ColorId); | ||
1722 | setFont_LabelWidget(item, defaultBig_FontId); | ||
1723 | removeChild_Widget(buttons, item); | ||
1724 | addChildFlags_Widget(topPanel, iClob(item), panelButtonFlags | | ||
1725 | (isFirstAction ? borderTop_WidgetFlag : 0)); | ||
1726 | updateSize_LabelWidget(item); | ||
1727 | isFirstAction = iFalse; | ||
1728 | } | ||
1729 | } | ||
1730 | } | ||
1731 | iRelease(removeChild_Widget(parent_Widget(buttons), buttons)); | ||
1732 | /* Styling for remaining elements. */ | ||
1733 | iForEach(ObjectList, i, children_Widget(topPanel)) { | ||
1734 | if (isInstance_Object(i.object, &Class_LabelWidget) && | ||
1735 | isEmpty_String(command_LabelWidget(i.object)) && | ||
1736 | isEmpty_String(id_Widget(i.object))) { | ||
1737 | setFlags_Widget(i.object, alignLeft_WidgetFlag, iTrue); | ||
1738 | if (font_LabelWidget(i.object) == uiLabel_FontId) { | ||
1739 | setFont_LabelWidget(i.object, uiContent_FontId); | ||
1740 | } | ||
1741 | } | ||
1742 | } | ||
1743 | } | ||
1744 | addChildFlags_Widget(sheet, iClob(navi), | ||
1745 | drawBackgroundToVerticalSafeArea_WidgetFlag | | ||
1746 | arrangeHeight_WidgetFlag | resizeWidthOfChildren_WidgetFlag | | ||
1747 | resizeToParentWidth_WidgetFlag | arrangeVertical_WidgetFlag); | ||
1748 | } | ||
1749 | updateSheetPanelMetrics_(sheet); | ||
1750 | iAssert(sheet->parent); | ||
1751 | arrange_Widget(sheet->parent); | ||
1752 | postCommand_App("widget.overflow"); /* with the correct dimensions */ | ||
1753 | // printTree_Widget(sheet); | ||
1754 | } | ||
1755 | else { | ||
1756 | arrange_Widget(sheet); | ||
1757 | } | ||
1758 | postRefresh_App(); | ||
1759 | } | ||
1760 | |||
1761 | void makeFilePath_Widget(iWidget * parent, | ||
1762 | const iString *initialPath, | ||
1763 | const char * title, | ||
1764 | const char * acceptLabel, | ||
1765 | const char * command) { | ||
1766 | setFocus_Widget(NULL); | ||
1767 | // processEvents_App(postedEventsOnly_AppEventMode); | ||
1768 | iWidget *dlg = makeSheet_Widget(command); | ||
1769 | setCommandHandler_Widget(dlg, filePathHandler_); | ||
1770 | addChild_Widget(parent, iClob(dlg)); | ||
1771 | addChildFlags_Widget(dlg, iClob(new_LabelWidget(title, NULL)), frameless_WidgetFlag); | ||
1772 | iInputWidget *input = addChild_Widget(dlg, iClob(new_InputWidget(0))); | ||
1773 | if (initialPath) { | ||
1774 | setText_InputWidget(input, collect_String(makeRelative_Path(initialPath))); | ||
1775 | } | ||
1776 | setId_Widget(as_Widget(input), "input"); | ||
1777 | as_Widget(input)->rect.size.x = dlg->rect.size.x; | ||
1778 | addChild_Widget(dlg, iClob(makePadding_Widget(gap_UI))); | ||
1779 | iWidget *div = new_Widget(); { | ||
1780 | setFlags_Widget(div, arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag, iTrue); | ||
1781 | addChild_Widget(div, iClob(newKeyMods_LabelWidget("${cancel}", SDLK_ESCAPE, 0, "filepath.cancel"))); | ||
1782 | addChild_Widget(div, iClob(newKeyMods_LabelWidget(acceptLabel, SDLK_RETURN, 0, "filepath.accept"))); | ||
1783 | } | ||
1784 | addChild_Widget(dlg, iClob(div)); | ||
1785 | finalizeSheet_Widget(dlg); | ||
1786 | setFocus_Widget(as_Widget(input)); | ||
1787 | } | ||
1788 | |||
1789 | static void acceptValueInput_(iWidget *dlg) { | 1136 | static void acceptValueInput_(iWidget *dlg) { |
1790 | const iInputWidget *input = findChild_Widget(dlg, "input"); | 1137 | const iInputWidget *input = findChild_Widget(dlg, "input"); |
1791 | if (!isEmpty_String(id_Widget(dlg))) { | 1138 | if (!isEmpty_String(id_Widget(dlg))) { |
@@ -1806,7 +1153,8 @@ static void updateValueInputWidth_(iWidget *dlg) { | |||
1806 | dlg->rect.size.x = rootSize.x; | 1153 | dlg->rect.size.x = rootSize.x; |
1807 | } | 1154 | } |
1808 | else { | 1155 | else { |
1809 | dlg->rect.size.x = iMaxi(iMaxi(rootSize.x / 2, title->rect.size.x), prompt->rect.size.x); | 1156 | dlg->rect.size.x = |
1157 | iMin(rootSize.x, iMaxi(iMaxi(100 * gap_UI, title->rect.size.x), prompt->rect.size.x)); | ||
1810 | } | 1158 | } |
1811 | } | 1159 | } |
1812 | 1160 | ||
@@ -1880,6 +1228,12 @@ iWidget *makeDialogButtons_Widget(const iMenuItem *actions, size_t numActions) { | |||
1880 | if (*label == '*' || *label == '&') { | 1228 | if (*label == '*' || *label == '&') { |
1881 | continue; /* Special value selection items for a Question dialog. */ | 1229 | continue; /* Special value selection items for a Question dialog. */ |
1882 | } | 1230 | } |
1231 | if (startsWith_CStr(label, "```")) { | ||
1232 | /* Annotation. */ | ||
1233 | iLabelWidget *annotation = addChild_Widget(div, iClob(new_LabelWidget(label + 3, NULL))); | ||
1234 | setTextColor_LabelWidget(annotation, uiTextAction_ColorId); | ||
1235 | continue; | ||
1236 | } | ||
1883 | if (!iCmpStr(label, "---")) { | 1237 | if (!iCmpStr(label, "---")) { |
1884 | /* Separator.*/ | 1238 | /* Separator.*/ |
1885 | addChildFlags_Widget(div, iClob(new_Widget()), expand_WidgetFlag); | 1239 | addChildFlags_Widget(div, iClob(new_Widget()), expand_WidgetFlag); |
@@ -1945,7 +1299,7 @@ iWidget *makeValueInput_Widget(iWidget *parent, const iString *initialValue, con | |||
1945 | iClob(makeDialogButtons_Widget( | 1299 | iClob(makeDialogButtons_Widget( |
1946 | (iMenuItem[]){ { "${cancel}", 0, 0, NULL }, { acceptLabel, 0, 0, "valueinput.accept" } }, | 1300 | (iMenuItem[]){ { "${cancel}", 0, 0, NULL }, { acceptLabel, 0, 0, "valueinput.accept" } }, |
1947 | 2))); | 1301 | 2))); |
1948 | finalizeSheet_Widget(dlg); | 1302 | finalizeSheet_Mobile(dlg); |
1949 | if (parent) { | 1303 | if (parent) { |
1950 | setFocus_Widget(as_Widget(input)); | 1304 | setFocus_Widget(as_Widget(input)); |
1951 | } | 1305 | } |
@@ -2002,13 +1356,17 @@ iWidget *makeQuestion_Widget(const char *title, const char *msg, | |||
2002 | const iMenuItem *item = &items[i]; | 1356 | const iMenuItem *item = &items[i]; |
2003 | const char first = item->label[0]; | 1357 | const char first = item->label[0]; |
2004 | if (first == '*' || first == '&') { | 1358 | if (first == '*' || first == '&') { |
2005 | addChildFlags_Widget(dlg, | 1359 | iLabelWidget *option = |
1360 | addChildFlags_Widget(dlg, | ||
2006 | iClob(newKeyMods_LabelWidget(item->label + 1, | 1361 | iClob(newKeyMods_LabelWidget(item->label + 1, |
2007 | item->key, | 1362 | item->key, |
2008 | item->kmods, | 1363 | item->kmods, |
2009 | item->command)), | 1364 | item->command)), |
2010 | resizeToParentWidth_WidgetFlag | | 1365 | resizeToParentWidth_WidgetFlag | |
2011 | (first == '&' ? selected_WidgetFlag : 0)); | 1366 | (first == '&' ? selected_WidgetFlag : 0)); |
1367 | if (deviceType_App() != desktop_AppDeviceType) { | ||
1368 | setFont_LabelWidget(option, defaultBig_FontId); | ||
1369 | } | ||
2012 | } | 1370 | } |
2013 | } | 1371 | } |
2014 | addChild_Widget(dlg, iClob(makePadding_Widget(gap_UI))); | 1372 | addChild_Widget(dlg, iClob(makePadding_Widget(gap_UI))); |
@@ -2016,7 +1374,7 @@ iWidget *makeQuestion_Widget(const char *title, const char *msg, | |||
2016 | addChild_Widget(dlg->root->widget, iClob(dlg)); | 1374 | addChild_Widget(dlg->root->widget, iClob(dlg)); |
2017 | arrange_Widget(dlg); /* BUG: This extra arrange shouldn't be needed but the dialog won't | 1375 | arrange_Widget(dlg); /* BUG: This extra arrange shouldn't be needed but the dialog won't |
2018 | be arranged correctly unless it's here. */ | 1376 | be arranged correctly unless it's here. */ |
2019 | finalizeSheet_Widget(dlg); | 1377 | finalizeSheet_Mobile(dlg); |
2020 | return dlg; | 1378 | return dlg; |
2021 | } | 1379 | } |
2022 | 1380 | ||
@@ -2155,7 +1513,7 @@ static int cmp_MenuItem_(const void *e1, const void *e2) { | |||
2155 | #endif | 1513 | #endif |
2156 | 1514 | ||
2157 | void updatePreferencesLayout_Widget(iWidget *prefs) { | 1515 | void updatePreferencesLayout_Widget(iWidget *prefs) { |
2158 | if (!prefs || deviceType_App() == phone_AppDeviceType) { | 1516 | if (!prefs || deviceType_App() != desktop_AppDeviceType) { |
2159 | return; | 1517 | return; |
2160 | } | 1518 | } |
2161 | /* Doing manual layout here because the widget arranging logic isn't sophisticated enough. */ | 1519 | /* Doing manual layout here because the widget arranging logic isn't sophisticated enough. */ |
@@ -2248,14 +1606,16 @@ iWidget *makePreferences_Widget(void) { | |||
2248 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.hoverlink"))); | 1606 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.hoverlink"))); |
2249 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.archive.openindex}"))); | 1607 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.archive.openindex}"))); |
2250 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.archive.openindex"))); | 1608 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.archive.openindex"))); |
2251 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.pinsplit}"))); | 1609 | if (deviceType_App() != phone_AppDeviceType) { |
2252 | iWidget *pinSplit = new_Widget(); | 1610 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.pinsplit}"))); |
2253 | /* Split mode document pinning. */ { | 1611 | iWidget *pinSplit = new_Widget(); |
2254 | addRadioButton_(pinSplit, "prefs.pinsplit.0", "${prefs.pinsplit.none}", "pinsplit.set arg:0"); | 1612 | /* Split mode document pinning. */ { |
2255 | addRadioButton_(pinSplit, "prefs.pinsplit.1", "${prefs.pinsplit.left}", "pinsplit.set arg:1"); | 1613 | addRadioButton_(pinSplit, "prefs.pinsplit.0", "${prefs.pinsplit.none}", "pinsplit.set arg:0"); |
2256 | addRadioButton_(pinSplit, "prefs.pinsplit.2", "${prefs.pinsplit.right}", "pinsplit.set arg:2"); | 1614 | addRadioButton_(pinSplit, "prefs.pinsplit.1", "${prefs.pinsplit.left}", "pinsplit.set arg:1"); |
2257 | } | 1615 | addRadioButton_(pinSplit, "prefs.pinsplit.2", "${prefs.pinsplit.right}", "pinsplit.set arg:2"); |
2258 | addChildFlags_Widget(values, iClob(pinSplit), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); | 1616 | } |
1617 | addChildFlags_Widget(values, iClob(pinSplit), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); | ||
1618 | } | ||
2259 | addChild_Widget(headings, iClob(makePadding_Widget(bigGap))); | 1619 | addChild_Widget(headings, iClob(makePadding_Widget(bigGap))); |
2260 | addChild_Widget(values, iClob(makePadding_Widget(bigGap))); | 1620 | addChild_Widget(values, iClob(makePadding_Widget(bigGap))); |
2261 | /* UI languages. */ { | 1621 | /* UI languages. */ { |
@@ -2268,6 +1628,7 @@ iWidget *makePreferences_Widget(void) { | |||
2268 | { "${lang.fr} - fr", 0, 0, "uilang id:fr" }, | 1628 | { "${lang.fr} - fr", 0, 0, "uilang id:fr" }, |
2269 | { "${lang.ia} - ia", 0, 0, "uilang id:ia" }, | 1629 | { "${lang.ia} - ia", 0, 0, "uilang id:ia" }, |
2270 | { "${lang.ie} - ie", 0, 0, "uilang id:ie" }, | 1630 | { "${lang.ie} - ie", 0, 0, "uilang id:ie" }, |
1631 | { "${lang.pl} - pl", 0, 0, "uilang id:pl" }, | ||
2271 | { "${lang.ru} - ru", 0, 0, "uilang id:ru" }, | 1632 | { "${lang.ru} - ru", 0, 0, "uilang id:ru" }, |
2272 | { "${lang.sr} - sr", 0, 0, "uilang id:sr" }, | 1633 | { "${lang.sr} - sr", 0, 0, "uilang id:sr" }, |
2273 | { "${lang.tok} - tok", 0, 0, "uilang id:tok" }, | 1634 | { "${lang.tok} - tok", 0, 0, "uilang id:tok" }, |
@@ -2475,7 +1836,8 @@ iWidget *makePreferences_Widget(void) { | |||
2475 | iClob(makeDialogButtons_Widget( | 1836 | iClob(makeDialogButtons_Widget( |
2476 | (iMenuItem[]){ { "${dismiss}", SDLK_ESCAPE, 0, "prefs.dismiss" } }, 1))); | 1837 | (iMenuItem[]){ { "${dismiss}", SDLK_ESCAPE, 0, "prefs.dismiss" } }, 1))); |
2477 | addChild_Widget(dlg->root->widget, iClob(dlg)); | 1838 | addChild_Widget(dlg->root->widget, iClob(dlg)); |
2478 | finalizeSheet_Widget(dlg); | 1839 | finalizeSheet_Mobile(dlg); |
1840 | setupSheetTransition_Mobile(dlg, iTrue); | ||
2479 | // printTree_Widget(dlg); | 1841 | // printTree_Widget(dlg); |
2480 | return dlg; | 1842 | return dlg; |
2481 | } | 1843 | } |
@@ -2519,15 +1881,10 @@ iWidget *makeBookmarkEditor_Widget(void) { | |||
2519 | "bmed.accept" } }, | 1881 | "bmed.accept" } }, |
2520 | 2))); | 1882 | 2))); |
2521 | addChild_Widget(get_Root()->widget, iClob(dlg)); | 1883 | addChild_Widget(get_Root()->widget, iClob(dlg)); |
2522 | finalizeSheet_Widget(dlg); | 1884 | finalizeSheet_Mobile(dlg); |
2523 | return dlg; | 1885 | return dlg; |
2524 | } | 1886 | } |
2525 | 1887 | ||
2526 | static void enableSidebars_(void) { | ||
2527 | setFlags_Widget(findWidget_App("sidebar"), disabled_WidgetFlag, iFalse); | ||
2528 | setFlags_Widget(findWidget_App("sidebar2"), disabled_WidgetFlag, iFalse); | ||
2529 | } | ||
2530 | |||
2531 | static iBool handleBookmarkCreationCommands_SidebarWidget_(iWidget *editor, const char *cmd) { | 1888 | static iBool handleBookmarkCreationCommands_SidebarWidget_(iWidget *editor, const char *cmd) { |
2532 | if (equal_Command(cmd, "bmed.accept") || equal_Command(cmd, "cancel")) { | 1889 | if (equal_Command(cmd, "bmed.accept") || equal_Command(cmd, "cancel")) { |
2533 | if (equal_Command(cmd, "bmed.accept")) { | 1890 | if (equal_Command(cmd, "bmed.accept")) { |
@@ -2552,8 +1909,6 @@ static iBool handleBookmarkCreationCommands_SidebarWidget_(iWidget *editor, cons | |||
2552 | postCommand_App("bookmarks.changed"); | 1909 | postCommand_App("bookmarks.changed"); |
2553 | } | 1910 | } |
2554 | destroy_Widget(editor); | 1911 | destroy_Widget(editor); |
2555 | /* Sidebars are disabled when a dialog is opened. */ | ||
2556 | enableSidebars_(); | ||
2557 | return iTrue; | 1912 | return iTrue; |
2558 | } | 1913 | } |
2559 | return iFalse; | 1914 | return iFalse; |
@@ -2583,8 +1938,6 @@ iWidget *makeBookmarkCreation_Widget(const iString *url, const iString *title, i | |||
2583 | static iBool handleFeedSettingCommands_(iWidget *dlg, const char *cmd) { | 1938 | static iBool handleFeedSettingCommands_(iWidget *dlg, const char *cmd) { |
2584 | if (equal_Command(cmd, "cancel")) { | 1939 | if (equal_Command(cmd, "cancel")) { |
2585 | destroy_Widget(dlg); | 1940 | destroy_Widget(dlg); |
2586 | /* Sidebars are disabled when a dialog is opened. */ | ||
2587 | enableSidebars_(); | ||
2588 | return iTrue; | 1941 | return iTrue; |
2589 | } | 1942 | } |
2590 | if (equal_Command(cmd, "feedcfg.accept")) { | 1943 | if (equal_Command(cmd, "feedcfg.accept")) { |
@@ -2619,8 +1972,6 @@ static iBool handleFeedSettingCommands_(iWidget *dlg, const char *cmd) { | |||
2619 | } | 1972 | } |
2620 | postCommand_App("bookmarks.changed"); | 1973 | postCommand_App("bookmarks.changed"); |
2621 | destroy_Widget(dlg); | 1974 | destroy_Widget(dlg); |
2622 | /* Sidebars are disabled when a dialog is opened. */ | ||
2623 | enableSidebars_(); | ||
2624 | return iTrue; | 1975 | return iTrue; |
2625 | } | 1976 | } |
2626 | return iFalse; | 1977 | return iFalse; |
@@ -2659,7 +2010,7 @@ iWidget *makeFeedSettings_Widget(uint32_t bookmarkId) { | |||
2659 | arrange_Widget(dlg); | 2010 | arrange_Widget(dlg); |
2660 | as_Widget(input)->rect.size.x = 100 * gap_UI - headings->rect.size.x; | 2011 | as_Widget(input)->rect.size.x = 100 * gap_UI - headings->rect.size.x; |
2661 | addChild_Widget(get_Root()->widget, iClob(dlg)); | 2012 | addChild_Widget(get_Root()->widget, iClob(dlg)); |
2662 | finalizeSheet_Widget(dlg); | 2013 | finalizeSheet_Mobile(dlg); |
2663 | /* Initialize. */ { | 2014 | /* Initialize. */ { |
2664 | const iBookmark *bm = bookmarkId ? get_Bookmarks(bookmarks_App(), bookmarkId) : NULL; | 2015 | const iBookmark *bm = bookmarkId ? get_Bookmarks(bookmarks_App(), bookmarkId) : NULL; |
2665 | setText_InputWidget(findChild_Widget(dlg, "feedcfg.title"), | 2016 | setText_InputWidget(findChild_Widget(dlg, "feedcfg.title"), |
@@ -2735,7 +2086,7 @@ iWidget *makeIdentityCreation_Widget(void) { | |||
2735 | "ident.accept" } }, | 2086 | "ident.accept" } }, |
2736 | 2))); | 2087 | 2))); |
2737 | addChild_Widget(get_Root()->widget, iClob(dlg)); | 2088 | addChild_Widget(get_Root()->widget, iClob(dlg)); |
2738 | finalizeSheet_Widget(dlg); | 2089 | finalizeSheet_Mobile(dlg); |
2739 | return dlg; | 2090 | return dlg; |
2740 | } | 2091 | } |
2741 | 2092 | ||
@@ -2786,6 +2137,7 @@ int languageIndex_CStr(const char *langId) { | |||
2786 | iWidget *makeTranslation_Widget(iWidget *parent) { | 2137 | iWidget *makeTranslation_Widget(iWidget *parent) { |
2787 | iWidget *dlg = makeSheet_Widget("xlt"); | 2138 | iWidget *dlg = makeSheet_Widget("xlt"); |
2788 | setFlags_Widget(dlg, keepOnTop_WidgetFlag, iFalse); | 2139 | setFlags_Widget(dlg, keepOnTop_WidgetFlag, iFalse); |
2140 | dlg->minSize.x = 70 * gap_UI; | ||
2789 | setCommandHandler_Widget(dlg, translationHandler_); | 2141 | setCommandHandler_Widget(dlg, translationHandler_); |
2790 | addChildFlags_Widget(dlg, | 2142 | addChildFlags_Widget(dlg, |
2791 | iClob(new_LabelWidget(uiHeading_ColorEscape "${heading.translate}", NULL)), | 2143 | iClob(new_LabelWidget(uiHeading_ColorEscape "${heading.translate}", NULL)), |
@@ -2831,6 +2183,6 @@ iWidget *makeTranslation_Widget(iWidget *parent) { | |||
2831 | 2))); | 2183 | 2))); |
2832 | addChild_Widget(parent, iClob(dlg)); | 2184 | addChild_Widget(parent, iClob(dlg)); |
2833 | arrange_Widget(dlg); | 2185 | arrange_Widget(dlg); |
2834 | finalizeSheet_Widget(dlg); | 2186 | finalizeSheet_Mobile(dlg); |
2835 | return dlg; | 2187 | return dlg; |
2836 | } | 2188 | } |
diff --git a/src/ui/util.h b/src/ui/util.h index 50845280..43aeb172 100644 --- a/src/ui/util.h +++ b/src/ui/util.h | |||
@@ -22,6 +22,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
22 | 22 | ||
23 | #pragma once | 23 | #pragma once |
24 | 24 | ||
25 | #include "mobile.h" | ||
26 | |||
25 | #include <the_Foundation/string.h> | 27 | #include <the_Foundation/string.h> |
26 | #include <the_Foundation/rect.h> | 28 | #include <the_Foundation/rect.h> |
27 | #include <the_Foundation/vec2.h> | 29 | #include <the_Foundation/vec2.h> |
@@ -255,9 +257,8 @@ size_t tabCount_Widget (const iWidget *tabs); | |||
255 | 257 | ||
256 | /*-----------------------------------------------------------------------------------------------*/ | 258 | /*-----------------------------------------------------------------------------------------------*/ |
257 | 259 | ||
258 | iWidget * makeSheet_Widget (const char *id); | 260 | iWidget * makeSheet_Widget (const char *id); |
259 | void finalizeSheet_Widget (iWidget *sheet); | 261 | iWidget * makeDialogButtons_Widget (const iMenuItem *actions, size_t numActions); |
260 | iWidget * makeDialogButtons_Widget(const iMenuItem *actions, size_t numActions); | ||
261 | 262 | ||
262 | iInputWidget *addTwoColumnDialogInputField_Widget(iWidget *headings, iWidget *values, | 263 | iInputWidget *addTwoColumnDialogInputField_Widget(iWidget *headings, iWidget *values, |
263 | const char *labelText, const char *inputId, | 264 | const char *labelText, const char *inputId, |
diff --git a/src/ui/widget.c b/src/ui/widget.c index c1c920d2..3439fb1b 100644 --- a/src/ui/widget.c +++ b/src/ui/widget.c | |||
@@ -23,6 +23,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
23 | #include "widget.h" | 23 | #include "widget.h" |
24 | 24 | ||
25 | #include "app.h" | 25 | #include "app.h" |
26 | #include "periodic.h" | ||
26 | #include "touch.h" | 27 | #include "touch.h" |
27 | #include "command.h" | 28 | #include "command.h" |
28 | #include "paint.h" | 29 | #include "paint.h" |
@@ -39,6 +40,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
39 | # include "../ios.h" | 40 | # include "../ios.h" |
40 | #endif | 41 | #endif |
41 | 42 | ||
43 | static void printInfo_Widget_(const iWidget *); | ||
44 | |||
42 | void releaseChildren_Widget(iWidget *d) { | 45 | void releaseChildren_Widget(iWidget *d) { |
43 | iForEach(ObjectList, i, d->children) { | 46 | iForEach(ObjectList, i, d->children) { |
44 | ((iWidget *) i.object)->parent = NULL; /* the actual reference being held */ | 47 | ((iWidget *) i.object)->parent = NULL; /* the actual reference being held */ |
@@ -55,6 +58,7 @@ void init_Widget(iWidget *d) { | |||
55 | d->rect = zero_Rect(); | 58 | d->rect = zero_Rect(); |
56 | d->minSize = zero_I2(); | 59 | d->minSize = zero_I2(); |
57 | d->sizeRef = NULL; | 60 | d->sizeRef = NULL; |
61 | d->offsetRef = NULL; | ||
58 | d->bgColor = none_ColorId; | 62 | d->bgColor = none_ColorId; |
59 | d->frameColor = none_ColorId; | 63 | d->frameColor = none_ColorId; |
60 | init_Anim(&d->visualOffset, 0.0f); | 64 | init_Anim(&d->visualOffset, 0.0f); |
@@ -92,6 +96,7 @@ void deinit_Widget(iWidget *d) { | |||
92 | } | 96 | } |
93 | 97 | ||
94 | static void aboutToBeDestroyed_Widget_(iWidget *d) { | 98 | static void aboutToBeDestroyed_Widget_(iWidget *d) { |
99 | d->flags |= destroyPending_WidgetFlag; | ||
95 | if (isFocused_Widget(d)) { | 100 | if (isFocused_Widget(d)) { |
96 | setFocus_Widget(NULL); | 101 | setFocus_Widget(NULL); |
97 | return; | 102 | return; |
@@ -99,6 +104,7 @@ static void aboutToBeDestroyed_Widget_(iWidget *d) { | |||
99 | if (flags_Widget(d) & keepOnTop_WidgetFlag) { | 104 | if (flags_Widget(d) & keepOnTop_WidgetFlag) { |
100 | removeOne_PtrArray(onTop_Root(d->root), d); | 105 | removeOne_PtrArray(onTop_Root(d->root), d); |
101 | } | 106 | } |
107 | remove_Periodic(periodic_App(), d); | ||
102 | if (isHover_Widget(d)) { | 108 | if (isHover_Widget(d)) { |
103 | get_Window()->hover = NULL; | 109 | get_Window()->hover = NULL; |
104 | } | 110 | } |
@@ -149,6 +155,11 @@ void setFlags_Widget(iWidget *d, int64_t flags, iBool set) { | |||
149 | removeOne_PtrArray(onTop, d); | 155 | removeOne_PtrArray(onTop, d); |
150 | } | 156 | } |
151 | } | 157 | } |
158 | if (d->flags & arrangeWidth_WidgetFlag && | ||
159 | d->flags & resizeToParentWidth_WidgetFlag) { | ||
160 | printf("[Widget] Conflicting flags for "); | ||
161 | identify_Widget(d); | ||
162 | } | ||
152 | } | 163 | } |
153 | } | 164 | } |
154 | 165 | ||
@@ -208,7 +219,7 @@ void setVisualOffset_Widget(iWidget *d, int value, uint32_t span, int animFlags) | |||
208 | else { | 219 | else { |
209 | setValue_Anim(&d->visualOffset, value, span); | 220 | setValue_Anim(&d->visualOffset, value, span); |
210 | d->visualOffset.flags = animFlags; | 221 | d->visualOffset.flags = animFlags; |
211 | addTicker_App(visualOffsetAnimation_Widget_, d); | 222 | addTickerRoot_App(visualOffsetAnimation_Widget_, d->root, d); |
212 | } | 223 | } |
213 | } | 224 | } |
214 | 225 | ||
@@ -364,10 +375,13 @@ iRect innerBounds_Widget(const iWidget *d) { | |||
364 | return ib; | 375 | return ib; |
365 | } | 376 | } |
366 | 377 | ||
367 | //iLocalDef iBool isArranged_Widget_(const iWidget *d) { | 378 | iRect innerBoundsWithoutVisualOffset_Widget(const iWidget *d) { |
368 | // return !isCollapsed_Widget_(d) && ~d->flags & fixedPosition_WidgetFlag; | 379 | iRect ib = adjusted_Rect(boundsWithoutVisualOffset_Widget(d), |
369 | //} | 380 | init_I2(d->padding[0], d->padding[1]), |
370 | 381 | init_I2(-d->padding[2], -d->padding[3])); | |
382 | ib.size = max_I2(zero_I2(), ib.size); | ||
383 | return ib; | ||
384 | } | ||
371 | 385 | ||
372 | static size_t numArrangedChildren_Widget_(const iWidget *d) { | 386 | static size_t numArrangedChildren_Widget_(const iWidget *d) { |
373 | size_t count = 0; | 387 | size_t count = 0; |
@@ -469,12 +483,19 @@ static void arrange_Widget_(iWidget *d) { | |||
469 | const int expCount = numExpandingChildren_Widget_(d); | 483 | const int expCount = numExpandingChildren_Widget_(d); |
470 | TRACE(d, "%d expanding children", expCount); | 484 | TRACE(d, "%d expanding children", expCount); |
471 | /* Resize children to fill the parent widget. */ | 485 | /* Resize children to fill the parent widget. */ |
486 | iAssert((d->flags & (resizeToParentWidth_WidgetFlag | arrangeWidth_WidgetFlag)) != | ||
487 | (resizeToParentWidth_WidgetFlag | arrangeWidth_WidgetFlag)); | ||
472 | if (d->flags & resizeChildren_WidgetFlag) { | 488 | if (d->flags & resizeChildren_WidgetFlag) { |
473 | const iInt2 dirs = init_I2((d->flags & resizeWidthOfChildren_WidgetFlag) != 0, | 489 | const iInt2 dirs = init_I2((d->flags & resizeWidthOfChildren_WidgetFlag) != 0, |
474 | (d->flags & resizeHeightOfChildren_WidgetFlag) != 0); | 490 | (d->flags & resizeHeightOfChildren_WidgetFlag) != 0); |
475 | #if !defined (NDEBUG) | 491 | #if !defined (NDEBUG) |
476 | /* Check for conflicting flags. */ | 492 | /* Check for conflicting flags. */ |
477 | if (dirs.x) iAssert(~d->flags & arrangeWidth_WidgetFlag); | 493 | if (dirs.x) { |
494 | if (d->flags & arrangeWidth_WidgetFlag) { | ||
495 | identify_Widget(d); | ||
496 | } | ||
497 | iAssert(~d->flags & arrangeWidth_WidgetFlag); | ||
498 | } | ||
478 | if (dirs.y) iAssert(~d->flags & arrangeHeight_WidgetFlag); | 499 | if (dirs.y) iAssert(~d->flags & arrangeHeight_WidgetFlag); |
479 | #endif | 500 | #endif |
480 | TRACE(d, "resize children, x:%d y:%d (own size: %dx%d)", dirs.x, dirs.y, | 501 | TRACE(d, "resize children, x:%d y:%d (own size: %dx%d)", dirs.x, dirs.y, |
@@ -697,26 +718,32 @@ static void arrange_Widget_(iWidget *d) { | |||
697 | TRACE(d, "END"); | 718 | TRACE(d, "END"); |
698 | } | 719 | } |
699 | 720 | ||
700 | #if 0 | 721 | static void resetArrangement_Widget_(iWidget *d) { |
701 | void resetSize_Widget(iWidget *d) { | ||
702 | if (~d->flags & fixedWidth_WidgetFlag) { | ||
703 | d->rect.size.x = d->minSize.x; | ||
704 | } | ||
705 | if (~d->flags & fixedHeight_WidgetFlag) { | ||
706 | d->rect.size.y = d->minSize.y; | ||
707 | } | ||
708 | iForEach(ObjectList, i, children_Widget(d)) { | 722 | iForEach(ObjectList, i, children_Widget(d)) { |
709 | iWidget *child = as_Widget(i.object); | 723 | iWidget *child = as_Widget(i.object); |
710 | if (isArrangedSize_Widget_(child)) { | 724 | resetArrangement_Widget_(child); |
711 | resetSize_Widget(child); | 725 | if (isArrangedPos_Widget_(child)) { |
726 | if (d->flags & arrangeHorizontal_WidgetFlag) { | ||
727 | child->rect.pos.x = 0; | ||
728 | } | ||
729 | if (d->flags & resizeWidthOfChildren_WidgetFlag && child->flags & expand_WidgetFlag) { | ||
730 | child->rect.size.x = 0; | ||
731 | } | ||
732 | if (d->flags & arrangeVertical_WidgetFlag) { | ||
733 | child->rect.pos.y = 0; | ||
734 | } | ||
735 | if (d->flags & resizeHeightOfChildren_WidgetFlag && child->flags & expand_WidgetFlag) { | ||
736 | child->rect.size.y = 0; | ||
737 | } | ||
712 | } | 738 | } |
713 | } | 739 | } |
714 | } | 740 | } |
715 | #endif | ||
716 | 741 | ||
717 | void arrange_Widget(iWidget *d) { | 742 | void arrange_Widget(iWidget *d) { |
718 | //resetSize_Widget_(d); /* back to initial default sizes */ | 743 | if (d) { |
719 | arrange_Widget_(d); | 744 | resetArrangement_Widget_(d); /* back to initial default sizes */ |
745 | arrange_Widget_(d); | ||
746 | } | ||
720 | } | 747 | } |
721 | 748 | ||
722 | static void applyVisualOffset_Widget_(const iWidget *d, iInt2 *pos) { | 749 | static void applyVisualOffset_Widget_(const iWidget *d, iInt2 *pos) { |
@@ -729,6 +756,16 @@ static void applyVisualOffset_Widget_(const iWidget *d, iInt2 *pos) { | |||
729 | pos->y += off; | 756 | pos->y += off; |
730 | } | 757 | } |
731 | } | 758 | } |
759 | if (d->flags & refChildrenOffset_WidgetFlag) { | ||
760 | iConstForEach(ObjectList, i, children_Widget(d->offsetRef)) { | ||
761 | const iWidget *child = i.object; | ||
762 | if (child == d) continue; | ||
763 | if (child->flags & (visualOffset_WidgetFlag | dragged_WidgetFlag)) { | ||
764 | const int invOff = size_Root(d->root).x - iRound(value_Anim(&child->visualOffset)); | ||
765 | pos->x -= invOff / 4; | ||
766 | } | ||
767 | } | ||
768 | } | ||
732 | } | 769 | } |
733 | 770 | ||
734 | iRect bounds_Widget(const iWidget *d) { | 771 | iRect bounds_Widget(const iWidget *d) { |
@@ -919,18 +956,23 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) { | |||
919 | } | 956 | } |
920 | } | 957 | } |
921 | if (class_Widget(d)->processEvent(d, ev)) { | 958 | if (class_Widget(d)->processEvent(d, ev)) { |
959 | iAssert(get_Root() == d->root); | ||
922 | return iTrue; | 960 | return iTrue; |
923 | } | 961 | } |
924 | } | 962 | } |
963 | iAssert(get_Root() == d->root); | ||
925 | return iFalse; | 964 | return iFalse; |
926 | } | 965 | } |
927 | 966 | ||
928 | static iBool scrollOverflow_Widget_(iWidget *d, int delta) { | 967 | iBool scrollOverflow_Widget(iWidget *d, int delta) { |
929 | iRect bounds = bounds_Widget(d); | 968 | iRect bounds = boundsWithoutVisualOffset_Widget(d); |
930 | const iInt2 rootSize = size_Root(d->root); | 969 | const iInt2 rootSize = size_Root(d->root); |
931 | const iRect winRect = safeRect_Root(d->root); | 970 | const iRect winRect = safeRect_Root(d->root); |
932 | const int yTop = top_Rect(winRect); | 971 | const int yTop = top_Rect(winRect); |
933 | const int yBottom = bottom_Rect(winRect); | 972 | const int yBottom = bottom_Rect(winRect); |
973 | if (top_Rect(bounds) >= yTop && bottom_Rect(bounds) < yBottom) { | ||
974 | return iFalse; /* fits inside just fine */ | ||
975 | } | ||
934 | //const int safeBottom = rootSize.y - yBottom; | 976 | //const int safeBottom = rootSize.y - yBottom; |
935 | bounds.pos.y += delta; | 977 | bounds.pos.y += delta; |
936 | const iRangei range = { bottom_Rect(winRect) - height_Rect(bounds), yTop }; | 978 | const iRangei range = { bottom_Rect(winRect) - height_Rect(bounds), yTop }; |
@@ -978,7 +1020,7 @@ iBool processEvent_Widget(iWidget *d, const SDL_Event *ev) { | |||
978 | if (!isPerPixel_MouseWheelEvent(&ev->wheel)) { | 1020 | if (!isPerPixel_MouseWheelEvent(&ev->wheel)) { |
979 | step *= lineHeight_Text(uiLabel_FontId); | 1021 | step *= lineHeight_Text(uiLabel_FontId); |
980 | } | 1022 | } |
981 | if (scrollOverflow_Widget_(d, step)) { | 1023 | if (scrollOverflow_Widget(d, step)) { |
982 | return iTrue; | 1024 | return iTrue; |
983 | } | 1025 | } |
984 | } | 1026 | } |
@@ -987,10 +1029,11 @@ iBool processEvent_Widget(iWidget *d, const SDL_Event *ev) { | |||
987 | if (d->flags & overflowScrollable_WidgetFlag && | 1029 | if (d->flags & overflowScrollable_WidgetFlag && |
988 | ~d->flags & visualOffset_WidgetFlag && | 1030 | ~d->flags & visualOffset_WidgetFlag && |
989 | isCommand_UserEvent(ev, "widget.overflow")) { | 1031 | isCommand_UserEvent(ev, "widget.overflow")) { |
990 | scrollOverflow_Widget_(d, 0); /* check bounds */ | 1032 | scrollOverflow_Widget(d, 0); /* check bounds */ |
991 | } | 1033 | } |
992 | if (ev->user.code == command_UserEventCode && d->commandHandler && | 1034 | if (ev->user.code == command_UserEventCode && d->commandHandler && |
993 | d->commandHandler(d, ev->user.data1)) { | 1035 | d->commandHandler(d, ev->user.data1)) { |
1036 | iAssert(get_Root() == d->root); | ||
994 | return iTrue; | 1037 | return iTrue; |
995 | } | 1038 | } |
996 | break; | 1039 | break; |
@@ -1043,11 +1086,18 @@ void drawBackground_Widget(const iWidget *d) { | |||
1043 | init_Paint(&p); | 1086 | init_Paint(&p); |
1044 | drawSoftShadow_Paint(&p, bounds_Widget(d), 12 * gap_UI, black_ColorId, 30); | 1087 | drawSoftShadow_Paint(&p, bounds_Widget(d), 12 * gap_UI, black_ColorId, 30); |
1045 | } | 1088 | } |
1046 | 1089 | const iBool isFaded = fadeBackground && | |
1047 | if (fadeBackground && ~d->flags & noFadeBackground_WidgetFlag) { | 1090 | ~d->flags & noFadeBackground_WidgetFlag && |
1091 | ~d->flags & destroyPending_WidgetFlag; | ||
1092 | if (isFaded) { | ||
1048 | iPaint p; | 1093 | iPaint p; |
1049 | init_Paint(&p); | 1094 | init_Paint(&p); |
1050 | p.alpha = 0x50; | 1095 | p.alpha = 0x50; |
1096 | if (flags_Widget(d) & (visualOffset_WidgetFlag | dragged_WidgetFlag)) { | ||
1097 | const float area = d->rect.size.x * d->rect.size.y; | ||
1098 | const float visibleArea = area_Rect(intersect_Rect(bounds_Widget(d), rect_Root(d->root))); | ||
1099 | p.alpha *= (area > 0 ? visibleArea / area : 0.0f); | ||
1100 | } | ||
1051 | SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND); | 1101 | SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND); |
1052 | int fadeColor; | 1102 | int fadeColor; |
1053 | switch (colorTheme_App()) { | 1103 | switch (colorTheme_App()) { |
@@ -1061,9 +1111,7 @@ void drawBackground_Widget(const iWidget *d) { | |||
1061 | fadeColor = gray50_ColorId; | 1111 | fadeColor = gray50_ColorId; |
1062 | break; | 1112 | break; |
1063 | } | 1113 | } |
1064 | fillRect_Paint(&p, | 1114 | fillRect_Paint(&p, rect_Root(d->root), fadeColor); |
1065 | rect_Root(d->root), | ||
1066 | fadeColor); | ||
1067 | SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE); | 1115 | SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE); |
1068 | } | 1116 | } |
1069 | if (d->bgColor >= 0 || d->frameColor >= 0) { | 1117 | if (d->bgColor >= 0 || d->frameColor >= 0) { |
@@ -1319,6 +1367,22 @@ iAny *findParentClass_Widget(const iWidget *d, const iAnyClass *class) { | |||
1319 | return i; | 1367 | return i; |
1320 | } | 1368 | } |
1321 | 1369 | ||
1370 | iAny *findOverflowScrollable_Widget(iWidget *d) { | ||
1371 | const iRect rootRect = rect_Root(d->root); | ||
1372 | for (iWidget *w = d; w; w = parent_Widget(w)) { | ||
1373 | if (flags_Widget(w) & overflowScrollable_WidgetFlag) { | ||
1374 | const iRect bounds = boundsWithoutVisualOffset_Widget(w); | ||
1375 | if ((bottom_Rect(bounds) > bottom_Rect(rootRect) || | ||
1376 | top_Rect(bounds) < top_Rect(rootRect)) && | ||
1377 | !hasVisibleChildOnTop_Widget(w)) { | ||
1378 | return w; | ||
1379 | } | ||
1380 | return NULL; | ||
1381 | } | ||
1382 | } | ||
1383 | return NULL; | ||
1384 | } | ||
1385 | |||
1322 | size_t childCount_Widget(const iWidget *d) { | 1386 | size_t childCount_Widget(const iWidget *d) { |
1323 | if (!d->children) return 0; | 1387 | if (!d->children) return 0; |
1324 | return size_ObjectList(d->children); | 1388 | return size_ObjectList(d->children); |
@@ -1567,7 +1631,8 @@ static void printInfo_Widget_(const iWidget *d) { | |||
1567 | cstr_String(text_LabelWidget((const iLabelWidget *) d)), | 1631 | cstr_String(text_LabelWidget((const iLabelWidget *) d)), |
1568 | cstr_String(command_LabelWidget((const iLabelWidget *) d))); | 1632 | cstr_String(command_LabelWidget((const iLabelWidget *) d))); |
1569 | } | 1633 | } |
1570 | printf("size:%dx%d {min:%dx%d} [%d..%d %d:%d] flags:%08llx%s%s%s%s%s\n", | 1634 | printf("pos:%d,%d size:%dx%d {min:%dx%d} [%d..%d %d:%d] flags:%08llx%s%s%s%s%s%s%s\n", |
1635 | d->rect.pos.x, d->rect.pos.y, | ||
1571 | d->rect.size.x, d->rect.size.y, | 1636 | d->rect.size.x, d->rect.size.y, |
1572 | d->minSize.x, d->minSize.y, | 1637 | d->minSize.x, d->minSize.y, |
1573 | d->padding[0], d->padding[2], | 1638 | d->padding[0], d->padding[2], |
@@ -1577,7 +1642,9 @@ static void printInfo_Widget_(const iWidget *d) { | |||
1577 | d->flags & tight_WidgetFlag ? " tight" : "", | 1642 | d->flags & tight_WidgetFlag ? " tight" : "", |
1578 | d->flags & fixedWidth_WidgetFlag ? " fixW" : "", | 1643 | d->flags & fixedWidth_WidgetFlag ? " fixW" : "", |
1579 | d->flags & fixedHeight_WidgetFlag ? " fixH" : "", | 1644 | d->flags & fixedHeight_WidgetFlag ? " fixH" : "", |
1580 | d->flags & resizeToParentWidth_WidgetFlag ? " rsPrnW" : ""); | 1645 | d->flags & resizeToParentWidth_WidgetFlag ? " prnW" : "", |
1646 | d->flags & arrangeWidth_WidgetFlag ? " aW" : "", | ||
1647 | d->flags & resizeWidthOfChildren_WidgetFlag ? " rsWChild" : ""); | ||
1581 | } | 1648 | } |
1582 | 1649 | ||
1583 | static void printTree_Widget_(const iWidget *d, int indent) { | 1650 | static void printTree_Widget_(const iWidget *d, int indent) { |
diff --git a/src/ui/widget.h b/src/ui/widget.h index 5c05e917..79d45f23 100644 --- a/src/ui/widget.h +++ b/src/ui/widget.h | |||
@@ -117,6 +117,9 @@ enum iWidgetFlag { | |||
117 | #define parentCannotResizeHeight_WidgetFlag iBit64(58) | 117 | #define parentCannotResizeHeight_WidgetFlag iBit64(58) |
118 | #define ignoreForParentWidth_WidgetFlag iBit64(59) | 118 | #define ignoreForParentWidth_WidgetFlag iBit64(59) |
119 | #define noFadeBackground_WidgetFlag iBit64(60) | 119 | #define noFadeBackground_WidgetFlag iBit64(60) |
120 | #define destroyPending_WidgetFlag iBit64(61) /* TODO: needed? */ | ||
121 | #define edgeDraggable_WidgetFlag iBit64(62) | ||
122 | #define refChildrenOffset_WidgetFlag iBit64(63) /* visual offset determined by the offset of referenced children */ | ||
120 | 123 | ||
121 | enum iWidgetAddPos { | 124 | enum iWidgetAddPos { |
122 | back_WidgetAddPos, | 125 | back_WidgetAddPos, |
@@ -135,13 +138,14 @@ struct Impl_Widget { | |||
135 | iRect rect; | 138 | iRect rect; |
136 | iInt2 minSize; | 139 | iInt2 minSize; |
137 | iWidget * sizeRef; | 140 | iWidget * sizeRef; |
141 | iWidget * offsetRef; | ||
138 | int padding[4]; /* left, top, right, bottom */ | 142 | int padding[4]; /* left, top, right, bottom */ |
139 | iAnim visualOffset; | 143 | iAnim visualOffset; |
140 | int bgColor; | 144 | int bgColor; |
141 | int frameColor; | 145 | int frameColor; |
142 | iObjectList *children; | 146 | iObjectList *children; |
143 | iWidget * parent; | 147 | iWidget * parent; |
144 | iBool (*commandHandler)(iWidget *, const char *); | 148 | iBool (*commandHandler)(iWidget *, const char *); |
145 | iRoot * root; | 149 | iRoot * root; |
146 | }; | 150 | }; |
147 | 151 | ||
@@ -193,6 +197,7 @@ iAny * findChild_Widget (const iWidget *, const char *id); | |||
193 | const iPtrArray *findChildren_Widget (const iWidget *, const char *id); | 197 | const iPtrArray *findChildren_Widget (const iWidget *, const char *id); |
194 | iAny * findParentClass_Widget (const iWidget *, const iAnyClass *class); | 198 | iAny * findParentClass_Widget (const iWidget *, const iAnyClass *class); |
195 | iAny * findFocusable_Widget (const iWidget *startFrom, enum iWidgetFocusDir focusDir); | 199 | iAny * findFocusable_Widget (const iWidget *startFrom, enum iWidgetFocusDir focusDir); |
200 | iAny * findOverflowScrollable_Widget (iWidget *); | ||
196 | size_t childCount_Widget (const iWidget *); | 201 | size_t childCount_Widget (const iWidget *); |
197 | void draw_Widget (const iWidget *); | 202 | void draw_Widget (const iWidget *); |
198 | void drawBackground_Widget (const iWidget *); | 203 | void drawBackground_Widget (const iWidget *); |
@@ -262,6 +267,7 @@ iAny * child_Widget (iWidget *, size_t index); /* O(n) */ | |||
262 | size_t childIndex_Widget (const iWidget *, const iAnyObject *child); /* O(n) */ | 267 | size_t childIndex_Widget (const iWidget *, const iAnyObject *child); /* O(n) */ |
263 | void arrange_Widget (iWidget *); | 268 | void arrange_Widget (iWidget *); |
264 | void resetSize_Widget (iWidget *); | 269 | void resetSize_Widget (iWidget *); |
270 | iBool scrollOverflow_Widget (iWidget *, int delta); /* moves the widget */ | ||
265 | iBool dispatchEvent_Widget (iWidget *, const SDL_Event *); | 271 | iBool dispatchEvent_Widget (iWidget *, const SDL_Event *); |
266 | iBool processEvent_Widget (iWidget *, const SDL_Event *); | 272 | iBool processEvent_Widget (iWidget *, const SDL_Event *); |
267 | void postCommand_Widget (const iAnyObject *, const char *cmd, ...); | 273 | void postCommand_Widget (const iAnyObject *, const char *cmd, ...); |
diff --git a/src/ui/window.c b/src/ui/window.c index 99430a05..abdc363d 100644 --- a/src/ui/window.c +++ b/src/ui/window.c | |||
@@ -171,19 +171,6 @@ int numRoots_Window(const iWindow *d) { | |||
171 | return num; | 171 | return num; |
172 | } | 172 | } |
173 | 173 | ||
174 | static void setupUserInterface_Window(iWindow *d) { | ||
175 | #if defined (iPlatformAppleDesktop) | ||
176 | insertMacMenus_(); | ||
177 | #endif | ||
178 | /* One root is created by default. */ | ||
179 | d->roots[0] = new_Root(); | ||
180 | setCurrent_Root(d->roots[0]); | ||
181 | createUserInterface_Root(d->roots[0]); | ||
182 | setCurrent_Root(NULL); | ||
183 | /* One of the roots always has keyboard input focus. */ | ||
184 | d->keyRoot = d->roots[0]; | ||
185 | } | ||
186 | |||
187 | static void windowSizeChanged_Window_(iWindow *d) { | 174 | static void windowSizeChanged_Window_(iWindow *d) { |
188 | const int numRoots = numRoots_Window(d); | 175 | const int numRoots = numRoots_Window(d); |
189 | const iInt2 rootSize = d->size; | 176 | const iInt2 rootSize = d->size; |
@@ -214,6 +201,19 @@ static void windowSizeChanged_Window_(iWindow *d) { | |||
214 | } | 201 | } |
215 | } | 202 | } |
216 | 203 | ||
204 | static void setupUserInterface_Window(iWindow *d) { | ||
205 | #if defined (iPlatformAppleDesktop) | ||
206 | insertMacMenus_(); | ||
207 | #endif | ||
208 | /* One root is created by default. */ | ||
209 | d->roots[0] = new_Root(); | ||
210 | setCurrent_Root(d->roots[0]); | ||
211 | createUserInterface_Root(d->roots[0]); | ||
212 | setCurrent_Root(NULL); | ||
213 | /* One of the roots always has keyboard input focus. */ | ||
214 | d->keyRoot = d->roots[0]; | ||
215 | } | ||
216 | |||
217 | static void updateSize_Window_(iWindow *d, iBool notifyAlways) { | 217 | static void updateSize_Window_(iWindow *d, iBool notifyAlways) { |
218 | iInt2 *size = &d->size; | 218 | iInt2 *size = &d->size; |
219 | const iInt2 oldSize = *size; | 219 | const iInt2 oldSize = *size; |
@@ -515,15 +515,15 @@ void init_Window(iWindow *d, iRect rect) { | |||
515 | 515 | ||
516 | void deinit_Window(iWindow *d) { | 516 | void deinit_Window(iWindow *d) { |
517 | iRecycle(); | 517 | iRecycle(); |
518 | if (theWindow_ == d) { | ||
519 | theWindow_ = NULL; | ||
520 | } | ||
521 | iForIndices(i, d->roots) { | 518 | iForIndices(i, d->roots) { |
522 | if (d->roots[i]) { | 519 | if (d->roots[i]) { |
523 | setCurrent_Root(d->roots[i]); | 520 | setCurrent_Root(d->roots[i]); |
524 | deinit_Root(d->roots[i]); | 521 | deinit_Root(d->roots[i]); |
525 | } | 522 | } |
526 | } | 523 | } |
524 | if (theWindow_ == d) { | ||
525 | theWindow_ = NULL; | ||
526 | } | ||
527 | setCurrent_Root(NULL); | 527 | setCurrent_Root(NULL); |
528 | delete_String(d->pendingSplitUrl); | 528 | delete_String(d->pendingSplitUrl); |
529 | deinit_Text(); | 529 | deinit_Text(); |
@@ -761,7 +761,7 @@ static iBool handleWindowEvent_Window_(iWindow *d, const SDL_WindowEvent *ev) { | |||
761 | #if defined (iPlatformMobile) | 761 | #if defined (iPlatformMobile) |
762 | case SDL_WINDOWEVENT_RESIZED: | 762 | case SDL_WINDOWEVENT_RESIZED: |
763 | /* On mobile, this occurs when the display is rotated. */ | 763 | /* On mobile, this occurs when the display is rotated. */ |
764 | invalidate_Window_(d); | 764 | invalidate_Window(d); |
765 | postRefresh_App(); | 765 | postRefresh_App(); |
766 | return iTrue; | 766 | return iTrue; |
767 | #endif | 767 | #endif |
@@ -772,7 +772,7 @@ static iBool handleWindowEvent_Window_(iWindow *d, const SDL_WindowEvent *ev) { | |||
772 | d->isExposed = iTrue; | 772 | d->isExposed = iTrue; |
773 | #if defined (iPlatformMobile) | 773 | #if defined (iPlatformMobile) |
774 | /* Returned to foreground, may have lost buffered content. */ | 774 | /* Returned to foreground, may have lost buffered content. */ |
775 | invalidate_Window_(d); | 775 | invalidate_Window(d); |
776 | postCommand_App("window.unfreeze"); | 776 | postCommand_App("window.unfreeze"); |
777 | #endif | 777 | #endif |
778 | return iFalse; | 778 | return iFalse; |
@@ -995,7 +995,7 @@ void draw_Window(iWindow *d) { | |||
995 | /* Check if root needs resizing. */ { | 995 | /* Check if root needs resizing. */ { |
996 | iInt2 renderSize; | 996 | iInt2 renderSize; |
997 | SDL_GetRendererOutputSize(d->render, &renderSize.x, &renderSize.y); | 997 | SDL_GetRendererOutputSize(d->render, &renderSize.x, &renderSize.y); |
998 | if (!isEqual_I2(renderSize, d->root->rect.size)) { | 998 | if (!isEqual_I2(renderSize, d->size)) { |
999 | updateSize_Window_(d, iTrue); | 999 | updateSize_Window_(d, iTrue); |
1000 | processEvents_App(postedEventsOnly_AppEventMode); | 1000 | processEvents_App(postedEventsOnly_AppEventMode); |
1001 | } | 1001 | } |
@@ -1007,8 +1007,13 @@ void draw_Window(iWindow *d) { | |||
1007 | init_Paint(&p); | 1007 | init_Paint(&p); |
1008 | /* Clear the window. The clear color is visible as a border around the window | 1008 | /* Clear the window. The clear color is visible as a border around the window |
1009 | when the custom frame is being used. */ { | 1009 | when the custom frame is being used. */ { |
1010 | setCurrent_Root(d->roots[0]); | ||
1010 | #if defined (iPlatformAppleMobile) | 1011 | #if defined (iPlatformAppleMobile) |
1011 | const iColor back = get_Color(tmBackground_ColorId); | 1012 | iColor back = get_Color(uiBackground_ColorId); |
1013 | if (deviceType_App() == phone_AppDeviceType) { | ||
1014 | /* Page background extends to safe area, so fill it completely. */ | ||
1015 | back = get_Color(tmBackground_ColorId); | ||
1016 | } | ||
1012 | #else | 1017 | #else |
1013 | const iColor back = get_Color(gotFocus && d->place.snap != maximized_WindowSnap && | 1018 | const iColor back = get_Color(gotFocus && d->place.snap != maximized_WindowSnap && |
1014 | ~winFlags & SDL_WINDOW_FULLSCREEN_DESKTOP | 1019 | ~winFlags & SDL_WINDOW_FULLSCREEN_DESKTOP |