summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main.c2
-rw-r--r--src/ui/documentwidget.c20
-rw-r--r--src/ui/inputwidget.c37
-rw-r--r--src/ui/labelwidget.c10
-rw-r--r--src/ui/window.c39
-rw-r--r--src/ui/window.h1
6 files changed, 86 insertions, 23 deletions
diff --git a/src/main.c b/src/main.c
index 457cc1f2..f6ff045d 100644
--- a/src/main.c
+++ b/src/main.c
@@ -15,7 +15,7 @@ int main(int argc, char **argv) {
15 init_Foundation(); 15 init_Foundation();
16 printf("Lagrange: A Beautiful Gemini Client\n"); 16 printf("Lagrange: A Beautiful Gemini Client\n");
17 /* Initialize SDL. */ 17 /* Initialize SDL. */
18 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO)) { 18 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
19 fprintf(stderr, "SDL init failed: %s\n", SDL_GetError()); 19 fprintf(stderr, "SDL init failed: %s\n", SDL_GetError());
20 return -1; 20 return -1;
21 } 21 }
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 9d95957a..71399d3b 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -301,6 +301,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
301 if (isResize_UserEvent(ev)) { 301 if (isResize_UserEvent(ev)) {
302 setWidth_GmDocument(d->doc, documentWidth_DocumentWidget_(d)); 302 setWidth_GmDocument(d->doc, documentWidth_DocumentWidget_(d));
303 updateVisible_DocumentWidget_(d); 303 updateVisible_DocumentWidget_(d);
304 refresh_Widget(w);
304 } 305 }
305 else if (isCommand_Widget(w, ev, "scroll.moved")) { 306 else if (isCommand_Widget(w, ev, "scroll.moved")) {
306 d->scrollY = arg_Command(command_UserEvent(ev)); 307 d->scrollY = arg_Command(command_UserEvent(ev));
@@ -410,30 +411,27 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
410} 411}
411 412
412static void draw_DocumentWidget_(const iDocumentWidget *d) { 413static void draw_DocumentWidget_(const iDocumentWidget *d) {
413 const iWidget *w = constAs_Widget(d); 414 const iWidget *w = constAs_Widget(d);
415 const iRect bounds = bounds_Widget(w);
414 draw_Widget(w); 416 draw_Widget(w);
417 iDrawContext ctx = { .widget = d, .bounds = documentBounds_DocumentWidget_(d) };
418 init_Paint(&ctx.paint);
419 fillRect_Paint(&ctx.paint, bounds, gray25_ColorId);
415 /* Update the document? */ 420 /* Update the document? */
416 if (!isEmpty_String(d->newSource)) { 421 if (!isEmpty_String(d->newSource)) {
417 iDocumentWidget *m = iConstCast(iDocumentWidget *, d); 422 iDocumentWidget *m = iConstCast(iDocumentWidget *, d);
418 /* TODO: Do this in the background. However, that requires a text metrics calculator 423 /* TODO: Do this in the background. However, that requires a text metrics calculator
419 that does not try to cache the glyph bitmaps. */ 424 that does not try to cache the glyph bitmaps. */
420 setSource_GmDocument(m->doc, m->newSource, documentWidth_DocumentWidget_(m)); 425 setSource_GmDocument(m->doc, m->newSource, width_Rect(ctx.bounds));
426 postCommandf_App("document.changed url:%s", cstr_String(d->url));
421 clear_String(m->newSource); 427 clear_String(m->newSource);
422 m->scrollY = 0; 428 m->scrollY = 0;
423 m->state = ready_DocumentState; 429 m->state = ready_DocumentState;
424 updateVisible_DocumentWidget_(m); 430 updateVisible_DocumentWidget_(m);
425 } 431 }
426 if (d->state != ready_DocumentState) return; 432 if (d->state != ready_DocumentState) return;
427 iDrawContext ctx = { .widget = d, .bounds = documentBounds_DocumentWidget_(d) };
428 const iRect bounds = bounds_Widget(w);
429 init_Paint(&ctx.paint);
430 fillRect_Paint(&ctx.paint, bounds, gray25_ColorId);
431 setClip_Paint(&ctx.paint, bounds); 433 setClip_Paint(&ctx.paint, bounds);
432 render_GmDocument( 434 render_GmDocument(d->doc, visibleRange_DocumentWidget_(d), drawRun_DrawContext_, &ctx);
433 d->doc,
434 visibleRange_DocumentWidget_(d),
435 drawRun_DrawContext_,
436 &ctx);
437 clearClip_Paint(&ctx.paint); 435 clearClip_Paint(&ctx.paint);
438 draw_Widget(w); 436 draw_Widget(w);
439} 437}
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c
index 1a692907..b73fac9e 100644
--- a/src/ui/inputwidget.c
+++ b/src/ui/inputwidget.c
@@ -5,6 +5,8 @@
5#include <the_Foundation/array.h> 5#include <the_Foundation/array.h>
6#include <SDL_timer.h> 6#include <SDL_timer.h>
7 7
8static const int REFRESH_INTERVAL = 256;
9
8struct Impl_InputWidget { 10struct Impl_InputWidget {
9 iWidget widget; 11 iWidget widget;
10 enum iInputMode mode; 12 enum iInputMode mode;
@@ -14,6 +16,7 @@ struct Impl_InputWidget {
14 size_t cursor; 16 size_t cursor;
15 int font; 17 int font;
16 iClick click; 18 iClick click;
19 uint32_t timer;
17}; 20};
18 21
19iDefineObjectConstructionArgs(InputWidget, (size_t maxLen), maxLen) 22iDefineObjectConstructionArgs(InputWidget, (size_t maxLen), maxLen)
@@ -33,9 +36,13 @@ void init_InputWidget(iInputWidget *d, size_t maxLen) {
33 setFlags_Widget(w, fixedHeight_WidgetFlag, iTrue); 36 setFlags_Widget(w, fixedHeight_WidgetFlag, iTrue);
34 } 37 }
35 init_Click(&d->click, d, SDL_BUTTON_LEFT); 38 init_Click(&d->click, d, SDL_BUTTON_LEFT);
39 d->timer = 0;
36} 40}
37 41
38void deinit_InputWidget(iInputWidget *d) { 42void deinit_InputWidget(iInputWidget *d) {
43 if (d->timer) {
44 SDL_RemoveTimer(d->timer);
45 }
39 deinit_Array(&d->oldText); 46 deinit_Array(&d->oldText);
40 deinit_Array(&d->text); 47 deinit_Array(&d->text);
41} 48}
@@ -68,6 +75,7 @@ void setText_InputWidget(iInputWidget *d, const iString *text) {
68 iConstForEach(String, i, text) { 75 iConstForEach(String, i, text) {
69 pushBack_Array(&d->text, &i.value); 76 pushBack_Array(&d->text, &i.value);
70 } 77 }
78 refresh_Widget(as_Widget(d));
71} 79}
72 80
73void setTextCStr_InputWidget(iInputWidget *d, const char *cstr) { 81void setTextCStr_InputWidget(iInputWidget *d, const char *cstr) {
@@ -80,6 +88,11 @@ void setCursor_InputWidget(iInputWidget *d, size_t pos) {
80 d->cursor = iMin(pos, size_Array(&d->text)); 88 d->cursor = iMin(pos, size_Array(&d->text));
81} 89}
82 90
91static uint32_t refreshTimer_(uint32_t interval, void *d) {
92 refresh_Widget(d);
93 return interval;
94}
95
83void begin_InputWidget(iInputWidget *d) { 96void begin_InputWidget(iInputWidget *d) {
84 iWidget *w = as_Widget(d); 97 iWidget *w = as_Widget(d);
85 if (flags_Widget(w) & selected_WidgetFlag) { 98 if (flags_Widget(w) & selected_WidgetFlag) {
@@ -96,6 +109,8 @@ void begin_InputWidget(iInputWidget *d) {
96 } 109 }
97 SDL_StartTextInput(); 110 SDL_StartTextInput();
98 setFlags_Widget(w, selected_WidgetFlag, iTrue); 111 setFlags_Widget(w, selected_WidgetFlag, iTrue);
112 refresh_Widget(w);
113 d->timer = SDL_AddTimer(REFRESH_INTERVAL, refreshTimer_, d);
99} 114}
100 115
101void end_InputWidget(iInputWidget *d, iBool accept) { 116void end_InputWidget(iInputWidget *d, iBool accept) {
@@ -107,19 +122,23 @@ void end_InputWidget(iInputWidget *d, iBool accept) {
107 if (!accept) { 122 if (!accept) {
108 setCopy_Array(&d->text, &d->oldText); 123 setCopy_Array(&d->text, &d->oldText);
109 } 124 }
125 SDL_RemoveTimer(d->timer);
126 d->timer = 0;
110 SDL_StopTextInput(); 127 SDL_StopTextInput();
111 setFlags_Widget(w, selected_WidgetFlag, iFalse); 128 setFlags_Widget(w, selected_WidgetFlag, iFalse);
112 const char *id = cstr_String(id_Widget(as_Widget(d))); 129 const char *id = cstr_String(id_Widget(as_Widget(d)));
113 if (!*id) id = "_"; 130 if (!*id) id = "_";
131 refresh_Widget(w);
114 postCommand_Widget(w, "input.ended id:%s arg:%d", id, accept ? 1 : 0); 132 postCommand_Widget(w, "input.ended id:%s arg:%d", id, accept ? 1 : 0);
115} 133}
116 134
117static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { 135static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
118 if (isCommand_Widget(as_Widget(d), ev, "focus.gained")) { 136 iWidget *w = as_Widget(d);
137 if (isCommand_Widget(w, ev, "focus.gained")) {
119 begin_InputWidget(d); 138 begin_InputWidget(d);
120 return iTrue; 139 return iTrue;
121 } 140 }
122 else if (isCommand_Widget(as_Widget(d), ev, "focus.lost")) { 141 else if (isCommand_Widget(w, ev, "focus.lost")) {
123 end_InputWidget(d, iTrue); 142 end_InputWidget(d, iTrue);
124 return iTrue; 143 return iTrue;
125 } 144 }
@@ -139,7 +158,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
139 return iTrue; 158 return iTrue;
140 } 159 }
141 const size_t curMax = iMin(size_Array(&d->text), d->maxLen - 1); 160 const size_t curMax = iMin(size_Array(&d->text), d->maxLen - 1);
142 if (ev->type == SDL_KEYDOWN && isFocused_Widget(as_Widget(d))) { 161 if (ev->type == SDL_KEYDOWN && isFocused_Widget(w)) {
143 const int key = ev->key.keysym.sym; 162 const int key = ev->key.keysym.sym;
144 const int mods = keyMods_Sym(ev->key.keysym.mod); 163 const int mods = keyMods_Sym(ev->key.keysym.mod);
145 switch (key) { 164 switch (key) {
@@ -159,28 +178,33 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
159 else if (d->cursor > 0) { 178 else if (d->cursor > 0) {
160 remove_Array(&d->text, --d->cursor); 179 remove_Array(&d->text, --d->cursor);
161 } 180 }
181 refresh_Widget(w);
162 return iTrue; 182 return iTrue;
163 case SDLK_d: 183 case SDLK_d:
164 if (mods != KMOD_CTRL) break; 184 if (mods != KMOD_CTRL) break;
165 case SDLK_DELETE: 185 case SDLK_DELETE:
166 if (d->cursor < size_Array(&d->text)) { 186 if (d->cursor < size_Array(&d->text)) {
167 remove_Array(&d->text, d->cursor); 187 remove_Array(&d->text, d->cursor);
188 refresh_Widget(w);
168 } 189 }
169 return iTrue; 190 return iTrue;
170 case SDLK_k: 191 case SDLK_k:
171 if (mods == KMOD_CTRL) { 192 if (mods == KMOD_CTRL) {
172 removeN_Array(&d->text, d->cursor, size_Array(&d->text) - d->cursor); 193 removeN_Array(&d->text, d->cursor, size_Array(&d->text) - d->cursor);
194 refresh_Widget(w);
173 return iTrue; 195 return iTrue;
174 } 196 }
175 break; 197 break;
176 case SDLK_HOME: 198 case SDLK_HOME:
177 case SDLK_END: 199 case SDLK_END:
178 d->cursor = (key == SDLK_HOME ? 0 : curMax); 200 d->cursor = (key == SDLK_HOME ? 0 : curMax);
201 refresh_Widget(w);
179 return iTrue; 202 return iTrue;
180 case SDLK_a: 203 case SDLK_a:
181 case SDLK_e: 204 case SDLK_e:
182 if (mods == KMOD_CTRL) { 205 if (mods == KMOD_CTRL) {
183 d->cursor = (key == 'a' ? 0 : curMax); 206 d->cursor = (key == 'a' ? 0 : curMax);
207 refresh_Widget(w);
184 return iTrue; 208 return iTrue;
185 } 209 }
186 break; 210 break;
@@ -191,6 +215,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
191 else if (d->cursor > 0) { 215 else if (d->cursor > 0) {
192 d->cursor--; 216 d->cursor--;
193 } 217 }
218 refresh_Widget(w);
194 return iTrue; 219 return iTrue;
195 case SDLK_RIGHT: 220 case SDLK_RIGHT:
196 if (mods & KMOD_PRIMARY) { 221 if (mods & KMOD_PRIMARY) {
@@ -199,6 +224,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
199 else if (d->cursor < curMax) { 224 else if (d->cursor < curMax) {
200 d->cursor++; 225 d->cursor++;
201 } 226 }
227 refresh_Widget(w);
202 return iTrue; 228 return iTrue;
203 case SDLK_TAB: 229 case SDLK_TAB:
204 /* Allow focus switching. */ 230 /* Allow focus switching. */
@@ -209,7 +235,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
209 } 235 }
210 return iTrue; 236 return iTrue;
211 } 237 }
212 else if (ev->type == SDL_TEXTINPUT && isFocused_Widget(as_Widget(d))) { 238 else if (ev->type == SDL_TEXTINPUT && isFocused_Widget(w)) {
213 const iString *uni = collectNewCStr_String(ev->text.text); 239 const iString *uni = collectNewCStr_String(ev->text.text);
214 const iChar chr = first_String(uni); 240 const iChar chr = first_String(uni);
215 if (d->mode == insert_InputMode) { 241 if (d->mode == insert_InputMode) {
@@ -225,9 +251,10 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
225 setFocus_Widget(NULL); 251 setFocus_Widget(NULL);
226 } 252 }
227 } 253 }
254 refresh_Widget(w);
228 return iTrue; 255 return iTrue;
229 } 256 }
230 return processEvent_Widget(as_Widget(d), ev); 257 return processEvent_Widget(w, ev);
231} 258}
232 259
233static void draw_InputWidget_(const iInputWidget *d) { 260static void draw_InputWidget_(const iInputWidget *d) {
diff --git a/src/ui/labelwidget.c b/src/ui/labelwidget.c
index 7b64857b..341c2f0e 100644
--- a/src/ui/labelwidget.c
+++ b/src/ui/labelwidget.c
@@ -30,19 +30,23 @@ static void trigger_LabelWidget_(const iLabelWidget *d) {
30} 30}
31 31
32static iBool processEvent_LabelWidget_(iLabelWidget *d, const SDL_Event *ev) { 32static iBool processEvent_LabelWidget_(iLabelWidget *d, const SDL_Event *ev) {
33 iWidget *w = &d->widget;
33 if (isCommand_UserEvent(ev, "metrics.changed")) { 34 if (isCommand_UserEvent(ev, "metrics.changed")) {
34 updateSize_LabelWidget(d); 35 updateSize_LabelWidget(d);
35 } 36 }
36 if (!isEmpty_String(&d->command)) { 37 if (!isEmpty_String(&d->command)) {
37 switch (processEvent_Click(&d->click, ev)) { 38 switch (processEvent_Click(&d->click, ev)) {
38 case started_ClickResult: 39 case started_ClickResult:
39 setFlags_Widget(&d->widget, pressed_WidgetFlag, iTrue); 40 setFlags_Widget(w, pressed_WidgetFlag, iTrue);
41 refresh_Widget(w);
40 return iTrue; 42 return iTrue;
41 case aborted_ClickResult: 43 case aborted_ClickResult:
42 setFlags_Widget(&d->widget, pressed_WidgetFlag, iFalse); 44 setFlags_Widget(w, pressed_WidgetFlag, iFalse);
45 refresh_Widget(w);
43 return iTrue; 46 return iTrue;
44 case finished_ClickResult: 47 case finished_ClickResult:
45 setFlags_Widget(&d->widget, pressed_WidgetFlag, iFalse); 48 setFlags_Widget(w, pressed_WidgetFlag, iFalse);
49 refresh_Widget(w);
46 trigger_LabelWidget_(d); 50 trigger_LabelWidget_(d);
47 return iTrue; 51 return iTrue;
48 case double_ClickResult: 52 case double_ClickResult:
diff --git a/src/ui/window.c b/src/ui/window.c
index 241cd3b0..43233010 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -49,6 +49,10 @@ static iBool handleRootCommands_(iWidget *root, const char *cmd) {
49 } 49 }
50 return iTrue; 50 return iTrue;
51 } 51 }
52 else if (equal_Command(cmd, "setfocus")) {
53 setFocus_Widget(findWidget_App(cstr_String(string_Command(cmd, "id"))));
54 return iTrue;
55 }
52 else if (handleCommand_App(cmd)) { 56 else if (handleCommand_App(cmd)) {
53 return iTrue; 57 return iTrue;
54 } 58 }
@@ -70,6 +74,23 @@ static const iMenuItem editMenuItems[] = {
70static const iMenuItem viewMenuItems[] = { 74static const iMenuItem viewMenuItems[] = {
71}; 75};
72 76
77static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) {
78 if (equal_Command(cmd, "input.ended")) {
79 iInputWidget *url = findChild_Widget(navBar, "url");
80 if (arg_Command(cmd) && pointer_Command(cmd) == url) {
81 postCommandf_App("open url:%s", cstr_String(text_InputWidget(url)));
82 return iTrue;
83 }
84 }
85 else if (equal_Command(cmd, "document.changed")) {
86 iInputWidget *url = findWidget_App("url");
87 setTextCStr_InputWidget(url, valuePtr_Command(cmd, "url"));
88 setTitle_Window(get_Window(), text_InputWidget(url));
89 return iTrue;
90 }
91 return iFalse;
92}
93
73static void setupUserInterface_Window(iWindow *d) { 94static void setupUserInterface_Window(iWindow *d) {
74 /* Children of root cover the entire window. */ 95 /* Children of root cover the entire window. */
75 setFlags_Widget(d->root, resizeChildren_WidgetFlag, iTrue); 96 setFlags_Widget(d->root, resizeChildren_WidgetFlag, iTrue);
@@ -87,12 +108,14 @@ static void setupUserInterface_Window(iWindow *d) {
87 arrangeHorizontal_WidgetFlag, 108 arrangeHorizontal_WidgetFlag,
88 iTrue); 109 iTrue);
89 addChild_Widget(div, iClob(navBar)); 110 addChild_Widget(div, iClob(navBar));
90 setBackgroundColor_Widget(div, gray25_ColorId); 111 setCommandHandler_Widget(navBar, handleNavBarCommands_);
112 setBackgroundColor_Widget(navBar, gray25_ColorId);
91 113
92 addChild_Widget(navBar, iClob(new_LabelWidget("Back", 0, 0, "navigate.back"))); 114 addChild_Widget(navBar, iClob(new_LabelWidget("Back", 0, 0, "navigate.back")));
93 addChild_Widget(navBar, iClob(new_LabelWidget("Fwd", 0, 0, "navigate.forward"))); 115 addChild_Widget(navBar, iClob(new_LabelWidget("Fwd", 0, 0, "navigate.forward")));
94 addChild_Widget(navBar, iClob(new_LabelWidget("Home", 0, 0, "navigate.home"))); 116 addChild_Widget(navBar, iClob(new_LabelWidget("Home", 0, 0, "navigate.home")));
95 iInputWidget *url = new_InputWidget(0); 117 iInputWidget *url = new_InputWidget(0);
118 setId_Widget(as_Widget(url), "url");
96 setTextCStr_InputWidget(url, "gemini://"); 119 setTextCStr_InputWidget(url, "gemini://");
97 addChildFlags_Widget(navBar, iClob(url), expand_WidgetFlag); 120 addChildFlags_Widget(navBar, iClob(url), expand_WidgetFlag);
98 } 121 }
@@ -250,6 +273,7 @@ static void setupUserInterface_Window(iWindow *d) {
250#endif 273#endif
251 /* Glboal keyboard shortcuts. */ { 274 /* Glboal keyboard shortcuts. */ {
252 // addAction_Widget(d->root, SDLK_LEFTBRACKET, KMOD_SHIFT | KMOD_PRIMARY, "tabs.prev"); 275 // addAction_Widget(d->root, SDLK_LEFTBRACKET, KMOD_SHIFT | KMOD_PRIMARY, "tabs.prev");
276 addAction_Widget(d->root, 'l', KMOD_PRIMARY, "setfocus id:url");
253 } 277 }
254} 278}
255 279
@@ -381,8 +405,13 @@ iBool processEvent_Window(iWindow *d, const SDL_Event *ev) {
381 widget = mouseGrab_Widget(); 405 widget = mouseGrab_Widget();
382 } 406 }
383 } 407 }
384 /* TODO: Auto-refresh when hover widget changes. */ 408 iWidget *oldHover = hover_Widget();
385 return dispatchEvent_Widget(widget, &event); 409 /* Dispatch the event to the tree of widgets. */
410 iBool wasUsed = dispatchEvent_Widget(widget, &event);
411 if (oldHover != hover_Widget()) {
412 postRefresh_App();
413 }
414 return wasUsed;
386 } 415 }
387 } 416 }
388 return iFalse; 417 return iFalse;
@@ -432,6 +461,10 @@ void resize_Window(iWindow *d, int w, int h) {
432 updateRootSize_Window_(d); 461 updateRootSize_Window_(d);
433} 462}
434 463
464void setTitle_Window(iWindow *d, const iString *title) {
465 SDL_SetWindowTitle(d->win, cstr_String(title));
466}
467
435void setUiScale_Window(iWindow *d, float uiScale) { 468void setUiScale_Window(iWindow *d, float uiScale) {
436 uiScale = iClamp(uiScale, 0.5f, 4.0f); 469 uiScale = iClamp(uiScale, 0.5f, 4.0f);
437 if (d) { 470 if (d) {
diff --git a/src/ui/window.h b/src/ui/window.h
index d0413af4..70e52751 100644
--- a/src/ui/window.h
+++ b/src/ui/window.h
@@ -23,6 +23,7 @@ struct Impl_Window {
23iBool processEvent_Window (iWindow *, const SDL_Event *); 23iBool processEvent_Window (iWindow *, const SDL_Event *);
24void draw_Window (iWindow *); 24void draw_Window (iWindow *);
25void resize_Window (iWindow *, int w, int h); 25void resize_Window (iWindow *, int w, int h);
26void setTitle_Window (iWindow *, const iString *title);
26void setUiScale_Window (iWindow *, float uiScale); 27void setUiScale_Window (iWindow *, float uiScale);
27 28
28iInt2 rootSize_Window (const iWindow *); 29iInt2 rootSize_Window (const iWindow *);