fckeuspy-go/vendor/fyne.io/fyne/v2/widget/check.go

400 lines
10 KiB
Go

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"
)
// 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
// Partial check is when there is an indeterminate state (usually meaning that child items are some-what checked).
// Turning this on will override the checked state and show a dash icon (neither checked nor unchecked).
// The user interaction cannot turn this on, tapping a partial check state will set `Checked` to true.
//
// Since: 2.6
Partial bool
OnChanged func(bool) `json:"-"`
focused bool
hovered bool
binder basicBinder
minSize fyne.Size // cached for hover/tap position calculations
}
// 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
}
// 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 checked state and refreshes widget
// If the `Partial` state is set this will be turned off to respect the `checked` bool passed in here.
func (c *Check) SetChecked(checked bool) {
if checked == c.Checked && !c.Partial {
return
}
c.Partial = false
c.Checked = checked
onChanged := c.OnChanged
if onChanged != nil {
onChanged(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(me *desktop.MouseEvent) {
c.MouseMoved(me)
}
// MouseOut is called when a desktop pointer exits the widget
func (c *Check) MouseOut() {
if c.hovered {
c.hovered = false
c.Refresh()
}
}
// MouseMoved is called when a desktop pointer hovers over the widget
func (c *Check) MouseMoved(me *desktop.MouseEvent) {
if c.Disabled() {
return
}
oldHovered := c.hovered
// only hovered if cached minSize has not been initialized (test code)
// or the pointer is within the "active" area of the widget (its minSize)
c.hovered = c.minSize.IsZero() ||
(me.Position.X <= c.minSize.Width && me.Position.Y <= c.minSize.Height)
if oldHovered != c.hovered {
c.Refresh()
}
}
// Tapped is called when a pointer tapped event is captured and triggers any change handler
func (c *Check) Tapped(pe *fyne.PointEvent) {
if c.Disabled() {
return
}
minHeight := c.minSize.Height
minY := (c.Size().Height - minHeight) / 2
if !c.minSize.IsZero() &&
(pe.Position.X > c.minSize.Width || pe.Position.Y < minY || pe.Position.Y > minY+minHeight) {
// tapped outside the active area of the widget
return
}
if !c.focused {
focusIfNotMobile(c.super())
}
c.SetChecked(!c.Checked)
}
// MinSize returns the size that this widget should not shrink below
func (c *Check) MinSize() fyne.Size {
c.ExtendBaseWidget(c)
c.minSize = c.BaseWidget.MinSize()
return c.minSize
}
// CreateRenderer is a private method to Fyne which links this widget to its renderer
func (c *Check) CreateRenderer() fyne.WidgetRenderer {
th := c.Theme()
v := fyne.CurrentApp().Settings().ThemeVariant()
c.ExtendBaseWidget(c)
bg := canvas.NewImageFromResource(th.Icon(theme.IconNameCheckButtonFill))
icon := canvas.NewImageFromResource(th.Icon(theme.IconNameCheckButton))
text := canvas.NewText(c.Text, th.Color(theme.ColorNameForeground, v))
text.Alignment = fyne.TextAlignLeading
focusIndicator := canvas.NewCircle(th.Color(theme.ColorNameBackground, v))
r := &checkRenderer{
widget.NewBaseRenderer([]fyne.CanvasObject{focusIndicator, bg, icon, text}),
bg,
icon,
text,
focusIndicator,
c,
}
r.applyTheme(th, v)
r.updateLabel()
r.updateResource(th)
r.updateFocusIndicator(th, v)
return r
}
// 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)
}
}
}
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 {
th := c.check.Theme()
pad4 := th.Size(theme.SizeNameInnerPadding) * 2
min := c.label.MinSize().Add(fyne.NewSize(th.Size(theme.SizeNameInlineIcon)+pad4, pad4))
if c.check.Text != "" {
min.Add(fyne.NewSize(th.Size(theme.SizeNamePadding), 0))
}
return min
}
// Layout the components of the check widget
func (c *checkRenderer) Layout(size fyne.Size) {
th := c.check.Theme()
innerPadding := th.Size(theme.SizeNameInnerPadding)
borderSize := th.Size(theme.SizeNameInputBorder)
iconInlineSize := th.Size(theme.SizeNameInlineIcon)
focusIndicatorSize := fyne.NewSquareSize(iconInlineSize + innerPadding)
c.focusIndicator.Resize(focusIndicatorSize)
c.focusIndicator.Move(fyne.NewPos(borderSize, (size.Height-focusIndicatorSize.Height)/2))
xOff := focusIndicatorSize.Width + borderSize*2
labelSize := size.SubtractWidthHeight(xOff, 0)
c.label.Resize(labelSize)
c.label.Move(fyne.NewPos(xOff, 0))
iconPos := fyne.NewPos(innerPadding/2+borderSize, (size.Height-iconInlineSize)/2)
iconSize := fyne.NewSquareSize(iconInlineSize)
c.bg.Move(iconPos)
c.bg.Resize(iconSize)
c.icon.Move(iconPos)
c.icon.Resize(iconSize)
}
// applyTheme updates this Check to the current theme
func (c *checkRenderer) applyTheme(th fyne.Theme, v fyne.ThemeVariant) {
c.label.Color = th.Color(theme.ColorNameForeground, v)
c.label.TextSize = th.Size(theme.SizeNameText)
if c.check.Disabled() {
c.label.Color = th.Color(theme.ColorNameDisabled, v)
}
}
func (c *checkRenderer) Refresh() {
th := c.check.Theme()
v := fyne.CurrentApp().Settings().ThemeVariant()
c.applyTheme(th, v)
c.updateLabel()
c.updateResource(th)
c.updateFocusIndicator(th, v)
canvas.Refresh(c.check.super())
}
// must be called while holding c.check.propertyLock for reading
func (c *checkRenderer) updateLabel() {
c.label.Text = c.check.Text
}
// must be called while holding c.check.propertyLock for reading
func (c *checkRenderer) updateResource(th fyne.Theme) {
res := theme.NewThemedResource(th.Icon(theme.IconNameCheckButton))
res.ColorName = theme.ColorNameInputBorder
bgRes := theme.NewThemedResource(th.Icon(theme.IconNameCheckButtonFill))
bgRes.ColorName = theme.ColorNameInputBackground
if c.check.Partial {
res = theme.NewThemedResource(th.Icon(theme.IconNameCheckButtonPartial))
res.ColorName = theme.ColorNamePrimary
bgRes.ColorName = theme.ColorNameBackground
} else if c.check.Checked {
res = theme.NewThemedResource(th.Icon(theme.IconNameCheckButtonChecked))
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.icon.Refresh()
c.bg.Resource = bgRes
c.bg.Refresh()
}
// must be called while holding c.check.propertyLock for reading
func (c *checkRenderer) updateFocusIndicator(th fyne.Theme, v fyne.ThemeVariant) {
if c.check.Disabled() {
c.focusIndicator.FillColor = color.Transparent
} else if c.check.focused {
c.focusIndicator.FillColor = th.Color(theme.ColorNameFocus, v)
} else if c.check.hovered {
c.focusIndicator.FillColor = th.Color(theme.ColorNameHover, v)
} else {
c.focusIndicator.FillColor = color.Transparent
}
}
func focusIfNotMobile(w fyne.Widget) {
if w == nil {
return
}
if !fyne.CurrentDevice().IsMobile() {
if c := fyne.CurrentApp().Driver().CanvasForObject(w); c != nil {
c.Focus(w.(fyne.Focusable))
}
}
}