From a4fda8303b601392596bcd99f8747d7270b5331a Mon Sep 17 00:00:00 2001 From: Andrey Egorov Date: Sun, 29 Sep 2024 21:09:59 +0300 Subject: [PATCH] Add: pages, text component --- Makefile | 5 +- api/api.go | 86 ++++++++++++++++++++++++++++++++++ api/pages/initpage.go | 77 +++++++++++++++++++++++++++++++ api/pages/pages.go | 10 ++++ api/progress/handler.go | 62 +++++++++++++++++++++++++ api/status.go | 67 +++++++++++++++++++++++++++ components/components.go | 37 +++++++++++++++ components/progressbar.go | 40 ++++------------ components/text.go | 85 ++++++++++++++++++++++++++++++++++ drawer/drawer.go | 1 + drawer/font.go | 19 ++++++++ go.mod | 2 + main.go | 97 +++++++++++++++++++-------------------- 13 files changed, 504 insertions(+), 84 deletions(-) create mode 100644 api/api.go create mode 100644 api/pages/initpage.go create mode 100644 api/pages/pages.go create mode 100644 api/progress/handler.go create mode 100644 api/status.go create mode 100644 components/text.go diff --git a/Makefile b/Makefile index 0c84490..a969545 100644 --- a/Makefile +++ b/Makefile @@ -9,10 +9,13 @@ build: @go build -o out/out main.go 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: go get . update: + go env go get -u \ No newline at end of file diff --git a/api/api.go b/api/api.go new file mode 100644 index 0000000..d496f59 --- /dev/null +++ b/api/api.go @@ -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() +} diff --git a/api/pages/initpage.go b/api/pages/initpage.go new file mode 100644 index 0000000..ef77e91 --- /dev/null +++ b/api/pages/initpage.go @@ -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 +} diff --git a/api/pages/pages.go b/api/pages/pages.go new file mode 100644 index 0000000..cc7d898 --- /dev/null +++ b/api/pages/pages.go @@ -0,0 +1,10 @@ +package pages + +import "io" + +type Page interface { + Activate() + Diactivate() + + io.Closer +} diff --git a/api/progress/handler.go b/api/progress/handler.go new file mode 100644 index 0000000..20aaf8f --- /dev/null +++ b/api/progress/handler.go @@ -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() +} diff --git a/api/status.go b/api/status.go new file mode 100644 index 0000000..0d92dbc --- /dev/null +++ b/api/status.go @@ -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 +} diff --git a/components/components.go b/components/components.go index b297433..fd8256e 100644 --- a/components/components.go +++ b/components/components.go @@ -1,7 +1,10 @@ package components +import "io" + // Some general types +// Rectangle and its' suplements type rect struct { x int y int @@ -18,3 +21,37 @@ type RectSetter interface { SetPos(x, y 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 +} diff --git a/components/progressbar.go b/components/progressbar.go index acc9cad..a4ba2f6 100644 --- a/components/progressbar.go +++ b/components/progressbar.go @@ -2,28 +2,23 @@ package components import ( "fmt" - "io" "gitea.unprism.ru/yotia/display-test/drawer" ) type progressBar struct { drawer drawer.Drawer // Drawer with dysplay - mask uint32 // Flush mask + mask mask // Flush mask progress float64 rect rect // Rect } type ProgressBar interface { - RectGetter + Component SetProgress(coef float64) // Value from 0 to 1 - Draw() // For any cases Clear() // Clear space of bar - Flush() // For those times like after SetPos - - io.Closer } // Creation function @@ -45,34 +40,14 @@ func NewProgressBar(drawer drawer.Drawer, x, y, w, h int) (ProgressBar, error) { rect: rect{x, y, w, h}, } - pb.updateMask() - pb.Draw() - pb.Flush() + pb.mask.Update(pb.rect) + // pb.Draw() return &pb, nil } -func (pb *progressBar) Flush() { - pb.drawer.GetDisplay().FlushByMask(pb.mask) -} - -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) flush() { + pb.drawer.GetDisplay().FlushByMask(pb.mask.bits) } func (pb *progressBar) Close() error { @@ -92,6 +67,7 @@ func (pb *progressBar) Draw() { 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.drawBar() + pb.flush() } // Draw only bar(without frames) @@ -105,7 +81,7 @@ func (pb *progressBar) SetProgress(coef float64) { pb.progress = max(0, min(1, coef)) pb.drawBar() - pb.Flush() + pb.flush() } func (pb *progressBar) GetPos() (int, int) { diff --git a/components/text.go b/components/text.go new file mode 100644 index 0000000..d07e08c --- /dev/null +++ b/components/text.go @@ -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 +} diff --git a/drawer/drawer.go b/drawer/drawer.go index 0353095..53521a0 100644 --- a/drawer/drawer.go +++ b/drawer/drawer.go @@ -55,6 +55,7 @@ func (d *drawer) Clear() { for i := range d.img.Pix { d.img.Pix[i] = 0 } + d.dev.FlushByMask(0xFF) } func (d *drawer) Flush() { diff --git a/drawer/font.go b/drawer/font.go index 418d089..fddc389 100644 --- a/drawer/font.go +++ b/drawer/font.go @@ -332,4 +332,23 @@ func (d *drawer) PutText(x0, y0 int, text string) { d.putChar(x, y0, c) 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) } diff --git a/go.mod b/go.mod index 52c0bfa..a171708 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module gitea.unprism.ru/yotia/display-test go 1.22.5 require ( + gitea.unprism.ru/KRBL/mpu v0.1.0 gitea.unprism.ru/KRBL/sim-modem v0.1.7 github.com/stianeikeland/go-rpio/v4 v4.6.0 golang.org/x/text v0.17.0 @@ -14,5 +15,6 @@ require ( require ( github.com/creack/goselect v0.1.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 ) diff --git a/main.go b/main.go index 6764580..7cea45b 100644 --- a/main.go +++ b/main.go @@ -4,11 +4,14 @@ import ( "context" "fmt" "log" + "math/rand" "os" "os/signal" "syscall" "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/display" "gitea.unprism.ru/yotia/display-test/drawer" @@ -20,6 +23,8 @@ const ( displayUpdateTimeout = 10 * time.Millisecond ) +type _ = api.StatusUpdater + func main() { log.Println("CGSG forever!!!") @@ -43,43 +48,6 @@ var d drawer.Drawer 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 { // Display init var err error @@ -101,23 +69,26 @@ func Init(ctx context.Context) error { time.Sleep(time.Second) - pb, err := components.NewProgressBar(d, 0, drawer.LineH*3, d.W(), drawer.LineH) - - time.Sleep(time.Second) + pbc, err := components.NewProgressBar(d, 0, drawer.LineH*3, d.W(), drawer.LineH) 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 { case <-ctx.Done(): return nil - case <-time.After(10 * time.Millisecond): - pb.SetProgress(float64(x) / 100) - + case <-time.After(time.Duration(rand.Int()%1000) * time.Millisecond): } + pb.Checkpoint() } - pb.Clear() + time.Sleep(400 * time.Millisecond) + + pb.GetBar().Clear() pb.Close() + d.FillBar(0, drawer.LineH*2, d.W(), drawer.LineH*3, 0) d.PutText(1, drawer.LineH*2, "Done") d.Flush() @@ -195,15 +166,39 @@ func Close() { func mainE(ctx context.Context) error { logger = log.New(os.Stdout, "main : ", log.LstdFlags) // - if err := Init(ctx); err != nil { - return err - } - //if err := dev.Test(ctx); err != nil { + //if err := Init(ctx); err != nil { // return err //} - defer Close() + ////if err := dev.Test(ctx); err != nil { + //// return err + ////} + //defer Close() // if err := MainLoop(ctx); err != nil { // 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 }