diff --git a/components/components.go b/components/components.go new file mode 100644 index 0000000..b297433 --- /dev/null +++ b/components/components.go @@ -0,0 +1,20 @@ +package components + +// Some general types + +type rect struct { + x int + y int + w int + h int +} + +type RectGetter interface { + GetPos() (int, int) + GetSize() (int, int) +} + +type RectSetter interface { + SetPos(x, y int) + SetSize(w, h int) +} diff --git a/components/progressbar.go b/components/progressbar.go new file mode 100644 index 0000000..acc9cad --- /dev/null +++ b/components/progressbar.go @@ -0,0 +1,117 @@ +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 + progress float64 + + rect rect // Rect +} + +type ProgressBar interface { + RectGetter + + 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 +// Now the only way to chage shape is to recreate component +func NewProgressBar(drawer drawer.Drawer, x, y, w, h int) (ProgressBar, error) { + if err := drawer.GetDisplay().IsReady(); err != nil { + return nil, fmt.Errorf("display is ready: %w", err) + } + + // Check for correct sizes + if w < 4 { + return nil, fmt.Errorf("invalid width: below 4") + } + if h < 4 { + return nil, fmt.Errorf("invalid height: below 4") + } + pb := progressBar{ + drawer: drawer, + rect: rect{x, y, w, h}, + } + + pb.updateMask() + pb.Draw() + pb.Flush() + + 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) Close() error { + return nil +} + +func (pb *progressBar) Clear() { + pb.drawer.FillBar(pb.rect.x, pb.rect.y, pb.rect.x+pb.rect.w, pb.rect.y+pb.rect.h, 0x00) +} + +// Draw whole bar +func (pb *progressBar) Draw() { + // Shape: + // Bounds - 1px + // Padding - 1px + // Progress - the rest space + 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() +} + +// Draw only bar(without frames) +func (pb *progressBar) drawBar() { + w := int(float64(pb.rect.w-4) * pb.progress) + + pb.drawer.FillBar(pb.rect.x+2, pb.rect.y+2, pb.rect.x+2+w, pb.rect.y+pb.rect.h-2, 0xFF) +} + +func (pb *progressBar) SetProgress(coef float64) { + pb.progress = max(0, min(1, coef)) + + pb.drawBar() + pb.Flush() +} + +func (pb *progressBar) GetPos() (int, int) { + return pb.rect.x, pb.rect.y +} + +func (pb *progressBar) GetSize() (int, int) { + return pb.rect.w, pb.rect.h +} diff --git a/display/display.go b/display/display.go index 1a37f24..2c51e1a 100644 --- a/display/display.go +++ b/display/display.go @@ -9,7 +9,11 @@ import ( ) type Display interface { + IsReady() error // Nil if it is ready, error with some info otherwise + Flush(crystal, page byte) + FlushByMask(mask uint32) + GetFlushMaskBit(crystal, page byte) uint32 // Image functions GetImg() *image.Gray @@ -17,7 +21,7 @@ type Display interface { UnlockImg() GetBounds() image.Rectangle - Test(ctx context.Context) error + Test(ctx context.Context) error // DEBUG ONLY io.Closer } diff --git a/display/mt-12232a.go b/display/mt-12232a.go index 2c392f2..293efc4 100644 --- a/display/mt-12232a.go +++ b/display/mt-12232a.go @@ -19,11 +19,16 @@ const ( mt12232aW = 122 mt12232aH = 32 flushChanCap = 24 // Flush channel capacity + + flushUpdateTimeout = 5 * time.Millisecond ) type displayMt12232a struct { logger *log.Logger + // Some state flags + isTurnedOn bool + // Image img *image.Gray imgMutex sync.Mutex @@ -145,6 +150,14 @@ func (d *displayMt12232a) powerOn() error { if d.dev.ReadStatus(1)&0x20 != 0 { return fmt.Errorf("Right cristal is off") } + d.isTurnedOn = true + return nil +} + +func (d *displayMt12232a) IsReady() error { + if !d.isTurnedOn { + return fmt.Errorf("display is turned off") + } return nil } @@ -217,12 +230,22 @@ func (d *displayMt12232a) Flush(crystal, page byte) { d.pagesFlushFlags.Store(d.pagesFlushFlags.Load() | bit) } +func (d *displayMt12232a) FlushByMask(mask uint32) { + // !!! TODO Need to update GO to 1.23 to use .Or !!! + d.pagesFlushFlags.Store(d.pagesFlushFlags.Load() | mask) +} + +func (d *displayMt12232a) GetFlushMaskBit(crystal, page byte) uint32 { + return uint32(1 << (crystal*4 + page)) +} + func (d *displayMt12232a) Close() error { if d.flushCancel != nil { d.flushCancel() d.flushCancel = nil <-d.flushDone } + d.isTurnedOn = false return d.dev.Close() } @@ -233,10 +256,10 @@ func (d *displayMt12232a) flushLoop(ctx context.Context) { case <-ctx.Done(): close(d.flushDone) return - case <-time.After(time.Millisecond): + case <-time.After(flushUpdateTimeout): forUpdate := d.pagesFlushFlags.Swap(0) checkBit := uint32(1) - st := time.Now() + //st := time.Now() d.LockImg() for p := byte(0); p < 4; p++ { if forUpdate&(checkBit) != 0 { @@ -257,7 +280,7 @@ func (d *displayMt12232a) flushLoop(ctx context.Context) { } checkBit = checkBit << 1 } - d.logger.Printf("%08b - %s\n", forUpdate, time.Since(st)) + //d.logger.Printf("%08b - %s\n", forUpdate, time.Since(st)) for p := byte(0); p < 4; p++ { if forUpdate&(checkBit) != 0 { d.dev.WriteCode(1, (3-p)|0xB8) diff --git a/display/mt-12864a.go b/display/mt-12864a.go index 6ee96ed..f386d43 100644 --- a/display/mt-12864a.go +++ b/display/mt-12864a.go @@ -37,7 +37,8 @@ func newMt12864a(logger *log.Logger) (Display, error) { d.test() d.logger.Println("Draw finished") - return &d, nil + //return &d, nil + return nil, fmt.Errorf("IMPLEMENTATION COMMENTED") } func (d *displayMt12864a) Test(ctx context.Context) error { @@ -145,6 +146,9 @@ func (d *displayMt12864a) GetBounds() image.Rectangle { func (d *displayMt12864a) Flush(crystal, page byte) { } +func (d *displayMt12864a) FlushByMask(mask uint32) { +} + func (d *displayMt12864a) Close() error { return rpio.Close() } diff --git a/display/ssd1306.go b/display/ssd1306.go index ebd9dd6..a8f6a60 100644 --- a/display/ssd1306.go +++ b/display/ssd1306.go @@ -42,11 +42,13 @@ func newSsd1306(logger *log.Logger) (Display, error) { return nil, fmt.Errorf("create i2c: %w", err) } - return &displaySsd1306{ + _ = displaySsd1306{ logger: logger, bus: bus, dev: dev, - }, nil + } + + return nil, fmt.Errorf("IMPLEMENTATION COMMENTED") } func (d *displaySsd1306) GetBounds() image.Rectangle { @@ -61,6 +63,10 @@ func (d *displaySsd1306) Flush(crystal, page byte) { //return d.dev.Draw(img.Bounds(), d.img, image.Point{}) } +func (d *displaySsd1306) FlushByMask(mask uint32) { + //return d.dev.Draw(img.Bounds(), d.img, image.Point{}) +} + func (d *displaySsd1306) Close() error { if err := d.bus.Close(); err != nil { d.logger.Println("ERROR: close i2c bus:", err.Error()) diff --git a/drawer/bar.go b/drawer/bar.go index 03de10e..ac289e9 100644 --- a/drawer/bar.go +++ b/drawer/bar.go @@ -2,7 +2,7 @@ package drawer import "math" -func (d *drawer) FillBar(sx, sy, ex, ey, color int) { +func (d *drawer) FillBar(sx, sy, ex, ey int, color byte) { d.dev.LockImg() defer d.dev.UnlockImg() @@ -18,12 +18,12 @@ func (d *drawer) FillBar(sx, sy, ex, ey, color int) { addr := lineaddr le := addr + w for ; addr < le; addr++ { - d.img.Pix[addr] = 255 + d.img.Pix[addr] = color } } } -func (d *drawer) PutBar(sx, sy, ex, ey, color int) { +func (d *drawer) PutBar(sx, sy, ex, ey int, color byte) { d.dev.LockImg() defer d.dev.UnlockImg() @@ -36,7 +36,7 @@ func (d *drawer) PutBar(sx, sy, ex, ey, color int) { x1 := int(math.Min(float64(ex), float64(bounds.Dx()))) for addr := sy*bounds.Dx() + x0; addr < sy*bounds.Dx()+x1; addr++ { - d.img.Pix[addr] = 255 + d.img.Pix[addr] = color } } // Bottom @@ -45,7 +45,7 @@ func (d *drawer) PutBar(sx, sy, ex, ey, color int) { x1 := int(math.Min(float64(ex), float64(bounds.Dx()))) for addr := (ey-1)*bounds.Dx() + x0; addr < (ey-1)*bounds.Dx()+x1; addr++ { - d.img.Pix[addr] = 255 + d.img.Pix[addr] = color } } // Left @@ -54,7 +54,7 @@ func (d *drawer) PutBar(sx, sy, ex, ey, color int) { y1 := int(math.Min(float64(ey), float64(bounds.Dy()))) for addr := y0*bounds.Dx() + sx; addr < y1*bounds.Dx()+sx; addr += bounds.Dx() { - d.img.Pix[addr] = 255 + d.img.Pix[addr] = color } } // Right @@ -63,7 +63,7 @@ func (d *drawer) PutBar(sx, sy, ex, ey, color int) { y1 := int(math.Min(float64(ey), float64(bounds.Dy()))) for addr := y0*bounds.Dx() + (ex - 1); addr < y1*bounds.Dx()+(ex-1); addr += bounds.Dx() { - d.img.Pix[addr] = 255 + d.img.Pix[addr] = color } } } diff --git a/drawer/drawer.go b/drawer/drawer.go index 01de412..0353095 100644 --- a/drawer/drawer.go +++ b/drawer/drawer.go @@ -15,13 +15,16 @@ type drawer struct { type Drawer interface { GetDisplay() display.Display + W() int + H() int + Clear() Flush() PutText(x, y int, text string) - PutBar(x0, y0, x1, y1, color int) - FillBar(x0, y0, x1, y1, color int) + PutBar(x0, y0, x1, y1 int, color byte) + FillBar(x0, y0, x1, y1 int, color byte) } func New(dev display.Display) Drawer { @@ -37,6 +40,14 @@ func (d *drawer) GetDisplay() display.Display { return d.dev } +func (d *drawer) W() int { + return d.img.Rect.Dx() +} + +func (d *drawer) H() int { + return d.img.Rect.Dy() +} + func (d *drawer) Clear() { d.dev.LockImg() defer d.dev.UnlockImg() diff --git a/drawer/font.go b/drawer/font.go index 41eff4c..418d089 100644 --- a/drawer/font.go +++ b/drawer/font.go @@ -11,6 +11,7 @@ const ( LineGap = 1 FontCharW = 5 FontCharH = 7 + LineH = FontCharH + LineGap ) // Standard ASCII 5x7 font diff --git a/main.go b/main.go index 4f854fd..6764580 100644 --- a/main.go +++ b/main.go @@ -4,12 +4,12 @@ import ( "context" "fmt" "log" - "math" "os" "os/signal" "syscall" "time" + "gitea.unprism.ru/yotia/display-test/components" "gitea.unprism.ru/yotia/display-test/display" "gitea.unprism.ru/yotia/display-test/drawer" @@ -94,22 +94,35 @@ func Init(ctx context.Context) error { d.Clear() //d.FillBar(0, 0, 10, 10, 1) - stTime := time.Now() - for { + d.PutText(2, 0, "12345678901234567890") + d.PutText(2, drawer.LineH, "WWWWWWWWWWWWWWWWWWWWWWW") + d.PutText(1, drawer.LineH*2, "Progress bar") + d.Flush() + + time.Sleep(time.Second) + + pb, err := components.NewProgressBar(d, 0, drawer.LineH*3, d.W(), drawer.LineH) + + time.Sleep(time.Second) + if err != nil { + return fmt.Errorf("create progress bar: %w", err) + } + for x := 0; x < 100; x++ { select { case <-ctx.Done(): return nil - default: - t := float64(time.Since(stTime).Milliseconds()) / 1000 - x := math.Sin(t)*10 + 5 - y := math.Cos(t)*10 + 5 - d.Clear() - d.PutText(int(x), int(y), "CGSG forever") - stT := time.Now() - d.Flush() - logger.Println("Iteration time:", time.Since(stT)) + case <-time.After(10 * time.Millisecond): + pb.SetProgress(float64(x) / 100) + } } + pb.Clear() + pb.Close() + d.FillBar(0, drawer.LineH*2, d.W(), drawer.LineH*3, 0) + d.PutText(1, drawer.LineH*2, "Done") + d.Flush() + time.Sleep(100 * time.Millisecond) + //d.PutText(0, 0, "можно 4 строки впихнуть") //d.PutText(0, drawer.FontCharH+drawer.LineGap, "каждая буква 5x7") //d.PutText(0, (drawer.FontCharH+drawer.LineGap)*2, "+ русский через жопу")