13 Commits

Author SHA1 Message Date
8ed1352fd4 Fixed non-cannonized rectangles being drawn 2023-08-24 17:15:34 -04:00
77ccde9e9b Upgrade tomo version 2023-08-24 15:54:22 -04:00
94db4e8ead Transparency support!! 2023-08-23 19:21:28 -04:00
2f0259d913 WIP moving ggfx stuff into xcanvas 2023-08-22 21:24:38 -04:00
8401b5d0f9 Add support for transparency
Need to break away from ggfx to do this, probably put everything
in xcanvas.
2023-08-22 13:17:48 -04:00
45fa282b61 Texture now checks for transparent pixels 2023-08-21 22:39:13 -04:00
fae918a196 Added textures to backend 2023-08-21 00:35:55 -04:00
3cffcfd4e5 Upgraded tomo version 2023-08-21 00:34:57 -04:00
ff8875535d Changed the way minimum sizes are calculated
Boxes that need their minimum size to be updated now use a map like
for layout and drawing. Size set with MinimumSize is now treated as
separate from the content size and the larger size is used.
2023-08-17 23:20:08 -04:00
00629a863d Behavior relating to hovering is more solid 2023-08-12 12:15:34 -04:00
e126c01055 Add support for mouse hover events 2023-08-12 12:10:47 -04:00
19dbc73968 Fixed keyboard navigation 2023-08-12 11:55:25 -04:00
2de079d063 I forgor 2023-08-12 01:03:34 -04:00
17 changed files with 826 additions and 229 deletions

View File

