Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3b4ab56914 | |||
| e7f16645eb | |||
| ccbbb735fd | |||
| ab6bdeaba3 | |||
| 93d7eed21f | |||
| b18f747f0c | |||
| fa2ef954b2 | |||
| e4fdde3da1 |
2
go.mod
2
go.mod
@@ -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
4
go.sum
@@ -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=
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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],
|
||||
|
||||
19
x/event.go
19
x/event.go
@@ -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() ||
|
||||
|
||||
33
x/window.go
33
x/window.go
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user