summaryrefslogtreecommitdiff
path: root/src/ui/window.c
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2020-07-21 15:06:52 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2020-07-21 15:07:38 +0300
commitd773b499e595a43b9b1ae449262dcf13cabf2d02 (patch)
treeb1baeb12025a04f8316636b5d0ab18e30ceedb2c /src/ui/window.c
Initial commit
Borrowing the app skeleton from Bitwise Harmony.
Diffstat (limited to 'src/ui/window.c')
-rw-r--r--src/ui/window.c441
1 files changed, 441 insertions, 0 deletions
diff --git a/src/ui/window.c b/src/ui/window.c
new file mode 100644
index 00000000..8b4226ef
--- /dev/null
+++ b/src/ui/window.c
@@ -0,0 +1,441 @@
1#include "window.h"
2
3#include "app.h"
4#include "command.h"
5#include "paint.h"
6#include "text.h"
7#include "util.h"
8#include "labelwidget.h"
9#include "inputwidget.h"
10#include "embedded.h"
11#if defined (iPlatformMsys)
12# include "../win32.h"
13#endif
14#if defined (iPlatformApple) && !defined (iPlatformIOS)
15# include "macos.h"
16#endif
17
18#include <the_Foundation/file.h>
19#include <the_Foundation/path.h>
20#include <SDL_hints.h>
21#include <SDL_timer.h>
22#include <SDL_syswm.h>
23
24#define STB_IMAGE_IMPLEMENTATION
25#include "stb_image.h"
26
27static iWindow *theWindow_ = NULL;
28
29#if defined (iPlatformApple)
30static float initialUiScale_ = 1.0f;
31#else
32static float initialUiScale_ = 1.1f;
33#endif
34
35iDefineTypeConstruction(Window)
36
37static iBool handleRootCommands_(iWidget *root, const char *cmd) {
38 iUnused(root);
39 if (equal_Command(cmd, "menu.open")) {
40 iWidget *button = pointer_Command(cmd);
41 iWidget *menu = findChild_Widget(button, "menu");
42 iAssert(menu);
43 if (!isVisible_Widget(menu)) {
44 openMenu_Widget(menu, init_I2(0, button->rect.size.y));
45 }
46 else {
47 closeMenu_Widget(menu);
48 }
49 return iTrue;
50 }
51 else if (handleCommand_App(cmd)) {
52 return iTrue;
53 }
54 return iFalse;
55}
56
57static const iMenuItem fileMenuItems[] = {
58#if !defined (iPlatformApple)
59 { "Quit Lagrange", 'q', KMOD_PRIMARY, "quit" }
60#endif
61};
62
63static const iMenuItem editMenuItems[] = {
64#if !defined (iPlatformApple)
65 { "Preferences...", SDLK_COMMA, KMOD_PRIMARY, "preferences" }
66#endif
67};
68
69static const iMenuItem viewMenuItems[] = {
70};
71
72static void setupUserInterface_Window(iWindow *d) {
73 /* Children of root cover the entire window. */
74 setFlags_Widget(d->root, resizeChildren_WidgetFlag, iTrue);
75 setCommandHandler_Widget(d->root, handleRootCommands_);
76#if 0
77 iWidget *mainDiv = makeHDiv_Widget();
78 setId_Widget(mainDiv, "maindiv");
79 addChild_Widget(d->root, iClob(mainDiv));
80
81 iWidget *sidebar = makeVDiv_Widget();
82 setFlags_Widget(sidebar, arrangeWidth_WidgetFlag, iTrue);
83 setId_Widget(sidebar, "sidebar");
84 addChild_Widget(mainDiv, iClob(sidebar));
85
86 /* Menus. */ {
87#if defined (iPlatformApple) && !defined (iPlatformIOS)
88 /* Use the native menus. */
89 insertMenuItems_MacOS("File", fileMenuItems, iElemCount(fileMenuItems));
90 insertMenuItems_MacOS("Edit", editMenuItems, iElemCount(editMenuItems));
91 insertMenuItems_MacOS("View", viewMenuItems, iElemCount(viewMenuItems));
92#else
93 iWidget *menubar = new_Widget();
94 setBackgroundColor_Widget(menubar, gray25_ColorId);
95 setFlags_Widget(menubar, arrangeHorizontal_WidgetFlag | arrangeHeight_WidgetFlag, iTrue);
96 addChild_Widget(menubar, iClob(makeMenuButton_LabelWidget("File", fileMenuItems, iElemCount(fileMenuItems))));
97 addChild_Widget(menubar, iClob(makeMenuButton_LabelWidget("Edit", editMenuItems, iElemCount(editMenuItems))));
98 addChild_Widget(menubar, iClob(makeMenuButton_LabelWidget("View", viewMenuItems, iElemCount(viewMenuItems))));
99 addChild_Widget(sidebar, iClob(menubar));
100#endif
101 }
102 /* Tracker info. */ {
103 iWidget *trackerInfo = addChild_Widget(sidebar, iClob(new_Widget()));
104 setId_Widget(trackerInfo, "trackerinfo");
105 trackerInfo->rect.size.y = lineHeight_Text(default_FontId) + 2 * gap_UI;
106 setFlags_Widget(trackerInfo, arrangeHorizontal_WidgetFlag | resizeChildren_WidgetFlag, iTrue);
107 setId_Widget(
108 addChild_Widget(trackerInfo, iClob(new_LabelWidget("", 'p', KMOD_PRIMARY, "pattern.goto arg:-1"))),
109 "trackerinfo.current");
110 iLabelWidget *dims = new_LabelWidget("", 'r', KMOD_PRIMARY | KMOD_ALT, "pattern.resize");
111 setId_Widget(addChild_Widget(trackerInfo, iClob(dims)), "trackerinfo.dims");
112 }
113
114 iLibraryWidget *lib = new_LibraryWidget();
115 setId_Widget(as_Widget(lib), "library");
116 addChildFlags_Widget(sidebar, iClob(lib), expand_WidgetFlag);
117
118 iPlaybackWidget *play = new_PlaybackWidget();
119 setId_Widget(as_Widget(play), "playback");
120 addChild_Widget(sidebar, iClob(play));
121
122 iWidget *mainTabs = makeTabs_Widget(mainDiv);
123 setId_Widget(mainTabs, "maintabs");
124 setFlags_Widget(mainTabs, expand_WidgetFlag, iTrue);
125
126 /* Optional sidebar on the right. */
127 iWidget *sidebar2 = new_Widget();
128 setId_Widget(addChild_Widget(mainDiv, iClob(sidebar2)), "sidebar2");
129 setFlags_Widget(
130 sidebar2, fixedWidth_WidgetFlag | frameless_WidgetFlag | resizeChildren_WidgetFlag, iTrue);
131
132 /* Pattern sequence. */ {
133 iSequenceWidget *seq = new_SequenceWidget();
134 appendTabPage_Widget(mainTabs, iClob(seq), "SEQUENCE", 0, 0);
135 }
136 /* Tracker. */ {
137 iTrackerWidget *tracker = new_TrackerWidget();
138 appendTabPage_Widget(mainTabs, as_Widget(tracker), "PATTERN", 0, 0);
139 }
140 /* Voice editor. */ {
141 iWidget *voice = as_Widget(new_VoiceWidget());
142 setId_Widget(voice, "voicelayers");
143 appendTabPage_Widget(mainTabs, iClob(voice), "VOICE", '3', KMOD_PRIMARY);
144 }
145 /* Song information. */ {
146 iWidget *songPage = new_Widget();
147 setId_Widget(songPage, "songinfo");
148 setFlags_Widget(songPage, arrangeHorizontal_WidgetFlag, iTrue);
149 iWidget *headings =
150 addChildFlags_Widget(songPage,
151 iClob(new_Widget()),
152 resizeToParentHeight_WidgetFlag | resizeChildren_WidgetFlag |
153 arrangeVertical_WidgetFlag | arrangeWidth_WidgetFlag);
154 iWidget *values = addChildFlags_Widget(
155 songPage, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag);
156
157 setId_Widget(addChild_Widget(headings, iClob(makePadding_Widget(2 * gap_UI))), "headings.padding");
158 setId_Widget(addChild_Widget(values, iClob(makePadding_Widget(2 * gap_UI))), "values.padding");
159
160 addChild_Widget(headings, iClob(makeHeading_Widget(cyan_ColorEscape "SONG PROPERTIES")));
161 addChild_Widget(values, iClob(makeHeading_Widget("")));
162
163 const int fieldWidth = advance_Text(monospace_FontId, "A").x * 40;
164 iWidget *field;
165
166 addChild_Widget(headings, iClob(makeHeading_Widget("Title:")));
167 setId_Widget(field = addChild_Widget(values, iClob(new_InputWidget(0))), "info.title");
168 field->rect.size.x = fieldWidth;
169
170 addChild_Widget(headings, iClob(makeHeading_Widget("Author:")));
171 setId_Widget(field = addChild_Widget(values, iClob(new_InputWidget(0))), "info.author");
172 field->rect.size.x = fieldWidth;
173
174 addChild_Widget(headings, iClob(makeHeading_Widget("Tempo:")));
175 setId_Widget(addChild_Widget(values, iClob(new_InputWidget(3))), "info.tempo");
176
177 addChild_Widget(headings, iClob(makeHeading_Widget("Events per Beat:")));
178 setId_Widget(addChild_Widget(values, iClob(new_InputWidget(2))), "info.eventsperbeat");
179
180 addChild_Widget(headings, iClob(makeHeading_Widget("Num of Tracks:")));
181 setId_Widget(addChild_Widget(values, iClob(new_InputWidget(2))), "info.numtracks");
182
183 addChild_Widget(headings, iClob(makePadding_Widget(2 * gap_UI)));
184 addChild_Widget(values, iClob(makePadding_Widget(2 * gap_UI)));
185
186 addChild_Widget(headings, iClob(makeHeading_Widget(cyan_ColorEscape "SONG METADATA")));
187 addChild_Widget(values, iClob(makeHeading_Widget("")));
188
189 addChild_Widget(headings, iClob(makeHeading_Widget("Duration:")));
190 setId_Widget(addChildFlags_Widget(values, iClob(newEmpty_LabelWidget()),
191 alignLeft_WidgetFlag | frameless_WidgetFlag),
192 "info.duration");
193 addChild_Widget(headings, iClob(makeHeading_Widget("Statistics:\n\n ")));
194 setId_Widget(addChildFlags_Widget(values,
195 iClob(newEmpty_LabelWidget()),
196 alignLeft_WidgetFlag | frameless_WidgetFlag),
197 "info.statistics");
198 addChild_Widget(headings, iClob(makeHeading_Widget("Created on:")));
199 setId_Widget(addChildFlags_Widget(values,
200 iClob(newEmpty_LabelWidget()),
201 alignLeft_WidgetFlag | frameless_WidgetFlag),
202 "info.created");
203
204 addChild_Widget(headings, iClob(makeHeading_Widget("Last Modified on:")));
205 setId_Widget(addChildFlags_Widget(values,
206 iClob(newEmpty_LabelWidget()),
207 alignLeft_WidgetFlag | frameless_WidgetFlag),
208 "info.lastmodified");
209 /* App info in the bottom. */ {
210 addChildFlags_Widget(headings, iClob(new_Widget()), expand_WidgetFlag);
211 addChildFlags_Widget(
212 headings,
213 iClob(new_LabelWidget(gray50_ColorEscape "Version " BWH_APP_VERSION, 0, 0, NULL)),
214 frameless_WidgetFlag | alignLeft_WidgetFlag);
215 }
216 appendTabPage_Widget(mainTabs, iClob(songPage), "INFO", '4', KMOD_PRIMARY);
217 }
218 /* Application status. */ {
219 iWidget *status = addChildFlags_Widget(d->root, iClob(newEmpty_LabelWidget()), 0);
220 setFont_LabelWidget((iLabelWidget *) status, monospace_FontId);
221 setFlags_Widget(status, frameless_WidgetFlag | alignRight_WidgetFlag, iTrue);
222 setId_Widget(status, "status");
223 }
224#endif
225 /* Glboal keyboard shortcuts. */ {
226 // addAction_Widget(d->root, SDLK_LEFTBRACKET, KMOD_SHIFT | KMOD_PRIMARY, "tabs.prev");
227 }
228}
229
230static void updateRootSize_Window_(iWindow *d) {
231 iInt2 *size = &d->root->rect.size;
232 SDL_GetRendererOutputSize(d->render, &size->x, &size->y);
233 arrange_Widget(d->root);
234 postCommandf_App("window.resized width:%d height:%d", size->x, size->y);
235}
236
237static float pixelRatio_Window_(const iWindow *d) {
238 int dx, x;
239 SDL_GetRendererOutputSize(d->render, &dx, NULL);
240 SDL_GetWindowSize(d->win, &x, NULL);
241 return (float) dx / (float) x;
242}
243
244void init_Window(iWindow *d) {
245 theWindow_ = d;
246 uint32_t flags = SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI;
247#if defined (iPlatformApple)
248 SDL_SetHint(SDL_HINT_RENDER_DRIVER, "metal");
249#else
250 flags |= SDL_WINDOW_OPENGL;
251#endif
252 SDL_SetHint(SDL_HINT_RENDER_VSYNC, "1");
253 if (SDL_CreateWindowAndRenderer(800, 500, flags, &d->win, &d->render)) {
254 fprintf(stderr, "Error when creating window: %s\n", SDL_GetError());
255 exit(-2);
256 }
257 SDL_SetWindowMinimumSize(d->win, 640, 480);
258 SDL_SetWindowTitle(d->win, "Lagrange");
259 SDL_ShowWindow(d->win);
260 /* Some info. */ {
261 SDL_RendererInfo info;
262 SDL_GetRendererInfo(d->render, &info);
263 printf("[window] renderer: %s\n", info.name);
264 }
265 d->uiScale = initialUiScale_;
266 d->pixelRatio = pixelRatio_Window_(d);
267 setPixelRatio_Metrics(d->pixelRatio * d->uiScale);
268#if defined (iPlatformMsys)
269 useExecutableIconResource_SDLWindow(d->win);
270#endif
271#if defined (iPlatformLinux)
272 /* Load the window icon. */ {
273 int w, h, num;
274 const iBlock *icon = &imageAppicon64_Embedded;
275 stbi_uc *pixels = stbi_load_from_memory(constData_Block(icon),
276 size_Block(icon),
277 &w,
278 &h,
279 &num,
280 STBI_rgb_alpha);
281 SDL_Surface *surf =
282 SDL_CreateRGBSurfaceWithFormatFrom(pixels, w, h, 32, 4 * w, SDL_PIXELFORMAT_RGBA32);
283 SDL_SetWindowIcon(d->win, surf);
284 SDL_FreeSurface(surf);
285 stbi_image_free(pixels);
286 }
287#endif
288 d->root = new_Widget();
289 d->presentTime = 0.0;
290 setId_Widget(d->root, "root");
291 init_Text(d->render);
292#if defined (iPlatformApple) && !defined (iPlatformIOS)
293 setupApplication_MacOS();
294#endif
295 setupUserInterface_Window(d);
296 updateRootSize_Window_(d);
297}
298
299void deinit_Window(iWindow *d) {
300 if (theWindow_ == d) {
301 theWindow_ = NULL;
302 }
303 iReleasePtr(&d->root);
304 deinit_Text();
305 SDL_DestroyRenderer(d->render);
306 SDL_DestroyWindow(d->win);
307}
308
309SDL_Renderer *renderer_Window(const iWindow *d) {
310 return d->render;
311}
312
313static iBool handleWindowEvent_Window_(iWindow *d, const SDL_WindowEvent *ev) {
314 switch (ev->event) {
315 case SDL_WINDOWEVENT_RESIZED:
316 updateRootSize_Window_(d);
317 return iTrue;
318 case SDL_WINDOWEVENT_LEAVE:
319 unhover_Widget();
320 return iTrue;
321 default:
322 break;
323 }
324 return iFalse;
325}
326
327iBool processEvent_Window(iWindow *d, const SDL_Event *ev) {
328 switch (ev->type) {
329 case SDL_WINDOWEVENT: {
330 return handleWindowEvent_Window_(d, &ev->window);
331 }
332 default: {
333 SDL_Event event = *ev;
334 /* Map mouse pointer coordinate to our coordinate system. */
335 if (event.type == SDL_MOUSEMOTION) {
336 const iInt2 pos = coord_Window(d, event.motion.x, event.motion.y);
337 event.motion.x = pos.x;
338 event.motion.y = pos.y;
339 }
340 else if (event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEBUTTONDOWN) {
341 const iInt2 pos = coord_Window(d, event.button.x, event.button.y);
342 event.button.x = pos.x;
343 event.button.y = pos.y;
344 }
345 iWidget *widget = d->root;
346 if (event.type == SDL_MOUSEMOTION || event.type == SDL_MOUSEWHEEL ||
347 event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEBUTTONDOWN) {
348 if (mouseGrab_Widget()) {
349 widget = mouseGrab_Widget();
350 }
351 }
352 return dispatchEvent_Widget(widget, &event);
353 }
354 }
355 return iFalse;
356}
357
358static void waitPresent_Window_(iWindow *d) {
359 const double ticksPerFrame = 1000.0 / 30.0;
360 uint32_t nowTime = SDL_GetTicks();
361 if (nowTime < d->presentTime) {
362 SDL_Delay((uint32_t) (d->presentTime - nowTime));
363 nowTime = SDL_GetTicks();
364 }
365 /* Now it is the presentation time. */
366 /* Figure out the next time in the future. */
367 if (d->presentTime <= nowTime) {
368 d->presentTime += ticksPerFrame * ((int) ((nowTime - d->presentTime) / ticksPerFrame) + 1);
369 }
370 else {
371 d->presentTime = nowTime;
372 }
373}
374
375void draw_Window(iWindow *d) {
376 /* Clear the window. */
377 SDL_SetRenderDrawColor(d->render, 0, 0, 0, 255);
378 SDL_RenderClear(d->render);
379 /* Draw widgets. */
380 d->frameTime = SDL_GetTicks();
381 draw_Widget(d->root);
382#if 0
383 /* Text cache debugging. */ {
384 SDL_Texture *cache = glyphCache_Text();
385 SDL_Rect rect = { 140, 60, 512, 512 };
386 SDL_SetRenderDrawColor(d->render, 0, 0, 0, 255);
387 SDL_RenderFillRect(d->render, &rect);
388 SDL_RenderCopy(d->render, glyphCache_Text(), NULL, &rect);
389 }
390#endif
391 waitPresent_Window_(d);
392 SDL_RenderPresent(d->render);
393}
394
395void resize_Window(iWindow *d, int w, int h) {
396 SDL_SetWindowSize(d->win, w, h);
397 updateRootSize_Window_(d);
398}
399
400void setUiScale_Window(iWindow *d, float uiScale) {
401 uiScale = iClamp(uiScale, 0.5f, 4.0f);
402 if (d) {
403 d->uiScale = uiScale;
404#if 0
405 deinit_Text();
406 setPixelRatio_Metrics(d->pixelRatio * d->uiScale);
407 init_Text(d->render);
408 postCommand_App("metrics.changed");
409 /* TODO: Dynamic UI metrics change. Widgets need to update themselves. */
410#endif
411 }
412 else {
413 initialUiScale_ = uiScale;
414 }
415}
416
417iInt2 rootSize_Window(const iWindow *d) {
418 return d->root->rect.size;
419}
420
421iInt2 coord_Window(const iWindow *d, int x, int y) {
422 return mulf_I2(init_I2(x, y), d->pixelRatio);
423}
424
425iInt2 mouseCoord_Window(const iWindow *d) {
426 int x, y;
427 SDL_GetMouseState(&x, &y);
428 return coord_Window(d, x, y);
429}
430
431float uiScale_Window(const iWindow *d) {
432 return d->uiScale;
433}
434
435uint32_t frameTime_Window(const iWindow *d) {
436 return d->frameTime;
437}
438
439iWindow *get_Window(void) {
440 return theWindow_;
441}