package pixel import "time" import "golang.org/x/image/font" import "github.com/faiface/pixel" import "github.com/faiface/pixel/text" import "github.com/faiface/pixel/imdraw" import "github.com/faiface/pixel/pixelgl" import "golang.org/x/image/font/basicfont" import "git.tebibyte.media/sashakoshka/stone" type Backend struct { window *pixelgl.Window boundsDirty bool windowBounds pixel.Vec fontFace font.Face application *stone.Application config *stone.Config backgroundStamper *imdraw.IMDraw fontAtlas *text.Atlas textDrawer *text.Text metrics struct { cellWidth int cellHeight int padding int paddingX int paddingY int } } func (backend *Backend) Run (callback func (application *stone.Application)) { if backend.fontFace == nil { backend.fontFace = basicfont.Face7x13 } faceMetrics := backend.fontFace.Metrics() backend.metrics.cellHeight = faceMetrics.Height.Round() // FIXME?: this might not be the best way to get the cell width faceAdvance, ok := backend.fontFace.GlyphAdvance('M') if ok { backend.metrics.cellWidth = faceAdvance.Round() } else { backend.metrics.cellWidth = backend.metrics.cellHeight / 2 } pixelgl.Run (func () { // construct the window, and all that var err error backend.window, err = pixelgl.NewWindow (pixelgl.WindowConfig { Resizable: true, Undecorated: true, VSync: true, NoIconify: true, Title: backend.application.Title(), Bounds: backend.calculateWindowSize(), }) backend.backgroundStamper = imdraw.New(nil) backend.fontAtlas = text.NewAtlas(backend.fontFace, text.ASCII) backend.textDrawer = text.New(pixel.V(0, 0), backend.fontAtlas) backend.Poll() if err != nil { panic(err.Error()) } callback(backend.application) }) } func (backend *Backend) Await (timeout time.Duration) (keepRunning bool) { if backend.window == nil { panic("call to Backend.Await before window exists") } backend.draw() backend.window.UpdateInputWait(timeout) backend.processEvents() keepRunning = !backend.window.Closed() return } func (backend *Backend) Poll () (keepRunning bool) { if backend.window == nil { panic("call to Backend.Poll before window exists") } backend.draw() backend.window.UpdateInput() backend.processEvents() keepRunning = !backend.window.Closed() return } func (backend *Backend) SetTitle (title string) { if backend.window != nil { backend.window.SetTitle(title) } } func (backend *Backend) JustPressed (button stone.Button) (pressed bool) { pressed = backend.window.JustPressed(pixelgl.Button(button)) return } func (backend *Backend) JustReleased (button stone.Button) (released bool) { released = backend.window.JustReleased(pixelgl.Button(button)) return } func (backend *Backend) Pressed (button stone.Button) (pressed bool) { pressed = backend.window.Pressed(pixelgl.Button(button)) return } func (backend *Backend) Repeated (button stone.Button) (repeated bool) { repeated = backend.window.Repeated(pixelgl.Button(button)) return } func (backend *Backend) Typed () (text string) { text = backend.window.Typed() return } func (backend *Backend) Resized () (resized bool) { resized = backend.boundsDirty return } func (backend *Backend) draw () { // didDrawing := false width, height := backend.application.Size() if backend.boundsDirty { backend.window.Clear ( backend.config.Color(stone.ColorApplication)) backend.boundsDirty = false // didDrawing = true } else { // clear out dirty cells before drawing them. this is in an else // block because if the bounds were dirty, we have already // cleared the entire screen. backend.backgroundStamper.Clear() backend.backgroundStamper.Color = backend.config.Color(stone.ColorApplication) for x := 0; x < width; x ++ { for y := 0; y < height; y ++ { clean := backend.application.Clean(x, y) if clean { continue } backend.backgroundStamper.Push ( backend.vectorAtPosition(x, y), backend.vectorAtPosition(x + 1, y + 1)) backend.backgroundStamper.Rectangle(1) // didDrawing = true } } backend.backgroundStamper.Draw(backend.window) } backend.textDrawer.Clear() backend.textDrawer.Color = backend.config.Color(stone.ColorForeground) for x := 0; x < width; x ++ { for y := 0; y < height; y ++ { clean := backend.application.Clean(x, y) if clean { continue } backend.application.MarkClean(x, y) cell := backend.application.Cell(x, y) content := cell.Rune() if content < 32 { continue } // draw cell backend.textDrawer.Dot = backend.vectorAtPosition(x, y + 1) backend.textDrawer.WriteRune(content) backend.textDrawer.Draw(backend.window, pixel.IM) } } backend.backgroundStamper.Clear() backend.backgroundStamper.Color = backend.config.Color(stone.ColorBackground) backend.backgroundStamper.Push ( backend.vectorAtPosition(0, 0), backend.vectorAtPosition(width, height)) backend.backgroundStamper.Rectangle(1) backend.backgroundStamper.Draw(backend.window) backend.window.SwapBuffers() } func (backend *Backend) processEvents () { newBounds := backend.window.Bounds().Max backend.boundsDirty = backend.windowBounds != newBounds backend.windowBounds = newBounds if backend.boundsDirty { // calculate padding backend.metrics.padding = backend.config.Padding() * backend.metrics.cellWidth deadArea := float64(backend.metrics.padding * 2) // calculate new width and height for buffer width := int ( (backend.windowBounds.X - deadArea) / float64(backend.metrics.cellWidth)) height := int ( (backend.windowBounds.Y - deadArea) / float64(backend.metrics.cellHeight)) backend.application.SetSize(width, height) // position buffer in center of screen frameWidth := width * backend.metrics.cellWidth frameHeight := height * backend.metrics.cellHeight backend.metrics.paddingX = (int(backend.windowBounds.X) - frameWidth) / 2 backend.metrics.paddingY = (int(backend.windowBounds.Y) - frameHeight) / 2 } } func (backend *Backend) vectorAtPosition (x, y int) (vector pixel.Vec) { vector = pixel.V ( float64 ( x * backend.metrics.cellWidth + backend.metrics.paddingX), backend.windowBounds.Y - float64 ( y * backend.metrics.cellHeight + backend.metrics.paddingY)) return } func (backend *Backend) calculateWindowSize () (bounds pixel.Rect) { width, height := backend.application.Size() bounds = pixel.R ( 0, 0, float64(width * backend.metrics.cellWidth), float64(height * backend.metrics.cellHeight)) return } func factory (application *stone.Application) (output stone.Backend, err error) { backend := &Backend { application: application, config: application.Config(), } output = backend return } func init () { stone.RegisterBackend(factory) }