mirror of
https://github.com/libretro/ludo.git
synced 2026-11-10 03:01:29 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a1f24ce8f | ||
|
|
5b3dbb744c | ||
|
|
ecc1443954 | ||
|
|
e1899e008b | ||
|
|
9deb43b9f5 | ||
|
|
522fd7cfab | ||
|
|
7992056855 | ||
|
|
d35ddec7e7 | ||
|
|
1db919b76b | ||
|
|
6282d8a613 | ||
|
|
22f684dafe | ||
|
|
5fec2ddce8 | ||
|
|
101342403d | ||
|
|
bdc2e8e9e4 | ||
|
|
52e295699c | ||
|
|
8752921579 | ||
|
|
75f4753b22 | ||
|
|
46b5996b70 | ||
|
|
c3e8ed35ca | ||
|
|
5052bc46cc | ||
|
|
b1ea7a9301 | ||
|
|
02f4466ffd | ||
|
|
247679e1ce | ||
|
|
285e3bff63 | ||
|
|
fc9710e383 |
No files matched your search
+11
-6
@@ -8,6 +8,9 @@ language: go
|
||||
go:
|
||||
- "1.11"
|
||||
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
|
||||
before_script:
|
||||
- GO_FILES=$(find . -iname '*.go' -type f | grep -v /vendor/)
|
||||
- go get github.com/golang/lint/golint
|
||||
@@ -21,7 +24,7 @@ matrix:
|
||||
before_install:
|
||||
- sudo apt-get install libopenal-dev xorg-dev -y --allow-unauthenticated
|
||||
script:
|
||||
- go get -t -v ./...
|
||||
- go get -v ./...
|
||||
- go test -v -race ./...
|
||||
- go vet ./...
|
||||
- megacheck ./...
|
||||
@@ -60,7 +63,7 @@ matrix:
|
||||
- brew update
|
||||
- brew install openal-soft
|
||||
script:
|
||||
- go get -t -v ./...
|
||||
- go get -v ./...
|
||||
- go test -v -race ./...
|
||||
- go vet ./...
|
||||
- megacheck ./...
|
||||
@@ -77,17 +80,19 @@ matrix:
|
||||
- 7z x openal-soft-1.19.0-bin.zip -o/c/Users/travis/openal-soft-1.19.0-bin
|
||||
- export CGO_CFLAGS="-I/c/Users/travis/openal-soft-1.19.0-bin/include/"
|
||||
- export CGO_LDFLAGS="-L/c/Users/travis/openal-soft-1.19.0-bin/build/Release/"
|
||||
- cp /c/ProgramData/chocolatey/lib/mingw/tools/install/mingw64/x86_64-w64-mingw32/lib/libwinpthread* .
|
||||
- cp /c/Users/travis/openal-soft-1.19.0-bin/build/Release/OpenAL32.* .
|
||||
script:
|
||||
- go get -t -v ./...
|
||||
#- go test -v -race ./...
|
||||
- go get -v ./...
|
||||
#- go test -v ./...
|
||||
#- go vet ./...
|
||||
- megacheck ./...
|
||||
- gocyclo -over 19 $GO_FILES
|
||||
- golint -set_exit_status $(go list ./...)
|
||||
- go build -v
|
||||
- go build -v -ldflags '-H=windowsgui'
|
||||
before_deploy:
|
||||
- if [[ -n "$TRAVIS_TAG" ]]; then mkdir -p ./Ludo-Windows-x86_64-${TRAVIS_TAG:1}/; fi
|
||||
- if [[ -n "$TRAVIS_TAG" ]]; then cp /c/Users/travis/openal-soft-1.19.0-bin/build/Release/OpenAL32.dll ./Ludo-Windows-x86_64-${TRAVIS_TAG:1}/; fi
|
||||
- if [[ -n "$TRAVIS_TAG" ]]; then cp *.dll ./Ludo-Windows-x86_64-${TRAVIS_TAG:1}/; fi
|
||||
- if [[ -n "$TRAVIS_TAG" ]]; then OS="Windows" ARCH=x86_64 VERSION=${TRAVIS_TAG:1} make zip; fi
|
||||
|
||||
deploy:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
APP = Ludo
|
||||
BUNDLENAME = $(APP)-$(OS)-$(ARCH)-$(VERSION)
|
||||
|
||||
CORES = atari800 fbalpha gambatte genesis_plus_gx handy mednafen_ngp mednafen_pce_fast mednafen_psx mednafen_saturn mednafen_supergrafx mednafen_vb mednafen_wswan mgba nestopia pcsx_rearmed picodrive prosystem snes9x stella vecx virtualjaguar
|
||||
CORES = atari800 fbalpha fceumm gambatte genesis_plus_gx handy mednafen_ngp mednafen_pce_fast mednafen_psx mednafen_saturn mednafen_supergrafx mednafen_vb mednafen_wswan mgba pcsx_rearmed picodrive prosystem snes9x stella vecx virtualjaguar
|
||||
|
||||
ifeq ($(OS), OSX)
|
||||
BUILDBOTURL=http://buildbot.libretro.com/nightly/apple/osx/$(ARCH)/latest
|
||||
|
||||
@@ -36,8 +36,8 @@ Setup openal headers and dll in mingw-w64 `include` and `lib` folders.
|
||||
|
||||
## Building
|
||||
|
||||
go get github.com/libretro/ludo
|
||||
cd $GOPATH/src/github.com/libretro/ludo
|
||||
git clone --recursive https://github.com/libretro/ludo.git
|
||||
cd ludo
|
||||
go build
|
||||
|
||||
## Running
|
||||
|
||||
@@ -44,6 +44,8 @@ func Init(rate int32) {
|
||||
audio.source = al.GenSources(1)[0]
|
||||
audio.buffers = al.GenBuffers(int(audio.numBuffers))
|
||||
audio.resPtr = audio.numBuffers
|
||||
audio.tmpBufPtr = 0
|
||||
audio.tmpBuf = [bufSize]byte{}
|
||||
|
||||
audio.source.SetGain(settings.Current.AudioVolume)
|
||||
}
|
||||
|
||||
+26
-18
@@ -25,13 +25,14 @@ import (
|
||||
// cyclic dependencies.
|
||||
type MenuInterface interface {
|
||||
ContextReset()
|
||||
UpdateOptions(*options.Options)
|
||||
}
|
||||
|
||||
var vid *video.Video
|
||||
var opts *options.Options
|
||||
var menu MenuInterface
|
||||
|
||||
// Options holds the settings for the current core
|
||||
var Options *options.Options
|
||||
|
||||
// Init is there mainly for dependency injection.
|
||||
// Call Init before calling other functions of this package.
|
||||
func Init(v *video.Video, m MenuInterface) {
|
||||
@@ -49,12 +50,13 @@ func Init(v *video.Video, m MenuInterface) {
|
||||
|
||||
// Load loads a libretro core
|
||||
func Load(sofile string) error {
|
||||
state.Global.CorePath = sofile
|
||||
|
||||
// In case the a core is already loaded, we need to close it properly
|
||||
// before loading the new core
|
||||
Unload()
|
||||
|
||||
// This must be set before the environment callback is called
|
||||
state.Global.CorePath = sofile
|
||||
|
||||
var err error
|
||||
state.Global.Core, err = libretro.Load(sofile)
|
||||
if err != nil {
|
||||
@@ -83,9 +85,8 @@ func Load(sofile string) error {
|
||||
}
|
||||
}
|
||||
|
||||
menu.UpdateOptions(opts)
|
||||
|
||||
log.Println("[Core]: Core loaded: " + si.LibraryName)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -121,17 +122,17 @@ func unzipGame(filename string) (string, int64, error) {
|
||||
}
|
||||
|
||||
// LoadGame loads a game. A core has to be loaded first.
|
||||
func LoadGame(filename string) error {
|
||||
func LoadGame(gamePath string) error {
|
||||
|
||||
// If we're loading a new game on the same core, save the RAM of the previous
|
||||
// game before closing it.
|
||||
if state.Global.GamePath != filename {
|
||||
if state.Global.GamePath != gamePath {
|
||||
UnloadGame()
|
||||
}
|
||||
|
||||
si := state.Global.Core.GetSystemInfo()
|
||||
|
||||
gi, err := getGameInfo(filename, si.BlockExtract)
|
||||
gi, err := getGameInfo(gamePath, si.BlockExtract)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -161,15 +162,18 @@ func LoadGame(filename string) error {
|
||||
|
||||
input.Init(vid, menu)
|
||||
audio.Init(int32(avi.Timing.SampleRate))
|
||||
if state.Global.AudioCb.SetState != nil {
|
||||
state.Global.AudioCb.SetState(true)
|
||||
if state.Global.Core.AudioCallback != nil {
|
||||
state.Global.Core.AudioCallback.SetState(true)
|
||||
}
|
||||
|
||||
state.Global.CoreRunning = true
|
||||
state.Global.MenuActive = false
|
||||
state.Global.GamePath = filename
|
||||
state.Global.GamePath = gamePath
|
||||
|
||||
log.Println("[Core]: Game loaded: " + filename)
|
||||
for p := uint(0); p < input.MaxPlayers; p++ {
|
||||
state.Global.Core.SetControllerPortDevice(p, libretro.DeviceJoypad)
|
||||
}
|
||||
|
||||
log.Println("[Core]: Game loaded: " + gamePath)
|
||||
savefiles.LoadSRAM()
|
||||
|
||||
return nil
|
||||
@@ -180,15 +184,19 @@ func Unload() {
|
||||
if state.Global.CoreRunning {
|
||||
UnloadGame()
|
||||
state.Global.Core.Deinit()
|
||||
state.Global.CorePath = ""
|
||||
state.Global.Core = nil
|
||||
}
|
||||
}
|
||||
|
||||
// UnloadGame unloads a game.
|
||||
func UnloadGame() {
|
||||
savefiles.SaveSRAM()
|
||||
state.Global.Core.UnloadGame()
|
||||
state.Global.GamePath = ""
|
||||
state.Global.CoreRunning = false
|
||||
if state.Global.CoreRunning {
|
||||
savefiles.SaveSRAM()
|
||||
state.Global.Core.UnloadGame()
|
||||
state.Global.GamePath = ""
|
||||
state.Global.CoreRunning = false
|
||||
}
|
||||
}
|
||||
|
||||
// getGameInfo opens a rom and return the libretro.GameInfo needed to launch it
|
||||
|
||||
+27
-29
@@ -7,7 +7,6 @@ import (
|
||||
|
||||
"github.com/go-gl/glfw/v3.2/glfw"
|
||||
"github.com/libretro/ludo/libretro"
|
||||
"github.com/libretro/ludo/options"
|
||||
"github.com/libretro/ludo/state"
|
||||
"github.com/libretro/ludo/utils"
|
||||
"github.com/libretro/ludo/video"
|
||||
@@ -28,8 +27,7 @@ func (m WindowMock) SwapBuffers() {}
|
||||
|
||||
type MenuMock struct{}
|
||||
|
||||
func (m MenuMock) ContextReset() {}
|
||||
func (m MenuMock) UpdateOptions(o *options.Options) {}
|
||||
func (m MenuMock) ContextReset() {}
|
||||
|
||||
func Test_coreLoad(t *testing.T) {
|
||||
state.Global.Verbose = true
|
||||
@@ -38,7 +36,7 @@ func Test_coreLoad(t *testing.T) {
|
||||
|
||||
Init(&video.Video{Window: &WindowMock{}}, &MenuMock{})
|
||||
|
||||
out := utils.CaptureOutput(func() { Load("testdata/uzem_libretro" + ext) })
|
||||
out := utils.CaptureOutput(func() { Load("testdata/vecx_libretro" + ext) })
|
||||
|
||||
t.Run("The core is loaded", func(t *testing.T) {
|
||||
if state.Global.Core == nil {
|
||||
@@ -48,12 +46,12 @@ func Test_coreLoad(t *testing.T) {
|
||||
|
||||
t.Run("Logs information about the loaded core", func(t *testing.T) {
|
||||
got := out
|
||||
want := `[Core]: Name: Uzem
|
||||
[Core]: Version: v2.0
|
||||
[Core]: Valid extensions: uze
|
||||
want := `[Core]: Name: VecX
|
||||
[Core]: Version: 1.2 42366f8
|
||||
[Core]: Valid extensions: bin|vec
|
||||
[Core]: Need fullpath: false
|
||||
[Core]: Block extract: false
|
||||
[Core]: Core loaded: Uzem
|
||||
[Core]: Core loaded: VecX
|
||||
`
|
||||
if got != want {
|
||||
t.Errorf("got = %v, want %v", got, want)
|
||||
@@ -79,52 +77,52 @@ func Test_getGameInfo(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "Returns the right path and size for an unzipped ROM",
|
||||
args: args{filename: "testdata/ZoomingSecretary.uze", blockExtract: false},
|
||||
args: args{filename: "testdata/Polar Rescue (USA).vec", blockExtract: false},
|
||||
want: &libretro.GameInfo{
|
||||
Path: "testdata/ZoomingSecretary.uze",
|
||||
Size: 61286,
|
||||
Path: "testdata/Polar Rescue (USA).vec",
|
||||
Size: 8192,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Returns the right path and size for a zipped ROM",
|
||||
args: args{filename: "testdata/ZoomingSecretary.zip", blockExtract: false},
|
||||
args: args{filename: "testdata/Polar Rescue (USA).zip", blockExtract: false},
|
||||
want: &libretro.GameInfo{
|
||||
Path: os.TempDir() + "/ZoomingSecretary.uze",
|
||||
Size: 61286,
|
||||
Path: os.TempDir() + "/Polar Rescue (USA).vec",
|
||||
Size: 8192,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Returns the right path and size for a zipped ROM with blockExtract",
|
||||
args: args{filename: "testdata/ZoomingSecretary.zip", blockExtract: true},
|
||||
args: args{filename: "testdata/Polar Rescue (USA).zip", blockExtract: true},
|
||||
want: &libretro.GameInfo{
|
||||
Path: "testdata/ZoomingSecretary.zip",
|
||||
Size: 25599,
|
||||
Path: "testdata/Polar Rescue (USA).zip",
|
||||
Size: 6829,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Returns the right path and size for a zipped ROM with blockExtract",
|
||||
args: args{filename: "testdata/ZoomingSecretary.zip", blockExtract: true},
|
||||
args: args{filename: "testdata/Polar Rescue (USA).zip", blockExtract: true},
|
||||
want: &libretro.GameInfo{
|
||||
Path: "testdata/ZoomingSecretary.zip",
|
||||
Size: 25599,
|
||||
Path: "testdata/Polar Rescue (USA).zip",
|
||||
Size: 6829,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Returns an error when a file doesn't exists",
|
||||
args: args{filename: "testdata/ZoomingSecretary2.zip", blockExtract: true},
|
||||
args: args{filename: "testdata/Polar Rescue (USA)2.zip", blockExtract: true},
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Doesn't attempt to unzip a file that has no .zip extension",
|
||||
args: args{filename: "testdata/ZoomingSecretary.uze", blockExtract: true},
|
||||
args: args{filename: "testdata/Polar Rescue (USA).vec", blockExtract: true},
|
||||
want: &libretro.GameInfo{
|
||||
Path: "testdata/ZoomingSecretary.uze",
|
||||
Size: 61286,
|
||||
Path: "testdata/Polar Rescue (USA).vec",
|
||||
Size: 8192,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
@@ -156,21 +154,21 @@ func Test_unzipGame(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "Should unzip to the right path",
|
||||
args: args{filename: "testdata/ZoomingSecretary.zip"},
|
||||
want: os.TempDir() + "/ZoomingSecretary.uze",
|
||||
want1: 61286,
|
||||
args: args{filename: "testdata/Polar Rescue (USA).zip"},
|
||||
want: os.TempDir() + "/Polar Rescue (USA).vec",
|
||||
want1: 8192,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Returns an error if the file is not a zip",
|
||||
args: args{filename: "testdata/ZoomingSecretary.uze"},
|
||||
args: args{filename: "testdata/Polar Rescue (USA).vec"},
|
||||
want: "",
|
||||
want1: 0,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Returns an error if the file doesn't exists",
|
||||
args: args{filename: "testdata/ZoomingSecretary2.zip"},
|
||||
args: args{filename: "testdata/Polar Rescue (USA)2.zip"},
|
||||
want: "",
|
||||
want1: 0,
|
||||
wantErr: true,
|
||||
|
||||
+7
-8
@@ -44,9 +44,9 @@ func environment(cmd uint32, data unsafe.Pointer) bool {
|
||||
case libretro.EnvironmentGetPerfInterface:
|
||||
state.Global.Core.BindPerfCallback(data, getTimeUsec)
|
||||
case libretro.EnvironmentSetFrameTimeCallback:
|
||||
state.Global.FrameTimeCb = libretro.SetFrameTimeCallback(data)
|
||||
state.Global.Core.SetFrameTimeCallback(data)
|
||||
case libretro.EnvironmentSetAudioCallback:
|
||||
state.Global.AudioCb = libretro.SetAudioCallback(data)
|
||||
state.Global.Core.SetAudioCallback(data)
|
||||
case libretro.EnvironmentGetCanDupe:
|
||||
libretro.SetBool(data, true)
|
||||
case libretro.EnvironmentSetPixelFormat:
|
||||
@@ -65,20 +65,19 @@ func environment(cmd uint32, data unsafe.Pointer) bool {
|
||||
vid.Window.SetShouldClose(true)
|
||||
case libretro.EnvironmentGetVariable:
|
||||
variable := libretro.GetVariable(data)
|
||||
for i, v := range opts.Vars {
|
||||
for i, v := range Options.Vars {
|
||||
if variable.Key() == v.Key() {
|
||||
variable.SetValue(v.Choices()[opts.Choices[i]])
|
||||
variable.SetValue(v.Choices()[Options.Choices[i]])
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
case libretro.EnvironmentSetVariables:
|
||||
opts = options.New(libretro.GetVariables(data))
|
||||
menu.UpdateOptions(opts)
|
||||
Options = options.New(libretro.GetVariables(data))
|
||||
return true
|
||||
case libretro.EnvironmentGetVariableUpdate:
|
||||
libretro.SetBool(data, opts.Updated)
|
||||
opts.Updated = false
|
||||
libretro.SetBool(data, Options.Updated)
|
||||
Options.Updated = false
|
||||
return true
|
||||
default:
|
||||
//log.Println("[Env]: Not implemented:", cmd)
|
||||
|
||||
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,15 @@
|
||||
module github.com/libretro/ludo
|
||||
|
||||
require (
|
||||
github.com/disintegration/imaging v1.5.0
|
||||
github.com/fatih/structs v1.1.0
|
||||
github.com/go-gl/gl v0.0.0-20181026044259-55b76b7df9d2
|
||||
github.com/go-gl/glfw v0.0.0-20181213070059-819e8ce5125f
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
github.com/kivutar/glfont v0.0.0-20190119064645-3f4c07235fb3
|
||||
github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08
|
||||
github.com/rs/xid v1.2.1
|
||||
github.com/tanema/gween v0.0.0-20171018143308-f05d97ee644d
|
||||
golang.org/x/image v0.0.0-20190118043309-183bebdce1b2 // indirect
|
||||
golang.org/x/mobile v0.0.0-20190127143845-a42111704963
|
||||
)
|
||||
@@ -0,0 +1,22 @@
|
||||
github.com/disintegration/imaging v1.5.0 h1:uYqUhwNmLU4K1FN44vhqS4TZJRAA4RhBINgbQlKyGi0=
|
||||
github.com/disintegration/imaging v1.5.0/go.mod h1:9B/deIUIrliYkyMTuXJd6OUFLcrZ2tf+3Qlwnaf/CjU=
|
||||
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/go-gl/gl v0.0.0-20181026044259-55b76b7df9d2 h1:78Hza2KHn2PX1jdydQnffaU2A/xM0g3Nx1xmMdep9Gk=
|
||||
github.com/go-gl/gl v0.0.0-20181026044259-55b76b7df9d2/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
|
||||
github.com/go-gl/glfw v0.0.0-20181213070059-819e8ce5125f h1:7MsFMbSn8Lcw0blK4+NEOf8DuHoOBDhJsHz04yh13pM=
|
||||
github.com/go-gl/glfw v0.0.0-20181213070059-819e8ce5125f/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/kivutar/glfont v0.0.0-20190119064645-3f4c07235fb3 h1:6znfTZG6AjHjvkNjSzCt+xdINmWPLTZXSt4UxYYFbxo=
|
||||
github.com/kivutar/glfont v0.0.0-20190119064645-3f4c07235fb3/go.mod h1:yvRxgIrpErStul4gzj88N+5yL15ze5JUVqk6Ze4mU+I=
|
||||
github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08 h1:5MnxBC15uMxFv5FY/J/8vzyaBiArCOkMdFT9Jsw78iY=
|
||||
github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08/go.mod h1:NXg0ArsFk0Y01623LgUqoqcouGDB+PwCCQlrwrG6xJ4=
|
||||
github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc=
|
||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/tanema/gween v0.0.0-20171018143308-f05d97ee644d h1:Fn0HTkToOAphvU4wB3x7vtuWbnXhWPu9OsEvBfSyQsQ=
|
||||
github.com/tanema/gween v0.0.0-20171018143308-f05d97ee644d/go.mod h1:q58RIyFkCWKEi86xWwyOYLA9YZCY6myIgdN1Os5HECU=
|
||||
golang.org/x/image v0.0.0-20190118043309-183bebdce1b2 h1:FNSSV4jv1PrPsiM2iKGpqLPPgYACqh9Muav7Pollk1k=
|
||||
golang.org/x/image v0.0.0-20190118043309-183bebdce1b2/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
||||
golang.org/x/mobile v0.0.0-20190127143845-a42111704963 h1:2HSxAhImj2OpXsNjXSqfnv1xtqeCpDjwPB3o1DnQqKM=
|
||||
golang.org/x/mobile v0.0.0-20190127143845-a42111704963/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
+90
-1
@@ -3,7 +3,13 @@ package input
|
||||
import "github.com/libretro/ludo/libretro"
|
||||
|
||||
var joyBinds = map[string]joybinds{
|
||||
"Microsoft X-Box 360 pad": xbox360JoyBinds,
|
||||
"Microsoft X-Box 360 pad": xbox360JoyBinds,
|
||||
"Microsoft X-Box One S pad": xboxOneJoyBinds,
|
||||
"Sony Computer Entertainment Wireless Controller": ds4JoyBinds,
|
||||
"Sony Interactive Entertainment Wireless Controller": ds4JoyBinds,
|
||||
"Sony PLAYSTATION(R)3 Controller": ds3JoyBinds,
|
||||
"SHANWAN PS3 GamePad": ds3JoyBinds,
|
||||
"8Bitdo NES30 Pro 8Bitdo NES30 Pro": nes30proJoyBinds,
|
||||
}
|
||||
|
||||
// Joypad bindings fox Xbox360 pad on Linux
|
||||
@@ -26,3 +32,86 @@ var xbox360JoyBinds = joybinds{
|
||||
bind{axis, 2, 1, 0.5}: libretro.DeviceIDJoypadL2,
|
||||
bind{axis, 5, 1, 0.5}: libretro.DeviceIDJoypadR2,
|
||||
}
|
||||
|
||||
// Joypad bindings fox Xbox One pad on Linux
|
||||
var xboxOneJoyBinds = joybinds{
|
||||
bind{btn, 0, 0, 0}: libretro.DeviceIDJoypadB,
|
||||
bind{btn, 1, 0, 0}: libretro.DeviceIDJoypadA,
|
||||
bind{btn, 2, 0, 0}: libretro.DeviceIDJoypadY,
|
||||
bind{btn, 3, 0, 0}: libretro.DeviceIDJoypadX,
|
||||
bind{btn, 4, 0, 0}: libretro.DeviceIDJoypadL,
|
||||
bind{btn, 5, 0, 0}: libretro.DeviceIDJoypadR,
|
||||
bind{btn, 6, 0, 0}: libretro.DeviceIDJoypadSelect,
|
||||
bind{btn, 7, 0, 0}: libretro.DeviceIDJoypadStart,
|
||||
bind{btn, 8, 0, 0}: ActionMenuToggle,
|
||||
bind{btn, 9, 0, 0}: libretro.DeviceIDJoypadL3,
|
||||
bind{btn, 10, 0, 0}: libretro.DeviceIDJoypadR3,
|
||||
bind{axis, 6, -1, -0.5}: libretro.DeviceIDJoypadLeft,
|
||||
bind{axis, 6, 1, 0.5}: libretro.DeviceIDJoypadRight,
|
||||
bind{axis, 7, -1, -0.5}: libretro.DeviceIDJoypadUp,
|
||||
bind{axis, 7, 1, 0.5}: libretro.DeviceIDJoypadDown,
|
||||
bind{axis, 2, 1, 0.5}: libretro.DeviceIDJoypadL2,
|
||||
bind{axis, 5, 1, 0.5}: libretro.DeviceIDJoypadR2,
|
||||
}
|
||||
|
||||
// Joypad bindings fox DualShock 4 pad on Linux
|
||||
var ds4JoyBinds = joybinds{
|
||||
bind{btn, 0, 0, 0}: libretro.DeviceIDJoypadB,
|
||||
bind{btn, 1, 0, 0}: libretro.DeviceIDJoypadA,
|
||||
bind{btn, 2, 0, 0}: libretro.DeviceIDJoypadX,
|
||||
bind{btn, 3, 0, 0}: libretro.DeviceIDJoypadY,
|
||||
bind{btn, 4, 0, 0}: libretro.DeviceIDJoypadL,
|
||||
bind{btn, 5, 0, 0}: libretro.DeviceIDJoypadR,
|
||||
bind{btn, 6, 0, 0}: libretro.DeviceIDJoypadL2,
|
||||
bind{btn, 7, 0, 0}: libretro.DeviceIDJoypadR2,
|
||||
bind{btn, 8, 0, 0}: libretro.DeviceIDJoypadSelect,
|
||||
bind{btn, 9, 0, 0}: libretro.DeviceIDJoypadStart,
|
||||
bind{btn, 10, 0, 0}: ActionMenuToggle,
|
||||
bind{btn, 11, 0, 0}: libretro.DeviceIDJoypadL3,
|
||||
bind{btn, 12, 0, 0}: libretro.DeviceIDJoypadR3,
|
||||
bind{axis, 6, -1, -0.5}: libretro.DeviceIDJoypadLeft,
|
||||
bind{axis, 6, 1, 0.5}: libretro.DeviceIDJoypadRight,
|
||||
bind{axis, 7, -1, -0.5}: libretro.DeviceIDJoypadUp,
|
||||
bind{axis, 7, 1, 0.5}: libretro.DeviceIDJoypadDown,
|
||||
}
|
||||
|
||||
// Joypad bindings fox DualShock 3 pad on Linux
|
||||
var ds3JoyBinds = joybinds{
|
||||
bind{btn, 0, 0, 0}: libretro.DeviceIDJoypadB,
|
||||
bind{btn, 1, 0, 0}: libretro.DeviceIDJoypadA,
|
||||
bind{btn, 2, 0, 0}: libretro.DeviceIDJoypadX,
|
||||
bind{btn, 3, 0, 0}: libretro.DeviceIDJoypadY,
|
||||
bind{btn, 4, 0, 0}: libretro.DeviceIDJoypadL,
|
||||
bind{btn, 5, 0, 0}: libretro.DeviceIDJoypadR,
|
||||
bind{btn, 6, 0, 0}: libretro.DeviceIDJoypadL2,
|
||||
bind{btn, 7, 0, 0}: libretro.DeviceIDJoypadR2,
|
||||
bind{btn, 8, 0, 0}: libretro.DeviceIDJoypadSelect,
|
||||
bind{btn, 9, 0, 0}: libretro.DeviceIDJoypadStart,
|
||||
bind{btn, 10, 0, 0}: ActionMenuToggle,
|
||||
bind{btn, 11, 0, 0}: libretro.DeviceIDJoypadL3,
|
||||
bind{btn, 12, 0, 0}: libretro.DeviceIDJoypadR3,
|
||||
bind{btn, 13, 0, 0}: libretro.DeviceIDJoypadUp,
|
||||
bind{btn, 14, 0, 0}: libretro.DeviceIDJoypadDown,
|
||||
bind{btn, 15, 0, 0}: libretro.DeviceIDJoypadLeft,
|
||||
bind{btn, 16, 0, 0}: libretro.DeviceIDJoypadRight,
|
||||
}
|
||||
|
||||
// Joypad bindings fox the 8BITDO NES30 PRO GamePad (Wired) on Linux
|
||||
var nes30proJoyBinds = joybinds{
|
||||
bind{btn, 0, 0, 0}: libretro.DeviceIDJoypadA,
|
||||
bind{btn, 1, 0, 0}: libretro.DeviceIDJoypadB,
|
||||
bind{btn, 3, 0, 0}: libretro.DeviceIDJoypadX,
|
||||
bind{btn, 4, 0, 0}: libretro.DeviceIDJoypadY,
|
||||
bind{btn, 6, 0, 0}: libretro.DeviceIDJoypadL,
|
||||
bind{btn, 7, 0, 0}: libretro.DeviceIDJoypadR,
|
||||
bind{btn, 8, 0, 0}: libretro.DeviceIDJoypadL2,
|
||||
bind{btn, 9, 0, 0}: libretro.DeviceIDJoypadR2,
|
||||
bind{btn, 10, 0, 0}: libretro.DeviceIDJoypadSelect,
|
||||
bind{btn, 11, 0, 0}: libretro.DeviceIDJoypadStart,
|
||||
bind{btn, 13, 0, 0}: libretro.DeviceIDJoypadL3,
|
||||
bind{btn, 14, 0, 0}: libretro.DeviceIDJoypadR3,
|
||||
bind{axis, 6, -1, -0.5}: libretro.DeviceIDJoypadLeft,
|
||||
bind{axis, 6, 1, 0.5}: libretro.DeviceIDJoypadRight,
|
||||
bind{axis, 7, -1, -0.5}: libretro.DeviceIDJoypadUp,
|
||||
bind{axis, 7, 1, 0.5}: libretro.DeviceIDJoypadDown,
|
||||
}
|
||||
+7
-6
@@ -6,11 +6,12 @@ package input
|
||||
import (
|
||||
"github.com/go-gl/glfw/v3.2/glfw"
|
||||
"github.com/libretro/ludo/libretro"
|
||||
"github.com/libretro/ludo/notifications"
|
||||
ntf "github.com/libretro/ludo/notifications"
|
||||
"github.com/libretro/ludo/video"
|
||||
)
|
||||
|
||||
const numPlayers = 5
|
||||
// MaxPlayers is the maximum number of players to poll input for
|
||||
const MaxPlayers = 5
|
||||
|
||||
type joybinds map[bind]uint32
|
||||
|
||||
@@ -24,7 +25,7 @@ type bind struct {
|
||||
threshold float32
|
||||
}
|
||||
|
||||
type inputstate [numPlayers][ActionLast]bool
|
||||
type inputstate [MaxPlayers][ActionLast]bool
|
||||
|
||||
// Input state for all the players
|
||||
var (
|
||||
@@ -38,11 +39,11 @@ var (
|
||||
func joystickCallback(joy int, event int) {
|
||||
switch glfw.MonitorEvent(event) {
|
||||
case glfw.Connected:
|
||||
notifications.DisplayAndLog("Input", "Joystick #%d plugged: %s.", joy, glfw.GetJoystickName(glfw.Joystick(joy)))
|
||||
ntf.DisplayAndLog(ntf.Info, "Input", "Joystick #%d plugged: %s.", joy, glfw.GetJoystickName(glfw.Joystick(joy)))
|
||||
case glfw.Disconnected:
|
||||
notifications.DisplayAndLog("Input", "Joystick #%d unplugged.", joy)
|
||||
ntf.DisplayAndLog(ntf.Info, "Input", "Joystick #%d unplugged.", joy)
|
||||
default:
|
||||
notifications.DisplayAndLog("Input", "Joystick #%d unhandled event: %d.", joy, event)
|
||||
ntf.DisplayAndLog(ntf.Warning, "Input", "Joystick #%d unhandled event: %d.", joy, event)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,10 @@ void bridge_retro_set_video_refresh(void *f, void *callback) {
|
||||
((bool (*)(retro_video_refresh_t))f)((retro_video_refresh_t)callback);
|
||||
}
|
||||
|
||||
void bridge_retro_set_controller_port_device(void *f, unsigned port, unsigned device) {
|
||||
return ((void (*)(unsigned, unsigned))f)(port, device);
|
||||
}
|
||||
|
||||
void bridge_retro_set_input_poll(void *f, void *callback) {
|
||||
((bool (*)(retro_input_poll_t))f)((retro_input_poll_t)callback);
|
||||
}
|
||||
|
||||
+60
-10
@@ -20,6 +20,7 @@ void bridge_retro_get_system_info(void *f, struct retro_system_info *si);
|
||||
void bridge_retro_get_system_av_info(void *f, struct retro_system_av_info *si);
|
||||
bool bridge_retro_set_environment(void *f, void *callback);
|
||||
void bridge_retro_set_video_refresh(void *f, void *callback);
|
||||
void bridge_retro_set_controller_port_device(void *f, unsigned port, unsigned device);
|
||||
void bridge_retro_set_input_poll(void *f, void *callback);
|
||||
void bridge_retro_set_input_state(void *f, void *callback);
|
||||
void bridge_retro_set_audio_sample(void *f, void *callback);
|
||||
@@ -138,10 +139,53 @@ const (
|
||||
PixelFormatRGB565 = uint32(C.RETRO_PIXEL_FORMAT_RGB565)
|
||||
)
|
||||
|
||||
// DeviceJoypad represents the RetroPad. It is essentially a Super Nintendo
|
||||
// controller, but with additional L2/R2/L3/R3 buttons, similar to a
|
||||
// PS1 DualShock.
|
||||
const DeviceJoypad = uint32(C.RETRO_DEVICE_JOYPAD)
|
||||
// Libretro's fundamental device abstractions.
|
||||
//
|
||||
// Libretro's input system consists of some standardized device types,
|
||||
// such as a joypad (with/without analog), mouse, keyboard, lightgun
|
||||
// and a pointer.
|
||||
//
|
||||
// The functionality of these devices are fixed, and individual cores
|
||||
// map their own concept of a controller to libretro's abstractions.
|
||||
// This makes it possible for frontends to map the abstract types to a
|
||||
// real input device, and not having to worry about binding input
|
||||
// correctly to arbitrary controller layouts.
|
||||
const (
|
||||
// DeviceNone means that input is disabled.
|
||||
DeviceNone = uint32(C.RETRO_DEVICE_NONE)
|
||||
|
||||
// DeviceJoypad represents the RetroPad. It is essentially a Super Nintendo
|
||||
// controller, but with additional L2/R2/L3/R3 buttons, similar to a
|
||||
// PS1 DualShock.
|
||||
DeviceJoypad = uint32(C.RETRO_DEVICE_JOYPAD)
|
||||
|
||||
// DeviceMouse is a simple mouse, similar to Super Nintendo's mouse.
|
||||
// X and Y coordinates are reported relatively to last poll (poll callback).
|
||||
// It is up to the libretro implementation to keep track of where the mouse
|
||||
// pointer is supposed to be on the screen.
|
||||
// The frontend must make sure not to interfere with its own hardware
|
||||
// mouse pointer.
|
||||
DeviceMouse = uint32(C.RETRO_DEVICE_MOUSE)
|
||||
|
||||
// DeviceKeyboard lets one poll for raw key pressed.
|
||||
// It is poll based, so input callback will return with the current
|
||||
// pressed state.
|
||||
// For event/text based keyboard input, see
|
||||
// RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK.
|
||||
DeviceKeyboard = uint32(C.RETRO_DEVICE_KEYBOARD)
|
||||
|
||||
// DeviceLightgun X/Y coordinates are reported relatively to last poll,
|
||||
// similar to mouse. */
|
||||
DeviceLightgun = uint32(C.RETRO_DEVICE_LIGHTGUN)
|
||||
|
||||
// DeviceAnalog device is an extension to JOYPAD (RetroPad).
|
||||
// Similar to DualShock it adds two analog sticks.
|
||||
// This is treated as a separate device type as it returns values in the
|
||||
// full analog range of [-0x8000, 0x7fff]. Positive X axis is right.
|
||||
// Positive Y axis is down.
|
||||
// Only use ANALOG type when polling for analog values of the axes.
|
||||
DeviceAnalog = uint32(C.RETRO_DEVICE_ANALOG)
|
||||
)
|
||||
|
||||
// Buttons for the RetroPad (JOYPAD).
|
||||
// The placement of these is equivalent to placements on the
|
||||
@@ -243,6 +287,7 @@ func Load(sofile string) (*Core, error) {
|
||||
core.symRetroGetSystemAVInfo = core.DlSym("retro_get_system_av_info")
|
||||
core.symRetroSetEnvironment = core.DlSym("retro_set_environment")
|
||||
core.symRetroSetVideoRefresh = core.DlSym("retro_set_video_refresh")
|
||||
core.symRetroSetControllerPortDevice = core.DlSym("retro_set_controller_port_device")
|
||||
core.symRetroSetInputPoll = core.DlSym("retro_set_input_poll")
|
||||
core.symRetroSetInputState = core.DlSym("retro_set_input_state")
|
||||
core.symRetroSetAudioSample = core.DlSym("retro_set_audio_sample")
|
||||
@@ -428,6 +473,11 @@ func (core *Core) BindPerfCallback(data unsafe.Pointer, f getTimeUsecFunc) {
|
||||
cb.get_time_usec = (C.retro_perf_get_time_usec_t)(C.coreGetTimeUsec_cgo)
|
||||
}
|
||||
|
||||
// SetControllerPortDevice sets the device type attached to a controller port
|
||||
func (core *Core) SetControllerPortDevice(port uint, device uint32) {
|
||||
C.bridge_retro_set_controller_port_device(core.symRetroSetControllerPortDevice, C.unsigned(port), C.unsigned(device))
|
||||
}
|
||||
|
||||
//export coreEnvironment
|
||||
func coreEnvironment(cmd C.unsigned, data unsafe.Pointer) bool {
|
||||
if environment == nil {
|
||||
@@ -534,27 +584,27 @@ func SetString(data unsafe.Pointer, val string) {
|
||||
}
|
||||
|
||||
// SetFrameTimeCallback is an environment callback helper to set the FrameTimeCallback
|
||||
func SetFrameTimeCallback(data unsafe.Pointer) FrameTimeCallback {
|
||||
func (core *Core) SetFrameTimeCallback(data unsafe.Pointer) {
|
||||
c := *(*C.struct_retro_frame_time_callback)(data)
|
||||
ftc := FrameTimeCallback{}
|
||||
ftc := &FrameTimeCallback{}
|
||||
ftc.Reference = int64(c.reference)
|
||||
ftc.Callback = func(usec int64) {
|
||||
C.bridge_retro_frame_time_callback(c.callback, C.retro_usec_t(usec))
|
||||
}
|
||||
return ftc
|
||||
core.FrameTimeCallback = ftc
|
||||
}
|
||||
|
||||
// SetAudioCallback is an environment callback helper to set the AudioCallback
|
||||
func SetAudioCallback(data unsafe.Pointer) AudioCallback {
|
||||
func (core *Core) SetAudioCallback(data unsafe.Pointer) {
|
||||
c := *(*C.struct_retro_audio_callback)(data)
|
||||
auc := AudioCallback{}
|
||||
auc := &AudioCallback{}
|
||||
auc.Callback = func() {
|
||||
C.bridge_retro_audio_callback(c.callback)
|
||||
}
|
||||
auc.SetState = func(state bool) {
|
||||
C.bridge_retro_audio_set_state(c.set_state, C.bool(state))
|
||||
}
|
||||
return auc
|
||||
core.AudioCallback = auc
|
||||
}
|
||||
|
||||
// GetMemorySize returns the size of a region of the memory.
|
||||
|
||||
@@ -18,26 +18,30 @@ import "C"
|
||||
type Core struct {
|
||||
handle unsafe.Pointer
|
||||
|
||||
symRetroInit unsafe.Pointer
|
||||
symRetroDeinit unsafe.Pointer
|
||||
symRetroAPIVersion unsafe.Pointer
|
||||
symRetroGetSystemInfo unsafe.Pointer
|
||||
symRetroGetSystemAVInfo unsafe.Pointer
|
||||
symRetroSetEnvironment unsafe.Pointer
|
||||
symRetroSetVideoRefresh unsafe.Pointer
|
||||
symRetroSetInputPoll unsafe.Pointer
|
||||
symRetroSetInputState unsafe.Pointer
|
||||
symRetroSetAudioSample unsafe.Pointer
|
||||
symRetroSetAudioSampleBatch unsafe.Pointer
|
||||
symRetroRun unsafe.Pointer
|
||||
symRetroReset unsafe.Pointer
|
||||
symRetroLoadGame unsafe.Pointer
|
||||
symRetroUnloadGame unsafe.Pointer
|
||||
symRetroSerializeSize unsafe.Pointer
|
||||
symRetroSerialize unsafe.Pointer
|
||||
symRetroUnserialize unsafe.Pointer
|
||||
symRetroGetMemorySize unsafe.Pointer
|
||||
symRetroGetMemoryData unsafe.Pointer
|
||||
symRetroInit unsafe.Pointer
|
||||
symRetroDeinit unsafe.Pointer
|
||||
symRetroAPIVersion unsafe.Pointer
|
||||
symRetroGetSystemInfo unsafe.Pointer
|
||||
symRetroGetSystemAVInfo unsafe.Pointer
|
||||
symRetroSetEnvironment unsafe.Pointer
|
||||
symRetroSetVideoRefresh unsafe.Pointer
|
||||
symRetroSetControllerPortDevice unsafe.Pointer
|
||||
symRetroSetInputPoll unsafe.Pointer
|
||||
symRetroSetInputState unsafe.Pointer
|
||||
symRetroSetAudioSample unsafe.Pointer
|
||||
symRetroSetAudioSampleBatch unsafe.Pointer
|
||||
symRetroRun unsafe.Pointer
|
||||
symRetroReset unsafe.Pointer
|
||||
symRetroLoadGame unsafe.Pointer
|
||||
symRetroUnloadGame unsafe.Pointer
|
||||
symRetroSerializeSize unsafe.Pointer
|
||||
symRetroSerialize unsafe.Pointer
|
||||
symRetroUnserialize unsafe.Pointer
|
||||
symRetroGetMemorySize unsafe.Pointer
|
||||
symRetroGetMemoryData unsafe.Pointer
|
||||
|
||||
AudioCallback *AudioCallback
|
||||
FrameTimeCallback *FrameTimeCallback
|
||||
}
|
||||
|
||||
// DlSym loads a symbol from a dynamic library
|
||||
|
||||
@@ -9,26 +9,30 @@ import (
|
||||
type Core struct {
|
||||
handle *syscall.DLL
|
||||
|
||||
symRetroInit unsafe.Pointer
|
||||
symRetroDeinit unsafe.Pointer
|
||||
symRetroAPIVersion unsafe.Pointer
|
||||
symRetroGetSystemInfo unsafe.Pointer
|
||||
symRetroGetSystemAVInfo unsafe.Pointer
|
||||
symRetroSetEnvironment unsafe.Pointer
|
||||
symRetroSetVideoRefresh unsafe.Pointer
|
||||
symRetroSetInputPoll unsafe.Pointer
|
||||
symRetroSetInputState unsafe.Pointer
|
||||
symRetroSetAudioSample unsafe.Pointer
|
||||
symRetroSetAudioSampleBatch unsafe.Pointer
|
||||
symRetroRun unsafe.Pointer
|
||||
symRetroReset unsafe.Pointer
|
||||
symRetroLoadGame unsafe.Pointer
|
||||
symRetroUnloadGame unsafe.Pointer
|
||||
symRetroSerializeSize unsafe.Pointer
|
||||
symRetroSerialize unsafe.Pointer
|
||||
symRetroUnserialize unsafe.Pointer
|
||||
symRetroGetMemorySize unsafe.Pointer
|
||||
symRetroGetMemoryData unsafe.Pointer
|
||||
symRetroInit unsafe.Pointer
|
||||
symRetroDeinit unsafe.Pointer
|
||||
symRetroAPIVersion unsafe.Pointer
|
||||
symRetroGetSystemInfo unsafe.Pointer
|
||||
symRetroGetSystemAVInfo unsafe.Pointer
|
||||
symRetroSetEnvironment unsafe.Pointer
|
||||
symRetroSetVideoRefresh unsafe.Pointer
|
||||
symRetroSetControllerPortDevice unsafe.Pointer
|
||||
symRetroSetInputPoll unsafe.Pointer
|
||||
symRetroSetInputState unsafe.Pointer
|
||||
symRetroSetAudioSample unsafe.Pointer
|
||||
symRetroSetAudioSampleBatch unsafe.Pointer
|
||||
symRetroRun unsafe.Pointer
|
||||
symRetroReset unsafe.Pointer
|
||||
symRetroLoadGame unsafe.Pointer
|
||||
symRetroUnloadGame unsafe.Pointer
|
||||
symRetroSerializeSize unsafe.Pointer
|
||||
symRetroSerialize unsafe.Pointer
|
||||
symRetroUnserialize unsafe.Pointer
|
||||
symRetroGetMemorySize unsafe.Pointer
|
||||
symRetroGetMemoryData unsafe.Pointer
|
||||
|
||||
AudioCallback *AudioCallback
|
||||
FrameTimeCallback *FrameTimeCallback
|
||||
}
|
||||
|
||||
// DlSym loads a symbol from a dynamic library
|
||||
|
||||
@@ -4,12 +4,13 @@ import (
|
||||
"flag"
|
||||
"log"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/go-gl/glfw/v3.2/glfw"
|
||||
"github.com/libretro/ludo/core"
|
||||
"github.com/libretro/ludo/input"
|
||||
"github.com/libretro/ludo/menu"
|
||||
"github.com/libretro/ludo/notifications"
|
||||
ntf "github.com/libretro/ludo/notifications"
|
||||
"github.com/libretro/ludo/playlists"
|
||||
"github.com/libretro/ludo/scanner"
|
||||
"github.com/libretro/ludo/settings"
|
||||
@@ -23,30 +24,34 @@ func init() {
|
||||
}
|
||||
|
||||
func runLoop(vid *video.Video) {
|
||||
var currTime, prevTime time.Time
|
||||
for !vid.Window.ShouldClose() {
|
||||
currTime = time.Now()
|
||||
dt := float32(currTime.Sub(prevTime)) / 1000000000
|
||||
glfw.PollEvents()
|
||||
notifications.Process()
|
||||
ntf.Process(dt)
|
||||
if !state.Global.MenuActive {
|
||||
if state.Global.CoreRunning {
|
||||
state.Global.Core.Run()
|
||||
if state.Global.FrameTimeCb.Callback != nil {
|
||||
state.Global.FrameTimeCb.Callback(state.Global.FrameTimeCb.Reference)
|
||||
if state.Global.Core.FrameTimeCallback != nil {
|
||||
state.Global.Core.FrameTimeCallback.Callback(state.Global.Core.FrameTimeCallback.Reference)
|
||||
}
|
||||
if state.Global.AudioCb.Callback != nil {
|
||||
state.Global.AudioCb.Callback()
|
||||
if state.Global.Core.AudioCallback != nil {
|
||||
state.Global.Core.AudioCallback.Callback()
|
||||
}
|
||||
}
|
||||
vid.Render()
|
||||
} else {
|
||||
input.Poll()
|
||||
menu.Update()
|
||||
menu.Update(dt)
|
||||
vid.Render()
|
||||
menu.Render()
|
||||
menu.Render(dt)
|
||||
}
|
||||
input.ProcessActions()
|
||||
menu.RenderNotifications()
|
||||
glfw.SwapInterval(1)
|
||||
vid.Window.SwapBuffers()
|
||||
prevTime = currTime
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,7 +110,7 @@ func main() {
|
||||
if len(gamePath) > 0 {
|
||||
err := core.LoadGame(gamePath)
|
||||
if err != nil {
|
||||
notifications.DisplayAndLog("Menu", err.Error())
|
||||
ntf.DisplayAndLog(ntf.Error, "Menu", err.Error())
|
||||
} else {
|
||||
m.WarpToQuickMenu()
|
||||
}
|
||||
|
||||
+8
-7
@@ -7,14 +7,15 @@ import (
|
||||
|
||||
// Update takes care of calling the update method of the current scene.
|
||||
// Each scene has it's own input logic to allow a variety of navigation systems.
|
||||
func Update() {
|
||||
func Update(dt float32) {
|
||||
currentScene := menu.stack[len(menu.stack)-1]
|
||||
currentScene.update()
|
||||
currentScene.update(dt)
|
||||
}
|
||||
|
||||
func genericInput(list *entry) {
|
||||
if menu.inputCooldown > 0 {
|
||||
menu.inputCooldown--
|
||||
func genericInput(list *entry, dt float32) {
|
||||
menu.inputCooldown -= dt
|
||||
if menu.inputCooldown < 0 {
|
||||
menu.inputCooldown = 0
|
||||
}
|
||||
|
||||
if input.NewState[0][libretro.DeviceIDJoypadDown] && menu.inputCooldown == 0 {
|
||||
@@ -23,7 +24,7 @@ func genericInput(list *entry) {
|
||||
list.ptr = 0
|
||||
}
|
||||
genericAnimate(list)
|
||||
menu.inputCooldown = 10
|
||||
menu.inputCooldown = 0.15
|
||||
}
|
||||
|
||||
if input.NewState[0][libretro.DeviceIDJoypadUp] && menu.inputCooldown == 0 {
|
||||
@@ -32,7 +33,7 @@ func genericInput(list *entry) {
|
||||
list.ptr = len(list.children) - 1
|
||||
}
|
||||
genericAnimate(list)
|
||||
menu.inputCooldown = 10
|
||||
menu.inputCooldown = 0.15
|
||||
}
|
||||
|
||||
// OK
|
||||
|
||||
+5
-13
@@ -7,7 +7,6 @@ import (
|
||||
"math"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/libretro/ludo/options"
|
||||
"github.com/libretro/ludo/settings"
|
||||
"github.com/libretro/ludo/state"
|
||||
"github.com/libretro/ludo/utils"
|
||||
@@ -18,7 +17,6 @@ import (
|
||||
)
|
||||
|
||||
var vid *video.Video
|
||||
var opts *options.Options
|
||||
|
||||
// entry is a menu entry. It can also represent a scene.
|
||||
// The menu data is a tree of entries.
|
||||
@@ -54,7 +52,7 @@ type Scene interface {
|
||||
segueMount()
|
||||
segueNext()
|
||||
segueBack()
|
||||
update()
|
||||
update(dt float32)
|
||||
render()
|
||||
drawHintBar()
|
||||
Entry() *entry
|
||||
@@ -64,7 +62,7 @@ type Scene interface {
|
||||
type Menu struct {
|
||||
stack []Scene
|
||||
icons map[string]uint32
|
||||
inputCooldown int
|
||||
inputCooldown float32
|
||||
tweens map[*float32]*gween.Tween
|
||||
scroll float32
|
||||
ratio float32
|
||||
@@ -85,12 +83,12 @@ func updateTweens(dt float32) {
|
||||
}
|
||||
|
||||
// Render takes care of rendering the menu
|
||||
func Render() {
|
||||
menu.t += 0.1
|
||||
func Render(dt float32) {
|
||||
menu.t += float64(dt * 8)
|
||||
w, _ := vid.Window.GetFramebufferSize()
|
||||
menu.ratio = float32(w) / 1920
|
||||
|
||||
updateTweens(1.0 / 60.0)
|
||||
updateTweens(dt)
|
||||
|
||||
currentScreenIndex := len(menu.stack) - 1
|
||||
for i := 0; i <= currentScreenIndex+1; i++ {
|
||||
@@ -330,12 +328,6 @@ func fastForwardTweens() {
|
||||
updateTweens(10)
|
||||
}
|
||||
|
||||
// UpdateOptions updates the menu with the core options of the newly loaded
|
||||
// libretro core.
|
||||
func (menu *Menu) UpdateOptions(o *options.Options) {
|
||||
opts = o
|
||||
}
|
||||
|
||||
// WarpToQuickMenu loads the contextual menu for games that are launched from
|
||||
// the command line interface.
|
||||
func (menu *Menu) WarpToQuickMenu() {
|
||||
|
||||
+1
-2
@@ -117,9 +117,8 @@ func Test_buildExplorer(t *testing.T) {
|
||||
menu.stack = []Scene{}
|
||||
|
||||
exec := 0
|
||||
cbMock := func(str string) error {
|
||||
cbMock := func(str string) {
|
||||
exec++
|
||||
return nil
|
||||
}
|
||||
|
||||
dirActionMock := &entry{
|
||||
|
||||
+40
-7
@@ -2,22 +2,55 @@ package menu
|
||||
|
||||
import (
|
||||
"github.com/libretro/ludo/notifications"
|
||||
ntf "github.com/libretro/ludo/notifications"
|
||||
"github.com/libretro/ludo/video"
|
||||
colorful "github.com/lucasb-eyer/go-colorful"
|
||||
)
|
||||
|
||||
var severityFgColor = map[ntf.Severity]colorful.Color{
|
||||
ntf.Error: colorful.Hcl(40, 0.75, 0.85),
|
||||
ntf.Warning: colorful.Hcl(90, 0.75, 0.85),
|
||||
ntf.Success: colorful.Hcl(120, 0.75, 0.85),
|
||||
ntf.Info: colorful.Hcl(230, 0.75, 0.85),
|
||||
}
|
||||
|
||||
var severityBgColor = map[ntf.Severity]colorful.Color{
|
||||
ntf.Error: colorful.Hcl(40, 0.65, 0.1),
|
||||
ntf.Warning: colorful.Hcl(90, 0.65, 0.1),
|
||||
ntf.Success: colorful.Hcl(120, 0.65, 0.1),
|
||||
ntf.Info: colorful.Hcl(230, 0.65, 0.1),
|
||||
}
|
||||
|
||||
// RenderNotifications draws the list of notification messages on the viewport
|
||||
func RenderNotifications() {
|
||||
fbw, fbh := vid.Window.GetFramebufferSize()
|
||||
vid.Font.UpdateResolution(fbw, fbh)
|
||||
for i, n := range notifications.List() {
|
||||
a := float32(n.Frames) / 120
|
||||
var h float32 = 75
|
||||
stack := h
|
||||
for _, n := range notifications.List() {
|
||||
fading := float32(n.Duration) * 4
|
||||
if fading > 1 {
|
||||
fading = 1
|
||||
}
|
||||
offset := fading*h - h
|
||||
lw := vid.Font.Width(0.5*menu.ratio, n.Message)
|
||||
fg := severityFgColor[n.Severity]
|
||||
bg := severityBgColor[n.Severity]
|
||||
vid.DrawRoundedRect(
|
||||
25*menu.ratio, (float32(75+75*i)-46)*menu.ratio,
|
||||
lw+40*menu.ratio, 70*menu.ratio, 0.25,
|
||||
video.Color{R: 0.4, G: 0.4, B: 0, A: a})
|
||||
vid.Font.SetColor(1, 1, 0.85, a)
|
||||
25*menu.ratio,
|
||||
(stack+offset-46)*menu.ratio,
|
||||
lw+40*menu.ratio,
|
||||
70*menu.ratio,
|
||||
0.25,
|
||||
video.Color{R: float32(bg.R), G: float32(bg.G), B: float32(bg.B), A: fading},
|
||||
)
|
||||
vid.Font.SetColor(float32(fg.R), float32(fg.G), float32(fg.B), fading)
|
||||
vid.Font.Printf(
|
||||
45*menu.ratio, float32(75+75*i)*menu.ratio, 0.5*menu.ratio, n.Message)
|
||||
45*menu.ratio,
|
||||
(stack+offset)*menu.ratio,
|
||||
0.5*menu.ratio,
|
||||
n.Message,
|
||||
)
|
||||
stack += h + offset
|
||||
}
|
||||
}
|
||||
+13
-11
@@ -2,6 +2,8 @@ package menu
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/libretro/ludo/core"
|
||||
)
|
||||
|
||||
type screenCoreOptions struct {
|
||||
@@ -12,25 +14,25 @@ func buildCoreOptions() Scene {
|
||||
var list screenCoreOptions
|
||||
list.label = "Core Options"
|
||||
|
||||
for i, v := range opts.Vars {
|
||||
for i, v := range core.Options.Vars {
|
||||
i := i
|
||||
v := v
|
||||
list.children = append(list.children, entry{
|
||||
label: strings.Replace(v.Desc(), "%", "%%", -1),
|
||||
icon: "subsetting",
|
||||
stringValue: func() string {
|
||||
val := v.Choices()[opts.Choices[i]]
|
||||
val := v.Choices()[core.Options.Choices[i]]
|
||||
return strings.Replace(val, "%", "%%", -1)
|
||||
},
|
||||
incr: func(direction int) {
|
||||
opts.Choices[i] += direction
|
||||
if opts.Choices[i] < 0 {
|
||||
opts.Choices[i] = opts.NumChoices(i) - 1
|
||||
} else if opts.Choices[i] > opts.NumChoices(i)-1 {
|
||||
opts.Choices[i] = 0
|
||||
core.Options.Choices[i] += direction
|
||||
if core.Options.Choices[i] < 0 {
|
||||
core.Options.Choices[i] = core.Options.NumChoices(i) - 1
|
||||
} else if core.Options.Choices[i] > core.Options.NumChoices(i)-1 {
|
||||
core.Options.Choices[i] = 0
|
||||
}
|
||||
opts.Updated = true
|
||||
opts.Save()
|
||||
core.Options.Updated = true
|
||||
core.Options.Save()
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -56,8 +58,8 @@ func (s *screenCoreOptions) segueBack() {
|
||||
genericAnimate(&s.entry)
|
||||
}
|
||||
|
||||
func (s *screenCoreOptions) update() {
|
||||
genericInput(&s.entry)
|
||||
func (s *screenCoreOptions) update(dt float32) {
|
||||
genericInput(&s.entry, dt)
|
||||
}
|
||||
|
||||
func (s *screenCoreOptions) render() {
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/libretro/ludo/notifications"
|
||||
ntf "github.com/libretro/ludo/notifications"
|
||||
"github.com/libretro/ludo/settings"
|
||||
"github.com/libretro/ludo/utils"
|
||||
)
|
||||
@@ -26,13 +26,13 @@ func contains(f os.FileInfo, exts []string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func buildExplorer(path string, exts []string, cb func(string) error, dirAction *entry) Scene {
|
||||
func buildExplorer(path string, exts []string, cb func(string), dirAction *entry) Scene {
|
||||
var list screenExplorer
|
||||
list.label = "Explorer"
|
||||
|
||||
files, err := ioutil.ReadDir(path)
|
||||
if err != nil {
|
||||
notifications.DisplayAndLog("Menu", err.Error())
|
||||
ntf.DisplayAndLog(ntf.Error, "Menu", err.Error())
|
||||
}
|
||||
|
||||
// Display the special directory action entry.
|
||||
@@ -120,8 +120,8 @@ func (explorer *screenExplorer) segueBack() {
|
||||
genericAnimate(&explorer.entry)
|
||||
}
|
||||
|
||||
func (explorer *screenExplorer) update() {
|
||||
genericInput(&explorer.entry)
|
||||
func (explorer *screenExplorer) update(dt float32) {
|
||||
genericInput(&explorer.entry, dt)
|
||||
}
|
||||
|
||||
func (explorer *screenExplorer) render() {
|
||||
|
||||
+18
-10
@@ -6,7 +6,7 @@ import (
|
||||
"github.com/libretro/ludo/settings"
|
||||
|
||||
"github.com/libretro/ludo/core"
|
||||
"github.com/libretro/ludo/notifications"
|
||||
ntf "github.com/libretro/ludo/notifications"
|
||||
"github.com/libretro/ludo/state"
|
||||
)
|
||||
|
||||
@@ -39,12 +39,12 @@ func buildMainMenu() Scene {
|
||||
menu.stack = append(menu.stack, buildExplorer(
|
||||
settings.Current.CoresDirectory,
|
||||
[]string{".dll", ".dylib", ".so"},
|
||||
func(path string) error {
|
||||
func(path string) {
|
||||
err := core.Load(path)
|
||||
if err == nil {
|
||||
notifications.DisplayAndLog("Core", "Core loaded.")
|
||||
if err != nil {
|
||||
ntf.DisplayAndLog(ntf.Error, "Core", err.Error())
|
||||
}
|
||||
return err
|
||||
ntf.DisplayAndLog(ntf.Success, "Core", "Core loaded.")
|
||||
},
|
||||
nil,
|
||||
))
|
||||
@@ -57,9 +57,17 @@ func buildMainMenu() Scene {
|
||||
callbackOK: func() {
|
||||
if state.Global.Core != nil {
|
||||
list.segueNext()
|
||||
menu.stack = append(menu.stack, buildExplorer(usr.HomeDir, nil, core.LoadGame, nil))
|
||||
menu.stack = append(menu.stack, buildExplorer(usr.HomeDir, nil,
|
||||
func(path string) {
|
||||
err := core.LoadGame(path)
|
||||
if err != nil {
|
||||
ntf.DisplayAndLog(ntf.Error, "Core", err.Error())
|
||||
return
|
||||
}
|
||||
state.Global.MenuActive = false
|
||||
}, nil))
|
||||
} else {
|
||||
notifications.DisplayAndLog("Menu", "Please load a core first.")
|
||||
ntf.DisplayAndLog(ntf.Warning, "Menu", "Please load a core first.")
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -77,7 +85,7 @@ func buildMainMenu() Scene {
|
||||
label: "Help",
|
||||
icon: "subsetting",
|
||||
callbackOK: func() {
|
||||
notifications.DisplayAndLog("Menu", "Not implemented yet.")
|
||||
ntf.DisplayAndLog(ntf.Warning, "Menu", "Not implemented yet.")
|
||||
},
|
||||
})
|
||||
|
||||
@@ -110,8 +118,8 @@ func (main *screenMain) segueNext() {
|
||||
genericSegueNext(&main.entry)
|
||||
}
|
||||
|
||||
func (main *screenMain) update() {
|
||||
genericInput(&main.entry)
|
||||
func (main *screenMain) update(dt float32) {
|
||||
genericInput(&main.entry, dt)
|
||||
}
|
||||
|
||||
func (main *screenMain) render() {
|
||||
|
||||
+12
-7
@@ -6,7 +6,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/libretro/ludo/core"
|
||||
"github.com/libretro/ludo/notifications"
|
||||
ntf "github.com/libretro/ludo/notifications"
|
||||
"github.com/libretro/ludo/playlists"
|
||||
"github.com/libretro/ludo/settings"
|
||||
"github.com/libretro/ludo/state"
|
||||
@@ -56,31 +56,36 @@ func extractTags(name string) (string, []string) {
|
||||
}
|
||||
|
||||
func loadEntry(list *screenPlaylist, playlist, gamePath string) {
|
||||
if _, err := os.Stat(gamePath); os.IsNotExist(err) {
|
||||
ntf.DisplayAndLog(ntf.Error, "Menu", "Game not found.")
|
||||
return
|
||||
}
|
||||
corePath, err := settings.CoreForPlaylist(playlist)
|
||||
if err != nil {
|
||||
notifications.DisplayAndLog("Menu", err.Error())
|
||||
ntf.DisplayAndLog(ntf.Error, "Menu", err.Error())
|
||||
return
|
||||
}
|
||||
if _, err := os.Stat(corePath); os.IsNotExist(err) {
|
||||
notifications.DisplayAndLog("Menu", "Core not found.")
|
||||
ntf.DisplayAndLog(ntf.Error, "Menu", "Core not found.")
|
||||
return
|
||||
}
|
||||
if state.Global.CorePath != corePath {
|
||||
err := core.Load(corePath)
|
||||
if err != nil {
|
||||
notifications.DisplayAndLog("Menu", err.Error())
|
||||
ntf.DisplayAndLog(ntf.Error, "Menu", err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
if state.Global.GamePath != gamePath {
|
||||
err := core.LoadGame(gamePath)
|
||||
if err != nil {
|
||||
notifications.DisplayAndLog("Menu", err.Error())
|
||||
ntf.DisplayAndLog(ntf.Error, "Menu", err.Error())
|
||||
return
|
||||
}
|
||||
list.segueNext()
|
||||
menu.stack = append(menu.stack, buildQuickMenu())
|
||||
fastForwardTweens() // position the elements without animating
|
||||
state.Global.MenuActive = false
|
||||
} else {
|
||||
list.segueNext()
|
||||
menu.stack = append(menu.stack, buildQuickMenu())
|
||||
@@ -100,8 +105,8 @@ func (s *screenPlaylist) segueNext() {
|
||||
func (s *screenPlaylist) segueBack() {
|
||||
genericAnimate(&s.entry)
|
||||
}
|
||||
func (s *screenPlaylist) update() {
|
||||
genericInput(&s.entry)
|
||||
func (s *screenPlaylist) update(dt float32) {
|
||||
genericInput(&s.entry, dt)
|
||||
}
|
||||
func (s *screenPlaylist) render() {
|
||||
list := &s.entry
|
||||
|
||||
+8
-8
@@ -1,7 +1,7 @@
|
||||
package menu
|
||||
|
||||
import (
|
||||
"github.com/libretro/ludo/notifications"
|
||||
ntf "github.com/libretro/ludo/notifications"
|
||||
"github.com/libretro/ludo/savestates"
|
||||
"github.com/libretro/ludo/state"
|
||||
)
|
||||
@@ -37,9 +37,9 @@ func buildQuickMenu() Scene {
|
||||
callbackOK: func() {
|
||||
err := savestates.Save()
|
||||
if err != nil {
|
||||
notifications.DisplayAndLog("Menu", err.Error())
|
||||
ntf.DisplayAndLog(ntf.Error, "Menu", err.Error())
|
||||
} else {
|
||||
notifications.DisplayAndLog("Menu", "State saved.")
|
||||
ntf.DisplayAndLog(ntf.Success, "Menu", "State saved.")
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -50,10 +50,10 @@ func buildQuickMenu() Scene {
|
||||
callbackOK: func() {
|
||||
err := savestates.Load()
|
||||
if err != nil {
|
||||
notifications.DisplayAndLog("Menu", err.Error())
|
||||
ntf.DisplayAndLog(ntf.Error, "Menu", err.Error())
|
||||
} else {
|
||||
state.Global.MenuActive = false
|
||||
notifications.DisplayAndLog("Menu", "State loaded.")
|
||||
ntf.DisplayAndLog(ntf.Success, "Menu", "State loaded.")
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -63,7 +63,7 @@ func buildQuickMenu() Scene {
|
||||
icon: "screenshot",
|
||||
callbackOK: func() {
|
||||
vid.TakeScreenshot()
|
||||
notifications.DisplayAndLog("Menu", "Took a screenshot.")
|
||||
ntf.DisplayAndLog(ntf.Success, "Menu", "Took a screenshot.")
|
||||
},
|
||||
})
|
||||
|
||||
@@ -97,8 +97,8 @@ func (s *screenQuick) segueBack() {
|
||||
genericAnimate(&s.entry)
|
||||
}
|
||||
|
||||
func (s *screenQuick) update() {
|
||||
genericInput(&s.entry)
|
||||
func (s *screenQuick) update(dt float32) {
|
||||
genericInput(&s.entry, dt)
|
||||
}
|
||||
|
||||
func (s *screenQuick) render() {
|
||||
|
||||
+12
-7
@@ -8,7 +8,7 @@ import (
|
||||
"github.com/go-gl/glfw/v3.2/glfw"
|
||||
|
||||
"github.com/libretro/ludo/audio"
|
||||
"github.com/libretro/ludo/notifications"
|
||||
ntf "github.com/libretro/ludo/notifications"
|
||||
"github.com/libretro/ludo/settings"
|
||||
"github.com/libretro/ludo/state"
|
||||
"github.com/libretro/ludo/utils"
|
||||
@@ -45,15 +45,20 @@ func buildSettings() Scene {
|
||||
menu.stack = append(menu.stack, buildExplorer(
|
||||
f.Value().(string),
|
||||
nil,
|
||||
func(path string) error {
|
||||
func(path string) {
|
||||
var err error
|
||||
path, err = filepath.Abs(path)
|
||||
if err != nil {
|
||||
return err
|
||||
ntf.DisplayAndLog(ntf.Error, "Settings", err.Error())
|
||||
return
|
||||
}
|
||||
f.Set(path)
|
||||
notifications.DisplayAndLog("Settings", "%s set to %s", f.Tag("label"), f.Value().(string))
|
||||
return settings.Save()
|
||||
ntf.DisplayAndLog(ntf.Success, "Settings", "%s set to %s", f.Tag("label"), f.Value().(string))
|
||||
err = settings.Save()
|
||||
if err != nil {
|
||||
ntf.DisplayAndLog(ntf.Error, "Settings", err.Error())
|
||||
return
|
||||
}
|
||||
},
|
||||
&entry{
|
||||
label: "<Select this directory>",
|
||||
@@ -181,8 +186,8 @@ func (s *screenSettings) segueBack() {
|
||||
genericAnimate(&s.entry)
|
||||
}
|
||||
|
||||
func (s *screenSettings) update() {
|
||||
genericInput(&s.entry)
|
||||
func (s *screenSettings) update(dt float32) {
|
||||
genericInput(&s.entry, dt)
|
||||
}
|
||||
|
||||
func (s *screenSettings) render() {
|
||||
|
||||
+7
-7
@@ -55,9 +55,8 @@ func buildTabs() Scene {
|
||||
callbackOK: func() {
|
||||
usr, _ := user.Current()
|
||||
menu.stack = append(menu.stack, buildExplorer(usr.HomeDir, nil,
|
||||
func(path string) error {
|
||||
func(path string) {
|
||||
scanner.ScanDir(path, refreshTabs)
|
||||
return nil
|
||||
},
|
||||
&entry{
|
||||
label: "<Scan this directory>",
|
||||
@@ -246,9 +245,10 @@ func (tabs *screenTabs) segueNext() {
|
||||
menu.tweens[&menu.scroll] = gween.New(menu.scroll, menu.scroll+3030, 0.15, ease.OutSine)
|
||||
}
|
||||
|
||||
func (tabs *screenTabs) update() {
|
||||
if menu.inputCooldown > 0 {
|
||||
menu.inputCooldown--
|
||||
func (tabs *screenTabs) update(dt float32) {
|
||||
menu.inputCooldown -= dt
|
||||
if menu.inputCooldown < 0 {
|
||||
menu.inputCooldown = 0
|
||||
}
|
||||
|
||||
// Right
|
||||
@@ -258,7 +258,7 @@ func (tabs *screenTabs) update() {
|
||||
tabs.ptr = 0
|
||||
}
|
||||
tabs.animate()
|
||||
menu.inputCooldown = 10
|
||||
menu.inputCooldown = 0.15
|
||||
}
|
||||
|
||||
// Left
|
||||
@@ -268,7 +268,7 @@ func (tabs *screenTabs) update() {
|
||||
tabs.ptr = len(tabs.children) - 1
|
||||
}
|
||||
tabs.animate()
|
||||
menu.inputCooldown = 10
|
||||
menu.inputCooldown = 0.15
|
||||
}
|
||||
|
||||
// OK
|
||||
|
||||
@@ -5,14 +5,39 @@ import (
|
||||
"log"
|
||||
|
||||
"github.com/libretro/ludo/state"
|
||||
|
||||
"github.com/rs/xid"
|
||||
)
|
||||
|
||||
// Notification is a message that will be displayed on the screen during a number of frames.
|
||||
// Severity represents the severity of a notification message. It will affect
|
||||
// the color of the notification text in the UI.
|
||||
type Severity uint8
|
||||
|
||||
const (
|
||||
// Info is for informative message, when everything is fine
|
||||
Info Severity = iota
|
||||
// Success is for successful actions
|
||||
Success
|
||||
// Warning is also for informative messages, when something is not right
|
||||
// for example, if a menu entry has not been implemented.
|
||||
Warning
|
||||
// Error is for failed actions. For example, trying to load a game that
|
||||
// doesn't exists.
|
||||
Error
|
||||
)
|
||||
|
||||
// Notification is a message that will be displayed on the screen during a
|
||||
// certain time.
|
||||
type Notification struct {
|
||||
Message string
|
||||
Frames int
|
||||
ID xid.ID
|
||||
Severity Severity
|
||||
Message string
|
||||
Duration float32
|
||||
}
|
||||
|
||||
// Medium is the standard duration for a notification
|
||||
const Medium float32 = 4
|
||||
|
||||
var notifications []Notification
|
||||
|
||||
// List lists the current notifications.
|
||||
@@ -21,19 +46,22 @@ func List() []Notification {
|
||||
}
|
||||
|
||||
// Display creates a new notification.
|
||||
func Display(message string, frames int) int {
|
||||
func Display(severity Severity, message string, duration float32) xid.ID {
|
||||
id := xid.New()
|
||||
n := Notification{
|
||||
id,
|
||||
severity,
|
||||
message,
|
||||
frames,
|
||||
duration,
|
||||
}
|
||||
|
||||
notifications = append(notifications, n)
|
||||
|
||||
return len(notifications) - 1
|
||||
return id
|
||||
}
|
||||
|
||||
// DisplayAndLog creates a new notification and also logs the message to stdout.
|
||||
func DisplayAndLog(prefix, message string, vars ...interface{}) int {
|
||||
func DisplayAndLog(severity Severity, prefix, message string, vars ...interface{}) xid.ID {
|
||||
var msg string
|
||||
if len(vars) > 0 {
|
||||
msg = fmt.Sprintf(message, vars...)
|
||||
@@ -43,16 +71,16 @@ func DisplayAndLog(prefix, message string, vars ...interface{}) int {
|
||||
if state.Global.Verbose {
|
||||
log.Print("[" + prefix + "]: " + msg + "\n")
|
||||
}
|
||||
return Display(msg, 240)
|
||||
return Display(severity, msg, Medium)
|
||||
}
|
||||
|
||||
// Process iterates over the notifications, update them, delete the old ones.
|
||||
func Process() {
|
||||
func Process(dt float32) {
|
||||
deleted := 0
|
||||
for i := range notifications {
|
||||
j := i - deleted
|
||||
notifications[j].Frames--
|
||||
if notifications[j].Frames <= 0 {
|
||||
notifications[j].Duration -= dt
|
||||
if notifications[j].Duration <= 0 {
|
||||
notifications = append(notifications[:j], notifications[j+1:]...)
|
||||
deleted++
|
||||
}
|
||||
@@ -64,13 +92,25 @@ func Clear() {
|
||||
notifications = []Notification{}
|
||||
}
|
||||
|
||||
// find notification by unique ID
|
||||
func find(id xid.ID) *Notification {
|
||||
for i := range notifications {
|
||||
if notifications[i].ID == id {
|
||||
return ¬ifications[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update the message of a given notification. Also resets the delay before
|
||||
// disapearing.
|
||||
func Update(id int, message string) {
|
||||
if id < len(notifications) {
|
||||
notifications[id].Frames = 240
|
||||
notifications[id].Message = message
|
||||
} else {
|
||||
Display(message, 240)
|
||||
func Update(id xid.ID, severity Severity, message string) {
|
||||
n := find(id)
|
||||
if n == nil {
|
||||
return
|
||||
}
|
||||
|
||||
n.Duration = Medium
|
||||
n.Message = message
|
||||
n.Severity = severity
|
||||
}
|
||||
@@ -11,14 +11,14 @@ import (
|
||||
func Test_List(t *testing.T) {
|
||||
Clear()
|
||||
t.Run("Returns the notifications", func(t *testing.T) {
|
||||
Display("Test1", 240)
|
||||
Display("Test2", 240)
|
||||
Display("Test3", 240)
|
||||
nid1 := Display(Error, "Test1", Medium)
|
||||
nid2 := Display(Error, "Test2", Medium)
|
||||
nid3 := Display(Error, "Test3", Medium)
|
||||
got := List()
|
||||
want := []Notification{
|
||||
Notification{Message: "Test1", Frames: 240},
|
||||
Notification{Message: "Test2", Frames: 240},
|
||||
Notification{Message: "Test3", Frames: 240},
|
||||
Notification{ID: nid1, Severity: Error, Message: "Test1", Duration: Medium},
|
||||
Notification{ID: nid2, Severity: Error, Message: "Test2", Duration: Medium},
|
||||
Notification{ID: nid3, Severity: Error, Message: "Test3", Duration: Medium},
|
||||
}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("got = %v, want %v", got, want)
|
||||
@@ -29,14 +29,14 @@ func Test_List(t *testing.T) {
|
||||
func Test_Display(t *testing.T) {
|
||||
Clear()
|
||||
t.Run("Stacks notifications correctly", func(t *testing.T) {
|
||||
Display("Test1", 240)
|
||||
Display("Test2", 240)
|
||||
Display("Test3", 240)
|
||||
nid1 := Display(Error, "Test1", Medium)
|
||||
nid2 := Display(Info, "Test2", Medium)
|
||||
nid3 := Display(Warning, "Test3", Medium)
|
||||
got := notifications
|
||||
want := []Notification{
|
||||
Notification{Message: "Test1", Frames: 240},
|
||||
Notification{Message: "Test2", Frames: 240},
|
||||
Notification{Message: "Test3", Frames: 240},
|
||||
Notification{ID: nid1, Severity: Error, Message: "Test1", Duration: Medium},
|
||||
Notification{ID: nid2, Severity: Info, Message: "Test2", Duration: Medium},
|
||||
Notification{ID: nid3, Severity: Warning, Message: "Test3", Duration: Medium},
|
||||
}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("got = %v, want %v", got, want)
|
||||
@@ -47,7 +47,7 @@ func Test_Display(t *testing.T) {
|
||||
func Test_DisplayAndLog(t *testing.T) {
|
||||
Clear()
|
||||
t.Run("Format message properly", func(t *testing.T) {
|
||||
DisplayAndLog("Tests", "Joypad #%d loaded with name %s.", 3, "Foo")
|
||||
DisplayAndLog(Info, "Tests", "Joypad #%d loaded with name %s.", 3, "Foo")
|
||||
got := notifications[0].Message
|
||||
want := "Joypad #3 loaded with name Foo."
|
||||
if got != want {
|
||||
@@ -57,7 +57,7 @@ func Test_DisplayAndLog(t *testing.T) {
|
||||
|
||||
Clear()
|
||||
t.Run("Format simple message properly", func(t *testing.T) {
|
||||
DisplayAndLog("Tests", "Hello world.")
|
||||
DisplayAndLog(Info, "Tests", "Hello world.")
|
||||
got := notifications[0].Message
|
||||
want := "Hello world."
|
||||
if got != want {
|
||||
@@ -68,7 +68,7 @@ func Test_DisplayAndLog(t *testing.T) {
|
||||
Clear()
|
||||
t.Run("Logs to stdout if verbose", func(t *testing.T) {
|
||||
state.Global.Verbose = true
|
||||
got := utils.CaptureOutput(func() { DisplayAndLog("Test", "Joypad #%d loaded with name %s.", 3, "Foo") })
|
||||
got := utils.CaptureOutput(func() { DisplayAndLog(Info, "Test", "Joypad #%d loaded with name %s.", 3, "Foo") })
|
||||
want := "[Test]: Joypad #3 loaded with name Foo.\n"
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("got = %v, want %v", got, want)
|
||||
@@ -78,7 +78,7 @@ func Test_DisplayAndLog(t *testing.T) {
|
||||
Clear()
|
||||
t.Run("Logs nothing if not verbose", func(t *testing.T) {
|
||||
state.Global.Verbose = false
|
||||
got := utils.CaptureOutput(func() { DisplayAndLog("Test", "Joypad #%d loaded with name %s.", 3, "Foo") })
|
||||
got := utils.CaptureOutput(func() { DisplayAndLog(Info, "Test", "Joypad #%d loaded with name %s.", 3, "Foo") })
|
||||
want := ""
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("got = %v, want %v", got, want)
|
||||
@@ -89,13 +89,13 @@ func Test_DisplayAndLog(t *testing.T) {
|
||||
func Test_processNotifications(t *testing.T) {
|
||||
Clear()
|
||||
t.Run("Delete outdated notification", func(t *testing.T) {
|
||||
Display("Test1", 5)
|
||||
Display("Test1", 4)
|
||||
Display("Test1", 3)
|
||||
Display("Test2", 2)
|
||||
Display("Test3", 1)
|
||||
Process()
|
||||
Process()
|
||||
Display(Error, "Test1", 5)
|
||||
Display(Error, "Test1", 4)
|
||||
Display(Error, "Test1", 3)
|
||||
Display(Error, "Test2", 2)
|
||||
Display(Error, "Test3", 1)
|
||||
Process(1)
|
||||
Process(1)
|
||||
got := len(notifications)
|
||||
want := 3
|
||||
if got != want {
|
||||
@@ -107,9 +107,9 @@ func Test_processNotifications(t *testing.T) {
|
||||
func Test_Clear(t *testing.T) {
|
||||
Clear()
|
||||
t.Run("Empties the notification list", func(t *testing.T) {
|
||||
Display("Test1", 240)
|
||||
Display("Test2", 240)
|
||||
Display("Test3", 240)
|
||||
Display(Error, "Test1", Medium)
|
||||
Display(Error, "Test2", Medium)
|
||||
Display(Error, "Test3", Medium)
|
||||
Clear()
|
||||
got := len(notifications)
|
||||
want := 0
|
||||
@@ -118,3 +118,28 @@ func Test_Clear(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Update(t *testing.T) {
|
||||
Clear()
|
||||
t.Run("Is able to update a notification independantly", func(t *testing.T) {
|
||||
Display(Error, "Test1", Medium/2)
|
||||
nid2 := Display(Error, "Test2", Medium)
|
||||
nid3 := Display(Error, "Test3", Medium)
|
||||
|
||||
Process(0.5)
|
||||
Process(0.5)
|
||||
Process(0.5)
|
||||
Process(0.5)
|
||||
Update(nid2, Success, "Test4")
|
||||
Process(0.5)
|
||||
|
||||
got := List()
|
||||
want := []Notification{
|
||||
Notification{ID: nid2, Severity: Success, Message: "Test4", Duration: Medium - 0.5},
|
||||
Notification{ID: nid3, Severity: Error, Message: "Test3", Duration: Medium - 0.5*5},
|
||||
}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("got = %v, want %v", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -64,7 +64,8 @@ func LoadPlaylists() {
|
||||
// ExistsInPlaylist checks if a game is already in a playlist.
|
||||
func ExistsInPlaylist(lplpath, path string, CRC32 uint32) bool {
|
||||
for _, entry := range Playlists[lplpath] {
|
||||
if entry.Path == path || entry.CRC32 == CRC32 {
|
||||
// Be careful, sometimes we don't have a CRC32
|
||||
if entry.Path == path || (CRC32 != 0 && entry.CRC32 == CRC32) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
+5
-5
@@ -8,7 +8,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/libretro/ludo/notifications"
|
||||
ntf "github.com/libretro/ludo/notifications"
|
||||
"github.com/libretro/ludo/playlists"
|
||||
"github.com/libretro/ludo/rdb"
|
||||
"github.com/libretro/ludo/settings"
|
||||
@@ -67,7 +67,7 @@ func ScanDir(dir string, doneCb func()) {
|
||||
|
||||
// Scan scans a list of roms against the database
|
||||
func Scan(dir string, roms []string, games chan (rdb.Entry), doneCb func()) {
|
||||
nid := notifications.DisplayAndLog("Menu", "Scanning %s", dir)
|
||||
nid := ntf.DisplayAndLog(ntf.Info, "Menu", "Scanning %s", dir)
|
||||
for i, f := range roms {
|
||||
ext := filepath.Ext(f)
|
||||
switch ext {
|
||||
@@ -78,16 +78,16 @@ func Scan(dir string, roms []string, games chan (rdb.Entry), doneCb func()) {
|
||||
if rom.CRC32 > 0 {
|
||||
// Look for a matching game entry in the database
|
||||
state.Global.DB.FindByCRC(f, rom.Name, rom.CRC32, games)
|
||||
notifications.Update(nid, strconv.Itoa(i)+"/"+strconv.Itoa(len(roms))+" "+f)
|
||||
ntf.Update(nid, ntf.Info, strconv.Itoa(i)+"/"+strconv.Itoa(len(roms))+" "+f)
|
||||
}
|
||||
}
|
||||
z.Close()
|
||||
case ".cue":
|
||||
// Look for a matching game entry in the database
|
||||
state.Global.DB.FindByROMName(f, filepath.Base(f), 0, games)
|
||||
notifications.Update(nid, strconv.Itoa(i)+"/"+strconv.Itoa(len(roms))+" "+f)
|
||||
ntf.Update(nid, ntf.Info, strconv.Itoa(i)+"/"+strconv.Itoa(len(roms))+" "+f)
|
||||
}
|
||||
}
|
||||
notifications.Update(nid, "Done scanning.")
|
||||
ntf.Update(nid, ntf.Success, "Done scanning.")
|
||||
doneCb()
|
||||
}
|
||||
@@ -36,11 +36,11 @@ func defaultSettings() Settings {
|
||||
"Microsoft - MSX2": "bluemsx_libretro",
|
||||
"NEC - PC Engine SuperGrafx": "mednafen_supergrafx_libretro",
|
||||
"NEC - PC Engine - TurboGrafx 16": "mednafen_pce_fast_libretro",
|
||||
"Nintendo - Family Computer Disk System": "nestopia_libretro",
|
||||
"Nintendo - Family Computer Disk System": "fceumm_libretro",
|
||||
"Nintendo - Game Boy Advance": "mgba_libretro",
|
||||
"Nintendo - Game Boy Color": "gambatte_libretro",
|
||||
"Nintendo - Game Boy": "gambatte_libretro",
|
||||
"Nintendo - Nintendo Entertainment System": "nestopia_libretro",
|
||||
"Nintendo - Nintendo Entertainment System": "fceumm_libretro",
|
||||
"Nintendo - Super Nintendo Entertainment System": "snes9x_libretro",
|
||||
"Nintendo - Virtual Boy": "mednafen_vb_libretro",
|
||||
"Sega - 32X": "picodrive_libretro",
|
||||
|
||||
@@ -91,5 +91,5 @@ func CoreForPlaylist(playlist string) (string, error) {
|
||||
if c != "" {
|
||||
return filepath.Join(Current.CoresDirectory, c+utils.CoreExt()), nil
|
||||
}
|
||||
return "", errors.New("Default core not set")
|
||||
return "", errors.New("default core not set")
|
||||
}
|
||||
+6
-8
@@ -10,14 +10,12 @@ import (
|
||||
// State is a type for the global state of the app
|
||||
type State struct {
|
||||
Core *libretro.Core // Current libretro core
|
||||
FrameTimeCb libretro.FrameTimeCallback
|
||||
AudioCb libretro.AudioCallback
|
||||
CoreRunning bool
|
||||
MenuActive bool // When set to true, will display the menu layer
|
||||
Verbose bool
|
||||
CorePath string // Path of the current libretro core
|
||||
GamePath string // Path of the current game
|
||||
DB rdb.DB
|
||||
CoreRunning bool // Should be true if a game or a gameless core is loaded
|
||||
MenuActive bool // When set to true, will display the menu layer
|
||||
Verbose bool // When set to true, will output a lots of logs
|
||||
CorePath string // Path of the current libretro core
|
||||
GamePath string // Path of the current game
|
||||
DB rdb.DB // The game database loaded on startup
|
||||
}
|
||||
|
||||
// Global state
|
||||
|
||||
@@ -29,6 +29,6 @@ void main() {
|
||||
vec2 halfRes = vec2(0.5*ratio, 0.5);
|
||||
float b = udRoundBox(fragTexCoord*vec2(ratio,1.0) - halfRes, halfRes, min(halfRes.x,halfRes.y)*radius);
|
||||
vec4 c = min(color, vec4(1.0, 1.0, 1.0, 1.0));
|
||||
COMPAT_FRAGCOLOR = vec4(c.r, c.g, c.b, min(c.a, 1.0-smoothstep(0.00001,0.01,b)));
|
||||
COMPAT_FRAGCOLOR = vec4(c.r, c.g, c.b, c.a * (1.0-smoothstep(0.00001,0.01,b)));
|
||||
}
|
||||
` + "\x00"
|
||||
Reference in new issue
Block a user