all: introduce Outline and Stroke builders

This CL introduces 2 new path builders:
- Outline which takes a PathSpec to be outlined
- Stroke which takes a PathSpec and a stroke style, to stroke a path.

typically, code like this:

  var p clip.Path
  ...
  p.Outline().Add(o)

should be replaced with:

  var p clip.Path
  ...
  clip.Outline{Path: p.End()}.Op().Add(o)

similarly, stroking should be modified from:

  var p clip.Path
  ...
  p.Stroke(width, clip.StrokeStyle{...}).Add(o)

to:

  var p clip.Path
  ...
  clip.Stroke{Path: p.End(), Style: clip.StrokeStyle{Width:...}}.Op().Add(o)

here are tentative 'rf' scripts (see rsc.io/rf for more details):

  ```
  ex {
  	import "gioui.org/op";
  	import "gioui.org/op/clip";

  	var p clip.Path;
  	var o *op.Ops;

  	p.Outline().Add(o) -> clip.Outline{Path:p.End()}.Op().Add(o);
  }

  ex {
  	import "gioui.org/op";
  	import "gioui.org/op/clip";

  	var o *op.Ops;
  	var p clip.Path;
  	var sty clip.StrokeStyle;
  	var width float32;

  	p.Stroke(width, sty).Add(o) ->   \
	    clip.Stroke{                 \
		Path:p.End(),            \
		Style: clip.StrokeStyle{ \
		    Width: width,        \
	    }}.Op().Add(o);
  }
  ```

