1 // Written in the D programming language. 2 3 /** 4 5 This module contains base implementation of scrolling capabilities for widgets 6 7 8 ScrollWidgetBase - abstract scrollable widget (used as a base for other widgets with scrolling) 9 10 ScrollWidget - widget which can scroll its content (directly usable class) 11 12 13 Synopsis: 14 15 ---- 16 import dlangui.widgets.scroll; 17 18 // Scroll view example 19 ScrollWidget scroll = new ScrollWidget("SCROLL1"); 20 scroll.layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT); 21 WidgetGroup scrollContent = new VerticalLayout("CONTENT"); 22 scrollContent.layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT); 23 24 TableLayout table2 = new TableLayout("TABLE2"); 25 table2.colCount = 2; 26 // headers 27 table2.addChild((new TextWidget(null, "Parameter Name"d)).alignment(Align.Right | Align.VCenter)); 28 table2.addChild((new TextWidget(null, "Edit Box to edit parameter"d)).alignment(Align.Left | Align.VCenter)); 29 // row 1 30 table2.addChild((new TextWidget(null, "Parameter 1 name"d)).alignment(Align.Right | Align.VCenter)); 31 table2.addChild((new EditLine("edit1", "Text 1"d)).layoutWidth(FILL_PARENT)); 32 // row 2 33 table2.addChild((new TextWidget(null, "Parameter 2 name bla bla"d)).alignment(Align.Right | Align.VCenter)); 34 table2.addChild((new EditLine("edit2", "Some text for parameter 2 blah blah blah"d)).layoutWidth(FILL_PARENT)); 35 // row 3 36 table2.addChild((new TextWidget(null, "Param 3"d)).alignment(Align.Right | Align.VCenter)); 37 table2.addChild((new EditLine("edit3", "Parameter 3 value"d)).layoutWidth(FILL_PARENT)); 38 // row 4 39 table2.addChild((new TextWidget(null, "Param 4"d)).alignment(Align.Right | Align.VCenter)); 40 table2.addChild((new EditLine("edit3", "Parameter 4 value shdjksdfh hsjdfas hdjkf hdjsfk ah"d)).layoutWidth(FILL_PARENT)); 41 // row 5 42 table2.addChild((new TextWidget(null, "Param 5 - edit text here - blah blah blah"d)).alignment(Align.Right | Align.VCenter)); 43 table2.addChild((new EditLine("edit3", "Parameter 5 value"d)).layoutWidth(FILL_PARENT)); 44 // row 6 45 table2.addChild((new TextWidget(null, "Param 6 - just to fill content widget"d)).alignment(Align.Right | Align.VCenter)); 46 table2.addChild((new EditLine("edit3", "Parameter 5 value"d)).layoutWidth(FILL_PARENT)); 47 // row 7 48 table2.addChild((new TextWidget(null, "Param 7 - just to fill content widget"d)).alignment(Align.Right | Align.VCenter)); 49 table2.addChild((new EditLine("edit3", "Parameter 5 value"d)).layoutWidth(FILL_PARENT)); 50 // row 8 51 table2.addChild((new TextWidget(null, "Param 8 - just to fill content widget"d)).alignment(Align.Right | Align.VCenter)); 52 table2.addChild((new EditLine("edit3", "Parameter 5 value"d)).layoutWidth(FILL_PARENT)); 53 table2.margins(Rect(10,10,10,10)).layoutWidth(FILL_PARENT); 54 scrollContent.addChild(table2); 55 56 scrollContent.addChild(new TextWidget(null, "Now - some buttons"d)); 57 scrollContent.addChild(new ImageTextButton("btn1", "fileclose", "Close"d)); 58 scrollContent.addChild(new ImageTextButton("btn2", "fileopen", "Open"d)); 59 scrollContent.addChild(new TextWidget(null, "And checkboxes"d)); 60 scrollContent.addChild(new CheckBox("btn1", "CheckBox 1"d)); 61 scrollContent.addChild(new CheckBox("btn2", "CheckBox 2"d)); 62 63 scroll.contentWidget = scrollContent; 64 65 ---- 66 67 Copyright: Vadim Lopatin, 2014 68 License: Boost License 1.0 69 Authors: Vadim Lopatin, coolreader.org@gmail.com 70 */ 71 module dlangui.widgets.scroll; 72 73 import dlangui.widgets.widget; 74 import dlangui.widgets.controls; 75 import dlangui.widgets.scrollbar; 76 import std.conv; 77 78 /** Scroll bar visibility mode. */ 79 enum ScrollBarMode { 80 /** always invisible */ 81 Invisible, 82 /** always visible */ 83 Visible, 84 /** automatically show/hide scrollbar depending on content size */ 85 Auto, 86 /** Scrollbar is provided by external control outside this widget */ 87 External, 88 } 89 90 /** 91 Abstract scrollable widget 92 93 Provides scroll bars and basic scrolling functionality. 94 95 */ 96 class ScrollWidgetBase : WidgetGroup, OnScrollHandler { 97 protected ScrollBarMode _vscrollbarMode; 98 protected ScrollBarMode _hscrollbarMode; 99 /// vertical scrollbar control 100 protected ScrollBar _vscrollbar; 101 /// horizontal scrollbar control 102 protected ScrollBar _hscrollbar; 103 /// inner area, excluding additional controls like scrollbars 104 protected Rect _clientRect; 105 106 protected Rect _fullScrollableArea; 107 protected Rect _visibleScrollableArea; 108 109 this(string ID = null, ScrollBarMode hscrollbarMode = ScrollBarMode.Visible, ScrollBarMode vscrollbarMode = ScrollBarMode.Visible) { 110 super(ID); 111 _hscrollbarMode = hscrollbarMode; 112 _vscrollbarMode = vscrollbarMode; 113 if (_vscrollbarMode != ScrollBarMode.Invisible) { 114 _vscrollbar = new ScrollBar("vscrollbar", Orientation.Vertical); 115 _vscrollbar.scrollEvent = this; 116 addChild(_vscrollbar); 117 } 118 if (_hscrollbarMode != ScrollBarMode.Invisible) { 119 _hscrollbar = new ScrollBar("hscrollbar", Orientation.Horizontal); 120 _hscrollbar.scrollEvent = this; 121 addChild(_hscrollbar); 122 } 123 } 124 125 /// vertical scrollbar mode 126 @property ScrollBarMode vscrollbarMode() { return _vscrollbarMode; } 127 @property void vscrollbarMode(ScrollBarMode m) { _vscrollbarMode = m; } 128 /// horizontal scrollbar mode 129 @property ScrollBarMode hscrollbarMode() { return _hscrollbarMode; } 130 @property void hscrollbarMode(ScrollBarMode m) { _hscrollbarMode = m; } 131 132 /// returns client area rectangle 133 @property Rect clientRect() { return _clientRect; } 134 135 /// process horizontal scrollbar event 136 bool onHScroll(ScrollEvent event) { 137 return true; 138 } 139 140 /// process vertical scrollbar event 141 bool onVScroll(ScrollEvent event) { 142 return true; 143 } 144 145 /// process mouse event; return true if event is processed by widget. 146 override bool onMouseEvent(MouseEvent event) { 147 if (event.action == MouseAction.Wheel) { 148 if (event.flags == MouseFlag.Shift) { 149 if (_hscrollbar) { 150 _hscrollbar.sendScrollEvent(event.wheelDelta > 0 ? ScrollAction.LineUp : ScrollAction.LineDown); 151 return true; 152 } 153 } else if (event.flags == 0) { 154 if (_vscrollbar) { 155 _vscrollbar.sendScrollEvent(event.wheelDelta > 0 ? ScrollAction.LineUp : ScrollAction.LineDown); 156 return true; 157 } 158 } 159 } 160 return super.onMouseEvent(event); 161 } 162 163 /// handle scroll event 164 override bool onScrollEvent(AbstractSlider source, ScrollEvent event) { 165 if (source.orientation == Orientation.Horizontal) { 166 return onHScroll(event); 167 } else { 168 return onVScroll(event); 169 } 170 } 171 172 /// update scrollbar positions 173 protected void updateScrollBars() { 174 if (_hscrollbar) { 175 updateHScrollBar(); 176 } 177 if (_vscrollbar) { 178 updateVScrollBar(); 179 } 180 } 181 182 public @property ScrollBar hscrollbar() { return _hscrollbar; } 183 public @property ScrollBar vscrollbar() { return _vscrollbar; } 184 185 public @property void hscrollbar(ScrollBar hscroll) { 186 if (_hscrollbar) { 187 removeChild(_hscrollbar); 188 destroy(_hscrollbar); 189 _hscrollbar = null; 190 _hscrollbarMode = ScrollBarMode.Invisible; 191 } 192 if (hscroll) { 193 _hscrollbar = hscroll; 194 _hscrollbarMode = ScrollBarMode.External; 195 } 196 } 197 198 public @property void vscrollbar(ScrollBar vscroll) { 199 if (_vscrollbar) { 200 removeChild(_vscrollbar); 201 destroy(_vscrollbar); 202 _vscrollbar = null; 203 _vscrollbarMode = ScrollBarMode.Invisible; 204 } 205 if (vscroll) { 206 _vscrollbar = vscroll; 207 _vscrollbarMode = ScrollBarMode.External; 208 } 209 } 210 211 /// update horizontal scrollbar widget position 212 protected void updateHScrollBar() { 213 // default implementation: use _fullScrollableArea, _visibleScrollableArea: override it if necessary 214 _hscrollbar.setRange(0, _fullScrollableArea.width); 215 _hscrollbar.pageSize(_visibleScrollableArea.width); 216 _hscrollbar.position(_visibleScrollableArea.left - _fullScrollableArea.left); 217 } 218 219 /// update verticat scrollbar widget position 220 protected void updateVScrollBar() { 221 // default implementation: use _fullScrollableArea, _visibleScrollableArea: override it if necessary 222 _vscrollbar.setRange(0, _fullScrollableArea.height); 223 _vscrollbar.pageSize(_visibleScrollableArea.height); 224 _vscrollbar.position(_visibleScrollableArea.top - _fullScrollableArea.top); 225 } 226 227 protected void drawClient(DrawBuf buf) { 228 // override it 229 } 230 231 protected void drawExtendedArea(DrawBuf buf) { 232 } 233 234 /// Draw widget at its position to buffer 235 override void onDraw(DrawBuf buf) { 236 if (visibility != Visibility.Visible) 237 return; 238 super.onDraw(buf); 239 Rect rc = _pos; 240 applyMargins(rc); 241 { 242 auto saver = ClipRectSaver(buf, rc, alpha); 243 DrawableRef bg = backgroundDrawable; 244 if (!bg.isNull) { 245 bg.drawTo(buf, rc, state); 246 } 247 applyPadding(rc); 248 if (_hscrollbar) 249 _hscrollbar.onDraw(buf); 250 if (_vscrollbar) 251 _vscrollbar.onDraw(buf); 252 // apply clipping 253 { 254 auto saver2 = ClipRectSaver(buf, _clientRect, alpha); 255 drawClient(buf); 256 } 257 { 258 // no clipping for drawing of extended area 259 Rect clipr = rc; 260 clipr.bottom = _clientRect.bottom; 261 auto saver3 = ClipRectSaver(buf, clipr, alpha); 262 drawExtendedArea(buf); 263 } 264 } 265 266 _needDraw = false; 267 } 268 269 /// calculate full content size in pixels 270 Point fullContentSize() { 271 // override it 272 Point sz; 273 return sz; 274 } 275 276 /// Measure widget according to desired width and height constraints. (Step 1 of two phase layout). 277 override void measure(int parentWidth, int parentHeight) { 278 if (visibility == Visibility.Gone) { 279 return; 280 } 281 Rect m = margins; 282 Rect p = padding; 283 // calc size constraints for children 284 int pwidth = parentWidth; 285 int pheight = parentHeight; 286 if (parentWidth != SIZE_UNSPECIFIED) 287 pwidth -= m.left + m.right + p.left + p.right; 288 if (parentHeight != SIZE_UNSPECIFIED) 289 pheight -= m.top + m.bottom + p.top + p.bottom; 290 if (_hscrollbar && _hscrollbarMode == ScrollBarMode.Visible) { 291 _hscrollbar.measure(pwidth, pheight); 292 } 293 if (_vscrollbar && _vscrollbarMode == ScrollBarMode.Visible) { 294 _vscrollbar.measure(pwidth, pheight); 295 } 296 Point sz = fullContentSize(); 297 if (_hscrollbar && _hscrollbarMode == ScrollBarMode.Visible) { 298 sz.y += _hscrollbar.measuredHeight; 299 } 300 if (_vscrollbar && _vscrollbarMode == ScrollBarMode.Visible) { 301 sz.x += _vscrollbar.measuredWidth; 302 } 303 measuredContent(parentWidth, parentHeight, sz.x, sz.y); 304 } 305 306 /// override to support modification of client rect after change, e.g. apply offset 307 protected void handleClientRectLayout(ref Rect rc) { 308 } 309 310 /// Set widget rectangle to specified value and layout widget contents. (Step 2 of two phase layout). 311 override void layout(Rect rc) { 312 if (visibility == Visibility.Gone) { 313 return; 314 } 315 _pos = rc; 316 _needLayout = false; 317 applyMargins(rc); 318 applyPadding(rc); 319 Point sz = fullContentSize(); 320 bool needHscroll = _hscrollbarMode != ScrollBarMode.External && _hscrollbarMode != ScrollBarMode.Invisible && sz.x > rc.width; 321 bool needVscroll = _vscrollbarMode != ScrollBarMode.External && _vscrollbarMode != ScrollBarMode.Invisible && sz.y > rc.height; 322 if (needVscroll && _vscrollbarMode != ScrollBarMode.Invisible) 323 needHscroll = sz.x > rc.width - _vscrollbar.measuredWidth; 324 if (needHscroll && _hscrollbarMode != ScrollBarMode.Invisible) 325 needVscroll = sz.y > rc.height - _hscrollbar.measuredHeight; 326 if (needVscroll && _vscrollbarMode != ScrollBarMode.Invisible) 327 needHscroll = sz.x > rc.width - _vscrollbar.measuredWidth; 328 needVscroll = needVscroll || (_vscrollbarMode == ScrollBarMode.Visible); 329 needHscroll = needHscroll || (_hscrollbarMode == ScrollBarMode.Visible); 330 // scrollbars 331 Rect vsbrc = rc; 332 vsbrc.left = vsbrc.right - (needVscroll ? _vscrollbar.measuredWidth : 0); 333 vsbrc.bottom = vsbrc.bottom - (needHscroll ? _hscrollbar.measuredHeight : 0); 334 Rect hsbrc = rc; 335 hsbrc.right = hsbrc.right - (needVscroll ? _vscrollbar.measuredWidth : 0); 336 hsbrc.top = hsbrc.bottom - (needHscroll ? _hscrollbar.measuredHeight : 0); 337 if (_vscrollbar && _vscrollbarMode != ScrollBarMode.External) { 338 _vscrollbar.visibility = needVscroll ? Visibility.Visible : Visibility.Gone; 339 _vscrollbar.layout(vsbrc); 340 } 341 if (_hscrollbar && _hscrollbarMode != ScrollBarMode.External) { 342 _hscrollbar.visibility = needHscroll ? Visibility.Visible : Visibility.Gone; 343 _hscrollbar.layout(hsbrc); 344 } 345 // client area 346 _clientRect = rc; 347 handleClientRectLayout(_clientRect); 348 if (needVscroll) 349 _clientRect.right = vsbrc.left; 350 if (needHscroll) 351 _clientRect.bottom = hsbrc.top; 352 updateScrollBars(); 353 } 354 355 void makeRectVisible(Rect rc, bool alignHorizontally = true, bool alignVertically = true) { 356 if (rc.isInsideOf(_visibleScrollableArea)) 357 return; 358 Rect oldRect = _visibleScrollableArea; 359 if (alignHorizontally && rc.right > _visibleScrollableArea.right) 360 _visibleScrollableArea.offset(rc.right - _visibleScrollableArea.right, 0); 361 if (alignVertically && rc.bottom > _visibleScrollableArea.bottom) 362 _visibleScrollableArea.offset(0, rc.bottom - _visibleScrollableArea.bottom); 363 if (alignHorizontally && rc.left < _visibleScrollableArea.left) 364 _visibleScrollableArea.offset(rc.left - _visibleScrollableArea.left, 0); 365 if (alignVertically && rc.top < _visibleScrollableArea.top) 366 _visibleScrollableArea.offset(0, rc.top - _visibleScrollableArea.top); 367 if (_visibleScrollableArea != oldRect) 368 requestLayout(); 369 } 370 } 371 372 /** 373 Widget which can show content of widget group with optional scrolling 374 375 If size of content widget exceeds available space, allows to scroll it. 376 */ 377 class ScrollWidget : ScrollWidgetBase { 378 protected Widget _contentWidget; 379 @property Widget contentWidget() { return _contentWidget; } 380 @property ScrollWidget contentWidget(Widget newContent) { 381 if (_contentWidget) { 382 removeChild(childIndex(_contentWidget)); 383 destroy(_contentWidget); 384 } 385 _contentWidget = newContent; 386 addChild(_contentWidget); 387 requestLayout(); 388 return this; 389 } 390 /// empty parameter list constructor - for usage by factory 391 this() { 392 this(null); 393 } 394 /// create with ID parameter 395 this(string ID, ScrollBarMode hscrollbarMode = ScrollBarMode.Visible, ScrollBarMode vscrollbarMode = ScrollBarMode.Visible) { 396 super(ID, hscrollbarMode, vscrollbarMode); 397 } 398 399 /// calculate full content size in pixels 400 override Point fullContentSize() { 401 // override it 402 Point sz; 403 if (_contentWidget) { 404 _contentWidget.measure(SIZE_UNSPECIFIED, SIZE_UNSPECIFIED); 405 sz.x = _contentWidget.measuredWidth; 406 sz.y = _contentWidget.measuredHeight; 407 } 408 _fullScrollableArea.right = sz.x; 409 _fullScrollableArea.bottom = sz.y; 410 return sz; 411 } 412 413 /// update scrollbar positions 414 override protected void updateScrollBars() { 415 Point sz = fullContentSize(); 416 _visibleScrollableArea.right = _visibleScrollableArea.left + _clientRect.width; 417 _visibleScrollableArea.bottom = _visibleScrollableArea.top + _clientRect.height; 418 // move back if scroll is too big after window resize 419 int extrax = _visibleScrollableArea.right - _fullScrollableArea.right; 420 int extray = _visibleScrollableArea.bottom - _fullScrollableArea.bottom; 421 if (extrax > _visibleScrollableArea.left) 422 extrax = _visibleScrollableArea.left; 423 if (extray > _visibleScrollableArea.top) 424 extray = _visibleScrollableArea.top; 425 if (extrax < 0) 426 extrax = 0; 427 if (extray < 0) 428 extray = 0; 429 _visibleScrollableArea.offset(-extrax, -extray); 430 super.updateScrollBars(); 431 } 432 433 override protected void drawClient(DrawBuf buf) { 434 if (_contentWidget) { 435 Point sz = fullContentSize(); 436 Point p = scrollPos; 437 _contentWidget.layout(Rect(_clientRect.left - p.x, _clientRect.top - p.y, _clientRect.left + sz.x - p.x, _clientRect.top + sz.y - p.y)); 438 _contentWidget.onDraw(buf); 439 } 440 } 441 442 443 @property Point scrollPos() { 444 return Point(_visibleScrollableArea.left - _fullScrollableArea.left, _visibleScrollableArea.top - _fullScrollableArea.top); 445 } 446 447 protected void scrollTo(int x, int y) { 448 if (x > _fullScrollableArea.right - _visibleScrollableArea.width) 449 x = _fullScrollableArea.right - _visibleScrollableArea.width; 450 if (y > _fullScrollableArea.bottom - _visibleScrollableArea.height) 451 y = _fullScrollableArea.bottom - _visibleScrollableArea.height; 452 if (x < 0) 453 x = 0; 454 if (y < 0) 455 y = 0; 456 _visibleScrollableArea.left = x; 457 _visibleScrollableArea.top = y; 458 updateScrollBars(); 459 invalidate(); 460 } 461 462 /// process horizontal scrollbar event 463 override bool onHScroll(ScrollEvent event) { 464 if (event.action == ScrollAction.SliderMoved || event.action == ScrollAction.SliderReleased) { 465 scrollTo(event.position, scrollPos.y); 466 } else if (event.action == ScrollAction.PageUp) { 467 scrollTo(scrollPos.x - _clientRect.width * 3 / 4, scrollPos.y); 468 } else if (event.action == ScrollAction.PageDown) { 469 scrollTo(scrollPos.x + _clientRect.width * 3 / 4, scrollPos.y); 470 } else if (event.action == ScrollAction.LineUp) { 471 scrollTo(scrollPos.x - _clientRect.width / 10, scrollPos.y); 472 } else if (event.action == ScrollAction.LineDown) { 473 scrollTo(scrollPos.x + _clientRect.width / 10, scrollPos.y); 474 } 475 return true; 476 } 477 478 /// process vertical scrollbar event 479 override bool onVScroll(ScrollEvent event) { 480 if (event.action == ScrollAction.SliderMoved || event.action == ScrollAction.SliderReleased) { 481 scrollTo(scrollPos.x, event.position); 482 } else if (event.action == ScrollAction.PageUp) { 483 scrollTo(scrollPos.x, scrollPos.y - _clientRect.height * 3 / 4); 484 } else if (event.action == ScrollAction.PageDown) { 485 scrollTo(scrollPos.x, scrollPos.y + _clientRect.height * 3 / 4); 486 } else if (event.action == ScrollAction.LineUp) { 487 scrollTo(scrollPos.x, scrollPos.y - _clientRect.height / 10); 488 } else if (event.action == ScrollAction.LineDown) { 489 scrollTo(scrollPos.x, scrollPos.y + _clientRect.height / 10); } 490 return true; 491 } 492 493 void makeWidgetVisible(Widget widget, bool alignHorizontally = true, bool alignVertically = true) { 494 if (!widget || !widget.visibility == Visibility.Gone) 495 return; 496 if (!_contentWidget || !_contentWidget.isChild(widget)) 497 return; 498 Rect wpos = widget.pos; 499 Rect cpos = _contentWidget.pos; 500 wpos.offset(-cpos.left, -cpos.top); 501 makeRectVisible(wpos, alignHorizontally, alignVertically); 502 } 503 }