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

TwoStateToolbarAction #93

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,29 @@ m := NewMap()

![](img/map.png)

### TwoStateToolbarAction

A TwoStateToolbarAction displays one of two icons based on the stored state. It is similar
to a regular ToolbarAction except that the icon and state are toggled each time the toolbar
action is activated. The current (new) state is passed to the `onActivated` function.

One potential use of this toolbar action include displaying the MediaPlayIcon when a media
file is not being played, and the MediaPauseIcon or MediaStopIcon when a media file is
being played. A second use may be seen in an application where a left or right panel is
di8splayed or not. For example, show the left panel open icon when the left panel is
closed, and the left panel close icon when the panel is open.


```go
action := NewTwoStateToolBar(theme.MediaPlayIcon(),
theme.MediaPauseIcon,
func(on bool) {
// Do something with state. For example, if on is true, start playback.
})
```

* [Demo App](cmd/twostatetoolbaraction_demo/main.go)

## Dialogs

### About
Expand Down
38 changes: 38 additions & 0 deletions cmd/twostatetoolbaraction_demo/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package main

import (
"fmt"

"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
xwidget "fyne.io/x/fyne/widget"
)

func main() {
a := app.New()
w := a.NewWindow("Two State Demo")

twoState0 := xwidget.NewTwoStateToolbarAction(nil,
nil, func(on bool) {
fmt.Println(on)
})
sep := widget.NewToolbarSeparator()
tb := widget.NewToolbar(twoState0, sep)

toggleButton := widget.NewButton("Toggle State", func() {
on := twoState0.GetOn()
twoState0.SetOn(!on)
})
offIconButton := widget.NewButton("Set OffIcon", func() {
twoState0.SetOffIcon(theme.MediaPlayIcon())
})
onIconButton := widget.NewButton("Set OnIcon", func() {
twoState0.SetOnIcon(theme.MediaStopIcon())
})
vc := container.NewVBox(toggleButton, offIconButton, onIconButton)
c := container.NewBorder(tb, vc, nil, nil)
w.SetContent(c)
w.ShowAndRun()
}
Binary file added widget/testdata/twostatetoolbaraction/offstate.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added widget/testdata/twostatetoolbaraction/onstate.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
93 changes: 93 additions & 0 deletions widget/twostatetoolbaraction.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package widget

import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/widget"
)

// TwoStateToolbarAction is a push button style of ToolbarItem that displays a different
// icon depending on its state.
//
// state is a boolean indicating off and on. The actual meaning of the boolean depends on how it is used. For
// example, in a media play app, false might indicate that the medium file is not being played, and true might
// indicate that the file is being played.
// Similarly, the two states could be used to indicate that a panel is being hidden or shown.
type TwoStateToolbarAction struct {
on bool
offIcon fyne.Resource
onIcon fyne.Resource
OnActivated func(bool) `json:"-"`

button widget.Button
}

// NewTwoStateToolbarAction returns a new push button style of Toolbar item that displays
// a different icon for each of its two states
func NewTwoStateToolbarAction(offStateIcon fyne.Resource,
onStateIcon fyne.Resource,
onTapped func(bool)) *TwoStateToolbarAction {
t := &TwoStateToolbarAction{offIcon: offStateIcon, onIcon: onStateIcon, OnActivated: onTapped}
t.button.SetIcon(t.offIcon)
t.button.OnTapped = t.activated
return t
}

// GetOn returns the current state of the toolbaraction
func (t *TwoStateToolbarAction) GetOn() bool {
return t.on
}

// SetOn sets the state of the toolbaraction
func (t *TwoStateToolbarAction) SetOn(on bool) {
t.on = on
if t.OnActivated != nil {
t.OnActivated(t.on)
}
t.setButtonIcon()
t.button.Refresh()
}

// SetOffIcon sets the icon that is displayed when the state is false
func (t *TwoStateToolbarAction) SetOffIcon(icon fyne.Resource) {
t.offIcon = icon
t.setButtonIcon()
t.button.Refresh()
}

