shapes.go 4.3 KB

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