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