package ui import ( "image" "image/color" "math" "gioui.org/f32" "gioui.org/layout" "gioui.org/op/clip" "gioui.org/op/paint" "gioui.org/unit" ) // progressRing renders a circular progress indicator. Progress is clamped // to [0, 1]; 1.0 fills the full circle. Drawing approximates the arc with // short line segments, which is plenty smooth at the small sizes used in // list rows. type progressRing struct { Size unit.Dp Stroke unit.Dp Progress float32 Color color.NRGBA BgColor color.NRGBA } func (r progressRing) Layout(gtx layout.Context) layout.Dimensions { sizePx := gtx.Dp(r.Size) if sizePx <= 0 { return layout.Dimensions{} } strokePx := float32(gtx.Dp(r.Stroke)) if strokePx <= 0 { strokePx = 2 } center := f32.Pt(float32(sizePx)/2, float32(sizePx)/2) radius := float32(sizePx)/2 - strokePx/2 if r.BgColor.A > 0 { var bg clip.Path bg.Begin(gtx.Ops) buildArc(&bg, center, radius, -math.Pi/2, math.Pi*2) paint.FillShape(gtx.Ops, r.BgColor, clip.Stroke{Path: bg.End(), Width: strokePx}.Op()) } if r.Progress > 0 { sweep := float64(r.Progress) * math.Pi * 2 if sweep > math.Pi*2 { sweep = math.Pi * 2 } var fg clip.Path fg.Begin(gtx.Ops) buildArc(&fg, center, radius, -math.Pi/2, sweep) paint.FillShape(gtx.Ops, r.Color, clip.Stroke{Path: fg.End(), Width: strokePx}.Op()) } return layout.Dimensions{Size: image.Pt(sizePx, sizePx)} } // buildArc emits a polyline approximation of an arc into p. startRad is // the starting angle (radians, 0 = +x axis, clockwise), sweepRad is the // signed angular extent. func buildArc(p *clip.Path, center f32.Point, radius float32, startRad, sweepRad float64) { const segs = 48 step := sweepRad / float64(segs) start := f32.Pt( center.X+radius*float32(math.Cos(startRad)), center.Y+radius*float32(math.Sin(startRad)), ) p.MoveTo(start) for i := 1; i <= segs; i++ { a := startRad + step*float64(i) p.LineTo(f32.Pt( center.X+radius*float32(math.Cos(a)), center.Y+radius*float32(math.Sin(a)), )) } }