@@ -48,9 +48,7 @@ func (backend *Backend) Run () error {
case <- pingQuit:
return nil // FIXME: if we exited due to an error say so
}
for _, window := range backend.windows {
window.afterEvent()
}
backend.afterEvent()
}
}
@@ -78,3 +76,9 @@ func (backend *Backend) Do (callback func ()) {
func (backend *Backend) assert () {
if backend == nil { panic("nil backend") }
}
func (backend *Backend) afterEvent () {
for _, window := range backend.windows {
window.afterEvent()
}
}

247
box.go
View File

@@ -3,6 +3,7 @@ package x
import "image"
import "image/color"
import "git.tebibyte.media/tomo/tomo"
import "git.tebibyte.media/tomo/x/canvas"
import "git.tebibyte.media/tomo/tomo/data"
import "git.tebibyte.media/tomo/tomo/input"
import "git.tebibyte.media/tomo/tomo/event"
@@ -13,12 +14,19 @@ type box struct {
parent parent
outer anyBox
bounds image.Rectangle
minSize image.Point
bounds image.Rectangle
minSize image.Point
userMinSize image.Point
minSizeQueued bool
focusQueued *bool
padding tomo.Inset
border []tomo.Border
color color.Color
texture *xcanvas.Texture
fillTransparent bool
dndData data.Data
dndAccept []data.Mime
@@ -45,17 +53,27 @@ type box struct {
}
}
func (backend *Backend) NewBox() tomo.Box {
func (backend *Backend) newBox (outer anyBox) *box {
box := &box {
backend: backend,
color: color.White,
outer: outer,
drawer: outer,
}
box.drawer = box
box.outer = box
if outer == nil {
box.drawer = box
box.outer = box
}
box.invalidateMinimum()
return box
}
func (this *box) Box () tomo.Box {
func (backend *Backend) NewBox() tomo.Box {
box := backend.newBox(nil)
return box
}
func (this *box) GetBox () tomo.Box {
return this.outer
}
@@ -97,31 +115,37 @@ func (this *box) SetBounds (bounds image.Rectangle) {
}
func (this *box) SetColor (c color.Color) {
if c == nil { c = color.Transparent }
if this.color == c { return }
this.color = c
this.determineFillTransparency()
this.invalidateDraw()
}
func (this *box) SetTexture (texture canvas.Texture) {
this.texture = xcanvas.AssertTexture(texture)
this.determineFillTransparency()
this.invalidateDraw()
}
func (this *box) SetBorder (border ...tomo.Border) {
this.border = border
this.determineFillTransparency()
this.invalidateLayout()
this.recalculateMinimumSize()
this.invalidateMinimum()
}
func (this *box) SetMinimumSize (size image.Point) {
if this.minSize == size { return }
this.minSize = size
if this.parent != nil {
this.parent.notifyMinimumSizeChange(this)
}
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.recalculateMinimumSize()
this.invalidateMinimum()
}
func (this *box) SetDNDData (dat data.Data) {
@@ -133,9 +157,11 @@ func (this *box) SetDNDAccept (types ...data.Mime) {
}
func (this *box) SetFocused (focused bool) {
if this.parent == nil { return }
window := this.parent.window()
if window == nil { return }
if this.parent == nil || this.parent.window () == nil {
focusedCopy := focused
this.focusQueued = &focusedCopy
return
}
if !this.focusable { return }
if this.Focused () && !focused {
@@ -208,49 +234,132 @@ func (this *box) OnKeyDown (callback func(key input.Key, numberPad bool)) event.
func (this *box) OnKeyUp (callback func(key input.Key, numberPad bool)) event.Cookie {
return this.on.keyUp.Connect(callback)
}
func (this *box) handleFocusEnter () {
this.on.focusEnter.Broadcast()
}
func (this *box) handleFocusLeave () {
this.on.focusLeave.Broadcast()
}
func (this *box) handleMouseEnter () {
this.on.mouseEnter.Broadcast()
}
func (this *box) handleMouseLeave () {
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 {
if this.parent == nil { return }
window := this.parent.window()
if window == nil { return }
window.focus(nil)
}
for _, listener := range this.on.mouseDown.Listeners() {
listener(button)
}
}
func (this *box) handleMouseUp (button input.Button) {
for _, listener := range this.on.mouseUp.Listeners() {
listener(button)
}
}
func (this *box) handleKeyDown (key input.Key, numberPad bool) {
for _, listener := range this.on.keyDown.Listeners() {
listener(key, numberPad)
}
}
func (this *box) handleKeyUp (key input.Key, numberPad bool) {
for _, listener := range this.on.keyUp.Listeners() {
listener(key, numberPad)
}
}
// -------------------------------------------------------------------------- //
func (this *box) Draw (can canvas.Canvas) {
if can == nil { return }
pen := can.Pen()
pen.Fill(this.color)
pen.Rectangle(this.bounds)
if this.texture == nil || !this.texture.Opaque() {
pen.Rectangle(this.bounds)
}
if this.texture != nil {
// TODO drawR texture
}
}
func (this *box) drawBorders (can canvas.Canvas) {
if can == nil { return }
pen := can.Pen()
bounds := this.bounds
rectangle := func (x0, y0, x1, y1 int, c color.Color) {
area := image.Rect(x0, y0, x1, y1)
_, _, _, a := c.RGBA()
if a != 0xFFFF && this.parent != nil {
this.parent.drawBackgroundPart(can.Clip(area))
}
pen.Fill(c)
pen.Rectangle(area)
}
for _, border := range this.border {
pen.Fill(border.Color[tomo.SideTop])
pen.Rectangle(image.Rect (
rectangle (
bounds.Min.X,
bounds.Min.Y,
bounds.Max.X,
bounds.Min.Y + border.Width[tomo.SideTop]))
pen.Fill(border.Color[tomo.SideBottom])
pen.Rectangle(image.Rect (
bounds.Min.Y + border.Width[tomo.SideTop],
border.Color[tomo.SideTop])
rectangle (
bounds.Min.X,
bounds.Max.Y - border.Width[tomo.SideBottom],
bounds.Max.X,
bounds.Max.Y))
pen.Fill(border.Color[tomo.SideLeft])
pen.Rectangle(image.Rect (
bounds.Max.Y,
border.Color[tomo.SideBottom])
rectangle (
bounds.Min.X,
bounds.Min.Y + border.Width[tomo.SideTop],
bounds.Min.X + border.Width[tomo.SideLeft],
bounds.Max.Y - border.Width[tomo.SideBottom]))
pen.Fill(border.Color[tomo.SideRight])
pen.Rectangle(image.Rect (
bounds.Max.Y - border.Width[tomo.SideBottom],
border.Color[tomo.SideLeft])
rectangle (
bounds.Max.X - border.Width[tomo.SideRight],
bounds.Min.Y + border.Width[tomo.SideTop],
bounds.Max.X,
bounds.Max.Y - border.Width[tomo.SideBottom]))
bounds.Max.Y - border.Width[tomo.SideBottom],
border.Color[tomo.SideRight])
bounds = border.Width.Apply(bounds)
}
}
func (this *box) contentMinimum () image.Point {
var minimum image.Point
minimum.X += this.padding.Horizontal()
minimum.Y += this.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
}
if this.minSize.Y < this.userMinSize.Y {
this.minSize.Y = this.userMinSize.Y
}
if this.parent != nil {
this.parent.notifyMinimumSizeChange(this)
}
}
func (this *box) doDraw () {
if this.canvas == nil { return }
if this.drawer != nil {
@@ -273,6 +382,17 @@ func (this *box) setParent (parent parent) {
this.parent = parent
}
func (this *box) flushActionQueue () {
if this.parent == nil || this.parent.window() == nil { return }
if this.minSizeQueued {
this.invalidateMinimum()
}
if this.focusQueued != nil {
this.SetFocused(*this.focusQueued)
}
}
func (this *box) recursiveRedo () {
this.doLayout()
this.doDraw()
@@ -288,10 +408,12 @@ func (this *box) invalidateDraw () {
this.parent.window().invalidateDraw(this.outer)
}
func (this *box) recalculateMinimumSize () {
if this.outer != anyBox(this) {
this.outer.recalculateMinimumSize()
func (this *box) invalidateMinimum () {
if this.parent == nil || this.parent.window() == nil {
this.minSizeQueued = true
return
}
this.parent.window().invalidateMinimum(this.outer)
}
func (this *box) canBeFocused () bool {
@@ -306,62 +428,17 @@ func (this *box) boxUnder (point image.Point) anyBox {
}
}
func (this *box) handleFocusEnter () {
this.on.focusEnter.Broadcast()
}
func (this *box) handleFocusLeave () {
this.on.focusLeave.Broadcast()
}
func (this *box) handleMouseEnter () {
this.on.mouseEnter.Broadcast()
}
func (this *box) handleMouseLeave () {
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 {
if this.parent == nil { return }
window := this.parent.window()
if window == nil { return }
window.focus(nil)
}
for _, listener := range this.on.mouseDown.Listeners() {
listener(button)
}
}
func (this *box) handleMouseUp (button input.Button) {
for _, listener := range this.on.mouseUp.Listeners() {
listener(button)
}
}
func (this *box) handleKeyDown (key input.Key, numberPad bool) {
for _, listener := range this.on.keyDown.Listeners() {
listener(key, numberPad)
}
}
func (this *box) handleKeyUp (key input.Key, numberPad bool) {
for _, listener := range this.on.keyUp.Listeners() {
listener(key, numberPad)
}
func (this *box) determineFillTransparency () {
_, _, _, a := this.color.RGBA()
this.fillTransparent =
a != 0xFFFF &&
!(this.texture != nil && this.texture.Opaque())
}
func (this *box) propagate (callback func (anyBox) bool) bool {
return callback(this)
return callback(this.outer)
}
func (this *box) propagateAlt (callback func (anyBox) bool) bool {
return callback(this)
return callback(this.outer)
}

View File

@@ -4,22 +4,23 @@ import "image"
import "image/color"
import "github.com/jezek/xgbutil"
import "github.com/jezek/xgb/xproto"
import "git.tebibyte.media/tomo/ggfx"
import "github.com/jezek/xgbutil/xgraphics"
import "git.tebibyte.media/tomo/tomo/canvas"
// Canvas satisfies the canvas.Canvas interface. It draws to an xgraphics.Image.
// It must be closed after use.
type Canvas struct {
*xgraphics.Image
}
// New creates a new canvas from a bounding rectangle.
func New (x *xgbutil.XUtil, bounds image.Rectangle) *Canvas {
return NewFrom(xgraphics.New(x, bounds))
// NewCanvas creates a new canvas from a bounding rectangle.
func NewCanvas (x *xgbutil.XUtil, bounds image.Rectangle) *Canvas {
return NewCanvasFrom(xgraphics.New(x, bounds))
}
// NewFrom creates a new canvas from an existing xgraphics.Image.
func NewFrom (image *xgraphics.Image) *Canvas {
// NewCanvasFrom creates a new canvas from an existing xgraphics.Image. Note
// that calling Close() on the resulting canvas will destroy this image.
func NewCanvasFrom (image *xgraphics.Image) *Canvas {
if image == nil { return nil }
return &Canvas { image }
}
@@ -28,12 +29,6 @@ func NewFrom (image *xgraphics.Image) *Canvas {
func (this *Canvas) Pen () canvas.Pen {
return &pen {
image: this.Image,
gfx: ggfx.Image[uint8] {
Pix: this.Image.Pix,
Stride: this.Image.Stride,
Rect: this.Image.Rect,
Width: 4,
},
}
}
@@ -53,6 +48,12 @@ func (this *Canvas) Push (window xproto.Window) {
this.XExpPaint(window, this.Bounds().Min.X, this.Bounds().Min.Y)
}
// Close frees this canvas from the X server.
func (this *Canvas) Close () {
this.assert()
this.Image.Destroy()
}
func (this *Canvas) assert () {
if this == nil { panic("nil canvas") }
}
@@ -64,50 +65,62 @@ func (this *Canvas) assert () {
type pen struct {
image *xgraphics.Image
gfx ggfx.Image[uint8]
closed bool
endCap canvas.Cap
joint canvas.Joint
weight int
align canvas.StrokeAlign
stroke [4]uint8
fill [4]uint8
closed bool
endCap canvas.Cap
joint canvas.Joint
weight int
align canvas.StrokeAlign
stroke xgraphics.BGRA
fill xgraphics.BGRA
texture *Texture
}
func (this *pen) Rectangle (bounds image.Rectangle) {
bounds = bounds.Canon()
if this.weight == 0 {
this.gfx.FillRectangle(this.fill[:], bounds)
if this.fill.A > 0 {
this.fillRectangle(this.fill, bounds)
}
} else {
this.gfx.StrokeRectangle(this.stroke[:], this.weight, bounds)
if this.stroke.A > 0 {
this.strokeRectangle(this.stroke, bounds)
}
}
}
func (this *pen) Path (points ...image.Point) {
if this.weight == 0 {
this.gfx.FillPolygon(this.fill[:], points...)
if this.fill.A > 0 {
this.fillPolygon(this.fill, points...)
}
} else if this.closed {
this.gfx.StrokePolygon(this.stroke[:], this.weight, points...)
if this.stroke.A > 0 {
this.strokePolygon(this.stroke, points...)
}
} else {
this.gfx.PolyLine(this.stroke[:], this.weight, points...)
if this.stroke.A > 0 {
this.polyLine(this.stroke, points...)
}
}
}
func (this *pen) Closed (closed bool) { this.closed = closed }
func (this *pen) Cap (endCap canvas.Cap) { this.endCap = endCap }
func (this *pen) Joint (joint canvas.Joint) { this.joint = joint }
func (this *pen) StrokeWeight (weight int) { this.weight = weight }
func (this *pen) StrokeAlign (align canvas.StrokeAlign) { this.align = align }
func (this *pen) Stroke (stroke color.Color) { this.stroke = convertColor(stroke) }
func (this *pen) Fill (fill color.Color) { this.fill = convertColor(fill) }
func (this *pen) Stroke (stroke color.Color) { this.stroke = convertColor(stroke) }
func (this *pen) Fill (fill color.Color) { this.fill = convertColor(fill) }
func (this *pen) Texture (texture canvas.Texture) { this.texture = AssertTexture(texture) }
func convertColor (c color.Color) [4]uint8 {
func convertColor (c color.Color) xgraphics.BGRA {
r, g, b, a := c.RGBA()
return [4]uint8 {
uint8(b >> 8),
uint8(g >> 8),
uint8(r >> 8),
uint8(a >> 8),
return xgraphics.BGRA {
B: uint8(b >> 8),
G: uint8(g >> 8),
R: uint8(r >> 8),
A: uint8(a >> 8),
}
}

229
canvas/draw.go Normal file
View File

@@ -0,0 +1,229 @@
package xcanvas
import "sort"
import "image"
import "github.com/jezek/xgbutil/xgraphics"
func (this *pen) fillRectangle (c xgraphics.BGRA, bounds image.Rectangle) {
if c.A == 255 {
this.fillRectangleOpaque(c, bounds)
} else {
this.fillRectangleTransparent(c, bounds)
}
}
func (this *pen) fillRectangleOpaque (c xgraphics.BGRA, bounds image.Rectangle) {
bounds = bounds.Intersect(this.image.Bounds())
var pos image.Point
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)
this.image.Pix[index + 0] = c.B
this.image.Pix[index + 1] = c.G
this.image.Pix[index + 2] = c.R
this.image.Pix[index + 3] = c.A
}}
}
func (this *pen) fillRectangleTransparent (c xgraphics.BGRA, bounds image.Rectangle) {
bounds = bounds.Intersect(this.image.Bounds())
var pos image.Point
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 {
B: this.image.Pix[index + 0],
G: this.image.Pix[index + 1],
R: this.image.Pix[index + 2],
A: this.image.Pix[index + 3],
}, c)
this.image.Pix[index + 0] = pixel.B
this.image.Pix[index + 1] = pixel.G
this.image.Pix[index + 2] = pixel.R
this.image.Pix[index + 3] = pixel.A
}}
}
func (this *pen) strokeRectangle (c xgraphics.BGRA, bounds image.Rectangle) {
if this.weight > bounds.Dx() / 2 || this.weight > bounds.Dy() / 2 {
this.fillRectangle(c, bounds)
return
}
top := image.Rect (
bounds.Min.X,
bounds.Min.Y,
bounds.Max.X,
bounds.Min.Y + this.weight)
bottom := image.Rect (
bounds.Min.X,
bounds.Max.Y - this.weight,
bounds.Max.X,
bounds.Max.Y)
left := image.Rect (
bounds.Min.X,
bounds.Min.Y + this.weight,
bounds.Min.X + this.weight,
bounds.Max.Y - this.weight)
right := image.Rect (
bounds.Max.X - this.weight,
bounds.Min.Y + this.weight,
bounds.Max.X,
bounds.Max.Y - this.weight)
this.fillRectangle(c, top,)
this.fillRectangle(c, bottom,)
this.fillRectangle(c, left,)
this.fillRectangle(c, right,)
}
// the polygon filling algorithm is adapted from:
// https://www.alienryderflex.com/polygon_fill/
// (if you write C like that i will disassemble you)
func (this *pen) fillPolygon (c xgraphics.BGRA, points ...image.Point) {
if len(points) < 3 { return }
// figure out the bounds of the polygon so we don't test empty space
var area image.Rectangle
area.Min = points[0]
area.Max = points[0]
for _, point := range points[1:] {
if point.X < area.Min.X { area.Min.X = point.X }
if point.Y < area.Min.Y { area.Min.Y = point.Y }
if point.X > area.Max.X { area.Max.X = point.X }
if point.Y > area.Max.Y { area.Max.Y = point.Y }
}
area = this.image.Bounds().Intersect(area)
if area.Empty() { return }
context := fillingContext {
image: this.image,
color: this.fill,
min: area.Min.X,
max: area.Max.X,
boundaries: make([]int, len(points)),
points: points,
}
for context.y = area.Min.Y; context.y < area.Max.Y; context.y ++ {
// build boundary list
boundaryCount := 0
prevPoint := points[len(points) - 1]
for _, point := range points {
fy := float64(context.y)
fPointX := float64(point.X)
fPointY := float64(point.Y)
fPrevX := float64(prevPoint.X)
fPrevY := float64(prevPoint.Y)
addboundary :=
(fPointY < fy && fPrevY >= fy) ||
(fPrevY < fy && fPointY >= fy)
if addboundary {
context.boundaries[boundaryCount] = int (
fPointX +
(fy - fPointY) /
(fPrevY - fPointY) *
(fPrevX - fPointX))
boundaryCount ++
}
prevPoint = point
}
// sort boundary list
cutBoundaries := context.boundaries[:boundaryCount]
sort.Ints(cutBoundaries)
// fill pixels between boundary pairs
if c.A == 255 {
context.fillPolygonHotOpaque()
} else {
context.fillPolygonHotTransparent()
}
}
}
type fillingContext struct {
image *xgraphics.Image
color xgraphics.BGRA
min, max int
y int
boundaries []int
points []image.Point
}
func (context *fillingContext) fillPolygonHotOpaque () {
for index := 0; index < len(context.boundaries); index += 2 {
left := context.boundaries[index]
right := context.boundaries[index + 1]
// stop if we have exited the polygon
if left >= context.max { break }
// begin filling if we are within the polygon
if right > context.min {
// constrain boundaries to image size
if left < context.min { left = context.min }
if right > context.max { right = context.max }
// fill pixels in between
for x := left; x < right; x ++ {
index := context.image.PixOffset(x, context.y)
context.image.Pix[index + 0] = context.color.B
context.image.Pix[index + 1] = context.color.G
context.image.Pix[index + 2] = context.color.R
context.image.Pix[index + 3] = context.color.A
}
}
}
}
func (context *fillingContext) fillPolygonHotTransparent () {
for index := 0; index < len(context.boundaries); index += 2 {
left := context.boundaries[index]
right := context.boundaries[index + 1]
// stop if we have exited the polygon
if left >= context.max { break }
// begin filling if we are within the polygon
if right > context.min {
// constrain boundaries to image size
if left < context.min { left = context.min }
if right > context.max { right = context.max }
// fill pixels in between
for x := left; x < right; x ++ {
index := context.image.PixOffset(x, context.y)
pixel := xgraphics.BlendBGRA(xgraphics.BGRA {
B: context.image.Pix[index + 0],
G: context.image.Pix[index + 1],
R: context.image.Pix[index + 2],
A: context.image.Pix[index + 3],
}, context.color)
context.image.Pix[index + 0] = pixel.B
context.image.Pix[index + 1] = pixel.G
context.image.Pix[index + 2] = pixel.R
context.image.Pix[index + 3] = pixel.A
}
}
}
}
func (this *pen) strokePolygon (c xgraphics.BGRA, points ...image.Point) {
prevPoint := points[len(points) - 1]
for _, point := range points {
this.line(c, prevPoint, point)
prevPoint = point
}
}
func (this *pen) polyLine (c xgraphics.BGRA, points ...image.Point) {
if len(points) < 2 { return }
prevPoint := points[0]
for _, point := range points[1:] {
this.line(c, prevPoint, point)
prevPoint = point
}
}

94
canvas/line.go Normal file
View File

@@ -0,0 +1,94 @@
package xcanvas
import "image"
import "github.com/jezek/xgbutil/xgraphics"
func (this *pen) line (
c xgraphics.BGRA,
min image.Point,
max image.Point,
) {
context := linePlottingContext {
plottingContext: plottingContext {
image: this.image,
color: c,
weight: this.weight,
},
min: min,
max: max,
}
if abs(max.Y - min.Y) < abs(max.X - min.X) {
if max.X < min.X { context.swap() }
context.lineLow()
} else {
if max.Y < min.Y { context.swap() }
context.lineHigh()
}
}
type linePlottingContext struct {
plottingContext
min image.Point
max image.Point
}
func (context *linePlottingContext) swap () {
temp := context.max
context.max = context.min
context.min = temp
}
func (context linePlottingContext) lineLow () {
deltaX := context.max.X - context.min.X
deltaY := context.max.Y - context.min.Y
yi := 1
if deltaY < 0 {
yi = -1
deltaY *= -1
}
D := (2 * deltaY) - deltaX
point := context.min
for ; point.X < context.max.X; point.X ++ {
context.plot(point)
if D > 0 {
D += 2 * (deltaY - deltaX)
point.Y += yi
} else {
D += 2 * deltaY
}
}
}
func (context linePlottingContext) lineHigh () {
deltaX := context.max.X - context.min.X
deltaY := context.max.Y - context.min.Y
xi := 1
if deltaX < 0 {
xi = -1
deltaX *= -1
}
D := (2 * deltaX) - deltaY
point := context.min
for ; point.Y < context.max.Y; point.Y ++ {
context.plot(point)
if D > 0 {
point.X += xi
D += 2 * (deltaX - deltaY)
} else {
D += 2 * deltaX
}
}
}
func abs (n int) int {
if n < 0 { n *= -1}
return n
}

47
canvas/plot.go Normal file
View File

@@ -0,0 +1,47 @@
package xcanvas
import "image"
import "github.com/jezek/xgbutil/xgraphics"
type plottingContext struct {
image *xgraphics.Image
color xgraphics.BGRA
weight int
}
func (context plottingContext) square (center image.Point) (square image.Rectangle) {
return image.Rect(0, 0, context.weight, context.weight).
Sub(image.Pt(context.weight / 2, context.weight / 2)).
Add(center).
Intersect(context.image.Bounds())
}
func (context plottingContext) plot (center image.Point) {
square := context.square(center)
if context.color.A == 255 {
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)
context.image.Pix[index + 0] = context.color.B
context.image.Pix[index + 1] = context.color.G
context.image.Pix[index + 2] = context.color.R
context.image.Pix[index + 3] = context.color.A
}}
} else {
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 {
B: context.image.Pix[index + 0],
G: context.image.Pix[index + 1],
R: context.image.Pix[index + 2],
A: context.image.Pix[index + 3],
}, context.color)
context.image.Pix[index + 0] = pixel.B
context.image.Pix[index + 1] = pixel.G
context.image.Pix[index + 2] = pixel.R
context.image.Pix[index + 3] = pixel.A
}}
}
}

69
canvas/texture.go Normal file
View File

@@ -0,0 +1,69 @@
package xcanvas
import "image"
import "git.tebibyte.media/tomo/tomo/canvas"
// Texture is a read-only image texture that can be quickly written to a canvas.
// It must be closed manually after use.
type Texture struct {
pix []uint8
stride int
rect image.Rectangle
transparent bool
}
// NewTextureFrom creates a new texture from a source image.
func NewTextureFrom (source image.Image) *Texture {
bounds := source.Bounds()
texture := &Texture {
pix: make([]uint8, bounds.Dx() * bounds.Dy() * 4),
stride: bounds.Dx(),
rect: bounds.Sub(bounds.Min),
}
index := 0
var point image.Point
for point.Y = bounds.Min.Y; point.Y < bounds.Max.Y; point.Y ++ {
for point.X = bounds.Min.X; point.X < bounds.Max.X; point.X ++ {
r, g, b, a := source.At(point.X, point.Y).RGBA()
texture.pix[index + 0] = uint8(b >> 8)
texture.pix[index + 1] = uint8(g >> 8)
texture.pix[index + 2] = uint8(r >> 8)
texture.pix[index + 3] = uint8(a >> 8)
index += 4
if a != 0xFFFF {
texture.transparent = true
}
}}
return texture
}
// Opaque reports whether or not the texture is fully opaque.
func (this *Texture) Opaque () bool {
return !this.transparent
}
// Close frees the texture from memory.
func (this *Texture) Close () error {
// i lied we dont actually need to close this, but we will once this
// texture resides on the x server or in video memory.
return nil
}
// Clip returns a subset of this texture that points to the same data.
func (this *Texture) Clip (bounds image.Rectangle) canvas.Texture {
clipped := *this
clipped.rect = bounds
return &clipped
}
// AssertTexture checks if a given canvas.Texture is a texture from this package.
func AssertTexture (unknown canvas.Texture) *Texture {
if tx, ok := unknown.(*Texture); ok {
return tx
} else {
panic("foregin texture implementation, i did not make this!")
}
}

View File

@@ -8,11 +8,9 @@ type canvasBox struct {
}
func (backend *Backend) NewCanvasBox () tomo.CanvasBox {
box := &canvasBox {
box: backend.NewBox().(*box),
}
box.outer = box
return box
this := &canvasBox { }
this.box = backend.newBox(this)
return this
}
func (this *canvasBox) Box () tomo.Box {

View File

@@ -24,16 +24,8 @@ type containerBox struct {
}
func (backend *Backend) NewContainerBox() tomo.ContainerBox {
box := &containerBox {
box: backend.NewBox().(*box),
propagateEvents: true,
}
box.drawer = box
box.outer = box
return box
}
func (this *containerBox) Box () tomo.Box {
this := &containerBox { propagateEvents: true }
this.box = backend.newBox(this)
return this
}
@@ -73,42 +65,43 @@ func (this *containerBox) SetGap (gap image.Point) {
if this.gap == gap { return }
this.gap = gap
this.invalidateLayout()
this.recalculateMinimumSize()
this.invalidateMinimum()
}
func (this *containerBox) Add (child tomo.Object) {
box := assertAnyBox(child.Box())
box := assertAnyBox(child.GetBox())
if indexOf(this.children, tomo.Box(box)) > -1 { return }
box.setParent(this)
box.flushActionQueue()
this.children = append(this.children, box)
this.invalidateLayout()
this.recalculateMinimumSize()
this.invalidateMinimum()
}
func (this *containerBox) Delete (child tomo.Object) {
box := assertAnyBox(child.Box())
box := assertAnyBox(child.GetBox())
index := indexOf(this.children, tomo.Box(box))
if index < 0 { return }
box.setParent(nil)
this.children = remove(this.children, index)
this.invalidateLayout()
this.recalculateMinimumSize()
this.invalidateMinimum()
}
func (this *containerBox) Insert (child, before tomo.Object) {
box := assertAnyBox(child.Box())
box := assertAnyBox(child.GetBox())
if indexOf(this.children, tomo.Box(box)) > -1 { return }
beforeBox := assertAnyBox(before.Box())
beforeBox := assertAnyBox(before.GetBox())
index := indexOf(this.children, tomo.Box(beforeBox))
if index < 0 { return }
box.setParent(this)
this.children = insert(this.children, index, tomo.Box(box))
this.invalidateLayout()
this.recalculateMinimumSize()
this.invalidateMinimum()
}
func (this *containerBox) Clear () {
@@ -117,7 +110,7 @@ func (this *containerBox) Clear () {
}
this.children = nil
this.invalidateLayout()
this.recalculateMinimumSize()
this.invalidateMinimum()
}
func (this *containerBox) Length () int {
@@ -134,12 +127,11 @@ func (this *containerBox) At (index int) tomo.Object {
func (this *containerBox) SetLayout (layout tomo.Layout) {
this.layout = layout
this.invalidateLayout()
this.recalculateMinimumSize()
this.invalidateMinimum()
}
func (this *containerBox) Draw (can canvas.Canvas) {
if can == nil { return }
this.drawBorders(can)
pen := can.Pen()
pen.Fill(this.color)
@@ -148,10 +140,43 @@ func (this *containerBox) Draw (can canvas.Canvas) {
rocks[index] = box.Bounds()
}
for _, tile := range canvas.Shatter(this.bounds, rocks...) {
pen.Rectangle(tile)
if this.fillTransparent && this.parent != nil {
this.parent.drawBackgroundPart(can.Clip(tile))
}
if this.texture == nil || !this.texture.Opaque() {
pen.Rectangle(tile)
}
if this.texture != nil {
// TODO draw texture
}
}
}
func (this *containerBox) drawBackgroundPart (can canvas.Canvas) {
if can == nil { return }
if this.fillTransparent && this.parent != nil {
this.parent.drawBackgroundPart(can)
}
pen := can.Pen()
pen.Fill(this.color)
if this.texture == nil || !this.texture.Opaque() {
pen.Rectangle(can.Bounds())
}
if this.texture != nil {
// TODO draw texture
}
}
func (this *containerBox) flushActionQueue () {
for _, box := range this.children {
box.(anyBox).flushActionQueue()
}
this.box.flushActionQueue()
}
func (this *containerBox) window () *window {
if this.parent == nil { return nil }
return this.parent.window()
@@ -162,8 +187,7 @@ func (this *containerBox) canvas () canvas.Canvas {
}
func (this *containerBox) notifyMinimumSizeChange (child anyBox) {
this.recalculateMinimumSize()
this.invalidateMinimum()
size := child.MinimumSize()
bounds := child.Bounds()
if bounds.Dx() < size.X || bounds.Dy() < size.Y {
@@ -183,18 +207,15 @@ func (this *containerBox) layoutHints () tomo.LayoutHints {
}
}
func (this *containerBox) recalculateMinimumSize () {
if this.layout == nil {
this.SetMinimumSize(image.Point { })
return
func (this *containerBox) contentMinimum () image.Point {
minimum := this.box.contentMinimum()
if this.layout != nil {
minimum = minimum.Add (
this.layout.MinimumSize (
this.layoutHints(),
this.children))
}
minimum := this.layout.MinimumSize(this.layoutHints(), this.children)
minimum.X += this.padding.Horizontal()
minimum.Y += this.padding.Vertical()
borderSum := this.borderSum()
minimum.X += borderSum.Horizontal()
minimum.Y += borderSum.Vertical()
this.SetMinimumSize(minimum)
return minimum
}
func (this *containerBox) doLayout () {

View File

@@ -293,6 +293,7 @@ func (window *window) handleButtonRelease (
window.updateModifiers(buttonEvent.State)
window.updateMousePosition(buttonEvent.EventX, buttonEvent.EventY)
dragging := window.drags[buttonEvent.Detail]
window.drags[buttonEvent.Detail] = nil
if dragging != nil {
dragging.handleMouseUp(input.Button(buttonEvent.Detail))
@@ -317,8 +318,11 @@ func (window *window) handleMotionNotify (
handled = true
}
underneath := window.boxUnder(image.Pt(x, y))
window.hover(underneath)
if !handled {
window.boxUnder(image.Pt(x, y)).handleMouseMove()
underneath.handleMouseMove()
}
}

5
go.mod
View File

@@ -3,13 +3,12 @@ module git.tebibyte.media/tomo/x
go 1.20
require (
git.tebibyte.media/tomo/ggfx v0.4.0
git.tebibyte.media/tomo/tomo v0.20.0
git.tebibyte.media/tomo/tomo v0.26.1
git.tebibyte.media/tomo/typeset v0.5.2
git.tebibyte.media/tomo/xgbkb v1.0.1
github.com/jezek/xgb v1.1.0
github.com/jezek/xgbutil v0.0.0-20230603163917-04188eb39cf0
golang.org/x/image v0.9.0
golang.org/x/image v0.11.0
)
require (

12
go.sum
View File

@@ -1,8 +1,6 @@
git.tebibyte.media/sashakoshka/xgbkb v1.0.0/go.mod h1:pNcE6TRO93vHd6q42SdwLSTTj25L0Yzggz7yLe0JV6Q=
git.tebibyte.media/tomo/ggfx v0.4.0 h1:3aUHeGS/yYWRV/zCDubBsXnik5ygkMnj/VgrM5Z75A4=
git.tebibyte.media/tomo/ggfx v0.4.0/go.mod h1:zPoz8BdVQyG2KhEmeGFQBK66V71i6Kj8oVFbrZaCwRA=
git.tebibyte.media/tomo/tomo v0.20.0 h1:dJJWmCAj/8XtbxjiCkgykP2vNCeP5zuvMEKGChbQ0AI=
git.tebibyte.media/tomo/tomo v0.20.0/go.mod h1:lTwjpiHbP4UN/kFw+6FwhG600B+PMKVtMOr7wpd5IUY=
git.tebibyte.media/tomo/tomo v0.26.1 h1:V5ciRuixMYb79aAawgquFEfJ1icyEmMKBKFPWwi94NE=
git.tebibyte.media/tomo/tomo v0.26.1/go.mod h1:C9EzepS9wjkTJjnZaPBh22YvVPyA4hbBAJVU20Rdmps=
git.tebibyte.media/tomo/typeset v0.5.2 h1:qHxN62/VDnrAouOuzxLmLleQNwAebshrfVYvtoOnAG4=
git.tebibyte.media/tomo/typeset v0.5.2/go.mod h1:PwDpSdBF3l/EzoIsa2ME7QffVVajnTHZN6l3MHEGe1g=
git.tebibyte.media/tomo/xgbkb v1.0.1 h1:b3HDUopjdQp1MZrb5Vpil4bOtk3NnNXtfQW27Blw2kE=
@@ -18,8 +16,8 @@ github.com/jezek/xgbutil v0.0.0-20230603163917-04188eb39cf0/go.mod h1:AHecLyFNy6
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/image v0.9.0 h1:QrzfX26snvCM20hIhBwuHI/ThTg18b/+kcKdXHvnR+g=
golang.org/x/image v0.9.0/go.mod h1:jtrku+n79PfroUbvDdeUWMAI+heR786BofxrbiSF+J0=
golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo=
golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -42,7 +40,7 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=

View File

@@ -36,17 +36,22 @@ type parent interface {
window () *window
canvas () canvas.Canvas
notifyMinimumSizeChange (anyBox)
drawBackgroundPart (canvas.Canvas)
}
type anyBox interface {
tomo.Box
doDraw ()
doLayout ()
setParent (parent)
recursiveRedo ()
canBeFocused () bool
boxUnder (image.Point) anyBox
recalculateMinimumSize ()
canvas.Drawer
doDraw ()
doLayout ()
doMinimumSize ()
contentMinimum () image.Point
setParent (parent)
flushActionQueue ()
recursiveRedo ()
canBeFocused () bool
boxUnder (image.Point) anyBox
propagate (func (anyBox) bool) bool
propagateAlt (func (anyBox) bool) bool
@@ -56,8 +61,8 @@ type anyBox interface {
// handleDndEnter ()
// handleDndLeave ()
// handleDndDrop (data.Data)
// handleMouseEnter ()
// handleMouseLeave ()
handleMouseEnter ()
handleMouseLeave ()
handleMouseMove ()
handleMouseDown (input.Button)
handleMouseUp (input.Button)
@@ -81,12 +86,13 @@ func (window *window) SetRoot (root tomo.Object) {
if root == nil {
window.root = nil
} else {
box := assertAnyBox(root.Box())
box := assertAnyBox(root.GetBox())
box.setParent(window)
box.flushActionQueue()
window.invalidateLayout(box)
window.root = box
}
window.recalculateMinimumSize()
window.minimumClean = false
}
func (window *window) window () *window {
@@ -98,7 +104,11 @@ func (window *window) canvas () canvas.Canvas {
}
func (window *window) notifyMinimumSizeChange (anyBox) {
window.recalculateMinimumSize()
window.minimumClean = false
}
func (window *window) invalidateMinimum (box anyBox) {
window.needMinimum.Add(box)
}
func (window *window) invalidateDraw (box anyBox) {
@@ -106,8 +116,6 @@ func (window *window) invalidateDraw (box anyBox) {
}
func (window *window) invalidateLayout (box anyBox) {
// TODO: use a priority queue for this and have the value be the amount
// of parents a box has
window.needLayout.Add(box)
window.invalidateDraw(box)
}
@@ -119,15 +127,27 @@ func (window *window) focus (box anyBox) {
window.focused = box
if previous != nil {
window.invalidateDraw(previous)
previous.handleFocusLeave()
}
if box != nil && box.canBeFocused() {
window.invalidateDraw(box)
box.handleFocusEnter()
}
}
func (window *window) hover (box anyBox) {
if window.hovered == box { return }
previous := window.hovered
window.hovered = box
if previous != nil {
previous.handleMouseLeave()
}
if box != nil {
box.handleMouseEnter()
}
}
func (window *window) anyFocused () bool {
return window.focused != nil
}
@@ -202,6 +222,12 @@ func (window *window) afterEvent () {
return
}
for len(window.needMinimum) > 0 {
window.needMinimum.Pop().doMinimumSize()
}
if !window.minimumClean {
window.doMinimumSize()
}
for len(window.needLayout) > 0 {
window.needLayout.Pop().doLayout()
}

View File

@@ -13,7 +13,7 @@ import "git.tebibyte.media/tomo/tomo/canvas"
type textBox struct {
*box
hOverflow, vOverflow bool
contentBounds image.Rectangle
scroll image.Point
@@ -29,9 +29,9 @@ type textBox struct {
selectStart int
dot text.Dot
dotColor color.Color
drawer typeset.Drawer
on struct {
contentBoundsChange event.FuncBroadcaster
dotChange event.FuncBroadcaster
@@ -39,14 +39,12 @@ type textBox struct {
}
func (backend *Backend) NewTextBox() tomo.TextBox {
box := &textBox {
box: backend.NewBox().(*box),
this := &textBox {
textColor: color.Black,
dotColor: color.RGBA { B: 255, G: 255, A: 255 },
}
box.box.drawer = box
box.outer = box
return box
this.box = backend.newBox(this)
return this
}
func (this *textBox) SetOverflow (horizontal, vertical bool) {
@@ -74,7 +72,7 @@ func (this *textBox) SetText (text string) {
if this.text == text { return }
this.text = text
this.drawer.SetText([]rune(text))
this.recalculateMinimumSize()
this.invalidateMinimum()
this.invalidateLayout()
}
@@ -88,14 +86,14 @@ func (this *textBox) SetFace (face font.Face) {
if this.face == face { return }
this.face = face
this.drawer.SetFace(face)
this.recalculateMinimumSize()
this.invalidateMinimum()
this.invalidateLayout()
}
func (this *textBox) SetWrap (wrap bool) {
if this.wrap == wrap { return }
this.drawer.SetWrap(wrap)
this.recalculateMinimumSize()
this.invalidateMinimum()
this.invalidateLayout()
}
@@ -139,7 +137,7 @@ func (this *textBox) SetAlign (x, y tomo.Align) {
case tomo.AlignEnd: this.drawer.SetAlign(typeset.AlignRight)
case tomo.AlignEven: this.drawer.SetAlign(typeset.AlignJustify)
}
this.invalidateDraw()
}
@@ -167,10 +165,11 @@ func fixPt (point image.Point) fixed.Point26_6 {
}
func (this *textBox) drawDot (can canvas.Canvas) {
if this.face == nil { return }
pen := can.Pen()
pen.Fill(color.Transparent)
pen.Stroke(this.textColor)
pen.StrokeWeight(1)
bounds := this.InnerBounds()
metrics := this.face.Metrics()
@@ -180,11 +179,12 @@ func (this *textBox) drawDot (can canvas.Canvas) {
height := this.drawer.LineHeight().Round()
ascent := fixed.Point26_6 { Y: metrics.Descent }
descent := fixed.Point26_6 { Y: metrics.Ascent }
switch {
case dot.Empty():
pen.StrokeWeight(1)
pen.Path(roundPt(start.Add(ascent)), roundPt(start.Sub(descent)))
case start.Y == end.Y:
pen.Fill(this.dotColor)
pen.StrokeWeight(0)
@@ -192,11 +192,11 @@ func (this *textBox) drawDot (can canvas.Canvas) {
Min: roundPt(start.Add(ascent)),
Max: roundPt(end.Sub(descent)),
})
default:
pen.Fill(this.dotColor)
pen.StrokeWeight(0)
rect := image.Rectangle {
Min: roundPt(start.Add(ascent)),
Max: roundPt(start.Sub(descent)),
@@ -269,7 +269,7 @@ func (this *textBox) normalizedLayoutBoundsSpace () image.Rectangle {
return bounds.Sub(bounds.Min)
}
func (this *textBox) recalculateMinimumSize () {
func (this *textBox) contentMinimum () image.Point {
minimum := image.Pt (
this.drawer.Em().Round(),
this.drawer.LineHeight().Round())
@@ -281,13 +281,8 @@ func (this *textBox) recalculateMinimumSize () {
if !this.vOverflow {
minimum.Y = textSize.Y
}
minimum.X += this.padding.Horizontal()
minimum.Y += this.padding.Vertical()
borderSum := this.borderSum()
minimum.X += borderSum.Horizontal()
minimum.Y += borderSum.Vertical()
this.SetMinimumSize(minimum)
return minimum.Add(this.box.contentMinimum())
}
func (this *textBox) doLayout () {
@@ -297,9 +292,9 @@ func (this *textBox) doLayout () {
innerBounds := this.InnerBounds()
this.drawer.SetMaxWidth(innerBounds.Dx())
this.drawer.SetMaxHeight(innerBounds.Dy())
this.contentBounds = this.normalizedLayoutBoundsSpace().Sub(this.scroll)
if previousContentBounds != this.contentBounds {
this.on.contentBoundsChange.Broadcast()
}

9
texture.go Normal file
View File

@@ -0,0 +1,9 @@
package x
import "image"
import "git.tebibyte.media/tomo/x/canvas"
import "git.tebibyte.media/tomo/tomo/canvas"
func (backend Backend) NewTexture (source image.Image) canvas.Texture {
return xcanvas.NewTextureFrom(source)
}

View File

@@ -8,6 +8,7 @@ import "git.tebibyte.media/tomo/x/canvas"
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 "github.com/jezek/xgb/xproto"
import "github.com/jezek/xgbutil/ewmh"
@@ -44,10 +45,19 @@ type window struct {
root anyBox
focused anyBox
hovered anyBox
needDraw boxSet
needLayout boxSet
needRedo bool
// TODO: needMinimum and needLayout should be priority queues. for the
// minimums, we need to start at the deeper parts of the layout tree and
// go upward towards the top. for the layouts, we need to start at the
// top of the layout tree and progressively go deeper. this will
// eliminate redundant layout calculations.
needMinimum boxSet
needLayout boxSet
needDraw boxSet
needRedo bool
minimumClean bool
}
func (backend *Backend) NewWindow (
@@ -129,7 +139,7 @@ func (backend *Backend) newWindow (
// Connect(backend.x, window.xWindow.Id)
window.metrics.bounds = bounds
window.setMinimumSize(image.Pt(8, 8))
window.doMinimumSize()
backend.windows[window.xWindow.Id] = window
@@ -322,7 +332,7 @@ func (window *window) reallocateCanvas () {
if window.xCanvas != nil {
window.xCanvas.Destroy()
}
window.xCanvas = xcanvas.NewFrom(xgraphics.New (
window.xCanvas = xcanvas.NewCanvasFrom(xgraphics.New (
window.backend.x,
image.Rect (
0, 0,
@@ -352,15 +362,19 @@ func (window *window) pushRegion (region image.Rectangle) {
subCanvas.(*xcanvas.Canvas).Push(window.xWindow.Id)
}
func (window *window) recalculateMinimumSize () {
rootMinimum := image.Point { }
if window.root != nil {
rootMinimum = window.root.MinimumSize()
}
window.setMinimumSize(rootMinimum)
func (window *window) drawBackgroundPart (canvas.Canvas) {
// no-op for now? maybe eventually windows will be able to have a
// background
}
func (window *window) setMinimumSize (size image.Point) {
func (window *window) doMinimumSize () {
window.minimumClean = true
size := image.Point { }
if window.root != nil {
size = window.root.MinimumSize()
}
if size.X < 8 { size.X = 8 }
if size.Y < 8 { size.Y = 8 }
icccm.WmNormalHintsSet (

View File

@@ -5,7 +5,7 @@ import "git.tebibyte.media/tomo/x"
import "git.tebibyte.media/tomo/tomo"
func init () {
tomo.Register(x.NewBackend)
tomo.Register(0, tomo.Factory(x.NewBackend))
}
func Name () string {