Add: pages, text component

This commit is contained in:
Andrey Egorov 2024-09-29 21:09:59 +03:00
parent bad142a565
commit a4fda8303b
13 changed files with 504 additions and 84 deletions

View File

@ -9,10 +9,13 @@ build:
@go build -o out/out main.go @go build -o out/out main.go
export GO111MODULE=on export GO111MODULE=on
export GOPRIVATE=gitea.unprism.ru/KRBL/* export GOPRIVATE=gitea.unprism.ru/KRBL/*\
# In my case even setting up "url.git@gitea.unprism.ru:8022/.insteadof=https://gitea.unprism.ru/" did not help and git continued to use https instead of ssh
# So just go to config(location is listed in error message) and change the URL yourself
get: get:
go get . go get .
update: update:
go env
go get -u go get -u

86
api/api.go Normal file
View File

@ -0,0 +1,86 @@
package api
import (
"fmt"
"io"
"log"
"time"
"gitea.unprism.ru/yotia/display-test/api/pages"
"gitea.unprism.ru/yotia/display-test/display"
"gitea.unprism.ru/yotia/display-test/drawer"
)
const (
loopTimeout = 10 * time.Millisecond
)
type displayApi struct {
d drawer.Drawer
curPage pages.Page
initPage pages.InitPage
// statusPage pages.StatusPage
st status
}
// Work process:
// Display has pages that you can switch between. Switch returns page controller.
// Every page has values that you can change:
// Init page - title, progress bar
// Status page - gps, time, rssi, service, ...
type Display interface {
SwitchToInitPage() pages.InitPageContent
io.Closer
}
func New() (Display, error) {
// Device
dev, err := display.New(log.New(log.Writer(), "display", log.LstdFlags), display.MT12232A)
if err != nil {
return nil, fmt.Errorf("create display: %w", err)
}
// Drawer
d := drawer.New(dev)
// Pages
// Init page
initPage, err := pages.NewInitPage(d)
if err != nil {
return nil, fmt.Errorf("new init page: %w", err)
}
return &displayApi{
d: d,
initPage: initPage,
}, nil
}
func (d *displayApi) switchToPage(p pages.Page) {
if d.curPage != nil {
d.curPage.Diactivate()
}
if p != nil {
p.Activate()
}
d.curPage = p
}
func (d *displayApi) SwitchToInitPage() pages.InitPageContent {
d.switchToPage(d.initPage)
return d.initPage
}
func (d *displayApi) Close() error {
// !!! TMP DEBUG BAD CODE
d.d.Clear()
time.Sleep(100 * time.Millisecond)
// !!!
d.switchToPage(nil) // To deactivate cur page
return d.d.GetDisplay().Close()
}

77
api/pages/initpage.go Normal file
View File

@ -0,0 +1,77 @@
package pages
import (
"fmt"
"gitea.unprism.ru/yotia/display-test/components"
"gitea.unprism.ru/yotia/display-test/drawer"
)
type initPage struct {
drawer drawer.Drawer // Drawer with dysplay
title components.Text
bar components.ProgressBar
}
// Only functions that control content of page
type InitPageContent interface {
SetTitle(title string)
GetProgressBar() components.ProgressBar
}
type InitPage interface {
Page
InitPageContent
}
func NewInitPage(d drawer.Drawer) (InitPage, error) {
if err := d.GetDisplay().IsReady(); err != nil {
return nil, fmt.Errorf("display is ready: %w", err)
}
title, err := components.NewText(d, 0, 0)
if err != nil {
return nil, fmt.Errorf("create title: %w", err)
}
pb, err := components.NewProgressBar(d, 0, drawer.LineH*3, d.W(), drawer.LineH) // Bottom
if err != nil {
return nil, fmt.Errorf("create progress bar: %w", err)
}
return &initPage{
drawer: d,
title: title,
bar: pb,
}, nil
}
func (p *initPage) Activate() {
// Draw
p.drawer.Clear()
p.title.Draw()
p.bar.Draw()
}
func (p *initPage) Diactivate() {
// Do not clear because next page will have to clear whole screen any way
}
func (p *initPage) SetTitle(title string) {
p.title.SetPos((p.drawer.W()-len(title)*(drawer.FontCharW+drawer.CharGap))/2, drawer.FontCharH) // Text in center
p.title.SetStr(title)
}
func (p *initPage) GetProgressBar() components.ProgressBar {
return p.bar
}
func (p *initPage) Close() (outErr error) {
// TODO Not the best way...
if err := p.title.Close(); err != nil {
outErr = fmt.Errorf("title close: %w:", err)
}
if err := p.bar.Close(); err != nil {
outErr = fmt.Errorf("progress bar close: %w:", err)
}
return
}

10
api/pages/pages.go Normal file
View File

@ -0,0 +1,10 @@
package pages
import "io"
type Page interface {
Activate()
Diactivate()
io.Closer
}

62
api/progress/handler.go Normal file
View File

@ -0,0 +1,62 @@
package progress
import (
"io"
"gitea.unprism.ru/yotia/display-test/components"
)
type handler struct {
bar components.ProgressBar
checkpointN int // Number of check points
curP int
}
// Handles progress bar update
// You set the amount of
type Handler interface {
Checkpoint() // Increments current progress
// SetProgress(n int) // Do not want to make it public because there is no places it is needed yet
Finish() // Set progress to end
GetBar() components.ProgressBar
Reset()
io.Closer
}
func NewHandler(bar components.ProgressBar, checkpointN int) Handler {
return &handler{
bar: bar,
checkpointN: checkpointN,
}
}
func (h *handler) GetBar() components.ProgressBar {
return h.bar
}
func (h *handler) Checkpoint() {
h.curP = min(h.curP+1, h.checkpointN)
h.bar.SetProgress(float64(h.curP) / float64(h.checkpointN))
}
func (h *handler) SetProgress(n int) {
h.curP = min(max(n, 0), h.checkpointN)
h.bar.SetProgress(float64(h.curP) / float64(h.checkpointN))
}
func (h *handler) Finish() {
h.curP = h.checkpointN
h.bar.SetProgress(1)
}
func (h *handler) Reset() {
h.curP = 0
h.bar.SetProgress(0)
}
func (h *handler) Close() error {
h.curP = 0
h.checkpointN = 0
return h.bar.Close()
}

67
api/status.go Normal file
View File

@ -0,0 +1,67 @@
package api
import (
"sync"
"time"
"gitea.unprism.ru/KRBL/mpu/mpu"
"gitea.unprism.ru/KRBL/sim-modem/api/modem/gps"
)
// Some supplement types and constants
// This struct contains status of the system that will be on the display
type status struct {
mutex sync.Mutex
// Status data
time time.Time
gpsData gps.Data
mpuData mpu.Data
rssi int // Received signal strength indicator (check gitea.unprism.ru/KRBL/sim-modem/api/modem/utils/signal.go)
service string // Internet service name, could be: "NO SERVICE", "GSM", "WCDMA", "LTE", "TDS"
}
type StatusUpdater interface {
UpdateTime(newTime time.Time)
UpdateGps(newData gps.Data)
UpdateMpu(newData mpu.Data)
UpdateRssi(newData int)
UpdateService(newData string)
}
func (st *status) UpdateTime(newTime time.Time) {
st.mutex.Lock()
defer st.mutex.Unlock()
st.time = newTime
}
func (st *status) UpdateGps(newData gps.Data) {
st.mutex.Lock()
defer st.mutex.Unlock()
st.gpsData = newData
}
func (st *status) UpdateMpu(newData mpu.Data) {
st.mutex.Lock()
defer st.mutex.Unlock()
st.mpuData = newData
}
func (st *status) UpdateRssi(newData int) {
st.mutex.Lock()
defer st.mutex.Unlock()
st.rssi = newData
}
func (st *status) UpdateService(newData string) {
st.mutex.Lock()
defer st.mutex.Unlock()
st.service = newData
}

View File

@ -1,7 +1,10 @@
package components package components
import "io"
// Some general types // Some general types
// Rectangle and its' suplements
type rect struct { type rect struct {
x int x int
y int y int
@ -18,3 +21,37 @@ type RectSetter interface {
SetPos(x, y int) SetPos(x, y int)
SetSize(w, h int) SetSize(w, h int)
} }
// Mask
// Implementation dependent !!!
// Now for mt12232
type mask struct {
bits uint32
}
func (m *mask) Update(r rect) {
m.bits = 0
y0 := min(3, max(0, int(r.y/8)))
y1 := min(3, max(0, int((r.y+r.h-1)/8)))
x0 := min(1, max(0, int(r.x/61)))
x1 := min(1, max(0, int((r.x+r.w-1)/61)))
//log.Println(x0, y0, x1, y1)
for y := y0; y <= y1; y++ {
for x := x0; x <= x1; x++ {
m.bits |= (1 << (x*4 + y))
}
}
}
// Component - all components should implement this interface
type Component interface {
RectGetter
Draw() // Draw and flush
io.Closer
}

View File

@ -2,28 +2,23 @@ package components
import ( import (
"fmt" "fmt"
"io"
"gitea.unprism.ru/yotia/display-test/drawer" "gitea.unprism.ru/yotia/display-test/drawer"
) )
type progressBar struct { type progressBar struct {
drawer drawer.Drawer // Drawer with dysplay drawer drawer.Drawer // Drawer with dysplay
mask uint32 // Flush mask mask mask // Flush mask
progress float64 progress float64
rect rect // Rect rect rect // Rect
} }
type ProgressBar interface { type ProgressBar interface {
RectGetter Component
SetProgress(coef float64) // Value from 0 to 1 SetProgress(coef float64) // Value from 0 to 1
Draw() // For any cases
Clear() // Clear space of bar Clear() // Clear space of bar
Flush() // For those times like after SetPos
io.Closer
} }
// Creation function // Creation function
@ -45,34 +40,14 @@ func NewProgressBar(drawer drawer.Drawer, x, y, w, h int) (ProgressBar, error) {
rect: rect{x, y, w, h}, rect: rect{x, y, w, h},
} }
pb.updateMask() pb.mask.Update(pb.rect)
pb.Draw() // pb.Draw()
pb.Flush()
return &pb, nil return &pb, nil
} }
func (pb *progressBar) Flush() { func (pb *progressBar) flush() {
pb.drawer.GetDisplay().FlushByMask(pb.mask) pb.drawer.GetDisplay().FlushByMask(pb.mask.bits)
}
func (pb *progressBar) updateMask() {
pb.mask = 0
// Implementation dependent !!!
// Now for mt12232
y0 := min(3, max(0, int(pb.rect.y/8)))
y1 := min(3, max(0, int((pb.rect.y+pb.rect.h-1)/8)))
x0 := min(1, max(0, int(pb.rect.x/61)))
x1 := min(1, max(0, int((pb.rect.x+pb.rect.w-1)/61)))
//log.Println(x0, y0, x1, y1)
for y := y0; y <= y1; y++ {
for x := x0; x <= x1; x++ {
pb.mask |= (1 << (x*4 + y))
}
}
} }
func (pb *progressBar) Close() error { func (pb *progressBar) Close() error {
@ -92,6 +67,7 @@ func (pb *progressBar) Draw() {
pb.Clear() pb.Clear()
pb.drawer.PutBar(pb.rect.x, pb.rect.y, pb.rect.x+pb.rect.w, pb.rect.y+pb.rect.h, 0xFF) pb.drawer.PutBar(pb.rect.x, pb.rect.y, pb.rect.x+pb.rect.w, pb.rect.y+pb.rect.h, 0xFF)
pb.drawBar() pb.drawBar()
pb.flush()
} }
// Draw only bar(without frames) // Draw only bar(without frames)
@ -105,7 +81,7 @@ func (pb *progressBar) SetProgress(coef float64) {
pb.progress = max(0, min(1, coef)) pb.progress = max(0, min(1, coef))
pb.drawBar() pb.drawBar()
pb.Flush() pb.flush()
} }
func (pb *progressBar) GetPos() (int, int) { func (pb *progressBar) GetPos() (int, int) {

85
components/text.go Normal file
View File

@ -0,0 +1,85 @@
package components
import (
"fmt"
"gitea.unprism.ru/yotia/display-test/drawer"
)
type text struct {
drawer drawer.Drawer // Drawer with dysplay
mask mask // Flush mask
str string
rect rect // Rect
}
type Text interface {
Component
RectSetter
SetStr(newStr string) // Update string and redraw
Clear() // Clear space of bar
}
// Creation function
// Now the only way to chage shape is to recreate component
func NewText(d drawer.Drawer, x, y int) (Text, error) {
if err := d.GetDisplay().IsReady(); err != nil {
return nil, fmt.Errorf("display is ready: %w", err)
}
t := text{
drawer: d,
rect: rect{x, y, 0, drawer.FontCharH},
}
t.mask.Update(t.rect)
//t.Draw()
return &t, nil
}
func (t *text) Close() error {
return nil
}
func (t *text) Clear() {
t.drawer.FillBar(t.rect.x, t.rect.y, t.rect.x+t.rect.w, t.rect.y+t.rect.h, 0x00)
}
func (t *text) Draw() {
t.Clear() // Assume that is draw is invoked string has been changed
t.drawer.PutText(t.rect.x, t.rect.y, t.str)
t.drawer.GetDisplay().FlushByMask(t.mask.bits)
}
func (t *text) updateW() {
t.rect.w = len(t.str)*(drawer.FontCharW+drawer.CharGap) - drawer.CharGap
}
func (t *text) SetStr(str string) {
t.str = str
t.updateW()
t.mask.Update(t.rect)
t.Draw()
}
func (t *text) GetPos() (int, int) {
return t.rect.x, t.rect.y
}
func (t *text) GetSize() (int, int) {
return t.rect.w, t.rect.h
}
func (t *text) SetPos(x, y int) {
t.rect.x = x
t.rect.y = y
t.mask.Update(t.rect)
}
func (t *text) SetSize(w, h int) {
// Empty
}

View File

@ -55,6 +55,7 @@ func (d *drawer) Clear() {
for i := range d.img.Pix { for i := range d.img.Pix {
d.img.Pix[i] = 0 d.img.Pix[i] = 0
} }
d.dev.FlushByMask(0xFF)
} }
func (d *drawer) Flush() { func (d *drawer) Flush() {

View File

@ -332,4 +332,23 @@ func (d *drawer) PutText(x0, y0 int, text string) {
d.putChar(x, y0, c) d.putChar(x, y0, c)
x += CharGap + FontCharW x += CharGap + FontCharW
} }
// Calculate update mask
mask := uint32(0)
// Implementation dependent !!!
// Now for mt12232
my0 := min(3, max(0, int((y0)/8)))
my1 := min(3, max(0, int((y0+FontCharH-1)/8)))
mx0 := min(1, max(0, int(x0/61)))
mx1 := min(1, max(0, int((x0+FontCharW*len(text)-1)/61)))
//log.Println(x0, y0, x1, y1)
for y := my0; y <= my1; y++ {
for x := mx0; x <= mx1; x++ {
mask |= (1 << (x*4 + y))
}
}
d.dev.FlushByMask(mask)
} }

2
go.mod
View File

@ -3,6 +3,7 @@ module gitea.unprism.ru/yotia/display-test
go 1.22.5 go 1.22.5
require ( require (
gitea.unprism.ru/KRBL/mpu v0.1.0
gitea.unprism.ru/KRBL/sim-modem v0.1.7 gitea.unprism.ru/KRBL/sim-modem v0.1.7
github.com/stianeikeland/go-rpio/v4 v4.6.0 github.com/stianeikeland/go-rpio/v4 v4.6.0
golang.org/x/text v0.17.0 golang.org/x/text v0.17.0
@ -14,5 +15,6 @@ require (
require ( require (
github.com/creack/goselect v0.1.2 // indirect github.com/creack/goselect v0.1.2 // indirect
go.bug.st/serial v1.6.2 // indirect go.bug.st/serial v1.6.2 // indirect
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
golang.org/x/sys v0.24.0 // indirect golang.org/x/sys v0.24.0 // indirect
) )

99
main.go
View File

@ -4,11 +4,14 @@ import (
"context" "context"
"fmt" "fmt"
"log" "log"
"math/rand"
"os" "os"
"os/signal" "os/signal"
"syscall" "syscall"
"time" "time"
"gitea.unprism.ru/yotia/display-test/api"
"gitea.unprism.ru/yotia/display-test/api/progress"
"gitea.unprism.ru/yotia/display-test/components" "gitea.unprism.ru/yotia/display-test/components"
"gitea.unprism.ru/yotia/display-test/display" "gitea.unprism.ru/yotia/display-test/display"
"gitea.unprism.ru/yotia/display-test/drawer" "gitea.unprism.ru/yotia/display-test/drawer"
@ -20,6 +23,8 @@ const (
displayUpdateTimeout = 10 * time.Millisecond displayUpdateTimeout = 10 * time.Millisecond
) )
type _ = api.StatusUpdater
func main() { func main() {
log.Println("CGSG forever!!!") log.Println("CGSG forever!!!")
@ -43,43 +48,6 @@ var d drawer.Drawer
var m modem.Modem var m modem.Modem
func DrawProgress(ctx context.Context, timeout time.Duration) error {
deadline := time.Now().Add(timeout)
// Draw outline
x := 0
y := drawer.FontCharH + 4
w := dev.GetImg().Rect.Dx()
h := drawer.FontCharH - 4
d.PutBar(x, y, x+w, y+h, 255)
gap := 3
// Draw body
x += gap
y += gap
w -= 2 * gap
h -= 2 * gap
lastX := x
for {
select {
case <-ctx.Done():
return nil
case <-time.After(displayUpdateTimeout):
value := 1 - float64(time.Until(deadline).Milliseconds())/float64(timeout.Milliseconds())
newX := x + int(value*float64(w))
// Draw
d.FillBar(lastX, y, newX, y+h, 255)
lastX = newX
if value >= 1 {
return nil
}
d.Flush()
}
}
}
func Init(ctx context.Context) error { func Init(ctx context.Context) error {
// Display init // Display init
var err error var err error
@ -101,23 +69,26 @@ func Init(ctx context.Context) error {
time.Sleep(time.Second) time.Sleep(time.Second)
pb, err := components.NewProgressBar(d, 0, drawer.LineH*3, d.W(), drawer.LineH) pbc, err := components.NewProgressBar(d, 0, drawer.LineH*3, d.W(), drawer.LineH)
time.Sleep(time.Second)
if err != nil { if err != nil {
return fmt.Errorf("create progress bar: %w", err) return fmt.Errorf("new progress bar: %w", err)
} }
for x := 0; x < 100; x++ {
pb := progress.NewHandler(pbc, 10)
for range 10 {
select { select {
case <-ctx.Done(): case <-ctx.Done():
return nil return nil
case <-time.After(10 * time.Millisecond): case <-time.After(time.Duration(rand.Int()%1000) * time.Millisecond):
pb.SetProgress(float64(x) / 100) }
pb.Checkpoint()
}
time.Sleep(400 * time.Millisecond)
} pb.GetBar().Clear()
}
pb.Clear()
pb.Close() pb.Close()
d.FillBar(0, drawer.LineH*2, d.W(), drawer.LineH*3, 0) d.FillBar(0, drawer.LineH*2, d.W(), drawer.LineH*3, 0)
d.PutText(1, drawer.LineH*2, "Done") d.PutText(1, drawer.LineH*2, "Done")
d.Flush() d.Flush()
@ -195,15 +166,39 @@ func Close() {
func mainE(ctx context.Context) error { func mainE(ctx context.Context) error {
logger = log.New(os.Stdout, "main : ", log.LstdFlags) logger = log.New(os.Stdout, "main : ", log.LstdFlags)
// //
if err := Init(ctx); err != nil { //if err := Init(ctx); err != nil {
return err
}
//if err := dev.Test(ctx); err != nil {
// return err // return err
//} //}
defer Close() ////if err := dev.Test(ctx); err != nil {
//// return err
////}
//defer Close()
// if err := MainLoop(ctx); err != nil { // if err := MainLoop(ctx); err != nil {
// return err // return err
// } // }
a, err := api.New()
if err != nil {
return fmt.Errorf("new api: %w", err)
}
defer a.Close()
logger.Println("Inited")
ip := a.SwitchToInitPage()
ip.SetTitle("KRBL")
ip.GetProgressBar().SetProgress(0)
time.Sleep(300 * time.Millisecond)
pb := progress.NewHandler(ip.GetProgressBar(), 100)
for range 100 {
select {
case <-ctx.Done():
return nil
case <-time.After(20 * time.Millisecond):
}
pb.Checkpoint()
}
time.Sleep(400 * time.Millisecond)
return nil return nil
} }