~eliasnaur/gio-example

ref: 914fd8dc60a3 gio-example/7gui/timer/timer.go -rw-r--r-- 3.1 KiB
914fd8dcChris Waldon deps: update to latest gioui.org/x a month ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package main

import (
	"context"
	"fmt"
	"sync"
	"time"
)

// Timer implements an
type Timer struct {
	// Updated is used to notify UI about changes in the timer.
	Updated chan struct{}

	// mu locks the state such that it can be modified and accessed
	// from multiple goroutines.
	mu       sync.Mutex
	start    time.Time     // start corresponds to when the timer was started.
	now      time.Time     // now corresponds to the last updated time.
	duration time.Duration // duration is the maximum progress.
}

// NewTimer creates a new timer with the specified timer.
func NewTimer(initialDuration time.Duration) *Timer {
	return &Timer{
		Updated:  make(chan struct{}),
		duration: initialDuration,
	}
}

// Start the timer goroutine and return a cancel func that
// that can be used to stop it.
func (t *Timer) Start() context.CancelFunc {
	// initialize the timer state.
	now := time.Now()
	t.now = now
	t.start = now

	// we use done to signal stopping the goroutine.
	// a context.Context could be also used.
	done := make(chan struct{})
	go t.run(done)
	return func() { close(done) }
}

// run is the main loop for the timer.
func (t *Timer) run(done chan struct{}) {
	// we use a time.Ticker to update the state,
	// in many cases, this could be a network access instead.
	tick := time.NewTicker(50 * time.Millisecond)
	defer tick.Stop()

	for {
		select {
		case now := <-tick.C:
			t.update(now)
		case <-done:
			return
		}
	}
}

// invalidate sends a signal to the UI that
// the internal state has changed.
func (t *Timer) invalidate() {
	// we use a non-blocking send, that way the Timer
	// can continue updating internally.
	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

	// first check whether we have not exceeded the duration.
	// in that case the progress advanced and we need to notify
	// about a change.
	progressAfter := t.now.Sub(t.start)
	if progressAfter <= t.duration {
		t.invalidate()
		return
	}

	// when we had progressed beyond the duration we also
	// need to update the first time it happens.
	progressBefore := previousNow.Sub(t.start)
	if progressBefore <= t.duration {
		t.invalidate()
		return
	}
}

// Reset resets timer to the last know time.
func (t *Timer) Reset() {
	t.mu.Lock()
	defer t.mu.Unlock()

	t.start = t.now
	t.invalidate()
}

// SetDuration changes the duration of the timer.
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 the latest know info about the timer.
func (t *Timer) Info() (info Info) {
	t.mu.Lock()
	defer t.mu.Unlock()

	info.Progress = t.now.Sub(t.start)
	info.Duration = t.duration
	if info.Progress > info.Duration {
		info.Progress = info.Duration
	}
	return info
}

// Info is the information about the timer.
type Info struct {
	Progress time.Duration
	Duration time.Duration
}

// ProgressString returns the progress formatted as seconds.
func (info *Info) ProgressString() string {
	return fmt.Sprintf("%.1fs", info.Progress.Seconds())
}