Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Option to CheckGroup to make Rows/Columns #5226

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
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
89 changes: 60 additions & 29 deletions widget/check_group.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package widget

import (
"math"
"strings"

"fyne.io/fyne/v2"
Expand All @@ -15,6 +16,7 @@ import (
type CheckGroup struct {
DisableableWidget
Horizontal bool
numColumns int
Required bool
OnChanged func([]string) `json:"-"`
Options []string
Expand All @@ -38,6 +40,10 @@ func NewCheckGroup(options []string, changed func([]string)) *CheckGroup {
return r
}

func (r *CheckGroup) SetColumns(columns int) {
r.numColumns = columns
}

// Append adds a new option to the end of a CheckGroup widget.
func (r *CheckGroup) Append(option string) {
r.Options = append(r.Options, option)
Expand Down Expand Up @@ -179,31 +185,57 @@ type checkGroupRenderer struct {
checks *CheckGroup
}

func (r *CheckGroup) countColumns() int {
if r.numColumns < 1 {
r.numColumns = 1
}
return r.numColumns
}

func (r *CheckGroup) countRows() int {
return int(math.Ceil(float64(len(r.items)) / float64(r.countColumns())))
}

// Layout the components of the checks widget
func (r *checkGroupRenderer) Layout(_ fyne.Size) {
count := 1
if len(r.items) > 0 {
count = len(r.items)
}
var itemHeight, itemWidth float32
minSize := r.checks.MinSize()
cols := r.checks.countColumns()

primaryObjects := cols
secondaryObjects := r.checks.countRows()
if r.checks.Horizontal {
itemHeight = minSize.Height
itemWidth = minSize.Width / float32(count)
} else {
itemHeight = minSize.Height / float32(count)
itemWidth = minSize.Width
primaryObjects, secondaryObjects = secondaryObjects, primaryObjects
}

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))
size := r.checks.Size()
cellWidth := size.Width / float32(primaryObjects)
cellHeight := size.Height / float32(secondaryObjects)

row, col := 0, 0
for i, child := range r.items {
// leading edge
x1 := cellWidth * float32(col)
y1 := cellHeight * float32(row)
// trailing edge
x2 := cellWidth * float32(col+1)
y2 := cellHeight * float32(row+1)

child.Move(fyne.NewPos(x1, y1))
child.Resize(fyne.NewSize(x2-x1, y2-y1))

if r.checks.Horizontal {
x += itemWidth
if (i+1)%cols == 0 {
col++
row = 0
} else {
row++
}
} else {
y += itemHeight
if (i+1)%cols == 0 {
row++
col = 0
} else {
col++
}
}
}
}
Expand All @@ -212,21 +244,20 @@ func (r *checkGroupRenderer) Layout(_ fyne.Size) {
// This is based on the contained text, the checks icon and a standard amount of padding
// between each item.
func (r *checkGroupRenderer) 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)
minSize := fyne.NewSize(0, 0)
for _, child := range r.items {
minSize = minSize.Max(child.MinSize())
}

primaryObjects := r.checks.countColumns()
secondaryObjects := r.checks.countRows()
if r.checks.Horizontal {
width = width * float32(len(r.items))
} else {
height = height * float32(len(r.items))
primaryObjects, secondaryObjects = secondaryObjects, primaryObjects
}

width := minSize.Width * float32(primaryObjects)
height := minSize.Height * float32(secondaryObjects)

return fyne.NewSize(width, height)
}

Expand All @@ -245,7 +276,7 @@ func (r *checkGroupRenderer) updateItems() {
r.SetObjects(append(r.Objects(), item))
r.items = append(r.items, item)
}
r.Layout(r.checks.Size())
r.Layout(r.checks.Size()) // argument is ignored
} else if len(r.items) > len(r.checks.Options) {
total := len(r.checks.Options)
r.items = r.items[:total]
Expand Down
109 changes: 109 additions & 0 deletions widget/check_group_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package widget_test

import (
"fmt"
"testing"

"fyne.io/fyne/v2"
Expand Down Expand Up @@ -217,3 +218,111 @@ func TestCheckGroup_ManipulateOptions(t *testing.T) {
assert.Equal(t, 1, len(check.Options))
assert.Equal(t, 0, len(check.Selected))
}

func TestCheckGroup_LayoutColumns(t *testing.T) {
lotsofoptions := []string{}
for i := 0; i < 50; i++ {
lotsofoptions = append(lotsofoptions, fmt.Sprintf("Test %d", i))
}

testmap := map[string]struct {
disabled bool
horizontal bool
options []string
selected []string
}{
"single": {
options: []string{"Test"},
},
"single_disabled": {
disabled: true,
options: []string{"Test"},
},
"single_horizontal": {
horizontal: true,
options: []string{"Test"},
},
"single_horizontal_disabled": {
disabled: true,
horizontal: true,
options: []string{"Test"},
},
"single_selected": {
options: []string{"Test"},
selected: []string{"Test"},
},
"single_selected_disabled": {
disabled: true,
options: []string{"Test"},
selected: []string{"Test"},
},
"single_selected_horizontal": {
horizontal: true,
options: []string{"Test"},
selected: []string{"Test"},
},
"single_selected_horizontal_disabled": {
disabled: true,
horizontal: true,
options: []string{"Test"},
selected: []string{"Test"},
},
"multiple": {
options: lotsofoptions,
},
"multiple_disabled": {
disabled: true,
options: lotsofoptions,
},
"multiple_horizontal": {
horizontal: true,
options: lotsofoptions,
},
"multiple_horizontal_disabled": {
disabled: true,
horizontal: true,
options: lotsofoptions,
},
"multiple_selected": {
options: lotsofoptions,
selected: []string{lotsofoptions[0], lotsofoptions[5]},
},
"multiple_selected_disabled": {
disabled: true,
options: lotsofoptions,
selected: []string{lotsofoptions[0], lotsofoptions[5]},
},
"multiple_selected_horizontal": {
horizontal: true,
options: lotsofoptions,
selected: []string{lotsofoptions[0], lotsofoptions[5]},
},
"multiple_selected_horizontal_disabled": {
disabled: true,
horizontal: true,
options: lotsofoptions,
selected: []string{lotsofoptions[0], lotsofoptions[5]},
},
}

for _, i := range []int{-2, 2, 5} {
for name, tt := range testmap {
t.Run(name, func(t *testing.T) {
check := &widget.CheckGroup{
Horizontal: tt.horizontal,
Options: tt.options,
Selected: tt.selected,
}
check.SetColumns(i)
if tt.disabled {
check.Disable()
}

window := test.NewTempWindow(t, check)
window.Resize(check.MinSize().Max(fyne.NewSize(1500, 1500)))

test.AssertRendersToMarkup(t, fmt.Sprintf("check_group/layout_columns_%d_%s.xml", i, name), window.Canvas())
})
}
}
}
30 changes: 15 additions & 15 deletions widget/testdata/check_group/disabled_append_none_selected.xml
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
<canvas padded size="150x200">
<content>
<widget pos="4,4" size="142x192" type="*widget.CheckGroup">
<widget size="94x35" type="*widget.Check">
<circle pos="2,3" size="28x28"/>
<image pos="6,7" rsc="checkButtonFillIcon" size="iconInlineSize" themed="background"/>
<image pos="6,7" rsc="checkButtonIcon" size="iconInlineSize" themed="disabled"/>
<text color="disabled" pos="32,0" size="62x35">Option A</text>
<widget size="142x64" type="*widget.Check">
<circle pos="2,18" size="28x28"/>
<image pos="6,22" rsc="checkButtonFillIcon" size="iconInlineSize" themed="background"/>
<image pos="6,22" rsc="checkButtonIcon" size="iconInlineSize" themed="disabled"/>
<text color="disabled" pos="32,0" size="110x64">Option A</text>
</widget>
<widget pos="0,35" size="94x35" type="*widget.Check">
<circle pos="2,3" size="28x28"/>
<image pos="6,7" rsc="checkButtonFillIcon" size="iconInlineSize" themed="background"/>
<image pos="6,7" rsc="checkButtonIcon" size="iconInlineSize" themed="disabled"/>
<text color="disabled" pos="32,0" size="62x35">Option B</text>
<widget pos="0,64" size="142x64" type="*widget.Check">
<circle pos="2,18" size="28x28"/>
<image pos="6,22" rsc="checkButtonFillIcon" size="iconInlineSize" themed="background"/>
<image pos="6,22" rsc="checkButtonIcon" size="iconInlineSize" themed="disabled"/>
<text color="disabled" pos="32,0" size="110x64">Option B</text>
</widget>
<widget pos="0,70" size="94x35" type="*widget.Check">
<circle pos="2,3" size="28x28"/>
<image pos="6,7" rsc="checkButtonFillIcon" size="iconInlineSize" themed="background"/>
<image pos="6,7" rsc="checkButtonIcon" size="iconInlineSize" themed="disabled"/>
<text color="disabled" pos="32,0" size="62x35">Option C</text>
<widget pos="0,128" size="142x64" type="*widget.Check">
<circle pos="2,18" size="28x28"/>
<image pos="6,22" rsc="checkButtonFillIcon" size="iconInlineSize" themed="background"/>
<image pos="6,22" rsc="checkButtonIcon" size="iconInlineSize" themed="disabled"/>
<text color="disabled" pos="32,0" size="110x64">Option C</text>
</widget>
</widget>
</content>
Expand Down
20 changes: 10 additions & 10 deletions widget/testdata/check_group/disabled_none_selected.xml
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
<canvas padded size="150x200">
<content>
<widget pos="4,4" size="142x192" type="*widget.CheckGroup">
<widget size="94x35" type="*widget.Check">
<circle pos="2,3" size="28x28"/>
<image pos="6,7" rsc="checkButtonFillIcon" size="iconInlineSize" themed="background"/>
<image pos="6,7" rsc="checkButtonIcon" size="iconInlineSize" themed="disabled"/>
<text color="disabled" pos="32,0" size="62x35">Option A</text>
<widget size="142x96" type="*widget.Check">
<circle pos="2,34" size="28x28"/>
<image pos="6,38" rsc="checkButtonFillIcon" size="iconInlineSize" themed="background"/>
<image pos="6,38" rsc="checkButtonIcon" size="iconInlineSize" themed="disabled"/>
<text color="disabled" pos="32,0" size="110x96">Option A</text>
</widget>
<widget pos="0,35" size="94x35" type="*widget.Check">
<circle pos="2,3" size="28x28"/>
<image pos="6,7" rsc="checkButtonFillIcon" size="iconInlineSize" themed="background"/>
<image pos="6,7" rsc="checkButtonIcon" size="iconInlineSize" themed="disabled"/>
<text color="disabled" pos="32,0" size="62x35">Option B</text>
<widget pos="0,96" size="142x96" type="*widget.Check">
<circle pos="2,34" size="28x28"/>
<image pos="6,38" rsc="checkButtonFillIcon" size="iconInlineSize" themed="background"/>
<image pos="6,38" rsc="checkButtonIcon" size="iconInlineSize" themed="disabled"/>
<text color="disabled" pos="32,0" size="110x96">Option B</text>
</widget>
</widget>
</content>
Expand Down
30 changes: 15 additions & 15 deletions widget/testdata/check_group/focus_a_focused_b_selected.xml
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
<canvas padded size="150x200">
<content>
<widget pos="4,4" size="142x192" type="*widget.CheckGroup">
<widget size="94x35" type="*widget.Check">
<circle fillColor="focus" pos="2,3" size="28x28"/>
<image pos="6,7" rsc="checkButtonFillIcon" size="iconInlineSize" themed="inputBackground"/>
<image pos="6,7" rsc="checkButtonIcon" size="iconInlineSize" themed="inputBorder"/>
<text pos="32,0" size="62x35">Option A</text>
<widget size="142x64" type="*widget.Check">
<circle fillColor="focus" pos="2,18" size="28x28"/>
<image pos="6,22" rsc="checkButtonFillIcon" size="iconInlineSize" themed="inputBackground"/>
<image pos="6,22" rsc="checkButtonIcon" size="iconInlineSize" themed="inputBorder"/>
<text pos="32,0" size="110x64">Option A</text>
</widget>
<widget pos="0,35" size="94x35" type="*widget.Check">
<circle pos="2,3" size="28x28"/>
<image pos="6,7" rsc="checkButtonFillIcon" size="iconInlineSize" themed="background"/>
<image pos="6,7" rsc="checkButtonCheckedIcon" size="iconInlineSize" themed="primary"/>
<text pos="32,0" size="62x35">Option B</text>
<widget pos="0,64" size="142x64" type="*widget.Check">
<circle pos="2,18" size="28x28"/>
<image pos="6,22" rsc="checkButtonFillIcon" size="iconInlineSize" themed="background"/>
<image pos="6,22" rsc="checkButtonCheckedIcon" size="iconInlineSize" themed="primary"/>
<text pos="32,0" size="110x64">Option B</text>
</widget>
<widget pos="0,70" size="94x35" type="*widget.Check">
<circle pos="2,3" size="28x28"/>
<image pos="6,7" rsc="checkButtonFillIcon" size="iconInlineSize" themed="background"/>
<image pos="6,7" rsc="checkButtonCheckedIcon" size="iconInlineSize" themed="primary"/>
<text pos="32,0" size="62x35">Option C</text>
<widget pos="0,128" size="142x64" type="*widget.Check">
<circle pos="2,18" size="28x28"/>
<image pos="6,22" rsc="checkButtonFillIcon" size="iconInlineSize" themed="background"/>
<image pos="6,22" rsc="checkButtonCheckedIcon" size="iconInlineSize" themed="primary"/>
<text pos="32,0" size="110x64">Option C</text>
</widget>
</widget>
</content>
Expand Down
Loading
Loading