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