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 t = (reinterpret_cast<INT>(p2) - reinterpret_cast<INT>(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, s_OnCompareItems, (LPARAM)this);
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 ~CUiWindow()
497     {
498         T::DestroyWindow();
499     }
500 
501     VOID GetWindowTextW(ATL::CStringW& szText)
502     {
503         INT length = CWindow::GetWindowTextLengthW() + 1;
504         CWindow::GetWindowTextW(szText.GetBuffer(length), length);
505         szText.ReleaseBuffer();
506     }
507 };
508 
509 class CUiSplitPanel :
510     public CUiPrimitive,
511     public CUiBox,
512     public CWindowImpl<CUiSplitPanel>
513 {
514     static const INT THICKNESS = 4;
515 
516 protected:
517 
518     HCURSOR m_hCursor;
519 
520     CUiPanel m_First;
521     CUiPanel m_Second;
522 
523     RECT m_LastRect;
524 
525     BOOL m_HasOldRect;
526 
527 public:
528     INT m_Pos;
529     BOOL m_Horizontal;
530     BOOL m_DynamicFirst;
531     INT m_MinFirst;
532     INT m_MinSecond;
533 
534     CUiMeasure m_Width;
535     CUiMeasure m_Height;
536 
537     CUiSplitPanel()
538     {
539         m_Width = CUiMeasure::FitParent();
540         m_Height = CUiMeasure::FitParent();
541         m_Pos = 100;
542         m_MinFirst = 100;
543         m_MinSecond = 100;
544         m_DynamicFirst = FALSE;
545         m_HasOldRect = FALSE;
546     }
547 
548     virtual ~CUiSplitPanel()
549     {
550     }
551 
552     virtual CUiBox * AsBox() { return this; }
553 
554     CUiCollection& First() { return m_First.Children(); }
555     CUiCollection& Second() { return m_Second.Children(); }
556 
557     virtual VOID ComputeMinimalSize(SIZE* size)
558     {
559         if (m_Horizontal)
560             size->cx = max(size->cx, THICKNESS);
561         else
562             size->cy = max(size->cy, THICKNESS);
563         m_First.ComputeMinimalSize(size);
564         m_Second.ComputeMinimalSize(size);
565     };
566 
567     virtual VOID ComputeContentBounds(RECT* rect)
568     {
569         RECT r;
570 
571         m_First.ComputeContentBounds(rect);
572         m_Second.ComputeContentBounds(rect);
573 
574         ::GetWindowRect(m_hWnd, &r);
575 
576         rect->left = min(rect->left, r.left);
577         rect->top = min(rect->top, r.top);
578         rect->right = max(rect->right, r.right);
579         rect->bottom = max(rect->bottom, r.bottom);
580     };
581 
582     virtual DWORD_PTR CountSizableChildren()
583     {
584         INT count = 1;
585         count += m_First.CountSizableChildren();
586         count += m_Second.CountSizableChildren();
587         return count;
588     };
589 
590     virtual HDWP OnParentSize(RECT parentRect, HDWP hDwp)
591     {
592         RECT rect = {0};
593 
594         SIZE content = {0};
595         ComputeMinimalSize(&content);
596 
597         INT preferredWidth = m_Width.ComputeMeasure(parentRect.right - parentRect.left, content.cx);
598         INT preferredHeight = m_Width.ComputeMeasure(parentRect.bottom - parentRect.top, content.cy);
599 
600         rect.right = preferredWidth;
601         rect.bottom = preferredHeight;
602 
603         ComputeRect(parentRect, rect, &rect);
604 
605         SIZE growth = {0};
606         if (m_HasOldRect)
607         {
608             RECT oldRect = m_LastRect;
609 
610             growth.cx = (parentRect.right - parentRect.left) - (oldRect.right - oldRect.left);
611             growth.cy = (parentRect.bottom - parentRect.top) - (oldRect.bottom - oldRect.top);
612         }
613 
614         RECT splitter = rect;
615         RECT first = rect;
616         RECT second = rect;
617 
618         if (m_Horizontal)
619         {
620             rect.top += m_MinFirst;
621             rect.bottom -= THICKNESS + m_MinSecond;
622             if (m_DynamicFirst)
623             {
624                 if (growth.cy > 0)
625                 {
626                     m_Pos += min(growth.cy, rect.bottom - (m_Pos + THICKNESS));
627                 }
628                 else if (growth.cy < 0)
629                 {
630                     m_Pos += max(growth.cy, rect.top - m_Pos);
631                 }
632             }
633 
634             if (m_Pos > rect.bottom)
635                 m_Pos = rect.bottom;
636 
637             if (m_Pos < rect.top)
638                 m_Pos = rect.top;
639 
640             splitter.top = m_Pos;
641             splitter.bottom = m_Pos + THICKNESS;
642             first.bottom = splitter.top;
643             second.top = splitter.bottom;
644         }
645         else
646         {
647             rect.left += m_MinFirst;
648             rect.right -= THICKNESS + m_MinSecond;
649             if (m_DynamicFirst)
650             {
651                 if (growth.cx > 0)
652                 {
653                     m_Pos += min(growth.cx, rect.right - (m_Pos + THICKNESS));
654                 }
655                 else if (growth.cx < 0)
656                 {
657                     m_Pos += max(growth.cy, rect.left - m_Pos);
658                 }
659             }
660 
661             if (m_Pos > rect.right)
662                 m_Pos = rect.right;
663 
664             if (m_Pos < rect.left)
665                 m_Pos = rect.left;
666 
667             splitter.left = m_Pos;
668             splitter.right = m_Pos + THICKNESS;
669             first.right = splitter.left;
670             second.left = splitter.right;
671         }
672 
673         m_LastRect = parentRect;
674         m_HasOldRect = TRUE;
675 
676         hDwp = m_First.OnParentSize(first, hDwp);
677         hDwp = m_Second.OnParentSize(second, hDwp);
678 
679         if (hDwp)
680         {
681             return DeferWindowPos(hDwp, NULL,
682                                   splitter.left, splitter.top,
683                                   splitter.right - splitter.left,
684                                   splitter.bottom - splitter.top,
685                                   SWP_NOACTIVATE | SWP_NOZORDER);
686         }
687         else
688         {
689             SetWindowPos(NULL,
690                          splitter.left, splitter.top,
691                          splitter.right - splitter.left,
692                          splitter.bottom - splitter.top,
693                          SWP_NOACTIVATE | SWP_NOZORDER);
694             return NULL;
695         }
696     };
697 
698 private:
699     BOOL ProcessWindowMessage(HWND hwnd, UINT Msg, WPARAM wParam, LPARAM lParam, LRESULT& theResult, DWORD dwMapId)
700     {
701         theResult = 0;
702         switch (Msg)
703         {
704         case WM_SETCURSOR:
705             SetCursor(m_hCursor);
706             theResult = TRUE;
707             break;
708 
709         case WM_LBUTTONDOWN:
710             SetCapture();
711             break;
712 
713         case WM_LBUTTONUP:
714         case WM_RBUTTONDOWN:
715             if (GetCapture() == m_hWnd)
716             {
717                 ReleaseCapture();
718             }
719             break;
720 
721         case WM_MOUSEMOVE:
722             if (GetCapture() == m_hWnd)
723             {
724                 POINT Point;
725                 GetCursorPos(&Point);
726                 ::ScreenToClient(GetParent(), &Point);
727                 if (m_Horizontal)
728                     SetPos(Point.y);
729                 else
730                     SetPos(Point.x);
731             }
732             break;
733 
734         default:
735             return FALSE;
736         }
737 
738         return TRUE;
739     }
740 
741 public:
742     INT GetPos()
743     {
744         return m_Pos;
745     }
746 
747     VOID SetPos(INT NewPos)
748     {
749         RECT rcParent;
750 
751         rcParent = m_LastRect;
752 
753         if (m_Horizontal)
754         {
755             rcParent.bottom -= THICKNESS;
756 
757             m_Pos = NewPos;
758 
759             if (m_Pos < rcParent.top)
760                 m_Pos = rcParent.top;
761 
762             if (m_Pos > rcParent.bottom)
763                 m_Pos = rcParent.bottom;
764         }
765         else
766         {
767             rcParent.right -= THICKNESS;
768 
769             m_Pos = NewPos;
770 
771             if (m_Pos < rcParent.left)
772                 m_Pos = rcParent.left;
773 
774             if (m_Pos > rcParent.right)
775                 m_Pos = rcParent.right;
776         }
777 
778         INT count = CountSizableChildren();
779 
780         HDWP hdwp = NULL;
781         hdwp = BeginDeferWindowPos(count);
782         if (hdwp) hdwp = OnParentSize(m_LastRect, hdwp);
783         if (hdwp) EndDeferWindowPos(hdwp);
784     }
785 
786 public:
787     DECLARE_WND_CLASS_EX(_T("SplitterWindowClass"), CS_HREDRAW | CS_VREDRAW, COLOR_BTNFACE)
788 
789     /* Create splitter bar */
790     HWND Create(HWND hwndParent)
791     {
792         if (m_Horizontal)
793             m_hCursor = LoadCursor(0, IDC_SIZENS);
794         else
795             m_hCursor = LoadCursor(0, IDC_SIZEWE);
796 
797         DWORD style = WS_CHILD | WS_VISIBLE;
798         DWORD exStyle = WS_EX_TRANSPARENT;
799 
800         RECT size = {205, 180, 465, THICKNESS};
801         size.right += size.left;
802         size.bottom += size.top;
803 
804         return CWindowImpl::Create(hwndParent, size, NULL, style, exStyle);
805     }
806 };
807