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
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/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.45.0 h1:fQH0WIPidW275hOq9dE6R7p064xG1RGx2QU68Avlr84=
git.tebibyte.media/tomo/tomo v0.45.0/go.mod h1:WrtilgKB1y8O2Yu7X4mYcRiqOlPR8NuUnoA/ynkQWrs=
git.tebibyte.media/tomo/tomo v0.46.1 h1:/8fT6I9l4TK529zokrThbNDHGRvUsNgif1Zs++0PBSQ=
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/go.mod h1:PwDpSdBF3l/EzoIsa2ME7QffVVajnTHZN6l3MHEGe1g=
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 }
parentCanvas := box.parent.getCanvas()
if parentCanvas == nil { return nil }
return parentCanvas.SubCanvas(box.bounds)
drawableArea := box.bounds.Intersect(box.parent.getInnerClippingBounds())
return parentCanvas.SubCanvas(drawableArea)
})
if outer == nil {
box.drawer = box
@@ -130,11 +131,15 @@ func (this *box) Tag (tag string) bool {
}
func (this *box) SetTag (tag string, on bool) {
wasOn := this.tags.Has(tag)
if on {
this.tags.Add(tag)
} else {
delete(this.tags, tag)
}
if wasOn != on {
this.invalidateStyle()
}
}
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) {
if this.bounds == bounds { return }
this.bounds = bounds
@@ -620,6 +643,7 @@ func (this *box) recursiveReApply () {
// 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.unsetAllAttrs(false)
this.lastStyleNonce = hierarchyStyleNonce
this.styleApplicator = hierarchy.newStyleApplicator()
this.invalidateStyle()

View File

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

View File

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

View File

@@ -12,6 +12,9 @@ type parent interface {
getHierarchy () *Hierarchy
// canvas returns the canvas held by the parent.
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
// one of its children has changed.
notifyMinimumSizeChange (anyBox)

View File

@@ -4,9 +4,10 @@ import "git.tebibyte.media/tomo/tomo"
import "git.tebibyte.media/tomo/backend/style"
type styleApplicator struct {
style *style.Style
role tomo.Role
rules []style.Rule
style *style.Style
role tomo.Role
rules []style.Rule
currentSet style.AttrSet
}
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
this.currentSet = attrs
for _, attr := range attrs {
box.setAttr(attr, false)
}

View File

@@ -132,3 +132,32 @@ func convertColor (c color.Color) xgraphics.BGRA {
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)
dstIndex := this.image.PixOffset(pos.X, pos.Y)
srcIndex := this.texture.PixOffset(srcPos.X, srcPos.Y)
pixel := xgraphics.BlendBGRA(xgraphics.BGRA {
pixel := blendPremultipliedBGRA(xgraphics.BGRA {
B: dst[dstIndex + 0],
G: dst[dstIndex + 1],
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.X = bounds.Min.X; pos.X < bounds.Max.X; pos.X ++ {
index := this.image.PixOffset(pos.X, pos.Y)
pixel := xgraphics.BlendBGRA(xgraphics.BGRA {
pixel := blendPremultipliedBGRA(xgraphics.BGRA {
B: this.image.Pix[index + 0],
G: this.image.Pix[index + 1],
R: this.image.Pix[index + 2],
@@ -256,7 +256,7 @@ func (context *fillingContext) fillPolygonHotTransparent () {
// fill pixels in between
for x := left; x < right; x ++ {
index := context.image.PixOffset(x, context.y)
pixel := xgraphics.BlendBGRA(xgraphics.BGRA {
pixel := blendPremultipliedBGRA(xgraphics.BGRA {
B: context.image.Pix[index + 0],
G: context.image.Pix[index + 1],
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 x := square.Min.X; x < square.Max.X; x ++ {
index := context.image.PixOffset(x, y)
pixel := xgraphics.BlendBGRA(xgraphics.BGRA {
pixel := blendPremultipliedBGRA(xgraphics.BGRA {
B: context.image.Pix[index + 0],
G: context.image.Pix[index + 1],
R: context.image.Pix[index + 2],

View File

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

View File

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