From 77cf88b8565e3a5b13c16a9ae583f2fe6b5f6f92 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Tue, 15 Nov 2022 17:36:41 -0500 Subject: [PATCH 01/10] There is only one type of buffer now --- application.go | 4 +- backends/x/event.go | 4 -- buffer.go | 113 ++++++++++++++------------------------------ config.go | 1 - 4 files changed, 37 insertions(+), 85 deletions(-) diff --git a/application.go b/application.go index 3d5b63b..8ca021f 100644 --- a/application.go +++ b/application.go @@ -4,7 +4,7 @@ import "image" // Application represents an application. type Application struct { - DamageBuffer + Buffer title string icons []image.Image @@ -22,7 +22,7 @@ func (application *Application) Run () ( width, height := application.Size() if width < 1 { width = 80 } if height < 1 { height = 20 } - application.DamageBuffer.SetSize(width, height) + application.Buffer.SetSize(width, height) application.config.load() diff --git a/backends/x/event.go b/backends/x/event.go index 1291f23..e55c61e 100644 --- a/backends/x/event.go +++ b/backends/x/event.go @@ -33,10 +33,6 @@ func (backend *Backend) handleConfigureNotify ( configureEvent = backend.compressConfigureNotify(configureEvent) - // we should not resize the canvas while drawing is taking place - backend.drawLock.Lock() - defer backend.drawLock.Unlock() - // resize buffer width, height := backend.calculateBufferSize() backend.application.SetSize(width, height) diff --git a/buffer.go b/buffer.go index 35b13c1..616ebd1 100644 --- a/buffer.go +++ b/buffer.go @@ -51,9 +51,14 @@ func (cell Cell) Rune () (content rune) { return } -// Buffer is a basic grid of cells. +// Buffer is a two dimensional text buffer that stores a grid of cells, as well +// as information stating whether each cell is clean or dirty. Cells are dirty +// by default, are only clean when marked as clean, and become dirty again when +// they are altered in some way. type Buffer struct { content []Cell + clean []bool + width int height int Dot struct { @@ -78,16 +83,11 @@ func (buffer *Buffer) Size () (width, height int) { return } -// SetSize sets the width and height of the buffer. This clears all data in the -// buffer. If the width or height is negative, this method does nothing. -func (buffer *Buffer) SetSize (width, height int) { - if width < 0 || height < 0 { return } - buffer.width = width - buffer.height = height - buffer.content = make([]Cell, width * height) - for index := 0; index < len(buffer.content); index ++ { - buffer.content[index].color = ColorForeground - } +// ResetDot is a convenience method to reset the dot to the buffer origin point +// (0, 0). +func (buffer *Buffer) ResetDot () { + buffer.Dot.X = 0 + buffer.Dot.Y = 0 } // Cell returns the cell at the specified x and y coordinates. If the @@ -101,19 +101,38 @@ func (buffer *Buffer) Cell (x, y int) (cell Cell) { // SetColor sets the color of the cell at the specified x and y coordinates. func (buffer *Buffer) SetColor (x, y int, color Color) { if buffer.isOutOfBounds(x, y) { return } - buffer.content[x + y * buffer.width].color = color + index := x + y * buffer.width + buffer.clean[index] = buffer.content[index].color == color + buffer.content[index].color = color +} + +// SetSize sets the width and height of the buffer. This clears all data in the +// buffer. If the width or height is negative, this method does nothing. +func (buffer *Buffer) SetSize (width, height int) { + if width < 0 || height < 0 { return } + buffer.width = width + buffer.height = height + buffer.content = make([]Cell, width * height) + buffer.clean = make([]bool, width * height) + for index := 0; index < len(buffer.content); index ++ { + buffer.content[index].color = ColorForeground + } } // SetStyle sets the style of the cell at the specified x and y coordinates. func (buffer *Buffer) SetStyle (x, y int, style Style) { if buffer.isOutOfBounds(x, y) { return } - buffer.content[x + y * buffer.width].style = style + index := x + y * buffer.width + buffer.clean[index] = buffer.content[index].style == style + buffer.content[index].style = style } // SetRune sets the rune of the cell at the specified x and y coordinates. func (buffer *Buffer) SetRune (x, y int, content rune) { if buffer.isOutOfBounds(x, y) { return } - buffer.content[x + y * buffer.width].content = content + index := x + y * buffer.width + buffer.clean[index] = buffer.content[index].content == content + buffer.content[index].content = content } // Write writes data stored in a byte slice to the buffer at the current dot @@ -131,78 +150,16 @@ func (buffer *Buffer) Write (bytes []byte) (bytesWritten int, err error) { return } -// ResetDot is a convenience method to reset the dot to the buffer origin point -// (0, 0). -func (buffer *Buffer) ResetDot () { - buffer.Dot.X = 0 - buffer.Dot.Y = 0 -} - -// DamageBuffer is a special buffer that keeps track of damage information. -// Cells are dirty by default, are only clean when marked as clean, and become -// dirty again when they are altered in some way. -type DamageBuffer struct { - Buffer - clean []bool -} - -// SetSize sets the width and height of the buffer. This clears all data in the -// buffer. If the width or height is negative, this method does nothing. -func (buffer *DamageBuffer) SetSize (width, height int) { - if width < 0 || height < 0 { return } - buffer.Buffer.SetSize(width, height) - buffer.clean = make([]bool, width * height) -} - -// SetColor sets the color of the cell at the specified x and y coordinates. -func (buffer *DamageBuffer) SetColor (x, y int, color Color) { - if buffer.isOutOfBounds(x, y) { return } - index := x + y * buffer.width - buffer.clean[index] = buffer.content[index].color == color - buffer.Buffer.SetColor(x, y, color) -} - -// SetStyle sets the style of the cell at the specified x and y coordinates. -func (buffer *DamageBuffer) SetStyle (x, y int, style Style) { - if buffer.isOutOfBounds(x, y) { return } - index := x + y * buffer.width - buffer.clean[index] = buffer.content[index].style == style - buffer.Buffer.SetStyle(x, y, style) -} - -// SetRune sets the rune of the cell at the specified x and y coordinates. -func (buffer *DamageBuffer) SetRune (x, y int, content rune) { - if buffer.isOutOfBounds(x, y) { return } - index := x + y * buffer.width - buffer.clean[index] = buffer.content[index].content == content - buffer.Buffer.SetRune(x, y, content) -} - -// Write writes data stored in a byte slice to the buffer at the current dot -// position. This makes DamageBuffer an io.Writer. -func (buffer *DamageBuffer) Write (bytes []byte) (bytesWritten int, err error) { - text := string(bytes) - bytesWritten = len(bytes) - - for _, character := range text { - buffer.SetRune(buffer.Dot.X, buffer.Dot.Y, character) - buffer.Dot.X ++ - if buffer.Dot.X > buffer.width { break } - } - - return -} - // Clean returns whether or not the cell at the specified x and y coordinates is // clean. -func (buffer *DamageBuffer) Clean (x, y int) (clean bool) { +func (buffer *Buffer) Clean (x, y int) (clean bool) { if buffer.isOutOfBounds(x, y) { return } clean = buffer.clean[x + y * buffer.width] return } // MarkClean marks the cell at the specified x and y coordinates as clean. -func (buffer *DamageBuffer) MarkClean (x, y int) { +func (buffer *Buffer) MarkClean (x, y int) { if buffer.isOutOfBounds(x, y) { return } buffer.clean[x + y * buffer.width] = true } diff --git a/config.go b/config.go index 74765ba..d08a7c9 100644 --- a/config.go +++ b/config.go @@ -42,7 +42,6 @@ func (config *Config) FontName () (fontName string) { } func (config *Config) load () { - // TODO: load these from a file config.colors = [8]color.Color { // background color.RGBA { R: 0, G: 0, B: 0, A: 0 }, From 85994112cf3cc715c9543e672e50f983d4b1ce87 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Tue, 15 Nov 2022 17:41:08 -0500 Subject: [PATCH 02/10] Made buffer's dot private --- buffer.go | 22 +++++++++++----------- examples/color/main.go | 3 +-- examples/hello/main.go | 2 +- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/buffer.go b/buffer.go index 616ebd1..659a63e 100644 --- a/buffer.go +++ b/buffer.go @@ -61,9 +61,9 @@ type Buffer struct { width int height int - Dot struct { - X int - Y int + dot struct { + x int + y int } } @@ -83,11 +83,11 @@ func (buffer *Buffer) Size () (width, height int) { return } -// ResetDot is a convenience method to reset the dot to the buffer origin point -// (0, 0). -func (buffer *Buffer) ResetDot () { - buffer.Dot.X = 0 - buffer.Dot.Y = 0 +// SetDot sets the buffer's text insertion position relative to the buffer +// origin point (0, 0). +func (buffer *Buffer) SetDot (x, y int) { + buffer.dot.x = x + buffer.dot.y = y } // Cell returns the cell at the specified x and y coordinates. If the @@ -142,9 +142,9 @@ func (buffer *Buffer) Write (bytes []byte) (bytesWritten int, err error) { bytesWritten = len(bytes) for _, character := range text { - buffer.SetRune(buffer.Dot.X, buffer.Dot.Y, character) - buffer.Dot.X ++ - if buffer.Dot.X > buffer.width { break } + buffer.SetRune(buffer.dot.x, buffer.dot.y, character) + buffer.dot.x ++ + if buffer.dot.x > buffer.width { break } } return diff --git a/examples/color/main.go b/examples/color/main.go index e18be99..2c84fbc 100644 --- a/examples/color/main.go +++ b/examples/color/main.go @@ -47,8 +47,7 @@ func redraw () { text := "RAINBOW :D" width, height := application.Size() - application.Dot.X = (width - len(text)) / 2 - application.Dot.Y = height / 2 + application.SetDot((width - len(text)) / 2, height / 2) fmt.Fprintln(application, text) application.SetColor(0, 0, stone.ColorYellow) diff --git a/examples/hello/main.go b/examples/hello/main.go index 86444f0..896a779 100644 --- a/examples/hello/main.go +++ b/examples/hello/main.go @@ -55,7 +55,7 @@ func main () { func redraw () { currentTime = time.Now() - application.ResetDot() + application.SetDot(0, 0) fmt.Fprintln(application, "hellorld!") hour := currentTime.Hour() From 3a3fb66db83bc4b7f2502f0ada3d8070112bef39 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Tue, 15 Nov 2022 17:45:48 -0500 Subject: [PATCH 03/10] Buffer is thread safe --- buffer.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/buffer.go b/buffer.go index 659a63e..fee6658 100644 --- a/buffer.go +++ b/buffer.go @@ -1,5 +1,7 @@ package stone +import "sync" + // Color represents all the different colors a cell can be. type Color uint8 @@ -65,6 +67,10 @@ type Buffer struct { x int y int } + + // This should be write locked when resizing the buffer, and read locked + // when writing to cells or reading information about the buffer. + lock sync.RWMutex } func (buffer *Buffer) isOutOfBounds (x, y int) (outOfBounds bool) { @@ -78,6 +84,9 @@ func (buffer *Buffer) isOutOfBounds (x, y int) (outOfBounds bool) { // Size returns the width and height of the buffer. func (buffer *Buffer) Size () (width, height int) { + buffer.lock.RLock() + defer buffer.lock.RUnlock() + width = buffer.width height = buffer.height return @@ -93,6 +102,9 @@ func (buffer *Buffer) SetDot (x, y int) { // Cell returns the cell at the specified x and y coordinates. If the // coordinates are out of bounds, this method will return a blank cell. func (buffer *Buffer) Cell (x, y int) (cell Cell) { + buffer.lock.RLock() + defer buffer.lock.RUnlock() + if buffer.isOutOfBounds(x, y) { return } cell = buffer.content[x + y * buffer.width] return @@ -100,6 +112,9 @@ func (buffer *Buffer) Cell (x, y int) (cell Cell) { // SetColor sets the color of the cell at the specified x and y coordinates. func (buffer *Buffer) SetColor (x, y int, color Color) { + buffer.lock.RLock() + defer buffer.lock.RUnlock() + if buffer.isOutOfBounds(x, y) { return } index := x + y * buffer.width buffer.clean[index] = buffer.content[index].color == color @@ -109,6 +124,9 @@ func (buffer *Buffer) SetColor (x, y int, color Color) { // SetSize sets the width and height of the buffer. This clears all data in the // buffer. If the width or height is negative, this method does nothing. func (buffer *Buffer) SetSize (width, height int) { + buffer.lock.Lock() + defer buffer.lock.Unlock() + if width < 0 || height < 0 { return } buffer.width = width buffer.height = height @@ -121,6 +139,9 @@ func (buffer *Buffer) SetSize (width, height int) { // SetStyle sets the style of the cell at the specified x and y coordinates. func (buffer *Buffer) SetStyle (x, y int, style Style) { + buffer.lock.RLock() + defer buffer.lock.RUnlock() + if buffer.isOutOfBounds(x, y) { return } index := x + y * buffer.width buffer.clean[index] = buffer.content[index].style == style @@ -129,6 +150,9 @@ func (buffer *Buffer) SetStyle (x, y int, style Style) { // SetRune sets the rune of the cell at the specified x and y coordinates. func (buffer *Buffer) SetRune (x, y int, content rune) { + buffer.lock.RLock() + defer buffer.lock.RUnlock() + if buffer.isOutOfBounds(x, y) { return } index := x + y * buffer.width buffer.clean[index] = buffer.content[index].content == content @@ -138,6 +162,9 @@ func (buffer *Buffer) SetRune (x, y int, content rune) { // Write writes data stored in a byte slice to the buffer at the current dot // position. This makes Buffer an io.Writer. func (buffer *Buffer) Write (bytes []byte) (bytesWritten int, err error) { + buffer.lock.RLock() + defer buffer.lock.RUnlock() + text := string(bytes) bytesWritten = len(bytes) @@ -153,6 +180,9 @@ func (buffer *Buffer) Write (bytes []byte) (bytesWritten int, err error) { // Clean returns whether or not the cell at the specified x and y coordinates is // clean. func (buffer *Buffer) Clean (x, y int) (clean bool) { + buffer.lock.RLock() + defer buffer.lock.RUnlock() + if buffer.isOutOfBounds(x, y) { return } clean = buffer.clean[x + y * buffer.width] return @@ -160,6 +190,9 @@ func (buffer *Buffer) Clean (x, y int) (clean bool) { // MarkClean marks the cell at the specified x and y coordinates as clean. func (buffer *Buffer) MarkClean (x, y int) { + buffer.lock.RLock() + defer buffer.lock.RUnlock() + if buffer.isOutOfBounds(x, y) { return } buffer.clean[x + y * buffer.width] = true } From e030f8632b6c6b3d57a10a74824b7bdb8fa03f5f Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Wed, 16 Nov 2022 00:29:23 -0500 Subject: [PATCH 04/10] Overhauled event system --- application.go | 42 ++++++++++++++-- backend.go | 12 +++-- backends/x/draw.go | 109 +++++++++++++++++++++++++----------------- backends/x/event.go | 36 +++++++------- backends/x/factory.go | 15 ++++-- backends/x/x.go | 31 +++--------- buffer.go | 6 ++- event.go | 61 +++++++++++++---------- examples/draw/main.go | 67 ++++++++++++-------------- 9 files changed, 221 insertions(+), 158 deletions(-) diff --git a/application.go b/application.go index 8ca021f..0523ac1 100644 --- a/application.go +++ b/application.go @@ -10,12 +10,12 @@ type Application struct { icons []image.Image backend Backend config Config + callbackManager CallbackManager } // 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 () ( - channel chan(Event), err error, ) { // default values for certain parameters @@ -29,12 +29,46 @@ func (application *Application) Run () ( application.backend, err = instantiateBackend(application) if err != nil { return } - channel = make(chan(Event)) - go application.backend.Run(channel) - + application.backend.Run() return } +func (application *Application) OnQuit ( + onQuit func (), +) { + application.callbackManager.onQuit = onQuit +} + +func (application *Application) OnPress ( + onPress func (button Button), +) { + application.callbackManager.onPress = onPress +} + +func (application *Application) OnRelease ( + onRelease func (button Button), +) { + application.callbackManager.onRelease = onRelease +} + +func (application *Application) OnResize ( + onResize func (), +) { + application.callbackManager.onResize = onResize +} + +func (application *Application) OnMouseMove ( + onMouseMove func (x, y int), +) { + application.callbackManager.onMouseMove = onMouseMove +} + +func (application *Application) OnStart ( + onStart func (), +) { + application.callbackManager.onStart = onStart +} + // Draw "commits" changes made in the buffer to the display. func (application *Application) Draw () { application.backend.Draw() diff --git a/backend.go b/backend.go index 96678fc..d29e526 100644 --- a/backend.go +++ b/backend.go @@ -4,13 +4,19 @@ import "image" import "errors" type Backend interface { - Run (channel chan(Event)) + Run () SetTitle (title string) (err error) SetIcon (icons []image.Image) (err error) Draw () } -type BackendFactory func (application *Application) (backend Backend, err error) +type BackendFactory func ( + application *Application, + callbackManager *CallbackManager, +) ( + backend Backend, + err error, +) var factories []BackendFactory @@ -21,7 +27,7 @@ func RegisterBackend (factory BackendFactory) { func instantiateBackend (application *Application) (backend Backend, err error) { // find a suitable backend for _, factory := range factories { - backend, err = factory(application) + backend, err = factory(application, &application.callbackManager) if err == nil && backend != nil { return } } diff --git a/backends/x/draw.go b/backends/x/draw.go index 4eb42a5..30bc680 100644 --- a/backends/x/draw.go +++ b/backends/x/draw.go @@ -3,12 +3,13 @@ package x import "image" import "image/draw" import "golang.org/x/image/math/fixed" +import "github.com/jezek/xgbutil/xgraphics" import "git.tebibyte.media/sashakoshka/stone" func (backend *Backend) Draw () { - backend.drawLock.Lock() - defer backend.drawLock.Unlock() + backend.lock.Lock() + defer backend.lock.Unlock() boundsChanged := backend.memory.windowWidth != backend.metrics.windowWidth || @@ -22,25 +23,77 @@ func (backend *Backend) Draw () { backend.canvas.XDraw() backend.canvas.XPaint(backend.window.Id) } else { - backend.updateWindowAreas(backend.drawCells(false)...) + backend.canvas.XPaintRects ( + backend.window.Id, + backend.drawCells(false)...) } } -func (backend *Backend) updateWindowAreas (areas ...image.Rectangle) { - backend.canvas.XPaintRects(backend.window.Id, areas...) +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.ColorBackground] + }) + + backend.canvas.XSurfaceSet(backend.window.Id) } -func (backend *Backend) drawRune (x, y int, character rune, runeColor stone.Color) { +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, cell.Color(), !forceRedraw) + }} + + 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 +} + +func (backend *Backend) drawRune ( + x, y int, + character rune, + runeColor stone.Color, + drawBackground bool, +) { // 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.ColorBackground), - }, - backend.canvas, - backend.boundsOfCell(x, y)) + + if drawBackground { + fillRectangle ( + &image.Uniform { + C: backend.config.Color(stone.ColorBackground), + }, + backend.canvas, + backend.boundsOfCell(x, y)) + } if character < 32 { return } @@ -73,36 +126,6 @@ func (backend *Backend) drawRune (x, y int, character rune, runeColor stone.Colo 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, cell.Color()) - }} - - 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 -} - func fillRectangle ( source image.Image, destination draw.Image, diff --git a/backends/x/event.go b/backends/x/event.go index e55c61e..3ef6e74 100644 --- a/backends/x/event.go +++ b/backends/x/event.go @@ -8,17 +8,19 @@ import "github.com/jezek/xgbutil/xevent" import "git.tebibyte.media/sashakoshka/stone" -func (backend *Backend) Run (channel chan(stone.Event)) { - backend.channel = channel +func (backend *Backend) Run () { + backend.callbackManager.RunStart() + backend.Draw() xevent.Main(backend.connection) backend.shutDown() } - func (backend *Backend) handleConfigureNotify ( connection *xgbutil.XUtil, event xevent.ConfigureNotifyEvent, ) { + backend.lock.Lock() + configureEvent := *event.ConfigureNotifyEvent newWidth := int(configureEvent.Width) @@ -44,8 +46,13 @@ func (backend *Backend) handleConfigureNotify ( (backend.metrics.windowWidth - frameWidth) / 2 backend.metrics.paddingY = (backend.metrics.windowHeight - frameHeight) / 2 - - backend.channel <- stone.EventResize { } + } + + backend.lock.Unlock() + + if sizeChanged { + backend.callbackManager.RunResize() + backend.Draw() } } @@ -54,9 +61,7 @@ func (backend *Backend) handleButtonPress ( event xevent.ButtonPressEvent, ) { buttonEvent := *event.ButtonPressEvent - backend.channel <- stone.EventPress { - Button: stone.Button(buttonEvent.Detail + 127), - } + backend.callbackManager.RunPress(stone.Button(buttonEvent.Detail + 127)) } func (backend *Backend) handleButtonRelease ( @@ -64,9 +69,7 @@ func (backend *Backend) handleButtonRelease ( event xevent.ButtonReleaseEvent, ) { buttonEvent := *event.ButtonReleaseEvent - backend.channel <- stone.EventRelease { - Button: stone.Button(buttonEvent.Detail + 127), - } + backend.callbackManager.RunRelease(stone.Button(buttonEvent.Detail + 127)) } func (backend *Backend) handleKeyPress ( @@ -75,7 +78,7 @@ func (backend *Backend) handleKeyPress ( ) { keyEvent := *event.KeyPressEvent button := backend.keycodeToButton(keyEvent.Detail, keyEvent.State) - backend.channel <- stone.EventPress { Button: button } + backend.callbackManager.RunPress(button) } func (backend *Backend) handleKeyRelease ( @@ -84,7 +87,7 @@ func (backend *Backend) handleKeyRelease ( ) { keyEvent := *event.KeyReleaseEvent button := backend.keycodeToButton(keyEvent.Detail, keyEvent.State) - backend.channel <- stone.EventRelease { Button: button } + backend.callbackManager.RunRelease(button) } func (backend *Backend) handleMotionNotify ( @@ -96,10 +99,7 @@ func (backend *Backend) handleMotionNotify ( X: int(motionEvent.EventX), Y: int(motionEvent.EventY), }) - backend.channel <- stone.EventMouseMove { - X: x, - Y: y, - } + backend.callbackManager.RunMouseMove(x, y) } func (backend *Backend) compressConfigureNotify ( @@ -127,5 +127,5 @@ func (backend *Backend) compressConfigureNotify ( } func (backend *Backend) shutDown () { - backend.channel <- stone.EventQuit { } + backend.callbackManager.RunQuit() } diff --git a/backends/x/factory.go b/backends/x/factory.go index c19fd51..dfd7ce7 100644 --- a/backends/x/factory.go +++ b/backends/x/factory.go @@ -18,10 +18,17 @@ 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) { +func factory ( + application *stone.Application, + callbackManager *stone.CallbackManager, +) ( + output stone.Backend, + err error, +) { backend := &Backend { - application: application, - config: application.Config(), + application: application, + config: application.Config(), + callbackManager: callbackManager, } // load font @@ -76,8 +83,6 @@ func factory (application *stone.Application) (output stone.Backend, err error) 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, diff --git a/backends/x/x.go b/backends/x/x.go index 00d4e6f..d767fae 100644 --- a/backends/x/x.go +++ b/backends/x/x.go @@ -14,17 +14,17 @@ import "github.com/jezek/xgbutil/xgraphics" import "git.tebibyte.media/sashakoshka/stone" type Backend struct { - application *stone.Application - config *stone.Config - connection *xgbutil.XUtil - window *xwindow.Window - canvas *xgraphics.Image - channel chan(stone.Event) + application *stone.Application + config *stone.Config + callbackManager *stone.CallbackManager + connection *xgbutil.XUtil + window *xwindow.Window + canvas *xgraphics.Image drawCellBounds bool drawBufferBounds bool - drawLock sync.Mutex + lock sync.Mutex font struct { face font.Face @@ -114,23 +114,6 @@ func (backend *Backend) calculateBufferSize () (width, height int) { return } -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.ColorBackground] - }) - - backend.canvas.XSurfaceSet(backend.window.Id) -} - 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 diff --git a/buffer.go b/buffer.go index fee6658..5580838 100644 --- a/buffer.go +++ b/buffer.go @@ -153,6 +153,10 @@ func (buffer *Buffer) SetRune (x, y int, content rune) { buffer.lock.RLock() defer buffer.lock.RUnlock() + buffer.setRune(x, y, content) +} + +func (buffer *Buffer) setRune (x, y int, content rune) { if buffer.isOutOfBounds(x, y) { return } index := x + y * buffer.width buffer.clean[index] = buffer.content[index].content == content @@ -169,7 +173,7 @@ func (buffer *Buffer) Write (bytes []byte) (bytesWritten int, err error) { bytesWritten = len(bytes) for _, character := range text { - buffer.SetRune(buffer.dot.x, buffer.dot.y, character) + buffer.setRune(buffer.dot.x, buffer.dot.y, character) buffer.dot.x ++ if buffer.dot.x > buffer.width { break } } diff --git a/event.go b/event.go index c878f00..1032e32 100644 --- a/event.go +++ b/event.go @@ -1,27 +1,40 @@ package stone -// Event can be any event. -type Event interface { } - -// EventQuit is sent when the backend shuts down due to a window close, error, -// or something else. -type EventQuit struct { } - -// EventPress is sent when a button is pressed, or a key repeat event is -// triggered. -type EventPress struct { Button } - -// Release is sent when a button is released. -type EventRelease struct { Button } - -// Resize is sent when the application window is resized by the user. This event -// must be handled, as it implies that the buffer has been resized and therefore -// cleared. Application.Draw() must be called after this event is recieved. -type EventResize struct { } - -// EventMouseMove is sent when the mouse changes position. It contains the X and -// Y position of the mouse. -type EventMouseMove struct { - X int - Y int +type CallbackManager struct { + onQuit func () + onPress func (button Button) + onRelease func (button Button) + onResize func () + onMouseMove func (x, y int) + onStart func () +} + +func (manager *CallbackManager) RunQuit () { + if manager.onQuit == nil { return } + manager.onQuit() +} + +func (manager *CallbackManager) RunPress (button Button) { + if manager.onPress == nil { return } + manager.onPress(button) +} + +func (manager *CallbackManager) RunRelease (button Button) { + if manager.onRelease == nil { return } + manager.onRelease(button) +} + +func (manager *CallbackManager) RunResize () { + if manager.onResize == nil { return } + manager.onResize() +} + +func (manager *CallbackManager) RunMouseMove (x, y int) { + if manager.onMouseMove == nil { return } + manager.onMouseMove(x, y) +} + +func (manager *CallbackManager) RunStart () { + if manager.onStart == nil { return } + manager.onStart() } diff --git a/examples/draw/main.go b/examples/draw/main.go index 7d78da8..98d3fee 100644 --- a/examples/draw/main.go +++ b/examples/draw/main.go @@ -10,7 +10,7 @@ var application = &stone.Application { } var mousePressed bool func main () { - application.SetTitle("hellorld") + application.SetTitle("drawing canvas") application.SetSize(32, 16) iconFile16, err := os.Open("assets/scaffold16.png") @@ -26,42 +26,37 @@ func main () { application.SetIcon([]image.Image { icon16, icon32 }) - channel, err := application.Run() - if err != nil { panic(err) } + application.OnPress(onPress) + application.OnRelease(onRelease) + application.OnMouseMove(onMouseMove) + application.OnQuit(onQuit) - application.Draw() + err = application.Run() + if err != nil { panic(err) } +} - for { - event := <- channel - switch event.(type) { - case stone.EventQuit: - os.Exit(0) - - case stone.EventPress: - button := event.(stone.EventPress).Button - if button == stone.MouseButtonLeft { - mousePressed = true - application.SetRune(0, 0, '+') - application.Draw() - } - - case stone.EventRelease: - button := event.(stone.EventRelease).Button - if button == stone.MouseButtonLeft { - mousePressed = false - application.SetRune(0, 0, 0) - application.Draw() - } - - case stone.EventMouseMove: - event := event.(stone.EventMouseMove) - if mousePressed { - application.SetRune(event.X, event.Y, '#') - application.Draw() - } - - case stone.EventResize: - application.Draw() - } +func onPress (button stone.Button) { + if button == stone.MouseButtonLeft { + mousePressed = true + application.SetRune(0, 0, '+') + application.Draw() } } + +func onRelease (button stone.Button) { + if button == stone.MouseButtonLeft { + mousePressed = false + application.SetRune(0, 0, 0) + application.Draw() + } +} + +func onMouseMove (x, y int) { if mousePressed { + application.SetRune(x, y, '#') + application.Draw() + } +} + +func onQuit () { + os.Exit(0) +} From cab280a371dab64c32604ff4ac1c3f179e2e810e Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Wed, 16 Nov 2022 11:08:30 -0500 Subject: [PATCH 05/10] Removed the need for a quit handler --- backends/x/event.go | 6 +----- backends/x/factory.go | 2 +- examples/color/main.go | 26 +++++++++++--------------- examples/draw/main.go | 5 ----- examples/hello/main.go | 33 ++++++++++++--------------------- 5 files changed, 25 insertions(+), 47 deletions(-) diff --git a/backends/x/event.go b/backends/x/event.go index 3ef6e74..d170a43 100644 --- a/backends/x/event.go +++ b/backends/x/event.go @@ -12,7 +12,7 @@ func (backend *Backend) Run () { backend.callbackManager.RunStart() backend.Draw() xevent.Main(backend.connection) - backend.shutDown() + backend.callbackManager.RunQuit() } func (backend *Backend) handleConfigureNotify ( @@ -125,7 +125,3 @@ func (backend *Backend) compressConfigureNotify ( return } - -func (backend *Backend) shutDown () { - backend.callbackManager.RunQuit() -} diff --git a/backends/x/factory.go b/backends/x/factory.go index dfd7ce7..bf0728b 100644 --- a/backends/x/factory.go +++ b/backends/x/factory.go @@ -115,7 +115,7 @@ func factory ( // attatch graceful close handler backend.window.WMGracefulClose (func (window *xwindow.Window) { backend.window.Destroy() - backend.shutDown() + xevent.Quit(backend.connection) }) // attatch event handlers diff --git a/examples/color/main.go b/examples/color/main.go index 2c84fbc..fab929b 100644 --- a/examples/color/main.go +++ b/examples/color/main.go @@ -10,7 +10,7 @@ import _ "git.tebibyte.media/sashakoshka/stone/backends/x" var application = &stone.Application { } func main () { - application.SetTitle("hellorld") + application.SetTitle("color demo") application.SetSize(12, 7) iconFile16, err := os.Open("assets/scaffold16.png") @@ -26,21 +26,19 @@ func main () { application.SetIcon([]image.Image { icon16, icon32 }) - channel, err := application.Run() - if err != nil { panic(err) } + application.OnStart(onStart) + application.OnResize(onResize) + err = application.Run() + if err != nil { panic(err) } +} + +func onStart () { redraw() +} - for { - event := <- channel - switch event.(type) { - case stone.EventQuit: - os.Exit(0) - - case stone.EventResize: - redraw() - } - } +func onResize () { + redraw() } func redraw () { @@ -69,6 +67,4 @@ func redraw () { application.SetRune(x, height - 1, '=') application.SetColor(x, height - 1, stone.ColorRed) } - - application.Draw() } diff --git a/examples/draw/main.go b/examples/draw/main.go index 98d3fee..3bab734 100644 --- a/examples/draw/main.go +++ b/examples/draw/main.go @@ -29,7 +29,6 @@ func main () { application.OnPress(onPress) application.OnRelease(onRelease) application.OnMouseMove(onMouseMove) - application.OnQuit(onQuit) err = application.Run() if err != nil { panic(err) } @@ -56,7 +55,3 @@ func onMouseMove (x, y int) { if mousePressed { application.Draw() } } - -func onQuit () { - os.Exit(0) -} diff --git a/examples/hello/main.go b/examples/hello/main.go index 896a779..1df4772 100644 --- a/examples/hello/main.go +++ b/examples/hello/main.go @@ -10,7 +10,6 @@ import _ "git.tebibyte.media/sashakoshka/stone/backends/x" var application = &stone.Application { } var currentTime = time.Time { } -var tickPing = make(chan(struct { })) func main () { application.SetTitle("hellorld") @@ -29,27 +28,20 @@ func main () { application.SetIcon([]image.Image { icon16, icon32 }) - channel, err := application.Run() - if err != nil { panic(err) } + application.OnStart(onStart) + application.OnResize(onResize) + err = application.Run() + if err != nil { panic(err) } +} + +func onStart () { redraw() go tick() +} - for { - select { - case <- tickPing: - redraw() - - case event := <- channel: - switch event.(type) { - case stone.EventQuit: - os.Exit(0) - - case stone.EventResize: - redraw() - } - } - } +func onResize () { + redraw() } func redraw () { @@ -70,13 +62,12 @@ 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 () { for { - tickPing <- struct { } { } + redraw() + application.Draw() time.Sleep(time.Second) } } From 81a0c60943613bad587fbf07a9277cbfa1c310d7 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Wed, 16 Nov 2022 11:14:30 -0500 Subject: [PATCH 06/10] Added simpler method of detecting window bounds change on draw --- backends/x/draw.go | 17 ++++++----------- backends/x/event.go | 2 ++ backends/x/x.go | 5 +---- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/backends/x/draw.go b/backends/x/draw.go index 30bc680..8200f06 100644 --- a/backends/x/draw.go +++ b/backends/x/draw.go @@ -11,21 +11,16 @@ func (backend *Backend) Draw () { backend.lock.Lock() defer backend.lock.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 { + if backend.windowBoundsClean { + backend.canvas.XPaintRects ( + backend.window.Id, + backend.drawCells(false)...) + } else { backend.reallocateCanvas() backend.drawCells(true) backend.canvas.XDraw() backend.canvas.XPaint(backend.window.Id) - } else { - backend.canvas.XPaintRects ( - backend.window.Id, - backend.drawCells(false)...) + backend.windowBoundsClean = true } } diff --git a/backends/x/event.go b/backends/x/event.go index d170a43..c2ac74d 100644 --- a/backends/x/event.go +++ b/backends/x/event.go @@ -46,6 +46,8 @@ func (backend *Backend) handleConfigureNotify ( (backend.metrics.windowWidth - frameWidth) / 2 backend.metrics.paddingY = (backend.metrics.windowHeight - frameHeight) / 2 + + backend.windowBoundsClean = false } backend.lock.Unlock() diff --git a/backends/x/x.go b/backends/x/x.go index d767fae..74a7818 100644 --- a/backends/x/x.go +++ b/backends/x/x.go @@ -43,10 +43,7 @@ type Backend struct { descent int } - memory struct { - windowWidth int - windowHeight int - } + windowBoundsClean bool } func (backend *Backend) SetTitle (title string) (err error) { From 0ee58d22f60d0dd9b2df9294cad63c4a21ab95c3 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Wed, 16 Nov 2022 11:31:35 -0500 Subject: [PATCH 07/10] Made centering the buffer in the window optional It causes jitter while resizing in floating wms --- backends/x/event.go | 19 ++++++++++++------- config.go | 16 ++++++++++++++++ 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/backends/x/event.go b/backends/x/event.go index c2ac74d..7a49f26 100644 --- a/backends/x/event.go +++ b/backends/x/event.go @@ -39,13 +39,18 @@ func (backend *Backend) handleConfigureNotify ( 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 + if backend.config.Center() { + // 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 + } else { + backend.metrics.paddingX = backend.metrics.padding + backend.metrics.paddingY = backend.metrics.padding + } backend.windowBoundsClean = false } diff --git a/config.go b/config.go index d08a7c9..1cbf732 100644 --- a/config.go +++ b/config.go @@ -12,6 +12,7 @@ import "path/filepath" type Config struct { colors [8]color.Color padding int + center bool fontSize int fontName string } @@ -29,6 +30,13 @@ func (config *Config) Padding () (padding int) { return } +// Center returns whether the buffer should be displayed in the center of the +// window like in kitty, or aligned to one corner like in gnome-terminal. +func (config *Config) Center () (center bool) { + center = config.center + return +} + // FontSize specifies how big the font should be. func (config *Config) FontSize () (fontSize int) { fontSize = config.fontSize @@ -98,8 +106,14 @@ func (config *Config) loadFile (path string) { } key = strings.TrimSpace(key) value = strings.TrimSpace(value) + var valueInt int var valueColor color.Color + var valueBoolean bool + + if value == "true" { + valueBoolean = true + } if value[0] == '#' { if len(value) != 7 { @@ -134,6 +148,8 @@ func (config *Config) loadFile (path string) { config.fontSize = valueInt case "padding": config.padding = valueInt + case "center": + config.center = valueBoolean case "colorBackground": config.colors[ColorBackground] = valueColor case "colorForeground": From 754017a2db2d8a01dd31f77d1a799d1903c63e66 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Wed, 16 Nov 2022 21:20:48 -0500 Subject: [PATCH 08/10] Added Buffer interface, and clear method --- application.go | 4 ++-- buffer.go | 58 +++++++++++++++++++++++++++++++++++--------------- config.go | 4 ++-- 3 files changed, 45 insertions(+), 21 deletions(-) diff --git a/application.go b/application.go index 0523ac1..a97ccd3 100644 --- a/application.go +++ b/application.go @@ -4,7 +4,7 @@ import "image" // Application represents an application. type Application struct { - Buffer + DamageBuffer title string icons []image.Image @@ -22,7 +22,7 @@ func (application *Application) Run () ( width, height := application.Size() if width < 1 { width = 80 } if height < 1 { height = 20 } - application.Buffer.SetSize(width, height) + application.DamageBuffer.SetSize(width, height) application.config.load() diff --git a/buffer.go b/buffer.go index 5580838..0609001 100644 --- a/buffer.go +++ b/buffer.go @@ -53,11 +53,22 @@ func (cell Cell) Rune () (content rune) { return } -// Buffer is a two dimensional text buffer that stores a grid of cells, as well -// as information stating whether each cell is clean or dirty. Cells are dirty -// by default, are only clean when marked as clean, and become dirty again when -// they are altered in some way. -type Buffer struct { +// Buffer represents a two dimensional text buffer. +type Buffer interface { + Size () (with, height int) + Cell (x, y int) (cell Cell) + SetColor (x, y int, color Color) + SetSize (with, height int) + SetStyle (x, y int, style Style) + SetRune (x, y int, content rune) + Clear () +} + +// DamageBuffer is a two dimensional text buffer that stores a grid of cells, as +// well as information stating whether each cell is clean or dirty. Cells are +// dirty by default, are only clean when marked as clean, and become dirty again +// when they are altered in some way. +type DamageBuffer struct { content []Cell clean []bool @@ -73,7 +84,7 @@ type Buffer struct { lock sync.RWMutex } -func (buffer *Buffer) isOutOfBounds (x, y int) (outOfBounds bool) { +func (buffer *DamageBuffer) isOutOfBounds (x, y int) (outOfBounds bool) { outOfBounds = x < 0 || y < 0 || @@ -83,7 +94,7 @@ func (buffer *Buffer) isOutOfBounds (x, y int) (outOfBounds bool) { } // Size returns the width and height of the buffer. -func (buffer *Buffer) Size () (width, height int) { +func (buffer *DamageBuffer) Size () (width, height int) { buffer.lock.RLock() defer buffer.lock.RUnlock() @@ -94,14 +105,14 @@ func (buffer *Buffer) Size () (width, height int) { // SetDot sets the buffer's text insertion position relative to the buffer // origin point (0, 0). -func (buffer *Buffer) SetDot (x, y int) { +func (buffer *DamageBuffer) SetDot (x, y int) { buffer.dot.x = x buffer.dot.y = y } // Cell returns the cell at the specified x and y coordinates. If the // coordinates are out of bounds, this method will return a blank cell. -func (buffer *Buffer) Cell (x, y int) (cell Cell) { +func (buffer *DamageBuffer) Cell (x, y int) (cell Cell) { buffer.lock.RLock() defer buffer.lock.RUnlock() @@ -111,7 +122,7 @@ func (buffer *Buffer) Cell (x, y int) (cell Cell) { } // SetColor sets the color of the cell at the specified x and y coordinates. -func (buffer *Buffer) SetColor (x, y int, color Color) { +func (buffer *DamageBuffer) SetColor (x, y int, color Color) { buffer.lock.RLock() defer buffer.lock.RUnlock() @@ -123,7 +134,7 @@ func (buffer *Buffer) SetColor (x, y int, color Color) { // SetSize sets the width and height of the buffer. This clears all data in the // buffer. If the width or height is negative, this method does nothing. -func (buffer *Buffer) SetSize (width, height int) { +func (buffer *DamageBuffer) SetSize (width, height int) { buffer.lock.Lock() defer buffer.lock.Unlock() @@ -138,7 +149,7 @@ func (buffer *Buffer) SetSize (width, height int) { } // SetStyle sets the style of the cell at the specified x and y coordinates. -func (buffer *Buffer) SetStyle (x, y int, style Style) { +func (buffer *DamageBuffer) SetStyle (x, y int, style Style) { buffer.lock.RLock() defer buffer.lock.RUnlock() @@ -149,14 +160,27 @@ func (buffer *Buffer) SetStyle (x, y int, style Style) { } // SetRune sets the rune of the cell at the specified x and y coordinates. -func (buffer *Buffer) SetRune (x, y int, content rune) { +func (buffer *DamageBuffer) SetRune (x, y int, content rune) { buffer.lock.RLock() defer buffer.lock.RUnlock() buffer.setRune(x, y, content) } -func (buffer *Buffer) setRune (x, y int, content rune) { +// Clear resets the entire buffer. +func (buffer *DamageBuffer) Clear () { + buffer.lock.RLock() + defer buffer.lock.RUnlock() + + for index := 0; index < len(buffer.content); index ++ { + buffer.clean[index] = false + buffer.content[index] = Cell { + color: ColorForeground, + } + } +} + +func (buffer *DamageBuffer) setRune (x, y int, content rune) { if buffer.isOutOfBounds(x, y) { return } index := x + y * buffer.width buffer.clean[index] = buffer.content[index].content == content @@ -165,7 +189,7 @@ func (buffer *Buffer) setRune (x, y int, content rune) { // Write writes data stored in a byte slice to the buffer at the current dot // position. This makes Buffer an io.Writer. -func (buffer *Buffer) Write (bytes []byte) (bytesWritten int, err error) { +func (buffer *DamageBuffer) Write (bytes []byte) (bytesWritten int, err error) { buffer.lock.RLock() defer buffer.lock.RUnlock() @@ -183,7 +207,7 @@ func (buffer *Buffer) Write (bytes []byte) (bytesWritten int, err error) { // Clean returns whether or not the cell at the specified x and y coordinates is // clean. -func (buffer *Buffer) Clean (x, y int) (clean bool) { +func (buffer *DamageBuffer) Clean (x, y int) (clean bool) { buffer.lock.RLock() defer buffer.lock.RUnlock() @@ -193,7 +217,7 @@ func (buffer *Buffer) Clean (x, y int) (clean bool) { } // MarkClean marks the cell at the specified x and y coordinates as clean. -func (buffer *Buffer) MarkClean (x, y int) { +func (buffer *DamageBuffer) MarkClean (x, y int) { buffer.lock.RLock() defer buffer.lock.RUnlock() diff --git a/config.go b/config.go index 1cbf732..6075f2d 100644 --- a/config.go +++ b/config.go @@ -64,9 +64,9 @@ func (config *Config) load () { // green color.RGBA { R: 0x00, G: 0xFF, B: 0x00, A: 0xFF }, // blue - color.RGBA { R: 0x00, G: 0x00, B: 0xFF, A: 0xFF }, + color.RGBA { R: 0x00, G: 0x80, B: 0xFF, A: 0xFF }, // purple - color.RGBA { R: 0x80, G: 0x00, B: 0xFF, A: 0xFF }, + color.RGBA { R: 0x80, G: 0x40, B: 0xFF, A: 0xFF }, } config.fontName = "" config.fontSize = 11 From 51fbd8acef839b02b1e2ebc3e95ffd7ebbf9f16c Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Thu, 17 Nov 2022 00:26:28 -0500 Subject: [PATCH 09/10] Added more efficient damage buffer method --- backends/x/draw.go | 16 +++++++++++++--- buffer.go | 38 ++++++++++++++++++-------------------- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/backends/x/draw.go b/backends/x/draw.go index 8200f06..5523463 100644 --- a/backends/x/draw.go +++ b/backends/x/draw.go @@ -46,9 +46,8 @@ func (backend *Backend) drawCells (forceRedraw bool) (areas []image.Rectangle) { 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) + cell := backend.application.GetForRendering(x, y) content := cell.Rune() if forceRedraw && content < 32 { continue } @@ -93,13 +92,24 @@ func (backend *Backend) drawRune ( if character < 32 { return } origin := backend.originOfCell(x, y + 1) - destinationRectangle, mask, maskPoint, _, _ := backend.font.face.Glyph ( + destinationRectangle, mask, maskPoint, _, ok := backend.font.face.Glyph ( fixed.Point26_6 { X: fixed.I(origin.X), Y: fixed.I(origin.Y - backend.metrics.descent), }, character) + if !ok { + println("warning") + strokeRectangle ( + &image.Uniform { + C: backend.config.Color(stone.ColorForeground), + }, + backend.canvas, + backend.boundsOfCell(x, y)) + return + } + if backend.drawCellBounds { strokeRectangle ( &image.Uniform { diff --git a/buffer.go b/buffer.go index 0609001..10b6271 100644 --- a/buffer.go +++ b/buffer.go @@ -69,8 +69,8 @@ type Buffer interface { // dirty by default, are only clean when marked as clean, and become dirty again // when they are altered in some way. type DamageBuffer struct { - content []Cell - clean []bool + content []Cell + onScreen []Cell width int height int @@ -127,9 +127,7 @@ func (buffer *DamageBuffer) SetColor (x, y int, color Color) { defer buffer.lock.RUnlock() if buffer.isOutOfBounds(x, y) { return } - index := x + y * buffer.width - buffer.clean[index] = buffer.content[index].color == color - buffer.content[index].color = color + buffer.content[x + y * buffer.width].color = color } // SetSize sets the width and height of the buffer. This clears all data in the @@ -139,10 +137,10 @@ func (buffer *DamageBuffer) SetSize (width, height int) { defer buffer.lock.Unlock() if width < 0 || height < 0 { return } - buffer.width = width - buffer.height = height - buffer.content = make([]Cell, width * height) - buffer.clean = make([]bool, width * height) + buffer.width = width + buffer.height = height + buffer.content = make([]Cell, width * height) + buffer.onScreen = make([]Cell, width * height) for index := 0; index < len(buffer.content); index ++ { buffer.content[index].color = ColorForeground } @@ -154,9 +152,7 @@ func (buffer *DamageBuffer) SetStyle (x, y int, style Style) { defer buffer.lock.RUnlock() if buffer.isOutOfBounds(x, y) { return } - index := x + y * buffer.width - buffer.clean[index] = buffer.content[index].style == style - buffer.content[index].style = style + buffer.content[x + y * buffer.width].style = style } // SetRune sets the rune of the cell at the specified x and y coordinates. @@ -173,7 +169,6 @@ func (buffer *DamageBuffer) Clear () { defer buffer.lock.RUnlock() for index := 0; index < len(buffer.content); index ++ { - buffer.clean[index] = false buffer.content[index] = Cell { color: ColorForeground, } @@ -182,9 +177,7 @@ func (buffer *DamageBuffer) Clear () { func (buffer *DamageBuffer) setRune (x, y int, content rune) { if buffer.isOutOfBounds(x, y) { return } - index := x + y * buffer.width - buffer.clean[index] = buffer.content[index].content == content - buffer.content[index].content = content + buffer.content[x + y * buffer.width].content = content } // Write writes data stored in a byte slice to the buffer at the current dot @@ -212,15 +205,20 @@ func (buffer *DamageBuffer) Clean (x, y int) (clean bool) { defer buffer.lock.RUnlock() if buffer.isOutOfBounds(x, y) { return } - clean = buffer.clean[x + y * buffer.width] + index := x + y * buffer.width + clean = buffer.content[index] == buffer.onScreen[index] return } -// MarkClean marks the cell at the specified x and y coordinates as clean. -func (buffer *DamageBuffer) MarkClean (x, y int) { +// GetForRendering returns the cell at the specified x and y coordinates and +// marks it as clean. +func (buffer *DamageBuffer) GetForRendering (x, y int) (cell Cell) { buffer.lock.RLock() defer buffer.lock.RUnlock() if buffer.isOutOfBounds(x, y) { return } - buffer.clean[x + y * buffer.width] = true + index := x + y * buffer.width + buffer.onScreen[index] = buffer.content[index] + cell = buffer.content[index] + return } From d6280a0d28bfc57bcb36905b01bafe876bb79b68 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Thu, 17 Nov 2022 11:25:27 -0500 Subject: [PATCH 10/10] Added two new styles (although no style has been implemented) --- buffer.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/buffer.go b/buffer.go index 10b6271..4160e87 100644 --- a/buffer.go +++ b/buffer.go @@ -24,6 +24,8 @@ const ( StyleNormal Style = iota StyleBold Style = iota >> 1 StyleItalic + StyleUnderline + StyleHighlight StyleBoldItalic Style = StyleBold | StyleItalic )