diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-02-12 20:52:30 +0200 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-02-12 20:52:30 +0200 |
commit | 51a74dc9c66b76dff795a9da3874602f3f0f0285 (patch) | |
tree | f7e5aed92bca8017027cf42bd38fdd298254b2e0 /src | |
parent | 73c8166d5c8b397c166ee9ece0173f2b278b6ddb (diff) |
Windows: Custom frame behavior
A borderless SDL window gets no standard window behavior, so
this commit implements support for some of them. There is
special handling for the window frame and various snapping modes.
Not quite finished yet... It might make sense to research if
a custom window class could work with this app (with SDL); could be
less work.
Diffstat (limited to 'src')
-rw-r--r-- | src/app.c | 25 | ||||
-rw-r--r-- | src/ui/window.c | 221 | ||||
-rw-r--r-- | src/ui/window.h | 34 | ||||
-rw-r--r-- | src/win32.c | 53 | ||||
-rw-r--r-- | src/win32.h | 6 |
5 files changed, 283 insertions, 56 deletions
@@ -160,12 +160,12 @@ static iString *serializePrefs_App_(const iApp *d) { | |||
160 | const iSidebarWidget *sidebar2 = findWidget_App("sidebar2"); | 160 | const iSidebarWidget *sidebar2 = findWidget_App("sidebar2"); |
161 | appendFormat_String(str, "window.retain arg:%d\n", d->prefs.retainWindowSize); | 161 | appendFormat_String(str, "window.retain arg:%d\n", d->prefs.retainWindowSize); |
162 | if (d->prefs.retainWindowSize) { | 162 | if (d->prefs.retainWindowSize) { |
163 | const iBool isMaximized = (SDL_GetWindowFlags(d->window->win) & SDL_WINDOW_MAXIMIZED) != 0; | 163 | const iBool isMaximized = snap_Window(d->window) == maximized_WindowSnap; |
164 | int w, h, x, y; | 164 | int w, h, x, y; |
165 | x = d->window->lastRect.pos.x; | 165 | x = d->window->place.normalRect.pos.x; |
166 | y = d->window->lastRect.pos.y; | 166 | y = d->window->place.normalRect.pos.y; |
167 | w = d->window->lastRect.size.x; | 167 | w = d->window->place.normalRect.size.x; |
168 | h = d->window->lastRect.size.y; | 168 | h = d->window->place.normalRect.size.y; |
169 | appendFormat_String(str, "window.setrect width:%d height:%d coord:%d %d\n", w, h, x, y); | 169 | appendFormat_String(str, "window.setrect width:%d height:%d coord:%d %d\n", w, h, x, y); |
170 | appendFormat_String(str, "sidebar.width arg:%d\n", width_SidebarWidget(sidebar)); | 170 | appendFormat_String(str, "sidebar.width arg:%d\n", width_SidebarWidget(sidebar)); |
171 | appendFormat_String(str, "sidebar2.width arg:%d\n", width_SidebarWidget(sidebar2)); | 171 | appendFormat_String(str, "sidebar2.width arg:%d\n", width_SidebarWidget(sidebar2)); |
@@ -1113,22 +1113,17 @@ iBool handleCommand_App(const char *cmd) { | |||
1113 | } | 1113 | } |
1114 | else if (equal_Command(cmd, "window.maximize")) { | 1114 | else if (equal_Command(cmd, "window.maximize")) { |
1115 | if (!argLabel_Command(cmd, "toggle")) { | 1115 | if (!argLabel_Command(cmd, "toggle")) { |
1116 | SDL_MaximizeWindow(d->window->win); | 1116 | setSnap_Window(d->window, maximized_WindowSnap); |
1117 | } | 1117 | } |
1118 | else { | 1118 | else { |
1119 | if (SDL_GetWindowFlags(d->window->win) & SDL_WINDOW_MAXIMIZED) { | 1119 | setSnap_Window(d->window, snap_Window(d->window) == maximized_WindowSnap ? 0 : |
1120 | SDL_RestoreWindow(d->window->win); | 1120 | maximized_WindowSnap); |
1121 | } | ||
1122 | else { | ||
1123 | SDL_MaximizeWindow(d->window->win); | ||
1124 | } | ||
1125 | } | 1121 | } |
1126 | return iTrue; | 1122 | return iTrue; |
1127 | } | 1123 | } |
1128 | else if (equal_Command(cmd, "window.fullscreen")) { | 1124 | else if (equal_Command(cmd, "window.fullscreen")) { |
1129 | const iBool wasFull = | 1125 | const iBool wasFull = snap_Window(d->window) == fullscreen_WindowSnap; |
1130 | (SDL_GetWindowFlags(d->window->win) & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0; | 1126 | setSnap_Window(d->window, wasFull ? 0 : fullscreen_WindowSnap); |
1131 | SDL_SetWindowFullscreen(d->window->win, wasFull ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP); | ||
1132 | postCommandf_App("window.fullscreen.changed arg:%d", !wasFull); | 1127 | postCommandf_App("window.fullscreen.changed arg:%d", !wasFull); |
1133 | return iTrue; | 1128 | return iTrue; |
1134 | } | 1129 | } |
diff --git a/src/ui/window.c b/src/ui/window.c index 6356b292..37586aab 100644 --- a/src/ui/window.c +++ b/src/ui/window.c | |||
@@ -93,15 +93,6 @@ static iBool handleRootCommands_(iWidget *root, const char *cmd) { | |||
93 | setTextColor_LabelWidget(findWidget_App("winbar.title"), uiTextStrong_ColorId); | 93 | setTextColor_LabelWidget(findWidget_App("winbar.title"), uiTextStrong_ColorId); |
94 | return iFalse; | 94 | return iFalse; |
95 | } | 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")) { | 96 | else if (equal_Command(cmd, "window.minimize")) { |
106 | SDL_MinimizeWindow(get_Window()->win); | 97 | SDL_MinimizeWindow(get_Window()->win); |
107 | return iTrue; | 98 | return iTrue; |
@@ -562,7 +553,7 @@ static void setupUserInterface_Window(iWindow *d) { | |||
562 | setFont_LabelWidget(appButton, uiContentBold_FontId); | 553 | setFont_LabelWidget(appButton, uiContentBold_FontId); |
563 | setId_Widget(addChildFlags_Widget(winBar, | 554 | setId_Widget(addChildFlags_Widget(winBar, |
564 | iClob(appTitle = new_LabelWidget("", NULL)), | 555 | iClob(appTitle = new_LabelWidget("", NULL)), |
565 | expand_WidgetFlag | alignLeft_WidgetFlag | | 556 | expand_WidgetFlag | /*alignLeft_WidgetFlag |*/ |
566 | fixedHeight_WidgetFlag | frameless_WidgetFlag | | 557 | fixedHeight_WidgetFlag | frameless_WidgetFlag | |
567 | commandOnClick_WidgetFlag), | 558 | commandOnClick_WidgetFlag), |
568 | "winbar.title"); | 559 | "winbar.title"); |
@@ -570,7 +561,7 @@ static void setupUserInterface_Window(iWindow *d) { | |||
570 | iLabelWidget *appMin, *appMax, *appClose; | 561 | iLabelWidget *appMin, *appMax, *appClose; |
571 | setId_Widget(addChildFlags_Widget( | 562 | setId_Widget(addChildFlags_Widget( |
572 | winBar, | 563 | winBar, |
573 | iClob(appMin = newLargeIcon_LabelWidget("\u2014", "window.minimize")), | 564 | iClob(appMin = newLargeIcon_LabelWidget("\u2013", "window.minimize")), |
574 | frameless_WidgetFlag), | 565 | frameless_WidgetFlag), |
575 | "winbar.min"); | 566 | "winbar.min"); |
576 | setSize_Widget(as_Widget(appMin), | 567 | setSize_Widget(as_Widget(appMin), |
@@ -582,6 +573,7 @@ static void setupUserInterface_Window(iWindow *d) { | |||
582 | addChildFlags_Widget(winBar, | 573 | addChildFlags_Widget(winBar, |
583 | iClob(appClose = newLargeIcon_LabelWidget("\u2a2f", "window.close")), | 574 | iClob(appClose = newLargeIcon_LabelWidget("\u2a2f", "window.close")), |
584 | frameless_WidgetFlag); | 575 | frameless_WidgetFlag); |
576 | setFont_LabelWidget(appClose, uiContent_FontId); | ||
585 | setSize_Widget(as_Widget(appMax), as_Widget(appMin)->rect.size); | 577 | setSize_Widget(as_Widget(appMax), as_Widget(appMin)->rect.size); |
586 | setSize_Widget(as_Widget(appClose), as_Widget(appMin)->rect.size); | 578 | setSize_Widget(as_Widget(appClose), as_Widget(appMin)->rect.size); |
587 | addChild_Widget(div, iClob(winBar)); | 579 | addChild_Widget(div, iClob(winBar)); |
@@ -744,8 +736,8 @@ static void updateRootSize_Window_(iWindow *d, iBool notifyAlways) { | |||
744 | const iInt2 oldSize = *size; | 736 | const iInt2 oldSize = *size; |
745 | SDL_GetRendererOutputSize(d->render, &size->x, &size->y); | 737 | SDL_GetRendererOutputSize(d->render, &size->x, &size->y); |
746 | if (notifyAlways || !isEqual_I2(oldSize, *size)) { | 738 | if (notifyAlways || !isEqual_I2(oldSize, *size)) { |
747 | const iBool isHoriz = (d->lastNotifiedSize.x != size->x); | 739 | const iBool isHoriz = (d->place.lastNotifiedSize.x != size->x); |
748 | const iBool isVert = (d->lastNotifiedSize.y != size->y); | 740 | const iBool isVert = (d->place.lastNotifiedSize.y != size->y); |
749 | arrange_Widget(d->root); | 741 | arrange_Widget(d->root); |
750 | postCommandf_App("window.resized width:%d height:%d horiz:%d vert:%d", | 742 | postCommandf_App("window.resized width:%d height:%d horiz:%d vert:%d", |
751 | size->x, | 743 | size->x, |
@@ -753,7 +745,7 @@ static void updateRootSize_Window_(iWindow *d, iBool notifyAlways) { | |||
753 | isHoriz, | 745 | isHoriz, |
754 | isVert); | 746 | isVert); |
755 | postRefresh_App(); | 747 | postRefresh_App(); |
756 | d->lastNotifiedSize = *size; | 748 | d->place.lastNotifiedSize = *size; |
757 | } | 749 | } |
758 | } | 750 | } |
759 | 751 | ||
@@ -793,6 +785,9 @@ static void drawBlank_Window_(iWindow *d) { | |||
793 | static SDL_HitTestResult hitTest_Window_(SDL_Window *win, const SDL_Point *pos, void *data) { | 785 | static SDL_HitTestResult hitTest_Window_(SDL_Window *win, const SDL_Point *pos, void *data) { |
794 | iWindow *d = data; | 786 | iWindow *d = data; |
795 | iAssert(d->win == win); | 787 | iAssert(d->win == win); |
788 | if (SDL_GetWindowFlags(d->win) & SDL_WINDOW_MOUSE_CAPTURE) { | ||
789 | return SDL_HITTEST_NORMAL; | ||
790 | } | ||
796 | int w, h; | 791 | int w, h; |
797 | SDL_GetWindowSize(win, &w, &h); | 792 | SDL_GetWindowSize(win, &w, &h); |
798 | /* TODO: Check if inside the caption label widget. */ | 793 | /* TODO: Check if inside the caption label widget. */ |
@@ -802,15 +797,16 @@ static SDL_HitTestResult hitTest_Window_(SDL_Window *win, const SDL_Point *pos, | |||
802 | const iBool isBottom = pos->y >= h - gap_UI; | 797 | const iBool isBottom = pos->y >= h - gap_UI; |
803 | const int captionHeight = lineHeight_Text(uiContent_FontId) + gap_UI * 2; | 798 | const int captionHeight = lineHeight_Text(uiContent_FontId) + gap_UI * 2; |
804 | const int rightEdge = left_Rect(bounds_Widget(findChild_Widget(d->root, "winbar.min"))); | 799 | const int rightEdge = left_Rect(bounds_Widget(findChild_Widget(d->root, "winbar.min"))); |
800 | d->place.lastHit = SDL_HITTEST_NORMAL; | ||
805 | if (isLeft) { | 801 | if (isLeft) { |
806 | return pos->y < captionHeight ? SDL_HITTEST_RESIZE_TOPLEFT | 802 | return pos->y < captionHeight ? SDL_HITTEST_RESIZE_TOPLEFT |
807 | : pos->y > h - captionHeight ? SDL_HITTEST_RESIZE_BOTTOMLEFT | 803 | : pos->y > h - captionHeight ? SDL_HITTEST_RESIZE_BOTTOMLEFT |
808 | : SDL_HITTEST_RESIZE_LEFT; | 804 | : (d->place.lastHit = SDL_HITTEST_RESIZE_LEFT); |
809 | } | 805 | } |
810 | if (isRight) { | 806 | if (isRight) { |
811 | return pos->y < captionHeight ? SDL_HITTEST_RESIZE_TOPRIGHT | 807 | return pos->y < captionHeight ? SDL_HITTEST_RESIZE_TOPRIGHT |
812 | : pos->y > h - captionHeight ? SDL_HITTEST_RESIZE_BOTTOMRIGHT | 808 | : pos->y > h - captionHeight ? SDL_HITTEST_RESIZE_BOTTOMRIGHT |
813 | : SDL_HITTEST_RESIZE_RIGHT; | 809 | : (d->place.lastHit = SDL_HITTEST_RESIZE_RIGHT); |
814 | } | 810 | } |
815 | if (isTop) { | 811 | if (isTop) { |
816 | return pos->x < captionHeight ? SDL_HITTEST_RESIZE_TOPLEFT | 812 | return pos->x < captionHeight ? SDL_HITTEST_RESIZE_TOPLEFT |
@@ -827,6 +823,10 @@ static SDL_HitTestResult hitTest_Window_(SDL_Window *win, const SDL_Point *pos, | |||
827 | } | 823 | } |
828 | return SDL_HITTEST_NORMAL; | 824 | return SDL_HITTEST_NORMAL; |
829 | } | 825 | } |
826 | |||
827 | SDL_HitTestResult hitTest_Window(const iWindow *d, iInt2 pos) { | ||
828 | return hitTest_Window_(d->win, &(SDL_Point){ pos.x, pos.y }, iConstCast(void *, d)); | ||
829 | } | ||
830 | #endif | 830 | #endif |
831 | 831 | ||
832 | iBool create_Window_(iWindow *d, iRect rect, uint32_t flags) { | 832 | iBool create_Window_(iWindow *d, iRect rect, uint32_t flags) { |
@@ -841,7 +841,7 @@ iBool create_Window_(iWindow *d, iRect rect, uint32_t flags) { | |||
841 | } | 841 | } |
842 | #if defined (LAGRANGE_CUSTOM_FRAME) | 842 | #if defined (LAGRANGE_CUSTOM_FRAME) |
843 | /* Register a handler for window hit testing (drag, resize). */ | 843 | /* Register a handler for window hit testing (drag, resize). */ |
844 | SDL_SetWindowHitTest(d->win, hitTest_Window_, d); | 844 | SDL_SetWindowHitTest(d->win, hitTest_Window_, d); |
845 | SDL_SetWindowResizable(d->win, SDL_TRUE); | 845 | SDL_SetWindowResizable(d->win, SDL_TRUE); |
846 | #endif | 846 | #endif |
847 | return iTrue; | 847 | return iTrue; |
@@ -850,12 +850,13 @@ iBool create_Window_(iWindow *d, iRect rect, uint32_t flags) { | |||
850 | void init_Window(iWindow *d, iRect rect) { | 850 | void init_Window(iWindow *d, iRect rect) { |
851 | theWindow_ = d; | 851 | theWindow_ = d; |
852 | iZap(d->cursors); | 852 | iZap(d->cursors); |
853 | d->initialPos = rect.pos; | 853 | d->place.initialPos = rect.pos; |
854 | d->lastRect = rect; | 854 | d->place.normalRect = rect; |
855 | d->lastNotifiedSize = zero_I2(); | 855 | d->place.lastNotifiedSize = zero_I2(); |
856 | d->pendingCursor = NULL; | 856 | d->pendingCursor = NULL; |
857 | d->isDrawFrozen = iTrue; | 857 | d->isDrawFrozen = iTrue; |
858 | d->isExposed = iFalse; | 858 | d->isExposed = iFalse; |
859 | d->isMinimized = iFalse; | ||
859 | d->isMouseInside = iTrue; | 860 | d->isMouseInside = iTrue; |
860 | d->focusGainedAt = 0; | 861 | d->focusGainedAt = 0; |
861 | uint32_t flags = 0; | 862 | uint32_t flags = 0; |
@@ -936,6 +937,9 @@ void init_Window(iWindow *d, iRect rect) { | |||
936 | setupUserInterface_Window(d); | 937 | setupUserInterface_Window(d); |
937 | postCommand_App("bindings.changed"); /* update from bindings */ | 938 | postCommand_App("bindings.changed"); /* update from bindings */ |
938 | updateRootSize_Window_(d, iFalse); | 939 | updateRootSize_Window_(d, iFalse); |
940 | #if defined (LAGRANGE_CUSTOM_FRAME) | ||
941 | SDL_EventState(SDL_SYSWMEVENT, SDL_TRUE); | ||
942 | #endif | ||
939 | } | 943 | } |
940 | 944 | ||
941 | void deinit_Window(iWindow *d) { | 945 | void deinit_Window(iWindow *d) { |
@@ -958,17 +962,8 @@ SDL_Renderer *renderer_Window(const iWindow *d) { | |||
958 | return d->render; | 962 | return d->render; |
959 | } | 963 | } |
960 | 964 | ||
961 | static iBool isMaximized_Window_(const iWindow *d) { | ||
962 | #if !defined (iPlatformApple) | ||
963 | return (SDL_GetWindowFlags(d->win) & (SDL_WINDOW_MINIMIZED | SDL_WINDOW_MAXIMIZED)) != 0; | ||
964 | #else | ||
965 | iUnused(d); | ||
966 | return iFalse; /* There is fullscreen mode but that is not handled at the moment. */ | ||
967 | #endif | ||
968 | } | ||
969 | |||
970 | iBool isFullscreen_Window(const iWindow *d) { | 965 | iBool isFullscreen_Window(const iWindow *d) { |
971 | return (SDL_GetWindowFlags(d->win) & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0; | 966 | return snap_Window(d) == fullscreen_WindowSnap; |
972 | } | 967 | } |
973 | 968 | ||
974 | static void invalidate_Window_(iWindow *d) { | 969 | static void invalidate_Window_(iWindow *d) { |
@@ -976,6 +971,31 @@ static void invalidate_Window_(iWindow *d) { | |||
976 | postCommand_App("theme.changed"); /* forces UI invalidation */ | 971 | postCommand_App("theme.changed"); /* forces UI invalidation */ |
977 | } | 972 | } |
978 | 973 | ||
974 | static iBool isNormalPlacement_Window_(const iWindow *d) { | ||
975 | if (snap_Window(d) || d->isDrawFrozen) return iFalse; | ||
976 | return !(SDL_GetWindowFlags(d->win) & SDL_WINDOW_MINIMIZED); | ||
977 | } | ||
978 | |||
979 | static iBool unsnap_Window_(iWindow *d, const iInt2 *newPos) { | ||
980 | #if defined (LAGRANGE_CUSTOM_FRAME) | ||
981 | const int snap = snap_Window(d); | ||
982 | if (snap == yMaximized_WindowSnap && | ||
983 | (!newPos || (d->place.lastHit == SDL_HITTEST_RESIZE_LEFT || | ||
984 | d->place.lastHit == SDL_HITTEST_RESIZE_RIGHT))) { | ||
985 | return iFalse; | ||
986 | } | ||
987 | if (snap && snap != fullscreen_WindowSnap) { | ||
988 | if (snap_Window(d) == yMaximized_WindowSnap && newPos) { | ||
989 | d->place.normalRect.pos = *newPos; | ||
990 | } | ||
991 | printf("unsnap\n"); fflush(stdout); | ||
992 | setSnap_Window(d, none_WindowSnap); | ||
993 | return iTrue; | ||
994 | } | ||
995 | #endif | ||
996 | return iFalse; | ||
997 | } | ||
998 | |||
979 | static iBool handleWindowEvent_Window_(iWindow *d, const SDL_WindowEvent *ev) { | 999 | static iBool handleWindowEvent_Window_(iWindow *d, const SDL_WindowEvent *ev) { |
980 | switch (ev->event) { | 1000 | switch (ev->event) { |
981 | case SDL_WINDOWEVENT_EXPOSED: | 1001 | case SDL_WINDOWEVENT_EXPOSED: |
@@ -989,33 +1009,62 @@ static iBool handleWindowEvent_Window_(iWindow *d, const SDL_WindowEvent *ev) { | |||
989 | is missing contents until other events trigger a refresh. */ | 1009 | is missing contents until other events trigger a refresh. */ |
990 | postRefresh_App(); | 1010 | postRefresh_App(); |
991 | #if defined (LAGRANGE_ENABLE_WINDOWPOS_FIX) | 1011 | #if defined (LAGRANGE_ENABLE_WINDOWPOS_FIX) |
992 | if (d->initialPos.x >= 0) { | 1012 | if (d->place.initialPos.x >= 0) { |
993 | int bx, by; | 1013 | int bx, by; |
994 | SDL_GetWindowBordersSize(d->win, &by, &bx, NULL, NULL); | 1014 | SDL_GetWindowBordersSize(d->win, &by, &bx, NULL, NULL); |
995 | SDL_SetWindowPosition(d->win, d->initialPos.x + bx, d->initialPos.y + by); | 1015 | SDL_SetWindowPosition(d->win, d->place.initialPos.x + bx, d->place.initialPos.y + by); |
996 | d->initialPos = init1_I2(-1); | 1016 | d->place.initialPos = init1_I2(-1); |
997 | } | 1017 | } |
998 | #endif | 1018 | #endif |
999 | return iFalse; | 1019 | return iFalse; |
1000 | case SDL_WINDOWEVENT_MOVED: { | 1020 | case SDL_WINDOWEVENT_MOVED: { |
1001 | if (!isMaximized_Window_(d) && !isFullscreen_Window(d) && !d->isDrawFrozen) { | 1021 | if (d->isMinimized) { |
1002 | d->lastRect.pos = init_I2(ev->data1, ev->data2); | 1022 | return iFalse; |
1023 | } | ||
1024 | const iInt2 newPos = init_I2(ev->data1, ev->data2); | ||
1025 | if (isEqual_I2(newPos, init1_I2(-32000))) { /* magic! */ | ||
1026 | /* Maybe minimized? Seems like a Windows constant of some kind. */ | ||
1027 | d->isMinimized = iTrue; | ||
1028 | return iFalse; | ||
1029 | } | ||
1030 | //printf("MOVED: %d, %d\n", ev->data1, ev->data2); fflush(stdout); | ||
1031 | if (unsnap_Window_(d, &newPos)) { | ||
1032 | return iTrue; | ||
1033 | } | ||
1034 | if (isNormalPlacement_Window_(d)) { | ||
1035 | d->place.normalRect.pos = newPos; | ||
1036 | //printf("normal rect set (move)\n"); fflush(stdout); | ||
1003 | iInt2 border = zero_I2(); | 1037 | iInt2 border = zero_I2(); |
1004 | #if !defined (iPlatformApple) | 1038 | #if !defined (iPlatformApple) |
1005 | SDL_GetWindowBordersSize(d->win, &border.y, &border.x, NULL, NULL); | 1039 | SDL_GetWindowBordersSize(d->win, &border.y, &border.x, NULL, NULL); |
1006 | #endif | 1040 | #endif |
1007 | d->lastRect.pos = max_I2(zero_I2(), sub_I2(d->lastRect.pos, border)); | 1041 | d->place.normalRect.pos = max_I2(zero_I2(), sub_I2(d->place.normalRect.pos, border)); |
1008 | } | 1042 | } |
1009 | return iTrue; | 1043 | return iTrue; |
1010 | } | 1044 | } |
1011 | case SDL_WINDOWEVENT_RESIZED: | 1045 | case SDL_WINDOWEVENT_RESIZED: |
1012 | if (!isMaximized_Window_(d) && !isFullscreen_Window(d) && !d->isDrawFrozen) { | 1046 | if (d->isMinimized) { |
1013 | d->lastRect.size = init_I2(ev->data1, ev->data2); | 1047 | updateRootSize_Window_(d, iTrue); |
1048 | return iTrue; | ||
1049 | } | ||
1050 | if (unsnap_Window_(d, NULL)) { | ||
1051 | return iTrue; | ||
1052 | } | ||
1053 | if (isNormalPlacement_Window_(d)) { | ||
1054 | d->place.normalRect.size = init_I2(ev->data1, ev->data2); | ||
1055 | //printf("normal rect set (resize)\n"); fflush(stdout); | ||
1014 | } | 1056 | } |
1015 | updateRootSize_Window_(d, iTrue /* we were already redrawing during the resize */); | 1057 | updateRootSize_Window_(d, iTrue /* we were already redrawing during the resize */); |
1016 | return iTrue; | 1058 | return iTrue; |
1017 | case SDL_WINDOWEVENT_RESTORED: | 1059 | case SDL_WINDOWEVENT_RESTORED: |
1060 | //updateRootSize_Window_(d, iTrue); | ||
1018 | invalidate_Window_(d); | 1061 | invalidate_Window_(d); |
1062 | d->isMinimized = iFalse; | ||
1063 | //printf("restored %d\n", snap_Window(d)); fflush(stdout); | ||
1064 | return iTrue; | ||
1065 | case SDL_WINDOWEVENT_MINIMIZED: | ||
1066 | d->isMinimized = iTrue; | ||
1067 | //printf("minimized\n"); fflush(stdout); | ||
1019 | return iTrue; | 1068 | return iTrue; |
1020 | case SDL_WINDOWEVENT_LEAVE: | 1069 | case SDL_WINDOWEVENT_LEAVE: |
1021 | unhover_Widget(); | 1070 | unhover_Widget(); |
@@ -1051,6 +1100,16 @@ static void applyCursor_Window_(iWindow *d) { | |||
1051 | 1100 | ||
1052 | iBool processEvent_Window(iWindow *d, const SDL_Event *ev) { | 1101 | iBool processEvent_Window(iWindow *d, const SDL_Event *ev) { |
1053 | switch (ev->type) { | 1102 | switch (ev->type) { |
1103 | #if defined (LAGRANGE_CUSTOM_FRAME) | ||
1104 | case SDL_SYSWMEVENT: { | ||
1105 | /* We observe native Win32 messages for better user interaction with the | ||
1106 | window frame. Mouse clicks especially will not generate normal SDL | ||
1107 | events if they happen on the custom hit-tested regions. These events | ||
1108 | are processed only there; the UI widgets do not get involved. */ | ||
1109 | processNativeEvent_Win32(ev->syswm.msg, d); | ||
1110 | break; | ||
1111 | } | ||
1112 | #endif | ||
1054 | case SDL_WINDOWEVENT: { | 1113 | case SDL_WINDOWEVENT: { |
1055 | return handleWindowEvent_Window_(d, &ev->window); | 1114 | return handleWindowEvent_Window_(d, &ev->window); |
1056 | } | 1115 | } |
@@ -1232,3 +1291,89 @@ uint32_t frameTime_Window(const iWindow *d) { | |||
1232 | iWindow *get_Window(void) { | 1291 | iWindow *get_Window(void) { |
1233 | return theWindow_; | 1292 | return theWindow_; |
1234 | } | 1293 | } |
1294 | |||
1295 | #if !defined (LAGRANGE_CUSTOM_FRAME) | ||
1296 | void setSnap_Window(iWindow *d, int snapMode) { | ||
1297 | if (snapMode == maximized_WindowSnap) { | ||
1298 | SDL_MaximizeWindow(d->win); | ||
1299 | } | ||
1300 | else if (snapMode == fullscreen_WindowSnap) { | ||
1301 | SDL_SetWindowFullscreen(d->win, SDL_WINDOW_FULLSCREEN_DESKTOP); | ||
1302 | } | ||
1303 | else { | ||
1304 | SDL_RestoreWindow(d->win); | ||
1305 | } | ||
1306 | } | ||
1307 | |||
1308 | int snap_Window(const iWindow *d) { | ||
1309 | const int flags = SDL_GetWindowFlags(d->win); | ||
1310 | if (flags & SDL_WINDOW_FULLSCREEN_DESKTOP) { | ||
1311 | return fullscreen_WindowSnap; | ||
1312 | } | ||
1313 | else if (flags & SDL_WINDOW_MAXIMIZED) { | ||
1314 | return maximized_WindowSnap; | ||
1315 | } | ||
1316 | return none_WindowSnap; | ||
1317 | } | ||
1318 | #endif | ||
1319 | |||
1320 | #if defined (LAGRANGE_CUSTOM_FRAME) | ||
1321 | void setSnap_Window(iWindow *d, int snapMode) { | ||
1322 | if (d->place.snap == snapMode) { | ||
1323 | return; | ||
1324 | } | ||
1325 | iRect newRect = zero_Rect(); | ||
1326 | SDL_Rect usable; | ||
1327 | SDL_GetDisplayUsableBounds(SDL_GetWindowDisplayIndex(d->win), &usable); | ||
1328 | if (d->place.snap == fullscreen_WindowSnap) { | ||
1329 | SDL_SetWindowFullscreen(d->win, 0); | ||
1330 | } | ||
1331 | d->place.snap = snapMode; | ||
1332 | switch (snapMode & mask_WindowSnap) { | ||
1333 | case none_WindowSnap: | ||
1334 | newRect = d->place.normalRect; | ||
1335 | break; | ||
1336 | case left_WindowSnap: | ||
1337 | newRect = init_Rect(usable.x, usable.y, usable.w / 2, usable.h); | ||
1338 | break; | ||
1339 | case right_WindowSnap: | ||
1340 | newRect = init_Rect(usable.x, usable.y + usable.w / 2, usable.w / 2, usable.h); | ||
1341 | break; | ||
1342 | case maximized_WindowSnap: | ||
1343 | newRect = init_Rect(usable.x, usable.y, usable.w, usable.h); | ||
1344 | break; | ||
1345 | case yMaximized_WindowSnap: | ||
1346 | newRect.pos.y = 0; | ||
1347 | newRect.size.y = usable.h; | ||
1348 | SDL_GetWindowPosition(d->win, &newRect.pos.x, NULL); | ||
1349 | SDL_GetWindowSize(d->win, &newRect.size.x, NULL); | ||
1350 | break; | ||
1351 | case fullscreen_WindowSnap: | ||
1352 | SDL_SetWindowFullscreen(d->win, SDL_WINDOW_FULLSCREEN_DESKTOP); | ||
1353 | break; | ||
1354 | } | ||
1355 | if (snapMode & (topBit_WindowSnap | bottomBit_WindowSnap)) { | ||
1356 | newRect.size.y /= 2; | ||
1357 | } | ||
1358 | if (snapMode & bottomBit_WindowSnap) { | ||
1359 | newRect.pos.y += newRect.size.y; | ||
1360 | } | ||
1361 | /* Show and hide the title bar. */ | ||
1362 | iWidget *winBar = findWidget_App("winbar"); | ||
1363 | const iBool wasVisible = isVisible_Widget(winBar); | ||
1364 | setFlags_Widget(winBar, hidden_WidgetFlag, snapMode == fullscreen_WindowSnap); | ||
1365 | if (newRect.size.x) { | ||
1366 | SDL_SetWindowPosition(d->win, newRect.pos.x, newRect.pos.y); | ||
1367 | SDL_SetWindowSize(d->win, newRect.size.x, newRect.size.y); | ||
1368 | postCommand_App("window.resized"); | ||
1369 | } | ||
1370 | if (wasVisible != isVisible_Widget(winBar)) { | ||
1371 | arrange_Widget(d->root); | ||
1372 | postRefresh_App(); | ||
1373 | } | ||
1374 | } | ||
1375 | |||
1376 | int snap_Window(const iWindow *d) { | ||
1377 | return d->place.snap; | ||
1378 | } | ||
1379 | #endif | ||
diff --git a/src/ui/window.h b/src/ui/window.h index 5bde9065..ddb03847 100644 --- a/src/ui/window.h +++ b/src/ui/window.h | |||
@@ -32,13 +32,35 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
32 | iDeclareType(Window) | 32 | iDeclareType(Window) |
33 | iDeclareTypeConstructionArgs(Window, iRect rect) | 33 | iDeclareTypeConstructionArgs(Window, iRect rect) |
34 | 34 | ||
35 | enum iWindowSnap { | ||
36 | none_WindowSnap = 0, | ||
37 | left_WindowSnap = 1, | ||
38 | right_WindowSnap = 2, | ||
39 | maximized_WindowSnap = 3, | ||
40 | yMaximized_WindowSnap = 4, | ||
41 | fullscreen_WindowSnap = 5, | ||
42 | mask_WindowSnap = 0xff, | ||
43 | topBit_WindowSnap = iBit(9), | ||
44 | bottomBit_WindowSnap = iBit(10), | ||
45 | }; | ||
46 | |||
47 | iDeclareType(WindowPlacement) | ||
48 | |||
49 | /* Tracking of window placement. */ | ||
50 | struct Impl_WindowPlacement { | ||
51 | iInt2 initialPos; | ||
52 | iRect normalRect; /* updated when window is moved/resized */ | ||
53 | iInt2 lastNotifiedSize; /* keep track of horizontal/vertical notifications */ | ||
54 | int snap; /* LAGRANGE_CUSTOM_FRAME */ | ||
55 | int lastHit; | ||
56 | }; | ||
57 | |||
35 | struct Impl_Window { | 58 | struct Impl_Window { |
36 | SDL_Window * win; | 59 | SDL_Window * win; |
37 | iInt2 initialPos; | 60 | iWindowPlacement place; |
38 | iRect lastRect; /* updated when window is moved/resized */ | ||
39 | iInt2 lastNotifiedSize; /* keep track of horizontal/vertical notifications */ | ||
40 | iBool isDrawFrozen; /* avoids premature draws while restoring window state */ | 61 | iBool isDrawFrozen; /* avoids premature draws while restoring window state */ |
41 | iBool isExposed; | 62 | iBool isExposed; |
63 | iBool isMinimized; | ||
42 | iBool isMouseInside; | 64 | iBool isMouseInside; |
43 | uint32_t focusGainedAt; | 65 | uint32_t focusGainedAt; |
44 | SDL_Renderer *render; | 66 | SDL_Renderer *render; |
@@ -60,6 +82,7 @@ void setTitle_Window (iWindow *, const iString *title); | |||
60 | void setUiScale_Window (iWindow *, float uiScale); | 82 | void setUiScale_Window (iWindow *, float uiScale); |
61 | void setFreezeDraw_Window (iWindow *, iBool freezeDraw); | 83 | void setFreezeDraw_Window (iWindow *, iBool freezeDraw); |
62 | void setCursor_Window (iWindow *, int cursor); | 84 | void setCursor_Window (iWindow *, int cursor); |
85 | void setSnap_Window (iWindow *, int snapMode); | ||
63 | 86 | ||
64 | uint32_t id_Window (const iWindow *); | 87 | uint32_t id_Window (const iWindow *); |
65 | iInt2 rootSize_Window (const iWindow *); | 88 | iInt2 rootSize_Window (const iWindow *); |
@@ -68,6 +91,11 @@ iInt2 coord_Window (const iWindow *, int x, int y); | |||
68 | iInt2 mouseCoord_Window (const iWindow *); | 91 | iInt2 mouseCoord_Window (const iWindow *); |
69 | uint32_t frameTime_Window (const iWindow *); | 92 | uint32_t frameTime_Window (const iWindow *); |
70 | SDL_Renderer *renderer_Window (const iWindow *); | 93 | SDL_Renderer *renderer_Window (const iWindow *); |
94 | int snap_Window (const iWindow *); | ||
71 | iBool isFullscreen_Window (const iWindow *); | 95 | iBool isFullscreen_Window (const iWindow *); |
72 | 96 | ||
73 | iWindow * get_Window (void); | 97 | iWindow * get_Window (void); |
98 | |||
99 | #if defined (LAGRANGE_CUSTOM_FRAME) | ||
100 | SDL_HitTestResult hitTest_Window(const iWindow *d, iInt2 pos); | ||
101 | #endif | ||
diff --git a/src/win32.c b/src/win32.c index 5eb2a0d5..585fdaaf 100644 --- a/src/win32.c +++ b/src/win32.c | |||
@@ -21,10 +21,13 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |||
21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | 21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ |
22 | 22 | ||
23 | #include "win32.h" | 23 | #include "win32.h" |
24 | #include "ui/window.h" | ||
25 | #include "app.h" | ||
24 | #include <SDL_syswm.h> | 26 | #include <SDL_syswm.h> |
25 | 27 | ||
26 | #define WIN32_LEAN_AND_MEAN | 28 | #define WIN32_LEAN_AND_MEAN |
27 | #include <Windows.h> | 29 | #include <Windows.h> |
30 | #include <windowsx.h> | ||
28 | #include <d2d1.h> | 31 | #include <d2d1.h> |
29 | 32 | ||
30 | void setDPIAware_Win32(void) { | 33 | void setDPIAware_Win32(void) { |
@@ -59,3 +62,53 @@ void useExecutableIconResource_SDLWindow(SDL_Window *win) { | |||
59 | } | 62 | } |
60 | } | 63 | } |
61 | } | 64 | } |
65 | |||
66 | #if 0 | ||
67 | void setup_SDLWindow(SDL_Window *win) { | ||
68 | SDL_SysWMinfo wmInfo; | ||
69 | SDL_VERSION(&wmInfo.version); | ||
70 | if (SDL_GetWindowWMInfo(win, &wmInfo)) { | ||
71 | HWND hwnd = wmInfo.info.win.window; | ||
72 | // printf("configuring window\n"); | ||
73 | // SetWindowLong(hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) | WS_SYSMENU); | ||
74 | } | ||
75 | } | ||
76 | #endif | ||
77 | |||
78 | #if defined (LAGRANGE_CUSTOM_FRAME) | ||
79 | void processNativeEvent_Win32(const struct SDL_SysWMmsg *msg, iWindow *window) { | ||
80 | HWND hwnd = msg->msg.win.hwnd; | ||
81 | // printf("[syswm] %x\n", msg->msg.win.msg); fflush(stdout); | ||
82 | switch (msg->msg.win.msg) { | ||
83 | case WM_NCLBUTTONDBLCLK: { | ||
84 | POINT point = { GET_X_LPARAM(msg->msg.win.lParam), GET_Y_LPARAM(msg->msg.win.lParam) }; | ||
85 | ScreenToClient(hwnd, &point); | ||
86 | iInt2 pos = init_I2(point.x, point.y); | ||
87 | switch (hitTest_Window(window, pos)) { | ||
88 | case SDL_HITTEST_DRAGGABLE: | ||
89 | postCommand_App("window.maximize toggle:1"); | ||
90 | break; | ||
91 | case SDL_HITTEST_RESIZE_TOP: | ||
92 | case SDL_HITTEST_RESIZE_BOTTOM: { | ||
93 | setSnap_Window(window, yMaximized_WindowSnap); | ||
94 | break; | ||
95 | } | ||
96 | } | ||
97 | //fflush(stdout); | ||
98 | break; | ||
99 | } | ||
100 | #if 0 | ||
101 | /* SDL does not use WS_SYSMENU on the window, so we can't display the system menu. | ||
102 | However, the only useful function in the menu would be moving-via-keyboard, | ||
103 | but that doesn't work with a custom frame. We could show a custom system menu? */ | ||
104 | case WM_NCRBUTTONUP: { | ||
105 | POINT point = { GET_X_LPARAM(msg->msg.win.lParam), GET_Y_LPARAM(msg->msg.win.lParam) }; | ||
106 | HMENU menu = GetSystemMenu(hwnd, FALSE); | ||
107 | printf("menu at %d,%d menu:%p\n", point.x, point.y, menu); fflush(stdout); | ||
108 | TrackPopupMenu(menu, TPM_RIGHTBUTTON, point.x, point.y, 0, hwnd, NULL); | ||
109 | break; | ||
110 | } | ||
111 | #endif | ||
112 | } | ||
113 | } | ||
114 | #endif /* defined (LAGRANGE_CUSTOM_FRAME) */ | ||
diff --git a/src/win32.h b/src/win32.h index b5bc0b45..b988c302 100644 --- a/src/win32.h +++ b/src/win32.h | |||
@@ -21,8 +21,14 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |||
21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | 21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ |
22 | 22 | ||
23 | #pragma once | 23 | #pragma once |
24 | |||
25 | #include <the_Foundation/defs.h> | ||
24 | #include <SDL_video.h> | 26 | #include <SDL_video.h> |
27 | #include <SDL_syswm.h> | ||
28 | |||
29 | iDeclareType(Window) | ||
25 | 30 | ||
26 | void setDPIAware_Win32(void); | 31 | void setDPIAware_Win32(void); |
27 | float desktopDPI_Win32(void); | 32 | float desktopDPI_Win32(void); |
28 | void useExecutableIconResource_SDLWindow(SDL_Window *win); | 33 | void useExecutableIconResource_SDLWindow(SDL_Window *win); |
34 | void processNativeEvent_Win32(const struct SDL_SysWMmsg *msg, iWindow *window); | ||