Initial commit

This commit is contained in:
Sasha Koshka 2022-10-31 15:51:28 -04:00
commit fea20558ac
10 changed files with 631 additions and 0 deletions

89
application.go Normal file
View File

@ -0,0 +1,89 @@
package stone
import "time"
import "image/color"
type Application struct {
DamageBuffer
title string
backend Backend
config Config
}
func (application *Application) SetSize (width, height int) {
application.DamageBuffer.SetSize(width, height)
// application.updateWindowSize()
}
func (application *Application) SetTitle (title string) {
application.title = title
application.backend.SetTitle(title)
}
func (application *Application) Run (callback func (application *Application)) {
// default values for certain parameters
width, height := application.Size()
if width < 1 { width = 80 }
if height < 1 { height = 20 }
application.DamageBuffer.SetSize(width, height)
// TODO: load these from a file
application.config.colors = [4]color.Color {
color.RGBA { R: 0x00, G: 0x00, B: 0x00, A: 0xFF },
color.RGBA { R: 0xFF, G: 0xFF, B: 0xFF, A: 0xFF },
color.RGBA { R: 0x00, G: 0x00, B: 0x00, A: 0xFF },
color.RGBA { R: 0xFF, G: 0xFF, B: 0xFF, A: 0xFF },
}
application.backend.Run(callback)
}
func (application *Application) Await (timeout time.Duration) (keepRunning bool) {
keepRunning = application.Await(timeout)
return
}
func (application *Application) Poll () (keepRunning bool) {
keepRunning = application.Poll()
return
}
func (application *Application) Title () (title string) {
title = application.title
return
}
func (application *Application) Config () (config *Config) {
config = &application.config
return
}
// func (application *Application) Resized () (resized bool) {
// resized = application.boundsDirty
// return
// }
// // updateWindowSize updates the window size according to the buffer size.
// func (application *Application) updateWindowSize () {
// if application.window == nil { return }
// application.window.SetBounds(application.calculateWindowSize())
// }
// updateBufferSize updats the buffer size according to the window size.
// func (application *Application) updateBufferSize () {
// if application.window == nil {
// panic("call to application.updateBufferSize before window exists")
// }
//
// windowBounds := application.window.Bounds().Max
//
// if windowBounds != application.previousBounds {
// application.previousBounds = windowBounds
// application.boundsDirty = true
// application.DamageBuffer.SetSize (
// int(windowBounds.X) / application.metrics.cellWidth,
// int(windowBounds.Y) / application.metrics.cellHeight)
// }
// }

41
backend.go Normal file
View File

@ -0,0 +1,41 @@
package stone
import "time"
import "errors"
type Backend interface {
Run (callback func (application *Application)) ()
Await (timeout time.Duration) (keepRunning bool)
Poll () (keepRunning bool)
SetTitle (title string)
JustPressed (button Button) (pressed bool)
JustReleased (button Button) (released bool)
Pressed (button Button) (pressed bool)
Repeated (button Button) (repeated bool)
Typed () (text string)
Resized () (resized bool)
}
type BackendFactory func (application *Application) (backend Backend, err error)
var factories []BackendFactory
func RegisterBackend (factory BackendFactory) {
factories = append(factories, factory)
}
func instantiateBackend (application *Application) (backend Backend, err error) {
// find a suitable backend
for _, factory := range factories {
backend, err = factory(application)
if err == nil && backend != nil { return }
}
// if none were found, but there was no error produced, produce an
// error
if err == nil {
err = errors.New("no available backends")
}
return
}

146
backends/pixel/pixel.go Normal file
View File

