1 /** 2 * Periodic and one-shot timers. 3 * 4 * `Timer` wraps the Win32 `SetTimer`/`KillTimer` pair. A timer is owned by a 5 * `Widget` (whose window receives the `WM_TIMER` messages) and fires its 6 * `onTick` event on each tick. The master window procedure routes `WM_TIMER` 7 * here by timer id. 8 * 9 * Timer ids are small integers from a process-wide counter, never object 10 * pointers — D's garbage collector may relocate an object, which would 11 * invalidate a pointer used as an id and misroute ticks. 12 */ 13 module deft.controls.timer; 14 15 version (Windows): 16 17 import core.sys.windows.windows; 18 19 import deft.events; 20 import deft.widget : Widget; 21 22 private __gshared Timer[uint] g_timers; 23 private __gshared uint g_nextTimerId = 1; 24 25 /** 26 * Dispatch a `WM_TIMER` to its `Timer`'s `onTick`. 27 * 28 * Returns `true` if a live timer with the given id was found. A one-shot timer 29 * stops itself after firing. 30 */ 31 bool dispatchTimer(uint id) 32 { 33 if (auto timer = id in g_timers) 34 { 35 auto t = *timer; 36 t.onTick.fire(); 37 if (t.oneShot_) 38 t.stop(); 39 return true; 40 } 41 return false; 42 } 43 44 /** 45 * Stop and forget every timer owned by `owner`. 46 * 47 * Called when the owner's window is destroyed: Win32 kills the native timers 48 * along with the `HWND`, but Deft's registry entries and `running_` flags would 49 * otherwise survive — leaking the timer objects and making `isRunning()` lie. 50 */ 51 void stopTimersFor(Widget owner) 52 { 53 uint[] dead; 54 foreach (id, t; g_timers) 55 if (t.owner_ is owner) 56 dead ~= id; 57 foreach (id; dead) 58 { 59 g_timers[id].running_ = false; 60 g_timers.remove(id); 61 } 62 } 63 64 /// A repeating or one-shot timer bound to an owner widget's window. 65 class Timer 66 { 67 private Widget owner_; 68 private uint id_; 69 private bool running_; 70 private bool oneShot_; 71 72 /// Fired on every tick. 73 Event!() onTick; 74 75 /** 76 * Create a timer owned by `owner`. 77 * 78 * The owner's window receives the underlying `WM_TIMER` messages, so the 79 * owner must have a live handle while the timer runs. 80 */ 81 this(Widget owner) 82 { 83 owner_ = owner; 84 id_ = g_nextTimerId++; 85 } 86 87 /// Whether the timer is currently running. 88 bool isRunning() const @safe pure nothrow @nogc 89 { 90 return running_; 91 } 92 93 /** 94 * Start (or restart) the timer. 95 * 96 * Params: 97 * intervalMs = tick interval in milliseconds. 98 * oneShot = when true, the timer stops itself after the first tick. 99 */ 100 void start(int intervalMs, bool oneShot = false) 101 { 102 if (owner_ is null || owner_.handle is null) 103 return; 104 oneShot_ = oneShot; 105 g_timers[id_] = this; 106 SetTimer(owner_.handle, id_, cast(UINT) intervalMs, null); 107 running_ = true; 108 } 109 110 /// Stop the timer. Safe to call when not running. 111 void stop() 112 { 113 if (!running_) 114 return; 115 if (owner_ !is null && owner_.handle !is null) 116 KillTimer(owner_.handle, id_); 117 g_timers.remove(id_); 118 running_ = false; 119 } 120 }