Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e38cbe7205 | |||
| 09cec81f30 | |||
| 0799f3645b | |||
| 9214f70b61 | |||
| 36ac66b644 | |||
| c13db1217d | |||
| c0124bf232 | |||
| 9dc929f545 | |||
| 0f9c27c19a | |||
| 74fd3fdd55 | |||
| 398ad08867 | |||
| c92377f50b | |||
| 8b44526c94 | |||
| a5830c9823 | |||
| dd201f1b5f | |||
| 8f47da654c | |||
| bb4080bd73 | |||
| c0c4bdb266 | |||
| b8ce9d15f7 | |||
| 05f3ebc9e5 | |||
| 996d747d45 | |||
| cd72ae5bdd | |||
| a64c27953a | |||
| 8f555f82ee | |||
| 79f81688bb | |||
| b926881233 | |||
| b9092eae87 | |||
| 96fa7b5623 | |||
| 664ce5f556 |
@@ -3,12 +3,3 @@
|
|||||||
[](https://pkg.go.dev/git.tebibyte.media/tomo/x)
|
[](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
|
|
||||||
```
|
|
||||||
|
|||||||
110
box.go
110
box.go
@@ -9,23 +9,29 @@ 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
|
bounds image.Rectangle
|
||||||
minSize image.Point
|
minSize image.Point
|
||||||
userMinSize 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 +122,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) {
|
||||||
@@ -279,13 +313,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 && this.texture != nil {
|
||||||
|
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 +351,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 +411,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 +464,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 +477,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 +504,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()
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 }
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)))
|
||||||
}
|
}
|
||||||
|
|||||||
103
containerbox.go
103
containerbox.go
@@ -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 {
|
||||||
|
|||||||
13
event.go
13
event.go
@@ -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
18
examples/blank/main.go
Normal 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
29
examples/text/main.go
Normal 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
83
examples/texture/main.go
Normal 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
2
go.mod
@@ -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.34.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
4
go.sum
@@ -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.34.0 h1:r5yJPks9rtzdDI2RyAUdqa1qb6BebG0QFe2cTmcFi+0=
|
||||||
git.tebibyte.media/tomo/tomo v0.31.0/go.mod h1:C9EzepS9wjkTJjnZaPBh22YvVPyA4hbBAJVU20Rdmps=
|
git.tebibyte.media/tomo/tomo v0.34.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
8
surfacebox.go
Normal 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")
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
|
|||||||
24
textbox.go
24
textbox.go
@@ -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)
|
||||||
|
|||||||
24
window.go
24
window.go
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user