@ -0,0 +1,146 @@
package pixel
import "time"
import "golang.org/x/image/font"
import "github.com/faiface/pixel"
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
previousBounds pixel.Vec
fontFace font.Face
application *stone.Application
config *stone.Config
metrics struct {
cellWidth int
cellHeight 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 () {
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.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) draw () {
didDrawing := false
if backend.boundsDirty {
backend.window.Clear (
backend.config.Color(stone.ColorApplication))
backend.boundsDirty = false
didDrawing = true
} else {
// TODO: clear out dirty cells before drawing them (we don't
// want to clear them out if we have already just cleared
// everything out)
}
// TODO: draw dirty cells.
width, height := backend.application.Size()
for x := 0; x < width; x ++ {
for y := 0; y < height; y ++ {
clean := backend.application.Clean(x, y)
// cell := application.content[index]
if clean { continue }
// draw cell
didDrawing = true // TODO: set didDrawing up there ^
backend.application.MarkClean(x, y)
}
}
if didDrawing {
backend.window.SwapBuffers()
}
}
func (backend *Backend) processEvents () {
}
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)
}

109
buffer.go Normal file
View File

@ -0,0 +1,109 @@
package stone
type Color uint8
const (
ColorBackground Color = 0x0
ColorSelection Color = 0x1
ColorForeground Color = 0x2
ColorApplication Color = 0x3
)
type Style uint8
const (
StyleNormal Style = iota
StyleBold Style = iota >> 1
StyleItalic
StyleBoldItalic Style = StyleBold | StyleItalic
)
type Cell struct {
color Color
style Style
content rune
}
func (cell Cell) Color (color Color) {
color = cell.color
return
}
func (cell Cell) Style (style Style) {
style = cell.style
return
}
func (cell Cell) Rune (content rune) {
content = cell.content
return
}
type Buffer struct {
content []Cell
width int
height int
}
func (buffer *Buffer) Size () (width, height int) {
width = buffer.width
height = buffer.height
return
}
func (buffer *Buffer) SetSize (width, height int) {
buffer.width = width
buffer.height = height
buffer.content = make([]Cell, width * height)
}
func (buffer *Buffer) Cell (x, y int) (cell Cell) {
cell = buffer.content[x + y * buffer.width]
return
}
func (buffer *Buffer) SetColor (x, y int, color Color) {
buffer.content[x + y * buffer.width].color = color
}
func (buffer *Buffer) SetStyle (x, y int, style Style) {
buffer.content[x + y * buffer.width].style = style
}
func (buffer *Buffer) SetRune (x, y int, content rune) {
buffer.content[x + y * buffer.width].content = content
}
type DamageBuffer struct {
Buffer
clean []bool
}
func (buffer *DamageBuffer) SetSize (width, height int) {
buffer.Buffer.SetSize(width, height)
buffer.clean = make([]bool, width * height)
}
func (buffer *DamageBuffer) SetColor (x, y int, color Color) {
buffer.Buffer.SetColor(x, y, color)
buffer.clean[x + y * buffer.width] = false
}
func (buffer *DamageBuffer) SetStyle (x, y int, style Style) {
buffer.Buffer.SetStyle(x, y, style)
buffer.clean[x + y * buffer.width] = false
}
func (buffer *DamageBuffer) SetRune (x, y int, content rune) {
buffer.Buffer.SetRune(x, y, content)
buffer.clean[x + y * buffer.width] = false
}
func (buffer *DamageBuffer) Clean (x, y int) (clean bool) {
clean = buffer.clean[x + y * buffer.width]
return
}
func (buffer *DamageBuffer) MarkClean (x, y int) {
buffer.clean[x + y * buffer.width] = true
}

30
config.go Normal file
View File

@ -0,0 +1,30 @@
package stone
import "image/color"
type Config struct {
colors [4]color.Color
padding int
fontSize int
fontName string
}
func (config *Config) Color (index Color) (value color.Color) {
value = config.colors[index]
return
}
func (config *Config) Padding () (padding int) {
padding = config.padding
return
}
func (config *Config) FontSize () (fontSize int) {
fontSize = config.fontSize
return
}
func (config *Config) FontName () (fontName string) {
fontName = config.fontName
return
}

