29 Commits

Author SHA1 Message Date
bacdd81f60 Propagate mouse motion events to boxes 2024-07-27 15:13:49 -04:00
d944f6016f Update Tomo API to v0.41.1 2024-07-27 15:04:11 -04:00
01582d4ad1 Same for TextBox 2024-07-27 13:47:22 -04:00
3941dae44a Invalidate container minimum size when overflow is changed 2024-07-27 13:46:52 -04:00
85b8536925 Propagate keyboard events to root if nothing is focused
This makes window-level keybinds possible. Exciting!
2024-07-27 02:20:06 -04:00
6ff5dea308 ContainerBox correctly checks for overflow when reporting recommended size 2024-07-27 02:18:52 -04:00
33969f45e9 BoxQuerier returns box minimum size as a fallback for reccomended sizes 2024-07-27 02:17:56 -04:00
832d7e02ef TextBox can be selected with left, middle, and right buttons 2024-07-26 21:17:30 -04:00
fd6297b4fb And arrow keys! Because, why not! 2024-07-26 20:55:34 -04:00
4deb581667 Use tab for keynav instead of alt-tab 2024-07-26 20:52:49 -04:00
192e6c6235 Keynav skips masked boxes 2024-07-26 20:49:10 -04:00
9729e3dfda Fix selectable detection when using keys on TextBox 2024-07-26 20:48:44 -04:00
fad46eafd3 All selectable TextBoxes have keyboard controls 2024-07-26 18:43:12 -04:00
ddde2a79a8 TextBox defaults to black for a cursor color 2024-07-26 17:34:33 -04:00
180a5eb8d1 Hierarchy is now responsible for focusing boxes when they are clicked 2024-07-26 17:34:14 -04:00
a92951f891 Remove debug message 2024-07-26 00:29:07 -04:00
37ec962d1f TextBox properly gives attributes values to the typeset drawer 2024-07-26 00:27:32 -04:00
4f89b11799 Box applies the style to the outer box (oops!) 2024-07-26 00:22:10 -04:00
7809aac72f Actually use layouts 2024-07-25 21:05:03 -04:00
bb082d3989 Change when the parent is notified of a child's minimum size change 2024-07-25 21:04:32 -04:00
fbb6d61cfc Fix style application 2024-07-25 21:04:21 -04:00
e4cba4a7c9 Add check while calculating min size to prevent goofy situations 2024-07-25 20:37:38 -04:00
6192a1e9cc Fixed util.Memo 2024-07-25 20:37:09 -04:00
5864c74691 Fix some segfaults 2024-07-25 18:17:43 -04:00
a62dff4236 Update code for x backend 2024-07-25 13:01:24 -04:00
196afbc2f3 Update code for internal system 2024-07-25 13:01:15 -04:00
9b61600f31 Update Tomo API to v0.41.0 2024-07-25 13:00:37 -04:00
5b62c9e162 Update Tomo API 2024-07-21 13:06:30 -04:00
19ca3e821a Add xgbsel as dependency 2024-07-21 13:06:09 -04:00
16 changed files with 1001 additions and 575 deletions

2
go.mod
View File

