1 // Written in the D programming language.
2
3 /**
4
5 This module implements dockable windows UI support.
6
7 DockHost is main layout for docking support - contains body widget and optional docked windows.
8
9 DockWindow is window to use with DockHost - can be docked on top, left, right or bottom side of DockHost.
10
11 Synopsis:
12
13 ----
14 import dlangui.widgets.docks;
15 ----
16
17
18 Copyright: Vadim Lopatin, 2015
19 License: Boost License 1.0
20 Authors: Vadim Lopatin, coolreader.org@gmail.com
21 */
22 module dlangui.widgets.docks;
23
24 import dlangui.widgets.layouts;
25 import dlangui.widgets.controls;
26 import dlangui.widgets.winframe;
27
28 /// dock alignment types
29 enum DockAlignment {
30 /// at left of body
31 Left,
32 /// at right of body
33 Right,
34 /// above body
35 Top,
36 /// below body
37 Bottom
38 }
39
40
41 struct DockSpace {
42 protected Rect _rc;
43 protected Rect _resizerRect;
44 protected Rect _dockRect;
45 protected DockWindow[] _docks;
46 @property DockWindow[] docks() { return _docks; }
47 protected DockHost _host;
48 protected DockAlignment _alignment;
49 @property DockAlignment alignment() { return _alignment; }
50 protected ResizerWidget _resizer;
51 @property ResizerWidget resizer() { return _resizer; }
52 protected int _space;
53 @property int space() { return _space; }
54 protected int _minSpace;
55 protected int _maxSpace;
56 ResizerWidget initialize(DockHost host, DockAlignment a) {
57 _host = host;
58 _alignment = a;
59 final switch (a) with(DockAlignment)
60 {
61 case Top:
62 _resizer = new ResizerWidget("top_resizer", Orientation.Vertical);
63 break;
64 case Bottom:
65 _resizer = new ResizerWidget("bottom_resizer", Orientation.Vertical);
66 break;
67 case Left:
68 _resizer = new ResizerWidget("left_resizer", Orientation.Horizontal);
69 break;
70 case Right:
71 _resizer = new ResizerWidget("right_resizer", Orientation.Horizontal);
72 break;
73 }
74 _resizer.visibility = Visibility.Gone;
75 _resizer.resizeEvent = &onResize;
76 return _resizer;
77 }
78 /// host to be layed out
79 void beforeLayout(Rect rc, DockWindow[] docks) {
80 _docks = docks;
81 int baseSize;
82 if (_resizer.orientation == Orientation.Vertical)
83 baseSize = rc.height;
84 else
85 baseSize = rc.width;
86 _minSpace = baseSize * 1 / 10;
87 _maxSpace = baseSize * 4 / 10;
88 if (_docks.length) {
89 if (_space < _minSpace) {
90 if (_space == 0)
91 _space = _minSpace + (_maxSpace - _minSpace) * 1 / 3;
92 else
93 _space = _minSpace;
94 }
95 if (_space > _maxSpace) {
96 _space = _maxSpace;
97 }
98 _resizer.visibility = Visibility.Visible;
99 } else {
100 _space = 0;
101 _resizer.visibility = Visibility.Gone;
102 }
103 }
104 void layout(Rect rc) {
105 int rsWidth = 3; // resizer width
106 if (_space) {
107 _rc = rc;
108 final switch (_alignment) with(DockAlignment)
109 {
110 case Top:
111 _resizerRect = Rect(rc.left, rc.bottom - rsWidth, rc.right, rc.bottom + rsWidth);
112 _dockRect = Rect(rc.left, rc.top, rc.right, rc.bottom - rsWidth);
113 break;
114 case Bottom:
115 _resizerRect = Rect(rc.left, rc.top - rsWidth, rc.right, rc.top + rsWidth);
116 _dockRect = Rect(rc.left, rc.top + rsWidth, rc.right, rc.bottom);
117 break;
118 case Left:
119 _resizerRect = Rect(rc.right - rsWidth, rc.top, rc.right + rsWidth, rc.bottom);
120 _dockRect = Rect(rc.left, rc.top, rc.right - rsWidth, rc.bottom);
121 break;
122 case Right:
123 _resizerRect = Rect(rc.left - rsWidth, rc.top, rc.left + rsWidth, rc.bottom);
124 _dockRect = Rect(rc.left + rsWidth, rc.top, rc.right, rc.bottom);
125 break;
126 }
127 // layout resizer
128 _resizer.layout(_resizerRect);
129 // layout docked
130 layoutDocked();
131 } else {
132 _rc = _resizerRect = _dockRect = Rect(0, 0, 0, 0); // empty rect
133 }
134 }
135 protected int _dragStartSpace;
136 protected int _dragStartPosition;
137 protected void onResize(ResizerWidget source, ResizerEventType event, int newPosition) {
138 if (!_space)
139 return;
140 if (event == ResizerEventType.StartDragging) {
141 _dragStartSpace = _space;
142 _dragStartPosition = newPosition;
143 } else if (event == ResizerEventType.Dragging) {
144 int dir = _alignment == DockAlignment.Right || _alignment == DockAlignment.Bottom ? -1 : 1;
145 _space = _dragStartSpace + dir * (newPosition - _dragStartPosition);
146 _host.onResize(source, event, newPosition);
147 }
148 }
149 protected void layoutDocked() {
150 Rect rc = _rc; //_dockRect;
151 int len = cast(int)_docks.length;
152 for (int i = 0; i < len; i++) {
153 Rect itemRc = rc;
154 if (len > 1) {
155 if (_resizer.orientation == Orientation.Horizontal) {
156 itemRc.top = rc.top + rc.height * i / len;
157 if (i != len - 1)
158 itemRc.bottom = rc.top + rc.height * (i + 1) / len;
159 else
160 itemRc.bottom = rc.bottom;
161 } else {
162 itemRc.left = rc.left + rc.width * i / len;
163 if (i != len - 1)
164 itemRc.right = rc.left + rc.width * (i + 1) / len;
165 else
166 itemRc.right = rc.right;
167 }
168 }
169 _docks[i].layout(itemRc);
170 }
171 }
172 }
173
174 /// Layout for docking support - contains body widget and optional docked windows
175 class DockHost : WidgetGroupDefaultDrawing {
176
177
178 protected DockSpace _topSpace;
179 protected DockSpace _bottomSpace;
180 protected DockSpace _rightSpace;
181 protected DockSpace _leftSpace;
182 protected Widget _bodyWidget;
183 @property Widget bodyWidget() { return _bodyWidget; }
184 @property void bodyWidget(Widget widget) {
185 _children.replace(widget, _bodyWidget);
186 _bodyWidget = widget;
187 _bodyWidget.layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT);
188 _bodyWidget.parent = this;
189 }
190
191 void addDockedWindow(DockWindow dockWin) {
192 addChild(dockWin);
193 }
194
195 DockWindow removeDockedWindow(string id) {
196 DockWindow res = childById!DockWindow(id);
197 if (res)
198 removeChild(id);
199 return res;
200 }
201
202 protected int _resizeStartPos;
203 void onResize(ResizerWidget source, ResizerEventType event, int newPosition) {
204 layout(_pos);
205 }
206
207 this() {
208 this("DOCK_HOST");
209 }
210
211 this(string ID) {
212 super(ID);
213 styleId = STYLE_DOCK_HOST;
214 addChild(_topSpace.initialize(this, DockAlignment.Top));
215 addChild(_bottomSpace.initialize(this, DockAlignment.Bottom));
216 addChild(_leftSpace.initialize(this, DockAlignment.Left));
217 addChild(_rightSpace.initialize(this, DockAlignment.Right));
218 }
219
220 protected DockWindow[] getDockedWindowList(DockAlignment alignType) {
221 DockWindow[] list;
222 for (int i = 0; i < _children.count; i++) {
223 DockWindow item = cast(DockWindow)_children.get(i);
224 if (!item)
225 continue; // not a docked window
226 if(item.dockAlignment == alignType && item.visibility == Visibility.Visible) {
227 list ~= item;
228 }
229 }
230 return list;
231 }
232
233 protected DockAlignment[4] _layoutPriority = [DockAlignment.Top, DockAlignment.Left, DockAlignment.Right, DockAlignment.Bottom];
234 @property DockAlignment[4] layoutPriority() { return _layoutPriority; }
235 @property void layoutPriority(DockAlignment[4] p) {
236 _layoutPriority = p;
237 requestLayout();
238 }
239
240 /// Set widget rectangle to specified value and layout widget contents. (Step 2 of two phase layout).
241 override void layout(Rect rc) {
242 _needLayout = false;
243 if (visibility == Visibility.Gone) {
244 return;
245 }
246 _pos = rc;
247 applyMargins(rc);
248 applyPadding(rc);
249 foreach(a; _layoutPriority) {
250 if (a == DockAlignment.Top) _topSpace.beforeLayout(rc, getDockedWindowList(DockAlignment.Top));
251 if (a == DockAlignment.Left) _leftSpace.beforeLayout(rc, getDockedWindowList(DockAlignment.Left));
252 if (a == DockAlignment.Right) _rightSpace.beforeLayout(rc, getDockedWindowList(DockAlignment.Right));
253 if (a == DockAlignment.Bottom) _bottomSpace.beforeLayout(rc, getDockedWindowList(DockAlignment.Bottom));
254 }
255 int topsp, bottomsp, leftsp, rightsp;
256 foreach(a; _layoutPriority) {
257 if (a == DockAlignment.Top) {
258 _topSpace.layout(Rect(rc.left + leftsp, rc.top, rc.right - rightsp, rc.top + _topSpace.space));
259 topsp = _topSpace.space;
260 }
261 if (a == DockAlignment.Bottom) {
262 _bottomSpace.layout(Rect(rc.left + leftsp, rc.bottom - _bottomSpace.space, rc.right - rightsp, rc.bottom));
263 bottomsp = _bottomSpace.space;
264 }
265 if (a == DockAlignment.Left) {
266 _leftSpace.layout(Rect(rc.left, rc.top + topsp, rc.left + _leftSpace.space, rc.bottom - bottomsp));
267 leftsp = _leftSpace.space;
268 }
269 if (a == DockAlignment.Right) {
270 _rightSpace.layout(Rect(rc.right - _rightSpace.space, rc.top + topsp, rc.right, rc.bottom - bottomsp));
271 rightsp = _rightSpace.space;
272 }
273 }
274 if (_bodyWidget)
275 _bodyWidget.layout(Rect(rc.left + _leftSpace.space, rc.top + _topSpace.space, rc.right - _rightSpace.space, rc.bottom - _bottomSpace.space));
276 }
277
278 /// Measure widget according to desired width and height constraints. (Step 1 of two phase layout).
279 override void measure(int parentWidth, int parentHeight) {
280 Rect m = margins;
281 Rect p = padding;
282 // calc size constraints for children
283 int pwidth = parentWidth;
284 int pheight = parentHeight;
285 if (parentWidth != SIZE_UNSPECIFIED)
286 pwidth -= m.left + m.right + p.left + p.right;
287 if (parentHeight != SIZE_UNSPECIFIED)
288 pheight -= m.top + m.bottom + p.top + p.bottom;
289 // measure children
290 Point sz;
291 Point bodySize;
292 if (_bodyWidget) {
293 _bodyWidget.measure(pwidth, pheight);
294 bodySize.x = _bodyWidget.measuredWidth;
295 bodySize.y = _bodyWidget.measuredHeight;
296 }
297 for (int i = 0; i < _children.count; i++) {
298 Widget item = _children.get(i);
299 // TODO: fix
300 if (item.visibility != Visibility.Gone) {
301 item.measure(pwidth, pheight);
302 if (sz.x < item.measuredWidth)
303 sz.x = item.measuredWidth;
304 if (sz.y < item.measuredHeight)
305 sz.y = item.measuredHeight;
306 }
307 }
308 measuredContent(parentWidth, parentHeight, sz.x, sz.y);
309 }
310 }
311
312 /// docked window
313 class DockWindow : WindowFrame {
314
315 protected DockAlignment _dockAlignment;
316
317 @property DockAlignment dockAlignment() { return _dockAlignment; }
318 @property DockWindow dockAlignment(DockAlignment a) {
319 if (_dockAlignment != a) {
320 _dockAlignment = a;
321 requestLayout();
322 }
323 return this;
324 }
325
326 this(string ID) {
327 super(ID);
328 focusGroup = true;
329 }
330
331 override protected void initialize() {
332 super.initialize();
333 _dockAlignment = DockAlignment.Right; // default alignment is right
334 }
335
336 //protected Widget createBodyWidget() {
337 // return new Widget("DOCK_WINDOW_BODY");
338 //}
339 }