// SPDX-License-Identifier: Unlicense OR MIT package main import ( "context" "sync" "time" ) // Timer implements a timer with adjustable duration. type Timer struct { // Updated is used to notify UI about changes in the timer. Updated chan struct{} mu sync.Mutex start time.Time now time.Time duration time.Duration } // NewTimer creates a new timer with the specified duration. func NewTimer(initialDuration time.Duration) *Timer { return &Timer{ Updated: make(chan struct{}), duration: initialDuration, } } // Start the timer goroutine and return a cancel func. func (t *Timer) Start() context.CancelFunc { now := time.Now() t.now = now t.start = now done := make(chan struct{}) go t.run(done) return func() { close(done) } } func (t *Timer) run(done chan struct{}) { tick := time.NewTicker(50 * time.Millisecond) defer tick.Stop() for { select { case now := <-tick.C: t.update(now) case <-done: return } } } func (t *Timer) invalidate() { select { case t.Updated <- struct{}{}: default: } } func (t *Timer) update(now time.Time) { t.mu.Lock() defer t.mu.Unlock() previousNow := t.now t.now = now progressAfter := t.now.Sub(t.start) if progressAfter <= t.duration { t.invalidate() return } progressBefore := previousNow.Sub(t.start) if progressBefore <= t.duration { t.invalidate() return } } // Reset resets the timer. func (t *Timer) Reset() { t.mu.Lock() defer t.mu.Unlock() t.start = t.now t.invalidate() } // SetDuration changes the duration. func (t *Timer) SetDuration(duration time.Duration) { t.mu.Lock() defer t.mu.Unlock() if t.duration == duration { return } t.duration = duration t.invalidate() } // Info returns timer information. type Info struct { Progress time.Duration Duration time.Duration } // Info returns the current timer state. func (t *Timer) Info() Info { t.mu.Lock() defer t.mu.Unlock() info := Info{ Progress: t.now.Sub(t.start), Duration: t.duration, } if info.Progress > info.Duration { info.Progress = info.Duration } return info }