1 /** 2 Copyright: Copyright (c) 2013 Andrey Penechko. 3 License: a$(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 4 Authors: Andrey Penechko. 5 */ 6 7 module anchovy.gui.behaviors.dockingrootbehavior; 8 9 import anchovy.gui; 10 import anchovy.gui.interfaces.iwidgetbehavior; 11 import std.math : abs; 12 13 // version = Button_debug; 14 15 enum dragTreshold = 10; 16 17 class DockingRootBehavior : IWidgetBehavior 18 { 19 protected: 20 Widget _dockingRoot; 21 Widget _undockedStorage; 22 Rect _dropRect; 23 Widget _hoveredDocked; 24 Widget _hoveredDockedParent; 25 Sides _hoveredSide; 26 27 VerticalLayout vlayout; 28 HorizontalLayout hlayout; 29 HorizontalLayout boxlayout; 30 31 public: 32 33 this() 34 { 35 vlayout = new VerticalLayout; 36 hlayout = new HorizontalLayout; 37 boxlayout = hlayout; 38 } 39 40 override void attachTo(Widget widget) 41 { 42 _dockingRoot = widget; 43 _dockingRoot.addEventHandler(&handleDraw); 44 _dockingRoot["isDocked"] = true; 45 } 46 47 void registerUndockedStorage(Widget storage) 48 { 49 assert(storage); 50 51 _undockedStorage = storage; 52 } 53 54 void registerFrame(Widget frame) 55 { 56 assert(frame); 57 assert(_undockedStorage); 58 59 frame["isDocked"] = false; 60 61 // extract frame header 62 auto frameHeader = frame["subwidgets"].get!(Widget[string]).get("header", null); 63 64 if (frameHeader && frameHeader.getWidgetBehavior!DragableBehavior) 65 { 66 // replace handler 67 frameHeader.removeEventHandlers!DragEvent; 68 frameHeader.addEventHandler(&handleFrameHeaderDrag); 69 frameHeader.addEventHandler(&handleFrameHeaderDragEnd); 70 } 71 72 _undockedStorage.addChild(frame); 73 } 74 75 bool handleFrameHeaderDrag(Widget frameHeader, DragEvent event) 76 { 77 Widget frame = frameHeader["root"].get!Widget; 78 79 if (frame["isDocked"].get!bool == true) 80 { 81 if (abs(event.delta.x) > dragTreshold || abs(event.delta.y) > dragTreshold) 82 { 83 handleUndock(frame); 84 // Center header after undocking 85 ivec2 newPosition = event.pointerPosition - frame.getPropertyAs!("prefSize", ivec2) / 2; 86 frame["position"] = newPosition; 87 event.dragOffset = event.pointerPosition - newPosition; 88 } 89 } 90 else 91 { 92 frame["position"] = frame.getPropertyAs!("position", ivec2) + event.delta; 93 checkForDropAt(event.pointerPosition); 94 } 95 96 return true; 97 } 98 99 bool handleFrameHeaderDragEnd(Widget frameHeader, DragEndEvent event) 100 { 101 Widget frame = frameHeader["root"].get!Widget; 102 103 if (_hoveredDocked) 104 { 105 handleDock(frame, _hoveredDocked, _hoveredDockedParent, _hoveredSide); 106 } 107 108 return true; 109 } 110 111 void checkForDropAt(ivec2 position) 112 { 113 Widget[] widgetChain = buildPathToLeaf!(EventDispatcher.containsPointer)(_dockingRoot, position); 114 115 _hoveredDocked = null; 116 _hoveredDockedParent = null; 117 118 // Find docked widget 119 foreach_reverse(widget; widgetChain) 120 { 121 if (widget.hasProperty!"isDocked" && widget.getPropertyAs!("isDocked", bool)) 122 { 123 _hoveredDocked = widget; 124 break; 125 } 126 } 127 128 Rect widgetRect; 129 130 // If docked is found get its rect, or return otherwise 131 if (_hoveredDocked) 132 { 133 widgetRect = _hoveredDocked.getPropertyAs!("staticRect", Rect); 134 if (_hoveredDocked !is _dockingRoot) 135 { 136 _hoveredDockedParent = _hoveredDocked.getPropertyAs!("parent", Widget); 137 } 138 } 139 else 140 { 141 return; 142 } 143 144 // Find corner positions 145 ivec2 leftTop = widgetRect.position; 146 ivec2 rightBottom = widgetRect.position + widgetRect.size; 147 ivec2 rightTop = widgetRect.position; 148 rightTop.x += widgetRect.size.x; 149 ivec2 leftBottom = widgetRect.position; 150 leftBottom.y += widgetRect.size.y; 151 152 // Find the sector of rect hit by cursor 153 // \T/ 154 // LXR 155 // /B\ 156 157 // D = (х3 - х1) * (у2 - у1) - (у3 - у1) * (х2 - х1) 158 // diagonal \ 159 int cursorPos1 = (position.x - leftTop.x) * (rightBottom.y - leftTop.y) 160 - (position.y - leftTop.y) * (rightBottom.x - leftTop.x); 161 162 // diagonal / 163 int cursorPos2 = (position.x - leftBottom.x) * (rightTop.y - leftBottom.y) 164 - (position.y - leftBottom.y) * (rightTop.x - leftBottom.x); 165 166 // Calculate rect that will be highlighted 167 _dropRect = widgetRect; 168 169 if (cursorPos1 > 0) //right top 170 { 171 if (cursorPos2 > 0) // left top 172 { 173 _dropRect.height /= 2; 174 _hoveredSide = Sides.top; 175 } 176 else // right bottom 177 { 178 _dropRect.width /= 2; 179 _dropRect.x += _dropRect.width; 180 _hoveredSide = Sides.right; 181 } 182 } 183 else // left bottom 184 { 185 if (cursorPos2 > 0) // left top 186 { 187 _dropRect.width /= 2; 188 _hoveredSide = Sides.left; 189 } 190 else // right bottom 191 { 192 _dropRect.height /= 2; 193 _dropRect.y += _dropRect.height; 194 _hoveredSide = Sides.bottom; 195 } 196 } 197 } 198 199 void handleDock(Widget floating, Widget docked, Widget dockedParent, Sides side) 200 { 201 assert(docked); 202 assert(floating); 203 204 floating["isDocked"] = true; 205 floating.detachFromParent; 206 207 // Drop over the empty dockRoot 208 if (!dockedParent) 209 { 210 docked.addChild(floating); 211 docked.setProperty!("layout", ILayout)(boxlayout); 212 } 213 else 214 { 215 auto layout = dockedParent.getPropertyAs!("layout", ILayout); 216 217 void splitDocked(Widget floating, bool after, ILayout layout) 218 { 219 auto context = floating.getPropertyAs!("context", GuiContext); 220 auto container = context.createWidget("dockContainer"); 221 222 container.setProperty!("layout", ILayout)(layout); 223 224 dockedParent.replaceChildBy(docked, container); 225 226 if (after) 227 { 228 container.addChild(docked); 229 container.addChild(floating); 230 } 231 else 232 { 233 container.addChild(floating); 234 container.addChild(docked); 235 } 236 } 237 238 if (layout is hlayout) 239 { 240 final switch(side) 241 { 242 case Sides.left: 243 dockedParent.addChildBefore(floating, docked); 244 break; 245 case Sides.right: 246 dockedParent.addChildAfter(floating, docked); 247 break; 248 case Sides.top: 249 splitDocked(floating, false, vlayout); 250 break; 251 case Sides.bottom: 252 splitDocked(floating, true, vlayout); 253 break; 254 } 255 } 256 else 257 { 258 final switch(side) 259 { 260 case Sides.left: 261 splitDocked(floating, false, hlayout); 262 break; 263 case Sides.right: 264 splitDocked(floating, true, hlayout); 265 break; 266 case Sides.top: 267 dockedParent.addChildBefore(floating, docked); 268 break; 269 case Sides.bottom: 270 dockedParent.addChildAfter(floating, docked); 271 break; 272 } 273 } 274 } 275 276 _hoveredDocked = null; 277 _hoveredDockedParent = null; 278 } 279 280 void foldContainer(Widget container) 281 { 282 assert(container !is _dockingRoot); 283 284 Widget[] children = container.getPropertyAs!("children", Widget[]); 285 Widget child = children[0]; 286 child.detachFromParent; 287 288 Widget parent = container.getPropertyAs!("parent", Widget); 289 290 parent.replaceChildBy(container, child); 291 } 292 293 void handleUndock(Widget docked) 294 { 295 Widget container = docked.getPropertyAs!("parent", Widget); 296 297 docked.detachFromParent; 298 _undockedStorage.addChild(docked); 299 docked["isDocked"] = false; 300 301 Widget[] children = container.getPropertyAs!("children", Widget[]); 302 if (children.length == 1 && container !is _dockingRoot) 303 { 304 foldContainer(container); 305 } 306 } 307 308 bool handleDraw(Widget dockRoot, DrawEvent event) 309 { 310 if (_hoveredDocked) 311 { 312 event.guiRenderer.renderer.setColor(Color(0,0,255, 64)); 313 event.guiRenderer.renderer.fillRect(_dropRect); 314 } 315 316 return true; 317 } 318 } 319