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