From 01f43a03a4cf6e7bfe775b78f8b2117927aa2f6e Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Wed, 9 Nov 2022 19:07:40 -0500 Subject: [PATCH 01/33] A canvas is now created --- backends/x/x.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/backends/x/x.go b/backends/x/x.go index 517c8b3..a514a65 100644 --- a/backends/x/x.go +++ b/backends/x/x.go @@ -74,6 +74,11 @@ func (backend *Backend) calculateWindowSize () (x, y int) { return } +func (backend *Backend) bindCanvas () { + backend.canvas.XSurfaceSet(backend.window.Id) + backend.canvas.XDraw() + backend.canvas.XPaint(backend.window.Id) +} // factory instantiates an X backend. func factory (application *stone.Application) (output stone.Backend, err error) { @@ -106,6 +111,15 @@ func factory (application *stone.Application) (output stone.Backend, err error) 0) backend.window.Map() + // create a canvas + backend.canvas = xgraphics.New ( + backend.connection, + image.Rect ( + 0, 0, + windowWidth, + windowHeight)) + backend.bindCanvas() + // attatch graceful close handler backend.window.WMGracefulClose (func (window *xwindow.Window) { backend.window.Destroy() From f807c8df357056225f4fe71f46045bb854177f46 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Wed, 9 Nov 2022 22:33:18 -0500 Subject: [PATCH 02/33] Handle actual X events --- backends/x/x.go | 66 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 56 insertions(+), 10 deletions(-) diff --git a/backends/x/x.go b/backends/x/x.go index a514a65..aa7f8f7 100644 --- a/backends/x/x.go +++ b/backends/x/x.go @@ -1,8 +1,13 @@ package x import "image" +import "image/color" +// import "golang.org/x/image/font" +// import "golang.org/x/image/font/basicfont" +import "github.com/jezek/xgb" import "github.com/jezek/xgbutil" +import "github.com/jezek/xgb/xproto" // import "github.com/jezek/xgbutil/ewmh" import "github.com/jezek/xgbutil/xevent" import "github.com/jezek/xgbutil/xwindow" @@ -25,12 +30,14 @@ type Backend struct { } metrics struct { - cellWidth int - cellHeight int - padding int - paddingX int - paddingY int - descent int + windowWidth int + windowHeight int + cellWidth int + cellHeight int + padding int + paddingX int + paddingY int + descent int } } @@ -40,6 +47,19 @@ func (backend *Backend) Run (channel chan(stone.Event)) { for { select { case <- backend.ping.before: + // if the queue is empty, don't dequeue anything because + // it would cause a fucking segfault lmao (???) + if !xevent.Empty(backend.connection) { + event, err := xevent.Dequeue(backend.connection) + if err != nil { + // TODO: do something with err + } + + if event != nil { + backend.handleXEvent(event) + } + } + <- backend.ping.after case <- backend.ping.quit: @@ -57,6 +77,25 @@ func (backend *Backend) SetIcon (icons []image.Image) { } +func (backend *Backend) handleXEvent (event xgb.Event) { + switch event.(type) { + case xproto.ConfigureNotifyEvent: + configureEvent := event.(xproto.ConfigureNotifyEvent) + + newWidth := int(configureEvent.Width) + newHeight := int(configureEvent.Height) + sizeChanged := + backend.metrics.windowWidth != newWidth || + backend.metrics.windowHeight != newHeight + backend.metrics.windowWidth = newWidth + backend.metrics.windowHeight = newHeight + + if sizeChanged { + // TODO: resize and rebind canvas + } + } +} + func (backend *Backend) shutDown () { backend.channel <- stone.EventQuit { } } @@ -96,6 +135,8 @@ func factory (application *stone.Application) (output stone.Backend, err error) backend.metrics.cellHeight backend.metrics.paddingX = backend.metrics.padding backend.metrics.paddingY = backend.metrics.padding + backend.metrics.windowWidth, + backend.metrics.windowHeight = backend.calculateWindowSize() // connect to X backend.connection, err = xgbutil.NewConn() @@ -104,20 +145,25 @@ func factory (application *stone.Application) (output stone.Backend, err error) if err != nil { return } // create the window - windowWidth, windowHeight := backend.calculateWindowSize() backend.window.Create ( backend.connection.RootWin(), - 0, 0, windowWidth, windowHeight, + 0, 0, + backend.metrics.windowWidth, backend.metrics.windowHeight, 0) backend.window.Map() + backend.window.Listen(xproto.EventMaskStructureNotify) // create a canvas backend.canvas = xgraphics.New ( backend.connection, image.Rect ( 0, 0, - windowWidth, - windowHeight)) + backend.metrics.windowWidth, + backend.metrics.windowHeight)) + + for i := 8; i < 64; i ++ { + backend.canvas.Set(8, i, color.RGBA { R: 0xFF, A: 0xFF }) + } backend.bindCanvas() // attatch graceful close handler From 4536932dde13b270d1e28cdf254b93e29242a4fc Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Thu, 10 Nov 2022 01:00:47 -0500 Subject: [PATCH 03/33] Resize canvas when the window is resized --- backends/x/x.go | 48 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/backends/x/x.go b/backends/x/x.go index aa7f8f7..07f0378 100644 --- a/backends/x/x.go +++ b/backends/x/x.go @@ -1,7 +1,6 @@ package x import "image" -import "image/color" // import "golang.org/x/image/font" // import "golang.org/x/image/font/basicfont" @@ -91,11 +90,52 @@ func (backend *Backend) handleXEvent (event xgb.Event) { backend.metrics.windowHeight = newHeight if sizeChanged { - // TODO: resize and rebind canvas + // compress events + configureEvent = + backend.compressConfigureNotify(configureEvent) + + // resize and rebind canvas + backend.canvas.Destroy() + backend.canvas = xgraphics.New ( + backend.connection, + image.Rect ( + 0, 0, + backend.metrics.windowWidth, + backend.metrics.windowHeight)) + // FIXME (?): this doesn't work. if it were to work, it + // would possibly be a cleaner way to resize the canvas. + // backend.canvas.Scale ( + // backend.metrics.windowWidth, + // backend.metrics.windowHeight) + backend.bindCanvas() } } } +func (backend *Backend) compressConfigureNotify ( + firstEvent xproto.ConfigureNotifyEvent, +) ( + lastEvent xproto.ConfigureNotifyEvent, +) { + backend.connection.Sync() + xevent.Read(backend.connection, false) + lastEvent = firstEvent + + for index, untypedEvent := range xevent.Peek(backend.connection) { + if untypedEvent.Err != nil { continue } + + typedEvent, ok := untypedEvent.Event.(xproto.ConfigureNotifyEvent) + if !ok { continue } + + lastEvent = typedEvent + defer func (index int) { + xevent.DequeueAt(backend.connection, index) + } (index) + } + + return +} + func (backend *Backend) shutDown () { backend.channel <- stone.EventQuit { } } @@ -160,10 +200,6 @@ func factory (application *stone.Application) (output stone.Backend, err error) 0, 0, backend.metrics.windowWidth, backend.metrics.windowHeight)) - - for i := 8; i < 64; i ++ { - backend.canvas.Set(8, i, color.RGBA { R: 0xFF, A: 0xFF }) - } backend.bindCanvas() // attatch graceful close handler From 23a04a9cdbd0a07485310d9241b890aa54efdc6d Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Thu, 10 Nov 2022 02:02:08 -0500 Subject: [PATCH 04/33] Added icon setting --- application.go | 39 +++++++++++++++++++++++++++++------- assets/scaffold16.png | Bin 0 -> 589 bytes assets/scaffold32.png | Bin 0 -> 1190 bytes backend.go | 4 ++-- backends/x/x.go | 44 +++++++++++++++++++++++++++++++++++++---- examples/hello/main.go | 18 +++++++++++++++++ 6 files changed, 92 insertions(+), 13 deletions(-) create mode 100644 assets/scaffold16.png create mode 100644 assets/scaffold32.png diff --git a/application.go b/application.go index 7fe44c4..811e197 100644 --- a/application.go +++ b/application.go @@ -1,5 +1,6 @@ package stone +import "image" import "image/color" // Application represents an application. @@ -7,17 +8,11 @@ type Application struct { DamageBuffer title string + icons []image.Image backend Backend config Config } -// SetTitle sets the application's title. If in a window, it will appear as the -// window's name. -func (application *Application) SetTitle (title string) { - application.title = title - application.backend.SetTitle(title) -} - // 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 () ( @@ -49,6 +44,36 @@ func (application *Application) Run () ( return } +// SetTitle sets the application's title. If in a window, it will appear as the +// window's name. +func (application *Application) SetTitle (title string) (err error) { + application.title = title + if application.backend != nil { + err = application.backend.SetTitle(title) + } + + return +} + +func (application *Application) Title () (title string) { + title = application.title + return +} + +func (application *Application) SetIcon (sizes []image.Image) (err error) { + application.icons = sizes + if application.backend != nil { + err = application.backend.SetIcon(sizes) + } + + return +} + +func (application *Application) Icon () (sizes []image.Image) { + sizes = application.icons + return +} + // Config returns a pointer to the application's configuration. func (application *Application) Config () (config *Config) { config = &application.config diff --git a/assets/scaffold16.png b/assets/scaffold16.png new file mode 100644 index 0000000000000000000000000000000000000000..6fb689039f27a07338097797f4a8d99422477113 GIT binary patch literal 589 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uuz(rC1}QWNE&K$eWHMd+Lx40B zNDK%#fLH@cgV;YOU5S1Lq>p;KIEGjVPMKgRbTC1{ZE?aepK}LQO)|GwHZwnAvXa$% zz!alUb*sefp~|Hdb?-MC25G!GGg<%l+q|+=&80$7Kg{1~8rO-ipGf3f{>J@{tmAMjM~IAriCFR1k$?}u|vhaJ`@i2Q1r*Y<6WT1kGx>b?II ze4D>>>O5e(D|xS;-`*A&B>(>>JEd3wJ+u$xp{WxM`I;OATs3txb$o2v-4|?rtP*qJ zmc&;%wQDzmi{&-89QuAEcJl?xlj&*y{u{q>G8ANWlybfZ$ad54ru zY-^|XTr6n0y?zJRy8d5Dj?EX;jK8w&IcQl}_=5Gij+jHq1Li%)ba?~h5174ve{u2k znFUOrI4ibm7kF~pteUgo+u5wDCv$%4&ajUYx$~~{j~X9Gw&m74*7_2P@>~bj##(){ oTJ72&m+to8DVOmdZ~80!6%B_a7Im!K3-k$tr>mdKI;Vst0IgxyuK)l5 literal 0 HcmV?d00001 diff --git a/assets/scaffold32.png b/assets/scaffold32.png new file mode 100644 index 0000000000000000000000000000000000000000..abb193a0fc03783866bbac183d69bf3cd19cd548 GIT binary patch literal 1190 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}E~ycoX}-P; zT0k}j17mw80}DtA5K93u0|WB{Mh0de%?J`(zyz07Sip>6gA}f5OZp6?WHMd+Lx40B zNDK%BfLH@c1KCVQQgctt1RD3x)5S3)f^*AMdwmv1iT1yq>xxZOREmV+9UplId|))u z3S4={QV`=5x&sUgiCiU)Wxv}QoqH}S}rIT~`cdLI)V~mSGul%_8?BeQ+ zM{YFbTC_{Q2(bvid~H=t{EYt8plL#8^6r9MFP>&Ft}kHvexXfuMyp$Ev1#tF7ui1h z_uOTwlAYT7g|F{%X}L&At=4@bwHGft`un#Q`mF%VT`` z`q9SKJAc;AO1Q8U?B?J-#rY9$S2GA3#j{yofA;ZbWy?)#clowZi-zPIjW1&oU(9RS zc_uUOyGDHWvG+`m5=v%k{AP5LZ#AxHbH35+UD0yX!hgo%>h0=@vTj@Mvo6?r!0()y z%&zzEna{X(+Dug5!4+k*==O|fI`J11lIQ9kIF`-6yyVEUyZ>KtZ(;o~%O^Qz@$asG z86I;_zju&nc*dTpd5vwRurYs}v1NV9e&OdEeV@lL)?H0YKJNJJ$eP2tJx~6ee zTe_`*uS&V>jp^}nF0+I7)3;j1=kUHbQg-dAIxs=}|G#zn>b<~}a1WFcwoWnhVh$8& z3%;q!=IvO-q@S28(4VN7^ptl`Lj~JGwFJc_CvD#}w?=V9sYJc_p8IXB{^NZvj^cIC z&)OUR@5xWU{PV%f6#jQIIVmBpFUovilRLrk&d7#6vIb{YxaNy8S+?Cezw^?}J=q&#yPofvl_0a@nzV)TMd_ z(PoPKvXS{gwj^`C;d7AW>S;o+53QA2;J!yHq0QEdcd_cTP+z6}=e8fa6(qM&+2Hej zsmAmR+gdUsSDjy`a_!h+a~8cQ&+GG)**|E>HM9i3tbTj;`K4>= 8 + g >>= 8 + b >>= 8 + a >>= 8 + wmIcon.Data[index] = + (uint(a) << 24) | + (uint(r) << 16) | + (uint(g) << 8) | + (uint(b) << 0) + index ++ + }} + + wmIcons = append(wmIcons, wmIcon) + } + + err = ewmh.WmIconSet(backend.connection, backend.window.Id, wmIcons) + return } func (backend *Backend) handleXEvent (event xgb.Event) { @@ -192,6 +226,8 @@ func factory (application *stone.Application) (output stone.Backend, err error) 0) backend.window.Map() backend.window.Listen(xproto.EventMaskStructureNotify) + backend.SetTitle(application.Title()) + backend.SetIcon(application.Icon()) // create a canvas backend.canvas = xgraphics.New ( diff --git a/examples/hello/main.go b/examples/hello/main.go index 1dd1f89..38b168b 100644 --- a/examples/hello/main.go +++ b/examples/hello/main.go @@ -3,11 +3,29 @@ package main import "os" import "fmt" import "time" +import "image" +import _ "image/png" import "git.tebibyte.media/sashakoshka/stone" import _ "git.tebibyte.media/sashakoshka/stone/backends/x" func main () { application := &stone.Application { } + application.SetTitle("hellorld") + application.SetSize(12, 2) + + iconFile16, err := os.Open("assets/scaffold16.png") + if err != nil { panic(err) } + icon16, _, err := image.Decode(iconFile16) + if err != nil { panic(err) } + iconFile16.Close() + iconFile32, err := os.Open("assets/scaffold32.png") + if err != nil { panic(err) } + icon32, _, err := image.Decode(iconFile32) + if err != nil { panic(err) } + iconFile16.Close() + + application.SetIcon([]image.Image { icon16, icon32 }) + channel, err := application.Run() if err != nil { panic(err) } From 1afbcdb6bc15b627af9dc8e33b951e556b9a6812 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Thu, 10 Nov 2022 12:38:02 -0500 Subject: [PATCH 05/33] Load an actual font --- backends/x/x.go | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/backends/x/x.go b/backends/x/x.go index 606602f..ec588e3 100644 --- a/backends/x/x.go +++ b/backends/x/x.go @@ -1,8 +1,8 @@ package x import "image" -// import "golang.org/x/image/font" -// import "golang.org/x/image/font/basicfont" +import "golang.org/x/image/font" +import "golang.org/x/image/font/basicfont" import "github.com/jezek/xgb" import "github.com/jezek/xgbutil" @@ -12,6 +12,7 @@ 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 { @@ -27,6 +28,10 @@ type Backend struct { after chan(struct { }) quit chan(struct { }) } + + font struct { + face font.Face + } metrics struct { windowWidth int @@ -141,6 +146,7 @@ func (backend *Backend) handleXEvent (event xgb.Event) { // backend.canvas.Scale ( // backend.metrics.windowWidth, // backend.metrics.windowHeight) + backend.drawRune(8, 16, 'X') backend.bindCanvas() } } @@ -193,6 +199,10 @@ func (backend *Backend) bindCanvas () { backend.canvas.XPaint(backend.window.Id) } +func (backend *Backend) drawRune (x, y int, character rune) { + +} + // factory instantiates an X backend. func factory (application *stone.Application) (output stone.Backend, err error) { backend := &Backend { @@ -200,10 +210,16 @@ func factory (application *stone.Application) (output stone.Backend, err error) config: application.Config(), } + // load font + // TODO: load this from a file + backend.font.face = basicfont.Face7x13 + // calculate metrics - // TODO: base these off of font metrics - backend.metrics.cellWidth = 8 - backend.metrics.cellHeight = 16 + metrics := backend.font.face.Metrics() + glyphAdvance, _ := backend.font.face.GlyphAdvance('M') + backend.metrics.cellWidth = glyphAdvance.Round() + backend.metrics.cellHeight = metrics.Height.Round() + backend.metrics.descent = metrics.Descent.Round() backend.metrics.padding = backend.config.Padding() * backend.metrics.cellHeight From d26845a4d662e5854f0414ffa186ff68c09bc6fa Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Thu, 10 Nov 2022 20:43:27 -0500 Subject: [PATCH 06/33] Draw background color in window --- backends/x/x.go | 63 +++++++++++++++++++++++++++++++------------------ 1 file changed, 40 insertions(+), 23 deletions(-) diff --git a/backends/x/x.go b/backends/x/x.go index ec588e3..df34e69 100644 --- a/backends/x/x.go +++ b/backends/x/x.go @@ -32,6 +32,8 @@ type Backend struct { font struct { face font.Face } + + colors [4]xgraphics.BGRA metrics struct { windowWidth int @@ -134,20 +136,7 @@ func (backend *Backend) handleXEvent (event xgb.Event) { backend.compressConfigureNotify(configureEvent) // resize and rebind canvas - backend.canvas.Destroy() - backend.canvas = xgraphics.New ( - backend.connection, - image.Rect ( - 0, 0, - backend.metrics.windowWidth, - backend.metrics.windowHeight)) - // FIXME (?): this doesn't work. if it were to work, it - // would possibly be a cleaner way to resize the canvas. - // backend.canvas.Scale ( - // backend.metrics.windowWidth, - // backend.metrics.windowHeight) - backend.drawRune(8, 16, 'X') - backend.bindCanvas() + backend.reallocateCanvas() } } } @@ -193,14 +182,32 @@ func (backend *Backend) calculateWindowSize () (x, y int) { return } -func (backend *Backend) bindCanvas () { +func (backend *Backend) reallocateCanvas () { + if backend.canvas != nil { + backend.canvas.Destroy() + } + backend.canvas = xgraphics.New ( + backend.connection, + image.Rect ( + 0, 0, + backend.metrics.windowWidth, + backend.metrics.windowHeight)) + backend.canvas.For (func (x, y int) xgraphics.BGRA { + return backend.colors[stone.ColorApplication] + }) + // FIXME (?): this doesn't work. if it were to work, it + // would possibly be a cleaner way to resize the canvas. + // backend.canvas.Scale ( + // backend.metrics.windowWidth, + // backend.metrics.windowHeight) + backend.drawRune(8, 16, 'X') backend.canvas.XSurfaceSet(backend.window.Id) backend.canvas.XDraw() backend.canvas.XPaint(backend.window.Id) } func (backend *Backend) drawRune (x, y int, character rune) { - + // bounds, image, point, _, _ := } // factory instantiates an X backend. @@ -214,6 +221,22 @@ func factory (application *stone.Application) (output stone.Backend, err error) // TODO: load this from a file backend.font.face = basicfont.Face7x13 + // pre-calculate colors + for index := 0; index < len(backend.colors); index ++ { + color := backend.config.Color(stone.Color(index)) + r, g, b, a := color.RGBA() + r >>= 8 + g >>= 8 + b >>= 8 + a >>= 8 + backend.colors[index] = xgraphics.BGRA { + R: uint8(r), + G: uint8(g), + B: uint8(b), + A: uint8(a), + } + } + // calculate metrics metrics := backend.font.face.Metrics() glyphAdvance, _ := backend.font.face.GlyphAdvance('M') @@ -246,13 +269,7 @@ func factory (application *stone.Application) (output stone.Backend, err error) backend.SetIcon(application.Icon()) // create a canvas - backend.canvas = xgraphics.New ( - backend.connection, - image.Rect ( - 0, 0, - backend.metrics.windowWidth, - backend.metrics.windowHeight)) - backend.bindCanvas() + backend.reallocateCanvas() // attatch graceful close handler backend.window.WMGracefulClose (func (window *xwindow.Window) { From 93b02b4628460e8f29a592532cb938363a9990c0 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Thu, 10 Nov 2022 21:32:02 -0500 Subject: [PATCH 07/33] We have text --- backends/x/x.go | 77 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 69 insertions(+), 8 deletions(-) diff --git a/backends/x/x.go b/backends/x/x.go index df34e69..d89e88c 100644 --- a/backends/x/x.go +++ b/backends/x/x.go @@ -1,7 +1,10 @@ package x import "image" +import "image/draw" +import "image/color" import "golang.org/x/image/font" +import "golang.org/x/image/math/fixed" import "golang.org/x/image/font/basicfont" import "github.com/jezek/xgb" @@ -12,7 +15,6 @@ 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 { @@ -47,6 +49,28 @@ type Backend struct { } } +type fakeImage struct { + color color.Color +} + +func (fake fakeImage) ColorModel () (model color.Model) { + model = color.RGBAModel + return +} + +func (fake fakeImage) Bounds () (bounds image.Rectangle) { + bounds.Max = image.Point { + X: 1024, + Y: 1024, + } + return +} + +func (fake fakeImage) At (x, y int) (pixel color.Color) { + pixel = fake.color + return +} + func (backend *Backend) Run (channel chan(stone.Event)) { backend.channel = channel @@ -195,19 +219,56 @@ func (backend *Backend) reallocateCanvas () { backend.canvas.For (func (x, y int) xgraphics.BGRA { return backend.colors[stone.ColorApplication] }) - // FIXME (?): this doesn't work. if it were to work, it - // would possibly be a cleaner way to resize the canvas. - // backend.canvas.Scale ( - // backend.metrics.windowWidth, - // backend.metrics.windowHeight) - backend.drawRune(8, 16, 'X') + + backend.drawRune(0, 0, 'T') + backend.drawRune(1, 0, 'h') + backend.drawRune(2, 0, 'e') + backend.drawRune(4, 0, 'q') + backend.drawRune(5, 0, 'u') + backend.drawRune(6, 0, 'i') + backend.drawRune(7, 0, 'c') + backend.drawRune(8, 0, 'k') + backend.canvas.XSurfaceSet(backend.window.Id) backend.canvas.XDraw() backend.canvas.XPaint(backend.window.Id) } func (backend *Backend) drawRune (x, y int, character rune) { - // bounds, image, point, _, _ := + _, mask, maskPoint, _, _ := backend.font.face.Glyph ( + fixed.Point26_6 { }, + character) + draw.DrawMask ( + backend.canvas, + backend.boundsOfCell(x, y), + fakeImage { + color: backend.config.Color(stone.ColorForeground), + }, + image.Point { }, + mask, + maskPoint, + draw.Over) +} + +func (backend *Backend) cellSubImage (x, y int) (cell *xgraphics.Image) { + cell = backend.canvas.SubImage(backend.boundsOfCell(x, y)).(*xgraphics.Image) + return +} + +func (backend *Backend) originOfCell (x, y int) (origin image.Point) { + origin = image.Point { + X: x * backend.metrics.cellWidth + backend.metrics.paddingX, + Y: y * backend.metrics.cellHeight + backend.metrics.paddingY, + } + return +} + +func (backend *Backend) boundsOfCell (x, y int) (bounds image.Rectangle) { + bounds = image.Rectangle { + Min: backend.originOfCell(x, y), + Max: backend.originOfCell(x + 1, y + 1), + } + return } // factory instantiates an X backend. From ed5c6829d4aa3d94622efc234aaf4c1600d2173b Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Fri, 11 Nov 2022 14:51:08 -0500 Subject: [PATCH 08/33] Remove fakeImage struct and replace with image.Uniform --- application.go | 2 ++ backends/x/x.go | 59 +++++++++++++++++++++++++++---------------------- go.mod | 4 +++- go.sum | 3 +++ 4 files changed, 40 insertions(+), 28 deletions(-) diff --git a/application.go b/application.go index 811e197..d483255 100644 --- a/application.go +++ b/application.go @@ -32,6 +32,8 @@ func (application *Application) Run () ( color.RGBA { R: 0x2E, G: 0x34, B: 0x40, A: 0xFF }, color.RGBA { R: 0xA8, G: 0x55, B: 0x5D, A: 0xFF }, } + application.config.fontName = "DM Mono" + application.config.fontSize = 11 application.config.padding = 4 diff --git a/backends/x/x.go b/backends/x/x.go index d89e88c..140a0f5 100644 --- a/backends/x/x.go +++ b/backends/x/x.go @@ -1,10 +1,11 @@ package x +// import "os" import "image" import "image/draw" -import "image/color" import "golang.org/x/image/font" import "golang.org/x/image/math/fixed" +// import "golang.org/x/image/font/opentype" import "golang.org/x/image/font/basicfont" import "github.com/jezek/xgb" @@ -17,6 +18,8 @@ import "github.com/jezek/xgbutil/xgraphics" import "git.tebibyte.media/sashakoshka/stone" +// import "github.com/flopp/go-findfont" + type Backend struct { application *stone.Application config *stone.Config @@ -49,28 +52,6 @@ type Backend struct { } } -type fakeImage struct { - color color.Color -} - -func (fake fakeImage) ColorModel () (model color.Model) { - model = color.RGBAModel - return -} - -func (fake fakeImage) Bounds () (bounds image.Rectangle) { - bounds.Max = image.Point { - X: 1024, - Y: 1024, - } - return -} - -func (fake fakeImage) At (x, y int) (pixel color.Color) { - pixel = fake.color - return -} - func (backend *Backend) Run (channel chan(stone.Event)) { backend.channel = channel @@ -235,14 +216,17 @@ func (backend *Backend) reallocateCanvas () { } func (backend *Backend) drawRune (x, y int, character rune) { + // TODO: cache these draws as non-transparent buffers with the + // application background color as the background. that way, we won't + // need to redraw the characters *or* composite them. _, mask, maskPoint, _, _ := backend.font.face.Glyph ( fixed.Point26_6 { }, character) draw.DrawMask ( backend.canvas, backend.boundsOfCell(x, y), - fakeImage { - color: backend.config.Color(stone.ColorForeground), + &image.Uniform { + C: backend.config.Color(stone.ColorForeground), }, image.Point { }, mask, @@ -279,8 +263,12 @@ func factory (application *stone.Application) (output stone.Backend, err error) } // load font - // TODO: load this from a file - backend.font.face = basicfont.Face7x13 + backend.font.face = findAndLoadFont ( + backend.config.FontName(), + float64(backend.config.FontSize())) + if backend.font.face == nil { + backend.font.face = basicfont.Face7x13 + } // pre-calculate colors for index := 0; index < len(backend.colors); index ++ { @@ -347,6 +335,23 @@ func factory (application *stone.Application) (output stone.Backend, err error) return } +func findAndLoadFont (name string, size float64) (face font.Face) { + // fontPath, err := findfont.Find(name) + // if err != nil { return } + // fontFile, err := os.Open(fontPath) + // if err != nil { return } + // fontObject, err := opentype.ParseReaderAt(fontFile) + // if err != nil { return } + // face, err = opentype.NewFace (fontObject, &opentype.FaceOptions { + // Size: size, + // DPI: 96, + // Hinting: font.HintingFull, + // }) + // if err != nil { face = nil } + + return +} + // init registers this backend when the program starts. func init () { stone.RegisterBackend(factory) diff --git a/go.mod b/go.mod index 7d11633..076f2a7 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,8 @@ go 1.18 require ( github.com/faiface/pixel v0.10.0 + github.com/flopp/go-findfont v0.1.0 + github.com/jezek/xgb v1.1.0 github.com/jezek/xgbutil v0.0.0-20210302171758-530099784e66 golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff ) @@ -16,6 +18,6 @@ require ( 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 + golang.org/x/text v0.3.0 // indirect ) diff --git a/go.sum b/go.sum index 6390802..58f1e2a 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,8 @@ github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3 h1:baVdMKlASEHr 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/flopp/go-findfont v0.1.0 h1:lPn0BymDUtJo+ZkV01VS3661HL6F4qFlkhcJN55u6mU= +github.com/flopp/go-findfont v0.1.0/go.mod h1:wKKxRDjD024Rh7VMwoU90i6ikQRCr+JTHB5n4Ejkqvw= 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= @@ -32,4 +34,5 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV 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 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From cc498fa89c533255fa50cfad5114c3292f39206a Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Fri, 11 Nov 2022 15:01:36 -0500 Subject: [PATCH 09/33] Find and load any font --- application.go | 4 ++-- backends/x/x.go | 30 +++++++++++++++--------------- go.mod | 4 ++-- go.sum | 28 ++++++++++++++++++++++++++-- 4 files changed, 45 insertions(+), 21 deletions(-) diff --git a/application.go b/application.go index d483255..c585b91 100644 --- a/application.go +++ b/application.go @@ -32,10 +32,10 @@ func (application *Application) Run () ( color.RGBA { R: 0x2E, G: 0x34, B: 0x40, A: 0xFF }, color.RGBA { R: 0xA8, G: 0x55, B: 0x5D, A: 0xFF }, } - application.config.fontName = "DM Mono" + application.config.fontName = "DMMono" application.config.fontSize = 11 - application.config.padding = 4 + application.config.padding = 2 application.backend, err = instantiateBackend(application) if err != nil { return } diff --git a/backends/x/x.go b/backends/x/x.go index 140a0f5..bf874de 100644 --- a/backends/x/x.go +++ b/backends/x/x.go @@ -1,11 +1,11 @@ package x -// import "os" +import "os" import "image" import "image/draw" import "golang.org/x/image/font" import "golang.org/x/image/math/fixed" -// import "golang.org/x/image/font/opentype" +import "golang.org/x/image/font/opentype" import "golang.org/x/image/font/basicfont" import "github.com/jezek/xgb" @@ -18,7 +18,7 @@ import "github.com/jezek/xgbutil/xgraphics" import "git.tebibyte.media/sashakoshka/stone" -// import "github.com/flopp/go-findfont" +import "github.com/flopp/go-findfont" type Backend struct { application *stone.Application @@ -336,18 +336,18 @@ func factory (application *stone.Application) (output stone.Backend, err error) } func findAndLoadFont (name string, size float64) (face font.Face) { - // fontPath, err := findfont.Find(name) - // if err != nil { return } - // fontFile, err := os.Open(fontPath) - // if err != nil { return } - // fontObject, err := opentype.ParseReaderAt(fontFile) - // if err != nil { return } - // face, err = opentype.NewFace (fontObject, &opentype.FaceOptions { - // Size: size, - // DPI: 96, - // Hinting: font.HintingFull, - // }) - // if err != nil { face = nil } + fontPath, err := findfont.Find(name) + if err != nil { return } + fontFile, err := os.Open(fontPath) + if err != nil { return } + fontObject, err := opentype.ParseReaderAt(fontFile) + if err != nil { return } + face, err = opentype.NewFace (fontObject, &opentype.FaceOptions { + Size: size, + DPI: 96, + Hinting: font.HintingFull, + }) + if err != nil { face = nil } return } diff --git a/go.mod b/go.mod index 076f2a7..ba9abd0 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/flopp/go-findfont v0.1.0 github.com/jezek/xgb v1.1.0 github.com/jezek/xgbutil v0.0.0-20210302171758-530099784e66 - golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff + golang.org/x/image v0.1.0 ) require ( @@ -19,5 +19,5 @@ require ( 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/text v0.3.0 // indirect + golang.org/x/text v0.4.0 // indirect ) diff --git a/go.sum b/go.sum index 58f1e2a..303b65f 100644 --- a/go.sum +++ b/go.sum @@ -31,8 +31,32 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN 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= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 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 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/image v0.1.0 h1:r8Oj8ZA2Xy12/b5KZYj3tuv7NG/fBz3TwQVvpJ9l8Rk= +golang.org/x/image v0.1.0/go.mod h1:iyPr49SD/G/TBxYVB/9RRtGUT5eNbo2u4NamWeQcD5c= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 8ef81ecc7f01d968e469f027dce3e2d70724a36d Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Fri, 11 Nov 2022 15:20:52 -0500 Subject: [PATCH 10/33] Implemented some shape drawing functions to debug character positioning --- backends/x/x.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/backends/x/x.go b/backends/x/x.go index bf874de..6c8280b 100644 --- a/backends/x/x.go +++ b/backends/x/x.go @@ -222,6 +222,12 @@ func (backend *Backend) drawRune (x, y int, character rune) { _, mask, maskPoint, _, _ := backend.font.face.Glyph ( fixed.Point26_6 { }, character) + strokeRectangle ( + &image.Uniform { + C: backend.config.Color(stone.ColorForeground), + }, + backend.canvas, + backend.boundsOfCell(x, y)) draw.DrawMask ( backend.canvas, backend.boundsOfCell(x, y), @@ -352,6 +358,44 @@ func findAndLoadFont (name string, size float64) (face font.Face) { return } +func fillRectangle ( + source image.Image, + destination draw.Image, + bounds image.Rectangle, +) { + for y := bounds.Min.Y; y < bounds.Max.Y; y ++ { + for x := bounds.Min.X; x < bounds.Max.X; x ++ { + destination.Set(x, y, source.At(x, y)) + }} +} + +func strokeRectangle ( + source image.Image, + destination draw.Image, + bounds image.Rectangle, +) { + x := 0 + y := bounds.Min.Y + for x = bounds.Min.X; x < bounds.Max.X; x ++ { + destination.Set(x, y, source.At(x, y)) + } + + y = bounds.Max.Y - 1 + for x = bounds.Min.X; x < bounds.Max.X; x ++ { + destination.Set(x, y, source.At(x, y)) + } + + x = bounds.Min.X + for y = bounds.Min.Y; y < bounds.Max.Y; y ++ { + destination.Set(x, y, source.At(x, y)) + } + + x = bounds.Max.X - 1 + for y = bounds.Min.Y; y < bounds.Max.Y; y ++ { + destination.Set(x, y, source.At(x, y)) + } +} + // init registers this backend when the program starts. func init () { stone.RegisterBackend(factory) From 85b6e5495e4ac69e7ec43ec0c2e69bd578623f1b Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Fri, 11 Nov 2022 17:25:17 -0500 Subject: [PATCH 11/33] Fonts now render properly with a baseline and all that --- application.go | 2 +- backends/x/x.go | 32 +++++++++++++++++++++++++++----- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/application.go b/application.go index c585b91..0e94e0b 100644 --- a/application.go +++ b/application.go @@ -32,7 +32,7 @@ func (application *Application) Run () ( color.RGBA { R: 0x2E, G: 0x34, B: 0x40, A: 0xFF }, color.RGBA { R: 0xA8, G: 0x55, B: 0x5D, A: 0xFF }, } - application.config.fontName = "DMMono" + application.config.fontName = "" application.config.fontSize = 11 application.config.padding = 2 diff --git a/backends/x/x.go b/backends/x/x.go index 6c8280b..1a1037e 100644 --- a/backends/x/x.go +++ b/backends/x/x.go @@ -209,6 +209,14 @@ func (backend *Backend) reallocateCanvas () { backend.drawRune(6, 0, 'i') backend.drawRune(7, 0, 'c') backend.drawRune(8, 0, 'k') + backend.drawRune(0, 1, 'b') + backend.drawRune(1, 1, 'r') + backend.drawRune(2, 1, 'o') + backend.drawRune(3, 1, 'w') + backend.drawRune(4, 1, 'n') + backend.drawRune(6, 1, 'f') + backend.drawRune(7, 1, 'o') + backend.drawRune(8, 1, 'x') backend.canvas.XSurfaceSet(backend.window.Id) backend.canvas.XDraw() @@ -219,18 +227,31 @@ func (backend *Backend) drawRune (x, y int, character rune) { // TODO: cache these draws as non-transparent buffers with the // application background color as the background. that way, we won't // need to redraw the characters *or* composite them. - _, mask, maskPoint, _, _ := backend.font.face.Glyph ( - fixed.Point26_6 { }, + origin := backend.originOfCell(x, y + 1) + destinationRectangle, mask, maskPoint, _, _ := backend.font.face.Glyph ( + fixed.Point26_6 { + X: fixed.I(origin.X), + Y: fixed.I(origin.Y - backend.metrics.descent), + }, character) - strokeRectangle ( + + fillRectangle ( &image.Uniform { - C: backend.config.Color(stone.ColorForeground), + C: backend.config.Color(stone.ColorApplication), }, backend.canvas, backend.boundsOfCell(x, y)) + + // strokeRectangle ( + // &image.Uniform { + // C: backend.config.Color(stone.ColorForeground), + // }, + // backend.canvas, + // backend.boundsOfCell(x, y)) + draw.DrawMask ( backend.canvas, - backend.boundsOfCell(x, y), + destinationRectangle, &image.Uniform { C: backend.config.Color(stone.ColorForeground), }, @@ -342,6 +363,7 @@ func factory (application *stone.Application) (output stone.Backend, err error) } func findAndLoadFont (name string, size float64) (face font.Face) { + if name == "" { return } fontPath, err := findfont.Find(name) if err != nil { return } fontFile, err := os.Open(fontPath) From 38f7185be2ace3c8a04e20a1dff320ae233a28d9 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Fri, 11 Nov 2022 18:14:43 -0500 Subject: [PATCH 12/33] Draw the actual buffer text on screen --- backends/x/x.go | 57 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/backends/x/x.go b/backends/x/x.go index 1a1037e..fc5be02 100644 --- a/backends/x/x.go +++ b/backends/x/x.go @@ -142,6 +142,9 @@ func (backend *Backend) handleXEvent (event xgb.Event) { // resize and rebind canvas backend.reallocateCanvas() + + // notify application of resize + backend.channel <- stone.EventResize { } } } } @@ -201,28 +204,50 @@ func (backend *Backend) reallocateCanvas () { return backend.colors[stone.ColorApplication] }) - backend.drawRune(0, 0, 'T') - backend.drawRune(1, 0, 'h') - backend.drawRune(2, 0, 'e') - backend.drawRune(4, 0, 'q') - backend.drawRune(5, 0, 'u') - backend.drawRune(6, 0, 'i') - backend.drawRune(7, 0, 'c') - backend.drawRune(8, 0, 'k') - backend.drawRune(0, 1, 'b') - backend.drawRune(1, 1, 'r') - backend.drawRune(2, 1, 'o') - backend.drawRune(3, 1, 'w') - backend.drawRune(4, 1, 'n') - backend.drawRune(6, 1, 'f') - backend.drawRune(7, 1, 'o') - backend.drawRune(8, 1, 'x') + // backend.drawRune(0, 0, 'T') + // backend.drawRune(1, 0, 'h') + // backend.drawRune(2, 0, 'e') + // backend.drawRune(4, 0, 'q') + // backend.drawRune(5, 0, 'u') + // backend.drawRune(6, 0, 'i') + // backend.drawRune(7, 0, 'c') + // backend.drawRune(8, 0, 'k') + // backend.drawRune(0, 1, 'b') + // backend.drawRune(1, 1, 'r') + // backend.drawRune(2, 1, 'o') + // backend.drawRune(3, 1, 'w') + // backend.drawRune(4, 1, 'n') + // backend.drawRune(6, 1, 'f') + // backend.drawRune(7, 1, 'o') + // backend.drawRune(8, 1, 'x') + + backend.drawCells(true) backend.canvas.XSurfaceSet(backend.window.Id) backend.canvas.XDraw() backend.canvas.XPaint(backend.window.Id) } +func (backend *Backend) drawCells (forceRedraw bool) (areas []image.Rectangle) { + width, height := backend.application.Size() + for y := 0; y < height; y ++ { + for x := 0; x < width; x ++ { + if !forceRedraw && backend.application.Clean(x, y) { continue } + backend.application.MarkClean(x, y) + + cell := backend.application.Cell(x, y) + content := cell.Rune() + if content < 32 { continue } + + backend.drawRune(x, y, content) + }} + return +} + +func (backend *Backend) updateWindowAreas (areas ...image.Rectangle) { + backend.canvas.XPaintRects(backend.window.Id, areas...) +} + func (backend *Backend) drawRune (x, y int, character rune) { // TODO: cache these draws as non-transparent buffers with the // application background color as the background. that way, we won't From ea32b7899b569bde5db4a40834f3f7ec493fa56e Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Fri, 11 Nov 2022 20:27:51 -0500 Subject: [PATCH 13/33] Remove pixel backend --- backends/pixel/pixel.go | 321 ---------------------------------------- go.mod | 7 - go.sum | 25 ---- 3 files changed, 353 deletions(-) delete mode 100644 backends/pixel/pixel.go diff --git a/backends/pixel/pixel.go b/backends/pixel/pixel.go deleted file mode 100644 index 544173e..0000000 --- a/backends/pixel/pixel.go +++ /dev/null @@ -1,321 +0,0 @@ -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" - -// Backend represents an instance of the pixel backend -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 - showBounds bool - showCellBounds bool - - metrics struct { - cellWidth int - cellHeight int - padding int - paddingX int - paddingY int - descent int - } -} - -// Run satisfies the Run method of the Backend interface. Due to the nature of -// pixel, this will forcibly bind to the main thread. -func (backend *Backend) Run (callback func (application *stone.Application)) { - // backend.showBounds = true - // backend.showCellBounds = true - - if backend.fontFace == nil { - backend.fontFace = basicfont.Face7x13 - } - - backend.backgroundStamper = imdraw.New(nil) - backend.fontAtlas = text.NewAtlas(backend.fontFace, text.ASCII) - backend.textDrawer = text.New(pixel.V(0, 0), backend.fontAtlas) - - backend.metrics.descent = int(backend.fontAtlas.Descent()) - backend.metrics.cellHeight = int(backend.fontAtlas.LineHeight()) - // 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.Poll() - - // TODO: this should return the error and not panic - if err != nil { panic(err.Error()) } - callback(backend.application) - }) -} - -// Await fulfills the Await method of the Backend interface. -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 -} - -// Poll fulfills the Poll method of the Backend interface. -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 -} - -// SetTitle fulfills the SetTitle method of the Backend interface. -func (backend *Backend) SetTitle (title string) { - if backend.window != nil { - backend.window.SetTitle(title) - } -} - -// JustPressed fulfills the JustPressed method of the Backend interface. -func (backend *Backend) JustPressed (button stone.Button) (pressed bool) { - pressed = backend.window.JustPressed(pixelgl.Button(button)) - return -} - -// JustReleased fulfills the JustReleased method of the Backend interface. -func (backend *Backend) JustReleased (button stone.Button) (released bool) { - released = backend.window.JustReleased(pixelgl.Button(button)) - return -} - -// Pressed fulfills the Pressed method of the Backend interface. -func (backend *Backend) Pressed (button stone.Button) (pressed bool) { - pressed = backend.window.Pressed(pixelgl.Button(button)) - return -} - -// Repeated fulfills the Repeated method of the Backend interface. -func (backend *Backend) Repeated (button stone.Button) (repeated bool) { - repeated = backend.window.Repeated(pixelgl.Button(button)) - return -} - -// Typed fulfills the Typed method of the Backend interface. -func (backend *Backend) Typed () (text string) { - text = backend.window.Typed() - return -} - -// Resized fulfills the Resized method of the Backend interface. -func (backend *Backend) Resized () (resized bool) { - resized = backend.boundsDirty - return -} - -// MousePosition fulfills the MousePosition method of the Backend interface. -func (backend *Backend) MousePosition () (x, y int) { - vector := backend.window.MousePosition() - x = int ( - (vector.X - float64(backend.metrics.paddingX)) / - float64(backend.metrics.cellWidth)) - y = int ( - (backend.windowBounds.Y - - vector.Y - - float64(backend.metrics.paddingY)) / - float64(backend.metrics.cellHeight)) - return -} - -// draw renders updates to the screen. -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(0) - // didDrawing = true - - if backend.showCellBounds { - backend.backgroundStamper.Color = - backend.config.Color(stone.ColorForeground) - backend.backgroundStamper.Push ( - backend.vectorAtPosition(x, y), - backend.vectorAtPosition(x + 1, y + 1)) - backend.backgroundStamper.Rectangle(1) - backend.backgroundStamper.Color = - backend.config.Color(stone.ColorApplication) - } - } - } - 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 = pixel.V ( - float64 ( - x * backend.metrics.cellWidth + - backend.metrics.paddingX), - backend.windowBounds.Y - float64 ( - (y + 1) * backend.metrics.cellHeight + - backend.metrics.paddingY - - backend.metrics.descent)) - - backend.textDrawer.WriteRune(content) - backend.textDrawer.Draw(backend.window, pixel.IM) - } - } - - // draw a rectangle around the buffer if we are showing bounds - if backend.showBounds { - 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() -} - -// processEvents reacts to events recieved from pixel, resizing and -// recalculating things as need be. -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 - } -} - -// vectorAtPosition generates a pixel vector at the top left of the specified -// cell. -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 -} - -// calculateWindowSize calculates window bounds based on the internal buffer -// size. -func (backend *Backend) calculateWindowSize () (bounds pixel.Rect) { - width, height := backend.application.Size() - bounds = pixel.R ( - 0, 0, - float64 ( - width * backend.metrics.cellWidth + - backend.metrics.padding * 2), - float64 ( - height * backend.metrics.cellHeight + - backend.metrics.padding * 2)) - return -} - -// factory instantiates a pixel backend. -func factory (application *stone.Application) (output stone.Backend, err error) { - backend := &Backend { - application: application, - config: application.Config(), - } - output = backend - return -} - -// init registers this backend when the program starts. -func init () { - stone.RegisterBackend(factory) -} diff --git a/go.mod b/go.mod index ba9abd0..292faa2 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module git.tebibyte.media/sashakoshka/stone go 1.18 require ( - github.com/faiface/pixel v0.10.0 github.com/flopp/go-findfont v0.1.0 github.com/jezek/xgb v1.1.0 github.com/jezek/xgbutil v0.0.0-20210302171758-530099784e66 @@ -13,11 +12,5 @@ require ( 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/pkg/errors v0.8.1 // indirect golang.org/x/text v0.4.0 // indirect ) diff --git a/go.sum b/go.sum index 303b65f..5b46941 100644 --- a/go.sum +++ b/go.sum @@ -2,40 +2,15 @@ github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298 h1:1qlsVAQJ 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= -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/flopp/go-findfont v0.1.0 h1:lPn0BymDUtJo+ZkV01VS3661HL6F4qFlkhcJN55u6mU= github.com/flopp/go-findfont v0.1.0/go.mod h1:wKKxRDjD024Rh7VMwoU90i6ikQRCr+JTHB5n4Ejkqvw= -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 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= -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= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.1.0 h1:r8Oj8ZA2Xy12/b5KZYj3tuv7NG/fBz3TwQVvpJ9l8Rk= golang.org/x/image v0.1.0/go.mod h1:iyPr49SD/G/TBxYVB/9RRtGUT5eNbo2u4NamWeQcD5c= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= From 05c448f058f046b6faa81f90b84ce421020b18a6 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Fri, 11 Nov 2022 22:30:59 -0500 Subject: [PATCH 14/33] Applications can now manually call a screen redraw in a way that I think is thread safe --- application.go | 5 +++ backend.go | 1 + backends/x/x.go | 17 ++++++++++ examples/hello/main.go | 70 +++++++++++++++++++++++++++--------------- 4 files changed, 69 insertions(+), 24 deletions(-) diff --git a/application.go b/application.go index 0e94e0b..206af06 100644 --- a/application.go +++ b/application.go @@ -46,6 +46,11 @@ func (application *Application) Run () ( return } +// Draw "commits" changes made in the buffer to the display. +func (application *Application) Draw () { + application.backend.Draw() +} + // SetTitle sets the application's title. If in a window, it will appear as the // window's name. func (application *Application) SetTitle (title string) (err error) { diff --git a/backend.go b/backend.go index c4f8278..96678fc 100644 --- a/backend.go +++ b/backend.go @@ -7,6 +7,7 @@ type Backend interface { Run (channel chan(Event)) SetTitle (title string) (err error) SetIcon (icons []image.Image) (err error) + Draw () } type BackendFactory func (application *Application) (backend Backend, err error) diff --git a/backends/x/x.go b/backends/x/x.go index fc5be02..ffd2f14 100644 --- a/backends/x/x.go +++ b/backends/x/x.go @@ -1,6 +1,7 @@ package x import "os" +import "sync" import "image" import "image/draw" import "golang.org/x/image/font" @@ -28,6 +29,8 @@ type Backend struct { canvas *xgraphics.Image channel chan(stone.Event) + drawLock sync.Mutex + ping struct { before chan(struct { }) after chan(struct { }) @@ -80,6 +83,17 @@ func (backend *Backend) Run (channel chan(stone.Event)) { } } +func (backend *Backend) Draw () { + backend.drawLock.Lock() + defer backend.drawLock.Unlock() + + backend.drawCells(true) + backend.canvas.XDraw() + backend.canvas.XPaint(backend.window.Id) + // FIXME use this instead once it works + // backend.updateWindowAreas(...) +} + func (backend *Backend) SetTitle (title string) (err error) { err = ewmh.WmNameSet(backend.connection, backend.window.Id, title) return @@ -191,6 +205,9 @@ func (backend *Backend) calculateWindowSize () (x, y int) { } func (backend *Backend) reallocateCanvas () { + backend.drawLock.Lock() + defer backend.drawLock.Unlock() + if backend.canvas != nil { backend.canvas.Destroy() } diff --git a/examples/hello/main.go b/examples/hello/main.go index 38b168b..0c37f84 100644 --- a/examples/hello/main.go +++ b/examples/hello/main.go @@ -8,8 +8,11 @@ import _ "image/png" import "git.tebibyte.media/sashakoshka/stone" import _ "git.tebibyte.media/sashakoshka/stone/backends/x" +var application = &stone.Application { } +var currentTime = time.Time { } +var tickPing = make(chan(struct { })) + func main () { - application := &stone.Application { } application.SetTitle("hellorld") application.SetSize(12, 2) @@ -29,32 +32,51 @@ func main () { channel, err := application.Run() if err != nil { panic(err) } - currentTime := time.Time { } + redraw() + application.Draw() + go tick() for { - event := <- channel - switch event.(type) { - case stone.EventQuit: - os.Exit(0) + select { + case <- tickPing: + redraw() + application.Draw() + + case event := <- channel: + switch event.(type) { + case stone.EventQuit: + os.Exit(0) - case stone.EventResize: - 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)) + case stone.EventResize: + redraw() + } } } } + +func redraw () { + 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)) +} + +func tick () { + for { + tickPing <- struct { } { } + time.Sleep(time.Second) + } +} From c865f7146a92921d974e4cef36ed69615d39be89 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Fri, 11 Nov 2022 22:46:07 -0500 Subject: [PATCH 15/33] Buffer is now reallocated on window size change --- backends/x/x.go | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/backends/x/x.go b/backends/x/x.go index ffd2f14..78845c7 100644 --- a/backends/x/x.go +++ b/backends/x/x.go @@ -150,14 +150,13 @@ func (backend *Backend) handleXEvent (event xgb.Event) { backend.metrics.windowHeight = newHeight if sizeChanged { - // compress events configureEvent = backend.compressConfigureNotify(configureEvent) - - // resize and rebind canvas + // TODO: remove draw functions from reallocate canvas, + // and only reallocate when drawing and the window + // dimensions have changed backend.reallocateCanvas() - - // notify application of resize + backend.application.SetSize(backend.calculateBufferSize()) backend.channel <- stone.EventResize { } } } @@ -204,6 +203,16 @@ func (backend *Backend) calculateWindowSize () (x, y int) { return } +func (backend *Backend) calculateBufferSize () (width, height int) { + width = + (backend.metrics.windowWidth - backend.metrics.padding * 2) / + backend.metrics.cellWidth + height = + (backend.metrics.windowHeight - backend.metrics.padding * 2) / + backend.metrics.cellHeight + return +} + func (backend *Backend) reallocateCanvas () { backend.drawLock.Lock() defer backend.drawLock.Unlock() From e4f97a918ad91c8748a32176f3f7bb66fe066cf2 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Fri, 11 Nov 2022 23:24:20 -0500 Subject: [PATCH 16/33] Application must now manually call application.Draw in all cases This behavior is way more consistent, and it makes the drawing code a bit simpler. --- backends/x/x.go | 43 +++++++++++++++--------------------------- examples/hello/main.go | 4 ++-- 2 files changed, 17 insertions(+), 30 deletions(-) diff --git a/backends/x/x.go b/backends/x/x.go index 78845c7..12513c7 100644 --- a/backends/x/x.go +++ b/backends/x/x.go @@ -53,6 +53,11 @@ type Backend struct { paddingY int descent int } + + memory struct { + windowWidth int + windowHeight int + } } func (backend *Backend) Run (channel chan(stone.Event)) { @@ -86,6 +91,16 @@ func (backend *Backend) Run (channel chan(stone.Event)) { func (backend *Backend) Draw () { backend.drawLock.Lock() defer backend.drawLock.Unlock() + + boundsChanged := + backend.memory.windowWidth != backend.metrics.windowWidth || + backend.memory.windowHeight != backend.metrics.windowHeight + backend.memory.windowWidth = backend.metrics.windowWidth + backend.memory.windowHeight = backend.metrics.windowHeight + + if boundsChanged { + backend.reallocateCanvas() + } backend.drawCells(true) backend.canvas.XDraw() @@ -152,10 +167,6 @@ func (backend *Backend) handleXEvent (event xgb.Event) { if sizeChanged { configureEvent = backend.compressConfigureNotify(configureEvent) - // TODO: remove draw functions from reallocate canvas, - // and only reallocate when drawing and the window - // dimensions have changed - backend.reallocateCanvas() backend.application.SetSize(backend.calculateBufferSize()) backend.channel <- stone.EventResize { } } @@ -214,9 +225,6 @@ func (backend *Backend) calculateBufferSize () (width, height int) { } func (backend *Backend) reallocateCanvas () { - backend.drawLock.Lock() - defer backend.drawLock.Unlock() - if backend.canvas != nil { backend.canvas.Destroy() } @@ -230,28 +238,7 @@ func (backend *Backend) reallocateCanvas () { return backend.colors[stone.ColorApplication] }) - // backend.drawRune(0, 0, 'T') - // backend.drawRune(1, 0, 'h') - // backend.drawRune(2, 0, 'e') - // backend.drawRune(4, 0, 'q') - // backend.drawRune(5, 0, 'u') - // backend.drawRune(6, 0, 'i') - // backend.drawRune(7, 0, 'c') - // backend.drawRune(8, 0, 'k') - // backend.drawRune(0, 1, 'b') - // backend.drawRune(1, 1, 'r') - // backend.drawRune(2, 1, 'o') - // backend.drawRune(3, 1, 'w') - // backend.drawRune(4, 1, 'n') - // backend.drawRune(6, 1, 'f') - // backend.drawRune(7, 1, 'o') - // backend.drawRune(8, 1, 'x') - - backend.drawCells(true) - backend.canvas.XSurfaceSet(backend.window.Id) - backend.canvas.XDraw() - backend.canvas.XPaint(backend.window.Id) } func (backend *Backend) drawCells (forceRedraw bool) (areas []image.Rectangle) { diff --git a/examples/hello/main.go b/examples/hello/main.go index 0c37f84..86444f0 100644 --- a/examples/hello/main.go +++ b/examples/hello/main.go @@ -33,14 +33,12 @@ func main () { if err != nil { panic(err) } redraw() - application.Draw() go tick() for { select { case <- tickPing: redraw() - application.Draw() case event := <- channel: switch event.(type) { @@ -72,6 +70,8 @@ func redraw () { application.SetRune(5, 1, ':') application.SetRune(6, 1, rune(second / 10 + 48)) application.SetRune(7, 1, rune(second % 10 + 48)) + + application.Draw() } func tick () { From 47ee6545cb60ffab94ec4bb16cca56d6945a3129 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Fri, 11 Nov 2022 23:46:46 -0500 Subject: [PATCH 17/33] X backend only sends what it needs to --- backends/x/x.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/backends/x/x.go b/backends/x/x.go index 12513c7..f22eb0f 100644 --- a/backends/x/x.go +++ b/backends/x/x.go @@ -100,13 +100,16 @@ func (backend *Backend) Draw () { if boundsChanged { backend.reallocateCanvas() + backend.drawCells(true) + backend.canvas.XDraw() + backend.canvas.XPaint(backend.window.Id) + } else { + // backend.drawCells(false) + // backend.canvas.XDraw() + // backend.canvas.XPaint(backend.window.Id) + // FIXME use this instead once it works + backend.updateWindowAreas(backend.drawCells(false)...) } - - backend.drawCells(true) - backend.canvas.XDraw() - backend.canvas.XPaint(backend.window.Id) - // FIXME use this instead once it works - // backend.updateWindowAreas(...) } func (backend *Backend) SetTitle (title string) (err error) { @@ -251,7 +254,8 @@ func (backend *Backend) drawCells (forceRedraw bool) (areas []image.Rectangle) { cell := backend.application.Cell(x, y) content := cell.Rune() if content < 32 { continue } - + + areas = append(areas, backend.boundsOfCell(x, y)) backend.drawRune(x, y, content) }} return From f02eb348eaef61b0d36fb71a95bc93cbb80052b9 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Sat, 12 Nov 2022 17:40:08 +0000 Subject: [PATCH 18/33] Add info about X backend to readme --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index cb858b7..5b5c3ef 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,10 @@ Stone is a backend-agnostic application framework designed to: -- Combine the simplicity of developing TUI programs with the input capabilities - of GUI programs +- Combine the simplicity and ease of development inherent to TUI programs with the extended capabilities of GUI programs - Be adaptable to run virtually anywhere Currently, the only supported backend is -[pixel](https://github.com/faiface/pixel), but it is very easy to write and link +[X](https://github.com/jezek/xgbutil), but it is very easy to write and link your own. Stone will automatically run through the list of registered backends and instantiate the first one that doesn't throw an error. From 48510db209ecae06444cd6465161c36f68df7d92 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Sat, 12 Nov 2022 19:02:24 -0500 Subject: [PATCH 19/33] Added mouse button press input --- backends/x/x.go | 133 +++++++++++++++++++++--------------------- examples/draw/main.go | 65 +++++++++++++++++++++ input.go | 31 +++++----- 3 files changed, 150 insertions(+), 79 deletions(-) create mode 100644 examples/draw/main.go diff --git a/backends/x/x.go b/backends/x/x.go index f22eb0f..d3c6b47 100644 --- a/backends/x/x.go +++ b/backends/x/x.go @@ -1,6 +1,7 @@ package x import "os" +// import "fmt" import "sync" import "image" import "image/draw" @@ -9,7 +10,7 @@ import "golang.org/x/image/math/fixed" import "golang.org/x/image/font/opentype" import "golang.org/x/image/font/basicfont" -import "github.com/jezek/xgb" +// import "github.com/jezek/xgb" import "github.com/jezek/xgbutil" import "github.com/jezek/xgb/xproto" import "github.com/jezek/xgbutil/ewmh" @@ -31,12 +32,6 @@ type Backend struct { drawLock sync.Mutex - ping struct { - before chan(struct { }) - after chan(struct { }) - quit chan(struct { }) - } - font struct { face font.Face } @@ -62,30 +57,8 @@ type Backend struct { func (backend *Backend) Run (channel chan(stone.Event)) { backend.channel = channel - - for { - select { - case <- backend.ping.before: - // if the queue is empty, don't dequeue anything because - // it would cause a fucking segfault lmao (???) - if !xevent.Empty(backend.connection) { - event, err := xevent.Dequeue(backend.connection) - if err != nil { - // TODO: do something with err - } - - if event != nil { - backend.handleXEvent(event) - } - } - - <- backend.ping.after - - case <- backend.ping.quit: - backend.shutDown() - return - } - } + xevent.Main(backend.connection) + backend.shutDown() } func (backend *Backend) Draw () { @@ -104,10 +77,6 @@ func (backend *Backend) Draw () { backend.canvas.XDraw() backend.canvas.XPaint(backend.window.Id) } else { - // backend.drawCells(false) - // backend.canvas.XDraw() - // backend.canvas.XPaint(backend.window.Id) - // FIXME use this instead once it works backend.updateWindowAreas(backend.drawCells(false)...) } } @@ -154,28 +123,44 @@ func (backend *Backend) SetIcon (icons []image.Image) (err error) { return } -func (backend *Backend) handleXEvent (event xgb.Event) { - switch event.(type) { - case xproto.ConfigureNotifyEvent: - configureEvent := event.(xproto.ConfigureNotifyEvent) - - newWidth := int(configureEvent.Width) - newHeight := int(configureEvent.Height) - sizeChanged := - backend.metrics.windowWidth != newWidth || - backend.metrics.windowHeight != newHeight - backend.metrics.windowWidth = newWidth - backend.metrics.windowHeight = newHeight +func (backend *Backend) handleConfigureNotify ( + connection *xgbutil.XUtil, + event xevent.ConfigureNotifyEvent, +) { + configureEvent := *event.ConfigureNotifyEvent + + newWidth := int(configureEvent.Width) + newHeight := int(configureEvent.Height) + sizeChanged := + backend.metrics.windowWidth != newWidth || + backend.metrics.windowHeight != newHeight + backend.metrics.windowWidth = newWidth + backend.metrics.windowHeight = newHeight - if sizeChanged { - configureEvent = - backend.compressConfigureNotify(configureEvent) - backend.application.SetSize(backend.calculateBufferSize()) - backend.channel <- stone.EventResize { } - } + if sizeChanged { + configureEvent = + backend.compressConfigureNotify(configureEvent) + backend.application.SetSize(backend.calculateBufferSize()) + backend.channel <- stone.EventResize { } } } +func (backend *Backend) handleButtonPress ( + connection *xgbutil.XUtil, + event xevent.ButtonPressEvent, +) { + buttonEvent := *event.ButtonPressEvent + backend.channel <- stone.EventPress(buttonEvent.Detail) +} + +func (backend *Backend) handleButtonRelease ( + connection *xgbutil.XUtil, + event xevent.ButtonReleaseEvent, +) { + buttonEvent := *event.ButtonReleaseEvent + backend.channel <- stone.EventRelease(buttonEvent.Detail) +} + func (backend *Backend) compressConfigureNotify ( firstEvent xproto.ConfigureNotifyEvent, ) ( @@ -253,7 +238,8 @@ func (backend *Backend) drawCells (forceRedraw bool) (areas []image.Rectangle) { cell := backend.application.Cell(x, y) content := cell.Rune() - if content < 32 { continue } + + if forceRedraw && content < 32 { continue } areas = append(areas, backend.boundsOfCell(x, y)) backend.drawRune(x, y, content) @@ -269,6 +255,16 @@ func (backend *Backend) drawRune (x, y int, character rune) { // TODO: cache these draws as non-transparent buffers with the // application background color as the background. that way, we won't // need to redraw the characters *or* composite them. + + fillRectangle ( + &image.Uniform { + C: backend.config.Color(stone.ColorApplication), + }, + backend.canvas, + backend.boundsOfCell(x, y)) + + if character < 32 { return } + origin := backend.originOfCell(x, y + 1) destinationRectangle, mask, maskPoint, _, _ := backend.font.face.Glyph ( fixed.Point26_6 { @@ -277,13 +273,6 @@ func (backend *Backend) drawRune (x, y int, character rune) { }, character) - fillRectangle ( - &image.Uniform { - C: backend.config.Color(stone.ColorApplication), - }, - backend.canvas, - backend.boundsOfCell(x, y)) - // strokeRectangle ( // &image.Uniform { // C: backend.config.Color(stone.ColorForeground), @@ -382,7 +371,16 @@ func factory (application *stone.Application) (output stone.Backend, err error) backend.metrics.windowWidth, backend.metrics.windowHeight, 0) backend.window.Map() - backend.window.Listen(xproto.EventMaskStructureNotify) + // TODO: also listen to mouse movement (compressed) and mouse and + // keyboard buttons (uncompressed) + err = backend.window.Listen ( + xproto.EventMaskStructureNotify, + // xproto.EventMaskPointerMotion, + // xproto.EventMaskKeyPress, + // xproto.EventMaskKeyRelease, + xproto.EventMaskButtonPress, + xproto.EventMaskButtonRelease, + ) backend.SetTitle(application.Title()) backend.SetIcon(application.Icon()) @@ -395,10 +393,13 @@ func factory (application *stone.Application) (output stone.Backend, err error) backend.shutDown() }) - // start event loop - backend.ping.before, - backend.ping.after, - backend.ping.quit = xevent.MainPing(backend.connection) + // attatch event handlers + xevent.ConfigureNotifyFun(backend.handleConfigureNotify). + Connect(backend.connection, backend.window.Id) + xevent.ButtonPressFun(backend.handleButtonPress). + Connect(backend.connection, backend.window.Id) + xevent.ButtonReleaseFun(backend.handleButtonRelease). + Connect(backend.connection, backend.window.Id) output = backend return diff --git a/examples/draw/main.go b/examples/draw/main.go new file mode 100644 index 0000000..5aca10c --- /dev/null +++ b/examples/draw/main.go @@ -0,0 +1,65 @@ +package main + +import "os" +import "image" +import _ "image/png" +import "git.tebibyte.media/sashakoshka/stone" +import _ "git.tebibyte.media/sashakoshka/stone/backends/x" + +var application = &stone.Application { } +var mousePressed bool + +func main () { + application.SetTitle("hellorld") + application.SetSize(32, 16) + + iconFile16, err := os.Open("assets/scaffold16.png") + if err != nil { panic(err) } + icon16, _, err := image.Decode(iconFile16) + if err != nil { panic(err) } + iconFile16.Close() + iconFile32, err := os.Open("assets/scaffold32.png") + if err != nil { panic(err) } + icon32, _, err := image.Decode(iconFile32) + if err != nil { panic(err) } + iconFile16.Close() + + application.SetIcon([]image.Image { icon16, icon32 }) + + channel, err := application.Run() + if err != nil { panic(err) } + + application.Draw() + + for { + event := <- channel + switch event.(type) { + case stone.EventQuit: + os.Exit(0) + + case stone.EventPress: + event := event.(stone.EventPress) + if stone.Button(event) == stone.MouseButtonLeft { + mousePressed = true + application.SetRune(0, 0, '+') + application.Draw() + } + + case stone.EventRelease: + event := event.(stone.EventRelease) + if stone.Button(event) == stone.MouseButtonLeft { + mousePressed = false + application.SetRune(0, 0, 0) + application.Draw() + } + + case stone.EventMouseMove: + event := event.(stone.EventMouseMove) + application.SetRune(event.X, event.Y, '#') + application.Draw() + + case stone.EventResize: + application.Draw() + } + } +} diff --git a/input.go b/input.go index 694a234..c76118b 100644 --- a/input.go +++ b/input.go @@ -1,7 +1,5 @@ package stone -// These should be identical to the glfw keys - type Button int const ( @@ -129,15 +127,22 @@ const ( KeyRightSuper Button = 257 KeyMenu Button = 256 - MouseButton1 Button = 0 - MouseButton2 Button = 1 - MouseButton3 Button = 2 - MouseButton4 Button = 3 - MouseButton5 Button = 4 - MouseButton6 Button = 5 - MouseButton7 Button = 6 - MouseButton8 Button = 7 - MouseButtonLeft Button = MouseButton1 - MouseButtonRight Button = MouseButton2 - MouseButtonMiddle Button = MouseButton3 + MouseButton1 Button = 1 + MouseButton2 Button = 2 + MouseButton3 Button = 3 + MouseButton4 Button = 4 + MouseButton5 Button = 5 + MouseButton6 Button = 6 + MouseButton7 Button = 7 + MouseButton8 Button = 8 + MouseButton9 Button = 9 + MouseButtonLeft Button = MouseButton1 + MouseButtonMiddle Button = MouseButton2 + MouseButtonRight Button = MouseButton3 + MouseButtonScrollUp Button = MouseButton4 + MouseButtonScrollDown Button = MouseButton5 + MouseButtonScrollLeft Button = MouseButton6 + MouseButtonScrollRight Button = MouseButton7 + MouseButtonBack Button = MouseButton8 + MouseButtonForward Button = MouseButton9 ) From 636e5ce7e7b7a51a29fa94e7afe103d7c7b1ae1d Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Sat, 12 Nov 2022 22:43:36 -0500 Subject: [PATCH 20/33] Sus. Sus amongus. amoogoos. --- backends/x/x.go | 25 ++++++++++++++++++++++++- examples/draw/main.go | 6 ++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/backends/x/x.go b/backends/x/x.go index d3c6b47..2eec2b4 100644 --- a/backends/x/x.go +++ b/backends/x/x.go @@ -161,6 +161,21 @@ func (backend *Backend) handleButtonRelease ( backend.channel <- stone.EventRelease(buttonEvent.Detail) } +func (backend *Backend) handleMotionNotify ( + connection *xgbutil.XUtil, + event xevent.MotionNotifyEvent, +) { + motionEvent := *event.MotionNotifyEvent + x, y := backend.cellAt (image.Point { + X: int(motionEvent.EventX), + Y: int(motionEvent.EventY), + }) + backend.channel <- stone.EventMouseMove { + X: x, + Y: y, + } +} + func (backend *Backend) compressConfigureNotify ( firstEvent xproto.ConfigureNotifyEvent, ) ( @@ -292,6 +307,12 @@ func (backend *Backend) drawRune (x, y int, character rune) { draw.Over) } +func (backend *Backend) cellAt (onScreen image.Point) (x, y int) { + x = (onScreen.X - backend.metrics.paddingX) / backend.metrics.cellWidth + y = (onScreen.Y - backend.metrics.paddingY) / backend.metrics.cellHeight + return +} + func (backend *Backend) cellSubImage (x, y int) (cell *xgraphics.Image) { cell = backend.canvas.SubImage(backend.boundsOfCell(x, y)).(*xgraphics.Image) return @@ -375,7 +396,7 @@ func factory (application *stone.Application) (output stone.Backend, err error) // keyboard buttons (uncompressed) err = backend.window.Listen ( xproto.EventMaskStructureNotify, - // xproto.EventMaskPointerMotion, + xproto.EventMaskPointerMotion, // xproto.EventMaskKeyPress, // xproto.EventMaskKeyRelease, xproto.EventMaskButtonPress, @@ -400,6 +421,8 @@ func factory (application *stone.Application) (output stone.Backend, err error) Connect(backend.connection, backend.window.Id) xevent.ButtonReleaseFun(backend.handleButtonRelease). Connect(backend.connection, backend.window.Id) + xevent.MotionNotifyFun(backend.handleMotionNotify). + Connect(backend.connection, backend.window.Id) output = backend return diff --git a/examples/draw/main.go b/examples/draw/main.go index 5aca10c..52d54ba 100644 --- a/examples/draw/main.go +++ b/examples/draw/main.go @@ -55,8 +55,10 @@ func main () { case stone.EventMouseMove: event := event.(stone.EventMouseMove) - application.SetRune(event.X, event.Y, '#') - application.Draw() + if mousePressed { + application.SetRune(event.X, event.Y, '#') + application.Draw() + } case stone.EventResize: application.Draw() From 5a0e0cd49b978cfc54f6776de9cc1e6e0436a776 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Sun, 13 Nov 2022 15:39:33 -0500 Subject: [PATCH 21/33] Set minimum window dimensions --- backends/x/x.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/backends/x/x.go b/backends/x/x.go index 2eec2b4..3f70a4b 100644 --- a/backends/x/x.go +++ b/backends/x/x.go @@ -14,6 +14,7 @@ import "golang.org/x/image/font/basicfont" import "github.com/jezek/xgbutil" import "github.com/jezek/xgb/xproto" import "github.com/jezek/xgbutil/ewmh" +import "github.com/jezek/xgbutil/icccm" import "github.com/jezek/xgbutil/xevent" import "github.com/jezek/xgbutil/xwindow" import "github.com/jezek/xgbutil/xgraphics" @@ -404,6 +405,22 @@ func factory (application *stone.Application) (output stone.Backend, err error) ) backend.SetTitle(application.Title()) backend.SetIcon(application.Icon()) + if err != nil { return } + + // set minimum dimensions + minWidth := + backend.metrics.cellWidth + backend.metrics.padding * 2 + minHeight := + backend.metrics.cellHeight + backend.metrics.padding * 2 + err = icccm.WmNormalHintsSet ( + backend.connection, + backend.window.Id, + &icccm.NormalHints { + Flags: icccm.SizeHintPMinSize, + MinWidth: uint(minWidth), + MinHeight: uint(minHeight), + }) + if err != nil { return } // create a canvas backend.reallocateCanvas() From 7dde0ad5a3edb60cf60203179eaaa089659eecb2 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Sun, 13 Nov 2022 17:35:37 -0500 Subject: [PATCH 22/33] Redid button codes --- input.go | 196 +++++++++++++++++++------------------------------------ 1 file changed, 66 insertions(+), 130 deletions(-) diff --git a/input.go b/input.go index c76118b..438f3b0 100644 --- a/input.go +++ b/input.go @@ -3,139 +3,49 @@ package stone type Button int const ( - KeyUnknown Button = -1 + ButtonUnknown Button = 0 - KeySpace Button = 162 - KeyApostrophe Button = 161 - KeyComma Button = 96 - KeyMinus Button = 93 - KeyPeriod Button = 92 - KeySlash Button = 91 - Key0 Button = 90 - Key1 Button = 89 - Key2 Button = 88 - Key3 Button = 87 - Key4 Button = 86 - Key5 Button = 85 - Key6 Button = 84 - Key7 Button = 83 - Key8 Button = 82 - Key9 Button = 81 - KeySemicolon Button = 80 - KeyEqual Button = 79 - KeyA Button = 78 - KeyB Button = 77 - KeyC Button = 76 - KeyD Button = 75 - KeyE Button = 74 - KeyF Button = 73 - KeyG Button = 72 - KeyH Button = 71 - KeyI Button = 70 - KeyJ Button = 69 - KeyK Button = 68 - KeyL Button = 67 - KeyM Button = 66 - KeyN Button = 65 - KeyO Button = 61 - KeyP Button = 59 - KeyQ Button = 57 - KeyR Button = 56 - KeyS Button = 55 - KeyT Button = 54 - KeyU Button = 53 - KeyV Button = 52 - KeyW Button = 51 - KeyX Button = 50 - KeyY Button = 49 - KeyZ Button = 48 - KeyLeftBracket Button = 47 - KeyBackslash Button = 46 - KeyRightBracket Button = 45 - KeyGraveAccent Button = 44 - KeyWorld1 Button = 39 - KeyWorld2 Button = 32 + KeyInsert Button = 1 + KeyMenu Button = 2 + KeyPrintScreen Button = 3 + KeyPause Button = 4 + KeyCapsLock Button = 5 + KeyScrollLock Button = 6 + KeyNumLock Button = 7 + KeyBackspace Button = 8 + KeyTab Button = 9 + KeyEnter Button = 10 + KeyEscape Button = 11 - KeyEscape Button = 348 - KeyEnter Button = 347 - KeyTab Button = 346 - KeyBackspace Button = 345 - KeyInsert Button = 344 - KeyDelete Button = 343 - KeyRight Button = 342 - KeyLeft Button = 341 - KeyDown Button = 340 - KeyUp Button = 336 - KeyPageUp Button = 335 - KeyPageDown Button = 334 - KeyHome Button = 333 - KeyEnd Button = 332 - KeyCapsLock Button = 331 - KeyScrollLock Button = 330 - KeyNumLock Button = 329 - KeyPrintScreen Button = 328 - KeyPause Button = 327 - KeyF1 Button = 326 - KeyF2 Button = 325 - KeyF3 Button = 324 - KeyF4 Button = 323 - KeyF5 Button = 322 - KeyF6 Button = 321 - KeyF7 Button = 320 - KeyF8 Button = 314 - KeyF9 Button = 313 - KeyF10 Button = 312 - KeyF11 Button = 311 - KeyF12 Button = 310 - KeyF13 Button = 309 - KeyF14 Button = 308 - KeyF15 Button = 307 - KeyF16 Button = 306 - KeyF17 Button = 305 - KeyF18 Button = 304 - KeyF19 Button = 303 - KeyF20 Button = 302 - KeyF21 Button = 301 - KeyF22 Button = 300 - KeyF23 Button = 299 - KeyF24 Button = 298 - KeyF25 Button = 297 - KeyKP0 Button = 296 - KeyKP1 Button = 295 - KeyKP2 Button = 294 - KeyKP3 Button = 293 - KeyKP4 Button = 292 - KeyKP5 Button = 291 - KeyKP6 Button = 290 - KeyKP7 Button = 284 - KeyKP8 Button = 283 - KeyKP9 Button = 282 - KeyKPDecimal Button = 281 - KeyKPDivide Button = 280 - KeyKPMultiply Button = 269 - KeyKPSubtract Button = 268 - KeyKPAdd Button = 267 - KeyKPEnter Button = 266 - KeyKPEqual Button = 265 - KeyLeftShift Button = 264 - KeyLeftControl Button = 263 - KeyLeftAlt Button = 262 - KeyLeftSuper Button = 261 - KeyRightShift Button = 260 - KeyRightControl Button = 259 - KeyRightAlt Button = 258 - KeyRightSuper Button = 257 - KeyMenu Button = 256 + KeyUp Button = 12 + KeyDown Button = 13 + KeyLeft Button = 14 + KeyRight Button = 15 + KeyPageUp Button = 16 + KeyPageDown Button = 17 + KeyHome Button = 18 + KeyEnd Button = 19 - MouseButton1 Button = 1 - MouseButton2 Button = 2 - MouseButton3 Button = 3 - MouseButton4 Button = 4 - MouseButton5 Button = 5 - MouseButton6 Button = 6 - MouseButton7 Button = 7 - MouseButton8 Button = 8 - MouseButton9 Button = 9 + KeyLeftShift Button = 20 + KeyRightShift Button = 21 + KeyLeftControl Button = 22 + KeyRightControl Button = 23 + KeyLeftAlt Button = 24 + KeyRightAlt Button = 25 + KeyLeftSuper Button = 26 + KeyRightSuper Button = 27 + + KeyDelete Button = 127 + + MouseButton1 Button = 128 + MouseButton2 Button = 129 + MouseButton3 Button = 130 + MouseButton4 Button = 131 + MouseButton5 Button = 132 + MouseButton6 Button = 133 + MouseButton7 Button = 134 + MouseButton8 Button = 135 + MouseButton9 Button = 136 MouseButtonLeft Button = MouseButton1 MouseButtonMiddle Button = MouseButton2 MouseButtonRight Button = MouseButton3 @@ -145,4 +55,30 @@ const ( MouseButtonScrollRight Button = MouseButton7 MouseButtonBack Button = MouseButton8 MouseButtonForward Button = MouseButton9 + + KeyF1 Button = 136 + KeyF2 Button = 137 + KeyF3 Button = 138 + KeyF4 Button = 139 + KeyF5 Button = 140 + KeyF6 Button = 141 + KeyF7 Button = 142 + KeyF8 Button = 143 + KeyF9 Button = 144 + KeyF10 Button = 145 + KeyF11 Button = 146 + KeyF12 Button = 147 + KeyF13 Button = 148 + KeyF14 Button = 149 + KeyF15 Button = 150 + KeyF16 Button = 151 + KeyF17 Button = 152 + KeyF18 Button = 153 + KeyF19 Button = 154 + KeyF20 Button = 155 + KeyF21 Button = 156 + KeyF22 Button = 157 + KeyF23 Button = 158 + KeyF24 Button = 159 + KeyF25 Button = 28 ) From 872b36d172527cfe7698901a48af109062c562c4 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Sun, 13 Nov 2022 22:28:44 -0500 Subject: [PATCH 23/33] Added text input example (non-working) --- examples/type/main.go | 52 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 examples/type/main.go diff --git a/examples/type/main.go b/examples/type/main.go new file mode 100644 index 0000000..92ebfb3 --- /dev/null +++ b/examples/type/main.go @@ -0,0 +1,52 @@ +package main + +import "os" +import "image" +import _ "image/png" +import "git.tebibyte.media/sashakoshka/stone" +import _ "git.tebibyte.media/sashakoshka/stone/backends/x" + +var application = &stone.Application { } +var caret = 0 + +func main () { + application.SetTitle("hellorld") + application.SetSize(32, 16) + + iconFile16, err := os.Open("assets/scaffold16.png") + if err != nil { panic(err) } + icon16, _, err := image.Decode(iconFile16) + if err != nil { panic(err) } + iconFile16.Close() + iconFile32, err := os.Open("assets/scaffold32.png") + if err != nil { panic(err) } + icon32, _, err := image.Decode(iconFile32) + if err != nil { panic(err) } + iconFile16.Close() + + application.SetIcon([]image.Image { icon16, icon32 }) + + channel, err := application.Run() + if err != nil { panic(err) } + + application.Draw() + + for { + event := <- channel + switch event.(type) { + case stone.EventQuit: + os.Exit(0) + + case stone.EventPress: + event := event.(stone.EventPress) + if stone.event.Printable() { + application.SetRune(caret, 0, rune(stone.event)) + caret ++ + application.Draw() + } + + case stone.EventResize: + application.Draw() + } + } +} From 82caf1efd8d889ecc3e328dfa7758607f3ba1e95 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Sun, 13 Nov 2022 22:44:19 -0500 Subject: [PATCH 24/33] Split X backend into multiple files --- backends/x/draw.go | 128 +++++++++++++++ backends/x/event.go | 107 +++++++++++++ backends/x/factory.go | 151 ++++++++++++++++++ backends/x/x.go | 351 ------------------------------------------ 4 files changed, 386 insertions(+), 351 deletions(-) create mode 100644 backends/x/draw.go create mode 100644 backends/x/event.go create mode 100644 backends/x/factory.go diff --git a/backends/x/draw.go b/backends/x/draw.go new file mode 100644 index 0000000..82ff82b --- /dev/null +++ b/backends/x/draw.go @@ -0,0 +1,128 @@ +package x + +import "image" +import "image/draw" +import "golang.org/x/image/math/fixed" + +import "git.tebibyte.media/sashakoshka/stone" + +func (backend *Backend) Draw () { + backend.drawLock.Lock() + defer backend.drawLock.Unlock() + + boundsChanged := + backend.memory.windowWidth != backend.metrics.windowWidth || + backend.memory.windowHeight != backend.metrics.windowHeight + backend.memory.windowWidth = backend.metrics.windowWidth + backend.memory.windowHeight = backend.metrics.windowHeight + + if boundsChanged { + backend.reallocateCanvas() + backend.drawCells(true) + backend.canvas.XDraw() + backend.canvas.XPaint(backend.window.Id) + } else { + backend.updateWindowAreas(backend.drawCells(false)...) + } +} + +func (backend *Backend) updateWindowAreas (areas ...image.Rectangle) { + backend.canvas.XPaintRects(backend.window.Id, areas...) +} + +func (backend *Backend) drawRune (x, y int, character rune) { + // TODO: cache these draws as non-transparent buffers with the + // application background color as the background. that way, we won't + // need to redraw the characters *or* composite them. + + fillRectangle ( + &image.Uniform { + C: backend.config.Color(stone.ColorApplication), + }, + backend.canvas, + backend.boundsOfCell(x, y)) + + if character < 32 { return } + + origin := backend.originOfCell(x, y + 1) + destinationRectangle, mask, maskPoint, _, _ := backend.font.face.Glyph ( + fixed.Point26_6 { + X: fixed.I(origin.X), + Y: fixed.I(origin.Y - backend.metrics.descent), + }, + character) + + // strokeRectangle ( + // &image.Uniform { + // C: backend.config.Color(stone.ColorForeground), + // }, + // backend.canvas, + // backend.boundsOfCell(x, y)) + + draw.DrawMask ( + backend.canvas, + destinationRectangle, + &image.Uniform { + C: backend.config.Color(stone.ColorForeground), + }, + image.Point { }, + mask, + maskPoint, + draw.Over) +} + +func (backend *Backend) drawCells (forceRedraw bool) (areas []image.Rectangle) { + width, height := backend.application.Size() + for y := 0; y < height; y ++ { + for x := 0; x < width; x ++ { + if !forceRedraw && backend.application.Clean(x, y) { continue } + backend.application.MarkClean(x, y) + + cell := backend.application.Cell(x, y) + content := cell.Rune() + + if forceRedraw && content < 32 { continue } + + areas = append(areas, backend.boundsOfCell(x, y)) + backend.drawRune(x, y, content) + }} + return +} + +func fillRectangle ( + source image.Image, + destination draw.Image, + bounds image.Rectangle, +) { + for y := bounds.Min.Y; y < bounds.Max.Y; y ++ { + for x := bounds.Min.X; x < bounds.Max.X; x ++ { + destination.Set(x, y, source.At(x, y)) + }} +} + +func strokeRectangle ( + source image.Image, + destination draw.Image, + bounds image.Rectangle, +) { + x := 0 + y := bounds.Min.Y + for x = bounds.Min.X; x < bounds.Max.X; x ++ { + destination.Set(x, y, source.At(x, y)) + } + + y = bounds.Max.Y - 1 + for x = bounds.Min.X; x < bounds.Max.X; x ++ { + destination.Set(x, y, source.At(x, y)) + } + + x = bounds.Min.X + for y = bounds.Min.Y; y < bounds.Max.Y; y ++ { + destination.Set(x, y, source.At(x, y)) + } + + x = bounds.Max.X - 1 + for y = bounds.Min.Y; y < bounds.Max.Y; y ++ { + destination.Set(x, y, source.At(x, y)) + } +} diff --git a/backends/x/event.go b/backends/x/event.go new file mode 100644 index 0000000..76a5e61 --- /dev/null +++ b/backends/x/event.go @@ -0,0 +1,107 @@ +package x + +import "image" + +import "github.com/jezek/xgbutil" +import "github.com/jezek/xgb/xproto" +import "github.com/jezek/xgbutil/xevent" +import "github.com/jezek/xgbutil/keybind" + +import "git.tebibyte.media/sashakoshka/stone" + +func (backend *Backend) Run (channel chan(stone.Event)) { + backend.channel = channel + xevent.Main(backend.connection) + backend.shutDown() +} + + +func (backend *Backend) handleConfigureNotify ( + connection *xgbutil.XUtil, + event xevent.ConfigureNotifyEvent, +) { + configureEvent := *event.ConfigureNotifyEvent + + newWidth := int(configureEvent.Width) + newHeight := int(configureEvent.Height) + sizeChanged := + backend.metrics.windowWidth != newWidth || + backend.metrics.windowHeight != newHeight + backend.metrics.windowWidth = newWidth + backend.metrics.windowHeight = newHeight + + if sizeChanged { + configureEvent = + backend.compressConfigureNotify(configureEvent) + backend.application.SetSize(backend.calculateBufferSize()) + backend.channel <- stone.EventResize { } + } +} + +func (backend *Backend) handleButtonPress ( + connection *xgbutil.XUtil, + event xevent.ButtonPressEvent, +) { + buttonEvent := *event.ButtonPressEvent + backend.channel <- stone.EventPress(buttonEvent.Detail) +} + +func (backend *Backend) handleButtonRelease ( + connection *xgbutil.XUtil, + event xevent.ButtonReleaseEvent, +) { + buttonEvent := *event.ButtonReleaseEvent + backend.channel <- stone.EventRelease(buttonEvent.Detail) +} + +func (backend *Backend) handleKeyPress ( + connection *xgbutil.XUtil, + event xevent.KeyPressEvent, +) { + keyEvent := *event.KeyPressEvent + keySym := keybind.KeysymGet(backend.connection, keyEvent.Detail, 0) + // TODO: convert to keysym and then to a button value +} + +func (backend *Backend) handleMotionNotify ( + connection *xgbutil.XUtil, + event xevent.MotionNotifyEvent, +) { + motionEvent := *event.MotionNotifyEvent + x, y := backend.cellAt (image.Point { + X: int(motionEvent.EventX), + Y: int(motionEvent.EventY), + }) + backend.channel <- stone.EventMouseMove { + X: x, + Y: y, + } +} + +func (backend *Backend) compressConfigureNotify ( + firstEvent xproto.ConfigureNotifyEvent, +) ( + lastEvent xproto.ConfigureNotifyEvent, +) { + backend.connection.Sync() + xevent.Read(backend.connection, false) + lastEvent = firstEvent + + for index, untypedEvent := range xevent.Peek(backend.connection) { + if untypedEvent.Err != nil { continue } + + typedEvent, ok := untypedEvent.Event.(xproto.ConfigureNotifyEvent) + if !ok { continue } + + lastEvent = typedEvent + defer func (index int) { + xevent.DequeueAt(backend.connection, index) + } (index) + } + + return +} + +func (backend *Backend) shutDown () { + backend.channel <- stone.EventQuit { } +} diff --git a/backends/x/factory.go b/backends/x/factory.go new file mode 100644 index 0000000..2ed9b3c --- /dev/null +++ b/backends/x/factory.go @@ -0,0 +1,151 @@ +package x + +import "os" +import "golang.org/x/image/font" +import "golang.org/x/image/font/opentype" +import "golang.org/x/image/font/basicfont" + +import "github.com/jezek/xgbutil" +import "github.com/jezek/xgb/xproto" +import "github.com/jezek/xgbutil/icccm" +import "github.com/jezek/xgbutil/xevent" +import "github.com/jezek/xgbutil/xwindow" +import "github.com/jezek/xgbutil/keybind" +import "github.com/jezek/xgbutil/xgraphics" + +import "git.tebibyte.media/sashakoshka/stone" + +import "github.com/flopp/go-findfont" + +// factory instantiates an X backend. +func factory (application *stone.Application) (output stone.Backend, err error) { + backend := &Backend { + application: application, + config: application.Config(), + } + + // load font + backend.font.face = findAndLoadFont ( + backend.config.FontName(), + float64(backend.config.FontSize())) + if backend.font.face == nil { + backend.font.face = basicfont.Face7x13 + } + + // pre-calculate colors + for index := 0; index < len(backend.colors); index ++ { + color := backend.config.Color(stone.Color(index)) + r, g, b, a := color.RGBA() + r >>= 8 + g >>= 8 + b >>= 8 + a >>= 8 + backend.colors[index] = xgraphics.BGRA { + R: uint8(r), + G: uint8(g), + B: uint8(b), + A: uint8(a), + } + } + + // calculate metrics + metrics := backend.font.face.Metrics() + glyphAdvance, _ := backend.font.face.GlyphAdvance('M') + backend.metrics.cellWidth = glyphAdvance.Round() + backend.metrics.cellHeight = metrics.Height.Round() + backend.metrics.descent = metrics.Descent.Round() + backend.metrics.padding = + backend.config.Padding() * + backend.metrics.cellHeight + backend.metrics.paddingX = backend.metrics.padding + backend.metrics.paddingY = backend.metrics.padding + backend.metrics.windowWidth, + backend.metrics.windowHeight = backend.calculateWindowSize() + + // connect to X + backend.connection, err = xgbutil.NewConn() + if err != nil { return } + backend.window, err = xwindow.Generate(backend.connection) + if err != nil { return } + keybind.Initialize(backend.connection) + + // create the window + backend.window.Create ( + backend.connection.RootWin(), + 0, 0, + backend.metrics.windowWidth, backend.metrics.windowHeight, + 0) + backend.window.Map() + // TODO: also listen to mouse movement (compressed) and mouse and + // keyboard buttons (uncompressed) + err = backend.window.Listen ( + xproto.EventMaskStructureNotify, + xproto.EventMaskPointerMotion, + xproto.EventMaskKeyPress, + xproto.EventMaskKeyRelease, + xproto.EventMaskButtonPress, + xproto.EventMaskButtonRelease) + backend.SetTitle(application.Title()) + backend.SetIcon(application.Icon()) + if err != nil { return } + + // set minimum dimensions + minWidth := + backend.metrics.cellWidth + backend.metrics.padding * 2 + minHeight := + backend.metrics.cellHeight + backend.metrics.padding * 2 + err = icccm.WmNormalHintsSet ( + backend.connection, + backend.window.Id, + &icccm.NormalHints { + Flags: icccm.SizeHintPMinSize, + MinWidth: uint(minWidth), + MinHeight: uint(minHeight), + }) + if err != nil { return } + + // create a canvas + backend.reallocateCanvas() + + // attatch graceful close handler + backend.window.WMGracefulClose (func (window *xwindow.Window) { + backend.window.Destroy() + backend.shutDown() + }) + + // attatch event handlers + xevent.ConfigureNotifyFun(backend.handleConfigureNotify). + Connect(backend.connection, backend.window.Id) + xevent.ButtonPressFun(backend.handleButtonPress). + Connect(backend.connection, backend.window.Id) + xevent.ButtonReleaseFun(backend.handleButtonRelease). + Connect(backend.connection, backend.window.Id) + xevent.MotionNotifyFun(backend.handleMotionNotify). + Connect(backend.connection, backend.window.Id) + + output = backend + return +} + +func findAndLoadFont (name string, size float64) (face font.Face) { + if name == "" { return } + fontPath, err := findfont.Find(name) + if err != nil { return } + fontFile, err := os.Open(fontPath) + if err != nil { return } + fontObject, err := opentype.ParseReaderAt(fontFile) + if err != nil { return } + face, err = opentype.NewFace (fontObject, &opentype.FaceOptions { + Size: size, + DPI: 96, + Hinting: font.HintingFull, + }) + if err != nil { face = nil } + + return +} + +// init registers this backend when the program starts. +func init () { + stone.RegisterBackend(factory) +} diff --git a/backends/x/x.go b/backends/x/x.go index 3f70a4b..b7c7cf2 100644 --- a/backends/x/x.go +++ b/backends/x/x.go @@ -1,28 +1,18 @@ package x -import "os" // import "fmt" import "sync" import "image" -import "image/draw" import "golang.org/x/image/font" -import "golang.org/x/image/math/fixed" -import "golang.org/x/image/font/opentype" -import "golang.org/x/image/font/basicfont" // import "github.com/jezek/xgb" import "github.com/jezek/xgbutil" -import "github.com/jezek/xgb/xproto" import "github.com/jezek/xgbutil/ewmh" -import "github.com/jezek/xgbutil/icccm" -import "github.com/jezek/xgbutil/xevent" import "github.com/jezek/xgbutil/xwindow" import "github.com/jezek/xgbutil/xgraphics" import "git.tebibyte.media/sashakoshka/stone" -import "github.com/flopp/go-findfont" - type Backend struct { application *stone.Application config *stone.Config @@ -56,32 +46,6 @@ type Backend struct { } } -func (backend *Backend) Run (channel chan(stone.Event)) { - backend.channel = channel - xevent.Main(backend.connection) - backend.shutDown() -} - -func (backend *Backend) Draw () { - backend.drawLock.Lock() - defer backend.drawLock.Unlock() - - boundsChanged := - backend.memory.windowWidth != backend.metrics.windowWidth || - backend.memory.windowHeight != backend.metrics.windowHeight - backend.memory.windowWidth = backend.metrics.windowWidth - backend.memory.windowHeight = backend.metrics.windowHeight - - if boundsChanged { - backend.reallocateCanvas() - backend.drawCells(true) - backend.canvas.XDraw() - backend.canvas.XPaint(backend.window.Id) - } else { - backend.updateWindowAreas(backend.drawCells(false)...) - } -} - func (backend *Backend) SetTitle (title string) (err error) { err = ewmh.WmNameSet(backend.connection, backend.window.Id, title) return @@ -124,87 +88,6 @@ func (backend *Backend) SetIcon (icons []image.Image) (err error) { return } -func (backend *Backend) handleConfigureNotify ( - connection *xgbutil.XUtil, - event xevent.ConfigureNotifyEvent, -) { - configureEvent := *event.ConfigureNotifyEvent - - newWidth := int(configureEvent.Width) - newHeight := int(configureEvent.Height) - sizeChanged := - backend.metrics.windowWidth != newWidth || - backend.metrics.windowHeight != newHeight - backend.metrics.windowWidth = newWidth - backend.metrics.windowHeight = newHeight - - if sizeChanged { - configureEvent = - backend.compressConfigureNotify(configureEvent) - backend.application.SetSize(backend.calculateBufferSize()) - backend.channel <- stone.EventResize { } - } -} - -func (backend *Backend) handleButtonPress ( - connection *xgbutil.XUtil, - event xevent.ButtonPressEvent, -) { - buttonEvent := *event.ButtonPressEvent - backend.channel <- stone.EventPress(buttonEvent.Detail) -} - -func (backend *Backend) handleButtonRelease ( - connection *xgbutil.XUtil, - event xevent.ButtonReleaseEvent, -) { - buttonEvent := *event.ButtonReleaseEvent - backend.channel <- stone.EventRelease(buttonEvent.Detail) -} - -func (backend *Backend) handleMotionNotify ( - connection *xgbutil.XUtil, - event xevent.MotionNotifyEvent, -) { - motionEvent := *event.MotionNotifyEvent - x, y := backend.cellAt (image.Point { - X: int(motionEvent.EventX), - Y: int(motionEvent.EventY), - }) - backend.channel <- stone.EventMouseMove { - X: x, - Y: y, - } -} - -func (backend *Backend) compressConfigureNotify ( - firstEvent xproto.ConfigureNotifyEvent, -) ( - lastEvent xproto.ConfigureNotifyEvent, -) { - backend.connection.Sync() - xevent.Read(backend.connection, false) - lastEvent = firstEvent - - for index, untypedEvent := range xevent.Peek(backend.connection) { - if untypedEvent.Err != nil { continue } - - typedEvent, ok := untypedEvent.Event.(xproto.ConfigureNotifyEvent) - if !ok { continue } - - lastEvent = typedEvent - defer func (index int) { - xevent.DequeueAt(backend.connection, index) - } (index) - } - - return -} - -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) { @@ -245,69 +128,6 @@ func (backend *Backend) reallocateCanvas () { backend.canvas.XSurfaceSet(backend.window.Id) } -func (backend *Backend) drawCells (forceRedraw bool) (areas []image.Rectangle) { - width, height := backend.application.Size() - for y := 0; y < height; y ++ { - for x := 0; x < width; x ++ { - if !forceRedraw && backend.application.Clean(x, y) { continue } - backend.application.MarkClean(x, y) - - cell := backend.application.Cell(x, y) - content := cell.Rune() - - if forceRedraw && content < 32 { continue } - - areas = append(areas, backend.boundsOfCell(x, y)) - backend.drawRune(x, y, content) - }} - return -} - -func (backend *Backend) updateWindowAreas (areas ...image.Rectangle) { - backend.canvas.XPaintRects(backend.window.Id, areas...) -} - -func (backend *Backend) drawRune (x, y int, character rune) { - // TODO: cache these draws as non-transparent buffers with the - // application background color as the background. that way, we won't - // need to redraw the characters *or* composite them. - - fillRectangle ( - &image.Uniform { - C: backend.config.Color(stone.ColorApplication), - }, - backend.canvas, - backend.boundsOfCell(x, y)) - - if character < 32 { return } - - origin := backend.originOfCell(x, y + 1) - destinationRectangle, mask, maskPoint, _, _ := backend.font.face.Glyph ( - fixed.Point26_6 { - X: fixed.I(origin.X), - Y: fixed.I(origin.Y - backend.metrics.descent), - }, - character) - - // strokeRectangle ( - // &image.Uniform { - // C: backend.config.Color(stone.ColorForeground), - // }, - // backend.canvas, - // backend.boundsOfCell(x, y)) - - draw.DrawMask ( - backend.canvas, - destinationRectangle, - &image.Uniform { - C: backend.config.Color(stone.ColorForeground), - }, - image.Point { }, - mask, - maskPoint, - draw.Over) -} - func (backend *Backend) cellAt (onScreen image.Point) (x, y int) { x = (onScreen.X - backend.metrics.paddingX) / backend.metrics.cellWidth y = (onScreen.Y - backend.metrics.paddingY) / backend.metrics.cellHeight @@ -334,174 +154,3 @@ func (backend *Backend) boundsOfCell (x, y int) (bounds image.Rectangle) { } return } - -// factory instantiates an X backend. -func factory (application *stone.Application) (output stone.Backend, err error) { - backend := &Backend { - application: application, - config: application.Config(), - } - - // load font - backend.font.face = findAndLoadFont ( - backend.config.FontName(), - float64(backend.config.FontSize())) - if backend.font.face == nil { - backend.font.face = basicfont.Face7x13 - } - - // pre-calculate colors - for index := 0; index < len(backend.colors); index ++ { - color := backend.config.Color(stone.Color(index)) - r, g, b, a := color.RGBA() - r >>= 8 - g >>= 8 - b >>= 8 - a >>= 8 - backend.colors[index] = xgraphics.BGRA { - R: uint8(r), - G: uint8(g), - B: uint8(b), - A: uint8(a), - } - } - - // calculate metrics - metrics := backend.font.face.Metrics() - glyphAdvance, _ := backend.font.face.GlyphAdvance('M') - backend.metrics.cellWidth = glyphAdvance.Round() - backend.metrics.cellHeight = metrics.Height.Round() - backend.metrics.descent = metrics.Descent.Round() - backend.metrics.padding = - backend.config.Padding() * - backend.metrics.cellHeight - backend.metrics.paddingX = backend.metrics.padding - backend.metrics.paddingY = backend.metrics.padding - backend.metrics.windowWidth, - backend.metrics.windowHeight = backend.calculateWindowSize() - - // 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 - backend.window.Create ( - backend.connection.RootWin(), - 0, 0, - backend.metrics.windowWidth, backend.metrics.windowHeight, - 0) - backend.window.Map() - // TODO: also listen to mouse movement (compressed) and mouse and - // keyboard buttons (uncompressed) - err = backend.window.Listen ( - xproto.EventMaskStructureNotify, - xproto.EventMaskPointerMotion, - // xproto.EventMaskKeyPress, - // xproto.EventMaskKeyRelease, - xproto.EventMaskButtonPress, - xproto.EventMaskButtonRelease, - ) - backend.SetTitle(application.Title()) - backend.SetIcon(application.Icon()) - if err != nil { return } - - // set minimum dimensions - minWidth := - backend.metrics.cellWidth + backend.metrics.padding * 2 - minHeight := - backend.metrics.cellHeight + backend.metrics.padding * 2 - err = icccm.WmNormalHintsSet ( - backend.connection, - backend.window.Id, - &icccm.NormalHints { - Flags: icccm.SizeHintPMinSize, - MinWidth: uint(minWidth), - MinHeight: uint(minHeight), - }) - if err != nil { return } - - // create a canvas - backend.reallocateCanvas() - - // attatch graceful close handler - backend.window.WMGracefulClose (func (window *xwindow.Window) { - backend.window.Destroy() - backend.shutDown() - }) - - // attatch event handlers - xevent.ConfigureNotifyFun(backend.handleConfigureNotify). - Connect(backend.connection, backend.window.Id) - xevent.ButtonPressFun(backend.handleButtonPress). - Connect(backend.connection, backend.window.Id) - xevent.ButtonReleaseFun(backend.handleButtonRelease). - Connect(backend.connection, backend.window.Id) - xevent.MotionNotifyFun(backend.handleMotionNotify). - Connect(backend.connection, backend.window.Id) - - output = backend - return -} - -func findAndLoadFont (name string, size float64) (face font.Face) { - if name == "" { return } - fontPath, err := findfont.Find(name) - if err != nil { return } - fontFile, err := os.Open(fontPath) - if err != nil { return } - fontObject, err := opentype.ParseReaderAt(fontFile) - if err != nil { return } - face, err = opentype.NewFace (fontObject, &opentype.FaceOptions { - Size: size, - DPI: 96, - Hinting: font.HintingFull, - }) - if err != nil { face = nil } - - return -} - -func fillRectangle ( - source image.Image, - destination draw.Image, - bounds image.Rectangle, -) { - for y := bounds.Min.Y; y < bounds.Max.Y; y ++ { - for x := bounds.Min.X; x < bounds.Max.X; x ++ { - destination.Set(x, y, source.At(x, y)) - }} -} - -func strokeRectangle ( - source image.Image, - destination draw.Image, - bounds image.Rectangle, -) { - x := 0 - y := bounds.Min.Y - for x = bounds.Min.X; x < bounds.Max.X; x ++ { - destination.Set(x, y, source.At(x, y)) - } - - y = bounds.Max.Y - 1 - for x = bounds.Min.X; x < bounds.Max.X; x ++ { - destination.Set(x, y, source.At(x, y)) - } - - x = bounds.Min.X - for y = bounds.Min.Y; y < bounds.Max.Y; y ++ { - destination.Set(x, y, source.At(x, y)) - } - - x = bounds.Max.X - 1 - for y = bounds.Min.Y; y < bounds.Max.Y; y ++ { - destination.Set(x, y, source.At(x, y)) - } -} - -// init registers this backend when the program starts. -func init () { - stone.RegisterBackend(factory) -} From aee4158d2da3ca6b861ed3c289e61892b2086bbe Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Sun, 13 Nov 2022 23:47:35 -0500 Subject: [PATCH 25/33] Added keyboard input --- backends/x/event.go | 13 +++++-- backends/x/unicode.go | 83 +++++++++++++++++++++++++++++++++++++++++++ input.go | 37 +++++++------------ 3 files changed, 106 insertions(+), 27 deletions(-) create mode 100644 backends/x/unicode.go diff --git a/backends/x/event.go b/backends/x/event.go index 76a5e61..e306795 100644 --- a/backends/x/event.go +++ b/backends/x/event.go @@ -59,8 +59,17 @@ func (backend *Backend) handleKeyPress ( event xevent.KeyPressEvent, ) { keyEvent := *event.KeyPressEvent - keySym := keybind.KeysymGet(backend.connection, keyEvent.Detail, 0) - // TODO: convert to keysym and then to a button value + keysym := keybind.KeysymGet(backend.connection, keyEvent.Detail, 0) + backend.channel <- stone.EventPress(keysymToButtonCode(keysym)) +} + +func (backend *Backend) handleKeyRelease ( + connection *xgbutil.XUtil, + event xevent.KeyPressEvent, +) { + keyEvent := *event.KeyPressEvent + keysym := keybind.KeysymGet(backend.connection, keyEvent.Detail, 0) + backend.channel <- stone.EventRelease(keysymToButtonCode(keysym)) } func (backend *Backend) handleMotionNotify ( diff --git a/backends/x/unicode.go b/backends/x/unicode.go new file mode 100644 index 0000000..1c0445f --- /dev/null +++ b/backends/x/unicode.go @@ -0,0 +1,83 @@ +package x + +import "github.com/jezek/xgb/xproto" +import "git.tebibyte.media/sashakoshka/stone" + +// when making changes to this file, look at keysymdef.h + +var buttonCodeTable = map[xproto.Keysym] stone.Button { + 0xFFFFFF: stone.ButtonUnknown, + + 0xFF63: stone.KeyInsert, + 0xFF67: stone.KeyMenu, + 0xFF61: stone.KeyPrintScreen, + 0xFF6B: stone.KeyPause, + 0xFFE5: stone.KeyCapsLock, + 0xFF14: stone.KeyScrollLock, + 0xFF7F: stone.KeyNumLock, + 0xFF08: stone.KeyBackspace, + 0xFF09: stone.KeyTab, + 0xFF0A: stone.KeyEnter, + 0xFF1B: stone.KeyEscape, + + 0xFF52: stone.KeyUp, + 0xFF54: stone.KeyDown, + 0xFF51: stone.KeyLeft, + 0xFF53: stone.KeyRight, + 0xFF55: stone.KeyPageUp, + 0xFF56: stone.KeyPageDown, + 0xFF50: stone.KeyHome, + 0xFF57: stone.KeyEnd, + + 0xFFE1: stone.KeyLeftShift, + 0xFFE2: stone.KeyRightShift, + 0xFFE3: stone.KeyLeftControl, + 0xFFE4: stone.KeyRightControl, + 0xFFE9: stone.KeyLeftAlt, + 0xFFEA: stone.KeyRightAlt, + 0xFFEB: stone.KeyLeftSuper, + 0xFFEC: stone.KeyRightSuper, + + 0xFFFF: stone.KeyDelete, + + 0xFFBE: stone.KeyF1, + 0xFFBF: stone.KeyF2, + 0xFFC0: stone.KeyF3, + 0xFFC1: stone.KeyF4, + 0xFFC2: stone.KeyF5, + 0xFFC3: stone.KeyF6, + 0xFFC4: stone.KeyF7, + 0xFFC5: stone.KeyF8, + 0xFFC6: stone.KeyF9, + 0xFFC7: stone.KeyF10, + 0xFFC8: stone.KeyF11, + 0xFFC9: stone.KeyF12, +} + +func keysymToButtonCode (keysym xproto.Keysym) (button stone.Button) { + var isControl bool + button, isControl = buttonCodeTable[keysym] + if isControl { return } + + // some X keysyms have a single bit set to 1 here. i believe this is to + // prevent conflicts with existing codes. if we mask it off we will get + // a correct utf-32 code point. + if keysym & 0xF000000 == 0x1000000 { + button = stone.Button(keysym & 0x0111111) + return + } + + // X keysyms like 0xFF.. or 0xFE.. are non-character keys. we already + // resolve these by looking them up in the button code table, so if any + // that we don't support pop up we should just silence them as it is + // effectively garbage data as far as stone applications are concerned. + if (keysym >> 8) == 0xFF || (keysym >> 8) == 0xFE { + button = stone.ButtonUnknown + return + } + + // if none of these things happened, we can safely (i think) assume that + // the keysym is an exact utf-32 code point. + button = stone.Button(keysym) + return +} diff --git a/input.go b/input.go index 438f3b0..d18ea7b 100644 --- a/input.go +++ b/input.go @@ -56,29 +56,16 @@ const ( MouseButtonBack Button = MouseButton8 MouseButtonForward Button = MouseButton9 - KeyF1 Button = 136 - KeyF2 Button = 137 - KeyF3 Button = 138 - KeyF4 Button = 139 - KeyF5 Button = 140 - KeyF6 Button = 141 - KeyF7 Button = 142 - KeyF8 Button = 143 - KeyF9 Button = 144 - KeyF10 Button = 145 - KeyF11 Button = 146 - KeyF12 Button = 147 - KeyF13 Button = 148 - KeyF14 Button = 149 - KeyF15 Button = 150 - KeyF16 Button = 151 - KeyF17 Button = 152 - KeyF18 Button = 153 - KeyF19 Button = 154 - KeyF20 Button = 155 - KeyF21 Button = 156 - KeyF22 Button = 157 - KeyF23 Button = 158 - KeyF24 Button = 159 - KeyF25 Button = 28 + KeyF1 Button = 144 + KeyF2 Button = 145 + KeyF3 Button = 146 + KeyF4 Button = 147 + KeyF5 Button = 148 + KeyF6 Button = 149 + KeyF7 Button = 150 + KeyF8 Button = 151 + KeyF9 Button = 152 + KeyF10 Button = 153 + KeyF11 Button = 154 + KeyF12 Button = 155 ) From 820c7d4f6a44d9389a3e3240c94f0580e069c3b4 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Mon, 14 Nov 2022 15:58:34 -0500 Subject: [PATCH 26/33] Made separate function for converting keycode to keysym --- backends/x/event.go | 9 ++++----- backends/x/factory.go | 4 ++++ backends/x/unicode.go | 19 +++++++++++++++++++ 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/backends/x/event.go b/backends/x/event.go index e306795..45d7ebb 100644 --- a/backends/x/event.go +++ b/backends/x/event.go @@ -5,7 +5,6 @@ import "image" import "github.com/jezek/xgbutil" import "github.com/jezek/xgb/xproto" import "github.com/jezek/xgbutil/xevent" -import "github.com/jezek/xgbutil/keybind" import "git.tebibyte.media/sashakoshka/stone" @@ -59,16 +58,16 @@ func (backend *Backend) handleKeyPress ( event xevent.KeyPressEvent, ) { keyEvent := *event.KeyPressEvent - keysym := keybind.KeysymGet(backend.connection, keyEvent.Detail, 0) + keysym := backend.keycodeToKeysym(keyEvent.Detail) backend.channel <- stone.EventPress(keysymToButtonCode(keysym)) } func (backend *Backend) handleKeyRelease ( connection *xgbutil.XUtil, - event xevent.KeyPressEvent, + event xevent.KeyReleaseEvent, ) { - keyEvent := *event.KeyPressEvent - keysym := keybind.KeysymGet(backend.connection, keyEvent.Detail, 0) + keyEvent := *event.KeyReleaseEvent + keysym := backend.keycodeToKeysym(keyEvent.Detail) backend.channel <- stone.EventRelease(keysymToButtonCode(keysym)) } diff --git a/backends/x/factory.go b/backends/x/factory.go index 2ed9b3c..1885496 100644 --- a/backends/x/factory.go +++ b/backends/x/factory.go @@ -122,6 +122,10 @@ func factory (application *stone.Application) (output stone.Backend, err error) Connect(backend.connection, backend.window.Id) xevent.MotionNotifyFun(backend.handleMotionNotify). Connect(backend.connection, backend.window.Id) + xevent.KeyPressFun(backend.handleKeyPress). + Connect(backend.connection, backend.window.Id) + xevent.KeyReleaseFun(backend.handleKeyRelease). + Connect(backend.connection, backend.window.Id) output = backend return diff --git a/backends/x/unicode.go b/backends/x/unicode.go index 1c0445f..b1c2df3 100644 --- a/backends/x/unicode.go +++ b/backends/x/unicode.go @@ -1,6 +1,7 @@ package x import "github.com/jezek/xgb/xproto" +import "github.com/jezek/xgbutil/keybind" import "git.tebibyte.media/sashakoshka/stone" // when making changes to this file, look at keysymdef.h @@ -54,6 +55,24 @@ var buttonCodeTable = map[xproto.Keysym] stone.Button { 0xFFC9: stone.KeyF12, } +func (backend *Backend) keycodeToKeysym ( + keycode xproto.Keycode, +) ( + keysym xproto.Keysym, +) { + keysym = keybind.KeysymGet(backend.connection, keycode, 0) + + // TODO: shift isnt working. follow + // https://tronche.com/gui/x/xlib/input/keyboard-encoding.html + + println("--") + println(keycode) + println(keysym) + println(stone.EventPress(keysymToButtonCode(keysym))) + + return +} + func keysymToButtonCode (keysym xproto.Keysym) (button stone.Button) { var isControl bool button, isControl = buttonCodeTable[keysym] From 84cd21b16d38589fd6d4a978e19d62890fbaa11d Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Mon, 14 Nov 2022 22:33:46 -0500 Subject: [PATCH 27/33] Added untested advanced keycode translation --- backends/x/event.go | 8 +-- backends/x/unicode.go | 133 +++++++++++++++++++++++++++++++++++------- 2 files changed, 116 insertions(+), 25 deletions(-) diff --git a/backends/x/event.go b/backends/x/event.go index 45d7ebb..04154e5 100644 --- a/backends/x/event.go +++ b/backends/x/event.go @@ -58,8 +58,8 @@ func (backend *Backend) handleKeyPress ( event xevent.KeyPressEvent, ) { keyEvent := *event.KeyPressEvent - keysym := backend.keycodeToKeysym(keyEvent.Detail) - backend.channel <- stone.EventPress(keysymToButtonCode(keysym)) + button := backend.keycodeToButton(keyEvent.Detail, keyEvent.State) + backend.channel <- stone.EventPress(button) } func (backend *Backend) handleKeyRelease ( @@ -67,8 +67,8 @@ func (backend *Backend) handleKeyRelease ( event xevent.KeyReleaseEvent, ) { keyEvent := *event.KeyReleaseEvent - keysym := backend.keycodeToKeysym(keyEvent.Detail) - backend.channel <- stone.EventRelease(keysymToButtonCode(keysym)) + button := backend.keycodeToButton(keyEvent.Detail, keyEvent.State) + backend.channel <- stone.EventRelease(button) } func (backend *Backend) handleMotionNotify ( diff --git a/backends/x/unicode.go b/backends/x/unicode.go index b1c2df3..0cb45a5 100644 --- a/backends/x/unicode.go +++ b/backends/x/unicode.go @@ -1,5 +1,6 @@ package x +import "unicode" import "github.com/jezek/xgb/xproto" import "github.com/jezek/xgbutil/keybind" import "git.tebibyte.media/sashakoshka/stone" @@ -55,48 +56,138 @@ var buttonCodeTable = map[xproto.Keysym] stone.Button { 0xFFC9: stone.KeyF12, } -func (backend *Backend) keycodeToKeysym ( +func (backend *Backend) keycodeToButton ( keycode xproto.Keycode, + state uint16, ) ( - keysym xproto.Keysym, + button stone.Button, ) { - keysym = keybind.KeysymGet(backend.connection, keycode, 0) + // FIXME: also set shift to true if the lock modifier is on and the lock + // modifier is interpreted as shiftLock + shift := state & xproto.ModMaskShift > 0 + + // FIXME: only set this to true if the lock modifier is on and the lock + // modifier is interpreted as capsLock + capsLock := state & xproto.ModMaskLock > 0 + + symbol1 := keybind.KeysymGet(backend.connection, keycode, 0) + symbol2 := keybind.KeysymGet(backend.connection, keycode, 1) + symbol3 := keybind.KeysymGet(backend.connection, keycode, 2) + symbol4 := keybind.KeysymGet(backend.connection, keycode, 3) + + cased := false + + // third paragraph + switch { + case symbol2 == 0 && symbol3 == 0 && symbol4 == 0: + symbol3 = symbol1 + case symbol3 == 0 && symbol4 == 0: + symbol3 = symbol1 + symbol2 = symbol2 + case symbol4 == 0: + symbol4 = 0 + } + + symbol1Rune := keysymToRune(symbol1) + symbol2Rune := keysymToRune(symbol2) + symbol3Rune := keysymToRune(symbol3) + symbol4Rune := keysymToRune(symbol4) + + // FIXME: we ignore mode switch stuff + _ = symbol4Rune + + // fourth paragraph + if symbol2 == 0 { + upper := unicode.IsUpper(symbol1Rune) + lower := unicode.IsLower(symbol1Rune) + if upper || lower { + symbol1Rune = unicode.ToLower(symbol1Rune) + symbol2Rune = unicode.ToUpper(symbol1Rune) + cased = true + } else { + symbol2 = symbol1 + } + } + if symbol4 == 0 { + upper := unicode.IsUpper(symbol3Rune) + lower := unicode.IsLower(symbol3Rune) + if upper || lower { + symbol3Rune = unicode.ToLower(symbol3Rune) + symbol4Rune = unicode.ToUpper(symbol3Rune) + cased = true + } else { + symbol4 = symbol3 + } + } + + var selectedKeysym xproto.Keysym + var selectedRune rune + + // big ol list in the middle + switch { + // FIXME: take into account numlock + case !shift && !capsLock: + selectedKeysym = symbol1 + selectedRune = symbol1Rune + + case !shift && capsLock: + if cased && unicode.IsLower(symbol1Rune) { + selectedRune = symbol2Rune + } else { + selectedKeysym = symbol1 + selectedRune = symbol1Rune + } + + case shift && capsLock: + if cased && unicode.IsLower(symbol2Rune) { + selectedRune = unicode.ToUpper(symbol2Rune) + } else { + selectedKeysym = symbol2 + selectedRune = symbol2Rune + } + + case shift: + selectedKeysym = symbol3 + selectedRune = symbol3Rune + } + + // look up in table + var isControl bool + button, isControl = buttonCodeTable[selectedKeysym] + if isControl { return } + + button = stone.Button(selectedRune) // TODO: shift isnt working. follow // https://tronche.com/gui/x/xlib/input/keyboard-encoding.html println("--") println(keycode) - println(keysym) - println(stone.EventPress(keysymToButtonCode(keysym))) + println(selectedKeysym) + println(selectedRune) + println(button) return } -func keysymToButtonCode (keysym xproto.Keysym) (button stone.Button) { - var isControl bool - button, isControl = buttonCodeTable[keysym] - if isControl { return } - +func keysymToRune (keysym xproto.Keysym) (character rune) { + // X keysyms like 0xFF.. or 0xFE.. are non-character keys. these cannot + // be converted so we return a zero. + if (keysym >> 8) == 0xFF || (keysym >> 8) == 0xFE { + character = 0 + return + } + // some X keysyms have a single bit set to 1 here. i believe this is to // prevent conflicts with existing codes. if we mask it off we will get // a correct utf-32 code point. if keysym & 0xF000000 == 0x1000000 { - button = stone.Button(keysym & 0x0111111) - return - } - - // X keysyms like 0xFF.. or 0xFE.. are non-character keys. we already - // resolve these by looking them up in the button code table, so if any - // that we don't support pop up we should just silence them as it is - // effectively garbage data as far as stone applications are concerned. - if (keysym >> 8) == 0xFF || (keysym >> 8) == 0xFE { - button = stone.ButtonUnknown + character = rune(keysym & 0x0111111) return } // if none of these things happened, we can safely (i think) assume that // the keysym is an exact utf-32 code point. - button = stone.Button(keysym) + character = rune(keysym) return } From c6c7383ef2ba6f9cf3cd864c863b259459f2dd47 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Mon, 14 Nov 2022 22:58:44 -0500 Subject: [PATCH 28/33] Fixed advance keycode translation --- backends/x/unicode.go | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/backends/x/unicode.go b/backends/x/unicode.go index 0cb45a5..ecfa329 100644 --- a/backends/x/unicode.go +++ b/backends/x/unicode.go @@ -5,7 +5,8 @@ import "github.com/jezek/xgb/xproto" import "github.com/jezek/xgbutil/keybind" import "git.tebibyte.media/sashakoshka/stone" -// when making changes to this file, look at keysymdef.h +// when making changes to this file, look at keysymdef.h and +// https://tronche.com/gui/x/xlib/input/keyboard-encoding.html var buttonCodeTable = map[xproto.Keysym] stone.Button { 0xFFFFFF: stone.ButtonUnknown, @@ -19,7 +20,7 @@ var buttonCodeTable = map[xproto.Keysym] stone.Button { 0xFF7F: stone.KeyNumLock, 0xFF08: stone.KeyBackspace, 0xFF09: stone.KeyTab, - 0xFF0A: stone.KeyEnter, + 0xFF0D: stone.KeyEnter, 0xFF1B: stone.KeyEscape, 0xFF52: stone.KeyUp, @@ -147,25 +148,18 @@ func (backend *Backend) keycodeToButton ( } case shift: - selectedKeysym = symbol3 - selectedRune = symbol3Rune + selectedKeysym = symbol2 + selectedRune = symbol2Rune } // look up in table var isControl bool button, isControl = buttonCodeTable[selectedKeysym] - if isControl { return } - button = stone.Button(selectedRune) - - // TODO: shift isnt working. follow - // https://tronche.com/gui/x/xlib/input/keyboard-encoding.html - - println("--") - println(keycode) - println(selectedKeysym) - println(selectedRune) - println(button) + // if it wasn't found, + if !isControl { + button = stone.Button(selectedRune) + } return } From b816a4abf583d70ae3b262583509d657e4ecdfb1 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Mon, 14 Nov 2022 23:09:31 -0500 Subject: [PATCH 29/33] Added a Printable method to button --- examples/type/main.go | 10 +++++++--- input.go | 7 +++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/examples/type/main.go b/examples/type/main.go index 92ebfb3..cfac4dc 100644 --- a/examples/type/main.go +++ b/examples/type/main.go @@ -38,10 +38,14 @@ func main () { os.Exit(0) case stone.EventPress: - event := event.(stone.EventPress) - if stone.event.Printable() { - application.SetRune(caret, 0, rune(stone.event)) + button := stone.Button(event.(stone.EventPress)) + if button.Printable() { + application.SetRune(caret, 0, rune(button)) caret ++ + width, _ := application.Size() + if caret >= width { + caret = 0 + } application.Draw() } diff --git a/input.go b/input.go index d18ea7b..5476ab1 100644 --- a/input.go +++ b/input.go @@ -1,5 +1,7 @@ package stone +import "unicode" + type Button int const ( @@ -69,3 +71,8 @@ const ( KeyF11 Button = 154 KeyF12 Button = 155 ) + +func (button Button) Printable () (printable bool) { + printable = unicode.IsPrint(rune(button)) + return +} From 1d71df180ddd1dec52e5e7178f6ded71f454037d Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Mon, 14 Nov 2022 23:15:21 -0500 Subject: [PATCH 30/33] Changed how buttons are stored in press and release events --- backends/x/event.go | 12 ++++++++---- event.go | 4 ++-- examples/draw/main.go | 8 ++++---- examples/type/main.go | 2 +- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/backends/x/event.go b/backends/x/event.go index 04154e5..a3a8b8e 100644 --- a/backends/x/event.go +++ b/backends/x/event.go @@ -42,7 +42,9 @@ func (backend *Backend) handleButtonPress ( event xevent.ButtonPressEvent, ) { buttonEvent := *event.ButtonPressEvent - backend.channel <- stone.EventPress(buttonEvent.Detail) + backend.channel <- stone.EventPress { + Button: stone.Button(buttonEvent.Detail), + } } func (backend *Backend) handleButtonRelease ( @@ -50,7 +52,9 @@ func (backend *Backend) handleButtonRelease ( event xevent.ButtonReleaseEvent, ) { buttonEvent := *event.ButtonReleaseEvent - backend.channel <- stone.EventRelease(buttonEvent.Detail) + backend.channel <- stone.EventRelease { + Button: stone.Button(buttonEvent.Detail), + } } func (backend *Backend) handleKeyPress ( @@ -59,7 +63,7 @@ func (backend *Backend) handleKeyPress ( ) { keyEvent := *event.KeyPressEvent button := backend.keycodeToButton(keyEvent.Detail, keyEvent.State) - backend.channel <- stone.EventPress(button) + backend.channel <- stone.EventPress { Button: button } } func (backend *Backend) handleKeyRelease ( @@ -68,7 +72,7 @@ func (backend *Backend) handleKeyRelease ( ) { keyEvent := *event.KeyReleaseEvent button := backend.keycodeToButton(keyEvent.Detail, keyEvent.State) - backend.channel <- stone.EventRelease(button) + backend.channel <- stone.EventRelease { Button: button } } func (backend *Backend) handleMotionNotify ( diff --git a/event.go b/event.go index f60cff2..8fbefb7 100644 --- a/event.go +++ b/event.go @@ -3,8 +3,8 @@ package stone type Event interface { } type EventQuit struct { } -type EventPress Button -type EventRelease Button +type EventPress struct { Button } +type EventRelease struct { Button } type EventResize struct { } type EventMouseMove struct { X int diff --git a/examples/draw/main.go b/examples/draw/main.go index 52d54ba..7d78da8 100644 --- a/examples/draw/main.go +++ b/examples/draw/main.go @@ -38,16 +38,16 @@ func main () { os.Exit(0) case stone.EventPress: - event := event.(stone.EventPress) - if stone.Button(event) == stone.MouseButtonLeft { + button := event.(stone.EventPress).Button + if button == stone.MouseButtonLeft { mousePressed = true application.SetRune(0, 0, '+') application.Draw() } case stone.EventRelease: - event := event.(stone.EventRelease) - if stone.Button(event) == stone.MouseButtonLeft { + button := event.(stone.EventRelease).Button + if button == stone.MouseButtonLeft { mousePressed = false application.SetRune(0, 0, 0) application.Draw() diff --git a/examples/type/main.go b/examples/type/main.go index cfac4dc..1cda63e 100644 --- a/examples/type/main.go +++ b/examples/type/main.go @@ -38,7 +38,7 @@ func main () { os.Exit(0) case stone.EventPress: - button := stone.Button(event.(stone.EventPress)) + button := event.(stone.EventPress).Button if button.Printable() { application.SetRune(caret, 0, rune(button)) caret ++ From d9c9895e82bf23f78e1c55f0d5e3f44791f0c45b Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Mon, 14 Nov 2022 23:17:10 -0500 Subject: [PATCH 31/33] Fixed mouse buttons --- backends/x/event.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backends/x/event.go b/backends/x/event.go index a3a8b8e..4195431 100644 --- a/backends/x/event.go +++ b/backends/x/event.go @@ -43,7 +43,7 @@ func (backend *Backend) handleButtonPress ( ) { buttonEvent := *event.ButtonPressEvent backend.channel <- stone.EventPress { - Button: stone.Button(buttonEvent.Detail), + Button: stone.Button(buttonEvent.Detail + 127), } } @@ -53,7 +53,7 @@ func (backend *Backend) handleButtonRelease ( ) { buttonEvent := *event.ButtonReleaseEvent backend.channel <- stone.EventRelease { - Button: stone.Button(buttonEvent.Detail), + Button: stone.Button(buttonEvent.Detail + 127), } } From 79d8891c5bc862a4ef490ab9ffaf8d91bd33fddb Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Mon, 14 Nov 2022 23:32:05 -0500 Subject: [PATCH 32/33] Added some stuff to draw debug bounding boxes --- backends/x/draw.go | 28 +++++++++++++++++++++------- backends/x/factory.go | 4 ++++ backends/x/x.go | 3 +++ 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/backends/x/draw.go b/backends/x/draw.go index 82ff82b..0a94d08 100644 --- a/backends/x/draw.go +++ b/backends/x/draw.go @@ -51,13 +51,15 @@ func (backend *Backend) drawRune (x, y int, character rune) { Y: fixed.I(origin.Y - backend.metrics.descent), }, character) - - // strokeRectangle ( - // &image.Uniform { - // C: backend.config.Color(stone.ColorForeground), - // }, - // backend.canvas, - // backend.boundsOfCell(x, y)) + + if backend.drawCellBounds { + strokeRectangle ( + &image.Uniform { + C: backend.config.Color(stone.ColorForeground), + }, + backend.canvas, + backend.boundsOfCell(x, y)) + } draw.DrawMask ( backend.canvas, @@ -86,6 +88,18 @@ func (backend *Backend) drawCells (forceRedraw bool) (areas []image.Rectangle) { areas = append(areas, backend.boundsOfCell(x, y)) backend.drawRune(x, y, content) }} + + if backend.drawBufferBounds && forceRedraw { + strokeRectangle ( + &image.Uniform { + C: backend.config.Color(stone.ColorForeground), + }, + backend.canvas, + image.Rectangle { + Min: backend.originOfCell(0, 0), + Max: backend.originOfCell(width, height), + }) + } return } diff --git a/backends/x/factory.go b/backends/x/factory.go index 1885496..c19fd51 100644 --- a/backends/x/factory.go +++ b/backends/x/factory.go @@ -126,6 +126,10 @@ func factory (application *stone.Application) (output stone.Backend, err error) Connect(backend.connection, backend.window.Id) xevent.KeyReleaseFun(backend.handleKeyRelease). Connect(backend.connection, backend.window.Id) + + // uncomment these to draw debug bounds + // backend.drawCellBounds = true + // backend.drawBufferBounds = true output = backend return diff --git a/backends/x/x.go b/backends/x/x.go index b7c7cf2..c930c84 100644 --- a/backends/x/x.go +++ b/backends/x/x.go @@ -21,6 +21,9 @@ type Backend struct { canvas *xgraphics.Image channel chan(stone.Event) + drawCellBounds bool + drawBufferBounds bool + drawLock sync.Mutex font struct { From 6b251e6fee1f9f437f0abc0609f0af5c75ec9dce Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Mon, 14 Nov 2022 23:38:41 -0500 Subject: [PATCH 33/33] Position buffer in center of screen --- backends/x/event.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/backends/x/event.go b/backends/x/event.go index 4195431..e55c61e 100644 --- a/backends/x/event.go +++ b/backends/x/event.go @@ -32,7 +32,19 @@ func (backend *Backend) handleConfigureNotify ( if sizeChanged { configureEvent = backend.compressConfigureNotify(configureEvent) - backend.application.SetSize(backend.calculateBufferSize()) + + // resize buffer + width, height := backend.calculateBufferSize() + backend.application.SetSize(width, height) + + // position buffer in the center of the screen + frameWidth := width * backend.metrics.cellWidth + frameHeight := height * backend.metrics.cellHeight + backend.metrics.paddingX = + (backend.metrics.windowWidth - frameWidth) / 2 + backend.metrics.paddingY = + (backend.metrics.windowHeight - frameHeight) / 2 + backend.channel <- stone.EventResize { } } }