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 }