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