summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-02-11 21:53:31 +0200
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-02-11 21:53:31 +0200
commit73c8166d5c8b397c166ee9ece0173f2b278b6ddb (patch)
treec365e8d387a0d1a02e0de69209d3f80164443157 /src
parentbfdda501cb01df95c968c9f5378bb91cd66eea87 (diff)
Windows: Experimenting with a custom window frame and title bar
Added the build option ENABLE_CUSTOM_FRAME that causes the window to be created as borderless. Lagrange's own UI widgets are used to draw the title bar elements, including the window buttons. There is plenty of sizing behavior still missing, for instance snapping to fullscreen left/right side, double-clicking the frame edges, and proper maximize mode that doesn't cover the entire screen. The window system menu is also missing, but that can be shown manually when appropriate. A command-line option should also be provided to disable winbar in case the default title bar is required.
Diffstat (limited to 'src')
-rw-r--r--src/app.c20
-rw-r--r--src/ui/documentwidget.c4
-rw-r--r--src/ui/labelwidget.c12
-rw-r--r--src/ui/labelwidget.h1
-rw-r--r--src/ui/widget.c2
-rw-r--r--src/ui/window.c142
6 files changed, 171 insertions, 10 deletions
diff --git a/src/app.c b/src/app.c
index 90ad271d..b50321b2 100644
--- a/src/app.c
+++ b/src/app.c
@@ -1112,14 +1112,24 @@ iBool handleCommand_App(const char *cmd) {
1112 return iTrue; 1112 return iTrue;
1113 } 1113 }
1114 else if (equal_Command(cmd, "window.maximize")) { 1114 else if (equal_Command(cmd, "window.maximize")) {
1115 SDL_MaximizeWindow(d->window->win); 1115 if (!argLabel_Command(cmd, "toggle")) {
1116 SDL_MaximizeWindow(d->window->win);
1117 }
1118 else {
1119 if (SDL_GetWindowFlags(d->window->win) & SDL_WINDOW_MAXIMIZED) {
1120 SDL_RestoreWindow(d->window->win);
1121 }
1122 else {
1123 SDL_MaximizeWindow(d->window->win);
1124 }
1125 }
1116 return iTrue; 1126 return iTrue;
1117 } 1127 }
1118 else if (equal_Command(cmd, "window.fullscreen")) { 1128 else if (equal_Command(cmd, "window.fullscreen")) {
1119 SDL_SetWindowFullscreen(d->window->win, 1129 const iBool wasFull =
1120 SDL_GetWindowFlags(d->window->win) & SDL_WINDOW_FULLSCREEN_DESKTOP 1130 (SDL_GetWindowFlags(d->window->win) & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0;
1121 ? 0 1131 SDL_SetWindowFullscreen(d->window->win, wasFull ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP);
1122 : SDL_WINDOW_FULLSCREEN_DESKTOP); 1132 postCommandf_App("window.fullscreen.changed arg:%d", !wasFull);
1123 return iTrue; 1133 return iTrue;
1124 } 1134 }
1125 else if (equal_Command(cmd, "font.set")) { 1135 else if (equal_Command(cmd, "font.set")) {
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index e35892cf..6ec76c0a 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -633,7 +633,9 @@ static void updateWindowTitle_DocumentWidget_(const iDocumentWidget *d) {
633 iUrl parts; 633 iUrl parts;
634 init_Url(&parts, d->mod.url); 634 init_Url(&parts, d->mod.url);
635 if (equalCase_Rangecc(parts.scheme, "about")) { 635 if (equalCase_Rangecc(parts.scheme, "about")) {
636 pushBackCStr_StringArray(title, "Lagrange"); 636 if (!findWidget_App("winbar")) {
637 pushBackCStr_StringArray(title, "Lagrange");
638 }
637 } 639 }
638 else if (!isEmpty_Range(&parts.host)) { 640 else if (!isEmpty_Range(&parts.host)) {
639 pushBackRange_StringArray(title, parts.host); 641 pushBackRange_StringArray(title, parts.host);
diff --git a/src/ui/labelwidget.c b/src/ui/labelwidget.c
index 57136509..d7e6c020 100644
--- a/src/ui/labelwidget.c
+++ b/src/ui/labelwidget.c
@@ -38,6 +38,7 @@ struct Impl_LabelWidget {
38 int font; 38 int font;
39 int key; 39 int key;
40 int kmods; 40 int kmods;
41 int forceFg;
41 iString command; 42 iString command;
42 iBool alignVisual; /* align according to visible bounds, not typography */ 43 iBool alignVisual; /* align according to visible bounds, not typography */
43 iClick click; 44 iClick click;
@@ -177,6 +178,9 @@ static void getColors_LabelWidget_(const iLabelWidget *d, int *bg, int *fg, int
177 } 178 }
178 *fg = uiTextPressed_ColorId | permanent_ColorId; 179 *fg = uiTextPressed_ColorId | permanent_ColorId;
179 } 180 }
181 if (d->forceFg >= 0) {
182 *fg = d->forceFg;
183 }
180} 184}
181 185
182static void draw_LabelWidget_(const iLabelWidget *d) { 186static void draw_LabelWidget_(const iLabelWidget *d) {
@@ -277,6 +281,7 @@ void updateSize_LabelWidget(iLabelWidget *d) {
277void init_LabelWidget(iLabelWidget *d, const char *label, const char *cmd) { 281void init_LabelWidget(iLabelWidget *d, const char *label, const char *cmd) {
278 init_Widget(&d->widget); 282 init_Widget(&d->widget);
279 d->font = uiLabel_FontId; 283 d->font = uiLabel_FontId;
284 d->forceFg = none_ColorId;
280 initCStr_String(&d->label, label); 285 initCStr_String(&d->label, label);
281 if (cmd) { 286 if (cmd) {
282 initCStr_String(&d->command, cmd); 287 initCStr_String(&d->command, cmd);
@@ -304,6 +309,13 @@ void setFont_LabelWidget(iLabelWidget *d, int fontId) {
304 updateSize_LabelWidget(d); 309 updateSize_LabelWidget(d);
305} 310}
306 311
312void setTextColor_LabelWidget(iLabelWidget *d, int color) {
313 if (d && d->forceFg != color) {
314 d->forceFg = color;
315 refresh_Widget(d);
316 }
317}
318
307void setText_LabelWidget(iLabelWidget *d, const iString *text) { 319void setText_LabelWidget(iLabelWidget *d, const iString *text) {
308 updateText_LabelWidget(d, text); 320 updateText_LabelWidget(d, text);
309 updateSize_LabelWidget(d); 321 updateSize_LabelWidget(d);
diff --git a/src/ui/labelwidget.h b/src/ui/labelwidget.h
index c91793e5..266c3b02 100644
--- a/src/ui/labelwidget.h
+++ b/src/ui/labelwidget.h
@@ -31,6 +31,7 @@ iDeclareObjectConstructionArgs(LabelWidget, const char *label, const char *comma
31 31
32void setAlignVisually_LabelWidget(iLabelWidget *, iBool alignVisual); 32void setAlignVisually_LabelWidget(iLabelWidget *, iBool alignVisual);
33void setFont_LabelWidget (iLabelWidget *, int fontId); 33void setFont_LabelWidget (iLabelWidget *, int fontId);
34void setTextColor_LabelWidget (iLabelWidget *, int color);
34void setText_LabelWidget (iLabelWidget *, const iString *text); /* resizes widget */ 35void setText_LabelWidget (iLabelWidget *, const iString *text); /* resizes widget */
35void setTextCStr_LabelWidget (iLabelWidget *, const char *text); 36void setTextCStr_LabelWidget (iLabelWidget *, const char *text);
36void setCommand_LabelWidget (iLabelWidget *, const iString *command); 37void setCommand_LabelWidget (iLabelWidget *, const iString *command);
diff --git a/src/ui/widget.c b/src/ui/widget.c
index ddb3f092..b60c67e3 100644
--- a/src/ui/widget.c
+++ b/src/ui/widget.c
@@ -149,6 +149,8 @@ void setPos_Widget(iWidget *d, iInt2 pos) {
149} 149}
150 150
151void setSize_Widget(iWidget *d, iInt2 size) { 151void setSize_Widget(iWidget *d, iInt2 size) {
152 if (size.x < 0) size.x = d->rect.size.x;
153 if (size.y < 0) size.y = d->rect.size.y;
152 d->rect.size = size; 154 d->rect.size = size;
153 setFlags_Widget(d, fixedSize_WidgetFlag, iTrue); 155 setFlags_Widget(d, fixedSize_WidgetFlag, iTrue);
154} 156}
diff --git a/src/ui/window.c b/src/ui/window.c
index 2d65a655..6356b292 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -84,8 +84,32 @@ static iBool handleRootCommands_(iWidget *root, const char *cmd) {
84 } 84 }
85 else if (equal_Command(cmd, "window.focus.lost")) { 85 else if (equal_Command(cmd, "window.focus.lost")) {
86 setFocus_Widget(NULL); 86 setFocus_Widget(NULL);
87 setTextColor_LabelWidget(findWidget_App("winbar.app"), uiAnnotation_ColorId);
88 setTextColor_LabelWidget(findWidget_App("winbar.title"), uiAnnotation_ColorId);
87 return iFalse; 89 return iFalse;
88 } 90 }
91 else if (equal_Command(cmd, "window.focus.gained")) {
92 setTextColor_LabelWidget(findWidget_App("winbar.app"), uiTextCaution_ColorId);
93 setTextColor_LabelWidget(findWidget_App("winbar.title"), uiTextStrong_ColorId);
94 return iFalse;
95 }
96 else if (equal_Command(cmd, "window.fullscreen.changed")) {
97 iWidget *winBar = findWidget_App("winbar");
98 if (winBar) {
99 setFlags_Widget(winBar, hidden_WidgetFlag, arg_Command(cmd));
100 arrange_Widget(get_Window()->root);
101 postRefresh_App();
102 }
103 return iFalse;
104 }
105 else if (equal_Command(cmd, "window.minimize")) {
106 SDL_MinimizeWindow(get_Window()->win);
107 return iTrue;
108 }
109 else if (equal_Command(cmd, "window.close")) {
110 SDL_PushEvent(&(SDL_Event){ .type = SDL_QUIT });
111 return iTrue;
112 }
89 else if (handleCommand_App(cmd)) { 113 else if (handleCommand_App(cmd)) {
90 return iTrue; 114 return iTrue;
91 } 115 }
@@ -503,6 +527,12 @@ static iBool handleSearchBarCommands_(iWidget *searchBar, const char *cmd) {
503 return iFalse; 527 return iFalse;
504} 528}
505 529
530static iLabelWidget *newLargeIcon_LabelWidget(const char *text, const char *cmd) {
531 iLabelWidget *lab = newIcon_LabelWidget(text, 0, 0, cmd);
532 setFont_LabelWidget(lab, uiLabelLarge_FontId);
533 return lab;
534}
535
506static void setupUserInterface_Window(iWindow *d) { 536static void setupUserInterface_Window(iWindow *d) {
507 /* Children of root cover the entire window. */ 537 /* Children of root cover the entire window. */
508 setFlags_Widget(d->root, resizeChildren_WidgetFlag, iTrue); 538 setFlags_Widget(d->root, resizeChildren_WidgetFlag, iTrue);
@@ -512,11 +542,58 @@ static void setupUserInterface_Window(iWindow *d) {
512 setId_Widget(div, "navdiv"); 542 setId_Widget(div, "navdiv");
513 addChild_Widget(d->root, iClob(div)); 543 addChild_Widget(d->root, iClob(div));
514 544
545#if defined (LAGRANGE_CUSTOM_FRAME)
546 /* Window title bar. */ {
547 const int border = gap_UI / 4;
548 setPadding1_Widget(div, border); /* draggable edges */
549 iWidget *winBar = new_Widget();
550 setId_Widget(winBar, "winbar");
551 setFlags_Widget(winBar,
552 arrangeHeight_WidgetFlag | resizeChildren_WidgetFlag |
553 arrangeHorizontal_WidgetFlag | collapse_WidgetFlag,
554 iTrue);
555 iLabelWidget *appButton =
556 addChildFlags_Widget(winBar,
557 iClob(new_LabelWidget("Lagrange", NULL)),
558 fixedHeight_WidgetFlag | frameless_WidgetFlag);
559 setTextColor_LabelWidget(appButton, uiTextCaution_ColorId);
560 setId_Widget(as_Widget(appButton), "winbar.app");
561 iLabelWidget *appTitle;
562 setFont_LabelWidget(appButton, uiContentBold_FontId);
563 setId_Widget(addChildFlags_Widget(winBar,
564 iClob(appTitle = new_LabelWidget("", NULL)),
565 expand_WidgetFlag | alignLeft_WidgetFlag |
566 fixedHeight_WidgetFlag | frameless_WidgetFlag |
567 commandOnClick_WidgetFlag),
568 "winbar.title");
569 setTextColor_LabelWidget(appTitle, uiTextStrong_ColorId);
570 iLabelWidget *appMin, *appMax, *appClose;
571 setId_Widget(addChildFlags_Widget(
572 winBar,
573 iClob(appMin = newLargeIcon_LabelWidget("\u2014", "window.minimize")),
574 frameless_WidgetFlag),
575 "winbar.min");
576 setSize_Widget(as_Widget(appMin),
577 init_I2(gap_UI * 11.5f, height_Widget(appTitle)));
578 addChildFlags_Widget(
579 winBar,
580 iClob(appMax = newLargeIcon_LabelWidget("\u25a1", "window.maximize toggle:1")),
581 frameless_WidgetFlag);
582 addChildFlags_Widget(winBar,
583 iClob(appClose = newLargeIcon_LabelWidget("\u2a2f", "window.close")),
584 frameless_WidgetFlag);
585 setSize_Widget(as_Widget(appMax), as_Widget(appMin)->rect.size);
586 setSize_Widget(as_Widget(appClose), as_Widget(appMin)->rect.size);
587 addChild_Widget(div, iClob(winBar));
588 setBackgroundColor_Widget(winBar, uiBackground_ColorId);
589 }
590#endif
591
515 /* Navigation bar. */ { 592 /* Navigation bar. */ {
516 iWidget *navBar = new_Widget(); 593 iWidget *navBar = new_Widget();
517 setId_Widget(navBar, "navbar"); 594 setId_Widget(navBar, "navbar");
518 /*setPadding_Widget(navBar, gap_UI / 2, 0, gap_UI / 2, 0);*/ 595 int topPad = !findChild_Widget(div, "winbar") ? gap_UI / 2 : 0;
519 setPadding_Widget(navBar, gap_UI, gap_UI / 2, gap_UI, gap_UI / 2); 596 setPadding_Widget(navBar, gap_UI, topPad, gap_UI, gap_UI / 2);
520 setFlags_Widget(navBar, 597 setFlags_Widget(navBar,
521 arrangeHeight_WidgetFlag | resizeChildren_WidgetFlag | 598 arrangeHeight_WidgetFlag | resizeChildren_WidgetFlag |
522 arrangeHorizontal_WidgetFlag, 599 arrangeHorizontal_WidgetFlag,
@@ -697,7 +774,7 @@ static float pixelRatio_Window_(const iWindow *d) {
697 SDL_GetDisplayDPI(SDL_GetWindowDisplayIndex(d->win), NULL, NULL, &vdpi); 774 SDL_GetDisplayDPI(SDL_GetWindowDisplayIndex(d->win), NULL, NULL, &vdpi);
698 const float factor = vdpi / 96.0f; 775 const float factor = vdpi / 96.0f;
699 return iMax(1.0f, factor); 776 return iMax(1.0f, factor);
700#else 777#else
701 int dx, x; 778 int dx, x;
702 SDL_GetRendererOutputSize(d->render, &dx, NULL); 779 SDL_GetRendererOutputSize(d->render, &dx, NULL);
703 SDL_GetWindowSize(d->win, &x, NULL); 780 SDL_GetWindowSize(d->win, &x, NULL);
@@ -712,12 +789,61 @@ static void drawBlank_Window_(iWindow *d) {
712 SDL_RenderPresent(d->render); 789 SDL_RenderPresent(d->render);
713} 790}
714 791
792#if defined (LAGRANGE_CUSTOM_FRAME)
793static SDL_HitTestResult hitTest_Window_(SDL_Window *win, const SDL_Point *pos, void *data) {
794 iWindow *d = data;
795 iAssert(d->win == win);
796 int w, h;
797 SDL_GetWindowSize(win, &w, &h);
798 /* TODO: Check if inside the caption label widget. */
799 const iBool isLeft = pos->x < gap_UI;
800 const iBool isRight = pos->x >= w - gap_UI;
801 const iBool isTop = pos->y < gap_UI;
802 const iBool isBottom = pos->y >= h - gap_UI;
803 const int captionHeight = lineHeight_Text(uiContent_FontId) + gap_UI * 2;
804 const int rightEdge = left_Rect(bounds_Widget(findChild_Widget(d->root, "winbar.min")));
805 if (isLeft) {
806 return pos->y < captionHeight ? SDL_HITTEST_RESIZE_TOPLEFT
807 : pos->y > h - captionHeight ? SDL_HITTEST_RESIZE_BOTTOMLEFT
808 : SDL_HITTEST_RESIZE_LEFT;
809 }
810 if (isRight) {
811 return pos->y < captionHeight ? SDL_HITTEST_RESIZE_TOPRIGHT
812 : pos->y > h - captionHeight ? SDL_HITTEST_RESIZE_BOTTOMRIGHT
813 : SDL_HITTEST_RESIZE_RIGHT;
814 }
815 if (isTop) {
816 return pos->x < captionHeight ? SDL_HITTEST_RESIZE_TOPLEFT
817 : pos->x > w - captionHeight ? SDL_HITTEST_RESIZE_TOPRIGHT
818 : SDL_HITTEST_RESIZE_TOP;
819 }
820 if (isBottom) {
821 return pos->x < captionHeight ? SDL_HITTEST_RESIZE_BOTTOMLEFT
822 : pos->x > w - captionHeight ? SDL_HITTEST_RESIZE_BOTTOMRIGHT
823 : SDL_HITTEST_RESIZE_BOTTOM;
824 }
825 if (pos->x < rightEdge && pos->y < captionHeight) {
826 return SDL_HITTEST_DRAGGABLE;
827 }
828 return SDL_HITTEST_NORMAL;
829}
830#endif
831
715iBool create_Window_(iWindow *d, iRect rect, uint32_t flags) { 832iBool create_Window_(iWindow *d, iRect rect, uint32_t flags) {
716 flags |= SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN; 833 flags |= SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN;
834#if defined (LAGRANGE_CUSTOM_FRAME)
835 /* We are drawing a custom frame so hide the default one. */
836 flags |= SDL_WINDOW_BORDERLESS;
837#endif
717 if (SDL_CreateWindowAndRenderer( 838 if (SDL_CreateWindowAndRenderer(
718 width_Rect(rect), height_Rect(rect), flags, &d->win, &d->render)) { 839 width_Rect(rect), height_Rect(rect), flags, &d->win, &d->render)) {
719 return iFalse; 840 return iFalse;
720 } 841 }
842#if defined (LAGRANGE_CUSTOM_FRAME)
843 /* Register a handler for window hit testing (drag, resize). */
844 SDL_SetWindowHitTest(d->win, hitTest_Window_, d);
845 SDL_SetWindowResizable(d->win, SDL_TRUE);
846#endif
721 return iTrue; 847 return iTrue;
722} 848}
723 849
@@ -776,6 +902,9 @@ void init_Window(iWindow *d, iRect rect) {
776 d->pixelRatio = pixelRatio_Window_(d); 902 d->pixelRatio = pixelRatio_Window_(d);
777 setPixelRatio_Metrics(d->pixelRatio * d->uiScale); 903 setPixelRatio_Metrics(d->pixelRatio * d->uiScale);
778#if defined (iPlatformMsys) 904#if defined (iPlatformMsys)
905 SDL_Rect usable;
906 SDL_GetDisplayUsableBounds(0, &usable);
907 SDL_SetWindowMaximumSize(d->win, usable.w, usable.h);
779 SDL_SetWindowMinimumSize(d->win, minSize.x * d->pixelRatio, minSize.y * d->pixelRatio); 908 SDL_SetWindowMinimumSize(d->win, minSize.x * d->pixelRatio, minSize.y * d->pixelRatio);
780 useExecutableIconResource_SDLWindow(d->win); 909 useExecutableIconResource_SDLWindow(d->win);
781#endif 910#endif
@@ -1006,7 +1135,8 @@ void draw_Window(iWindow *d) {
1006// printf("draw %d\n", d->frameTime); fflush(stdout); 1135// printf("draw %d\n", d->frameTime); fflush(stdout);
1007//#endif 1136//#endif
1008 /* Clear the window. */ 1137 /* Clear the window. */
1009 SDL_SetRenderDrawColor(d->render, 0, 0, 0, 255); 1138 const iColor back = get_Color(uiSeparator_ColorId);
1139 SDL_SetRenderDrawColor(d->render, back.r, back.g, back.b, 255);
1010 SDL_RenderClear(d->render); 1140 SDL_RenderClear(d->render);
1011 /* Draw widgets. */ 1141 /* Draw widgets. */
1012 d->frameTime = SDL_GetTicks(); 1142 d->frameTime = SDL_GetTicks();
@@ -1030,6 +1160,10 @@ void resize_Window(iWindow *d, int w, int h) {
1030 1160
1031void setTitle_Window(iWindow *d, const iString *title) { 1161void setTitle_Window(iWindow *d, const iString *title) {
1032 SDL_SetWindowTitle(d->win, cstr_String(title)); 1162 SDL_SetWindowTitle(d->win, cstr_String(title));
1163 iLabelWidget *bar = findChild_Widget(d->root, "winbar.title");
1164 if (bar) {
1165 updateText_LabelWidget(bar, title);
1166 }
1033} 1167}
1034 1168
1035void setUiScale_Window(iWindow *d, float uiScale) { 1169void setUiScale_Window(iWindow *d, float uiScale) {