8 Commits

12 changed files with 119 additions and 31 deletions

2
go.mod
View File

@@ -3,7 +3,7 @@ module git.tebibyte.media/tomo/backend
go 1.20 go 1.20
require ( require (
git.tebibyte.media/tomo/tomo v0.45.0 git.tebibyte.media/tomo/tomo v0.46.1
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.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/sashakoshka/xgbkb v1.0.0/go.mod h1:pNcE6TRO93vHd6q42SdwLSTTj25L0Yzggz7yLe0JV6Q=
git.tebibyte.media/tomo/tomo v0.45.0 h1:fQH0WIPidW275hOq9dE6R7p064xG1RGx2QU68Avlr84= git.tebibyte.media/tomo/tomo v0.46.1 h1:/8fT6I9l4TK529zokrThbNDHGRvUsNgif1Zs++0PBSQ=
git.tebibyte.media/tomo/tomo v0.45.0/go.mod h1:WrtilgKB1y8O2Yu7X4mYcRiqOlPR8NuUnoA/ynkQWrs= git.tebibyte.media/tomo/tomo v0.46.1/go.mod h1:WrtilgKB1y8O2Yu7X4mYcRiqOlPR8NuUnoA/ynkQWrs=
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=

View File

@@ -75,7 +75,8 @@ func (this *System) newBox (outer anyBox) *box {
if box.parent == nil { return nil } if box.parent == nil { return nil }
parentCanvas := box.parent.getCanvas() parentCanvas := box.parent.getCanvas()
if parentCanvas == nil { return nil } if parentCanvas == nil { return nil }
return parentCanvas.SubCanvas(box.bounds) drawableArea := box.bounds.Intersect(box.parent.getInnerClippingBounds())
return parentCanvas.SubCanvas(drawableArea)
}) })
if outer == nil { if outer == nil {
box.drawer = box box.drawer = box
@@ -130,11 +131,15 @@ func (this *box) Tag (tag string) bool {
} }
func (this *box) SetTag (tag string, on bool) { func (this *box) SetTag (tag string, on bool) {
wasOn := this.tags.Has(tag)
if on { if on {
this.tags.Add(tag) this.tags.Add(tag)
} else { } else {
delete(this.tags, tag) delete(this.tags, tag)
} }
if wasOn != on {
this.invalidateStyle()
}
} }
func (this *box) SetAttr (attr tomo.Attr) { func (this *box) SetAttr (attr tomo.Attr) {
@@ -255,6 +260,24 @@ func (this *box) unsetAttr (kind tomo.AttrKind, user bool) {
} }
} }
func (this *box) unsetAllAttrs (user bool) {
// keep this in sync with tomo.AttrKind!
this.outer.unsetAttr(tomo.AttrKindColor, user)
this.outer.unsetAttr(tomo.AttrKindTexture, user)
this.outer.unsetAttr(tomo.AttrKindTextureMode, user)
this.outer.unsetAttr(tomo.AttrKindBorder, user)
this.outer.unsetAttr(tomo.AttrKindMinimumSize, user)
this.outer.unsetAttr(tomo.AttrKindPadding, user)
this.outer.unsetAttr(tomo.AttrKindGap, user)
this.outer.unsetAttr(tomo.AttrKindTextColor, user)
this.outer.unsetAttr(tomo.AttrKindDotColor, user)
this.outer.unsetAttr(tomo.AttrKindFace, user)
this.outer.unsetAttr(tomo.AttrKindWrap, user)
this.outer.unsetAttr(tomo.AttrKindAlign, user)
this.outer.unsetAttr(tomo.AttrKindOverflow, user)
this.outer.unsetAttr(tomo.AttrKindLayout, user)
}
func (this *box) setBounds (bounds image.Rectangle) { func (this *box) setBounds (bounds image.Rectangle) {
if this.bounds == bounds { return } if this.bounds == bounds { return }
this.bounds = bounds this.bounds = bounds
@@ -620,6 +643,7 @@ func (this *box) recursiveReApply () {
// applicator for every box, it's so style applicators can cache // applicator for every box, it's so style applicators can cache
// information about the boxes they're linked to (like all rules // information about the boxes they're linked to (like all rules
// with a matching role). // with a matching role).
this.unsetAllAttrs(false)
this.lastStyleNonce = hierarchyStyleNonce this.lastStyleNonce = hierarchyStyleNonce
this.styleApplicator = hierarchy.newStyleApplicator() this.styleApplicator = hierarchy.newStyleApplicator()
this.invalidateStyle() this.invalidateStyle()

View File

@@ -298,6 +298,10 @@ func (this *containerBox) getCanvas () canvas.Canvas {
return this.canvas.Value() return this.canvas.Value()
} }
func (this *containerBox) getInnerClippingBounds () image.Rectangle {
return this.innerClippingBounds
}
func (this *containerBox) notifyMinimumSizeChange (child anyBox) { func (this *containerBox) notifyMinimumSizeChange (child anyBox) {
this.invalidateMinimum() this.invalidateMinimum()
size := child.minimumSize() size := child.minimumSize()

View File

@@ -193,6 +193,10 @@ func (this *Hierarchy) getCanvas () canvas.Canvas {
return this.canvas return this.canvas
} }
func (this *Hierarchy) getInnerClippingBounds () image.Rectangle {
return this.canvas.Bounds()
}
func (this *Hierarchy) getModifiers () input.Modifiers { func (this *Hierarchy) getModifiers () input.Modifiers {
return this.modifiers return this.modifiers
} }

View File

@@ -12,6 +12,9 @@ type parent interface {
getHierarchy () *Hierarchy getHierarchy () *Hierarchy
// canvas returns the canvas held by the parent. // canvas returns the canvas held by the parent.
getCanvas () canvas.Canvas getCanvas () canvas.Canvas
// getInnerClippingBounds returns the area of the canvas that children
// can draw to.
getInnerClippingBounds () image.Rectangle
// notifyMinimumSizeChange informs the parent that the minimum size of // notifyMinimumSizeChange informs the parent that the minimum size of
// one of its children has changed. // one of its children has changed.
notifyMinimumSizeChange (anyBox) notifyMinimumSizeChange (anyBox)

View File

@@ -7,6 +7,7 @@ type styleApplicator struct {
style *style.Style style *style.Style
role tomo.Role role tomo.Role
rules []style.Rule rules []style.Rule
currentSet style.AttrSet
} }
func (this *styleApplicator) apply (box anyBox) { func (this *styleApplicator) apply (box anyBox) {
@@ -41,7 +42,18 @@ func (this *styleApplicator) apply (box anyBox) {
} }
} }
// reset an attribute if it is no longer specified
if this.currentSet != nil {
for kind := range this.currentSet {
_, exists := attrs[kind]
if !exists {
box.unsetAttr(kind, false)
}
}
}
// apply that list of attributes // apply that list of attributes
this.currentSet = attrs
for _, attr := range attrs { for _, attr := range attrs {
box.setAttr(attr, false) box.setAttr(attr, false)
} }

View File

@@ -132,3 +132,32 @@ func convertColor (c color.Color) xgraphics.BGRA {
A: uint8(a >> 8), A: uint8(a >> 8),
} }
} }
// For some reason, xgraphics.BGRA does not specify whether or not it uses
// premultiplied alpha, and information regarding this is contradictory.
// Basically:
// - BGRAModel just takes the result of c.RGBA and bit shifts it, without
// un-doing the aplha premultiplication that is required by Color.RGBA,
// suggesting that xgraphics.BGRA stores alpha-premultiplied color.
// - xgraphics.BlendBGRA lerps between dest and src using only the alpha of
// src (temporarily converting the colors to fucking floats for some reason)
// which seems to suggest that xgraphics.BGRA *does not* store alpha-
// premultiplied color.
// There is no issues page on xgbutil so we may never get an answer to this
// question. However, in this package we just use xgraphics.BGRA to store alpha-
// premultiplied color anyway because its way faster, and I would sooner eat
// spaghetti with a spoon than convert to and from float64 to blend pixels.
func blendPremultipliedBGRA (dst, src xgraphics.BGRA) xgraphics.BGRA {
// https://en.wikipedia.org/wiki/Alpha_compositing
return xgraphics.BGRA {
B: blendPremultipliedChannel(dst.B, src.B, src.A),
G: blendPremultipliedChannel(dst.G, src.G, src.A),
R: blendPremultipliedChannel(dst.R, src.R, src.A),
A: blendPremultipliedChannel(dst.A, src.A, src.A),
}
}
func blendPremultipliedChannel (dst, src, a uint8) uint8 {
dst16, src16, a16 := uint16(dst), uint16(src), uint16(a)
return uint8(src16 + ((dst16 * (255 - a16)) >> 8))
}