// SetOnIcon sets the icon that is displayed when the state is true
func (t *TwoStateToolbarAction) SetOnIcon(icon fyne.Resource) {
t.onIcon = icon
t.setButtonIcon()
t.button.Refresh()
}

// ToolbarObject gets a button to render this ToolbarAction
func (t *TwoStateToolbarAction) ToolbarObject() fyne.CanvasObject {
t.button.Importance = widget.LowImportance

// synchronize properties
t.setButtonIcon()
t.button.OnTapped = t.activated
return &t.button
}

func (t *TwoStateToolbarAction) activated() {
if !t.on {
t.on = true
} else {
t.on = false
}
t.setButtonIcon()
if t.OnActivated != nil {
t.OnActivated(t.on)
}
t.button.Refresh()
}

func (t *TwoStateToolbarAction) setButtonIcon() {
if !t.on {
t.button.Icon = t.offIcon
} else {
t.button.Icon = t.onIcon
}
}
87 changes: 87 additions & 0 deletions widget/twostatetoolbaraction_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package widget

import (
"testing"

"fyne.io/fyne/v2/test"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestNewTwoStateToolbarAction(t *testing.T) {
action := NewTwoStateToolbarAction(theme.MediaPlayIcon(),
theme.MediaPauseIcon(),
func(_ bool) {})
assert.Equal(t, theme.MediaPlayIcon().Name(), action.offIcon.Name())
assert.Equal(t, theme.MediaPauseIcon().Name(), action.onIcon.Name())
assert.Equal(t, action.offIcon.Name(), action.button.Icon.Name())
}

func TestTwoStateToolbarAction_Activated(t *testing.T) {
action := NewTwoStateToolbarAction(theme.MediaPlayIcon(),
theme.MediaPauseIcon(),
func(_ bool) {})
require.Equal(t, action.offIcon.Name(), action.button.Icon.Name())
action.button.Tapped(nil)
assert.Equal(t, action.onIcon.Name(), action.button.Icon.Name())
}

func TestTwoStateToolbarAction_Tapped(t *testing.T) {
test.NewApp()
action := NewTwoStateToolbarAction(theme.MediaPlayIcon(),
theme.MediaPauseIcon(),
func(_ bool) {})
tb := widget.NewToolbar(action)
w := test.NewWindow(tb)
defer w.Close()
test.AssertRendersToImage(t, "twostatetoolbaraction/offstate.png", w.Canvas())
action.button.Tapped(nil)
test.AssertRendersToImage(t, "twostatetoolbaraction/onstate.png", w.Canvas())
}

func TestTwoStateToolbarAction_GetSetState(t *testing.T) {
var ts bool
playState := false
test.NewApp()
action := NewTwoStateToolbarAction(theme.MediaPlayIcon(),
theme.MediaPauseIcon(),
func(on bool) {
ts = on
})
tb := widget.NewToolbar(action)
w := test.NewWindow(tb)
defer w.Close()
assert.Equal(t, playState, action.GetOn())
action.SetOn(true)
assert.Equal(t, true, action.GetOn())
assert.Equal(t, true, ts)
test.AssertRendersToImage(t, "twostatetoolbaraction/onstate.png", w.Canvas())
}

func TestTwoStateToolbarAction_SetOffStateIcon(t *testing.T) {
test.NewApp()
action := NewTwoStateToolbarAction(nil,
theme.MediaPauseIcon(),
func(staone bool) {})
tb := widget.NewToolbar(action)
w := test.NewWindow(tb)
defer w.Close()

action.SetOffIcon(theme.MediaPlayIcon())
assert.Equal(t, theme.MediaPlayIcon().Name(), action.offIcon.Name())
}

func TestTwoStateToolbarAction_SetOnStateIcon(t *testing.T) {
test.NewApp()
action := NewTwoStateToolbarAction(theme.MediaPlayIcon(),
nil,
func(on bool) {})
tb := widget.NewToolbar(action)
w := test.NewWindow(tb)
defer w.Close()

action.SetOnIcon(theme.MediaPauseIcon())
assert.Equal(t, theme.MediaPauseIcon().Name(), action.onIcon.Name())
}