Initial commit
This commit is contained in:
commit
5e7c666d92
80
backend.go
Normal file
80
backend.go
Normal file
@ -0,0 +1,80 @@
|
||||
package x
|
||||
|
||||
import "git.tebibyte.media/tomo/tomo"
|
||||
import "git.tebibyte.media/tomo/xgbkb"
|
||||
|
||||
import "github.com/jezek/xgbutil"
|
||||
import "github.com/jezek/xgb/xproto"
|
||||
import "github.com/jezek/xgbutil/xevent"
|
||||
import "github.com/jezek/xgbutil/keybind"
|
||||
import "github.com/jezek/xgbutil/mousebind"
|
||||
|
||||
type Backend struct {
|
||||
x *xgbutil.XUtil
|
||||
doChannel chan func()
|
||||
windows map[xproto.Window] *window
|
||||
open bool
|
||||
}
|
||||
|
||||
func NewBackend () (tomo.Backend, error) {
|
||||
backend := &Backend {
|
||||
doChannel: make(chan func (), 32),
|
||||
windows: make(map[xproto.Window] *window),
|
||||
open: true,
|
||||
}
|
||||
|
||||
var err error
|
||||
backend.x, err = xgbutil.NewConn()
|
||||
if err != nil { return nil, err }
|
||||
|
||||
keybind .Initialize(backend.x)
|
||||
xgbkb .Initialize(backend.x)
|
||||
mousebind.Initialize(backend.x)
|
||||
|
||||
return backend, nil
|
||||
}
|
||||
|
||||
func (backend *Backend) Run () error {
|
||||
backend.assert()
|
||||
pingBefore,
|
||||
pingAfter,
|
||||
pingQuit := xevent.MainPing(backend.x)
|
||||
for {
|
||||
select {
|
||||
case <- pingBefore:
|
||||
<- pingAfter
|
||||
case callback := <- backend.doChannel:
|
||||
callback()
|
||||
case <- pingQuit:
|
||||
return nil // FIXME: if we exited due to an error say so
|
||||
}
|
||||
for _, window := range backend.windows {
|
||||
window.afterEvent()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (backend *Backend) Stop () {
|
||||
backend.assert()
|
||||
if !backend.open { return }
|
||||
backend.open = false
|
||||
|
||||
toClose := []*window { }
|
||||
for _, panel := range backend.windows {
|
||||
toClose = append(toClose, panel)
|
||||
}
|
||||
for _, panel := range toClose {
|
||||
panel.Close()
|
||||
}
|
||||
xevent.Quit(backend.x)
|
||||
backend.x.Conn().Close()
|
||||
}
|
||||
|
||||
func (backend *Backend) Do (callback func ()) {
|
||||
backend.assert()
|
||||
backend.doChannel <- callback
|
||||
}
|
||||
|
||||
func (backend *Backend) assert () {
|
||||
if backend == nil { panic("nil backend") }
|
||||
}
|
228
box.go
Normal file
228
box.go
Normal file
@ -0,0 +1,228 @@
|
||||
package x
|
||||
|
||||
import "image"
|
||||
import "image/color"
|
||||
import "git.tebibyte.media/tomo/tomo"
|
||||
import "git.tebibyte.media/tomo/tomo/data"
|
||||
import "git.tebibyte.media/tomo/tomo/input"
|
||||
import "git.tebibyte.media/tomo/tomo/event"
|
||||
import "git.tebibyte.media/tomo/tomo/canvas"
|
||||
|
||||
type box struct {
|
||||
backend *Backend
|
||||
window *window
|
||||
|
||||
bounds image.Rectangle
|
||||
minSize image.Point
|
||||
|
||||
padding tomo.Inset
|
||||
border []tomo.Border
|
||||
color color.Color
|
||||
|
||||
dndData data.Data
|
||||
dndAccept []data.Mime
|
||||
focused bool
|
||||
focusable bool
|
||||
|
||||
drawClean bool
|
||||
layoutClean bool
|
||||
|
||||
drawer canvas.Drawer
|
||||
|
||||
on struct {
|
||||
focusEnter event.FuncBroadcaster
|
||||
focusLeave event.FuncBroadcaster
|
||||
dndEnter event.FuncBroadcaster
|
||||
dndLeave event.FuncBroadcaster
|
||||
dndDrop event.Broadcaster[func (data.Data)]
|
||||
mouseEnter event.FuncBroadcaster
|
||||
mouseLeave event.FuncBroadcaster
|
||||
mouseMove event.FuncBroadcaster
|
||||
mouseDown event.Broadcaster[func (input.Button)]
|
||||
mouseUp event.Broadcaster[func (input.Button)]
|
||||
scroll event.Broadcaster[func (float64, float64)]
|
||||
keyDown event.Broadcaster[func (input.Key, bool)]
|
||||
keyUp event.Broadcaster[func (input.Key, bool)]
|
||||
}
|
||||
}
|
||||
|
||||
func (backend *Backend) NewBox() tomo.Box {
|
||||
box := &box {
|
||||
backend: backend,
|
||||
}
|
||||
box.drawer = box
|
||||
return box
|
||||
}
|
||||
|
||||
func (this *box) Box () tomo.Box {
|
||||
return this
|
||||
}
|
||||
|
||||
func (this *box) Window () tomo.Window {
|
||||
return this.window
|
||||
}
|
||||
|
||||
func (this *box) Bounds () image.Rectangle {
|
||||
return this.bounds
|
||||
}
|
||||
|
||||
func (this *box) InnerBounds () image.Rectangle {
|
||||
innerBounds := this.padding.Apply(this.bounds)
|
||||
for _, border := range this.border {
|
||||
innerBounds = border.Width.Apply(innerBounds)
|
||||
}
|
||||
return innerBounds
|
||||
}
|
||||
|
||||
func (this *box) SetBounds (bounds image.Rectangle) {
|
||||
if this.bounds == bounds { return }
|
||||
this.bounds = bounds
|
||||
this.invalidateLayout()
|
||||
}
|
||||
|
||||
func (this *box) SetColor (c color.Color) {
|
||||
if this.color == c { return }
|
||||
this.color = c
|
||||
this.invalidateDraw()
|
||||
}
|
||||
|
||||
func (this *box) SetBorder (border ...tomo.Border) {
|
||||
this.border = border
|
||||
this.invalidateLayout()
|
||||
}
|
||||
|
||||
func (this *box) SetMinimumSize (width, height int) {
|
||||
this.minSize = image.Pt(width, height)
|
||||
if this.bounds.Dx() < width || this.bounds.Dy() < height {
|
||||
this.invalidateLayout()
|
||||
}
|
||||
}
|
||||
|
||||
func (this *box) SetPadding (padding tomo.Inset) {
|
||||
if this.padding == padding { return }
|
||||
this.padding = padding
|
||||
this.invalidateLayout()
|
||||
}
|
||||
|
||||
func (this *box) SetDNDData (dat data.Data) {
|
||||
this.dndData = dat
|
||||
}
|
||||
|
||||
func (this *box) SetDNDAccept (types ...data.Mime) {
|
||||
this.dndAccept = types
|
||||
}
|
||||
|
||||
func (this *box) SetFocused (focused bool) {
|
||||
if this.Focused () && !focused {
|
||||
this.window.focus(nil)
|
||||
} else if !this.Focused() && focused {
|
||||
this.window.focus(this)
|
||||
}
|
||||
}
|
||||
|
||||
func (this *box) SetFocusable (focusable bool) {
|
||||
if this.focusable == focusable { return }
|
||||
this.focusable = focusable
|
||||
if !focusable {
|
||||
this.SetFocused(false)
|
||||
}
|
||||
}
|
||||
|
||||
func (this *box) Focused () bool {
|
||||
return this == this.window.focused
|
||||
}
|
||||
|
||||
func (this *box) Modifiers () input.Modifiers {
|
||||
return this.window.modifiers
|
||||
}
|
||||
|
||||
func (this *box) MousePosition () image.Point {
|
||||
return this.window.mousePosition
|
||||
}
|
||||
|
||||
// ----- event handlers ----------------------------------------------------- //
|
||||
func (this *box) OnFocusEnter (callback func()) event.Cookie {
|
||||
return this.on.focusEnter.Connect(callback)
|
||||
}
|
||||
func (this *box) OnFocusLeave (callback func()) event.Cookie {
|
||||
return this.on.focusLeave.Connect(callback)
|
||||
}
|
||||
func (this *box) OnDNDEnter (callback func()) event.Cookie {
|
||||
return this.on.dndEnter.Connect(callback)
|
||||
}
|
||||
func (this *box) OnDNDLeave (callback func()) event.Cookie {
|
||||
return this.on.dndLeave.Connect(callback)
|
||||
}
|
||||
func (this *box) OnDNDDrop (callback func(data.Data)) event.Cookie {
|
||||
return this.on.dndDrop.Connect(callback)
|
||||
}
|
||||
func (this *box) OnMouseEnter (callback func()) event.Cookie {
|
||||
return this.on.mouseEnter.Connect(callback)
|
||||
}
|
||||
func (this *box) OnMouseLeave (callback func()) event.Cookie {
|
||||
return this.on.mouseLeave.Connect(callback)
|
||||
}
|
||||
func (this *box) OnMouseMove (callback func()) event.Cookie {
|
||||
return this.on.mouseMove.Connect(callback)
|
||||
}
|
||||
func (this *box) OnMouseDown (callback func(input.Button)) event.Cookie {
|
||||
return this.on.mouseDown.Connect(callback)
|
||||
}
|
||||
func (this *box) OnMouseUp (callback func(input.Button)) event.Cookie {
|
||||
return this.on.mouseUp.Connect(callback)
|
||||
}
|
||||
func (this *box) OnScroll (callback func(deltaX, deltaY float64)) event.Cookie {
|
||||
return this.on.scroll.Connect(callback)
|
||||
}
|
||||
func (this *box) OnKeyDown (callback func(key input.Key, numberPad bool)) event.Cookie {
|
||||
return this.on.keyDown.Connect(callback)
|
||||
}
|
||||
func (this *box) OnKeyUp (callback func(key input.Key, numberPad bool)) event.Cookie {
|
||||
return this.on.keyUp.Connect(callback)
|
||||
}
|
||||
// -------------------------------------------------------------------------- //
|
||||
|
||||
func (this *box) Draw (can canvas.Canvas) {
|
||||
pen := can.Pen()
|
||||
|
||||
bounds := this.bounds
|
||||
for _, border := range this.border {
|
||||
pen.Fill(border.Color[tomo.SideTop])
|
||||
pen.Rectangle(image.Rect (
|
||||
bounds.Min.X,
|
||||
bounds.Min.Y,
|
||||
bounds.Max.X,
|
||||
bounds.Min.Y + border.Width[tomo.SideTop]))
|
||||
pen.Fill(border.Color[tomo.SideBottom])
|
||||
pen.Rectangle(image.Rect (
|
||||
bounds.Min.X,
|
||||
bounds.Max.Y - border.Width[tomo.SideBottom],
|
||||
bounds.Max.X,
|
||||
bounds.Max.Y))
|
||||
pen.Fill(border.Color[tomo.SideLeft])
|
||||
pen.Rectangle(image.Rect (
|
||||
bounds.Min.X,
|
||||
bounds.Min.Y + border.Width[tomo.SideTop],
|
||||
bounds.Min.X + border.Width[tomo.SideLeft],
|
||||
bounds.Max.Y - border.Width[tomo.SideBottom]))
|
||||
pen.Fill(border.Color[tomo.SideRight])
|
||||
pen.Rectangle(image.Rect (
|
||||
bounds.Max.X - border.Width[tomo.SideRight],
|
||||
bounds.Min.Y + border.Width[tomo.SideTop],
|
||||
bounds.Max.X,
|
||||
bounds.Max.Y - border.Width[tomo.SideBottom]))
|
||||
|
||||
bounds = border.Width.Apply(bounds)
|
||||
}
|
||||
pen.Fill(this.color)
|
||||
pen.Rectangle(bounds)
|
||||
}
|
||||
|
||||
func (this *box) invalidateDraw () {
|
||||
this.drawClean = false
|
||||
}
|
||||
|
||||
func (this *box) invalidateLayout () {
|
||||
this.invalidateDraw()
|
||||
this.layoutClean = false
|
||||
}
|
96
canvas/canvas.go
Normal file
96
canvas/canvas.go
Normal file
@ -0,0 +1,96 @@
|
||||
package xcanvas
|
||||
|
||||
import "image"
|
||||
import "image/color"
|
||||
import "github.com/jezek/xgbutil"
|
||||
import "git.tebibyte.media/tomo/ggfx"
|
||||
import "github.com/jezek/xgbutil/xgraphics"
|
||||
import "git.tebibyte.media/tomo/tomo/canvas"
|
||||
|
||||
// Canvas satisfies the canvas.Canvas interface. It draws to an xgraphics.Image.
|
||||
type Canvas struct {
|
||||
*xgraphics.Image
|
||||
}
|
||||
|
||||
// New creates a new canvas from a bounding rectangle.
|
||||
func New (x *xgbutil.XUtil, bounds image.Rectangle) Canvas {
|
||||
return Canvas { xgraphics.New(x, bounds) }
|
||||
}
|
||||
|
||||
// NewFrom creates a new canvas from an existing xgraphics.Image.
|
||||
func NewFrom (image *xgraphics.Image) Canvas {
|
||||
return Canvas { image }
|
||||
}
|
||||
|
||||
// Pen returns a new drawing context.
|
||||
func (this Canvas) Pen () canvas.Pen {
|
||||
return pen {
|
||||
image: this.Image,
|
||||
gfx: ggfx.Image[uint8] {
|
||||
Pix: this.Image.Pix,
|
||||
Stride: this.Image.Stride,
|
||||
Bounds: this.Image.Rect,
|
||||
Width: 4,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Clip returns a sub-canvas of this canvas.
|
||||
func (this Canvas) Clip (bounds image.Rectangle) Canvas {
|
||||
return Canvas { this.Image.SubImage(bounds).(*xgraphics.Image) }
|
||||
}
|
||||
|
||||
// TODO: we need to implement:
|
||||
// - cap
|
||||
// - joint
|
||||
// - align
|
||||
|
||||
type pen struct {
|
||||
image *xgraphics.Image
|
||||
gfx ggfx.Image[uint8]
|
||||
|
||||
closed bool
|
||||
endCap canvas.Cap
|
||||
joint canvas.Joint
|
||||
weight int
|
||||
align canvas.StrokeAlign
|
||||
stroke [4]uint8
|
||||
fill [4]uint8
|
||||
}
|
||||
|
||||
func (this pen) Rectangle (bounds image.Rectangle) {
|
||||
if this.weight == 0 {
|
||||
this.gfx.FillRectangle(this.fill[:], bounds)
|
||||
} else {
|
||||
this.gfx.StrokeRectangle(this.stroke[:], this.weight, bounds)
|
||||
}
|
||||
}
|
||||
|
||||
func (this pen) Path (points ...image.Point) {
|
||||
if this.weight == 0 {
|
||||
this.gfx.FillPolygon(this.fill[:], points...)
|
||||
} else if this.closed {
|
||||
this.gfx.StrokePolygon(this.stroke[:], this.weight, points...)
|
||||
} else {
|
||||
this.gfx.PolyLine(this.stroke[:], this.weight, points...)
|
||||
}
|
||||
}
|
||||
|
||||
func (this pen) Closed (closed bool) { this.closed = closed }
|
||||
func (this pen) Cap (endCap canvas.Cap) { this.endCap = endCap }
|
||||
func (this pen) Joint (joint canvas.Joint) { this.joint = joint }
|
||||
func (this pen) StrokeWeight (weight int) { this.weight = weight }
|
||||
func (this pen) StrokeAlign (align canvas.StrokeAlign) { this.align = align }
|
||||
|
||||
func (this pen) Stroke (stroke color.Color) { this.stroke = convertColor(stroke) }
|
||||
func (this pen) Fill (fill color.Color) { this.fill = convertColor(fill) }
|
||||
|
||||
func convertColor (c color.Color) [4]uint8 {
|
||||
r, g, b, a := c.RGBA()
|
||||
return [4]uint8 {
|
||||
uint8(b >> 8),
|
||||
uint8(g >> 8),
|
||||
uint8(a >> 8),
|
||||
uint8(r >> 8),
|
||||
}
|
||||
}
|
27
canvasbox.go
Normal file
27
canvasbox.go
Normal file
@ -0,0 +1,27 @@
|
||||
package x
|
||||
|
||||
import "git.tebibyte.media/tomo/tomo"
|
||||
import "git.tebibyte.media/tomo/tomo/canvas"
|
||||
|
||||
type canvasBox struct {
|
||||
*box
|
||||
}
|
||||
|
||||
func (backend *Backend) NewCanvasBox () tomo.CanvasBox {
|
||||
return &canvasBox {
|
||||
box: backend.NewBox().(*box),
|
||||
}
|
||||
}
|
||||
|
||||
func (this *canvasBox) Box () tomo.Box {
|
||||
return this
|
||||
}
|
||||
|
||||
func (this *canvasBox) SetDrawer (drawer canvas.Drawer) {
|
||||
this.drawer = drawer
|
||||
this.invalidateDraw()
|
||||
}
|
||||
|
||||
func (this *canvasBox) Invalidate () {
|
||||
this.invalidateDraw()
|
||||
}
|
8
containerbox.go
Normal file
8
containerbox.go
Normal file
@ -0,0 +1,8 @@
|
||||
package x
|
||||
|
||||
import "git.tebibyte.media/tomo/tomo"
|
||||
|
||||
func (backend *Backend) NewContainerBox() tomo.ContainerBox {
|
||||
// TODO
|
||||
return nil
|
||||
}
|
17
go.mod
Normal file
17
go.mod
Normal file
@ -0,0 +1,17 @@
|
||||
module git.tebibyte.media/tomo/x
|
||||
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
git.tebibyte.media/tomo/ggfx v0.3.0
|
||||
git.tebibyte.media/tomo/tomo v0.7.3
|
||||
git.tebibyte.media/tomo/xgbkb v1.0.1
|
||||
github.com/jezek/xgb v1.1.0
|
||||
github.com/jezek/xgbutil v0.0.0-20230603163917-04188eb39cf0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298 // indirect
|
||||
github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966 // indirect
|
||||
golang.org/x/image v0.8.0 // indirect
|
||||
)
|
66
go.sum
Normal file
66
go.sum
Normal file
@ -0,0 +1,66 @@
|
||||
git.tebibyte.media/sashakoshka/xgbkb v1.0.0/go.mod h1:pNcE6TRO93vHd6q42SdwLSTTj25L0Yzggz7yLe0JV6Q=
|
||||
git.tebibyte.media/tomo/ggfx v0.2.0 h1:TSWfNQgnnHewwHiGC3VPFssdOIYCfgqCcOiPX4Sgv00=
|
||||
git.tebibyte.media/tomo/ggfx v0.2.0/go.mod h1:zPoz8BdVQyG2KhEmeGFQBK66V71i6Kj8oVFbrZaCwRA=
|
||||
git.tebibyte.media/tomo/ggfx v0.3.0 h1:h+RfairZTt4jT76KwmJN8OcdU7Ew0vFRqMZFqz3iHaE=
|
||||
git.tebibyte.media/tomo/ggfx v0.3.0/go.mod h1:zPoz8BdVQyG2KhEmeGFQBK66V71i6Kj8oVFbrZaCwRA=
|
||||
git.tebibyte.media/tomo/tomo v0.4.0 h1:nraUtsmYLSe8BZOolmeBuD+aaMk4duSxI84RqnzflCs=
|
||||
git.tebibyte.media/tomo/tomo v0.4.0/go.mod h1:lTwjpiHbP4UN/kFw+6FwhG600B+PMKVtMOr7wpd5IUY=
|
||||
git.tebibyte.media/tomo/tomo v0.5.0 h1:bfHNExPewlt+n7nq8LvNiAbemqSllrCY/tAI08r8sAo=
|
||||
git.tebibyte.media/tomo/tomo v0.5.0/go.mod h1:lTwjpiHbP4UN/kFw+6FwhG600B+PMKVtMOr7wpd5IUY=
|
||||
git.tebibyte.media/tomo/tomo v0.5.1 h1:APOTY+YSV8JJwNmJsKFYzBYLPUy3DqNr49rrSspOKZ8=
|
||||
git.tebibyte.media/tomo/tomo v0.5.1/go.mod h1:lTwjpiHbP4UN/kFw+6FwhG600B+PMKVtMOr7wpd5IUY=
|
||||
git.tebibyte.media/tomo/tomo v0.6.0 h1:/gjY6neXEqyKQ2Ye05mZi3yIOvsRVyIKSddvCySGN2Y=
|
||||
git.tebibyte.media/tomo/tomo v0.6.0/go.mod h1:lTwjpiHbP4UN/kFw+6FwhG600B+PMKVtMOr7wpd5IUY=
|
||||
git.tebibyte.media/tomo/tomo v0.6.1 h1:XdtHfF2xhz9pZXqyrwSsPaore/8PHVqFrnT4NwlBOhY=
|
||||
git.tebibyte.media/tomo/tomo v0.6.1/go.mod h1:lTwjpiHbP4UN/kFw+6FwhG600B+PMKVtMOr7wpd5IUY=
|
||||
git.tebibyte.media/tomo/tomo v0.7.0 h1:dUYBB/gZzmkiKR8Cq/nmEQGwMqVE01CnQFtvjmInif0=
|
||||
git.tebibyte.media/tomo/tomo v0.7.0/go.mod h1:lTwjpiHbP4UN/kFw+6FwhG600B+PMKVtMOr7wpd5IUY=
|
||||
git.tebibyte.media/tomo/tomo v0.7.1 h1:CHOBGel7Acp88cVW+5SEIx41cRdwuuP/niSSp9/CRRg=
|
||||
git.tebibyte.media/tomo/tomo v0.7.1/go.mod h1:lTwjpiHbP4UN/kFw+6FwhG600B+PMKVtMOr7wpd5IUY=
|
||||
git.tebibyte.media/tomo/tomo v0.7.2 h1:15dMJm4Sm339b23o9RZSq87u99SaF2q+b5CRB5P58fA=
|
||||
git.tebibyte.media/tomo/tomo v0.7.2/go.mod h1:lTwjpiHbP4UN/kFw+6FwhG600B+PMKVtMOr7wpd5IUY=
|
||||
git.tebibyte.media/tomo/tomo v0.7.3 h1:eHwuYKe+0nLWoEfPZid8njirxmWY3dFmdY+PsPp1RN0=
|
||||
git.tebibyte.media/tomo/tomo v0.7.3/go.mod h1:lTwjpiHbP4UN/kFw+6FwhG600B+PMKVtMOr7wpd5IUY=
|
||||
git.tebibyte.media/tomo/xgbkb v1.0.1 h1:b3HDUopjdQp1MZrb5Vpil4bOtk3NnNXtfQW27Blw2kE=
|
||||
git.tebibyte.media/tomo/xgbkb v1.0.1/go.mod h1:P5Du0yo5hUsojchW08t+Mds0XPIJXwMi733ZfklzjRw=
|
||||
github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298 h1:1qlsVAQJXZHsaM8b6OLVo6muQUQd4CwkH/D3fnnbHXA=
|
||||
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/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-20230603163917-04188eb39cf0 h1:Pf/0BAbppEOq4azPH6fnvUX2dycAwZdGkdxFn25j44c=
|
||||
github.com/jezek/xgbutil v0.0.0-20230603163917-04188eb39cf0/go.mod h1:AHecLyFNy6AN9f/+0AH/h1MI7X1+JL5bmCz4XlVZk7Y=
|
||||
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.8.0 h1:agUcRXV/+w6L9ryntYYsF2x9fQTMd4T8fiiYXAVW6Jg=
|
||||
golang.org/x/image v0.8.0/go.mod h1:PwLxp3opCYg4WR2WO9P0L6ESnsD6bLTWcw8zanLMVFM=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
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/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
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/sync v0.1.0/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/sys v0.5.0/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/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
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.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
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/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
20
system.go
Normal file
20
system.go
Normal file
@ -0,0 +1,20 @@
|
||||
package x
|
||||
|
||||
import "git.tebibyte.media/tomo/tomo"
|
||||
|
||||
type anyBox interface {
|
||||
tomo.Box
|
||||
invalidateDraw ()
|
||||
invalidateLayout ()
|
||||
}
|
||||
|
||||
func (window *window) SetRoot (root tomo.Object) {
|
||||
|
||||
}
|
||||
|
||||
func (window *window) focus (bx anyBox) {
|
||||
if window.focused == bx { return }
|
||||
window.focused.invalidateDraw()
|
||||
window.focused = bx
|
||||
bx.invalidateDraw()
|
||||
}
|
8
textbox.go
Normal file
8
textbox.go
Normal file
@ -0,0 +1,8 @@
|
||||
package x
|
||||
|
||||
import "git.tebibyte.media/tomo/tomo"
|
||||
|
||||
func (backend *Backend) NewTextBox() tomo.TextBox {
|
||||
// TODO
|
||||
return nil
|
||||
}
|
329
window.go
Normal file
329
window.go
Normal file
@ -0,0 +1,329 @@
|
||||
package x
|
||||
|
||||
import "image"
|
||||
// import "errors"
|
||||
|
||||
import "git.tebibyte.media/tomo/tomo"
|
||||
import "git.tebibyte.media/tomo/tomo/data"
|
||||
import "git.tebibyte.media/tomo/tomo/input"
|
||||
import "git.tebibyte.media/tomo/tomo/event"
|
||||
|
||||
import "github.com/jezek/xgb/xproto"
|
||||
import "github.com/jezek/xgbutil/ewmh"
|
||||
import "github.com/jezek/xgbutil/icccm"
|
||||
// import "github.com/jezek/xgbutil/xprop"
|
||||
import "github.com/jezek/xgbutil/xevent"
|
||||
import "github.com/jezek/xgbutil/xwindow"
|
||||
import "github.com/jezek/xgbutil/keybind"
|
||||
import "github.com/jezek/xgbutil/mousebind"
|
||||
import "github.com/jezek/xgbutil/xgraphics"
|
||||
|
||||
type mainWindow struct { *window }
|
||||
type window struct {
|
||||
backend *Backend
|
||||
xWindow *xwindow.Window
|
||||
xCanvas *xgraphics.Image
|
||||
|
||||
title string
|
||||
|
||||
modalParent *window
|
||||
hasModal bool
|
||||
shy bool
|
||||
|
||||
metrics struct {
|
||||
bounds image.Rectangle
|
||||
}
|
||||
|
||||
modifiers input.Modifiers
|
||||
mousePosition image.Point
|
||||
|
||||
onClose event.FuncBroadcaster
|
||||
|
||||
root anyBox
|
||||
focused anyBox
|
||||
}
|
||||
|
||||
func (backend *Backend) NewWindow (
|
||||
bounds image.Rectangle,
|
||||
) (
|
||||
output tomo.MainWindow,
|
||||
err error,
|
||||
) {
|
||||
if backend == nil { panic("nil backend") }
|
||||
window, err := backend.newWindow(bounds, false)
|
||||
|
||||
output = mainWindow { window: window }
|
||||
return output, err
|
||||
}
|
||||
|
||||
func (backend *Backend) newWindow (
|
||||
bounds image.Rectangle,
|
||||
override bool,
|
||||
) (
|
||||
output *window,
|
||||
err error,
|
||||
) {
|
||||
if bounds.Dx() == 0 { bounds.Max.X = bounds.Min.X + 8 }
|
||||
if bounds.Dy() == 0 { bounds.Max.Y = bounds.Min.Y + 8 }
|
||||
|
||||
window := &window { backend: backend }
|
||||
|
||||
window.xWindow, err = xwindow.Generate(backend.x)
|
||||
if err != nil { return }
|
||||
|
||||
if override {
|
||||
err = window.xWindow.CreateChecked (
|
||||
backend.x.RootWin(),
|
||||
bounds.Min.X, bounds.Min.Y, bounds.Dx(), bounds.Dy(),
|
||||
xproto.CwOverrideRedirect, 1)
|
||||
} else {
|
||||
err = window.xWindow.CreateChecked (
|
||||
backend.x.RootWin(),
|
||||
bounds.Min.X, bounds.Min.Y, bounds.Dx(), bounds.Dy(), 0)
|
||||
}
|
||||
if err != nil { return }
|
||||
|
||||
err = window.xWindow.Listen (
|
||||
xproto.EventMaskExposure,
|
||||
xproto.EventMaskStructureNotify,
|
||||
xproto.EventMaskPropertyChange,
|
||||
xproto.EventMaskPointerMotion,
|
||||
xproto.EventMaskKeyPress,
|
||||
xproto.EventMaskKeyRelease,
|
||||
xproto.EventMaskButtonPress,
|
||||
xproto.EventMaskButtonRelease)
|
||||
if err != nil { return }
|
||||
|
||||
window.xWindow.WMGracefulClose (func (xWindow *xwindow.Window) {
|
||||
window.Close()
|
||||
})
|
||||
|
||||
// xevent.ExposeFun(window.handleExpose).
|
||||
// Connect(backend.x, window.xWindow.Id)
|
||||
// xevent.ConfigureNotifyFun(window.handleConfigureNotify).
|
||||
// Connect(backend.x, window.xWindow.Id)
|
||||
// xevent.KeyPressFun(window.handleKeyPress).
|
||||
// Connect(backend.x, window.xWindow.Id)
|
||||
// xevent.KeyReleaseFun(window.handleKeyRelease).
|
||||
// Connect(backend.x, window.xWindow.Id)
|
||||
// xevent.ButtonPressFun(window.handleButtonPress).
|
||||
// Connect(backend.x, window.xWindow.Id)
|
||||
// xevent.ButtonReleaseFun(window.handleButtonRelease).
|
||||
// Connect(backend.x, window.xWindow.Id)
|
||||
// xevent.MotionNotifyFun(window.handleMotionNotify).
|
||||
// Connect(backend.x, window.xWindow.Id)
|
||||
// xevent.SelectionNotifyFun(window.handleSelectionNotify).
|
||||
// Connect(backend.x, window.xWindow.Id)
|
||||
// xevent.PropertyNotifyFun(window.handlePropertyNotify).
|
||||
// Connect(backend.x, window.xWindow.Id)
|
||||
// xevent.SelectionClearFun(window.handleSelectionClear).
|
||||
// Connect(backend.x, window.xWindow.Id)
|
||||
// xevent.SelectionRequestFun(window.handleSelectionRequest).
|
||||
// Connect(backend.x, window.xWindow.Id)
|
||||
|
||||
window.metrics.bounds = bounds
|
||||
window.setMinimumSize(8, 8)
|
||||
|
||||
// window.reallocateCanvas()
|
||||
|
||||
backend.windows[window.xWindow.Id] = window
|
||||
|
||||
output = window
|
||||
return
|
||||
}
|
||||
|
||||
func (window *window) SetTitle (title string) {
|
||||
window.title = title
|
||||
ewmh .WmNameSet (window.backend.x, window.xWindow.Id, title)
|
||||
icccm.WmNameSet (window.backend.x, window.xWindow.Id, title)
|
||||
icccm.WmIconNameSet (window.backend.x, window.xWindow.Id, title)
|
||||
}
|
||||
|
||||
func (window *window) SetIcon (sizes []image.Image) {
|
||||
wmIcons := []ewmh.WmIcon { }
|
||||
|
||||
for _, icon := range sizes {
|
||||
width := icon.Bounds().Max.X
|
||||
height := icon.Bounds().Max.Y
|
||||
wmIcon := ewmh.WmIcon {
|
||||
Width: uint(width),
|
||||
Height: uint(height),
|
||||
Data: make([]uint, width * height),
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
ewmh.WmIconSet (
|
||||
window.backend.x,
|
||||
window.xWindow.Id,
|
||||
wmIcons)
|
||||
}
|
||||
|
||||
func (window *window) NewMenu (bounds image.Rectangle) (tomo.Window, error) {
|
||||
menu, err := window.backend.newWindow (
|
||||
bounds.Add(window.metrics.bounds.Min), true)
|
||||
menu.shy = true
|
||||
icccm.WmTransientForSet (
|
||||
window.backend.x,
|
||||
menu.xWindow.Id,
|
||||
window.xWindow.Id)
|
||||
menu.setType("POPUP_MENU")
|
||||
// menu.inheritProperties(window)
|
||||
return menu, err
|
||||
}
|
||||
|
||||
func (window *window) NewModal (bounds image.Rectangle) (tomo.Window, error) {
|
||||
modal, err := window.backend.newWindow (
|
||||
bounds.Add(window.metrics.bounds.Min), false)
|
||||
icccm.WmTransientForSet (
|
||||
window.backend.x,
|
||||
modal.xWindow.Id,
|
||||
window.xWindow.Id)
|
||||
ewmh.WmStateSet (
|
||||
window.backend.x,
|
||||
modal.xWindow.Id,
|
||||
[]string { "_NET_WM_STATE_MODAL" })
|
||||
modal.modalParent = window
|
||||
window.hasModal = true
|
||||
// modal.inheritProperties(window)
|
||||
return modal, err
|
||||
}
|
||||
|
||||
func (window mainWindow) NewChild (bounds image.Rectangle) (tomo.Window, error) {
|
||||
child, err := window.backend.newWindow (
|
||||
bounds.Add(window.metrics.bounds.Min), false)
|
||||
if err != nil { return nil, err }
|
||||
child.setClientLeader(window.window)
|
||||
window.setClientLeader(window.window)
|
||||
icccm.WmTransientForSet (
|
||||
window.backend.x,
|
||||
window.xWindow.Id,
|
||||
window.xWindow.Id)
|
||||
window.setType("UTILITY")
|
||||
// window.inheritProperties(window.window)
|
||||
return window, err
|
||||
}
|
||||
|
||||
func (window *window) Widget () (tomo.Window, error) {
|
||||
// TODO
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (window *window) Copy (data.Data) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
func (window *window) Paste (callback func (data.Data, error), accept ...data.Mime) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
func (window *window) Show () {
|
||||
// if window.child == nil {
|
||||
// window.xCanvas.For (func (x, y int) xgraphics.BGRA {
|
||||
// return xgraphics.BGRA { }
|
||||
// })
|
||||
//
|
||||
// window.pushRegion(window.xCanvas.Bounds())
|
||||
// }
|
||||
|
||||
window.xWindow.Map()
|
||||
if window.shy { window.grabInput() }
|
||||
}
|
||||
|
||||
func (window *window) Hide () {
|
||||
window.xWindow.Unmap()
|
||||
if window.shy { window.ungrabInput() }
|
||||
}
|
||||
|
||||
func (window *window) Close () {
|
||||
xevent .Detach(window.backend.x, window.xWindow.Id)
|
||||
keybind .Detach(window.backend.x, window.xWindow.Id)
|
||||
mousebind.Detach(window.backend.x, window.xWindow.Id)
|
||||
|
||||
window.onClose.Broadcast()
|
||||
if window.modalParent != nil {
|
||||
// we are a modal dialog, so unlock the parent
|
||||
window.modalParent.hasModal = false
|
||||
}
|
||||
window.Hide()
|
||||
// window.Adopt(nil)
|
||||
delete(window.backend.windows, window.xWindow.Id)
|
||||
window.xWindow.Destroy()
|
||||
}
|
||||
|
||||
func (window *window) OnClose (callback func ()) event.Cookie {
|
||||
return window.onClose.Connect(callback)
|
||||
}
|
||||
|
||||
func (window *window) grabInput () {
|
||||
keybind.GrabKeyboard(window.backend.x, window.xWindow.Id)
|
||||
mousebind.GrabPointer (
|
||||
window.backend.x,
|
||||
window.xWindow.Id,
|
||||
window.backend.x.RootWin(), 0)
|
||||
}
|
||||
|
||||
func (window *window) ungrabInput () {
|
||||
keybind.UngrabKeyboard(window.backend.x)
|
||||
mousebind.UngrabPointer(window.backend.x)
|
||||
}
|
||||
|
||||
func (window *window) setType (ty string) error {
|
||||
return ewmh.WmWindowTypeSet (
|
||||
window.backend.x,
|
||||
window.xWindow.Id,
|
||||
[]string { "_NET_WM_WINDOW_TYPE_" + ty })
|
||||
}
|
||||
|
||||
func (window *window) setClientLeader (leader *window) error {
|
||||
hints, _ := icccm.WmHintsGet(window.backend.x, window.xWindow.Id)
|
||||
if hints == nil {
|
||||
hints = &icccm.Hints { }
|
||||
}
|
||||
hints.Flags |= icccm.HintWindowGroup
|
||||
hints.WindowGroup = leader.xWindow.Id
|
||||
return icccm.WmHintsSet (
|
||||
window.backend.x,
|
||||
window.xWindow.Id,
|
||||
hints)
|
||||
}
|
||||
|
||||
func (window *window) setMinimumSize (width, height int) {
|
||||
if width < 8 { width = 8 }
|
||||
if height < 8 { height = 8 }
|
||||
icccm.WmNormalHintsSet (
|
||||
window.backend.x,
|
||||
window.xWindow.Id,
|
||||
&icccm.NormalHints {
|
||||
Flags: icccm.SizeHintPMinSize,
|
||||
MinWidth: uint(width),
|
||||
MinHeight: uint(height),
|
||||
})
|
||||
newWidth := window.metrics.bounds.Dx()
|
||||
newHeight := window.metrics.bounds.Dy()
|
||||
if newWidth < width { newWidth = width }
|
||||
if newHeight < height { newHeight = height }
|
||||
if newWidth != window.metrics.bounds.Dx() ||
|
||||
newHeight != window.metrics.bounds.Dy() {
|
||||
window.xWindow.Resize(newWidth, newHeight)
|
||||
}
|
||||
}
|
17
x/plugin.go
Normal file
17
x/plugin.go
Normal file
@ -0,0 +1,17 @@
|
||||
// Plugin x provides the X11 backend as a plugin.
|
||||
package main
|
||||
|
||||
import "git.tebibyte.media/tomo/x"
|
||||
import "git.tebibyte.media/tomo/tomo"
|
||||
|
||||
func init () {
|
||||
tomo.Register(x.NewBackend)
|
||||
}
|
||||
|
||||
func Name () string {
|
||||
return "X"
|
||||
}
|
||||
|
||||
func Description () string {
|
||||
return "Provides an X11 backend."
|
||||
}
|
Reference in New Issue
Block a user