1 
2 module ws.gui.base;
3 
4 import
5 	std.algorithm,
6 	std.array,
7 	ws.draw,
8 	ws.wm,
9 	ws.gui.dragger;
10 
11 public import
12 	ws.gui.point,
13 	ws.gui.input,
14 	ws.gui.style;
15 
16 T[] without(T)(T[] array, T elem){
17 	auto i = array.countUntil(elem);
18 	if(i < 0)
19 		return array;
20 	return array[0..i] ~ array[i+1..$];
21 }
22 
23 
24 class Base {
25 
26 	Style style;
27 	Mouse.cursor cursor;
28 	int[2] size;
29 	int[2] pos;
30 	Base parent;
31 	Base mouseChild;
32 	Base keyboardChild;
33 	bool hidden = false;
34 	Base[] children;
35 	bool[int] buttons;
36 	bool drawOutside = true;
37 
38 	T addNew(alias T, Args...)(Args args){
39 		T e = new T(args);
40 		add(e);
41 		return e;
42 	}
43 
44 	Base add(Base gui){
45 		if(gui.parent)
46 			throw new Exception("Trying to embed element that already has a parent");
47 		gui.parent = this;
48 		gui.hidden = false;
49 		children ~= gui;
50 		return gui;
51 	}
52 
53 	Base[] hiddenChildren(){
54 		return children.filter!"a.hidden".array;
55 	}
56 
57 	void remove(Base widget){
58 		assert(widget.parent == this);
59 		children = children.without(widget);
60 		widget.parent = null;
61 	}
62 
63 	Base findChild(int x, int y, Base[] filter=[]){
64 		foreach(child; children){
65 			if(child.hidden || filter.canFind(child))
66 				continue;
67 			if(child.pos.x <= x && child.pos.x+child.size.x >= x && child.pos.y <= y && child.pos.y+child.size.y >= y){
68 				return child;
69 			}
70 		}
71 		if(filter.canFind(this))
72 			return null;
73 		return this;
74 	}
75 
76 	Base draggingChild(){
77 		if(parent)
78 			return parent.draggingChild;
79 		return null;
80 	}
81 
82 	Base drag(int[2] offset){
83 		return null;
84 	}
85 
86 	Base dropTarget(int x, int y, Base draggable){
87 		auto targetChild = findChild(x, y, [this,draggable]);
88 		if(targetChild)
89 			return targetChild.dropTarget(x, y, draggable);
90 		return null;
91 	}
92 
93 	void dropPreview(int x, int y, Base draggable, bool start){
94 		assert(false, "dropPreview not implemented");
95 	}
96 
97 	void drop(int x, int y, Base draggable){
98 		if(findChild(x, y, [this,draggable]))
99 			findChild(x, y, [this,draggable]).drop(x, y, draggable);
100 	}
101 
102 
103 	Base root(){
104 		if(parent)
105 			return parent.root;
106 		return this;
107 	}
108 
109 	Draggable grab(int x, int y){
110 		if(findChild(x, y) != this)
111 			return findChild(x, y).grab(x, y);
112 		return null;
113 	}
114 
115 	void receive(Draggable what){
116 		assert(0, "receiveShadow implies receive");
117 	}
118 
119 	Base receiveShadow(Draggable what, int x, int y){
120 		foreach(child; children)
121 			if(child.pos.x < x && child.pos.x+child.size.x > x && child.pos.y < y && child.pos.y+child.size.y > y)
122 				return child.receiveShadow(what, x, y);
123 		return null;
124 	}
125 
126 
127 	void setTop(Base child){
128 		if(child.hidden || keyboardChild == child)
129 			return;
130 
131 		if(keyboardChild)
132 			keyboardChild.onKeyboardFocus(false);
133 
134 		if(parent)
135 			parent.setTop(this);
136 
137 		children = children.without(child);
138 		children = child ~ children;
139 
140 		keyboardChild = child;
141 		child.onKeyboardFocus(true);
142 	}
143 
144 	bool hasFocus(){
145 		if(!parent)
146 			return false;
147 		return parent.keyboardChild == this;
148 	}
149 
150 	bool hasMouseFocus(){
151 		if(!parent)
152 			return false;
153 		return parent.mouseChild == this && parent.hasMouseFocus;
154 	}
155 
156 	void show(){
157 		if(!hidden)
158 			return;
159 		hidden = false;
160 		if(parent)
161 			root.onMouseMove(cursorPos.x, cursorPos.y);
162 		onShow;
163 	}
164 
165 	void hide(){
166 		if(hidden)
167 			return;
168 		hidden = true;
169 		onMouseFocus(false);
170 		onKeyboardFocus(false);
171 		if(parent){
172 			if(parent.keyboardChild == this){
173 				foreach(pc; parent.children){
174 					if(pc.hidden)
175 						continue;
176 					parent.setTop(pc);
177 					break;
178 				}
179 			}
180 			auto p = parent;
181 			while(p.parent)
182 				p = p.parent;
183 			p.onMouseMove(cursorPos.x, cursorPos.y);
184 		}
185 		onHide();
186 	}
187 
188 	int[2] cursorPos(){
189 		if(parent)
190 			return parent.cursorPos;
191 		return [-1, -1];
192 	}
193 
194 	void onShow(){};
195 	void onHide(){};
196 
197 	void onClose(){};
198 
199 	void resize(int[2] size){
200 		this.size = size;
201 	}
202 
203 	void resizeRequest(Base child, int[2] size){}
204 
205 	void move(int[2] pos){
206 		foreach(c; children)
207 			c.move([c.pos.x+pos.x-this.pos.x, c.pos.y+pos.y-this.pos.y]);
208 		this.pos = pos;
209 	}
210 
211 	void moveLocal(int[2] pos){
212 		if(parent)
213 			move([parent.pos.x+pos.x, parent.pos.y+pos.y]);
214 		else
215 			move(pos);
216 	}
217 
218 	void onKeyboard(Keyboard.key key, bool pressed){
219 		if(keyboardChild)
220 			keyboardChild.onKeyboard(key, pressed);
221 	};
222 
223 	void onKeyboard(dchar c){
224 		if(keyboardChild)
225 			keyboardChild.onKeyboard(c);
226 	};
227 
228 	void onKeyboardFocus(bool focus){
229 		if(keyboardChild)
230 			keyboardChild.onKeyboardFocus(false);
231 	}
232 
233 	void onMouseMove(int x, int y){
234 		bool foundFocus = false;
235 		auto child = findChild(x, y);
236 		if(child == mouseChild || draggingChild){
237 			foundFocus = true;
238 		}else if(child && child != this){
239 			if(mouseChild)
240 				mouseChild.onMouseFocus(false);
241 			child.onMouseFocus(true);
242 			if(wm.active)
243 				wm.active.setCursor(child.cursor);
244 			mouseChild = child;
245 			foundFocus = true;
246 		}
247 
248 		if(mouseChild){
249 			if(!foundFocus){
250 				mouseChild.onMouseFocus(false);
251 				mouseChild = null;
252 				if(wm.active)
253 					wm.active.setCursor(cursor);
254 			}else{
255 				mouseChild.onMouseMove(x, y);
256 			}
257 		}
258 	};
259 
260 	void onMouseButton(Mouse.button b, bool p, int x, int y){
261 		buttons[b] = p;
262 		if(mouseChild)
263 			mouseChild.onMouseButton(b, p, x, y);
264 	};
265 
266 	void onMouseFocus(bool f){
267 		if(!f && mouseChild){
268 			mouseChild.onMouseFocus(false);
269 			mouseChild = null;
270 		}
271 	};
272 
273 	void setCursor(Mouse.cursor c){
274 		cursor = c;
275 		if(parent && parent.mouseChild == this)
276 			wm.active.setCursor(cursor);
277 	}
278 
279 	DrawEmpty draw(){
280 		return parent.draw;
281 	}
282 
283 	void onDraw(){
284 		foreach_reverse(c; children)
285 			if(!c.hidden &&
286 					(drawOutside
287 					|| c.pos.x-c.size.w > pos.x
288 					&& c.pos.x < pos.x+size.w
289 					&& c.pos.y-c.size.h > pos.y
290 					&& c.pos.y < pos.y+size.h))
291 				c.onDraw;
292 	}
293 
294 	void setStyle(Style style){
295 		this.style = style;
296 	}
297 
298 }