1 /** 2 Copyright: Copyright (c) 2013-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 voxelman.gui.eventpropagators; 8 9 import std.traits; 10 import std.typecons : Flag, Yes, No; 11 import voxelman.gui; 12 13 /// 14 enum PropagatingStrategy 15 { 16 /// First visits parent then child until target is reached. 17 /// Then sets event's bubbling flag to true and visits widgets from 18 /// target to root. 19 SinkBubble, 20 21 /// First visits target then its parent etc, until reaches root. 22 /// Then sets event's sinking flag to true and visits widgets from root to target. 23 BubbleSink, 24 25 /// Visit all the subtree from bottom up. parent gets visited after all its children was visited. 26 /// Also called Pre-order. 27 ChildrenFirst, 28 29 /// Visits all subtree from root to leafs, visiting parent first and then all its subtrees. Depth-first. 30 /// Also called Post-order. 31 ParentFirst 32 } 33 34 enum OnHandle 35 { 36 StopTraversing, 37 ContinueTraversing 38 } 39 40 /// Returns sub-chain that handled event if onHandle is StopTraversing 41 /// Doesn't check for hidden flag 42 WidgetId[] propagateEventSinkBubble(Event)( 43 GuiContext context, 44 return WidgetId[] widgets, 45 auto ref Event event, 46 OnHandle onHandle) 47 { 48 // Phase 1: event sinking to target. 49 event.sinking = true; 50 51 foreach(index, widgetId; widgets) 52 { 53 context.postEvent(widgetId, event); 54 55 if(onHandle == OnHandle.StopTraversing) 56 { 57 if (event.handled) return widgets[0..index+1]; 58 } 59 } 60 61 // Phase 2: event bubling from target. 62 event.bubbling = true; 63 foreach_reverse(index, widgetId; widgets) 64 { 65 context.postEvent(widgetId, event); 66 67 if(onHandle == OnHandle.StopTraversing) 68 { 69 if (event.handled) return widgets[0..index+1]; 70 } 71 } 72 73 return null; 74 } 75 76 void propagateEventParentFirst(Event)(GuiContext context, WidgetId root, auto ref Event event) 77 { 78 event.sinking = true; 79 80 void propagateEvent(WidgetId root) 81 { 82 if (context.has!hidden(root)) return; 83 context.postEvent(root, event); 84 85 foreach(WidgetId child; context.widgetChildren(root)) 86 { 87 propagateEvent(child); 88 } 89 } 90 91 propagateEvent(root); 92 } 93 94 void propagateEventSinkBubbleTree 95 (Flag!"CheckHidden" checkHidden = Yes.CheckHidden, Event) 96 (GuiContext context, WidgetId root, auto ref Event event) 97 { 98 static if(checkHidden) 99 { 100 if (context.has!hidden(root)) return; 101 } 102 103 event.sinking = true; 104 context.postEvent(root, event); 105 106 foreach (WidgetId widgetId; context.widgetChildren(root)) 107 { 108 event.sinking = true; 109 propagateEventSinkBubbleTree!checkHidden(context, widgetId, event); 110 } 111 112 event.bubbling = true; 113 context.postEvent(root, event); 114 } 115 116 void propagateEventChildrenFirst(Event)(GuiContext context, WidgetId root, auto ref Event event) 117 { 118 if (context.has!hidden(root)) return; 119 120 event.bubbling = true; 121 122 void propagateEvent(WidgetId root) 123 { 124 foreach(child; context.widgetChildren(root)) propagateEvent(child); 125 context.postEvent(root, event); 126 } 127 128 propagateEvent(root); 129 } 130 131 /// Tests all root's children with pred. 132 /// Then calls itself with found child. 133 /// Adds widgets satisfying pred to returned array. 134 /// Root widget is added first. 135 /// Can be used to find widget that is under cursor 136 /// Parameters: 137 /// pred function like bool fun(WidgetId widget, ...) 138 /// root root of widget subtree/tree 139 WidgetId[] buildPathToLeaf(alias pred, T...)(GuiContext context, WidgetId root, T data) 140 { 141 WidgetId[] path; 142 143 bool traverse(WidgetId root) 144 { 145 if (context.has!hidden(root)) return false; 146 147 if(!pred(root, data)) return false; 148 149 path ~= root; 150 151 foreach_reverse(child; context.widgetChildren(root)) 152 { 153 if (traverse(child)) 154 { 155 return true; 156 } 157 } 158 159 return true; 160 } 161 162 traverse(root); 163 164 return path; 165 }