diff --git a/ui/app/internal/gpu/gpu.go b/ui/app/internal/gpu/gpu.go index 5266ac1a..1bec7c1a 100644 --- a/ui/app/internal/gpu/gpu.go +++ b/ui/app/internal/gpu/gpu.go @@ -66,6 +66,10 @@ type drawOps struct { zimageOps []imageOp pathOps []*pathOp pathOpCache []pathOp + + // Current OpImage image and rect, if any. + img image.Image + imgRect image.Rectangle } type drawState struct { @@ -692,13 +696,18 @@ loop: case ops.TypeImage: var op gdraw.OpImage op.Decode(data, r.Refs) + d.img = op.Img + d.imgRect = op.Rect + case ops.TypeDraw: + var op gdraw.OpDraw + op.Decode(data, r.Refs) off := t.Transform(f32.Point{}) clip := clip.Intersect(op.Rect.Add(off)) if clip.Empty() { continue } bounds := boundRectF(clip) - mat := materialFor(d.cache, op, off, bounds) + mat := d.materialFor(d.cache, op.Rect, off, bounds) if bounds.Min == (image.Point{}) && bounds.Max == d.viewport && mat.opaque && mat.material == materialColor { // The image is a uniform opaque color and takes up the whole screen. // Scrap images up to and including this image and set clear color. @@ -745,16 +754,16 @@ func expandPathOp(p *pathOp, clip image.Rectangle) { } } -func materialFor(cache *resourceCache, op gdraw.OpImage, off f32.Point, clip image.Rectangle) material { +func (d *drawOps) materialFor(cache *resourceCache, rect f32.Rectangle, off f32.Point, clip image.Rectangle) material { var m material - if uniform, ok := op.Src.(*image.Uniform); ok { + if uniform, ok := d.img.(*image.Uniform); ok { m.material = materialColor m.color = gamma(uniform.RGBA()) m.opaque = m.color[3] == 1.0 } else { m.material = materialTexture - dr := boundRectF(op.Rect.Add(off)) - sr := op.SrcRect + dr := boundRectF(rect.Add(off)) + sr := d.imgRect if dx := dr.Dx(); dx != 0 { // Don't clip 1 px width sources. if sdx := sr.Dx(); sdx > 1 { @@ -769,16 +778,16 @@ func materialFor(cache *resourceCache, op gdraw.OpImage, off f32.Point, clip ima sr.Max.Y -= ((dr.Max.Y-clip.Max.Y)*sdy + dy/2) / dy } } - tex, exists := cache.get(op.Src) + tex, exists := cache.get(d.img) if !exists { t := &texture{ - src: op.Src, + src: d.img, } - cache.put(op.Src, t) + cache.put(d.img, t) tex = t } m.texture = tex.(*texture) - m.uvScale, m.uvOffset = texSpaceTransform(sr, op.Src.Bounds().Size()) + m.uvScale, m.uvOffset = texSpaceTransform(sr, d.img.Bounds().Size()) } return m } diff --git a/ui/draw/draw.go b/ui/draw/draw.go index de10c062..d4ff8c26 100644 --- a/ui/draw/draw.go +++ b/ui/draw/draw.go @@ -14,58 +14,77 @@ import ( ) type OpImage struct { - Rect f32.Rectangle - Src image.Image - SrcRect image.Rectangle + Img image.Image + Rect image.Rectangle +} + +type OpDraw struct { + Rect f32.Rectangle } func (i OpImage) Add(o *ui.Ops) { data := make([]byte, ops.TypeImageLen) data[0] = byte(ops.TypeImage) bo := binary.LittleEndian - ref := o.Ref(i.Src) + ref := o.Ref(i.Img) bo.PutUint32(data[1:], uint32(ref)) - bo.PutUint32(data[5:], math.Float32bits(i.Rect.Min.X)) - bo.PutUint32(data[9:], math.Float32bits(i.Rect.Min.Y)) - bo.PutUint32(data[13:], math.Float32bits(i.Rect.Max.X)) - bo.PutUint32(data[17:], math.Float32bits(i.Rect.Max.Y)) - bo.PutUint32(data[21:], uint32(i.SrcRect.Min.X)) - bo.PutUint32(data[25:], uint32(i.SrcRect.Min.Y)) - bo.PutUint32(data[29:], uint32(i.SrcRect.Max.X)) - bo.PutUint32(data[33:], uint32(i.SrcRect.Max.Y)) + bo.PutUint32(data[5:], uint32(i.Rect.Min.X)) + bo.PutUint32(data[9:], uint32(i.Rect.Min.Y)) + bo.PutUint32(data[13:], uint32(i.Rect.Max.X)) + bo.PutUint32(data[17:], uint32(i.Rect.Max.Y)) o.Write(data) } -func (i *OpImage) Decode(d []byte, refs []interface{}) { +func (i *OpImage) Decode(data []byte, refs []interface{}) { bo := binary.LittleEndian - if ops.OpType(d[0]) != ops.TypeImage { + if ops.OpType(data[0]) != ops.TypeImage { panic("invalid op") } - ref := int(bo.Uint32(d[1:])) - r := f32.Rectangle{ - Min: f32.Point{ - X: math.Float32frombits(bo.Uint32(d[5:])), - Y: math.Float32frombits(bo.Uint32(d[9:])), - }, - Max: f32.Point{ - X: math.Float32frombits(bo.Uint32(d[13:])), - Y: math.Float32frombits(bo.Uint32(d[17:])), - }, - } + ref := int(bo.Uint32(data[1:])) sr := image.Rectangle{ Min: image.Point{ - X: int(bo.Uint32(d[21:])), - Y: int(bo.Uint32(d[25:])), + X: int(bo.Uint32(data[5:])), + Y: int(bo.Uint32(data[9:])), }, Max: image.Point{ - X: int(bo.Uint32(d[29:])), - Y: int(bo.Uint32(d[33:])), + X: int(bo.Uint32(data[13:])), + Y: int(bo.Uint32(data[17:])), }, } *i = OpImage{ - Rect: r, - Src: refs[ref].(image.Image), - SrcRect: sr, + Img: refs[ref].(image.Image), + Rect: sr, + } +} + +func (d OpDraw) Add(o *ui.Ops) { + data := make([]byte, ops.TypeDrawLen) + data[0] = byte(ops.TypeDraw) + bo := binary.LittleEndian + bo.PutUint32(data[1:], math.Float32bits(d.Rect.Min.X)) + bo.PutUint32(data[5:], math.Float32bits(d.Rect.Min.Y)) + bo.PutUint32(data[9:], math.Float32bits(d.Rect.Max.X)) + bo.PutUint32(data[13:], math.Float32bits(d.Rect.Max.Y)) + o.Write(data) +} + +func (d *OpDraw) Decode(data []byte, refs []interface{}) { + bo := binary.LittleEndian + if ops.OpType(data[0]) != ops.TypeDraw { + panic("invalid op") + } + r := f32.Rectangle{ + Min: f32.Point{ + X: math.Float32frombits(bo.Uint32(data[1:])), + Y: math.Float32frombits(bo.Uint32(data[5:])), + }, + Max: f32.Point{ + X: math.Float32frombits(bo.Uint32(data[9:])), + Y: math.Float32frombits(bo.Uint32(data[13:])), + }, + } + *d = OpDraw{ + Rect: r, } } diff --git a/ui/internal/ops/ops.go b/ui/internal/ops/ops.go index 3070286e..768d83bd 100644 --- a/ui/internal/ops/ops.go +++ b/ui/internal/ops/ops.go @@ -28,6 +28,7 @@ const ( TypeRedraw TypeClip TypeImage + TypeDraw TypePointerHandler TypeKeyHandler TypeHideInput @@ -42,7 +43,8 @@ const ( TypeLayerLen = 1 TypeRedrawLen = 1 + 8 TypeClipLen = 1 + 4 - TypeImageLen = 1 + 4 + 4*4 + 4*4 + TypeImageLen = 1 + 4 + 4*4 + TypeDrawLen = 1 + 4*4 TypePointerHandlerLen = 1 + 4 + 4 + 1 TypeKeyHandlerLen = 1 + 4 + 1 TypeHideInputLen = 1 @@ -58,6 +60,7 @@ var typeLengths = [...]int{ TypeRedrawLen, TypeClipLen, TypeImageLen, + TypeDrawLen, TypePointerHandlerLen, TypeKeyHandlerLen, TypeHideInputLen, diff --git a/ui/text/editor.go b/ui/text/editor.go index 9afae1ea..8bcb55c7 100644 --- a/ui/text/editor.go +++ b/ui/text/editor.go @@ -20,7 +20,6 @@ import ( ) type Editor struct { - Src image.Image Face Face Alignment Alignment SingleLine bool @@ -175,7 +174,7 @@ func (e *Editor) Layout(ops *ui.Ops, cs layout.Constraints) layout.Dimens { ops.Begin() ui.OpTransform{Transform: ui.Offset(lineOff)}.Add(ops) draw.OpClip{Path: path}.Add(ops) - draw.OpImage{Rect: toRectF(clip).Sub(lineOff), Src: e.Src, SrcRect: e.Src.Bounds()}.Add(ops) + draw.OpDraw{Rect: toRectF(clip).Sub(lineOff)}.Add(ops) ops.End().Add(ops) } if e.focused { @@ -199,8 +198,7 @@ func (e *Editor) Layout(ops *ui.Ops, cs layout.Constraints) layout.Dimens { }) carRect = clip.Intersect(carRect) if !carRect.Empty() { - img := draw.OpImage{Src: e.Src, Rect: toRectF(carRect), SrcRect: e.Src.Bounds()} - img.Add(ops) + draw.OpDraw{Rect: toRectF(carRect)}.Add(ops) } } if blinking { diff --git a/ui/text/label.go b/ui/text/label.go index ac02f4d2..d79a59be 100644 --- a/ui/text/label.go +++ b/ui/text/label.go @@ -17,7 +17,6 @@ import ( type Label struct { Face Face - Src image.Image Alignment Alignment Text string @@ -106,7 +105,7 @@ func (l Label) Layout(ops *ui.Ops, cs layout.Constraints) layout.Dimens { ops.Begin() ui.OpTransform{Transform: ui.Offset(off)}.Add(ops) draw.OpClip{Path: path}.Add(ops) - draw.OpImage{Rect: lclip, Src: l.Src, SrcRect: l.Src.Bounds()}.Add(ops) + draw.OpDraw{Rect: lclip}.Add(ops) ops.End().Add(ops) } return dims diff --git a/ui/widget/image.go b/ui/widget/image.go index 3eaef624..727a6618 100644 --- a/ui/widget/image.go +++ b/ui/widget/image.go @@ -27,6 +27,7 @@ func (im Image) Layout(ops *ui.Ops, cs layout.Constraints) layout.Dimens { dr := f32.Rectangle{ Max: f32.Point{X: float32(d.X), Y: float32(d.Y)}, } - draw.OpImage{Rect: dr, Src: im.Src, SrcRect: im.Rect}.Add(ops) + draw.OpImage{Img: im.Src, Rect: im.Rect}.Add(ops) + draw.OpDraw{Rect: dr}.Add(ops) return layout.Dimens{Size: d, Baseline: d.Y} }