adam-gui/vendor/fyne.io/fyne/v2/widget/menu_item.go

403 lines
9.3 KiB
Go
Raw Normal View History

2024-04-29 19:13:50 +02:00
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
}