support custom events and execution of tasks in GUI thread - win32 implementation

This commit is contained in:
Vadim Lopatin 2015-01-19 15:55:16 +03:00
parent ec0f7ea8f4
commit 414563de0c
4 changed files with 188 additions and 0 deletions

View File

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

View File

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

View File

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

View File

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