summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/app.c3
-rw-r--r--src/gmdocument.c87
-rw-r--r--src/gmdocument.h7
-rw-r--r--src/ui/documentwidget.c177
4 files changed, 254 insertions, 20 deletions
diff --git a/src/app.c b/src/app.c
index 2192e9ae..1164484e 100644
--- a/src/app.c
+++ b/src/app.c
@@ -3,6 +3,7 @@
3#include "ui/window.h" 3#include "ui/window.h"
4#include "ui/inputwidget.h" 4#include "ui/inputwidget.h"
5#include "ui/labelwidget.h" 5#include "ui/labelwidget.h"
6#include "ui/documentwidget.h"
6#include "ui/util.h" 7#include "ui/util.h"
7#include "ui/text.h" 8#include "ui/text.h"
8#include "ui/color.h" 9#include "ui/color.h"
@@ -290,6 +291,8 @@ iBool handleCommand_App(const char *cmd) {
290 iApp *d = &app_; 291 iApp *d = &app_;
291 iWidget *root = d->window->root; 292 iWidget *root = d->window->root;
292 if (equal_Command(cmd, "open")) { 293 if (equal_Command(cmd, "open")) {
294 setUrl_DocumentWidget(findChild_Widget(root, "document"),
295 collect_String(newCStr_String(valuePtr_Command(cmd, "url"))));
293 } 296 }
294 else if (equal_Command(cmd, "quit")) { 297 else if (equal_Command(cmd, "quit")) {
295 SDL_Event ev; 298 SDL_Event ev;
diff --git a/src/gmdocument.c b/src/gmdocument.c
index 4132a15e..1aefe133 100644
--- a/src/gmdocument.c
+++ b/src/gmdocument.c
@@ -3,13 +3,31 @@
3#include "ui/text.h" 3#include "ui/text.h"
4#include "ui/metrics.h" 4#include "ui/metrics.h"
5 5
6#include <the_Foundation/array.h> 6#include <the_Foundation/ptrarray.h>
7#include <the_Foundation/regexp.h>
8
9iDeclareType(GmLink)
10
11struct Impl_GmLink {
12 iString url;
13};
14
15void init_GmLink(iGmLink *d) {
16 init_String(&d->url);
17}
18
19void deinit_GmLink(iGmLink *d) {
20 deinit_String(&d->url);
21}
22
23iDefineTypeConstruction(GmLink)
7 24
8struct Impl_GmDocument { 25struct Impl_GmDocument {
9 iObject object; 26 iObject object;
10 iString source; 27 iString source;
11 iInt2 size; 28 iInt2 size;
12 iArray layout; /* contents of source, laid out in document space */ 29 iArray layout; /* contents of source, laid out in document space */
30 iPtrArray links;
13}; 31};
14 32
15iDefineObjectConstruction(GmDocument) 33iDefineObjectConstruction(GmDocument)
@@ -86,15 +104,45 @@ iInt2 measurePreformattedBlock_GmDocument_(const iGmDocument *d, const char *sta
86 return measureRange_Text(font, preBlock); 104 return measureRange_Text(font, preBlock);
87} 105}
88 106
107static iRangecc addLink_GmDocument_(iGmDocument *d, iRangecc line, iGmLinkId *linkId) {
108 iRegExp *pattern = new_RegExp("=>\\s*([^\\s]+)(\\s.*)?", caseInsensitive_RegExpOption);
109 iRegExpMatch m;
110 if (matchRange_RegExp(pattern, line, &m)) {
111 iGmLink *link = new_GmLink();
112 setRange_String(&link->url, capturedRange_RegExpMatch(&m, 1));
113 pushBack_PtrArray(&d->links, link);
114 *linkId = size_PtrArray(&d->links); /* index + 1 */
115 iRangecc desc = capturedRange_RegExpMatch(&m, 2);
116 trim_Rangecc(&desc);
117 if (!isEmpty_Range(&desc)) {
118 line = desc; /* Just show the description. */
119 }
120 else {
121 line = capturedRange_RegExpMatch(&m, 1); /* Show the URL. */
122 }
123 }
124 iRelease(pattern);
125 return line;
126}
127
128static void clearLinks_GmDocument_(iGmDocument *d) {
129 iForEach(PtrArray, i, &d->links) {
130 delete_GmLink(i.ptr);
131 }
132 clear_PtrArray(&d->links);
133}
134
89static void doLayout_GmDocument_(iGmDocument *d) { 135static void doLayout_GmDocument_(iGmDocument *d) {
90 if (d->size.x <= 0 || isEmpty_String(&d->source)) { 136 if (d->size.x <= 0 || isEmpty_String(&d->source)) {
91 return; 137 return;
92 } 138 }
93 clear_Array(&d->layout); 139 clear_Array(&d->layout);
140 clearLinks_GmDocument_(d);
94 iBool isPreformat = iFalse; 141 iBool isPreformat = iFalse;
95 iInt2 pos = zero_I2(); 142 iInt2 pos = zero_I2();
96 const iRangecc content = range_String(&d->source); 143 const iRangecc content = range_String(&d->source);
97 iRangecc line = iNullRange; 144 iRangecc line = iNullRange;
145 /* TODO: Collect these parameters into a GmTheme. */
98 static const int fonts[max_GmLineType] = { 146 static const int fonts[max_GmLineType] = {
99 paragraph_FontId, 147 paragraph_FontId,
100 paragraph_FontId, /* bullet */ 148 paragraph_FontId, /* bullet */
@@ -151,6 +199,13 @@ static void doLayout_GmDocument_(iGmDocument *d) {
151 /* TODO: store and link the alt text to this run */ 199 /* TODO: store and link the alt text to this run */
152 continue; 200 continue;
153 } 201 }
202 else if (type == link_GmLineType) {
203 line = addLink_GmDocument_(d, line, &run.linkId);
204 if (!run.linkId) {
205 /* Invalid formatting. */
206 type = text_GmLineType;
207 }
208 }
154 trimLine_Rangecc_(&line, type); 209 trimLine_Rangecc_(&line, type);
155 run.font = fonts[type]; 210 run.font = fonts[type];
156 } 211 }
@@ -175,6 +230,10 @@ static void doLayout_GmDocument_(iGmDocument *d) {
175 if (!isPreformat || (prevType != preformatted_GmLineType)) { 230 if (!isPreformat || (prevType != preformatted_GmLineType)) {
176 int required = 231 int required =
177 iMax(topMargin[type], bottomMargin[prevType]) * lineHeight_Text(paragraph_FontId); 232 iMax(topMargin[type], bottomMargin[prevType]) * lineHeight_Text(paragraph_FontId);
233 if (type == link_GmLineType && prevType == link_GmLineType) {
234 /* No margin between consecutive links. */
235 required = 0;
236 }
178 if (isEmpty_Array(&d->layout)) { 237 if (isEmpty_Array(&d->layout)) {
179 required = 0; /* top of document */ 238 required = 0; /* top of document */
180 } 239 }
@@ -202,6 +261,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
202 isFirstText = iFalse; 261 isFirstText = iFalse;
203 } 262 }
204 iRangecc runLine = line; 263 iRangecc runLine = line;
264 /* Create one or more runs for this line. */
205 while (!isEmpty_Range(&runLine)) { 265 while (!isEmpty_Range(&runLine)) {
206 run.bounds.pos = addX_I2(pos, indent * gap_UI); 266 run.bounds.pos = addX_I2(pos, indent * gap_UI);
207 const char *contPos; 267 const char *contPos;
@@ -222,9 +282,12 @@ void init_GmDocument(iGmDocument *d) {
222 init_String(&d->source); 282 init_String(&d->source);
223 d->size = zero_I2(); 283 d->size = zero_I2();
224 init_Array(&d->layout, sizeof(iGmRun)); 284 init_Array(&d->layout, sizeof(iGmRun));
285 init_PtrArray(&d->links);
225} 286}
226 287
227void deinit_GmDocument(iGmDocument *d) { 288void deinit_GmDocument(iGmDocument *d) {
289 clearLinks_GmDocument_(d);
290 deinit_PtrArray(&d->links);
228 deinit_Array(&d->layout); 291 deinit_Array(&d->layout);
229 deinit_String(&d->source); 292 deinit_String(&d->source);
230} 293}
@@ -243,7 +306,7 @@ static void normalize_GmDocument(iGmDocument *d) {
243 iRangecc src = range_String(&d->source); 306 iRangecc src = range_String(&d->source);
244 iRangecc line = iNullRange; 307 iRangecc line = iNullRange;
245 iBool isPreformat = iFalse; 308 iBool isPreformat = iFalse;
246 const int preTabWidth = 4; /* TODO: user-configurable parameter */ 309 const int preTabWidth = 8; /* TODO: user-configurable parameter */
247 while (nextSplit_Rangecc(&src, "\n", &line)) { 310 while (nextSplit_Rangecc(&src, "\n", &line)) {
248 if (isPreformat) { 311 if (isPreformat) {
249 /* Replace any tab characters with spaces for visualization. */ 312 /* Replace any tab characters with spaces for visualization. */
@@ -302,6 +365,7 @@ void setSource_GmDocument(iGmDocument *d, const iString *source, int width) {
302void render_GmDocument(const iGmDocument *d, iRangei visRangeY, iGmDocumentRenderFunc render, 365void render_GmDocument(const iGmDocument *d, iRangei visRangeY, iGmDocumentRenderFunc render,
303 void *context) { 366 void *context) {
304 iBool isInside = iFalse; 367 iBool isInside = iFalse;
368 /* TODO: Check lookup table for quick starting position. */
305 iConstForEach(Array, i, &d->layout) { 369 iConstForEach(Array, i, &d->layout) {
306 const iGmRun *run = i.value; 370 const iGmRun *run = i.value;
307 if (isInside) { 371 if (isInside) {
@@ -321,4 +385,23 @@ iInt2 size_GmDocument(const iGmDocument *d) {
321 return d->size; 385 return d->size;
322} 386}
323 387
388const iGmRun *findRun_GmDocument(const iGmDocument *d, iInt2 pos) {
389 /* TODO: Perf optimization likely needed; use a block map? */
390 iConstForEach(Array, i, &d->layout) {
391 const iGmRun *run = i.value;
392 if (contains_Rect(run->bounds, pos)) {
393 return run;
394 }
395 }
396 return NULL;
397}
398
399const iString *linkUrl_GmDocument(const iGmDocument *d, iGmLinkId linkId) {
400 if (linkId > 0 && linkId <= size_PtrArray(&d->links)) {
401 const iGmLink *link = constAt_PtrArray(&d->links, linkId - 1);
402 return &link->url;
403 }
404 return NULL;
405}
406
324iDefineClass(GmDocument) 407iDefineClass(GmDocument)
diff --git a/src/gmdocument.h b/src/gmdocument.h
index 70e912df..b5102044 100644
--- a/src/gmdocument.h
+++ b/src/gmdocument.h
@@ -7,12 +7,14 @@
7 7
8iDeclareType(GmRun) 8iDeclareType(GmRun)
9 9
10typedef uint16_t iGmLinkId;
11
10struct Impl_GmRun { 12struct Impl_GmRun {
11 iRangecc text; 13 iRangecc text;
12 iRect bounds; /* advance metrics */ 14 iRect bounds; /* advance metrics */
13 uint8_t font; 15 uint8_t font;
14 uint8_t color; 16 uint8_t color;
15 uint16_t linkId; 17 iGmLinkId linkId; /* zero for non-links */
16}; 18};
17 19
18iDeclareType(GmDocument) 20iDeclareType(GmDocument)
@@ -27,3 +29,6 @@ typedef void (*iGmDocumentRenderFunc)(void *, const iGmRun *);
27 29
28void render_GmDocument (const iGmDocument *, iRangei visRangeY, iGmDocumentRenderFunc render, void *); 30void render_GmDocument (const iGmDocument *, iRangei visRangeY, iGmDocumentRenderFunc render, void *);
29iInt2 size_GmDocument (const iGmDocument *); 31iInt2 size_GmDocument (const iGmDocument *);
32
33const iGmRun * findRun_GmDocument (const iGmDocument *, iInt2 pos);
34const iString * linkUrl_GmDocument (const iGmDocument *, iGmLinkId linkId);
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 56726320..ca91d896 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -7,6 +7,7 @@
7 7
8#include <the_Foundation/file.h> 8#include <the_Foundation/file.h>
9#include <the_Foundation/path.h> 9#include <the_Foundation/path.h>
10#include <the_Foundation/ptrarray.h>
10#include <the_Foundation/regexp.h> 11#include <the_Foundation/regexp.h>
11#include <the_Foundation/tlsrequest.h> 12#include <the_Foundation/tlsrequest.h>
12 13
@@ -17,6 +18,8 @@ enum iDocumentState {
17 ready_DocumentState, 18 ready_DocumentState,
18}; 19};
19 20
21
22
20struct Impl_DocumentWidget { 23struct Impl_DocumentWidget {
21 iWidget widget; 24 iWidget widget;
22 enum iDocumentState state; 25 enum iDocumentState state;
@@ -27,6 +30,9 @@ struct Impl_DocumentWidget {
27 iGmDocument *doc; 30 iGmDocument *doc;
28 int pageMargin; 31 int pageMargin;
29 int scrollY; 32 int scrollY;
33 iPtrArray visibleLinks;
34 const iGmRun *hoverLink;
35 iClick click;
30}; 36};
31 37
32iDeclareType(Url) 38iDeclareType(Url)
@@ -44,15 +50,15 @@ void init_Url(iUrl *d, const iString *text) {
44 new_RegExp("(.+)://([^/:?]*)(:[0-9]+)?([^?]*)(\\?.*)?", caseInsensitive_RegExpOption); 50 new_RegExp("(.+)://([^/:?]*)(:[0-9]+)?([^?]*)(\\?.*)?", caseInsensitive_RegExpOption);
45 iRegExpMatch m; 51 iRegExpMatch m;
46 if (matchString_RegExp(pattern, text, &m)) { 52 if (matchString_RegExp(pattern, text, &m)) {
47 capturedRange_RegExpMatch(&m, 1, &d->protocol); 53 d->protocol = capturedRange_RegExpMatch(&m, 1);
48 capturedRange_RegExpMatch(&m, 2, &d->host); 54 d->host = capturedRange_RegExpMatch(&m, 2);
49 capturedRange_RegExpMatch(&m, 3, &d->port); 55 d->port = capturedRange_RegExpMatch(&m, 3);
50 if (!isEmpty_Range(&d->port)) { 56 if (!isEmpty_Range(&d->port)) {
51 /* Don't include the colon. */ 57 /* Don't include the colon. */
52 d->port.start++; 58 d->port.start++;
53 } 59 }
54 capturedRange_RegExpMatch(&m, 4, &d->path); 60 d->path = capturedRange_RegExpMatch(&m, 4);
55 capturedRange_RegExpMatch(&m, 5, &d->query); 61 d->query = capturedRange_RegExpMatch(&m, 5);
56 } 62 }
57 else { 63 else {
58 iZap(*d); 64 iZap(*d);
@@ -65,6 +71,7 @@ iDefineObjectConstruction(DocumentWidget)
65void init_DocumentWidget(iDocumentWidget *d) { 71void init_DocumentWidget(iDocumentWidget *d) {
66 iWidget *w = as_Widget(d); 72 iWidget *w = as_Widget(d);
67 init_Widget(w); 73 init_Widget(w);
74 setId_Widget(w, "document");
68 setBackgroundColor_Widget(w, gray25_ColorId); 75 setBackgroundColor_Widget(w, gray25_ColorId);
69 d->state = blank_DocumentState; 76 d->state = blank_DocumentState;
70 d->url = new_String(); 77 d->url = new_String();
@@ -74,11 +81,15 @@ void init_DocumentWidget(iDocumentWidget *d) {
74 d->doc = new_GmDocument(); 81 d->doc = new_GmDocument();
75 d->pageMargin = 5; 82 d->pageMargin = 5;
76 d->scrollY = 0; 83 d->scrollY = 0;
84 init_PtrArray(&d->visibleLinks);
85 d->hoverLink = NULL;
86 init_Click(&d->click, d, SDL_BUTTON_LEFT);
77 setUrl_DocumentWidget( 87 setUrl_DocumentWidget(
78 d, collectNewFormat_String("file://%s/test.gmi", cstr_String(collect_String(home_Path())))); 88 d, collectNewFormat_String("file://%s/test.gmi", cstr_String(collect_String(home_Path()))));
79} 89}
80 90
81void deinit_DocumentWidget(iDocumentWidget *d) { 91void deinit_DocumentWidget(iDocumentWidget *d) {
92 deinit_PtrArray(&d->visibleLinks);
82 delete_String(d->url); 93 delete_String(d->url);
83 delete_String(d->newSource); 94 delete_String(d->newSource);
84 iRelease(d->doc); 95 iRelease(d->doc);
@@ -90,6 +101,17 @@ static int documentWidth_DocumentWidget_(const iDocumentWidget *d) {
90 return iMini(bounds.size.x - gap_UI * d->pageMargin * 2, fontSize_UI * 40); 101 return iMini(bounds.size.x - gap_UI * d->pageMargin * 2, fontSize_UI * 40);
91} 102}
92 103
104static iRect documentBounds_DocumentWidget_(const iDocumentWidget *d) {
105 const iRect bounds = bounds_Widget(constAs_Widget(d));
106 const int margin = gap_UI * d->pageMargin;
107 iRect rect;
108 rect.size.x = documentWidth_DocumentWidget_(d);
109 rect.pos.x = bounds.size.x / 2 - rect.size.x / 2;
110 rect.pos.y = top_Rect(bounds) + margin;
111 rect.size.y = height_Rect(bounds) - 2 * margin;
112 return rect;
113}
114
93void setSource_DocumentWidget(iDocumentWidget *d, const iString *source) { 115void setSource_DocumentWidget(iDocumentWidget *d, const iString *source) {
94 /* TODO: lock source during update */ 116 /* TODO: lock source during update */
95 setSource_GmDocument(d->doc, source, documentWidth_DocumentWidget_(d)); 117 setSource_GmDocument(d->doc, source, documentWidth_DocumentWidget_(d));
@@ -114,6 +136,12 @@ static void requestFinished_DocumentWidget_(iAnyObject *obj) {
114 d->statusCode = toInt_String(line); 136 d->statusCode = toInt_String(line);
115 printf("response (%02d): %s\n", d->statusCode, cstr_String(line)); 137 printf("response (%02d): %s\n", d->statusCode, cstr_String(line));
116 /* TODO: post a command with the status code */ 138 /* TODO: post a command with the status code */
139 switch (d->statusCode) {
140 case redirectPermanent_GmStatusCode:
141 case redirectTemporary_GmStatusCode:
142 postCommandf_App("open url:%s", cstr_String(line) + 3);
143 break;
144 }
117 delete_String(line); 145 delete_String(line);
118 } 146 }
119 setCStrN_String(d->newSource, responseRange.start, size_Range(&responseRange)); 147 setCStrN_String(d->newSource, responseRange.start, size_Range(&responseRange));
@@ -155,19 +183,99 @@ static void fetch_DocumentWidget_(iDocumentWidget *d) {
155} 183}
156 184
157void setUrl_DocumentWidget(iDocumentWidget *d, const iString *url) { 185void setUrl_DocumentWidget(iDocumentWidget *d, const iString *url) {
158 clear_String(d->url); 186 iString *newUrl = new_String();
159 if (indexOfCStr_String(url, "://") == iInvalidPos && !startsWithCase_String(url, "gemini:")) { 187 if (indexOfCStr_String(url, "://") == iInvalidPos && !startsWithCase_String(url, "gemini:")) {
160 /* Prepend default protocol. */ 188 /* Prepend default protocol. */
161 setCStr_String(d->url, "gemini://"); 189 setCStr_String(newUrl, "gemini://");
190 }
191 append_String(newUrl, url);
192 if (cmpStringSc_String(d->url, newUrl, &iCaseInsensitive)) {
193 set_String(d->url, newUrl);
194 fetch_DocumentWidget_(d);
195 }
196 delete_String(newUrl);
197}
198
199static void addVisibleLink_DocumentWidget_(void *context, const iGmRun *run) {
200 iDocumentWidget *d = context;
201 if (run->linkId) {
202 pushBack_PtrArray(&d->visibleLinks, run);
203 }
204}
205
206static iRangei visibleRange_DocumentWidget_(const iDocumentWidget *d) {
207 const int margin = gap_UI * d->pageMargin;
208 return (iRangei){ d->scrollY - margin,
209 d->scrollY + height_Rect(bounds_Widget(constAs_Widget(d))) - margin };
210}
211
212static void updateVisible_DocumentWidget_(iDocumentWidget *d) {
213 clear_PtrArray(&d->visibleLinks);
214 render_GmDocument(
215 d->doc,
216 visibleRange_DocumentWidget_(d),
217 addVisibleLink_DocumentWidget_,
218 d);
219}
220
221static iRangecc dirPath_(iRangecc path) {
222 const size_t pos = lastIndexOfCStr_Rangecc(&path, "/");
223 if (pos == iInvalidPos) return path;
224 return (iRangecc){ path.start, path.start + pos };
225}
226
227static const iString *absoluteUrl_DocumentWidget_(const iDocumentWidget *d, const iString *url) {
228 if (indexOfCStr_String(url, "://") != iInvalidPos) {
229 /* Already absolute. */
230 return url;
231 }
232 iUrl parts;
233 init_Url(&parts, d->url);
234 iString *absolute = new_String();
235 appendRange_String(absolute, parts.protocol);
236 appendCStr_String(absolute, "://");
237 appendRange_String(absolute, parts.host);
238 if (!isEmpty_Range(&parts.port)) {
239 appendCStr_String(absolute, ":");
240 appendRange_String(absolute, parts.port);
241 }
242 if (startsWith_String(url, "/")) {
243 append_String(absolute, url);
244 }
245 else {
246 iRangecc relPath = range_String(url);
247 iRangecc dir = dirPath_(parts.path);
248 for (;;) {
249 if (equal_Rangecc(&relPath, ".")) {
250 relPath.start++;
251 }
252 else if (startsWith_Rangecc(&relPath, "./")) {
253 relPath.start += 2;
254 }
255 else if (equal_Rangecc(&relPath, "..")) {
256 relPath.start += 2;
257 dir = dirPath_(dir);
258 }
259 else if (startsWith_Rangecc(&relPath, "../")) {
260 relPath.start += 3;
261 dir = dirPath_(dir);
262 }
263 else break;
264 }
265 appendRange_String(absolute, dir);
266 if (!endsWith_String(absolute, "/")) {
267 appendCStr_String(absolute, "/");
268 }
269 appendRange_String(absolute, relPath);
162 } 270 }
163 append_String(d->url, url); 271 return collect_String(absolute);
164 fetch_DocumentWidget_(d);
165} 272}
166 273
167static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *ev) { 274static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *ev) {
168 iWidget *w = as_Widget(d); 275 iWidget *w = as_Widget(d);
169 if (isResize_UserEvent(ev)) { 276 if (isResize_UserEvent(ev)) {
170 setWidth_GmDocument(d->doc, documentWidth_DocumentWidget_(d)); 277 setWidth_GmDocument(d->doc, documentWidth_DocumentWidget_(d));
278 updateVisible_DocumentWidget_(d);
171 } 279 }
172 if (ev->type == SDL_KEYDOWN) { 280 if (ev->type == SDL_KEYDOWN) {
173 const int mods = keyMods_Sym(ev->key.keysym.mod); 281 const int mods = keyMods_Sym(ev->key.keysym.mod);
@@ -202,9 +310,46 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
202 else { 310 else {
203 d->scrollY = 0; 311 d->scrollY = 0;
204 } 312 }
313 updateVisible_DocumentWidget_(d);
205 postRefresh_App(); 314 postRefresh_App();
206 return iTrue; 315 return iTrue;
207 } 316 }
317 else if (ev->type == SDL_MOUSEMOTION) {
318 const iGmRun *oldHoverLink = d->hoverLink;
319 d->hoverLink = NULL;
320 const iRect docBounds = documentBounds_DocumentWidget_(d);
321 const iInt2 hoverPos =
322 addY_I2(sub_I2(localCoord_Widget(w, init_I2(ev->motion.x, ev->motion.y)),
323 topLeft_Rect(docBounds)),
324 d->scrollY);
325 iConstForEach(PtrArray, i, &d->visibleLinks) {
326 const iGmRun *run = i.ptr;
327 if (contains_Rect(run->bounds, hoverPos)) {
328 d->hoverLink = run;
329 break;
330 }
331 }
332 if (d->hoverLink != oldHoverLink) {
333 postRefresh_App();
334 }
335 }
336 switch (processEvent_Click(&d->click, ev)) {
337 case finished_ClickResult:
338 if (d->hoverLink) {
339 iAssert(d->hoverLink->linkId);
340 postCommandf_App("open url:%s",
341 cstr_String(absoluteUrl_DocumentWidget_(
342 d, linkUrl_GmDocument(d->doc, d->hoverLink->linkId))));
343 }
344 return iTrue;
345 case started_ClickResult:
346 case double_ClickResult:
347 case drag_ClickResult:
348 case aborted_ClickResult:
349 return iTrue;
350 default:
351 break;
352 }
208 return processEvent_Widget(w, ev); 353 return processEvent_Widget(w, ev);
209} 354}
210 355
@@ -223,7 +368,9 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
223 initRange_String(&text, run->text); 368 initRange_String(&text, run->text);
224 iInt2 origin = addY_I2(d->bounds.pos, -d->widget->scrollY); 369 iInt2 origin = addY_I2(d->bounds.pos, -d->widget->scrollY);
225 drawString_Text(run->font, add_I2(run->bounds.pos, origin), run->color, &text); 370 drawString_Text(run->font, add_I2(run->bounds.pos, origin), run->color, &text);
226// drawRect_Paint(&d->paint, moved_Rect(run->bounds, origin), red_ColorId); 371 if (run == d->widget->hoverLink) {
372 drawRect_Paint(&d->paint, moved_Rect(run->bounds, origin), orange_ColorId);
373 }
227 deinit_String(&text); 374 deinit_String(&text);
228} 375}
229 376
@@ -237,18 +384,14 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) {
237 setSource_GmDocument(d->doc, d->newSource, documentWidth_DocumentWidget_(d)); 384 setSource_GmDocument(d->doc, d->newSource, documentWidth_DocumentWidget_(d));
238 clear_String(d->newSource); 385 clear_String(d->newSource);
239 iConstCast(iDocumentWidget *, d)->state = ready_DocumentState; 386 iConstCast(iDocumentWidget *, d)->state = ready_DocumentState;
387 updateVisible_DocumentWidget_(iConstCast(iDocumentWidget *, d));
240 } 388 }
241 if (d->state != ready_DocumentState) return; 389 if (d->state != ready_DocumentState) return;
242 iDrawContext ctx = {.widget = d, .bounds = bounds_Widget(w) }; 390 iDrawContext ctx = { .widget = d, .bounds = documentBounds_DocumentWidget_(d) };
243 const int margin = gap_UI * d->pageMargin;
244 shrink_Rect(&ctx.bounds, init1_I2(margin));
245 ctx.bounds.size.x = documentWidth_DocumentWidget_(d);
246 ctx.bounds.pos.x = bounds_Widget(w).size.x / 2 - ctx.bounds.size.x / 2;
247 init_Paint(&ctx.paint); 391 init_Paint(&ctx.paint);
248// drawRect_Paint(&ctx.paint, ctx.bounds, teal_ColorId);
249 render_GmDocument( 392 render_GmDocument(
250 d->doc, 393 d->doc,
251 (iRangei){ d->scrollY - margin, d->scrollY + height_Rect(ctx.bounds) + margin }, 394 visibleRange_DocumentWidget_(d),
252 drawRun_DrawContext_, 395 drawRun_DrawContext_,
253 &ctx); 396 &ctx);
254} 397}