package timer import ( "image" "sync" "time" "gioui.org/app" "gioui.org/io/event" "gioui.org/layout" "gioui.org/op" "gioui.org/op/clip" "gioui.org/op/paint" "gioui.org/unit" "gioui.org/widget" "gioui.org/widget/material" "git.mstar.dev/mstar/goutils/other" "git.mstar.dev/mstar/timer/shared" "github.com/rs/zerolog/log" ) type StateEnterTime struct { initialSetupDone bool editor *widget.Editor inputField *material.EditorStyle inputErrorMessage *string ops op.Ops } func (s *StateEnterTime) initialSetup(globalState *shared.GlobalState) { s.editor = &widget.Editor{ SingleLine: true, Submit: true, Filter: "01234567890TZ+-:", } field := material.Editor(globalState.Theme, s.editor, "timestamp") s.inputField = &field s.initialSetupDone = true globalState.Window.Option(func(m unit.Metric, c *app.Config) { c.MaxSize.X = 520 c.MaxSize.Y = 150 c.MinSize = c.MaxSize c.Size = c.MaxSize }) } func (s *StateEnterTime) 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 *StateEnterTime) Exit(globalState *shared.GlobalState, wg *sync.WaitGroup) { wg.Done() } func (s *StateEnterTime) handleFrameEvent( globalState *shared.GlobalState, frameEvent *app.FrameEvent, ) shared.NewState { gtx := app.NewContext(&s.ops, *frameEvent) for ev, ok := s.editor.Update(gtx); ok; ev, ok = s.editor.Update(gtx) { switch ev := ev.(type) { case widget.SubmitEvent: t, err := time.Parse(time.RFC3339, ev.Text) if err == nil { return s.handleTimeEntered(t) } s.inputErrorMessage = other.IntoPointer(err.Error()) log.Debug().Err(err).Msg("Invalid timestamp") } log.Debug().Any("Editor event", ev).Type("type", ev).Send() } paint.FillShape( gtx.Ops, globalState.Theme.Bg, clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Op(), ) label := material.H3(globalState.Theme, "Enter timestamp") hint := material.Subtitle1( globalState.Theme, "Format: --
T::(Z|+)", ) var errorMessage material.LabelStyle nrOfElements := 3 if s.inputErrorMessage != nil { nrOfElements++ errorMessage = material.Subtitle2(globalState.Theme, *s.inputErrorMessage) } list := layout.List{Axis: layout.Vertical} list.Layout(gtx, nrOfElements, func(gtx layout.Context, index int) layout.Dimensions { switch index { case 0: return label.Layout(gtx) case 1: return hint.Layout(gtx) case 2: return s.inputField.Layout(gtx) case 3: return errorMessage.Layout(gtx) default: return hint.Layout(gtx) } }) frameEvent.Frame(&s.ops) return shared.NewState{} } func (s *StateEnterTime) handleQuitEvent() shared.NewState { return shared.NewState{ExitCode: shared.ErrExitOk} } func (s *StateEnterTime) handleTimeEntered(stamp time.Time) shared.NewState { // TODO: Switch to countdown state once that is implemented return shared.NewState{ NextState: &StateCountdown{TargetTime: stamp}, } }