diff --git a/launcher/viewMain.go b/launcher/viewMain.go new file mode 100644 index 0000000..6724b6a --- /dev/null +++ b/launcher/viewMain.go @@ -0,0 +1,67 @@ +package launcher + +import ( + "sync" + + "gioui.org/app" + "gioui.org/io/event" + "git.mstar.dev/mstar/timer/shared" + "github.com/rs/zerolog/log" +) + +type LauncherState struct { + possibleTargets map[string]func() shared.WindowState + possibleTargetsLock sync.RWMutex + activeWindows map[string]bool + activeWindowsLock sync.RWMutex + initialSetupDone bool + initialSetupLock sync.RWMutex +} + +func (s *LauncherState) Run(globalState *shared.GlobalState, event event.Event) shared.NewState { + if !s.initialSetupDone { + s.initialSetup(globalState) + } + switch event := event.(type) { + case app.FrameEvent: + return s.handleFrameEvent(globalState, &event) + case app.DestroyEvent: + return s.handleQuitEvent() + case app.WaylandViewEvent, app.X11ViewEvent: + return shared.EmptyEvent() + default: + log.Debug().Any("event", event).Type("type", event).Msg("Unknown event") + return shared.NewState{} + } +} + +func (s *LauncherState) Exit(globalState *shared.GlobalState, wg *sync.WaitGroup) { + wg.Done() +} + +func (s *LauncherState) AddTarget(name string, builder func() shared.WindowState) { + s.possibleTargets[name] = builder +} + +func (s *LauncherState) initialSetup(globalState *shared.GlobalState) { + s.activeWindowsLock.Lock() + defer s.activeWindowsLock.Unlock() + s.possibleTargetsLock.Lock() + defer s.possibleTargetsLock.Unlock() + s.initialSetupLock.Lock() + defer s.initialSetupLock.Unlock() + s.initialSetupDone = true + s.possibleTargets = map[string]func() shared.WindowState{} + s.activeWindows = map[string]bool{} +} + +func (s *LauncherState) handleQuitEvent() shared.NewState { + return shared.NewState{ExitCode: shared.ErrExitOk} +} + +func (s *LauncherState) handleFrameEvent( + globalState *shared.GlobalState, + frameEvent *app.FrameEvent, +) shared.NewState { + return shared.NewState{} +} diff --git a/shared/window.go b/shared/window.go index bd6013c..422f630 100644 --- a/shared/window.go +++ b/shared/window.go @@ -1,9 +1,11 @@ package shared import ( + "context" "sync" "gioui.org/app" + "gioui.org/io/event" "gioui.org/widget/material" "github.com/rs/zerolog/log" ) @@ -14,7 +16,7 @@ type GlobalState struct { state WindowState } -func Run(window *app.Window, initialState WindowState) error { +func Run(ctx context.Context, window *app.Window, initialState WindowState) error { // Set up global data first state := GlobalState{} theme := material.NewTheme() @@ -26,21 +28,38 @@ func Run(window *app.Window, initialState WindowState) error { // Then start showing the window and polling for events // Though technically, I don't think window.Event will cause the window to display yet // since what should it display? It doesn't have anything to display. So first display happens at event.Frame - for { - e := window.Event() - // Let the current view handle the event, then update accordingly - newState := state.state.Run(&state, e) - if newState.NextState != nil { - state.state = newState.NextState + eventChan := make(chan event.Event) + go func() { + for { + eventChan <- window.Event() + if ctx.Err() != nil { + return + } } - if newState.ExitCode != nil { - log.Info().Msg("Received shutdown request") - wg := sync.WaitGroup{} - wg.Add(1) - go state.state.Exit(&state, &wg) - wg.Wait() - log.Info().Msg("Shutdown of window complete") - return nil + }() + for { + select { + case <-ctx.Done(): + log.Info().Msg("Context cancelled, preparing shutdown") + // Force one update for polling goroutine to exit + state.Window.Invalidate() + // Then keep awaiting more events. Since quit is just another event + // and the event channel is unbuffered, at least one more event has to be read for the poller to exit + case e := <-eventChan: + // Let the current view handle the event, then update accordingly + newState := state.state.Run(&state, e) + if newState.NextState != nil { + state.state = newState.NextState + } + if newState.ExitCode != nil { + log.Info().Msg("Received shutdown request from window") + wg := sync.WaitGroup{} + wg.Add(1) + go state.state.Exit(&state, &wg) + wg.Wait() + log.Info().Msg("Shutdown of window complete") + return nil + } } } }