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