diff --git a/confirmationdialog.go b/confirmationdialog.go new file mode 100644 index 0000000..413df4d --- /dev/null +++ b/confirmationdialog.go @@ -0,0 +1,140 @@ +package tins2020 + +import ( + "opslag.de/schobers/geom" + "opslag.de/schobers/zntg" + "opslag.de/schobers/zntg/ui" +) + +type confirmationDialog struct { + ui.Proxy + + active string + cancel ui.Button + confirm ui.Button + question ui.Paragraph + + userDecided ui.Events +} + +func newConfirmationDialog(caption, question string) *confirmationDialog { + dialog := &confirmationDialog{} + + dialog.active = "confirm" + dialog.cancel.Text = "Cancel" + dialog.cancel.ButtonClicked().AddHandler(func(ctx ui.Context, _ ui.ControlClickedArgs) { dialog.userCanceled(ctx) }) + dialog.cancel.Type = ui.ButtonTypeText + dialog.cancel.HoverColor = zntg.MustHexColor(`FFFFFF1F`) + dialog.confirm.Text = "OK" + dialog.confirm.ButtonClicked().AddHandler(func(ctx ui.Context, _ ui.ControlClickedArgs) { dialog.userConfirmed(ctx) }) + dialog.confirm.Type = ui.ButtonTypeText + dialog.confirm.HoverColor = zntg.MustHexColor(`FFFFFF1F`) + dialog.question.Text = question + dialog.updateActive() + + responses := ui.BuildStackPanel(ui.OrientationHorizontal, func(p *ui.StackPanel) { + p.AddChild(ui.FixedWidth(&dialog.cancel, 160)) + p.AddChild(ui.FixedWidth(&dialog.confirm, 160)) + }) + + content := &dialogBase{} + content.Background = zntg.MustHexColor(`#0000007F`) + content.Init(caption, ui.BuildStackPanel(ui.OrientationVertical, func(p *ui.StackPanel) { + p.AddChild(&dialog.question) + p.AddChild(responses) + })) + + dialog.Content = ui.Background(ui.BuildSpacing(content, func(s *ui.Spacing) { + s.Width = ui.Fixed(320) + s.Center() + }), zntg.MustHexColor(`#0000007F`)) + + return dialog +} + +func (d *confirmationDialog) toggleActive() { + if d.active == "confirm" { + d.active = "cancel" + } else { + d.active = "confirm" + } + d.updateActive() +} + +func (d *confirmationDialog) updateActive() { + d.cancel.Background = nil + d.confirm.Background = nil + switch d.active { + case "cancel": + d.cancel.Background = hoverTransparentColor + case "confirm": + d.confirm.Background = hoverTransparentColor + } +} + +func (d *confirmationDialog) userCanceled(ctx ui.Context) { + d.userDecided.Notify(ctx, false) +} + +func (d *confirmationDialog) userConfirmed(ctx ui.Context) { + d.userDecided.Notify(ctx, true) +} + +func (d *confirmationDialog) Handle(ctx ui.Context, e ui.Event) bool { + if d.Proxy.Handle(ctx, e) { + return true + } + switch e := e.(type) { + case *ui.MouseButtonDownEvent: + if e.Button == ui.MouseButtonRight { + d.userCanceled(ctx) + return true + } + case *ui.MouseMoveEvent: + if d.cancel.IsOver() { + d.active = "cancel" + } + if d.confirm.IsOver() { + d.active = "confirm" + } + d.updateActive() + case *ui.KeyDownEvent: + switch e.Key { + case ui.KeyEscape: + d.userCanceled(ctx) + return true + case ui.KeyEnter: + switch d.active { + case "cancel": + d.userCanceled(ctx) + case "confirm": + d.userConfirmed(ctx) + } + return true + case ui.KeyLeft: + d.toggleActive() + case ui.KeyRight: + d.toggleActive() + case ui.KeyTab: + d.toggleActive() + } + } + return false +} + +type dialogBase struct { + ui.StackPanel + + caption ui.Label + content ui.Proxy +} + +func (d *dialogBase) DesiredSize(ctx ui.Context, size geom.PointF32) geom.PointF32 { + return d.StackPanel.DesiredSize(ctx, size) +} + +func (d *dialogBase) Init(caption string, content ui.Control) { + d.caption.Text = caption + d.content.Content = content + d.Children = []ui.Control{&d.caption, &d.content} +} diff --git a/dialogs.go b/dialogs.go index e52bdad..6678c87 100644 --- a/dialogs.go +++ b/dialogs.go @@ -67,6 +67,20 @@ func (d *Dialogs) Hidden() { d.Proxy.Hidden() } +func (d *Dialogs) AskConfirmation(ctx ui.Context, caption, question string, confirm, cancel ui.EventFn) { + dialog := newConfirmationDialog(caption, question) + dialog.userDecided.AddHandler(func(ctx ui.Context, state interface{}) { + decision := state.(bool) + if decision { + confirm(ctx, nil) + } else { + cancel(ctx, nil) + } + d.Close(ctx) + }) + d.showDialog(ctx, dialog) +} + func (d *Dialogs) ShowIntro(ctx ui.Context) { d.showDialog(ctx, d.intro) } diff --git a/gamecontrols.go b/gamecontrols.go index b06d711..b458a9d 100644 --- a/gamecontrols.go +++ b/gamecontrols.go @@ -32,6 +32,36 @@ func NewGameControls(game *Game, dialogs *Dialogs) *GameControls { return &GameControls{game: game, dialogs: dialogs} } +func (c *GameControls) askUserBeforeLoad(ctx ui.Context) { + c.dialogs.AskConfirmation(ctx, + "Do you want to load?", + "You will lose any progress you made during this session.", + func(ui.Context, interface{}) { + c.game.Load(ctx) + c.updateFlowerControls() + }, + func(ui.Context, interface{}) {}) +} + +func (c *GameControls) askUserBeforeNew(ctx ui.Context) { + c.dialogs.AskConfirmation(ctx, + "Do you want to start a new game?", + "You will lose any progress you made during this session.", + func(ui.Context, interface{}) { + c.game.New(ctx) + c.updateFlowerControls() + }, + func(ui.Context, interface{}) {}) +} + +func (c *GameControls) askUserBeforeSave(ctx ui.Context) { + c.dialogs.AskConfirmation(ctx, + "Do you want to save?", + "Saving will overwrite any previous save game.", + func(ui.Context, interface{}) { c.game.Save() }, + func(ui.Context, interface{}) {}) +} + func (c *GameControls) Init(ctx ui.Context) { ctx.Overlays().AddOnTop(fpsOverlayName, &play.FPS{}, false) c.game.SpeedChanged().AddHandler(c.speedChanged) @@ -76,20 +106,14 @@ func (c *GameControls) Init(ctx ui.Context) { b.Disabled = true b.DisabledColor = zntg.MustHexColor("#AFAFAF") }), - NewIconButtonConfigure("control-save", func(ui.Context) { c.game.Save() }, func(b *IconButton) { - b.Tooltip = "Save game (overwrites previous save; no confirmation)" + NewIconButtonConfigure("control-save", c.askUserBeforeSave, func(b *IconButton) { + b.Tooltip = "Save game" }), - NewIconButtonConfigure("control-load", func(ctx ui.Context) { - c.game.Load(ctx) - c.updateFlowerControls() - }, func(b *IconButton) { - b.Tooltip = "Load last saved game (no confirmation)" + NewIconButtonConfigure("control-load", c.askUserBeforeLoad, func(b *IconButton) { + b.Tooltip = "Load last saved game" }), - NewIconButtonConfigure("control-new", func(ctx ui.Context) { - c.game.New(ctx) - c.updateFlowerControls() - }, func(b *IconButton) { - b.Tooltip = "Start new game (no confirmation)" + NewIconButtonConfigure("control-new", c.askUserBeforeNew, func(b *IconButton) { + b.Tooltip = "Start new game" }), NewIconButtonConfigure("control-information", c.dialogs.ShowIntro, func(b *IconButton) { b.Tooltip = "Show information/intro"