1 /* 2 Copyright (c) 2013 Andrey Penechko 3 4 Boost Software License - Version 1.0 - August 17th, 2003 5 6 Permission is hereby granted, free of charge, to any person or organization 7 obtaining a copy of the software and accompanying documentation covered by 8 this license the "Software" to use, reproduce, display, distribute, 9 execute, and transmit the Software, and to prepare derivative works of the 10 Software, and to permit third-parties to whom the Software is furnished to 11 do so, all subject to the following: 12 13 The copyright notices in the Software and this entire statement, including 14 the above license grant, this restriction and the following disclaimer, 15 must be included in all copies of the Software, in whole or in part, and 16 all derivative works of the Software, unless such copies or derivative 17 works are solely in the form of machine-executable object code generated by 18 a source language processor. 19 20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 23 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 24 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 25 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 DEALINGS IN THE SOFTWARE. 27 */ 28 29 module anchovy.gui.guicontext; 30 31 import anchovy.gui; 32 import anchovy.gui.interfaces.iwidgetbehavior : IWidgetBehavior; 33 34 //version = Debug_guicontext; 35 36 class GuiContext 37 { 38 alias WidgetCreator = Widget delegate(); 39 alias LayoutCreator = ILayout delegate(); 40 alias BehaviorCreator = IWidgetBehavior delegate(); 41 42 WidgetCreator[string] widgetFactories; 43 LayoutCreator[string] layoutFactories; 44 BehaviorCreator[][string] behaviorFactories; 45 46 Widget[] roots; 47 48 bool isLayoutValid; // Will be updated in update method 49 50 void invalidateWidgetLayout(Widget container) 51 { 52 isLayoutValid = false; 53 } 54 55 void doLayout() 56 { 57 foreach(root; roots) 58 { 59 root.propagateEventChildrenFirst(new MinimizeLayoutEvent); 60 root.propagateEventParentFirst(new ExpandLayoutEvent); 61 root.propagateEventParentFirst(new UpdatePositionEvent); 62 } 63 } 64 65 void update(double deltaTime) 66 { 67 if (!isLayoutValid) 68 { 69 doLayout(); 70 isLayoutValid = true; 71 72 if (pressedWidget !is null) 73 { 74 scope moveEvent = new PointerMoveEvent(lastPointerPosition, ivec2(0, 0)); 75 moveEvent.context = this; 76 updateHovered(moveEvent); 77 } 78 } 79 } 80 81 protected: 82 KeyModifiers modifiers; 83 84 /// Gui renderer used for drawing all children widgets. 85 IGuiRenderer _guiRenderer; 86 87 /// Used for timers. 88 TimerManager _timerManager; 89 90 TemplateManager _templateManager; 91 92 /// Current input owner If set, this widget will receive all pointer moved events. 93 /// See_Also: inputOwnerWidget 94 Widget _inputOwnerWidget; 95 96 /// Currently dragging widget. Will receive onDrag events. 97 Widget _draggingWidget; 98 99 /// Last clicked widget. Used for double-click checking. 100 /// See_Also: lastClickedWidget 101 Widget _lastClickedWidget; 102 103 Widget _pressedWidget; 104 105 /// Hovered widget. Widget over which pointer is located. 106 /// See_Also: hoveredWidget 107 Widget _hoveredWidget; 108 109 /// Focused widget. 110 /// 111 /// Will receive all key events if input is not grabbed by other widget. 112 Widget _focusedWidget; 113 114 /// Stores widgets with id property. 115 Widget[string] ids; 116 117 ivec2 lastPointerPosition = ivec2(int.max, int.max); 118 119 /// This will be called when widget sets clipboard string. 120 void delegate(dstring newClipboardString) _setClipboardStringCallback; 121 122 /// This will be called when widget requests clipboard string. 123 dstring delegate() _getClipboardStringCallback; 124 125 public: 126 127 this(IGuiRenderer guiRenderer, TimerManager timerManager, TemplateManager templateManager, GuiSkin skin) 128 in 129 { 130 assert(guiRenderer); 131 assert(skin); 132 assert(timerManager); 133 } 134 body 135 { 136 _guiRenderer = guiRenderer; 137 _timerManager = timerManager; 138 _templateManager = templateManager; 139 } 140 141 void addRoot(Widget root) 142 { 143 root.setProperty!"size"(cast(ivec2)_guiRenderer.renderer.windowSize); 144 roots ~= root; 145 } 146 147 Widget createBaseWidget(string type) 148 { 149 if (auto factory = type in widgetFactories) 150 { 151 return widgetFactories[type](); 152 } 153 else 154 { 155 return new Widget; 156 } 157 } 158 159 //---------------------------- Helpers --------------------------------- 160 import std.conv : parse; 161 import std.string : munch; 162 163 private Variant parseProperty(string name, ref Variant value, Widget widget) 164 { 165 switch(name) 166 { 167 case "layout": 168 //writeln("found layout property: ", value.get!string); 169 if (auto factory = value.get!string in layoutFactories) 170 { 171 return Variant((*factory)()); 172 } 173 writeln("Error: unknown layout '", value.get!string, "' found"); 174 break; 175 case "minSize", "prefSize": 176 try 177 { 178 string nums = value.get!string; 179 int w = parse!int(nums); 180 munch(nums, " \t\n\r"); 181 int h = parse!int(nums); 182 return Variant(ivec2(w, h)); 183 } 184 catch (Exception e) 185 { 186 writeln("Error parsing "~name~" ", e); 187 } 188 return Variant(ivec2(16, 16)); 189 break; 190 case "id": 191 string id = value.get!string; 192 if (id in ids) 193 { 194 writeln("Duplicate id found: ", id, ", overriding..."); 195 } 196 ids[id] = widget; 197 198 return value; 199 break; 200 default: 201 return value; 202 } 203 204 return value; 205 } 206 207 Widget createSubwidget(SubwidgetTemplate sub, Widget subwidget, Widget root) 208 { 209 //----------------------- Forwarding properties ------------------------ 210 foreach(forwardedProperty; sub.forwardedProperties) 211 { 212 version(Debug_guicontext) writeln("forwarding ", forwardedProperty.propertyName ," to ", 213 forwardedProperty.targetPropertyName); 214 root[forwardedProperty.propertyName] = subwidget.property(forwardedProperty.targetPropertyName); 215 } 216 217 //------------------------ Assigning properties ------------------------ 218 foreach(propertyKey; sub.properties.byKey) 219 { 220 auto value = parseProperty(propertyKey, sub.properties[propertyKey], subwidget); 221 version(Debug_guicontext) writeln("Assigning properties ", propertyKey," ",value, 222 " ", subwidget["name"], " ", subwidget["type"], " ", root["name"], " ", root["type"]); 223 subwidget[propertyKey] = value; 224 version(Debug_guicontext) writeln(subwidget[propertyKey]); 225 } 226 227 if (sub.isContainer) 228 { 229 root["container"] = subwidget; 230 } 231 232 Variant* name = "name" in sub.properties; 233 if(name) 234 { 235 if (auto subtemplateName = name.peek!string) 236 { 237 Widget[string] subwidgets = root["subwidgets"].get!(Widget[string]); 238 subwidgets[*subtemplateName] = subwidget; 239 root["subwidgets"] = subwidgets; 240 } 241 } 242 243 //------------------------ Creating subwidgets ------------------------- 244 foreach(subtemplate; sub.subwidgets) 245 { 246 createSubwidget(subtemplate, createWidget(subtemplate.properties["type"].get!string, subwidget), root); 247 } 248 249 return subwidget; 250 } 251 252 Widget createWidget(string type, Widget parent = null) 253 { 254 Widget widget; 255 256 //----------------------- Instatiating templates --------------------------- 257 258 //writeln("template for ", type, " is ", _templateManager.getTemplate(type)); 259 if (WidgetTemplate templ = _templateManager.getTemplate(type)) 260 { 261 //----------------------- Base type construction ----------------------- 262 263 Widget baseWidget; 264 265 if (templ.baseType != "widget") 266 { 267 baseWidget = createWidget(templ.baseType); 268 } 269 else 270 { 271 baseWidget = createBaseWidget(type); // Create using factory. 272 } 273 274 baseWidget["type"] = type; 275 baseWidget["context"] = this; // widget may access context before construction ends. 276 baseWidget.setProperty!("subwidgets", Widget[string])(null); 277 278 //----------------------- Template construction ------------------------ 279 // Recursively creates widgets as stated in template. widget is root of that tree. 280 widget = createSubwidget(templ.tree, baseWidget, baseWidget); 281 282 widget["template"] = templ; 283 } 284 else 285 { 286 // if there is no template, lets create regular one. 287 widget = createBaseWidget(type); 288 } 289 290 // default style 291 if (widget["style"] == Variant(null)) 292 { 293 widget["style"] = type; 294 } 295 296 widget["context"] = this; // if widget attempts to override context. 297 298 // adding parent 299 if (parent !is null) 300 { 301 addChild(parent, widget); 302 } 303 else 304 { 305 widget["parent"] = null; 306 } 307 308 //----------------------- Attaching behaviors --------------------------- 309 if (auto factories = type in behaviorFactories) 310 { 311 foreach(factory; *factories) 312 { 313 factory().attachTo(widget); 314 } 315 } 316 317 return widget; 318 } 319 320 /// Returns widget found by given id. 321 Widget getWidgetById(string id) 322 { 323 if (auto widget = id in ids) 324 { 325 return *widget; 326 } 327 else 328 return null; 329 } 330 331 //+-------------------------------------------------------------------------------+ 332 //| Properties | 333 //+-------------------------------------------------------------------------------+ 334 335 @property 336 { 337 IGuiRenderer guiRenderer() 338 { 339 return _guiRenderer; 340 } 341 342 /// Sets new size for all root widgets. 343 void size(ivec2 newSize) 344 { 345 foreach(widget; roots) 346 { 347 widget.setProperty!"size"(newSize); 348 } 349 } 350 351 /// Used to get last clicked widget. 352 Widget lastClickedWidget() @safe 353 { 354 return _lastClickedWidget; 355 } 356 357 /// Used to set last clicked widget. 358 void lastClickedWidget(Widget widget) @safe 359 { 360 _lastClickedWidget = widget; 361 } 362 363 /// Used to get current hovered widget. 364 Widget hoveredWidget() @safe 365 { 366 return _hoveredWidget; 367 } 368 369 /// Used to set current hovered widget. 370 void hoveredWidget(Widget widget) @trusted 371 { 372 if (_hoveredWidget !is widget) 373 { 374 if (_hoveredWidget !is null) 375 { 376 auto event = new PointerLeaveEvent; 377 event.context = this; 378 _hoveredWidget.handleEvent(event); 379 } 380 381 if (widget !is null) 382 { 383 auto event = new PointerEnterEvent; 384 event.context = this; 385 widget.handleEvent(event); 386 } 387 388 _hoveredWidget = widget; 389 } 390 } 391 392 /// Used to get current focused input owner widget 393 Widget inputOwnerWidget() @safe pure 394 { 395 return _inputOwnerWidget; 396 } 397 398 /// Used to set current focused input owner widget 399 void inputOwnerWidget(Widget widget) @trusted 400 { 401 _inputOwnerWidget = widget; 402 } 403 404 /// Used to get current focused input owner widget 405 Widget pressedWidget() @safe pure 406 { 407 return _pressedWidget; 408 } 409 410 /// Used to set current focused input owner widget 411 void pressedWidget(Widget widget) @trusted 412 { 413 _pressedWidget = widget; 414 } 415 416 /// Used to get current focused widget 417 Widget focusedWidget() @safe pure 418 { 419 return _focusedWidget; 420 } 421 422 /// Used to set current focused widget 423 void focusedWidget(Widget widget) 424 { 425 if (_focusedWidget !is widget) 426 { 427 428 429 if (_focusedWidget !is null) 430 { 431 auto event = new FocusLoseEvent; 432 event.context = this; 433 _focusedWidget.handleEvent(event); 434 } 435 436 if (widget !is null) 437 { 438 auto event = new FocusGainEvent; 439 event.context = this; 440 widget.handleEvent(event); 441 //write("focused ", widget, " "); 442 //write(widget["name"], " ", widget["type"]); 443 } 444 //writeln; 445 446 _focusedWidget = widget; 447 } 448 } 449 450 /// Used to get current clipboard string 451 string clipboardString() 452 { 453 if (_getClipboardStringCallback !is null) 454 return to!string(_getClipboardStringCallback()); 455 else 456 return ""; 457 } 458 459 /// Used to set current clipboard string 460 void clipboardString(string newString) 461 { 462 if (_setClipboardStringCallback !is null) 463 _setClipboardStringCallback(to!dstring(newString)); 464 } 465 466 /// Will be used by window to provide clipboard functionality. 467 void getClipboardStringCallback(dstring delegate() callback) 468 { 469 _getClipboardStringCallback = callback; 470 } 471 472 /// ditto 473 void setClipboardStringCallback(void delegate(dstring) callback) 474 { 475 _setClipboardStringCallback = callback; 476 } 477 478 TimerManager timerManager() 479 { 480 return _timerManager; 481 } 482 } 483 484 //+-------------------------------------------------------------------------------+ 485 //| Event handling | 486 //+-------------------------------------------------------------------------------+ 487 488 static bool containsPointer(Widget widget, ivec2 pointerPosition) 489 { 490 return widget.getPropertyAs!("staticRect", Rect).contains(pointerPosition); 491 } 492 493 void draw() 494 { 495 auto event = new DrawEvent(_guiRenderer); 496 event.context = this; 497 498 foreach(root; roots) 499 { 500 root.propagateEventParentFirst(event); 501 } 502 } 503 504 /// Handler for key press event. 505 /// 506 /// Must be called by user application. 507 bool keyPressed(in KeyCode key, KeyModifiers modifiers) 508 { 509 if (_focusedWidget !is null) 510 { 511 auto event = new KeyPressEvent(key, modifiers); 512 event.context = this; 513 _focusedWidget.handleEvent(event); 514 } 515 516 return false; 517 } 518 519 /// Handler for key release event. 520 /// 521 /// Must be called by user application. 522 bool keyReleased(in KeyCode key, KeyModifiers modifiers) 523 { 524 if (_focusedWidget !is null) 525 { 526 auto event = new KeyReleaseEvent(key, modifiers); 527 event.context = this; 528 _focusedWidget.handleEvent(event); 529 } 530 531 return false; 532 } 533 534 /// Handler for char enter event. 535 /// 536 /// Must be called by user application. 537 bool charEntered(in dchar chr) 538 { 539 if (_focusedWidget !is null) 540 { 541 auto event = new CharEnterEvent(chr); 542 event.context = this; 543 _focusedWidget.handleEvent(event); 544 } 545 546 return false; 547 } 548 549 /// Handler for pointer press event. 550 /// 551 /// Must be called by user application. 552 bool pointerPressed(ivec2 pointerPosition, PointerButton button) 553 { 554 lastPointerPosition = pointerPosition; 555 556 auto event = new PointerPressEvent(pointerPosition, button); 557 event.context = this; 558 559 foreach_reverse(rootWidget; roots) 560 { 561 Widget[] widgetChain = buildPathToLeaf!(containsPointer)(rootWidget, pointerPosition); 562 563 Widget[] eventConsumerChain = propagateEventSinkBubble(widgetChain, event); 564 565 if (eventConsumerChain.length > 0) 566 { 567 if (eventConsumerChain[$-1].getPropertyAs!("isFocusable", bool)) 568 focusedWidget = eventConsumerChain[$-1]; 569 570 pressedWidget = eventConsumerChain[$-1]; 571 572 return false; 573 } 574 } 575 576 focusedWidget = null; 577 578 return false; 579 } 580 581 /// Handler for pointer release event. 582 /// 583 /// Must be called by user application. 584 bool pointerReleased(ivec2 pointerPosition, PointerButton button) 585 { 586 lastPointerPosition = pointerPosition; 587 588 scope event = new PointerReleaseEvent(pointerPosition, button); 589 event.context = this; 590 591 root_loop: 592 foreach_reverse(rootWidget; roots) 593 { 594 Widget[] widgetChain = buildPathToLeaf!(containsPointer)(rootWidget, pointerPosition); 595 596 foreach_reverse(item; widgetChain) // test if pointer over pressed widget. 597 { 598 if (item is pressedWidget) 599 { 600 Widget[] eventConsumerChain = propagateEventSinkBubble(widgetChain, event); 601 602 if (eventConsumerChain.length > 0) 603 { 604 if (pressedWidget is eventConsumerChain[$-1]) 605 { 606 scope clickEvent = new PointerClickEvent(pointerPosition, button); 607 clickEvent.context = this; 608 609 pressedWidget.handleEvent(clickEvent); 610 611 lastClickedWidget = pressedWidget; 612 } 613 } 614 615 pressedWidget = null; 616 617 return true; 618 } 619 } 620 } 621 622 if (pressedWidget !is null) // no one handled event. Let's pressed widget know that pointer released. 623 { 624 pressedWidget.handleEvent(event); // pressed widget will know if pointer unpressed somwhere else. 625 626 scope moveEvent = new PointerMoveEvent(pointerPosition, ivec2(0, 0)); 627 moveEvent.context = this; 628 updateHovered(moveEvent); // So widget knows if pointer released not over it. 629 } 630 631 pressedWidget = null; 632 633 return false; 634 } 635 636 637 /// Handler for pointer move event. 638 /// 639 /// Must be called by user application. 640 bool pointerMoved(ivec2 newPointerPosition, ivec2 delta) 641 { 642 lastPointerPosition = newPointerPosition; 643 644 scope event = new PointerMoveEvent(newPointerPosition, delta); 645 event.context = this; 646 647 if (pressedWidget !is null) 648 { 649 bool handled = pressedWidget.handleEvent(event); 650 651 if (handled) 652 { 653 hoveredWidget = pressedWidget; 654 655 return true; 656 } 657 } 658 else 659 { 660 if (updateHovered(event)) 661 return true; 662 } 663 664 hoveredWidget = null; 665 666 return false; 667 } 668 669 bool updateHovered(PointerMoveEvent event) 670 { 671 foreach_reverse(rootWidget; roots) 672 { 673 Widget[] widgetChain = buildPathToLeaf!(containsPointer)(rootWidget, event.pointerPosition); 674 675 Widget[] eventConsumerChain = propagateEventSinkBubble(widgetChain, event); 676 677 if (eventConsumerChain.length > 0) 678 { 679 hoveredWidget = eventConsumerChain[$-1]; 680 681 return true; 682 } 683 } 684 685 hoveredWidget = null; 686 687 return false; 688 } 689 }