@@ -3,7 +3,7 @@ module git.tebibyte.media/tomo/backend
go 1.20
require (
git.tebibyte.media/tomo/tomo v0.38.0
git.tebibyte.media/tomo/tomo v0.41.1
git.tebibyte.media/tomo/typeset v0.7.1
git.tebibyte.media/tomo/xgbkb v1.0.1
github.com/jezek/xgb v1.1.1

4
go.sum
View File

@@ -1,6 +1,6 @@
git.tebibyte.media/sashakoshka/xgbkb v1.0.0/go.mod h1:pNcE6TRO93vHd6q42SdwLSTTj25L0Yzggz7yLe0JV6Q=
git.tebibyte.media/tomo/tomo v0.38.0 h1:K5TP67RxnszudeNfmGZiU5cFTRjFueXiI3NCsgw+05U=
git.tebibyte.media/tomo/tomo v0.38.0/go.mod h1:C9EzepS9wjkTJjnZaPBh22YvVPyA4hbBAJVU20Rdmps=
git.tebibyte.media/tomo/tomo v0.41.1 h1:XdbyF3VjsLj1Zppr70gUaufuh49hU32JQo2ENnw4PcA=
git.tebibyte.media/tomo/tomo v0.41.1/go.mod h1:C9EzepS9wjkTJjnZaPBh22YvVPyA4hbBAJVU20Rdmps=
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/xgbkb v1.0.1 h1:b3HDUopjdQp1MZrb5Vpil4bOtk3NnNXtfQW27Blw2kE=

View File

@@ -0,0 +1,38 @@
package system
import "git.tebibyte.media/tomo/tomo"
type attrHierarchy [T tomo.Attr] struct {
style T
user T
userExists bool
}
func (this *attrHierarchy[T]) SetStyle (style T) (different bool) {
styleEquals := this.style.Equals(style)
this.style = style
return !styleEquals && !this.userExists
}
func (this *attrHierarchy[T]) SetUser (user T) (different bool) {
userEquals := this.user.Equals(user)
this.user = user
this.userExists = true
return !userEquals
}
func (this *attrHierarchy[T]) Set (attr T, user bool) (different bool) {
if user {
return this.SetUser(attr)
} else {
return this.SetStyle(attr)
}
}
func (this *attrHierarchy[T]) Value () T {
if this.userExists {
return this.user
} else {
return this.style
}
}

View File

@@ -9,68 +9,67 @@ import "git.tebibyte.media/tomo/tomo/event"
import "git.tebibyte.media/tomo/tomo/canvas"
import "git.tebibyte.media/tomo/backend/internal/util"
type textureMode int; const (
textureModeTile textureMode = iota
textureModeCenter
)
type box struct {
system *System
parent parent
outer anyBox
role tomo.Role
styleCookie event.Cookie
lastStyleNonce int
lastIconsNonce int
tags util.Set[string]
role tomo.Role
lastStyleNonce int
lastIconsNonce int
styleApplicator *styleApplicator
minSize util.Memo[image.Point]
bounds image.Rectangle
minSize image.Point
userMinSize image.Point
innerClippingBounds image.Rectangle
minSizeQueued bool
focusQueued *bool
focusQueued *bool
padding tomo.Inset
border []tomo.Border
color color.Color
texture canvas.Texture
textureMode textureMode
attrColor attrHierarchy[tomo.AttrColor]
attrTexture attrHierarchy[tomo.AttrTexture]
attrTextureMode attrHierarchy[tomo.AttrTextureMode]
attrBorder attrHierarchy[tomo.AttrBorder]
attrMinimumSize attrHierarchy[tomo.AttrMinimumSize]
attrPadding attrHierarchy[tomo.AttrPadding]
dndData data.Data
dndAccept []data.Mime
focused bool
focusable bool
hovered bool
focused bool
pressed bool
canvas util.Memo[canvas.Canvas]
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)]
styleChange event.FuncBroadcaster
iconsChange event.FuncBroadcaster
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.Broadcaster[func () bool]
buttonDown event.Broadcaster[func (input.Button) bool]
buttonUp event.Broadcaster[func (input.Button) bool]
scroll event.Broadcaster[func (float64, float64) bool]
keyDown event.Broadcaster[func (input.Key, bool) bool]
keyUp event.Broadcaster[func (input.Key, bool) bool]
styleChange event.FuncBroadcaster
iconSetChange event.FuncBroadcaster
}
}
func (this *System) newBox (outer anyBox) *box {
box := &box {
system: this,
color: color.Transparent,
outer: outer,
drawer: outer,
tags: make(util.Set[string]),
}
box.canvas = util.NewMemo (func () canvas.Canvas {
if box.parent == nil { return nil }
@@ -82,15 +81,16 @@ func (this *System) newBox (outer anyBox) *box {
box.drawer = box
box.outer = box
}
box.invalidateMinimum()
box.minSize = util.NewMemo(box.calculateMinimumSize)
return box
}
func (this *System) NewBox () tomo.Box {
return this.newBox(nil)
}
// ----- public methods ----------------------------------------------------- //
func (this *box) GetBox () tomo.Box {
return this.outer
}
@@ -106,102 +106,13 @@ func (this *box) Bounds () image.Rectangle {
}
func (this *box) InnerBounds () image.Rectangle {
return this.padding.Apply(this.innerClippingBounds)
}
func (this *box) MinimumSize () image.Point {
return this.minSize
return tomo.Inset(this.attrPadding.Value()).Apply(this.innerClippingBounds)
}
func (this *box) Role () tomo.Role {
return this.role
}
func (this *box) borderSum () tomo.Inset {
sum := tomo.Inset { }
for _, border := range this.border {
sum[0] += border.Width[0]
sum[1] += border.Width[1]
sum[2] += border.Width[2]
sum[3] += border.Width[3]
}
return sum
}
func (this *box) borderAndPaddingSum () tomo.Inset {
sum := this.borderSum()
sum[0] += this.padding[0]
sum[1] += this.padding[1]
sum[2] += this.padding[2]
sum[3] += this.padding[3]
return sum
}
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 c == nil { c = color.Transparent }
if this.color == c { return }
this.color = c
this.invalidateDraw()
}
func (this *box) SetTextureTile (texture canvas.Texture) {
if this.texture == texture && this.textureMode == textureModeTile { return }
this.textureMode = textureModeTile
this.texture = texture
this.invalidateDraw()
}
func (this *box) SetTextureCenter (texture canvas.Texture) {
if this.texture == texture && this.textureMode == textureModeCenter { return }
this.texture = texture
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) {
if this.userMinSize == size { return }
this.userMinSize = size
this.invalidateMinimum()
}
func (this *box) SetPadding (padding tomo.Inset) {
if this.padding == padding { return }
this.padding = padding
this.invalidateLayout()
this.invalidateMinimum()
}
func (this *box) SetRole (role tomo.Role) {
if this.role == role { return }
this.role = role
@@ -209,6 +120,27 @@ func (this *box) SetRole (role tomo.Role) {
this.outer.recursiveReApply()
}
func (this *box) Tag (tag string) bool {
switch tag {
case "hovered": return this.hovered
case "focused": return this.focused
case "pressed": return this.pressed
default: return this.tags.Has(tag)
}
}
func (this *box) SetTag (tag string, on bool) {
if on {
this.tags.Add(tag)
} else {
delete(this.tags, tag)
}
}
func (this *box) SetAttr (attr tomo.Attr) {
this.outer.setAttr(attr, true)
}
func (this *box) SetDNDData (dat data.Data) {
this.dndData = dat
}
@@ -233,6 +165,12 @@ func (this *box) SetFocused (focused bool) {
}
}
func (this *box) Focused () bool {
hierarchy := this.getHierarchy()
if hierarchy == nil { return false }
return hierarchy.isFocused(this.outer)
}
func (this *box) SetFocusable (focusable bool) {
if this.focusable == focusable { return }
this.focusable = focusable
@@ -241,22 +179,83 @@ func (this *box) SetFocusable (focusable bool) {
}
}
func (this *box) Focused () bool {
hierarchy := this.getHierarchy()
if hierarchy == nil { return false }
return hierarchy.isFocused(this.outer)
// ----- private methods ---------------------------------------------------- //
func (this *box) setAttr (attr tomo.Attr, user bool) {
switch attr := attr.(type) {
case tomo.AttrColor:
if this.attrColor.Set(attr, user) {
this.invalidateDraw()
}
case tomo.AttrTexture:
if this.attrTexture.Set(attr, user) {
this.invalidateDraw()
}
case tomo.AttrTextureMode:
if this.attrTextureMode.Set(attr, user) {
this.invalidateDraw()
}
case tomo.AttrBorder:
previousBorderSum := this.borderSum()
different := this.attrBorder.Set(attr, user)
// only invalidate the layout if the border is sized differently
if this.borderSum() != previousBorderSum {
this.invalidateLayout()
this.invalidateMinimum()
}
// if the border takes up the same amount of space, only invalidate the
// drawing if it looks different
if different {
this.invalidateDraw()
}
case tomo.AttrMinimumSize:
if this.attrMinimumSize.Set(attr, user) {
this.invalidateMinimum()
}
case tomo.AttrPadding:
if this.attrPadding.Set(attr, true) {
this.invalidateLayout()
this.invalidateMinimum()
}
}
}
func (this *box) Modifiers () input.Modifiers {
hierarchy := this.getHierarchy()
if hierarchy == nil { return input.Modifiers { } }
return hierarchy.getModifiers()
func (this *box) setBounds (bounds image.Rectangle) {
if this.bounds == bounds { return }
this.bounds = bounds
this.invalidateLayout()
}
func (this *box) MousePosition () image.Point {
hierarchy := this.getHierarchy()
if hierarchy == nil { return image.Point { } }
return hierarchy.getMousePosition()
func (this *box) minimumSize () image.Point {
return this.minSize.Value()
}
func (this *box) borderSum () tomo.Inset {
sum := tomo.Inset { }
for _, border := range this.attrBorder.Value() {
sum[0] += border.Width[0]
sum[1] += border.Width[1]
sum[2] += border.Width[2]
sum[3] += border.Width[3]
}
return sum
}
func (this *box) borderAndPaddingSum () tomo.Inset {
sum := this.borderSum()
padding := this.attrPadding.Value()
sum[0] += padding[0]
sum[1] += padding[1]
sum[2] += padding[2]
sum[3] += padding[3]
return sum
}
// ----- event handler setters ---------------------------------------------- //
@@ -281,76 +280,95 @@ func (this *box) OnMouseEnter (callback func()) event.Cookie {
func (this *box) OnMouseLeave (callback func()) event.Cookie {
return this.on.mouseLeave.Connect(callback)
}
func (this *box) OnMouseMove (callback func()) event.Cookie {
func (this *box) OnMouseMove (callback func() bool) 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) OnButtonDown (callback func(input.Button) bool) event.Cookie {
return this.on.buttonDown.Connect(callback)
}
func (this *box) OnMouseUp (callback func(input.Button)) event.Cookie {
return this.on.mouseUp.Connect(callback)
func (this *box) OnButtonUp (callback func(input.Button) bool) event.Cookie {
return this.on.buttonUp.Connect(callback)
}
func (this *box) OnScroll (callback func(deltaX, deltaY float64)) event.Cookie {
func (this *box) OnScroll (callback func(float64, float64) bool) event.Cookie {
return this.on.scroll.Connect(callback)
}
func (this *box) OnKeyDown (callback func(key input.Key, numberPad bool)) event.Cookie {
func (this *box) OnKeyDown (callback func(input.Key, bool) bool) event.Cookie {
return this.on.keyDown.Connect(callback)
}
func (this *box) OnKeyUp (callback func(key input.Key, numberPad bool)) event.Cookie {
func (this *box) OnKeyUp (callback func(input.Key, bool) bool) event.Cookie {
return this.on.keyUp.Connect(callback)
}
func (this *box) OnStyleChange (callback func()) event.Cookie {
return this.on.styleChange.Connect(callback)
}
func (this *box) OnIconsChange (callback func()) event.Cookie {
return this.on.iconsChange.Connect(callback)
func (this *box) OnIconSetChange (callback func()) event.Cookie {
return this.on.iconSetChange.Connect(callback)
}
func (this *box) handleFocusEnter () {
this.focused = true
this.invalidateStyle()
this.on.focusEnter.Broadcast()
}
func (this *box) handleFocusLeave () {
this.focused = false
this.invalidateStyle()
this.on.focusLeave.Broadcast()
}
func (this *box) handleMouseEnter () {
this.hovered = true
this.invalidateStyle()
this.on.mouseEnter.Broadcast()
}
func (this *box) handleMouseLeave () {
this.hovered = false
this.invalidateStyle()
this.on.mouseLeave.Broadcast()
}
func (this *box) handleMouseMove () {
this.on.mouseMove.Broadcast()
}
func (this *box) handleMouseDown (button input.Button) {
if this.focusable {
this.SetFocused(true)
} else {
hierarchy := this.getHierarchy()
if hierarchy == nil { return }
hierarchy.focus(nil)
}
for _, listener := range this.on.mouseDown.Listeners() {
listener(button)
func (this *box) handleMouseMove () (caught bool) {
for _, listener := range this.on.mouseMove.Listeners() {
if listener() { caught = true }
}
return
}
func (this *box) handleMouseUp (button input.Button) {
for _, listener := range this.on.mouseUp.Listeners() {
listener(button)
func (this *box) handleMouseDown (button input.Button) (caught bool) {
if button == input.ButtonLeft {
this.pressed = true
this.invalidateStyle()
}
for _, listener := range this.on.buttonDown.Listeners() {
if listener(button) { caught = true }
}
return
}
func (this *box) handleScroll (x, y float64) {
func (this *box) handleMouseUp (button input.Button) (caught bool) {
if button == input.ButtonLeft {
this.pressed = false
this.invalidateStyle()
}
for _, listener := range this.on.buttonUp.Listeners() {
if listener(button) { caught = true }
}
return
}
func (this *box) handleScroll (x, y float64) (caught bool) {
for _, listener := range this.on.scroll.Listeners() {
listener(x, y)
if listener(x, y) { caught = true }
}
return
}
func (this *box) handleKeyDown (key input.Key, numberPad bool) {
func (this *box) handleKeyDown (key input.Key, numberPad bool) (caught bool) {
for _, listener := range this.on.keyDown.Listeners() {
listener(key, numberPad)
if listener(key, numberPad) { caught = true }
}
return
}
func (this *box) handleKeyUp (key input.Key, numberPad bool) {
func (this *box) handleKeyUp (key input.Key, numberPad bool) (caught bool) {
for _, listener := range this.on.keyUp.Listeners() {
listener(key, numberPad)
if listener(key, numberPad) { caught = true }
}
return
}
// -------------------------------------------------------------------------- //
@@ -359,19 +377,25 @@ func (this *box) Draw (can canvas.Canvas) {
pen := can.Pen()
bounds := this.Bounds()
// get values
textureMode := tomo.TextureMode(this.attrTextureMode.Value())
texture := this.attrTexture.Value().Texture
col := this.attrColor.Value().Color
if col == nil { col = color.Transparent }
// background
pen.Fill(this.color)
if this.textureMode == textureModeTile {
pen.Texture(this.texture)
}
if this.transparent() && this.parent != nil {
this.parent.drawBackgroundPart(can)
}
pen.Fill(col)
if textureMode == tomo.TextureModeTile && texture != nil {
pen.Texture(texture)
}
pen.Rectangle(bounds)
// centered texture
if this.textureMode == textureModeCenter && this.texture != nil {
textureBounds := this.texture.Bounds()
if textureMode == tomo.TextureModeCenter && texture != nil {
textureBounds := texture.Bounds()
textureOrigin :=
bounds.Min.
Add(image.Pt (
@@ -382,7 +406,7 @@ func (this *box) Draw (can canvas.Canvas) {
textureBounds.Dy() / 2))
pen.Fill(color.Transparent)
pen.Texture(this.texture)
pen.Texture(texture)
pen.Rectangle(textureBounds.Sub(textureBounds.Min).Add(textureOrigin))
}
}
@@ -394,6 +418,7 @@ func (this *box) drawBorders (can canvas.Canvas) {
rectangle := func (x0, y0, x1, y1 int, c color.Color) {
area := image.Rect(x0, y0, x1, y1)
if area.Empty() { return }
if util.Transparent(c) && this.parent != nil {
this.parent.drawBackgroundPart(can.SubCanvas(area))
}
@@ -401,7 +426,7 @@ func (this *box) drawBorders (can canvas.Canvas) {
pen.Rectangle(area)
}
for _, border := range this.border {
for _, border := range this.attrBorder.Value() {
rectangle (
bounds.Min.X,
bounds.Min.Y,
@@ -433,26 +458,26 @@ func (this *box) drawBorders (can canvas.Canvas) {
func (this *box) contentMinimum () image.Point {
var minimum image.Point
minimum.X += this.padding.Horizontal()
minimum.Y += this.padding.Vertical()
padding := tomo.Inset(this.attrPadding.Value())
minimum.X += padding.Horizontal()
minimum.Y += padding.Vertical()
borderSum := this.borderSum()
minimum.X += borderSum.Horizontal()
minimum.Y += borderSum.Vertical()
return minimum
}
func (this *box) doMinimumSize () {
this.minSize = this.outer.contentMinimum()
if this.minSize.X < this.userMinSize.X {
this.minSize.X = this.userMinSize.X
func (this *box) calculateMinimumSize () image.Point {
userMinSize := this.attrMinimumSize.Value()
minSize := this.outer.contentMinimum()
if minSize.X < userMinSize.X {
minSize.X = userMinSize.X
}
if this.minSize.Y < this.userMinSize.Y {
this.minSize.Y = this.userMinSize.Y
if minSize.Y < userMinSize.Y {
minSize.Y = userMinSize.Y
}
if this.parent != nil {
this.parent.notifyMinimumSizeChange(this)
}
return minSize
}
// var drawcnt int
@@ -477,6 +502,10 @@ func (this *box) doLayout () {
this.outer.recursiveLoseCanvas()
}
func (this *box) doStyle () {
this.styleApplicator.apply(this.outer)
}
func (this *box) setParent (parent parent) {
if this.parent != parent && this.Focused() {
this.SetFocused(false)
@@ -491,10 +520,6 @@ func (this *box) getParent () parent {
func (this *box) flushActionQueue () {
if this.getHierarchy() == nil { return }
if this.minSizeQueued {
this.invalidateMinimum()
}
if this.focusQueued != nil {
this.SetFocused(*this.focusQueued)
}
@@ -509,6 +534,12 @@ func (this *box) recursiveLoseCanvas () {
this.canvas.InvalidateTo(nil)
}
func (this *box) invalidateStyle () {
hierarchy := this.getHierarchy()
if hierarchy == nil { return }
hierarchy.invalidateStyle(this.outer)
}
func (this *box) invalidateLayout () {
hierarchy := this.getHierarchy()
if hierarchy == nil { return }
@@ -522,11 +553,9 @@ func (this *box) invalidateDraw () {
}
func (this *box) invalidateMinimum () {
hierarchy := this.getHierarchy()
if hierarchy == nil {
this.minSizeQueued = true
} else {
hierarchy.invalidateMinimum(this.outer)
this.minSize.Invalidate()
if this.parent != nil {
this.parent.notifyMinimumSizeChange(this)
}
}
@@ -538,17 +567,13 @@ func (this *box) recursiveReApply () {
// style
hierarchyStyleNonce := this.getStyleNonce()
if this.lastStyleNonce != hierarchyStyleNonce {
// remove old style
// i should probably explain why we have a specific style
// applicator for every box, it's so style applicators can cache
// information about the boxes they're linked to (like all rules
// with a matching role).
this.lastStyleNonce = hierarchyStyleNonce
if this.styleCookie != nil {
this.styleCookie.Close()
this.styleCookie = nil
}
// apply new one
if style := this.getStyle(); style != nil {
this.styleCookie = style.Apply(this.outer)
}
this.styleApplicator = this.getHierarchy().newStyleApplicator()
this.invalidateStyle()
this.on.styleChange.Broadcast()
}
@@ -556,7 +581,7 @@ func (this *box) recursiveReApply () {
hierarchyIconsNonce := this.getIconsNonce()
if this.lastIconsNonce != hierarchyIconsNonce {
this.lastIconsNonce = hierarchyIconsNonce
this.on.iconsChange.Broadcast()
this.on.iconSetChange.Broadcast()
}
}
@@ -564,7 +589,7 @@ func (this *box) canBeFocused () bool {
return this.focusable
}
func (this *box) boxUnder (point image.Point, category eventCategory) anyBox {
func (this *box) boxUnder (point image.Point) anyBox {
if point.In(this.bounds) {
return this.outer
} else {
@@ -583,7 +608,8 @@ func (this *box) propagateAlt (callback func (anyBox) bool) bool {
func (this *box) transparent () bool {
// TODO uncomment once we have
// a way to detect texture transparency
return util.Transparent(this.color) /*&&
col := this.attrColor.Value().Color
return col == nil || util.Transparent(col) /*&&
(this.texture == nil || !this.texture.Opaque())*/
}
@@ -593,12 +619,6 @@ func (this *box) getWindow () tomo.Window {
return hierarchy.getWindow()
}
func (this *box) getStyle () tomo.Style {
hierarchy := this.getHierarchy()
if hierarchy == nil { return nil }
return hierarchy.getStyle()
}
func (this *box) getHierarchy () *Hierarchy {
if this.parent == nil { return nil }
return this.parent.getHierarchy()

View File

@@ -0,0 +1,65 @@
package system
import "image"
type boxQuerier []anyBox
func (querier boxQuerier) Len () int {
return len(querier)
}
func (querier boxQuerier) MinimumSize (index int) image.Point {
if box, ok := querier.box(index); ok {
return box.minimumSize()
}
return image.Point { }
}
func (querier boxQuerier) RecommendedWidth (index int, height int) int {
if box, ok := querier.box(index); ok {
if box, ok := box.(anyContentBox); ok {
return box.recommendedWidth(height)
}
return box.minimumSize().X
}
return 0
}
func (querier boxQuerier) RecommendedHeight (index int, width int) int {
if box, ok := querier.box(index); ok {
if box, ok := box.(anyContentBox); ok {
return box.recommendedHeight(width)
}
return box.minimumSize().Y
}
return 0
}
func (querier boxQuerier) box (index int) (anyBox, bool) {
if index < 0 || index >= len(querier) { return nil, false }
return querier[index], true
}
type boxArranger []anyBox
func (arranger boxArranger) Len () int {
return boxQuerier(arranger).Len()
}
func (arranger boxArranger) MinimumSize (index int) image.Point {
return boxQuerier(arranger).MinimumSize(index)
}
func (arranger boxArranger) RecommendedWidth (index int, height int) int {
return boxQuerier(arranger).RecommendedWidth(index, height)
}
func (arranger boxArranger) RecommendedHeight (index int, width int) int {
return boxQuerier(arranger).RecommendedHeight(index, width)
}
func (arranger boxArranger) SetBounds (index int, bounds image.Rectangle) {
if box, ok := boxQuerier(arranger).box(index); ok {
box.setBounds(bounds)
}
}

View File

@@ -15,10 +15,6 @@ func (this *System) NewCanvasBox () tomo.CanvasBox {
return box
}
func (this *canvasBox) Box () tomo.Box {
return this
}
func (this *canvasBox) SetDrawer (drawer canvas.Drawer) {
this.userDrawer = drawer
this.invalidateDraw()
@@ -32,7 +28,8 @@ func (this *canvasBox) Draw (can canvas.Canvas) {
if can == nil { return }
this.box.Draw(can)
if this.userDrawer != nil {
padding := tomo.Inset(this.attrPadding.Value())
this.userDrawer.Draw (
can.SubCanvas(this.padding.Apply(this.innerClippingBounds)))
can.SubCanvas(padding.Apply(this.innerClippingBounds)))
}
}

View File

@@ -10,15 +10,16 @@ import "git.tebibyte.media/tomo/backend/internal/util"
type containerBox struct {
*box
hOverflow, vOverflow bool
hAlign, vAlign tomo.Align
contentBounds image.Rectangle
scroll image.Point
capture [4]bool
contentBounds image.Rectangle
scroll image.Point
mask bool
gap image.Point
children []tomo.Box
layout tomo.Layout
attrGap attrHierarchy[tomo.AttrGap]
attrAlign attrHierarchy[tomo.AttrAlign]
attrOverflow attrHierarchy[tomo.AttrOverflow]
attrLayout attrHierarchy[tomo.AttrLayout]
children []anyBox
on struct {
contentBoundsChange event.FuncBroadcaster
@@ -31,37 +32,7 @@ func (this *System) NewContainerBox () tomo.ContainerBox {
return box
}
func (this *containerBox) SetColor (c color.Color) {
if this.color == c { return }
this.box.SetColor(c)
this.invalidateTransparentChildren()
}
func (this *containerBox) SetTextureTile (texture canvas.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()
}
func (this *containerBox) SetOverflow (horizontal, vertical bool) {
if this.hOverflow == horizontal && this.vOverflow == vertical { return }
this.hOverflow = horizontal
this.vOverflow = vertical
this.invalidateLayout()
}
func (this *containerBox) SetAlign (x, y tomo.Align) {
if this.hAlign == x && this.vAlign == y { return }
this.hAlign = x
this.vAlign = y
this.invalidateLayout()
}
// ----- public methods ----------------------------------------------------- //
func (this *containerBox) ContentBounds () image.Rectangle {
return this.contentBounds
@@ -73,54 +44,13 @@ func (this *containerBox) ScrollTo (point image.Point) {
this.invalidateLayout()
}
func (this *containerBox) RecommendedHeight (width int) int {
if this.layout == nil || this.vOverflow {
return this.MinimumSize().Y
} else {
return this.layout.RecommendedHeight(this.layoutHints(), this.children, width) +
this.borderAndPaddingSum().Vertical()
}
}
func (this *containerBox) RecommendedWidth (height int) int {
if this.layout == nil || this.hOverflow {
return this.MinimumSize().X
} else {
return this.layout.RecommendedWidth(this.layoutHints(), this.children, height) +
this.borderAndPaddingSum().Horizontal()
}
}
func (this *containerBox) OnContentBoundsChange (callback func()) event.Cookie {
return this.on.contentBoundsChange.Connect(callback)
}
func (this *containerBox) CaptureDND (capture bool) {
this.capture[eventCategoryDND] = capture
}
func (this *containerBox) CaptureMouse (capture bool) {
this.capture[eventCategoryMouse] = capture
}
func (this *containerBox) CaptureScroll (capture bool) {
this.capture[eventCategoryScroll] = capture
}
func (this *containerBox) CaptureKeyboard (capture bool) {
this.capture[eventCategoryKeyboard] = capture
}
func (this *containerBox) SetGap (gap image.Point) {
if this.gap == gap { return }
this.gap = gap
this.invalidateLayout()
this.invalidateMinimum()
}
func (this *containerBox) Add (child tomo.Object) {
box := assertAnyBox(child.GetBox())
if util.IndexOf(this.children, tomo.Box(box)) > -1 { return }
if util.IndexOf(this.children, box) > -1 { return }
box.setParent(this)
box.flushActionQueue()
@@ -131,7 +61,7 @@ func (this *containerBox) Add (child tomo.Object) {
func (this *containerBox) Remove (child tomo.Object) {
box := assertAnyBox(child.GetBox())
index := util.IndexOf(this.children, tomo.Box(box))
index := util.IndexOf(this.children, box)
if index < 0 { return }
box.setParent(nil)
@@ -142,15 +72,15 @@ func (this *containerBox) Remove (child tomo.Object) {
func (this *containerBox) Insert (child, before tomo.Object) {
box := assertAnyBox(child.GetBox())
if util.IndexOf(this.children, tomo.Box(box)) > -1 { return }
if util.IndexOf(this.children, box) > -1 { return }
beforeBox := assertAnyBox(before.GetBox())
index := util.IndexOf(this.children, tomo.Box(beforeBox))
index := util.IndexOf(this.children, beforeBox)
if index < 0 {
this.children = append(this.children, tomo.Box(box))
this.children = append(this.children, box)
} else {
this.children = util.Insert(this.children, index, tomo.Box(box))
this.children = util.Insert(this.children, index, box)
}
box.setParent(this)
@@ -167,7 +97,7 @@ func (this *containerBox) Clear () {
this.invalidateMinimum()
}
func (this *containerBox) Length () int {
func (this *containerBox) Len () int {
return len(this.children)
}
@@ -178,14 +108,19 @@ func (this *containerBox) At (index int) tomo.Object {
return this.children[index]
}
func (this *containerBox) SetLayout (layout tomo.Layout) {
this.layout = layout
this.invalidateLayout()
this.invalidateMinimum()
func (this *containerBox) SetInputMask (mask bool) {
this.mask = mask
}
// ----- private methods ---------------------------------------------------- //
func (this *containerBox) Draw (can canvas.Canvas) {
if can == nil { return }
// textureMode := tomo.TextureMode(this.attrTextureMode.Value())
texture := this.attrTexture.Value().Texture
col := this.attrColor.Value().Color
if col == nil { col = color.Transparent }
rocks := make([]image.Rectangle, len(this.children))
for index, box := range this.children {
@@ -198,17 +133,90 @@ func (this *containerBox) Draw (can canvas.Canvas) {
}
if clipped == nil { continue }
pen := clipped.Pen()
pen.Fill(this.color)
pen.Texture(this.texture)
pen.Fill(col)
pen.Texture(texture)
pen.Rectangle(this.innerClippingBounds)
}
}
func (this *containerBox) setAttr (attr tomo.Attr, user bool) {
switch attr := attr.(type) {
case tomo.AttrColor:
if this.attrColor.Set(attr, user) {
this.invalidateTransparentChildren()
this.invalidateDraw()
}
case tomo.AttrTexture:
if this.attrTexture.Set(attr, user) {
this.invalidateTransparentChildren()
this.invalidateDraw()
}
case tomo.AttrTextureMode:
if this.attrTextureMode.Set(attr, user) {
this.invalidateTransparentChildren()
this.invalidateDraw()
}
case tomo.AttrGap:
if this.attrGap.Set(attr, user) {
this.invalidateLayout()
this.invalidateMinimum()
}
case tomo.AttrAlign:
if this.attrAlign.Set(attr, user) {
this.invalidateLayout()
}
case tomo.AttrOverflow:
if this.attrOverflow.Set(attr, user) {
this.invalidateLayout()
this.invalidateMinimum()
}
case tomo.AttrLayout:
if this.attrLayout.Set(attr, user) {
this.invalidateLayout()
this.invalidateMinimum()
}
default: this.box.setAttr(attr, user)
}
}
func (this *containerBox) recommendedHeight (width int) int {
layout := this.attrLayout.Value().Layout
if layout == nil || !this.attrOverflow.Value().Y {
return this.minSize.Value().Y
} else {
return layout.RecommendedHeight(this.layoutHints(), this.boxQuerier(), width) +
this.borderAndPaddingSum().Vertical()
}
}
func (this *containerBox) recommendedWidth (height int) int {
layout := this.attrLayout.Value().Layout
if layout == nil || !this.attrOverflow.Value().X {
return this.minSize.Value().X
} else {
return layout.RecommendedWidth(this.layoutHints(), this.boxQuerier(), height) +
this.borderAndPaddingSum().Horizontal()
}
}
func (this *containerBox) drawBackgroundPart (can canvas.Canvas) {
if can == nil { return }
pen := can.Pen()
pen.Fill(this.color)
pen.Texture(this.texture)
// textureMode := tomo.TextureMode(this.attrTextureMode.Value())
texture := this.attrTexture.Value().Texture
col := this.attrColor.Value().Color
if col == nil { col = color.Transparent }
pen.Fill(col)
pen.Texture(texture)
if this.transparent() && this.parent != nil {
this.parent.drawBackgroundPart(can)
@@ -245,7 +253,7 @@ func (this *containerBox) getCanvas () canvas.Canvas {
func (this *containerBox) notifyMinimumSizeChange (child anyBox) {
this.invalidateMinimum()
size := child.MinimumSize()
size := child.minimumSize()
bounds := child.Bounds()
if bounds.Dx() < size.X || bounds.Dy() < size.Y {
this.invalidateLayout()
@@ -253,23 +261,28 @@ func (this *containerBox) notifyMinimumSizeChange (child anyBox) {
}
func (this *containerBox) layoutHints () tomo.LayoutHints {
overflow := this.attrOverflow.Value()
align := this.attrAlign.Value()
gap := image.Point(this.attrGap.Value())
return tomo.LayoutHints {
OverflowX: this.hOverflow,
OverflowY: this.vOverflow,
AlignX: this.hAlign,
AlignY: this.vAlign,
Gap: this.gap,
OverflowX: overflow.X,
OverflowY: overflow.Y,
AlignX: align.X,
AlignY: align.Y,
Gap: gap,
}
}
func (this *containerBox) contentMinimum () image.Point {
minimum := this.box.contentMinimum()
if this.layout != nil {
layoutMinimum := this.layout.MinimumSize (
overflow := this.attrOverflow.Value()
minimum := this.box.contentMinimum()
layout := this.attrLayout.Value().Layout
if layout != nil {
layoutMinimum := layout.MinimumSize (
this.layoutHints(),
this.children)
if this.hOverflow { layoutMinimum.X = 0 }
if this.vOverflow { layoutMinimum.Y = 0 }
this.boxQuerier())
if overflow.X { layoutMinimum.X = 0 }
if overflow.Y { layoutMinimum.Y = 0 }
minimum = minimum.Add(layoutMinimum)
}
return minimum
@@ -278,25 +291,27 @@ func (this *containerBox) contentMinimum () image.Point {
func (this *containerBox) doLayout () {
this.box.doLayout()
previousContentBounds := this.contentBounds
layout := this.attrLayout.Value().Layout
// 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 {
minimum = this.layout.MinimumSize (
if layout != nil {
minimum = layout.MinimumSize (
this.layoutHints(),
this.children)
this.boxQuerier())
}
innerBounds := this.InnerBounds()
overflow := this.attrOverflow.Value()
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 }
if overflow.X { this.contentBounds.Max.X = this.contentBounds.Min.X + minimum.X }
if overflow.Y { this.contentBounds.Max.Y = this.contentBounds.Min.Y + minimum.Y }
// arrange children
if this.layout != nil {
if layout != nil {
layoutHints := this.layoutHints()
layoutHints.Bounds = this.contentBounds
this.layout.Arrange(layoutHints, this.children)
layout.Arrange(layoutHints, this.boxArranger())
}
// build an accurate contentBounds by unioning the bounds of all child
@@ -312,7 +327,7 @@ func (this *containerBox) doLayout () {
// offset children and contentBounds by scroll
for _, box := range this.children {
box.SetBounds(box.Bounds().Add(this.scroll).Add(innerBounds.Min))
box.setBounds(box.Bounds().Add(this.scroll).Add(innerBounds.Min))
}
this.contentBounds = this.contentBounds.Add(this.scroll)
@@ -345,6 +360,14 @@ func (this *containerBox) constrainScroll () {
}
}
func (this *containerBox) boxQuerier () boxQuerier {
return boxQuerier(this.children)
}
func (this *containerBox) boxArranger () boxArranger {
return boxArranger(this.children)
}
func (this *containerBox) recursiveRedo () {
this.doLayout()
this.doDraw()
@@ -367,12 +390,12 @@ func (this *containerBox) recursiveReApply () {
}
}
func (this *containerBox) boxUnder (point image.Point, category eventCategory) anyBox {
func (this *containerBox) boxUnder (point image.Point) anyBox {
if !point.In(this.bounds) { return nil }
if !this.capture[category] {
if !this.mask {
for _, box := range this.children {
candidate := box.(anyBox).boxUnder(point, category)
candidate := box.(anyBox).boxUnder(point)
if candidate != nil { return candidate }
}
}
@@ -400,6 +423,6 @@ func (this *containerBox) propagateAlt (callback func (anyBox) bool) bool {
return true
}
func (this *containerBox) captures (category eventCategory) bool {
return this.capture[category]
func (this *containerBox) masks () bool {
return this.mask
}

View File

@@ -3,6 +3,9 @@ package system
import "image"
import "git.tebibyte.media/tomo/tomo/input"
// TODO: once go v1.23 comes out, replace the explicit iterator calls here with
// range loops
// HandleFocusChange sets whether or not the window containing this Hierarchy
// has input focus.
func (this *Hierarchy) HandleFocusChange (focused bool) {
@@ -20,14 +23,31 @@ func (this *Hierarchy) HandleModifiers (modifiers input.Modifiers) {
// event which triggers this comes with modifier key information,
// HandleModifiers must be called *before* HandleKeyDown.
func (this *Hierarchy) HandleKeyDown (key input.Key, numberPad bool) {
if key == input.KeyTab && this.modifiers.Alt {
caught := false
if this.anyFocused() {
this.keyboardTargets(func (target anyBox) bool {
if target.handleKeyDown(key, numberPad) {
caught = true
return false
}
return true
})
} else {
if this.root != nil {
caught = this.root.handleKeyDown(key, numberPad)
}
}
if caught { return }
switch key {
case input.KeyTab:
if this.modifiers.Shift {
this.focusPrevious()
} else {
this.focusNext()
}
} else if target := this.keyboardTarget(); target != nil {
target.handleKeyDown(key, numberPad)
case input.KeyUp: this.focusPrevious()
case input.KeyDown: this.focusNext()
}
}
@@ -35,63 +55,88 @@ func (this *Hierarchy) HandleKeyDown (key input.Key, numberPad bool) {
// which triggers this comes with modifier key information, HandleModifiers must
// be called *before* HandleKeyUp.
func (this *Hierarchy) HandleKeyUp (key input.Key, numberPad bool) {
if target := this.keyboardTarget(); target != nil {
target.handleKeyUp(key, numberPad)
}
}
// HandleMouseDown sends a mouse down event to the Box positioned underneath the
// mouse cursor and marks it as being "dragged" by that mouse button. If the
// event which triggers this comes with mouse position information,
// HandleMouseMove must be called *before* HandleMouseDown.
func (this *Hierarchy) HandleMouseDown (button input.Button) {
underneath := this.boxUnder(this.mousePosition, eventCategoryMouse)
this.drags[button] = underneath
if underneath != nil {
underneath.handleMouseDown(button)
}
}
// HandleMouseUp sends a mouse up event to the Box currently being "dragged" by
// the specified mouse button, and marks it as being "not dragged" by that mouse
// button. If the event which triggers this comes with mouse position
// information, HandleMouseMove must be caleld *before* HandleMouseUp
func (this *Hierarchy) HandleMouseUp (button input.Button) {
dragging := this.drags[button]
this.drags[button] = nil
if dragging != nil {
dragging.handleMouseUp(button)
}
}
// HandleMouseMove sends a mouse move event to any Boxes currently being
// "dragged" by a mouse button. If none are, it sends the event to the Box which
// is underneath the mouse pointer.
func (this *Hierarchy) HandleMouseMove (position image.Point) {
if this.mousePosition == position { return }
this.mousePosition = position
handled := false
for _, child := range this.drags {
if child == nil { continue }
child.handleMouseMove()
handled = true
}
underneath := this.boxUnder(position, eventCategoryMouse)
if underneath != nil {
this.hover(underneath)
if !handled {
underneath.handleMouseMove()
if this.anyFocused() {
this.keyboardTargets(func (target anyBox) bool {
if target.handleKeyUp(key, numberPad) {
return false
}
return true
})
} else {
if this.root != nil {
this.root.handleKeyUp(key, numberPad)
}
}
}
// HandleScroll sends a scroll event to the Box currently underneath the mouse
// cursor.
func (this *Hierarchy) HandleScroll (x, y float64) {
underneath := this.boxUnder(this.mousePosition, eventCategoryScroll)
if underneath != nil {
underneath.handleScroll(x, y)
// HandleMouseDown sends a mouse down event to the Boxes positioned underneath
// the mouse cursor and marks them as being "dragged" by that mouse button,
// starting at the first Box to mask events and ending at the first box to
// catch the event. If the event which triggers this comes with mouse position
// information, HandleMouseMove must be called *before* HandleMouseDown.
func (this *Hierarchy) HandleMouseDown (button input.Button) {
boxes := []anyBox { }
first := true
this.boxesUnder(this.mousePosition)(func (box anyBox) bool {
if first {
if box.canBeFocused() {
this.focus(box)
} else {
this.focus(nil)
}
first = false
}
boxes = append(boxes, box)
return !box.handleMouseDown(button)
})
this.drags[button] = boxes
}
// HandleMouseUp sends a mouse up event to the Boxes currently being "dragged"
// by the specified mouse button, and marks them as being "not dragged" by that
// mouse button. If the event which triggers this comes with mouse position
// information, HandleMouseMove must be caleld *before* HandleMouseUp
func (this *Hierarchy) HandleMouseUp (button input.Button) {
for _, box := range this.drags[button] {
box.handleMouseUp(button)
}
this.drags[button] = nil
}
// HandleMouseMove sends a mouse move event to any Boxes currently being
// "dragged" by a mouse button. If none are, it sends the event to the Boxes
// which are underneath the mouse pointer, starting at the first Box to mask
// events and ending at the first box to catch the event.
func (this *Hierarchy) HandleMouseMove (position image.Point) {
if this.mousePosition == position { return }
this.mousePosition = position
dragged := false
for _, dragSet := range this.drags {
for _, box := range dragSet {
if box.handleMouseMove() { break }
dragged = true
}
}
if dragged { return }
// TODO we can hover over multiple boxes at once. however, any way of
// detecting this involves several slice allocations every time we
// process a MouseMove event. perhaps we just ought to suck it up and do
// it. or perhaps doing *this* is the better way? we may never know.
box := this.boxUnder(position)
if box != nil {
box := this.considerMaskingParents(box)
this.hover(box)
box.handleMouseMove()
}
}
// HandleScroll sends a scroll event to the Box currently underneath the mouse
// cursor. If the event which triggers this comes with mouse position
// information, HandleMouseMove must be called *before* HandleScroll.
func (this *Hierarchy) HandleScroll (x, y float64) {
this.boxesUnder(this.mousePosition)(func (box anyBox) bool {
return !box.handleScroll(x, y)
})
}

View File

@@ -21,10 +21,10 @@ type Hierarchy struct {
modifiers input.Modifiers
mousePosition image.Point
drags [10]anyBox
drags [10][]anyBox
minimumSize image.Point
needMinimum util.Set[anyBox]
needStyle util.Set[anyBox]
needLayout util.Set[anyBox]
needDraw util.Set[anyBox]
needRedo bool
@@ -52,7 +52,7 @@ func (this *System) NewHierarchy (link WindowLink) *Hierarchy {
hierarchy := &Hierarchy {
system: this,
link: link,
needMinimum: make(util.Set[anyBox]),
needStyle: make(util.Set[anyBox]),
needLayout: make(util.Set[anyBox]),
needDraw: make(util.Set[anyBox]),
}
@@ -95,6 +95,16 @@ func (this *Hierarchy) MinimumSize () image.Point {
return this.minimumSize
}
// Modifiers returns the current modifier keys being held.
func (this *Hierarchy) Modifiers () input.Modifiers {
return this.modifiers
}
// MousePosition returns the current mouse position.
func (this *Hierarchy) MousePosition () image.Point {
return this.mousePosition
}
// AfterEvent should be called at the end of every event cycle.
func (this *Hierarchy) AfterEvent () {
if this.canvas == nil { return }
@@ -104,7 +114,7 @@ func (this *Hierarchy) AfterEvent () {
childBounds := this.canvas.Bounds()
childBounds = childBounds.Sub(childBounds.Min)
if this.root != nil {
this.root.SetBounds(childBounds)
this.root.setBounds(childBounds)
}
// full relayout/redraw
@@ -116,8 +126,8 @@ func (this *Hierarchy) AfterEvent () {
return
}
for len(this.needMinimum) > 0 {
this.needMinimum.Pop().doMinimumSize()
for len(this.needStyle) > 0 {
this.needStyle.Pop().doStyle()
}
if !this.minimumClean {
this.doMinimumSize()
@@ -146,7 +156,7 @@ func (this *Hierarchy) setStyle () {
if this.root != nil { this.root.recursiveReApply() }
}
func (this *Hierarchy) setIcons () {
func (this *Hierarchy) setIconSet () {
if this.root != nil { this.root.recursiveReApply() }
}
@@ -158,7 +168,7 @@ func (this *Hierarchy) getWindow () tomo.Window {
return this.link.GetWindow()
}
func (this *Hierarchy) getStyle () tomo.Style {
func (this *Hierarchy) getStyle () *tomo.Style {
return this.system.style
}
@@ -186,8 +196,8 @@ func (this *Hierarchy) notifyMinimumSizeChange (anyBox) {
this.minimumClean = false
}
func (this *Hierarchy) invalidateMinimum (box anyBox) {
this.needMinimum.Add(box)
func (this *Hierarchy) invalidateStyle (box anyBox) {
this.needStyle.Add(box)
}
func (this *Hierarchy) invalidateDraw (box anyBox) {
@@ -235,38 +245,68 @@ func (this *Hierarchy) anyFocused () bool {
return this.focused != nil
}
func (this *Hierarchy) boxUnder (point image.Point, category eventCategory) anyBox {
if this.root == nil { return nil }
return this.root.boxUnder(point, category)
}
func (this *Hierarchy) captures (eventCategory) bool {
func (this *Hierarchy) masks () bool {
return false
}
func (this *Hierarchy) keyboardTarget () anyBox {
func (this *Hierarchy) boxUnder (point image.Point) anyBox {
if this.root == nil { return nil }
return this.root.boxUnder(point)
}
func (this *Hierarchy) parents (box anyBox) func (func (anyBox) bool) {
return func (yield func (anyBox) bool) {
for box != nil && yield(box) {
parent, ok := box.getParent().(anyBox)
if !ok { break }
box = parent
}
}
}
func (this *Hierarchy) boxesUnder (point image.Point) func (func (anyBox) bool) {
return this.parents(this.boxUnder(point))
}
func (this *Hierarchy) keyboardTargets (yield func (anyBox) bool) {
focused := this.focused
if focused == nil { return nil }
parent := focused.getParent()
if focused == nil { return }
this.parents(this.considerMaskingParents(focused))(yield)
}
func (this *Hierarchy) considerMaskingParents (box anyBox) anyBox {
parent := box.getParent()
for {
parentBox, ok := parent.(anyBox)
if !ok { break }
if parent.captures(eventCategoryKeyboard) {
if parent.masks() {
return parentBox
}
parent = parentBox.getParent()
}
return box
}
return focused
func (this *Hierarchy) isMasked (box anyBox) bool {
parent := box.getParent()
for {
parentBox, ok := parent.(anyBox)
if !ok { break }
if parent.masks() {
return true
}
parent = parentBox.getParent()
}
return false
}
func (this *Hierarchy) focusNext () {
found := !this.anyFocused()
focused := false
this.propagateAlt (func (box anyBox) bool {
this.propagateAlt(func (box anyBox) bool {
if found {
// looking for the next box to select
if box.canBeFocused() {
if box.canBeFocused() && !this.isMasked(box) {
// found it
this.focus(box)
focused = true
@@ -287,11 +327,11 @@ func (this *Hierarchy) focusNext () {
func (this *Hierarchy) focusPrevious () {
var behind anyBox
this.propagate (func (box anyBox) bool {
this.propagate(func (box anyBox) bool {
if box == this.focused {
return false
}
if box.canBeFocused() { behind = box }
if box.canBeFocused() && !this.isMasked(box) { behind = box }
return true
})
this.focus(behind)
@@ -314,13 +354,25 @@ func (this *Hierarchy) drawBackgroundPart (canvas.Canvas) {
// if so, windows should be transparent if the color has transparency
}
// var minimumSizeCount = 0
func (this *Hierarchy) doMinimumSize () {
this.minimumClean = true
// println("doMinimumSize", minimumSizeCount)
// minimumSizeCount ++
previousMinimumSize := this.minimumSize
this.minimumSize = image.Point { }
if this.root != nil {
this.minimumSize = this.root.MinimumSize()
this.minimumSize = this.root.minimumSize()
}
this.link.NotifyMinimumSizeChange()
if previousMinimumSize != this.minimumSize {
this.link.NotifyMinimumSizeChange()
}
}
func (this *Hierarchy) newStyleApplicator () *styleApplicator {
return &styleApplicator {
style: this.getStyle(),
}
}

View File

@@ -5,14 +5,6 @@ import "git.tebibyte.media/tomo/tomo"
import "git.tebibyte.media/tomo/tomo/input"
import "git.tebibyte.media/tomo/tomo/canvas"
// eventCategory lists kinds of Tomo events.
type eventCategory int; const (
eventCategoryDND eventCategory = iota
eventCategoryMouse
eventCategoryScroll
eventCategoryKeyboard
)
// parent is any hierarchical type which contains other boxes. This can be a
// Hierarchy, containerBox, etc.
type parent interface {
@@ -27,9 +19,8 @@ type parent interface {
// given Canvas, filling the Canvas's entire bounds. The origin (0, 0)
// of the given Canvas is assumed to be the same as the parent's canvas.
drawBackgroundPart (canvas.Canvas)
// captures returns whether or not this parent captures the given event
// category.
captures (eventCategory) bool
// catches returns whether or not this parent masks events.
masks () bool
}
// anyBox is any tomo.Box type that is implemented by this package.
@@ -44,10 +35,10 @@ type anyBox interface {
// doDraw re-paints the anyBox onto its currently held Canvas non-recursively
// doLayout re-calculates the layout of the anyBox non-recursively
// doMinimumSize re-calculates the minimum size of the anyBox non-recursively
doDraw ()
doLayout ()
doMinimumSize ()
// doStyle re-applies the box's style non-recursively
doDraw ()
doLayout ()
doStyle ()
// flushActionQueue performs any queued actions, like invalidating the
// minimum size or grabbing input focus.
@@ -62,7 +53,9 @@ type anyBox interface {
// to check whether they have an outdated style or icon set, and if so,
// update it and trigger the appropriate event broadcasters.
recursiveReApply ()
// minimumSize returns the box's minimum size
minimumSize () image.Point
// contentMinimum returns the minimum dimensions of this box's content
contentMinimum () image.Point
// canBeFocused returns whether or not this anyBox is capable of holding
@@ -71,12 +64,18 @@ type anyBox interface {
// boxUnder returns the anyBox under the mouse pointer. It can be this
// anyBox, one of its children (if applicable). It must return nil if
// the mouse pointer is outside of this anyBox's bounds.
boxUnder (image.Point, eventCategory) anyBox
boxUnder (image.Point) anyBox
// transparent returns whether or not this anyBox contains transparent
// pixels or not, and thus needs its parent's backround to be painted
// underneath it.
transparent () bool
// setBounds sets the box's bounds.
setBounds (image.Rectangle)
// setAttr sets an attribute at the user or style level depending
// on the value of user.
setAttr (attr tomo.Attr, user bool)
// propagate recursively calls a function on this anyBox, and all of its
// children (if applicable) The normal propagate behavior calls the
// callback on all children before calling it on this anyBox, and
@@ -92,12 +91,21 @@ type anyBox interface {
// handleDndDrop (data.Data)
handleMouseEnter ()
handleMouseLeave ()
handleMouseMove ()
handleMouseDown (input.Button)
handleMouseUp (input.Button)
handleScroll (float64, float64)
handleKeyDown (input.Key, bool)
handleKeyUp (input.Key, bool)
handleMouseMove () bool
handleMouseDown (input.Button) bool
handleMouseUp (input.Button) bool
handleScroll (float64, float64) bool
handleKeyDown (input.Key, bool) bool
handleKeyUp (input.Key, bool) bool
}
type anyContentBox interface {
anyBox
// recommendedWidth returns the recommended width for a given height.
recommendedWidth (int) int
// recommendedHeight returns the recommended height for a given height.
recommendedHeight (int) int
}
func assertAnyBox (unknown tomo.Box) anyBox {

47
internal/system/style.go Normal file
View File

@@ -0,0 +1,47 @@
package system
import "git.tebibyte.media/tomo/tomo"
type styleApplicator struct {
style *tomo.Style
role tomo.Role
rules []tomo.Rule
}
func (this *styleApplicator) apply (box anyBox) {
if box.Role() != this.role {
this.role = box.Role()
// the role has changed, so re-cache the list of rules
this.rules = nil
for _, rule := range this.style.Rules {
role := box.Role()
// blank fields match anything
if rule.Role.Package == "" { role.Package = "" }
if rule.Role.Object == "" { role.Object = "" }
if rule.Role == role {
this.rules = append(this.rules, rule)
}
}
}
// compile list of attributes by searching through the cached ruleset
attrs := make(tomo.AttrSet)
for _, rule := range this.rules {
satisifed := true
for _, tag := range rule.Tags {
if !box.Tag(tag) {
satisifed = false
break
}
}
if satisifed {
attrs.MergeOver(rule.Set)
}
}
// apply that list of attributes
for _, attr := range attrs {
box.setAttr(attr, false)
}
}

View File

@@ -11,7 +11,7 @@ import "git.tebibyte.media/tomo/backend/internal/util"
type System struct {
link BackendLink
style tomo.Style
style *tomo.Style
styleNonce int
iconsNonce int
@@ -45,7 +45,7 @@ func New (link BackendLink) *System {
// SetStyle sets the tomo.Style that is applied to objects, and notifies them
// that the style has changed.
func (this *System) SetStyle (style tomo.Style) {
func (this *System) SetStyle (style *tomo.Style) {
this.style = style
this.styleNonce ++
for hierarchy := range this.hierarchies {
@@ -53,11 +53,11 @@ func (this *System) SetStyle (style tomo.Style) {
}
}
// SetIcons notifies objects that the icons have changed.
func (this *System) SetIcons (icons tomo.Icons) {
// SetIconSet notifies objects that the icons have changed.
func (this *System) SetIconSet (iconSet tomo.IconSet) {
this.iconsNonce ++
for hierarchy := range this.hierarchies {
hierarchy.setIcons()
hierarchy.setIconSet()
}
}

View File

@@ -2,7 +2,6 @@ package system
import "image"
import "image/color"
import "golang.org/x/image/font"
import "git.tebibyte.media/tomo/tomo"
import "golang.org/x/image/math/fixed"
import "git.tebibyte.media/tomo/typeset"
@@ -14,22 +13,22 @@ import "git.tebibyte.media/tomo/tomo/canvas"
type textBox struct {
*box
hOverflow, vOverflow bool
contentBounds image.Rectangle
scroll image.Point
text string
textColor color.Color
face font.Face
wrap bool
hAlign tomo.Align
vAlign tomo.Align
attrTextColor attrHierarchy[tomo.AttrTextColor]
attrDotColor attrHierarchy[tomo.AttrDotColor]
attrFace attrHierarchy[tomo.AttrFace]
attrWrap attrHierarchy[tomo.AttrWrap]
attrAlign attrHierarchy[tomo.AttrAlign]
attrOverflow attrHierarchy[tomo.AttrOverflow]
text string
runes []rune
selectable bool
selecting bool
selectStart int
dot text.Dot
dotColor color.Color
drawer typeset.Drawer
@@ -40,20 +39,12 @@ type textBox struct {
}
func (this *System) NewTextBox () tomo.TextBox {
box := &textBox {
textColor: color.Black,
dotColor: color.RGBA { B: 255, G: 255, A: 255 },
}
box := &textBox { }
box.box = this.newBox(box)
return box
}
func (this *textBox) SetOverflow (horizontal, vertical bool) {
if this.hOverflow == horizontal && this.vOverflow == vertical { return }
this.hOverflow = horizontal
this.vOverflow = vertical
this.invalidateLayout()
}
// ----- public methods ----------------------------------------------------- //
func (this *textBox) ContentBounds () image.Rectangle {
return this.contentBounds
@@ -71,7 +62,7 @@ func (this *textBox) RecommendedHeight (width int) int {
func (this *textBox) RecommendedWidth (height int) int {
// TODO maybe not the best idea?
return this.MinimumSize().X
return this.minimumSize().X
}
func (this *textBox) OnContentBoundsChange (callback func()) event.Cookie {
@@ -80,29 +71,9 @@ func (this *textBox) OnContentBoundsChange (callback func()) event.Cookie {
func (this *textBox) SetText (text string) {
if this.text == text { return }
this.text = text
this.drawer.SetText([]rune(text))
this.invalidateMinimum()
this.invalidateLayout()
}
func (this *textBox) SetTextColor (c color.Color) {
if this.textColor == c { return }
this.textColor = c
this.invalidateDraw()
}
func (this *textBox) SetFace (face font.Face) {
if this.face == face { return }
this.face = face
this.drawer.SetFace(face)
this.invalidateMinimum()
this.invalidateLayout()
}
func (this *textBox) SetWrap (wrap bool) {
if this.wrap == wrap { return }
this.drawer.SetWrap(wrap)
this.text = text
this.runes = []rune(text)
this.drawer.SetText(this.runes)
this.invalidateMinimum()
this.invalidateLayout()
}
@@ -112,14 +83,6 @@ func (this *textBox) SetSelectable (selectable bool) {
this.selectable = selectable
}
func (this *textBox) SetDotColor (c color.Color) {
if this.dotColor == c { return }
this.dotColor = c
if !this.dot.Empty() {
this.invalidateDraw()
}
}
func (this *textBox) Select (dot text.Dot) {
if !this.selectable { return }
if this.dot == dot { return }
@@ -138,21 +101,20 @@ func (this *textBox) OnDotChange (callback func ()) event.Cookie {
return this.on.dotChange.Connect(callback)
}
func (this *textBox) SetAlign (x, y tomo.Align) {
if this.hAlign == x && this.vAlign == y { return }
this.hAlign = x
this.vAlign = y
this.drawer.SetAlign(typeset.Align(x), typeset.Align(y))
this.invalidateDraw()
}
// ----- private methods ---------------------------------------------------- //
func (this *textBox) Draw (can canvas.Canvas) {
if can == nil { return }
this.drawBorders(can)
pen := can.Pen()
pen.Fill(this.color)
pen.Texture(this.texture)
texture := this.attrTexture.Value().Texture
col := this.attrColor.Value().Color
if col == nil { col = color.Transparent }
this.drawBorders(can)
pen := can.Pen()
pen.Fill(col)
pen.Texture(texture)
if this.transparent() && this.parent != nil {
this.parent.drawBackgroundPart(can)
}
@@ -162,8 +124,55 @@ func (this *textBox) Draw (can canvas.Canvas) {
this.drawDot(can)
}
if this.face == nil { return }
this.drawer.Draw(can, this.textColor, this.textOffset())
if this.attrFace.Value().Face != nil {
textColor := this.attrTextColor.Value().Color
if textColor == nil { textColor = color.Black }
this.drawer.Draw(can, textColor, this.textOffset())
}
}
func (this *textBox) setAttr (attr tomo.Attr, user bool) {
switch attr := attr.(type) {
case tomo.AttrTextColor:
if this.attrTextColor.Set(attr, user) && !this.dot.Empty() {
this.invalidateDraw()
}
case tomo.AttrDotColor:
if this.attrDotColor.Set(attr, user) && !this.dot.Empty() {
this.invalidateDraw()
}
case tomo.AttrFace:
if this.attrFace.Set(attr, user) {
this.drawer.SetFace(attr.Face)
this.invalidateMinimum()
this.invalidateLayout()
}
case tomo.AttrWrap:
if this.attrWrap.Set(attr, user) {
this.drawer.SetWrap(bool(attr))
this.invalidateMinimum()
this.invalidateLayout()
}
case tomo.AttrAlign:
if this.attrAlign.Set(attr, user) {
this.drawer.SetAlign (
typeset.Align(attr.X),
typeset.Align(attr.Y))
this.invalidateDraw()
}
case tomo.AttrOverflow:
if this.attrOverflow.Set(attr, user) {
this.invalidateMinimum()
this.invalidateLayout()
}
default: this.box.setAttr(attr, user)
}
}
func roundPt (point fixed.Point26_6) image.Point {
@@ -175,14 +184,18 @@ func fixPt (point image.Point) fixed.Point26_6 {
}
func (this *textBox) drawDot (can canvas.Canvas) {
if this.face == nil { return }
if this.attrFace.Value().Face == nil { return }
face := this.attrFace.Value().Face
textColor := this.attrTextColor.Value().Color
dotColor := this.attrDotColor.Value().Color
if textColor == nil { textColor = color.Black }
if dotColor == nil { dotColor = color.RGBA { G: 255, B: 255, A: 255 } }
pen := can.Pen()
pen.Fill(color.Transparent)
pen.Stroke(this.textColor)
bounds := this.InnerBounds()
metrics := this.face.Metrics()
metrics := face.Metrics()
dot := this.dot.Canon()
start := this.drawer.PositionAt(dot.Start).Add(fixPt(this.textOffset()))
end := this.drawer.PositionAt(dot.End ).Add(fixPt(this.textOffset()))
@@ -192,11 +205,12 @@ func (this *textBox) drawDot (can canvas.Canvas) {
switch {
case dot.Empty():
pen.Stroke(textColor)
pen.StrokeWeight(1)
pen.Path(roundPt(start.Add(ascent)), roundPt(start.Sub(descent)))
case start.Y == end.Y:
pen.Fill(this.dotColor)
pen.Fill(dotColor)
pen.StrokeWeight(0)
pen.Rectangle(image.Rectangle {
Min: roundPt(start.Add(ascent)),
@@ -204,7 +218,7 @@ func (this *textBox) drawDot (can canvas.Canvas) {
})
default:
pen.Fill(this.dotColor)
pen.Fill(dotColor)
pen.StrokeWeight(0)
rect := image.Rectangle {
@@ -235,44 +249,121 @@ func (this *textBox) textOffset () image.Point {
Sub(this.drawer.LayoutBoundsSpace().Min)
}
func (this *textBox) handleFocusEnter () {
this.invalidateDraw()
this.box.handleFocusEnter()
}
func (this *textBox) handleFocusLeave () {
this.on.dotChange.Broadcast()
this.invalidateDraw()
this.box.handleFocusLeave()
}
func (this *textBox) handleMouseDown (button input.Button) {
if button == input.ButtonLeft {
func (this *textBox) handleMouseDown (button input.Button) bool {
if this.mouseButtonCanDrag(button) {
index := this.runeUnderMouse()
this.selectStart = index
this.selecting = true
this.Select(text.Dot { Start: this.selectStart, End: index })
}
this.box.handleMouseDown(button)
return this.box.handleMouseDown(button)
}
func (this *textBox) handleMouseUp (button input.Button) {
if button == input.ButtonLeft && this.selecting {
func (this *textBox) handleMouseUp (button input.Button) bool {
if this.mouseButtonCanDrag(button) && this.selecting {
index := this.runeUnderMouse()
this.selecting = false
this.Select(text.Dot { Start: this.selectStart, End: index })
}
this.box.handleMouseUp(button)
return this.box.handleMouseUp(button)
}
func (this *textBox) handleMouseMove () {
func (this *textBox) mouseButtonCanDrag (button input.Button) bool {
return button == input.ButtonLeft ||
button == input.ButtonMiddle ||
button == input.ButtonRight
}
func (this *textBox) handleMouseMove () bool {
if this.selecting {
index := this.runeUnderMouse()
this.Select(text.Dot { Start: this.selectStart, End: index })
}
this.box.handleMouseMove()
return this.box.handleMouseMove()
}
func (this *textBox) runeUnderMouse () int {
position := this.MousePosition().Sub(this.textOffset())
window := this.Window()
if window == nil { return 0 }
position := window.MousePosition().Sub(this.textOffset())
return this.drawer.AtPosition(fixPt(position))
}
func (this *textBox) handleKeyDown (key input.Key, numberPad bool) bool {
if this.box.handleKeyDown(key, numberPad) { return true }
if !this.selectable { return false }
// because fuck you thats why!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
modifiers := this.Window().Modifiers()
dot := this.Dot()
sel := modifiers.Shift
word := modifiers.Control
switch {
case key == input.KeyHome || (modifiers.Alt && key == input.KeyLeft):
dot.End = 0
if !sel { dot.Start = dot.End }
this.Select(dot)
return true
case key == input.KeyEnd || (modifiers.Alt && key == input.KeyRight):
dot.End = len(this.text)
if !sel { dot.Start = dot.End }
this.Select(dot)
return true
case key == input.KeyLeft:
if sel {
this.Select(text.SelectLeft(this.runes, dot, word))
} else {
this.Select(text.MoveLeft(this.runes, dot, word))
}
return true
case key == input.KeyRight:
if sel {
this.Select(text.SelectRight(this.runes, dot, word))
} else {
this.Select(text.MoveRight(this.runes, dot, word))
}
return true
case key == input.Key('a') && modifiers.Control:
dot.Start = 0
dot.End = len(this.text)
this.Select(dot)
return true
default:
return false
}
}
func (this *textBox) handleKeyUp (key input.Key, numberPad bool) bool {
if this.box.handleKeyUp(key, numberPad) { return true }
if !this.selectable { return false }
modifiers := this.Window().Modifiers()
switch {
case key == input.KeyHome || (modifiers.Alt && key == input.KeyLeft):
return true
case key == input.KeyEnd || (modifiers.Alt && key == input.KeyRight):
return true
case key == input.KeyLeft:
return true
case key == input.KeyRight:
return true
case key == input.Key('a') && modifiers.Control:
return true
default:
return false
}
}
func (this *textBox) normalizedLayoutBoundsSpace () image.Rectangle {
bounds := this.drawer.LayoutBoundsSpace()
return bounds.Sub(bounds.Min)
@@ -281,10 +372,10 @@ func (this *textBox) normalizedLayoutBoundsSpace () image.Rectangle {
func (this *textBox) contentMinimum () image.Point {
minimum := this.drawer.MinimumSize()
if this.hOverflow || this.wrap {
if this.attrOverflow.Value().X || bool(this.attrWrap.Value()) {
minimum.X = this.drawer.Em().Round()
}
if this.vOverflow {
if this.attrOverflow.Value().Y {
minimum.Y = this.drawer.LineHeight().Round()
}

View File

@@ -1,5 +1,6 @@
package util
import "io"
import "image/color"
// IndexOf returns the index of needle within haystack. If needle does not exist
@@ -82,6 +83,7 @@ func NewMemo[T any] (update func () T) Memo[T] {
func (this *Memo[T]) Value () T {
if !this.valid {
this.cache = this.update()
this.valid = true
}
return this.cache
}
@@ -99,3 +101,32 @@ func (this *Memo[T]) InvalidateTo (value T) {
this.Invalidate()
this.cache = value
}
// Cycler stores a value and an accompanying io.Closer. When the value is set,
// the closer associated with the previous value is closed.
type Cycler[T any] struct {
value T
closer io.Closer
}
// Value returns the cycler's value.
func (this *Cycler[T]) Value () T {
return this.value
}
// Set sets the value and associated closer, closing the previous one.
func (this *Cycler[T]) Set (value T, closer io.Closer) (err error) {
if this.closer != nil {
err = this.closer.Close()
}
this.value = value
this.closer = closer
return err
}
// Close closes the associated closer early.
func (this *Cycler[T]) Close () error {
err := this.closer.Close()
this.closer = nil
return err
}

View File

@@ -102,20 +102,20 @@ func (this *Backend) NewBox () tomo.Box {
return this.system.NewBox()
}
func (this *Backend) NewCanvasBox () tomo.CanvasBox {
return this.system.NewCanvasBox()
func (this *Backend) NewTextBox () tomo.TextBox {
return this.system.NewTextBox()
}
func (this *Backend) NewContainerBox () tomo.ContainerBox {
return this.system.NewContainerBox()
func (this *Backend) NewCanvasBox () tomo.CanvasBox {
return this.system.NewCanvasBox()
}
func (this *Backend) NewSurfaceBox () (tomo.SurfaceBox, error) {
return this.system.NewSurfaceBox()
}
func (this *Backend) NewTextBox () tomo.TextBox {
return this.system.NewTextBox()
func (this *Backend) NewContainerBox () tomo.ContainerBox {
return this.system.NewContainerBox()
}
func (this *Backend) NewTexture (source image.Image) canvas.TextureCloser {
@@ -126,12 +126,12 @@ func (this *Backend) NewCanvas (bounds image.Rectangle) canvas.CanvasCloser {
return xcanvas.NewCanvas(this.x, bounds)
}
func (this *Backend) SetStyle (style tomo.Style) {
func (this *Backend) SetStyle (style *tomo.Style) {
this.system.SetStyle(style)
}
func (this *Backend) SetIcons (icons tomo.Icons) {
this.system.SetIcons(icons)
func (this *Backend) SetIconSet (icons tomo.IconSet) {
this.system.SetIconSet(icons)
}
func (this *Backend) assert () {

View File

@@ -4,6 +4,7 @@ import "image"
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"
import "git.tebibyte.media/tomo/backend/x/canvas"
@@ -179,12 +180,17 @@ func (this *window) SetTitle (title string) {
icccm.WmIconNameSet (this.backend.x, this.xWindow.Id, title)
}
func (this *window) SetIcon (sizes ...canvas.Texture) {
func (this *window) SetIcon (icon tomo.Icon) {
textures := []canvas.Texture {
icon.Texture(tomo.IconSizeSmall),
icon.Texture(tomo.IconSizeMedium),
icon.Texture(tomo.IconSizeLarge),
}
wmIcons := []ewmh.WmIcon { }
for _, icon := range sizes {
icon, ok := icon.(*xcanvas.Texture)
if !ok { continue }
for _, icon := range textures {
icon := xcanvas.AssertTexture(icon)
bounds := icon.Bounds()
width := bounds.Dx()
@@ -232,6 +238,26 @@ func (this *window) SetBounds (bounds image.Rectangle) {
bounds.Min.Y + bounds.Dy())
}
func (this *window) NewChild (bounds image.Rectangle) (tomo.Window, error) {
leader := this.leader
child, err := this.backend.newWindow (
bounds.Add(this.metrics.bounds.Min), false)
child.leader = leader
if err != nil { return nil, err }
child.setClientLeader(leader)
leader.setClientLeader(leader)
icccm.WmTransientForSet (
this.backend.x,
child.xWindow.Id,
leader.xWindow.Id)
child.setType("UTILITY")
// child.inheritProperties(leader.window)
return child, err
}
func (this *window) NewMenu (bounds image.Rectangle) (tomo.Window, error) {
menu, err := this.backend.newWindow (
bounds.Add(this.metrics.bounds.Min), true)
@@ -262,29 +288,12 @@ func (this *window) NewModal (bounds image.Rectangle) (tomo.Window, error) {
return modal, err
}
func (this *window) NewChild (bounds image.Rectangle) (tomo.Window, error) {
leader := this.leader
child, err := this.backend.newWindow (
bounds.Add(this.metrics.bounds.Min), false)
child.leader = leader
if err != nil { return nil, err }
child.setClientLeader(leader)
leader.setClientLeader(leader)
icccm.WmTransientForSet (
this.backend.x,
child.xWindow.Id,
leader.xWindow.Id)
child.setType("UTILITY")
// child.inheritProperties(leader.window)
return child, err
func (this *window) Modifiers () input.Modifiers {
return this.hierarchy.Modifiers()
}
func (this *window) Widget () (tomo.Window, error) {
// TODO
return nil, nil
func (this *window) MousePosition () image.Point {
return this.hierarchy.MousePosition()
}
func (this *window) Copy (data.Data) {