diff --git a/src/dlangui/core/events.d b/src/dlangui/core/events.d index f8a098b7..50a313b7 100644 --- a/src/dlangui/core/events.d +++ b/src/dlangui/core/events.d @@ -142,16 +142,58 @@ class ScrollEvent { private int _maxValue; private int _pageSize; private int _position; + private bool _positionChanged; @property ScrollAction action() { return _action; } @property int minValue() { return _minValue; } @property int maxValue() { return _maxValue; } @property int pageSize() { return _pageSize; } @property int position() { return _position; } + @property bool positionChanged() { return _positionChanged; } + /// change position in event handler to update slider position + @property void position(int newPosition) { _position = newPosition; _positionChanged = true; } this(ScrollAction action, int minValue, int maxValue, int pageSize, int position) { _action = action; _minValue = minValue; - _maxValue = minValue; + _maxValue = maxValue; _pageSize = pageSize; _position = position; } + /// default update position for actions like PageUp/PageDown, LineUp/LineDown + int defaultUpdatePosition() { + int delta = 0; + switch (_action) { + case ScrollAction.LineUp: + delta = _pageSize / 20; + if (delta < 1) + delta = 1; + delta = -delta; + break; + case ScrollAction.LineDown: + delta = _pageSize / 20; + if (delta < 1) + delta = 1; + break; + case ScrollAction.PageUp: + delta = _pageSize * 3 / 4; + if (delta < 1) + delta = 1; + delta = -delta; + break; + case ScrollAction.PageDown: + delta = _pageSize * 3 / 4; + if (delta < 1) + delta = 1; + break; + default: + return position; + } + int newPosition = _position + delta; + if (newPosition > _maxValue - _pageSize) + newPosition = _maxValue - _pageSize; + if (newPosition < _minValue) + newPosition = _minValue; + if (_position != newPosition) + position = newPosition; + return position; + } } diff --git a/src/dlangui/widgets/controls.d b/src/dlangui/widgets/controls.d index 8571dd38..403badc2 100644 --- a/src/dlangui/widgets/controls.d +++ b/src/dlangui/widgets/controls.d @@ -181,10 +181,13 @@ class AbstractSlider : WidgetGroup { @property AbstractSlider position(int newPosition) { if (_position != newPosition) { _position = newPosition; - requestLayout(); + onPositionChanged(); } return this; } + protected void onPositionChanged() { + requestLayout(); + } /// returns slider range min value @property int minValue() const { return _minValue; } /// returns slider range max value @@ -213,7 +216,16 @@ class AbstractSlider : WidgetGroup { if (_onScrollEventListener is null) return false; ScrollEvent event = new ScrollEvent(action, _minValue, _maxValue, _pageSize, position); - return _onScrollEventListener(this, event); + bool res = _onScrollEventListener(this, event); + if (event.positionChanged) { + _position = event.position; + if (_position > _maxValue) + _position = _maxValue; + if (_position < _minValue) + _position = _minValue; + onPositionChanged(); + } + return true; } } @@ -259,6 +271,7 @@ class ScrollBar : AbstractSlider, OnClickHandler { _dragStart.y = event.y; _dragStartPosition = _position; _dragStartRect = _pos; + sendScrollEvent(ScrollAction.SliderPressed, _position); return true; } if (event.action == MouseAction.FocusOut && _dragging) { @@ -304,7 +317,7 @@ class ScrollBar : AbstractSlider, OnClickHandler { if (event.action == MouseAction.ButtonUp && event.button == MouseButton.Left) { resetState(State.Pressed); if (_dragging) { - + sendScrollEvent(ScrollAction.SliderReleased, _position); _dragging = false; } return true; @@ -334,7 +347,7 @@ class ScrollBar : AbstractSlider, OnClickHandler { protected bool onIndicatorDragging(int initialPosition, int currentPosition) { _position = currentPosition; - return true; + return sendScrollEvent(ScrollAction.SliderMoved, currentPosition); } private bool calcButtonSizes(int availableSize, ref int spaceBackSize, ref int spaceForwardSize, ref int indicatorSize) { @@ -527,6 +540,14 @@ class ScrollBar : AbstractSlider, OnClickHandler { override bool onClick(Widget source) { Log.d("Scrollbar.onClick ", source.id); + if (source.compareId("BACK")) + return sendScrollEvent(ScrollAction.LineUp, position); + if (source.compareId("FORWARD")) + return sendScrollEvent(ScrollAction.LineDown, position); + if (source.compareId("PAGE_UP")) + return sendScrollEvent(ScrollAction.PageUp, position); + if (source.compareId("PAGE_DOWN")) + return sendScrollEvent(ScrollAction.PageDown, position); return true; } diff --git a/src/dlangui/widgets/lists.d b/src/dlangui/widgets/lists.d index ed03f29b..37c94c2e 100644 --- a/src/dlangui/widgets/lists.d +++ b/src/dlangui/widgets/lists.d @@ -26,7 +26,8 @@ class WidgetListAdapter : ListAdapter { } } -class ListWidget : WidgetGroup { +/// List +class ListWidget : WidgetGroup, OnScrollHandler { protected Orientation _orientation = Orientation.Vertical; /// returns linear layout orientation (Vertical, Horizontal) @property Orientation orientation() { return _orientation; } @@ -46,6 +47,42 @@ class ListWidget : WidgetGroup { protected int _lastMeasureWidth; protected int _lastMeasureHeight; + /// first visible item index + protected int _firstVisibleItem; + /// scroll position - offset of scroll area + protected int _scrollPosition; + /// maximum scroll position + protected int _maxScrollPosition; + /// client area rectangle (counting padding, margins, and scrollbar) + protected Rect _clientRc; + /// total height of all items for Vertical orientation, or width for Horizontal + protected int _totalSize; + + /// returns rectangle for item (not scrolled, first item starts at 0,0) + Rect itemRectNoScroll(int index) { + Rect res; + res = _itemRects[index]; + return res; + } + + /// returns rectangle for item (scrolled) + Rect itemRect(int index) { + Rect res = itemRectNoScroll(index); + if (_orientation == Orientation.Horizontal) { + res.left += _scrollPosition; + res.right += _scrollPosition; + } else { + res.top += _scrollPosition; + res.bottom += _scrollPosition; + } + return res; + } + + /// returns item index by 0-based offset from top/left of list content + int itemByPosition(int pos) { + return 0; + } + protected ListAdapter _adapter; /// when true, need to destroy adapter on list destroy protected bool _ownAdapter; @@ -94,6 +131,7 @@ class ListWidget : WidgetGroup { _orientation = orientation; _scrollbar = new ScrollBar("listscroll", orientation); _scrollbar.visibility = Visibility.Gone; + _scrollbar.onScrollEventListener = &onScrollEvent; addChild(_scrollbar); } @@ -103,6 +141,27 @@ class ListWidget : WidgetGroup { _adapter = null; } + /// handle scroll event + override bool onScrollEvent(AbstractSlider source, ScrollEvent event) { + int newPosition = _scrollPosition; + if (event.action == ScrollAction.SliderMoved) { + // scroll + newPosition = event.position; + } else { + // use default handler for page/line up/down events + newPosition = event.defaultUpdatePosition(); + } + if (_scrollPosition != newPosition) { + _scrollPosition = newPosition; + if (_scrollPosition > _maxScrollPosition) + _scrollPosition = _maxScrollPosition; + if (_scrollPosition < 0) + _scrollPosition = 0; + invalidate(); + } + return true; + } + /// Measure widget according to desired width and height constraints. (Step 1 of two phase layout). override void measure(int parentWidth, int parentHeight) { if (visibility == Visibility.Gone) { @@ -198,6 +257,70 @@ class ListWidget : WidgetGroup { measuredContent(parentWidth, parentHeight, sz.x + _sbsz.x, sz.y + _sbsz.y); } + + protected void updateItemPositions() { + Rect r; + int p = 0; + for (int i = 0; i < itemCount; i++) { + if (_itemSizes[i].x == 0 && _itemSizes[i].y == 0) + continue; + if (_orientation == Orientation.Vertical) { + // Vertical + int w = _clientRc.width; + int h = _itemSizes[i].y; + r.top = p; + r.bottom = p + h; + r.left = 0; + r.right = w; + _itemRects[i] = r; + p += h; + } else { + // Horizontal + int h = _clientRc.height; + int w = _itemSizes[i].x; + r.top = 0; + r.bottom = h; + r.left = p; + r.right = p + w; + _itemRects[i] = r; + p += w; + } + } + _totalSize = p; + if (_needScrollbar) { + if (_orientation == Orientation.Vertical) { + _scrollbar.setRange(0, p); + _scrollbar.pageSize = _clientRc.height; + _scrollbar.position = _scrollPosition; + } else { + _scrollbar.setRange(0, p); + _scrollbar.pageSize = _clientRc.width; + _scrollbar.position = _scrollPosition; + } + } + /// maximum scroll position + if (_orientation == Orientation.Vertical) { + _maxScrollPosition = _totalSize - _clientRc.height; + if (_maxScrollPosition < 0) + _maxScrollPosition = 0; + } else { + _maxScrollPosition = _totalSize - _clientRc.width; + if (_maxScrollPosition < 0) + _maxScrollPosition = 0; + } + if (_scrollPosition > _maxScrollPosition) + _scrollPosition = _maxScrollPosition; + if (_scrollPosition < 0) + _scrollPosition = 0; + if (_needScrollbar) { + if (_orientation == Orientation.Vertical) { + _scrollbar.position = _scrollPosition; + } else { + _scrollbar.position = _scrollPosition; + } + } + } + /// Set widget rectangle to specified value and layout widget contents. (Step 2 of two phase layout). override void layout(Rect rc) { if (visibility == Visibility.Gone) { @@ -230,45 +353,10 @@ class ListWidget : WidgetGroup { _scrollbar.visibility = Visibility.Gone; } + _clientRc = rc; + // calc item rectangles - Rect r; - int p = 0; - for (int i = 0; i < itemCount; i++) { - if (_itemSizes[i].x == 0 && _itemSizes[i].y == 0) - continue; - if (_orientation == Orientation.Vertical) { - // Vertical - int w = rc.width; - int h = _itemSizes[i].y; - r.top = p; - r.bottom = p + h; - r.left = 0; - r.right = w; - _itemRects[i] = r; - p += h; - } else { - // Horizontal - int h = rc.height; - int w = _itemSizes[i].x; - r.top = 0; - r.bottom = h; - r.left = p; - r.right = p + w; - _itemRects[i] = r; - p += w; - } - } - if (_needScrollbar) { - if (_orientation == Orientation.Vertical) { - _scrollbar.setRange(0, p); - _scrollbar.pageSize = rc.height; - _scrollbar.position = 0; - } else { - _scrollbar.setRange(0, p); - _scrollbar.pageSize = rc.width; - _scrollbar.position = 0; - } - } + updateItemPositions(); _needLayout = false; } @@ -287,6 +375,11 @@ class ListWidget : WidgetGroup { _scrollbar.onDraw(buf); Point scrollOffset; + if (_orientation == Orientation.Vertical) { + scrollOffset.y = _scrollPosition; + } else { + scrollOffset.x = _scrollPosition; + } // todo: scrollOffset // draw items for (int i = 0; i < itemCount; i++) {