/* * PROJECT: ReactOS UI Layout Engine * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) * FILE: base/applications/rapps/include/rosui.h * PURPOSE: ATL Layout engine for RAPPS * COPYRIGHT: Copyright 2015 David Quintana (gigaherz@gmail.com) */ #pragma once #include template class CPointerArray { protected: HDPA m_hDpa; public: CPointerArray() { m_hDpa = DPA_Create(GrowthRate); } ~CPointerArray() { DPA_DestroyCallback(m_hDpa, s_OnRemoveItem, this); } private: static INT CALLBACK s_OnRemoveItem(PVOID ptr, PVOID context) { CPointerArray *self = (CPointerArray *)context; return (INT)self->OnRemoveItem(reinterpret_cast(ptr)); } static INT CALLBACK s_OnCompareItems(PVOID p1, PVOID p2, LPARAM lParam) { CPointerArray *self = (CPointerArray *)lParam; return self->OnCompareItems(reinterpret_cast(p1), reinterpret_cast(p2)); } public: virtual BOOL OnRemoveItem(T *ptr) { return TRUE; } virtual INT OnCompareItems(T *p1, T *p2) { INT_PTR t = (reinterpret_cast(p2) - reinterpret_cast(p1)); if (t > 0) return 1; if (t < 0) return -1; return 0; } public: INT GetCount() const { return DPA_GetPtrCount(m_hDpa); } T * Get(INT i) const { return (T *)DPA_GetPtr(m_hDpa, i); } BOOL Set(INT i, T *ptr) { return DPA_SetPtr(m_hDpa, i, ptr); } INT Insert(INT at, T *ptr) { return DPA_InsertPtr(m_hDpa, at, ptr); } INT Append(T *ptr) { return DPA_InsertPtr(m_hDpa, DA_LAST, ptr); } INT IndexOf(T *ptr) const { return DPA_GetPtrIndex(m_hDpa, ptr); } BOOL Remove(T *ptr) { INT i = IndexOf(ptr); if (i < 0) return FALSE; return RemoveAt(i); } BOOL RemoveAt(INT i) { PVOID ptr = DPA_DeletePtr(m_hDpa, i); if (ptr != NULL) { OnRemoveItem(reinterpret_cast(ptr)); return TRUE; } return FALSE; } BOOL Clear() { DPA_EnumCallback(s_OnRemoveItem, this); return DPA_DeleteAllPtrs(m_hDpa); } BOOL Sort() { return DPA_Sort(m_hDpa, s_OnCompareItems, (LPARAM)this); } INT Search(T *item, INT iStart, UINT uFlags) { return DPA_Search(m_hDpa, item, 0, s_OnCompareItems, (LPARAM)this, 0); } }; class CUiRect : public RECT { public: CUiRect() { left = right = top = bottom = 0; } CUiRect(INT l, INT t, INT r, INT b) { left = l; right = r; top = t; bottom = b; } }; class CUiMargin : public CUiRect { public: CUiMargin() { } CUiMargin(INT all) : CUiRect(all, all, all, all) { } CUiMargin(INT horz, INT vert) : CUiRect(horz, vert, horz, vert) { } }; class CUiMeasure { public: enum MeasureType { Type_FitContent = 0, Type_Fixed = 1, Type_Percent = 2, Type_FitParent = 3 }; private: MeasureType m_Type; INT m_Value; public: CUiMeasure() { m_Type = Type_FitContent; m_Value = 0; } CUiMeasure(MeasureType type, INT value) { m_Type = type; m_Value = value; } INT ComputeMeasure(INT parent, INT content) { switch (m_Type) { case Type_FitContent: return content; case Type_Fixed: return m_Value; case Type_Percent: return max(content, parent * m_Value / 100); case Type_FitParent: return parent; } return 0; } public: static CUiMeasure FitContent() { return CUiMeasure(Type_FitContent, 0); } static CUiMeasure FitParent() { return CUiMeasure(Type_FitParent, 0); } static CUiMeasure Fixed(INT pixels) { return CUiMeasure(Type_Fixed, pixels); } static CUiMeasure Percent(INT percent) { return CUiMeasure(Type_Percent, percent); } }; enum CUiAlignment { UiAlign_LeftTop, UiAlign_Middle, UiAlign_RightBtm, UiAlign_Stretch }; class CUiBox { public: CUiMargin m_Margin; CUiAlignment m_HorizontalAlignment; CUiAlignment m_VerticalAlignment; protected: CUiBox() { m_HorizontalAlignment = UiAlign_LeftTop; m_VerticalAlignment = UiAlign_LeftTop; } virtual VOID ComputeRect(RECT parentRect, RECT currentRect, RECT *newRect) { parentRect.left += m_Margin.left; parentRect.right -= m_Margin.right; parentRect.top += m_Margin.top; parentRect.bottom -= m_Margin.bottom; if (parentRect.right < parentRect.left) parentRect.right = parentRect.left; if (parentRect.bottom < parentRect.top) parentRect.bottom = parentRect.top; SIZE szParent = {parentRect.right - parentRect.left, parentRect.bottom - parentRect.top}; SIZE szCurrent = {currentRect.right - currentRect.left, currentRect.bottom - currentRect.top}; currentRect = parentRect; switch (m_HorizontalAlignment) { case UiAlign_LeftTop: currentRect.right = currentRect.left + szCurrent.cx; break; case UiAlign_Middle: currentRect.left = parentRect.left + (szParent.cx - szCurrent.cx) / 2; currentRect.right = currentRect.left + szCurrent.cx; break; case UiAlign_RightBtm: currentRect.left = currentRect.right - szCurrent.cx; break; default: break; } switch (m_VerticalAlignment) { case UiAlign_LeftTop: currentRect.bottom = currentRect.top + szCurrent.cy; break; case UiAlign_Middle: currentRect.top = parentRect.top + (szParent.cy - szCurrent.cy) / 2; currentRect.bottom = currentRect.top + szCurrent.cy; break; case UiAlign_RightBtm: currentRect.top = currentRect.bottom - szCurrent.cy; break; default: break; } *newRect = currentRect; } public: virtual VOID ComputeMinimalSize(SIZE *size) { // Override in subclass size->cx = max(size->cx, 0); size->cy = min(size->cy, 0); }; virtual VOID ComputeContentBounds(RECT *rect){ // Override in subclass }; virtual DWORD_PTR CountSizableChildren() { // Override in subclass return 0; }; virtual HDWP OnParentSize(RECT parentRect, HDWP hDwp) { // Override in subclass return NULL; }; }; class CUiPrimitive { protected: CUiPrimitive *m_Parent; public: virtual ~CUiPrimitive() { } virtual CUiBox * AsBox() { return NULL; } }; class CUiCollection : public CPointerArray { virtual BOOL OnRemoveItem(CUiPrimitive *ptr) { delete ptr; return TRUE; } }; class CUiContainer { protected: CUiCollection m_Children; public: CUiCollection & Children() { return m_Children; } }; class CUiPanel : public CUiPrimitive, public CUiBox, public CUiContainer { public: CUiMeasure m_Width; CUiMeasure m_Height; CUiPanel() { m_Width = CUiMeasure::FitParent(); m_Height = CUiMeasure::FitParent(); } virtual ~CUiPanel() { } virtual CUiBox * AsBox() { return this; } virtual VOID ComputeMinimalSize(SIZE *size) { for (INT i = 0; i < m_Children.GetCount(); i++) { CUiBox *box = m_Children.Get(i)->AsBox(); if (box) { box->ComputeMinimalSize(size); } } }; virtual VOID ComputeContentBounds(RECT *rect) { for (INT i = 0; i < m_Children.GetCount(); i++) { CUiBox *box = m_Children.Get(i)->AsBox(); if (box) { box->ComputeContentBounds(rect); } } }; virtual DWORD_PTR CountSizableChildren() { INT count = 0; for (INT i = 0; i < m_Children.GetCount(); i++) { CUiBox *box = m_Children.Get(i)->AsBox(); if (box) { count += box->CountSizableChildren(); } } return count; } virtual HDWP OnParentSize(RECT parentRect, HDWP hDwp) { RECT rect = {0}; SIZE content = {0}; ComputeMinimalSize(&content); INT preferredWidth = m_Width.ComputeMeasure(parentRect.right - parentRect.left, content.cx); INT preferredHeight = m_Height.ComputeMeasure(parentRect.bottom - parentRect.top, content.cy); rect.right = preferredWidth; rect.bottom = preferredHeight; ComputeRect(parentRect, rect, &rect); for (INT i = 0; i < m_Children.GetCount(); i++) { CUiBox *box = m_Children.Get(i)->AsBox(); if (box) { hDwp = box->OnParentSize(rect, hDwp); } } return hDwp; } }; template class CUiWindow : public CUiPrimitive, public CUiBox, public T { public: virtual CUiBox * AsBox() { return this; } HWND GetWindow() { return T::m_hWnd; } virtual VOID ComputeMinimalSize(SIZE *size) { // TODO: Maybe use WM_GETMINMAXINFO? return CUiBox::ComputeMinimalSize(size); }; virtual VOID ComputeContentBounds(RECT *rect) { RECT r; ::GetWindowRect(T::m_hWnd, &r); rect->left = min(rect->left, r.left); rect->top = min(rect->top, r.top); rect->right = max(rect->right, r.right); rect->bottom = max(rect->bottom, r.bottom); }; virtual DWORD_PTR CountSizableChildren() { return 1; }; virtual HDWP OnParentSize(RECT parentRect, HDWP hDwp) { RECT rect; ::GetWindowRect(T::m_hWnd, &rect); ComputeRect(parentRect, rect, &rect); if (hDwp) { return ::DeferWindowPos( hDwp, T::m_hWnd, NULL, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOACTIVATE | SWP_NOZORDER); } else { T::SetWindowPos( NULL, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOACTIVATE | SWP_NOZORDER | SWP_DEFERERASE); return NULL; } }; virtual VOID AppendTabOrderWindow(int Direction, ATL::CSimpleArray &TabOrderList) { TabOrderList.Add(T::m_hWnd); return; } virtual ~CUiWindow() { if (T::IsWindow()) { T::DestroyWindow(); } } VOID GetWindowTextW(CStringW &szText) { INT length = CWindow::GetWindowTextLengthW() + 1; CWindow::GetWindowTextW(szText.GetBuffer(length), length); szText.ReleaseBuffer(); } }; class CUiSplitPanel : public CUiPrimitive, public CUiBox, public CWindowImpl { static const INT THICKNESS = 4; protected: HCURSOR m_hCursor; CUiPanel m_First; CUiPanel m_Second; RECT m_LastRect; BOOL m_HasOldRect; public: INT m_Pos; BOOL m_Horizontal; BOOL m_DynamicFirst; INT m_MinFirst; INT m_MinSecond; CUiMeasure m_Width; CUiMeasure m_Height; CUiSplitPanel() { m_hCursor = NULL; m_Width = CUiMeasure::FitParent(); m_Height = CUiMeasure::FitParent(); m_Pos = 100; m_Horizontal = FALSE; m_MinFirst = 100; m_MinSecond = 100; m_DynamicFirst = FALSE; m_HasOldRect = FALSE; memset(&m_LastRect, 0, sizeof(m_LastRect)); } virtual ~CUiSplitPanel() { } virtual CUiBox * AsBox() { return this; } CUiCollection & First() { return m_First.Children(); } CUiCollection & Second() { return m_Second.Children(); } virtual VOID ComputeMinimalSize(SIZE *size) { if (m_Horizontal) size->cx = max(size->cx, THICKNESS); else size->cy = max(size->cy, THICKNESS); m_First.ComputeMinimalSize(size); m_Second.ComputeMinimalSize(size); }; virtual VOID ComputeContentBounds(RECT *rect) { RECT r; m_First.ComputeContentBounds(rect); m_Second.ComputeContentBounds(rect); ::GetWindowRect(m_hWnd, &r); rect->left = min(rect->left, r.left); rect->top = min(rect->top, r.top); rect->right = max(rect->right, r.right); rect->bottom = max(rect->bottom, r.bottom); }; virtual DWORD_PTR CountSizableChildren() { INT count = 1; count += m_First.CountSizableChildren(); count += m_Second.CountSizableChildren(); return count; }; virtual HDWP OnParentSize(RECT parentRect, HDWP hDwp) { RECT rect = {0}; SIZE content = {0}; ComputeMinimalSize(&content); INT preferredWidth = m_Width.ComputeMeasure(parentRect.right - parentRect.left, content.cx); INT preferredHeight = m_Width.ComputeMeasure(parentRect.bottom - parentRect.top, content.cy); rect.right = preferredWidth; rect.bottom = preferredHeight; ComputeRect(parentRect, rect, &rect); SIZE growth = {0}; if (m_HasOldRect) { RECT oldRect = m_LastRect; growth.cx = (parentRect.right - parentRect.left) - (oldRect.right - oldRect.left); growth.cy = (parentRect.bottom - parentRect.top) - (oldRect.bottom - oldRect.top); } RECT splitter = rect; RECT first = rect; RECT second = rect; if (m_Horizontal) { rect.top += m_MinFirst; rect.bottom -= THICKNESS + m_MinSecond; if (m_DynamicFirst) { if (growth.cy > 0) { m_Pos += min(growth.cy, rect.bottom - (m_Pos + THICKNESS)); } else if (growth.cy < 0) { m_Pos += max(growth.cy, rect.top - m_Pos); } } if (m_Pos > rect.bottom) m_Pos = rect.bottom; if (m_Pos < rect.top) m_Pos = rect.top; splitter.top = m_Pos; splitter.bottom = m_Pos + THICKNESS; first.bottom = splitter.top; second.top = splitter.bottom; } else { rect.left += m_MinFirst; rect.right -= THICKNESS + m_MinSecond; if (m_DynamicFirst) { if (growth.cx > 0) { m_Pos += min(growth.cx, rect.right - (m_Pos + THICKNESS)); } else if (growth.cx < 0) { m_Pos += max(growth.cy, rect.left - m_Pos); } } if (m_Pos > rect.right) m_Pos = rect.right; if (m_Pos < rect.left) m_Pos = rect.left; splitter.left = m_Pos; splitter.right = m_Pos + THICKNESS; first.right = splitter.left; second.left = splitter.right; } m_LastRect = parentRect; m_HasOldRect = TRUE; hDwp = m_First.OnParentSize(first, hDwp); hDwp = m_Second.OnParentSize(second, hDwp); if (hDwp) { return DeferWindowPos( hDwp, NULL, splitter.left, splitter.top, splitter.right - splitter.left, splitter.bottom - splitter.top, SWP_NOACTIVATE | SWP_NOZORDER); } else { SetWindowPos( NULL, splitter.left, splitter.top, splitter.right - splitter.left, splitter.bottom - splitter.top, SWP_NOACTIVATE | SWP_NOZORDER); return NULL; } }; private: BOOL ProcessWindowMessage(HWND hwnd, UINT Msg, WPARAM wParam, LPARAM lParam, LRESULT &theResult, DWORD dwMapId) { theResult = 0; switch (Msg) { case WM_SETCURSOR: SetCursor(m_hCursor); theResult = TRUE; break; case WM_LBUTTONDOWN: SetCapture(); break; case WM_LBUTTONUP: case WM_RBUTTONDOWN: if (GetCapture() == m_hWnd) { ReleaseCapture(); } break; case WM_MOUSEMOVE: if (GetCapture() == m_hWnd) { POINT Point; GetCursorPos(&Point); ::ScreenToClient(GetParent(), &Point); if (m_Horizontal) SetPos(Point.y); else SetPos(Point.x); } break; default: return FALSE; } return TRUE; } public: INT GetPos() { return m_Pos; } VOID SetPos(INT NewPos) { RECT rcParent; rcParent = m_LastRect; if (m_Horizontal) { rcParent.bottom -= THICKNESS; m_Pos = NewPos; if (m_Pos < rcParent.top) m_Pos = rcParent.top; if (m_Pos > rcParent.bottom) m_Pos = rcParent.bottom; } else { rcParent.right -= THICKNESS; m_Pos = NewPos; if (m_Pos < rcParent.left) m_Pos = rcParent.left; if (m_Pos > rcParent.right) m_Pos = rcParent.right; } INT count = CountSizableChildren(); HDWP hdwp = NULL; hdwp = BeginDeferWindowPos(count); if (hdwp) hdwp = OnParentSize(m_LastRect, hdwp); if (hdwp) EndDeferWindowPos(hdwp); } public: DECLARE_WND_CLASS_EX(_T("SplitterWindowClass"), CS_HREDRAW | CS_VREDRAW, COLOR_BTNFACE) /* Create splitter bar */ HWND Create(HWND hwndParent) { if (m_Horizontal) m_hCursor = LoadCursor(0, IDC_SIZENS); else m_hCursor = LoadCursor(0, IDC_SIZEWE); DWORD style = WS_CHILD | WS_VISIBLE; DWORD exStyle = WS_EX_TRANSPARENT; RECT size = {205, 180, 465, THICKNESS}; size.right += size.left; size.bottom += size.top; return CWindowImpl::Create(hwndParent, size, NULL, style, exStyle); } };