diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-07-21 22:26:40 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-07-21 22:26:40 +0300 |
commit | 896c024b4ff1a44affe3d749e09e7b7e8c31970c (patch) | |
tree | 78f8c81d30b5ac0d8143fd142b40a82ab7db4508 | |
parent | d623d42d937d8e7f90deda3f49fe1eb680a7b15e (diff) |
Added GmDocument; very basic layout and render
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | CMakeLists.txt | 3 | ||||
-rw-r--r-- | src/gemini.h | 24 | ||||
-rw-r--r-- | src/gmdocument.c | 78 | ||||
-rw-r--r-- | src/gmdocument.h | 28 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 67 | ||||
-rw-r--r-- | src/ui/text.c | 4 | ||||
-rw-r--r-- | src/ui/text.h | 3 |
8 files changed, 195 insertions, 13 deletions
@@ -1,2 +1,3 @@ | |||
1 | .DS_Store | 1 | .DS_Store |
2 | *.user | 2 | *.user |
3 | build-* | ||
diff --git a/CMakeLists.txt b/CMakeLists.txt index a3d1d9b3..8498cd6e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt | |||
@@ -31,6 +31,9 @@ set (SOURCES | |||
31 | src/main.c | 31 | src/main.c |
32 | src/app.c | 32 | src/app.c |
33 | src/app.h | 33 | src/app.h |
34 | src/gemini.h | ||
35 | src/gmdocument.h | ||
36 | src/gmdocument.c | ||
34 | src/stb_image.h | 37 | src/stb_image.h |
35 | src/stb_truetype.h | 38 | src/stb_truetype.h |
36 | # User interface: | 39 | # User interface: |
diff --git a/src/gemini.h b/src/gemini.h new file mode 100644 index 00000000..dd281e20 --- /dev/null +++ b/src/gemini.h | |||
@@ -0,0 +1,24 @@ | |||
1 | #pragma once | ||
2 | |||
3 | /* Response status codes. */ | ||
4 | enum iGmStatusCode { | ||
5 | none_GmStatusCode = 0, | ||
6 | input_GmStatusCode = 10, | ||
7 | sensitiveInput_GmStatusCode = 11, | ||
8 | success_GmStatusCode = 20, | ||
9 | redirectTemporary_GmStatusCode = 30, | ||
10 | redirectPermanent_GmStatusCode = 31, | ||
11 | temporaryFailure_GmStatusCode = 40, | ||
12 | serverUnavailable_GmStatusCode = 41, | ||
13 | cgiError_GmStatusCode = 42, | ||
14 | proxyError_GmStatusCode = 43, | ||
15 | slowDown_GmStatusCode = 44, | ||
16 | permanentFailure_GmStatusCode = 50, | ||
17 | notFound_GmStatusCode = 51, | ||
18 | gone_GmStatusCode = 52, | ||
19 | proxyRequestRefused_GmStatusCode = 53, | ||
20 | badRequest_GmStatusCode = 59, | ||
21 | clientCertificateRequired_GmStatusCode = 60, | ||
22 | certificateNotAuthorized_GmStatusCode = 61, | ||
23 | certificateNotValid_GmStatusCode = 62, | ||
24 | }; | ||
diff --git a/src/gmdocument.c b/src/gmdocument.c new file mode 100644 index 00000000..2c12a5a9 --- /dev/null +++ b/src/gmdocument.c | |||
@@ -0,0 +1,78 @@ | |||
1 | #include "gmdocument.h" | ||
2 | #include "ui/color.h" | ||
3 | #include "ui/text.h" | ||
4 | #include <the_Foundation/array.h> | ||
5 | |||
6 | struct Impl_GmDocument { | ||
7 | iObject object; | ||
8 | iString source; | ||
9 | iInt2 size; | ||
10 | iArray layout; /* contents of source, laid out in document space */ | ||
11 | }; | ||
12 | |||
13 | iDefineObjectConstruction(GmDocument) | ||
14 | |||
15 | static void doLayout_GmDocument_(iGmDocument *d) { | ||
16 | if (d->size.x <= 0 || isEmpty_String(&d->source)) { | ||
17 | return; | ||
18 | } | ||
19 | clear_Array(&d->layout); | ||
20 | iBool isPreformat = iFalse; | ||
21 | iInt2 pos = zero_I2(); | ||
22 | const iRangecc content = range_String(&d->source); | ||
23 | iRangecc line = iNullRange; | ||
24 | while (nextSplit_Rangecc(&content, "\n", &line)) { | ||
25 | iGmRun run; | ||
26 | run.text = line; | ||
27 | run.font = paragraph_FontId; | ||
28 | run.color = white_ColorId; | ||
29 | run.bounds.pos = pos; | ||
30 | run.bounds.size = advanceN_Text(run.font, line.start, size_Range(&line)); | ||
31 | run.linkId = 0; | ||
32 | pushBack_Array(&d->layout, &run); | ||
33 | pos.y += run.bounds.size.y; | ||
34 | } | ||
35 | d->size.y = pos.y; | ||
36 | } | ||
37 | |||
38 | void init_GmDocument(iGmDocument *d) { | ||
39 | init_String(&d->source); | ||
40 | d->size = zero_I2(); | ||
41 | init_Array(&d->layout, sizeof(iGmRun)); | ||
42 | } | ||
43 | |||
44 | void deinit_GmDocument(iGmDocument *d) { | ||
45 | deinit_Array(&d->layout); | ||
46 | deinit_String(&d->source); | ||
47 | } | ||
48 | |||
49 | void setWidth_GmDocument(iGmDocument *d, int width) { | ||
50 | d->size.x = width; | ||
51 | doLayout_GmDocument_(d); /* TODO: just flag need-layout and do it later */ | ||
52 | } | ||
53 | |||
54 | void setSource_GmDocument(iGmDocument *d, const iString *source, int width) { | ||
55 | set_String(&d->source, source); | ||
56 | setWidth_GmDocument(d, width); | ||
57 | /* TODO: just flag need-layout and do it later */ | ||
58 | } | ||
59 | |||
60 | void render_GmDocument(const iGmDocument *d, iRangei visRangeY, iGmDocumentRenderFunc render, | ||
61 | void *context) { | ||
62 | iBool isInside = iFalse; | ||
63 | iConstForEach(Array, i, &d->layout) { | ||
64 | const iGmRun *run = i.value; | ||
65 | if (isInside) { | ||
66 | if (top_Rect(run->bounds) > visRangeY.end) { | ||
67 | break; | ||
68 | } | ||
69 | render(context, run); | ||
70 | } | ||
71 | else if (bottom_Rect(run->bounds) >= visRangeY.start) { | ||
72 | isInside = iTrue; | ||
73 | render(context, run); | ||
74 | } | ||
75 | } | ||
76 | } | ||
77 | |||
78 | iDefineClass(GmDocument) | ||
diff --git a/src/gmdocument.h b/src/gmdocument.h new file mode 100644 index 00000000..dfad1d46 --- /dev/null +++ b/src/gmdocument.h | |||
@@ -0,0 +1,28 @@ | |||
1 | #pragma once | ||
2 | |||
3 | #include "gemini.h" | ||
4 | #include <the_Foundation/object.h> | ||
5 | #include <the_Foundation/rect.h> | ||
6 | #include <the_Foundation/string.h> | ||
7 | |||
8 | iDeclareType(GmRun) | ||
9 | |||
10 | struct Impl_GmRun { | ||
11 | iRangecc text; | ||
12 | iRect bounds; /* advance metrics */ | ||
13 | uint8_t font; | ||
14 | uint8_t color; | ||
15 | uint16_t linkId; | ||
16 | }; | ||
17 | |||
18 | iDeclareType(GmDocument) | ||
19 | iDeclareClass(GmDocument) | ||
20 | |||
21 | iDeclareObjectConstruction(GmDocument) | ||
22 | |||
23 | void setWidth_GmDocument (iGmDocument *, int width); | ||
24 | void setSource_GmDocument (iGmDocument *, const iString *source, int width); | ||
25 | |||
26 | typedef void (*iGmDocumentRenderFunc)(void *, const iGmRun *); | ||
27 | |||
28 | void render_GmDocument (const iGmDocument *, iRangei visRangeY, iGmDocumentRenderFunc render, void *); | ||
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index e8a0ded9..aab010c9 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c | |||
@@ -1,5 +1,8 @@ | |||
1 | #include "documentwidget.h" | 1 | #include "documentwidget.h" |
2 | #include "paint.h" | 2 | #include "paint.h" |
3 | #include "util.h" | ||
4 | #include "../gemini.h" | ||
5 | #include "../gmdocument.h" | ||
3 | 6 | ||
4 | #include <the_Foundation/regexp.h> | 7 | #include <the_Foundation/regexp.h> |
5 | #include <the_Foundation/tlsrequest.h> | 8 | #include <the_Foundation/tlsrequest.h> |
@@ -15,9 +18,10 @@ struct Impl_DocumentWidget { | |||
15 | iWidget widget; | 18 | iWidget widget; |
16 | enum iDocumentState state; | 19 | enum iDocumentState state; |
17 | iString *url; | 20 | iString *url; |
18 | iString *source; | ||
19 | int statusCode; | ||
20 | iTlsRequest *request; | 21 | iTlsRequest *request; |
22 | int statusCode; | ||
23 | iString *newSource; | ||
24 | iGmDocument *doc; | ||
21 | }; | 25 | }; |
22 | 26 | ||
23 | iDeclareType(Url) | 27 | iDeclareType(Url) |
@@ -31,7 +35,8 @@ struct Impl_Url { | |||
31 | }; | 35 | }; |
32 | 36 | ||
33 | void init_Url(iUrl *d, const iString *text) { | 37 | void init_Url(iUrl *d, const iString *text) { |
34 | iRegExp *pattern = new_RegExp("(.+)://([^/:?]+)(:[0-9]+)?([^?]*)(\\?.*)?", caseInsensitive_RegExpOption); | 38 | iRegExp *pattern = |
39 | new_RegExp("(.+)://([^/:?]+)(:[0-9]+)?([^?]*)(\\?.*)?", caseInsensitive_RegExpOption); | ||
35 | iRegExpMatch m; | 40 | iRegExpMatch m; |
36 | if (matchString_RegExp(pattern, text, &m)) { | 41 | if (matchString_RegExp(pattern, text, &m)) { |
37 | capturedRange_RegExpMatch(&m, 1, &d->protocol); | 42 | capturedRange_RegExpMatch(&m, 1, &d->protocol); |
@@ -59,22 +64,28 @@ void init_DocumentWidget(iDocumentWidget *d) { | |||
59 | d->state = blank_DocumentState; | 64 | d->state = blank_DocumentState; |
60 | d->url = new_String(); | 65 | d->url = new_String(); |
61 | d->statusCode = 0; | 66 | d->statusCode = 0; |
62 | d->source = new_String(); | ||
63 | d->request = NULL; | 67 | d->request = NULL; |
64 | 68 | d->newSource = new_String(); | |
69 | d->doc = new_GmDocument(); | ||
65 | setUrl_DocumentWidget(d, collectNewCStr_String("gemini.circumlunar.space/")); | 70 | setUrl_DocumentWidget(d, collectNewCStr_String("gemini.circumlunar.space/")); |
66 | } | 71 | } |
67 | 72 | ||
68 | void deinit_DocumentWidget(iDocumentWidget *d) { | 73 | void deinit_DocumentWidget(iDocumentWidget *d) { |
69 | delete_String(d->source); | ||
70 | delete_String(d->url); | 74 | delete_String(d->url); |
75 | delete_String(d->newSource); | ||
76 | iRelease(d->doc); | ||
77 | } | ||
78 | |||
79 | static int documentWidth_DocumentWidget_(const iDocumentWidget *d) { | ||
80 | const iWidget *w = constAs_Widget(d); | ||
81 | const iRect bounds = bounds_Widget(w); | ||
82 | return bounds.size.x; | ||
71 | } | 83 | } |
72 | 84 | ||
73 | void setSource_DocumentWidget(iDocumentWidget *d, const iString *source) { | 85 | void setSource_DocumentWidget(iDocumentWidget *d, const iString *source) { |
74 | /* TODO: lock source during update */ | 86 | /* TODO: lock source during update */ |
75 | set_String(d->source, source); | 87 | setSource_GmDocument(d->doc, source, documentWidth_DocumentWidget_(d)); |
76 | printf("%s\n", cstr_String(d->source)); | 88 | d->state = ready_DocumentState; |
77 | d->state = layout_DocumentState; | ||
78 | } | 89 | } |
79 | 90 | ||
80 | static iRangecc getLine_(iRangecc text) { | 91 | static iRangecc getLine_(iRangecc text) { |
@@ -92,10 +103,12 @@ static void requestFinished_DocumentWidget_(iAnyObject *obj) { | |||
92 | /* First line is the status code. */ { | 103 | /* First line is the status code. */ { |
93 | iString *line = newRange_String(respLine); | 104 | iString *line = newRange_String(respLine); |
94 | trim_String(line); | 105 | trim_String(line); |
95 | printf("response: %s\n", cstr_String(line)); | 106 | d->statusCode = toInt_String(line); |
107 | printf("response (%02d): %s\n", d->statusCode, cstr_String(line)); | ||
108 | /* TODO: post a command with the status code */ | ||
96 | delete_String(line); | 109 | delete_String(line); |
97 | } | 110 | } |
98 | setSource_DocumentWidget(d, collect_String(newRange_String(responseRange))); | 111 | setCStrN_String(d->newSource, responseRange.start, size_Range(&responseRange)); |
99 | delete_Block(response); | 112 | delete_Block(response); |
100 | iReleaseLater(d->request); | 113 | iReleaseLater(d->request); |
101 | d->request = NULL; | 114 | d->request = NULL; |
@@ -135,14 +148,44 @@ void setUrl_DocumentWidget(iDocumentWidget *d, const iString *url) { | |||
135 | 148 | ||
136 | static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *ev) { | 149 | static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *ev) { |
137 | iWidget *w = as_Widget(d); | 150 | iWidget *w = as_Widget(d); |
151 | if (isResize_UserEvent(ev)) { | ||
152 | setWidth_GmDocument(d->doc, documentWidth_DocumentWidget_(d)); | ||
153 | } | ||
138 | return processEvent_Widget(w, ev); | 154 | return processEvent_Widget(w, ev); |
139 | } | 155 | } |
140 | 156 | ||
157 | iDeclareType(DrawContext) | ||
158 | |||
159 | struct Impl_DrawContext { | ||
160 | const iDocumentWidget *d; | ||
161 | iRect bounds; | ||
162 | iPaint paint; | ||
163 | }; | ||
164 | |||
165 | static void drawRun_DrawContext_(void *context, const iGmRun *run) { | ||
166 | iDrawContext *d = context; | ||
167 | iString text; | ||
168 | /* TODO: making a copy is unnecessary; the text routines should accept Rangecc */ | ||
169 | initRange_String(&text, run->text); | ||
170 | drawString_Text(run->font, add_I2(d->bounds.pos, run->bounds.pos), run->color, &text); | ||
171 | deinit_String(&text); | ||
172 | } | ||
173 | |||
141 | static void draw_DocumentWidget_(const iDocumentWidget *d) { | 174 | static void draw_DocumentWidget_(const iDocumentWidget *d) { |
142 | const iWidget *w = constAs_Widget(d); | 175 | const iWidget *w = constAs_Widget(d); |
143 | draw_Widget(w); | 176 | draw_Widget(w); |
177 | /* Update the document? */ | ||
178 | if (!isEmpty_String(d->newSource)) { | ||
179 | /* TODO: Do this in the background. However, that requires a text metrics calculator | ||
180 | that does not try to cache the glyph bitmaps. */ | ||
181 | setSource_GmDocument(d->doc, d->newSource, documentWidth_DocumentWidget_(d)); | ||
182 | clear_String(d->newSource); | ||
183 | iConstCast(iDocumentWidget *, d)->state = ready_DocumentState; | ||
184 | } | ||
144 | if (d->state != ready_DocumentState) return; | 185 | if (d->state != ready_DocumentState) return; |
145 | /* TODO: lock source during draw */ | 186 | iDrawContext ctx = {.d = d, .bounds = bounds_Widget(w) }; |
187 | init_Paint(&ctx.paint); | ||
188 | render_GmDocument(d->doc, (iRangei){ 0, height_Rect(ctx.bounds) }, drawRun_DrawContext_, &ctx); | ||
146 | } | 189 | } |
147 | 190 | ||
148 | iBeginDefineSubclass(DocumentWidget, Widget) | 191 | iBeginDefineSubclass(DocumentWidget, Widget) |
diff --git a/src/ui/text.c b/src/ui/text.c index ac211481..edd1f69d 100644 --- a/src/ui/text.c +++ b/src/ui/text.c | |||
@@ -484,6 +484,10 @@ void draw_Text(int fontId, iInt2 pos, int color, const char *text, ...) { | |||
484 | deinit_Block(&chars); | 484 | deinit_Block(&chars); |
485 | } | 485 | } |
486 | 486 | ||
487 | void drawString_Text(int fontId, iInt2 pos, int color, const iString *text) { | ||
488 | draw_Text_(fontId, pos, color, cstr_String(text)); | ||
489 | } | ||
490 | |||
487 | void drawCentered_Text(int fontId, iRect rect, int color, const char *text, ...) { | 491 | void drawCentered_Text(int fontId, iRect rect, int color, const char *text, ...) { |
488 | iBlock chars; | 492 | iBlock chars; |
489 | init_Block(&chars, 0); { | 493 | init_Block(&chars, 0); { |
diff --git a/src/ui/text.h b/src/ui/text.h index b689582e..85cd39cf 100644 --- a/src/ui/text.h +++ b/src/ui/text.h | |||
@@ -32,9 +32,10 @@ void deinit_Text (void); | |||
32 | int lineHeight_Text (int font); | 32 | int lineHeight_Text (int font); |
33 | iInt2 measure_Text (int font, const char *text); | 33 | iInt2 measure_Text (int font, const char *text); |
34 | iInt2 advance_Text (int font, const char *text); | 34 | iInt2 advance_Text (int font, const char *text); |
35 | iInt2 advanceN_Text (int font, const char *text, size_t n); | 35 | iInt2 advanceN_Text (int font, const char *text, size_t n); /* `n` in characters */ |
36 | 36 | ||
37 | void draw_Text (int font, iInt2 pos, int color, const char *text, ...); /* negative pos to switch alignment */ | 37 | void draw_Text (int font, iInt2 pos, int color, const char *text, ...); /* negative pos to switch alignment */ |
38 | void drawString_Text (int font, iInt2 pos, int color, const iString *text); | ||
38 | void drawCentered_Text (int font, iRect rect, int color, const char *text, ...); | 39 | void drawCentered_Text (int font, iRect rect, int color, const char *text, ...); |
39 | 40 | ||
40 | SDL_Texture * glyphCache_Text (void); | 41 | SDL_Texture * glyphCache_Text (void); |