diff --git a/cmd/pad/main.go b/cmd/pad/main.go index 0711f90..71786b3 100644 --- a/cmd/pad/main.go +++ b/cmd/pad/main.go @@ -10,6 +10,7 @@ import ( "gioui.org/text" "gioui.org/unit" + "pad/internal/editor" "pad/internal/ui" ) @@ -32,27 +33,22 @@ func run(w *app.Window) error { shaper := text.NewShaper() renderer := ui.New(ui.Theme{FontSize: 14}, shaper) - // Channels for communication between goroutines - configChan := make(chan ui.ConfigEvent) - frameChan := make(chan []ui.Element) - inputChan := make(chan []ui.InputEvent) + // Initial screen size + screenWidth := unit.Dp(390) + screenHeight := unit.Dp(844) + + // Create logic instance + logic := editor.NewLogic(screenWidth, screenHeight) // Shared state protected by mutex var mu sync.Mutex var elems []ui.Element - // Initial screen size - screenWidth := unit.Dp(390) - screenHeight := unit.Dp(844) - - // Compute initial elements - elems = ui.EditorLayout(screenWidth, screenHeight) - // Start logic goroutine - go logic(w, shaper, screenWidth, screenHeight, configChan, frameChan, inputChan) + go logic.Run() // Start frame receiver goroutine - go frameReceiver(w, &mu, &elems, frameChan) + go frameReceiver(w, &mu, &elems, logic.FrameChan()) // Main event loop for { @@ -61,7 +57,7 @@ func run(w *app.Window) error { return e.Err case app.ConfigEvent: // Send config event to logic goroutine - configChan <- ui.ConfigEvent{ + logic.ConfigChan() <- editor.ConfigEvent{ Width: unit.Dp(e.Config.Size.X), Height: unit.Dp(e.Config.Size.Y), } @@ -78,28 +74,6 @@ func run(w *app.Window) error { } } -// logic runs in a separate goroutine and owns all application state. -func logic(w *app.Window, shaper *text.Shaper, screenWidth, screenHeight unit.Dp, - configChan <-chan ui.ConfigEvent, frameChan chan<- []ui.Element, inputChan <-chan []ui.InputEvent) { - - // Initial elements - elems := ui.EditorLayout(screenWidth, screenHeight) - - for { - select { - case cfg := <-configChan: - // Update screen size and recompute layout - screenWidth = cfg.Width - screenHeight = cfg.Height - elems = ui.EditorLayout(screenWidth, screenHeight) - frameChan <- elems - case <-inputChan: - // Process input (not implemented in mockup) - frameChan <- elems - } - } -} - // frameReceiver runs in a separate goroutine and bridges frameChan to the main loop. func frameReceiver(w *app.Window, mu *sync.Mutex, elems *[]ui.Element, frameChan <-chan []ui.Element) { for { diff --git a/internal/editor/logic.go b/internal/editor/logic.go new file mode 100644 index 0000000..6ebdfb5 --- /dev/null +++ b/internal/editor/logic.go @@ -0,0 +1,90 @@ +package editor + +import ( + "gioui.org/unit" + + "pad/internal/ui" +) + +// ConfigEvent represents a window configuration change (resize, orientation). +type ConfigEvent struct { + Width unit.Dp + Height unit.Dp +} + +// InputEvent represents a user input event routed to an element. +type InputEvent struct { + ElementID string + Type ui.InputType + Data any +} + +// 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 ConfigEvent + frameChan chan []ui.Element + inputChan chan []InputEvent + resultChan chan ResultEvent +} + +// NewLogic creates a new Logic instance. +func NewLogic(screenWidth, screenHeight unit.Dp) *Logic { + return &Logic{ + state: NewState(screenWidth, screenHeight), + configChan: make(chan ConfigEvent), + frameChan: make(chan []ui.Element), + inputChan: make(chan []InputEvent), + resultChan: make(chan ResultEvent), + } +} + +// ConfigChan returns the config channel for the logic goroutine. +func (l *Logic) ConfigChan() chan<- ConfigEvent { + 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<- []InputEvent { + return l.inputChan +} + +// ResultChan returns the result channel for the logic goroutine. +func (l *Logic) ResultChan() <-chan ResultEvent { + return l.resultChan +} + +// Run runs the logic goroutine loop. +func (l *Logic) Run() { + for { + select { + case cfg := <-l.configChan: + // Update screen size and recompute layout + l.state.ScreenWidth = cfg.Width + l.state.ScreenHeight = cfg.Height + l.state.Elems = EditorLayout(cfg.Width, cfg.Height) + l.frameChan <- l.state.Elems + case <-l.inputChan: + // Process input (not implemented in mockup) + l.frameChan <- l.state.Elems + case <-l.resultChan: + // Handle result (not implemented in mockup) + l.frameChan <- l.state.Elems + } + } +} + +// State returns the current state. +func (l *Logic) State() *State { + return l.state +} diff --git a/internal/editor/state.go b/internal/editor/state.go new file mode 100644 index 0000000..d62da8c --- /dev/null +++ b/internal/editor/state.go @@ -0,0 +1,70 @@ +package editor + +import ( + "gioui.org/unit" + + "pad/internal/ui" +) + +// State holds all application state owned by the logic goroutine. +type State struct { + ScreenWidth unit.Dp + ScreenHeight unit.Dp + Elems []ui.Element +} + +// NewState creates a new State with initial editor layout. +func NewState(screenWidth, screenHeight unit.Dp) *State { + return &State{ + ScreenWidth: screenWidth, + ScreenHeight: screenHeight, + Elems: EditorLayout(screenWidth, screenHeight), + } +} + +// EditorLayout computes regions for the editor page. +// It takes screen dimensions and returns []Element. +func EditorLayout(screenWidth, screenHeight unit.Dp) []ui.Element { + // Compute StatusBar region + // Line 1: filename (24 DP) + // Line 2: icons (24 DP) + statusBarHeight := unit.Dp(48) + statusBarRegion := ui.Region{ + X: 0, + Y: 0, + W: screenWidth, + H: statusBarHeight, + } + + // Compute BottomBar region (fixed height, at bottom of screen) + bottomBarHeight := unit.Dp(24) + bottomBarY := screenHeight - bottomBarHeight + bottomBarRegion := ui.Region{ + X: 0, + Y: bottomBarY, + W: screenWidth, + H: bottomBarHeight, + } + + // Create StatusBar with mocked filename + statusBar := ui.NewStatusBar( + statusBarRegion, + "very_long_filename_that_does_not_fit_on_a_single_line.txt", + false, // FilenameExp + true, // CutCopy + false, // Copy + true, // Paste + false, // ConflictIcon + true, // Search + ) + + // Create BottomBar with mocked data + bottomBar := ui.NewBottomBar( + bottomBarRegion, + "Ln 47, Col 12", + "1024 / 50000", + true, // WordWrap + ) + + return []ui.Element{statusBar, bottomBar} +} diff --git a/pad b/pad index 25078ce..065f543 100755 Binary files a/pad and b/pad differ