diff --git a/examples/example1/main.d b/examples/example1/main.d index 0327f02e..d370092c 100644 --- a/examples/example1/main.d +++ b/examples/example1/main.d @@ -63,10 +63,17 @@ extern (C) int UIAppMain(string[] args) { hlayout.backgroundColor = 0x8080C0; layout.addChild(hlayout); + LinearLayout vlayoutgroup = new HorizontalLayout(); LinearLayout vlayout = new VerticalLayout(); vlayout.addChild((new TextWidget()).text("VLayout line 1").textColor(0x40FF4000)); // - vlayout.addChild((new TextWidget()).text("VLayout line 2").textColor(0x40FFFF00)); - layout.addChild(vlayout); + vlayout.addChild((new TextWidget()).text("VLayout line 2").textColor(0x40FF8000)); + vlayout.addChild((new TextWidget()).text("VLayout line 2").textColor(0x40008000)); + vlayout.layoutWidth(FILL_PARENT); + vlayoutgroup.addChild(vlayout); + vlayoutgroup.layoutWidth(FILL_PARENT); + ScrollBar vsb = new ScrollBar("vscroll", Orientation.Vertical); + vlayoutgroup.addChild(vsb); + layout.addChild(vlayoutgroup); ScrollBar sb = new ScrollBar("hscroll", Orientation.Horizontal); layout.addChild(sb.layoutHeight(WRAP_CONTENT).layoutWidth(FILL_PARENT)); diff --git a/examples/example1/res/btn_default_small_normal_hover.9.png b/examples/example1/res/btn_default_small_normal_hover.9.png index 27a7caf2..576acf55 100644 Binary files a/examples/example1/res/btn_default_small_normal_hover.9.png and b/examples/example1/res/btn_default_small_normal_hover.9.png differ diff --git a/src/dlangui/core/events.d b/src/dlangui/core/events.d index b302773d..f8a098b7 100644 --- a/src/dlangui/core/events.d +++ b/src/dlangui/core/events.d @@ -7,9 +7,10 @@ enum MouseAction : ubyte { ButtonDown, // button is down ButtonUp, // button is up Move, // mouse pointer is moving - FocusIn, // pointer moved outside of widget while button was down - FocusOut, // pointer is back inside widget while button is down after FocusIn + FocusIn, // pointer is back inside widget while button is down after FocusOut + FocusOut, // pointer moved outside of widget while button was down (if handler returns true, Move events will be sent even while pointer is outside widget) Wheel, // scroll wheel movement + //Hover, // pointer entered widget which while button was not down (return true to track Hover state) Leave // pointer left widget which has before processed Move message, while button was not down } diff --git a/src/dlangui/platforms/common/platform.d b/src/dlangui/platforms/common/platform.d index 9bb977b8..0eee7106 100644 --- a/src/dlangui/platforms/common/platform.d +++ b/src/dlangui/platforms/common/platform.d @@ -122,6 +122,7 @@ class Window { protected Widget _mouseCaptureWidget; protected ushort _mouseCaptureButtons; protected bool _mouseCaptureFocusedOut; + protected bool _mouseCaptureFocusedOutTrackMovements; protected bool dispatchCancel(MouseEvent event) { event.changeAction(MouseAction.Cancel); @@ -157,6 +158,9 @@ class Window { event.changeAction(MouseAction.FocusOut); _mouseCaptureFocusedOut = true; _mouseCaptureButtons = event.flags & (MouseFlag.LButton|MouseFlag.RButton|MouseFlag.MButton); + _mouseCaptureFocusedOutTrackMovements = _mouseCaptureWidget.onMouseEvent(event); + return true; + } else if (_mouseCaptureFocusedOutTrackMovements) { return _mouseCaptureWidget.onMouseEvent(event); } return true; @@ -189,27 +193,35 @@ class Window { } return res; } + bool processed = false; if (event.action == MouseAction.Move && _mouseTrackingWidget !is null) { if (!_mouseTrackingWidget.isPointInside(event.x, event.y)) { // send Leave message MouseEvent leaveEvent = new MouseEvent(event); leaveEvent.changeAction(MouseAction.Leave); - _mouseCaptureWidget.onMouseEvent(event); + _mouseTrackingWidget.onMouseEvent(leaveEvent); // stop tracking _mouseTrackingWidget = null; + processed = true; } } if (!res) { res = dispatchMouseEvent(_mainWidget, event); } - return res; + return res || processed; } + /// checks content widgets for necessary redraw and/or layout protected void checkUpdateNeeded(Widget root, ref bool needDraw, ref bool needLayout, ref bool animationActive) { if (!root.visibility == Visibility.Visible) return; needDraw = root.needDraw || needDraw; - needLayout = root.needLayout || needLayout; + if (!needLayout) { + needLayout = root.needLayout || needLayout; + if (needLayout) { + Log.d("need layout: ", root.id); + } + } animationActive = root.animating || animationActive; for (int i = 0; i < root.childCount; i++) checkUpdateNeeded(root.child(i), needDraw, needLayout, animationActive); diff --git a/src/dlangui/platforms/windows/winapp.d b/src/dlangui/platforms/windows/winapp.d index cca16ecf..85f126ea 100644 --- a/src/dlangui/platforms/windows/winapp.d +++ b/src/dlangui/platforms/windows/winapp.d @@ -201,7 +201,6 @@ class Win32Window : Window { version (USE_OPENGL) { private void paintUsingOpenGL() { - Log.d("paintUsingOpenGL()"); // hack to stop infinite WM_PAINT loop PAINTSTRUCT ps; HDC hdc2 = BeginPaint(_hwnd, &ps); @@ -223,11 +222,9 @@ class Win32Window : Window { float r = ((_backgroundColor >> 16) & 255) / 255.0f; float g = ((_backgroundColor >> 8) & 255) / 255.0f; float b = ((_backgroundColor >> 0) & 255) / 255.0f; - Log.d("paintUsingOpenGL() - clearing buffer"); glClearColor(r, g, b, a); glClear(GL_COLOR_BUFFER_BIT); - Log.d("paintUsingOpenGL() - creating drawbuf"); GLDrawBuf buf = new GLDrawBuf(_dx, _dy, false); buf.beforeDrawing(); static if (false) { @@ -244,9 +241,7 @@ class Win32Window : Window { } else { onDraw(buf); } - Log.d("paintUsingOpenGL() - calling buf.afterDrawing"); buf.afterDrawing(); - Log.d("onPaint() end drawing opengl"); SwapBuffers(hdc); wglMakeCurrent(hdc, null); } diff --git a/src/dlangui/widgets/controls.d b/src/dlangui/widgets/controls.d index 90f62444..9549f63a 100644 --- a/src/dlangui/widgets/controls.d +++ b/src/dlangui/widgets/controls.d @@ -117,6 +117,7 @@ class ImageButton : ImageWidget { super(ID); styleId = "BUTTON"; _drawableId = drawableId; + trackHover = true; } } @@ -127,6 +128,7 @@ class Button : Widget { this(string ID = null) { super(ID); styleId = "BUTTON"; + trackHover = true; } override void measure(int parentWidth, int parentHeight) { @@ -169,6 +171,7 @@ class ScrollBar : WidgetGroup, OnClickHandler { this(string ID) { super(ID); styleId = "PAGE_SCROLL"; + trackHover = true; } } @@ -177,8 +180,10 @@ class ScrollBar : WidgetGroup, OnClickHandler { int _dragStartPosition; bool _dragging; Rect _dragStartRect; + this(string resourceId) { super("SLIDER", resourceId); + trackHover = true; } /// process mouse event; return true if event is processed by widget. @@ -193,6 +198,9 @@ class ScrollBar : WidgetGroup, OnClickHandler { _dragStartRect = _pos; return true; } + if (event.action == MouseAction.FocusOut && _dragging) { + return true; + } if (event.action == MouseAction.Move && _dragging) { int delta = _orientation == Orientation.Vertical ? event.y - _dragStart.y : event.x - _dragStart.x; Rect rc = _dragStartRect; @@ -205,7 +213,7 @@ class ScrollBar : WidgetGroup, OnClickHandler { rc.top = _scrollArea.top; rc.bottom = _scrollArea.top + _dragStartRect.height; } else if (rc.bottom > _scrollArea.bottom) { - rc.top = _scrollArea.top - _dragStartRect.height; + rc.top = _scrollArea.bottom - _dragStartRect.height; rc.bottom = _scrollArea.bottom; } offset = rc.top - _scrollArea.top; @@ -223,7 +231,8 @@ class ScrollBar : WidgetGroup, OnClickHandler { offset = rc.left - _scrollArea.left; space = _scrollArea.width - rc.width; } - _pos = rc; + layoutButtons(rc); + //_pos = rc; int position = space > 0 ? _minValue + offset * (_maxValue - _minValue - _pageSize) / space : 0; invalidate(); onIndicatorDragging(_dragStartPosition, position); @@ -237,6 +246,18 @@ class ScrollBar : WidgetGroup, OnClickHandler { } return true; } + if (event.action == MouseAction.Move && trackHover) { + if (!(state & State.Hover)) { + Log.d("Hover ", id); + setState(State.Hover); + } + return true; + } + if ((event.action == MouseAction.Leave || event.action == MouseAction.Cancel) && trackHover) { + Log.d("Leave ", id); + resetState(State.Hover); + return true; + } if (event.action == MouseAction.Cancel) { Log.d("SliderButton.onMouseEvent event.action == MouseAction.Cancel"); resetState(State.Pressed); @@ -347,6 +368,47 @@ class ScrollBar : WidgetGroup, OnClickHandler { measuredContent(parentWidth, parentHeight, sz.x, sz.y); } + protected void layoutButtons(Rect irc) { + Rect r; + 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) { applyMargins(rc); applyPadding(rc); @@ -372,23 +434,7 @@ class ScrollBar : WidgetGroup, OnClickHandler { Rect irc = r; irc.top += spaceBackSize; irc.bottom -= spaceForwardSize; - _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; - } + layoutButtons(irc); } else { // horizontal int backbtnpos = rc.left + _btnSize; @@ -409,23 +455,7 @@ class ScrollBar : WidgetGroup, OnClickHandler { Rect irc = r; irc.left += spaceBackSize; irc.right -= spaceForwardSize; - _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; - } + layoutButtons(irc); } _pos = rc; _needLayout = false; diff --git a/src/dlangui/widgets/styles.d b/src/dlangui/widgets/styles.d index 9c6ac143..663ad845 100644 --- a/src/dlangui/widgets/styles.d +++ b/src/dlangui/widgets/styles.d @@ -25,6 +25,7 @@ enum State : uint { Pressed = 1, Focused = 2, Disabled = 4, + Hover = 8, // mouse pointer is over control, buttons not pressed } enum Align : ubyte { @@ -77,7 +78,7 @@ class Style { protected FontFamily _fontFamily = FontFamily.Unspecified; protected ushort _fontSize = FONT_SIZE_UNSPECIFIED; protected ushort _fontWeight = FONT_WEIGHT_UNSPECIFIED; - protected uint _backgroundColor = COLOR_TRANSPARENT; + protected uint _backgroundColor = COLOR_UNSPECIFIED; protected uint _textColor = COLOR_UNSPECIFIED; protected string _fontFace; protected string _backgroundImageId; @@ -621,6 +622,7 @@ Theme createDefaultTheme() { button.createState(State.Disabled, State.Disabled).backgroundImageId("btn_default_small_normal_disable"); button.createState(State.Pressed, State.Pressed).backgroundImageId("btn_default_small_pressed"); button.createState(State.Focused, State.Focused).backgroundImageId("btn_default_small_selected"); + button.createState(State.Hover, State.Hover).backgroundImageId("btn_default_small_normal_hover"); res.setCustomDrawable(ATTR_SCROLLBAR_BUTTON_UP, "scrollbar_btn_up"); res.setCustomDrawable(ATTR_SCROLLBAR_BUTTON_DOWN, "scrollbar_btn_down"); res.setCustomDrawable(ATTR_SCROLLBAR_BUTTON_LEFT, "scrollbar_btn_left"); @@ -629,11 +631,12 @@ Theme createDefaultTheme() { res.setCustomDrawable(ATTR_SCROLLBAR_INDICATOR_HORIZONTAL, "scrollbar_indicator_horizontal"); Style scrollbar = res.createSubstyle("SCROLLBAR"); + scrollbar.backgroundColor(0xC0808080); Style scrollbarButton = button.createSubstyle("SCROLLBAR_BUTTON"); - scrollbar.backgroundColor(0xC0C0C0C0); Style scrollbarSlider = res.createSubstyle("SLIDER"); - Style scrollbarPage = res.createSubstyle("PAGE_SCROLL").backgroundColor(0xFFFFFFFF); // transparent - scrollbarPage.createState(State.Pressed, State.Pressed).backgroundColor(0xC0404040); + Style scrollbarPage = res.createSubstyle("PAGE_SCROLL").backgroundColor(0xFFFFFFFF); + scrollbarPage.createState(State.Pressed, State.Pressed).backgroundColor(0xC0404080); + scrollbarPage.createState(State.Hover, State.Hover).backgroundColor(0xF0404080); //res.dumpStats(); return res; diff --git a/src/dlangui/widgets/widget.d b/src/dlangui/widgets/widget.d index 7da5f48b..7fd2b489 100644 --- a/src/dlangui/widgets/widget.d +++ b/src/dlangui/widgets/widget.d @@ -67,6 +67,14 @@ class Widget { /// window (to be used for top level widgets only!) protected Window _window; + /// does widget need to track mouse Hover + protected bool _trackHover; + + /// mouse movement processing flag (when true, widget will change Hover state while mouse is moving) + @property bool trackHover() const { return _trackHover; } + /// set new trackHover flag value (when true, widget will change Hover state while mouse is moving) + @property Widget trackHover(bool v) { _trackHover = v; return this; } + //private static int _instanceCount = 0; /// create widget, with optional id this(string ID = null) { @@ -277,8 +285,11 @@ class Widget { @property Visibility visibility() { return _visibility; } /// sets widget visibility (Visible, Invisible, Gone) @property Widget visibility(Visibility visible) { - _visibility = visible; - requestLayout(); + if (_visibility != visible) { + if ((_visibility == Visibility.Gone) || (visible == Visibility.Gone)) + _visibility = visible; + requestLayout(); + } return this; } @@ -305,12 +316,25 @@ class Widget { } if (event.action == MouseAction.FocusOut || event.action == MouseAction.Cancel) { resetState(State.Pressed); + resetState(State.Hover); return true; } if (event.action == MouseAction.FocusIn) { setState(State.Pressed); return true; } + if (event.action == MouseAction.Move && trackHover) { + if (!(state & State.Hover)) { + Log.d("Hover ", id); + setState(State.Hover); + } + return true; + } + if (event.action == MouseAction.Leave && trackHover) { + Log.d("Leave ", id); + resetState(State.Hover); + return true; + } } return false; }