// Package modal provides UI pop-up modals for Doodle. package modal import ( "git.kirsle.net/SketchyMaze/doodle/pkg/balance" "git.kirsle.net/SketchyMaze/doodle/pkg/cursor" "git.kirsle.net/SketchyMaze/doodle/pkg/keybind" "git.kirsle.net/SketchyMaze/doodle/pkg/modal/loadscreen" "git.kirsle.net/SketchyMaze/doodle/pkg/shmem" "git.kirsle.net/go/render" "git.kirsle.net/go/render/event" "git.kirsle.net/go/ui" ) // Package global variables. var ( ready bool // Has been initialized with a render.Engine current *Modal // Current modal object, nil if no modal active. engine render.Engine window render.Rect // cached window dimensions supervisor *ui.Supervisor screen *ui.Frame ) // Initialize the modal package. func Initialize(e render.Engine) { engine = e supervisor = ui.NewSupervisor() width, height := engine.WindowSize() window = render.NewRect(width, height) screen = ui.NewFrame("Modal Screen") screen.SetBackground(balance.ModalBackdrop) screen.Resize(window) screen.Compute(e) ready = true } // Reset the modal state (closing all modals). func Reset() { supervisor = nil current = nil } // Handled runs the modal manager's logic. Returns true if a modal // is presently active, to signal to Doodle not to run game logic. // // This function also returns true if the pkg/modal/loadscreen is // currently active. func Handled(ev *event.State) bool { // The loadscreen counts as a modal for this purpose. if loadscreen.IsActive() { // Pass in window resize events in case the user maximizes the window during loading. if ev.WindowResized { loadscreen.Resized() } return true } // Check if we have a modal currently active. if !ready || current == nil { return false } // Enter key submits the default button. if keybind.Enter(ev) && !current.force { current.Dismiss(true) return true } // Escape key cancels the modal. if keybind.Shutdown(ev) && current.cancelable && !current.force { current.Dismiss(false) return true } supervisor.Loop(ev) // Has the window changed size? size := render.NewRect(engine.WindowSize()) if size != window { window = size screen.Resize(window) } return true } // Draw the modal UI to the screen. func Draw() { if ready && current != nil { cursor.Current = cursor.NewPointer(engine) screen.Present(engine, render.Origin) supervisor.Present(engine) } } // Center the window on screen. func center(win *ui.Window) { var ( modSize = win.Size() width, height = shmem.CurrentRenderEngine.WindowSize() ) var moveTo = render.Point{ X: (width / 2) - (modSize.W / 2), Y: (height / 4) - (modSize.H / 2), } win.MoveTo(moveTo) // HACK: ideally the modal should auto-size itself, but currently // the body of the window juts out the right and bottom side by // a few pixels. Fix the underlying problem later, for now we // set the modal size to big enough to hide the problem. win.Children()[0].Resize(render.NewRect(modSize.W+12, modSize.H+12)) } // Modal is an instance of a modal, i.e. Alert or Confirm. type Modal struct { title string message string window *ui.Window callback func() cancelable bool // Escape key can cancel the modal force bool // Enter key can not close the modal (e.g. Wait) teardown func() // Optional teardown logic a modal can attach. } // WithTitle sets the title of the modal. func (m *Modal) WithTitle(title string) *Modal { m.title = title return m } // Then calls a function after the modal is answered. func (m *Modal) Then(f func()) *Modal { m.callback = f return m } // Dismiss the modal and optionally call the callback function. func (m *Modal) Dismiss(call bool) { Reset() if call && m.callback != nil { m.callback() } if m.teardown != nil { m.teardown() } }