27
examples/hello/main.go Normal file
View File

@ -0,0 +1,27 @@
package main
import "git.tebibyte.media/sashakoshka/stone"
import _ "git.tebibyte.media/sashakoshka/stone/backends/pixel"
func main () {
application := stone.Application { }
application.Run(run)
}
func run (application *stone.Application) {
for application.Await(0) {
if application.Resized() {
application.SetRune(0, 0, 'h')
application.SetRune(1, 0, 'e')
application.SetRune(2, 0, 'l')
application.SetRune(3, 0, 'l')
application.SetRune(4, 0, 'o')
application.SetRune(5, 0, 'r')
application.SetRune(6, 0, 'l')
application.SetRune(7, 0, 'd')
application.SetRune(8, 0, '!')
application.SetRune(4, 4, ':')
application.SetRune(5, 4, '3')
}
}
}

15
go.mod Normal file
View File

@ -0,0 +1,15 @@
module git.tebibyte.media/sashakoshka/stone
go 1.19
require github.com/faiface/pixel v0.10.0
require (
github.com/faiface/glhf v0.0.0-20181018222622-82a6317ac380 // indirect
github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3 // indirect
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 // indirect
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72 // indirect
github.com/go-gl/mathgl v0.0.0-20190416160123-c4601bc793c7 // indirect
github.com/pkg/errors v0.8.1 // indirect
golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff // indirect
)

26
go.sum Normal file
View File

@ -0,0 +1,26 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/faiface/glhf v0.0.0-20181018222622-82a6317ac380 h1:FvZ0mIGh6b3kOITxUnxS3tLZMh7yEoHo75v3/AgUqg0=
github.com/faiface/glhf v0.0.0-20181018222622-82a6317ac380/go.mod h1:zqnPFFIuYFFxl7uH2gYByJwIVKG7fRqlqQCbzAnHs9g=
github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3 h1:baVdMKlASEHrj19iqjARrPbaRisD7EuZEVJj6ZMLl1Q=
github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3/go.mod h1:VEPNJUlxl5KdWjDvz6Q1l+rJlxF2i6xqDeGuGAxa87M=
github.com/faiface/pixel v0.10.0 h1:EHm3ZdQw2Ck4y51cZqFfqQpwLqNHOoXwbNEc9Dijql0=
github.com/faiface/pixel v0.10.0/go.mod h1:lU0YYcW77vL0F1CG8oX51GXurymL45MXd57otHNLK7A=
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 h1:SCYMcCJ89LjRGwEa0tRluNRiMjZHalQZrVrvTbPh+qw=
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72 h1:b+9H1GAsx5RsjvDFLoS5zkNBzIQMuVKUYQDmxU3N5XE=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/mathgl v0.0.0-20190416160123-c4601bc793c7 h1:THttjeRn1iiz69E875U6gAik8KTWk/JYAHoSVpUxBBI=
github.com/go-gl/mathgl v0.0.0-20190416160123-c4601bc793c7/go.mod h1:yhpkQzEiH9yPyxDUGzkmgScbaBVlhC06qodikEM0ZwQ=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff h1:+2zgJKVDVAz/BWSsuniCmU1kLCjL88Z8/kv39xCI9NQ=
golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

146
input.go Normal file
View File

