package browser import ( "pad/internal/ui" ) // BrowserLayout computes the element tree for the browser (file listing) page. // Pure function: (screen dimensions, browser state, sort handler) → []ui.Element func BrowserLayout(screenW, screenH ui.Dp, state *BrowserState, sortHandler func(any)) []ui.Element { margin := ui.Dp(10) contentWidth := screenW - margin*2 // --- Header bar: directory path + sort toggle --- headerHeight := ui.Dp(24) headerRegion := ui.Region{ X: margin, Y: margin, W: contentWidth, H: headerHeight, } sortLabel := sortModeLabel(state.SortMode) headerBar := ui.NewContainer( headerRegion, ui.Color{R: 230, G: 230, B: 230, A: 255}, []ui.Element{ ui.NewLabel(state.CurrentPath, 14, ui.Region{X: 0, Y: 0, W: contentWidth, H: headerHeight}, ui.AlignStart, "", nil), ui.NewLabel(sortLabel, 12, ui.Region{X: 0, Y: 0, W: contentWidth, H: headerHeight}, ui.AlignEnd, "sort", []ui.Interaction{{Gesture: ui.Tap, Handler: sortHandler}}), }, ) // --- Search bar (Gio editor) --- searchHeight := ui.Dp(36) searchY := headerRegion.Y + headerRegion.H + margin/2 searchRegion := ui.Region{ X: margin, Y: searchY, W: contentWidth, H: searchHeight, } state.SearchEditor.SingleLine = true searchBar := ui.NewGioEditor("search_bar", searchRegion, &state.SearchEditor) var searchPlaceholder ui.Element if state.SearchEditor.Len() == 0 { searchPlaceholder = ui.NewLabel("Search…", 14, ui.Region{X: margin + ui.Dp(8), Y: searchY + ui.Dp(8), W: contentWidth - ui.Dp(16), H: searchHeight - ui.Dp(16)}, ui.AlignStart, "", nil) } // --- ListView --- listY := searchY + searchHeight + margin/2 listHeight := screenH - listY - margin listRegion := ui.Region{ X: margin, Y: listY, W: contentWidth, H: listHeight, } // Compute visible entries visibleEntries := computeVisibleEntries(state) // Create a scroll handler that captures the current browser state scrollHandler := func(data any) { delta := data.(int) // entries HandleScroll(state, delta) } listView := ui.NewListView( "browser_list", visibleEntries, listRegion, ui.Dp(state.ScrollIndex)*ui.Dp(48), // scroll offset in pixels state.SelectedIndex, []ui.Interaction{{Gesture: ui.Scroll, Handler: scrollHandler}}, ) elems := []ui.Element{headerBar, searchBar} if searchPlaceholder != nil { elems = append(elems, searchPlaceholder) } elems = append(elems, listView) return elems } // sortModeLabel returns the display label for the given sort mode. func sortModeLabel(mode SortMode) string { switch mode { case SortModeNameAsc: return "Name ↑" case SortModeNameDesc: return "Name ↓" case SortModeDateAsc: return "Date ↑" case SortModeDateDesc: return "Date ↓" default: return "Name ↑" } } // ComputeVisibleEntriesForTest is exported for testing purposes. // It builds the list of ui.ListItem entries that should be rendered. func ComputeVisibleEntriesForTest(state *BrowserState) []ui.ListItem { return computeVisibleEntries(state) } // computeVisibleEntries builds the list of ui.ListItem entries that should be // rendered based on the current scroll position and visible count. func computeVisibleEntries(state *BrowserState) []ui.ListItem { if state.TotalEntries == 0 { return nil } startIndex := state.ScrollIndex endIndex := startIndex + state.VisibleCount if endIndex > state.TotalEntries { endIndex = state.TotalEntries } var items []ui.ListItem for idx := startIndex; idx < endIndex; idx++ { entry, ok := getEntryByIndex(state, idx) if !ok { // Page not loaded yet; add placeholder items = append(items, ui.ListItem{ Text: "...", Subtext: "Loading...", }) continue } item := entry.ToListItem() item.Selected = (idx == state.SelectedIndex) items = append(items, item) } return items }