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