@ -0,0 +1,146 @@
package stone
// FIXME: do not require import of glfw. this may require linking it, and also
// including the glfw go lib in the binary. horrible. we need to copy the key
// codes here ourselves.
import "github.com/go-gl/glfw/v3.3/glfw"
type Button int
const (
MouseButton1 Button = Button(glfw.MouseButton1)
MouseButton2 Button = Button(glfw.MouseButton2)
MouseButton3 Button = Button(glfw.MouseButton3)
MouseButton4 Button = Button(glfw.MouseButton4)
MouseButton5 Button = Button(glfw.MouseButton5)
MouseButton6 Button = Button(glfw.MouseButton6)
MouseButton7 Button = Button(glfw.MouseButton7)
MouseButton8 Button = Button(glfw.MouseButton8)
MouseButtonLast Button = Button(glfw.MouseButtonLast)
MouseButtonLeft Button = Button(glfw.MouseButtonLeft)
MouseButtonRight Button = Button(glfw.MouseButtonRight)
MouseButtonMiddle Button = Button(glfw.MouseButtonMiddle)
KeyUnknown Button = Button(glfw.KeyUnknown)
KeySpace Button = Button(glfw.KeySpace)
KeyApostrophe Button = Button(glfw.KeyApostrophe)
KeyComma Button = Button(glfw.KeyComma)
KeyMinus Button = Button(glfw.KeyMinus)
KeyPeriod Button = Button(glfw.KeyPeriod)
KeySlash Button = Button(glfw.KeySlash)
Key0 Button = Button(glfw.Key0)
Key1 Button = Button(glfw.Key1)
Key2 Button = Button(glfw.Key2)
Key3 Button = Button(glfw.Key3)
Key4 Button = Button(glfw.Key4)
Key5 Button = Button(glfw.Key5)
Key6 Button = Button(glfw.Key6)
Key7 Button = Button(glfw.Key7)
Key8 Button = Button(glfw.Key8)
Key9 Button = Button(glfw.Key9)
KeySemicolon Button = Button(glfw.KeySemicolon)
KeyEqual Button = Button(glfw.KeyEqual)
KeyA Button = Button(glfw.KeyA)
KeyB Button = Button(glfw.KeyB)
KeyC Button = Button(glfw.KeyC)
KeyD Button = Button(glfw.KeyD)
KeyE Button = Button(glfw.KeyE)
KeyF Button = Button(glfw.KeyF)
KeyG Button = Button(glfw.KeyG)
KeyH Button = Button(glfw.KeyH)
KeyI Button = Button(glfw.KeyI)
KeyJ Button = Button(glfw.KeyJ)
KeyK Button = Button(glfw.KeyK)
KeyL Button = Button(glfw.KeyL)
KeyM Button = Button(glfw.KeyM)
KeyN Button = Button(glfw.KeyN)
KeyO Button = Button(glfw.KeyO)
KeyP Button = Button(glfw.KeyP)
KeyQ Button = Button(glfw.KeyQ)
KeyR Button = Button(glfw.KeyR)
KeyS Button = Button(glfw.KeyS)
KeyT Button = Button(glfw.KeyT)
KeyU Button = Button(glfw.KeyU)
KeyV Button = Button(glfw.KeyV)
KeyW Button = Button(glfw.KeyW)
KeyX Button = Button(glfw.KeyX)
KeyY Button = Button(glfw.KeyY)
KeyZ Button = Button(glfw.KeyZ)
KeyLeftBracket Button = Button(glfw.KeyLeftBracket)
KeyBackslash Button = Button(glfw.KeyBackslash)
KeyRightBracket Button = Button(glfw.KeyRightBracket)
KeyGraveAccent Button = Button(glfw.KeyGraveAccent)
KeyWorld1 Button = Button(glfw.KeyWorld1)
KeyWorld2 Button = Button(glfw.KeyWorld2)
KeyEscape Button = Button(glfw.KeyEscape)
KeyEnter Button = Button(glfw.KeyEnter)
KeyTab Button = Button(glfw.KeyTab)
KeyBackspace Button = Button(glfw.KeyBackspace)
KeyInsert Button = Button(glfw.KeyInsert)
KeyDelete Button = Button(glfw.KeyDelete)
KeyRight Button = Button(glfw.KeyRight)
KeyLeft Button = Button(glfw.KeyLeft)
KeyDown Button = Button(glfw.KeyDown)
KeyUp Button = Button(glfw.KeyUp)
KeyPageUp Button = Button(glfw.KeyPageUp)
KeyPageDown Button = Button(glfw.KeyPageDown)
KeyHome Button = Button(glfw.KeyHome)
KeyEnd Button = Button(glfw.KeyEnd)
KeyCapsLock Button = Button(glfw.KeyCapsLock)
KeyScrollLock Button = Button(glfw.KeyScrollLock)
KeyNumLock Button = Button(glfw.KeyNumLock)
KeyPrintScreen Button = Button(glfw.KeyPrintScreen)
KeyPause Button = Button(glfw.KeyPause)
KeyF1 Button = Button(glfw.KeyF1)
KeyF2 Button = Button(glfw.KeyF2)
KeyF3 Button = Button(glfw.KeyF3)
KeyF4 Button = Button(glfw.KeyF4)
KeyF5 Button = Button(glfw.KeyF5)
KeyF6 Button = Button(glfw.KeyF6)
KeyF7 Button = Button(glfw.KeyF7)
KeyF8 Button = Button(glfw.KeyF8)
KeyF9 Button = Button(glfw.KeyF9)
KeyF10 Button = Button(glfw.KeyF10)
KeyF11 Button = Button(glfw.KeyF11)
KeyF12 Button = Button(glfw.KeyF12)
KeyF13 Button = Button(glfw.KeyF13)
KeyF14 Button = Button(glfw.KeyF14)
KeyF15 Button = Button(glfw.KeyF15)
KeyF16 Button = Button(glfw.KeyF16)
KeyF17 Button = Button(glfw.KeyF17)
KeyF18 Button = Button(glfw.KeyF18)
KeyF19 Button = Button(glfw.KeyF19)
KeyF20 Button = Button(glfw.KeyF20)
KeyF21 Button = Button(glfw.KeyF21)
KeyF22 Button = Button(glfw.KeyF22)
KeyF23 Button = Button(glfw.KeyF23)
KeyF24 Button = Button(glfw.KeyF24)
KeyF25 Button = Button(glfw.KeyF25)
KeyKP0 Button = Button(glfw.KeyKP0)
KeyKP1 Button = Button(glfw.KeyKP1)
KeyKP2 Button = Button(glfw.KeyKP2)
KeyKP3 Button = Button(glfw.KeyKP3)
KeyKP4 Button = Button(glfw.KeyKP4)
KeyKP5 Button = Button(glfw.KeyKP5)
KeyKP6 Button = Button(glfw.KeyKP6)
KeyKP7 Button = Button(glfw.KeyKP7)
KeyKP8 Button = Button(glfw.KeyKP8)
KeyKP9 Button = Button(glfw.KeyKP9)
KeyKPDecimal Button = Button(glfw.KeyKPDecimal)
KeyKPDivide Button = Button(glfw.KeyKPDivide)
KeyKPMultiply Button = Button(glfw.KeyKPMultiply)
KeyKPSubtract Button = Button(glfw.KeyKPSubtract)
KeyKPAdd Button = Button(glfw.KeyKPAdd)
KeyKPEnter Button = Button(glfw.KeyKPEnter)
KeyKPEqual Button = Button(glfw.KeyKPEqual)
KeyLeftShift Button = Button(glfw.KeyLeftShift)
KeyLeftControl Button = Button(glfw.KeyLeftControl)
KeyLeftAlt Button = Button(glfw.KeyLeftAlt)
KeyLeftSuper Button = Button(glfw.KeyLeftSuper)
KeyRightShift Button = Button(glfw.KeyRightShift)
KeyRightControl Button = Button(glfw.KeyRightControl)
KeyRightAlt Button = Button(glfw.KeyRightAlt)
KeyRightSuper Button = Button(glfw.KeyRightSuper)
KeyMenu Button = Button(glfw.KeyMenu)
KeyLast Button = Button(glfw.KeyLast)
)

2
stone.go Normal file
View File

@ -0,0 +1,2 @@
package stone