x-backend #2
128
backends/x/draw.go
Normal file
128
backends/x/draw.go
Normal file
@ -0,0 +1,128 @@
|
||||
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)
|
||||
|
||||
// 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)
|
||||
}}
|
||||
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))
|
||||
}
|
||||
}
|
107
backends/x/event.go
Normal file
107
backends/x/event.go
Normal file
@ -0,0 +1,107 @@
|
||||
package x
|
||||
|
||||
import "image"
|
||||
|
||||
import "github.com/jezek/xgbutil"
|
||||
import "github.com/jezek/xgb/xproto"
|
||||
import "github.com/jezek/xgbutil/xevent"
|
||||
import "github.com/jezek/xgbutil/keybind"
|
||||
|
||||
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)
|
||||
backend.application.SetSize(backend.calculateBufferSize())
|
||||
backend.channel <- stone.EventResize { }
|
||||
}
|
||||
}
|
||||
|
||||
func (backend *Backend) handleButtonPress (
|
||||
connection *xgbutil.XUtil,
|
||||
event xevent.ButtonPressEvent,
|
||||
) {
|
||||
buttonEvent := *event.ButtonPressEvent
|
||||
backend.channel <- stone.EventPress(buttonEvent.Detail)
|
||||
}
|
||||
|
||||
func (backend *Backend) handleButtonRelease (
|
||||
connection *xgbutil.XUtil,
|
||||
event xevent.ButtonReleaseEvent,
|
||||
) {
|
||||
buttonEvent := *event.ButtonReleaseEvent
|
||||
backend.channel <- stone.EventRelease(buttonEvent.Detail)
|
||||
}
|
||||
|
||||
func (backend *Backend) handleKeyPress (
|
||||
connection *xgbutil.XUtil,
|
||||
event xevent.KeyPressEvent,
|
||||
) {
|
||||
keyEvent := *event.KeyPressEvent
|
||||
keySym := keybind.KeysymGet(backend.connection, keyEvent.Detail, 0)
|
||||
// TODO: convert to keysym and then to a button value
|
||||
}
|
||||
|
||||
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 { }
|
||||
}
|
151
backends/x/factory.go
Normal file
151
backends/x/factory.go
Normal file
@ -0,0 +1,151 @@
|
||||
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)
|
||||
|
||||
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)
|
||||
}
|
351
backends/x/x.go
351
backends/x/x.go
@ -1,28 +1,18 @@
|
||||
package x
|
||||
|
||||
import "os"
|
||||
// import "fmt"
|
||||
import "sync"
|
||||
import "image"
|
||||
import "image/draw"
|
||||
import "golang.org/x/image/font"
|
||||
import "golang.org/x/image/math/fixed"
|
||||
import "golang.org/x/image/font/opentype"
|
||||
import "golang.org/x/image/font/basicfont"
|
||||
|
||||
// import "github.com/jezek/xgb"
|
||||
import "github.com/jezek/xgbutil"
|
||||
import "github.com/jezek/xgb/xproto"
|
||||
import "github.com/jezek/xgbutil/ewmh"
|
||||
import "github.com/jezek/xgbutil/icccm"
|
||||
import "github.com/jezek/xgbutil/xevent"
|
||||
import "github.com/jezek/xgbutil/xwindow"
|
||||
import "github.com/jezek/xgbutil/xgraphics"
|
||||
|
||||
import "git.tebibyte.media/sashakoshka/stone"
|
||||
|
||||
import "github.com/flopp/go-findfont"
|
||||
|
||||
type Backend struct {
|
||||
application *stone.Application
|
||||
config *stone.Config
|
||||
@ -56,32 +46,6 @@ type Backend struct {
|
||||
}
|
||||
}
|
||||
|
||||
func (backend *Backend) Run (channel chan(stone.Event)) {
|
||||
backend.channel = channel
|
||||
xevent.Main(backend.connection)
|
||||
backend.shutDown()
|
||||
}
|
||||
|
||||
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) SetTitle (title string) (err error) {
|
||||
err = ewmh.WmNameSet(backend.connection, backend.window.Id, title)
|
||||
return
|
||||
@ -124,87 +88,6 @@ func (backend *Backend) SetIcon (icons []image.Image) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
backend.application.SetSize(backend.calculateBufferSize())
|
||||
backend.channel <- stone.EventResize { }
|
||||
}
|
||||
}
|
||||
|
||||
func (backend *Backend) handleButtonPress (
|
||||
connection *xgbutil.XUtil,
|
||||
event xevent.ButtonPressEvent,
|
||||
) {
|
||||
buttonEvent := *event.ButtonPressEvent
|
||||
backend.channel <- stone.EventPress(buttonEvent.Detail)
|
||||
}
|
||||
|
||||
func (backend *Backend) handleButtonRelease (
|
||||
connection *xgbutil.XUtil,
|
||||
event xevent.ButtonReleaseEvent,
|
||||
) {
|
||||
buttonEvent := *event.ButtonReleaseEvent
|
||||
backend.channel <- stone.EventRelease(buttonEvent.Detail)
|
||||
}
|
||||
|
||||
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 { }
|
||||
}
|
||||
|
||||
// calculateWindowSize calculates window bounds based on the internal buffer
|
||||
// size.
|
||||
func (backend *Backend) calculateWindowSize () (x, y int) {
|
||||
@ -245,69 +128,6 @@ func (backend *Backend) reallocateCanvas () {
|
||||
backend.canvas.XSurfaceSet(backend.window.Id)
|
||||
}
|
||||
|
||||
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)
|
||||
}}
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
// 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) 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
|
||||
@ -334,174 +154,3 @@ func (backend *Backend) boundsOfCell (x, y int) (bounds image.Rectangle) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 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 }
|
||||
|
||||
// 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)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
// init registers this backend when the program starts.
|
||||
func init () {
|
||||
stone.RegisterBackend(factory)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user