~eliasnaur/gio-example

ref: 914fd8dc60a3 gio-example/7gui/timer/main.go -rw-r--r-- 4.2 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
143
144
145
146
147
148
149
150
151
152
153
154
155
package main

import (
	"log"
	"os"
	"time"

	"gioui.org/app"             // app contains Window handling.
	"gioui.org/font/gofont"     // gofont is used for loading the default font.
	"gioui.org/io/key"          // key is used for keyboard events.
	"gioui.org/io/system"       // system is used for system events (e.g. closing the window).
	"gioui.org/layout"          // layout is used for layouting widgets.
	"gioui.org/op"              // op is used for recording different operations.
	"gioui.org/unit"            // unit is used to define pixel-independent sizes
	"gioui.org/widget"          // widget contains state handling for widgets.
	"gioui.org/widget/material" // material contains material design widgets.
)

func main() {
	// The ui loop is separated from the application window creation
	// such that it can be used for testing.
	ui := NewUI()

	// This creates a new application window and starts the UI.
	go func() {
		w := app.NewWindow(
			app.Title("Timer"),
			app.Size(unit.Dp(360), unit.Dp(360)),
		)
		if err := ui.Run(w); err != nil {
			log.Println(err)
			os.Exit(1)
		}
		os.Exit(0)
	}()

	// This starts Gio main.
	app.Main()
}

// defaultMargin is a margin applied in multiple places to give
// widgets room to breathe.
var defaultMargin = unit.Dp(10)

// UI holds all of the application state.
type UI struct {
	// Theme is used to hold the fonts used throughout the application.
	Theme *material.Theme

	Timer *Timer

	duration widget.Float
	reset    widget.Clickable
}

// NewUI creates a new UI using the Go Fonts.
func NewUI() *UI {
	ui := &UI{}
	ui.Theme = material.NewTheme(gofont.Collection())

	// start with reasonable defaults.
	ui.Timer = NewTimer(5 * time.Second)
	ui.duration.Value = 5

	return ui
}

// Run handles window events and renders the application.
func (ui *UI) Run(w *app.Window) error {

	// start the timer goroutine and ensure it's closed
	// when the application closes.
	closeTimer := ui.Timer.Start()
	defer closeTimer()

	var ops op.Ops
	for {
		select {
		// when the timer is updated we should update the screen.
		case <-ui.Timer.Updated:
			w.Invalidate()

		case e := <-w.Events():
			// detect the type of the event.
			switch e := e.(type) {
			// this is sent when the application should re-render.
			case system.FrameEvent:
				// gtx is used to pass around rendering and event information.
				gtx := layout.NewContext(&ops, e)
				// render and handle UI.
				ui.Layout(gtx)
				// render and handle the operations from the UI.
				e.Frame(gtx.Ops)

			// handle a global key press.
			case key.Event:
				switch e.Name {
				// when we click escape, let's close the window.
				case key.NameEscape:
					return nil
				}

			// this is sent when the application is closed.
			case system.DestroyEvent:
				return e.Err
			}
		}
	}

	return nil
}

// Layout displays the main program layout.
func (ui *UI) Layout(gtx layout.Context) layout.Dimensions {
	th := ui.Theme

	// check whether the reset button was clicked.
	if ui.reset.Clicked() {
		ui.Timer.Reset()
	}
	// check whether the slider value has changed.
	if ui.duration.Changed() {
		ui.Timer.SetDuration(secondsToDuration(float64(ui.duration.Value)))
	}

	// get the latest information about the timer.
	info := ui.Timer.Info()
	progress := float32(0)
	if info.Duration == 0 {
		progress = 1
	} else {
		progress = float32(info.Progress.Seconds() / info.Duration.Seconds())
	}

	// inset is used to add padding around the window border.
	inset := layout.UniformInset(defaultMargin)
	return inset.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
		return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
			layout.Rigid(material.Body1(th, "Elapsed Time").Layout),
			layout.Rigid(material.ProgressBar(th, progress).Layout),
			layout.Rigid(material.Body1(th, info.ProgressString()).Layout),

			layout.Rigid(layout.Spacer{Height: th.TextSize}.Layout),
			layout.Rigid(material.Body1(th, "Duration").Layout),
			layout.Rigid(material.Slider(th, &ui.duration, 0, 15).Layout),

			layout.Rigid(layout.Spacer{Height: th.TextSize}.Layout),
			layout.Rigid(material.Button(th, &ui.reset, "Reset").Layout),
		)
	})
}

func secondsToDuration(s float64) time.Duration {
	return time.Duration(s * float64(time.Second))
}