| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338 |
- package widget
- import (
- "fmt"
- "image/color"
- "fyne.io/fyne/v2"
- "fyne.io/fyne/v2/canvas"
- "fyne.io/fyne/v2/data/binding"
- "fyne.io/fyne/v2/driver/desktop"
- "fyne.io/fyne/v2/internal/widget"
- "fyne.io/fyne/v2/theme"
- )
- type checkRenderer struct {
- widget.BaseRenderer
- bg, icon *canvas.Image
- label *canvas.Text
- focusIndicator *canvas.Circle
- check *Check
- }
- // MinSize calculates the minimum size of a check.
- // This is based on the contained text, the check icon and a standard amount of padding added.
- func (c *checkRenderer) MinSize() fyne.Size {
- pad4 := theme.InnerPadding() * 2
- min := c.label.MinSize().Add(fyne.NewSize(theme.IconInlineSize()+pad4, pad4))
- if c.check.Text != "" {
- min.Add(fyne.NewSize(theme.Padding(), 0))
- }
- return min
- }
- // Layout the components of the check widget
- func (c *checkRenderer) Layout(size fyne.Size) {
- focusIndicatorSize := fyne.NewSquareSize(theme.IconInlineSize() + theme.InnerPadding())
- c.focusIndicator.Resize(focusIndicatorSize)
- c.focusIndicator.Move(fyne.NewPos(theme.InputBorderSize(), (size.Height-focusIndicatorSize.Height)/2))
- xOff := focusIndicatorSize.Width + theme.InputBorderSize()*2
- labelSize := size.SubtractWidthHeight(xOff, 0)
- c.label.Resize(labelSize)
- c.label.Move(fyne.NewPos(xOff, 0))
- iconPos := fyne.NewPos(theme.InnerPadding()/2+theme.InputBorderSize(), (size.Height-theme.IconInlineSize())/2)
- iconSize := fyne.NewSquareSize(theme.IconInlineSize())
- c.bg.Move(iconPos)
- c.bg.Resize(iconSize)
- c.icon.Resize(iconSize)
- c.icon.Move(iconPos)
- }
- // applyTheme updates this Check to the current theme
- func (c *checkRenderer) applyTheme() {
- c.label.Color = theme.ForegroundColor()
- c.label.TextSize = theme.TextSize()
- if c.check.disabled {
- c.label.Color = theme.DisabledColor()
- }
- }
- func (c *checkRenderer) Refresh() {
- c.check.propertyLock.RLock()
- c.applyTheme()
- c.updateLabel()
- c.updateResource()
- c.updateFocusIndicator()
- c.check.propertyLock.RUnlock()
- canvas.Refresh(c.check.super())
- }
- func (c *checkRenderer) updateLabel() {
- c.label.Text = c.check.Text
- }
- func (c *checkRenderer) updateResource() {
- res := theme.NewThemedResource(theme.CheckButtonIcon())
- res.ColorName = theme.ColorNameInputBorder
- // TODO move to `theme.CheckButtonFillIcon()` when we add it in 2.4
- bgRes := theme.NewThemedResource(fyne.CurrentApp().Settings().Theme().Icon("iconNameCheckButtonFill"))
- bgRes.ColorName = theme.ColorNameInputBackground
- if c.check.Checked {
- res = theme.NewThemedResource(theme.CheckButtonCheckedIcon())
- res.ColorName = theme.ColorNamePrimary
- bgRes.ColorName = theme.ColorNameBackground
- }
- if c.check.disabled {
- if c.check.Checked {
- res = theme.NewThemedResource(theme.CheckButtonCheckedIcon())
- }
- res.ColorName = theme.ColorNameDisabled
- bgRes.ColorName = theme.ColorNameBackground
- }
- c.icon.Resource = res
- c.bg.Resource = bgRes
- }
- func (c *checkRenderer) updateFocusIndicator() {
- if c.check.disabled {
- c.focusIndicator.FillColor = color.Transparent
- } else if c.check.focused {
- c.focusIndicator.FillColor = theme.FocusColor()
- } else if c.check.hovered {
- c.focusIndicator.FillColor = theme.HoverColor()
- } else {
- c.focusIndicator.FillColor = color.Transparent
- }
- }
- // Check widget has a text label and a checked (or unchecked) icon and triggers an event func when toggled
- type Check struct {
- DisableableWidget
- Text string
- Checked bool
- OnChanged func(bool) `json:"-"`
- focused bool
- hovered bool
- binder basicBinder
- }
- // Bind connects the specified data source to this Check.
- // The current value will be displayed and any changes in the data will cause the widget to update.
- // User interactions with this Check will set the value into the data source.
- //
- // Since: 2.0
- func (c *Check) Bind(data binding.Bool) {
- c.binder.SetCallback(c.updateFromData)
- c.binder.Bind(data)
- c.OnChanged = func(_ bool) {
- c.binder.CallWithData(c.writeData)
- }
- }
- // SetChecked sets the the checked state and refreshes widget
- func (c *Check) SetChecked(checked bool) {
- if checked == c.Checked {
- return
- }
- c.Checked = checked
- if c.OnChanged != nil {
- c.OnChanged(c.Checked)
- }
- c.Refresh()
- }
- // Hide this widget, if it was previously visible
- func (c *Check) Hide() {
- if c.focused {
- c.FocusLost()
- impl := c.super()
- if c := fyne.CurrentApp().Driver().CanvasForObject(impl); c != nil {
- c.Focus(nil)
- }
- }
- c.BaseWidget.Hide()
- }
- // MouseIn is called when a desktop pointer enters the widget
- func (c *Check) MouseIn(*desktop.MouseEvent) {
- if c.Disabled() {
- return
- }
- c.hovered = true
- c.Refresh()
- }
- // MouseOut is called when a desktop pointer exits the widget
- func (c *Check) MouseOut() {
- c.hovered = false
- c.Refresh()
- }
- // MouseMoved is called when a desktop pointer hovers over the widget
- func (c *Check) MouseMoved(*desktop.MouseEvent) {
- }
- // Tapped is called when a pointer tapped event is captured and triggers any change handler
- func (c *Check) Tapped(*fyne.PointEvent) {
- if !c.focused && !fyne.CurrentDevice().IsMobile() {
- impl := c.super()
- if c := fyne.CurrentApp().Driver().CanvasForObject(impl); c != nil {
- c.Focus(impl.(fyne.Focusable))
- }
- }
- if !c.Disabled() {
- c.SetChecked(!c.Checked)
- }
- }
- // MinSize returns the size that this widget should not shrink below
- func (c *Check) MinSize() fyne.Size {
- c.ExtendBaseWidget(c)
- return c.BaseWidget.MinSize()
- }
- // CreateRenderer is a private method to Fyne which links this widget to its renderer
- func (c *Check) CreateRenderer() fyne.WidgetRenderer {
- c.ExtendBaseWidget(c)
- c.propertyLock.RLock()
- defer c.propertyLock.RUnlock()
- // TODO move to `theme.CheckButtonFillIcon()` when we add it in 2.4
- bg := canvas.NewImageFromResource(fyne.CurrentApp().Settings().Theme().Icon("iconNameCheckButtonFill"))
- icon := canvas.NewImageFromResource(theme.CheckButtonIcon())
- text := canvas.NewText(c.Text, theme.ForegroundColor())
- text.Alignment = fyne.TextAlignLeading
- focusIndicator := canvas.NewCircle(theme.BackgroundColor())
- r := &checkRenderer{
- widget.NewBaseRenderer([]fyne.CanvasObject{focusIndicator, bg, icon, text}),
- bg,
- icon,
- text,
- focusIndicator,
- c,
- }
- r.applyTheme()
- r.updateLabel()
- r.updateResource()
- r.updateFocusIndicator()
- return r
- }
- // NewCheck creates a new check widget with the set label and change handler
- func NewCheck(label string, changed func(bool)) *Check {
- c := &Check{
- Text: label,
- OnChanged: changed,
- }
- c.ExtendBaseWidget(c)
- return c
- }
- // NewCheckWithData returns a check widget connected with the specified data source.
- //
- // Since: 2.0
- func NewCheckWithData(label string, data binding.Bool) *Check {
- check := NewCheck(label, nil)
- check.Bind(data)
- return check
- }
- // FocusGained is called when the Check has been given focus.
- func (c *Check) FocusGained() {
- if c.Disabled() {
- return
- }
- c.focused = true
- c.Refresh()
- }
- // FocusLost is called when the Check has had focus removed.
- func (c *Check) FocusLost() {
- c.focused = false
- c.Refresh()
- }
- // TypedRune receives text input events when the Check is focused.
- func (c *Check) TypedRune(r rune) {
- if c.Disabled() {
- return
- }
- if r == ' ' {
- c.SetChecked(!c.Checked)
- }
- }
- // TypedKey receives key input events when the Check is focused.
- func (c *Check) TypedKey(key *fyne.KeyEvent) {}
- // SetText sets the text of the Check
- //
- // Since: 2.4
- func (c *Check) SetText(text string) {
- c.Text = text
- c.Refresh()
- }
- // Unbind disconnects any configured data source from this Check.
- // The current value will remain at the last value of the data source.
- //
- // Since: 2.0
- func (c *Check) Unbind() {
- c.OnChanged = nil
- c.binder.Unbind()
- }
- func (c *Check) updateFromData(data binding.DataItem) {
- if data == nil {
- return
- }
- boolSource, ok := data.(binding.Bool)
- if !ok {
- return
- }
- val, err := boolSource.Get()
- if err != nil {
- fyne.LogError("Error getting current data value", err)
- return
- }
- c.SetChecked(val) // if val != c.Checked, this will call updateFromData again, but only once
- }
- func (c *Check) writeData(data binding.DataItem) {
- if data == nil {
- return
- }
- boolTarget, ok := data.(binding.Bool)
- if !ok {
- return
- }
- currentValue, err := boolTarget.Get()
- if err != nil {
- return
- }
- if currentValue != c.Checked {
- err := boolTarget.Set(c.Checked)
- if err != nil {
- fyne.LogError(fmt.Sprintf("Failed to set binding value to %t", c.Checked), err)
- }
- }
- }
|