diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-02-11 21:53:31 +0200 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-02-11 21:53:31 +0200 |
commit | 73c8166d5c8b397c166ee9ece0173f2b278b6ddb (patch) | |
tree | c365e8d387a0d1a02e0de69209d3f80164443157 /src/ui/window.c | |
parent | bfdda501cb01df95c968c9f5378bb91cd66eea87 (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/ui/window.c')
-rw-r--r-- | src/ui/window.c | 142 |
1 files changed, 138 insertions, 4 deletions
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 | ||
530 | static 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 | |||
506 | static void setupUserInterface_Window(iWindow *d) { | 536 | static 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) | ||
793 | static 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 | |||
715 | iBool create_Window_(iWindow *d, iRect rect, uint32_t flags) { | 832 | iBool 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 | ||
1031 | void setTitle_Window(iWindow *d, const iString *title) { | 1161 | void 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 | ||
1035 | void setUiScale_Window(iWindow *d, float uiScale) { | 1169 | void setUiScale_Window(iWindow *d, float uiScale) { |