diff --git a/pkg/appui/item_container.go b/pkg/appui/item_container.go index fd655b6..04cf7a2 100644 --- a/pkg/appui/item_container.go +++ b/pkg/appui/item_container.go @@ -1,7 +1,12 @@ package appui import ( + "image/color" + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" ) @@ -13,19 +18,31 @@ var _ fyne.DoubleTappable = (*ItemContainer)(nil) // ItemContainer 是单个列表项,只负责显示文字和点击回调 type ItemContainer struct { widget.BaseWidget - label *widget.Label - index int - onTapped func(index int) - onRightClicked func(index int, pos fyne.Position) - onDoubleTapped func(index int) + label *widget.Label + background *canvas.Rectangle + containerObj fyne.CanvasObject + index int + selected bool + selectionColor color.Color + transparentColor color.Color + onTapped func(index int) + onRightClicked func(index int, pos fyne.Position) + onDoubleTapped func(index int) } // NewItemContainer 创建新ItemContainer func NewItemContainer(onTapped func(int), onRightClicked func(int, fyne.Position)) *ItemContainer { + label := widget.NewLabel("") + background := canvas.NewRectangle(color.Transparent) + ic := &ItemContainer{ - label: widget.NewLabel(""), - onTapped: onTapped, - onRightClicked: onRightClicked, + label: label, + background: background, + containerObj: container.NewBorder(nil, nil, nil, nil, label), + selectionColor: theme.Color(theme.ColorNameSelection), + transparentColor: color.Transparent, + onTapped: onTapped, + onRightClicked: onRightClicked, } ic.ExtendBaseWidget(ic) return ic @@ -38,7 +55,11 @@ func (ic *ItemContainer) SetOnDoubleTapped(callback func(int)) { // CreateRenderer 实现 fyne.Widget 接口 func (ic *ItemContainer) CreateRenderer() fyne.WidgetRenderer { - return widget.NewSimpleRenderer(ic.label) + return &itemContainerRenderer{ + container: ic, + background: ic.background, + content: ic.containerObj, + } } // SetText 更新显示文本 @@ -51,6 +72,30 @@ func (ic *ItemContainer) SetIndex(i int) { ic.index = i } +// SetSelected 设置选中状态 +func (ic *ItemContainer) SetSelected(selected bool) { + if ic.selected == selected { + return // 状态没有变化,直接返回 + } + + ic.selected = selected + + // 使用缓存的颜色,避免重复查询主题系统 + if selected { + ic.background.FillColor = ic.selectionColor + } else { + ic.background.FillColor = ic.transparentColor + } + + // 只刷新背景,避免双重刷新 + ic.background.Refresh() +} + +// IsSelected 获取选中状态 +func (ic *ItemContainer) IsSelected() bool { + return ic.selected +} + // Tapped 左键点击 func (ic *ItemContainer) Tapped(pe *fyne.PointEvent) { if ic.onTapped != nil { @@ -71,3 +116,32 @@ func (ic *ItemContainer) DoubleTapped(pe *fyne.PointEvent) { ic.onDoubleTapped(ic.index) } } + + +// itemContainerRenderer 自定义渲染器 +type itemContainerRenderer struct { + container *ItemContainer + background *canvas.Rectangle + content fyne.CanvasObject +} + +func (r *itemContainerRenderer) Layout(size fyne.Size) { + r.background.Resize(size) + r.content.Resize(size) +} + +func (r *itemContainerRenderer) MinSize() fyne.Size { + return r.content.MinSize() +} + +func (r *itemContainerRenderer) Refresh() { + r.background.Refresh() + r.content.Refresh() +} + +func (r *itemContainerRenderer) Objects() []fyne.CanvasObject { + return []fyne.CanvasObject{r.background, r.content} +} + +func (r *itemContainerRenderer) Destroy() {} + diff --git a/pkg/appui/right_clickable_list.go b/pkg/appui/right_clickable_list.go index 46b0932..27d6d0e 100644 --- a/pkg/appui/right_clickable_list.go +++ b/pkg/appui/right_clickable_list.go @@ -12,6 +12,7 @@ type RightClickableList struct { widget.BaseWidget list *widget.List items []string + selectedIndex int OnItemTapped func(index int) OnItemRightClick func(index int, pos fyne.Position) OnItemDoubleTapped func(index int) @@ -19,7 +20,9 @@ type RightClickableList struct { // NewRightClickableList 创建新RightClickableList func NewRightClickableList() *RightClickableList { - rcl := &RightClickableList{} + rcl := &RightClickableList{ + selectedIndex: -1, + } rcl.ExtendBaseWidget(rcl) return rcl } @@ -59,8 +62,18 @@ func (rcl *RightClickableList) Build() { }, func(i int, o fyne.CanvasObject) { itemContainer := o.(*ItemContainer) - itemContainer.SetText(rcl.items[i]) - itemContainer.SetIndex(i) + // 只在必要时更新文本和索引 + if itemContainer.label.Text != rcl.items[i] { + itemContainer.SetText(rcl.items[i]) + } + if itemContainer.index != i { + itemContainer.SetIndex(i) + } + // 只在选中状态真正变化时才调用SetSelected + isSelected := i == rcl.selectedIndex + if itemContainer.IsSelected() != isSelected { + itemContainer.SetSelected(isSelected) + } }, ) } @@ -77,10 +90,31 @@ func (rcl *RightClickableList) Refresh() { } } +// SetSelectedIndex 设置选中的索引 +func (rcl *RightClickableList) SetSelectedIndex(index int) { + if rcl.selectedIndex == index { + return // 状态没有变化,直接返回 + } + + rcl.selectedIndex = index + + // 立即刷新列表以更新选中状态 + if rcl.list != nil { + rcl.list.Refresh() + } +} + +// GetSelectedIndex 获取选中的索引 +func (rcl *RightClickableList) GetSelectedIndex() int { + return rcl.selectedIndex +} + // UnselectAll 取消选中 func (rcl *RightClickableList) UnselectAll() { + rcl.selectedIndex = -1 if rcl.list != nil { rcl.list.UnselectAll() + rcl.list.Refresh() } } diff --git a/pkg/appui/ui.go b/pkg/appui/ui.go index 89c7fa2..2857cd7 100644 --- a/pkg/appui/ui.go +++ b/pkg/appui/ui.go @@ -112,6 +112,8 @@ func (ui *AppUI) setupUI() { ui.refreshItems() ui.rightClickableList = NewRightClickableList() ui.rightClickableList.OnItemTapped = func(i int) { + // 立即更新选中状态,避免延迟 + ui.rightClickableList.SetSelectedIndex(i) ui.selectedIndex = i ui.selectedName = ui.items[i] ui.logger.Debug("left click", slog.String("item", ui.selectedName))