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