1 /**
2  * Push buttons and the two-state / grouped button controls.
3  *
4  * All four classes here wrap the Win32 `"BUTTON"` class with different styles:
5  * `Button` is a plain push button, `CheckBox` an auto check box, and
6  * `RadioButton` an auto radio button (optionally starting a new group). Each is
7  * interactive (`WS_TABSTOP`) and exposes a delegate event fired on click.
8  */
9 module deft.controls.button;
10 
11 version (Windows):
12 
13 import core.sys.windows.windows;
14 
15 import deft.controls.control;
16 import deft.events;
17 import deft.widget;
18 
19 /// A standard clickable push button.
20 class Button : Control
21 {
22 	/// Fired when the button is clicked (`BN_CLICKED`).
23 	Event!() onClicked;
24 
25 	/// Create a push button captioned `text` inside `parent`.
26 	this(Widget parent, string text)
27 	{
28 		super(parent, "BUTTON", BS_PUSHBUTTON | WS_TABSTOP);
29 		setText(text);
30 	}
31 
32 	/// Fire `onClicked` on a `BN_CLICKED` notification.
33 	override bool processCommand(ushort notificationCode)
34 	{
35 		if (notificationCode == BN_CLICKED)
36 		{
37 			onClicked.fire();
38 			return true;
39 		}
40 		return false;
41 	}
42 
43 	/// Buttons prefer a modest fixed size.
44 	override Size getPreferredSize()
45 	{
46 		return Size(100, 30);
47 	}
48 }
49 
50 /// A labeled two-state check box.
51 class CheckBox : Control
52 {
53 	/// Fired when the check box is toggled (`BN_CLICKED`).
54 	Event!() onToggled;
55 
56 	/// Create a check box captioned `text` inside `parent`.
57 	this(Widget parent, string text)
58 	{
59 		super(parent, "BUTTON", BS_AUTOCHECKBOX | WS_TABSTOP);
60 		setText(text);
61 	}
62 
63 	/// Whether the box is currently checked.
64 	bool isChecked()
65 	{
66 		return SendMessageW(handle, BM_GETCHECK, 0, 0) == BST_CHECKED;
67 	}
68 
69 	/// Set the checked state.
70 	void setChecked(bool value)
71 	{
72 		SendMessageW(handle, BM_SETCHECK, value ? BST_CHECKED : BST_UNCHECKED, 0);
73 	}
74 
75 	/// Fire `onToggled` on a `BN_CLICKED` notification.
76 	override bool processCommand(ushort notificationCode)
77 	{
78 		if (notificationCode == BN_CLICKED)
79 		{
80 			onToggled.fire();
81 			return true;
82 		}
83 		return false;
84 	}
85 
86 	/// Check boxes prefer room for their caption.
87 	override Size getPreferredSize()
88 	{
89 		return Size(120, 24);
90 	}
91 }
92 
93 /// A labeled radio button; one selection per group.
94 class RadioButton : Control
95 {
96 	/// Fired when the radio button is selected (`BN_CLICKED`).
97 	Event!() onSelected;
98 
99 	/**
100 	 * Create a radio button captioned `text` inside `parent`.
101 	 *
102 	 * Pass `firstInGroup = true` for the first button of a group. That button is
103 	 * the group's single tab stop (`WS_TABSTOP`) and starts the group; the
104 	 * remaining buttons are reached with the arrow keys, not Tab. A non-first
105 	 * button therefore drops both its tab stop and the `WS_GROUP` the control base
106 	 * adds by default, so it continues the previous button's group instead of
107 	 * starting a new one. End the group by giving the next control `WS_GROUP`
108 	 * (every Deft control has it by default, so a following non-radio control
109 	 * terminates the group automatically).
110 	 */
111 	this(Widget parent, string text, bool firstInGroup = false)
112 	{
113 		DWORD style = BS_AUTORADIOBUTTON;
114 		if (firstInGroup)
115 			style |= WS_TABSTOP; // WS_GROUP comes from the control base
116 		super(parent, "BUTTON", style);
117 
118 		if (!firstInGroup)
119 		{
120 			// Continue the previous radio button's group: a continuation button is
121 			// not a tab stop and must not start a new group.
122 			auto s = GetWindowLongW(handle, GWL_STYLE);
123 			SetWindowLongW(handle, GWL_STYLE, s & ~WS_GROUP);
124 		}
125 
126 		setText(text);
127 	}
128 
129 	/// Whether this radio button is currently selected.
130 	bool isChecked()
131 	{
132 		return SendMessageW(handle, BM_GETCHECK, 0, 0) == BST_CHECKED;
133 	}
134 
135 	/// Set the selected state.
136 	void setChecked(bool value)
137 	{
138 		SendMessageW(handle, BM_SETCHECK, value ? BST_CHECKED : BST_UNCHECKED, 0);
139 	}
140 
141 	/// Fire `onSelected` on a `BN_CLICKED` notification.
142 	override bool processCommand(ushort notificationCode)
143 	{
144 		if (notificationCode == BN_CLICKED)
145 		{
146 			onSelected.fire();
147 			return true;
148 		}
149 		return false;
150 	}
151 
152 	/// Radio buttons prefer room for their caption.
153 	override Size getPreferredSize()
154 	{
155 		return Size(120, 24);
156 	}
157 }