Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 17 additions & 17 deletions canvas/arbitrary_polygon.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,54 +10,54 @@ import (
var _ fyne.CanvasObject = (*ArbitraryPolygon)(nil)

// ArbitraryPolygon describes a colored arbitrary polygon primitive in a Fyne canvas.
// The polygon is defined by a list of vertex positions in clockwise order, specified in absolute coordinates
// The polygon is defined by a list of vertex positions in clockwise order,
// relative to the object (top-left is (0,0), bottom-right is (width,height)).
// Each corner can have an individually specified rounding radius.
//
// Since: 2.8
type ArbitraryPolygon struct {
baseObject

Points []fyne.Position // Vertices in coordinates relative to the object. If NormalizedPoints is true, these are (0.0 to 1.0), otherwise absolute.
NormalizedPoints bool // True if Points are specified in normalized coordinates (0.0 to 1.0) relative to the object's size.
CornerRadii []float32 // Per-corner rounding radius, must match len(Points); missing entries default to 0
Points []fyne.Position // Vertices in coordinates relative to the object. If NormalizedPoints is true, these are (0.0 to 1.0), otherwise absolute
NormalizedPoints bool // True if Points are specified in normalized coordinates (0.0 to 1.0) relative to the object's size
CornerRadii []float32 // Per-corner rounding radius, must match len(Points), missing entries default to 0
FillColor color.Color // The polygon fill color
StrokeColor color.Color // The polygon stroke color
StrokeWidth float32 // The stroke width of the polygon
}

// Hide will set this arbitrary polygon to not be visible
func (r *ArbitraryPolygon) Hide() {
r.baseObject.Hide()
func (p *ArbitraryPolygon) Hide() {
p.baseObject.Hide()

repaint(r)
repaint(p)
}

// Move the arbitrary polygon to a new position, relative to its parent / canvas
func (r *ArbitraryPolygon) Move(pos fyne.Position) {
if r.Position() == pos {
func (p *ArbitraryPolygon) Move(pos fyne.Position) {
if p.Position() == pos {
return
}

r.baseObject.Move(pos)
p.baseObject.Move(pos)

repaint(r)
repaint(p)
}

// Refresh causes this arbitrary polygon to be redrawn with its configured state.
func (r *ArbitraryPolygon) Refresh() {
Refresh(r)
func (p *ArbitraryPolygon) Refresh() {
Refresh(p)
}

// Resize on an arbitrary polygon updates the new size of this object.
func (r *ArbitraryPolygon) Resize(s fyne.Size) {
if s == r.Size() {
func (p *ArbitraryPolygon) Resize(s fyne.Size) {
if s == p.Size() {
return
}

r.baseObject.Resize(s)
p.baseObject.Resize(s)

Refresh(r)
Refresh(p)
}

// NewArbitraryPolygon returns a new ArbitraryPolygon instance
Expand Down
4 changes: 0 additions & 4 deletions canvas/canvas.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,6 @@ const (
// This constant represents the maximum possible corner radius, resulting in a circular appearance.
// Since: 2.7
RadiusMaximum float32 = math.MaxFloat32

// ArbitraryPolygonVerticesMaximum defines the maximum number of vertices supported by the arbitrary polygon
// Since: 2.8
ArbitraryPolygonVerticesMaximum = 16
)

// Refresh instructs the containing canvas to refresh the specified obj.
Expand Down
1 change: 1 addition & 0 deletions internal/driver/mobile/gl/fn.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ const (
glfnUniform1f
glfnUniform1fv
glfnUniform2f
glfnUniform2fv
glfnUniform4f
glfnUniform4fv
glfnUseProgram
Expand Down
12 changes: 12 additions & 0 deletions internal/driver/mobile/gl/gl.go
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,18 @@ func (ctx *context) Uniform2f(dst Uniform, v0, v1 float32) {
})
}

func (ctx *context) Uniform2fv(dst Uniform, src []float32) {
ctx.enqueue(call{
args: fnargs{
fn: glfnUniform2fv,
a0: dst.c(),
a1: uintptr(len(src) / 2),
},
parg: unsafe.Pointer(&src[0]),
blocking: true,
})
}

func (ctx *context) Uniform4f(dst Uniform, v0, v1, v2, v3 float32) {
ctx.enqueue(call{
args: fnargs{
Expand Down
5 changes: 5 additions & 0 deletions internal/driver/mobile/gl/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,11 @@ type Context interface {
// http://www.khronos.org/opengles/sdk/docs/man3/html/glUniform.xhtml
Uniform2f(dst Uniform, v0, v1 float32)

// Uniform2fv writes a vec2 uniform array of len(src)/2 elements.
//
// http://www.khronos.org/opengles/sdk/docs/man3/html/glUniform.xhtml
Uniform2fv(dst Uniform, v []float32)

// Uniform4f writes a vec4 uniform variable.
//
// http://www.khronos.org/opengles/sdk/docs/man3/html/glUniform.xhtml
Expand Down
3 changes: 3 additions & 0 deletions internal/driver/mobile/gl/work.c
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,9 @@ uintptr_t processFn(struct fnargs* args, char* parg) {
case glfnUniform2f:
glUniform2f((GLint)args->a0, *(GLfloat*)&args->a1, *(GLfloat*)&args->a2);
break;
case glfnUniform2fv:
glUniform2fv((GLint)args->a0, (GLsizeiptr)args->a1, (GLvoid*)parg);
break;
case glfnUniform4f:
glUniform4f((GLint)args->a0, *(GLfloat*)&args->a1, *(GLfloat*)&args->a2, *(GLfloat*)&args->a3, *(GLfloat*)&args->a4);
break;
Expand Down
1 change: 1 addition & 0 deletions internal/driver/mobile/gl/work.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ typedef enum {
glfnUniform1f,
glfnUniform1fv,
glfnUniform2f,
glfnUniform2fv,
glfnUniform4f,
glfnUniform4fv,
glfnUseProgram,
Expand Down
5 changes: 5 additions & 0 deletions internal/driver/mobile/gl/work_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,10 @@ var glfnFuncs = [...]func(c call) (ret uintptr){
syscall.SyscallN(glUniform2f.Addr(), c.args.a0, c.args.a1, c.args.a2)
return ret
},
glfnUniform2fv: func(c call) (ret uintptr) {
syscall.SyscallN(glUniform2fv.Addr(), c.args.a0, c.args.a1, uintptr(c.parg))
return
},
glfnUniform4f: func(c call) (ret uintptr) {
syscall.SyscallN(glUniform4f.Addr(), c.args.a0, c.args.a1, c.args.a2, c.args.a3, c.args.a4)
return ret
Expand Down Expand Up @@ -350,6 +354,7 @@ var (
glUniform1f = libGLESv2.NewProc("glUniform1f")
glUniform1fv = libGLESv2.NewProc("glUniform1fv")
glUniform2f = libGLESv2.NewProc("glUniform2f")
glUniform2fv = libGLESv2.NewProc("glUniform2fv")
glUniform4f = libGLESv2.NewProc("glUniform4f")
glUniform4fv = libGLESv2.NewProc("glUniform4fv")
glUseProgram = libGLESv2.NewProc("glUseProgram")
Expand Down
51 changes: 26 additions & 25 deletions internal/painter/draw.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ import (
"golang.org/x/image/math/fixed"
)

const quarterCircleControl = 1 - 0.55228
const (
quarterCircleControl = 1 - 0.55228
ArbitraryPolygonVerticesMaximum = 32
)

// DrawArc rasterizes the given arc object into an image.
// The scale function is used to understand how many pixels are required per unit of size.
Expand Down Expand Up @@ -190,26 +193,25 @@ func DrawArbitraryPolygon(polygon *canvas.ArbitraryPolygon, vectorPad float32, s
width := int(scale(size.Width + vectorPad*2))
height := int(scale(size.Height + vectorPad*2))
vertices := polygon.Points
radii := polygon.CornerRadii
num := int(fyne.Min(canvas.ArbitraryPolygonVerticesMaximum, float32(len(vertices))))
numPoints := int(fyne.Min(ArbitraryPolygonVerticesMaximum, float32(len(vertices))))

raw := image.NewRGBA(image.Rect(0, 0, width, height))
scanner := rasterx.NewScannerGV(int(size.Width), int(size.Height), raw, raw.Bounds())

if num < 3 {
if numPoints < 3 {
return raw
}

clampPoint := func(p fyne.Position) (float32, float32) {
return fyne.Min(fyne.Max(p.X, 0), fyne.Max(size.Width, 0)), fyne.Min(fyne.Max(p.Y, 0), fyne.Max(size.Height, 0))
}

xScaled := make([]float64, num)
yScaled := make([]float64, num)
radius := make([]float32, num)
xScaled := make([]float64, numPoints)
yScaled := make([]float64, numPoints)
cornerRadii := make([]float32, numPoints)

fixedPoints := make([]fyne.Position, num)
for i := 0; i < num; i++ {
fixedPoints := make([]fyne.Position, numPoints)
for i := 0; i < numPoints; i++ {
px, py := vertices[i].X, vertices[i].Y
if polygon.NormalizedPoints {
px, py = px*size.Width, py*size.Height
Expand All @@ -219,32 +221,31 @@ func DrawArbitraryPolygon(polygon *canvas.ArbitraryPolygon, vectorPad float32, s
xScaled[i] = float64(scale(px + vectorPad))
yScaled[i] = float64(scale(py + vectorPad))

var r float32
if i < len(radii) {
r = radii[i]
var radius float32
if i < len(polygon.CornerRadii) {
radius = polygon.CornerRadii[i]
}
radius[i] = r
cornerRadii[i] = radius
}

radius = GetMaximumCornerRadiusPolygon(fixedPoints, radius)

radiiScaled := make([]float64, num)
for i := 0; i < num; i++ {
radiiScaled[i] = float64(scale(radius[i]))
cornerRadii = GetMaximumCornerRadii(fixedPoints, cornerRadii)
cornerRadiiScaled := make([]float64, numPoints)
for i := 0; i < numPoints; i++ {
cornerRadiiScaled[i] = float64(scale(cornerRadii[i]))
}

if polygon.FillColor != nil {
filler := rasterx.NewFiller(width, height, scanner)
filler.SetColor(polygon.FillColor)
drawArbitraryPolygon(xScaled, yScaled, radiiScaled, filler)
drawArbitraryPolygon(xScaled, yScaled, cornerRadiiScaled, filler)
filler.Draw()
}

if polygon.StrokeColor != nil && polygon.StrokeWidth > 0 {
dasher := rasterx.NewDasher(width, height, scanner)
dasher.SetColor(polygon.StrokeColor)
dasher.SetStroke(fixed.Int26_6(float64(scale(polygon.StrokeWidth))*64), 0, nil, nil, nil, 0, nil, 0)
drawArbitraryPolygon(xScaled, yScaled, radiiScaled, dasher)
drawArbitraryPolygon(xScaled, yScaled, cornerRadiiScaled, dasher)
dasher.Draw()
}

Expand Down Expand Up @@ -913,12 +914,12 @@ func NormalizeBezierCurvePoints(startPoint, endPoint fyne.Position, controlPoint
return fyne.NewPos(p1x, p1y), fyne.NewPos(p2x, p2y), cp
}

// GetMaximumCornerRadiusPolygon computes the maximum possible corner radius for an individual corner,
// considering the specified corner radius, the radii of adjacent corners, and the maximum radii
// allowed for the width and height of the shape. Corner radius may utilize unused capacity from adjacent corners with radius smaller than maximum value.
// GetMaximumCornerRadii calculates the maximum possible corner radii for an arbitrary polygon with given vertices and desired corner radii.
// It ensures that the specified corner radius do not cause overlaps between adjacent corners by calculating the interior angles at each vertex
// and adjusting the radius proportionally if necessary. The function returns a slice of adjusted corner radii that fit within the geometry of the polygon.
//
// This is typically used for drawing circular corners in arbitrary polygons with different corner radii.
func GetMaximumCornerRadiusPolygon(points []fyne.Position, radii []float32) []float32 {
// This is typically used for drawing arbitrary polygons with rounded corners, ensuring that the corners do not overlap and the shape remains visually consistent.
func GetMaximumCornerRadii(points []fyne.Position, radii []float32) []float32 {
n := len(points)
if n < 3 || len(radii) != n {
return radii
Expand Down
1 change: 1 addition & 0 deletions internal/painter/gl/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type context interface {
Uniform1f(uniform Uniform, v float32)
Uniform1fv(uniform Uniform, v []float32)
Uniform2f(uniform Uniform, v0, v1 float32)
Uniform2fv(uniform Uniform, v []float32)
Uniform4f(uniform Uniform, v0, v1, v2, v3 float32)
UseProgram(program Program)
VertexAttribPointerWithOffset(attribute Attribute, size int, typ uint32, normalized bool, stride, offset int)
Expand Down
35 changes: 15 additions & 20 deletions internal/painter/gl/draw.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package gl

import (
"fmt"
"image/color"
"math"

Expand All @@ -12,7 +11,7 @@ import (
paint "fyne.io/fyne/v2/internal/painter"
)

const edgeSoftness = 1.0
const edgeSoftness = 0.5

func (p *painter) createBuffer(size int) Buffer {
vbo := p.ctx.CreateBuffer()
Expand All @@ -24,13 +23,6 @@ func (p *painter) createBuffer(size int) Buffer {
return vbo
}

func (p *painter) defineVertexArray(prog Program, name string, size, stride, offset int) {
vertAttrib := p.ctx.GetAttribLocation(prog, name)
p.ctx.EnableVertexAttribArray(vertAttrib)
p.ctx.VertexAttribPointerWithOffset(vertAttrib, size, float, false, stride*floatSize, offset*floatSize)
p.logError()
}

func (p *painter) drawBlur(b *canvas.Blur, pos fyne.Position, frame fyne.Size) {
if b.Radius == 0 {
return
Expand Down Expand Up @@ -73,8 +65,8 @@ func (p *painter) drawBlur(b *canvas.Blur, pos fyne.Position, frame fyne.Size) {

p.ctx.UseProgram(p.blurProgram.ref)
p.updateBuffer(p.blurProgram.buff, points)
p.defineVertexArray(p.blurProgram.ref, "vert", 3, 5, 0)
p.defineVertexArray(p.blurProgram.ref, "vertTexCoord", 2, 5, 3)
p.UpdateVertexArray(p.blurProgram, "vert", 3, 5, 0)
p.UpdateVertexArray(p.blurProgram, "vertTexCoord", 2, 5, 3)

p.ctx.BlendFunc(one, oneMinusSrcAlpha)
p.logError()
Expand Down Expand Up @@ -270,7 +262,7 @@ func (p *painter) drawArbitraryPolygon(polygon *canvas.ArbitraryPolygon, pos fyn
edgeSoftnessScaled := roundToPixel(edgeSoftness*p.pixScale, 1.0)
p.SetUniform1f(program, "edge_softness", edgeSoftnessScaled)

numPoints := int(fyne.Min(canvas.ArbitraryPolygonVerticesMaximum, float32(len(polygon.Points))))
numPoints := int(fyne.Min(paint.ArbitraryPolygonVerticesMaximum, float32(len(polygon.Points))))
p.SetUniform1f(program, "vertex_count", float32(numPoints))

size := polygon.Size()
Expand All @@ -279,7 +271,7 @@ func (p *painter) drawArbitraryPolygon(polygon *canvas.ArbitraryPolygon, pos fyn
}

fixedPoints := make([]fyne.Position, numPoints)
radii := make([]float32, numPoints)
cornerRadii := make([]float32, numPoints)

for i := 0; i < numPoints; i++ {
px, py := polygon.Points[i].X, polygon.Points[i].Y
Expand All @@ -293,19 +285,22 @@ func (p *painter) drawArbitraryPolygon(polygon *canvas.ArbitraryPolygon, pos fyn
if i < len(polygon.CornerRadii) {
radius = polygon.CornerRadii[i]
}
radii[i] = radius
cornerRadii[i] = radius
}

scaledRadii := paint.GetMaximumCornerRadiusPolygon(fixedPoints, radii)
cornerRadii = paint.GetMaximumCornerRadii(fixedPoints, cornerRadii)

verticesScaled := make([]float32, numPoints*2)
cornerRadiiScaled := make([]float32, numPoints)
for i := 0; i < numPoints; i++ {
pXScaled, pYScaled := roundToPixel(fixedPoints[i].X*p.pixScale, 1.0), roundToPixel(fixedPoints[i].Y*p.pixScale, 1.0)
p.SetUniform2f(program, fmt.Sprintf("vertices[%d]", i), pXScaled, pYScaled)

radiusScaled := roundToPixel(scaledRadii[i]*p.pixScale, 1.0)
p.SetUniform1f(program, fmt.Sprintf("radii[%d]", i), radiusScaled)
verticesScaled[i*2] = roundToPixel(fixedPoints[i].X*p.pixScale, 1.0)
verticesScaled[i*2+1] = roundToPixel(fixedPoints[i].Y*p.pixScale, 1.0)
cornerRadiiScaled[i] = roundToPixel(cornerRadii[i]*p.pixScale, 1.0)
}

p.SetUniform2fv(program, "vertices", verticesScaled)
p.SetUniform1fv(program, "corner_radii", cornerRadiiScaled)

// Colors and Stroke
r, g, b, a := getFragmentColor(polygon.FillColor)
p.SetUniform4f(program, "fill_color", r, g, b, a)
Expand Down
Loading
Loading