From 414563de0c432be37ec84172fde6d3155de9df66 Mon Sep 17 00:00:00 2001 From: Vadim Lopatin Date: Mon, 19 Jan 2015 15:55:16 +0300 Subject: [PATCH] support custom events and execution of tasks in GUI thread - win32 implementation --- src/dlangui/core/events.d | 51 ++++++++++++ src/dlangui/platforms/common/platform.d | 103 ++++++++++++++++++++++++ src/dlangui/platforms/windows/winapp.d | 14 ++++ src/dlangui/widgets/widget.d | 20 +++++ 4 files changed, 188 insertions(+) diff --git a/src/dlangui/core/events.d b/src/dlangui/core/events.d index 98718fe9..2d5f745d 100644 --- a/src/dlangui/core/events.d +++ b/src/dlangui/core/events.d @@ -1014,3 +1014,54 @@ string keyName(uint keyCode) { } } +/// base class for custom events +class CustomEvent { + protected int _id; + protected uint _uniqueId; + + protected static uint _uniqueIdGenerator; + + protected Widget _destinationWidget; + // event id + @property int id() { return _id; } + @property uint uniqueId() { return _uniqueId; } + @property Widget destinationWidget() { return _destinationWidget; } + + protected Object _objectParam; + @property Object objectParam() { + return _objectParam; + } + @property CustomEvent objectParam(Object value) { + _objectParam = value; + return this; + } + + protected int _intParam; + @property int intParam() { + return _intParam; + } + @property CustomEvent intParam(int value) { + _intParam = value; + return this; + } + + this(int ID) { + _id = ID; + _uniqueId = ++_uniqueIdGenerator; + } +} + +immutable int CUSTOM_RUNNABLE = 1; + +/// operation to execute (usually sent from background threads to run some code in UI thread) +class RunnableEvent : CustomEvent { + protected void delegate() _action; + this(int ID, Widget destinationWidget, void delegate() action) { + super(ID); + _destinationWidget = destinationWidget; + _action = action; + } + void run() { + _action(); + } +} diff --git a/src/dlangui/platforms/common/platform.d b/src/dlangui/platforms/common/platform.d index 56d4a55f..c80e65bc 100644 --- a/src/dlangui/platforms/common/platform.d +++ b/src/dlangui/platforms/common/platform.d @@ -29,6 +29,7 @@ import dlangui.dialogs.msgbox; private import dlangui.graphics.gldrawbuf; private import std.algorithm; +private import core.sync.mutex; // specify debug=DebugMouseEvents for logging mouse handling // specify debug=DebugRedraw for logging drawing and layouts handling @@ -44,6 +45,45 @@ enum WindowFlag : uint { Modal = 4, } +/// protected event list +/// references to posted messages can be stored here at least to keep live reference and avoid GC +/// as well, on some platforms it's easy to send id to message queue, but not pointer +class EventList { + protected Mutex _mutex; + protected Collection!CustomEvent _events; + this() { + _mutex = new Mutex(); + } + ~this() { + destroy(_mutex); + _mutex = null; + } + /// puts event into queue, returns event's unique id + long put(CustomEvent event) { + _mutex.lock(); + scope(exit) _mutex.unlock(); + _events.pushBack(event); + return event.uniqueId; + } + /// return next event + CustomEvent get() { + _mutex.lock(); + scope(exit) _mutex.unlock(); + return _events.popFront(); + } + /// return event by unique id + CustomEvent get(uint uniqueId) { + _mutex.lock(); + scope(exit) _mutex.unlock(); + for (int i = 0; i < _events.length; i++) { + if (_events[i].uniqueId == uniqueId) { + return _events.remove(i); + } + } + // not found + return null; + } +} /** * Window abstraction layer. Widgets can be shown only inside window. @@ -55,6 +95,8 @@ class Window { protected uint _keyboardModifiers; protected uint _backgroundColor; protected Widget _mainWidget; + protected EventList _eventList; + @property uint backgroundColor() const { return _backgroundColor; } @property void backgroundColor(uint color) { _backgroundColor = color; } @property int width() const { return _dx; } @@ -163,6 +205,7 @@ class Window { private long lastDrawTs; this() { + _eventList = new EventList(); _backgroundColor = 0xFFFFFF; } ~this() { @@ -173,6 +216,8 @@ class Window { destroy(_mainWidget); _mainWidget = null; } + destroy(_eventList); + _eventList = null; } private void animate(Widget root, long interval) { @@ -455,6 +500,64 @@ class Window { return false; } + /// post event to handle in UI thread (this method can be used from background thread) + void postEvent(CustomEvent event) { + // override to post event into window message queue + _eventList.put(event); + } + + /// post task to execute in UI thread (this method can be used from background thread) + void executeInUiThread(void delegate() runnable) { + RunnableEvent event = new RunnableEvent(CUSTOM_RUNNABLE, null, runnable); + postEvent(event); + } + + /// remove event from queue by unique id if not yet dispatched (this method can be used from background thread) + void cancelEvent(uint uniqueId) { + CustomEvent ev = _eventList.get(uniqueId); + if (ev) { + //destroy(ev); + } + } + + /// remove event from queue by unique id if not yet dispatched and dispatch it + void handlePostedEvent(uint uniqueId) { + CustomEvent ev = _eventList.get(uniqueId); + if (ev) { + dispatchCustomEvent(ev); + } + } + + /// handle all events from queue, if any (call from UI thread only) + void handlePostedEvents() { + for(;;) { + CustomEvent e = _eventList.get(); + if (!e) + break; + dispatchCustomEvent(e); + } + } + + /// dispatch custom event + bool dispatchCustomEvent(CustomEvent event) { + if (event.destinationWidget) { + if (!isChild(event.destinationWidget)) { + //Event is sent to widget which does not exist anymore + return false; + } + return event.destinationWidget.onEvent(event); + } else { + // no destination widget + RunnableEvent runnable = cast(RunnableEvent)event; + if (runnable) { + // handle runnable + runnable.run(); + return true; + } + } + return false; + } + /// dispatch mouse event to window content widgets bool dispatchMouseEvent(MouseEvent event) { // ignore events if there is no root diff --git a/src/dlangui/platforms/windows/winapp.d b/src/dlangui/platforms/windows/winapp.d index 158f5fd0..c29ae686 100644 --- a/src/dlangui/platforms/windows/winapp.d +++ b/src/dlangui/platforms/windows/winapp.d @@ -152,6 +152,8 @@ version (USE_OPENGL) { private __gshared bool DERELICT_GL3_RELOADED = false; } +const uint CUSTOM_MESSAGE_ID = WM_USER + 1; + class Win32Window : Window { Win32Platform _platform; @@ -309,6 +311,13 @@ class Win32Window : Window { DestroyWindow(_hwnd); _hwnd = null; } + + /// post event to handle in UI thread (this method can be used from background thread) + override void postEvent(CustomEvent event) { + super.postEvent(event); + PostMessageW(_hwnd, CUSTOM_MESSAGE_ID, 0, event.uniqueId); + } + Win32ColorDrawBuf getDrawBuf() { //RECT rect; //GetClientRect(_hwnd, &rect); @@ -984,6 +993,11 @@ LRESULT WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) } } return 0; + case CUSTOM_MESSAGE_ID: + if (window !is null) { + window.handlePostedEvent(cast(uint)lParam); + } + return 1; case WM_ERASEBKGND: // processed return 1; diff --git a/src/dlangui/widgets/widget.d b/src/dlangui/widgets/widget.d index 545e40df..59a88bf9 100644 --- a/src/dlangui/widgets/widget.d +++ b/src/dlangui/widgets/widget.d @@ -954,6 +954,26 @@ class Widget { return false; } + /// handle custom event + bool onEvent(CustomEvent event) { + RunnableEvent runnable = cast(RunnableEvent)event; + if (runnable) { + // handle runnable + runnable.run(); + return true; + } + // override to handle more events + return false; + } + + /// execute delegate later in UI thread if this widget will be still available (can be used to modify UI from background thread, or just to postpone execution of action) + void executeInUiThread(void delegate() runnable) { + if (!window) + return; + RunnableEvent event = new RunnableEvent(CUSTOM_RUNNABLE, this, runnable); + window.postEvent(event); + } + /// process mouse event; return true if event is processed by widget. bool onMouseEvent(MouseEvent event) { if (onMouseListener.assigned && onMouseListener(this, event))