diff --git a/examples/example1/main.d b/examples/example1/main.d index a9ac975b..80620b6f 100644 --- a/examples/example1/main.d +++ b/examples/example1/main.d @@ -50,7 +50,11 @@ extern (C) int UIAppMain(string[] args) { static if (true) { VerticalLayout contentLayout = new VerticalLayout(); MenuItem mainMenuItems = new MenuItem(); - mainMenuItems.add(new Action(1, "File"d)); + MenuItem fileItem = new MenuItem(new Action(1, "File"d)); + fileItem.add(new Action(10, "Open..."d)); + fileItem.add(new Action(11, "Save..."d)); + fileItem.add(new Action(12, "Exit"d)); + mainMenuItems.add(fileItem); mainMenuItems.add(new Action(2, "Edit"d)); mainMenuItems.add(new Action(3, "Window"d)); mainMenuItems.add(new Action(4, "Help"d)); @@ -66,7 +70,7 @@ extern (C) int UIAppMain(string[] args) { layout.addChild((new TextWidget()).textColor(0x40FF4000).text("Text widget")); layout.addChild((new Button("BTN1")).textResource("EXIT")); //.textColor(0x40FF4000) - + static if (false) { LinearLayout hlayout = new HorizontalLayout(); @@ -107,18 +111,21 @@ extern (C) int UIAppMain(string[] args) { layout.childById("BTN2").onClickListener(delegate (Widget w) { Log.d("onClick ", w.id); return true; }); layout.childById("BTN3").onClickListener(delegate (Widget w) { Log.d("onClick ", w.id); return true; }); + } layout.layoutHeight(FILL_PARENT).layoutWidth(FILL_PARENT); tabs.addTab(layout, "Tab 1"d); - ListWidget list = new ListWidget("tab2", Orientation.Vertical); - WidgetListAdapter listAdapter = new WidgetListAdapter(); - for (int i = 0; i < 3000; i++) - listAdapter.widgets.add((new TextWidget()).text("List item "d ~ to!dstring(i))); - list.ownAdapter = listAdapter; - list.layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT); - tabs.addTab(list, "Lists"d); + static if (false) { + ListWidget list = new ListWidget("tab2", Orientation.Vertical); + WidgetListAdapter listAdapter = new WidgetListAdapter(); + for (int i = 0; i < 3000; i++) + listAdapter.widgets.add((new TextWidget()).text("List item "d ~ to!dstring(i))); + list.ownAdapter = listAdapter; + list.layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT); + tabs.addTab(list, "Lists"d); + } tabs.addTab((new TextWidget()).id("tab3").textColor(0x00802000).text("Tab 3 contents"), "Tab 3"d); tabs.addTab((new TextWidget()).id("tab4").textColor(0x00802000).text("Tab 4 contents some long string"), "Tab 4"d); diff --git a/src/dlangui/core/events.d b/src/dlangui/core/events.d index 5e74adeb..68388783 100644 --- a/src/dlangui/core/events.d +++ b/src/dlangui/core/events.d @@ -1,6 +1,7 @@ module dlangui.core.events; import dlangui.core.i18n; +private import dlangui.widgets.widget; import std.conv; @@ -129,6 +130,7 @@ class MouseEvent { protected short _y; protected ushort _flags; protected short _wheelDelta; + protected Widget _trackingWidget; protected ButtonDetails _lbutton; protected ButtonDetails _mbutton; protected ButtonDetails _rbutton; @@ -142,6 +144,12 @@ class MouseEvent { @property short wheelDelta() { return _wheelDelta; } @property short x() { return _x; } @property short y() { return _y; } + /// get event tracking widget to override + @property Widget trackingWidget() { return _trackingWidget; } + /// override mouse tracking widget + void track(Widget w) { + _trackingWidget = w; + } this (MouseEvent e) { _eventTimestamp = e._eventTimestamp; _action = e._action; diff --git a/src/dlangui/core/types.d b/src/dlangui/core/types.d index 5dde1419..2fa909de 100644 --- a/src/dlangui/core/types.d +++ b/src/dlangui/core/types.d @@ -46,6 +46,24 @@ struct Rect { @property bool empty() { return right <= left || bottom <= top; } + void moveBy(int deltax, int deltay) { + left += deltax; + right += deltax; + top += deltay; + bottom += deltay; + } + /// moves this rect to fit rc bounds, retaining the same size + void moveToFit(ref Rect rc) { + if (right > rc.right) + moveBy(rc.right - right, 0); + if (bottom > rc.bottom) + moveBy(0, rc.bottom - bottom); + if (left < rc.left) + moveBy(rc.left - left, 0); + if (top < rc.top) + moveBy(0, rc.top - top); + + } /// updates this rect to intersection with rc, returns true if result is non empty bool intersect(Rect rc) { if (left < rc.left) diff --git a/src/dlangui/platforms/common/platform.d b/src/dlangui/platforms/common/platform.d index b38e5a74..55bfc758 100644 --- a/src/dlangui/platforms/common/platform.d +++ b/src/dlangui/platforms/common/platform.d @@ -61,9 +61,10 @@ class Window { protected PopupWidget[] _popups; /// show new popup - PopupWidget showPopup(Widget content) { + PopupWidget showPopup(Widget content, Widget anchor = null, uint alignment = PopupAlign.Center) { PopupWidget res = new PopupWidget(content, this); - res.anchor.widget = _mainWidget; + res.anchor.widget = anchor !is null ? anchor : _mainWidget; + res.anchor.alignment = alignment; _popups ~= res; if (_mainWidget !is null) _mainWidget.requestLayout(); @@ -169,6 +170,12 @@ class Window { // override if necessary } + + protected void setCaptureWidget(Widget w, MouseEvent event) { + _mouseCaptureWidget = w; + _mouseCaptureButtons = event.flags & (MouseFlag.LButton|MouseFlag.RButton|MouseFlag.MButton); + } + protected bool dispatchMouseEvent(Widget root, MouseEvent event) { // only route mouse events to visible widgets if (root.visibility != Visibility.Visible) @@ -182,12 +189,11 @@ class Window { return true; } // if not processed by children, offer event to root - if (root.onMouseEvent(event)) { + if (sendAndCheckOverride(root, event)) { Log.d("MouseEvent is processed"); if (event.action == MouseAction.ButtonDown && _mouseCaptureWidget is null) { Log.d("Setting active widget"); - _mouseCaptureWidget = root; - _mouseCaptureButtons = event.flags & (MouseFlag.LButton|MouseFlag.RButton|MouseFlag.MButton); + setCaptureWidget(root, event); } else if (event.action == MouseAction.Move) { addTracking(root); } @@ -256,6 +262,14 @@ class Window { return res; } + protected bool sendAndCheckOverride(Widget widget, MouseEvent event) { + bool res = widget.onMouseEvent(event); + if (event.trackingWidget !is null && _mouseCaptureWidget !is event.trackingWidget) { + setCaptureWidget(event.trackingWidget, event); + } + return res; + } + /// dispatch mouse event to window content widgets bool dispatchMouseEvent(MouseEvent event) { // ignore events if there is no root @@ -282,11 +296,11 @@ class Window { event.changeAction(MouseAction.FocusOut); _mouseCaptureFocusedOut = true; _mouseCaptureButtons = currentButtons; - _mouseCaptureFocusedOutTrackMovements = _mouseCaptureWidget.onMouseEvent(event); + _mouseCaptureFocusedOutTrackMovements = sendAndCheckOverride(_mouseCaptureWidget, event); return true; } else if (_mouseCaptureFocusedOutTrackMovements) { // pointer is outside, but we still need to track pointer - return _mouseCaptureWidget.onMouseEvent(event); + return sendAndCheckOverride(_mouseCaptureWidget, event); } // don't forward message return true; @@ -298,7 +312,7 @@ class Window { return dispatchCancel(event); event.changeAction(MouseAction.FocusIn); // back in after focus out } - return _mouseCaptureWidget.onMouseEvent(event); + return sendAndCheckOverride(_mouseCaptureWidget, event); } } else if (event.action == MouseAction.Leave) { if (!_mouseCaptureFocusedOut) { @@ -306,12 +320,12 @@ class Window { event.changeAction(MouseAction.FocusOut); _mouseCaptureFocusedOut = true; _mouseCaptureButtons = event.flags & (MouseFlag.LButton|MouseFlag.RButton|MouseFlag.MButton); - return _mouseCaptureWidget.onMouseEvent(event); + return sendAndCheckOverride(_mouseCaptureWidget, event); } return true; } // other messages - res = _mouseCaptureWidget.onMouseEvent(event); + res = sendAndCheckOverride(_mouseCaptureWidget, event); if (!currentButtons) { // usable capturing - no more buttons pressed Log.d("unsetting active widget"); @@ -324,6 +338,10 @@ class Window { processed = checkRemoveTracking(event); } if (!res) { + foreach(p; _popups) { + if (dispatchMouseEvent(p, event)) + return true; + } res = dispatchMouseEvent(_mainWidget, event); } return res || processed; diff --git a/src/dlangui/widgets/lists.d b/src/dlangui/widgets/lists.d index ce4fc0e9..02d9763e 100644 --- a/src/dlangui/widgets/lists.d +++ b/src/dlangui/widgets/lists.d @@ -211,6 +211,7 @@ class ListWidget : WidgetGroup, OnScrollHandler { sz.x += w.measuredWidth; } } + _needScrollbar = false; if (_orientation == Orientation.Vertical) { if (pheight != SIZE_UNSPECIFIED && sz.y > pheight) { // need scrollbar @@ -329,6 +330,7 @@ class ListWidget : WidgetGroup, OnScrollHandler { } _pos = rc; + Rect parentrc = rc; applyMargins(rc); applyPadding(rc); @@ -337,7 +339,7 @@ class ListWidget : WidgetGroup, OnScrollHandler { // measure again if client size has been changed if (_lastMeasureWidth != rc.width || _lastMeasureHeight != rc.height) - measure(rc.width, rc.height); + measure(parentrc.width, parentrc.height); // layout scrollbar if (_needScrollbar) { diff --git a/src/dlangui/widgets/menu.d b/src/dlangui/widgets/menu.d index 576ce49e..898616cf 100644 --- a/src/dlangui/widgets/menu.d +++ b/src/dlangui/widgets/menu.d @@ -4,6 +4,7 @@ import dlangui.core.events; import dlangui.widgets.controls; import dlangui.widgets.layouts; import dlangui.widgets.lists; +import dlangui.widgets.popup; class MenuItem { protected bool _checkable; @@ -55,9 +56,18 @@ class MenuItem { } } +interface MenuItemWidgetHandler { + bool onItemMouseDown(MenuItemWidget itemWidget, MouseEvent ev); + bool onItemMouseUp(MenuItemWidget itemWidget, MouseEvent ev); +} + class MenuItemWidget : HorizontalLayout { protected MenuItem _item; protected TextWidget _label; + protected MenuItemWidgetHandler _handler; + @property MenuItemWidgetHandler handler() { return _handler; } + @property MenuItemWidget handler(MenuItemWidgetHandler h) { _handler = h; return this; } + @property MenuItem item() { return _item; } this(MenuItem item) { id="menuitem"; _item = item; @@ -67,21 +77,73 @@ class MenuItemWidget : HorizontalLayout { addChild(_label); trackHover = true; } + + /// process mouse event; return true if event is processed by widget. + override bool onMouseEvent(MouseEvent event) { + Log.d("onMouseEvent ", id, " ", event.action, " (", event.x, ",", event.y, ")"); + // support onClick + if (_handler !is null) { + if (event.action == MouseAction.ButtonDown && event.button == MouseButton.Left) { + setState(State.Pressed); + _handler.onItemMouseDown(this, event); + return true; + } + if (event.action == MouseAction.ButtonUp && event.button == MouseButton.Left) { + resetState(State.Pressed); + _handler.onItemMouseDown(this, event); + return true; + } + /* + if (event.action == MouseAction.ButtonUp && event.button == MouseButton.Left) { + resetState(State.Pressed); + _onClickListener(this); + return true; + } + if (event.action == MouseAction.FocusOut || event.action == MouseAction.Cancel) { + resetState(State.Pressed); + resetState(State.Hovered); + return true; + } + if (event.action == MouseAction.FocusIn) { + setState(State.Pressed); + return true; + } + */ + } + return super.onMouseEvent(event); + } + } -class MainMenu : HorizontalLayout { +class MainMenu : HorizontalLayout, MenuItemWidgetHandler { protected MenuItem _item; - protected bool onItemClick(Widget w) { - Log.d("onItemClick ", w.id); + + override protected bool onItemMouseDown(MenuItemWidget itemWidget, MouseEvent ev) { + PopupMenu popupMenu = new PopupMenu(itemWidget.item); + PopupWidget popup = window.showPopup(popupMenu, itemWidget, PopupAlign.Below); + ev.track(popupMenu); return true; } + override protected bool onItemMouseUp(MenuItemWidget itemWidget, MouseEvent ev) { + return true; + } + + /* + protected bool onItemClick(Widget w) { + MenuItemWidget itemWidget = cast(MenuItemWidget)w; + Log.d("onItemClick ", w.id); + window.showPopup(new PopupMenu(itemWidget.item), itemWidget, PopupAlign.Below); + return true; + } + */ this(MenuItem item) { id = "MAIN_MENU"; styleId = "MAIN_MENU"; _item = item; for (int i = 0; i < item.subitemCount; i++) { MenuItemWidget subitem = new MenuItemWidget(item.subitem(i)); - subitem.onClickListener = &onItemClick; + //subitem.onClickListener = &onItemClick; + subitem.handler = this; addChild(subitem); } addChild((new Widget()).layoutWidth(FILL_PARENT)); diff --git a/src/dlangui/widgets/popup.d b/src/dlangui/widgets/popup.d index 009f0b57..b68e487a 100644 --- a/src/dlangui/widgets/popup.d +++ b/src/dlangui/widgets/popup.d @@ -4,9 +4,17 @@ import dlangui.widgets.widget; import dlangui.widgets.layouts; import dlangui.platforms.common.platform; +/// popup alignment option flags +enum PopupAlign : uint { + /// center popup around anchor widget center + Center = 1, + /// place popup below anchor widget close to lower bound + Below = 2, +} + struct PopupAnchor { Widget widget; - Align alignment; + uint alignment = PopupAlign.Center; } /// popup widget container @@ -36,24 +44,33 @@ class PopupWidget : LinearLayout { w = rc.width; if (h > rc.height) h = rc.height; - int extraw = rc.width - w; - int extrah = rc.height - h; + + Rect anchorrc; + if (anchor.widget !is null) + anchorrc = anchor.widget.pos; + else + anchorrc = rc; Rect r; - if (anchor.widget !is null) - r = anchor.widget.pos; - else - r = rc; - r.left += extraw / 2; - r.top += extrah / 2; - r.right -= extraw / 2; - r.bottom -= extrah / 2; + Point anchorPt; + + if (anchor.alignment & PopupAlign.Center) { + // center around center of anchor widget + r.left = anchorrc.middlex - w / 2; + r.top = anchorrc.middley - h / 2; + } else if (anchor.alignment & PopupAlign.Below) { + r.left = anchorrc.left; + r.top = anchorrc.bottom; + } + r.right = r.left + w; + r.bottom = r.top + h; + r.moveToFit(rc); super.layout(r); } this(Widget content, Window window) { _window = window; - styleId = "POPUP_MENU"; + //styleId = "POPUP_MENU"; addChild(content); } } diff --git a/src/dlangui/widgets/widget.d b/src/dlangui/widgets/widget.d index 968df485..7a9c3d03 100644 --- a/src/dlangui/widgets/widget.d +++ b/src/dlangui/widgets/widget.d @@ -312,7 +312,7 @@ class Widget { /// process mouse event; return true if event is processed by widget. bool onMouseEvent(MouseEvent event) { - Log.d("onMouseEvent ", id, " ", event.action, " (", event.x, ",", event.y, ")"); + //Log.d("onMouseEvent ", id, " ", event.action, " (", event.x, ",", event.y, ")"); // support onClick if (_onClickListener !is null) { if (event.action == MouseAction.ButtonDown && event.button == MouseButton.Left) {