fbd62e65492933080c7b59871d225e288a8c139f
[reactos.git] / reactos / base / applications / rapps / include / rosui.h
1 /*
2 * PROJECT: ReactOS UI Layout Engine
3 * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4 * FILE: base/applications/rapps/include/rosui.h
5 * PURPOSE: ATL Layout engine for RAPPS
6 * COPYRIGHT: Copyright 2015 David Quintana (gigaherz@gmail.com)
7 * Copyright 2017 Alexander Shaposhnikov (chaez.san@gmail.com)
8 */
9 #pragma once
10
11 #include <atlwin.h>
12
13 template<class T, INT GrowthRate = 10>
14 class CPointerArray
15 {
16 protected:
17 HDPA m_hDpa;
18
19 public:
20 CPointerArray()
21 {
22 m_hDpa = DPA_Create(GrowthRate);
23 }
24
25 ~CPointerArray()
26 {
27 DPA_DestroyCallback(m_hDpa, s_OnRemoveItem, this);
28 }
29
30 private:
31 static INT CALLBACK s_OnRemoveItem(PVOID ptr, PVOID context)
32 {
33 CPointerArray * self = (CPointerArray*) context;
34 return (INT) self->OnRemoveItem(reinterpret_cast<T*>(ptr));
35 }
36
37 static INT CALLBACK s_OnCompareItems(PVOID p1, PVOID p2, LPARAM lParam)
38 {
39 CPointerArray * self = (CPointerArray*) lParam;
40 return self->OnCompareItems(reinterpret_cast<T*>(p1), reinterpret_cast<T*>(p2));
41 }
42
43 public:
44 virtual BOOL OnRemoveItem(T * ptr)
45 {
46 return TRUE;
47 }
48
49 virtual INT OnCompareItems(T * p1, T * p2)
50 {
51 INT t = (reinterpret_cast<INT>(p2) - reinterpret_cast<INT>(p1));
52 if (t > 0)
53 return 1;
54 if (t < 0)
55 return -1;
56 return 0;
57 }
58
59 public:
60 INT GetCount() const
61 {
62 return DPA_GetPtrCount(m_hDpa);
63 }
64
65 T* Get(INT i) const
66 {
67 return (T*) DPA_GetPtr(m_hDpa, i);
68 }
69
70 BOOL Set(INT i, T* ptr)
71 {
72 return DPA_SetPtr(m_hDpa, i, ptr);
73 }
74
75 INT Insert(INT at, T* ptr)
76 {
77 return DPA_InsertPtr(m_hDpa, at, ptr);
78 }
79
80 INT Append(T* ptr)
81 {
82 return DPA_InsertPtr(m_hDpa, DA_LAST, ptr);
83 }
84
85 INT IndexOf(T* ptr) const
86 {
87 return DPA_GetPtrIndex(m_hDpa, ptr);
88 }
89
90 BOOL Remove(T* ptr)
91 {
92 INT i = IndexOf(ptr);
93 if (i < 0)
94 return FALSE;
95 return RemoveAt(i);
96 }
97
98 BOOL RemoveAt(INT i)
99 {
100 T* ptr = (T*) DPA_GetPtr(m_hDpa, i);
101 OnRemoveItem(ptr);
102 return DPA_DeletePtr(m_hDpa, i);
103 }
104
105 BOOL Clear()
106 {
107 DPA_EnumCallback(s_OnRemoveItem, this);
108 return DPA_DeleteAllPtrs(m_hDpa);
109 }
110
111 BOOL Sort()
112 {
113 return DPA_Sort(m_hDpa, s_OnCompareItems, (LPARAM)this);
114 }
115
116 INT Search(T* item, INT iStart, UINT uFlags)
117 {
118 return DPA_Search(m_hDpa, s_OnCompareItems, (LPARAM)this);
119 }
120 };
121
122 class CUiRect
123 : public RECT
124 {
125 public:
126 CUiRect()
127 {
128 left = right = top = bottom = 0;
129 }
130
131 CUiRect(INT l, INT t, INT r, INT b)
132 {
133 left = l;
134 right = r;
135 top = t;
136 bottom = b;
137 }
138 };
139
140 class CUiMargin
141 : public CUiRect
142 {
143 public:
144 CUiMargin()
145 {
146 }
147
148 CUiMargin(INT all)
149 : CUiRect(all, all, all, all)
150 {
151 }
152
153 CUiMargin(INT horz, INT vert)
154 : CUiRect(horz, vert, horz, vert)
155 {
156 }
157 };
158
159 class CUiMeasure
160 {
161 public:
162 enum MeasureType
163 {
164 Type_FitContent = 0,
165 Type_Fixed = 1,
166 Type_Percent = 2,
167 Type_FitParent = 3
168 };
169
170 private:
171 MeasureType m_Type;
172 INT m_Value;
173
174 public:
175 CUiMeasure()
176 {
177 m_Type = Type_FitContent;
178 m_Value = 0;
179 }
180
181 CUiMeasure(MeasureType type, INT value)
182 {
183 m_Type = type;
184 m_Value = value;
185 }
186
187 INT ComputeMeasure(INT parent, INT content)
188 {
189 switch (m_Type)
190 {
191 case Type_FitContent:
192 return content;
193 case Type_Fixed:
194 return m_Value;
195 case Type_Percent:
196 return max(content, parent * m_Value / 100);
197 case Type_FitParent:
198 return parent;
199 }
200
201 return 0;
202 }
203
204 public:
205 static CUiMeasure FitContent()
206 {
207 return CUiMeasure(Type_FitContent, 0);
208 }
209
210 static CUiMeasure FitParent()
211 {
212 return CUiMeasure(Type_FitParent, 0);
213 }
214
215 static CUiMeasure Fixed(INT pixels)
216 {
217 return CUiMeasure(Type_Fixed, pixels);
218 }
219
220 static CUiMeasure Percent(INT percent)
221 {
222 return CUiMeasure(Type_Percent, percent);
223 }
224 };
225
226 enum CUiAlignment
227 {
228 UiAlign_LeftTop,
229 UiAlign_Middle,
230 UiAlign_RightBtm,
231 UiAlign_Stretch
232 };
233
234 class CUiBox
235 {
236 public:
237 CUiMargin m_Margin;
238
239 CUiAlignment m_HorizontalAlignment;
240 CUiAlignment m_VerticalAlignment;
241
242 protected:
243 CUiBox()
244 {
245 m_HorizontalAlignment = UiAlign_LeftTop;
246 m_VerticalAlignment = UiAlign_LeftTop;
247 }
248
249 virtual VOID ComputeRect(RECT parentRect, RECT currentRect, RECT* newRect)
250 {
251 parentRect.left += m_Margin.left;
252 parentRect.right -= m_Margin.right;
253 parentRect.top += m_Margin.top;
254 parentRect.bottom -= m_Margin.bottom;
255
256 if (parentRect.right < parentRect.left)
257 parentRect.right = parentRect.left;
258
259 if (parentRect.bottom < parentRect.top)
260 parentRect.bottom = parentRect.top;
261
262 SIZE szParent = {parentRect.right - parentRect.left, parentRect.bottom - parentRect.top};
263 SIZE szCurrent = {currentRect.right - currentRect.left, currentRect.bottom - currentRect.top};
264
265 currentRect = parentRect;
266
267 switch (m_HorizontalAlignment)
268 {
269 case UiAlign_LeftTop:
270 currentRect.right = currentRect.left + szCurrent.cx;
271 break;
272 case UiAlign_Middle:
273 currentRect.left = parentRect.left + (szParent.cx - szCurrent.cx) / 2;
274 currentRect.right = currentRect.left + szCurrent.cx;
275 break;
276 case UiAlign_RightBtm:
277 currentRect.left = currentRect.right - szCurrent.cx;
278 break;
279 default:
280 break;
281 }
282
283 switch (m_VerticalAlignment)
284 {
285 case UiAlign_LeftTop:
286 currentRect.bottom = currentRect.top + szCurrent.cy;
287 break;
288 case UiAlign_Middle:
289 currentRect.top = parentRect.top + (szParent.cy - szCurrent.cy) / 2;
290 currentRect.bottom = currentRect.top + szCurrent.cy;
291 break;
292 case UiAlign_RightBtm:
293 currentRect.top = currentRect.bottom - szCurrent.cy;
294 break;
295 default:
296 break;
297 }
298
299 *newRect = currentRect;
300 }
301
302
303 public:
304 virtual VOID ComputeMinimalSize(SIZE* size)
305 {
306 // Override in subclass
307 size->cx = max(size->cx, 0);
308 size->cy = min(size->cy, 0);
309 };
310
311 virtual VOID ComputeContentBounds(RECT* rect)
312 {
313 // Override in subclass
314 };
315
316 virtual DWORD_PTR CountSizableChildren()
317 {
318 // Override in subclass
319 return 0;
320 };
321
322 virtual HDWP OnParentSize(RECT parentRect, HDWP hDwp)
323 {
324 // Override in subclass
325 return NULL;
326 };
327 };
328
329 class CUiPrimitive
330 {
331 protected:
332 CUiPrimitive * m_Parent;
333
334 public:
335 virtual ~CUiPrimitive() {}
336
337 virtual CUiBox * AsBox() { return NULL; }
338 };
339
340 class CUiCollection :
341 public CPointerArray < CUiPrimitive >
342 {
343 virtual BOOL OnRemoveItem(CUiPrimitive * ptr)
344 {
345 delete ptr;
346 return TRUE;
347 }
348 };
349
350 class CUiContainer
351 {
352 protected:
353 CUiCollection m_Children;
354
355 public:
356 CUiCollection& Children() { return m_Children; }
357 };
358
359 class CUiPanel :
360 public CUiPrimitive,
361 public CUiBox,
362 public CUiContainer
363 {
364 public:
365 CUiMeasure m_Width;
366 CUiMeasure m_Height;
367
368 CUiPanel()
369 {
370 m_Width = CUiMeasure::FitParent();
371 m_Height = CUiMeasure::FitParent();
372 }
373
374 virtual ~CUiPanel()
375 {
376 }
377
378 virtual CUiBox * AsBox() { return this; }
379
380 virtual VOID ComputeMinimalSize(SIZE* size)
381 {
382 for (INT i = 0; i < m_Children.GetCount(); i++)
383 {
384 CUiBox * box = m_Children.Get(i)->AsBox();
385 if (box)
386 {
387 box->ComputeMinimalSize(size);
388 }
389 }
390 };
391
392 virtual VOID ComputeContentBounds(RECT* rect)
393 {
394 for (INT i = 0; i < m_Children.GetCount(); i++)
395 {
396 CUiBox * box = m_Children.Get(i)->AsBox();
397 if (box)
398 {
399 box->ComputeContentBounds(rect);
400 }
401 }
402 };
403
404 virtual DWORD_PTR CountSizableChildren()
405 {
406 INT count = 0;
407 for (INT i = 0; i < m_Children.GetCount(); i++)
408 {
409 CUiBox * box = m_Children.Get(i)->AsBox();
410 if (box)
411 {
412 count += box->CountSizableChildren();
413 }
414 }
415 return count;
416 }
417
418 virtual HDWP OnParentSize(RECT parentRect, HDWP hDwp)
419 {
420 RECT rect = {0};
421
422 SIZE content = {0};
423 ComputeMinimalSize(&content);
424
425 INT preferredWidth = m_Width.ComputeMeasure(parentRect.right - parentRect.left, content.cx);
426 INT preferredHeight = m_Height.ComputeMeasure(parentRect.bottom - parentRect.top, content.cy);
427
428 rect.right = preferredWidth;
429 rect.bottom = preferredHeight;
430
431 ComputeRect(parentRect, rect, &rect);
432
433 for (INT i = 0; i < m_Children.GetCount(); i++)
434 {
435 CUiBox * box = m_Children.Get(i)->AsBox();
436 if (box)
437 {
438 hDwp = box->OnParentSize(rect, hDwp);
439 }
440 }
441
442 return hDwp;
443 }
444 };
445
446 template<class T = CWindow>
447 class CUiWindow :
448 public CUiPrimitive,
449 public CUiBox,
450 public T
451 {
452 public:
453 virtual CUiBox * AsBox() { return this; }
454
455 HWND GetWindow() { return T::m_hWnd; }
456
457 virtual VOID ComputeMinimalSize(SIZE* size)
458 {
459 // TODO: Maybe use WM_GETMINMAXINFO?
460 return CUiBox::ComputeMinimalSize(size);
461 };
462
463 virtual VOID ComputeContentBounds(RECT* rect)
464 {
465 RECT r;
466 ::GetWindowRect(T::m_hWnd, &r);
467 rect->left = min(rect->left, r.left);
468 rect->top = min(rect->top, r.top);
469 rect->right = max(rect->right, r.right);
470 rect->bottom = max(rect->bottom, r.bottom);
471 };
472
473 virtual DWORD_PTR CountSizableChildren()
474 {
475 return 1;
476 };
477
478 virtual HDWP OnParentSize(RECT parentRect, HDWP hDwp)
479 {
480 RECT rect;
481
482 ::GetWindowRect(T::m_hWnd, &rect);
483
484 ComputeRect(parentRect, rect, &rect);
485
486 if (hDwp)
487 {
488 return ::DeferWindowPos(hDwp, T::m_hWnd, NULL, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOACTIVATE | SWP_NOZORDER);
489 }
490 else
491 {
492 T::SetWindowPos(NULL, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOACTIVATE | SWP_NOZORDER | SWP_DEFERERASE);
493 return NULL;
494 }
495 };
496
497 virtual ~CUiWindow()
498 {
499 T::DestroyWindow();
500 }
501
502 VOID GetWindowTextW(ATL::CStringW& szText)
503 {
504 INT length = CWindow::GetWindowTextLengthW() + 1;
505 CWindow::GetWindowTextW(szText.GetBuffer(length), length);
506 szText.ReleaseBuffer();
507 }
508 };
509
510 class CUiSplitPanel :
511 public CUiPrimitive,
512 public CUiBox,
513 public CWindowImpl<CUiSplitPanel>
514 {
515 static const INT THICKNESS = 4;
516
517 protected:
518
519 HCURSOR m_hCursor;
520
521 CUiPanel m_First;
522 CUiPanel m_Second;
523
524 RECT m_LastRect;
525
526 BOOL m_HasOldRect;
527
528 public:
529 INT m_Pos;
530 BOOL m_Horizontal;
531 BOOL m_DynamicFirst;
532 INT m_MinFirst;
533 INT m_MinSecond;
534
535 CUiMeasure m_Width;
536 CUiMeasure m_Height;
537
538 CUiSplitPanel()
539 {
540 m_Width = CUiMeasure::FitParent();
541 m_Height = CUiMeasure::FitParent();
542 m_Pos = 100;
543 m_MinFirst = 100;
544 m_MinSecond = 100;
545 m_DynamicFirst = FALSE;
546 m_HasOldRect = FALSE;
547 }
548
549 virtual ~CUiSplitPanel()
550 {
551 }
552
553 virtual CUiBox * AsBox() { return this; }
554
555 CUiCollection& First() { return m_First.Children(); }
556 CUiCollection& Second() { return m_Second.Children(); }
557
558 virtual VOID ComputeMinimalSize(SIZE* size)
559 {
560 if (m_Horizontal)
561 size->cx = max(size->cx, THICKNESS);
562 else
563 size->cy = max(size->cy, THICKNESS);
564 m_First.ComputeMinimalSize(size);
565 m_Second.ComputeMinimalSize(size);
566 };
567
568 virtual VOID ComputeContentBounds(RECT* rect)
569 {
570 RECT r;
571
572 m_First.ComputeContentBounds(rect);
573 m_Second.ComputeContentBounds(rect);
574
575 ::GetWindowRect(m_hWnd, &r);
576
577 rect->left = min(rect->left, r.left);
578 rect->top = min(rect->top, r.top);
579 rect->right = max(rect->right, r.right);
580 rect->bottom = max(rect->bottom, r.bottom);
581 };
582
583 virtual DWORD_PTR CountSizableChildren()
584 {
585 INT count = 1;
586 count += m_First.CountSizableChildren();
587 count += m_Second.CountSizableChildren();
588 return count;
589 };
590
591 virtual HDWP OnParentSize(RECT parentRect, HDWP hDwp)
592 {
593 RECT rect = {0};
594
595 SIZE content = {0};
596 ComputeMinimalSize(&content);
597
598 INT preferredWidth = m_Width.ComputeMeasure(parentRect.right - parentRect.left, content.cx);
599 INT preferredHeight = m_Width.ComputeMeasure(parentRect.bottom - parentRect.top, content.cy);
600
601 rect.right = preferredWidth;
602 rect.bottom = preferredHeight;
603
604 ComputeRect(parentRect, rect, &rect);
605
606 SIZE growth = {0};
607 if (m_HasOldRect)
608 {
609 RECT oldRect = m_LastRect;
610
611 growth.cx = (parentRect.right - parentRect.left) - (oldRect.right - oldRect.left);
612 growth.cy = (parentRect.bottom - parentRect.top) - (oldRect.bottom - oldRect.top);
613 }
614
615 RECT splitter = rect;
616 RECT first = rect;
617 RECT second = rect;
618
619 if (m_Horizontal)
620 {
621 rect.top += m_MinFirst;
622 rect.bottom -= THICKNESS + m_MinSecond;
623 if (m_DynamicFirst)
624 {
625 if (growth.cy > 0)
626 {
627 m_Pos += min(growth.cy, rect.bottom - (m_Pos + THICKNESS));
628 }
629 else if (growth.cy < 0)
630 {
631 m_Pos += max(growth.cy, rect.top - m_Pos);
632 }
633 }
634
635 if (m_Pos > rect.bottom)
636 m_Pos = rect.bottom;
637
638 if (m_Pos < rect.top)
639 m_Pos = rect.top;
640
641 splitter.top = m_Pos;
642 splitter.bottom = m_Pos + THICKNESS;
643 first.bottom = splitter.top;
644 second.top = splitter.bottom;
645 }
646 else
647 {
648 rect.left += m_MinFirst;
649 rect.right -= THICKNESS + m_MinSecond;
650 if (m_DynamicFirst)
651 {
652 if (growth.cx > 0)
653 {
654 m_Pos += min(growth.cx, rect.right - (m_Pos + THICKNESS));
655 }
656 else if (growth.cx < 0)
657 {
658 m_Pos += max(growth.cy, rect.left - m_Pos);
659 }
660 }
661
662 if (m_Pos > rect.right)
663 m_Pos = rect.right;
664
665 if (m_Pos < rect.left)
666 m_Pos = rect.left;
667
668 splitter.left = m_Pos;
669 splitter.right = m_Pos + THICKNESS;
670 first.right = splitter.left;
671 second.left = splitter.right;
672 }
673
674 m_LastRect = parentRect;
675 m_HasOldRect = TRUE;
676
677 hDwp = m_First.OnParentSize(first, hDwp);
678 hDwp = m_Second.OnParentSize(second, hDwp);
679
680 if (hDwp)
681 {
682 return DeferWindowPos(hDwp, NULL,
683 splitter.left, splitter.top,
684 splitter.right - splitter.left,
685 splitter.bottom - splitter.top,
686 SWP_NOACTIVATE | SWP_NOZORDER);
687 }
688 else
689 {
690 SetWindowPos(NULL,
691 splitter.left, splitter.top,
692 splitter.right - splitter.left,
693 splitter.bottom - splitter.top,
694 SWP_NOACTIVATE | SWP_NOZORDER);
695 return NULL;
696 }
697 };
698
699 private:
700 BOOL ProcessWindowMessage(HWND hwnd, UINT Msg, WPARAM wParam, LPARAM lParam, LRESULT& theResult, DWORD dwMapId)
701 {
702 theResult = 0;
703 switch (Msg)
704 {
705 case WM_SETCURSOR:
706 SetCursor(m_hCursor);
707 theResult = TRUE;
708 break;
709
710 case WM_LBUTTONDOWN:
711 SetCapture();
712 break;
713
714 case WM_LBUTTONUP:
715 case WM_RBUTTONDOWN:
716 if (GetCapture() == m_hWnd)
717 {
718 ReleaseCapture();
719 }
720 break;
721
722 case WM_MOUSEMOVE:
723 if (GetCapture() == m_hWnd)
724 {
725 POINT Point;
726 GetCursorPos(&Point);
727 ::ScreenToClient(GetParent(), &Point);
728 if (m_Horizontal)
729 SetPos(Point.y);
730 else
731 SetPos(Point.x);
732 }
733 break;
734
735 default:
736 return FALSE;
737 }
738
739 return TRUE;
740 }
741
742 public:
743 INT GetPos()
744 {
745 return m_Pos;
746 }
747
748 VOID SetPos(INT NewPos)
749 {
750 RECT rcParent;
751
752 rcParent = m_LastRect;
753
754 if (m_Horizontal)
755 {
756 rcParent.bottom -= THICKNESS;
757
758 m_Pos = NewPos;
759
760 if (m_Pos < rcParent.top)
761 m_Pos = rcParent.top;
762
763 if (m_Pos > rcParent.bottom)
764 m_Pos = rcParent.bottom;
765 }
766 else
767 {
768 rcParent.right -= THICKNESS;
769
770 m_Pos = NewPos;
771
772 if (m_Pos < rcParent.left)
773 m_Pos = rcParent.left;
774
775 if (m_Pos > rcParent.right)
776 m_Pos = rcParent.right;
777 }
778
779 INT count = CountSizableChildren();
780
781 HDWP hdwp = NULL;
782 hdwp = BeginDeferWindowPos(count);
783 if (hdwp) hdwp = OnParentSize(m_LastRect, hdwp);
784 if (hdwp) EndDeferWindowPos(hdwp);
785 }
786
787 public:
788 DECLARE_WND_CLASS_EX(_T("SplitterWindowClass"), CS_HREDRAW | CS_VREDRAW, COLOR_BTNFACE)
789
790 /* Create splitter bar */
791 HWND Create(HWND hwndParent)
792 {
793 if (m_Horizontal)
794 m_hCursor = LoadCursor(0, IDC_SIZENS);
795 else
796 m_hCursor = LoadCursor(0, IDC_SIZEWE);
797
798 DWORD style = WS_CHILD | WS_VISIBLE;
799 DWORD exStyle = WS_EX_TRANSPARENT;
800
801 RECT size = {205, 180, 465, THICKNESS};
802 size.right += size.left;
803 size.bottom += size.top;
804
805 return CWindowImpl::Create(hwndParent, size, NULL, style, exStyle);
806 }
807 };