package editor import ( "sync" "pad/internal/browser" "pad/internal/io/pool" "pad/internal/io/pool/mock" "pad/internal/ui" ) // ConfigEvent represents a window configuration change (resize, orientation). // PixelWidth and PixelHeight are the raw pixel dimensions from Gio. type ConfigEvent struct { PixelWidth int PixelHeight int } // ScaleEvent represents a metric change (HiDPI scale factor). type ScaleEvent struct { Scale float32 } // ConfigUpdate is a common interface for all configuration updates. // Both ConfigEvent and ScaleEvent implement this interface. type ConfigUpdate interface { apply(*State) } func (e ConfigEvent) apply(s *State) { s.PixelWidth = e.PixelWidth s.PixelHeight = e.PixelHeight } func (e ScaleEvent) apply(s *State) { s.SetScale(e.Scale) } // ResultEvent represents a completed async task result. type ResultEvent struct { // Future: add result fields here } // Logic runs the logic goroutine and provides channels for communication. type Logic struct { state *State configChan chan ConfigUpdate frameChan chan []ui.Element 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 workerPool *pool.WorkerPool mockFS *mock.FileSystem mu sync.Mutex done chan struct{} } // NewLogic creates a new Logic instance. func NewLogic() *Logic { state := NewState() TheState = state // Initialize mock filesystem with sample data mockFS := mock.NewFileSystem() populateMockFileSystem(mockFS) // Initialize worker pool wp := pool.NewWorkerPool(4) wp.Start() // Set browser initial path to mock root state.Browser.CurrentPath = "/" return &Logic{ state: state, configChan: make(chan ConfigUpdate), frameChan: make(chan []ui.Element), inputChan: make(chan []ui.InputEvent), lastLineYChan: make(chan int), resultChan: make(chan ResultEvent), searchQueryChan: make(chan string), workerPool: wp, mockFS: mockFS, done: make(chan struct{}), } } // ConfigChan returns the unified config channel for the logic goroutine. // Accepts ConfigEvent (size) and ScaleEvent (scale factor). func (l *Logic) ConfigChan() chan<- ConfigUpdate { return l.configChan } // FrameChan returns the frame channel for the logic goroutine. func (l *Logic) FrameChan() <-chan []ui.Element { return l.frameChan } // InputChan returns the input channel for the logic goroutine. func (l *Logic) InputChan() chan<- []ui.InputEvent { return l.inputChan } // DisplayLineChan returns the last line Y feedback channel. func (l *Logic) DisplayLineChan() chan<- int { return l.lastLineYChan } // ResultChan returns the result channel for the logic goroutine. 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 // Scale returns the current scale factor (pixels per DP). func (l *Logic) Scale() float32 { return l.state.Scale() } // Run runs the logic goroutine loop. func (l *Logic) Run() { // Dispatch initial directory index build on startup l.workerPool.Dispatch(pool.NewBuildIndexTask(l.state.Browser.CurrentPath, l.mockFS)) for { select { case <-l.done: return case update := <-l.configChan: update.apply(l.state) l.frameChan <- l.state.layout() case y := <-l.lastLineYChan: if ui.Dp(y) != l.state.LastLineY { l.state.LastLineY = ui.Dp(y) l.frameChan <- l.state.layout() } case events := <-l.inputChan: for _, evt := range events { evt.Handler(evt.Data) } l.frameChan <- l.state.layout() case query := <-l.searchQueryChan: if query != l.state.Browser.Query { l.state.Browser.Query = query if l.state.page == BrowserPage { browser.HandleSearch(&l.state.Browser, query) } } l.frameChan <- l.state.layout() case res := <-l.workerPool.ResultChan(): l.handleWorkerResult(res) case <-l.resultChan: l.frameChan <- l.state.layout() } } } // handleWorkerResult processes results from the worker pool. func (l *Logic) handleWorkerResult(res pool.Result) { switch res.TaskType { case pool.TypeBuildIndex: l.applyBuildIndexResult(res) case pool.TypeLoadPages: l.applyLoadPagesResult(res) case pool.TypeReadDir: l.applyReadDirResult(res) } l.frameChan <- l.state.layout() } // applyBuildIndexResult applies a completed BuildIndexTask result to browser state. func (l *Logic) applyBuildIndexResult(res pool.Result) { if !res.Success { return } // Convert mock DirEntry results to browser entries if entries, ok := res.Data.([]mock.DirEntry); ok { l.state.Browser.TotalEntries = len(entries) // Build the in-memory index with position maps l.state.Browser.SortIndex = buildBrowserIndex(entries) // Load initial visible pages from the index into the Pages map browser.LoadInitialPages(&l.state.Browser) } } // applyLoadPagesResult applies a completed LoadPagesTask result to browser state. func (l *Logic) applyLoadPagesResult(res pool.Result) { _ = res // TODO: implement page loading from worker results } // applyReadDirResult applies a completed ReadDirTask result to browser state. func (l *Logic) applyReadDirResult(res pool.Result) { _ = res // TODO: implement directory read result handling } // State returns the current state. func (l *Logic) State() *State { return l.state } // Done signals the logic goroutine to stop. func (l *Logic) Done() { close(l.done) }