26 Commits

Author SHA1 Message Date
9214f70b61 Fix centered texture rendering 2024-05-26 16:54:00 -04:00
36ac66b644 Add texture example 2024-05-26 16:53:49 -04:00
c13db1217d Fixed a null ptr deref in system.go 2024-05-26 15:33:40 -04:00
c0124bf232 Add some very basic examples 2024-05-26 15:33:15 -04:00
9dc929f545 Update window.go 2024-05-26 15:20:25 -04:00
0f9c27c19a Fix ContainerBox's SetTexture methods 2024-05-26 15:20:11 -04:00
74fd3fdd55 Rename ContainerBox.Delete to ContainerBox.Remove 2024-05-26 15:18:56 -04:00
398ad08867 Replace references to Canvas.Clip 2024-05-26 15:18:27 -04:00
c92377f50b Add Visible, SetVisible to Window 2024-05-26 15:16:31 -04:00
8b44526c94 Add Visible, SetVisible to Box 2024-05-26 15:14:40 -04:00
a5830c9823 Add SetTextureCenter 2024-05-26 15:04:58 -04:00
dd201f1b5f Add stub for SurfaceBox 2024-05-26 14:55:33 -04:00
8f47da654c Update canvas 2024-05-26 00:55:20 -04:00
bb4080bd73 Update Tomo API 2024-05-26 00:40:47 -04:00
c0c4bdb266 TextBox remembers dot when unfocused
Remedy #8
2024-05-20 12:58:02 -04:00
b8ce9d15f7 Remove plugin install instructions from README.md 2024-05-17 23:45:38 -04:00
05f3ebc9e5 Remedy #1 2024-05-17 23:19:03 -04:00
996d747d45 Remedy #4 2024-05-17 15:36:49 -04:00
cd72ae5bdd Add support for scrolling flow layouts to ContainerBox impl 2024-05-17 15:03:59 -04:00
a64c27953a Don't invalidate ContainerBox's children when something is set
redundantly
2024-05-15 01:18:58 -04:00
8f555f82ee Don't invalidate box if texture was set redundantly 2024-05-15 01:18:17 -04:00
79f81688bb SetBorder compares borders before doing re-layout/draw 2024-05-15 01:07:52 -04:00
b926881233 Box draws texture according to its bounds 2024-05-14 00:19:16 -04:00
b9092eae87 Fix TextBox scroll behavior 2024-05-13 19:39:36 -04:00
96fa7b5623 Fix scroll behavior for ContainerBox 2024-05-13 19:35:03 -04:00
664ce5f556 Add scroll code for ContainerBox 2024-05-13 17:43:26 -04:00
16 changed files with 370 additions and 82 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
```

121
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,15 +123,43 @@ 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 && this.textureMode == textureModeTile { return }
this.textureMode = textureModeTile
this.texture = xcanvas.AssertTexture(texture) this.texture = xcanvas.AssertTexture(texture)
this.invalidateDraw() this.invalidateDraw()
} }
func (this *box) SetBorder (border ...tomo.Border) { func (this *box) SetTextureCenter (texture canvas.Texture) {
this.border = border if this.texture == texture && this.textureMode == textureModeCenter { return }
this.invalidateLayout() this.texture = xcanvas.AssertTexture(texture)
this.invalidateMinimum() this.textureMode = textureModeCenter
this.invalidateDraw()
}
func (this *box) SetBorder (borders ...tomo.Border) {
previousBorderSum := this.borderSum()
previousBorders := this.border
this.border = borders
// only invalidate the layout if the border is sized differently
if this.borderSum() != previousBorderSum {
this.invalidateLayout()
this.invalidateMinimum()
return
}
// if the border takes up the same amount of space, only invalidate the
// drawing if it looks different
for index, newBorder := range this.border {
different :=
index >= len(previousBorders) ||
newBorder != previousBorders[index]
if different {
this.invalidateDraw()
return
}
}
} }
func (this *box) SetMinimumSize (size image.Point) { func (this *box) SetMinimumSize (size image.Point) {
@@ -140,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
} }
@@ -279,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()
bounds := this.Bounds()
// background
pen.Fill(this.color) pen.Fill(this.color)
pen.Texture(this.texture) 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(can.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) {
@@ -296,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)
@@ -356,20 +422,28 @@ func (this *box) doMinimumSize () {
} }
} }
// var drawcnt int
func (this *box) doDraw () { func (this *box) doDraw () {
// println("DRAW", drawcnt)
// drawcnt ++
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))
} }
} }
// var laycnt int
func (this *box) doLayout () { func (this *box) doLayout () {
// println("LAYOUT", laycnt)
// laycnt ++
this.innerClippingBounds = this.borderSum().Apply(this.bounds) this.innerClippingBounds = this.borderSum().Apply(this.bounds)
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) {
@@ -401,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 () {
@@ -414,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 {
@@ -441,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

@@ -31,12 +31,20 @@ func (backend *Backend) NewContainerBox() tomo.ContainerBox {
} }
func (this *containerBox) SetColor (c color.Color) { func (this *containerBox) SetColor (c color.Color) {
if this.color == c { return }
this.box.SetColor(c) this.box.SetColor(c)
this.invalidateTransparentChildren() this.invalidateTransparentChildren()
} }
func (this *containerBox) SetTexture (texture canvas.Texture) { func (this *containerBox) SetTextureTile (texture canvas.Texture) {
this.box.SetTexture(texture) if this.texture == texture { return }
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()
} }
@@ -59,7 +67,7 @@ func (this *containerBox) ContentBounds () image.Rectangle {
} }
func (this *containerBox) ScrollTo (point image.Point) { func (this *containerBox) ScrollTo (point image.Point) {
// TODO: constrain scroll if this.scroll == point { return }
this.scroll = point this.scroll = point
this.invalidateLayout() this.invalidateLayout()
} }
@@ -68,19 +76,19 @@ func (this *containerBox) OnContentBoundsChange (callback func()) event.Cookie {
return this.on.contentBoundsChange.Connect(callback) return this.on.contentBoundsChange.Connect(callback)
} }
func (this *containerBox) CaptureDND (capture bool) { func (this *containerBox) CaptureDND (capture bool) {
this.capture[eventCategoryDND] = capture this.capture[eventCategoryDND] = capture
} }
func (this *containerBox) CaptureMouse (capture bool) { func (this *containerBox) CaptureMouse (capture bool) {
this.capture[eventCategoryMouse] = capture this.capture[eventCategoryMouse] = capture
} }
func (this *containerBox) CaptureScroll (capture bool) { func (this *containerBox) CaptureScroll (capture bool) {
this.capture[eventCategoryScroll] = capture this.capture[eventCategoryScroll] = capture
} }
func (this *containerBox) CaptureKeyboard (capture bool) { func (this *containerBox) CaptureKeyboard (capture bool) {
this.capture[eventCategoryKeyboard] = capture this.capture[eventCategoryKeyboard] = capture
} }
@@ -102,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 }
@@ -165,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)
} }
@@ -226,9 +234,7 @@ func (this *containerBox) notifyMinimumSizeChange (child anyBox) {
} }
func (this *containerBox) layoutHints () tomo.LayoutHints { func (this *containerBox) layoutHints () tomo.LayoutHints {
innerBounds := this.InnerBounds().Sub(this.scroll)
return tomo.LayoutHints { return tomo.LayoutHints {
Bounds: innerBounds,
OverflowX: this.hOverflow, OverflowX: this.hOverflow,
OverflowY: this.vOverflow, OverflowY: this.vOverflow,
AlignX: this.hAlign, AlignX: this.hAlign,
@@ -240,10 +246,12 @@ func (this *containerBox) layoutHints () tomo.LayoutHints {
func (this *containerBox) contentMinimum () image.Point { func (this *containerBox) contentMinimum () image.Point {
minimum := this.box.contentMinimum() minimum := this.box.contentMinimum()
if this.layout != nil { if this.layout != nil {
minimum = minimum.Add ( layoutMinimum := this.layout.MinimumSize (
this.layout.MinimumSize ( this.layoutHints(),
this.layoutHints(), this.children)
this.children)) if this.hOverflow { layoutMinimum.X = 0 }
if this.vOverflow { layoutMinimum.Y = 0 }
minimum = minimum.Add(layoutMinimum)
} }
return minimum return minimum
} }
@@ -251,14 +259,73 @@ func (this *containerBox) contentMinimum () image.Point {
func (this *containerBox) doLayout () { func (this *containerBox) doLayout () {
this.box.doLayout() this.box.doLayout()
previousContentBounds := this.contentBounds previousContentBounds := this.contentBounds
// by default, use innerBounds (translated to 0, 0) for contentBounds.
// if a direction overflows, use the layout's minimum size for it.
var minimum image.Point
if this.layout != nil { if this.layout != nil {
this.layout.Arrange(this.layoutHints(), this.children) minimum = this.layout.MinimumSize (
this.layoutHints(),
this.children)
} }
innerBounds := this.InnerBounds()
this.contentBounds = innerBounds.Sub(innerBounds.Min)
if this.hOverflow { this.contentBounds.Max.X = this.contentBounds.Min.X + minimum.X }
if this.vOverflow { this.contentBounds.Max.Y = this.contentBounds.Min.Y + minimum.Y }
// arrange children
if this.layout != nil {
layoutHints := this.layoutHints()
layoutHints.Bounds = this.contentBounds
this.layout.Arrange(layoutHints, this.children)
}
// build an accurate contentBounds by unioning the bounds of all child
// boxes
this.contentBounds = image.Rectangle { }
for _, box := range this.children {
bounds := box.Bounds()
this.contentBounds = this.contentBounds.Union(bounds)
}
// constrain the scroll
this.constrainScroll()
// offset children and contentBounds by scroll
for _, box := range this.children {
box.SetBounds(box.Bounds().Add(this.scroll).Add(innerBounds.Min))
}
this.contentBounds = this.contentBounds.Add(this.scroll)
if previousContentBounds != this.contentBounds { if previousContentBounds != this.contentBounds {
this.on.contentBoundsChange.Broadcast() this.on.contentBoundsChange.Broadcast()
} }
} }
func (this *containerBox) constrainScroll () {
innerBounds := this.InnerBounds()
width := this.contentBounds.Dx()
height := this.contentBounds.Dy()
// X
if width <= innerBounds.Dx() {
this.scroll.X = 0
} else if this.scroll.X > 0 {
this.scroll.X = 0
} else if this.scroll.X < innerBounds.Dx() - width {
this.scroll.X = innerBounds.Dx() - width
}
// Y
if height <= innerBounds.Dy() {
this.scroll.Y = 0
} else if this.scroll.Y > 0 {
this.scroll.Y = 0
} else if this.scroll.Y < innerBounds.Dy() - height {
this.scroll.Y = innerBounds.Dy() - height
}
}
func (this *containerBox) recursiveRedo () { func (this *containerBox) recursiveRedo () {
this.doLayout() this.doLayout()
this.doDraw() this.doDraw()
@@ -268,6 +335,8 @@ func (this *containerBox) recursiveRedo () {
} }
func (this *containerBox) boxUnder (point image.Point, category eventCategory) anyBox { func (this *containerBox) boxUnder (point image.Point, category eventCategory) anyBox {
if !point.In(this.bounds) { return nil }
if !this.capture[category] { if !this.capture[category] {
for _, box := range this.children { for _, box := range this.children {
candidate := box.(anyBox).boxUnder(point, category) candidate := box.(anyBox).boxUnder(point, category)
@@ -275,7 +344,7 @@ func (this *containerBox) boxUnder (point image.Point, category eventCategory) a
} }
} }
return this.box.boxUnder(point, category) return this
} }
func (this *containerBox) propagate (callback func (anyBox) bool) bool { func (this *containerBox) propagate (callback func (anyBox) bool) bool {

View File

@@ -161,16 +161,17 @@ func (window *window) handleConfigureNotify (
if window.root == nil { return } if window.root == nil { return }
configureEvent := *event.ConfigureNotifyEvent configureEvent := *event.ConfigureNotifyEvent
configureEvent = window.compressConfigureNotify(configureEvent)
newWidth := int(configureEvent.Width) oldBounds := window.metrics.bounds
newHeight := int(configureEvent.Height)
sizeChanged :=
window.metrics.bounds.Dx() != newWidth ||
window.metrics.bounds.Dy() != newHeight
window.updateBounds() window.updateBounds()
newBounds := window.metrics.bounds
sizeChanged :=
oldBounds.Dx() != newBounds.Dx() ||
oldBounds.Dy() != newBounds.Dy()
if sizeChanged { if sizeChanged {
configureEvent = window.compressConfigureNotify(configureEvent)
window.reallocateCanvas() window.reallocateCanvas()
// TODO figure out what to do with this // TODO figure out what to do with this

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

@@ -222,12 +222,11 @@ func (this *textBox) drawDot (can canvas.Canvas) {
func (this *textBox) textOffset () image.Point { func (this *textBox) textOffset () image.Point {
return this.InnerBounds().Min. return this.InnerBounds().Min.
Sub(this.scroll). Add(this.scroll).
Sub(this.drawer.LayoutBoundsSpace().Min) Sub(this.drawer.LayoutBoundsSpace().Min)
} }
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()
@@ -294,6 +293,7 @@ func (this *textBox) doLayout () {
this.contentBounds = this.normalizedLayoutBoundsSpace() this.contentBounds = this.normalizedLayoutBoundsSpace()
this.constrainScroll() this.constrainScroll()
this.contentBounds = this.contentBounds.Add(this.scroll) this.contentBounds = this.contentBounds.Add(this.scroll)
// println(this.InnerBounds().String(), this.contentBounds.String())
if previousContentBounds != this.contentBounds { if previousContentBounds != this.contentBounds {
this.on.contentBoundsChange.Broadcast() this.on.contentBoundsChange.Broadcast()
@@ -308,19 +308,19 @@ func (this *textBox) constrainScroll () {
// X // X
if width <= innerBounds.Dx() { if width <= innerBounds.Dx() {
this.scroll.X = 0 this.scroll.X = 0
} else if this.scroll.X < 0 { } else if this.scroll.X > 0 {
this.scroll.X = 0 this.scroll.X = 0
} else if this.scroll.X > width - innerBounds.Dx() { } else if this.scroll.X < innerBounds.Dx() - width {
this.scroll.X = width - innerBounds.Dx() this.scroll.X = innerBounds.Dx() - width
} }
// Y // Y
if height <= innerBounds.Dy() { if height <= innerBounds.Dy() {
this.scroll.Y = 0 this.scroll.Y = 0
} else if this.scroll.Y < 0 { } else if this.scroll.Y > 0 {
this.scroll.Y = 0 this.scroll.Y = 0
} else if this.scroll.Y > height - innerBounds.Dy() { } else if this.scroll.Y < innerBounds.Dy() - height {
this.scroll.Y = height - innerBounds.Dy() this.scroll.Y = innerBounds.Dy() - height
} }
} }
@@ -333,16 +333,16 @@ func (this *textBox) scrollToDot () {
// X // X
if dot.X < innerBounds.Min.X + em { if dot.X < innerBounds.Min.X + em {
scroll.X -= innerBounds.Min.X - dot.X + em scroll.X += innerBounds.Min.X - dot.X + em
} else if dot.X > innerBounds.Max.X - em { } else if dot.X > innerBounds.Max.X - em {
scroll.X += dot.X - innerBounds.Max.X + em scroll.X -= dot.X - innerBounds.Max.X + em
} }
// Y // Y
if dot.Y < innerBounds.Min.Y + lineHeight { if dot.Y < innerBounds.Min.Y + lineHeight {
scroll.Y -= innerBounds.Min.Y - dot.Y + lineHeight scroll.Y += innerBounds.Min.Y - dot.Y + lineHeight
} else if dot.Y > innerBounds.Max.Y - lineHeight { } else if dot.Y > innerBounds.Max.Y - lineHeight {
scroll.Y += dot.Y - innerBounds.Max.Y + lineHeight scroll.Y -= dot.Y - innerBounds.Max.Y + lineHeight
} }
this.ScrollTo(scroll) this.ScrollTo(scroll)

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
} }