Compare commits

...
25 Commits
Author SHA1 Message Date
Jean-André Santoni 6a1f24ce8f Hide console on Windows 2019-02-05 00:51:05 +07:00
Jean-André Santoni 5b3dbb744c Revert "Generate an MSI for Windows (#137)"
This reverts commit 9deb43b9f5.
2019-02-05 00:50:27 +07:00
Jean-André Santoni ecc1443954 Delta timing (#136)
* [WIP] Delta timing

* Fix for windows

* Apply dt to cursor

* Apply dt to notifications

* Fix tests

* Code cleaning
2019-02-05 00:48:39 +07:00
Jean-André Santoni e1899e008b Fix rescanning cue files 2019-02-03 21:18:48 +07:00
Jean-André Santoni 9deb43b9f5 Generate an MSI for Windows (#137)
* Generate an MSI for Windows

* Various fixes

* Hide the console on Windows
2019-02-02 23:29:28 +07:00
Jean-André Santoni 522fd7cfab Attempts to fix the windows releases (#128)
* Add core testdata that will work on windows
* Try copying the libwinpthread-1.dll in the bundle
2019-01-31 15:46:43 +07:00
Jean-André Santoni 7992056855 Use fceumm for NES 2019-01-30 21:13:45 +07:00
Jean-André Santoni d35ddec7e7 Migrate to go modules (#131)
* Migrate to go modules

* Update README

* Try this

* -t is not needed anymore
2019-01-30 11:45:31 +07:00
Rob Loach 1db919b76b Add retro_set_controller_port_device() (#129)
* Add retro_set_controller_port_device()

* Fix callback

* Fix fceumm crashing

* Use libretro.h const

* Set device for all players
2019-01-29 18:28:27 +07:00
Jean-André Santoni 6282d8a613 Colored notifications (#124)
* Colored notifications

* Fix tests

* Severity enum

* Make notifications unique by ID

* Fix tests

* Test Update
2019-01-25 13:36:45 +07:00
Jean-André Santoni 22f684dafe Improve notifications fading effect (#123)
* Improve notifications fading effect

* Fade faster, and fade from the right height
2019-01-23 00:16:55 +07:00
Jean-André Santoni 5fec2ddce8 Prevent the quick menu to flash when loading a game (#122)
* Prevent the quick menu to flash when loading a game

* Unload game only if game is loaded

* Clear the audio buffer when launching a game

* Fix tests
2019-01-20 13:23:40 +07:00
Jean-André Santoni 101342403d Fix linter 2019-01-19 13:50:15 +07:00
Jean-André Santoni bdc2e8e9e4 8BITDO NES30 PRO Linux bindings 2019-01-19 13:38:55 +07:00
Jean-André Santoni 52e295699c Shanwan DS3 binding for Linux 2019-01-19 13:18:16 +07:00
Jean-André Santoni 8752921579 DS3 bindings for Linux 2019-01-19 13:14:58 +07:00
Jean-André Santoni 75f4753b22 Another DS4 Linux binding 2019-01-19 13:03:02 +07:00
Jean-André Santoni 46b5996b70 DS4 binding for Linux 2019-01-19 13:00:17 +07:00
Jean-André Santoni c3e8ed35ca XBox One S pad bindings for Linux 2019-01-19 12:40:38 +07:00
Jean-André Santoni 5052bc46cc Refactor core options 2019-01-19 11:37:14 +07:00
Jean-André Santoni b1ea7a9301 Error if a playlist entry points to not existing file 2019-01-18 22:55:39 +07:00
Jean-André Santoni 02f4466ffd Fixes core options loading 2019-01-18 22:48:22 +07:00
Jean-André Santoni 247679e1ce Move frametime callback to the libretro package 2019-01-18 22:02:28 +07:00
Jean-André Santoni 285e3bff63 Move audio callback to libretro package (#121) 2019-01-18 21:53:44 +07:00
Jean-André Santoni fc9710e383 Code cleaning 2019-01-18 21:06:44 +07:00
44 changed files with 590 additions and 275 deletions

No files matched your search

+11 -6
View File
@@ -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 -1
View File
@@ -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
+2 -2
View File
@@ -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
+2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+15
View File
@@ -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
)
+22
View File
@@ -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
View File
@@ -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
View File
@@ -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)
}
}
+4
View File
@@ -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
View File
@@ -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.
+24 -20
View File
@@ -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
+24 -20
View File
@@ -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
+14 -9
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 -5
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+57 -17
View File
@@ -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 &notifications[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
}
+51 -26
View File
@@ -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)
}
})
}
+2 -1
View File
@@ -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
View File
@@ -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()
}
+2 -2
View File
@@ -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",
+1 -1
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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"