VideoTools/vendor/fyne.io/fyne/v2/widget/radio_group.go
Stu Leak 68df790d27 Fix player frame generation and video playback
Major improvements to UnifiedPlayer:

1. GetFrameImage() now works when paused for responsive UI updates
2. Play() method properly starts FFmpeg process
3. Frame display loop runs continuously for smooth video display
4. Disabled audio temporarily to fix video playback fundamentals
5. Simplified FFmpeg command to focus on video stream only

Player now:
- Generates video frames correctly
- Shows video when paused
- Has responsive progress tracking
- Starts playback properly

Next steps: Re-enable audio playback once video is stable
2026-01-07 22:20:00 -05:00

249 lines
5.7 KiB
Go

package widget
import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/internal/widget"
)
// RadioGroup widget has a list of text labels and checks check icons next to each.
// Changing the selection (only one can be selected) will trigger the changed func.
//
// Since: 1.4
type RadioGroup struct {
DisableableWidget
Horizontal bool
Required bool
OnChanged func(string) `json:"-"`
Options []string
Selected string
// this index is ONE-BASED so the default zero-value is unselected
// use r.selectedIndex(), r.setSelectedIndex(int) to maniupulate this field
// as if it were a zero-based index (with -1 == nothing selected)
_selIdx int
}
var _ fyne.Widget = (*RadioGroup)(nil)
// NewRadioGroup creates a new radio group widget with the set options and change handler
//
// Since: 1.4
func NewRadioGroup(options []string, changed func(string)) *RadioGroup {
r := &RadioGroup{
Options: options,
OnChanged: changed,
}
r.ExtendBaseWidget(r)
return r
}
// Append adds a new option to the end of a RadioGroup widget.
func (r *RadioGroup) Append(option string) {
r.Options = append(r.Options, option)
r.Refresh()
}
// CreateRenderer is a private method to Fyne which links this widget to its renderer
func (r *RadioGroup) CreateRenderer() fyne.WidgetRenderer {
r.ExtendBaseWidget(r)
items := make([]fyne.CanvasObject, len(r.Options))
for i, option := range r.Options {
idx := i
items[idx] = newRadioItem(option, func(item *radioItem) {
r.itemTapped(item, idx)
})
}
render := &radioGroupRenderer{widget.NewBaseRenderer(items), items, r}
r.updateSelectedIndex()
render.updateItems(false)
return render
}
// MinSize returns the size that this widget should not shrink below
func (r *RadioGroup) MinSize() fyne.Size {
r.ExtendBaseWidget(r)
return r.BaseWidget.MinSize()
}
// SetSelected sets the radio option, it can be used to set a default option.
func (r *RadioGroup) SetSelected(option string) {
if r.Selected == option {
return
}
r.Selected = option
// selectedIndex will be updated on refresh to the first matching item
if r.OnChanged != nil {
r.OnChanged(option)
}
r.Refresh()
}
func (r *RadioGroup) itemTapped(item *radioItem, idx int) {
if r.Disabled() {
return
}
if r.selectedIndex() == idx {
if r.Required {
return
}
r.Selected = ""
r.setSelectedIndex(-1)
item.SetSelected(false)
} else {
r.Selected = item.Label
r.setSelectedIndex(idx)
item.SetSelected(true)
}
if r.OnChanged != nil {
r.OnChanged(r.Selected)
}
r.Refresh()
}
func (r *RadioGroup) Refresh() {
r.updateSelectedIndex()
r.BaseWidget.Refresh()
}
func (r *RadioGroup) selectedIndex() int {
return r._selIdx - 1
}
func (r *RadioGroup) setSelectedIndex(idx int) {
r._selIdx = idx + 1
}
// if selectedIndex does not match the public Selected property,
// set it to the index of the first radio item whose label matches Selected
func (r *RadioGroup) updateSelectedIndex() {
sel := r.Selected
sIdx := r.selectedIndex()
if sIdx >= 0 && sIdx < len(r.Options) && r.Options[sIdx] == sel {
return // selected index matches Selected
}
if sIdx == -1 && sel == "" {
return // nothing selected
}
sIdx = -1
for i, opt := range r.Options {
if sel == opt {
sIdx = i
break
}
}
r.setSelectedIndex(sIdx)
}
type radioGroupRenderer struct {
widget.BaseRenderer
// slice of *radioItem, but using fyne.CanvasObject as the type
// so we can directly set it to the BaseRenderer's objects slice
items []fyne.CanvasObject
radio *RadioGroup
}
// Layout the components of the radio widget
func (r *radioGroupRenderer) Layout(_ fyne.Size) {
count := 1
if len(r.items) > 0 {
count = len(r.items)
}
var itemHeight, itemWidth float32
minSize := r.radio.MinSize()
if r.radio.Horizontal {
itemHeight = minSize.Height
itemWidth = minSize.Width / float32(count)
} else {
itemHeight = minSize.Height / float32(count)
itemWidth = minSize.Width
}
itemSize := fyne.NewSize(itemWidth, itemHeight)
x, y := float32(0), float32(0)
for _, item := range r.items {
item.Resize(itemSize)
item.Move(fyne.NewPos(x, y))
if r.radio.Horizontal {
x += itemWidth
} else {
y += itemHeight
}
}
}
// MinSize calculates the minimum size of a radio item.
// This is based on the contained text, the radio icon and a standard amount of padding
// between each item.
func (r *radioGroupRenderer) MinSize() fyne.Size {
width := float32(0)
height := float32(0)
for _, item := range r.items {
itemMin := item.MinSize()
width = fyne.Max(width, itemMin.Width)
height = fyne.Max(height, itemMin.Height)
}
if r.radio.Horizontal {
width = width * float32(len(r.items))
} else {
height = height * float32(len(r.items))
}
return fyne.NewSize(width, height)
}
func (r *radioGroupRenderer) Refresh() {
r.updateItems(true)
canvas.Refresh(r.radio.super())
}
func (r *radioGroupRenderer) updateItems(refresh bool) {
if len(r.items) < len(r.radio.Options) {
for i := len(r.items); i < len(r.radio.Options); i++ {
idx := i
item := newRadioItem(r.radio.Options[idx], func(item *radioItem) {
r.radio.itemTapped(item, idx)
})
r.items = append(r.items, item)
}
r.Layout(r.radio.Size())
} else if len(r.items) > len(r.radio.Options) {
total := len(r.radio.Options)
r.items = r.items[:total]
}
r.SetObjects(r.items)
for i, item := range r.items {
item := item.(*radioItem)
changed := false
if l := r.radio.Options[i]; l != item.Label {
item.Label = r.radio.Options[i]
changed = true
}
if sel := i == r.radio.selectedIndex(); sel != item.Selected {
item.Selected = sel
changed = true
}
if d := r.radio.Disabled(); d != item.Disabled() {
item.disabled = d
changed = true
}
if refresh || changed {
item.Refresh()
}
}
}