Pad/cmd/pad/main.go
Greg Pomerantz beae5fc026 Fix directory navigation and path initialization
- Initialize browser path to the actual startup directory instead of '/'
- Improve '..' navigation to be relative to the filesystem root
- Fix path duplication issues by avoiding redundant absolute path joining
2026-06-05 13:43:27 -04:00

162 lines
4.2 KiB
Go

package main
import (
"flag"
"log"
"os"
"path/filepath"
"sync"
"gioui.org/app"
"gioui.org/op"
"gioui.org/text"
"gioui.org/unit"
"gioui.org/font/gofont"
"gioui.org/io/key"
"pad/internal/editor"
"pad/internal/io/pool/real"
"pad/internal/ui"
)
func main() {
go func() {
w := new(app.Window)
w.Option(app.Title("Pad"))
w.Option(app.Size(unit.Dp(390), unit.Dp(844)))
if err := run(w); err != nil {
log.Fatal(err)
}
os.Exit(0)
}()
app.Main()
}
func run(w *app.Window) error {
log.Printf("run: starting")
var ops op.Ops
shaper := text.NewShaper(text.WithCollection(gofont.Collection()))
// Determine root directory for real filesystem
rootDir := flag.String("root", startpath, "root directory for the filesystem")
flag.Parse()
// Initialize the real filesystem rooted at the system root "/"
// so that the browser can navigate the entire system.
fs := real.NewRealFileSystem("/")
// Get the absolute path of the startup directory
startAbs, err := filepath.Abs(*rootDir)
if err != nil {
startAbs = *rootDir
}
log.Printf("using filesystem at / (startup directory: %s)", startAbs)
logic := editor.NewLogic(fs, startAbs)
renderer := ui.New(ui.Theme{FontSize: 14}, shaper, logic.State())
var mu sync.Mutex
var elems []ui.Element
var curScale, newScale float32
log.Printf("run: starting frameReceiver")
go frameReceiver(w, &mu, &elems, logic.FrameChan())
log.Printf("run: starting logic.Run")
go logic.Run()
for {
switch e := w.Event().(type) {
case app.DestroyEvent:
logic.Shutdown()
return e.Err
case app.ConfigEvent:
// ConfigEvent: raw pixel dimensions only.
logic.ConfigChan() <- editor.ConfigEvent{
PixelWidth: e.Config.Size.X,
PixelHeight: e.Config.Size.Y,
}
case app.FrameEvent:
log.Printf("run: FrameEvent")
gtx := app.NewContext(&ops, e)
newScale = gtx.Metric.PxPerDp
curScale = logic.State().Scale()
// Deadlock risk: ensure no channel sends while lock is held
mu.Lock()
currentElems := elems
renderer.Draw(gtx, currentElems)
glyphLayout := renderer.GlyphLayout()
// Send search query update to the logic goroutine when it changes.
// The logic goroutine handles filtering and triggers a new frame.
newQuery := logic.State().Browser.SearchEditor.Text()
sendQuery := false
if newQuery != logic.State().Browser.Query {
sendQuery = true
}
events := renderer.CheckGestures(e.Source, gtx.Metric)
// Gather key events
focusedID := logic.State().FocusedElementID
log.Printf("focusedID = %s", focusedID)
if focusedID != "" {
if reg, ok := renderer.Keys[focusedID]; ok {
log.Printf("looking for events")
// Use key.Filter to only receive events destined for the focused element.
// We need both Key events (for arrow keys) and Edit events (for text input).
// In Gio, key.Filter covers key presses, while key.FocusFilter covers
// focus and text edit events.
for {
// Filter for key events and focus/edit events targeted at focusedID
evt, ok := gtx.Event(key.Filter{Focus: focusedID}, key.FocusFilter{Target: focusedID})
if !ok {
break
}
log.Printf("found an event: %T", evt)
switch k := evt.(type) {
case key.Event:
log.Printf("key event: %v state=%v", k.Name, k.State)
if k.State == key.Press {
events = append(events, ui.InputEvent{
Handler: reg.Handler,
Data: k.Name,
})
}
case key.EditEvent:
log.Printf("edit event: %v", k.Text)
events = append(events, ui.InputEvent{
Handler: reg.Handler,
Data: k,
})
}
}
}
}
e.Frame(&ops)
mu.Unlock()
if newScale != curScale {
logic.ConfigChan() <- editor.ScaleEvent{newScale}
}
if len(events) > 0 {
logic.InputChan() <- events
}
if sendQuery {
logic.SearchQueryChan() <- newQuery
}
logic.LayoutChan() <- glyphLayout
default:
log.Printf("pad: default event")
handleEvent(e)
}
}
}
func frameReceiver(w *app.Window, mu *sync.Mutex, elems *[]ui.Element, frameChan <-chan []ui.Element) {
log.Printf("frameReceiver: loop starting")
for {
frame := <-frameChan
log.Printf("frameReceiver: received frame")
mu.Lock()
*elems = frame
w.Invalidate()
mu.Unlock()
}
}