From 3865e91d894d9a380c68ee8560d330c02a7d259b Mon Sep 17 00:00:00 2001 From: mingregister <774262573@qq.com> Date: Mon, 29 Sep 2025 17:38:00 +0800 Subject: [PATCH 1/4] feat: double click enter dir Signed-off-by: mingregister <774262573@qq.com> --- .gitignore | 1 + pkg/appui/item_container.go | 14 ++++++++++++++ pkg/appui/right_clickable_list.go | 18 +++++++++++++----- pkg/appui/ui.go | 27 ++++++++++++++++++--------- pkg/dir/dir.go | 29 +++++++++++++++++++++++++++++ 5 files changed, 75 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index a7c6c32..ff37e10 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ app.exe *.exe tmp +fyne-cross *.log diff --git a/pkg/appui/item_container.go b/pkg/appui/item_container.go index 151cd6d..fd655b6 100644 --- a/pkg/appui/item_container.go +++ b/pkg/appui/item_container.go @@ -8,6 +8,7 @@ import ( var _ fyne.Widget = (*ItemContainer)(nil) var _ fyne.Tappable = (*ItemContainer)(nil) var _ fyne.SecondaryTappable = (*ItemContainer)(nil) +var _ fyne.DoubleTappable = (*ItemContainer)(nil) // ItemContainer 是单个列表项,只负责显示文字和点击回调 type ItemContainer struct { @@ -16,6 +17,7 @@ type ItemContainer struct { index int onTapped func(index int) onRightClicked func(index int, pos fyne.Position) + onDoubleTapped func(index int) } // NewItemContainer 创建新ItemContainer @@ -29,6 +31,11 @@ func NewItemContainer(onTapped func(int), onRightClicked func(int, fyne.Position return ic } +// SetOnDoubleTapped 设置双击回调 +func (ic *ItemContainer) SetOnDoubleTapped(callback func(int)) { + ic.onDoubleTapped = callback +} + // CreateRenderer 实现 fyne.Widget 接口 func (ic *ItemContainer) CreateRenderer() fyne.WidgetRenderer { return widget.NewSimpleRenderer(ic.label) @@ -57,3 +64,10 @@ func (ic *ItemContainer) TappedSecondary(pe *fyne.PointEvent) { ic.onRightClicked(ic.index, pe.AbsolutePosition) } } + +// DoubleTapped 双击 +func (ic *ItemContainer) DoubleTapped(pe *fyne.PointEvent) { + if ic.onDoubleTapped != nil { + ic.onDoubleTapped(ic.index) + } +} diff --git a/pkg/appui/right_clickable_list.go b/pkg/appui/right_clickable_list.go index bcb9437..46b0932 100644 --- a/pkg/appui/right_clickable_list.go +++ b/pkg/appui/right_clickable_list.go @@ -10,10 +10,11 @@ var _ fyne.Widget = (*RightClickableList)(nil) // RightClickableList 是可右键点击的列表控件 type RightClickableList struct { widget.BaseWidget - list *widget.List - items []string - OnItemTapped func(index int) - OnItemRightClick func(index int, pos fyne.Position) + list *widget.List + items []string + OnItemTapped func(index int) + OnItemRightClick func(index int, pos fyne.Position) + OnItemDoubleTapped func(index int) } // NewRightClickableList 创建新RightClickableList @@ -36,7 +37,7 @@ func (rcl *RightClickableList) Build() { rcl.list = widget.NewList( func() int { return len(rcl.items) }, func() fyne.CanvasObject { - return NewItemContainer( + itemContainer := NewItemContainer( func(i int) { if rcl.OnItemTapped != nil { rcl.OnItemTapped(i) @@ -48,6 +49,13 @@ func (rcl *RightClickableList) Build() { } }, ) + // 设置双击回调 + itemContainer.SetOnDoubleTapped(func(i int) { + if rcl.OnItemDoubleTapped != nil { + rcl.OnItemDoubleTapped(i) + } + }) + return itemContainer }, func(i int, o fyne.CanvasObject) { itemContainer := o.(*ItemContainer) diff --git a/pkg/appui/ui.go b/pkg/appui/ui.go index 33d391c..89c7fa2 100644 --- a/pkg/appui/ui.go +++ b/pkg/appui/ui.go @@ -122,6 +122,12 @@ func (ui *AppUI) setupUI() { ui.logger.Debug("right click", slog.String("item", ui.selectedName)) ui.showContextMenu(pos) } + ui.rightClickableList.OnItemDoubleTapped = func(i int) { + ui.selectedIndex = i + ui.selectedName = ui.items[i] + ui.logger.Debug("double click", slog.String("item", ui.selectedName)) + ui.handleDoubleClick() + } ui.rightClickableList.SetItems(ui.items) ui.rightClickableList.Build() @@ -133,16 +139,8 @@ func (ui *AppUI) setupUI() { logScroll := container.NewScroll(ui.logWidget) logScroll.SetMinSize(fyne.NewSize(LogPaneMinWidth, LogPaneMinHeight)) - // Navigation buttons - navButtons := container.NewHBox( - widget.NewButton("Up", ui.goUpDirectory), - widget.NewButton("Enter", ui.enterSelectedDirectory), - ) - // Operation buttons buttons := container.NewVBox( - navButtons, - widget.NewSeparator(), ui.createEncryptUploadButton(), ui.createSyncDownloadButton(), ui.createDownloadSpecificButton(), @@ -166,7 +164,7 @@ func (ui *AppUI) setupUI() { // refreshItems updates the items list func (ui *AppUI) refreshItems() { - ui.items = dir.List(ui.currentDir) + ui.items = dir.ListWithParent(ui.currentDir, ui.fileManager.GetWorkingDir()) } // refreshList refreshes the UI list @@ -228,6 +226,17 @@ func (ui *AppUI) enterSelectedDirectory() { ui.enterDirectory(ui.selectedName) } +// handleDoubleClick handles double-click navigation +func (ui *AppUI) handleDoubleClick() { + if ui.selectedName == ".." { + // 双击".."返回上一级目录 + ui.goUpDirectory() + } else { + // 双击其他项目,尝试进入目录 + ui.enterDirectory(ui.selectedName) + } +} + // enterDirectory enters the specified directory func (ui *AppUI) enterDirectory(dirName string) { fullPath := filepath.Join(ui.currentDir, dirName) diff --git a/pkg/dir/dir.go b/pkg/dir/dir.go index 930c322..f02e0e5 100644 --- a/pkg/dir/dir.go +++ b/pkg/dir/dir.go @@ -2,6 +2,7 @@ package dir import ( "os" + "path/filepath" "strings" ) @@ -21,3 +22,31 @@ func List(dir string) []string { } return out } + +// ListWithParent 返回给定目录的文件/目录名称,如果不在根目录则包含".." +func ListWithParent(dir string, workingDir string) []string { + fis, err := os.ReadDir(dir) + if err != nil { + return []string{} + } + + var out []string + + // 检查是否需要添加".."父目录条目 + cleanCurrentDir := filepath.Clean(dir) + cleanWorkingDir := filepath.Clean(workingDir) + + // 如果当前目录不是工作目录,则添加".." + if cleanCurrentDir != cleanWorkingDir { + out = append(out, "..") + } + + for _, fi := range fis { + // // skip hidden start-with-dot entries (可根据需要修改) + // if strings.HasPrefix(fi.Name(), ".") { + // continue + // } + out = append(out, fi.Name()) + } + return out +} From 0dc76baf27035a6f0f0e3529b61c8c1a46ce4837 Mon Sep 17 00:00:00 2001 From: mingregister <774262573@qq.com> Date: Mon, 29 Sep 2025 18:01:36 +0800 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20=E9=80=89=E4=B8=AD=E9=A2=9C?= =?UTF-8?q?=E8=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: mingregister <774262573@qq.com> --- pkg/appui/item_container.go | 64 ++++++++++++++++++++++++++++++- pkg/appui/right_clickable_list.go | 21 +++++++++- pkg/appui/ui.go | 1 + 3 files changed, 83 insertions(+), 3 deletions(-) diff --git a/pkg/appui/item_container.go b/pkg/appui/item_container.go index fd655b6..0eef5f2 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" ) @@ -14,7 +19,10 @@ var _ fyne.DoubleTappable = (*ItemContainer)(nil) type ItemContainer struct { widget.BaseWidget label *widget.Label + background *canvas.Rectangle + containerObj fyne.CanvasObject index int + selected bool onTapped func(index int) onRightClicked func(index int, pos fyne.Position) onDoubleTapped func(index int) @@ -22,8 +30,13 @@ type ItemContainer struct { // 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(""), + label: label, + background: background, + containerObj: container.NewBorder(nil, nil, nil, nil, label), onTapped: onTapped, onRightClicked: onRightClicked, } @@ -38,7 +51,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 +68,22 @@ func (ic *ItemContainer) SetIndex(i int) { ic.index = i } +// SetSelected 设置选中状态 +func (ic *ItemContainer) SetSelected(selected bool) { + ic.selected = selected + if selected { + ic.background.FillColor = theme.Color(theme.ColorNameSelection) + } else { + ic.background.FillColor = color.Transparent + } + 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 +104,30 @@ 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..e74c0fa 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 } @@ -61,6 +64,7 @@ func (rcl *RightClickableList) Build() { itemContainer := o.(*ItemContainer) itemContainer.SetText(rcl.items[i]) itemContainer.SetIndex(i) + itemContainer.SetSelected(i == rcl.selectedIndex) }, ) } @@ -77,10 +81,25 @@ func (rcl *RightClickableList) Refresh() { } } +// SetSelectedIndex 设置选中的索引 +func (rcl *RightClickableList) SetSelectedIndex(index int) { + 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..6bc5776 100644 --- a/pkg/appui/ui.go +++ b/pkg/appui/ui.go @@ -114,6 +114,7 @@ func (ui *AppUI) setupUI() { ui.rightClickableList.OnItemTapped = func(i int) { ui.selectedIndex = i ui.selectedName = ui.items[i] + ui.rightClickableList.SetSelectedIndex(i) ui.logger.Debug("left click", slog.String("item", ui.selectedName)) } ui.rightClickableList.OnItemRightClick = func(i int, pos fyne.Position) { From c052513dbd48f4ec62457a5dd6d840eeafb02540 Mon Sep 17 00:00:00 2001 From: mingregister <774262573@qq.com> Date: Mon, 29 Sep 2025 18:17:02 +0800 Subject: [PATCH 3/4] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E9=80=9F?= =?UTF-8?q?=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: mingregister <774262573@qq.com> --- pkg/appui/item_container.go | 7 +++++++ pkg/appui/right_clickable_list.go | 6 ++++++ pkg/appui/ui.go | 3 ++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/pkg/appui/item_container.go b/pkg/appui/item_container.go index 0eef5f2..106f7e2 100644 --- a/pkg/appui/item_container.go +++ b/pkg/appui/item_container.go @@ -70,13 +70,20 @@ func (ic *ItemContainer) SetIndex(i int) { // SetSelected 设置选中状态 func (ic *ItemContainer) SetSelected(selected bool) { + if ic.selected == selected { + return // 状态没有变化,直接返回 + } + ic.selected = selected if selected { ic.background.FillColor = theme.Color(theme.ColorNameSelection) } else { ic.background.FillColor = color.Transparent } + + // 立即刷新背景和整个组件 ic.background.Refresh() + ic.Refresh() } // IsSelected 获取选中状态 diff --git a/pkg/appui/right_clickable_list.go b/pkg/appui/right_clickable_list.go index e74c0fa..4fd513f 100644 --- a/pkg/appui/right_clickable_list.go +++ b/pkg/appui/right_clickable_list.go @@ -83,7 +83,13 @@ 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() } diff --git a/pkg/appui/ui.go b/pkg/appui/ui.go index 6bc5776..2857cd7 100644 --- a/pkg/appui/ui.go +++ b/pkg/appui/ui.go @@ -112,9 +112,10 @@ 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.rightClickableList.SetSelectedIndex(i) ui.logger.Debug("left click", slog.String("item", ui.selectedName)) } ui.rightClickableList.OnItemRightClick = func(i int, pos fyne.Position) { From 8a26caa45d381ac2be8b1987e20d9e750bc8f460 Mon Sep 17 00:00:00 2001 From: mingregister <774262573@qq.com> Date: Mon, 29 Sep 2025 18:23:41 +0800 Subject: [PATCH 4/4] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E9=80=9F?= =?UTF-8?q?=E5=BA=A6v2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: mingregister <774262573@qq.com> --- pkg/appui/item_container.go | 39 +++++++++++++++++-------------- pkg/appui/right_clickable_list.go | 15 +++++++++--- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/pkg/appui/item_container.go b/pkg/appui/item_container.go index 106f7e2..040ef40 100644 --- a/pkg/appui/item_container.go +++ b/pkg/appui/item_container.go @@ -18,14 +18,16 @@ var _ fyne.DoubleTappable = (*ItemContainer)(nil) // ItemContainer 是单个列表项,只负责显示文字和点击回调 type ItemContainer struct { widget.BaseWidget - label *widget.Label - background *canvas.Rectangle - containerObj fyne.CanvasObject - index int - selected bool - 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 @@ -34,11 +36,13 @@ func NewItemContainer(onTapped func(int), onRightClicked func(int, fyne.Position background := canvas.NewRectangle(color.Transparent) ic := &ItemContainer{ - label: label, - background: background, - containerObj: container.NewBorder(nil, nil, nil, nil, label), - 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 @@ -75,15 +79,16 @@ func (ic *ItemContainer) SetSelected(selected bool) { } ic.selected = selected + + // 使用缓存的颜色,避免重复查询主题系统 if selected { - ic.background.FillColor = theme.Color(theme.ColorNameSelection) + ic.background.FillColor = ic.selectionColor } else { - ic.background.FillColor = color.Transparent + ic.background.FillColor = ic.transparentColor } - // 立即刷新背景和整个组件 + // 只刷新背景,避免双重刷新 ic.background.Refresh() - ic.Refresh() } // IsSelected 获取选中状态 diff --git a/pkg/appui/right_clickable_list.go b/pkg/appui/right_clickable_list.go index 4fd513f..27d6d0e 100644 --- a/pkg/appui/right_clickable_list.go +++ b/pkg/appui/right_clickable_list.go @@ -62,9 +62,18 @@ func (rcl *RightClickableList) Build() { }, func(i int, o fyne.CanvasObject) { itemContainer := o.(*ItemContainer) - itemContainer.SetText(rcl.items[i]) - itemContainer.SetIndex(i) - itemContainer.SetSelected(i == rcl.selectedIndex) + // 只在必要时更新文本和索引 + 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) + } }, ) }