package browser import ( "fmt" "time" "gioui.org/widget" "pad/internal/ui" ) // Entry represents a single file or directory in the browser. type Entry struct { Path string // Relative path from configured directory Name string // Display name (basename) Size int64 // File size in bytes (0 for directories) ModTime time.Time // Last modification time IsDir bool // True if this is a directory IsFirst bool // True if this is the first entry for its letter section } // Page represents a chunk of directory entries loaded from disk. type Page struct { Index int // Page number (0-based) Entries []Entry // Entries in this page Loaded bool // True if page data is in memory Dirty bool // True if page needs refresh (external change detected) } // BrowserState holds all mutable browser state owned by the logic goroutine. type BrowserState struct { // Navigation CurrentPath string // Currently browsed directory (relative to root) ScrollIndex int // Index of first visible entry (not pixel offset) VisibleCount int // Number of entries currently visible // Lazy loading Pages map[int]*Page // Loaded pages by page index TotalEntries int // Total entry count (from cached index) Loading bool // True if a page load is in flight // Sorted index (position maps for each sort mode) SortIndex *DirectoryIndex // Cached index with position maps SortMode SortMode // Current sort mode // Search Query string // Current search query SearchResults []int // Indices of matching entries (empty = no filter) SearchEditor widget.Editor // Gio editor for search input // Alphabetical index ActiveLetter string // Currently pressed letter (for highlighting) LetterOffsets map[string]int // First entry index for each letter // Interaction SelectedIndex int // Currently selected entry (-1 = none) TapTimestamp time.Time // For double-tap detection } const ( PageSize = 100 // Entries per page (tunable; ~5KB per page in memory) PrefetchDist = 2 // Pages to prefetch beyond visible region ) // NewEntry creates a new Entry from the given parameters. func NewEntry(path, name string, size int64, modTime time.Time, isDir bool) Entry { return Entry{ Path: path, Name: name, Size: size, ModTime: modTime, IsDir: isDir, } } // NewPage creates a new Page with the given index and entries. func NewPage(index int, entries []Entry) *Page { return &Page{ Index: index, Entries: entries, Loaded: true, } } // NewBrowserState creates a new BrowserState with default values. func NewBrowserState() *BrowserState { return &BrowserState{ Pages: make(map[int]*Page), LetterOffsets: make(map[string]int), SelectedIndex: -1, SortMode: SortModeNameAsc, // Default sort mode } } // ToListItem converts an Entry to a ui.ListItem for rendering. func (e *Entry) ToListItem() ui.ListItem { subtext := formatSize(e.Size) if e.IsDir { subtext = "Directory" } return ui.ListItem{ Text: e.Name, Subtext: subtext, } } // formatSize returns a human-readable file size string. func formatSize(bytes int64) string { switch { case bytes < 1024: return fmt.Sprintf("%d B", bytes) case bytes < 1024*1024: return fmt.Sprintf("%.1f KB", float64(bytes)/1024) case bytes < 1024*1024*1024: return fmt.Sprintf("%.1f MB", float64(bytes)/(1024*1024)) default: return fmt.Sprintf("%.1f GB", float64(bytes)/(1024*1024*1024)) } } // EvictPages removes pages that are far from the current scroll position. func (s *BrowserState) EvictPages() { minPage := s.ScrollIndex/PageSize - PrefetchDist maxPage := (s.ScrollIndex + s.VisibleCount)/PageSize + PrefetchDist for idx, page := range s.Pages { if idx < minPage || idx > maxPage { page.Entries = nil // Release memory page.Loaded = false } } } // Reset clears all browser state for navigation. func (s *BrowserState) Reset() { s.Pages = make(map[int]*Page) s.ScrollIndex = 0 s.SelectedIndex = -1 s.SearchResults = nil s.Query = "" }