1 // Written by Christopher E. Miller
2 // See the included license.txt for copyright and license details.
3 
4 
5 ///
6 module dfl.splitter;
7 
8 private import dfl.control, dfl.internal.winapi, dfl.base, dfl.drawing;
9 private import dfl.event;
10 
11 
12 ///
13 class SplitterEventArgs: EventArgs
14 {
15 	///
16 	this(int x, int y, int splitX, int splitY)
17 	{
18 		_x = x;
19 		_y = y;
20 		_splitX = splitX;
21 		_splitY = splitY;
22 	}
23 	
24 	
25 	///
26 	final @property int x() // getter
27 	{
28 		return _x;
29 	}
30 	
31 	
32 	///
33 	final @property int y() // getter
34 	{
35 		return _y;
36 	}
37 	
38 	
39 	///
40 	final @property void splitX(int val) // setter
41 	{
42 		_splitX = val;
43 	}
44 	
45 	/// ditto
46 	final @property int splitX() // getter
47 	{
48 		return _splitX;
49 	}
50 	
51 	
52 	///
53 	final @property void splitY(int val) // setter
54 	{
55 		_splitY = val;
56 	}
57 	
58 	/// ditto
59 	final @property int splitY() // getter
60 	{
61 		return _splitY;
62 	}
63 	
64 	
65 	private:
66 	int _x, _y, _splitX, _splitY;
67 }
68 
69 
70 ///
71 class Splitter: Control // docmain
72 {
73 	this()
74 	{
75 		// DMD 0.95: need 'this' to access member dock
76 		this.dock = DockStyle.LEFT;
77 		
78 		if(HBRUSH.init == hbrxor)
79 			inithbrxor();
80 	}
81 	
82 	
83 	/+
84 	override @property void anchor(AnchorStyles a) // setter
85 	{
86 		throw new DflException("Splitter cannot be anchored");
87 	}
88 	
89 	alias Control.anchor anchor; // Overload.
90 	+/
91 	
92 	
93 	override @property void dock(DockStyle ds) // setter
94 	{
95 		switch(ds)
96 		{
97 			case DockStyle.LEFT:
98 			case DockStyle.RIGHT:
99 				//cursor = new Cursor(LoadCursorA(null, IDC_SIZEWE), false);
100 				cursor = Cursors.vSplit;
101 				break;
102 			
103 			case DockStyle.TOP:
104 			case DockStyle.BOTTOM:
105 				//cursor = new Cursor(LoadCursorA(null, IDC_SIZENS), false);
106 				cursor = Cursors.hSplit;
107 				break;
108 			
109 			default:
110 				throw new DflException("Invalid splitter dock");
111 		}
112 		
113 		super.dock(ds);
114 	}
115 	
116 	alias Control.dock dock; // Overload.
117 	
118 	
119 	package void initsplit(int sx, int sy)
120 	{
121 		capture = true;
122 		
123 		downing = true;
124 		//downpos = Point(mea.x, mea.y);
125 		
126 		switch(dock)
127 		{
128 			case DockStyle.TOP:
129 			case DockStyle.BOTTOM:
130 				downpos = sy;
131 				lastpos = 0;
132 				drawxorClient(0, lastpos);
133 				break;
134 			
135 			default: // LEFT / RIGHT.
136 				downpos = sx;
137 				lastpos = 0;
138 				drawxorClient(lastpos, 0);
139 		}
140 	}
141 	
142 	
143 	final void resumeSplit(int sx, int sy) // package
144 	{
145 		if(Control.mouseButtons & MouseButtons.LEFT)
146 		{
147 			initsplit(sx, sy);
148 			
149 			if(cursor)
150 				Cursor.current = cursor;
151 		}
152 	}
153 	
154 	// /// ditto
155 	final void resumeSplit() // package
156 	{
157 		Point pt = pointToClient(Cursor.position);
158 		return resumeSplit(pt.x, pt.y);
159 	}
160 	
161 	
162 	///
163 	@property void movingGrip(bool byes) // setter
164 	{
165 		if(mgrip == byes)
166 			return;
167 		
168 		this.mgrip = byes;
169 		
170 		if(created)
171 		{
172 			invalidate();
173 		}
174 	}
175 	
176 	/// ditto
177 	@property bool movingGrip() // getter
178 	{
179 		return mgrip;
180 	}
181 	
182 	deprecated alias movingGrip moveingGrip;
183 	deprecated alias movingGrip moveGrip;
184 	deprecated alias movingGrip sizingGrip;
185 	
186 	
187 	protected override void onPaint(PaintEventArgs ea)
188 	{
189 		super.onPaint(ea);
190 		
191 		if(mgrip)
192 		{
193 			ea.graphics.drawMoveGrip(displayRectangle, DockStyle.LEFT == dock || DockStyle.RIGHT == dock);
194 		}
195 	}
196 	
197 	
198 	protected override void onResize(EventArgs ea)
199 	{
200 		if(mgrip)
201 		{
202 			invalidate();
203 		}
204 		
205 		resize(this, ea);
206 	}
207 	
208 	
209 	protected override void onMouseDown(MouseEventArgs mea)
210 	{
211 		super.onMouseDown(mea);
212 		
213 		if(mea.button == MouseButtons.LEFT && 1 == mea.clicks)
214 		{
215 			initsplit(mea.x, mea.y);
216 		}
217 	}
218 	
219 	
220 	protected override void onMouseMove(MouseEventArgs mea)
221 	{
222 		super.onMouseMove(mea);
223 		
224 		if(downing)
225 		{
226 			switch(dock)
227 			{
228 				case DockStyle.TOP:
229 				case DockStyle.BOTTOM:
230 					drawxorClient(0, mea.y - downpos, 0, lastpos);
231 					lastpos = mea.y - downpos;
232 					break;
233 				
234 				default: // LEFT / RIGHT.
235 					drawxorClient(mea.x - downpos, 0, lastpos, 0);
236 					lastpos = mea.x - downpos;
237 			}
238 			
239 			scope sea = new SplitterEventArgs(mea.x, mea.y, left, top);
240 			onSplitterMoving(sea);
241 		}
242 	}
243 	
244 	
245 	protected override void onMove(EventArgs ea)
246 	{
247 		super.onMove(ea);
248 		
249 		if(downing) // ?
250 		{
251 			Point curpos = Cursor.position;
252 			curpos = pointToClient(curpos);
253 			scope sea = new SplitterEventArgs(curpos.x, curpos.y, left, top);
254 			onSplitterMoved(sea);
255 		}
256 	}
257 	
258 	
259 	final Control getSplitControl() // package
260 	{
261 		Control splat; // Splitted.
262 		// DMD 0.95: need 'this' to access member dock
263 		//switch(dock())
264 		final switch(this.dock())
265 		{
266 			case DockStyle.LEFT:
267 				foreach(Control ctrl; parent.controls())
268 				{
269 					if(DockStyle.LEFT != ctrl.dock) //if(this.dock != ctrl.dock)
270 						continue;
271 					// DMD 0.95: overloads int(Object o) and int(Control ctrl) both match argument list for opEquals
272 					//if(ctrl == this)
273 					if(ctrl == cast(Control)this)
274 						return splat;
275 					splat = ctrl;
276 				}
277 				break;
278 			
279 			case DockStyle.RIGHT:
280 				foreach(Control ctrl; parent.controls())
281 				{
282 					if(DockStyle.RIGHT != ctrl.dock) //if(this.dock != ctrl.dock)
283 						continue;
284 					// DMD 0.95: overloads int(Object o) and int(Control ctrl) both match argument list for opEquals
285 					//if(ctrl == this)
286 					if(ctrl == cast(Control)this)
287 						return splat;
288 					splat = ctrl;
289 				}
290 				break;
291 			
292 			case DockStyle.TOP:
293 				foreach(Control ctrl; parent.controls())
294 				{
295 					if(DockStyle.TOP != ctrl.dock) //if(this.dock != ctrl.dock)
296 						continue;
297 					// DMD 0.95: overloads int(Object o) and int(Control ctrl) both match argument list for opEquals
298 					//if(ctrl == this)
299 					if(ctrl == cast(Control)this)
300 						return splat;
301 					splat = ctrl;
302 				}
303 				break;
304 			
305 			case DockStyle.BOTTOM:
306 				foreach(Control ctrl; parent.controls())
307 				{
308 					if(DockStyle.BOTTOM != ctrl.dock) //if(this.dock != ctrl.dock)
309 						continue;
310 					// DMD 0.95: overloads int(Object o) and int(Control ctrl) both match argument list for opEquals
311 					//if(ctrl == this)
312 					if(ctrl == cast(Control)this)
313 						return splat;
314 					splat = ctrl;
315 				}
316 				break;
317 			
318 			case DockStyle.FILL:
319 				assert("DockStyle.FILL is not allowed in Splitter");
320 				break;
321 			
322 			case DockStyle.NONE:
323 				assert("DockStyle.NONE is not allowed in Splitter");
324 				break;
325 		}
326 		return null;
327 	}
328 	
329 	
330 	protected override void onMouseUp(MouseEventArgs mea)
331 	{
332 		if(downing)
333 		{
334 			capture = false;
335 			
336 			downing = false;
337 			
338 			if(mea.button != MouseButtons.LEFT)
339 			{
340 				// Abort.
341 				switch(dock)
342 				{
343 					case DockStyle.TOP:
344 					case DockStyle.BOTTOM:
345 						drawxorClient(0, lastpos);
346 						break;
347 					
348 					default: // LEFT / RIGHT.
349 						drawxorClient(lastpos, 0);
350 				}
351 				super.onMouseUp(mea);
352 				return;
353 			}
354 			
355 			int adj, val, vx;
356 			auto splat = getSplitControl(); // Splitted.
357 			if(splat)
358 			{
359 				// DMD 0.95: need 'this' to access member dock
360 				//switch(dock())
361 				switch(this.dock())
362 				{
363 					case DockStyle.LEFT:
364 						drawxorClient(lastpos, 0);
365 						//val = left - splat.left + mea.x - downpos.x;
366 						val = left - splat.left + mea.x - downpos;
367 						if(val < msize)
368 							val = msize;
369 						splat.width = val;
370 						break;
371 					
372 					case DockStyle.RIGHT:
373 						drawxorClient(lastpos, 0);
374 						//adj = right - splat.left + mea.x - downpos.x;
375 						adj = right - splat.left + mea.x - downpos;
376 						val = splat.width - adj;
377 						vx = splat.left + adj;
378 						if(val < msize)
379 						{
380 							vx -= msize - val;
381 							val = msize;
382 						}
383 						splat.bounds = Rect(vx, splat.top, val, splat.height);
384 						break;
385 					
386 					case DockStyle.TOP:
387 						drawxorClient(0, lastpos);
388 						//val = top - splat.top + mea.y - downpos.y;
389 						val = top - splat.top + mea.y - downpos;
390 						if(val < msize)
391 							val = msize;
392 						splat.height = val;
393 						break;
394 					
395 					case DockStyle.BOTTOM:
396 						drawxorClient(0, lastpos);
397 						//adj = bottom - splat.top + mea.y - downpos.y;
398 						adj = bottom - splat.top + mea.y - downpos;
399 						val = splat.height - adj;
400 						vx = splat.top + adj;
401 						if(val < msize)
402 						{
403 							vx -= msize - val;
404 							val = msize;
405 						}
406 						splat.bounds = Rect(splat.left, vx, splat.width, val);
407 						break;
408 					
409 					default:
410 				}
411 			}
412 			
413 			// This is needed when the moved control first overlaps the splitter and the splitter
414 			// gets bumped over, causing a little area to not be updated correctly.
415 			// I'll fix it someday.
416 			parent.invalidate(true);
417 			
418 			// Event..
419 		}
420 		
421 		super.onMouseUp(mea);
422 	}
423 	
424 	
425 	/+
426 	// Not quite sure how to implement this yet.
427 	// Might need to scan all controls until one of:
428 	//    Control with opposite dock (right if left dock): stay -mextra- away from it,
429 	//    Control with fill dock: that control can't have less than -mextra- width,
430 	//    Reached end of child controls: stay -mextra- away from the edge.
431 	
432 	///
433 	final @property void minExtra(int min) // setter
434 	{
435 		mextra = min;
436 	}
437 	
438 	/// ditto
439 	final @property int minExtra() // getter
440 	{
441 		return mextra;
442 	}
443 	+/
444 	
445 	
446 	///
447 	final @property void minSize(int min) // setter
448 	{
449 		msize = min;
450 	}
451 	
452 	/// ditto
453 	final @property int minSize() // getter
454 	{
455 		return msize;
456 	}
457 	
458 	
459 	///
460 	final @property void splitPosition(int pos) // setter
461 	{
462 		auto splat = getSplitControl(); // Splitted.
463 		if(splat)
464 		{
465 			// DMD 0.95: need 'this' to access member dock
466 			//switch(dock())
467 			switch(this.dock())
468 			{
469 				case DockStyle.LEFT:
470 				case DockStyle.RIGHT:
471 					splat.width = pos;
472 					break;
473 				
474 				case DockStyle.TOP:
475 				case DockStyle.BOTTOM:
476 					splat.height = pos;
477 					break;
478 				
479 				default:
480 			}
481 		}
482 	}
483 	
484 	/// ditto
485 	// -1 if not docked to a control.
486 	final @property int splitPosition() // getter
487 	{
488 		auto splat = getSplitControl(); // Splitted.
489 		if(splat)
490 		{
491 			// DMD 0.95: need 'this' to access member dock
492 			//switch(dock())
493 			switch(this.dock())
494 			{
495 				case DockStyle.LEFT:
496 				case DockStyle.RIGHT:
497 					return splat.width;
498 				
499 				case DockStyle.TOP:
500 				case DockStyle.BOTTOM:
501 					return splat.height;
502 				
503 				default:
504 			}
505 		}
506 		return -1;
507 	}
508 	
509 	
510 	//SplitterEventHandler splitterMoved;
511 	Event!(Splitter, SplitterEventArgs) splitterMoved; ///
512 	//SplitterEventHandler splitterMoving;
513 	Event!(Splitter, SplitterEventArgs) splitterMoving; ///
514 	
515 	
516 	protected:
517 	
518 	override @property Size defaultSize() // getter
519 	{
520 		//return Size(GetSystemMetrics(SM_CXSIZEFRAME), GetSystemMetrics(SM_CYSIZEFRAME));
521 		int sx = GetSystemMetrics(SM_CXSIZEFRAME);
522 		int sy = GetSystemMetrics(SM_CYSIZEFRAME);
523 		// Need a bit extra room for the move-grips.
524 		if(sx < 5)
525 			sx = 5;
526 		if(sy < 5)
527 			sy = 5;
528 		return Size(sx, sy);
529 	}
530 	
531 	
532 	///
533 	void onSplitterMoving(SplitterEventArgs sea)
534 	{
535 		splitterMoving(this, sea);
536 	}
537 	
538 	
539 	///
540 	void onSplitterMoved(SplitterEventArgs sea)
541 	{
542 		splitterMoving(this, sea);
543 	}
544 	
545 	
546 	private:
547 	
548 	bool downing = false;
549 	bool mgrip = true;
550 	//Point downpos;
551 	int downpos;
552 	int lastpos;
553 	int msize = 25; // Min size of control that's being sized from the splitter.
554 	int mextra = 25; // Min size of the control on the opposite side.
555 	
556 	static HBRUSH hbrxor;
557 	
558 	
559 	static void inithbrxor()
560 	{
561 		static ubyte[] bmbits = [0xAA, 0, 0x55, 0, 0xAA, 0, 0x55, 0,
562 			0xAA, 0, 0x55, 0, 0xAA, 0, 0x55, 0, ];
563 		
564 		HBITMAP hbm;
565 		hbm = CreateBitmap(8, 8, 1, 1, bmbits.ptr);
566 		hbrxor = CreatePatternBrush(hbm);
567 		DeleteObject(hbm);
568 	}
569 	
570 	
571 	static void drawxor(HDC hdc, Rect r)
572 	{
573 		SetBrushOrgEx(hdc, r.x, r.y, null);
574 		HGDIOBJ hbrold = SelectObject(hdc, hbrxor);
575 		PatBlt(hdc, r.x, r.y, r.width, r.height, PATINVERT);
576 		SelectObject(hdc, hbrold);
577 	}
578 	
579 	
580 	void drawxorClient(HDC hdc, int x, int y)
581 	{
582 		POINT pt;
583 		pt.x = x;
584 		pt.y = y;
585 		//ClientToScreen(handle, &pt);
586 		MapWindowPoints(handle, parent.handle, &pt, 1);
587 		
588 		drawxor(hdc, Rect(pt.x, pt.y, width, height));
589 	}
590 	
591 	
592 	void drawxorClient(int x, int y, int xold = int.min, int yold = int.min)
593 	{
594 		HDC hdc;
595 		//hdc = GetWindowDC(null);
596 		hdc = GetDCEx(parent.handle, null, DCX_CACHE);
597 		
598 		if(xold != int.min)
599 			drawxorClient(hdc, xold, yold);
600 		
601 		drawxorClient(hdc, x, y);
602 		
603 		ReleaseDC(null, hdc);
604 	}
605 }
606