gioui.org@v0.6.1-0.20240506124620-7a9ce51988ce/op/clip/shapes.go (about)

     1  // SPDX-License-Identifier: Unlicense OR MIT
     2  
     3  package clip
     4  
     5  import (
     6  	"image"
     7  	"math"
     8  
     9  	"gioui.org/f32"
    10  	f32internal "gioui.org/internal/f32"
    11  	"gioui.org/internal/ops"
    12  	"gioui.org/op"
    13  )
    14  
    15  // Rect represents the clip area of a pixel-aligned rectangle.
    16  type Rect image.Rectangle
    17  
    18  // Op returns the op for the rectangle.
    19  func (r Rect) Op() Op {
    20  	return Op{
    21  		outline: true,
    22  		path:    r.Path(),
    23  	}
    24  }
    25  
    26  // Push the clip operation on the clip stack.
    27  func (r Rect) Push(ops *op.Ops) Stack {
    28  	return r.Op().Push(ops)
    29  }
    30  
    31  // Path returns the PathSpec for the rectangle.
    32  func (r Rect) Path() PathSpec {
    33  	return PathSpec{
    34  		shape:  ops.Rect,
    35  		bounds: image.Rectangle(r),
    36  	}
    37  }
    38  
    39  // UniformRRect returns an RRect with all corner radii set to the
    40  // provided radius.
    41  func UniformRRect(rect image.Rectangle, radius int) RRect {
    42  	return RRect{
    43  		Rect: rect,
    44  		SE:   radius,
    45  		SW:   radius,
    46  		NE:   radius,
    47  		NW:   radius,
    48  	}
    49  }
    50  
    51  // RRect represents the clip area of a rectangle with rounded
    52  // corners.
    53  //
    54  // Specify a square with corner radii equal to half the square size to
    55  // construct a circular clip area.
    56  type RRect struct {
    57  	Rect image.Rectangle
    58  	// The corner radii.
    59  	SE, SW, NW, NE int
    60  }
    61  
    62  // Op returns the op for the rounded rectangle.
    63  func (rr RRect) Op(ops *op.Ops) Op {
    64  	if rr.SE == 0 && rr.SW == 0 && rr.NW == 0 && rr.NE == 0 {
    65  		return Rect(rr.Rect).Op()
    66  	}
    67  	return Outline{Path: rr.Path(ops)}.Op()
    68  }
    69  
    70  // Push the rectangle clip on the clip stack.
    71  func (rr RRect) Push(ops *op.Ops) Stack {
    72  	return rr.Op(ops).Push(ops)
    73  }
    74  
    75  // Path returns the PathSpec for the rounded rectangle.
    76  func (rr RRect) Path(ops *op.Ops) PathSpec {
    77  	var p Path
    78  	p.Begin(ops)
    79  
    80  	// https://pomax.github.io/bezierinfo/#circles_cubic.
    81  	const q = 4 * (math.Sqrt2 - 1) / 3
    82  	const iq = 1 - q
    83  
    84  	se, sw, nw, ne := float32(rr.SE), float32(rr.SW), float32(rr.NW), float32(rr.NE)
    85  	rrf := f32internal.FRect(rr.Rect)
    86  	w, n, e, s := rrf.Min.X, rrf.Min.Y, rrf.Max.X, rrf.Max.Y
    87  
    88  	p.MoveTo(f32.Point{X: w + nw, Y: n})
    89  	p.LineTo(f32.Point{X: e - ne, Y: n}) // N
    90  	p.CubeTo(                            // NE
    91  		f32.Point{X: e - ne*iq, Y: n},
    92  		f32.Point{X: e, Y: n + ne*iq},
    93  		f32.Point{X: e, Y: n + ne})
    94  	p.LineTo(f32.Point{X: e, Y: s - se}) // E
    95  	p.CubeTo(                            // SE
    96  		f32.Point{X: e, Y: s - se*iq},
    97  		f32.Point{X: e - se*iq, Y: s},
    98  		f32.Point{X: e - se, Y: s})
    99  	p.LineTo(f32.Point{X: w + sw, Y: s}) // S
   100  	p.CubeTo(                            // SW
   101  		f32.Point{X: w + sw*iq, Y: s},
   102  		f32.Point{X: w, Y: s - sw*iq},
   103  		f32.Point{X: w, Y: s - sw})
   104  	p.LineTo(f32.Point{X: w, Y: n + nw}) // W
   105  	p.CubeTo(                            // NW
   106  		f32.Point{X: w, Y: n + nw*iq},
   107  		f32.Point{X: w + nw*iq, Y: n},
   108  		f32.Point{X: w + nw, Y: n})
   109  
   110  	return p.End()
   111  }
   112  
   113  // Ellipse represents the largest axis-aligned ellipse that
   114  // is contained in its bounds.
   115  type Ellipse image.Rectangle
   116  
   117  // Op returns the op for the filled ellipse.
   118  func (e Ellipse) Op(ops *op.Ops) Op {
   119  	return Outline{Path: e.Path(ops)}.Op()
   120  }
   121  
   122  // Push the filled ellipse clip op on the clip stack.
   123  func (e Ellipse) Push(ops *op.Ops) Stack {
   124  	return e.Op(ops).Push(ops)
   125  }
   126  
   127  // Path constructs a path for the ellipse.
   128  func (e Ellipse) Path(o *op.Ops) PathSpec {
   129  	bounds := image.Rectangle(e)
   130  	if bounds.Dx() == 0 || bounds.Dy() == 0 {
   131  		return PathSpec{shape: ops.Rect}
   132  	}
   133  
   134  	var p Path
   135  	p.Begin(o)
   136  
   137  	bf := f32internal.FRect(bounds)
   138  	center := bf.Max.Add(bf.Min).Mul(.5)
   139  	diam := bf.Dx()
   140  	r := diam * .5
   141  	// We'll model the ellipse as a circle scaled in the Y
   142  	// direction.
   143  	scale := bf.Dy() / diam
   144  
   145  	// https://pomax.github.io/bezierinfo/#circles_cubic.
   146  	const q = 4 * (math.Sqrt2 - 1) / 3
   147  
   148  	curve := r * q
   149  	top := f32.Point{X: center.X, Y: center.Y - r*scale}
   150  
   151  	p.MoveTo(top)
   152  	p.CubeTo(
   153  		f32.Point{X: center.X + curve, Y: center.Y - r*scale},
   154  		f32.Point{X: center.X + r, Y: center.Y - curve*scale},
   155  		f32.Point{X: center.X + r, Y: center.Y},
   156  	)
   157  	p.CubeTo(
   158  		f32.Point{X: center.X + r, Y: center.Y + curve*scale},
   159  		f32.Point{X: center.X + curve, Y: center.Y + r*scale},
   160  		f32.Point{X: center.X, Y: center.Y + r*scale},
   161  	)
   162  	p.CubeTo(
   163  		f32.Point{X: center.X - curve, Y: center.Y + r*scale},
   164  		f32.Point{X: center.X - r, Y: center.Y + curve*scale},
   165  		f32.Point{X: center.X - r, Y: center.Y},
   166  	)
   167  	p.CubeTo(
   168  		f32.Point{X: center.X - r, Y: center.Y - curve*scale},
   169  		f32.Point{X: center.X - curve, Y: center.Y - r*scale},
   170  		top,
   171  	)
   172  	ellipse := p.End()
   173  	ellipse.shape = ops.Ellipse
   174  	return ellipse
   175  }