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