From 3638407c421877f6ff4ca6a5e79305b5514ed773 Mon Sep 17 00:00:00 2001 From: Allen Webster Date: Fri, 19 Jan 2024 19:36:38 -0800 Subject: [PATCH] update header comment email; sync bug fixes into 'mc' version --- win32_custom_window.c | 1300 +++++++++++++++++++------------------- win32_custom_window_mc.c | 900 +++++++++++++------------- 2 files changed, 1101 insertions(+), 1099 deletions(-) diff --git a/win32_custom_window.c b/win32_custom_window.c index 3c34f41..64de0af 100644 --- a/win32_custom_window.c +++ b/win32_custom_window.c @@ -1,7 +1,7 @@ /* ** Win32 Custom Window Example Program -** v1.3.0 - May 8th 2020 -** by Allen Webster allenwebster@4coder.net +** v1.3.1 - Jan 19th 2024 +** by Allen Webster allenw@mr4th.com ** ** public domain example program ** NO WARRANTY IMPLIED; USE AT YOUR OWN RISK @@ -93,23 +93,23 @@ void ToggleMaximize(HWND hwnd); void EmbeddedWidgetRect(RECT rect); typedef enum{ - InputEventKind_MouseLeftPress, - InputEventKind_MouseLeftRelease, + InputEventKind_MouseLeftPress, + InputEventKind_MouseLeftRelease, } Input_Event_Kind; typedef struct Input_Event Input_Event; struct Input_Event{ - Input_Event *next; - Input_Event_Kind kind; + Input_Event *next; + Input_Event_Kind kind; }; typedef struct Input Input; struct Input{ - Input_Event *first_event; - Input_Event *last_event; - int mouse_x; - int mouse_y; - int left; + Input_Event *first_event; + Input_Event *last_event; + int mouse_x; + int mouse_y; + int left; }; void UpdateAndRender(HWND hwnd, Input *input); @@ -120,7 +120,7 @@ void UpdateAndRender(HWND hwnd, Input *input); int HitTest(int x, int y, RECT rect){ - return((rect.left <= x && x < rect.right) && (rect.top <= y && y < rect.bottom)); + return((rect.left <= x && x < rect.right) && (rect.top <= y && y < rect.bottom)); } //////////////////////////////// @@ -135,19 +135,19 @@ int toggle_maximize_at_end_of_update = 0; int WindowIsActive(void){ - return(window_is_active); + return(window_is_active); } void StopRunning(void){ - keep_running = 0; + keep_running = 0; } void Minimize(HWND hwnd){ - minimize_at_end_of_update = 1; + minimize_at_end_of_update = 1; } void ToggleMaximize(HWND hwnd){ - toggle_maximize_at_end_of_update = 1; + toggle_maximize_at_end_of_update = 1; } #define MAX_EMBEDDED_WIDGETS 128 @@ -155,10 +155,10 @@ int embedded_widget_count = 0; RECT embedded_widget_rect[MAX_EMBEDDED_WIDGETS]; void EmbeddedWidgetRect(RECT rect){ - if (embedded_widget_count < MAX_EMBEDDED_WIDGETS){ - embedded_widget_rect[embedded_widget_count] = rect; - embedded_widget_count += 1; - } + if (embedded_widget_count < MAX_EMBEDDED_WIDGETS){ + embedded_widget_rect[embedded_widget_count] = rect; + embedded_widget_count += 1; + } } //////////////////////////////// @@ -172,19 +172,19 @@ Input input; Input_Event* PushEvent(void){ - Input_Event *result = 0; - if (input_event_cursor < MAX_INPUT_EVENT_COUNT){ - result = &input_event_memory[input_event_cursor]; - input_event_cursor += 1; - if (input.first_event == 0){ - input.first_event = result; - } - if (input.last_event != 0){ - input.last_event->next = result; - } - input.last_event = result; + Input_Event *result = 0; + if (input_event_cursor < MAX_INPUT_EVENT_COUNT){ + result = &input_event_memory[input_event_cursor]; + input_event_cursor += 1; + if (input.first_event == 0){ + input.first_event = result; } - return(result); + if (input.last_event != 0){ + input.last_event->next = result; + } + input.last_event = result; + } + return(result); } // The WindowProc callback does most of the work for a custom boredred window. @@ -196,338 +196,338 @@ CustomBorderWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam){ - LRESULT result = 0; - switch (uMsg){ - // The WM_NCCALCSIZE is used to establish the mapping from the window's - // non-client area to the client area. The non-client area is passed to - // this code through a pointer in lParam that points to the RECT type. - // Then this code indicates the mapping to a client area by modifying - // that RECT. + LRESULT result = 0; + switch (uMsg){ + // The WM_NCCALCSIZE is used to establish the mapping from the window's + // non-client area to the client area. The non-client area is passed to + // this code through a pointer in lParam that points to the RECT type. + // Then this code indicates the mapping to a client area by modifying + // that RECT. + + // The WM_NCCALCSIZE documentation mentions a circumstance where wParam + // is true and another where wParam is false, and the possibility that + // lParam points to the type NCCALCSIZE_PARAMS. It turns out that the + // first member of NCCALCSIZE_PARAMS is a RECT, and that if the rest + // of the NCCALCSIZE_PARAMS structure is ignored then this message + // behaves the same way in eitehr case. NCCALCSIZE_PARAMS offers + // additional features that are not not used here. + + // The main strategy is to leave the client area the same as the + // non-client area by not modifying the RECT at all. This way the whole + // window will be considered client area and the rendering of the border + // is just the same as rendering anything else inside the window. + + // However there is a circumstance when this strategy doesn't work. + // When a window is maximized the OS will automatically allow the + // window's area hang a little outside of the monitor. When a window + // uses the default border this makes it so that the maximized window + // doesn't show it's border. This behavior can't be disabled but + // it is possible nullify the effect by querying how wide the overhang + // area will be and pushing the non-client area in by that amount. + + // Pushing in the client area gives the render target for the application + // the correct area, but it still leaves an unrendered artifact hanging + // outside of the window, which is visible for users with multiple + // monitors. Calling DwmExtendFrameIntoClientArea addresses this. Normally + // this call widens the area thw window paints as part of border by + // pushing in the interior of the window frame. In this case where border + // rendering is in application code it just pushes in the area where + // the overhang artifact is visible to the point where it is removed. + case WM_NCCALCSIZE: + { + MARGINS m = { 0, 0, 0, 0 }; + + RECT* r = (RECT*)lParam; + + // A convenient function for checking if a window is maximized. + if (IsZoomed(hwnd)){ - // The WM_NCCALCSIZE documentation mentions a circumstance where wParam - // is true and another where wParam is false, and the possibility that - // lParam points to the type NCCALCSIZE_PARAMS. It turns out that the - // first member of NCCALCSIZE_PARAMS is a RECT, and that if the rest - // of the NCCALCSIZE_PARAMS structure is ignored then this message - // behaves the same way in eitehr case. NCCALCSIZE_PARAMS offers - // additional features that are not not used here. + int x_push_in = GetSystemMetrics(SM_CXFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER); + int y_push_in = GetSystemMetrics(SM_CYFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER); + r->left += x_push_in; + r->top += y_push_in; + r->bottom -= x_push_in; + r->right -= y_push_in; - // The main strategy is to leave the client area the same as the - // non-client area by not modifying the RECT at all. This way the whole - // window will be considered client area and the rendering of the border - // is just the same as rendering anything else inside the window. + m.cxLeftWidth = m.cxRightWidth = x_push_in; + m.cyTopHeight = m.cyBottomHeight = y_push_in; + } + + if (composition_enabled){ + DwmExtendFrameIntoClientArea(hwnd, &m); + } + + }break; + + // TODO(allen): This message relates to Windows 7 and Vista where the DWM mode + // can change. I need to experiment with it before leaving commentary. + case WM_DWMCOMPOSITIONCHANGED: + { + DwmIsCompositionEnabled(&composition_enabled); + if (composition_enabled){ + MARGINS m = { 0, 0, 0, 0 }; + if (IsZoomed(hwnd)){ + int x_push_in = GetSystemMetrics(SM_CXFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER); + int y_push_in = GetSystemMetrics(SM_CYFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER); + m.cxLeftWidth = m.cxRightWidth = x_push_in; + m.cyTopHeight = m.cyBottomHeight = y_push_in; + } + DwmExtendFrameIntoClientArea(hwnd, &m); + } + }break; + + + // The WM_NCACTIVATE message is sent to a window before the WM_ACTIVATE + // message. When a window uses the default border this message is used + // to repaint the border to indicate the change of status to active or + // inactive. This would paint over the custom window border. This code + // simply overrides that behavior with tracking the window state. It is + // still necessary to return true from this message so that normal window + // activation handling continues and so that the WM_ACTIVATE message is + // still sent. + + // The documentation for WM_NCACTIVATE says without explanation: + // "If the window is minimized when this message is received, the + // application should pass the message to the DefWindowProc function" + // This code follows this instruction, but for extra caution passes + // -1 instead of lParam as the docs suggest this will prevent it from + // rendering. + case WM_NCACTIVATE: + { + result = 1; + window_is_active = wParam; + // A convenient function for checking if a window is minimized. + if (IsIconic(hwnd)){ + result = DefWindowProcW(hwnd, uMsg, wParam, -1); + } + }break; + + // The WM_NCHITTEST message is used to map points on the window to + // a symbol indicating the "behavior" of that point. The possible + // "behaviors" include resizing, moving, clicking close, minimize, + // and maximize buttons, being outside of the window, and being + // inside the client area. + + // In some versions of Windows using the behaviors for the various + // buttons causes unwanted rendering for the buttons, so those + // are not used in this custom window border. Luckily there is not + // much difficulty in recreating the buttons. + + // There is one tricky problem with handling this message. The problem + // occurs if your custom border embeds any widgets along the area that + // would be considered the "caption" of the window which really means + // the area that can be grabbed to move the window around. For example + // the close, minimize, and maximize buttons are widgets embedded in + // the caption. Generally any widget that overlaps any part of the + // area that would be for moving the window. + // + // When a point is inside an embedded widget this message should return + // HTCLIENT so that normal mouse events are sent to the application layer. + // The problem is that the application is going to be in charge of placing + // those buttons, so there must be a strategy for getting that information + // back to this message which expects an immediate response, not a response + // later on in the frame. + + // Some ideas of strategies to consider: + + // 1. Just don't have any embedded widgets. + // + // > This works but it does mean you're officially giving up on replacing + // the normal close, minimize, maximize buttons. + + // 2. Only allow embedded widgets that immediately move the window when + // clicked so that there is no chance of accidentally moving the + // window when trying to use the widgets. + // + // > This works but is very restricting, and requires buttons that + // respond on mouse down immediately instead of mouse up. + + // 3. Pre-define the embedded widget layout and have both the application + // layer and platform layer depend on this pre-defined layout rule. + // + // > This works but it does couple the platform layer to things that could + // have been purely application controlled concepts. + + // 4. Have the application layer set a "widget rectangle list" when it + // updates and always use the most recent one + // + // > This works but it is also the most work to set up in the platform + // layer and requires the update function to do new work too. + + // Certainly there are more possible strategies, this list is just mean + // to offer a few ideas, mostly to clarify the nature of the problem. + + // This example is using strategy 4. + + case WM_NCHITTEST: + { + POINT pos; + pos.x = GET_X_LPARAM(lParam); + pos.y = GET_Y_LPARAM(lParam); + + // Make sure the point is inside of the window + RECT frame_rect; + GetWindowRect(hwnd, &frame_rect); + if (!HitTest(pos.x, pos.y, frame_rect)){ + result = HTNOWHERE; + } + + else{ - // However there is a circumstance when this strategy doesn't work. - // When a window is maximized the OS will automatically allow the - // window's area hang a little outside of the monitor. When a window - // uses the default border this makes it so that the maximized window - // doesn't show it's border. This behavior can't be disabled but - // it is possible nullify the effect by querying how wide the overhang - // area will be and pushing the non-client area in by that amount. + RECT rect; + GetClientRect(hwnd, &rect); + ScreenToClient(hwnd, &pos); - // Pushing in the client area gives the render target for the application - // the correct area, but it still leaves an unrendered artifact hanging - // outside of the window, which is visible for users with multiple - // monitors. Calling DwmExtendFrameIntoClientArea addresses this. Normally - // this call widens the area thw window paints as part of border by - // pushing in the interior of the window frame. In this case where border - // rendering is in application code it just pushes in the area where - // the overhang artifact is visible to the point where it is removed. - case WM_NCCALCSIZE: - { - MARGINS m = { 0, 0, 0, 0 }; - - RECT* r = (RECT*)lParam; - - // A convenient function for checking if a window is maximized. - if (IsZoomed(hwnd)){ - - int x_push_in = GetSystemMetrics(SM_CXFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER); - int y_push_in = GetSystemMetrics(SM_CYFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER); - r->left += x_push_in; - r->top += y_push_in; - r->bottom -= x_push_in; - r->right -= y_push_in; - - m.cxLeftWidth = m.cxRightWidth = x_push_in; - m.cyTopHeight = m.cyBottomHeight = y_push_in; + // Check each border + int l = 0; + int r = 0; + int b = 0; + int t = 0; + if (!IsZoomed(hwnd)){ + if (rect.left <= pos.x && pos.x < rect.left + border_width){ + l = 1; + } + if (rect.right - border_width <= pos.x && pos.x < rect.right){ + r = 1; + } + if (rect.bottom - border_width <= pos.y && pos.y < rect.bottom){ + b = 1; + } + if (rect.top <= pos.y && pos.y < rect.top + border_width){ + t = 1; + } + } + + // If the point is in two borders, use the corresponding corner resize. + // If the point is in just one border, use the corresponding side resize. + if (l){ + if (t){ + result = HTTOPLEFT; + } + else if (b){ + result = HTBOTTOMLEFT; + } + else{ + result = HTLEFT; + } + } + else if (r){ + if (t){ + result = HTTOPRIGHT; + } + else if (b){ + result = HTBOTTOMRIGHT; + } + else{ + result = HTRIGHT; + } + } + else if (t){ + result = HTTOP; + } + else if (b){ + result = HTBOTTOM; + } + + // Here the point must be further inside the window than the resize + // borders, so the final options are the window moving area (caption) + // and the client area. + else{ + if (rect.top <= pos.y && pos.y < rect.top + caption_width){ + result = HTCAPTION; + // Check the application defined widget areas + for (int i = 0; i < embedded_widget_count; i += 1){ + if (HitTest(pos.x, pos.y, embedded_widget_rect[i])){ + result = HTCLIENT; + break; + } } - - if (composition_enabled){ - DwmExtendFrameIntoClientArea(hwnd, &m); - } - - }break; - - // TODO(allen): This message relates to Windows 7 and Vista where the DWM mode - // can change. I need to experiment with it before leaving commentary. - case WM_DWMCOMPOSITIONCHANGED: - { - DwmIsCompositionEnabled(&composition_enabled); - if (composition_enabled){ - MARGINS m = { 0, 0, 0, 0 }; - if (IsZoomed(hwnd)){ - int x_push_in = GetSystemMetrics(SM_CXFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER); - int y_push_in = GetSystemMetrics(SM_CYFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER); - m.cxLeftWidth = m.cxRightWidth = x_push_in; - m.cyTopHeight = m.cyBottomHeight = y_push_in; - } - DwmExtendFrameIntoClientArea(hwnd, &m); - } - }break; - - - // The WM_NCACTIVATE message is sent to a window before the WM_ACTIVATE - // message. When a window uses the default border this message is used - // to repaint the border to indicate the change of status to active or - // inactive. This would paint over the custom window border. This code - // simply overrides that behavior with tracking the window state. It is - // still necessary to return true from this message so that normal window - // activation handling continues and so that the WM_ACTIVATE message is - // still sent. - - // The documentation for WM_NCACTIVATE says without explanation: - // "If the window is minimized when this message is received, the - // application should pass the message to the DefWindowProc function" - // This code follows this instruction, but for extra caution passes - // -1 instead of lParam as the docs suggest this will prevent it from - // rendering. - case WM_NCACTIVATE: - { - result = 1; - window_is_active = wParam; - // A convenient function for checking if a window is minimized. - if (IsIconic(hwnd)){ - result = DefWindowProcW(hwnd, uMsg, wParam, -1); - } - }break; - - // The WM_NCHITTEST message is used to map points on the window to - // a symbol indicating the "behavior" of that point. The possible - // "behaviors" include resizing, moving, clicking close, minimize, - // and maximize buttons, being outside of the window, and being - // inside the client area. - - // In some versions of Windows using the behaviors for the various - // buttons causes unwanted rendering for the buttons, so those - // are not used in this custom window border. Luckily there is not - // much difficulty in recreating the buttons. - - // There is one tricky problem with handling this message. The problem - // occurs if your custom border embeds any widgets along the area that - // would be considered the "caption" of the window which really means - // the area that can be grabbed to move the window around. For example - // the close, minimize, and maximize buttons are widgets embedded in - // the caption. Generally any widget that overlaps any part of the - // area that would be for moving the window. - // - // When a point is inside an embedded widget this message should return - // HTCLIENT so that normal mouse events are sent to the application layer. - // The problem is that the application is going to be in charge of placing - // those buttons, so there must be a strategy for getting that information - // back to this message which expects an immediate response, not a response - // later on in the frame. - - // Some ideas of strategies to consider: - - // 1. Just don't have any embedded widgets. - // - // > This works but it does mean you're officially giving up on replacing - // the normal close, minimize, maximize buttons. - - // 2. Only allow embedded widgets that immediately move the window when - // clicked so that there is no chance of accidentally moving the - // window when trying to use the widgets. - // - // > This works but is very restricting, and requires buttons that - // respond on mouse down immediately instead of mouse up. - - // 3. Pre-define the embedded widget layout and have both the application - // layer and platform layer depend on this pre-defined layout rule. - // - // > This works but it does couple the platform layer to things that could - // have been purely application controlled concepts. - - // 4. Have the application layer set a "widget rectangle list" when it - // updates and always use the most recent one - // - // > This works but it is also the most work to set up in the platform - // layer and requires the update function to do new work too. - - // Certainly there are more possible strategies, this list is just mean - // to offer a few ideas, mostly to clarify the nature of the problem. - - // This example is using strategy 4. - - case WM_NCHITTEST: - { - POINT pos; - pos.x = GET_X_LPARAM(lParam); - pos.y = GET_Y_LPARAM(lParam); - - // Make sure the point is inside of the window - RECT frame_rect; - GetWindowRect(hwnd, &frame_rect); - if (!HitTest(pos.x, pos.y, frame_rect)){ - result = HTNOWHERE; - } - - else{ - - RECT rect; - GetClientRect(hwnd, &rect); - ScreenToClient(hwnd, &pos); - - // Check each border - int l = 0; - int r = 0; - int b = 0; - int t = 0; - if (!IsZoomed(hwnd)){ - if (rect.left <= pos.x && pos.x < rect.left + border_width){ - l = 1; - } - if (rect.right - border_width <= pos.x && pos.x < rect.right){ - r = 1; - } - if (rect.bottom - border_width <= pos.y && pos.y < rect.bottom){ - b = 1; - } - if (rect.top <= pos.y && pos.y < rect.top + border_width){ - t = 1; - } - } - - // If the point is in two borders, use the corresponding corner resize. - // If the point is in just one border, use the corresponding side resize. - if (l){ - if (t){ - result = HTTOPLEFT; - } - else if (b){ - result = HTBOTTOMLEFT; - } - else{ - result = HTLEFT; - } - } - else if (r){ - if (t){ - result = HTTOPRIGHT; - } - else if (b){ - result = HTBOTTOMRIGHT; - } - else{ - result = HTRIGHT; - } - } - else if (t){ - result = HTTOP; - } - else if (b){ - result = HTBOTTOM; - } - - // Here the point must be further inside the window than the resize - // borders, so the final options are the window moving area (caption) - // and the client area. - else{ - if (rect.top <= pos.y && pos.y < rect.top + caption_width){ - result = HTCAPTION; - // Check the application defined widget areas - for (int i = 0; i < embedded_widget_count; i += 1){ - if (HitTest(pos.x, pos.y, embedded_widget_rect[i])){ - result = HTCLIENT; - break; - } - } - } - else{ - result = HTCLIENT; - } - } - } - - }break; - - // This is only included to emphasize that there is no reason to handle - // any of these WM_NC* messages. They give the window the opportunity - // to handle mouse events in the non-client area before generating the - // equivalent client area versions. If WM_NCHITTEST returns HTCLIENT - // then the default handling for these is to generate the client area - // version of the message. If WM_NCHITTEST generates one of the symbols, - // HTLEFT, HTTOP, ... HTTOPLEFT ... HTCAPTION, then it determined that - // the OS should provide default handling for a resize or move operation. - // In either case, these should just do their default behavior. + } + else{ + result = HTCLIENT; + } + } + } + + }break; + + // This is only included to emphasize that there is no reason to handle + // any of these WM_NC* messages. They give the window the opportunity + // to handle mouse events in the non-client area before generating the + // equivalent client area versions. If WM_NCHITTEST returns HTCLIENT + // then the default handling for these is to generate the client area + // version of the message. If WM_NCHITTEST generates one of the symbols, + // HTLEFT, HTTOP, ... HTTOPLEFT ... HTCAPTION, then it determined that + // the OS should provide default handling for a resize or move operation. + // In either case, these should just do their default behavior. #if 0 - case WM_NCLBUTTONDOWN: - case WM_NCLBUTTONUP: - case WM_NCLBUTTONDBLCLK: - case WM_NCMBUTTONDBLCLK: - case WM_NCMBUTTONDOWN: - case WM_NCMBUTTONUP: - case WM_NCMOUSEHOVER: - case WM_NCMOUSELEAVE: - case WM_NCMOUSEMOVE: - case WM_NCRBUTTONDBLCLK: - case WM_NCRBUTTONDOWN: - case WM_NCRBUTTONUP: - case WM_NCXBUTTONDBLCLK: - case WM_NCXBUTTONDOWN: - case WM_NCXBUTTONUP: - { - result = DefWindowProcW(hwnd, uMsg, wParam, lParam); - }break; + case WM_NCLBUTTONDOWN: + case WM_NCLBUTTONUP: + case WM_NCLBUTTONDBLCLK: + case WM_NCMBUTTONDBLCLK: + case WM_NCMBUTTONDOWN: + case WM_NCMBUTTONUP: + case WM_NCMOUSEHOVER: + case WM_NCMOUSELEAVE: + case WM_NCMOUSEMOVE: + case WM_NCRBUTTONDBLCLK: + case WM_NCRBUTTONDOWN: + case WM_NCRBUTTONUP: + case WM_NCXBUTTONDBLCLK: + case WM_NCXBUTTONDOWN: + case WM_NCXBUTTONUP: + { + result = DefWindowProcW(hwnd, uMsg, wParam, lParam); + }break; #endif - - case WM_CLOSE: - { - keep_running = 0; - }break; - - case WM_SIZING: - { - InvalidateRect(hwnd, 0, 0); - }break; - - // It's always a good idea to do *something* with the WM_PAINT message - // so that your window looks better when resizing, but in this case it's - // especially important. Remember your code creates the border graphics - // now, so if you don't render anything when the window is reszing then - // there will be nothing at all to indicate that the window is changing - // size until the resize finishes. - case WM_PAINT: - { - PAINTSTRUCT ps; - BeginPaint(hwnd, &ps); - embedded_widget_count = 0; - UpdateAndRender(hwnd, 0); - EndPaint(hwnd, &ps); - }break; - - case WM_LBUTTONDOWN: - { - Input_Event *event = PushEvent(); - if (event != 0){ - event->kind = InputEventKind_MouseLeftPress; - } - }break; - - case WM_LBUTTONUP: - { - Input_Event *event = PushEvent(); - if (event != 0){ - event->kind = InputEventKind_MouseLeftRelease; - } - }break; - - default: - { - result = DefWindowProcW(hwnd, uMsg, wParam, lParam); - }break; - } - return(result); + + case WM_CLOSE: + { + keep_running = 0; + }break; + + case WM_SIZING: + { + InvalidateRect(hwnd, 0, 0); + }break; + + // It's always a good idea to do *something* with the WM_PAINT message + // so that your window looks better when resizing, but in this case it's + // especially important. Remember your code creates the border graphics + // now, so if you don't render anything when the window is reszing then + // there will be nothing at all to indicate that the window is changing + // size until the resize finishes. + case WM_PAINT: + { + PAINTSTRUCT ps; + BeginPaint(hwnd, &ps); + embedded_widget_count = 0; + UpdateAndRender(hwnd, 0); + EndPaint(hwnd, &ps); + }break; + + case WM_LBUTTONDOWN: + { + Input_Event *event = PushEvent(); + if (event != 0){ + event->kind = InputEventKind_MouseLeftPress; + } + }break; + + case WM_LBUTTONUP: + { + Input_Event *event = PushEvent(); + if (event != 0){ + event->kind = InputEventKind_MouseLeftRelease; + } + }break; + + default: + { + result = DefWindowProcW(hwnd, uMsg, wParam, lParam); + }break; + } + return(result); } int @@ -535,126 +535,126 @@ WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd){ - - // With CustomBorderWindowProc done, creating the window is not - // that much more work, and is almost identical to creating a - // default window. - + + // With CustomBorderWindowProc done, creating the window is not + // that much more work, and is almost identical to creating a + // default window. + #define WINDOW_CLASS L"MainWindow" + + // Nothing about the window class differs from a default window. + WNDCLASSW window_class = {0}; + window_class.style = 0; + window_class.lpfnWndProc = CustomBorderWindowProc; + window_class.hInstance = hInstance; + window_class.hCursor = LoadCursorA(0, IDC_ARROW); + window_class.lpszClassName = WINDOW_CLASS; + + ATOM atom = RegisterClassW(&window_class); + if (atom == 0){ + fprintf(stderr, "RegisterClassW failed\n"); + exit(1); + } + + // One key aspect is making sure these styles are present: + // WS_SIZEBOX, WS_MAXIMIZEBOX, WS_MINIMIZEBOX, WS_CAPTION, WM_SYSMENU + // Because these styles are needed for making shortcuts and + // default resize and moving behavior work, even when the border + // itself is overriden. These are exactly equivalent to the combined style: + // WS_OVERLAPPEDWINDOW + DWORD style = WS_OVERLAPPEDWINDOW; + HWND hwnd = CreateWindowW(WINDOW_CLASS, + L"Window Name", + style, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + 0, + 0, + hInstance, + 0); + if (hwnd == 0){ + fprintf(stderr, "CreateWindowExW failed\n"); + exit(1); + } + + // The other key aspect is SetWindowTheme. Using SetWindowTheme with single + // spaces effectively disables an theme on the window. Doing this is critical + // for removing special shapes, shading, etc. from the default window on + // any given version of Windows. + SetWindowTheme(hwnd, L" ", L" "); + + + // A convenient way to get vsync since this example doesn't have a more + // sophistcated graphics context. + if (DwmIsCompositionEnabled(&composition_enabled) != S_OK){ + fprintf(stderr, "DwmIsCompositionEnabled failed\n"); + composition_enabled = 0; + } + fprintf(stdout, "Has compositor? %s\n", composition_enabled?"Yes":"No"); + + ShowWindow(hwnd, SW_SHOW); + + keep_running = 1; + for (;keep_running;){ + input.first_event = 0; + input.last_event = 0; + input_event_cursor = 0; - // Nothing about the window class differs from a default window. - WNDCLASSW window_class = {0}; - window_class.style = 0; - window_class.lpfnWndProc = CustomBorderWindowProc; - window_class.hInstance = hInstance; - window_class.hCursor = LoadCursorA(0, IDC_ARROW); - window_class.lpszClassName = WINDOW_CLASS; + // Personal preference - I like to put anything that resizes the window + // from my code after the main update so that I can assume that the + // size never changes during the update. This also happens to be a + // convenient way to make sure I immediately redo my layout and render + // after a size change. - ATOM atom = RegisterClassW(&window_class); - if (atom == 0){ - fprintf(stderr, "RegisterClassW failed\n"); - exit(1); + MSG msg; + + if (minimize_at_end_of_update){ + ShowWindow(hwnd, SW_MINIMIZE); + } + else if (toggle_maximize_at_end_of_update){ + if (IsZoomed(hwnd)){ + ShowWindow(hwnd, SW_RESTORE); + } + else{ + ShowWindow(hwnd, SW_MAXIMIZE); + } + } + else{ + GetMessage(&msg, 0, 0, 0); + TranslateMessage(&msg); + DispatchMessage(&msg); } - // One key aspect is making sure these styles are present: - // WS_SIZEBOX, WS_MAXIMIZEBOX, WS_MINIMIZEBOX, WS_CAPTION, WM_SYSMENU - // Because these styles are needed for making shortcuts and - // default resize and moving behavior work, even when the border - // itself is overriden. These are exactly equivalent to the combined style: - // WS_OVERLAPPEDWINDOW - DWORD style = WS_OVERLAPPEDWINDOW; - HWND hwnd = CreateWindowW(WINDOW_CLASS, - L"Window Name", - style, - CW_USEDEFAULT, - CW_USEDEFAULT, - CW_USEDEFAULT, - CW_USEDEFAULT, - 0, - 0, - hInstance, - 0); - if (hwnd == 0){ - fprintf(stderr, "CreateWindowExW failed\n"); - exit(1); + for (;PeekMessageW(&msg, 0, 0, 0, PM_REMOVE);){ + TranslateMessage(&msg); + DispatchMessage(&msg); } - // The other key aspect is SetWindowTheme. Using SetWindowTheme with single - // spaces effectively disables an theme on the window. Doing this is critical - // for removing special shapes, shading, etc. from the default window on - // any given version of Windows. - SetWindowTheme(hwnd, L" ", L" "); + POINT mpos; + GetCursorPos(&mpos); + ScreenToClient(hwnd, &mpos); + input.mouse_x = mpos.x; + input.mouse_y = mpos.y; + input.left = (GetKeyState(VK_LBUTTON)&(1 << 15)); + minimize_at_end_of_update = 0; + toggle_maximize_at_end_of_update = 0; + embedded_widget_count = 0; + UpdateAndRender(hwnd, &input); - // A convenient way to get vsync since this example doesn't have a more - // sophistcated graphics context. - if (DwmIsCompositionEnabled(&composition_enabled) != S_OK){ - fprintf(stderr, "DwmIsCompositionEnabled failed\n"); - composition_enabled = 0; + // This can be whatever vsync or frame rate limiting method you'd + // like or none at all. + if (composition_enabled){ + DwmFlush(); } - fprintf(stdout, "Has compositor? %s\n", composition_enabled?"Yes":"No"); - - ShowWindow(hwnd, SW_SHOW); - - keep_running = 1; - for (;keep_running;){ - input.first_event = 0; - input.last_event = 0; - input_event_cursor = 0; - - // Personal preference - I like to put anything that resizes the window - // from my code after the main update so that I can assume that the - // size never changes during the update. This also happens to be a - // convenient way to make sure I immediately redo my layout and render - // after a size change. - - MSG msg; - - if (minimize_at_end_of_update){ - ShowWindow(hwnd, SW_MINIMIZE); - } - else if (toggle_maximize_at_end_of_update){ - if (IsZoomed(hwnd)){ - ShowWindow(hwnd, SW_RESTORE); - } - else{ - ShowWindow(hwnd, SW_MAXIMIZE); - } - } - else{ - GetMessage(&msg, 0, 0, 0); - TranslateMessage(&msg); - DispatchMessage(&msg); - } - - for (;PeekMessageW(&msg, 0, 0, 0, PM_REMOVE);){ - TranslateMessage(&msg); - DispatchMessage(&msg); - } - - POINT mpos; - GetCursorPos(&mpos); - ScreenToClient(hwnd, &mpos); - input.mouse_x = mpos.x; - input.mouse_y = mpos.y; - input.left = (GetKeyState(VK_LBUTTON)&(1 << 15)); - - minimize_at_end_of_update = 0; - toggle_maximize_at_end_of_update = 0; - embedded_widget_count = 0; - UpdateAndRender(hwnd, &input); - - // This can be whatever vsync or frame rate limiting method you'd - // like or none at all. - if (composition_enabled){ - DwmFlush(); - } - else{ - Sleep(33); - } + else{ + Sleep(33); } - - return(0); + } + + return(0); } //////////////////////////////// @@ -665,37 +665,37 @@ WinMain(HINSTANCE hInstance, int HasEvent(Input *input, Input_Event_Kind kind){ - int result = 0; - if (input != 0){ - Input_Event **ptr_to = &input->first_event; - Input_Event *last = 0; - for (Input_Event *event = input->first_event, *next = 0; - event != 0; - event = next){ - next = event->next; - if (event->kind == kind){ - result = 1; - *ptr_to = next; - if (event == input->last_event){ - input->last_event = last; - } - break; - } - else{ - ptr_to = &event->next; - last = event; - } + int result = 0; + if (input != 0){ + Input_Event **ptr_to = &input->first_event; + Input_Event *last = 0; + for (Input_Event *event = input->first_event, *next = 0; + event != 0; + event = next){ + next = event->next; + if (event->kind == kind){ + result = 1; + *ptr_to = next; + if (event == input->last_event){ + input->last_event = last; } + break; + } + else{ + ptr_to = &event->next; + last = event; + } } - return(result); + } + return(result); } typedef enum{ - Widget_None, - Widget_Close, - Widget_Minimize, - Widget_Maximize, - Widget_Slider, + Widget_None, + Widget_Close, + Widget_Minimize, + Widget_Maximize, + Widget_Slider, } Widget; Widget click_started = Widget_None; @@ -704,166 +704,166 @@ int delta_from_mouse_x = 0; void UpdateAndRender(HWND hwnd, Input *input){ - RECT window_rect; - GetClientRect(hwnd, &window_rect); + RECT window_rect; + GetClientRect(hwnd, &window_rect); + + RECT inside_rect; + inside_rect.top = window_rect.top + caption_width; + if (!IsZoomed(hwnd)){ + inside_rect.left = window_rect.left + border_width; + inside_rect.right = window_rect.right - border_width; + inside_rect.bottom = window_rect.bottom - border_width; + } + else{ + inside_rect.left = window_rect.left; + inside_rect.right = window_rect.right; + inside_rect.bottom = window_rect.bottom; + } + + HDC dc = GetDC(hwnd); + { + SelectObject(dc, GetStockObject(DC_PEN)); + SelectObject(dc, GetStockObject(DC_BRUSH)); - RECT inside_rect; - inside_rect.top = window_rect.top + caption_width; - if (!IsZoomed(hwnd)){ - inside_rect.left = window_rect.left + border_width; - inside_rect.right = window_rect.right - border_width; - inside_rect.bottom = window_rect.bottom - border_width; - } - else{ - inside_rect.left = window_rect.left; - inside_rect.right = window_rect.right; - inside_rect.bottom = window_rect.bottom; - } - - HDC dc = GetDC(hwnd); + // Window border { - SelectObject(dc, GetStockObject(DC_PEN)); - SelectObject(dc, GetStockObject(DC_BRUSH)); - - // Window border - { - if (WindowIsActive()){ - SetDCPenColor(dc, RGB(255, 200, 0)); - SetDCBrushColor(dc, RGB(255, 200, 0)); - } - else{ - SetDCPenColor(dc, RGB(128, 100, 0)); - SetDCBrushColor(dc, RGB(128, 100, 0)); - } - - Rectangle(dc, window_rect.left, window_rect.top, window_rect.right, inside_rect.top); - Rectangle(dc, window_rect.left, inside_rect.bottom, window_rect.right, window_rect.bottom); - Rectangle(dc, window_rect.left, inside_rect.top, inside_rect.left, inside_rect.bottom); - Rectangle(dc, inside_rect.right, inside_rect.top, window_rect.right, inside_rect.bottom); - } - - // Close button - if (window_rect.right > 20){ - RECT button; - button.left = window_rect.right - 20; - button.top = window_rect.top + 10; - button.right = window_rect.right - 10; - button.bottom = window_rect.top + 20; - - SetDCPenColor(dc, RGB(180, 0, 0)); - SetDCBrushColor(dc, RGB(180, 0, 0)); - - EmbeddedWidgetRect(button); - Rectangle(dc, button.left, button.top, button.right, button.bottom); - if (input != 0 && HitTest(input->mouse_x, input->mouse_y, button)){ - if (HasEvent(input, InputEventKind_MouseLeftPress)){ - click_started = Widget_Close; - } - if (click_started == Widget_Close && HasEvent(input, InputEventKind_MouseLeftRelease)){ - StopRunning(); - } - } - } - - // Minimize button - if (window_rect.right > 40){ - RECT button; - button.left = window_rect.right - 40; - button.top = window_rect.top + 10; - button.right = window_rect.right - 30; - button.bottom = window_rect.top + 20; - - SetDCPenColor(dc, RGB(0, 0, 180)); - SetDCBrushColor(dc, RGB(0, 0, 180)); - - EmbeddedWidgetRect(button); - Rectangle(dc, button.left, button.top, button.right, button.bottom); - if (input != 0 && HitTest(input->mouse_x, input->mouse_y, button)){ - if (HasEvent(input, InputEventKind_MouseLeftPress)){ - click_started = Widget_Minimize; - } - if (click_started == Widget_Minimize && HasEvent(input, InputEventKind_MouseLeftRelease)){ - Minimize(hwnd); - } - } - } - - // Maximize button - if (window_rect.right > 60){ - RECT button; - button.left = window_rect.right - 60; - button.top = window_rect.top + 10; - button.right = window_rect.right - 50; - button.bottom = window_rect.top + 20; - - SetDCPenColor(dc, RGB(0, 180, 0)); - SetDCBrushColor(dc, RGB(0, 180, 0)); - - EmbeddedWidgetRect(button); - Rectangle(dc, button.left, button.top, button.right, button.bottom); - if (input != 0 && HitTest(input->mouse_x, input->mouse_y, button)){ - if (HasEvent(input, InputEventKind_MouseLeftPress)){ - click_started = Widget_Maximize; - } - if (click_started == Widget_Maximize && HasEvent(input, InputEventKind_MouseLeftRelease)){ - ToggleMaximize(hwnd); - } - } - } - - // Slider - if (window_rect.right > 200){ - RECT track; - track.left = window_rect.right - 195; - track.top = window_rect.top + 14; - track.right = window_rect.right - 95; - track.bottom = window_rect.top + 16; - - SetDCPenColor(dc, RGB(0, 0, 0)); - SetDCBrushColor(dc, RGB(0, 0, 0)); - Rectangle(dc, track.left, track.top, track.right, track.bottom); - - RECT button; - button.left = window_rect.right - 200 + slider_x; - button.top = window_rect.top + 10; - button.right = window_rect.right - 190 + slider_x; - button.bottom = window_rect.top + 20; - - SetDCPenColor(dc, RGB(100, 200, 200)); - SetDCBrushColor(dc, RGB(100, 200, 200)); - - EmbeddedWidgetRect(button); - Rectangle(dc, button.left, button.top, button.right, button.bottom); - if (input != 0){ - if (HitTest(input->mouse_x, input->mouse_y, button)){ - if (HasEvent(input, InputEventKind_MouseLeftPress)){ - click_started = Widget_Slider; - delta_from_mouse_x = slider_x - input->mouse_x; - } - } - if (click_started == Widget_Slider){ - slider_x = delta_from_mouse_x + input->mouse_x; - if (slider_x < 0){ - slider_x = 0; - } - if (slider_x > 100){ - slider_x = 100; - } - } - } - } - - if (input != 0 && !input->left){ - click_started = Widget_None; - } - - // Inside area - { - SetDCPenColor(dc, RGB(127, 127, 127)); - SetDCBrushColor(dc, RGB(127, 127, 127)); - Rectangle(dc, inside_rect.left, inside_rect.top, inside_rect.right, inside_rect.bottom); - } + if (WindowIsActive()){ + SetDCPenColor(dc, RGB(255, 200, 0)); + SetDCBrushColor(dc, RGB(255, 200, 0)); + } + else{ + SetDCPenColor(dc, RGB(128, 100, 0)); + SetDCBrushColor(dc, RGB(128, 100, 0)); + } + + Rectangle(dc, window_rect.left, window_rect.top, window_rect.right, inside_rect.top); + Rectangle(dc, window_rect.left, inside_rect.bottom, window_rect.right, window_rect.bottom); + Rectangle(dc, window_rect.left, inside_rect.top, inside_rect.left, inside_rect.bottom); + Rectangle(dc, inside_rect.right, inside_rect.top, window_rect.right, inside_rect.bottom); } - ReleaseDC(hwnd, dc); + + // Close button + if (window_rect.right > 20){ + RECT button; + button.left = window_rect.right - 20; + button.top = window_rect.top + 10; + button.right = window_rect.right - 10; + button.bottom = window_rect.top + 20; + + SetDCPenColor(dc, RGB(180, 0, 0)); + SetDCBrushColor(dc, RGB(180, 0, 0)); + + EmbeddedWidgetRect(button); + Rectangle(dc, button.left, button.top, button.right, button.bottom); + if (input != 0 && HitTest(input->mouse_x, input->mouse_y, button)){ + if (HasEvent(input, InputEventKind_MouseLeftPress)){ + click_started = Widget_Close; + } + if (click_started == Widget_Close && HasEvent(input, InputEventKind_MouseLeftRelease)){ + StopRunning(); + } + } + } + + // Minimize button + if (window_rect.right > 40){ + RECT button; + button.left = window_rect.right - 40; + button.top = window_rect.top + 10; + button.right = window_rect.right - 30; + button.bottom = window_rect.top + 20; + + SetDCPenColor(dc, RGB(0, 0, 180)); + SetDCBrushColor(dc, RGB(0, 0, 180)); + + EmbeddedWidgetRect(button); + Rectangle(dc, button.left, button.top, button.right, button.bottom); + if (input != 0 && HitTest(input->mouse_x, input->mouse_y, button)){ + if (HasEvent(input, InputEventKind_MouseLeftPress)){ + click_started = Widget_Minimize; + } + if (click_started == Widget_Minimize && HasEvent(input, InputEventKind_MouseLeftRelease)){ + Minimize(hwnd); + } + } + } + + // Maximize button + if (window_rect.right > 60){ + RECT button; + button.left = window_rect.right - 60; + button.top = window_rect.top + 10; + button.right = window_rect.right - 50; + button.bottom = window_rect.top + 20; + + SetDCPenColor(dc, RGB(0, 180, 0)); + SetDCBrushColor(dc, RGB(0, 180, 0)); + + EmbeddedWidgetRect(button); + Rectangle(dc, button.left, button.top, button.right, button.bottom); + if (input != 0 && HitTest(input->mouse_x, input->mouse_y, button)){ + if (HasEvent(input, InputEventKind_MouseLeftPress)){ + click_started = Widget_Maximize; + } + if (click_started == Widget_Maximize && HasEvent(input, InputEventKind_MouseLeftRelease)){ + ToggleMaximize(hwnd); + } + } + } + + // Slider + if (window_rect.right > 200){ + RECT track; + track.left = window_rect.right - 195; + track.top = window_rect.top + 14; + track.right = window_rect.right - 95; + track.bottom = window_rect.top + 16; + + SetDCPenColor(dc, RGB(0, 0, 0)); + SetDCBrushColor(dc, RGB(0, 0, 0)); + Rectangle(dc, track.left, track.top, track.right, track.bottom); + + RECT button; + button.left = window_rect.right - 200 + slider_x; + button.top = window_rect.top + 10; + button.right = window_rect.right - 190 + slider_x; + button.bottom = window_rect.top + 20; + + SetDCPenColor(dc, RGB(100, 200, 200)); + SetDCBrushColor(dc, RGB(100, 200, 200)); + + EmbeddedWidgetRect(button); + Rectangle(dc, button.left, button.top, button.right, button.bottom); + if (input != 0){ + if (HitTest(input->mouse_x, input->mouse_y, button)){ + if (HasEvent(input, InputEventKind_MouseLeftPress)){ + click_started = Widget_Slider; + delta_from_mouse_x = slider_x - input->mouse_x; + } + } + if (click_started == Widget_Slider){ + slider_x = delta_from_mouse_x + input->mouse_x; + if (slider_x < 0){ + slider_x = 0; + } + if (slider_x > 100){ + slider_x = 100; + } + } + } + } + + if (input != 0 && !input->left){ + click_started = Widget_None; + } + + // Inside area + { + SetDCPenColor(dc, RGB(127, 127, 127)); + SetDCBrushColor(dc, RGB(127, 127, 127)); + Rectangle(dc, inside_rect.left, inside_rect.top, inside_rect.right, inside_rect.bottom); + } + } + ReleaseDC(hwnd, dc); } diff --git a/win32_custom_window_mc.c b/win32_custom_window_mc.c index e877aeb..dc5908f 100644 --- a/win32_custom_window_mc.c +++ b/win32_custom_window_mc.c @@ -1,7 +1,7 @@ /* ** Win32 Custom Window Example Program -** v1.2 - April 30th 2020 -** by Allen Webster allenwebster@4coder.net +** v1.3.1 - Jan 19th 2024 +** by Allen Webster allenw@mr4th.com ** ** public domain example program ** NO WARRANTY IMPLIED; USE AT YOUR OWN RISK @@ -29,23 +29,23 @@ void ToggleMaximize(HWND hwnd); void EmbeddedWidgetRect(RECT rect); typedef enum{ - InputEventKind_MouseLeftPress, - InputEventKind_MouseLeftRelease, + InputEventKind_MouseLeftPress, + InputEventKind_MouseLeftRelease, } Input_Event_Kind; typedef struct Input_Event Input_Event; struct Input_Event{ - Input_Event *next; - Input_Event_Kind kind; + Input_Event *next; + Input_Event_Kind kind; }; typedef struct Input Input; struct Input{ - Input_Event *first_event; - Input_Event *last_event; - int mouse_x; - int mouse_y; - int left; + Input_Event *first_event; + Input_Event *last_event; + int mouse_x; + int mouse_y; + int left; }; void UpdateAndRender(HWND hwnd, Input *input); @@ -54,7 +54,7 @@ void UpdateAndRender(HWND hwnd, Input *input); int HitTest(int x, int y, RECT rect){ - return((rect.left <= x && x < rect.right) && (rect.top <= y && y < rect.bottom)); + return((rect.left <= x && x < rect.right) && (rect.top <= y && y < rect.bottom)); } //////////////////////////////// @@ -67,19 +67,19 @@ int toggle_maximize_at_end_of_update = 0; int WindowIsActive(void){ - return(window_is_active); + return(window_is_active); } void StopRunning(void){ - keep_running = 0; + keep_running = 0; } void Minimize(HWND hwnd){ - minimize_at_end_of_update = 1; + minimize_at_end_of_update = 1; } void ToggleMaximize(HWND hwnd){ - toggle_maximize_at_end_of_update = 1; + toggle_maximize_at_end_of_update = 1; } #define MAX_EMBEDDED_WIDGETS 128 @@ -87,10 +87,10 @@ int embedded_widget_count = 0; RECT embedded_widget_rect[MAX_EMBEDDED_WIDGETS]; void EmbeddedWidgetRect(RECT rect){ - if (embedded_widget_count < MAX_EMBEDDED_WIDGETS){ - embedded_widget_rect[embedded_widget_count] = rect; - embedded_widget_count += 1; - } + if (embedded_widget_count < MAX_EMBEDDED_WIDGETS){ + embedded_widget_rect[embedded_widget_count] = rect; + embedded_widget_count += 1; + } } //////////////////////////////// @@ -102,19 +102,19 @@ Input input; Input_Event* PushEvent(void){ - Input_Event *result = 0; - if (input_event_cursor < MAX_INPUT_EVENT_COUNT){ - result = &input_event_memory[input_event_cursor]; - input_event_cursor += 1; - if (input.first_event == 0){ - input.first_event = result; - } - if (input.last_event != 0){ - input.last_event->next = result; - } - input.last_event = result; + Input_Event *result = 0; + if (input_event_cursor < MAX_INPUT_EVENT_COUNT){ + result = &input_event_memory[input_event_cursor]; + input_event_cursor += 1; + if (input.first_event == 0){ + input.first_event = result; } - return(result); + if (input.last_event != 0){ + input.last_event->next = result; + } + input.last_event = result; + } + return(result); } LRESULT @@ -122,157 +122,157 @@ CustomBorderWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam){ - LRESULT result = 0; - switch (uMsg){ - case WM_NCCALCSIZE: - { - RECT* r = (RECT*)lParam; - if (IsZoomed(hwnd)){ - int x_push_in = GetSystemMetrics(SM_CYFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER); - int y_push_in = GetSystemMetrics(SM_CXFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER); - r->top += x_push_in; - r->left += y_push_in; - r->right -= x_push_in; - r->bottom -= y_push_in; + LRESULT result = 0; + switch (uMsg){ + case WM_NCCALCSIZE: + { + RECT* r = (RECT*)lParam; + if (IsZoomed(hwnd)){ + int x_push_in = GetSystemMetrics(SM_CYFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER); + int y_push_in = GetSystemMetrics(SM_CXFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER); + r->top += x_push_in; + r->left += y_push_in; + r->right -= x_push_in; + r->bottom -= y_push_in; + } + }break; + + case WM_NCACTIVATE: + { + result = 1; + window_is_active = wParam; + // A convenient function for checking if a window is minimized. + if (IsIconic(hwnd)){ + result = DefWindowProcW(hwnd, uMsg, wParam, -1); + } + }break; + + case WM_NCHITTEST: + { + POINT pos; + pos.x = GET_X_LPARAM(lParam); + pos.y = GET_Y_LPARAM(lParam); + + RECT frame_rect; + GetWindowRect(hwnd, &frame_rect); + if (!HitTest(pos.x, pos.y, frame_rect)){ + result = HTNOWHERE; + } + + else{ + + RECT rect; + GetClientRect(hwnd, &rect); + ScreenToClient(hwnd, &pos); + + // Borders + int l = 0; + int r = 0; + int b = 0; + int t = 0; + if (!IsZoomed(hwnd)){ + if (rect.left <= pos.x && pos.x < rect.left + border_width){ + l = 1; + } + if (rect.right - border_width <= pos.x && pos.x < rect.right){ + r = 1; + } + if (rect.bottom - border_width <= pos.y && pos.y < rect.bottom){ + b = 1; + } + if (rect.top <= pos.y && pos.y < rect.top + border_width){ + t = 1; + } + } + if (l){ + if (t){ + result = HTTOPLEFT; + } + else if (b){ + result = HTBOTTOMLEFT; + } + else{ + result = HTLEFT; + } + } + else if (r){ + if (t){ + result = HTTOPRIGHT; + } + else if (b){ + result = HTBOTTOMRIGHT; + } + else{ + result = HTRIGHT; + } + } + else if (t){ + result = HTTOP; + } + else if (b){ + result = HTBOTTOM; + } + + // Inside + else{ + if (rect.top <= pos.y && pos.y < rect.top + caption_width){ + result = HTCAPTION; + for (int i = 0; i < embedded_widget_count; i += 1){ + if (HitTest(pos.x, pos.y, embedded_widget_rect[i])){ + result = HTCLIENT; + break; + } } - }break; - - case WM_NCACTIVATE: - { - result = 1; - window_is_active = wParam; - // A convenient function for checking if a window is minimized. - if (IsIconic(hwnd)){ - result = DefWindowProcW(hwnd, uMsg, wParam, -1); - } - }break; - - case WM_NCHITTEST: - { - POINT pos; - pos.x = GET_X_LPARAM(lParam); - pos.y = GET_Y_LPARAM(lParam); - - RECT frame_rect; - GetWindowRect(hwnd, &frame_rect); - if (!HitTest(pos.x, pos.y, frame_rect)){ - result = HTNOWHERE; - } - - else{ - - RECT rect; - GetClientRect(hwnd, &rect); - ScreenToClient(hwnd, &pos); - - // Borders - int l = 0; - int r = 0; - int b = 0; - int t = 0; - if (!IsZoomed(hwnd)){ - if (rect.left <= pos.x && pos.x < rect.left + border_width){ - l = 1; - } - if (rect.right - border_width <= pos.x && pos.x < rect.right){ - r = 1; - } - if (rect.bottom - border_width <= pos.y && pos.y < rect.bottom){ - b = 1; - } - if (rect.top <= pos.y && pos.y < rect.top + border_width){ - t = 1; - } - } - if (l){ - if (t){ - result = HTTOPLEFT; - } - else if (b){ - result = HTBOTTOMLEFT; - } - else{ - result = HTLEFT; - } - } - else if (r){ - if (t){ - result = HTTOPRIGHT; - } - else if (b){ - result = HTBOTTOMRIGHT; - } - else{ - result = HTRIGHT; - } - } - else if (t){ - result = HTTOP; - } - else if (b){ - result = HTBOTTOM; - } - - // Inside - else{ - if (rect.top <= pos.y && pos.y < rect.top + caption_width){ - result = HTCAPTION; - for (int i = 0; i < embedded_widget_count; i += 1){ - if (HitTest(pos.x, pos.y, embedded_widget_rect[i])){ - result = HTCLIENT; - break; - } - } - } - else{ - result = HTCLIENT; - } - } - } - - }break; - - case WM_CLOSE: - { - keep_running = 0; - }break; - - case WM_SIZING: - { - InvalidateRect(hwnd, 0, 0); - }break; - - case WM_PAINT: - { - PAINTSTRUCT ps; - BeginPaint(hwnd, &ps); - embedded_widget_count = 0; - UpdateAndRender(hwnd, 0); - EndPaint(hwnd, &ps); - }break; - - case WM_LBUTTONDOWN: - { - Input_Event *event = PushEvent(); - if (event != 0){ - event->kind = InputEventKind_MouseLeftPress; - } - }break; - - case WM_LBUTTONUP: - { - Input_Event *event = PushEvent(); - if (event != 0){ - event->kind = InputEventKind_MouseLeftRelease; - } - }break; - - default: - { - result = DefWindowProcW(hwnd, uMsg, wParam, lParam); - }break; - } - return(result); + } + else{ + result = HTCLIENT; + } + } + } + + }break; + + case WM_CLOSE: + { + keep_running = 0; + }break; + + case WM_SIZING: + { + InvalidateRect(hwnd, 0, 0); + }break; + + case WM_PAINT: + { + PAINTSTRUCT ps; + BeginPaint(hwnd, &ps); + embedded_widget_count = 0; + UpdateAndRender(hwnd, 0); + EndPaint(hwnd, &ps); + }break; + + case WM_LBUTTONDOWN: + { + Input_Event *event = PushEvent(); + if (event != 0){ + event->kind = InputEventKind_MouseLeftPress; + } + }break; + + case WM_LBUTTONUP: + { + Input_Event *event = PushEvent(); + if (event != 0){ + event->kind = InputEventKind_MouseLeftRelease; + } + }break; + + default: + { + result = DefWindowProcW(hwnd, uMsg, wParam, lParam); + }break; + } + return(result); } int @@ -280,137 +280,139 @@ WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd){ - + #define WINDOW_CLASS L"MainWindow" + + WNDCLASSW window_class = {0}; + window_class.style = 0; + window_class.lpfnWndProc = CustomBorderWindowProc; + window_class.hInstance = hInstance; + window_class.hCursor = LoadCursorA(0, IDC_ARROW); + window_class.lpszClassName = WINDOW_CLASS; + + ATOM atom = RegisterClassW(&window_class); + if (atom == 0){ + fprintf(stderr, "RegisterClassW failed\n"); + exit(1); + } + + DWORD style = WS_OVERLAPPEDWINDOW; + HWND hwnd = CreateWindowW(WINDOW_CLASS, + L"Window Name", + style, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + 0, + 0, + hInstance, + 0); + if (hwnd == 0){ + fprintf(stderr, "CreateWindowExW failed\n"); + exit(1); + } + + SetWindowTheme(hwnd, L" ", L" "); + + + BOOL composition_enabled; + if (DwmIsCompositionEnabled(&composition_enabled) != S_OK){ + fprintf(stderr, "DwmIsCompositionEnabled failed\n"); + composition_enabled = 0; + } + fprintf(stdout, "Has compositor? %s\n", composition_enabled?"Yes":"No"); + + ShowWindow(hwnd, SW_SHOW); + + keep_running = 1; + for (;keep_running;){ + input.first_event = 0; + input.last_event = 0; + input_event_cursor = 0; - WNDCLASSW window_class = {0}; - window_class.style = 0; - window_class.lpfnWndProc = CustomBorderWindowProc; - window_class.hInstance = hInstance; - window_class.hCursor = LoadCursorA(0, IDC_ARROW); - window_class.lpszClassName = WINDOW_CLASS; + MSG msg; - ATOM atom = RegisterClassW(&window_class); - if (atom == 0){ - fprintf(stderr, "RegisterClassW failed\n"); - exit(1); + if (minimize_at_end_of_update){ + ShowWindow(hwnd, SW_MINIMIZE); + } + else if (toggle_maximize_at_end_of_update){ + if (IsZoomed(hwnd)){ + ShowWindow(hwnd, SW_RESTORE); + } + else{ + ShowWindow(hwnd, SW_MAXIMIZE); + } + } + else{ + GetMessage(&msg, 0, 0, 0); + TranslateMessage(&msg); + DispatchMessage(&msg); } - DWORD style = WS_OVERLAPPEDWINDOW; - HWND hwnd = CreateWindowW(WINDOW_CLASS, - L"Window Name", - style, - CW_USEDEFAULT, - CW_USEDEFAULT, - CW_USEDEFAULT, - CW_USEDEFAULT, - 0, - 0, - hInstance, - 0); - if (hwnd == 0){ - fprintf(stderr, "CreateWindowExW failed\n"); - exit(1); + for (;PeekMessageW(&msg, 0, 0, 0, PM_REMOVE);){ + TranslateMessage(&msg); + DispatchMessage(&msg); } - SetWindowTheme(hwnd, L" ", L" "); + POINT mpos; + GetCursorPos(&mpos); + ScreenToClient(hwnd, &mpos); + input.mouse_x = mpos.x; + input.mouse_y = mpos.y; + input.left = (GetKeyState(VK_LBUTTON)&(1 << 15)); + minimize_at_end_of_update = 0; + toggle_maximize_at_end_of_update = 0; + embedded_widget_count = 0; + UpdateAndRender(hwnd, &input); - BOOL composition_enabled; - if (DwmIsCompositionEnabled(&composition_enabled) != S_OK){ - fprintf(stderr, "DwmIsCompositionEnabled failed\n"); - composition_enabled = 0; + if (composition_enabled){ + DwmFlush(); } - fprintf(stdout, "Has compositor? %s\n", composition_enabled?"Yes":"No"); - - ShowWindow(hwnd, SW_SHOW); - - keep_running = 1; - for (;keep_running;){ - input.first_event = 0; - input.last_event = 0; - input_event_cursor = 0; - - MSG msg; - - if (minimize_at_end_of_update){ - ShowWindow(hwnd, SW_MINIMIZE); - } - else if (toggle_maximize_at_end_of_update){ - if (IsZoomed(hwnd)){ - ShowWindow(hwnd, SW_RESTORE); - } - else{ - ShowWindow(hwnd, SW_MAXIMIZE); - } - } - else{ - GetMessage(&msg, 0, 0, 0); - TranslateMessage(&msg); - DispatchMessage(&msg); - } - - for (;PeekMessageW(&msg, 0, 0, 0, PM_REMOVE);){ - TranslateMessage(&msg); - DispatchMessage(&msg); - } - - POINT mpos; - GetCursorPos(&mpos); - ScreenToClient(hwnd, &mpos); - input.mouse_x = mpos.x; - input.mouse_y = mpos.y; - input.left = (GetKeyState(VK_LBUTTON)&(1 << 15)); - - minimize_at_end_of_update = 0; - toggle_maximize_at_end_of_update = 0; - embedded_widget_count = 0; - UpdateAndRender(hwnd, &input); - - if (composition_enabled){ - DwmFlush(); - } - else{ - Sleep(33); - } + else{ + Sleep(33); } - - return(0); + } + + return(0); } //////////////////////////////// int HasEvent(Input *input, Input_Event_Kind kind){ - int result = 0; - if (input != 0){ - Input_Event **ptr_to = &input->first_event; - Input_Event *last = 0; - for (Input_Event *event = input->first_event, *next = 0; - event != 0; - event = next){ - next = event->next; - if (event->kind == kind){ - result = 1; - *ptr_to = next; - break; - } - else{ - ptr_to = &event->next; - last = event; - } + int result = 0; + if (input != 0){ + Input_Event **ptr_to = &input->first_event; + Input_Event *last = 0; + for (Input_Event *event = input->first_event, *next = 0; + event != 0; + event = next){ + next = event->next; + if (event->kind == kind){ + result = 1; + *ptr_to = next; + if (event == input->last_event){ + input->last_event = last; } - input->last_event = last; + break; + } + else{ + ptr_to = &event->next; + last = event; + } } - return(result); + } + return(result); } typedef enum{ - Widget_None, - Widget_Close, - Widget_Minimize, - Widget_Maximize, - Widget_Slider, + Widget_None, + Widget_Close, + Widget_Minimize, + Widget_Maximize, + Widget_Slider, } Widget; Widget click_started = Widget_None; @@ -419,166 +421,166 @@ int delta_from_mouse_x = 0; void UpdateAndRender(HWND hwnd, Input *input){ - RECT window_rect; - GetClientRect(hwnd, &window_rect); + RECT window_rect; + GetClientRect(hwnd, &window_rect); + + RECT inside_rect; + inside_rect.top = window_rect.top + caption_width; + if (!IsZoomed(hwnd)){ + inside_rect.left = window_rect.left + border_width; + inside_rect.right = window_rect.right - border_width; + inside_rect.bottom = window_rect.bottom - border_width; + } + else{ + inside_rect.left = window_rect.left; + inside_rect.right = window_rect.right; + inside_rect.bottom = window_rect.bottom; + } + + HDC dc = GetDC(hwnd); + { + SelectObject(dc, GetStockObject(DC_PEN)); + SelectObject(dc, GetStockObject(DC_BRUSH)); - RECT inside_rect; - inside_rect.top = window_rect.top + caption_width; - if (!IsZoomed(hwnd)){ - inside_rect.left = window_rect.left + border_width; - inside_rect.right = window_rect.right - border_width; - inside_rect.bottom = window_rect.bottom - border_width; - } - else{ - inside_rect.left = window_rect.left; - inside_rect.right = window_rect.right; - inside_rect.bottom = window_rect.bottom; - } - - HDC dc = GetDC(hwnd); + // Window border { - SelectObject(dc, GetStockObject(DC_PEN)); - SelectObject(dc, GetStockObject(DC_BRUSH)); - - // Window border - { - if (WindowIsActive()){ - SetDCPenColor(dc, RGB(255, 200, 0)); - SetDCBrushColor(dc, RGB(255, 200, 0)); - } - else{ - SetDCPenColor(dc, RGB(128, 100, 0)); - SetDCBrushColor(dc, RGB(128, 100, 0)); - } - - Rectangle(dc, window_rect.left, window_rect.top, window_rect.right, inside_rect.top); - Rectangle(dc, window_rect.left, inside_rect.bottom, window_rect.right, window_rect.bottom); - Rectangle(dc, window_rect.left, inside_rect.top, inside_rect.left, inside_rect.bottom); - Rectangle(dc, inside_rect.right, inside_rect.top, window_rect.right, inside_rect.bottom); - } - - // Close button - if (window_rect.right > 20){ - RECT button; - button.left = window_rect.right - 20; - button.top = window_rect.top + 10; - button.right = window_rect.right - 10; - button.bottom = window_rect.top + 20; - - SetDCPenColor(dc, RGB(180, 0, 0)); - SetDCBrushColor(dc, RGB(180, 0, 0)); - - EmbeddedWidgetRect(button); - Rectangle(dc, button.left, button.top, button.right, button.bottom); - if (input != 0 && HitTest(input->mouse_x, input->mouse_y, button)){ - if (HasEvent(input, InputEventKind_MouseLeftPress)){ - click_started = Widget_Close; - } - if (click_started == Widget_Close && HasEvent(input, InputEventKind_MouseLeftRelease)){ - StopRunning(); - } - } - } - - // Minimize button - if (window_rect.right > 40){ - RECT button; - button.left = window_rect.right - 40; - button.top = window_rect.top + 10; - button.right = window_rect.right - 30; - button.bottom = window_rect.top + 20; - - SetDCPenColor(dc, RGB(0, 0, 180)); - SetDCBrushColor(dc, RGB(0, 0, 180)); - - EmbeddedWidgetRect(button); - Rectangle(dc, button.left, button.top, button.right, button.bottom); - if (input != 0 && HitTest(input->mouse_x, input->mouse_y, button)){ - if (HasEvent(input, InputEventKind_MouseLeftPress)){ - click_started = Widget_Minimize; - } - if (click_started == Widget_Minimize && HasEvent(input, InputEventKind_MouseLeftRelease)){ - Minimize(hwnd); - } - } - } - - // Maximize button - if (window_rect.right > 60){ - RECT button; - button.left = window_rect.right - 60; - button.top = window_rect.top + 10; - button.right = window_rect.right - 50; - button.bottom = window_rect.top + 20; - - SetDCPenColor(dc, RGB(0, 180, 0)); - SetDCBrushColor(dc, RGB(0, 180, 0)); - - EmbeddedWidgetRect(button); - Rectangle(dc, button.left, button.top, button.right, button.bottom); - if (input != 0 && HitTest(input->mouse_x, input->mouse_y, button)){ - if (HasEvent(input, InputEventKind_MouseLeftPress)){ - click_started = Widget_Maximize; - } - if (click_started == Widget_Maximize && HasEvent(input, InputEventKind_MouseLeftRelease)){ - ToggleMaximize(hwnd); - } - } - } - - // Slider - if (window_rect.right > 200){ - RECT track; - track.left = window_rect.right - 195; - track.top = window_rect.top + 14; - track.right = window_rect.right - 95; - track.bottom = window_rect.top + 16; - - SetDCPenColor(dc, RGB(0, 0, 0)); - SetDCBrushColor(dc, RGB(0, 0, 0)); - Rectangle(dc, track.left, track.top, track.right, track.bottom); - - RECT button; - button.left = window_rect.right - 200 + slider_x; - button.top = window_rect.top + 10; - button.right = window_rect.right - 190 + slider_x; - button.bottom = window_rect.top + 20; - - SetDCPenColor(dc, RGB(100, 200, 200)); - SetDCBrushColor(dc, RGB(100, 200, 200)); - - EmbeddedWidgetRect(button); - Rectangle(dc, button.left, button.top, button.right, button.bottom); - if (input != 0){ - if (HitTest(input->mouse_x, input->mouse_y, button)){ - if (HasEvent(input, InputEventKind_MouseLeftPress)){ - click_started = Widget_Slider; - delta_from_mouse_x = slider_x - input->mouse_x; - } - } - if (click_started == Widget_Slider){ - slider_x = delta_from_mouse_x + input->mouse_x; - if (slider_x < 0){ - slider_x = 0; - } - if (slider_x > 100){ - slider_x = 100; - } - } - } - } - - if (input != 0 && !input->left){ - click_started = Widget_None; - } - - // Inside area - { - SetDCPenColor(dc, RGB(127, 127, 127)); - SetDCBrushColor(dc, RGB(127, 127, 127)); - Rectangle(dc, inside_rect.left, inside_rect.top, inside_rect.right, inside_rect.bottom); - } + if (WindowIsActive()){ + SetDCPenColor(dc, RGB(255, 200, 0)); + SetDCBrushColor(dc, RGB(255, 200, 0)); + } + else{ + SetDCPenColor(dc, RGB(128, 100, 0)); + SetDCBrushColor(dc, RGB(128, 100, 0)); + } + + Rectangle(dc, window_rect.left, window_rect.top, window_rect.right, inside_rect.top); + Rectangle(dc, window_rect.left, inside_rect.bottom, window_rect.right, window_rect.bottom); + Rectangle(dc, window_rect.left, inside_rect.top, inside_rect.left, inside_rect.bottom); + Rectangle(dc, inside_rect.right, inside_rect.top, window_rect.right, inside_rect.bottom); } - ReleaseDC(hwnd, dc); + + // Close button + if (window_rect.right > 20){ + RECT button; + button.left = window_rect.right - 20; + button.top = window_rect.top + 10; + button.right = window_rect.right - 10; + button.bottom = window_rect.top + 20; + + SetDCPenColor(dc, RGB(180, 0, 0)); + SetDCBrushColor(dc, RGB(180, 0, 0)); + + EmbeddedWidgetRect(button); + Rectangle(dc, button.left, button.top, button.right, button.bottom); + if (input != 0 && HitTest(input->mouse_x, input->mouse_y, button)){ + if (HasEvent(input, InputEventKind_MouseLeftPress)){ + click_started = Widget_Close; + } + if (click_started == Widget_Close && HasEvent(input, InputEventKind_MouseLeftRelease)){ + StopRunning(); + } + } + } + + // Minimize button + if (window_rect.right > 40){ + RECT button; + button.left = window_rect.right - 40; + button.top = window_rect.top + 10; + button.right = window_rect.right - 30; + button.bottom = window_rect.top + 20; + + SetDCPenColor(dc, RGB(0, 0, 180)); + SetDCBrushColor(dc, RGB(0, 0, 180)); + + EmbeddedWidgetRect(button); + Rectangle(dc, button.left, button.top, button.right, button.bottom); + if (input != 0 && HitTest(input->mouse_x, input->mouse_y, button)){ + if (HasEvent(input, InputEventKind_MouseLeftPress)){ + click_started = Widget_Minimize; + } + if (click_started == Widget_Minimize && HasEvent(input, InputEventKind_MouseLeftRelease)){ + Minimize(hwnd); + } + } + } + + // Maximize button + if (window_rect.right > 60){ + RECT button; + button.left = window_rect.right - 60; + button.top = window_rect.top + 10; + button.right = window_rect.right - 50; + button.bottom = window_rect.top + 20; + + SetDCPenColor(dc, RGB(0, 180, 0)); + SetDCBrushColor(dc, RGB(0, 180, 0)); + + EmbeddedWidgetRect(button); + Rectangle(dc, button.left, button.top, button.right, button.bottom); + if (input != 0 && HitTest(input->mouse_x, input->mouse_y, button)){ + if (HasEvent(input, InputEventKind_MouseLeftPress)){ + click_started = Widget_Maximize; + } + if (click_started == Widget_Maximize && HasEvent(input, InputEventKind_MouseLeftRelease)){ + ToggleMaximize(hwnd); + } + } + } + + // Slider + if (window_rect.right > 200){ + RECT track; + track.left = window_rect.right - 195; + track.top = window_rect.top + 14; + track.right = window_rect.right - 95; + track.bottom = window_rect.top + 16; + + SetDCPenColor(dc, RGB(0, 0, 0)); + SetDCBrushColor(dc, RGB(0, 0, 0)); + Rectangle(dc, track.left, track.top, track.right, track.bottom); + + RECT button; + button.left = window_rect.right - 200 + slider_x; + button.top = window_rect.top + 10; + button.right = window_rect.right - 190 + slider_x; + button.bottom = window_rect.top + 20; + + SetDCPenColor(dc, RGB(100, 200, 200)); + SetDCBrushColor(dc, RGB(100, 200, 200)); + + EmbeddedWidgetRect(button); + Rectangle(dc, button.left, button.top, button.right, button.bottom); + if (input != 0){ + if (HitTest(input->mouse_x, input->mouse_y, button)){ + if (HasEvent(input, InputEventKind_MouseLeftPress)){ + click_started = Widget_Slider; + delta_from_mouse_x = slider_x - input->mouse_x; + } + } + if (click_started == Widget_Slider){ + slider_x = delta_from_mouse_x + input->mouse_x; + if (slider_x < 0){ + slider_x = 0; + } + if (slider_x > 100){ + slider_x = 100; + } + } + } + } + + if (input != 0 && !input->left){ + click_started = Widget_None; + } + + // Inside area + { + SetDCPenColor(dc, RGB(127, 127, 127)); + SetDCBrushColor(dc, RGB(127, 127, 127)); + Rectangle(dc, inside_rect.left, inside_rect.top, inside_rect.right, inside_rect.bottom); + } + } + ReleaseDC(hwnd, dc); }