window layout and update after mouse events

This commit is contained in:
Vadim Lopatin 2014-03-17 15:28:13 +04:00
parent 5fd93700a0
commit 56b3eeb6a6
5 changed files with 250 additions and 67 deletions

View File

@ -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;
} }
} }

View File

@ -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 {

View File

@ -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

View File

@ -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;
} }

View File

@ -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)