From 3456d4e862fa283b8d8c58a759e2a944f6924f3c Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Wed, 9 Nov 2022 01:04:53 -0500 Subject: [PATCH 1/7] Got rid of old event getters --- application.go | 72 -------------------------------------------------- 1 file changed, 72 deletions(-) diff --git a/application.go b/application.go index 4bd40c9..0d40a9c 100644 --- a/application.go +++ b/application.go @@ -1,6 +1,5 @@ package stone -import "time" import "image/color" // Application represents an application. @@ -12,12 +11,6 @@ type Application struct { config Config } -// SetSize sets the application's buffer size. This may or may not change the -// size of the window, depending on the backend used. -func (application *Application) SetSize (width, height int) { - application.DamageBuffer.SetSize(width, height) -} - // SetTitle sets the application's title. If in a window, it will appear as the // window's name. func (application *Application) SetTitle (title string) { @@ -55,68 +48,3 @@ func (application *Application) Run ( return } - -// Await blocks until an event is recieved, or until the specified timeout has -// elapsed. If the timeout is zero, it will wait forever. This function returns -// true if the backend is still active, and false if it has closed. -func (application *Application) Await (timeout time.Duration) (keepRunning bool) { - keepRunning = application.backend.Await(timeout) - return -} - -// Poll updates the window and checks for new events. This function returns true -// if the backend is still active, and false if it has closed. -func (application *Application) Poll () (keepRunning bool) { - keepRunning = application.backend.Poll() - return -} - -// Title returns the application's title. -func (application *Application) Title () (title string) { - title = application.title - return -} - -// Config returns a pointer to the application's configuration. -func (application *Application) Config () (config *Config) { - config = &application.config - return -} - -// JustPressed returns true if the specified button is pressed, but was not -// pressed the last time events were checked. -func (application *Application) JustPressed (button Button) (pressed bool) { - pressed = application.backend.JustPressed(button) - return -} - -// JustReleased returns true if the specified button -func (application *Application) JustReleased (button Button) (released bool) { - released = application.backend.JustReleased(button) - return -} - -func (application *Application) Pressed (button Button) (pressed bool) { - pressed = application.backend.Pressed(button) - return -} - -func (application *Application) Repeated (button Button) (repeated bool) { - repeated = application.backend.Repeated(button) - return -} - -func (application *Application) Typed () (text string) { - text = application.backend.Typed() - return -} - -func (application *Application) Resized () (resized bool) { - resized = application.backend.Resized() - return -} - -func (application *Application) MousePosition () (x, y int) { - x, y = application.backend.MousePosition() - return -} From b60d7518e05a7078b5f3812a3a111a351a78f913 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Wed, 9 Nov 2022 01:12:47 -0500 Subject: [PATCH 2/7] Add new event interface --- event.go | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 event.go diff --git a/event.go b/event.go new file mode 100644 index 0000000..3eba784 --- /dev/null +++ b/event.go @@ -0,0 +1,11 @@ +package application + +type Event interface { } + +type EventPress Button +type EventRelease Button +type EventResize struct { } +type EventMouseMove struct { + X int + Y int +} From 60269d554cefc2b0d44320538770c73880c0971d Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Wed, 9 Nov 2022 01:12:55 -0500 Subject: [PATCH 3/7] Updated backend interface to use new event interface --- backend.go | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/backend.go b/backend.go index 39561e3..8a43502 100644 --- a/backend.go +++ b/backend.go @@ -4,17 +4,8 @@ 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) - MousePosition () (x, y int) + Run () (chan(Event)) + SetTitle (title string) } type BackendFactory func (application *Application) (backend Backend, err error) From 11bdae78a0ce03eb5265ef57c27af5af5a29f77e Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Wed, 9 Nov 2022 01:13:44 -0500 Subject: [PATCH 4/7] Add icon setter to backend --- backend.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend.go b/backend.go index 8a43502..088aa01 100644 --- a/backend.go +++ b/backend.go @@ -1,11 +1,13 @@ package stone import "time" +import "image" import "errors" type Backend interface { Run () (chan(Event)) SetTitle (title string) + SetIcon (icons []image.Image) } type BackendFactory func (application *Application) (backend Backend, err error) From e7b5136ea671df19b21617e7630d5a5947449642 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Wed, 9 Nov 2022 01:18:56 -0500 Subject: [PATCH 5/7] Return channel from run method --- application.go | 10 +++++----- backend.go | 3 +-- event.go | 3 ++- examples/hello/main.go | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/application.go b/application.go index 0d40a9c..b8acbc4 100644 --- a/application.go +++ b/application.go @@ -18,13 +18,13 @@ func (application *Application) SetTitle (title string) { application.backend.SetTitle(title) } -// Run initializes the application, and then calls callback. Operations inside -// of callback are allowed to interact with the application. Depending on the -// backend used, this function may bind to the main thread. +// Run initializes the application, starts it, and then returns a channel that +// broadcasts events. If no suitable backend can be found, an error is returned. func (application *Application) Run ( callback func (application *Application), ) ( - err error, + channel chan(Event), + err error, ) { // default values for certain parameters width, height := application.Size() @@ -44,7 +44,7 @@ func (application *Application) Run ( application.backend, err = instantiateBackend(application) if err != nil { return } - application.backend.Run(callback) + channel = application.backend.Run() return } diff --git a/backend.go b/backend.go index 088aa01..336c1a0 100644 --- a/backend.go +++ b/backend.go @@ -1,11 +1,10 @@ package stone -import "time" import "image" import "errors" type Backend interface { - Run () (chan(Event)) + Run () (channel chan(Event)) SetTitle (title string) SetIcon (icons []image.Image) } diff --git a/event.go b/event.go index 3eba784..f60cff2 100644 --- a/event.go +++ b/event.go @@ -1,7 +1,8 @@ -package application +package stone type Event interface { } +type EventQuit struct { } type EventPress Button type EventRelease Button type EventResize struct { } diff --git a/examples/hello/main.go b/examples/hello/main.go index 0623cde..158116d 100644 --- a/examples/hello/main.go +++ b/examples/hello/main.go @@ -3,7 +3,7 @@ package main import "fmt" import "time" import "git.tebibyte.media/sashakoshka/stone" -import _ "git.tebibyte.media/sashakoshka/stone/backends/pixel" +// import _ "git.tebibyte.media/sashakoshka/stone/backends/x" func main () { application := stone.Application { } From 0c5118b59ac0c8b102f7334f075cbc3a60cbc738 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Wed, 9 Nov 2022 15:52:49 -0500 Subject: [PATCH 6/7] Added X backend stub --- application.go | 8 ++-- backend.go | 2 +- backends/x/x.go | 34 ++++++++++++++++ examples/hello/main.go | 88 ++++++++++++++++++++++++++++-------------- go.mod | 4 ++ go.sum | 9 +++++ 6 files changed, 111 insertions(+), 34 deletions(-) create mode 100644 backends/x/x.go diff --git a/application.go b/application.go index b8acbc4..525021b 100644 --- a/application.go +++ b/application.go @@ -20,9 +20,7 @@ func (application *Application) SetTitle (title string) { // Run initializes the application, starts it, and then returns a channel that // broadcasts events. If no suitable backend can be found, an error is returned. -func (application *Application) Run ( - callback func (application *Application), -) ( +func (application *Application) Run () ( channel chan(Event), err error, ) { @@ -44,7 +42,9 @@ func (application *Application) Run ( application.backend, err = instantiateBackend(application) if err != nil { return } - channel = application.backend.Run() + + channel = make(chan(Event)) + application.backend.Run(channel) return } diff --git a/backend.go b/backend.go index 336c1a0..a6b5b98 100644 --- a/backend.go +++ b/backend.go @@ -4,7 +4,7 @@ import "image" import "errors" type Backend interface { - Run () (channel chan(Event)) + Run (channel chan(Event)) SetTitle (title string) SetIcon (icons []image.Image) } diff --git a/backends/x/x.go b/backends/x/x.go new file mode 100644 index 0000000..67b1ab5 --- /dev/null +++ b/backends/x/x.go @@ -0,0 +1,34 @@ +package x + +import "image" + +import "github.com/jezek/xgbutil" +// import "github.com/jezek/xgbutil/ewmh" +// import "github.com/jezek/xgbutil/xevent" +import "github.com/jezek/xgbutil/xwindow" +import "github.com/jezek/xgbutil/xgraphics" + +import "git.tebibyte.media/sashakoshka/stone" + +type Backend struct { + connection *xgbutil.Conn + window *xwindow.Window + canvas *xgraphics.Image +} + +func (backend *Backend) Run (channel chan(stone.Event)) { + // TODO: setup + go backend.mainLoop(channel) +} + +func (backend *Backend) mainLoop (channel chan(stone.Event)) { + +} + +func (backend *Backend) SetTitle (title string) { + +} + +func (backend *Backend) SetIcon (icons []image.Image) { + +} diff --git a/examples/hello/main.go b/examples/hello/main.go index 158116d..456631f 100644 --- a/examples/hello/main.go +++ b/examples/hello/main.go @@ -1,31 +1,25 @@ package main +import "os" import "fmt" import "time" import "git.tebibyte.media/sashakoshka/stone" // import _ "git.tebibyte.media/sashakoshka/stone/backends/x" func main () { - application := stone.Application { } - err := application.Run(run) + application := &stone.Application { } + channel, err := application.Run() if err != nil { panic(err) } -} - -func run (application *stone.Application) { + currentTime := time.Time { } - frameDelay := time.Second / 2 - textBuffer := "" - + for { - typed := application.Typed() - textBuffer += typed - - shouldRender := - application.Resized() || - time.Since(currentTime) > frameDelay || - len(typed) > 0 - - if shouldRender { + event := <- channel + switch event.(type) { + case stone.EventQuit: + os.Exit(0) + + case stone.EventResize: currentTime = time.Now() application.ResetDot() @@ -43,18 +37,54 @@ func run (application *stone.Application) { application.SetRune(5, 1, ':') application.SetRune(6, 1, rune(second / 10 + 48)) application.SetRune(7, 1, rune(second % 10 + 48)) - - application.Dot.X = 0 - application.Dot.Y = 2 - fmt.Fprintln(application, textBuffer) - } - - if application.Pressed(stone.MouseButtonLeft) { - x, y := application.MousePosition() - application.SetRune(x, y, '#') - } - - if !application.Await(frameDelay) { break } } } + +// func run (application *stone.Application) { + // currentTime := time.Time { } + // frameDelay := time.Second / 2 + // textBuffer := "" + // + // for { + // typed := application.Typed() + // textBuffer += typed + // + // shouldRender := + // application.Resized() || + // time.Since(currentTime) > frameDelay || + // len(typed) > 0 + // + // if shouldRender { + // currentTime = time.Now() +// + // application.ResetDot() + // fmt.Fprintln(application, "hellorld!") +// + // hour := currentTime.Hour() + // minute := currentTime.Minute() + // second := currentTime.Second() +// + // application.SetRune(0, 1, rune(hour / 10 + 48)) + // application.SetRune(1, 1, rune(hour % 10 + 48)) + // application.SetRune(2, 1, ':') + // application.SetRune(3, 1, rune(minute / 10 + 48)) + // application.SetRune(4, 1, rune(minute % 10 + 48)) + // application.SetRune(5, 1, ':') + // application.SetRune(6, 1, rune(second / 10 + 48)) + // application.SetRune(7, 1, rune(second % 10 + 48)) +// + // application.Dot.X = 0 + // application.Dot.Y = 2 + // fmt.Fprintln(application, textBuffer) +// + // } +// + // if application.Pressed(stone.MouseButtonLeft) { + // x, y := application.MousePosition() + // application.SetRune(x, y, '#') + // } + // + // if !application.Await(frameDelay) { break } + // } +// } diff --git a/go.mod b/go.mod index adede01..7d11633 100644 --- a/go.mod +++ b/go.mod @@ -4,14 +4,18 @@ go 1.18 require ( github.com/faiface/pixel v0.10.0 + github.com/jezek/xgbutil v0.0.0-20210302171758-530099784e66 golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff ) require ( + github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298 // indirect + github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966 // indirect 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/jezek/xgb v1.1.0 // indirect github.com/pkg/errors v0.8.1 // indirect ) diff --git a/go.sum b/go.sum index 6f3f951..6390802 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,7 @@ +github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298 h1:1qlsVAQJXZHsaM8b6OLVo6muQUQd4CwkH/D3fnnbHXA= +github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298/go.mod h1:D+QujdIlUNfa0igpNMk6UIvlb6C252URs4yupRUV4lQ= +github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966 h1:lTG4HQym5oPKjL7nGs+csTgiDna685ZXjxijkne828g= +github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966/go.mod h1:Mid70uvE93zn9wgF92A/r5ixgnvX8Lh68fxp9KQBaI0= 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= @@ -12,7 +16,12 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72 h1:b+9H1GAsx5 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 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/jezek/xgb v1.1.0 h1:wnpxJzP1+rkbGclEkmwpVFQWpuE2PUGNUzP8SbfFobk= +github.com/jezek/xgb v1.1.0/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk= +github.com/jezek/xgbutil v0.0.0-20210302171758-530099784e66 h1:+wPhoJD8EH0/bXipIq8Lc2z477jfox9zkXPCJdhvHj8= +github.com/jezek/xgbutil v0.0.0-20210302171758-530099784e66/go.mod h1:KACeV+k6b+aoLTVrrurywEbu3UpqoQcQywj4qX8aQKM= 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= From c93ca17fe5b80b04f2f2782eb6fa6bdc834c81c8 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Wed, 9 Nov 2022 18:53:14 -0500 Subject: [PATCH 7/7] x backend has an event loop --- application.go | 8 ++- backends/x/x.go | 111 +++++++++++++++++++++++++++++++++++++---- examples/hello/main.go | 50 +------------------ 3 files changed, 110 insertions(+), 59 deletions(-) diff --git a/application.go b/application.go index 525021b..7fe44c4 100644 --- a/application.go +++ b/application.go @@ -44,7 +44,13 @@ func (application *Application) Run () ( if err != nil { return } channel = make(chan(Event)) - application.backend.Run(channel) + go application.backend.Run(channel) return } + +// Config returns a pointer to the application's configuration. +func (application *Application) Config () (config *Config) { + config = &application.config + return +} diff --git a/backends/x/x.go b/backends/x/x.go index 67b1ab5..517c8b3 100644 --- a/backends/x/x.go +++ b/backends/x/x.go @@ -4,25 +4,49 @@ import "image" import "github.com/jezek/xgbutil" // import "github.com/jezek/xgbutil/ewmh" -// import "github.com/jezek/xgbutil/xevent" +import "github.com/jezek/xgbutil/xevent" import "github.com/jezek/xgbutil/xwindow" import "github.com/jezek/xgbutil/xgraphics" import "git.tebibyte.media/sashakoshka/stone" type Backend struct { - connection *xgbutil.Conn - window *xwindow.Window - canvas *xgraphics.Image + application *stone.Application + config *stone.Config + connection *xgbutil.XUtil + window *xwindow.Window + canvas *xgraphics.Image + channel chan(stone.Event) + + ping struct { + before chan(struct { }) + after chan(struct { }) + quit chan(struct { }) + } + + metrics struct { + cellWidth int + cellHeight int + padding int + paddingX int + paddingY int + descent int + } } func (backend *Backend) Run (channel chan(stone.Event)) { - // TODO: setup - go backend.mainLoop(channel) -} - -func (backend *Backend) mainLoop (channel chan(stone.Event)) { + backend.channel = channel + for { + select { + case <- backend.ping.before: + <- backend.ping.after + + case <- backend.ping.quit: + backend.shutDown() + return + } + } } func (backend *Backend) SetTitle (title string) { @@ -32,3 +56,72 @@ func (backend *Backend) SetTitle (title string) { func (backend *Backend) SetIcon (icons []image.Image) { } + +func (backend *Backend) shutDown () { + backend.channel <- stone.EventQuit { } +} + +// calculateWindowSize calculates window bounds based on the internal buffer +// size. +func (backend *Backend) calculateWindowSize () (x, y int) { + width, height := backend.application.Size() + x = + width * backend.metrics.cellWidth + + backend.metrics.padding * 2 + y = + height * backend.metrics.cellHeight + + backend.metrics.padding * 2 + return +} + + +// factory instantiates an X backend. +func factory (application *stone.Application) (output stone.Backend, err error) { + backend := &Backend { + application: application, + config: application.Config(), + } + + // calculate metrics + // TODO: base these off of font metrics + backend.metrics.cellWidth = 8 + backend.metrics.cellHeight = 16 + backend.metrics.padding = + backend.config.Padding() * + backend.metrics.cellHeight + backend.metrics.paddingX = backend.metrics.padding + backend.metrics.paddingY = backend.metrics.padding + + // connect to X + backend.connection, err = xgbutil.NewConn() + if err != nil { return } + backend.window, err = xwindow.Generate(backend.connection) + if err != nil { return } + + // create the window + windowWidth, windowHeight := backend.calculateWindowSize() + backend.window.Create ( + backend.connection.RootWin(), + 0, 0, windowWidth, windowHeight, + 0) + backend.window.Map() + + // attatch graceful close handler + backend.window.WMGracefulClose (func (window *xwindow.Window) { + backend.window.Destroy() + backend.shutDown() + }) + + // start event loop + backend.ping.before, + backend.ping.after, + backend.ping.quit = xevent.MainPing(backend.connection) + + output = backend + return +} + +// init registers this backend when the program starts. +func init () { + stone.RegisterBackend(factory) +} diff --git a/examples/hello/main.go b/examples/hello/main.go index 456631f..1dd1f89 100644 --- a/examples/hello/main.go +++ b/examples/hello/main.go @@ -4,7 +4,7 @@ import "os" import "fmt" import "time" import "git.tebibyte.media/sashakoshka/stone" -// import _ "git.tebibyte.media/sashakoshka/stone/backends/x" +import _ "git.tebibyte.media/sashakoshka/stone/backends/x" func main () { application := &stone.Application { } @@ -40,51 +40,3 @@ func main () { } } } - -// func run (application *stone.Application) { - // currentTime := time.Time { } - // frameDelay := time.Second / 2 - // textBuffer := "" - // - // for { - // typed := application.Typed() - // textBuffer += typed - // - // shouldRender := - // application.Resized() || - // time.Since(currentTime) > frameDelay || - // len(typed) > 0 - // - // if shouldRender { - // currentTime = time.Now() -// - // application.ResetDot() - // fmt.Fprintln(application, "hellorld!") -// - // hour := currentTime.Hour() - // minute := currentTime.Minute() - // second := currentTime.Second() -// - // application.SetRune(0, 1, rune(hour / 10 + 48)) - // application.SetRune(1, 1, rune(hour % 10 + 48)) - // application.SetRune(2, 1, ':') - // application.SetRune(3, 1, rune(minute / 10 + 48)) - // application.SetRune(4, 1, rune(minute % 10 + 48)) - // application.SetRune(5, 1, ':') - // application.SetRune(6, 1, rune(second / 10 + 48)) - // application.SetRune(7, 1, rune(second % 10 + 48)) -// - // application.Dot.X = 0 - // application.Dot.Y = 2 - // fmt.Fprintln(application, textBuffer) -// - // } -// - // if application.Pressed(stone.MouseButtonLeft) { - // x, y := application.MousePosition() - // application.SetRune(x, y, '#') - // } - // - // if !application.Await(frameDelay) { break } - // } -// }