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