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 }