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