5 Commits

16 changed files with 238 additions and 287 deletions

View File

@@ -11,6 +11,7 @@ type Application struct {
backend Backend backend Backend
config Config config Config
callbackManager CallbackManager callbackManager CallbackManager
imageManager ImageManager
} }
// Run initializes the application, starts it, and then returns a channel that // Run initializes the application, starts it, and then returns a channel that
@@ -33,60 +34,42 @@ func (application *Application) Run () (
return return
} }
// OnQuit registers an event handler to be called just before the application
// quits. This can happen when the user closes the application, or the backend
// experiences an unrecoverable error.
func (application *Application) OnQuit ( func (application *Application) OnQuit (
onQuit func (), onQuit func (),
) { ) {
application.callbackManager.onQuit = onQuit application.callbackManager.onQuit = onQuit
} }
// OnPress registers an event handler to be called when a key or mouse button
// is pressed.
func (application *Application) OnPress ( func (application *Application) OnPress (
onPress func (button Button, modifiers Modifiers), onPress func (button Button),
) { ) {
application.callbackManager.onPress = onPress application.callbackManager.onPress = onPress
} }
// OnPress registers an event handler to be called when a key or mouse button
// is released.
func (application *Application) OnRelease ( func (application *Application) OnRelease (
onRelease func (button Button), onRelease func (button Button),
) { ) {
application.callbackManager.onRelease = onRelease application.callbackManager.onRelease = onRelease
} }
// OnResize registers an event handler to be called when the application window
// is resized. After the event handler is called, any updates it makes will
// automatically be pushed to the screen.
func (application *Application) OnResize ( func (application *Application) OnResize (
onResize func (), onResize func (),
) { ) {
application.callbackManager.onResize = onResize application.callbackManager.onResize = onResize
} }
// OnMouseMove registers an event handler to be called when mouse motion is
// detected. The coordinates of the cell that the mouse now hovers over are
// given as input.
func (application *Application) OnMouseMove ( func (application *Application) OnMouseMove (
onMouseMove func (x, y int), onMouseMove func (x, y int),
) { ) {
application.callbackManager.onMouseMove = onMouseMove application.callbackManager.onMouseMove = onMouseMove
} }
// OnScroll registers an event handler to be called when the user uses the mouse
// scroll wheel. Horizontal and vertical amounts are given as input.
func (application *Application) OnScroll ( func (application *Application) OnScroll (
onScroll func (x, y int), onScroll func (x, y int),
) { ) {
application.callbackManager.onScroll = onScroll application.callbackManager.onScroll = onScroll
} }
// OnStart registers an event handler to be called once when the application
// starts, right before the first time updates are pushed to the screen.
// Anything done in here will be the first thing to appear on screen.
func (application *Application) OnStart ( func (application *Application) OnStart (
onStart func (), onStart func (),
) { ) {
@@ -138,3 +121,21 @@ func (application *Application) Config () (config *Config) {
config = &application.config config = &application.config
return return
} }
// AddImage adds a new image buffer and returns a pointer to it.
func (application *Application) NewImage () (im *ColorImage) {
cellWidth, cellHeight := application.backend.CellMetrics()
im = &ColorImage {
cellWidth: cellWidth,
cellHeight: cellHeight,
}
application.imageManager.Add(im)
return
}
// Remove removes the specified image buffer, if the application has it. If the
// image was found and removed, removed will be true.
func (application *Application) RemoveImage (im *ColorImage) (removed bool) {
removed = application.imageManager.Remove(im)
return
}

View File

@@ -3,54 +3,18 @@ package stone
import "image" import "image"
import "errors" import "errors"
// Backend represents a backend for stone. Backends can be registered for use
// with the RegisterBackend() function. All of the below methods MUST be thread
// safe!
type Backend interface { type Backend interface {
// Run is the backend's event loop. It must cleanly exit when the user Run ()
// closes the window, but not before calling the OnQuit event. Run
// must call event handlers within its own event loop in a
// non-concurrent fashion.
//
// The OnStart event handler must run after the backend has been fully
// initialized, and right before updates are first pushed to the screen.
// Whatever the application draws from within this event handler must be
// the first thing that appears on-screen.
//
// The OnResize event handler must run whenever the window is resized.
// The backend must push updates to the screen after OnResize has been
// run.
//
// The backend must not push updates to the screen in any other case,
// except when its Draw() method is specifically called.
//
// The OnPress, OnRelease, OnMouseMove, and OnMouseScroll events are to
// be called when such events happen. It is reccommended to compress
// resize, mouse move, and mouse scroll events whenever possible to
// reduce the likelihood of event buildup.
Run ()
// SetTitle sets the application title. This will most often be the
// window title. This method may not always produce an effect, depending
// on the backend.
SetTitle (title string) (err error) SetTitle (title string) (err error)
SetIcon (icons []image.Image) (err error)
// SetIcon takes in a set of images of different sizes and sets the Draw ()
// window's icon to them. This method may not always produce an effect, CellMetrics () (width, height int)
// depending on the backend.
SetIcon (icons []image.Image) (err error)
// Draw pushes all updates made to the application's buffer to the
// screen.
Draw ()
} }
// BackendFactory must completely initialize a backend, and return it. If
// anything goes wrong, it must stop, clean up any resources and return an
// error so another backend can be chosen.
type BackendFactory func ( type BackendFactory func (
application *Application, application *Application,
callbackManager *CallbackManager, callbackManager *CallbackManager,
imageManager *ImageManager,
) ( ) (
backend Backend, backend Backend,
err error, err error,
@@ -58,7 +22,6 @@ type BackendFactory func (
var factories []BackendFactory var factories []BackendFactory
// RegisterBackend registers a backend factory.
func RegisterBackend (factory BackendFactory) { func RegisterBackend (factory BackendFactory) {
factories = append(factories, factory) factories = append(factories, factory)
} }
@@ -66,7 +29,10 @@ 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, &application.callbackManager) backend, err = factory (
application,
&application.callbackManager,
&application.imageManager)
if err == nil && backend != nil { return } if err == nil && backend != nil { return }
} }

View File

@@ -90,16 +90,17 @@ func (backend *Backend) drawRune (
} }
if character < 32 { return } if character < 32 { return }
origin := backend.originOfCell(x, y + 1) origin := backend.originOfCell(x, y + 1)
destinationRectangle, mask, maskPoint, _, ok := 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), Y: fixed.I(origin.Y - backend.metrics.descent),
}, },
character) character)
if !ok { if !ok {
println("warning")
strokeRectangle ( strokeRectangle (
&image.Uniform { &image.Uniform {
C: backend.config.Color(stone.ColorForeground), C: backend.config.Color(stone.ColorForeground),
@@ -109,7 +110,7 @@ func (backend *Backend) drawRune (
return return
} }
if backend.drawCellBounds { if backend.drawCellBounds {
strokeRectangle ( strokeRectangle (
&image.Uniform { &image.Uniform {
C: backend.config.Color(stone.ColorForeground), C: backend.config.Color(stone.ColorForeground),
@@ -117,71 +118,17 @@ func (backend *Backend) drawRune (
backend.canvas, backend.canvas,
backend.boundsOfCell(x, y)) backend.boundsOfCell(x, y))
} }
// cue a series of pointless optimizations draw.DrawMask (
alphaMask, isAlpha := mask.(*image.Alpha) backend.canvas,
if isAlpha { destinationRectangle,
backend.sprayRuneMaskAlpha ( &image.Uniform {
alphaMask, destinationRectangle, C: backend.config.Color(runeColor),
maskPoint, backend.colors[runeColor]) },
} else { image.Point { },
backend.sprayRuneMask ( mask,
mask, destinationRectangle, maskPoint,
maskPoint, backend.colors[runeColor]) draw.Over)
}
}
func (backend *Backend) sprayRuneMask (
mask image.Image,
bounds image.Rectangle,
maskPoint image.Point,
fill xgraphics.BGRA,
) {
maxX := bounds.Max.X - bounds.Min.X
maxY := bounds.Max.Y - bounds.Min.Y
for y := 0; y < maxY; y ++ {
for x := 0; x < maxX; x ++ {
_, _, _,
alpha := mask.At(x + maskPoint.X, y + maskPoint.Y).RGBA()
backend.canvas.SetBGRA (
x + bounds.Min.X,
y + bounds.Min.Y - backend.metrics.descent,
xgraphics.BlendBGRA (
backend.colors[stone.ColorBackground],
xgraphics.BGRA {
R: fill.R,
G: fill.G,
B: fill.B,
A: uint8(alpha >> 8),
}))
}}
}
func (backend *Backend) sprayRuneMaskAlpha (
mask *image.Alpha,
bounds image.Rectangle,
maskPoint image.Point,
fill xgraphics.BGRA,
) {
maxX := bounds.Max.X - bounds.Min.X
maxY := bounds.Max.Y - bounds.Min.Y
for y := 0; y < maxY; y ++ {
for x := 0; x < maxX; x ++ {
alpha := mask.AlphaAt(x + maskPoint.X, y + maskPoint.Y).A
backend.canvas.SetBGRA (
x + bounds.Min.X,
y + bounds.Min.Y - backend.metrics.descent,
xgraphics.BlendBGRA (
backend.colors[stone.ColorBackground],
xgraphics.BGRA {
R: fill.R,
G: fill.G,
B: fill.B,
A: alpha,
}))
}}
} }
func fillRectangle ( func fillRectangle (

View File

@@ -91,9 +91,7 @@ func (backend *Backend) handleButtonPress (
backend.compressScrollSum(&sum) backend.compressScrollSum(&sum)
backend.callbackManager.RunScroll(sum.x, sum.y) backend.callbackManager.RunScroll(sum.x, sum.y)
} else { } else {
backend.callbackManager.RunPress ( backend.callbackManager.RunPress(stone.Button(buttonEvent.Detail + 127))
stone.Button(buttonEvent.Detail + 127),
stone.Modifiers { })
} }
} }
@@ -112,15 +110,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.callbackManager.RunPress (button, stone.Modifiers { backend.callbackManager.RunPress(button)
// FIXME these may not be correct in all cases
Shift: (keyEvent.State & xproto.ModMaskShift) > 0,
Control: (keyEvent.State & xproto.ModMaskControl) > 0,
Alt: (keyEvent.State & xproto.ModMask1) > 0,
// Meta: (keyEvent.State & xproto.??) > 0,
Super: (keyEvent.State & xproto.ModMask4) > 0,
// Hyper: (keyEvent.State & xproto.??) > 0,
})
} }
func (backend *Backend) handleKeyRelease ( func (backend *Backend) handleKeyRelease (

View File

@@ -19,8 +19,9 @@ import "github.com/flopp/go-findfont"
// factory instantiates an X backend. // factory instantiates an X backend.
func factory ( func factory (
application *stone.Application, application *stone.Application,
callbackManager *stone.CallbackManager, callbackManager *stone.CallbackManager,
imageManager *stone.ImageManager,
) ( ) (
output stone.Backend, output stone.Backend,
err error, err error,
@@ -29,6 +30,7 @@ func factory (
application: application, application: application,
config: application.Config(), config: application.Config(),
callbackManager: callbackManager, callbackManager: callbackManager,
imageManager: imageManager,
} }
// load font // load font
@@ -133,7 +135,7 @@ func factory (
Connect(backend.connection, backend.window.Id) Connect(backend.connection, backend.window.Id)
// uncomment these to draw debug bounds // uncomment these to draw debug bounds
// backend.drawCellBounds = true // backend.drawCellBounds = true
// backend.drawBufferBounds = true // backend.drawBufferBounds = true
output = backend output = backend

View File

@@ -1,6 +1,5 @@
package x package x
// import "fmt"
import "unicode" import "unicode"
import "github.com/jezek/xgb/xproto" import "github.com/jezek/xgb/xproto"
import "github.com/jezek/xgbutil/keybind" import "github.com/jezek/xgbutil/keybind"
@@ -37,15 +36,10 @@ var buttonCodeTable = map[xproto.Keysym] stone.Button {
0xFFE2: stone.KeyRightShift, 0xFFE2: stone.KeyRightShift,
0xFFE3: stone.KeyLeftControl, 0xFFE3: stone.KeyLeftControl,
0xFFE4: stone.KeyRightControl, 0xFFE4: stone.KeyRightControl,
0xFFE7: stone.KeyLeftMeta,
0xFFE8: stone.KeyRightMeta,
0xFFE9: stone.KeyLeftAlt, 0xFFE9: stone.KeyLeftAlt,
0xFFEA: stone.KeyRightAlt, 0xFFEA: stone.KeyRightAlt,
0xFFEB: stone.KeyLeftSuper, 0xFFEB: stone.KeyLeftSuper,
0xFFEC: stone.KeyRightSuper, 0xFFEC: stone.KeyRightSuper,
0xFFED: stone.KeyLeftHyper,
0xFFEE: stone.KeyRightHyper,
0xFFFF: stone.KeyDelete, 0xFFFF: stone.KeyDelete,
@@ -61,14 +55,6 @@ var buttonCodeTable = map[xproto.Keysym] stone.Button {
0xFFC7: stone.KeyF10, 0xFFC7: stone.KeyF10,
0xFFC8: stone.KeyF11, 0xFFC8: stone.KeyF11,
0xFFC9: stone.KeyF12, 0xFFC9: stone.KeyF12,
// TODO: send this whenever a compose key, dead key, etc is pressed,
// and then send the resulting character while witholding the key
// presses that were used to compose it. As far as the program is
// concerned, a magical key with the final character was pressed and the
// KeyDead key is just so that the program might provide some visual
// feedback to the user while input is being waited for.
0xFF20: stone.KeyDead,
} }
func (backend *Backend) keycodeToButton ( func (backend *Backend) keycodeToButton (
@@ -98,7 +84,7 @@ func (backend *Backend) keycodeToButton (
symbol3 = symbol1 symbol3 = symbol1
case symbol3 == 0 && symbol4 == 0: case symbol3 == 0 && symbol4 == 0:
symbol3 = symbol1 symbol3 = symbol1
symbol4 = symbol2 symbol2 = symbol2
case symbol4 == 0: case symbol4 == 0:
symbol4 = 0 symbol4 = 0
} }
@@ -120,8 +106,7 @@ func (backend *Backend) keycodeToButton (
symbol2Rune = unicode.ToUpper(symbol1Rune) symbol2Rune = unicode.ToUpper(symbol1Rune)
cased = true cased = true
} else { } else {
symbol2 = symbol1 symbol2 = symbol1
symbol2Rune = symbol1Rune
} }
} }
if symbol4 == 0 { if symbol4 == 0 {
@@ -132,8 +117,7 @@ func (backend *Backend) keycodeToButton (
symbol4Rune = unicode.ToUpper(symbol3Rune) symbol4Rune = unicode.ToUpper(symbol3Rune)
cased = true cased = true
} else { } else {
symbol4 = symbol3 symbol4 = symbol3
symbol4Rune = symbol3Rune
} }
} }

View File

@@ -17,6 +17,7 @@ type Backend struct {
application *stone.Application application *stone.Application
config *stone.Config config *stone.Config
callbackManager *stone.CallbackManager callbackManager *stone.CallbackManager
imageManager *stone.ImageManager
connection *xgbutil.XUtil connection *xgbutil.XUtil
window *xwindow.Window window *xwindow.Window
canvas *xgraphics.Image canvas *xgraphics.Image
@@ -88,6 +89,12 @@ func (backend *Backend) SetIcon (icons []image.Image) (err error) {
return return
} }
func (backend *Backend) CellMetrics () (width, height int) {
width = backend.metrics.cellWidth
height = backend.metrics.cellHeight
return
}
// calculateWindowSize calculates window bounds based on the internal buffer // calculateWindowSize calculates window bounds based on the internal buffer
// size. // size.
func (backend *Backend) calculateWindowSize () (x, y int) { func (backend *Backend) calculateWindowSize () (x, y int) {

View File

@@ -8,8 +8,8 @@ type Color uint8
const ( const (
ColorBackground Color = 0x0 ColorBackground Color = 0x0
ColorForeground Color = 0x1 ColorForeground Color = 0x1
ColorDim Color = 0x2 ColorRed Color = 0x2
ColorRed Color = 0x3 ColorOrange Color = 0x3
ColorYellow Color = 0x4 ColorYellow Color = 0x4
ColorGreen Color = 0x5 ColorGreen Color = 0x5
ColorBlue Color = 0x6 ColorBlue Color = 0x6

View File

@@ -55,10 +55,10 @@ func (config *Config) load () {
color.RGBA { R: 0, G: 0, B: 0, A: 0 }, color.RGBA { R: 0, G: 0, B: 0, A: 0 },
// foreground // foreground
color.RGBA { R: 0xFF, G: 0xFF, B: 0xFF, A: 0xFF }, color.RGBA { R: 0xFF, G: 0xFF, B: 0xFF, A: 0xFF },
// dim
color.RGBA { R: 0x80, G: 0x80, B: 0x80, A: 0xFF },
// red // red
color.RGBA { R: 0xFF, G: 0x00, B: 0x00, A: 0xFF }, color.RGBA { R: 0xFF, G: 0x00, B: 0x00, A: 0xFF },
// orange
color.RGBA { R: 0xFF, G: 0x80, B: 0x00, A: 0xFF },
// yellow // yellow
color.RGBA { R: 0xFF, G: 0xFF, B: 0x00, A: 0xFF }, color.RGBA { R: 0xFF, G: 0xFF, B: 0x00, A: 0xFF },
// green // green
@@ -154,10 +154,10 @@ func (config *Config) loadFile (path string) {
config.colors[ColorBackground] = valueColor config.colors[ColorBackground] = valueColor
case "colorForeground": case "colorForeground":
config.colors[ColorForeground] = valueColor config.colors[ColorForeground] = valueColor
case "colorDim":
config.colors[ColorDim] = valueColor
case "colorRed": case "colorRed":
config.colors[ColorRed] = valueColor config.colors[ColorRed] = valueColor
case "colorOrange":
config.colors[ColorOrange] = valueColor
case "colorYellow": case "colorYellow":
config.colors[ColorYellow] = valueColor config.colors[ColorYellow] = valueColor
case "colorGreen": case "colorGreen":

View File

@@ -2,7 +2,7 @@ package stone
type CallbackManager struct { type CallbackManager struct {
onQuit func () onQuit func ()
onPress func (button Button, modifiers Modifiers) onPress func (button Button)
onRelease func (button Button) onRelease func (button Button)
onResize func () onResize func ()
onMouseMove func (x, y int) onMouseMove func (x, y int)
@@ -15,9 +15,9 @@ func (manager *CallbackManager) RunQuit () {
manager.onQuit() manager.onQuit()
} }
func (manager *CallbackManager) RunPress (button Button, modifiers Modifiers) { func (manager *CallbackManager) RunPress (button Button) {
if manager.onPress == nil { return } if manager.onPress == nil { return }
manager.onPress(button, modifiers) manager.onPress(button)
} }
func (manager *CallbackManager) RunRelease (button Button) { func (manager *CallbackManager) RunRelease (button Button) {

View File

@@ -58,7 +58,7 @@ func redraw () {
application.SetRune(0, height - 1, '+') application.SetRune(0, height - 1, '+')
for x := 0; x < width; x ++ { for x := 0; x < width; x ++ {
application.SetColor(x, height / 2, stone.Color(x % 5 + 3)) application.SetColor(x, height / 2, stone.Color(x % 6 + 2))
} }
for x := 1; x < width - 1; x ++ { for x := 1; x < width - 1; x ++ {

View File

@@ -34,7 +34,7 @@ func main () {
if err != nil { panic(err) } if err != nil { panic(err) }
} }
func onPress (button stone.Button, modifiers stone.Modifiers) { func onPress (button stone.Button) {
if button == stone.MouseButtonLeft { if button == stone.MouseButtonLeft {
mousePressed = true mousePressed = true
application.SetRune(0, 0, '+') application.SetRune(0, 0, '+')

View File

@@ -1,41 +0,0 @@
package main
import "os"
import "image"
import _ "image/png"
import "git.tebibyte.media/sashakoshka/stone"
import _ "git.tebibyte.media/sashakoshka/stone/backends/x"
var application = &stone.Application { }
func main () {
application.SetTitle("press any key")
application.SetSize(8, 1)
iconFile16, err := os.Open("assets/scaffold16.png")
if err != nil { panic(err) }
icon16, _, err := image.Decode(iconFile16)
if err != nil { panic(err) }
iconFile16.Close()
iconFile32, err := os.Open("assets/scaffold32.png")
if err != nil { panic(err) }
icon32, _, err := image.Decode(iconFile32)
if err != nil { panic(err) }
iconFile16.Close()
application.SetIcon([]image.Image { icon16, icon32 })
application.OnPress(onPress)
application.OnRelease(onRelease)
err = application.Run()
if err != nil { panic(err) }
}
func onPress (button stone.Button, modifiers stone.Modifiers) {
println("press", button)
}
func onRelease (button stone.Button) {
println("release", button)
}

View File

@@ -1,20 +1,17 @@
package main package main
import "os" import "os"
import "fmt"
import "image" import "image"
import _ "image/png" import _ "image/png"
import "git.tebibyte.media/sashakoshka/stone" import "git.tebibyte.media/sashakoshka/stone"
import _ "git.tebibyte.media/sashakoshka/stone/backends/x" import _ "git.tebibyte.media/sashakoshka/stone/backends/x"
var application = &stone.Application { } var application = &stone.Application { }
var caretX = 0 var caret = 0
var caretY = 2
var page = 1
func main () { func main () {
application.SetTitle("hellorld") application.SetTitle("hellorld")
application.SetSize(32, 28) application.SetSize(32, 16)
iconFile16, err := os.Open("assets/scaffold16.png") iconFile16, err := os.Open("assets/scaffold16.png")
if err != nil { panic(err) } if err != nil { panic(err) }
@@ -28,57 +25,32 @@ func main () {
iconFile16.Close() iconFile16.Close()
application.SetIcon([]image.Image { icon16, icon32 }) application.SetIcon([]image.Image { icon16, icon32 })
application.OnStart(redraw)
application.OnPress(onPress)
application.OnResize(redraw)
err = application.Run() channel, err := application.Run()
if err != nil { panic(err) } if err != nil { panic(err) }
application.Draw() application.Draw()
}
func redraw () { for {
application.Clear() event := <- channel
_, height := application.Size() switch event.(type) {
application.SetDot(0, 0) case stone.EventQuit:
fmt.Fprint(application, "type some text below:") os.Exit(0)
caretX = 0
caretY = 2
application.SetDot(0, height - 1)
fmt.Fprintf(application, "page %d", page)
drawCaret()
}
func drawCaret () { case stone.EventPress:
application.SetRune(caretX, caretY, '+') button := event.(stone.EventPress).Button
application.SetColor(caretX, caretY, stone.ColorDim) if button.Printable() {
} application.SetRune(caret, 0, rune(button))
caret ++
width, _ := application.Size()
if caret >= width {
caret = 0
}
application.Draw()
}
func onPress (button stone.Button, modifiers stone.Modifiers) { case stone.EventResize:
width, height := application.Size() application.Draw()
if button == stone.KeyEnter {
application.SetRune(caretX, caretY, 0)
caretX = 0
caretY ++
} else if button.Printable() {
application.SetRune(caretX, caretY, rune(button))
application.SetColor(caretX, caretY, stone.ColorForeground)
caretX ++
if caretX >= width {
caretX = 0
caretY ++
} }
} }
if caretY >= height - 2 {
page ++
redraw()
}
drawCaret()
application.Draw()
} }

142
image.go Normal file
View File

@@ -0,0 +1,142 @@
package stone
import "sync"
import "image"
import "image/color"
type ImageManager struct {
lock sync.RWMutex
images []*ColorImage
}
func (manager *ImageManager) For (callback func (im *ColorImage)) {
manager.lock.RLock()
defer manager.lock.RUnlock()
for _, im := range manager.images {
callback(im)
}
}
func (manager *ImageManager) Add (im *ColorImage) {
manager.lock.Lock()
defer manager.lock.Unlock()
manager.images = append(manager.images, im)
}
func (manager *ImageManager) Size () (size int) {
manager.lock.RLock()
defer manager.lock.RUnlock()
size = len(manager.images)
return
}
func (manager *ImageManager) At (index int) (im *ColorImage) {
manager.lock.RLock()
defer manager.lock.RUnlock()
if index < 0 || index > len(manager.images) { return }
im = manager.images[index]
return
}
func (manager *ImageManager) Remove (im *ColorImage) (removed bool) {
manager.lock.Lock()
defer manager.lock.Unlock()
index := 0
for manager.images[index] != im && index < len(manager.images) {
index ++
}
if index >= len(manager.images) { return }
manager.images = append (
manager.images[:index],
manager.images[index + 1:]...)
removed = true
return
}
type ColorImage struct {
x, y int
width, height int
bufferWidth, bufferHeight int
cellWidth, cellHeight int
buffer []color.RGBA
clean bool
}
func (im *ColorImage) Model () (model color.Model) {
model = color.RGBAModel
return
}
func (im *ColorImage) Bounds () (bounds image.Rectangle) {
bounds.Max.X = im.width
bounds.Max.Y = im.height
return
}
func (im *ColorImage) Size () (width, height int) {
width = im.width
height = im.height
return
}
func (im *ColorImage) SetSize (width, height int) {
im.width = width
im.height = height
im.bufferWidth = im.cellWidth * im.width
im.bufferHeight = im.cellHeight * im.height
im.buffer = make([]color.RGBA, im.bufferWidth * im.bufferHeight)
im.clean = false
}
func (im *ColorImage) At (x, y int) (pixel color.Color) {
if im.outOfBounds(x, y) { return }
pixel = im.buffer[x + y * im.width]
return
}
func (im *ColorImage) AtRGBA (x, y int) (pixel color.RGBA) {
if im.outOfBounds(x, y) { return }
pixel = im.buffer[x + y * im.width]
return
}
func (im *ColorImage) Set (x, y int, pixel color.Color) {
if im.outOfBounds(x, y) { return }
r, g, b, a := pixel.RGBA()
im.buffer[x + y * im.width] = color.RGBA {
R: uint8(r >> 8),
G: uint8(g >> 8),
B: uint8(b >> 8),
A: uint8(a >> 8),
}
im.clean = false
return
}
func (im *ColorImage) SetRGBA (x, y int, pixel color.RGBA) {
if im.outOfBounds(x, y) { return }
im.buffer[x + y * im.width] = pixel
return
}
func (im *ColorImage) MarkClean () {
im.clean = true
}
func (im *ColorImage) outOfBounds (x, y int) (outOfBounds bool) {
outOfBounds =
x >= im.width ||
y >= im.height ||
x < 0 ||
y < 0
return
}

View File

@@ -34,13 +34,9 @@ const (
KeyLeftControl Button = 22 KeyLeftControl Button = 22
KeyRightControl Button = 23 KeyRightControl Button = 23
KeyLeftAlt Button = 24 KeyLeftAlt Button = 24
KeyRightAlt Button = 25 KeyRightAlt Button = 25
KeyLeftMeta Button = 26 KeyLeftSuper Button = 26
KeyRightMeta Button = 27 KeyRightSuper Button = 27
KeyLeftSuper Button = 28
KeyRightSuper Button = 29
KeyLeftHyper Button = 30
KeyRightHyper Button = 31
KeyDelete Button = 127 KeyDelete Button = 127
@@ -71,8 +67,6 @@ const (
KeyF10 Button = 153 KeyF10 Button = 153
KeyF11 Button = 154 KeyF11 Button = 154
KeyF12 Button = 155 KeyF12 Button = 155
KeyDead Button = 156
) )
// Printable returns whether or not the character could show up on screen. If // Printable returns whether or not the character could show up on screen. If
@@ -82,16 +76,3 @@ func (button Button) Printable () (printable bool) {
printable = unicode.IsPrint(rune(button)) printable = unicode.IsPrint(rune(button))
return return
} }
// Modifiers lists what modifier keys are being pressed. This is used in
// conjunction with a button code in a button press event. These should be used
// instead of attempting to track the state of the modifier keys, because there
// is no guarantee that one press event will be coupled with one release event.
type Modifiers struct {
Shift bool
Control bool
Alt bool
Meta bool
Super bool
Hyper bool
}