16 Commits

15 changed files with 244 additions and 49 deletions

View File

@@ -3,12 +3,3 @@
[![Go Reference](https://pkg.go.dev/badge/git.tebibyte.media/tomo/x.svg)](https://pkg.go.dev/git.tebibyte.media/tomo/x) [![Go Reference](https://pkg.go.dev/badge/git.tebibyte.media/tomo/x.svg)](https://pkg.go.dev/git.tebibyte.media/tomo/x)
An X11 backend for Tomo. An X11 backend for Tomo.
## Installation
```
cd x/x
go build -buildmode=plugin .
mkdir -p ~/.local/lib/tomo/plugins
mv x.so ~/.local/lib/tomo/plugins
```

87
box.go
View File

@@ -9,23 +9,30 @@ import "git.tebibyte.media/tomo/tomo/input"
import "git.tebibyte.media/tomo/tomo/event" import "git.tebibyte.media/tomo/tomo/event"
import "git.tebibyte.media/tomo/tomo/canvas" import "git.tebibyte.media/tomo/tomo/canvas"
type textureMode int; const (
textureModeTile textureMode = iota
textureModeCenter
)
type box struct { type box struct {
backend *Backend backend *Backend
parent parent parent parent
outer anyBox outer anyBox
bounds image.Rectangle visible bool
minSize image.Point bounds image.Rectangle
userMinSize image.Point minSize image.Point
userMinSize image.Point
innerClippingBounds image.Rectangle innerClippingBounds image.Rectangle
minSizeQueued bool minSizeQueued bool
focusQueued *bool focusQueued *bool
padding tomo.Inset padding tomo.Inset
border []tomo.Border border []tomo.Border
color color.Color color color.Color
texture *xcanvas.Texture texture *xcanvas.Texture
textureMode textureMode
dndData data.Data dndData data.Data
dndAccept []data.Mime dndAccept []data.Mime
@@ -116,12 +123,20 @@ func (this *box) SetColor (c color.Color) {
this.invalidateDraw() this.invalidateDraw()
} }
func (this *box) SetTexture (texture canvas.Texture) { func (this *box) SetTextureTile (texture canvas.Texture) {
if this.texture == texture { return } if this.texture == texture && this.textureMode == textureModeTile { return }
this.textureMode = textureModeTile
this.texture = xcanvas.AssertTexture(texture) this.texture = xcanvas.AssertTexture(texture)
this.invalidateDraw() this.invalidateDraw()
} }
func (this *box) SetTextureCenter (texture canvas.Texture) {
if this.texture == texture && this.textureMode == textureModeCenter { return }
this.texture = xcanvas.AssertTexture(texture)
this.textureMode = textureModeCenter
this.invalidateDraw()
}
func (this *box) SetBorder (borders ...tomo.Border) { func (this *box) SetBorder (borders ...tomo.Border) {
previousBorderSum := this.borderSum() previousBorderSum := this.borderSum()
previousBorders := this.border previousBorders := this.border
@@ -160,6 +175,16 @@ func (this *box) SetPadding (padding tomo.Inset) {
this.invalidateMinimum() this.invalidateMinimum()
} }
func (this *box) SetVisible (visible bool) {
if this.visible == visible { return }
this.visible = visible
this.invalidateMinimum()
}
func (this *box) Visible () bool {
return this.visible
}
func (this *box) SetDNDData (dat data.Data) { func (this *box) SetDNDData (dat data.Data) {
this.dndData = dat this.dndData = dat
} }
@@ -299,13 +324,34 @@ func (this *box) handleKeyUp (key input.Key, numberPad bool) {
func (this *box) Draw (can canvas.Canvas) { func (this *box) Draw (can canvas.Canvas) {
if can == nil { return } if can == nil { return }
pen := can.Pen() pen := can.Pen()
pen.Fill(this.color) bounds := this.Bounds()
pen.Texture(this.texture)
// background
pen.Fill(this.color)
if this.textureMode == textureModeTile {
pen.Texture(this.texture)
}
if this.transparent() && this.parent != nil { if this.transparent() && this.parent != nil {
this.parent.drawBackgroundPart(can) this.parent.drawBackgroundPart(can)
} }
pen.Rectangle(this.Bounds()) pen.Rectangle(bounds)
// centered texture
if this.textureMode == textureModeCenter {
textureBounds := this.texture.Bounds()
textureOrigin :=
bounds.Min.
Add(image.Pt (
bounds.Dx() / 2,
bounds.Dy() / 2)).
Sub(image.Pt (
textureBounds.Dx() / 2,
textureBounds.Dy() / 2))
pen.Fill(color.Transparent)
pen.Texture(this.texture)
pen.Rectangle(textureBounds.Sub(textureBounds.Min).Add(textureOrigin))
}
} }
func (this *box) drawBorders (can canvas.Canvas) { func (this *box) drawBorders (can canvas.Canvas) {
@@ -316,7 +362,7 @@ func (this *box) drawBorders (can canvas.Canvas) {
rectangle := func (x0, y0, x1, y1 int, c color.Color) { rectangle := func (x0, y0, x1, y1 int, c color.Color) {
area := image.Rect(x0, y0, x1, y1) area := image.Rect(x0, y0, x1, y1)
if transparent(c) && this.parent != nil { if transparent(c) && this.parent != nil {
this.parent.drawBackgroundPart(can.Clip(area)) this.parent.drawBackgroundPart(can.SubCanvas(area))
} }
pen.Fill(c) pen.Fill(c)
pen.Rectangle(area) pen.Rectangle(area)
@@ -384,7 +430,7 @@ func (this *box) doDraw () {
if this.canvas == nil { return } if this.canvas == nil { return }
if this.drawer != nil { if this.drawer != nil {
this.drawBorders(this.canvas) this.drawBorders(this.canvas)
this.drawer.Draw(this.canvas.Clip(this.innerClippingBounds)) this.drawer.Draw(this.canvas.SubCanvas(this.innerClippingBounds))
} }
} }
@@ -397,7 +443,7 @@ func (this *box) doLayout () {
if this.parent == nil { this.canvas = nil; return } if this.parent == nil { this.canvas = nil; return }
parentCanvas := this.parent.canvas() parentCanvas := this.parent.canvas()
if parentCanvas == nil { this.canvas = nil; return } if parentCanvas == nil { this.canvas = nil; return }
this.canvas = parentCanvas.Clip(this.bounds) this.canvas = parentCanvas.SubCanvas(this.bounds)
} }
func (this *box) setParent (parent parent) { func (this *box) setParent (parent parent) {
@@ -429,12 +475,12 @@ func (this *box) recursiveRedo () {
func (this *box) invalidateLayout () { func (this *box) invalidateLayout () {
if this.parent == nil || this.parent.window() == nil { return } if this.parent == nil || this.parent.window() == nil { return }
this.parent.window().invalidateLayout(this.outer) this.window().invalidateLayout(this.outer)
} }
func (this *box) invalidateDraw () { func (this *box) invalidateDraw () {
if this.parent == nil || this.parent.window() == nil { return } if this.parent == nil || this.parent.window() == nil { return }
this.parent.window().invalidateDraw(this.outer) this.window().invalidateDraw(this.outer)
} }
func (this *box) invalidateMinimum () { func (this *box) invalidateMinimum () {
@@ -442,7 +488,7 @@ func (this *box) invalidateMinimum () {
this.minSizeQueued = true this.minSizeQueued = true
return return
} }
this.parent.window().invalidateMinimum(this.outer) this.window().invalidateMinimum(this.outer)
} }
func (this *box) canBeFocused () bool { func (this *box) canBeFocused () bool {
@@ -469,3 +515,8 @@ func (this *box) transparent () bool {
return transparent(this.color) && return transparent(this.color) &&
(this.texture == nil || !this.texture.Opaque()) (this.texture == nil || !this.texture.Opaque())
} }
func (this *box) window () *window {
if this.parent == nil { return nil }
return this.parent.window()
}

View File

@@ -32,8 +32,8 @@ func (this *Canvas) Pen () canvas.Pen {
} }
} }
// Clip returns a sub-canvas of this canvas. // SubCanvas returns a subset of this canvas that points to the same data.
func (this *Canvas) Clip (bounds image.Rectangle) canvas.Canvas { func (this *Canvas) SubCanvas (bounds image.Rectangle) canvas.Canvas {
this.assert() this.assert()
subImage := this.Image.SubImage(bounds) subImage := this.Image.SubImage(bounds)
if subImage == nil { return nil } if subImage == nil { return nil }

View File

@@ -86,8 +86,8 @@ func (this *Texture) Close () error {
return nil return nil
} }
// Clip returns a subset of this texture that points to the same data. // SubTexture returns a subset of this texture that points to the same data.
func (this *Texture) Clip (bounds image.Rectangle) canvas.Texture { func (this *Texture) SubTexture (bounds image.Rectangle) canvas.Texture {
clipped := *this clipped := *this
clipped.rect = bounds clipped.rect = bounds
return &clipped return &clipped

View File

@@ -31,5 +31,5 @@ func (this *canvasBox) Invalidate () {
func (this *canvasBox) Draw (can canvas.Canvas) { func (this *canvasBox) Draw (can canvas.Canvas) {
this.box.Draw(can) this.box.Draw(can)
this.userDrawer.Draw ( this.userDrawer.Draw (
can.Clip(this.padding.Apply(this.innerClippingBounds))) can.SubCanvas(this.padding.Apply(this.innerClippingBounds)))
} }

View File

@@ -36,9 +36,15 @@ func (this *containerBox) SetColor (c color.Color) {
this.invalidateTransparentChildren() this.invalidateTransparentChildren()
} }
func (this *containerBox) SetTexture (texture canvas.Texture) { func (this *containerBox) SetTextureTile (texture canvas.Texture) {
if this.texture == texture { return } if this.texture == texture { return }
this.box.SetTexture(texture) this.box.SetTextureTile(texture)
this.invalidateTransparentChildren()
}
func (this *containerBox) SetTextureCenter (texture canvas.Texture) {
if this.texture == texture { return }
this.box.SetTextureTile(texture)
this.invalidateTransparentChildren() this.invalidateTransparentChildren()
} }
@@ -104,7 +110,7 @@ func (this *containerBox) Add (child tomo.Object) {
this.invalidateMinimum() this.invalidateMinimum()
} }
func (this *containerBox) Delete (child tomo.Object) { func (this *containerBox) Remove (child tomo.Object) {
box := assertAnyBox(child.GetBox()) box := assertAnyBox(child.GetBox())
index := indexOf(this.children, tomo.Box(box)) index := indexOf(this.children, tomo.Box(box))
if index < 0 { return } if index < 0 { return }
@@ -167,7 +173,7 @@ func (this *containerBox) Draw (can canvas.Canvas) {
rocks[index] = box.Bounds() rocks[index] = box.Bounds()
} }
for _, tile := range canvas.Shatter(this.bounds, rocks...) { for _, tile := range canvas.Shatter(this.bounds, rocks...) {
clipped := can.Clip(tile) clipped := can.SubCanvas(tile)
if this.transparent() && this.parent != nil { if this.transparent() && this.parent != nil {
this.parent.drawBackgroundPart(clipped) this.parent.drawBackgroundPart(clipped)
} }

18
examples/blank/main.go Normal file
View File

@@ -0,0 +1,18 @@
package main
import "image"
import "git.tebibyte.media/tomo/x"
import "git.tebibyte.media/tomo/tomo"
func main () {
tomo.Register(0, x.NewBackend)
err := tomo.Run(run)
if err != nil { panic(err) }
}
func run () {
window, err := tomo.NewWindow(image.Rect(0, 0, 200, 300))
if err != nil { panic(err) }
window.OnClose(tomo.Stop)
window.SetVisible(true)
}

29
examples/text/main.go Normal file
View File

@@ -0,0 +1,29 @@
package main
import "image"
import "image/color"
import "git.tebibyte.media/tomo/x"
import "git.tebibyte.media/tomo/tomo"
import "golang.org/x/image/font/basicfont"
func main () {
tomo.Register(0, x.NewBackend)
err := tomo.Run(run)
if err != nil { panic(err) }
}
func run () {
window, err := tomo.NewWindow(image.Rectangle { })
if err != nil { panic(err) }
text := tomo.NewTextBox()
text.SetText("hello, world!")
text.SetTextColor(color.White)
text.SetColor(color.Black)
text.SetFace(basicfont.Face7x13)
text.SetPadding(tomo.I(8))
window.SetRoot(text)
window.OnClose(tomo.Stop)
window.SetVisible(true)
}

83
examples/texture/main.go Normal file
View File

@@ -0,0 +1,83 @@
package main
import "math"
import "image"
import "math/rand"
import "image/color"
import "git.tebibyte.media/tomo/x"
import "git.tebibyte.media/tomo/tomo"
func main () {
tomo.Register(0, x.NewBackend)
err := tomo.Run(run)
if err != nil { panic(err) }
}
func run () {
window, err := tomo.NewWindow(image.Rect(0, 0, 256, 256))
if err != nil { panic(err) }
texture := tomo.NewTexture(coolTexture())
box := tomo.NewBox()
box.SetColor(color.Black)
box.SetTextureCenter(texture)
box.SetBorder (
tomo.Border {
Color: [4] color.Color {
color.Black, color.Black,
color.Black, color.Black },
Width: tomo.I(2),
},
tomo.Border {
Color: [4] color.Color {
color.White, color.White,
color.White, color.White },
Width: tomo.I(2),
})
window.SetRoot(box)
window.OnClose(tomo.Stop)
window.SetVisible(true)
}
func coolTexture () image.Image {
// this picture IS COOL because i spent AN HOUR on it when i could have
// been FIXING BUGS!
speedX := 0.015
speedY := 0.035
bitmap := image.NewRGBA (image.Rect(0, 0, 200, 200))
for y := bitmap.Bounds().Min.Y; y < bitmap.Bounds().Max.Y; y++ {
for x := bitmap.Bounds().Min.X; x < bitmap.Bounds().Max.X; x++ {
value := ((
math.Sin(float64(y) * speedY) +
math.Cos(float64(x) * speedX)) + 2) / 4
value *= 0.7
r := value * 0.7 + 0.3
g := math.Sin(value * 7) * 0.7
b := (1 - value) * 0.7
noise := math.Mod(rand.Float64(), 1)
noise = math.Pow(noise, 2) + noise * 0.5
noise *= 0.05
channel := func (f float64) uint8 {
contrast := 1.4
f = f * (contrast) - ((contrast - 1) / 2)
if f < 0 { f = 0 }
if f > 1 { f = 1 }
return uint8(f * 255)
}
bitmap.Set(x, y, color.RGBA {
R: channel(r + noise),
G: channel(g + noise),
B: channel(b + noise),
A: 255,
})
}}
return bitmap
}

2
go.mod
View File

@@ -3,7 +3,7 @@ module git.tebibyte.media/tomo/x
go 1.20 go 1.20
require ( require (
git.tebibyte.media/tomo/tomo v0.31.0 git.tebibyte.media/tomo/tomo v0.33.0
git.tebibyte.media/tomo/typeset v0.7.1 git.tebibyte.media/tomo/typeset v0.7.1
git.tebibyte.media/tomo/xgbkb v1.0.1 git.tebibyte.media/tomo/xgbkb v1.0.1
github.com/jezek/xgb v1.1.0 github.com/jezek/xgb v1.1.0

4
go.sum
View File

@@ -1,6 +1,6 @@
git.tebibyte.media/sashakoshka/xgbkb v1.0.0/go.mod h1:pNcE6TRO93vHd6q42SdwLSTTj25L0Yzggz7yLe0JV6Q= git.tebibyte.media/sashakoshka/xgbkb v1.0.0/go.mod h1:pNcE6TRO93vHd6q42SdwLSTTj25L0Yzggz7yLe0JV6Q=
git.tebibyte.media/tomo/tomo v0.31.0 h1:LHPpj3AWycochnC8F441aaRNS6Tq6w6WnBrp/LGjyhM= git.tebibyte.media/tomo/tomo v0.33.0 h1:BBm1oRsogBLeqVKeevNqG9RPCOdmbGeiQM/9hd2GHE8=
git.tebibyte.media/tomo/tomo v0.31.0/go.mod h1:C9EzepS9wjkTJjnZaPBh22YvVPyA4hbBAJVU20Rdmps= git.tebibyte.media/tomo/tomo v0.33.0/go.mod h1:C9EzepS9wjkTJjnZaPBh22YvVPyA4hbBAJVU20Rdmps=
git.tebibyte.media/tomo/typeset v0.7.1 h1:aZrsHwCG5ZB4f5CruRFsxLv5ezJUCFUFsQJJso2sXQ8= git.tebibyte.media/tomo/typeset v0.7.1 h1:aZrsHwCG5ZB4f5CruRFsxLv5ezJUCFUFsQJJso2sXQ8=
git.tebibyte.media/tomo/typeset v0.7.1/go.mod h1:PwDpSdBF3l/EzoIsa2ME7QffVVajnTHZN6l3MHEGe1g= git.tebibyte.media/tomo/typeset v0.7.1/go.mod h1:PwDpSdBF3l/EzoIsa2ME7QffVVajnTHZN6l3MHEGe1g=
git.tebibyte.media/tomo/xgbkb v1.0.1 h1:b3HDUopjdQp1MZrb5Vpil4bOtk3NnNXtfQW27Blw2kE= git.tebibyte.media/tomo/xgbkb v1.0.1 h1:b3HDUopjdQp1MZrb5Vpil4bOtk3NnNXtfQW27Blw2kE=

8
surfacebox.go Normal file
View File

@@ -0,0 +1,8 @@
package x
import "errors"
import "git.tebibyte.media/tomo/tomo"
func (backend *Backend) NewSurfaceBox() (tomo.SurfaceBox, error) {
return nil, errors.New("SurfaceBox not implemented yet")
}

View File

@@ -241,7 +241,9 @@ func (window *window) afterEvent () {
// set child bounds // set child bounds
childBounds := window.metrics.bounds childBounds := window.metrics.bounds
childBounds = childBounds.Sub(childBounds.Min) childBounds = childBounds.Sub(childBounds.Min)
window.root.SetBounds(childBounds) if window.root != nil {
window.root.SetBounds(childBounds)
}
// full relayout/redraw // full relayout/redraw
if window.root != nil { if window.root != nil {

View File

@@ -227,7 +227,6 @@ func (this *textBox) textOffset () image.Point {
} }
func (this *textBox) handleFocusLeave () { func (this *textBox) handleFocusLeave () {
this.dot = text.EmptyDot(0)
this.on.dotChange.Broadcast() this.on.dotChange.Broadcast()
this.invalidateDraw() this.invalidateDraw()
this.box.handleFocusLeave() this.box.handleFocusLeave()

View File

@@ -32,6 +32,7 @@ type window struct {
modalParent *window modalParent *window
hasModal bool hasModal bool
shy bool shy bool
visible bool
metrics struct { metrics struct {
bounds image.Rectangle bounds image.Rectangle
@@ -265,14 +266,21 @@ func (window *window) Paste (callback func (data.Data, error), accept ...data.Mi
// TODO // TODO
} }
func (window *window) Show () { func (window *window) SetVisible (visible bool) {
window.xWindow.Map() if window.visible == visible { return }
if window.shy { window.grabInput() } window.visible = visible
if window.visible {
window.xWindow.Map()
if window.shy { window.grabInput() }
} else {
window.xWindow.Unmap()
if window.shy { window.ungrabInput() }
}
} }
func (window *window) Hide () { func (window *window) Visible () bool {
window.xWindow.Unmap() return window.visible
if window.shy { window.ungrabInput() }
} }
func (window *window) Close () { func (window *window) Close () {
@@ -285,7 +293,7 @@ func (window *window) Close () {
// we are a modal dialog, so unlock the parent // we are a modal dialog, so unlock the parent
window.modalParent.hasModal = false window.modalParent.hasModal = false
} }
window.Hide() window.SetVisible(false)
window.SetRoot(nil) window.SetRoot(nil)
delete(window.backend.windows, window.xWindow.Id) delete(window.backend.windows, window.xWindow.Id)
window.xWindow.Destroy() window.xWindow.Destroy()
@@ -369,7 +377,7 @@ func (window *window) pushRegion (region image.Rectangle) {
return return
} }
subCanvas := window.xCanvas.Clip(region) subCanvas := window.xCanvas.SubCanvas(region)
if subCanvas == nil { if subCanvas == nil {
return return
} }