1 /** 2 Copyright: Copyright (c) 2014 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.eventdispatcher; 8 9 import anchovy.gui; 10 11 struct EventDispatcher 12 { 13 private: 14 GuiContext _context; 15 16 ivec2 _lastPointerPosition = ivec2(int.max, int.max); 17 18 /// Current input owner If set, this widget will receive all pointer moved events. 19 /// See_Also: inputOwnerWidget 20 Widget _inputOwnerWidget; 21 22 /// Currently dragging widget. Will receive onDrag events. 23 Widget _draggingWidget; 24 25 /// Last clicked widget. Used for double-click checking. 26 /// See_Also: lastClickedWidget 27 Widget _lastClickedWidget; 28 29 /// Currently pressed widget 30 Widget _pressedWidget; 31 32 /// Hovered widget. Widget over which pointer is located. 33 /// See_Also: hoveredWidget 34 Widget _hoveredWidget; 35 36 /// Focused widget. 37 /// 38 /// Will receive all key events if input is not grabbed by other widget. 39 Widget _focusedWidget; 40 41 bool isLayoutValid; // Will be updated in update method 42 43 public: 44 45 @disable this(); 46 47 this(GuiContext context) 48 { 49 _context = context; 50 } 51 52 @property 53 { 54 ivec2 lastPointerPosition() 55 { 56 return _lastPointerPosition; 57 } 58 /// Used to get last clicked widget. 59 Widget lastClickedWidget() @safe 60 { 61 return _lastClickedWidget; 62 } 63 64 /// Used to set last clicked widget. 65 void lastClickedWidget(Widget widget) @safe 66 { 67 _lastClickedWidget = widget; 68 } 69 70 /// Used to get current hovered widget. 71 Widget hoveredWidget() @safe 72 { 73 return _hoveredWidget; 74 } 75 76 /// Used to set current hovered widget. 77 void hoveredWidget(Widget widget) @trusted 78 { 79 if (_hoveredWidget !is widget) 80 { 81 if (_hoveredWidget !is null) 82 { 83 auto event = new PointerLeaveEvent; 84 event.context = _context; 85 _hoveredWidget.handleEvent(event); 86 } 87 88 if (widget !is null) 89 { 90 auto event = new PointerEnterEvent; 91 event.context = _context; 92 widget.handleEvent(event); 93 } 94 95 _context.tooltipManager.onWidgetHovered(widget); 96 _hoveredWidget = widget; 97 } 98 } 99 100 /// Used to get current focused input owner widget 101 Widget inputOwnerWidget() @safe pure 102 { 103 return _inputOwnerWidget; 104 } 105 106 /// Used to set current focused input owner widget 107 void inputOwnerWidget(Widget widget) @trusted 108 { 109 _inputOwnerWidget = widget; 110 } 111 112 /// Used to get current focused input owner widget 113 Widget pressedWidget() @safe pure 114 { 115 return _pressedWidget; 116 } 117 118 /// Used to set current focused input owner widget 119 void pressedWidget(Widget widget) @trusted 120 { 121 _pressedWidget = widget; 122 } 123 124 /// Used to get current focused widget 125 Widget focusedWidget() @safe pure 126 { 127 return _focusedWidget; 128 } 129 130 /// Used to set current focused widget 131 void focusedWidget(Widget widget) 132 { 133 if (_focusedWidget !is widget) 134 { 135 if (_focusedWidget !is null) 136 { 137 auto event = new FocusLoseEvent; 138 event.context = _context; 139 _focusedWidget.handleEvent(event); 140 } 141 142 if (widget !is null) 143 { 144 auto event = new FocusGainEvent; 145 event.context = _context; 146 widget.handleEvent(event); 147 } 148 149 _focusedWidget = widget; 150 } 151 } 152 } 153 154 //+-------------------------------------------------------------------------------+ 155 //| Event handling | 156 //+-------------------------------------------------------------------------------+ 157 158 static bool containsPointer(Widget widget, ivec2 pointerPosition) 159 { 160 return widget.getPropertyAs!("staticRect", Rect).contains(pointerPosition); 161 } 162 163 void invalidateWidgetLayout(Widget container) 164 { 165 isLayoutValid = false; 166 } 167 168 void update(double deltaTime) 169 { 170 if (!isLayoutValid) 171 { 172 doLayout(); 173 isLayoutValid = true; 174 175 if (pressedWidget is null) 176 { 177 scope moveEvent = new PointerMoveEvent(lastPointerPosition, ivec2(0, 0)); 178 moveEvent.context = _context; 179 updateHovered(moveEvent); 180 } 181 } 182 } 183 184 void doLayout() 185 { 186 foreach(root; _context.roots) 187 { 188 root.propagateEventChildrenFirst(new MinimizeLayoutEvent); 189 root.propagateEventParentFirst(new ExpandLayoutEvent); 190 root.propagateEventParentFirst(new UpdatePositionEvent); 191 } 192 } 193 194 void draw() 195 { 196 auto event = new DrawEvent(_context.guiRenderer); 197 event.context = _context; 198 199 foreach(root; _context.roots) 200 { 201 root.propagateEventSinkBubbleTree(event); 202 } 203 } 204 205 /// Handler for key press event. 206 /// 207 /// Must be called by user application. 208 bool keyPressed(in KeyCode key, uint modifiers) 209 { 210 if (_focusedWidget !is null) 211 { 212 auto event = new KeyPressEvent(key, modifiers); 213 event.context = _context; 214 _focusedWidget.handleEvent(event); 215 } 216 217 return false; 218 } 219 220 /// Handler for key release event. 221 /// 222 /// Must be called by user application. 223 bool keyReleased(in KeyCode key, uint modifiers) 224 { 225 if (_focusedWidget !is null) 226 { 227 auto event = new KeyReleaseEvent(key, modifiers); 228 event.context = _context; 229 _focusedWidget.handleEvent(event); 230 } 231 232 return false; 233 } 234 235 /// Handler for char enter event. 236 /// 237 /// Must be called by user application. 238 bool charEntered(in dchar chr) 239 { 240 if (_focusedWidget !is null) 241 { 242 auto event = new CharEnterEvent(chr); 243 event.context = _context; 244 _focusedWidget.handleEvent(event); 245 } 246 247 return false; 248 } 249 250 /// Handler for pointer press event. 251 /// 252 /// Must be called by user application. 253 bool pointerPressed(ivec2 pointerPosition, PointerButton button) 254 { 255 _lastPointerPosition = pointerPosition; 256 257 auto event = new PointerPressEvent(pointerPosition, button); 258 event.context = _context; 259 260 foreach_reverse(rootWidget; _context.roots) 261 { 262 Widget[] widgetChain = buildPathToLeaf!(containsPointer)(rootWidget, pointerPosition); 263 264 Widget[] eventConsumerChain = propagateEventSinkBubble(widgetChain, event); 265 266 if (eventConsumerChain.length > 0) 267 { 268 if (eventConsumerChain[$-1].getPropertyAs!("isFocusable", bool)) 269 focusedWidget = eventConsumerChain[$-1]; 270 271 pressedWidget = eventConsumerChain[$-1]; 272 273 return false; 274 } 275 } 276 277 focusedWidget = null; 278 279 return false; 280 } 281 282 /// Handler for pointer release event. 283 /// 284 /// Must be called by user application. 285 bool pointerReleased(ivec2 pointerPosition, PointerButton button) 286 { 287 _lastPointerPosition = pointerPosition; 288 289 scope event = new PointerReleaseEvent(pointerPosition, button); 290 event.context = _context; 291 292 root_loop: 293 foreach_reverse(rootWidget; _context.roots) 294 { 295 Widget[] widgetChain = buildPathToLeaf!(containsPointer)(rootWidget, pointerPosition); 296 297 foreach_reverse(item; widgetChain) // test if pointer over pressed widget. 298 { 299 if (item is pressedWidget) 300 { 301 Widget[] eventConsumerChain = propagateEventSinkBubble(widgetChain, event); 302 303 if (eventConsumerChain.length > 0) 304 { 305 if (pressedWidget is eventConsumerChain[$-1]) 306 { 307 scope clickEvent = new PointerClickEvent(pointerPosition, button); 308 clickEvent.context = _context; 309 310 pressedWidget.handleEvent(clickEvent); 311 312 lastClickedWidget = pressedWidget; 313 } 314 } 315 316 pressedWidget = null; 317 318 return true; 319 } 320 } 321 } 322 323 if (pressedWidget !is null) // no one handled event. Let's pressed widget know that pointer released. 324 { 325 pressedWidget.handleEvent(event); // pressed widget will know if pointer unpressed somwhere else. 326 327 scope moveEvent = new PointerMoveEvent(pointerPosition, ivec2(0, 0)); 328 moveEvent.context = _context; 329 updateHovered(moveEvent); // So widget knows if pointer released not over it. 330 } 331 332 pressedWidget = null; 333 334 return false; 335 } 336 337 338 /// Handler for pointer move event. 339 /// 340 /// Must be called by user application. 341 bool pointerMoved(ivec2 newPointerPosition, ivec2 delta) 342 { 343 _lastPointerPosition = newPointerPosition; 344 345 scope event = new PointerMoveEvent(newPointerPosition, delta); 346 event.context = _context; 347 348 if (pressedWidget !is null) 349 { 350 bool handled = pressedWidget.handleEvent(event); 351 352 if (handled) 353 { 354 hoveredWidget = pressedWidget; 355 356 return true; 357 } 358 } 359 else 360 { 361 if (updateHovered(event)) 362 return true; 363 } 364 365 hoveredWidget = null; 366 367 return false; 368 } 369 370 bool updateHovered(PointerMoveEvent event) 371 { 372 foreach_reverse(rootWidget; _context.roots) 373 { 374 Widget[] widgetChain = buildPathToLeaf!(containsPointer)(rootWidget, event.pointerPosition); 375 376 foreach_reverse(widget; widgetChain) 377 { 378 if (widget.getPropertyAs!("respondsToPointer", bool)) 379 { 380 hoveredWidget = widget; 381 return true; 382 } 383 } 384 } 385 386 hoveredWidget = null; 387 388 return false; 389 } 390 }