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