(Mostly) under the hoood improvements #3
@ -10,12 +10,12 @@ type Application struct {
 | 
				
			|||||||
	icons []image.Image
 | 
						icons []image.Image
 | 
				
			||||||
	backend Backend
 | 
						backend Backend
 | 
				
			||||||
	config Config
 | 
						config Config
 | 
				
			||||||
 | 
						callbackManager CallbackManager
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Run initializes the application, starts it, and then returns a channel that
 | 
					// 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.
 | 
					// broadcasts events. If no suitable backend can be found, an error is returned.
 | 
				
			||||||
func (application *Application) Run () (
 | 
					func (application *Application) Run () (
 | 
				
			||||||
	channel chan(Event),
 | 
					 | 
				
			||||||
	err     error,
 | 
						err     error,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
	// default values for certain parameters
 | 
						// default values for certain parameters
 | 
				
			||||||
@ -29,12 +29,46 @@ func (application *Application) Run () (
 | 
				
			|||||||
	application.backend, err = instantiateBackend(application)
 | 
						application.backend, err = instantiateBackend(application)
 | 
				
			||||||
	if err != nil { return }
 | 
						if err != nil { return }
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	channel = make(chan(Event))
 | 
						application.backend.Run()
 | 
				
			||||||
	go application.backend.Run(channel)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return
 | 
						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.
 | 
					// Draw "commits" changes made in the buffer to the display.
 | 
				
			||||||
func (application *Application) Draw () {
 | 
					func (application *Application) Draw () {
 | 
				
			||||||
	application.backend.Draw()
 | 
						application.backend.Draw()
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										12
									
								
								backend.go
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								backend.go
									
									
									
									
									
								
							@ -4,13 +4,19 @@ import "image"
 | 
				
			|||||||
import "errors"
 | 
					import "errors"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Backend interface {
 | 
					type Backend interface {
 | 
				
			||||||
	Run      (channel chan(Event))
 | 
						Run      ()
 | 
				
			||||||
	SetTitle (title string) (err error)
 | 
						SetTitle (title string) (err error)
 | 
				
			||||||
	SetIcon  (icons []image.Image) (err error)
 | 
						SetIcon  (icons []image.Image) (err error)
 | 
				
			||||||
	Draw     ()
 | 
						Draw     ()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type BackendFactory func (application *Application) (backend Backend, err error)
 | 
					type BackendFactory func (
 | 
				
			||||||
 | 
						application *Application,
 | 
				
			||||||
 | 
						callbackManager *CallbackManager,
 | 
				
			||||||
 | 
					) (
 | 
				
			||||||
 | 
						backend Backend,
 | 
				
			||||||
 | 
						err error,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var factories []BackendFactory
 | 
					var factories []BackendFactory
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -21,7 +27,7 @@ func RegisterBackend (factory BackendFactory) {
 | 
				
			|||||||
func instantiateBackend (application *Application) (backend Backend, err error) {
 | 
					func instantiateBackend (application *Application) (backend Backend, err error) {
 | 
				
			||||||
	// find a suitable backend
 | 
						// find a suitable backend
 | 
				
			||||||
	for _, factory := range factories {
 | 
						for _, factory := range factories {
 | 
				
			||||||
		backend, err = factory(application)
 | 
							backend, err = factory(application, &application.callbackManager)
 | 
				
			||||||
		if err == nil && backend != nil { return }
 | 
							if err == nil && backend != nil { return }
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -3,12 +3,13 @@ package x
 | 
				
			|||||||
import "image"
 | 
					import "image"
 | 
				
			||||||
import "image/draw"
 | 
					import "image/draw"
 | 
				
			||||||
import "golang.org/x/image/math/fixed"
 | 
					import "golang.org/x/image/math/fixed"
 | 
				
			||||||
 | 
					import "github.com/jezek/xgbutil/xgraphics"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/stone"
 | 
					import "git.tebibyte.media/sashakoshka/stone"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (backend *Backend) Draw () {
 | 
					func (backend *Backend) Draw () {
 | 
				
			||||||
	backend.drawLock.Lock()
 | 
						backend.lock.Lock()
 | 
				
			||||||
	defer backend.drawLock.Unlock()
 | 
						defer backend.lock.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	boundsChanged :=
 | 
						boundsChanged :=
 | 
				
			||||||
		backend.memory.windowWidth  != backend.metrics.windowWidth ||
 | 
							backend.memory.windowWidth  != backend.metrics.windowWidth ||
 | 
				
			||||||
@ -22,25 +23,77 @@ func (backend *Backend) Draw () {
 | 
				
			|||||||
		backend.canvas.XDraw()
 | 
							backend.canvas.XDraw()
 | 
				
			||||||
		backend.canvas.XPaint(backend.window.Id)
 | 
							backend.canvas.XPaint(backend.window.Id)
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		backend.updateWindowAreas(backend.drawCells(false)...)
 | 
							backend.canvas.XPaintRects (
 | 
				
			||||||
 | 
								backend.window.Id,
 | 
				
			||||||
 | 
								backend.drawCells(false)...)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (backend *Backend) updateWindowAreas (areas ...image.Rectangle) {
 | 
					func (backend *Backend) reallocateCanvas () {
 | 
				
			||||||
	backend.canvas.XPaintRects(backend.window.Id, areas...)
 | 
						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
 | 
						// TODO: cache these draws as non-transparent buffers with the
 | 
				
			||||||
	// application background color as the background. that way, we won't
 | 
						// application background color as the background. that way, we won't
 | 
				
			||||||
	// need to redraw the characters *or* composite them.
 | 
						// need to redraw the characters *or* composite them.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	fillRectangle (
 | 
						if drawBackground {
 | 
				
			||||||
		&image.Uniform {
 | 
							fillRectangle (
 | 
				
			||||||
			C: backend.config.Color(stone.ColorBackground),
 | 
								&image.Uniform {
 | 
				
			||||||
		},
 | 
									C: backend.config.Color(stone.ColorBackground),
 | 
				
			||||||
		backend.canvas,
 | 
								},
 | 
				
			||||||
		backend.boundsOfCell(x, y))
 | 
								backend.canvas,
 | 
				
			||||||
 | 
								backend.boundsOfCell(x, y))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if character < 32 { return }
 | 
						if character < 32 { return }
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
@ -73,36 +126,6 @@ func (backend *Backend) drawRune (x, y int, character rune, runeColor stone.Colo
 | 
				
			|||||||
		draw.Over)
 | 
							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 (
 | 
					func fillRectangle (
 | 
				
			||||||
	source      image.Image,
 | 
						source      image.Image,
 | 
				
			||||||
	destination draw.Image,
 | 
						destination draw.Image,
 | 
				
			||||||
 | 
				
			|||||||
@ -8,17 +8,19 @@ import "github.com/jezek/xgbutil/xevent"
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/stone"
 | 
					import "git.tebibyte.media/sashakoshka/stone"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (backend *Backend) Run (channel chan(stone.Event)) {
 | 
					func (backend *Backend) Run () {
 | 
				
			||||||
	backend.channel = channel
 | 
						backend.callbackManager.RunStart()
 | 
				
			||||||
 | 
						backend.Draw()
 | 
				
			||||||
	xevent.Main(backend.connection)
 | 
						xevent.Main(backend.connection)
 | 
				
			||||||
	backend.shutDown()
 | 
						backend.shutDown()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
func (backend *Backend) handleConfigureNotify (
 | 
					func (backend *Backend) handleConfigureNotify (
 | 
				
			||||||
	connection *xgbutil.XUtil,
 | 
						connection *xgbutil.XUtil,
 | 
				
			||||||
	event xevent.ConfigureNotifyEvent,
 | 
						event xevent.ConfigureNotifyEvent,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
 | 
						backend.lock.Lock()
 | 
				
			||||||
 | 
						
 | 
				
			||||||
	configureEvent := *event.ConfigureNotifyEvent
 | 
						configureEvent := *event.ConfigureNotifyEvent
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	newWidth  := int(configureEvent.Width)
 | 
						newWidth  := int(configureEvent.Width)
 | 
				
			||||||
@ -44,8 +46,13 @@ func (backend *Backend) handleConfigureNotify (
 | 
				
			|||||||
			(backend.metrics.windowWidth - frameWidth) / 2
 | 
								(backend.metrics.windowWidth - frameWidth) / 2
 | 
				
			||||||
		backend.metrics.paddingY =
 | 
							backend.metrics.paddingY =
 | 
				
			||||||
			(backend.metrics.windowHeight - frameHeight) / 2
 | 
								(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,
 | 
						event xevent.ButtonPressEvent,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
	buttonEvent := *event.ButtonPressEvent
 | 
						buttonEvent := *event.ButtonPressEvent
 | 
				
			||||||
	backend.channel <- stone.EventPress {
 | 
						backend.callbackManager.RunPress(stone.Button(buttonEvent.Detail + 127))
 | 
				
			||||||
		Button: stone.Button(buttonEvent.Detail + 127),
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (backend *Backend) handleButtonRelease (
 | 
					func (backend *Backend) handleButtonRelease (
 | 
				
			||||||
@ -64,9 +69,7 @@ func (backend *Backend) handleButtonRelease (
 | 
				
			|||||||
	event xevent.ButtonReleaseEvent,
 | 
						event xevent.ButtonReleaseEvent,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
	buttonEvent := *event.ButtonReleaseEvent
 | 
						buttonEvent := *event.ButtonReleaseEvent
 | 
				
			||||||
	backend.channel <- stone.EventRelease {
 | 
						backend.callbackManager.RunRelease(stone.Button(buttonEvent.Detail + 127))
 | 
				
			||||||
		Button: stone.Button(buttonEvent.Detail + 127),
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (backend *Backend) handleKeyPress (
 | 
					func (backend *Backend) handleKeyPress (
 | 
				
			||||||
@ -75,7 +78,7 @@ func (backend *Backend) handleKeyPress (
 | 
				
			|||||||
) {
 | 
					) {
 | 
				
			||||||
	keyEvent := *event.KeyPressEvent
 | 
						keyEvent := *event.KeyPressEvent
 | 
				
			||||||
	button   := backend.keycodeToButton(keyEvent.Detail, keyEvent.State)
 | 
						button   := backend.keycodeToButton(keyEvent.Detail, keyEvent.State)
 | 
				
			||||||
	backend.channel <- stone.EventPress { Button: button }
 | 
						backend.callbackManager.RunPress(button)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (backend *Backend) handleKeyRelease (
 | 
					func (backend *Backend) handleKeyRelease (
 | 
				
			||||||
@ -84,7 +87,7 @@ func (backend *Backend) handleKeyRelease (
 | 
				
			|||||||
) {
 | 
					) {
 | 
				
			||||||
	keyEvent := *event.KeyReleaseEvent
 | 
						keyEvent := *event.KeyReleaseEvent
 | 
				
			||||||
	button   := backend.keycodeToButton(keyEvent.Detail, keyEvent.State)
 | 
						button   := backend.keycodeToButton(keyEvent.Detail, keyEvent.State)
 | 
				
			||||||
	backend.channel <- stone.EventRelease { Button: button }
 | 
						backend.callbackManager.RunRelease(button)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (backend *Backend) handleMotionNotify (
 | 
					func (backend *Backend) handleMotionNotify (
 | 
				
			||||||
@ -96,10 +99,7 @@ func (backend *Backend) handleMotionNotify (
 | 
				
			|||||||
		X: int(motionEvent.EventX),
 | 
							X: int(motionEvent.EventX),
 | 
				
			||||||
		Y: int(motionEvent.EventY),
 | 
							Y: int(motionEvent.EventY),
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	backend.channel <- stone.EventMouseMove {
 | 
						backend.callbackManager.RunMouseMove(x, y)
 | 
				
			||||||
		X: x,
 | 
					 | 
				
			||||||
		Y: y,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (backend *Backend) compressConfigureNotify (
 | 
					func (backend *Backend) compressConfigureNotify (
 | 
				
			||||||
@ -127,5 +127,5 @@ func (backend *Backend) compressConfigureNotify (
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (backend *Backend) shutDown () {
 | 
					func (backend *Backend) shutDown () {
 | 
				
			||||||
	backend.channel <- stone.EventQuit { }
 | 
						backend.callbackManager.RunQuit()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -18,10 +18,17 @@ import "git.tebibyte.media/sashakoshka/stone"
 | 
				
			|||||||
import "github.com/flopp/go-findfont"
 | 
					import "github.com/flopp/go-findfont"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// factory instantiates an X backend.
 | 
					// 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 {
 | 
						backend := &Backend {
 | 
				
			||||||
		application: application,
 | 
							application:     application,
 | 
				
			||||||
		config:      application.Config(),
 | 
							config:          application.Config(),
 | 
				
			||||||
 | 
							callbackManager: callbackManager,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// load font
 | 
						// load font
 | 
				
			||||||
@ -76,8 +83,6 @@ func factory (application *stone.Application) (output stone.Backend, err error)
 | 
				
			|||||||
		backend.metrics.windowWidth, backend.metrics.windowHeight,
 | 
							backend.metrics.windowWidth, backend.metrics.windowHeight,
 | 
				
			||||||
		0)
 | 
							0)
 | 
				
			||||||
	backend.window.Map()
 | 
						backend.window.Map()
 | 
				
			||||||
	// TODO: also listen to mouse movement (compressed) and mouse and
 | 
					 | 
				
			||||||
	// keyboard buttons (uncompressed)
 | 
					 | 
				
			||||||
	err = backend.window.Listen (
 | 
						err = backend.window.Listen (
 | 
				
			||||||
		xproto.EventMaskStructureNotify,
 | 
							xproto.EventMaskStructureNotify,
 | 
				
			||||||
		xproto.EventMaskPointerMotion,
 | 
							xproto.EventMaskPointerMotion,
 | 
				
			||||||
 | 
				
			|||||||
@ -14,17 +14,17 @@ import "github.com/jezek/xgbutil/xgraphics"
 | 
				
			|||||||
import "git.tebibyte.media/sashakoshka/stone"
 | 
					import "git.tebibyte.media/sashakoshka/stone"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Backend struct {
 | 
					type Backend struct {
 | 
				
			||||||
	application *stone.Application
 | 
						application     *stone.Application
 | 
				
			||||||
	config      *stone.Config
 | 
						config          *stone.Config
 | 
				
			||||||
	connection  *xgbutil.XUtil
 | 
						callbackManager *stone.CallbackManager
 | 
				
			||||||
	window      *xwindow.Window
 | 
						connection      *xgbutil.XUtil
 | 
				
			||||||
	canvas      *xgraphics.Image
 | 
						window          *xwindow.Window
 | 
				
			||||||
	channel     chan(stone.Event)
 | 
						canvas          *xgraphics.Image
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	drawCellBounds   bool
 | 
						drawCellBounds   bool
 | 
				
			||||||
	drawBufferBounds bool
 | 
						drawBufferBounds bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	drawLock sync.Mutex
 | 
						lock sync.Mutex
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	font struct {
 | 
						font struct {
 | 
				
			||||||
		face font.Face
 | 
							face font.Face
 | 
				
			||||||
@ -114,23 +114,6 @@ func (backend *Backend) calculateBufferSize () (width, height int) {
 | 
				
			|||||||
	return
 | 
						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) {
 | 
					func (backend *Backend) cellAt (onScreen image.Point) (x, y int) {
 | 
				
			||||||
	x = (onScreen.X - backend.metrics.paddingX) / backend.metrics.cellWidth
 | 
						x = (onScreen.X - backend.metrics.paddingX) / backend.metrics.cellWidth
 | 
				
			||||||
	y = (onScreen.Y - backend.metrics.paddingY) / backend.metrics.cellHeight
 | 
						y = (onScreen.Y - backend.metrics.paddingY) / backend.metrics.cellHeight
 | 
				
			||||||
 | 
				
			|||||||
@ -153,6 +153,10 @@ func (buffer *Buffer) SetRune (x, y int, content rune) {
 | 
				
			|||||||
	buffer.lock.RLock()
 | 
						buffer.lock.RLock()
 | 
				
			||||||
	defer buffer.lock.RUnlock()
 | 
						defer buffer.lock.RUnlock()
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
 | 
						buffer.setRune(x, y, content)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (buffer *Buffer) setRune (x, y int, content rune) {
 | 
				
			||||||
	if buffer.isOutOfBounds(x, y) { return }
 | 
						if buffer.isOutOfBounds(x, y) { return }
 | 
				
			||||||
	index := x + y * buffer.width
 | 
						index := x + y * buffer.width
 | 
				
			||||||
	buffer.clean[index] = buffer.content[index].content == content
 | 
						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)
 | 
						bytesWritten = len(bytes)
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	for _, character := range text {
 | 
						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 ++
 | 
							buffer.dot.x ++
 | 
				
			||||||
		if buffer.dot.x > buffer.width { break }
 | 
							if buffer.dot.x > buffer.width { break }
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										61
									
								
								event.go
									
									
									
									
									
								
							
							
						
						
									
										61
									
								
								event.go
									
									
									
									
									
								
							@ -1,27 +1,40 @@
 | 
				
			|||||||
package stone
 | 
					package stone
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Event can be any event.
 | 
					type CallbackManager struct {
 | 
				
			||||||
type Event interface { }
 | 
						onQuit      func ()
 | 
				
			||||||
 | 
						onPress     func (button Button)
 | 
				
			||||||
// EventQuit is sent when the backend shuts down due to a window close, error,
 | 
						onRelease   func (button Button)
 | 
				
			||||||
// or something else.
 | 
						onResize    func ()
 | 
				
			||||||
type EventQuit struct { }
 | 
						onMouseMove func (x, y int)
 | 
				
			||||||
 | 
						onStart     func ()
 | 
				
			||||||
// EventPress is sent when a button is pressed, or a key repeat event is
 | 
					}
 | 
				
			||||||
// triggered.
 | 
					
 | 
				
			||||||
type EventPress struct { Button }
 | 
					func (manager *CallbackManager) RunQuit () {
 | 
				
			||||||
 | 
						if manager.onQuit == nil { return }
 | 
				
			||||||
// Release is sent when a button is released.
 | 
						manager.onQuit()
 | 
				
			||||||
type EventRelease struct { Button }
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Resize is sent when the application window is resized by the user. This event
 | 
					func (manager *CallbackManager) RunPress (button Button) {
 | 
				
			||||||
// must be handled, as it implies that the buffer has been resized and therefore
 | 
						if manager.onPress == nil { return }
 | 
				
			||||||
// cleared. Application.Draw() must be called after this event is recieved.
 | 
						manager.onPress(button)
 | 
				
			||||||
type EventResize struct { }
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// EventMouseMove is sent when the mouse changes position. It contains the X and
 | 
					func (manager *CallbackManager) RunRelease (button Button) {
 | 
				
			||||||
// Y position of the mouse.
 | 
						if manager.onRelease == nil { return }
 | 
				
			||||||
type EventMouseMove struct {
 | 
						manager.onRelease(button)
 | 
				
			||||||
	X int
 | 
					}
 | 
				
			||||||
	Y int
 | 
					
 | 
				
			||||||
 | 
					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()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -10,7 +10,7 @@ var application = &stone.Application { }
 | 
				
			|||||||
var mousePressed bool
 | 
					var mousePressed bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func main () {
 | 
					func main () {
 | 
				
			||||||
	application.SetTitle("hellorld")
 | 
						application.SetTitle("drawing canvas")
 | 
				
			||||||
	application.SetSize(32, 16)
 | 
						application.SetSize(32, 16)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	iconFile16, err := os.Open("assets/scaffold16.png")
 | 
						iconFile16, err := os.Open("assets/scaffold16.png")
 | 
				
			||||||
@ -26,42 +26,37 @@ func main () {
 | 
				
			|||||||
	
 | 
						
 | 
				
			||||||
	application.SetIcon([]image.Image { icon16, icon32 })
 | 
						application.SetIcon([]image.Image { icon16, icon32 })
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	channel, err := application.Run()
 | 
						application.OnPress(onPress)
 | 
				
			||||||
 | 
						application.OnRelease(onRelease)
 | 
				
			||||||
 | 
						application.OnMouseMove(onMouseMove)
 | 
				
			||||||
 | 
						application.OnQuit(onQuit)
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						err = application.Run()
 | 
				
			||||||
	if err != nil { panic(err) }
 | 
						if err != nil { panic(err) }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	application.Draw()
 | 
					func onPress (button stone.Button) {
 | 
				
			||||||
 | 
						if button == stone.MouseButtonLeft {
 | 
				
			||||||
	for {
 | 
							mousePressed = true
 | 
				
			||||||
		event := <- channel
 | 
							application.SetRune(0, 0, '+')
 | 
				
			||||||
		switch event.(type) {
 | 
							application.Draw()
 | 
				
			||||||
		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 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)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user