x-backend #2
@ -2,11 +2,10 @@
|
||||
|
||||
Stone is a backend-agnostic application framework designed to:
|
||||
|
||||
- Combine the simplicity of developing TUI programs with the input capabilities
|
||||
of GUI programs
|
||||
- Combine the simplicity and ease of development inherent to TUI programs with the extended capabilities of GUI programs
|
||||
- Be adaptable to run virtually anywhere
|
||||
|
||||
Currently, the only supported backend is
|
||||
[pixel](https://github.com/faiface/pixel), but it is very easy to write and link
|
||||
[X](https://github.com/jezek/xgbutil), but it is very easy to write and link
|
||||
your own. Stone will automatically run through the list of registered backends
|
||||
and instantiate the first one that doesn't throw an error.
|
||||
|
@ -1,5 +1,6 @@
|
||||
package stone
|
||||
|
||||
import "image"
|
||||
import "image/color"
|
||||
|
||||
// Application represents an application.
|
||||
@ -7,17 +8,11 @@ type Application struct {
|
||||
DamageBuffer
|
||||
|
||||
title string
|
||||
icons []image.Image
|
||||
backend Backend
|
||||
config Config
|
||||
}
|
||||
|
||||
// SetTitle sets the application's title. If in a window, it will appear as the
|
||||
// window's name.
|
||||
func (application *Application) SetTitle (title string) {
|
||||
application.title = title
|
||||
application.backend.SetTitle(title)
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (application *Application) Run () (
|
||||
@ -37,8 +32,10 @@ func (application *Application) Run () (
|
||||
color.RGBA { R: 0x2E, G: 0x34, B: 0x40, A: 0xFF },
|
||||
color.RGBA { R: 0xA8, G: 0x55, B: 0x5D, A: 0xFF },
|
||||
}
|
||||
application.config.fontName = ""
|
||||
application.config.fontSize = 11
|
||||
|
||||
application.config.padding = 4
|
||||
application.config.padding = 2
|
||||
|
||||
application.backend, err = instantiateBackend(application)
|
||||
if err != nil { return }
|
||||
@ -49,6 +46,41 @@ func (application *Application) Run () (
|
||||
return
|
||||
}
|
||||
|
||||
// Draw "commits" changes made in the buffer to the display.
|
||||
func (application *Application) Draw () {
|
||||
application.backend.Draw()
|
||||
}
|
||||
|
||||
// SetTitle sets the application's title. If in a window, it will appear as the
|
||||
// window's name.
|
||||
func (application *Application) SetTitle (title string) (err error) {
|
||||
application.title = title
|
||||
if application.backend != nil {
|
||||
err = application.backend.SetTitle(title)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (application *Application) Title () (title string) {
|
||||
title = application.title
|
||||
return
|
||||
}
|
||||
|
||||
func (application *Application) SetIcon (sizes []image.Image) (err error) {
|
||||
application.icons = sizes
|
||||
if application.backend != nil {
|
||||
err = application.backend.SetIcon(sizes)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (application *Application) Icon () (sizes []image.Image) {
|
||||
sizes = application.icons
|
||||
return
|
||||
}
|
||||
|
||||
// Config returns a pointer to the application's configuration.
|
||||
func (application *Application) Config () (config *Config) {
|
||||
config = &application.config
|
||||
|
BIN
assets/scaffold16.png
Normal file
BIN
assets/scaffold16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 589 B |
BIN
assets/scaffold32.png
Normal file
BIN
assets/scaffold32.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
@ -5,8 +5,9 @@ import "errors"
|
||||
|
||||
type Backend interface {
|
||||
Run (channel chan(Event))
|
||||
SetTitle (title string)
|
||||
SetIcon (icons []image.Image)
|
||||
SetTitle (title string) (err error)
|
||||
SetIcon (icons []image.Image) (err error)
|
||||
Draw ()
|
||||
}
|
||||
|
||||
type BackendFactory func (application *Application) (backend Backend, err error)
|
||||
|
@ -1,321 +0,0 @@
|
||||
package pixel
|
||||
|
||||
import "time"
|
||||
import "golang.org/x/image/font"
|
||||
import "github.com/faiface/pixel"
|
||||
import "github.com/faiface/pixel/text"
|
||||
import "github.com/faiface/pixel/imdraw"
|
||||
import "github.com/faiface/pixel/pixelgl"
|
||||
import "golang.org/x/image/font/basicfont"
|
||||
import "git.tebibyte.media/sashakoshka/stone"
|
||||
|
||||
// Backend represents an instance of the pixel backend
|
||||
type Backend struct {
|
||||
window *pixelgl.Window
|
||||
boundsDirty bool
|
||||
windowBounds pixel.Vec
|
||||
fontFace font.Face
|
||||
application *stone.Application
|
||||
config *stone.Config
|
||||
backgroundStamper *imdraw.IMDraw
|
||||
fontAtlas *text.Atlas
|
||||
textDrawer *text.Text
|
||||
showBounds bool
|
||||
showCellBounds bool
|
||||
|
||||
metrics struct {
|
||||
cellWidth int
|
||||
cellHeight int
|
||||
padding int
|
||||
paddingX int
|
||||
paddingY int
|
||||
descent int
|
||||
}
|
||||
}
|
||||
|
||||
// Run satisfies the Run method of the Backend interface. Due to the nature of
|
||||
// pixel, this will forcibly bind to the main thread.
|
||||
func (backend *Backend) Run (callback func (application *stone.Application)) {
|
||||
// backend.showBounds = true
|
||||
// backend.showCellBounds = true
|
||||
|
||||
if backend.fontFace == nil {
|
||||
backend.fontFace = basicfont.Face7x13
|
||||
}
|
||||
|
||||
backend.backgroundStamper = imdraw.New(nil)
|
||||
backend.fontAtlas = text.NewAtlas(backend.fontFace, text.ASCII)
|
||||
backend.textDrawer = text.New(pixel.V(0, 0), backend.fontAtlas)
|
||||
|
||||
backend.metrics.descent = int(backend.fontAtlas.Descent())
|
||||
backend.metrics.cellHeight = int(backend.fontAtlas.LineHeight())
|
||||
// FIXME?: this might not be the best way to get the cell width
|
||||
faceAdvance, ok := backend.fontFace.GlyphAdvance('M')
|
||||
if ok {
|
||||
backend.metrics.cellWidth = faceAdvance.Round()
|
||||
} else {
|
||||
backend.metrics.cellWidth = backend.metrics.cellHeight / 2
|
||||
}
|
||||
|
||||
pixelgl.Run (func () {
|
||||
// construct the window, and all that
|
||||
var err error
|
||||
backend.window, err = pixelgl.NewWindow (pixelgl.WindowConfig {
|
||||
Resizable: true,
|
||||
Undecorated: true,
|
||||
VSync: true,
|
||||
NoIconify: true,
|
||||
Title: backend.application.Title(),
|
||||
Bounds: backend.calculateWindowSize(),
|
||||
})
|
||||
backend.Poll()
|
||||
|
||||
// TODO: this should return the error and not panic
|
||||
if err != nil { panic(err.Error()) }
|
||||
callback(backend.application)
|
||||
})
|
||||
}
|
||||
|
||||
// Await fulfills the Await method of the Backend interface.
|
||||
func (backend *Backend) Await (timeout time.Duration) (keepRunning bool) {
|
||||
if backend.window == nil {
|
||||
panic("call to Backend.Await before window exists")
|
||||
}
|
||||
|
||||
backend.draw()
|
||||
backend.window.UpdateInputWait(timeout)
|
||||
backend.processEvents()
|
||||
keepRunning = !backend.window.Closed()
|
||||
return
|
||||
}
|
||||
|
||||
// Poll fulfills the Poll method of the Backend interface.
|
||||
func (backend *Backend) Poll () (keepRunning bool) {
|
||||
if backend.window == nil {
|
||||
panic("call to Backend.Poll before window exists")
|
||||
}
|
||||
|
||||
backend.draw()
|
||||
backend.window.UpdateInput()
|
||||
backend.processEvents()
|
||||
keepRunning = !backend.window.Closed()
|
||||
return
|
||||
}
|
||||
|
||||
// SetTitle fulfills the SetTitle method of the Backend interface.
|
||||
func (backend *Backend) SetTitle (title string) {
|
||||
if backend.window != nil {
|
||||
backend.window.SetTitle(title)
|
||||
}
|
||||
}
|
||||
|
||||
// JustPressed fulfills the JustPressed method of the Backend interface.
|
||||
func (backend *Backend) JustPressed (button stone.Button) (pressed bool) {
|
||||
pressed = backend.window.JustPressed(pixelgl.Button(button))
|
||||
return
|
||||
}
|
||||
|
||||
// JustReleased fulfills the JustReleased method of the Backend interface.
|
||||
func (backend *Backend) JustReleased (button stone.Button) (released bool) {
|
||||
released = backend.window.JustReleased(pixelgl.Button(button))
|
||||
return
|
||||
}
|
||||
|
||||
// Pressed fulfills the Pressed method of the Backend interface.
|
||||
func (backend *Backend) Pressed (button stone.Button) (pressed bool) {
|
||||
pressed = backend.window.Pressed(pixelgl.Button(button))
|
||||
return
|
||||
}
|
||||
|
||||
// Repeated fulfills the Repeated method of the Backend interface.
|
||||
func (backend *Backend) Repeated (button stone.Button) (repeated bool) {
|
||||
repeated = backend.window.Repeated(pixelgl.Button(button))
|
||||
return
|
||||
}
|
||||
|
||||
// Typed fulfills the Typed method of the Backend interface.
|
||||
func (backend *Backend) Typed () (text string) {
|
||||
text = backend.window.Typed()
|
||||
return
|
||||
}
|
||||
|
||||
// Resized fulfills the Resized method of the Backend interface.
|
||||
func (backend *Backend) Resized () (resized bool) {
|
||||
resized = backend.boundsDirty
|
||||
return
|
||||
}
|
||||
|
||||
// MousePosition fulfills the MousePosition method of the Backend interface.
|
||||
func (backend *Backend) MousePosition () (x, y int) {
|
||||
vector := backend.window.MousePosition()
|
||||
x = int (
|
||||
(vector.X - float64(backend.metrics.paddingX)) /
|
||||
float64(backend.metrics.cellWidth))
|
||||
y = int (
|
||||
(backend.windowBounds.Y -
|
||||
vector.Y -
|
||||
float64(backend.metrics.paddingY)) /
|
||||
float64(backend.metrics.cellHeight))
|
||||
return
|
||||
}
|
||||
|
||||
// draw renders updates to the screen.
|
||||
func (backend *Backend) draw () {
|
||||
// didDrawing := false
|
||||
width, height := backend.application.Size()
|
||||
|
||||
if backend.boundsDirty {
|
||||
backend.window.Clear (
|
||||
backend.config.Color(stone.ColorApplication))
|
||||
backend.boundsDirty = false
|
||||
// didDrawing = true
|
||||
} else {
|
||||
// clear out dirty cells before drawing them. this is in an else
|
||||
// block because if the bounds were dirty, we have already
|
||||
// cleared the entire screen.
|
||||
|
||||
backend.backgroundStamper.Clear()
|
||||
backend.backgroundStamper.Color =
|
||||
backend.config.Color(stone.ColorApplication)
|
||||
for x := 0; x < width; x ++ {
|
||||
for y := 0; y < height; y ++ {
|
||||
clean := backend.application.Clean(x, y)
|
||||
if clean { continue }
|
||||
|
||||
backend.backgroundStamper.Push (
|
||||
backend.vectorAtPosition(x, y),
|
||||
backend.vectorAtPosition(x + 1, y + 1))
|
||||
backend.backgroundStamper.Rectangle(0)
|
||||
// didDrawing = true
|
||||
|
||||
if backend.showCellBounds {
|
||||
backend.backgroundStamper.Color =
|
||||
backend.config.Color(stone.ColorForeground)
|
||||
backend.backgroundStamper.Push (
|
||||
backend.vectorAtPosition(x, y),
|
||||
backend.vectorAtPosition(x + 1, y + 1))
|
||||
backend.backgroundStamper.Rectangle(1)
|
||||
backend.backgroundStamper.Color =
|
||||
backend.config.Color(stone.ColorApplication)
|
||||
}
|
||||
}
|
||||
}
|
||||
backend.backgroundStamper.Draw(backend.window)
|
||||
}
|
||||
|
||||
backend.textDrawer.Clear()
|
||||
backend.textDrawer.Color =
|
||||
backend.config.Color(stone.ColorForeground)
|
||||
for x := 0; x < width; x ++ {
|
||||
for y := 0; y < height; y ++ {
|
||||
clean := backend.application.Clean(x, y)
|
||||
if clean { continue }
|
||||
backend.application.MarkClean(x, y)
|
||||
|
||||
cell := backend.application.Cell(x, y)
|
||||
content := cell.Rune()
|
||||
if content < 32 { continue }
|
||||
|
||||
// draw cell
|
||||
backend.textDrawer.Dot = pixel.V (
|
||||
float64 (
|
||||
x * backend.metrics.cellWidth +
|
||||
backend.metrics.paddingX),
|
||||
backend.windowBounds.Y - float64 (
|
||||
(y + 1) * backend.metrics.cellHeight +
|
||||
backend.metrics.paddingY -
|
||||
backend.metrics.descent))
|
||||
|
||||
backend.textDrawer.WriteRune(content)
|
||||
backend.textDrawer.Draw(backend.window, pixel.IM)
|
||||
}
|
||||
}
|
||||
|
||||
// draw a rectangle around the buffer if we are showing bounds
|
||||
if backend.showBounds {
|
||||
backend.backgroundStamper.Clear()
|
||||
backend.backgroundStamper.Color =
|
||||
backend.config.Color(stone.ColorBackground)
|
||||
backend.backgroundStamper.Push (
|
||||
backend.vectorAtPosition(0, 0),
|
||||
backend.vectorAtPosition(width, height))
|
||||
backend.backgroundStamper.Rectangle(1)
|
||||
backend.backgroundStamper.Draw(backend.window)
|
||||
}
|
||||
|
||||
backend.window.SwapBuffers()
|
||||
}
|
||||
|
||||
// processEvents reacts to events recieved from pixel, resizing and
|
||||
// recalculating things as need be.
|
||||
func (backend *Backend) processEvents () {
|
||||
newBounds := backend.window.Bounds().Max
|
||||
backend.boundsDirty = backend.windowBounds != newBounds
|
||||
backend.windowBounds = newBounds
|
||||
|
||||
if backend.boundsDirty {
|
||||
// calculate padding
|
||||
backend.metrics.padding =
|
||||
backend.config.Padding() *
|
||||
backend.metrics.cellWidth
|
||||
deadArea := float64(backend.metrics.padding * 2)
|
||||
|
||||
// calculate new width and height for buffer
|
||||
width := int (
|
||||
(backend.windowBounds.X - deadArea) /
|
||||
float64(backend.metrics.cellWidth))
|
||||
height := int (
|
||||
(backend.windowBounds.Y - deadArea) /
|
||||
float64(backend.metrics.cellHeight))
|
||||
backend.application.SetSize(width, height)
|
||||
|
||||
// position buffer in center of screen
|
||||
frameWidth := width * backend.metrics.cellWidth
|
||||
frameHeight := height * backend.metrics.cellHeight
|
||||
backend.metrics.paddingX = (int(backend.windowBounds.X) - frameWidth) / 2
|
||||
backend.metrics.paddingY = (int(backend.windowBounds.Y) - frameHeight) / 2
|
||||
}
|
||||
}
|
||||
|
||||
// vectorAtPosition generates a pixel vector at the top left of the specified
|
||||
// cell.
|
||||
func (backend *Backend) vectorAtPosition (x, y int) (vector pixel.Vec) {
|
||||
vector = pixel.V (
|
||||
float64 (
|
||||
x * backend.metrics.cellWidth +
|
||||
backend.metrics.paddingX),
|
||||
backend.windowBounds.Y - float64 (
|
||||
y * backend.metrics.cellHeight +
|
||||
backend.metrics.paddingY))
|
||||
return
|
||||
}
|
||||
|
||||
// calculateWindowSize calculates window bounds based on the internal buffer
|
||||
// size.
|
||||
func (backend *Backend) calculateWindowSize () (bounds pixel.Rect) {
|
||||
width, height := backend.application.Size()
|
||||
bounds = pixel.R (
|
||||
0, 0,
|
||||
float64 (
|
||||
width * backend.metrics.cellWidth +
|
||||
backend.metrics.padding * 2),
|
||||
float64 (
|
||||
height * backend.metrics.cellHeight +
|
||||
backend.metrics.padding * 2))
|
||||
return
|
||||
}
|
||||
|
||||
// factory instantiates a pixel backend.
|
||||
func factory (application *stone.Application) (output stone.Backend, err error) {
|
||||
backend := &Backend {
|
||||
application: application,
|
||||
config: application.Config(),
|
||||
}
|
||||
output = backend
|
||||
return
|
||||
}
|
||||
|
||||
// init registers this backend when the program starts.
|
||||
func init () {
|
||||
stone.RegisterBackend(factory)
|
||||
}
|
142
backends/x/draw.go
Normal file
142
backends/x/draw.go
Normal file
@ -0,0 +1,142 @@
|
||||
package x
|
||||
|
||||
import "image"
|
||||
import "image/draw"
|
||||
import "golang.org/x/image/math/fixed"
|
||||
|
||||
import "git.tebibyte.media/sashakoshka/stone"
|
||||
|
||||
func (backend *Backend) Draw () {
|
||||
backend.drawLock.Lock()
|
||||
defer backend.drawLock.Unlock()
|
||||
|
||||
boundsChanged :=
|
||||
backend.memory.windowWidth != backend.metrics.windowWidth ||
|
||||
backend.memory.windowHeight != backend.metrics.windowHeight
|
||||
backend.memory.windowWidth = backend.metrics.windowWidth
|
||||
backend.memory.windowHeight = backend.metrics.windowHeight
|
||||
|
||||
if boundsChanged {
|
||||
backend.reallocateCanvas()
|
||||
backend.drawCells(true)
|
||||
backend.canvas.XDraw()
|
||||
backend.canvas.XPaint(backend.window.Id)
|
||||
} else {
|
||||
backend.updateWindowAreas(backend.drawCells(false)...)
|
||||
}
|
||||
}
|
||||
|
||||
func (backend *Backend) updateWindowAreas (areas ...image.Rectangle) {
|
||||
backend.canvas.XPaintRects(backend.window.Id, areas...)
|
||||
}
|
||||
|
||||
func (backend *Backend) drawRune (x, y int, character rune) {
|
||||
// TODO: cache these draws as non-transparent buffers with the
|
||||
// application background color as the background. that way, we won't
|
||||
// need to redraw the characters *or* composite them.
|
||||
|
||||
fillRectangle (
|
||||
&image.Uniform {
|
||||
C: backend.config.Color(stone.ColorApplication),
|
||||
},
|
||||
backend.canvas,
|
||||
backend.boundsOfCell(x, y))
|
||||
|
||||
if character < 32 { return }
|
||||
|
||||
origin := backend.originOfCell(x, y + 1)
|
||||
destinationRectangle, mask, maskPoint, _, _ := backend.font.face.Glyph (
|
||||
fixed.Point26_6 {
|
||||
X: fixed.I(origin.X),
|
||||
Y: fixed.I(origin.Y - backend.metrics.descent),
|
||||
},
|
||||
character)
|
||||
|
||||
if backend.drawCellBounds {
|
||||
strokeRectangle (
|
||||
&image.Uniform {
|
||||
C: backend.config.Color(stone.ColorForeground),
|
||||
},
|
||||
backend.canvas,
|
||||
backend.boundsOfCell(x, y))
|
||||
}
|
||||
|
||||
draw.DrawMask (
|
||||
backend.canvas,
|
||||
destinationRectangle,
|
||||
&image.Uniform {
|
||||
C: backend.config.Color(stone.ColorForeground),
|
||||
},
|
||||
image.Point { },
|
||||
mask,
|
||||
maskPoint,
|
||||
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)
|
||||
}}
|
||||
|
||||
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 (
|
||||
source image.Image,
|
||||
destination draw.Image,
|
||||
bounds image.Rectangle,
|
||||
) {
|
||||
for y := bounds.Min.Y; y < bounds.Max.Y; y ++ {
|
||||
for x := bounds.Min.X; x < bounds.Max.X; x ++ {
|
||||
destination.Set(x, y, source.At(x, y))
|
||||
}}
|
||||
}
|
||||
|
||||
func strokeRectangle (
|
||||
source image.Image,
|
||||
destination draw.Image,
|
||||
bounds image.Rectangle,
|
||||
) {
|
||||
x := 0
|
||||
y := bounds.Min.Y
|
||||
for x = bounds.Min.X; x < bounds.Max.X; x ++ {
|
||||
destination.Set(x, y, source.At(x, y))
|
||||
}
|
||||
|
||||
y = bounds.Max.Y - 1
|
||||
for x = bounds.Min.X; x < bounds.Max.X; x ++ {
|
||||
destination.Set(x, y, source.At(x, y))
|
||||
}
|
||||
|
||||
x = bounds.Min.X
|
||||
for y = bounds.Min.Y; y < bounds.Max.Y; y ++ {
|
||||
destination.Set(x, y, source.At(x, y))
|
||||
}
|
||||
|
||||
x = bounds.Max.X - 1
|
||||
for y = bounds.Min.Y; y < bounds.Max.Y; y ++ {
|
||||
destination.Set(x, y, source.At(x, y))
|
||||
}
|
||||
}
|
131
backends/x/event.go
Normal file
131
backends/x/event.go
Normal file
@ -0,0 +1,131 @@
|
||||
package x
|
||||
|
||||
import "image"
|
||||
|
||||
import "github.com/jezek/xgbutil"
|
||||
import "github.com/jezek/xgb/xproto"
|
||||
import "github.com/jezek/xgbutil/xevent"
|
||||
|
||||
import "git.tebibyte.media/sashakoshka/stone"
|
||||
|
||||
func (backend *Backend) Run (channel chan(stone.Event)) {
|
||||
backend.channel = channel
|
||||
xevent.Main(backend.connection)
|
||||
backend.shutDown()
|
||||
}
|
||||
|
||||
|
||||
func (backend *Backend) handleConfigureNotify (
|
||||
connection *xgbutil.XUtil,
|
||||
event xevent.ConfigureNotifyEvent,
|
||||
) {
|
||||
configureEvent := *event.ConfigureNotifyEvent
|
||||
|
||||
newWidth := int(configureEvent.Width)
|
||||
newHeight := int(configureEvent.Height)
|
||||
sizeChanged :=
|
||||
backend.metrics.windowWidth != newWidth ||
|
||||
backend.metrics.windowHeight != newHeight
|
||||
backend.metrics.windowWidth = newWidth
|
||||
backend.metrics.windowHeight = newHeight
|
||||
|
||||
if sizeChanged {
|
||||
configureEvent =
|
||||
backend.compressConfigureNotify(configureEvent)
|
||||
|
||||
// resize buffer
|
||||
width, height := backend.calculateBufferSize()
|
||||
backend.application.SetSize(width, height)
|
||||
|
||||
// position buffer in the center of the screen
|
||||
frameWidth := width * backend.metrics.cellWidth
|
||||
frameHeight := height * backend.metrics.cellHeight
|
||||
backend.metrics.paddingX =
|
||||
(backend.metrics.windowWidth - frameWidth) / 2
|
||||
backend.metrics.paddingY =
|
||||
(backend.metrics.windowHeight - frameHeight) / 2
|
||||
|
||||
backend.channel <- stone.EventResize { }
|
||||
}
|
||||
}
|
||||
|
||||
func (backend *Backend) handleButtonPress (
|
||||
connection *xgbutil.XUtil,
|
||||
event xevent.ButtonPressEvent,
|
||||
) {
|
||||
buttonEvent := *event.ButtonPressEvent
|
||||
backend.channel <- stone.EventPress {
|
||||
Button: stone.Button(buttonEvent.Detail + 127),
|
||||
}
|
||||
}
|
||||
|
||||
func (backend *Backend) handleButtonRelease (
|
||||
connection *xgbutil.XUtil,
|
||||
event xevent.ButtonReleaseEvent,
|
||||
) {
|
||||
buttonEvent := *event.ButtonReleaseEvent
|
||||
backend.channel <- stone.EventRelease {
|
||||
Button: stone.Button(buttonEvent.Detail + 127),
|
||||
}
|
||||
}
|
||||
|
||||
func (backend *Backend) handleKeyPress (
|
||||
connection *xgbutil.XUtil,
|
||||
event xevent.KeyPressEvent,
|
||||
) {
|
||||
keyEvent := *event.KeyPressEvent
|
||||
button := backend.keycodeToButton(keyEvent.Detail, keyEvent.State)
|
||||
backend.channel <- stone.EventPress { Button: button }
|
||||
}
|
||||
|
||||
func (backend *Backend) handleKeyRelease (
|
||||
connection *xgbutil.XUtil,
|
||||
event xevent.KeyReleaseEvent,
|
||||
) {
|
||||
keyEvent := *event.KeyReleaseEvent
|
||||
button := backend.keycodeToButton(keyEvent.Detail, keyEvent.State)
|
||||
backend.channel <- stone.EventRelease { Button: button }
|
||||
}
|
||||
|
||||
func (backend *Backend) handleMotionNotify (
|
||||
connection *xgbutil.XUtil,
|
||||
event xevent.MotionNotifyEvent,
|
||||
) {
|
||||
motionEvent := *event.MotionNotifyEvent
|
||||
x, y := backend.cellAt (image.Point {
|
||||
X: int(motionEvent.EventX),
|
||||
Y: int(motionEvent.EventY),
|
||||
})
|
||||
backend.channel <- stone.EventMouseMove {
|
||||
X: x,
|
||||
Y: y,
|
||||
}
|
||||
}
|
||||
|
||||
func (backend *Backend) compressConfigureNotify (
|
||||
firstEvent xproto.ConfigureNotifyEvent,
|
||||
) (
|
||||
lastEvent xproto.ConfigureNotifyEvent,
|
||||
) {
|
||||
backend.connection.Sync()
|
||||
xevent.Read(backend.connection, false)
|
||||
lastEvent = firstEvent
|
||||
|
||||
for index, untypedEvent := range xevent.Peek(backend.connection) {
|
||||
if untypedEvent.Err != nil { continue }
|
||||
|
||||
typedEvent, ok := untypedEvent.Event.(xproto.ConfigureNotifyEvent)
|
||||
if !ok { continue }
|
||||
|
||||
lastEvent = typedEvent
|
||||
defer func (index int) {
|
||||
xevent.DequeueAt(backend.connection, index)
|
||||
} (index)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (backend *Backend) shutDown () {
|
||||
backend.channel <- stone.EventQuit { }
|
||||
}
|
159
backends/x/factory.go
Normal file
159
backends/x/factory.go
Normal file
@ -0,0 +1,159 @@
|
||||
package x
|
||||
|
||||
import "os"
|
||||
import "golang.org/x/image/font"
|
||||
import "golang.org/x/image/font/opentype"
|
||||
import "golang.org/x/image/font/basicfont"
|
||||
|
||||
import "github.com/jezek/xgbutil"
|
||||
import "github.com/jezek/xgb/xproto"
|
||||
import "github.com/jezek/xgbutil/icccm"
|
||||
import "github.com/jezek/xgbutil/xevent"
|
||||
import "github.com/jezek/xgbutil/xwindow"
|
||||
import "github.com/jezek/xgbutil/keybind"
|
||||
import "github.com/jezek/xgbutil/xgraphics"
|
||||
|
||||
import "git.tebibyte.media/sashakoshka/stone"
|
||||
|
||||
import "github.com/flopp/go-findfont"
|
||||
|
||||
// factory instantiates an X backend.
|
||||
func factory (application *stone.Application) (output stone.Backend, err error) {
|
||||
backend := &Backend {
|
||||
application: application,
|
||||
config: application.Config(),
|
||||
}
|
||||
|
||||
// load font
|
||||
backend.font.face = findAndLoadFont (
|
||||
backend.config.FontName(),
|
||||
float64(backend.config.FontSize()))
|
||||
if backend.font.face == nil {
|
||||
backend.font.face = basicfont.Face7x13
|
||||
}
|
||||
|
||||
// pre-calculate colors
|
||||
for index := 0; index < len(backend.colors); index ++ {
|
||||
color := backend.config.Color(stone.Color(index))
|
||||
r, g, b, a := color.RGBA()
|
||||
r >>= 8
|
||||
g >>= 8
|
||||
b >>= 8
|
||||
a >>= 8
|
||||
backend.colors[index] = xgraphics.BGRA {
|
||||
R: uint8(r),
|
||||
G: uint8(g),
|
||||
B: uint8(b),
|
||||
A: uint8(a),
|
||||
}
|
||||
}
|
||||
|
||||
// calculate metrics
|
||||
metrics := backend.font.face.Metrics()
|
||||
glyphAdvance, _ := backend.font.face.GlyphAdvance('M')
|
||||
backend.metrics.cellWidth = glyphAdvance.Round()
|
||||
backend.metrics.cellHeight = metrics.Height.Round()
|
||||
backend.metrics.descent = metrics.Descent.Round()
|
||||
backend.metrics.padding =
|
||||
backend.config.Padding() *
|
||||
backend.metrics.cellHeight
|
||||
backend.metrics.paddingX = backend.metrics.padding
|
||||
backend.metrics.paddingY = backend.metrics.padding
|
||||
backend.metrics.windowWidth,
|
||||
backend.metrics.windowHeight = backend.calculateWindowSize()
|
||||
|
||||
// connect to X
|
||||
backend.connection, err = xgbutil.NewConn()
|
||||
if err != nil { return }
|
||||
backend.window, err = xwindow.Generate(backend.connection)
|
||||
if err != nil { return }
|
||||
keybind.Initialize(backend.connection)
|
||||
|
||||
// create the window
|
||||
backend.window.Create (
|
||||
backend.connection.RootWin(),
|
||||
0, 0,
|
||||
backend.metrics.windowWidth, backend.metrics.windowHeight,
|
||||
0)
|
||||
backend.window.Map()
|
||||
// TODO: also listen to mouse movement (compressed) and mouse and
|
||||
// keyboard buttons (uncompressed)
|
||||
err = backend.window.Listen (
|
||||
xproto.EventMaskStructureNotify,
|
||||
xproto.EventMaskPointerMotion,
|
||||
xproto.EventMaskKeyPress,
|
||||
xproto.EventMaskKeyRelease,
|
||||
xproto.EventMaskButtonPress,
|
||||
xproto.EventMaskButtonRelease)
|
||||
backend.SetTitle(application.Title())
|
||||
backend.SetIcon(application.Icon())
|
||||
if err != nil { return }
|
||||
|
||||
// set minimum dimensions
|
||||
minWidth :=
|
||||
backend.metrics.cellWidth + backend.metrics.padding * 2
|
||||
minHeight :=
|
||||
backend.metrics.cellHeight + backend.metrics.padding * 2
|
||||
err = icccm.WmNormalHintsSet (
|
||||
backend.connection,
|
||||
backend.window.Id,
|
||||
&icccm.NormalHints {
|
||||
Flags: icccm.SizeHintPMinSize,
|
||||
MinWidth: uint(minWidth),
|
||||
MinHeight: uint(minHeight),
|
||||
})
|
||||
if err != nil { return }
|
||||
|
||||
// create a canvas
|
||||
backend.reallocateCanvas()
|
||||
|
||||
// attatch graceful close handler
|
||||
backend.window.WMGracefulClose (func (window *xwindow.Window) {
|
||||
backend.window.Destroy()
|
||||
backend.shutDown()
|
||||
})
|
||||
|
||||
// attatch event handlers
|
||||
xevent.ConfigureNotifyFun(backend.handleConfigureNotify).
|
||||
Connect(backend.connection, backend.window.Id)
|
||||
xevent.ButtonPressFun(backend.handleButtonPress).
|
||||
Connect(backend.connection, backend.window.Id)
|
||||
xevent.ButtonReleaseFun(backend.handleButtonRelease).
|
||||
Connect(backend.connection, backend.window.Id)
|
||||
xevent.MotionNotifyFun(backend.handleMotionNotify).
|
||||
Connect(backend.connection, backend.window.Id)
|
||||
xevent.KeyPressFun(backend.handleKeyPress).
|
||||
Connect(backend.connection, backend.window.Id)
|
||||
xevent.KeyReleaseFun(backend.handleKeyRelease).
|
||||
Connect(backend.connection, backend.window.Id)
|
||||
|
||||
// uncomment these to draw debug bounds
|
||||
// backend.drawCellBounds = true
|
||||
// backend.drawBufferBounds = true
|
||||
|
||||
output = backend
|
||||
return
|
||||
}
|
||||
|
||||
func findAndLoadFont (name string, size float64) (face font.Face) {
|
||||
if name == "" { return }
|
||||
fontPath, err := findfont.Find(name)
|
||||
if err != nil { return }
|
||||
fontFile, err := os.Open(fontPath)
|
||||
if err != nil { return }
|
||||
fontObject, err := opentype.ParseReaderAt(fontFile)
|
||||
if err != nil { return }
|
||||
face, err = opentype.NewFace (fontObject, &opentype.FaceOptions {
|
||||
Size: size,
|
||||
DPI: 96,
|
||||
Hinting: font.HintingFull,
|
||||
})
|
||||
if err != nil { face = nil }
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// init registers this backend when the program starts.
|
||||
func init () {
|
||||
stone.RegisterBackend(factory)
|
||||
}
|
187
backends/x/unicode.go
Normal file
187
backends/x/unicode.go
Normal file
@ -0,0 +1,187 @@
|
||||
package x
|
||||
|
||||
import "unicode"
|
||||
import "github.com/jezek/xgb/xproto"
|
||||
import "github.com/jezek/xgbutil/keybind"
|
||||
import "git.tebibyte.media/sashakoshka/stone"
|
||||
|
||||
// when making changes to this file, look at keysymdef.h and
|
||||
// https://tronche.com/gui/x/xlib/input/keyboard-encoding.html
|
||||
|
||||
var buttonCodeTable = map[xproto.Keysym] stone.Button {
|
||||
0xFFFFFF: stone.ButtonUnknown,
|
||||
|
||||
0xFF63: stone.KeyInsert,
|
||||
0xFF67: stone.KeyMenu,
|
||||
0xFF61: stone.KeyPrintScreen,
|
||||
0xFF6B: stone.KeyPause,
|
||||
0xFFE5: stone.KeyCapsLock,
|
||||
0xFF14: stone.KeyScrollLock,
|
||||
0xFF7F: stone.KeyNumLock,
|
||||
0xFF08: stone.KeyBackspace,
|
||||
0xFF09: stone.KeyTab,
|
||||
0xFF0D: stone.KeyEnter,
|
||||
0xFF1B: stone.KeyEscape,
|
||||
|
||||
0xFF52: stone.KeyUp,
|
||||
0xFF54: stone.KeyDown,
|
||||
0xFF51: stone.KeyLeft,
|
||||
0xFF53: stone.KeyRight,
|
||||
0xFF55: stone.KeyPageUp,
|
||||
0xFF56: stone.KeyPageDown,
|
||||
0xFF50: stone.KeyHome,
|
||||
0xFF57: stone.KeyEnd,
|
||||
|
||||
0xFFE1: stone.KeyLeftShift,
|
||||
0xFFE2: stone.KeyRightShift,
|
||||
0xFFE3: stone.KeyLeftControl,
|
||||
0xFFE4: stone.KeyRightControl,
|
||||
0xFFE9: stone.KeyLeftAlt,
|
||||
0xFFEA: stone.KeyRightAlt,
|
||||
0xFFEB: stone.KeyLeftSuper,
|
||||
0xFFEC: stone.KeyRightSuper,
|
||||
|
||||
0xFFFF: stone.KeyDelete,
|
||||
|
||||
0xFFBE: stone.KeyF1,
|
||||
0xFFBF: stone.KeyF2,
|
||||
0xFFC0: stone.KeyF3,
|
||||
0xFFC1: stone.KeyF4,
|
||||
0xFFC2: stone.KeyF5,
|
||||
0xFFC3: stone.KeyF6,
|
||||
0xFFC4: stone.KeyF7,
|
||||
0xFFC5: stone.KeyF8,
|
||||
0xFFC6: stone.KeyF9,
|
||||
0xFFC7: stone.KeyF10,
|
||||
0xFFC8: stone.KeyF11,
|
||||
0xFFC9: stone.KeyF12,
|
||||
}
|
||||
|
||||
func (backend *Backend) keycodeToButton (
|
||||
keycode xproto.Keycode,
|
||||
state uint16,
|
||||
) (
|
||||
button stone.Button,
|
||||
) {
|
||||
// FIXME: also set shift to true if the lock modifier is on and the lock
|
||||
// modifier is interpreted as shiftLock
|
||||
shift := state & xproto.ModMaskShift > 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)
|
||||
symbol2 := keybind.KeysymGet(backend.connection, keycode, 1)
|
||||
symbol3 := keybind.KeysymGet(backend.connection, keycode, 2)
|
||||
symbol4 := keybind.KeysymGet(backend.connection, keycode, 3)
|
||||
|
||||
cased := false
|
||||
|
||||
// third paragraph
|
||||
switch {
|
||||
case symbol2 == 0 && symbol3 == 0 && symbol4 == 0:
|
||||
symbol3 = symbol1
|
||||
case symbol3 == 0 && symbol4 == 0:
|
||||
symbol3 = symbol1
|
||||
symbol2 = symbol2
|
||||
case symbol4 == 0:
|
||||
symbol4 = 0
|
||||
}
|
||||
|
||||
symbol1Rune := keysymToRune(symbol1)
|
||||
symbol2Rune := keysymToRune(symbol2)
|
||||
symbol3Rune := keysymToRune(symbol3)
|
||||
symbol4Rune := keysymToRune(symbol4)
|
||||
|
||||
// FIXME: we ignore mode switch stuff
|
||||
_ = symbol4Rune
|
||||
|
||||
// fourth paragraph
|
||||
if symbol2 == 0 {
|
||||
upper := unicode.IsUpper(symbol1Rune)
|
||||
lower := unicode.IsLower(symbol1Rune)
|
||||
if upper || lower {
|
||||
symbol1Rune = unicode.ToLower(symbol1Rune)
|
||||
symbol2Rune = unicode.ToUpper(symbol1Rune)
|
||||
cased = true
|
||||
} else {
|
||||
symbol2 = symbol1
|
||||
}
|
||||
}
|
||||
if symbol4 == 0 {
|
||||
upper := unicode.IsUpper(symbol3Rune)
|
||||
lower := unicode.IsLower(symbol3Rune)
|
||||
if upper || lower {
|
||||
symbol3Rune = unicode.ToLower(symbol3Rune)
|
||||
symbol4Rune = unicode.ToUpper(symbol3Rune)
|
||||
cased = true
|
||||
} else {
|
||||
symbol4 = symbol3
|
||||
}
|
||||
}
|
||||
|
||||
var selectedKeysym xproto.Keysym
|
||||
var selectedRune rune
|
||||
|
||||
// big ol list in the middle
|
||||
switch {
|
||||
// FIXME: take into account numlock
|
||||
case !shift && !capsLock:
|
||||
selectedKeysym = symbol1
|
||||
selectedRune = symbol1Rune
|
||||
|
||||
case !shift && capsLock:
|
||||
if cased && unicode.IsLower(symbol1Rune) {
|
||||
selectedRune = symbol2Rune
|
||||
} else {
|
||||
selectedKeysym = symbol1
|
||||
selectedRune = symbol1Rune
|
||||
}
|
||||
|
||||
case shift && capsLock:
|
||||
if cased && unicode.IsLower(symbol2Rune) {
|
||||
selectedRune = unicode.ToUpper(symbol2Rune)
|
||||
} else {
|
||||
selectedKeysym = symbol2
|
||||
selectedRune = symbol2Rune
|
||||
}
|
||||
|
||||
case shift:
|
||||
selectedKeysym = symbol2
|
||||
selectedRune = symbol2Rune
|
||||
}
|
||||
|
||||
// look up in table
|
||||
var isControl bool
|
||||
button, isControl = buttonCodeTable[selectedKeysym]
|
||||
|
||||
// if it wasn't found,
|
||||
if !isControl {
|
||||
button = stone.Button(selectedRune)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func keysymToRune (keysym xproto.Keysym) (character rune) {
|
||||
// X keysyms like 0xFF.. or 0xFE.. are non-character keys. these cannot
|
||||
// be converted so we return a zero.
|
||||
if (keysym >> 8) == 0xFF || (keysym >> 8) == 0xFE {
|
||||
character = 0
|
||||
return
|
||||
}
|
||||
|
||||
// some X keysyms have a single bit set to 1 here. i believe this is to
|
||||
// prevent conflicts with existing codes. if we mask it off we will get
|
||||
// a correct utf-32 code point.
|
||||
if keysym & 0xF000000 == 0x1000000 {
|
||||
character = rune(keysym & 0x0111111)
|
||||
return
|
||||
}
|
||||
|
||||
// if none of these things happened, we can safely (i think) assume that
|
||||
// the keysym is an exact utf-32 code point.
|
||||
character = rune(keysym)
|
||||
return
|
||||
}
|
172
backends/x/x.go
172
backends/x/x.go
@ -1,10 +1,13 @@
|
||||
package x
|
||||
|
||||
// import "fmt"
|
||||
import "sync"
|
||||
import "image"
|
||||
import "golang.org/x/image/font"
|
||||
|
||||
// import "github.com/jezek/xgb"
|
||||
import "github.com/jezek/xgbutil"
|
||||
// import "github.com/jezek/xgbutil/ewmh"
|
||||
import "github.com/jezek/xgbutil/xevent"
|
||||
import "github.com/jezek/xgbutil/ewmh"
|
||||
import "github.com/jezek/xgbutil/xwindow"
|
||||
import "github.com/jezek/xgbutil/xgraphics"
|
||||
|
||||
@ -18,13 +21,20 @@ type Backend struct {
|
||||
canvas *xgraphics.Image
|
||||
channel chan(stone.Event)
|
||||
|
||||
ping struct {
|
||||
before chan(struct { })
|
||||
after chan(struct { })
|
||||
quit chan(struct { })
|
||||
drawCellBounds bool
|
||||
drawBufferBounds bool
|
||||
|
||||
drawLock sync.Mutex
|
||||
|
||||
font struct {
|
||||
face font.Face
|
||||
}
|
||||
|
||||
colors [4]xgraphics.BGRA
|
||||
|
||||
metrics struct {
|
||||
windowWidth int
|
||||
windowHeight int
|
||||
cellWidth int
|
||||
cellHeight int
|
||||
padding int
|
||||
@ -32,33 +42,53 @@ type Backend struct {
|
||||
paddingY int
|
||||
descent int
|
||||
}
|
||||
|
||||
memory struct {
|
||||
windowWidth int
|
||||
windowHeight int
|
||||
}
|
||||
}
|
||||
|
||||
func (backend *Backend) Run (channel chan(stone.Event)) {
|
||||
backend.channel = channel
|
||||
|
||||
for {
|
||||
select {
|
||||
case <- backend.ping.before:
|
||||
<- backend.ping.after
|
||||
|
||||
case <- backend.ping.quit:
|
||||
backend.shutDown()
|
||||
func (backend *Backend) SetTitle (title string) (err error) {
|
||||
err = ewmh.WmNameSet(backend.connection, backend.window.Id, title)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (backend *Backend) SetIcon (icons []image.Image) (err error) {
|
||||
wmIcons := []ewmh.WmIcon { }
|
||||
|
||||
for _, icon := range icons {
|
||||
width := icon.Bounds().Max.X
|
||||
height := icon.Bounds().Max.Y
|
||||
wmIcon := ewmh.WmIcon {
|
||||
Width: uint(width),
|
||||
Height: uint(height),
|
||||
Data: make ([]uint, width * height),
|
||||
}
|
||||
|
||||
func (backend *Backend) SetTitle (title string) {
|
||||
// manually convert image data beacuse of course we have to do
|
||||
// this
|
||||
index := 0
|
||||
for y := 0; y < height; y ++ {
|
||||
for x := 0; x < width; x ++ {
|
||||
r, g, b, a := icon.At(x, y).RGBA()
|
||||
r >>= 8
|
||||
g >>= 8
|
||||
b >>= 8
|
||||
a >>= 8
|
||||
wmIcon.Data[index] =
|
||||
(uint(a) << 24) |
|
||||
(uint(r) << 16) |
|
||||
(uint(g) << 8) |
|
||||
(uint(b) << 0)
|
||||
index ++
|
||||
}}
|
||||
|
||||
wmIcons = append(wmIcons, wmIcon)
|
||||
}
|
||||
|
||||
func (backend *Backend) SetIcon (icons []image.Image) {
|
||||
|
||||
}
|
||||
|
||||
func (backend *Backend) shutDown () {
|
||||
backend.channel <- stone.EventQuit { }
|
||||
err = ewmh.WmIconSet(backend.connection, backend.window.Id, wmIcons)
|
||||
return
|
||||
}
|
||||
|
||||
// calculateWindowSize calculates window bounds based on the internal buffer
|
||||
@ -74,54 +104,56 @@ func (backend *Backend) calculateWindowSize () (x, y int) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// factory instantiates an X backend.
|
||||
func factory (application *stone.Application) (output stone.Backend, err error) {
|
||||
backend := &Backend {
|
||||
application: application,
|
||||
config: application.Config(),
|
||||
}
|
||||
|
||||
// calculate metrics
|
||||
// TODO: base these off of font metrics
|
||||
backend.metrics.cellWidth = 8
|
||||
backend.metrics.cellHeight = 16
|
||||
backend.metrics.padding =
|
||||
backend.config.Padding() *
|
||||
func (backend *Backend) calculateBufferSize () (width, height int) {
|
||||
width =
|
||||
(backend.metrics.windowWidth - backend.metrics.padding * 2) /
|
||||
backend.metrics.cellWidth
|
||||
height =
|
||||
(backend.metrics.windowHeight - backend.metrics.padding * 2) /
|
||||
backend.metrics.cellHeight
|
||||
backend.metrics.paddingX = backend.metrics.padding
|
||||
backend.metrics.paddingY = backend.metrics.padding
|
||||
|
||||
// connect to X
|
||||
backend.connection, err = xgbutil.NewConn()
|
||||
if err != nil { return }
|
||||
backend.window, err = xwindow.Generate(backend.connection)
|
||||
if err != nil { return }
|
||||
|
||||
// create the window
|
||||
windowWidth, windowHeight := backend.calculateWindowSize()
|
||||
backend.window.Create (
|
||||
backend.connection.RootWin(),
|
||||
0, 0, windowWidth, windowHeight,
|
||||
0)
|
||||
backend.window.Map()
|
||||
|
||||
// attatch graceful close handler
|
||||
backend.window.WMGracefulClose (func (window *xwindow.Window) {
|
||||
backend.window.Destroy()
|
||||
backend.shutDown()
|
||||
})
|
||||
|
||||
// start event loop
|
||||
backend.ping.before,
|
||||
backend.ping.after,
|
||||
backend.ping.quit = xevent.MainPing(backend.connection)
|
||||
|
||||
output = backend
|
||||
return
|
||||
}
|
||||
|
||||
// init registers this backend when the program starts.
|
||||
func init () {
|
||||
stone.RegisterBackend(factory)
|
||||
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.ColorApplication]
|
||||
})
|
||||
|
||||
backend.canvas.XSurfaceSet(backend.window.Id)
|
||||
}
|
||||
|
||||
func (backend *Backend) cellAt (onScreen image.Point) (x, y int) {
|
||||
x = (onScreen.X - backend.metrics.paddingX) / backend.metrics.cellWidth
|
||||
y = (onScreen.Y - backend.metrics.paddingY) / backend.metrics.cellHeight
|
||||
return
|
||||
}
|
||||
|
||||
func (backend *Backend) cellSubImage (x, y int) (cell *xgraphics.Image) {
|
||||
cell = backend.canvas.SubImage(backend.boundsOfCell(x, y)).(*xgraphics.Image)
|
||||
return
|
||||
}
|
||||
|
||||
func (backend *Backend) originOfCell (x, y int) (origin image.Point) {
|
||||
origin = image.Point {
|
||||
X: x * backend.metrics.cellWidth + backend.metrics.paddingX,
|
||||
Y: y * backend.metrics.cellHeight + backend.metrics.paddingY,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (backend *Backend) boundsOfCell (x, y int) (bounds image.Rectangle) {
|
||||
bounds = image.Rectangle {
|
||||
Min: backend.originOfCell(x, y),
|
||||
Max: backend.originOfCell(x + 1, y + 1),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
4
event.go
4
event.go
@ -3,8 +3,8 @@ package stone
|
||||
type Event interface { }
|
||||
|
||||
type EventQuit struct { }
|
||||
type EventPress Button
|
||||
type EventRelease Button
|
||||
type EventPress struct { Button }
|
||||
type EventRelease struct { Button }
|
||||
type EventResize struct { }
|
||||
type EventMouseMove struct {
|
||||
X int
|
||||
|
67
examples/draw/main.go
Normal file
67
examples/draw/main.go
Normal file
@ -0,0 +1,67 @@
|
||||
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 { }
|
||||
var mousePressed bool
|
||||
|
||||
func main () {
|
||||
application.SetTitle("hellorld")
|
||||
application.SetSize(32, 16)
|
||||
|
||||
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 })
|
||||
|
||||
channel, err := application.Run()
|
||||
if err != nil { panic(err) }
|
||||
|
||||
application.Draw()
|
||||
|
||||
for {
|
||||
event := <- channel
|
||||
switch event.(type) {
|
||||
case stone.EventQuit:
|
||||
os.Exit(0)
|
||||
|
||||
case stone.EventPress:
|
||||
button := event.(stone.EventPress).Button
|
||||
if button == stone.MouseButtonLeft {
|
||||
mousePressed = true
|
||||
application.SetRune(0, 0, '+')
|
||||
application.Draw()
|
||||
}
|
||||
|
||||
case stone.EventRelease:
|
||||
button := event.(stone.EventRelease).Button
|
||||
if button == stone.MouseButtonLeft {
|
||||
mousePressed = false
|
||||
application.SetRune(0, 0, 0)
|
||||
application.Draw()
|
||||
}
|
||||
|
||||
case stone.EventMouseMove:
|
||||
event := event.(stone.EventMouseMove)
|
||||
if mousePressed {
|
||||
application.SetRune(event.X, event.Y, '#')
|
||||
application.Draw()
|
||||
}
|
||||
|
||||
case stone.EventResize:
|
||||
application.Draw()
|
||||
}
|
||||
}
|
||||
}
|
@ -3,23 +3,56 @@ package main
|
||||
import "os"
|
||||
import "fmt"
|
||||
import "time"
|
||||
import "image"
|
||||
import _ "image/png"
|
||||
import "git.tebibyte.media/sashakoshka/stone"
|
||||
import _ "git.tebibyte.media/sashakoshka/stone/backends/x"
|
||||
|
||||
var application = &stone.Application { }
|
||||
var currentTime = time.Time { }
|
||||
var tickPing = make(chan(struct { }))
|
||||
|
||||
func main () {
|
||||
application := &stone.Application { }
|
||||
application.SetTitle("hellorld")
|
||||
application.SetSize(12, 2)
|
||||
|
||||
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 })
|
||||
|
||||
channel, err := application.Run()
|
||||
if err != nil { panic(err) }
|
||||
|
||||
currentTime := time.Time { }
|
||||
redraw()
|
||||
go tick()
|
||||
|
||||
for {
|
||||
event := <- channel
|
||||
select {
|
||||
case <- tickPing:
|
||||
redraw()
|
||||
|
||||
case event := <- channel:
|
||||
switch event.(type) {
|
||||
case stone.EventQuit:
|
||||
os.Exit(0)
|
||||
|
||||
case stone.EventResize:
|
||||
redraw()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func redraw () {
|
||||
currentTime = time.Now()
|
||||
|
||||
application.ResetDot()
|
||||
@ -37,6 +70,13 @@ func main () {
|
||||
application.SetRune(5, 1, ':')
|
||||
application.SetRune(6, 1, rune(second / 10 + 48))
|
||||
application.SetRune(7, 1, rune(second % 10 + 48))
|
||||
}
|
||||
|
||||
application.Draw()
|
||||
}
|
||||
|
||||
func tick () {
|
||||
for {
|
||||
tickPing <- struct { } { }
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
||||
|
56
examples/type/main.go
Normal file
56
examples/type/main.go
Normal file
@ -0,0 +1,56 @@
|
||||
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 { }
|
||||
var caret = 0
|
||||
|
||||
func main () {
|
||||
application.SetTitle("hellorld")
|
||||
application.SetSize(32, 16)
|
||||
|
||||
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 })
|
||||
|
||||
channel, err := application.Run()
|
||||
if err != nil { panic(err) }
|
||||
|
||||
application.Draw()
|
||||
|
||||
for {
|
||||
event := <- channel
|
||||
switch event.(type) {
|
||||
case stone.EventQuit:
|
||||
os.Exit(0)
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
case stone.EventResize:
|
||||
application.Draw()
|
||||
}
|
||||
}
|
||||
}
|
13
go.mod
13
go.mod
@ -3,19 +3,14 @@ module git.tebibyte.media/sashakoshka/stone
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/faiface/pixel v0.10.0
|
||||
github.com/flopp/go-findfont v0.1.0
|
||||
github.com/jezek/xgb v1.1.0
|
||||
github.com/jezek/xgbutil v0.0.0-20210302171758-530099784e66
|
||||
golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff
|
||||
golang.org/x/image v0.1.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298 // indirect
|
||||
github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966 // indirect
|
||||
github.com/faiface/glhf v0.0.0-20181018222622-82a6317ac380 // indirect
|
||||
github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3 // indirect
|
||||
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 // indirect
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72 // indirect
|
||||
github.com/go-gl/mathgl v0.0.0-20190416160123-c4601bc793c7 // indirect
|
||||
github.com/jezek/xgb v1.1.0 // indirect
|
||||
github.com/pkg/errors v0.8.1 // indirect
|
||||
golang.org/x/text v0.4.0 // indirect
|
||||
)
|
||||
|
54
go.sum
54
go.sum
@ -2,34 +2,36 @@ github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298 h1:1qlsVAQJ
|
||||
github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298/go.mod h1:D+QujdIlUNfa0igpNMk6UIvlb6C252URs4yupRUV4lQ=
|
||||
github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966 h1:lTG4HQym5oPKjL7nGs+csTgiDna685ZXjxijkne828g=
|
||||
github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966/go.mod h1:Mid70uvE93zn9wgF92A/r5ixgnvX8Lh68fxp9KQBaI0=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/faiface/glhf v0.0.0-20181018222622-82a6317ac380 h1:FvZ0mIGh6b3kOITxUnxS3tLZMh7yEoHo75v3/AgUqg0=
|
||||
github.com/faiface/glhf v0.0.0-20181018222622-82a6317ac380/go.mod h1:zqnPFFIuYFFxl7uH2gYByJwIVKG7fRqlqQCbzAnHs9g=
|
||||
github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3 h1:baVdMKlASEHrj19iqjARrPbaRisD7EuZEVJj6ZMLl1Q=
|
||||
github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3/go.mod h1:VEPNJUlxl5KdWjDvz6Q1l+rJlxF2i6xqDeGuGAxa87M=
|
||||
github.com/faiface/pixel v0.10.0 h1:EHm3ZdQw2Ck4y51cZqFfqQpwLqNHOoXwbNEc9Dijql0=
|
||||
github.com/faiface/pixel v0.10.0/go.mod h1:lU0YYcW77vL0F1CG8oX51GXurymL45MXd57otHNLK7A=
|
||||
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 h1:SCYMcCJ89LjRGwEa0tRluNRiMjZHalQZrVrvTbPh+qw=
|
||||
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72 h1:b+9H1GAsx5RsjvDFLoS5zkNBzIQMuVKUYQDmxU3N5XE=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/mathgl v0.0.0-20190416160123-c4601bc793c7 h1:THttjeRn1iiz69E875U6gAik8KTWk/JYAHoSVpUxBBI=
|
||||
github.com/go-gl/mathgl v0.0.0-20190416160123-c4601bc793c7/go.mod h1:yhpkQzEiH9yPyxDUGzkmgScbaBVlhC06qodikEM0ZwQ=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/flopp/go-findfont v0.1.0 h1:lPn0BymDUtJo+ZkV01VS3661HL6F4qFlkhcJN55u6mU=
|
||||
github.com/flopp/go-findfont v0.1.0/go.mod h1:wKKxRDjD024Rh7VMwoU90i6ikQRCr+JTHB5n4Ejkqvw=
|
||||
github.com/jezek/xgb v1.1.0 h1:wnpxJzP1+rkbGclEkmwpVFQWpuE2PUGNUzP8SbfFobk=
|
||||
github.com/jezek/xgb v1.1.0/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
|
||||
github.com/jezek/xgbutil v0.0.0-20210302171758-530099784e66 h1:+wPhoJD8EH0/bXipIq8Lc2z477jfox9zkXPCJdhvHj8=
|
||||
github.com/jezek/xgbutil v0.0.0-20210302171758-530099784e66/go.mod h1:KACeV+k6b+aoLTVrrurywEbu3UpqoQcQywj4qX8aQKM=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff h1:+2zgJKVDVAz/BWSsuniCmU1kLCjL88Z8/kv39xCI9NQ=
|
||||
golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/image v0.1.0 h1:r8Oj8ZA2Xy12/b5KZYj3tuv7NG/fBz3TwQVvpJ9l8Rk=
|
||||
golang.org/x/image v0.1.0/go.mod h1:iyPr49SD/G/TBxYVB/9RRtGUT5eNbo2u4NamWeQcD5c=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
199
input.go
199
input.go
@ -1,143 +1,78 @@
|
||||
package stone
|
||||
|
||||
// These should be identical to the glfw keys
|
||||
import "unicode"
|
||||
|
||||
type Button int
|
||||
|
||||
const (
|
||||
KeyUnknown Button = -1
|
||||
ButtonUnknown Button = 0
|
||||
|
||||
KeySpace Button = 162
|
||||
KeyApostrophe Button = 161
|
||||
KeyComma Button = 96
|
||||
KeyMinus Button = 93
|
||||
KeyPeriod Button = 92
|
||||
KeySlash Button = 91
|
||||
Key0 Button = 90
|
||||
Key1 Button = 89
|
||||
Key2 Button = 88
|
||||
Key3 Button = 87
|
||||
Key4 Button = 86
|
||||
Key5 Button = 85
|
||||
Key6 Button = 84
|
||||
Key7 Button = 83
|
||||
Key8 Button = 82
|
||||
Key9 Button = 81
|
||||
KeySemicolon Button = 80
|
||||
KeyEqual Button = 79
|
||||
KeyA Button = 78
|
||||
KeyB Button = 77
|
||||
KeyC Button = 76
|
||||
KeyD Button = 75
|
||||
KeyE Button = 74
|
||||
KeyF Button = 73
|
||||
KeyG Button = 72
|
||||
KeyH Button = 71
|
||||
KeyI Button = 70
|
||||
KeyJ Button = 69
|
||||
KeyK Button = 68
|
||||
KeyL Button = 67
|
||||
KeyM Button = 66
|
||||
KeyN Button = 65
|
||||
KeyO Button = 61
|
||||
KeyP Button = 59
|
||||
KeyQ Button = 57
|
||||
KeyR Button = 56
|
||||
KeyS Button = 55
|
||||
KeyT Button = 54
|
||||
KeyU Button = 53
|
||||
KeyV Button = 52
|
||||
KeyW Button = 51
|
||||
KeyX Button = 50
|
||||
KeyY Button = 49
|
||||
KeyZ Button = 48
|
||||
KeyLeftBracket Button = 47
|
||||
KeyBackslash Button = 46
|
||||
KeyRightBracket Button = 45
|
||||
KeyGraveAccent Button = 44
|
||||
KeyWorld1 Button = 39
|
||||
KeyWorld2 Button = 32
|
||||
KeyInsert Button = 1
|
||||
KeyMenu Button = 2
|
||||
KeyPrintScreen Button = 3
|
||||
KeyPause Button = 4
|
||||
KeyCapsLock Button = 5
|
||||
KeyScrollLock Button = 6
|
||||
KeyNumLock Button = 7
|
||||
KeyBackspace Button = 8
|
||||
KeyTab Button = 9
|
||||
KeyEnter Button = 10
|
||||
KeyEscape Button = 11
|
||||
|
||||
KeyEscape Button = 348
|
||||
KeyEnter Button = 347
|
||||
KeyTab Button = 346
|
||||
KeyBackspace Button = 345
|
||||
KeyInsert Button = 344
|
||||
KeyDelete Button = 343
|
||||
KeyRight Button = 342
|
||||
KeyLeft Button = 341
|
||||
KeyDown Button = 340
|
||||
KeyUp Button = 336
|
||||
KeyPageUp Button = 335
|
||||
KeyPageDown Button = 334
|
||||
KeyHome Button = 333
|
||||
KeyEnd Button = 332
|
||||
KeyCapsLock Button = 331
|
||||
KeyScrollLock Button = 330
|
||||
KeyNumLock Button = 329
|
||||
KeyPrintScreen Button = 328
|
||||
KeyPause Button = 327
|
||||
KeyF1 Button = 326
|
||||
KeyF2 Button = 325
|
||||
KeyF3 Button = 324
|
||||
KeyF4 Button = 323
|
||||
KeyF5 Button = 322
|
||||
KeyF6 Button = 321
|
||||
KeyF7 Button = 320
|
||||
KeyF8 Button = 314
|
||||
KeyF9 Button = 313
|
||||
KeyF10 Button = 312
|
||||
KeyF11 Button = 311
|
||||
KeyF12 Button = 310
|
||||
KeyF13 Button = 309
|
||||
KeyF14 Button = 308
|
||||
KeyF15 Button = 307
|
||||
KeyF16 Button = 306
|
||||
KeyF17 Button = 305
|
||||
KeyF18 Button = 304
|
||||
KeyF19 Button = 303
|
||||
KeyF20 Button = 302
|
||||
KeyF21 Button = 301
|
||||
KeyF22 Button = 300
|
||||
KeyF23 Button = 299
|
||||
KeyF24 Button = 298
|
||||
KeyF25 Button = 297
|
||||
KeyKP0 Button = 296
|
||||
KeyKP1 Button = 295
|
||||
KeyKP2 Button = 294
|
||||
KeyKP3 Button = 293
|
||||
KeyKP4 Button = 292
|
||||
KeyKP5 Button = 291
|
||||
KeyKP6 Button = 290
|
||||
KeyKP7 Button = 284
|
||||
KeyKP8 Button = 283
|
||||
KeyKP9 Button = 282
|
||||
KeyKPDecimal Button = 281
|
||||
KeyKPDivide Button = 280
|
||||
KeyKPMultiply Button = 269
|
||||
KeyKPSubtract Button = 268
|
||||
KeyKPAdd Button = 267
|
||||
KeyKPEnter Button = 266
|
||||
KeyKPEqual Button = 265
|
||||
KeyLeftShift Button = 264
|
||||
KeyLeftControl Button = 263
|
||||
KeyLeftAlt Button = 262
|
||||
KeyLeftSuper Button = 261
|
||||
KeyRightShift Button = 260
|
||||
KeyRightControl Button = 259
|
||||
KeyRightAlt Button = 258
|
||||
KeyRightSuper Button = 257
|
||||
KeyMenu Button = 256
|
||||
KeyUp Button = 12
|
||||
KeyDown Button = 13
|
||||
KeyLeft Button = 14
|
||||
KeyRight Button = 15
|
||||
KeyPageUp Button = 16
|
||||
KeyPageDown Button = 17
|
||||
KeyHome Button = 18
|
||||
KeyEnd Button = 19
|
||||
|
||||
MouseButton1 Button = 0
|
||||
MouseButton2 Button = 1
|
||||
MouseButton3 Button = 2
|
||||
MouseButton4 Button = 3
|
||||
MouseButton5 Button = 4
|
||||
MouseButton6 Button = 5
|
||||
MouseButton7 Button = 6
|
||||
MouseButton8 Button = 7
|
||||
KeyLeftShift Button = 20
|
||||
KeyRightShift Button = 21
|
||||
KeyLeftControl Button = 22
|
||||
KeyRightControl Button = 23
|
||||
KeyLeftAlt Button = 24
|
||||
KeyRightAlt Button = 25
|
||||
KeyLeftSuper Button = 26
|
||||
KeyRightSuper Button = 27
|
||||
|
||||
KeyDelete Button = 127
|
||||
|
||||
MouseButton1 Button = 128
|
||||
MouseButton2 Button = 129
|
||||
MouseButton3 Button = 130
|
||||
MouseButton4 Button = 131
|
||||
MouseButton5 Button = 132
|
||||
MouseButton6 Button = 133
|
||||
MouseButton7 Button = 134
|
||||
MouseButton8 Button = 135
|
||||
MouseButton9 Button = 136
|
||||
MouseButtonLeft Button = MouseButton1
|
||||
MouseButtonRight Button = MouseButton2
|
||||
MouseButtonMiddle Button = MouseButton3
|
||||
MouseButtonMiddle Button = MouseButton2
|
||||
MouseButtonRight Button = MouseButton3
|
||||
MouseButtonScrollUp Button = MouseButton4
|
||||
MouseButtonScrollDown Button = MouseButton5
|
||||
MouseButtonScrollLeft Button = MouseButton6
|
||||
MouseButtonScrollRight Button = MouseButton7
|
||||
MouseButtonBack Button = MouseButton8
|
||||
MouseButtonForward Button = MouseButton9
|
||||
|
||||
KeyF1 Button = 144
|
||||
KeyF2 Button = 145
|
||||
KeyF3 Button = 146
|
||||
KeyF4 Button = 147
|
||||
KeyF5 Button = 148
|
||||
KeyF6 Button = 149
|
||||
KeyF7 Button = 150
|
||||
KeyF8 Button = 151
|
||||
KeyF9 Button = 152
|
||||
KeyF10 Button = 153
|
||||
KeyF11 Button = 154
|
||||
KeyF12 Button = 155
|
||||
)
|
||||
|
||||
func (button Button) Printable () (printable bool) {
|
||||
printable = unicode.IsPrint(rune(button))
|
||||
return
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user