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 super("DOCK_HOST");
209 styleId = STYLE_DOCK_HOST;
210 addChild(_topSpace.initialize(this, DockAlignment.Top));
211 addChild(_bottomSpace.initialize(this, DockAlignment.Bottom));
212 addChild(_leftSpace.initialize(this, DockAlignment.Left));
213 addChild(_rightSpace.initialize(this, DockAlignment.Right));
214 }
215
216 protected DockWindow[] getDockedWindowList(DockAlignment alignType) {
217 DockWindow[] list;
218 for (int i = 0; i < _children.count; i++) {
219 DockWindow item = cast(DockWindow)_children.get(i);
220 if (!item)
221 continue; // not a docked window
222 if(item.dockAlignment == alignType && item.visibility == Visibility.Visible) {
223 list ~= item;
224 }
225 }
226 return list;
227 }
228
229 protected DockAlignment[4] _layoutPriority = [DockAlignment.Top, DockAlignment.Left, DockAlignment.Right, DockAlignment.Bottom];
230 @property DockAlignment[4] layoutPriority() { return _layoutPriority; }
231 @property void layoutPriority(DockAlignment[4] p) {
232 _layoutPriority = p;
233 requestLayout();
234 }
235
236 /// Set widget rectangle to specified value and layout widget contents. (Step 2 of two phase layout).
237 override void layout(Rect rc) {
238 _needLayout = false;
239 if (visibility == Visibility.Gone) {
240 return;
241 }
242 _pos = rc;
243 applyMargins(rc);
244 applyPadding(rc);
245 foreach(a; _layoutPriority) {
246 if (a == DockAlignment.Top) _topSpace.beforeLayout(rc, getDockedWindowList(DockAlignment.Top));
247 if (a == DockAlignment.Left) _leftSpace.beforeLayout(rc, getDockedWindowList(DockAlignment.Left));
248 if (a == DockAlignment.Right) _rightSpace.beforeLayout(rc, getDockedWindowList(DockAlignment.Right));
249 if (a == DockAlignment.Bottom) _bottomSpace.beforeLayout(rc, getDockedWindowList(DockAlignment.Bottom));
250 }
251 int topsp, bottomsp, leftsp, rightsp;
252 foreach(a; _layoutPriority) {
253 if (a == DockAlignment.Top) {
254 _topSpace.layout(Rect(rc.left + leftsp, rc.top, rc.right - rightsp, rc.top + _topSpace.space));
255 topsp = _topSpace.space;
256 }
257 if (a == DockAlignment.Bottom) {
258 _bottomSpace.layout(Rect(rc.left + leftsp, rc.bottom - _bottomSpace.space, rc.right - rightsp, rc.bottom));
259 bottomsp = _bottomSpace.space;
260 }
261 if (a == DockAlignment.Left) {
262 _leftSpace.layout(Rect(rc.left, rc.top + topsp, rc.left + _leftSpace.space, rc.bottom - bottomsp));
263 leftsp = _leftSpace.space;
264 }
265 if (a == DockAlignment.Right) {
266 _rightSpace.layout(Rect(rc.right - _rightSpace.space, rc.top + topsp, rc.right, rc.bottom - bottomsp));
267 rightsp = _rightSpace.space;
268 }
269 }
270 if (_bodyWidget)
271 _bodyWidget.layout(Rect(rc.left + _leftSpace.space, rc.top + _topSpace.space, rc.right - _rightSpace.space, rc.bottom - _bottomSpace.space));
272 }
273
274 /// Measure widget according to desired width and height constraints. (Step 1 of two phase layout).
275 override void measure(int parentWidth, int parentHeight) {
276 Rect m = margins;
277 Rect p = padding;
278 // calc size constraints for children
279 int pwidth = parentWidth;
280 int pheight = parentHeight;
281 if (parentWidth != SIZE_UNSPECIFIED)
282 pwidth -= m.left + m.right + p.left + p.right;
283 if (parentHeight != SIZE_UNSPECIFIED)
284 pheight -= m.top + m.bottom + p.top + p.bottom;
285 // measure children
286 Point sz;
287 Point bodySize;
288 if (_bodyWidget) {
289 _bodyWidget.measure(pwidth, pheight);
290 bodySize.x = _bodyWidget.measuredWidth;
291 bodySize.y = _bodyWidget.measuredHeight;
292 }
293 for (int i = 0; i < _children.count; i++) {
294 Widget item = _children.get(i);
295 // TODO: fix
296 if (item.visibility != Visibility.Gone) {
297 item.measure(pwidth, pheight);
298 if (sz.x < item.measuredWidth)
299 sz.x = item.measuredWidth;
300 if (sz.y < item.measuredHeight)
301 sz.y = item.measuredHeight;
302 }
303 }
304 measuredContent(parentWidth, parentHeight, sz.x, sz.y);
305 }
306 }
307
308 /// docked window
309 class DockWindow : WindowFrame {
310
311 protected DockAlignment _dockAlignment;
312
313 @property DockAlignment dockAlignment() { return _dockAlignment; }
314 @property DockWindow dockAlignment(DockAlignment a) {
315 if (_dockAlignment != a) {
316 _dockAlignment = a;
317 requestLayout();
318 }
319 return this;
320 }
321
322 this(string ID) {
323 super(ID);
324 focusGroup = true;
325 }
326
327 override protected void initialize() {
328 super.initialize();
329 _dockAlignment = DockAlignment.Right; // default alignment is right
330 }
331
332 //protected Widget createBodyWidget() {
333 // return new Widget("DOCK_WINDOW_BODY");
334 //}
335 }