diff --git a/dlanguilib.visualdproj b/dlanguilib.visualdproj index 510b6bd6..8b86a837 100644 --- a/dlanguilib.visualdproj +++ b/dlanguilib.visualdproj @@ -66,7 +66,7 @@ 0 DebugFocus 0 - EmbedStandardResources Unicode USE_SDL USE_FREETYPE + EmbedStandardResources Unicode 0 0 1 diff --git a/src/dlangui/platforms/common/platform.d b/src/dlangui/platforms/common/platform.d index 089ea51f..9fbb5949 100644 --- a/src/dlangui/platforms/common/platform.d +++ b/src/dlangui/platforms/common/platform.d @@ -117,20 +117,39 @@ class TimerInfo { @property long nextTimestamp() { return _nextTimestamp; } /// widget to route timer event to @property Widget targetWidget() { return _targetWidget; } + /// return true if timer is not yet cancelled + @property bool valid() { return _targetWidget !is null; } protected ulong _id; protected long _interval; protected long _nextTimestamp; protected Widget _targetWidget; -} -class TimerQueue { - protected TimerInfo[] _queue; - void add(TimerInfo event) { - int len = cast(int)_queue.length; + override bool opEquals(Object obj) { + TimerInfo b = cast(TimerInfo)obj; + if (!b) + return false; + return b._nextTimestamp == _nextTimestamp; + } + override int opCmp(Object obj) { + TimerInfo b = cast(TimerInfo)obj; + if (!b) + return false; + if (valid && !b.valid) + return -1; + if (!valid && b.valid) + return 1; + if (!valid && !b.valid) + return 0; + if (_nextTimestamp < b._nextTimestamp) + return -1; + if (_nextTimestamp > b._nextTimestamp) + return 1; + return 0; } } + /** * Window abstraction layer. Widgets can be shown only inside window. * @@ -261,6 +280,7 @@ class Window { this() { _eventList = new EventList(); + _timerQueue = new TimerQueue(); _backgroundColor = 0xFFFFFF; } ~this() { @@ -272,6 +292,7 @@ class Window { _mainWidget = null; } destroy(_eventList); + destroy(_timerQueue); _eventList = null; } @@ -818,6 +839,124 @@ class Window { dlg.show(); } + protected TimerQueue _timerQueue; + + + /// schedule timer for interval in milliseconds - call window.onTimer when finished + protected void scheduleSystemTimer(long intervalMillis) { + Log.d("override scheduleSystemTimer to support timers"); + } + + /// system timer interval expired - notify queue + protected void onTimer() { + bool res = _timerQueue.notify(); + if (res) { + // check if update needed and redraw if so + update(false); + } + long nextInterval = _timerQueue.nextIntervalMillis(); + if (nextInterval > 0) { + scheduleSystemTimer(nextInterval); + } + } + + /// set timer for destination widget - destination.onTimer() will be called after interval expiration; returns timer id + ulong setTimer(Widget destination, long intervalMillis) { + if (!isChild(destination)) { + Log.e("setTimer() is called not for child widget of window"); + return 0; + } + ulong res = _timerQueue.add(destination, intervalMillis); + long nextInterval = _timerQueue.nextIntervalMillis(); + if (nextInterval > 0) { + scheduleSystemTimer(intervalMillis); + } + return res; + } + + /// cancel previously scheduled widget timer (for timerId pass value returned from setTimer) + void cancelTimer(ulong timerId) { + _timerQueue.cancelTimer(timerId); + } + + /// timers queue + private class TimerQueue { + protected TimerInfo[] _queue; + /// add new timer + ulong add(Widget destination, long intervalMillis) { + TimerInfo item = new TimerInfo(destination, intervalMillis); + _queue ~= item; + sort(_queue); + return item.id; + } + /// cancel timer + void cancelTimer(ulong timerId) { + for (size_t i = _queue.length - 1; i >= 0; i--) { + if (_queue[i].id == timerId) { + _queue[i].cancel(); + break; + } + } + } + /// returns interval if millis of next scheduled event or -1 if no events queued + long nextIntervalMillis() { + if (!_queue.length || !_queue[0].valid) + return -1; + long delta = _queue[0].nextTimestamp - currentTimeMillis; + if (delta < 1) + delta = 1; + return delta; + } + private void cleanup() { + sort(_queue); + size_t newsize = 0; + for (size_t i = _queue.length - 1; i >= 0; i--) { + if (!_queue[i].valid) { + newsize = i; + } + } + if (_queue.length > newsize) + _queue.length = newsize; + } + private TimerInfo[] expired() { + long ts = currentTimeMillis; + TimerInfo[] res; + for (int i = 0; i < _queue.length; i++) { + if (_queue[i].nextTimestamp <= ts) + res ~= _queue[i]; + } + return res; + } + /// returns true if at least one widget was notified + bool notify() { + bool res = false; + checkValidWidgets(); + TimerInfo[] list = expired(); + if (list) { + for (int i = 0; i < list.length; i++) { + Widget w = _queue[i].targetWidget; + if (w && !isChild(w)) + _queue[i].cancel(); + else { + _queue[i].notify(); + res = true; + } + } + } + cleanup(); + return res; + } + private void checkValidWidgets() { + for (int i = 0; i < _queue.length; i++) { + Widget w = _queue[i].targetWidget; + if (w && !isChild(w)) + _queue[i].cancel(); + } + cleanup(); + } + } + + } /** diff --git a/src/dlangui/platforms/windows/winapp.d b/src/dlangui/platforms/windows/winapp.d index 07924e44..0f1448d0 100644 --- a/src/dlangui/platforms/windows/winapp.d +++ b/src/dlangui/platforms/windows/winapp.d @@ -318,6 +318,27 @@ class Win32Window : Window { PostMessageW(_hwnd, CUSTOM_MESSAGE_ID, 0, event.uniqueId); } + private long _nextExpectedTimerTs; + private UINT_PTR _timerId; + + /// schedule timer for interval in milliseconds - call window.onTimer when finished + override protected void scheduleSystemTimer(long intervalMillis) { + if (intervalMillis < 10) + intervalMillis = 10; + long nextts = currentTimeMillis + intervalMillis; + if (_timerId && _nextExpectedTimerTs && _nextExpectedTimerTs < nextts + 10) + return; // don't reschedule timer, timer event will be received soon + if (_hwnd) { + _timerId = SetTimer(_hwnd, _timerId, cast(uint)intervalMillis, null); + _nextExpectedTimerTs = nextts; + } + } + + void handleTimer(UINT_PTR timerId) { + onTimer(); + } + + Win32ColorDrawBuf getDrawBuf() { //RECT rect; //GetClientRect(_hwnd, &rect); @@ -1062,6 +1083,12 @@ LRESULT WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) return 1; } break; + case WM_TIMER: + if (window !is null) { + window.handleTimer(wParam); + return 0; + } + break; case WM_GETMINMAXINFO: case WM_NCCREATE: case WM_NCCALCSIZE: diff --git a/src/dlangui/widgets/widget.d b/src/dlangui/widgets/widget.d index 920b7b2b..db9b5661 100644 --- a/src/dlangui/widgets/widget.d +++ b/src/dlangui/widgets/widget.d @@ -923,9 +923,21 @@ class Widget { return res; } + + /// set new timer to call onTimer() after specified interval (for recurred notifications, return true from onTimer) + ulong setTimer(long intervalMillis) { + return window.setTimer(this, intervalMillis); + } + + /// cancel timer - pass value returned from setTimer() as timerId parameter + void cancelTimer(ulong timerId) { + window.cancelTimer(timerId); + } + /// handle timer; return true to repeat timer event after next interval, false cancel timer bool onTimer(ulong id) { // override to do something useful + // return true to repeat after the same interval, false to stop timer return false; }