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 }