Compare commits

...

2 Commits

Author SHA1 Message Date
31fa374c7f Downgrade to pre-Blessed version of git.wow.st/gmp/ble.
Improve layout generally and add wide (horizontal) layouts.

Add stopwatch widget.
2019-12-06 12:16:46 -05:00
14bbbf0679 UI enhancements for horizontal mode, intermediate checkin. 2019-12-05 21:00:17 -05:00
2 changed files with 230 additions and 75 deletions

2
go.mod
View File

@ -4,7 +4,7 @@ go 1.13
require ( require (
gioui.org v0.0.0-20191126175243-2ca2e5462f16 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 github.com/google/go-github/v24 v24.0.1 // indirect
golang.org/x/oauth2 v0.0.0-20191122200657-5d9234df094c // indirect golang.org/x/oauth2 v0.0.0-20191122200657-5d9234df094c // indirect
gopkg.in/yaml.v2 v2.2.7 gopkg.in/yaml.v2 v2.2.7

303
main.go
View File

@ -9,6 +9,7 @@ import (
"log" "log"
"os" "os"
"path" "path"
"time"
"git.wow.st/gmp/ble" "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() { func eventloop() {
w := app.NewWindow( w := app.NewWindow(
app.Size(unit.Dp(400), unit.Dp(400)), app.Size(unit.Dp(350), unit.Dp(600)),
app.Title("HRM"), app.Title("HRM"),
) )
gofont.Register() gofont.Register()
@ -102,6 +119,7 @@ func eventloop() {
state := "starting" state := "starting"
var hr int var hr int
var periph ble.Peripheral var periph ble.Peripheral
var wide bool
periphs := make([]ble.Peripheral, 0) periphs := make([]ble.Peripheral, 0)
btns := make([]*widget.Button, 0) btns := make([]*widget.Button, 0)
backBtn := &widget.Button{} backBtn := &widget.Button{}
@ -110,91 +128,101 @@ func eventloop() {
f := &layout.Flex{Axis: layout.Vertical} f := &layout.Flex{Axis: layout.Vertical}
offpage = func() { offpage = func() {
c1 := f.Rigid(gtx, func() { f.Layout(gtx,
th.Body1("Heart Rate Monitor").Layout(gtx) f.Rigid(gtx, func() {
}) th.Body1("Heart Rate Monitor").Layout(gtx)
c2 := f.Rigid(gtx, func() { }),
th.Body1("Bluetooth is Powered Off").Layout(gtx) f.Rigid(gtx, func() {
}) th.Body1("Bluetooth is Powered Off").Layout(gtx)
f.Layout(gtx, c1, c2) }),
)
} }
scanpage = func() {
c1 := f.Rigid(gtx, func() { appname := func() {
layout.Align(layout.Center).Layout(gtx, func() {
th.Body1("Heart Rate Monitor").Layout(gtx) th.Body1("Heart Rate Monitor").Layout(gtx)
}) })
c2 := f.Rigid(gtx, func() { }
appstate := func() {
layout.Align(layout.Center).Layout(gtx, func() {
th.Body1(state).Layout(gtx) th.Body1(state).Layout(gtx)
}) })
c3 := f.Rigid(gtx, func() { }
lst := &layout.List{Axis: layout.Vertical} periphname := func() {
lst.Layout(gtx, len(periphs), func(i int) { layout.Align(layout.Center).Layout(gtx, func() {
gtx.Constraints.Width.Min = gtx.Constraints.Width.Max th.Body1("COOSPO").Layout(gtx)
layout.UniformInset(unit.Dp(2)).Layout(gtx, func() {
th.Button(periphs[i].Name).Layout(gtx, btns[i])
})
if btns[i].Clicked(gtx) {
b.StopScan()
periph = periphs[i]
b.Connect(periph)
state = "connecting"
page = connpage
w.Invalidate()
}
})
}) })
f.Layout(gtx, c1, c2, c3) }
leftbar := func() {
f := &layout.Flex{Axis: layout.Vertical}
f.Layout(gtx,
f.Rigid(gtx, appname),
f.Rigid(gtx, func() { th.Body1("").Layout(gtx) }),
f.Rigid(gtx, appstate),
)
}
scanlist := func() {
lst := &layout.List{Axis: layout.Vertical}
lst.Layout(gtx, len(periphs), func(i int) {
gtx.Constraints.Width.Min = gtx.Constraints.Width.Max
layout.UniformInset(unit.Dp(2)).Layout(gtx, func() {
th.Button(periphs[i].Name).Layout(gtx, btns[i])
})
if btns[i].Clicked(gtx) {
b.StopScan()
periph = periphs[i]
b.Connect(periph)
state = "connecting"
page = connpage
w.Invalidate()
}
})
}
scanpage = func() {
if wide {
f2 := &layout.Flex{Axis: layout.Horizontal}
f2.Layout(gtx,
f2.Flex(gtx, 0.2, leftbar),
f2.Rigid(gtx, scanlist),
)
} else {
f.Layout(gtx,
f.Rigid(gtx, appname),
f.Rigid(gtx, appstate),
f.Rigid(gtx, scanlist),
)
}
} }
connpage = func() { connpage = func() {
c1 := f.Rigid(gtx, func() { f.Layout(gtx,
th.Body1("Heart Rate Monitor").Layout(gtx) f.Rigid(gtx, appname),
}) f.Rigid(gtx, appstate),
c2 := f.Rigid(gtx, func() { f.Rigid(gtx, periphname),
th.Body1("Connecting").Layout(gtx) f.Rigid(gtx, func() {
}) th.Button("Cancel").Layout(gtx, backBtn)
c3 := f.Rigid(gtx, func() { if backBtn.Clicked(gtx) {
th.Body1(periph.Name).Layout(gtx) ble.CancelConnection(periph)
}) periphs = periphs[:0]
c4 := f.Rigid(gtx, func() { Config.Autoconnect = ""
th.Button("Cancel").Layout(gtx, backBtn) saveConfig()
if backBtn.Clicked(gtx) { b.Scan()
ble.CancelConnection(periph) state = "scanning"
periphs = periphs[:0] page = scanpage
Config.Autoconnect = "" }
saveConfig() }),
b.Scan() )
state = "scanning"
page = scanpage
}
})
f.Layout(gtx, c1, c2, c3, c4)
} }
hrpage = func() { hrcircle := func() int {
c1 := f.Rigid(gtx, func() { var w, h1 float32
th.Body1("Heart Rate Monitor").Layout(gtx) layout.Align(layout.Center).Layout(gtx, func() {
})
c2 := f.Rigid(gtx, func() {
th.Body1(periph.Name).Layout(gtx)
})
c4 := f.Rigid(gtx, func() {
layout.UniformInset(unit.Dp(2)).Layout(gtx, func() {
th.Button("Stop").Layout(gtx, backBtn)
})
if backBtn.Clicked(gtx) {
ble.Disconnect(periph)
periphs = periphs[:0]
Config.Autoconnect = ""
saveConfig()
b.Scan()
state = "scanning"
page = scanpage
}
})
c3 := f.Rigid(gtx, func() {
blue := color.RGBA{0x3f, 0x51, 0xb5, 255} blue := color.RGBA{0x3f, 0x51, 0xb5, 255}
white := color.RGBA{255, 255, 255, 255} white := color.RGBA{255, 255, 255, 255}
w, h1 := float32(gtx.Constraints.Width.Max), float32(gtx.Constraints.Height.Max) w, h1 = float32(gtx.Constraints.Width.Max), float32(gtx.Constraints.Height.Max)
if w < h1 { if w < h1 {
h1 = w h1 = w
} }
@ -221,13 +249,135 @@ func eventloop() {
}) })
gtx.Dimensions.Size = image.Point{int(h1), int(h1)} gtx.Dimensions.Size = image.Point{int(h1), int(h1)}
}) })
f.Layout(gtx, c1, c2, c3, c4) return int(h1)
}
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, func() {
layout.Align(layout.Center).Layout(gtx, func() {
gtx.Constraints.Width.Max = 1e6
th.H4(sw.elapsed.Format("15:04:05.00")).Layout(gtx)
})
}),
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
}
}),
)
}
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 page = offpage
t := time.NewTicker(time.Second/30)
for { for {
select { select {
case <-t.C:
w.Invalidate()
case e := <-b.Events(): case e := <-b.Events():
switch e := e.(type) { switch e := e.(type) {
case ble.UpdateStateEvent: case ble.UpdateStateEvent:
@ -297,6 +447,11 @@ func eventloop() {
return return
case system.FrameEvent: case system.FrameEvent:
gtx.Reset(e.Config, e.Size) gtx.Reset(e.Config, e.Size)
if e.Size.X > e.Size.Y {
wide = true
} else {
wide = false
}
resetSysinset(e.Insets) resetSysinset(e.Insets)
sysinset.Layout(gtx, func() { sysinset.Layout(gtx, func() {
margin.Layout(gtx, page) margin.Layout(gtx, page)