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 }