Signed-off-by: Sebastien Binet <s@sbinet.org>
This commit is contained in:
Sebastien Binet
2020-12-08 14:55:26 +00:00
committed by Elias Naur
parent 7c5bcd3db8
commit be89f8b945
12 changed files with 355 additions and 259 deletions
+72 -49
View File
@@ -13,6 +13,52 @@ import (
"gioui.org/op"
)
// Op represents a clip area. Op intersects the current clip area with
// itself.
type Op struct {
bounds image.Rectangle
path PathSpec
outline bool
stroke StrokeStyle
}
func (p Op) Add(o *op.Ops) {
if p.path.quads > 0 {
data := o.Write(opconst.TypePathLen)
data[0] = byte(opconst.TypePath)
bo := binary.LittleEndian
bo.PutUint32(data[1:], p.path.quads)
p.path.spec.Add(o)
}
if p.stroke.Width > 0 {
data := o.Write(opconst.TypeStrokeLen)
data[0] = byte(opconst.TypeStroke)
bo := binary.LittleEndian
bo.PutUint32(data[1:], math.Float32bits(p.stroke.Width))
bo.PutUint32(data[5:], math.Float32bits(p.stroke.Miter))
data[9] = uint8(p.stroke.Cap)
data[10] = uint8(p.stroke.Join)
}
data := o.Write(opconst.TypeClipLen)
data[0] = byte(opconst.TypeClip)
bo := binary.LittleEndian
bo.PutUint32(data[1:], uint32(p.bounds.Min.X))
bo.PutUint32(data[5:], uint32(p.bounds.Min.Y))
bo.PutUint32(data[9:], uint32(p.bounds.Max.X))
bo.PutUint32(data[13:], uint32(p.bounds.Max.Y))
if p.outline {
data[17] = byte(1)
}
}
type PathSpec struct {
spec op.CallOp
quads uint32 // quads is the number Bézier segments in that path.
}
// Path constructs a Op clip path described by lines and
// Bézier curves, where drawing outside the Path is discarded.
// The inside-ness of a pixel is determines by the non-zero winding rule,
@@ -26,38 +72,12 @@ type Path struct {
pen f32.Point
macro op.MacroOp
start f32.Point
quads uint32
}
// Pos returns the current pen position.
func (p *Path) Pos() f32.Point { return p.pen }
// Op sets the current clip to the intersection of
// the existing clip with this clip.
//
// If you need to reset the clip to its previous values after
// applying a Op, use op.StackOp.
type Op struct {
call op.CallOp
bounds image.Rectangle
width float32 // Width of the stroked path, 0 for outline paths.
style StrokeStyle // Style of the stroked path, zero for outline paths.
}
func (p Op) Add(o *op.Ops) {
p.call.Add(o)
data := o.Write(opconst.TypeClipLen)
data[0] = byte(opconst.TypeClip)
bo := binary.LittleEndian
bo.PutUint32(data[1:], uint32(p.bounds.Min.X))
bo.PutUint32(data[5:], uint32(p.bounds.Min.Y))
bo.PutUint32(data[9:], uint32(p.bounds.Max.X))
bo.PutUint32(data[13:], uint32(p.bounds.Max.Y))
bo.PutUint32(data[17:], math.Float32bits(p.width))
data[21] = uint8(p.style.Cap)
data[22] = uint8(p.style.Join)
bo.PutUint32(data[23:], math.Float32bits(p.style.Miter))
}
// Begin the path, storing the path data and final Op into ops.
func (p *Path) Begin(ops *op.Ops) {
p.ops = ops
@@ -67,6 +87,15 @@ func (p *Path) Begin(ops *op.Ops) {
data[0] = byte(opconst.TypeAux)
}
// End returns a PathSpec ready to use in clipping operations.
func (p *Path) End() PathSpec {
c := p.macro.Stop()
return PathSpec{
spec: c,
quads: p.quads,
}
}
// Move moves the pen by the amount specified by delta.
func (p *Path) Move(delta f32.Point) {
to := delta.Add(p.pen)
@@ -120,6 +149,7 @@ func (p *Path) QuadTo(ctrl, to f32.Point) {
To: to,
})
p.pen = to
p.quads++
}
// Arc adds an elliptical arc to the path. The implied ellipse is defined
@@ -329,32 +359,22 @@ func (p *Path) approxCubeTo(splits int, maxDist float32, ctrl0, ctrl1, to f32.Po
return splits
}
// Outline closes the path and returns a clip operation that represents it.
func (p *Path) Outline() Op {
// Close closes the path.
func (p *Path) Close() {
p.end()
c := p.macro.Stop()
return Op{
call: c,
}
}
// Stroke returns a stroked path with the specified width
// and configuration.
// If the provided width is <= 0, the path won't be stroked.
func (p *Path) Stroke(width float32, sty StrokeStyle) Op {
if width <= 0 {
// Explicitly discard the macro to ignore the path.
p.macro.Stop()
return Op{
call: op.Record(p.ops).Stop(),
}
}
// Outline represents the area inside of a path, according to the
// non-zero winding rule.
type Outline struct {
Path PathSpec
}
c := p.macro.Stop()
// Op returns a clip operation representing the outline.
func (o Outline) Op() Op {
return Op{
call: c,
width: width,
style: sty,
path: o.Path,
outline: true,
}
}
@@ -363,7 +383,10 @@ type Rect image.Rectangle
// Op returns the op for the rectangle.
func (r Rect) Op() Op {
return Op{bounds: image.Rectangle(r)}
return Op{
bounds: image.Rectangle(r),
outline: true,
}
}
// Add the clip operation.
+10 -4
View File
@@ -36,7 +36,10 @@ func (rr RRect) Op(ops *op.Ops) Op {
p.Begin(ops)
p.Move(rr.Rect.Min)
roundRect(&p, rr.Rect.Size(), rr.SE, rr.SW, rr.NW, rr.NE)
return p.Outline()
return Outline{
Path: p.End(),
}.Op()
}
// Add the rectangle clip.
@@ -49,7 +52,6 @@ type Border struct {
// Rect is the bounds of the border.
Rect f32.Rectangle
Width float32
Style StrokeStyle
// The corner radii.
SE, SW, NW, NE float32
}
@@ -58,11 +60,15 @@ type Border struct {
func (b Border) Op(ops *op.Ops) Op {
var p Path
p.Begin(ops)
p.Move(b.Rect.Min)
roundRect(&p, b.Rect.Size(), b.SE, b.SW, b.NW, b.NE)
return p.Stroke(b.Width, b.Style)
return Stroke{
Path: p.End(),
Style: StrokeStyle{
Width: b.Width,
},
}.Op()
}
// Add the border clip.
+18 -5
View File
@@ -2,17 +2,30 @@
package clip
// StrokeStyle describes how a stroked path should be drawn.
// The zero value of StrokeStyle represents bevel-joined and flat-capped
// strokes.
// Stroke represents a stroked path.
type Stroke struct {
Path PathSpec
Style StrokeStyle
}
// Op returns a clip operation representing the stroke.
func (s Stroke) Op() Op {
return Op{
path: s.Path,
stroke: s.Style,
}
}
// StrokeStyle describes how a path should be stroked.
type StrokeStyle struct {
Cap StrokeCap
Join StrokeJoin
Width float32 // Width of the stroked path.
// Miter is the limit to apply to a miter joint.
// The zero Miter disables the miter joint; setting Miter to +∞
// unconditionally enables the miter joint.
Miter float32
Cap StrokeCap // Cap describes the head or tail of a stroked path.
Join StrokeJoin // Join describes how stroked paths are collated.
}
// StrokeCap describes the head or tail of a stroked path.