1 /**
2  * A tab control wrapping the native Win32 `SysTabControl32` common control.
3  *
4  * Each tab page is an arbitrary `Widget` whose bounds track the tab control's
5  * display area. Selecting a tab shows its page and hides the others.
6  */
7 module deft.controls.tabcontrol;
8 
9 version (Windows):
10 
11 import core.sys.windows.windows;
12 import core.sys.windows.commctrl;
13 
14 import deft.controls.control;
15 import deft.events;
16 import deft.util.strings;
17 import deft.widget;
18 
19 /**
20  * A native tab control hosting one `Widget` per page.
21  *
22  * Pages are positioned into the tab control's display rect (the area below the
23  * tab strip). The page widgets are expected to be children of the same parent
24  * window as the tab control.
25  */
26 class TabControl : Control
27 {
28 	private Widget[] pages_;
29 	private int selected_ = -1;
30 
31 	/// Fired when the selected page changes, with the new page index.
32 	Event!(int) onPageChanged;
33 
34 	/// Create a tab control as a child of `parent`.
35 	this(Widget parent)
36 	{
37 		super(parent, "SysTabControl32", WS_TABSTOP);
38 	}
39 
40 	/**
41 	 * Add a page captioned `title` showing `pageContent` when selected. Returns
42 	 * the index of the newly inserted page.
43 	 */
44 	int addPage(string title, Widget pageContent)
45 	{
46 		TCITEMW item;
47 		item.mask = TCIF_TEXT;
48 		item.pszText = cast(LPWSTR) title.toWStringz;
49 		int index = cast(int) SendMessageW(handle, TCM_INSERTITEMW, pages_.length, cast(LPARAM)&item);
50 
51 		pages_ ~= pageContent;
52 		layoutPages();
53 
54 		if (pages_.length == 1)
55 			setSelectedPage(0);
56 		else
57 			pageContent.setVisible(false);
58 
59 		return index;
60 	}
61 
62 	/// Change the label of the tab at `index` (e.g. when the UI language changes).
63 	void setTabTitle(int index, string title)
64 	{
65 		TCITEMW item;
66 		item.mask = TCIF_TEXT;
67 		item.pszText = cast(LPWSTR) title.toWStringz;
68 		SendMessageW(handle, TCM_SETITEMW, index, cast(LPARAM)&item);
69 	}
70 
71 	/// The index of the currently selected page, or -1 if none.
72 	int getSelectedPage()
73 	{
74 		return cast(int) SendMessageW(handle, TCM_GETCURSEL, 0, 0);
75 	}
76 
77 	/// Select the page at `index`, showing its content and hiding the rest.
78 	void setSelectedPage(int index)
79 	{
80 		SendMessageW(handle, TCM_SETCURSEL, index, 0);
81 		foreach (i, p; pages_)
82 			p.setVisible(i == index);
83 		selected_ = index;
84 		layoutPages();
85 	}
86 
87 	/// The display rect (content area below the tab strip), in client coordinates.
88 	Rect getDisplayRect()
89 	{
90 		RECT rc;
91 		GetClientRect(handle, &rc);
92 		SendMessageW(handle, TCM_ADJUSTRECT, FALSE, cast(LPARAM)&rc);
93 		return Rect.fromRECT(rc);
94 	}
95 
96 	private void layoutPages()
97 	{
98 		auto r = getDisplayRect();
99 		r.x += bounds.x;
100 		r.y += bounds.y;
101 		foreach (p; pages_)
102 			p.setBounds(r);
103 	}
104 
105 	/// Re-positions all pages into the current display rect; call on window resize.
106 	void relayout()
107 	{
108 		layoutPages();
109 	}
110 
111 	/// Move/resize the tab control (bounds `r`, parent-relative), then re-lay out its pages.
112 	override void setBounds(Rect r)
113 	{
114 		super.setBounds(r);
115 		layoutPages();
116 	}
117 
118 	/// Translate tab-selection-change notifications into `onPageChanged`.
119 	override bool processNotify(NMHDR* header)
120 	{
121 		if (header.code == TCN_SELCHANGE)
122 		{
123 			int idx = getSelectedPage();
124 			setSelectedPage(idx);
125 			onPageChanged.fire(idx);
126 			return true;
127 		}
128 		return false;
129 	}
130 
131 	/// The preferred size of the tab control.
132 	override Size getPreferredSize()
133 	{
134 		return Size(400, 300);
135 	}
136 }