From b4974cb1771f25e074253c449606f9cea12c3be4 Mon Sep 17 00:00:00 2001 From: Vadim Lopatin Date: Tue, 11 Oct 2016 10:36:47 +0300 Subject: [PATCH] progress bar initial implementatino --- dlangui-msvc.visualdproj | 1 + examples/example1/src/example1.d | 3 + src/dlangui/package.d | 1 + src/dlangui/platforms/common/startup.d | 5 +- src/dlangui/widgets/progressbar.d | 192 ++++ src/dlangui/widgets/scrollbar.d | 995 +++++++++++++++++++++ src/dlangui/widgets/styles.d | 2 + src/dlangui/widgets/widget.d | 7 +- views/res/progress_bar_gauge_animation.png | Bin 0 -> 11595 bytes views/res/theme_default.xml | 12 + views/standard_resources.list | 1 + 11 files changed, 1216 insertions(+), 3 deletions(-) create mode 100644 src/dlangui/widgets/progressbar.d create mode 100644 src/dlangui/widgets/scrollbar.d create mode 100644 views/res/progress_bar_gauge_animation.png diff --git a/dlangui-msvc.visualdproj b/dlangui-msvc.visualdproj index 1e132160..ca70008d 100644 --- a/dlangui-msvc.visualdproj +++ b/dlangui-msvc.visualdproj @@ -982,6 +982,7 @@ + diff --git a/examples/example1/src/example1.d b/examples/example1/src/example1.d index f23b965e..11174d90 100644 --- a/examples/example1/src/example1.d +++ b/examples/example1/src/example1.d @@ -569,8 +569,11 @@ extern (C) int UIAppMain(string[] args) { LinearLayout layout = new LinearLayout("tab1"); + layout.addChild((new TextWidget()).textColor(0x00802000).text("Text widget 0")); layout.addChild((new TextWidget()).textColor(0x40FF4000).text("Text widget")); + layout.addChild(new ProgressBarWidget().progress(300).animationInterval(50)); + layout.addChild(new ProgressBarWidget().progress(-1).animationInterval(50)); layout.addChild((new Button("BTN1")).textResource("EXIT")); //.textColor(0x40FF4000) layout.addChild(new TimerTest()); diff --git a/src/dlangui/package.d b/src/dlangui/package.d index 7727d6e2..6e55264a 100644 --- a/src/dlangui/package.d +++ b/src/dlangui/package.d @@ -60,6 +60,7 @@ public { import dlangui.widgets.widget; import dlangui.widgets.controls; import dlangui.widgets.scrollbar; + import dlangui.widgets.progressbar; import dlangui.widgets.layouts; import dlangui.widgets.groupbox; import dlangui.widgets.lists; diff --git a/src/dlangui/platforms/common/startup.d b/src/dlangui/platforms/common/startup.d index f3603261..f43027ee 100644 --- a/src/dlangui/platforms/common/startup.d +++ b/src/dlangui/platforms/common/startup.d @@ -327,14 +327,17 @@ void registerStandardWidgets() { import dlangui.widgets.editors; import dlangui.widgets.grid; import dlangui.widgets.groupbox; + import dlangui.widgets.progressbar; import dlangui.dialogs.filedlg; import dlangui.widgets.menu; mixin(registerWidgets!(FileNameEditLine, DirEditLine, //dlangui.dialogs.filedlg ComboBox, ComboEdit, //dlangui.widgets.combobox Widget, TextWidget, MultilineTextWidget, Button, ImageWidget, ImageButton, ImageCheckButton, ImageTextButton, - SwitchButton, RadioButton, CheckBox, ScrollBar, SliderWidget, HSpacer, VSpacer, CanvasWidget, // dlangui.widgets.controls + SwitchButton, RadioButton, CheckBox, HSpacer, VSpacer, CanvasWidget, // dlangui.widgets.controls + ScrollBar, SliderWidget, // dlangui.widgets.scrollbar EditLine, EditBox, LogWidget,//dlangui.widgets.editors GroupBox, // dlangui.widgets.groupbox + ProgressBarWidget, // dlangui.widgets.progressbar StringGridWidget, //dlangui.widgets.grid VerticalLayout, HorizontalLayout, TableLayout, FrameLayout, // dlangui.widgets.layouts ListWidget, StringListWidget,//dlangui.widgets.lists diff --git a/src/dlangui/widgets/progressbar.d b/src/dlangui/widgets/progressbar.d new file mode 100644 index 00000000..d0a9a0b4 --- /dev/null +++ b/src/dlangui/widgets/progressbar.d @@ -0,0 +1,192 @@ +// Written in the D programming language. + +/** +This module contains progress bar controls implementation. + +ProgressBarWidget - progeress bar control + + +Synopsis: + +---- +import dlangui.widgets.progressbar; + +---- + +Copyright: Vadim Lopatin, 2016 +License: Boost License 1.0 +Authors: Vadim Lopatin, coolreader.org@gmail.com +*/ +module dlangui.widgets.progressbar; + +import dlangui.widgets.widget; + +enum PROGRESS_INDETERMINATE = -1; +enum PROGRESS_HIDDEN = -2; +enum PROGRESS_ANIMATION_OFF = 0; +enum PROGRESS_MAX = 1000; + +/// Base for different progress bar controls +class AbstractProgressBar : Widget { + this(string ID = null, int progress = PROGRESS_INDETERMINATE) { + super(ID); + _progress = progress; + } + + protected int _progress = PROGRESS_INDETERMINATE; + + /// Set current progress value, 0 .. 1000; -1 == indeterminate, -2 == hidden + @property AbstractProgressBar progress(int progress) { + if (progress < -2) + progress = -2; + if (progress > 1000) + progress = 1000; + if (_progress != progress) { + _progress = progress; + invalidate(); + } + requestLayout(); + return this; + } + /// Get current progress value, 0 .. 1000; -1 == indeterminate + @property int progress() { + return _progress; + } + /// returns true if progress bar is in indeterminate state + @property bool indeterminate() { return _progress == PROGRESS_INDETERMINATE; } + + protected int _animationInterval = 0; // no animation by default + /// get animation interval in milliseconds, if 0 - no animation + @property int animationInterval() { return _animationInterval; } + /// set animation interval in milliseconds, if 0 - no animation + @property AbstractProgressBar animationInterval(int animationIntervalMillis) { + if (animationIntervalMillis < 0) + animationIntervalMillis = 0; + if (animationIntervalMillis > 5000) + animationIntervalMillis = 5000; + if (_animationInterval != animationIntervalMillis) { + _animationInterval = animationIntervalMillis; + if (!animationIntervalMillis) + stopAnimation(); + else + scheduleAnimation(); + } + return this; + } + + protected ulong _animationTimerId; + protected void scheduleAnimation() { + if (!visible || !_animationInterval) { + if (_animationTimerId) + stopAnimation(); + return; + } + stopAnimation(); + _animationTimerId = setTimer(_animationInterval); + invalidate(); + } + + protected void stopAnimation() { + if (_animationTimerId) { + cancelTimer(_animationTimerId); + _animationTimerId = 0; + } + _lastAnimationTs = 0; + } + + protected int _animationSpeedPixelsPerSecond = 20; + protected long _animationPhase; + protected long _lastAnimationTs; + /// called on animation timer + protected void onAnimationTimer(long millisElapsed) { + _animationPhase += millisElapsed; + invalidate(); + } + + /// handle timer; return true to repeat timer event after next interval, false cancel timer + override bool onTimer(ulong id) { + if (id == _animationTimerId) { + if (!visible || _progress == PROGRESS_HIDDEN) { + stopAnimation(); + return false; + } + long elapsed = 0; + long ts = currentTimeMillis; + if (_lastAnimationTs) { + elapsed = ts - _lastAnimationTs; + if (elapsed < 0) + elapsed = 0; + else if (elapsed > 5000) + elapsed = 5000; + } + _lastAnimationTs = ts; + onAnimationTimer(elapsed); + return _animationInterval != 0; + } + // return true to repeat after the same interval, false to stop timer + return super.onTimer(id); + } +} + +/// Progress bar widget +class ProgressBarWidget : AbstractProgressBar { + this(string ID = null, int progress = PROGRESS_INDETERMINATE) { + super(ID, progress); + styleId = STYLE_PROGRESS_BAR; + } + + /** + Measure widget according to desired width and height constraints. (Step 1 of two phase layout). + + */ + override void measure(int parentWidth, int parentHeight) { + int h = 0; + int w = 0; + DrawableRef gaugeDrawable = style.customDrawable("progress_bar_gauge"); + DrawableRef indeterminateDrawable = style.customDrawable("progress_bar_indeterminate"); + if (!gaugeDrawable.isNull) { + if (h < gaugeDrawable.height) + h = gaugeDrawable.height; + } + if (!indeterminateDrawable.isNull) { + if (h < indeterminateDrawable.height) + h = indeterminateDrawable.height; + } + measuredContent(parentWidth, parentHeight, w, h); + } + + + /// Draw widget at its position to buffer + override void onDraw(DrawBuf buf) { + if (visibility != Visibility.Visible) + return; + super.onDraw(buf); + Rect rc = _pos; + applyMargins(rc); + applyPadding(rc); + DrawableRef animDrawable; + if (_progress >= 0) { + DrawableRef gaugeDrawable = style.customDrawable("progress_bar_gauge"); + animDrawable = style.customDrawable("progress_bar_gauge_animation"); + int x = rc.left + _progress * rc.width / PROGRESS_MAX; + if (!gaugeDrawable.isNull) { + gaugeDrawable.drawTo(buf, Rect(rc.left, rc.top, x, rc.bottom)); + } else { + } + } else { + DrawableRef indeterminateDrawable = style.customDrawable("progress_bar_indeterminate"); + if (!indeterminateDrawable.isNull) { + indeterminateDrawable.drawTo(buf, rc); + } + animDrawable = style.customDrawable("progress_bar_indeterminate_animation"); + } + if (!animDrawable.isNull && _animationInterval) { + if (!_animationTimerId) + scheduleAnimation(); + int w = animDrawable.width; + _animationPhase %= w * 1000; + animDrawable.drawTo(buf, rc, 0, cast(int)(_animationPhase * _animationSpeedPixelsPerSecond / 1000), 0); + Log.d("progress animation draw ", _animationPhase, " rc=", rc); + } + } +} diff --git a/src/dlangui/widgets/scrollbar.d b/src/dlangui/widgets/scrollbar.d new file mode 100644 index 00000000..432e2f29 --- /dev/null +++ b/src/dlangui/widgets/scrollbar.d @@ -0,0 +1,995 @@ +// Written in the D programming language. + +/** +This module contains simple scrollbar-like controls implementation. + +ScrollBar - scrollbar control + +SliderWidget - slider control + + + +Synopsis: + +---- +import dlangui.widgets.scrollbar; + +---- + +Copyright: Vadim Lopatin, 2014 +License: Boost License 1.0 +Authors: Vadim Lopatin, coolreader.org@gmail.com +*/ +module dlangui.widgets.scrollbar; + + +import dlangui.widgets.widget; +import dlangui.widgets.layouts; +import dlangui.widgets.controls; +import dlangui.core.events; +import dlangui.core.stdaction; + +private import std.algorithm; +private import std.conv : to; +private import std.utf : toUTF32; + +/// scroll event handler interface +interface OnScrollHandler { + /// handle scroll event + bool onScrollEvent(AbstractSlider source, ScrollEvent event); +} + +/// base class for widgets like scrollbars and sliders +class AbstractSlider : WidgetGroup { + protected int _minValue = 0; + protected int _maxValue = 100; + protected int _pageSize = 30; + protected int _position = 20; + + /// create with ID parameter + this(string ID) { + super(ID); + } + + /// scroll event listeners + Signal!OnScrollHandler scrollEvent; + + /// returns slider position + @property int position() const { return _position; } + /// sets new slider position + @property AbstractSlider position(int newPosition) { + if (_position != newPosition) { + _position = newPosition; + onPositionChanged(); + } + return this; + } + protected void onPositionChanged() { + requestLayout(); + } + /// returns slider range min value + @property int minValue() const { return _minValue; } + /// sets slider range min value + @property AbstractSlider minValue(int v) { _minValue = v; return this; } + /// returns slider range max value + @property int maxValue() const { return _maxValue; } + /// sets slider range max value + @property AbstractSlider maxValue(int v) { _maxValue = v; return this; } + + + + /// page size (visible area size) + @property int pageSize() const { return _pageSize; } + /// set page size (visible area size) + @property AbstractSlider pageSize(int size) { + if (_pageSize != size) { + _pageSize = size; + //requestLayout(); + } + return this; + } + + /// set int property value, for ML loaders + //mixin(generatePropertySettersMethodOverride("setIntProperty", "int", + // "minValue", "maxValue", "pageSize", "position")); + /// set int property value, for ML loaders + override bool setIntProperty(string name, int value) { + if (name.equal("orientation")) { // use same value for all sides + orientation = cast(Orientation)value; + return true; + } + mixin(generatePropertySetters("minValue", "maxValue", "pageSize", "position")); + return super.setIntProperty(name, value); + } + + /// set new range (min and max values for slider) + AbstractSlider setRange(int min, int max) { + if (_minValue != min || _maxValue != max) { + _minValue = min; + _maxValue = max; + //requestLayout(); + } + return this; + } + + bool sendScrollEvent(ScrollAction action) { + return sendScrollEvent(action, _position); + } + + bool sendScrollEvent(ScrollAction action, int position) { + if (!scrollEvent.assigned) + return false; + ScrollEvent event = new ScrollEvent(action, _minValue, _maxValue, _pageSize, position); + bool res = scrollEvent(this, event); + if (event.positionChanged) { + _position = event.position; + if (_position > _maxValue) + _position = _maxValue; + if (_position < _minValue) + _position = _minValue; + onPositionChanged(); + } + return true; + } + + protected Orientation _orientation = Orientation.Vertical; + /// returns scrollbar orientation (Vertical, Horizontal) + @property Orientation orientation() { return _orientation; } + /// sets scrollbar orientation + @property AbstractSlider orientation(Orientation value) { + if (_orientation != value) { + _orientation = value; + requestLayout(); + } + return this; + } + +} + +/// scroll bar - either vertical or horizontal +class ScrollBar : AbstractSlider, OnClickHandler { + protected ImageButton _btnBack; + protected ImageButton _btnForward; + protected SliderButton _indicator; + protected PageScrollButton _pageUp; + protected PageScrollButton _pageDown; + protected Rect _scrollArea; + protected int _btnSize; + protected int _minIndicatorSize; + + + + class PageScrollButton : Widget { + this(string ID) { + super(ID); + styleId = STYLE_PAGE_SCROLL; + trackHover = true; + clickable = true; + } + } + + class SliderButton : ImageButton { + Point _dragStart; + int _dragStartPosition; + bool _dragging; + Rect _dragStartRect; + + this(string resourceId) { + super("SLIDER", resourceId); + styleId = STYLE_SCROLLBAR_BUTTON; + trackHover = true; + } + + /// process mouse event; return true if event is processed by widget. + override bool onMouseEvent(MouseEvent event) { + // support onClick + if (event.action == MouseAction.ButtonDown && event.button == MouseButton.Left) { + setState(State.Pressed); + _dragging = true; + _dragStart.x = event.x; + _dragStart.y = event.y; + _dragStartPosition = _position; + _dragStartRect = _pos; + sendScrollEvent(ScrollAction.SliderPressed, _position); + return true; + } + if (event.action == MouseAction.FocusOut && _dragging) { + debug(scrollbar) Log.d("ScrollBar slider dragging - FocusOut"); + return true; + } + if (event.action == MouseAction.FocusIn && _dragging) { + debug(scrollbar) Log.d("ScrollBar slider dragging - FocusIn"); + return true; + } + if (event.action == MouseAction.Move && _dragging) { + int delta = _orientation == Orientation.Vertical ? event.y - _dragStart.y : event.x - _dragStart.x; + debug(scrollbar) Log.d("ScrollBar slider dragging - Move delta=", delta); + Rect rc = _dragStartRect; + int offset; + int space; + if (_orientation == Orientation.Vertical) { + rc.top += delta; + rc.bottom += delta; + if (rc.top < _scrollArea.top) { + rc.top = _scrollArea.top; + rc.bottom = _scrollArea.top + _dragStartRect.height; + } else if (rc.bottom > _scrollArea.bottom) { + rc.top = _scrollArea.bottom - _dragStartRect.height; + rc.bottom = _scrollArea.bottom; + } + offset = rc.top - _scrollArea.top; + space = _scrollArea.height - rc.height; + } else { + rc.left += delta; + rc.right += delta; + if (rc.left < _scrollArea.left) { + rc.left = _scrollArea.left; + rc.right = _scrollArea.left + _dragStartRect.width; + } else if (rc.right > _scrollArea.right) { + rc.left = _scrollArea.right - _dragStartRect.width; + rc.right = _scrollArea.right; + } + offset = rc.left - _scrollArea.left; + space = _scrollArea.width - rc.width; + } + layoutButtons(rc); + //_pos = rc; + int position = cast(int)(space > 0 ? _minValue + cast(long)offset * (_maxValue - _minValue - _pageSize) / space : 0); + invalidate(); + onIndicatorDragging(_dragStartPosition, position); + return true; + } + if (event.action == MouseAction.ButtonUp && event.button == MouseButton.Left) { + resetState(State.Pressed); + if (_dragging) { + sendScrollEvent(ScrollAction.SliderReleased, _position); + _dragging = false; + } + return true; + } + if (event.action == MouseAction.Move && trackHover) { + if (!(state & State.Hovered)) { + debug(scrollbar) Log.d("Hover ", id); + setState(State.Hovered); + } + return true; + } + if (event.action == MouseAction.Leave && trackHover) { + debug(scrollbar) Log.d("Leave ", id); + resetState(State.Hovered); + return true; + } + if (event.action == MouseAction.Cancel && trackHover) { + debug(scrollbar) Log.d("Cancel ? trackHover", id); + resetState(State.Hovered); + resetState(State.Pressed); + _dragging = false; + return true; + } + if (event.action == MouseAction.Cancel) { + debug(scrollbar) Log.d("SliderButton.onMouseEvent event.action == MouseAction.Cancel"); + resetState(State.Pressed); + _dragging = false; + return true; + } + return false; + } + + } + + protected bool onIndicatorDragging(int initialPosition, int currentPosition) { + _position = currentPosition; + return sendScrollEvent(ScrollAction.SliderMoved, currentPosition); + } + + private bool calcButtonSizes(int availableSize, ref int spaceBackSize, ref int spaceForwardSize, ref int indicatorSize) { + int dv = _maxValue - _minValue; + if (_pageSize >= dv) { + // full size + spaceBackSize = spaceForwardSize = 0; + indicatorSize = availableSize; + return false; + } + if (dv < 0) + dv = 0; + indicatorSize = dv ? _pageSize * availableSize / dv : _minIndicatorSize; + if (indicatorSize < _minIndicatorSize) + indicatorSize = _minIndicatorSize; + if (indicatorSize >= availableSize) { + // full size + spaceBackSize = spaceForwardSize = 0; + indicatorSize = availableSize; + return false; + } + int spaceLeft = availableSize - indicatorSize; + int topv = _position - _minValue; + int bottomv = _position + _pageSize - _minValue; + if (topv < 0) + topv = 0; + if (bottomv > dv) + bottomv = dv; + bottomv = dv - bottomv; + spaceBackSize = cast(int)(cast(long)spaceLeft * topv / (topv + bottomv)); + spaceForwardSize = spaceLeft - spaceBackSize; + return true; + } + + /// returns scrollbar orientation (Vertical, Horizontal) + override @property Orientation orientation() { return _orientation; } + /// sets scrollbar orientation + override @property AbstractSlider orientation(Orientation value) { + if (_orientation != value) { + _orientation = value; + _btnBack.drawableId = style.customDrawableId(_orientation == Orientation.Vertical ? ATTR_SCROLLBAR_BUTTON_UP : ATTR_SCROLLBAR_BUTTON_LEFT); + _btnForward.drawableId = style.customDrawableId(_orientation == Orientation.Vertical ? ATTR_SCROLLBAR_BUTTON_DOWN : ATTR_SCROLLBAR_BUTTON_RIGHT); + _indicator.drawableId = style.customDrawableId(_orientation == Orientation.Vertical ? ATTR_SCROLLBAR_INDICATOR_VERTICAL : ATTR_SCROLLBAR_INDICATOR_HORIZONTAL); + requestLayout(); + } + return this; + } + + /// set string property value, for ML loaders + override bool setStringProperty(string name, string value) { + if (name.equal("orientation")) { + if (value.equal("Vertical") || value.equal("vertical")) + orientation = Orientation.Vertical; + else + orientation = Orientation.Horizontal; + return true; + } + return super.setStringProperty(name, value); + } + + + /// empty parameter list constructor - for usage by factory + this() { + this(null, Orientation.Vertical); + } + /// create with ID parameter + this(string ID, Orientation orient = Orientation.Vertical) { + super(ID); + styleId = STYLE_SCROLLBAR; + _orientation = orient; + _btnBack = new ImageButton("BACK", style.customDrawableId(_orientation == Orientation.Vertical ? ATTR_SCROLLBAR_BUTTON_UP : ATTR_SCROLLBAR_BUTTON_LEFT)); + _btnForward = new ImageButton("FORWARD", style.customDrawableId(_orientation == Orientation.Vertical ? ATTR_SCROLLBAR_BUTTON_DOWN : ATTR_SCROLLBAR_BUTTON_RIGHT)); + _pageUp = new PageScrollButton("PAGE_UP"); + _pageDown = new PageScrollButton("PAGE_DOWN"); + _btnBack.styleId = STYLE_SCROLLBAR_BUTTON_TRANSPARENT; + _btnForward.styleId = STYLE_SCROLLBAR_BUTTON_TRANSPARENT; + _indicator = new SliderButton(style.customDrawableId(_orientation == Orientation.Vertical ? ATTR_SCROLLBAR_INDICATOR_VERTICAL : ATTR_SCROLLBAR_INDICATOR_HORIZONTAL)); + addChild(_btnBack); + addChild(_btnForward); + addChild(_indicator); + addChild(_pageUp); + addChild(_pageDown); + _btnBack.focusable = false; + _btnForward.focusable = false; + _indicator.focusable = false; + _pageUp.focusable = false; + _pageDown.focusable = false; + _btnBack.click = &onClick; + _btnForward.click = &onClick; + _pageUp.click = &onClick; + _pageDown.click = &onClick; + } + + override void measure(int parentWidth, int parentHeight) { + Point sz; + _btnBack.measure(parentWidth, parentHeight); + _btnForward.measure(parentWidth, parentHeight); + _indicator.measure(parentWidth, parentHeight); + _pageUp.measure(parentWidth, parentHeight); + _pageDown.measure(parentWidth, parentHeight); + _btnSize = _btnBack.measuredWidth; + _minIndicatorSize = _orientation == Orientation.Vertical ? _indicator.measuredHeight : _indicator.measuredWidth; + if (_btnSize < _minIndicatorSize) + _btnSize = _minIndicatorSize; + if (_btnSize < _btnForward.measuredWidth) + _btnSize = _btnForward.measuredWidth; + if (_btnSize < _btnForward.measuredHeight) + _btnSize = _btnForward.measuredHeight; + if (_btnSize < _btnBack.measuredHeight) + _btnSize = _btnBack.measuredHeight; + static if (BACKEND_GUI) { + if (_btnSize < 16) + _btnSize = 16; + } + if (_orientation == Orientation.Vertical) { + // vertical + sz.x = _btnSize; + sz.y = _btnSize * 5; // min height + } else { + // horizontal + sz.y = _btnSize; + sz.x = _btnSize * 5; // min height + } + measuredContent(parentWidth, parentHeight, sz.x, sz.y); + } + + override protected void onPositionChanged() { + if (!needLayout) + layoutButtons(); + } + + /// hide controls when scroll is not possible + protected void updateState() { + bool canScroll = _maxValue - _minValue > _pageSize; + if (canScroll) { + _btnBack.setState(State.Enabled); + _btnForward.setState(State.Enabled); + _indicator.visibility = Visibility.Visible; + _pageUp.visibility = Visibility.Visible; + _pageDown.visibility = Visibility.Visible; + } else { + _btnBack.resetState(State.Enabled); + _btnForward.resetState(State.Enabled); + _indicator.visibility = Visibility.Gone; + _pageUp.visibility = Visibility.Gone; + _pageDown.visibility = Visibility.Gone; + } + cancelLayout(); + } + + override void cancelLayout() { + _btnBack.cancelLayout(); + _btnForward.cancelLayout(); + _indicator.cancelLayout(); + _pageUp.cancelLayout(); + _pageDown.cancelLayout(); + super.cancelLayout(); + } + + protected void layoutButtons() { + Rect irc = _scrollArea; + if (_orientation == Orientation.Vertical) { + // vertical + int spaceBackSize, spaceForwardSize, indicatorSize; + bool indicatorVisible = calcButtonSizes(_scrollArea.height, spaceBackSize, spaceForwardSize, indicatorSize); + irc.top += spaceBackSize; + irc.bottom -= spaceForwardSize; + layoutButtons(irc); + } else { + // horizontal + int spaceBackSize, spaceForwardSize, indicatorSize; + bool indicatorVisible = calcButtonSizes(_scrollArea.width, spaceBackSize, spaceForwardSize, indicatorSize); + irc.left += spaceBackSize; + irc.right -= spaceForwardSize; + layoutButtons(irc); + } + updateState(); + cancelLayout(); + } + + protected void layoutButtons(Rect irc) { + Rect r; + _indicator.visibility = Visibility.Visible; + if (_orientation == Orientation.Vertical) { + _indicator.layout(irc); + if (_scrollArea.top < irc.top) { + r = _scrollArea; + r.bottom = irc.top; + _pageUp.layout(r); + _pageUp.visibility = Visibility.Visible; + } else { + _pageUp.visibility = Visibility.Invisible; + } + if (_scrollArea.bottom > irc.bottom) { + r = _scrollArea; + r.top = irc.bottom; + _pageDown.layout(r); + _pageDown.visibility = Visibility.Visible; + } else { + _pageDown.visibility = Visibility.Invisible; + } + } else { + _indicator.layout(irc); + if (_scrollArea.left < irc.left) { + r = _scrollArea; + r.right = irc.left; + _pageUp.layout(r); + _pageUp.visibility = Visibility.Visible; + } else { + _pageUp.visibility = Visibility.Invisible; + } + if (_scrollArea.right > irc.right) { + r = _scrollArea; + r.left = irc.right; + _pageDown.layout(r); + _pageDown.visibility = Visibility.Visible; + } else { + _pageDown.visibility = Visibility.Invisible; + } + } + } + + override void layout(Rect rc) { + _needLayout = false; + applyMargins(rc); + applyPadding(rc); + Rect r; + if (_orientation == Orientation.Vertical) { + // vertical + // buttons + int backbtnpos = rc.top + _btnSize; + int fwdbtnpos = rc.bottom - _btnSize; + r = rc; + r.bottom = backbtnpos; + _btnBack.layout(r); + r = rc; + r.top = fwdbtnpos; + _btnForward.layout(r); + // indicator + r = rc; + r.top = backbtnpos; + r.bottom = fwdbtnpos; + _scrollArea = r; + } else { + // horizontal + int backbtnpos = rc.left + _btnSize; + int fwdbtnpos = rc.right - _btnSize; + r = rc; + r.right = backbtnpos; + _btnBack.layout(r); + r = rc; + r.left = fwdbtnpos; + _btnForward.layout(r); + // indicator + r = rc; + r.left = backbtnpos; + r.right = fwdbtnpos; + _scrollArea = r; + } + layoutButtons(); + _pos = rc; + } + + 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; + } + + /// handle mouse wheel events + override bool onMouseEvent(MouseEvent event) { + if (visibility != Visibility.Visible) + return false; + if (event.action == MouseAction.Wheel) { + int delta = event.wheelDelta; + if (delta > 0) + sendScrollEvent(ScrollAction.LineUp, position); + else if (delta < 0) + sendScrollEvent(ScrollAction.LineDown, position); + return true; + } + return super.onMouseEvent(event); + } + + /// Draw widget at its position to buffer + override void onDraw(DrawBuf buf) { + if (visibility != Visibility.Visible && !buf.isClippedOut(_pos)) + return; + super.onDraw(buf); + Rect rc = _pos; + applyMargins(rc); + applyPadding(rc); + auto saver = ClipRectSaver(buf, rc, alpha); + _btnForward.onDraw(buf); + _btnBack.onDraw(buf); + _pageUp.onDraw(buf); + _pageDown.onDraw(buf); + _indicator.onDraw(buf); + } +} + +/// scroll bar - either vertical or horizontal +class SliderWidget : AbstractSlider, OnClickHandler { + protected SliderButton _indicator; + protected PageScrollButton _pageUp; + protected PageScrollButton _pageDown; + protected Rect _scrollArea; + protected int _btnSize; + protected int _minIndicatorSize; + + class PageScrollButton : Widget { + this(string ID) { + super(ID); + styleId = STYLE_PAGE_SCROLL; + trackHover = true; + clickable = true; + } + } + + class SliderButton : ImageButton { + Point _dragStart; + int _dragStartPosition; + bool _dragging; + Rect _dragStartRect; + + this(string resourceId) { + super("SLIDER", resourceId); + styleId = STYLE_SCROLLBAR_BUTTON; + trackHover = true; + } + + /// process mouse event; return true if event is processed by widget. + override bool onMouseEvent(MouseEvent event) { + // support onClick + if (event.action == MouseAction.ButtonDown && event.button == MouseButton.Left) { + setState(State.Pressed); + _dragging = true; + _dragStart.x = event.x; + _dragStart.y = event.y; + _dragStartPosition = _position; + _dragStartRect = _pos; + sendScrollEvent(ScrollAction.SliderPressed, _position); + return true; + } + if (event.action == MouseAction.FocusOut && _dragging) { + debug(scrollbar) Log.d("ScrollBar slider dragging - FocusOut"); + return true; + } + if (event.action == MouseAction.FocusIn && _dragging) { + debug(scrollbar) Log.d("ScrollBar slider dragging - FocusIn"); + return true; + } + if (event.action == MouseAction.Move && _dragging) { + int delta = _orientation == Orientation.Vertical ? event.y - _dragStart.y : event.x - _dragStart.x; + debug(scrollbar) Log.d("ScrollBar slider dragging - Move delta=", delta); + Rect rc = _dragStartRect; + int offset; + int space; + if (_orientation == Orientation.Vertical) { + rc.top += delta; + rc.bottom += delta; + if (rc.top < _scrollArea.top) { + rc.top = _scrollArea.top; + rc.bottom = _scrollArea.top + _dragStartRect.height; + } else if (rc.bottom > _scrollArea.bottom) { + rc.top = _scrollArea.bottom - _dragStartRect.height; + rc.bottom = _scrollArea.bottom; + } + offset = rc.top - _scrollArea.top; + space = _scrollArea.height - rc.height; + } else { + rc.left += delta; + rc.right += delta; + if (rc.left < _scrollArea.left) { + rc.left = _scrollArea.left; + rc.right = _scrollArea.left + _dragStartRect.width; + } else if (rc.right > _scrollArea.right) { + rc.left = _scrollArea.right - _dragStartRect.width; + rc.right = _scrollArea.right; + } + offset = rc.left - _scrollArea.left; + space = _scrollArea.width - rc.width; + } + layoutButtons(rc); + //_pos = rc; + int position = cast(int)(space > 0 ? _minValue + cast(long)offset * (_maxValue - _minValue - _pageSize) / space : 0); + invalidate(); + onIndicatorDragging(_dragStartPosition, position); + return true; + } + if (event.action == MouseAction.ButtonUp && event.button == MouseButton.Left) { + resetState(State.Pressed); + if (_dragging) { + sendScrollEvent(ScrollAction.SliderReleased, _position); + _dragging = false; + } + return true; + } + if (event.action == MouseAction.Move && trackHover) { + if (!(state & State.Hovered)) { + debug(scrollbar) Log.d("Hover ", id); + setState(State.Hovered); + } + return true; + } + if (event.action == MouseAction.Leave && trackHover) { + debug(scrollbar) Log.d("Leave ", id); + resetState(State.Hovered); + return true; + } + if (event.action == MouseAction.Cancel && trackHover) { + debug(scrollbar) Log.d("Cancel ? trackHover", id); + resetState(State.Hovered); + resetState(State.Pressed); + _dragging = false; + return true; + } + if (event.action == MouseAction.Cancel) { + debug(scrollbar) Log.d("SliderButton.onMouseEvent event.action == MouseAction.Cancel"); + resetState(State.Pressed); + _dragging = false; + return true; + } + return false; + } + + } + + protected bool onIndicatorDragging(int initialPosition, int currentPosition) { + _position = currentPosition; + return sendScrollEvent(ScrollAction.SliderMoved, currentPosition); + } + + private bool calcButtonSizes(int availableSize, ref int spaceBackSize, ref int spaceForwardSize, ref int indicatorSize) { + int dv = _maxValue - _minValue; + if (_pageSize >= dv) { + // full size + spaceBackSize = spaceForwardSize = 0; + indicatorSize = availableSize; + return false; + } + if (dv < 0) + dv = 0; + indicatorSize = dv ? _pageSize * availableSize / dv : _minIndicatorSize; + if (indicatorSize < _minIndicatorSize) + indicatorSize = _minIndicatorSize; + if (indicatorSize >= availableSize) { + // full size + spaceBackSize = spaceForwardSize = 0; + indicatorSize = availableSize; + return false; + } + int spaceLeft = availableSize - indicatorSize; + int topv = _position - _minValue; + int bottomv = _position + _pageSize - _minValue; + if (topv < 0) + topv = 0; + if (bottomv > dv) + bottomv = dv; + bottomv = dv - bottomv; + spaceBackSize = cast(int)(cast(long)spaceLeft * topv / (topv + bottomv)); + spaceForwardSize = spaceLeft - spaceBackSize; + return true; + } + + /// returns scrollbar orientation (Vertical, Horizontal) + override @property Orientation orientation() { return _orientation; } + /// sets scrollbar orientation + override @property AbstractSlider orientation(Orientation value) { + if (_orientation != value) { + _orientation = value; + _indicator.drawableId = style.customDrawableId(_orientation == Orientation.Vertical ? ATTR_SCROLLBAR_INDICATOR_VERTICAL : ATTR_SCROLLBAR_INDICATOR_HORIZONTAL); + requestLayout(); + } + return this; + } + + /// set string property value, for ML loaders + override bool setStringProperty(string name, string value) { + if (name.equal("orientation")) { + if (value.equal("Vertical") || value.equal("vertical")) + orientation = Orientation.Vertical; + else + orientation = Orientation.Horizontal; + return true; + } + return super.setStringProperty(name, value); + } + + + /// empty parameter list constructor - for usage by factory + this() { + this(null, Orientation.Horizontal); + } + /// create with ID parameter + this(string ID, Orientation orient = Orientation.Horizontal) { + super(ID); + styleId = STYLE_SLIDER; + _orientation = orient; + _pageSize = 1; + _pageUp = new PageScrollButton("PAGE_UP"); + _pageDown = new PageScrollButton("PAGE_DOWN"); + _indicator = new SliderButton(style.customDrawableId(_orientation == Orientation.Vertical ? ATTR_SCROLLBAR_INDICATOR_VERTICAL : ATTR_SCROLLBAR_INDICATOR_HORIZONTAL)); + addChild(_indicator); + addChild(_pageUp); + addChild(_pageDown); + _indicator.focusable = false; + _pageUp.focusable = false; + _pageDown.focusable = false; + _pageUp.click = &onClick; + _pageDown.click = &onClick; + } + + override void measure(int parentWidth, int parentHeight) { + Point sz; + _indicator.measure(parentWidth, parentHeight); + _pageUp.measure(parentWidth, parentHeight); + _pageDown.measure(parentWidth, parentHeight); + _minIndicatorSize = _orientation == Orientation.Vertical ? _indicator.measuredHeight : _indicator.measuredWidth; + _btnSize = _minIndicatorSize; + if (_btnSize < _minIndicatorSize) + _btnSize = _minIndicatorSize; + static if (BACKEND_GUI) { + if (_btnSize < 16) + _btnSize = 16; + } + if (_orientation == Orientation.Vertical) { + // vertical + sz.x = _btnSize; + sz.y = _btnSize * 5; // min height + } else { + // horizontal + sz.y = _btnSize; + sz.x = _btnSize * 5; // min height + } + measuredContent(parentWidth, parentHeight, sz.x, sz.y); + } + + override protected void onPositionChanged() { + if (!needLayout) + layoutButtons(); + } + + /// hide controls when scroll is not possible + protected void updateState() { + bool canScroll = _maxValue - _minValue > _pageSize; + if (canScroll) { + _indicator.visibility = Visibility.Visible; + _pageUp.visibility = Visibility.Visible; + _pageDown.visibility = Visibility.Visible; + } else { + _indicator.visibility = Visibility.Gone; + _pageUp.visibility = Visibility.Gone; + _pageDown.visibility = Visibility.Gone; + } + cancelLayout(); + } + + override void cancelLayout() { + _indicator.cancelLayout(); + _pageUp.cancelLayout(); + _pageDown.cancelLayout(); + super.cancelLayout(); + } + + protected void layoutButtons() { + Rect irc = _scrollArea; + if (_orientation == Orientation.Vertical) { + // vertical + int spaceBackSize, spaceForwardSize, indicatorSize; + bool indicatorVisible = calcButtonSizes(_scrollArea.height, spaceBackSize, spaceForwardSize, indicatorSize); + irc.top += spaceBackSize; + irc.bottom -= spaceForwardSize; + layoutButtons(irc); + } else { + // horizontal + int spaceBackSize, spaceForwardSize, indicatorSize; + bool indicatorVisible = calcButtonSizes(_scrollArea.width, spaceBackSize, spaceForwardSize, indicatorSize); + irc.left += spaceBackSize; + irc.right -= spaceForwardSize; + layoutButtons(irc); + } + updateState(); + cancelLayout(); + } + + protected void layoutButtons(Rect irc) { + Rect r; + _indicator.visibility = Visibility.Visible; + if (_orientation == Orientation.Vertical) { + _indicator.layout(irc); + if (_scrollArea.top < irc.top) { + r = _scrollArea; + r.bottom = irc.top; + _pageUp.layout(r); + _pageUp.visibility = Visibility.Visible; + } else { + _pageUp.visibility = Visibility.Invisible; + } + if (_scrollArea.bottom > irc.bottom) { + r = _scrollArea; + r.top = irc.bottom; + _pageDown.layout(r); + _pageDown.visibility = Visibility.Visible; + } else { + _pageDown.visibility = Visibility.Invisible; + } + } else { + _indicator.layout(irc); + if (_scrollArea.left < irc.left) { + r = _scrollArea; + r.right = irc.left; + _pageUp.layout(r); + _pageUp.visibility = Visibility.Visible; + } else { + _pageUp.visibility = Visibility.Invisible; + } + if (_scrollArea.right > irc.right) { + r = _scrollArea; + r.left = irc.right; + _pageDown.layout(r); + _pageDown.visibility = Visibility.Visible; + } else { + _pageDown.visibility = Visibility.Invisible; + } + } + } + + override void layout(Rect rc) { + _needLayout = false; + applyMargins(rc); + applyPadding(rc); + Rect r; + if (_orientation == Orientation.Vertical) { + // vertical + // buttons + // indicator + r = rc; + _scrollArea = r; + } else { + // horizontal + // indicator + r = rc; + _scrollArea = r; + } + layoutButtons(); + _pos = rc; + } + + override bool onClick(Widget source) { + Log.d("Scrollbar.onClick ", source.id); + if (source.compareId("PAGE_UP")) + return sendScrollEvent(ScrollAction.PageUp, position); + if (source.compareId("PAGE_DOWN")) + return sendScrollEvent(ScrollAction.PageDown, position); + return true; + } + + /// handle mouse wheel events + override bool onMouseEvent(MouseEvent event) { + if (visibility != Visibility.Visible) + return false; + if (event.action == MouseAction.Wheel) { + int delta = event.wheelDelta; + if (delta > 0) + sendScrollEvent(ScrollAction.LineUp, position); + else if (delta < 0) + sendScrollEvent(ScrollAction.LineDown, position); + return true; + } + return super.onMouseEvent(event); + } + + /// Draw widget at its position to buffer + override void onDraw(DrawBuf buf) { + if (visibility != Visibility.Visible && !buf.isClippedOut(_pos)) + return; + Rect rc = _pos; + applyMargins(rc); + auto saver = ClipRectSaver(buf, rc, alpha); + DrawableRef bg = backgroundDrawable; + if (!bg.isNull) { + Rect r = rc; + if (_orientation == Orientation.Vertical) { + int dw = bg.width; + r.left += (rc.width - dw)/2; + r.right = r.left + dw; + } else { + int dw = bg.height; + r.top += (rc.height - dw)/2; + r.bottom = r.top + dw; + } + bg.drawTo(buf, r, state); + } + applyPadding(rc); + if (state & State.Focused) { + rc.expand(FOCUS_RECT_PADDING, FOCUS_RECT_PADDING); + drawFocusRect(buf, rc); + } + _needDraw = false; + _pageUp.onDraw(buf); + _pageDown.onDraw(buf); + _indicator.onDraw(buf); + } +} + diff --git a/src/dlangui/widgets/styles.d b/src/dlangui/widgets/styles.d index 8f76f293..d1a3585d 100644 --- a/src/dlangui/widgets/styles.d +++ b/src/dlangui/widgets/styles.d @@ -119,6 +119,8 @@ immutable string STYLE_TRANSPARENT_BUTTON_BACKGROUND = "TRANSPARENT_BUTTON_BACKG immutable string STYLE_GROUP_BOX = "GROUP_BOX"; /// standard style id for GroupBox caption immutable string STYLE_GROUP_BOX_CAPTION = "GROUP_BOX_CAPTION"; +/// standard style id for ProgressBarWidget caption +immutable string STYLE_PROGRESS_BAR = "PROGRESS_BAR"; /// standard style id for tree item immutable string STYLE_TREE_ITEM = "TREE_ITEM"; diff --git a/src/dlangui/widgets/widget.d b/src/dlangui/widgets/widget.d index 75ed2ceb..f7741d25 100644 --- a/src/dlangui/widgets/widget.d +++ b/src/dlangui/widgets/widget.d @@ -1143,12 +1143,15 @@ public: /// set new timer to call onTimer() after specified interval (for recurred notifications, return true from onTimer) ulong setTimer(long intervalMillis) { - return window.setTimer(this, intervalMillis); + if (auto w = window) + return w.setTimer(this, intervalMillis); + return 0; // no window - no timer } /// cancel timer - pass value returned from setTimer() as timerId parameter void cancelTimer(ulong timerId) { - window.cancelTimer(timerId); + if (auto w = window) + w.cancelTimer(timerId); } /// handle timer; return true to repeat timer event after next interval, false cancel timer diff --git a/views/res/progress_bar_gauge_animation.png b/views/res/progress_bar_gauge_animation.png new file mode 100644 index 0000000000000000000000000000000000000000..7c333382efbccd2889e8d53b9834dc05c03a01f3 GIT binary patch literal 11595 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 W@^xC2>6ZU%nlG8zBC9T3kNF=yOqGiO literal 0 HcmV?d00001 diff --git a/views/res/theme_default.xml b/views/res/theme_default.xml index 68ef8289..2a2d7d0d 100644 --- a/views/res/theme_default.xml +++ b/views/res/theme_default.xml @@ -505,5 +505,17 @@ padding="1pt,1pt,1pt,1pt"> + + diff --git a/views/standard_resources.list b/views/standard_resources.list index c9c766a6..9b3d4676 100644 --- a/views/standard_resources.list +++ b/views/standard_resources.list @@ -55,6 +55,7 @@ res/group_box_frame_bottom_dark.9.png res/group_box_frame_up_left_dark.9.png res/group_box_frame_up_right_dark.9.png res/slider_background_dark.9.png +res/progress_bar_gauge_animation.png res/i18n/std_en.ini res/i18n/std_ru.ini res/list_item_background.xml