adam-gui/vendor/fyne.io/fyne/v2/widget/menu_item.go
2024-04-29 19:13:50 +02:00

403 lines
9.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package widget
import (
"image/color"
"strings"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/driver/desktop"
"fyne.io/fyne/v2/internal/widget"
"fyne.io/fyne/v2/theme"
)
const (
runeModifierAlt = '⌥'
runeModifierControl = '⌃'
runeModifierShift = '⇧'
)
var keySymbols = map[fyne.KeyName]rune{
fyne.KeyBackspace: '⌫',
fyne.KeyDelete: '⌦',
fyne.KeyDown: '↓',
fyne.KeyEnd: '↘',
fyne.KeyEnter: '↩',
fyne.KeyEscape: '⎋',
fyne.KeyHome: '↖',
fyne.KeyLeft: '←',
fyne.KeyPageDown: '⇟',
fyne.KeyPageUp: '⇞',
fyne.KeyReturn: '↩',
fyne.KeyRight: '→',
fyne.KeySpace: '␣',
fyne.KeyTab: '⇥',
fyne.KeyUp: '↑',
}
var _ fyne.Widget = (*menuItem)(nil)
// menuItem is a widget for displaying a fyne.menuItem.
type menuItem struct {
widget.Base
Item *fyne.MenuItem
Parent *Menu
alignment fyne.TextAlign
child *Menu
}
// newMenuItem creates a new menuItem.
func newMenuItem(item *fyne.MenuItem, parent *Menu) *menuItem {
i := &menuItem{Item: item, Parent: parent}
i.alignment = parent.alignment
i.ExtendBaseWidget(i)
return i
}
func (i *menuItem) Child() *Menu {
if i.Item.ChildMenu != nil && i.child == nil {
child := NewMenu(i.Item.ChildMenu)
child.Hide()
child.OnDismiss = i.Parent.Dismiss
i.child = child
}
return i.child
}
// CreateRenderer returns a new renderer for the menu item.
//
// Implements: fyne.Widget
func (i *menuItem) CreateRenderer() fyne.WidgetRenderer {
background := canvas.NewRectangle(theme.HoverColor())
background.CornerRadius = theme.SelectionRadiusSize()
background.Hide()
text := canvas.NewText(i.Item.Label, theme.ForegroundColor())
text.Alignment = i.alignment
objects := []fyne.CanvasObject{background, text}
var expandIcon *canvas.Image
if i.Item.ChildMenu != nil {
expandIcon = canvas.NewImageFromResource(theme.MenuExpandIcon())
objects = append(objects, expandIcon)
}
checkIcon := canvas.NewImageFromResource(theme.ConfirmIcon())
if !i.Item.Checked {
checkIcon.Hide()
}
var icon *canvas.Image
if i.Item.Icon != nil {
icon = canvas.NewImageFromResource(i.Item.Icon)
objects = append(objects, icon)
}
var shortcutTexts []*canvas.Text
if s, ok := i.Item.Shortcut.(fyne.KeyboardShortcut); ok {
shortcutTexts = textsForShortcut(s)
for _, t := range shortcutTexts {
objects = append(objects, t)
}
}
objects = append(objects, checkIcon)
r := &menuItemRenderer{
BaseRenderer: widget.NewBaseRenderer(objects),
i: i,
expandIcon: expandIcon,
checkIcon: checkIcon,
icon: icon,
shortcutTexts: shortcutTexts,
text: text,
background: background,
}
r.updateVisuals()
return r
}
// MouseIn activates the item which shows the submenu if the item has one.
// The submenu of any sibling of the item will be hidden.
//
// Implements: desktop.Hoverable
func (i *menuItem) MouseIn(*desktop.MouseEvent) {
i.activate()
}
// MouseMoved does nothing.
//
// Implements: desktop.Hoverable
func (i *menuItem) MouseMoved(*desktop.MouseEvent) {
}
// MouseOut deactivates the item unless it has an open submenu.
//
// Implements: desktop.Hoverable
func (i *menuItem) MouseOut() {
if !i.isSubmenuOpen() {
i.deactivate()
}
}
// Tapped performs the action of the item and dismisses the menu.
// It does nothing if the item doesnt have an action.
//
// Implements: fyne.Tappable
func (i *menuItem) Tapped(*fyne.PointEvent) {
if i.Item.Disabled {
return
}
if i.Item.Action == nil {
if fyne.CurrentDevice().IsMobile() {
i.activate()
}
return
}
i.trigger()
}
func (i *menuItem) activate() {
if i.Item.Disabled {
return
}
if i.Child() != nil {
i.Child().Show()
}
i.Parent.activateItem(i)
}
func (i *menuItem) activateLastSubmenu() bool {
if i.Child() == nil {
return false
}
if i.isSubmenuOpen() {
return i.Child().ActivateLastSubmenu()
}
i.Child().Show()
i.Child().ActivateNext()
return true
}
func (i *menuItem) deactivate() {
if i.Child() != nil {
i.Child().Hide()
}
i.Parent.DeactivateChild()
}
func (i *menuItem) deactivateLastSubmenu() bool {
if !i.isSubmenuOpen() {
return false
}
if !i.Child().DeactivateLastSubmenu() {
i.Child().DeactivateChild()
i.Child().Hide()
}
return true
}
func (i *menuItem) isActive() bool {
return i.Parent.activeItem == i
}
func (i *menuItem) isSubmenuOpen() bool {
return i.Child() != nil && i.Child().Visible()
}
func (i *menuItem) trigger() {
i.Parent.Dismiss()
if i.Item.Action != nil {
i.Item.Action()
}
}
func (i *menuItem) triggerLast() {
if i.isSubmenuOpen() {
i.Child().TriggerLast()
return
}
i.trigger()
}
type menuItemRenderer struct {
widget.BaseRenderer
i *menuItem
background *canvas.Rectangle
checkIcon *canvas.Image
expandIcon *canvas.Image
icon *canvas.Image
lastThemePadding float32
minSize fyne.Size
shortcutTexts []*canvas.Text
text *canvas.Text
}
func (r *menuItemRenderer) Layout(size fyne.Size) {
leftOffset := theme.InnerPadding() + r.checkSpace()
rightOffset := size.Width
iconSize := fyne.NewSize(theme.IconInlineSize(), theme.IconInlineSize())
iconTopOffset := (size.Height - theme.IconInlineSize()) / 2
if r.expandIcon != nil {
rightOffset -= theme.IconInlineSize()
r.expandIcon.Resize(iconSize)
r.expandIcon.Move(fyne.NewPos(rightOffset, iconTopOffset))
}
rightOffset -= theme.InnerPadding()
textHeight := r.text.MinSize().Height
for i := len(r.shortcutTexts) - 1; i >= 0; i-- {
text := r.shortcutTexts[i]
text.Resize(text.MinSize())
rightOffset -= text.MinSize().Width
text.Move(fyne.NewPos(rightOffset, theme.InnerPadding()+(textHeight-text.Size().Height)))
if i == 0 {
rightOffset -= theme.InnerPadding()
}
}
r.checkIcon.Resize(iconSize)
r.checkIcon.Move(fyne.NewPos(theme.InnerPadding(), iconTopOffset))
if r.icon != nil {
r.icon.Resize(iconSize)
r.icon.Move(fyne.NewPos(leftOffset, iconTopOffset))
leftOffset += theme.IconInlineSize()
leftOffset += theme.InnerPadding()
}
r.text.Resize(fyne.NewSize(rightOffset-leftOffset, textHeight))
r.text.Move(fyne.NewPos(leftOffset, theme.InnerPadding()))
r.background.Resize(size)
}
func (r *menuItemRenderer) MinSize() fyne.Size {
if r.minSizeUnchanged() {
return r.minSize
}
minSize := r.text.MinSize().AddWidthHeight(theme.InnerPadding()*2+r.checkSpace(), theme.InnerPadding()*2)
if r.expandIcon != nil {
minSize = minSize.AddWidthHeight(theme.IconInlineSize(), 0)
}
if r.icon != nil {
minSize = minSize.AddWidthHeight(theme.IconInlineSize()+theme.InnerPadding(), 0)
}
if r.shortcutTexts != nil {
var textWidth float32
for _, text := range r.shortcutTexts {
textWidth += text.MinSize().Width
}
minSize = minSize.AddWidthHeight(textWidth+theme.InnerPadding(), 0)
}
r.minSize = minSize
return r.minSize
}
func (r *menuItemRenderer) updateVisuals() {
r.background.CornerRadius = theme.SelectionRadiusSize()
if fyne.CurrentDevice().IsMobile() {
r.background.Hide()
} else if r.i.isActive() {
r.background.FillColor = theme.FocusColor()
r.background.Show()
} else {
r.background.Hide()
}
r.background.Refresh()
r.text.Alignment = r.i.alignment
r.refreshText(r.text, false)
for _, text := range r.shortcutTexts {
r.refreshText(text, true)
}
if r.i.Item.Checked {
r.checkIcon.Show()
} else {
r.checkIcon.Hide()
}
r.updateIcon(r.checkIcon, theme.ConfirmIcon())
r.updateIcon(r.expandIcon, theme.MenuExpandIcon())
r.updateIcon(r.icon, r.i.Item.Icon)
}
func (r *menuItemRenderer) Refresh() {
r.updateVisuals()
canvas.Refresh(r.i)
}
func (r *menuItemRenderer) checkSpace() float32 {
if r.i.Parent.containsCheck {
return theme.IconInlineSize() + theme.InnerPadding()
}
return 0
}
func (r *menuItemRenderer) minSizeUnchanged() bool {
return !r.minSize.IsZero() &&
r.text.TextSize == theme.TextSize() &&
(r.expandIcon == nil || r.expandIcon.Size().Width == theme.IconInlineSize()) &&
r.lastThemePadding == theme.InnerPadding()
}
func (r *menuItemRenderer) updateIcon(img *canvas.Image, rsc fyne.Resource) {
if img == nil {
return
}
if r.i.Item.Disabled {
img.Resource = theme.NewDisabledResource(rsc)
} else {
img.Resource = rsc
}
}
func (r *menuItemRenderer) refreshText(text *canvas.Text, shortcut bool) {
text.TextSize = theme.TextSize()
if r.i.Item.Disabled {
text.Color = theme.DisabledColor()
} else {
if shortcut {
text.Color = shortcutColor()
} else {
text.Color = theme.ForegroundColor()
}
}
text.Refresh()
}
func shortcutColor() color.Color {
r, g, b, a := theme.ForegroundColor().RGBA()
a = uint32(float32(a) * 0.95)
return color.NRGBA{R: uint8(r), G: uint8(g), B: uint8(b), A: uint8(a)}
}
func textsForShortcut(s fyne.KeyboardShortcut) (texts []*canvas.Text) {
b := strings.Builder{}
mods := s.Mod()
if mods&fyne.KeyModifierControl != 0 {
b.WriteRune(runeModifierControl)
}
if mods&fyne.KeyModifierAlt != 0 {
b.WriteRune(runeModifierAlt)
}
if mods&fyne.KeyModifierShift != 0 {
b.WriteRune(runeModifierShift)
}
if mods&fyne.KeyModifierSuper != 0 {
b.WriteRune(runeModifierSuper)
}
r := keySymbols[s.Key()]
if r != 0 {
b.WriteRune(r)
}
shortColor := shortcutColor()
t := canvas.NewText(b.String(), shortColor)
t.TextStyle.Symbol = true
texts = append(texts, t)
if r == 0 {
text := canvas.NewText(string(s.Key()), shortColor)
text.TextStyle.Monospace = true
texts = append(texts, text)
}
return
}