163 lines
3.3 KiB
Go
163 lines
3.3 KiB
Go
package fbdraw
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// Screen represents a single screen in the carousel
|
|
type Screen struct {
|
|
Name string
|
|
Generator FrameGenerator
|
|
ticker *time.Ticker
|
|
stop chan struct{}
|
|
}
|
|
|
|
// Carousel manages rotating between multiple screens
|
|
type Carousel struct {
|
|
display FramebufferDisplay
|
|
screens []*Screen
|
|
currentIndex int
|
|
rotationInterval time.Duration
|
|
|
|
mu sync.Mutex
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
wg sync.WaitGroup
|
|
}
|
|
|
|
// NewCarousel creates a new carousel
|
|
func NewCarousel(display FramebufferDisplay, rotationInterval time.Duration) *Carousel {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
return &Carousel{
|
|
display: display,
|
|
screens: make([]*Screen, 0),
|
|
currentIndex: 0,
|
|
rotationInterval: rotationInterval,
|
|
ctx: ctx,
|
|
cancel: cancel,
|
|
}
|
|
}
|
|
|
|
// AddScreen adds a new screen to the carousel
|
|
func (c *Carousel) AddScreen(name string, generator FrameGenerator) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
screen := &Screen{
|
|
Name: name,
|
|
Generator: generator,
|
|
stop: make(chan struct{}),
|
|
}
|
|
|
|
c.screens = append(c.screens, screen)
|
|
}
|
|
|
|
// Run starts the carousel
|
|
func (c *Carousel) Run() error {
|
|
if len(c.screens) == 0 {
|
|
return fmt.Errorf("no screens added to carousel")
|
|
}
|
|
|
|
// Start rotation timer
|
|
rotationTicker := time.NewTicker(c.rotationInterval)
|
|
defer rotationTicker.Stop()
|
|
|
|
// Start with first screen
|
|
if err := c.activateScreen(0); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Main loop
|
|
for {
|
|
select {
|
|
case <-c.ctx.Done():
|
|
return c.ctx.Err()
|
|
|
|
case <-rotationTicker.C:
|
|
// Move to next screen
|
|
c.mu.Lock()
|
|
nextIndex := (c.currentIndex + 1) % len(c.screens)
|
|
c.mu.Unlock()
|
|
|
|
if err := c.activateScreen(nextIndex); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Stop stops the carousel
|
|
func (c *Carousel) Stop() {
|
|
c.cancel()
|
|
c.wg.Wait()
|
|
}
|
|
|
|
// activateScreen switches to the specified screen
|
|
func (c *Carousel) activateScreen(index int) error {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
// Stop current screen if any
|
|
if c.currentIndex >= 0 && c.currentIndex < len(c.screens) {
|
|
close(c.screens[c.currentIndex].stop)
|
|
if c.screens[c.currentIndex].ticker != nil {
|
|
c.screens[c.currentIndex].ticker.Stop()
|
|
}
|
|
}
|
|
|
|
// Wait for current screen to stop
|
|
c.wg.Wait()
|
|
|
|
// Start new screen
|
|
c.currentIndex = index
|
|
screen := c.screens[index]
|
|
|
|
// Calculate frame interval
|
|
fps := screen.Generator.FramesPerSecond()
|
|
if fps <= 0 {
|
|
fps = 1 // Default to 1 FPS if invalid
|
|
}
|
|
frameInterval := time.Duration(float64(time.Second) / fps)
|
|
|
|
// Create new stop channel and ticker
|
|
screen.stop = make(chan struct{})
|
|
screen.ticker = time.NewTicker(frameInterval)
|
|
|
|
// Start frame generation goroutine
|
|
c.wg.Add(1)
|
|
go c.runScreen(screen)
|
|
|
|
return nil
|
|
}
|
|
|
|
// runScreen runs a single screen's frame generation loop
|
|
func (c *Carousel) runScreen(screen *Screen) {
|
|
defer c.wg.Done()
|
|
|
|
// Get display size
|
|
width, height := c.display.Size()
|
|
grid := NewCharGrid(width, height)
|
|
|
|
// Generate first frame immediately
|
|
if err := screen.Generator.GenerateFrame(grid); err == nil {
|
|
_ = c.display.Write(grid)
|
|
}
|
|
|
|
// Frame generation loop
|
|
for {
|
|
select {
|
|
case <-screen.stop:
|
|
return
|
|
|
|
case <-screen.ticker.C:
|
|
if err := screen.Generator.GenerateFrame(grid); err == nil {
|
|
_ = c.display.Write(grid)
|
|
}
|
|
}
|
|
}
|
|
}
|