1 /** 2 * A container widget that groups and lays out child controls. 3 * 4 * `Panel` is a real child window of Deft's own window class — not a native 5 * `STATIC` — so, unlike a bare static control, it forwards the `WM_COMMAND` and 6 * `WM_NOTIFY` notifications its children raise. Without this, a button or list 7 * placed inside a static container would never deliver its click/selection 8 * events (the static control's window procedure drops them). A panel arranges 9 * its children with a `Sizer`, making it the natural content host for a tab 10 * page or any nested region. 11 * 12 * `WS_EX_CONTROLPARENT` is set so the dialog manager tabs into the panel's 13 * children — keyboard navigation reaches everything inside. 14 */ 15 module deft.controls.panel; 16 17 version (Windows): 18 19 import core.sys.windows.windows; 20 21 import deft.controls.control : routeCommand, routeNotify; 22 import deft.layout : Sizer; 23 import deft.widget; 24 import deft.platform.win32.init : deftWindowClassName, ensureWindowClass, hInstance; 25 26 /// A sizer-arranged container that forwards its children's notifications. 27 class Panel : Widget 28 { 29 private Sizer sizer_; 30 31 /// Create a panel as a child of `parent`. 32 this(Widget parent) 33 { 34 ensureWindowClass(); 35 this.parent_ = parent; 36 37 HWND parentHandle = parent !is null ? parent.handle : null; 38 39 handle_ = CreateWindowExW( 40 WS_EX_CONTROLPARENT, // let the dialog manager tab into the children 41 deftWindowClassName.ptr, 42 ""w.ptr, 43 WS_CHILD | WS_VISIBLE, 44 0, 0, 0, 0, 45 parentHandle, 46 null, 47 hInstance(), 48 null); 49 50 registerHandle(); 51 52 if (parent !is null) 53 parent.addChild(this); 54 } 55 56 /// Install the sizer that arranges the panel's children and lay it out now. 57 void setSizer(Sizer sizer) 58 { 59 sizer_ = sizer; 60 relayout(); 61 } 62 63 /// Re-run the sizer over the panel's client area. 64 void relayout() 65 { 66 if (sizer_ !is null) 67 sizer_.layout(getClientRect()); 68 } 69 70 /// Moving/resizing the panel re-lays out its contents. 71 override void setBounds(Rect r) 72 { 73 super.setBounds(r); 74 relayout(); 75 } 76 77 /// The panel's natural size is its sizer's preferred size. 78 override Size getPreferredSize() 79 { 80 return sizer_ !is null ? sizer_.preferredSize() : Size.init; 81 } 82 83 /// Forward children's `WM_COMMAND`/`WM_NOTIFY` and relayout on size; else defer. 84 override LRESULT processMessage(UINT msg, WPARAM wParam, LPARAM lParam) 85 { 86 switch (msg) 87 { 88 case WM_COMMAND: 89 // Forward control notifications (non-null lParam) to the originating 90 // control, exactly as a top-level Window does. 91 if (cast(HWND) lParam !is null && routeCommand(wParam, lParam)) 92 return 0; 93 break; 94 95 case WM_NOTIFY: 96 if (routeNotify(lParam)) 97 return 0; 98 break; 99 100 case WM_SIZE: 101 relayout(); 102 return 0; 103 104 default: 105 break; 106 } 107 return super.processMessage(msg, wParam, lParam); 108 } 109 }