xref: /reactos/sdk/include/reactos/ui/layout.h (revision d4433ee0)
1 /*
2  * PROJECT:     ReactOS headers
3  * LICENSE:     LGPL-2.0-or-later (https://spdx.org/licenses/LGPL-2.0-or-later)
4  * PURPOSE:     The layout engine of resizable dialog boxes / windows
5  * COPYRIGHT:   Copyright 2020-2021 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
6  */
7 #pragma once
8 #include <assert.h>
9 
10 typedef struct LAYOUT_INFO {
11     UINT m_nCtrlID;
12     UINT m_uEdges; /* BF_* flags */
13     HWND m_hwndCtrl;
14     SIZE m_margin1;
15     SIZE m_margin2;
16 } LAYOUT_INFO;
17 
18 typedef struct LAYOUT_DATA {
19     HWND m_hwndParent;
20     HWND m_hwndGrip;
21     LAYOUT_INFO *m_pLayouts;
22     UINT m_cLayouts;
23 } LAYOUT_DATA;
24 
25 static __inline void
_layout_ModifySystemMenu(LAYOUT_DATA * pData,BOOL bEnableResize)26 _layout_ModifySystemMenu(LAYOUT_DATA *pData, BOOL bEnableResize)
27 {
28     if (bEnableResize)
29     {
30         GetSystemMenu(pData->m_hwndParent, TRUE); /* revert */
31     }
32     else
33     {
34         HMENU hSysMenu = GetSystemMenu(pData->m_hwndParent, FALSE);
35         RemoveMenu(hSysMenu, SC_MAXIMIZE, MF_BYCOMMAND);
36         RemoveMenu(hSysMenu, SC_SIZE, MF_BYCOMMAND);
37         RemoveMenu(hSysMenu, SC_RESTORE, MF_BYCOMMAND);
38     }
39 }
40 
41 static __inline HDWP
_layout_MoveGrip(LAYOUT_DATA * pData,HDWP hDwp OPTIONAL)42 _layout_MoveGrip(LAYOUT_DATA *pData, HDWP hDwp OPTIONAL)
43 {
44     if (!IsWindowVisible(pData->m_hwndGrip))
45         return hDwp;
46 
47     SIZE size = { GetSystemMetrics(SM_CXVSCROLL), GetSystemMetrics(SM_CYHSCROLL) };
48     const UINT uFlags = SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER | SWP_NOCOPYBITS;
49     RECT rcClient;
50     GetClientRect(pData->m_hwndParent, &rcClient);
51 
52     if (hDwp)
53     {
54         hDwp = DeferWindowPos(hDwp, pData->m_hwndGrip, NULL,
55                               rcClient.right - size.cx, rcClient.bottom - size.cy,
56                               size.cx, size.cy, uFlags);
57     }
58     else
59     {
60         SetWindowPos(pData->m_hwndGrip, NULL,
61                      rcClient.right - size.cx, rcClient.bottom - size.cy,
62                      size.cx, size.cy, uFlags);
63     }
64     return hDwp;
65 }
66 
67 static __inline void
LayoutShowGrip(LAYOUT_DATA * pData,BOOL bShow)68 LayoutShowGrip(LAYOUT_DATA *pData, BOOL bShow)
69 {
70     UINT uSWP = SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE |
71                 SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_FRAMECHANGED;
72     DWORD style = GetWindowLongPtrW(pData->m_hwndParent, GWL_STYLE);
73     DWORD new_style = (bShow ? (style | WS_SIZEBOX) : (style & ~WS_SIZEBOX));
74     if (style != new_style)
75     {
76         SetWindowLongPtrW(pData->m_hwndParent, GWL_STYLE, new_style); /* change style */
77         SetWindowPos(pData->m_hwndParent, NULL, 0, 0, 0, 0, uSWP); /* frame changed */
78     }
79 
80     if (!bShow)
81     {
82         ShowWindow(pData->m_hwndGrip, SW_HIDE);
83         return;
84     }
85 
86     if (pData->m_hwndGrip == NULL)
87     {
88         /* CORE-19585: WS_GROUP set to avoid navigation over the grip control */
89         DWORD style = WS_GROUP | WS_CHILD | WS_CLIPSIBLINGS | SBS_SIZEGRIP;
90         pData->m_hwndGrip = CreateWindowExW(0, L"SCROLLBAR", NULL, style,
91                                             0, 0, 0, 0, pData->m_hwndParent,
92                                             NULL, GetModuleHandleW(NULL), NULL);
93     }
94     _layout_MoveGrip(pData, NULL);
95     ShowWindow(pData->m_hwndGrip, SW_SHOWNOACTIVATE);
96 }
97 
98 static __inline void
_layout_GetPercents(LPRECT prcPercents,UINT uEdges)99 _layout_GetPercents(LPRECT prcPercents, UINT uEdges)
100 {
101     prcPercents->left = (uEdges & BF_LEFT) ? 0 : 100;
102     prcPercents->right = (uEdges & BF_RIGHT) ? 100 : 0;
103     prcPercents->top = (uEdges & BF_TOP) ? 0 : 100;
104     prcPercents->bottom = (uEdges & BF_BOTTOM) ? 100 : 0;
105 }
106 
107 static __inline HDWP
_layout_DoMoveItem(LAYOUT_DATA * pData,HDWP hDwp,const LAYOUT_INFO * pLayout,const RECT * rcClient)108 _layout_DoMoveItem(LAYOUT_DATA *pData, HDWP hDwp, const LAYOUT_INFO *pLayout,
109                    const RECT *rcClient)
110 {
111     RECT rcChild, NewRect, rcPercents;
112     LONG nWidth, nHeight;
113 
114     if (!GetWindowRect(pLayout->m_hwndCtrl, &rcChild))
115         return hDwp;
116     MapWindowPoints(NULL, pData->m_hwndParent, (LPPOINT)&rcChild, 2);
117 
118     nWidth = rcClient->right - rcClient->left;
119     nHeight = rcClient->bottom - rcClient->top;
120 
121     _layout_GetPercents(&rcPercents, pLayout->m_uEdges);
122     NewRect.left = pLayout->m_margin1.cx + nWidth * rcPercents.left / 100;
123     NewRect.top = pLayout->m_margin1.cy + nHeight * rcPercents.top / 100;
124     NewRect.right = pLayout->m_margin2.cx + nWidth * rcPercents.right / 100;
125     NewRect.bottom = pLayout->m_margin2.cy + nHeight * rcPercents.bottom / 100;
126 
127     if (!EqualRect(&NewRect, &rcChild))
128     {
129         hDwp = DeferWindowPos(hDwp, pLayout->m_hwndCtrl, NULL, NewRect.left, NewRect.top,
130                               NewRect.right - NewRect.left, NewRect.bottom - NewRect.top,
131                               SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREPOSITION | SWP_NOCOPYBITS);
132     }
133     return hDwp;
134 }
135 
136 static __inline void
_layout_ArrangeLayout(LAYOUT_DATA * pData)137 _layout_ArrangeLayout(LAYOUT_DATA *pData)
138 {
139     RECT rcClient;
140     UINT iItem;
141     HDWP hDwp = BeginDeferWindowPos(pData->m_cLayouts + 1);
142     if (hDwp == NULL)
143         return;
144 
145     GetClientRect(pData->m_hwndParent, &rcClient);
146 
147     for (iItem = 0; iItem < pData->m_cLayouts; ++iItem)
148         hDwp = _layout_DoMoveItem(pData, hDwp, &pData->m_pLayouts[iItem], &rcClient);
149 
150     hDwp = _layout_MoveGrip(pData, hDwp);
151     EndDeferWindowPos(hDwp);
152 }
153 
154 static __inline void
_layout_InitLayouts(LAYOUT_DATA * pData)155 _layout_InitLayouts(LAYOUT_DATA *pData)
156 {
157     RECT rcClient, rcChild, rcPercents;
158     LONG nWidth, nHeight;
159     UINT iItem;
160 
161     GetClientRect(pData->m_hwndParent, &rcClient);
162     nWidth = rcClient.right - rcClient.left;
163     nHeight = rcClient.bottom - rcClient.top;
164 
165     for (iItem = 0; iItem < pData->m_cLayouts; ++iItem)
166     {
167         LAYOUT_INFO *pInfo = &pData->m_pLayouts[iItem];
168         if (pInfo->m_hwndCtrl == NULL)
169         {
170             pInfo->m_hwndCtrl = GetDlgItem(pData->m_hwndParent, pInfo->m_nCtrlID);
171             if (pInfo->m_hwndCtrl == NULL)
172                 continue;
173         }
174 
175         GetWindowRect(pInfo->m_hwndCtrl, &rcChild);
176         MapWindowPoints(NULL, pData->m_hwndParent, (LPPOINT)&rcChild, 2);
177 
178         _layout_GetPercents(&rcPercents, pInfo->m_uEdges);
179         pInfo->m_margin1.cx = rcChild.left - nWidth * rcPercents.left / 100;
180         pInfo->m_margin1.cy = rcChild.top - nHeight * rcPercents.top / 100;
181         pInfo->m_margin2.cx = rcChild.right - nWidth * rcPercents.right / 100;
182         pInfo->m_margin2.cy = rcChild.bottom - nHeight * rcPercents.bottom / 100;
183     }
184 }
185 
186 /* NOTE: Please call LayoutUpdate on parent's WM_SIZE. */
187 static __inline void
LayoutUpdate(HWND ignored1,LAYOUT_DATA * pData,LPCVOID ignored2,UINT ignored3)188 LayoutUpdate(HWND ignored1, LAYOUT_DATA *pData, LPCVOID ignored2, UINT ignored3)
189 {
190     UNREFERENCED_PARAMETER(ignored1);
191     UNREFERENCED_PARAMETER(ignored2);
192     UNREFERENCED_PARAMETER(ignored3);
193     if (pData == NULL || !pData->m_hwndParent)
194         return;
195     assert(IsWindow(pData->m_hwndParent));
196     _layout_ArrangeLayout(pData);
197 }
198 
199 static __inline void
LayoutEnableResize(LAYOUT_DATA * pData,BOOL bEnable)200 LayoutEnableResize(LAYOUT_DATA *pData, BOOL bEnable)
201 {
202     LayoutShowGrip(pData, bEnable);
203     _layout_ModifySystemMenu(pData, bEnable);
204 }
205 
206 static __inline LAYOUT_DATA *
LayoutInit(HWND hwndParent,const LAYOUT_INFO * pLayouts,INT cLayouts)207 LayoutInit(HWND hwndParent, const LAYOUT_INFO *pLayouts, INT cLayouts)
208 {
209     BOOL bShowGrip;
210     SIZE_T cb;
211     LAYOUT_DATA *pData = (LAYOUT_DATA *)HeapAlloc(GetProcessHeap(), 0, sizeof(LAYOUT_DATA));
212     if (pData == NULL)
213     {
214         assert(0);
215         return NULL;
216     }
217 
218     if (cLayouts < 0) /* NOTE: If cLayouts was negative, then don't show size grip */
219     {
220         cLayouts = -cLayouts;
221         bShowGrip = FALSE;
222     }
223     else
224     {
225         bShowGrip = TRUE;
226     }
227 
228     cb = cLayouts * sizeof(LAYOUT_INFO);
229     pData->m_cLayouts = cLayouts;
230     pData->m_pLayouts = (LAYOUT_INFO *)HeapAlloc(GetProcessHeap(), 0, cb);
231     if (pData->m_pLayouts == NULL)
232     {
233         assert(0);
234         HeapFree(GetProcessHeap(), 0, pData);
235         return NULL;
236     }
237     memcpy(pData->m_pLayouts, pLayouts, cb);
238 
239     assert(IsWindow(hwndParent));
240 
241     pData->m_hwndParent = hwndParent;
242 
243     pData->m_hwndGrip = NULL;
244     if (bShowGrip)
245         LayoutShowGrip(pData, bShowGrip);
246 
247     _layout_InitLayouts(pData);
248     return pData;
249 }
250 
251 static __inline void
LayoutDestroy(LAYOUT_DATA * pData)252 LayoutDestroy(LAYOUT_DATA *pData)
253 {
254     if (!pData)
255         return;
256     HeapFree(GetProcessHeap(), 0, pData->m_pLayouts);
257     HeapFree(GetProcessHeap(), 0, pData);
258 }
259