View File

@@ -46,7 +46,7 @@ func (this *pen) textureRectangleTransparent (bounds image.Rectangle) {
srcPos := pos.Add(offset) srcPos := pos.Add(offset)
dstIndex := this.image.PixOffset(pos.X, pos.Y) dstIndex := this.image.PixOffset(pos.X, pos.Y)
srcIndex := this.texture.PixOffset(srcPos.X, srcPos.Y) srcIndex := this.texture.PixOffset(srcPos.X, srcPos.Y)
pixel := xgraphics.BlendBGRA(xgraphics.BGRA { pixel := blendPremultipliedBGRA(xgraphics.BGRA {
B: dst[dstIndex + 0], B: dst[dstIndex + 0],
G: dst[dstIndex + 1], G: dst[dstIndex + 1],
R: dst[dstIndex + 2], R: dst[dstIndex + 2],
@@ -93,7 +93,7 @@ func (this *pen) fillRectangleTransparent (c xgraphics.BGRA, bounds image.Rectan
for pos.Y = bounds.Min.Y; pos.Y < bounds.Max.Y; pos.Y ++ { for pos.Y = bounds.Min.Y; pos.Y < bounds.Max.Y; pos.Y ++ {
for pos.X = bounds.Min.X; pos.X < bounds.Max.X; pos.X ++ { for pos.X = bounds.Min.X; pos.X < bounds.Max.X; pos.X ++ {
index := this.image.PixOffset(pos.X, pos.Y) index := this.image.PixOffset(pos.X, pos.Y)
pixel := xgraphics.BlendBGRA(xgraphics.BGRA { pixel := blendPremultipliedBGRA(xgraphics.BGRA {
B: this.image.Pix[index + 0], B: this.image.Pix[index + 0],
G: this.image.Pix[index + 1], G: this.image.Pix[index + 1],
R: this.image.Pix[index + 2], R: this.image.Pix[index + 2],
@@ -256,7 +256,7 @@ func (context *fillingContext) fillPolygonHotTransparent () {
// fill pixels in between // fill pixels in between
for x := left; x < right; x ++ { for x := left; x < right; x ++ {
index := context.image.PixOffset(x, context.y) index := context.image.PixOffset(x, context.y)
pixel := xgraphics.BlendBGRA(xgraphics.BGRA { pixel := blendPremultipliedBGRA(xgraphics.BGRA {
B: context.image.Pix[index + 0], B: context.image.Pix[index + 0],
G: context.image.Pix[index + 1], G: context.image.Pix[index + 1],
R: context.image.Pix[index + 2], R: context.image.Pix[index + 2],

View File

@@ -32,7 +32,7 @@ func (context plottingContext) plot (center image.Point) {
for y := square.Min.Y; y < square.Max.Y; y ++ { for y := square.Min.Y; y < square.Max.Y; y ++ {
for x := square.Min.X; x < square.Max.X; x ++ { for x := square.Min.X; x < square.Max.X; x ++ {
index := context.image.PixOffset(x, y) index := context.image.PixOffset(x, y)
pixel := xgraphics.BlendBGRA(xgraphics.BGRA { pixel := blendPremultipliedBGRA(xgraphics.BGRA {
B: context.image.Pix[index + 0], B: context.image.Pix[index + 0],
G: context.image.Pix[index + 1], G: context.image.Pix[index + 1],
R: context.image.Pix[index + 2], R: context.image.Pix[index + 2],

View File

@@ -145,12 +145,15 @@ func (window *window) updateBounds () {
// need to sum up all their positions. // need to sum up all their positions.
decorGeometry, _ := window.xWindow.DecorGeometry() decorGeometry, _ := window.xWindow.DecorGeometry()
windowGeometry, _ := window.xWindow.Geometry() windowGeometry, _ := window.xWindow.Geometry()
origin := image.Pt( origin := image.Pt (
windowGeometry.X() + decorGeometry.X(), decorGeometry.X(),
windowGeometry.Y() + decorGeometry.Y()) decorGeometry.Y())
window.metrics.bounds = image.Rectangle { innerOrigin := origin.Add(image.Pt (
Min: origin, windowGeometry.X(),
Max: origin.Add(image.Pt(windowGeometry.Width(), windowGeometry.Height())), windowGeometry.Y()))
window.metrics.innerBounds = image.Rectangle {
Min: innerOrigin,
Max: innerOrigin.Add(image.Pt(windowGeometry.Width(), windowGeometry.Height())),
} }
} }
@@ -161,9 +164,9 @@ func (window *window) handleConfigureNotify (
configureEvent := *event.ConfigureNotifyEvent configureEvent := *event.ConfigureNotifyEvent
configureEvent = window.compressConfigureNotify(configureEvent) configureEvent = window.compressConfigureNotify(configureEvent)
oldBounds := window.metrics.bounds oldBounds := window.metrics.innerBounds
window.updateBounds() window.updateBounds()
newBounds := window.metrics.bounds newBounds := window.metrics.innerBounds
sizeChanged := sizeChanged :=
oldBounds.Dx() != newBounds.Dx() || oldBounds.Dx() != newBounds.Dx() ||

View File

@@ -37,7 +37,8 @@ type window struct {
resizeY bool resizeY bool
metrics struct { metrics struct {
bounds image.Rectangle bounds image.Rectangle // bounds, including frame
innerBounds image.Rectangle // bounds of the drawable area
} }
onClose event.FuncBroadcaster onClose event.FuncBroadcaster
@@ -156,7 +157,7 @@ func (this *Backend) newWindow (
// xevent.SelectionRequestFun(window.handleSelectionRequest). // xevent.SelectionRequestFun(window.handleSelectionRequest).
// Connect(this.x, window.xWindow.Id) // Connect(this.x, window.xWindow.Id)
window.metrics.bounds = bounds window.metrics.innerBounds = bounds
window.doMinimumSize() window.doMinimumSize()
this.windows[window.xWindow.Id] = window this.windows[window.xWindow.Id] = window
@@ -165,6 +166,14 @@ func (this *Backend) newWindow (
return return
} }
func (this *window) Bounds () image.Rectangle {
return this.metrics.bounds.Sub(this.metrics.innerBounds.Min)
}
func (this *window) InnerBounds () image.Rectangle {
return this.metrics.innerBounds.Sub(this.metrics.innerBounds.Min)
}
func (this *window) SetRoot (root tomo.Object) { func (this *window) SetRoot (root tomo.Object) {
if root == nil { if root == nil {
this.hierarchy.SetRoot(nil) this.hierarchy.SetRoot(nil)
@@ -242,7 +251,7 @@ func (this *window) NewChild (bounds image.Rectangle) (tomo.Window, error) {
leader := this.leader leader := this.leader
child, err := this.backend.newWindow ( child, err := this.backend.newWindow (
bounds.Add(this.metrics.bounds.Min), false) bounds.Add(this.metrics.innerBounds.Min), false)
child.leader = leader child.leader = leader
if err != nil { return nil, err } if err != nil { return nil, err }
@@ -260,7 +269,7 @@ func (this *window) NewChild (bounds image.Rectangle) (tomo.Window, error) {
func (this *window) NewMenu (bounds image.Rectangle) (tomo.Window, error) { func (this *window) NewMenu (bounds image.Rectangle) (tomo.Window, error) {
menu, err := this.backend.newWindow ( menu, err := this.backend.newWindow (
bounds.Add(this.metrics.bounds.Min), true) bounds.Add(this.metrics.innerBounds.Min), true)
menu.shy = true menu.shy = true
icccm.WmTransientForSet ( icccm.WmTransientForSet (
this.backend.x, this.backend.x,
@@ -273,7 +282,7 @@ func (this *window) NewMenu (bounds image.Rectangle) (tomo.Window, error) {
func (this *window) NewModal (bounds image.Rectangle) (tomo.Window, error) { func (this *window) NewModal (bounds image.Rectangle) (tomo.Window, error) {
modal, err := this.backend.newWindow ( modal, err := this.backend.newWindow (
bounds.Add(this.metrics.bounds.Min), false) bounds.Add(this.metrics.innerBounds.Min), false)
icccm.WmTransientForSet ( icccm.WmTransientForSet (
this.backend.x, this.backend.x,
modal.xWindow.Id, modal.xWindow.Id,
@@ -382,8 +391,8 @@ func (this *window) reallocateCanvas () {
previousHeight = this.xCanvas.Bounds().Dy() previousHeight = this.xCanvas.Bounds().Dy()
} }
newWidth := this.metrics.bounds.Dx() newWidth := this.metrics.innerBounds.Dx()
newHeight := this.metrics.bounds.Dy() newHeight := this.metrics.innerBounds.Dy()
larger := newWidth > previousWidth || newHeight > previousHeight larger := newWidth > previousWidth || newHeight > previousHeight
smaller := newWidth < previousWidth / 2 || newHeight < previousHeight / 2 smaller := newWidth < previousWidth / 2 || newHeight < previousHeight / 2
@@ -403,7 +412,7 @@ func (this *window) reallocateCanvas () {
} }
this.hierarchy.SetCanvas(this.xCanvas.SubCanvas ( this.hierarchy.SetCanvas(this.xCanvas.SubCanvas (
this.metrics.bounds.Sub(this.metrics.bounds.Min))) this.metrics.innerBounds.Sub(this.metrics.innerBounds.Min)))
} }
func (this *window) pushAll () { func (this *window) pushAll () {
@@ -458,12 +467,12 @@ func (this *window) doMinimumSize () {
this.backend.x, this.backend.x,
this.xWindow.Id, this.xWindow.Id,
&hints) &hints)
newWidth := this.metrics.bounds.Dx() newWidth := this.metrics.innerBounds.Dx()
newHeight := this.metrics.bounds.Dy() newHeight := this.metrics.innerBounds.Dy()
if newWidth < size.X { newWidth = size.X } if newWidth < size.X { newWidth = size.X }
if newHeight < size.Y { newHeight = size.Y } if newHeight < size.Y { newHeight = size.Y }
if newWidth != this.metrics.bounds.Dx() || if newWidth != this.metrics.innerBounds.Dx() ||
newHeight != this.metrics.bounds.Dy() { newHeight != this.metrics.innerBounds.Dy() {
this.xWindow.Resize(newWidth, newHeight) this.xWindow.Resize(newWidth, newHeight)
} }
} }