mirror of https://github.com/buggins/dlangui.git
window layout and update after mouse events
This commit is contained in:
parent
5fd93700a0
commit
56b3eeb6a6
|
@ -3,13 +3,14 @@ module dlangui.core.events;
|
||||||
import std.conv;
|
import std.conv;
|
||||||
|
|
||||||
enum MouseAction : ubyte {
|
enum MouseAction : ubyte {
|
||||||
Cancel,
|
Cancel, // button down handling is cancelled
|
||||||
ButtonDown, // button is down
|
ButtonDown, // button is down
|
||||||
ButtonUp, // button is up
|
ButtonUp, // button is up
|
||||||
Move, // mouse pointer is moving
|
Move, // mouse pointer is moving
|
||||||
Wheel,
|
FocusIn, // pointer moved outside of widget while button was down
|
||||||
FocusIn,
|
FocusOut, // pointer is back inside widget while button is down after FocusIn
|
||||||
FocusOut
|
Wheel, // scroll wheel movement
|
||||||
|
Leave // pointer left widget which has before processed Move message, while button was not down
|
||||||
}
|
}
|
||||||
|
|
||||||
enum MouseFlag : ushort {
|
enum MouseFlag : ushort {
|
||||||
|
@ -77,6 +78,7 @@ class MouseEvent {
|
||||||
protected short _x;
|
protected short _x;
|
||||||
protected short _y;
|
protected short _y;
|
||||||
protected ushort _flags;
|
protected ushort _flags;
|
||||||
|
protected short _wheelDelta;
|
||||||
protected ButtonDetails _lbutton;
|
protected ButtonDetails _lbutton;
|
||||||
protected ButtonDetails _mbutton;
|
protected ButtonDetails _mbutton;
|
||||||
protected ButtonDetails _rbutton;
|
protected ButtonDetails _rbutton;
|
||||||
|
@ -85,15 +87,30 @@ class MouseEvent {
|
||||||
@property ref ButtonDetails mbutton() { return _mbutton; }
|
@property ref ButtonDetails mbutton() { return _mbutton; }
|
||||||
@property MouseButton button() { return _button; }
|
@property MouseButton button() { return _button; }
|
||||||
@property MouseAction action() { return _action; }
|
@property MouseAction action() { return _action; }
|
||||||
|
void changeAction(MouseAction a) { _action = a; }
|
||||||
@property ushort flags() { return _flags; }
|
@property ushort flags() { return _flags; }
|
||||||
|
@property short wheelDelta() { return _wheelDelta; }
|
||||||
@property short x() { return _x; }
|
@property short x() { return _x; }
|
||||||
@property short y() { return _y; }
|
@property short y() { return _y; }
|
||||||
this (MouseAction a, MouseButton b, ushort f, short x, short y) {
|
this (MouseEvent e) {
|
||||||
|
_eventTimestamp = e._eventTimestamp;
|
||||||
|
_action = e._action;
|
||||||
|
_button = e._button;
|
||||||
|
_flags = e._flags;
|
||||||
|
_x = e._x;
|
||||||
|
_y = e._y;
|
||||||
|
_lbutton = e._lbutton;
|
||||||
|
_rbutton = e._rbutton;
|
||||||
|
_mbutton = e._mbutton;
|
||||||
|
_wheelDelta = e._wheelDelta;
|
||||||
|
}
|
||||||
|
this (MouseAction a, MouseButton b, ushort f, short x, short y, short wheelDelta = 0) {
|
||||||
_eventTimestamp = std.datetime.Clock.currStdTime;
|
_eventTimestamp = std.datetime.Clock.currStdTime;
|
||||||
_action = a;
|
_action = a;
|
||||||
_button = b;
|
_button = b;
|
||||||
_flags = f;
|
_flags = f;
|
||||||
_x = x;
|
_x = x;
|
||||||
_y = y;
|
_y = y;
|
||||||
|
_wheelDelta = wheelDelta;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,12 +33,179 @@ class Window {
|
||||||
_mainWidget.layout(Rect(0, 0, _dx, _dy));
|
_mainWidget.layout(Rect(0, 0, _dx, _dy));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
long lastDrawTs;
|
||||||
|
|
||||||
|
private void animate(Widget root, long interval) {
|
||||||
|
if (root.visibility != Visibility.Visible)
|
||||||
|
return;
|
||||||
|
for (int i = 0; i < root.childCount; i++)
|
||||||
|
animate(root.child(i), interval);
|
||||||
|
if (root.animating)
|
||||||
|
root.animate(interval);
|
||||||
|
}
|
||||||
|
|
||||||
void onDraw(DrawBuf buf) {
|
void onDraw(DrawBuf buf) {
|
||||||
if (_mainWidget !is null) {
|
if (_mainWidget !is null) {
|
||||||
|
bool needDraw = false;
|
||||||
|
bool needLayout = false;
|
||||||
|
bool animationActive = false;
|
||||||
|
checkUpdateNeeded(needDraw, needLayout, animationActive);
|
||||||
|
if (needLayout || animationActive)
|
||||||
|
needDraw = true;
|
||||||
|
long ts = std.datetime.Clock.currStdTime;
|
||||||
|
if (animationActive && lastDrawTs != 0) {
|
||||||
|
animate(_mainWidget, ts - lastDrawTs);
|
||||||
|
// layout required flag could be changed during animate - check again
|
||||||
|
checkUpdateNeeded(needDraw, needLayout, animationActive);
|
||||||
|
}
|
||||||
|
if (needLayout) {
|
||||||
|
_mainWidget.measure(_dx, _dy);
|
||||||
|
_mainWidget.layout(Rect(0, 0, _dx, _dy));
|
||||||
|
}
|
||||||
_mainWidget.onDraw(buf);
|
_mainWidget.onDraw(buf);
|
||||||
|
lastDrawTs = ts;
|
||||||
|
if (animationActive)
|
||||||
|
scheduleAnimation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
abstract bool onMouseEvent(MouseEvent event);
|
|
||||||
|
/// after drawing, call to schedule redraw if animation is active
|
||||||
|
void scheduleAnimation() {
|
||||||
|
// override if necessary
|
||||||
|
}
|
||||||
|
|
||||||
|
protected bool dispatchMouseEvent(Widget root, MouseEvent event) {
|
||||||
|
// only route mouse events to visible widgets
|
||||||
|
if (root.visibility != Visibility.Visible)
|
||||||
|
return false;
|
||||||
|
if (!root.isPointInside(event.x, event.y))
|
||||||
|
return false;
|
||||||
|
// offer event to children first
|
||||||
|
for (int i = 0; i < root.childCount; i++) {
|
||||||
|
Widget child = root.child(i);
|
||||||
|
if (dispatchMouseEvent(child, event))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// if not processed by children, offer event to root
|
||||||
|
if (root.onMouseEvent(event)) {
|
||||||
|
Log.d("MouseEvent is processed");
|
||||||
|
if (event.action == MouseAction.ButtonDown && _mouseCaptureWidget is null) {
|
||||||
|
Log.d("Setting active widget");
|
||||||
|
_mouseCaptureWidget = root;
|
||||||
|
} else if (event.action == MouseAction.Move && _mouseTrackingWidget is null) {
|
||||||
|
Log.d("Setting tracking widget");
|
||||||
|
_mouseTrackingWidget = root;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// widget which tracks Move events
|
||||||
|
protected Widget _mouseTrackingWidget;
|
||||||
|
/// widget which tracks all events after processed ButtonDown
|
||||||
|
protected Widget _mouseCaptureWidget;
|
||||||
|
protected bool _mouseCaptureFocusedOut;
|
||||||
|
/// dispatch mouse event to window content widgets
|
||||||
|
bool dispatchMouseEvent(MouseEvent event) {
|
||||||
|
// ignore events if there is no root
|
||||||
|
if (_mainWidget is null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// check if _mouseCaptureWidget and _mouseTrackingWidget still exist in child of root widget
|
||||||
|
if (_mouseCaptureWidget !is null && !_mainWidget.isChild(_mouseCaptureWidget))
|
||||||
|
_mouseCaptureWidget = null;
|
||||||
|
if (_mouseTrackingWidget !is null && !_mainWidget.isChild(_mouseTrackingWidget))
|
||||||
|
_mouseTrackingWidget = null;
|
||||||
|
|
||||||
|
bool res = false;
|
||||||
|
if (_mouseCaptureWidget !is null) {
|
||||||
|
// try to forward message directly to active widget
|
||||||
|
if (event.action == MouseAction.Move) {
|
||||||
|
if (!_mouseCaptureWidget.isPointInside(event.x, event.y)) {
|
||||||
|
// point is no more inside of captured widget
|
||||||
|
if (!_mouseCaptureFocusedOut) {
|
||||||
|
// sending FocusOut message
|
||||||
|
event.changeAction(MouseAction.FocusOut);
|
||||||
|
_mouseCaptureFocusedOut = true;
|
||||||
|
return _mouseCaptureWidget.onMouseEvent(event);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
// point is inside widget
|
||||||
|
if (_mouseCaptureFocusedOut) {
|
||||||
|
event.changeAction(MouseAction.FocusIn); // back in after focus out
|
||||||
|
_mouseCaptureFocusedOut = false;
|
||||||
|
}
|
||||||
|
return _mouseCaptureWidget.onMouseEvent(event);
|
||||||
|
}
|
||||||
|
} else if (event.action == MouseAction.Leave) {
|
||||||
|
if (!_mouseCaptureFocusedOut) {
|
||||||
|
// sending FocusOut message
|
||||||
|
event.changeAction(MouseAction.FocusOut);
|
||||||
|
_mouseCaptureFocusedOut = true;
|
||||||
|
return _mouseCaptureWidget.onMouseEvent(event);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// other messages
|
||||||
|
res = _mouseCaptureWidget.onMouseEvent(event);
|
||||||
|
if ((event.flags & (MouseFlag.LButton | MouseFlag.MButton | MouseFlag.RButton)) == 0) {
|
||||||
|
// usable capturing - no more buttons pressed
|
||||||
|
Log.d("unsetting active widget");
|
||||||
|
_mouseCaptureWidget = null;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
// stop tracking
|
||||||
|
_mouseTrackingWidget = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!res) {
|
||||||
|
res = dispatchMouseEvent(_mainWidget, event);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
/// 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;
|
||||||
|
animationActive = root.animating || animationActive;
|
||||||
|
for (int i = 0; i < root.childCount; i++)
|
||||||
|
checkUpdateNeeded(root.child(i), needDraw, needLayout, animationActive);
|
||||||
|
}
|
||||||
|
/// checks content widgets for necessary redraw and/or layout
|
||||||
|
bool checkUpdateNeeded(ref bool needDraw, ref bool needLayout, ref bool animationActive) {
|
||||||
|
needDraw = needLayout = animationActive = false;
|
||||||
|
if (_mainWidget is null)
|
||||||
|
return false;
|
||||||
|
checkUpdateNeeded(_mainWidget, needDraw, needLayout, animationActive);
|
||||||
|
return needDraw || needLayout || animationActive;
|
||||||
|
}
|
||||||
|
/// requests update for window (unless force is true, update will be performed only if layout, redraw or animation is required).
|
||||||
|
void update(bool force = false) {
|
||||||
|
if (_mainWidget is null)
|
||||||
|
return;
|
||||||
|
bool needDraw = false;
|
||||||
|
bool needLayout = false;
|
||||||
|
bool animationActive = false;
|
||||||
|
if (checkUpdateNeeded(needDraw, needLayout, animationActive) || force) {
|
||||||
|
Log.d("Requesting update");
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
Log.d("checkUpdateNeeded returned needDraw=", needDraw, " needLayout=", needLayout, " animationActive=", animationActive);
|
||||||
|
}
|
||||||
|
/// request window redraw
|
||||||
|
abstract void invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
class Platform {
|
class Platform {
|
||||||
|
|
|
@ -318,67 +318,15 @@ class Win32Window : Window {
|
||||||
protected ButtonDetails _mbutton;
|
protected ButtonDetails _mbutton;
|
||||||
protected ButtonDetails _rbutton;
|
protected ButtonDetails _rbutton;
|
||||||
|
|
||||||
override bool onMouseEvent(MouseEvent event) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected bool dispatchMouseEvent(Widget root, MouseEvent event) {
|
|
||||||
// only route mouse events to visible widgets
|
|
||||||
if (root.visibility != Visibility.Visible)
|
|
||||||
return false;
|
|
||||||
// offer event to children first
|
|
||||||
for (int i = 0; i < root.childCount; i++) {
|
|
||||||
Widget child = root.child(i);
|
|
||||||
if (dispatchMouseEvent(child, event))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// if not processed by children, offer event to root
|
|
||||||
if (root.onMouseEvent(event)) {
|
|
||||||
Log.d("MouseEvent is processed");
|
|
||||||
if (event.action == MouseAction.ButtonDown && _mouseCaptureWidget is null) {
|
|
||||||
Log.d("Setting active widget");
|
|
||||||
_mouseCaptureWidget = root;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Widget _mouseCaptureWidget;
|
bool _mouseTracking;
|
||||||
bool dispatchMouseEvent(MouseEvent event) {
|
bool onMouse(uint message, uint flags, short x, short y) {
|
||||||
// ignore events if there is no root
|
|
||||||
if (_mainWidget is null)
|
|
||||||
return false;
|
|
||||||
// check if _mouseCaptureWidget still exists in child of root widget
|
|
||||||
if (_mouseCaptureWidget !is null && !_mainWidget.isChild(_mouseCaptureWidget))
|
|
||||||
_mouseCaptureWidget = null;
|
|
||||||
bool res = false;
|
|
||||||
if (_mouseCaptureWidget !is null) {
|
|
||||||
// try to forward message directly to active widget
|
|
||||||
res = _mouseCaptureWidget.onMouseEvent(event);
|
|
||||||
}
|
|
||||||
if (_mouseCaptureWidget !is null && (event.flags & (MouseFlag.LButton | MouseFlag.MButton | MouseFlag.RButton)) == 0) {
|
|
||||||
// usable capturing - no more buttons pressed
|
|
||||||
Log.d("unsetting active widget");
|
|
||||||
_mouseCaptureWidget = null;
|
|
||||||
}
|
|
||||||
if (res)
|
|
||||||
return res;
|
|
||||||
if (!res) {
|
|
||||||
res = dispatchMouseEvent(_mainWidget, event);
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
void requestUpdate() {
|
|
||||||
InvalidateRect(_hwnd, null, FALSE);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool onMouse(uint message, ushort flags, short x, short y) {
|
|
||||||
Log.d("Win32 Mouse Message ", message, " flags=", flags, " x=", x, " y=", y);
|
Log.d("Win32 Mouse Message ", message, " flags=", flags, " x=", x, " y=", y);
|
||||||
MouseButton button = MouseButton.None;
|
MouseButton button = MouseButton.None;
|
||||||
MouseAction action = MouseAction.ButtonDown;
|
MouseAction action = MouseAction.ButtonDown;
|
||||||
ButtonDetails * pbuttonDetails = null;
|
ButtonDetails * pbuttonDetails = null;
|
||||||
|
short wheelDelta = 0;
|
||||||
switch (message) {
|
switch (message) {
|
||||||
case WM_MOUSEMOVE:
|
case WM_MOUSEMOVE:
|
||||||
action = MouseAction.Move;
|
action = MouseAction.Move;
|
||||||
|
@ -413,24 +361,59 @@ class Win32Window : Window {
|
||||||
button = MouseButton.Middle;
|
button = MouseButton.Middle;
|
||||||
pbuttonDetails = &_mbutton;
|
pbuttonDetails = &_mbutton;
|
||||||
break;
|
break;
|
||||||
|
case WM_MOUSELEAVE:
|
||||||
|
action = MouseAction.Leave;
|
||||||
|
if (_mouseTracking) {
|
||||||
|
_mouseTracking = false;
|
||||||
|
ReleaseCapture();
|
||||||
|
}
|
||||||
|
case WM_MOUSEWHEEL:
|
||||||
|
{
|
||||||
|
action = MouseAction.Wheel;
|
||||||
|
wheelDelta = (cast(short)(flags >> 16)) / 120;
|
||||||
|
POINT pt;
|
||||||
|
pt.x = x;
|
||||||
|
pt.y = y;
|
||||||
|
ScreenToClient(_hwnd, &pt);
|
||||||
|
x = cast(short)pt.x;
|
||||||
|
y = cast(short)pt.y;
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// unsupported event
|
// unsupported event
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (action == MouseAction.ButtonDown) {
|
if (action == MouseAction.ButtonDown) {
|
||||||
pbuttonDetails.down(x, y, flags);
|
pbuttonDetails.down(x, y, cast(ushort)flags);
|
||||||
} else if (action == MouseAction.ButtonDown) {
|
} else if (action == MouseAction.ButtonDown) {
|
||||||
pbuttonDetails.up(x, y, flags);
|
pbuttonDetails.up(x, y, cast(ushort)flags);
|
||||||
}
|
}
|
||||||
MouseEvent event = new MouseEvent(action, button, flags, x, y);
|
if (message != WM_MOUSELEAVE && !_mouseTracking) {
|
||||||
|
_mouseTracking = true;
|
||||||
|
SetCapture(_hwnd);
|
||||||
|
}
|
||||||
|
MouseEvent event = new MouseEvent(action, button, cast(ushort)flags, x, y, wheelDelta);
|
||||||
event.lbutton = _lbutton;
|
event.lbutton = _lbutton;
|
||||||
event.rbutton = _rbutton;
|
event.rbutton = _rbutton;
|
||||||
event.mbutton = _mbutton;
|
event.mbutton = _mbutton;
|
||||||
bool res = dispatchMouseEvent(event);
|
bool res = dispatchMouseEvent(event);
|
||||||
if (res)
|
if (res) {
|
||||||
requestUpdate();
|
Log.d("Calling update() after mouse event");
|
||||||
|
update();
|
||||||
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// request window redraw
|
||||||
|
override void invalidate() {
|
||||||
|
InvalidateRect(_hwnd, null, FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// after drawing, call to schedule redraw if animation is active
|
||||||
|
override void scheduleAnimation() {
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Win32Platform : Platform {
|
class Win32Platform : Platform {
|
||||||
|
@ -687,6 +670,7 @@ LRESULT WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
||||||
window.onPaint();
|
window.onPaint();
|
||||||
}
|
}
|
||||||
return 0; // processed
|
return 0; // processed
|
||||||
|
case WM_MOUSELEAVE:
|
||||||
case WM_MOUSEMOVE:
|
case WM_MOUSEMOVE:
|
||||||
case WM_LBUTTONDOWN:
|
case WM_LBUTTONDOWN:
|
||||||
case WM_MBUTTONDOWN:
|
case WM_MBUTTONDOWN:
|
||||||
|
@ -694,6 +678,7 @@ LRESULT WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
||||||
case WM_LBUTTONUP:
|
case WM_LBUTTONUP:
|
||||||
case WM_MBUTTONUP:
|
case WM_MBUTTONUP:
|
||||||
case WM_RBUTTONUP:
|
case WM_RBUTTONUP:
|
||||||
|
case WM_MOUSEWHEEL:
|
||||||
if (window !is null)
|
if (window !is null)
|
||||||
window.onMouse(message, cast(ushort)wParam, cast(short)(lParam & 0xFFFF), cast(short)((lParam >> 16) & 0xFFFF));
|
window.onMouse(message, cast(ushort)wParam, cast(short)(lParam & 0xFFFF), cast(short)((lParam >> 16) & 0xFFFF));
|
||||||
return 0; // processed
|
return 0; // processed
|
||||||
|
|
|
@ -145,6 +145,14 @@ class Button : Widget {
|
||||||
Log.d("Button state: ", state);
|
Log.d("Button state: ", state);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (event.action == MouseAction.FocusOut) {
|
||||||
|
resetState(State.Pressed);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (event.action == MouseAction.FocusIn) {
|
||||||
|
setState(State.Pressed);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -119,6 +119,7 @@ class Widget {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//======================================================
|
//======================================================
|
||||||
// Style related properties
|
// Style related properties
|
||||||
|
|
||||||
|
@ -201,6 +202,11 @@ class Widget {
|
||||||
@property bool needLayout() { return _needLayout; }
|
@property bool needLayout() { return _needLayout; }
|
||||||
/// returns true if redraw is required for widget and its children
|
/// returns true if redraw is required for widget and its children
|
||||||
@property bool needDraw() { return _needDraw; }
|
@property bool needDraw() { return _needDraw; }
|
||||||
|
/// returns true is widget is being animated - need to call animate() and redraw
|
||||||
|
@property bool animating() { return false; }
|
||||||
|
/// animates window; interval is time left from previous draw, in hnsecs (1/10000 of second)
|
||||||
|
void animate(long interval) {
|
||||||
|
}
|
||||||
/// returns measured width (calculated during measure() call)
|
/// returns measured width (calculated during measure() call)
|
||||||
@property measuredWidth() { return _measuredWidth; }
|
@property measuredWidth() { return _measuredWidth; }
|
||||||
/// returns measured height (calculated during measure() call)
|
/// returns measured height (calculated during measure() call)
|
||||||
|
|
Loading…
Reference in New Issue