5 Commits

16 changed files with 276 additions and 494 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 { })
} }
} }
@@ -110,27 +108,17 @@ func (backend *Backend) handleKeyPress (
connection *xgbutil.XUtil, connection *xgbutil.XUtil,
event xevent.KeyPressEvent, event xevent.KeyPressEvent,
) { ) {
keyEvent := *event.KeyPressEvent keyEvent := *event.KeyPressEvent
button, num := backend.keycodeToButton(keyEvent.Detail, keyEvent.State) button := backend.keycodeToButton(keyEvent.Detail, keyEvent.State)
backend.callbackManager.RunPress (button, stone.Modifiers { backend.callbackManager.RunPress(button)
Shift:
(keyEvent.State & xproto.ModMaskShift) > 0 ||
(keyEvent.State & backend.modifierMasks.shiftLock) > 0,
Control: (keyEvent.State & xproto.ModMaskControl) > 0,
Alt: (keyEvent.State & backend.modifierMasks.alt) > 0,
Meta: (keyEvent.State & backend.modifierMasks.meta) > 0,
Super: (keyEvent.State & backend.modifierMasks.super) > 0,
Hyper: (keyEvent.State & backend.modifierMasks.hyper) > 0,
NumberPad: num,
})
} }
func (backend *Backend) handleKeyRelease ( func (backend *Backend) handleKeyRelease (
connection *xgbutil.XUtil, connection *xgbutil.XUtil,
event xevent.KeyReleaseEvent, event xevent.KeyReleaseEvent,
) { ) {
keyEvent := *event.KeyReleaseEvent keyEvent := *event.KeyReleaseEvent
button, _ := backend.keycodeToButton(keyEvent.Detail, keyEvent.State) button := backend.keycodeToButton(keyEvent.Detail, keyEvent.State)
backend.callbackManager.RunRelease(button) backend.callbackManager.RunRelease(button)
} }

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
@@ -74,18 +76,7 @@ func factory (
if err != nil { return } if err != nil { return }
backend.window, err = xwindow.Generate(backend.connection) backend.window, err = xwindow.Generate(backend.connection)
if err != nil { return } if err != nil { return }
// get keyboard mapping information
keybind.Initialize(backend.connection) keybind.Initialize(backend.connection)
backend.modifierMasks.capsLock = backend.keysymToMask(0xFFE5)
backend.modifierMasks.shiftLock = backend.keysymToMask(0xFFE6)
backend.modifierMasks.numLock = backend.keysymToMask(0xFF7F)
backend.modifierMasks.modeSwitch = backend.keysymToMask(0xFF7E)
backend.modifierMasks.hyper = backend.keysymToMask(0xffed)
backend.modifierMasks.super = backend.keysymToMask(0xffeb)
backend.modifierMasks.meta = backend.keysymToMask(0xffe7)
backend.modifierMasks.alt = backend.keysymToMask(0xffe9)
// create the window // create the window
backend.window.Create ( backend.window.Create (
@@ -144,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
@@ -169,38 +160,6 @@ func findAndLoadFont (name string, size float64) (face font.Face) {
return return
} }
func (backend *Backend) keysymToKeycode (
symbol xproto.Keysym,
) (
code xproto.Keycode,
) {
mapping := keybind.KeyMapGet(backend.connection)
for index, testSymbol := range mapping.Keysyms {
if testSymbol == symbol {
code = xproto.Keycode (
index /
int(mapping.KeysymsPerKeycode) +
int(backend.connection.Setup().MinKeycode))
break
}
}
return
}
func (backend *Backend) keysymToMask (
symbol xproto.Keysym,
) (
mask uint16,
) {
mask = keybind.ModGet (
backend.connection,
backend.keysymToKeycode(symbol))
return
}
// init registers this backend when the program starts. // init registers this backend when the program starts.
func init () { func init () {
stone.RegisterBackend(factory) stone.RegisterBackend(factory)

View File

@@ -1,8 +1,5 @@
package x package x
// TODO: rename this file? lol
// 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"
@@ -39,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,
@@ -63,67 +55,21 @@ 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,
}
var keypadCodeTable = map[xproto.Keysym] stone.Button {
0xff80: stone.Button(' '),
0xff89: stone.KeyTab,
0xff8d: stone.KeyEnter,
0xff91: stone.KeyF1,
0xff92: stone.KeyF2,
0xff93: stone.KeyF3,
0xff94: stone.KeyF4,
0xff95: stone.KeyHome,
0xff96: stone.KeyLeft,
0xff97: stone.KeyUp,
0xff98: stone.KeyRight,
0xff99: stone.KeyDown,
0xff9a: stone.KeyPageUp,
0xff9b: stone.KeyPageDown,
0xff9c: stone.KeyEnd,
0xff9d: stone.KeyHome,
0xff9e: stone.KeyInsert,
0xff9f: stone.KeyDelete,
0xffbd: stone.Button('='),
0xffaa: stone.Button('*'),
0xffab: stone.Button('+'),
0xffac: stone.Button(','),
0xffad: stone.Button('-'),
0xffae: stone.Button('.'),
0xffaf: stone.Button('/'),
0xffb0: stone.Button('0'),
0xffb1: stone.Button('1'),
0xffb2: stone.Button('2'),
0xffb3: stone.Button('3'),
0xffb4: stone.Button('4'),
0xffb5: stone.Button('5'),
0xffb6: stone.Button('6'),
0xffb7: stone.Button('7'),
0xffb8: stone.Button('8'),
0xffb9: stone.Button('9'),
} }
func (backend *Backend) keycodeToButton ( func (backend *Backend) keycodeToButton (
keycode xproto.Keycode, keycode xproto.Keycode,
state uint16, state uint16,
) ( ) (
button stone.Button, button stone.Button,
numberPad bool,
) { ) {
shift := // FIXME: also set shift to true if the lock modifier is on and the lock
state & xproto.ModMaskShift > 0 || // modifier is interpreted as shiftLock
state & backend.modifierMasks.shiftLock > 0 shift := state & xproto.ModMaskShift > 0
capsLock := state & backend.modifierMasks.capsLock > 0
numLock := state & backend.modifierMasks.numLock > 0 // FIXME: only set this to true if the lock modifier is on and the lock
// modifier is interpreted as capsLock
capsLock := state & xproto.ModMaskLock > 0
symbol1 := keybind.KeysymGet(backend.connection, keycode, 0) symbol1 := keybind.KeysymGet(backend.connection, keycode, 0)
symbol2 := keybind.KeysymGet(backend.connection, keycode, 1) symbol2 := keybind.KeysymGet(backend.connection, keycode, 1)
@@ -132,25 +78,13 @@ func (backend *Backend) keycodeToButton (
cased := false cased := false
// PARAGRAPH 3 // third paragraph
//
// A list of KeySyms is associated with each KeyCode. The list is
// intended to convey the set of symbols on the corresponding key. If
// the list (ignoring trailing NoSymbol entries) is a single KeySym
// ``K'', then the list is treated as if it were the list ``K NoSymbol
// K NoSymbol''. If the list (ignoring trailing NoSymbol entries) is a
// pair of KeySyms ``K1 K2'', then the list is treated as if it were the
// list ``K1 K2 K1 K2''. If the list (ignoring trailing NoSymbol
// entries) is a triple of KeySyms ``K1 K2 K3'', then the list is
// treated as if it were the list ``K1 K2 K3 NoSymbol''. When an
// explicit ``void'' element is desired in the list, the value
// VoidSymbol can be used.
switch { switch {
case symbol2 == 0 && symbol3 == 0 && symbol4 == 0: case symbol2 == 0 && symbol3 == 0 && symbol4 == 0:
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
} }
@@ -163,18 +97,7 @@ func (backend *Backend) keycodeToButton (
// FIXME: we ignore mode switch stuff // FIXME: we ignore mode switch stuff
_ = symbol4Rune _ = symbol4Rune
// PARAGRAPH 4 // fourth paragraph
//
// The first four elements of the list are split into two groups of
// KeySyms. Group 1 contains the first and second KeySyms; Group 2
// contains the third and fourth KeySyms. Within each group, if the
// second element of the group is NoSymbol , then the group should be
// treated as if the second element were the same as the first element,
// except when the first element is an alphabetic KeySym ``K'' for which
// both lowercase and uppercase forms are defined. In that case, the
// group should be treated as if the first element were the lowercase
// form of ``K'' and the second element were the uppercase form of
// ``K.''
if symbol2 == 0 { if symbol2 == 0 {
upper := unicode.IsUpper(symbol1Rune) upper := unicode.IsUpper(symbol1Rune)
lower := unicode.IsLower(symbol1Rune) lower := unicode.IsLower(symbol1Rune)
@@ -183,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 {
@@ -195,83 +117,49 @@ func (backend *Backend) keycodeToButton (
symbol4Rune = unicode.ToUpper(symbol3Rune) symbol4Rune = unicode.ToUpper(symbol3Rune)
cased = true cased = true
} else { } else {
symbol4 = symbol3 symbol4 = symbol3
symbol4Rune = symbol3Rune
} }
} }
var selectedKeysym xproto.Keysym var selectedKeysym xproto.Keysym
var selectedRune rune var selectedRune rune
_, symbol2IsNumPad := keypadCodeTable[symbol2] // big ol list in the middle
// "PARAGRAPH" 5
//
// Within a group, the choice of KeySym is determined by applying the
// first rule that is satisfied from the following list:
switch { switch {
case numLock && symbol2IsNumPad: // FIXME: take into account numlock
// The numlock modifier is on and the second KeySym is a keypad
// KeySym. In this case, if the Shift modifier is on, or if the
// Lock modifier is on and is interpreted as ShiftLock, then the
// first KeySym is used, otherwise the second KeySym is used.
if shift {
selectedKeysym = symbol1
selectedRune = symbol1Rune
} else {
selectedKeysym = symbol2
selectedRune = symbol2Rune
}
case !shift && !capsLock: case !shift && !capsLock:
// The Shift and Lock modifiers are both off. In this case, the
// first KeySym is used.
selectedKeysym = symbol1 selectedKeysym = symbol1
selectedRune = symbol1Rune selectedRune = symbol1Rune
case !shift && capsLock: case !shift && capsLock:
// The Shift modifier is off, and the Lock modifier is on and is
// interpreted as CapsLock. In this case, the first KeySym is
// used, but if that KeySym is lowercase alphabetic, then the
// corresponding uppercase KeySym is used instead.
if cased && unicode.IsLower(symbol1Rune) { if cased && unicode.IsLower(symbol1Rune) {
selectedRune = symbol2Rune selectedRune = symbol2Rune
} else { } else {
selectedKeysym = symbol1 selectedKeysym = symbol1
selectedRune = symbol1Rune selectedRune = symbol1Rune
} }
case shift && capsLock: case shift && capsLock:
// The Shift modifier is on, and the Lock modifier is on and is
// interpreted as CapsLock. In this case, the second KeySym is
// used, but if that KeySym is lowercase alphabetic, then the
// corresponding uppercase KeySym is used instead.
if cased && unicode.IsLower(symbol2Rune) { if cased && unicode.IsLower(symbol2Rune) {
selectedRune = unicode.ToUpper(symbol2Rune) selectedRune = unicode.ToUpper(symbol2Rune)
} else { } else {
selectedKeysym = symbol2 selectedKeysym = symbol2
selectedRune = symbol2Rune selectedRune = symbol2Rune
} }
case shift: case shift:
// The Shift modifier is on, or the Lock modifier is on and is
// interpreted as ShiftLock, or both. In this case, the second
// KeySym is used.
selectedKeysym = symbol2 selectedKeysym = symbol2
selectedRune = symbol2Rune selectedRune = symbol2Rune
} }
// look up in control code table // look up in table
var isControl bool var isControl bool
button, isControl = buttonCodeTable[selectedKeysym] button, isControl = buttonCodeTable[selectedKeysym]
if isControl { return }
// look up in keypad table // if it wasn't found,
button, numberPad = keypadCodeTable[selectedKeysym] if !isControl {
if numberPad { return } button = stone.Button(selectedRune)
}
// otherwise, use the rune
button = stone.Button(selectedRune)
return return
} }

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
@@ -43,18 +44,6 @@ type Backend struct {
descent int descent int
} }
modifierMasks struct {
capsLock uint16
shiftLock uint16
numLock uint16
modeSwitch uint16
alt uint16
meta uint16
super uint16
hyper uint16
}
windowBoundsClean bool windowBoundsClean bool
} }
@@ -100,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,51 +0,0 @@
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 { }
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) {
fmt.Printf (
"=>>\t0x%X\tsh: %t\tctrl: %t\talt: %t\tm: %t\ts: %t \th: %t\tnumpad: %t\n",
button,
modifiers.Shift,
modifiers.Control,
modifiers.Alt,
modifiers.Meta,
modifiers.Super,
modifiers.Hyper,
modifiers.NumberPad)
}
func onRelease (button stone.Button) {
fmt.Printf("<--\t0x%X\n", 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,30 +34,26 @@ 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
MouseButton1 Button = 128 MouseButton1 Button = 128
MouseButton2 Button = 129 MouseButton2 Button = 129
MouseButton3 Button = 130 MouseButton3 Button = 130
MouseButton4 Button = 131 MouseButton4 Button = 131
MouseButton5 Button = 132 MouseButton5 Button = 132
MouseButton6 Button = 133 MouseButton6 Button = 133
MouseButton7 Button = 134 MouseButton7 Button = 134
MouseButton8 Button = 135 MouseButton8 Button = 135
MouseButton9 Button = 136 MouseButton9 Button = 136
MouseButtonLeft Button = MouseButton1 MouseButtonLeft Button = MouseButton1
MouseButtonMiddle Button = MouseButton2 MouseButtonMiddle Button = MouseButton2
MouseButtonRight Button = MouseButton3 MouseButtonRight Button = MouseButton3
MouseButtonBack Button = MouseButton8 MouseButtonBack Button = MouseButton8
MouseButtonForward Button = MouseButton9 MouseButtonForward Button = MouseButton9
KeyF1 Button = 144 KeyF1 Button = 144
KeyF2 Button = 145 KeyF2 Button = 145
@@ -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,22 +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
// NumberPad does not represent a key, but it behaves like one. If it is
// set to true, the button was pressed on the number pad. It is treated
// as a modifier key because if you don't care whether a key was pressed
// on the number pad or not, you can just ignore this value.
NumberPad bool
}