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 }