diff --git a/cmd/pad/main.go b/cmd/pad/main.go index 31ecdf1..b680c3f 100644 --- a/cmd/pad/main.go +++ b/cmd/pad/main.go @@ -59,12 +59,11 @@ func run(w *app.Window) error { currentElems := elems renderer.Draw(gtx, currentElems) logic.DisplayLineChan() <- int(renderer.LastLineY()) - // Check for search query change — trigger a new frame if the - // widget.Editor text differs from the logic's stored query. - // This ensures the filtered list updates as the user types. + // Send search query update to the logic goroutine when it changes. + // The logic goroutine handles filtering and triggers a new frame. newQuery := logic.State().SearchEditor.Text() if newQuery != logic.State().SearchQuery { - logic.InputChan() <- []ui.InputEvent{} + logic.SearchQueryChan() <- newQuery } if events := renderer.CheckGestures(e.Source, gtx.Metric); len(events) > 0 { logic.InputChan() <- events diff --git a/internal/editor/logic.go b/internal/editor/logic.go index b35d0c2..242a44f 100644 --- a/internal/editor/logic.go +++ b/internal/editor/logic.go @@ -46,6 +46,7 @@ type Logic struct { inputChan chan []ui.InputEvent lastLineYChan chan int // last line Y (Dp, sent as int) feedback from renderer resultChan chan ResultEvent + searchQueryChan chan string // search text updates from main goroutine mu sync.Mutex } @@ -60,6 +61,7 @@ func NewLogic() *Logic { inputChan: make(chan []ui.InputEvent), lastLineYChan: make(chan int), resultChan: make(chan ResultEvent), + searchQueryChan: make(chan string), } } @@ -89,6 +91,12 @@ func (l *Logic) ResultChan() chan<- ResultEvent { return l.resultChan } +// SearchQueryChan returns the search query channel for the logic goroutine. +// The main goroutine sends updated search text here when it detects a change. +func (l *Logic) SearchQueryChan() chan<- string { + return l.searchQueryChan +} + // TheState is the global editor state, set once at startup. var TheState *State @@ -114,6 +122,16 @@ func (l *Logic) Run() { evt.Handler(evt.Data) } l.frameChan <- l.state.layout() + case query := <-l.searchQueryChan: + if query != l.state.SearchQuery { + l.state.SearchQuery = query + if l.state.page == BrowserPage { + l.state.BrowserScrollOffset = 0 // reset scroll on query change + filtered := getFilteredEntriesRaw() + l.state.SortedEntries = sortEntries(filtered, l.state.SortMode) + } + } + l.frameChan <- l.state.layout() case <-l.resultChan: l.frameChan <- l.state.layout() } diff --git a/internal/editor/state.go b/internal/editor/state.go index 031c047..91feac5 100644 --- a/internal/editor/state.go +++ b/internal/editor/state.go @@ -105,20 +105,11 @@ func (s *State) Scale() float32 { // layout converts stored pixel dimensions to Dp using the current scale // and computes the element tree. Called only when a frame is needed. +// Search query sync is handled by the logic goroutine via searchQueryChan, +// not here, to ensure proper channel-based state flow. func (s *State) layout() []ui.Element { dpW := ui.ToDp(ui.Px(s.PixelWidth), s.scale) dpH := ui.ToDp(ui.Px(s.PixelHeight), s.scale) - // Sync search query from Editor widget, reset scroll on query change - if s.page == BrowserPage { - newQuery := s.SearchEditor.Text() - if newQuery != s.SearchQuery { - s.BrowserScrollOffset = 0 // reset on query change - // Re-sort the filtered entries - filtered := getFilteredEntriesRaw() - s.SortedEntries = sortEntries(filtered, s.SortMode) - } - s.SearchQuery = newQuery - } switch s.page { case BrowserPage: s.Elems = BrowserLayout(dpW, dpH, s.SortMode)