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