Implement four-way sort cycling for browser listview
- Added four sort modes: Date ↓, Date ↑, Name ↑, Name ↓ - Sorting happens only when sort mode changes, not every frame - Sorted entries are cached in SortedEntries field - Search query changes trigger re-filtering and re-sorting - Scroll offset resets on sort change
This commit is contained in:
parent
8f185341b3
commit
831f0dac81
|
|
@ -1,7 +1,7 @@
|
||||||
package editor
|
package editor
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gioui.org/widget"
|
"gioui.org/widget"
|
||||||
|
|
@ -56,8 +56,10 @@ const (
|
||||||
type SortMode int
|
type SortMode int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
SortByDateNewest SortMode = iota // default: newest modified first
|
SortByDateDesc SortMode = iota // default: newest first
|
||||||
|
SortByDateAsc
|
||||||
SortByNameAsc
|
SortByNameAsc
|
||||||
|
SortByNameDesc
|
||||||
)
|
)
|
||||||
|
|
||||||
// State holds all application state owned by the logic goroutine.
|
// State holds all application state owned by the logic goroutine.
|
||||||
|
|
@ -78,6 +80,7 @@ type State struct {
|
||||||
SortOrderLabel string // label text for sort order toggle
|
SortOrderLabel string // label text for sort order toggle
|
||||||
SearchQuery string // current search query
|
SearchQuery string // current search query
|
||||||
SearchEditor widget.Editor // Gio editor for search input
|
SearchEditor widget.Editor // Gio editor for search input
|
||||||
|
SortedEntries []ui.ListItem // pre-sorted entries (computed only when sort mode changes)
|
||||||
// Editor state
|
// Editor state
|
||||||
ActiveFilename string // filename shown in editor status bar
|
ActiveFilename string // filename shown in editor status bar
|
||||||
}
|
}
|
||||||
|
|
@ -86,8 +89,9 @@ func NewState() *State {
|
||||||
return &State{
|
return &State{
|
||||||
scale: 1.0,
|
scale: 1.0,
|
||||||
page: EditorPage,
|
page: EditorPage,
|
||||||
SortMode: SortByDateNewest,
|
SortMode: SortByDateDesc,
|
||||||
SortOrderLabel: "Date",
|
SortOrderLabel: "Date ↓",
|
||||||
|
SortedEntries: sortEntries(browserEntries, SortByDateDesc),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -109,6 +113,9 @@ func (s *State) layout() []ui.Element {
|
||||||
newQuery := s.SearchEditor.Text()
|
newQuery := s.SearchEditor.Text()
|
||||||
if newQuery != s.SearchQuery {
|
if newQuery != s.SearchQuery {
|
||||||
s.BrowserScrollOffset = 0 // reset on query change
|
s.BrowserScrollOffset = 0 // reset on query change
|
||||||
|
// Re-sort the filtered entries
|
||||||
|
filtered := getFilteredEntriesRaw()
|
||||||
|
s.SortedEntries = sortEntries(filtered, s.SortMode)
|
||||||
}
|
}
|
||||||
s.SearchQuery = newQuery
|
s.SearchQuery = newQuery
|
||||||
}
|
}
|
||||||
|
|
@ -204,17 +211,22 @@ func OpenFile(data any) {
|
||||||
TheState.page = EditorPage
|
TheState.page = EditorPage
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToggleSortOrder cycles the browser sort mode between date and name.
|
// ToggleSortOrder cycles the browser sort mode through four modes.
|
||||||
func ToggleSortOrder(data any) {
|
func ToggleSortOrder(data any) {
|
||||||
fmt.Printf("[ToggleSortOrder] called, mode was %v\n", TheState.SortMode)
|
TheState.SortMode = (TheState.SortMode + 1) % 4
|
||||||
TheState.SortMode = (TheState.SortMode + 1) % 2
|
|
||||||
switch TheState.SortMode {
|
switch TheState.SortMode {
|
||||||
case SortByDateNewest:
|
case SortByDateDesc:
|
||||||
TheState.SortOrderLabel = "Date"
|
TheState.SortOrderLabel = "Date ↓"
|
||||||
|
case SortByDateAsc:
|
||||||
|
TheState.SortOrderLabel = "Date ↑"
|
||||||
case SortByNameAsc:
|
case SortByNameAsc:
|
||||||
TheState.SortOrderLabel = "Name"
|
TheState.SortOrderLabel = "Name ↑"
|
||||||
|
case SortByNameDesc:
|
||||||
|
TheState.SortOrderLabel = "Name ↓"
|
||||||
}
|
}
|
||||||
fmt.Printf("[ToggleSortOrder] mode is now %v\n", TheState.SortMode)
|
// Sort once when mode changes, not every frame
|
||||||
|
TheState.SortedEntries = sortEntries(getFilteredEntriesRaw(), TheState.SortMode)
|
||||||
|
TheState.BrowserScrollOffset = 0 // reset scroll on sort change
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditorLayout computes the element tree for the editor page.
|
// EditorLayout computes the element tree for the editor page.
|
||||||
|
|
@ -301,8 +313,9 @@ func EditorLayout(screenWidth, screenHeight ui.Dp, wordWrap bool) []ui.Element {
|
||||||
return []ui.Element{statusBar, editor, bottomBar}
|
return []ui.Element{statusBar, editor, bottomBar}
|
||||||
}
|
}
|
||||||
|
|
||||||
// getFilteredEntries returns browser entries filtered by the current search query.
|
// getFilteredEntriesRaw returns browser entries filtered by the current search query.
|
||||||
func getFilteredEntries() []ui.ListItem {
|
// Does not use the SortedEntries cache.
|
||||||
|
func getFilteredEntriesRaw() []ui.ListItem {
|
||||||
var filtered []ui.ListItem
|
var filtered []ui.ListItem
|
||||||
query := strings.ToLower(TheState.SearchQuery)
|
query := strings.ToLower(TheState.SearchQuery)
|
||||||
if query == "" {
|
if query == "" {
|
||||||
|
|
@ -317,6 +330,52 @@ func getFilteredEntries() []ui.ListItem {
|
||||||
return filtered
|
return filtered
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getFilteredEntries returns browser entries filtered by the current search query.
|
||||||
|
// If SortedEntries is non-nil (sort mode has been set), returns that instead.
|
||||||
|
func getFilteredEntries() []ui.ListItem {
|
||||||
|
if TheState.SortedEntries != nil {
|
||||||
|
return TheState.SortedEntries
|
||||||
|
}
|
||||||
|
return getFilteredEntriesRaw()
|
||||||
|
}
|
||||||
|
|
||||||
|
// sortEntries sorts the entries according to the given sort mode.
|
||||||
|
func sortEntries(entries []ui.ListItem, mode SortMode) []ui.ListItem {
|
||||||
|
// Make a copy to avoid modifying the original slice
|
||||||
|
sorted := make([]ui.ListItem, len(entries))
|
||||||
|
copy(sorted, entries)
|
||||||
|
|
||||||
|
// Parse date from subtext (format: "2025-01-15 • 4.2 KB")
|
||||||
|
getDate := func(entry ui.ListItem) string {
|
||||||
|
// Extract date part before the first space
|
||||||
|
parts := strings.Split(entry.Subtext, " ")
|
||||||
|
if len(parts) > 0 {
|
||||||
|
return parts[0]
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
switch mode {
|
||||||
|
case SortByDateDesc:
|
||||||
|
sort.Slice(sorted, func(i, j int) bool {
|
||||||
|
return getDate(sorted[i]) > getDate(sorted[j])
|
||||||
|
})
|
||||||
|
case SortByDateAsc:
|
||||||
|
sort.Slice(sorted, func(i, j int) bool {
|
||||||
|
return getDate(sorted[i]) < getDate(sorted[j])
|
||||||
|
})
|
||||||
|
case SortByNameAsc:
|
||||||
|
sort.Slice(sorted, func(i, j int) bool {
|
||||||
|
return strings.ToLower(sorted[i].Text) < strings.ToLower(sorted[j].Text)
|
||||||
|
})
|
||||||
|
case SortByNameDesc:
|
||||||
|
sort.Slice(sorted, func(i, j int) bool {
|
||||||
|
return strings.ToLower(sorted[i].Text) > strings.ToLower(sorted[j].Text)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return sorted
|
||||||
|
}
|
||||||
|
|
||||||
// browserEntries is a static list of sample files for the browser page.
|
// browserEntries is a static list of sample files for the browser page.
|
||||||
// It is longer than the viewport so scrolling can be tested.
|
// It is longer than the viewport so scrolling can be tested.
|
||||||
var browserEntries = []ui.ListItem{
|
var browserEntries = []ui.ListItem{
|
||||||
|
|
@ -365,10 +424,14 @@ func BrowserLayout(screenWidth, screenHeight ui.Dp, sortMode SortMode) []ui.Elem
|
||||||
}
|
}
|
||||||
var sortLabel string
|
var sortLabel string
|
||||||
switch sortMode {
|
switch sortMode {
|
||||||
case SortByDateNewest:
|
case SortByDateDesc:
|
||||||
sortLabel = "Date ↓"
|
sortLabel = "Date ↓"
|
||||||
|
case SortByDateAsc:
|
||||||
|
sortLabel = "Date ↑"
|
||||||
case SortByNameAsc:
|
case SortByNameAsc:
|
||||||
sortLabel = "Name ↑"
|
sortLabel = "Name ↑"
|
||||||
|
case SortByNameDesc:
|
||||||
|
sortLabel = "Name ↓"
|
||||||
}
|
}
|
||||||
headerBar := ui.NewContainer(
|
headerBar := ui.NewContainer(
|
||||||
headerRegion,
|
headerRegion,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user