Compare commits
5 Commits
v0.1.0
...
image-buff
| Author | SHA1 | Date | |
|---|---|---|---|
| bbf067d463 | |||
| 32d95b338b | |||
| ef1e1f4ac2 | |||
| db20ea707d | |||
| 0fb6d8a6b2 |
@@ -11,6 +11,7 @@ type Application struct {
|
||||
backend Backend
|
||||
config Config
|
||||
callbackManager CallbackManager
|
||||
imageManager ImageManager
|
||||
}
|
||||
|
||||
// Run initializes the application, starts it, and then returns a channel that
|
||||
@@ -33,60 +34,42 @@ func (application *Application) Run () (
|
||||
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 (
|
||||
onQuit func (),
|
||||
) {
|
||||
application.callbackManager.onQuit = onQuit
|
||||
}
|
||||
|
||||
// OnPress registers an event handler to be called when a key or mouse button
|
||||
// is pressed.
|
||||
func (application *Application) OnPress (
|
||||
onPress func (button Button, modifiers Modifiers),
|
||||
onPress func (button Button),
|
||||
) {
|
||||
application.callbackManager.onPress = onPress
|
||||
}
|
||||
|
||||
// OnPress registers an event handler to be called when a key or mouse button
|
||||
// is released.
|
||||
func (application *Application) OnRelease (
|
||||
onRelease func (button Button),
|
||||
) {
|
||||
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 (
|
||||
onResize func (),
|
||||
) {
|
||||
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 (
|
||||
onMouseMove func (x, y int),
|
||||
) {
|
||||
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 (
|
||||
onScroll func (x, y int),
|
||||
) {
|
||||
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 (
|
||||
onStart func (),
|
||||
) {
|
||||
@@ -138,3 +121,21 @@ func (application *Application) Config () (config *Config) {
|
||||
config = &application.config
|
||||
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
|
||||
}
|
||||
|
||||
54
backend.go
54
backend.go
@@ -3,54 +3,18 @@ package stone
|
||||
import "image"
|
||||
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 {
|
||||
// Run is the backend's event loop. It must cleanly exit when the user
|
||||
// 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.
|
||||
Run ()
|
||||
SetTitle (title string) (err error)
|
||||
|
||||
// SetIcon takes in a set of images of different sizes and sets the
|
||||
// window's icon to them. This method may not always produce an effect,
|
||||
// depending on the backend.
|
||||
SetIcon (icons []image.Image) (err error)
|
||||
|
||||
// Draw pushes all updates made to the application's buffer to the
|
||||
// screen.
|
||||
Draw ()
|
||||
SetIcon (icons []image.Image) (err error)
|
||||
Draw ()
|
||||
CellMetrics () (width, height int)
|
||||
}
|
||||
|
||||
// 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 (
|
||||
application *Application,
|
||||
application *Application,
|
||||
callbackManager *CallbackManager,
|
||||
imageManager *ImageManager,
|
||||
) (
|
||||
backend Backend,
|
||||
err error,
|
||||
@@ -58,7 +22,6 @@ type BackendFactory func (
|
||||
|
||||
var factories []BackendFactory
|
||||
|
||||
// RegisterBackend registers a backend factory.
|
||||
func RegisterBackend (factory BackendFactory) {
|
||||
factories = append(factories, factory)
|
||||
}
|
||||
@@ -66,7 +29,10 @@ func RegisterBackend (factory BackendFactory) {
|
||||
func instantiateBackend (application *Application) (backend Backend, err error) {
|
||||
// find a suitable backend
|
||||
for _, factory := range factories {
|
||||
backend, err = factory(application, &application.callbackManager)
|
||||
backend, err = factory (
|
||||
application,
|
||||
&application.callbackManager,
|
||||
&application.imageManager)
|
||||
if err == nil && backend != nil { return }
|
||||
}
|
||||
|
||||
|
||||
@@ -90,16 +90,17 @@ func (backend *Backend) drawRune (
|
||||
}
|
||||
|
||||
if character < 32 { return }
|
||||
|
||||
|
||||
origin := backend.originOfCell(x, y + 1)
|
||||
destinationRectangle, mask, maskPoint, _, ok := backend.font.face.Glyph (
|
||||
fixed.Point26_6 {
|
||||
X: fixed.I(origin.X),
|
||||
Y: fixed.I(origin.Y),
|
||||
Y: fixed.I(origin.Y - backend.metrics.descent),
|
||||
},
|
||||
character)
|
||||
|
||||
if !ok {
|
||||
println("warning")
|
||||
strokeRectangle (
|
||||
&image.Uniform {
|
||||
C: backend.config.Color(stone.ColorForeground),
|
||||
@@ -109,7 +110,7 @@ func (backend *Backend) drawRune (
|
||||
return
|
||||
}
|
||||
|
||||
if backend.drawCellBounds {
|
||||
if backend.drawCellBounds {
|
||||
strokeRectangle (
|
||||
&image.Uniform {
|
||||
C: backend.config.Color(stone.ColorForeground),
|
||||
@@ -117,71 +118,17 @@ func (backend *Backend) drawRune (
|
||||
backend.canvas,
|
||||
backend.boundsOfCell(x, y))
|
||||
}
|
||||
|
||||
// cue a series of pointless optimizations
|
||||
alphaMask, isAlpha := mask.(*image.Alpha)
|
||||
if isAlpha {
|
||||
backend.sprayRuneMaskAlpha (
|
||||
alphaMask, destinationRectangle,
|
||||
maskPoint, backend.colors[runeColor])
|
||||
} else {
|
||||
backend.sprayRuneMask (
|
||||
mask, destinationRectangle,
|
||||
maskPoint, backend.colors[runeColor])
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}))
|
||||
}}
|
||||
|
||||
draw.DrawMask (
|
||||
backend.canvas,
|
||||
destinationRectangle,
|
||||
&image.Uniform {
|
||||
C: backend.config.Color(runeColor),
|
||||
},
|
||||
image.Point { },
|
||||
mask,
|
||||
maskPoint,
|
||||
draw.Over)
|
||||
}
|
||||
|
||||
func fillRectangle (
|
||||
|
||||
@@ -91,9 +91,7 @@ func (backend *Backend) handleButtonPress (
|
||||
backend.compressScrollSum(&sum)
|
||||
backend.callbackManager.RunScroll(sum.x, sum.y)
|
||||
} else {
|
||||
backend.callbackManager.RunPress (
|
||||
stone.Button(buttonEvent.Detail + 127),
|
||||
stone.Modifiers { })
|
||||
backend.callbackManager.RunPress(stone.Button(buttonEvent.Detail + 127))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,15 +110,7 @@ func (backend *Backend) handleKeyPress (
|
||||
) {
|
||||
keyEvent := *event.KeyPressEvent
|
||||
button := backend.keycodeToButton(keyEvent.Detail, keyEvent.State)
|
||||
backend.callbackManager.RunPress (button, stone.Modifiers {
|
||||
// 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,
|
||||
})
|
||||
backend.callbackManager.RunPress(button)
|
||||
}
|
||||
|
||||
func (backend *Backend) handleKeyRelease (
|
||||
|
||||
@@ -19,8 +19,9 @@ import "github.com/flopp/go-findfont"
|
||||
|
||||
// factory instantiates an X backend.
|
||||
func factory (
|
||||
application *stone.Application,
|
||||
application *stone.Application,
|
||||
callbackManager *stone.CallbackManager,
|
||||
imageManager *stone.ImageManager,
|
||||
) (
|
||||
output stone.Backend,
|
||||
err error,
|
||||
@@ -29,6 +30,7 @@ func factory (
|
||||
application: application,
|
||||
config: application.Config(),
|
||||
callbackManager: callbackManager,
|
||||
imageManager: imageManager,
|
||||
}
|
||||
|
||||
// load font
|
||||
@@ -133,7 +135,7 @@ func factory (
|
||||
Connect(backend.connection, backend.window.Id)
|
||||
|
||||
// uncomment these to draw debug bounds
|
||||
// backend.drawCellBounds = true
|
||||
// backend.drawCellBounds = true
|
||||
// backend.drawBufferBounds = true
|
||||
|
||||
output = backend
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package x
|
||||
|
||||
// import "fmt"
|
||||
import "unicode"
|
||||
import "github.com/jezek/xgb/xproto"
|
||||
import "github.com/jezek/xgbutil/keybind"
|
||||
@@ -37,15 +36,10 @@ var buttonCodeTable = map[xproto.Keysym] stone.Button {
|
||||
0xFFE2: stone.KeyRightShift,
|
||||
0xFFE3: stone.KeyLeftControl,
|
||||
0xFFE4: stone.KeyRightControl,
|
||||
|
||||
0xFFE7: stone.KeyLeftMeta,
|
||||
0xFFE8: stone.KeyRightMeta,
|
||||
0xFFE9: stone.KeyLeftAlt,
|
||||
0xFFEA: stone.KeyRightAlt,
|
||||
0xFFEB: stone.KeyLeftSuper,
|
||||
0xFFEC: stone.KeyRightSuper,
|
||||
0xFFED: stone.KeyLeftHyper,
|
||||
0xFFEE: stone.KeyRightHyper,
|
||||
|
||||
0xFFFF: stone.KeyDelete,
|
||||
|
||||
@@ -61,14 +55,6 @@ var buttonCodeTable = map[xproto.Keysym] stone.Button {
|
||||
0xFFC7: stone.KeyF10,
|
||||
0xFFC8: stone.KeyF11,
|
||||
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 (
|
||||
@@ -98,7 +84,7 @@ func (backend *Backend) keycodeToButton (
|
||||
symbol3 = symbol1
|
||||
case symbol3 == 0 && symbol4 == 0:
|
||||
symbol3 = symbol1
|
||||
symbol4 = symbol2
|
||||
symbol2 = symbol2
|
||||
case symbol4 == 0:
|
||||
symbol4 = 0
|
||||
}
|
||||
@@ -120,8 +106,7 @@ func (backend *Backend) keycodeToButton (
|
||||
symbol2Rune = unicode.ToUpper(symbol1Rune)
|
||||
cased = true
|
||||
} else {
|
||||
symbol2 = symbol1
|
||||
symbol2Rune = symbol1Rune
|
||||
symbol2 = symbol1
|
||||
}
|
||||
}
|
||||
if symbol4 == 0 {
|
||||
@@ -132,8 +117,7 @@ func (backend *Backend) keycodeToButton (
|
||||
symbol4Rune = unicode.ToUpper(symbol3Rune)
|
||||
cased = true
|
||||
} else {
|
||||
symbol4 = symbol3
|
||||
symbol4Rune = symbol3Rune
|
||||
symbol4 = symbol3
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ type Backend struct {
|
||||
application *stone.Application
|
||||
config *stone.Config
|
||||
callbackManager *stone.CallbackManager
|
||||
imageManager *stone.ImageManager
|
||||
connection *xgbutil.XUtil
|
||||
window *xwindow.Window
|
||||
canvas *xgraphics.Image
|
||||
@@ -88,6 +89,12 @@ func (backend *Backend) SetIcon (icons []image.Image) (err error) {
|
||||
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
|
||||
// size.
|
||||
func (backend *Backend) calculateWindowSize () (x, y int) {
|
||||
|
||||
@@ -8,8 +8,8 @@ type Color uint8
|
||||
const (
|
||||
ColorBackground Color = 0x0
|
||||
ColorForeground Color = 0x1
|
||||
ColorDim Color = 0x2
|
||||
ColorRed Color = 0x3
|
||||
ColorRed Color = 0x2
|
||||
ColorOrange Color = 0x3
|
||||
ColorYellow Color = 0x4
|
||||
ColorGreen Color = 0x5
|
||||
ColorBlue Color = 0x6
|
||||
|
||||
@@ -55,10 +55,10 @@ func (config *Config) load () {
|
||||
color.RGBA { R: 0, G: 0, B: 0, A: 0 },
|
||||
// foreground
|
||||
color.RGBA { R: 0xFF, G: 0xFF, B: 0xFF, A: 0xFF },
|
||||
// dim
|
||||
color.RGBA { R: 0x80, G: 0x80, B: 0x80, A: 0xFF },
|
||||
// red
|
||||
color.RGBA { R: 0xFF, G: 0x00, B: 0x00, A: 0xFF },
|
||||
// orange
|
||||
color.RGBA { R: 0xFF, G: 0x80, B: 0x00, A: 0xFF },
|
||||
// yellow
|
||||
color.RGBA { R: 0xFF, G: 0xFF, B: 0x00, A: 0xFF },
|
||||
// green
|
||||
@@ -154,10 +154,10 @@ func (config *Config) loadFile (path string) {
|
||||
config.colors[ColorBackground] = valueColor
|
||||
case "colorForeground":
|
||||
config.colors[ColorForeground] = valueColor
|
||||
case "colorDim":
|
||||
config.colors[ColorDim] = valueColor
|
||||
case "colorRed":
|
||||
config.colors[ColorRed] = valueColor
|
||||
case "colorOrange":
|
||||
config.colors[ColorOrange] = valueColor
|
||||
case "colorYellow":
|
||||
config.colors[ColorYellow] = valueColor
|
||||
case "colorGreen":
|
||||
|
||||
6
event.go
6
event.go
@@ -2,7 +2,7 @@ package stone
|
||||
|
||||
type CallbackManager struct {
|
||||
onQuit func ()
|
||||
onPress func (button Button, modifiers Modifiers)
|
||||
onPress func (button Button)
|
||||
onRelease func (button Button)
|
||||
onResize func ()
|
||||
onMouseMove func (x, y int)
|
||||
@@ -15,9 +15,9 @@ func (manager *CallbackManager) RunQuit () {
|
||||
manager.onQuit()
|
||||
}
|
||||
|
||||
func (manager *CallbackManager) RunPress (button Button, modifiers Modifiers) {
|
||||
func (manager *CallbackManager) RunPress (button Button) {
|
||||
if manager.onPress == nil { return }
|
||||
manager.onPress(button, modifiers)
|
||||
manager.onPress(button)
|
||||
}
|
||||
|
||||
func (manager *CallbackManager) RunRelease (button Button) {
|
||||
|
||||
@@ -58,7 +58,7 @@ func redraw () {
|
||||
application.SetRune(0, height - 1, '+')
|
||||
|
||||
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 ++ {
|
||||
|
||||
@@ -34,7 +34,7 @@ func main () {
|
||||
if err != nil { panic(err) }
|
||||
}
|
||||
|
||||
func onPress (button stone.Button, modifiers stone.Modifiers) {
|
||||
func onPress (button stone.Button) {
|
||||
if button == stone.MouseButtonLeft {
|
||||
mousePressed = true
|
||||
application.SetRune(0, 0, '+')
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -1,20 +1,17 @@
|
||||
package main
|
||||
|
||||
import "os"
|
||||
import "fmt"
|
||||
import "image"
|
||||
import _ "image/png"
|
||||
import "git.tebibyte.media/sashakoshka/stone"
|
||||
import _ "git.tebibyte.media/sashakoshka/stone/backends/x"
|
||||
|
||||
var application = &stone.Application { }
|
||||
var caretX = 0
|
||||
var caretY = 2
|
||||
var page = 1
|
||||
var caret = 0
|
||||
|
||||
func main () {
|
||||
application.SetTitle("hellorld")
|
||||
application.SetSize(32, 28)
|
||||
application.SetSize(32, 16)
|
||||
|
||||
iconFile16, err := os.Open("assets/scaffold16.png")
|
||||
if err != nil { panic(err) }
|
||||
@@ -28,57 +25,32 @@ func main () {
|
||||
iconFile16.Close()
|
||||
|
||||
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) }
|
||||
|
||||
application.Draw()
|
||||
}
|
||||
|
||||
func redraw () {
|
||||
application.Clear()
|
||||
_, height := application.Size()
|
||||
application.SetDot(0, 0)
|
||||
fmt.Fprint(application, "type some text below:")
|
||||
caretX = 0
|
||||
caretY = 2
|
||||
application.SetDot(0, height - 1)
|
||||
fmt.Fprintf(application, "page %d", page)
|
||||
drawCaret()
|
||||
}
|
||||
for {
|
||||
event := <- channel
|
||||
switch event.(type) {
|
||||
case stone.EventQuit:
|
||||
os.Exit(0)
|
||||
|
||||
func drawCaret () {
|
||||
application.SetRune(caretX, caretY, '+')
|
||||
application.SetColor(caretX, caretY, stone.ColorDim)
|
||||
}
|
||||
case stone.EventPress:
|
||||
button := event.(stone.EventPress).Button
|
||||
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) {
|
||||
width, height := application.Size()
|
||||
|
||||
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 ++
|
||||
case stone.EventResize:
|
||||
application.Draw()
|
||||
}
|
||||
}
|
||||
|
||||
if caretY >= height - 2 {
|
||||
page ++
|
||||
redraw()
|
||||
}
|
||||
|
||||
drawCaret()
|
||||
application.Draw()
|
||||
}
|
||||
|
||||
142
image.go
Normal file
142
image.go
Normal 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
|
||||
}
|
||||
25
input.go
25
input.go
@@ -34,13 +34,9 @@ const (
|
||||
KeyLeftControl Button = 22
|
||||
KeyRightControl Button = 23
|
||||
KeyLeftAlt Button = 24
|
||||
KeyRightAlt Button = 25
|
||||
KeyLeftMeta Button = 26
|
||||
KeyRightMeta Button = 27
|
||||
KeyLeftSuper Button = 28
|
||||
KeyRightSuper Button = 29
|
||||
KeyLeftHyper Button = 30
|
||||
KeyRightHyper Button = 31
|
||||
KeyRightAlt Button = 25
|
||||
KeyLeftSuper Button = 26
|
||||
KeyRightSuper Button = 27
|
||||
|
||||
KeyDelete Button = 127
|
||||
|
||||
@@ -71,8 +67,6 @@ const (
|
||||
KeyF10 Button = 153
|
||||
KeyF11 Button = 154
|
||||
KeyF12 Button = 155
|
||||
|
||||
KeyDead Button = 156
|
||||
)
|
||||
|
||||
// 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))
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user