diff --git a/assets b/assets index 8569e194..5eb1f9d0 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 8569e1940af77da08bbd62440f395932e32b2f8d +Subproject commit 5eb1f9d0f0019c9016dcd6b137678cae18feabb6 diff --git a/menu/scene_quick.go b/menu/scene_quick.go index 0eceaad5..c7d1fd4c 100644 --- a/menu/scene_quick.go +++ b/menu/scene_quick.go @@ -2,7 +2,6 @@ package menu import ( ntf "github.com/libretro/ludo/notifications" - "github.com/libretro/ludo/savestates" "github.com/libretro/ludo/state" ) @@ -32,29 +31,11 @@ func buildQuickMenu() Scene { }) list.children = append(list.children, entry{ - label: "Save State", - icon: "savestate", + label: "Savestates", + icon: "states", callbackOK: func() { - err := savestates.Save() - if err != nil { - ntf.DisplayAndLog(ntf.Error, "Menu", err.Error()) - } else { - ntf.DisplayAndLog(ntf.Success, "Menu", "State saved.") - } - }, - }) - - list.children = append(list.children, entry{ - label: "Load State", - icon: "loadstate", - callbackOK: func() { - err := savestates.Load() - if err != nil { - ntf.DisplayAndLog(ntf.Error, "Menu", err.Error()) - } else { - state.Global.MenuActive = false - ntf.DisplayAndLog(ntf.Success, "Menu", "State loaded.") - } + list.segueNext() + menu.stack = append(menu.stack, buildSavestates()) }, }) diff --git a/menu/scene_savestates.go b/menu/scene_savestates.go new file mode 100644 index 00000000..3d7b421e --- /dev/null +++ b/menu/scene_savestates.go @@ -0,0 +1,138 @@ +package menu + +import ( + "path/filepath" + "sort" + "strings" + + ntf "github.com/libretro/ludo/notifications" + "github.com/libretro/ludo/savestates" + "github.com/libretro/ludo/settings" + "github.com/libretro/ludo/state" + "github.com/libretro/ludo/utils" + "github.com/libretro/ludo/video" +) + +type screenSavestates struct { + entry +} + +func buildSavestates() Scene { + var list screenSavestates + list.label = "Savestates" + + list.children = append(list.children, entry{ + label: "Save State", + icon: "savestate", + callbackOK: func() { + vid.TakeScreenshot() + err := savestates.Save() + if err != nil { + ntf.DisplayAndLog(ntf.Error, "Menu", err.Error()) + } else { + menu.stack[len(menu.stack)-1] = buildSavestates() + fastForwardTweens() + ntf.DisplayAndLog(ntf.Success, "Menu", "State saved.") + } + }, + }) + + gameName := utils.Filename(state.Global.GamePath) + paths, _ := filepath.Glob(settings.Current.SavestatesDirectory + "/" + gameName + "@*.state") + sort.Sort(sort.Reverse(sort.StringSlice(paths))) + for _, path := range paths { + path := path + date := strings.Replace(utils.Filename(path), gameName+"@", "", 1) + list.children = append(list.children, entry{ + label: "Load " + date, + icon: "loadstate", + path: path, + callbackOK: func() { + err := savestates.Load(path) + if err != nil { + ntf.DisplayAndLog(ntf.Error, "Menu", err.Error()) + } else { + state.Global.MenuActive = false + ntf.DisplayAndLog(ntf.Success, "Menu", "State loaded.") + } + }, + }) + } + + list.segueMount() + + return &list +} + +func (s *screenSavestates) Entry() *entry { + return &s.entry +} + +func (s *screenSavestates) segueMount() { + genericSegueMount(&s.entry) +} + +func (s *screenSavestates) segueNext() { + genericSegueNext(&s.entry) +} + +func (s *screenSavestates) segueBack() { + genericAnimate(&s.entry) +} + +func (s *screenSavestates) update(dt float32) { + genericInput(&s.entry, dt) +} + +// Override rendering +func (s *screenSavestates) render() { + list := &s.entry + + _, h := vid.Window.GetFramebufferSize() + + for i, e := range list.children { + if e.yp < -0.1 || e.yp > 1.1 { + continue + } + + fontOffset := 64 * 0.7 * menu.ratio * 0.3 + + color := video.Color{R: 0, G: 0, B: 0, A: e.iconAlpha} + if state.Global.CoreRunning { + color = video.Color{R: 1, G: 1, B: 1, A: e.iconAlpha} + } + + if e.labelAlpha > 0 { + drawSavestateThumbnail( + list, i, + filepath.Join(settings.Current.ScreenshotsDirectory, utils.Filename(e.path)+".png"), + 680*menu.ratio-85*e.scale*menu.ratio, + float32(h)*e.yp-14*menu.ratio-64*e.scale*menu.ratio+fontOffset, + 170*menu.ratio, 128*menu.ratio, + e.scale, video.Color{R: 1, G: 1, B: 1, A: e.iconAlpha}, + ) + vid.DrawBorder( + 680*menu.ratio-85*e.scale*menu.ratio, + float32(h)*e.yp-14*menu.ratio-64*e.scale*menu.ratio+fontOffset, + 170*menu.ratio*e.scale, 128*menu.ratio*e.scale, 0.02/e.scale, + video.Color{R: color.R, G: color.G, B: color.B, A: e.iconAlpha}) + if i == 0 { + vid.DrawImage(menu.icons["savestate"], + 680*menu.ratio-64*e.scale*menu.ratio, + float32(h)*e.yp-14*menu.ratio-64*e.scale*menu.ratio+fontOffset, + 128*menu.ratio, 128*menu.ratio, + e.scale, video.Color{R: 1, G: 1, B: 1, A: e.iconAlpha}) + } + + vid.Font.SetColor(color.R, color.G, color.B, e.labelAlpha) + vid.Font.Printf( + 840*menu.ratio, + float32(h)*e.yp+fontOffset, + 0.6*menu.ratio, e.label) + } + } +} + +func (s *screenSavestates) drawHintBar() { + genericDrawHintBar() +} diff --git a/menu/thumbnail.go b/menu/thumbnail.go index 35953924..c95873ef 100644 --- a/menu/thumbnail.go +++ b/menu/thumbnail.go @@ -81,3 +81,17 @@ func drawThumbnail(list *entry, i int, system, gameName string, x, y, w, h, scal color, ) } + +func drawSavestateThumbnail(list *entry, i int, path string, x, y, w, h, scale float32, color video.Color) { + if list.children[i].thumbnail == 0 { + if _, err := os.Stat(path); !os.IsNotExist(err) { + list.children[i].thumbnail = video.NewImage(path) + } + } + + vid.DrawImage( + list.children[i].thumbnail, + x, y, w, h, scale, + color, + ) +} diff --git a/savestates/savestates.go b/savestates/savestates.go index 76900fb5..b0703222 100644 --- a/savestates/savestates.go +++ b/savestates/savestates.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "os" "path/filepath" + "time" "github.com/libretro/ludo/settings" "github.com/libretro/ludo/state" @@ -15,7 +16,8 @@ func name() string { name := filepath.Base(state.Global.GamePath) ext := filepath.Ext(name) name = name[0 : len(name)-len(ext)] - return name + ".state" + date := time.Now().Format("2006-01-02-15-04-05") + return name + "@" + date + ".state" } // Save the current state to the filesystem @@ -34,9 +36,8 @@ func Save() error { } // Load the state from the filesystem -func Load() error { +func Load(path string) error { s := state.Global.Core.SerializeSize() - path := filepath.Join(settings.Current.SavestatesDirectory, name()) bytes, err := ioutil.ReadFile(path) if err != nil { return err