diff --git a/go.mod b/go.mod index fdf28b2..1a8a68d 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.13 require ( gioui.org v0.0.0-20191126175243-2ca2e5462f16 - git.wow.st/gmp/ble v0.0.0-20191205134941-3bc712870db2 + git.wow.st/gmp/ble v0.0.0-20191127164604-2af636b9461a github.com/google/go-github/v24 v24.0.1 // indirect golang.org/x/oauth2 v0.0.0-20191122200657-5d9234df094c // indirect gopkg.in/yaml.v2 v2.2.7 diff --git a/main.go b/main.go index 4614de4..c47d371 100644 --- a/main.go +++ b/main.go @@ -9,6 +9,7 @@ import ( "log" "os" "path" + "time" "git.wow.st/gmp/ble" @@ -77,9 +78,25 @@ func hrDecode(x []byte) int { } } +type Stopwatch struct { + lasttime time.Time + elapsed time.Time + running bool + startstopBtn *widget.Button + resetBtn *widget.Button + h, m, s int +} + +func NewStopwatch() Stopwatch { + return Stopwatch{ + startstopBtn: &widget.Button{}, + resetBtn: &widget.Button{}, + } +} + func eventloop() { w := app.NewWindow( - app.Size(unit.Dp(400), unit.Dp(400)), + app.Size(unit.Dp(350), unit.Dp(600)), app.Title("HRM"), ) gofont.Register() @@ -121,9 +138,21 @@ func eventloop() { ) } - appname := func() { th.Body1("Heart Rate Monitor").Layout(gtx) } - appstate := func() { th.Body1(state).Layout(gtx) } - periphname := func() { th.Body1(periph.Name).Layout(gtx) } + appname := func() { + layout.Align(layout.Center).Layout(gtx, func() { + th.Body1("Heart Rate Monitor").Layout(gtx) + }) + } + appstate := func() { + layout.Align(layout.Center).Layout(gtx, func() { + th.Body1(state).Layout(gtx) + }) + } + periphname := func() { + layout.Align(layout.Center).Layout(gtx, func() { + th.Body1("COOSPO").Layout(gtx) + }) + } leftbar := func() { f := &layout.Flex{Axis: layout.Vertical} @@ -171,9 +200,7 @@ func eventloop() { connpage = func() { f.Layout(gtx, f.Rigid(gtx, appname), - f.Rigid(gtx, func() { - th.Body1("Connecting").Layout(gtx) - }), + f.Rigid(gtx, appstate), f.Rigid(gtx, periphname), f.Rigid(gtx, func() { th.Button("Cancel").Layout(gtx, backBtn) @@ -190,63 +217,167 @@ func eventloop() { ) } - hrcircle := func() { - blue := color.RGBA{0x3f, 0x51, 0xb5, 255} - white := color.RGBA{255, 255, 255, 255} - w, h1 := float32(gtx.Constraints.Width.Max), float32(gtx.Constraints.Height.Max) - if w < h1 { - h1 = w - } - s2 := h1 * 0.9 - r1 := f32.Rectangle{f32.Point{0, 0}, f32.Point{h1, h1}} - p2a := f32.Point{(h1-s2)/2, (h1-s2)/2} - p2b := f32.Point{s2 + (h1-s2)/2, s2 + (h1-s2)/2} - r2 := f32.Rectangle{p2a, p2b} - clip.Rect{ Rect: r1, NE: h1/2, NW: h1/2, SE: h1/2, SW: h1/2}.Op(gtx.Ops).Add(gtx.Ops) - paint.ColorOp{Color: blue}.Add(gtx.Ops) - paint.PaintOp{Rect: r1}.Add(gtx.Ops) - clip.Rect{ Rect: r2, NE: s2/2, NW: s2/2, SE: s2/2, SW: s2/2}.Op(gtx.Ops).Add(gtx.Ops) - paint.ColorOp{Color: white}.Add(gtx.Ops) - paint.PaintOp{Rect: r2}.Add(gtx.Ops) - op.TransformOp{}.Offset(p2a).Add(gtx.Ops) - gtx.Constraints.Width.Max = int(s2) - gtx.Constraints.Height.Max = int(s2) - gtx.Constraints.Width.Min = int(s2) - gtx.Constraints.Height.Min = int(s2) - l := th.H1(fmt.Sprintf("%d", hr)) - l.Alignment = text.Middle + hrcircle := func() int { + var w, h1 float32 layout.Align(layout.Center).Layout(gtx, func() { - l.Layout(gtx) + blue := color.RGBA{0x3f, 0x51, 0xb5, 255} + white := color.RGBA{255, 255, 255, 255} + w, h1 = float32(gtx.Constraints.Width.Max), float32(gtx.Constraints.Height.Max) + if w < h1 { + h1 = w + } + s2 := h1 * 0.9 + r1 := f32.Rectangle{f32.Point{0, 0}, f32.Point{h1, h1}} + p2a := f32.Point{(h1-s2)/2, (h1-s2)/2} + p2b := f32.Point{s2 + (h1-s2)/2, s2 + (h1-s2)/2} + r2 := f32.Rectangle{p2a, p2b} + clip.Rect{ Rect: r1, NE: h1/2, NW: h1/2, SE: h1/2, SW: h1/2}.Op(gtx.Ops).Add(gtx.Ops) + paint.ColorOp{Color: blue}.Add(gtx.Ops) + paint.PaintOp{Rect: r1}.Add(gtx.Ops) + clip.Rect{ Rect: r2, NE: s2/2, NW: s2/2, SE: s2/2, SW: s2/2}.Op(gtx.Ops).Add(gtx.Ops) + paint.ColorOp{Color: white}.Add(gtx.Ops) + paint.PaintOp{Rect: r2}.Add(gtx.Ops) + op.TransformOp{}.Offset(p2a).Add(gtx.Ops) + gtx.Constraints.Width.Max = int(s2) + gtx.Constraints.Height.Max = int(s2) + gtx.Constraints.Width.Min = int(s2) + gtx.Constraints.Height.Min = int(s2) + l := th.H1(fmt.Sprintf("%d", hr)) + l.Alignment = text.Middle + layout.Align(layout.Center).Layout(gtx, func() { + l.Layout(gtx) + }) + gtx.Dimensions.Size = image.Point{int(h1), int(h1)} }) - gtx.Dimensions.Size = image.Point{int(h1), int(h1)} + return int(h1) } - hrpage = func() { + sw := NewStopwatch() + + stopwatch := func() { + startstoptxt := "start" + if sw.running { + sw.elapsed = sw.elapsed.Add(time.Since(sw.lasttime)) + startstoptxt = "stop" + } + sw.lasttime = time.Now() + f := layout.Flex{Axis: layout.Vertical} f.Layout(gtx, - f.Rigid(gtx, appname), - f.Rigid(gtx, periphname), f.Rigid(gtx, func() { - layout.UniformInset(unit.Dp(2)).Layout(gtx, func() { - th.Button("Stop").Layout(gtx, backBtn) + layout.Align(layout.Center).Layout(gtx, func() { + gtx.Constraints.Width.Max = 1e6 + th.H4(sw.elapsed.Format("15:04:05.00")).Layout(gtx) }) - if backBtn.Clicked(gtx) { - ble.Disconnect(periph) - periphs = periphs[:0] - Config.Autoconnect = "" - saveConfig() - b.Scan() - state = "scanning" - page = scanpage + }), + f.Rigid(gtx, func() { + f2 := layout.Flex{Axis: layout.Horizontal} + gtx.Constraints.Height.Min = 100 + f2.Layout(gtx, + f2.Flex(gtx, 0.5, func() { + layout.UniformInset(unit.Dp(2)).Layout(gtx, func() { + th.Button(startstoptxt).Layout(gtx, sw.startstopBtn) + }) + }), + f2.Flex(gtx, 0.5, func() { + layout.UniformInset(unit.Dp(2)).Layout(gtx, func() { + th.Button("reset").Layout(gtx, sw.resetBtn) + }) + }), + ) + if sw.startstopBtn.Clicked(gtx) { + if sw.running { + sw.running = false + } else { + sw.running = true + } + } + if sw.resetBtn.Clicked(gtx) { + sw.elapsed = time.Time{} + sw.running = false } }), - f.Rigid(gtx, hrcircle), ) } + swidth := new(int) + *swidth = 900 + hrpage = func() { + stopbtn := func() { + gtx.Constraints.Width.Min = *swidth + gtx.Constraints.Height.Min = 100 + layout.UniformInset(unit.Dp(2)).Layout(gtx, func() { + th.Button("Disconnect").Layout(gtx, backBtn) + }) + } + if wide { + f2 := &layout.Flex{Axis: layout.Horizontal} + f2.Layout(gtx, + f2.Flex(gtx, 0.4, func() { + f3 := &layout.Flex{Axis: layout.Vertical} + c1 := f3.Rigid(gtx, func() { + appname() + *swidth = gtx.Dimensions.Size.X + }) + c2 := f3.Rigid(gtx, func() { + periphname() + s2 := gtx.Dimensions.Size.X + if s2 > *swidth { + *swidth = s2 + } + }) + c3 := f3.Rigid(gtx, stopwatch) + c4 := f3.Rigid(gtx, stopbtn) + f3.Layout(gtx, + c1, + f3.Rigid(gtx, appstate), + c2, + f3.Flex(gtx, 1.0, func() { th.Body1("").Layout(gtx) }), + c3, + c4, + ) + }), + f2.Flex(gtx, 0.6, func() { hrcircle() }), + ) + } else { // !wide + c1 := f.Rigid(gtx, func() { + layout.Align(layout.Center).Layout(gtx, func() { + gtx.Constraints.Width.Min = *swidth + gtx.Constraints.Width.Max = *swidth + stopwatch() + }) + }) + c2 := f.Rigid(gtx, func() { + layout.Align(layout.Center).Layout(gtx, stopbtn) + }) + f.Layout(gtx, + f.Rigid(gtx, appname), + f.Rigid(gtx, appstate), + f.Rigid(gtx, periphname), + f.Flex(gtx, 1.0, func() { + *swidth = hrcircle() + }), + c1, + c2, + ) + } + if backBtn.Clicked(gtx) { + ble.Disconnect(periph) + periphs = periphs[:0] + Config.Autoconnect = "" + saveConfig() + b.Scan() + state = "scanning" + page = scanpage + } + } + page = offpage + t := time.NewTicker(time.Second/30) for { select { + case <-t.C: + w.Invalidate() case e := <-b.Events(): switch e := e.(type) { case ble.UpdateStateEvent: