Merge pull request '(Mostly) under the hoood improvements' (#3) from fix-x-concurrency into main
Reviewed-on: #3
This commit is contained in:
commit
19b744250f
@ -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,55 +3,113 @@ 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 :=
|
if backend.windowBoundsClean {
|
||||||
backend.memory.windowWidth != backend.metrics.windowWidth ||
|
backend.canvas.XPaintRects (
|
||||||
backend.memory.windowHeight != backend.metrics.windowHeight
|
backend.window.Id,
|
||||||
backend.memory.windowWidth = backend.metrics.windowWidth
|
backend.drawCells(false)...)
|
||||||
backend.memory.windowHeight = backend.metrics.windowHeight
|
} else {
|
||||||
|
|
||||||
if boundsChanged {
|
|
||||||
backend.reallocateCanvas()
|
backend.reallocateCanvas()
|
||||||
backend.drawCells(true)
|
backend.drawCells(true)
|
||||||
backend.canvas.XDraw()
|
backend.canvas.XDraw()
|
||||||
backend.canvas.XPaint(backend.window.Id)
|
backend.canvas.XPaint(backend.window.Id)
|
||||||
} else {
|
backend.windowBoundsClean = true
|
||||||
backend.updateWindowAreas(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 }
|
||||||
|
|
||||||
|
cell := backend.application.GetForRendering(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 }
|
||||||
|
|
||||||
origin := backend.originOfCell(x, y + 1)
|
origin := backend.originOfCell(x, y + 1)
|
||||||
destinationRectangle, mask, maskPoint, _, _ := backend.font.face.Glyph (
|
destinationRectangle, mask, maskPoint, _, ok := backend.font.face.Glyph (
|
||||||
fixed.Point26_6 {
|
fixed.Point26_6 {
|
||||||
X: fixed.I(origin.X),
|
X: fixed.I(origin.X),
|
||||||
Y: fixed.I(origin.Y - backend.metrics.descent),
|
Y: fixed.I(origin.Y - backend.metrics.descent),
|
||||||
},
|
},
|
||||||
character)
|
character)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
println("warning")
|
||||||
|
strokeRectangle (
|
||||||
|
&image.Uniform {
|
||||||
|
C: backend.config.Color(stone.ColorForeground),
|
||||||
|
},
|
||||||
|
backend.canvas,
|
||||||
|
backend.boundsOfCell(x, y))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if backend.drawCellBounds {
|
if backend.drawCellBounds {
|
||||||
strokeRectangle (
|
strokeRectangle (
|
||||||
&image.Uniform {
|
&image.Uniform {
|
||||||
@ -73,36 +131,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.callbackManager.RunQuit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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)
|
||||||
@ -33,23 +35,31 @@ func (backend *Backend) handleConfigureNotify (
|
|||||||
configureEvent =
|
configureEvent =
|
||||||
backend.compressConfigureNotify(configureEvent)
|
backend.compressConfigureNotify(configureEvent)
|
||||||
|
|
||||||
// we should not resize the canvas while drawing is taking place
|
|
||||||
backend.drawLock.Lock()
|
|
||||||
defer backend.drawLock.Unlock()
|
|
||||||
|
|
||||||
// resize buffer
|
// resize buffer
|
||||||
width, height := backend.calculateBufferSize()
|
width, height := backend.calculateBufferSize()
|
||||||
backend.application.SetSize(width, height)
|
backend.application.SetSize(width, height)
|
||||||
|
|
||||||
// position buffer in the center of the screen
|
if backend.config.Center() {
|
||||||
frameWidth := width * backend.metrics.cellWidth
|
// position buffer in the center of the screen
|
||||||
frameHeight := height * backend.metrics.cellHeight
|
frameWidth := width * backend.metrics.cellWidth
|
||||||
backend.metrics.paddingX =
|
frameHeight := height * backend.metrics.cellHeight
|
||||||
(backend.metrics.windowWidth - frameWidth) / 2
|
backend.metrics.paddingX =
|
||||||
backend.metrics.paddingY =
|
(backend.metrics.windowWidth - frameWidth) / 2
|
||||||
(backend.metrics.windowHeight - frameHeight) / 2
|
backend.metrics.paddingY =
|
||||||
|
(backend.metrics.windowHeight - frameHeight) / 2
|
||||||
|
} else {
|
||||||
|
backend.metrics.paddingX = backend.metrics.padding
|
||||||
|
backend.metrics.paddingY = backend.metrics.padding
|
||||||
|
}
|
||||||
|
|
||||||
backend.channel <- stone.EventResize { }
|
backend.windowBoundsClean = false
|
||||||
|
}
|
||||||
|
|
||||||
|
backend.lock.Unlock()
|
||||||
|
|
||||||
|
if sizeChanged {
|
||||||
|
backend.callbackManager.RunResize()
|
||||||
|
backend.Draw()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,9 +68,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 (
|
||||||
@ -68,9 +76,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 (
|
||||||
@ -79,7 +85,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 (
|
||||||
@ -88,7 +94,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 (
|
||||||
@ -100,10 +106,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 (
|
||||||
@ -129,7 +132,3 @@ func (backend *Backend) compressConfigureNotify (
|
|||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (backend *Backend) shutDown () {
|
|
||||||
backend.channel <- stone.EventQuit { }
|
|
||||||
}
|
|
||||||
|
@ -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,
|
||||||
@ -110,7 +115,7 @@ func factory (application *stone.Application) (output stone.Backend, err error)
|
|||||||
// attatch graceful close handler
|
// attatch graceful close handler
|
||||||
backend.window.WMGracefulClose (func (window *xwindow.Window) {
|
backend.window.WMGracefulClose (func (window *xwindow.Window) {
|
||||||
backend.window.Destroy()
|
backend.window.Destroy()
|
||||||
backend.shutDown()
|
xevent.Quit(backend.connection)
|
||||||
})
|
})
|
||||||
|
|
||||||
// attatch event handlers
|
// attatch event handlers
|
||||||
|
@ -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
|
||||||
@ -43,10 +43,7 @@ type Backend struct {
|
|||||||
descent int
|
descent int
|
||||||
}
|
}
|
||||||
|
|
||||||
memory struct {
|
windowBoundsClean bool
|
||||||
windowWidth int
|
|
||||||
windowHeight int
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (backend *Backend) SetTitle (title string) (err error) {
|
func (backend *Backend) SetTitle (title string) (err error) {
|
||||||
@ -114,23 +111,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
|
||||||
|
206
buffer.go
206
buffer.go
@ -1,5 +1,7 @@
|
|||||||
package stone
|
package stone
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
// Color represents all the different colors a cell can be.
|
// Color represents all the different colors a cell can be.
|
||||||
type Color uint8
|
type Color uint8
|
||||||
|
|
||||||
@ -22,6 +24,8 @@ const (
|
|||||||
StyleNormal Style = iota
|
StyleNormal Style = iota
|
||||||
StyleBold Style = iota >> 1
|
StyleBold Style = iota >> 1
|
||||||
StyleItalic
|
StyleItalic
|
||||||
|
StyleUnderline
|
||||||
|
StyleHighlight
|
||||||
StyleBoldItalic Style = StyleBold | StyleItalic
|
StyleBoldItalic Style = StyleBold | StyleItalic
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -51,18 +55,38 @@ func (cell Cell) Rune () (content rune) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Buffer is a basic grid of cells.
|
// Buffer represents a two dimensional text buffer.
|
||||||
type Buffer struct {
|
type Buffer interface {
|
||||||
content []Cell
|
Size () (with, height int)
|
||||||
width int
|
Cell (x, y int) (cell Cell)
|
||||||
height int
|
SetColor (x, y int, color Color)
|
||||||
Dot struct {
|
SetSize (with, height int)
|
||||||
X int
|
SetStyle (x, y int, style Style)
|
||||||
Y int
|
SetRune (x, y int, content rune)
|
||||||
}
|
Clear ()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (buffer *Buffer) isOutOfBounds (x, y int) (outOfBounds bool) {
|
// 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
|
||||||
|
onScreen []Cell
|
||||||
|
|
||||||
|
width int
|
||||||
|
height int
|
||||||
|
dot 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 *DamageBuffer) isOutOfBounds (x, y int) (outOfBounds bool) {
|
||||||
outOfBounds =
|
outOfBounds =
|
||||||
x < 0 ||
|
x < 0 ||
|
||||||
y < 0 ||
|
y < 0 ||
|
||||||
@ -72,122 +96,105 @@ func (buffer *Buffer) isOutOfBounds (x, y int) (outOfBounds bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Size returns the width and height of the buffer.
|
// 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()
|
||||||
|
|
||||||
width = buffer.width
|
width = buffer.width
|
||||||
height = buffer.height
|
height = buffer.height
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetSize sets the width and height of the buffer. This clears all data in the
|
// SetDot sets the buffer's text insertion position relative to the buffer
|
||||||
// buffer. If the width or height is negative, this method does nothing.
|
// origin point (0, 0).
|
||||||
func (buffer *Buffer) SetSize (width, height int) {
|
func (buffer *DamageBuffer) SetDot (x, y int) {
|
||||||
if width < 0 || height < 0 { return }
|
buffer.dot.x = x
|
||||||
buffer.width = width
|
buffer.dot.y = y
|
||||||
buffer.height = height
|
|
||||||
buffer.content = make([]Cell, width * height)
|
|
||||||
for index := 0; index < len(buffer.content); index ++ {
|
|
||||||
buffer.content[index].color = ColorForeground
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cell returns the cell at the specified x and y coordinates. If the
|
// 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.
|
// 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()
|
||||||
|
|
||||||
if buffer.isOutOfBounds(x, y) { return }
|
if buffer.isOutOfBounds(x, y) { return }
|
||||||
cell = buffer.content[x + y * buffer.width]
|
cell = buffer.content[x + y * buffer.width]
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetColor sets the color of the cell at the specified x and y coordinates.
|
// 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()
|
||||||
|
|
||||||
if buffer.isOutOfBounds(x, y) { return }
|
if buffer.isOutOfBounds(x, y) { return }
|
||||||
buffer.content[x + y * buffer.width].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
|
||||||
|
// buffer. If the width or height is negative, this method does nothing.
|
||||||
|
func (buffer *DamageBuffer) SetSize (width, height int) {
|
||||||
|
buffer.lock.Lock()
|
||||||
|
defer buffer.lock.Unlock()
|
||||||
|
|
||||||
|
if width < 0 || height < 0 { return }
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SetStyle sets the style of the cell at the specified x and y coordinates.
|
// 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()
|
||||||
|
|
||||||
if buffer.isOutOfBounds(x, y) { return }
|
if buffer.isOutOfBounds(x, y) { return }
|
||||||
buffer.content[x + y * buffer.width].style = style
|
buffer.content[x + y * buffer.width].style = style
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetRune sets the rune of the cell at the specified x and y coordinates.
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.content[index] = Cell {
|
||||||
|
color: ColorForeground,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (buffer *DamageBuffer) setRune (x, y int, content rune) {
|
||||||
if buffer.isOutOfBounds(x, y) { return }
|
if buffer.isOutOfBounds(x, y) { return }
|
||||||
buffer.content[x + y * buffer.width].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
|
// Write writes data stored in a byte slice to the buffer at the current dot
|
||||||
// position. This makes Buffer an io.Writer.
|
// position. This makes Buffer an io.Writer.
|
||||||
func (buffer *Buffer) 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) {
|
func (buffer *DamageBuffer) Write (bytes []byte) (bytesWritten int, err error) {
|
||||||
|
buffer.lock.RLock()
|
||||||
|
defer buffer.lock.RUnlock()
|
||||||
|
|
||||||
text := string(bytes)
|
text := string(bytes)
|
||||||
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 }
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@ -196,13 +203,24 @@ func (buffer *DamageBuffer) Write (bytes []byte) (bytesWritten int, err error) {
|
|||||||
// Clean returns whether or not the cell at the specified x and y coordinates is
|
// Clean returns whether or not the cell at the specified x and y coordinates is
|
||||||
// clean.
|
// clean.
|
||||||
func (buffer *DamageBuffer) Clean (x, y int) (clean bool) {
|
func (buffer *DamageBuffer) Clean (x, y int) (clean bool) {
|
||||||
|
buffer.lock.RLock()
|
||||||
|
defer buffer.lock.RUnlock()
|
||||||
|
|
||||||
if buffer.isOutOfBounds(x, y) { return }
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkClean marks the cell at the specified x and y coordinates as clean.
|
// GetForRendering returns the cell at the specified x and y coordinates and
|
||||||
func (buffer *DamageBuffer) MarkClean (x, y int) {
|
// 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 }
|
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
|
||||||
}
|
}
|
||||||
|
21
config.go
21
config.go
@ -12,6 +12,7 @@ import "path/filepath"
|
|||||||
type Config struct {
|
type Config struct {
|
||||||
colors [8]color.Color
|
colors [8]color.Color
|
||||||
padding int
|
padding int
|
||||||
|
center bool
|
||||||
fontSize int
|
fontSize int
|
||||||
fontName string
|
fontName string
|
||||||
}
|
}
|
||||||
@ -29,6 +30,13 @@ func (config *Config) Padding () (padding int) {
|
|||||||
return
|
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.
|
// FontSize specifies how big the font should be.
|
||||||
func (config *Config) FontSize () (fontSize int) {
|
func (config *Config) FontSize () (fontSize int) {
|
||||||
fontSize = config.fontSize
|
fontSize = config.fontSize
|
||||||
@ -42,7 +50,6 @@ func (config *Config) FontName () (fontName string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (config *Config) load () {
|
func (config *Config) load () {
|
||||||
// TODO: load these from a file
|
|
||||||
config.colors = [8]color.Color {
|
config.colors = [8]color.Color {
|
||||||
// background
|
// background
|
||||||
color.RGBA { R: 0, G: 0, B: 0, A: 0 },
|
color.RGBA { R: 0, G: 0, B: 0, A: 0 },
|
||||||
@ -57,9 +64,9 @@ func (config *Config) load () {
|
|||||||
// green
|
// green
|
||||||
color.RGBA { R: 0x00, G: 0xFF, B: 0x00, A: 0xFF },
|
color.RGBA { R: 0x00, G: 0xFF, B: 0x00, A: 0xFF },
|
||||||
// blue
|
// blue
|
||||||
color.RGBA { R: 0x00, G: 0x00, B: 0xFF, A: 0xFF },
|
color.RGBA { R: 0x00, G: 0x80, B: 0xFF, A: 0xFF },
|
||||||
// purple
|
// purple
|
||||||
color.RGBA { R: 0x80, G: 0x00, B: 0xFF, A: 0xFF },
|
color.RGBA { R: 0x80, G: 0x40, B: 0xFF, A: 0xFF },
|
||||||
}
|
}
|
||||||
config.fontName = ""
|
config.fontName = ""
|
||||||
config.fontSize = 11
|
config.fontSize = 11
|
||||||
@ -99,8 +106,14 @@ func (config *Config) loadFile (path string) {
|
|||||||
}
|
}
|
||||||
key = strings.TrimSpace(key)
|
key = strings.TrimSpace(key)
|
||||||
value = strings.TrimSpace(value)
|
value = strings.TrimSpace(value)
|
||||||
|
|
||||||
var valueInt int
|
var valueInt int
|
||||||
var valueColor color.Color
|
var valueColor color.Color
|
||||||
|
var valueBoolean bool
|
||||||
|
|
||||||
|
if value == "true" {
|
||||||
|
valueBoolean = true
|
||||||
|
}
|
||||||
|
|
||||||
if value[0] == '#' {
|
if value[0] == '#' {
|
||||||
if len(value) != 7 {
|
if len(value) != 7 {
|
||||||
@ -135,6 +148,8 @@ func (config *Config) loadFile (path string) {
|
|||||||
config.fontSize = valueInt
|
config.fontSize = valueInt
|
||||||
case "padding":
|
case "padding":
|
||||||
config.padding = valueInt
|
config.padding = valueInt
|
||||||
|
case "center":
|
||||||
|
config.center = valueBoolean
|
||||||
case "colorBackground":
|
case "colorBackground":
|
||||||
config.colors[ColorBackground] = valueColor
|
config.colors[ColorBackground] = valueColor
|
||||||
case "colorForeground":
|
case "colorForeground":
|
||||||
|
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 @@ import _ "git.tebibyte.media/sashakoshka/stone/backends/x"
|
|||||||
var application = &stone.Application { }
|
var application = &stone.Application { }
|
||||||
|
|
||||||
func main () {
|
func main () {
|
||||||
application.SetTitle("hellorld")
|
application.SetTitle("color demo")
|
||||||
application.SetSize(12, 7)
|
application.SetSize(12, 7)
|
||||||
|
|
||||||
iconFile16, err := os.Open("assets/scaffold16.png")
|
iconFile16, err := os.Open("assets/scaffold16.png")
|
||||||
@ -26,29 +26,26 @@ func main () {
|
|||||||
|
|
||||||
application.SetIcon([]image.Image { icon16, icon32 })
|
application.SetIcon([]image.Image { icon16, icon32 })
|
||||||
|
|
||||||
channel, err := application.Run()
|
application.OnStart(onStart)
|
||||||
|
application.OnResize(onResize)
|
||||||
|
|
||||||
|
err = application.Run()
|
||||||
if err != nil { panic(err) }
|
if err != nil { panic(err) }
|
||||||
|
}
|
||||||
|
|
||||||
|
func onStart () {
|
||||||
redraw()
|
redraw()
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
func onResize () {
|
||||||
event := <- channel
|
redraw()
|
||||||
switch event.(type) {
|
|
||||||
case stone.EventQuit:
|
|
||||||
os.Exit(0)
|
|
||||||
|
|
||||||
case stone.EventResize:
|
|
||||||
redraw()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func redraw () {
|
func redraw () {
|
||||||
text := "RAINBOW :D"
|
text := "RAINBOW :D"
|
||||||
width, height := application.Size()
|
width, height := application.Size()
|
||||||
|
|
||||||
application.Dot.X = (width - len(text)) / 2
|
application.SetDot((width - len(text)) / 2, height / 2)
|
||||||
application.Dot.Y = height / 2
|
|
||||||
fmt.Fprintln(application, text)
|
fmt.Fprintln(application, text)
|
||||||
|
|
||||||
application.SetColor(0, 0, stone.ColorYellow)
|
application.SetColor(0, 0, stone.ColorYellow)
|
||||||
@ -70,6 +67,4 @@ func redraw () {
|
|||||||
application.SetRune(x, height - 1, '=')
|
application.SetRune(x, height - 1, '=')
|
||||||
application.SetColor(x, height - 1, stone.ColorRed)
|
application.SetColor(x, height - 1, stone.ColorRed)
|
||||||
}
|
}
|
||||||
|
|
||||||
application.Draw()
|
|
||||||
}
|
}
|
||||||
|
@ -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,32 @@ 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)
|
||||||
|
|
||||||
|
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:
|
func onRelease (button stone.Button) {
|
||||||
button := event.(stone.EventPress).Button
|
if button == stone.MouseButtonLeft {
|
||||||
if button == stone.MouseButtonLeft {
|
mousePressed = false
|
||||||
mousePressed = true
|
application.SetRune(0, 0, 0)
|
||||||
application.SetRune(0, 0, '+')
|
application.Draw()
|
||||||
application.Draw()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case stone.EventRelease:
|
func onMouseMove (x, y int) { if mousePressed {
|
||||||
button := event.(stone.EventRelease).Button
|
application.SetRune(x, y, '#')
|
||||||
if button == stone.MouseButtonLeft {
|
application.Draw()
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ import _ "git.tebibyte.media/sashakoshka/stone/backends/x"
|
|||||||
|
|
||||||
var application = &stone.Application { }
|
var application = &stone.Application { }
|
||||||
var currentTime = time.Time { }
|
var currentTime = time.Time { }
|
||||||
var tickPing = make(chan(struct { }))
|
|
||||||
|
|
||||||
func main () {
|
func main () {
|
||||||
application.SetTitle("hellorld")
|
application.SetTitle("hellorld")
|
||||||
@ -29,33 +28,26 @@ func main () {
|
|||||||
|
|
||||||
application.SetIcon([]image.Image { icon16, icon32 })
|
application.SetIcon([]image.Image { icon16, icon32 })
|
||||||
|
|
||||||
channel, err := application.Run()
|
application.OnStart(onStart)
|
||||||
if err != nil { panic(err) }
|
application.OnResize(onResize)
|
||||||
|
|
||||||
|
err = application.Run()
|
||||||
|
if err != nil { panic(err) }
|
||||||
|
}
|
||||||
|
|
||||||
|
func onStart () {
|
||||||
redraw()
|
redraw()
|
||||||
go tick()
|
go tick()
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
func onResize () {
|
||||||
select {
|
redraw()
|
||||||
case <- tickPing:
|
|
||||||
redraw()
|
|
||||||
|
|
||||||
case event := <- channel:
|
|
||||||
switch event.(type) {
|
|
||||||
case stone.EventQuit:
|
|
||||||
os.Exit(0)
|
|
||||||
|
|
||||||
case stone.EventResize:
|
|
||||||
redraw()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func redraw () {
|
func redraw () {
|
||||||
currentTime = time.Now()
|
currentTime = time.Now()
|
||||||
|
|
||||||
application.ResetDot()
|
application.SetDot(0, 0)
|
||||||
fmt.Fprintln(application, "hellorld!")
|
fmt.Fprintln(application, "hellorld!")
|
||||||
|
|
||||||
hour := currentTime.Hour()
|
hour := currentTime.Hour()
|
||||||
@ -70,13 +62,12 @@ func redraw () {
|
|||||||
application.SetRune(5, 1, ':')
|
application.SetRune(5, 1, ':')
|
||||||
application.SetRune(6, 1, rune(second / 10 + 48))
|
application.SetRune(6, 1, rune(second / 10 + 48))
|
||||||
application.SetRune(7, 1, rune(second % 10 + 48))
|
application.SetRune(7, 1, rune(second % 10 + 48))
|
||||||
|
|
||||||
application.Draw()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func tick () {
|
func tick () {
|
||||||
for {
|
for {
|
||||||
tickPing <- struct { } { }
|
redraw()
|
||||||
|
application.Draw()
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user