mirror of
https://github.com/libretro/ludo.git
synced 2026-11-11 23:42:02 -10:00
Compare commits
49
Commits
master
...
v0.16.2-altui
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ad9611123 | ||
|
|
bca097e333 | ||
|
|
189c56d008 | ||
|
|
fc332586a6 | ||
|
|
98966a71c7 | ||
|
|
e23af93404 | ||
|
|
437ca0fa92 | ||
|
|
ce142a4972 | ||
|
|
61f399ed68 | ||
|
|
1e98126d08 | ||
|
|
1483700d44 | ||
|
|
654bc37bd6 | ||
|
|
eb522a6115 | ||
|
|
7303547284 | ||
|
|
876a072fc2 | ||
|
|
dcb50a2c47 | ||
|
|
465ec1729f | ||
|
|
58f9aeef6b | ||
|
|
bf47149392 | ||
|
|
a0a36d55e7 | ||
|
|
013da42c81 | ||
|
|
b3a5cfcb38 | ||
|
|
be63145460 | ||
|
|
df3d86e92f | ||
|
|
669da8fa40 | ||
|
|
55040f528d | ||
|
|
20dc029003 | ||
|
|
3742fc21cf | ||
|
|
d0c2786900 | ||
|
|
4551641c87 | ||
|
|
784c76bf10 | ||
|
|
75582663c3 | ||
|
|
fce5b93d2c | ||
|
|
2a07b39372 | ||
|
|
6f1655e5f5 | ||
|
|
aa41965711 | ||
|
|
c2945fa6e0 | ||
|
|
117417f9d5 | ||
|
|
c3b23e756a | ||
|
|
8087e65cd7 | ||
|
|
cab234a1ff | ||
|
|
3cf76e948a | ||
|
|
ba40c54544 | ||
|
|
2a6433045e | ||
|
|
0ecad7cf3c | ||
|
|
1011d9975f | ||
|
|
9c516b7fdf | ||
|
|
1b82ca1aae | ||
|
|
5d7a07479c |
No files matched your search
+1
-1
Submodule assets updated: e72d3737fb...98b3c0a088.
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/go-gl/glfw/v3.3/glfw"
|
||||
"github.com/libretro/ludo/audio"
|
||||
"github.com/libretro/ludo/core"
|
||||
"github.com/libretro/ludo/dat"
|
||||
"github.com/libretro/ludo/history"
|
||||
"github.com/libretro/ludo/input"
|
||||
"github.com/libretro/ludo/menu"
|
||||
@@ -19,6 +20,7 @@ import (
|
||||
"github.com/libretro/ludo/scanner"
|
||||
"github.com/libretro/ludo/settings"
|
||||
"github.com/libretro/ludo/state"
|
||||
"github.com/libretro/ludo/utils"
|
||||
"github.com/libretro/ludo/video"
|
||||
)
|
||||
|
||||
@@ -39,6 +41,9 @@ func runLoop(vid *video.Video, m *menu.Menu) {
|
||||
m.ProcessHotkeys()
|
||||
ntf.Process(dt)
|
||||
vid.ResizeViewport()
|
||||
w, h := vid.Window.GetFramebufferSize()
|
||||
vid.Font.UpdateResolution(w, h)
|
||||
vid.BoldFont.UpdateResolution(w, h)
|
||||
m.UpdatePalette()
|
||||
if !state.MenuActive {
|
||||
if state.CoreRunning {
|
||||
@@ -123,7 +128,20 @@ func main() {
|
||||
if err != nil {
|
||||
ntf.DisplayAndLog(ntf.Error, "Menu", err.Error())
|
||||
} else {
|
||||
m.WarpToQuickMenu()
|
||||
scanner.ScanFile(gamePath, func(game dat.Game) {
|
||||
name := game.Name
|
||||
if name == "" {
|
||||
name = utils.FileName(gamePath)
|
||||
}
|
||||
history.Push(history.Game{
|
||||
Path: gamePath,
|
||||
Name: name,
|
||||
System: game.System,
|
||||
CorePath: state.CorePath,
|
||||
})
|
||||
history.Load()
|
||||
m.WarpToQuickMenu()
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
+15
-5
@@ -5,13 +5,23 @@ import (
|
||||
)
|
||||
|
||||
// Used to easily compose different hint bars based on the context.
|
||||
func stackHint(stack *float32, icon uint32, label string, h int) {
|
||||
func stackHintLeft(stack *float32, icon uint32, label string, h int) {
|
||||
menu.Font.SetColor(darkGrey)
|
||||
*stack += 30 * menu.ratio
|
||||
menu.DrawImage(icon, *stack, float32(h)-70*menu.ratio, 70*menu.ratio, 70*menu.ratio, 1.0, darkGrey)
|
||||
menu.DrawImage(icon, *stack, float32(h)-79*menu.ratio, 70*menu.ratio, 70*menu.ratio, 1.0, 0, darkGrey)
|
||||
*stack += 70 * menu.ratio
|
||||
menu.Font.Printf(*stack, float32(h)-23*menu.ratio, 0.4*menu.ratio, label)
|
||||
*stack += menu.Font.Width(0.4*menu.ratio, label)
|
||||
menu.Font.Printf(*stack, float32(h)-30*menu.ratio, 0.5*menu.ratio, label)
|
||||
*stack += menu.Font.Width(0.5*menu.ratio, label)
|
||||
*stack += 32 * menu.ratio
|
||||
}
|
||||
|
||||
// Used to easily compose different hint bars based on the context.
|
||||
func stackHintRight(stack *float32, icon uint32, label string, h int) {
|
||||
*stack -= menu.Font.Width(0.5*menu.ratio, label)
|
||||
menu.Font.SetColor(darkGrey)
|
||||
menu.Font.Printf(*stack, float32(h)-30*menu.ratio, 0.5*menu.ratio, label)
|
||||
*stack -= 70 * menu.ratio
|
||||
menu.DrawImage(icon, *stack, float32(h)-79*menu.ratio, 70*menu.ratio, 70*menu.ratio, 1.0, 0, darkGrey)
|
||||
*stack -= 32 * menu.ratio
|
||||
}
|
||||
|
||||
func hintIcons() (arrows, upDown, leftRight, a, b, x, y, start, slct, guide uint32) {
|
||||
|
||||
+13
-12
@@ -20,7 +20,7 @@ var (
|
||||
// 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 (m *Menu) Update(dt float32) {
|
||||
currentScene := m.stack[len(m.stack)-1]
|
||||
currentScene := m.stack[m.focus-1]
|
||||
currentScene.update(dt)
|
||||
}
|
||||
|
||||
@@ -67,22 +67,20 @@ func withRepeat() func(dt float32, pressed bool, f func()) {
|
||||
func genericInput(list *entry, dt float32) {
|
||||
// Down
|
||||
repeatDown(dt, input.NewState[0][libretro.DeviceIDJoypadDown] == 1, func() {
|
||||
list.ptr++
|
||||
if list.ptr >= len(list.children) {
|
||||
list.ptr = 0
|
||||
if list.ptr < len(list.children)-1 {
|
||||
list.ptr++
|
||||
audio.PlayEffect(audio.Effects["down"])
|
||||
genericAnimate(list)
|
||||
}
|
||||
audio.PlayEffect(audio.Effects["down"])
|
||||
genericAnimate(list)
|
||||
})
|
||||
|
||||
// Up
|
||||
repeatUp(dt, input.NewState[0][libretro.DeviceIDJoypadUp] == 1, func() {
|
||||
list.ptr--
|
||||
if list.ptr < 0 {
|
||||
list.ptr = len(list.children) - 1
|
||||
if list.ptr > 0 {
|
||||
list.ptr--
|
||||
audio.PlayEffect(audio.Effects["up"])
|
||||
genericAnimate(list)
|
||||
}
|
||||
audio.PlayEffect(audio.Effects["up"])
|
||||
genericAnimate(list)
|
||||
})
|
||||
|
||||
// OK
|
||||
@@ -122,7 +120,10 @@ func genericInput(list *entry, dt float32) {
|
||||
if len(menu.stack) > 1 {
|
||||
audio.PlayEffect(audio.Effects["cancel"])
|
||||
menu.stack[len(menu.stack)-2].segueBack()
|
||||
menu.stack = menu.stack[:len(menu.stack)-1]
|
||||
if len(menu.stack) > 2 {
|
||||
menu.stack = menu.stack[:len(menu.stack)-1]
|
||||
}
|
||||
menu.focus--
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+21
-6
@@ -16,6 +16,7 @@ var menu *Menu
|
||||
|
||||
// Menu is a type holding the menu state, the stack of scenes, tweens, etc
|
||||
type Menu struct {
|
||||
focus int // this is a hack to switch focus between top tabs and the other scenes
|
||||
stack []Scene
|
||||
icons map[string]uint32
|
||||
tweens Tweens
|
||||
@@ -40,6 +41,7 @@ func Init(v *video.Video) *Menu {
|
||||
menu.icons = map[string]uint32{}
|
||||
|
||||
menu.Push(buildTabs())
|
||||
menu.Push(buildHome())
|
||||
|
||||
menu.ContextReset()
|
||||
|
||||
@@ -50,6 +52,16 @@ func Init(v *video.Video) *Menu {
|
||||
// OK on a menu entry.
|
||||
func (m *Menu) Push(s Scene) {
|
||||
m.stack = append(m.stack, s)
|
||||
m.focus++
|
||||
}
|
||||
|
||||
func haveTransparentBackground() bool {
|
||||
for i := 0; i <= len(menu.stack)-1; i++ {
|
||||
if menu.stack[i].Entry().label == "Quick Menu" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Render takes care of rendering the menu
|
||||
@@ -64,11 +76,11 @@ func (m *Menu) Render(dt float32) {
|
||||
w, h := m.GetFramebufferSize()
|
||||
m.ratio = float32(w) / 1920
|
||||
|
||||
if state.CoreRunning {
|
||||
m.DrawRect(0, 0, float32(w), float32(h), 0, bgColor.Alpha(0.85))
|
||||
} else {
|
||||
m.DrawRect(0, 0, float32(w), float32(h), 0, bgColor)
|
||||
c := white
|
||||
if haveTransparentBackground() {
|
||||
c = white.Alpha(0.85)
|
||||
}
|
||||
m.DrawImage(menu.icons["bg"], 0, 0, float32(w), float32(h), 1, 0, c)
|
||||
|
||||
m.tweens.Update(dt)
|
||||
|
||||
@@ -80,7 +92,9 @@ func (m *Menu) Render(dt float32) {
|
||||
|
||||
m.stack[i].render()
|
||||
}
|
||||
m.stack[currentScreenIndex].drawHintBar()
|
||||
if m.focus-1 < len(m.stack) {
|
||||
m.stack[m.focus-1].drawHintBar()
|
||||
}
|
||||
}
|
||||
|
||||
// ContextReset uploads the UI images to the GPU.
|
||||
@@ -116,8 +130,9 @@ func (m *Menu) WarpToQuickMenu() {
|
||||
m.stack = []Scene{}
|
||||
m.Push(buildTabs())
|
||||
m.stack[0].segueNext()
|
||||
m.Push(buildMainMenu())
|
||||
m.Push(buildHome())
|
||||
m.stack[1].segueNext()
|
||||
m.Push(buildQuickMenu())
|
||||
m.tweens.FastForward()
|
||||
menu.focus = len(menu.stack)
|
||||
}
|
||||
+3
-3
@@ -15,9 +15,9 @@ import (
|
||||
func Test_WarpToQuickMenu(t *testing.T) {
|
||||
m := Init(&video.Video{})
|
||||
|
||||
t.Run("Starts with a single scene if no game is running", func(t *testing.T) {
|
||||
t.Run("Starts with 2 scenes if no game is running", func(t *testing.T) {
|
||||
got := len(menu.stack)
|
||||
want := 1
|
||||
want := 2
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("got = %v, want %v", got, want)
|
||||
}
|
||||
@@ -25,7 +25,7 @@ func Test_WarpToQuickMenu(t *testing.T) {
|
||||
|
||||
t.Run("Starts on the tabs scene if no game is running", func(t *testing.T) {
|
||||
got := menu.stack[0].Entry().label
|
||||
want := "Ludo"
|
||||
want := "Tabs"
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("got = %v, want %v", got, want)
|
||||
}
|
||||
|
||||
@@ -21,8 +21,6 @@ var severityBgColor = map[ntf.Severity]video.Color{
|
||||
|
||||
// RenderNotifications draws the list of notification messages on the viewport
|
||||
func (m *Menu) RenderNotifications() {
|
||||
fbw, fbh := m.GetFramebufferSize()
|
||||
m.Font.UpdateResolution(fbw, fbh)
|
||||
var h float32 = 75
|
||||
stack := h
|
||||
for _, n := range ntf.List() {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
var white = video.Color{R: 1, G: 1, B: 1, A: 1}
|
||||
var black = video.Color{R: 0, G: 0, B: 0, A: 1}
|
||||
|
||||
var blue = video.Color{R: 0.129, G: 0.441, B: 0.684, A: 1}
|
||||
var orange = video.Color{R: 0.8, G: 0.4, B: 0.1, A: 1}
|
||||
var cyan = video.Color{R: 0.8784, G: 1, B: 1, A: 1}
|
||||
var darkBlue = video.Color{R: 0.1, G: 0.1, B: 0.4, A: 1}
|
||||
|
||||
+128
-103
@@ -1,6 +1,8 @@
|
||||
package menu
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/libretro/ludo/state"
|
||||
"github.com/tanema/gween"
|
||||
"github.com/tanema/gween/ease"
|
||||
@@ -9,8 +11,13 @@ import (
|
||||
// entry is a menu entry. It can also represent a scene.
|
||||
// The menu data is a tree of entries.
|
||||
type entry struct {
|
||||
yp, scale float32
|
||||
width, margin float32
|
||||
alpha float32
|
||||
scale float32
|
||||
scroll float32
|
||||
y float32
|
||||
entryHeight float32
|
||||
height float32
|
||||
margin float32
|
||||
label, subLabel string
|
||||
path string // full path of the rom linked to the entry
|
||||
system string // name of the game system
|
||||
@@ -19,22 +26,19 @@ type entry struct {
|
||||
iconAlpha float32
|
||||
tagAlpha float32
|
||||
subLabelAlpha float32
|
||||
borderAlpha float32
|
||||
callbackOK func() // callback executed when user presses OK
|
||||
callbackX func() // callback executed when user presses X
|
||||
value func() interface{}
|
||||
stringValue func() string
|
||||
widget func(*entry) // widget draw callback used in settings
|
||||
incr func(int) // increment callback used in settings
|
||||
tags []string // flags extracted from game title
|
||||
thumbnail uint32 // thumbnail texture id
|
||||
gameName string // title of the game in db, used for thumbnails
|
||||
cursor struct {
|
||||
alpha float32
|
||||
yp float32
|
||||
}
|
||||
children []entry // children entries
|
||||
ptr int // index of the active child
|
||||
indexes []struct {
|
||||
widget func(*entry, *entry, int) // widget draw callback used in settings
|
||||
incr func(int) // increment callback used in settings
|
||||
tags []string // flags extracted from game title
|
||||
thumbnail uint32 // thumbnail texture id
|
||||
gameName string // title of the game in db, used for thumbnails
|
||||
children []entry // children entries
|
||||
ptr int // index of the active child
|
||||
indexes []struct {
|
||||
Char byte
|
||||
Index int
|
||||
}
|
||||
@@ -55,26 +59,12 @@ type Scene interface {
|
||||
|
||||
// genericSegueMount is the smooth transition of the menu entries first appearance
|
||||
func genericSegueMount(list *entry) {
|
||||
for i := range list.children {
|
||||
e := &list.children[i]
|
||||
|
||||
if i == list.ptr {
|
||||
e.yp = 0.5 + 0.3
|
||||
e.scale = 1.5
|
||||
} else if i < list.ptr {
|
||||
e.yp = 0.4 + 0.3 + 0.08*float32(i-list.ptr)
|
||||
e.scale = 0.5
|
||||
} else if i > list.ptr {
|
||||
e.yp = 0.6 + 0.3 + 0.08*float32(i-list.ptr)
|
||||
e.scale = 0.5
|
||||
}
|
||||
e.labelAlpha = 0
|
||||
e.iconAlpha = 0
|
||||
e.tagAlpha = 0
|
||||
e.subLabelAlpha = 0
|
||||
if list.entryHeight == 0 {
|
||||
list.entryHeight = 100
|
||||
}
|
||||
list.cursor.alpha = 0
|
||||
list.cursor.yp = 0.5 + 0.3
|
||||
|
||||
list.scroll = -float32(list.ptr) * list.entryHeight
|
||||
list.y = 300
|
||||
|
||||
genericAnimate(list)
|
||||
}
|
||||
@@ -85,90 +75,90 @@ func genericAnimate(list *entry) {
|
||||
e := &list.children[i]
|
||||
|
||||
// performance improvement
|
||||
// if math.Abs(float64(i-list.ptr)) > 6 && i > 6 && i < len(list.children)-6 {
|
||||
// continue
|
||||
// }
|
||||
if math.Abs(float64(i-list.ptr)) > 8 {
|
||||
continue
|
||||
}
|
||||
|
||||
var yp, tagAlpha, subLabelAlpha, scale float32
|
||||
var tagAlpha, subLabelAlpha float32
|
||||
if i == list.ptr {
|
||||
yp = 0.5
|
||||
tagAlpha = 1
|
||||
subLabelAlpha = 1
|
||||
scale = 1.5
|
||||
} else if i < list.ptr {
|
||||
yp = 0.4 + 0.08*float32(i-list.ptr)
|
||||
tagAlpha = 0
|
||||
subLabelAlpha = 0
|
||||
scale = 0.5
|
||||
} else if i > list.ptr {
|
||||
yp = 0.6 + 0.08*float32(i-list.ptr)
|
||||
tagAlpha = 0
|
||||
subLabelAlpha = 0
|
||||
scale = 0.5
|
||||
}
|
||||
|
||||
menu.tweens[&e.yp] = gween.New(e.yp, yp, 0.15, ease.OutSine)
|
||||
menu.tweens[&e.labelAlpha] = gween.New(e.labelAlpha, 1, 0.15, ease.OutSine)
|
||||
menu.tweens[&e.iconAlpha] = gween.New(e.iconAlpha, 1, 0.15, ease.OutSine)
|
||||
menu.tweens[&e.tagAlpha] = gween.New(e.tagAlpha, tagAlpha, 0.15, ease.OutSine)
|
||||
menu.tweens[&e.subLabelAlpha] = gween.New(e.subLabelAlpha, subLabelAlpha, 0.15, ease.OutSine)
|
||||
menu.tweens[&e.scale] = gween.New(e.scale, scale, 0.15, ease.OutSine)
|
||||
}
|
||||
menu.tweens[&list.cursor.alpha] = gween.New(list.cursor.alpha, 1, 0.15, ease.OutSine)
|
||||
menu.tweens[&list.cursor.yp] = gween.New(list.cursor.yp, 0.5, 0.15, ease.OutSine)
|
||||
|
||||
margin := 32
|
||||
containerHeight := float32(1080 - 88 - 270 - margin*2)
|
||||
contentHeight := float32(len(list.children)) * list.entryHeight
|
||||
|
||||
scroll := float32(0)
|
||||
if list.ptr >= 3 {
|
||||
scroll = -float32(list.ptr-3) * list.entryHeight
|
||||
}
|
||||
|
||||
if -scroll > contentHeight-containerHeight {
|
||||
scroll = -(contentHeight - containerHeight)
|
||||
}
|
||||
|
||||
if contentHeight < containerHeight {
|
||||
scroll = 0
|
||||
}
|
||||
|
||||
menu.tweens[&list.scroll] = gween.New(list.scroll, scroll, 0.15, ease.OutSine)
|
||||
menu.tweens[&list.y] = gween.New(list.y, 0, 0.15, ease.OutSine)
|
||||
menu.tweens[&list.alpha] = gween.New(list.alpha, 1, 0.15, ease.OutSine)
|
||||
}
|
||||
|
||||
// genericSegueNext is a smooth transition that fades out the current list
|
||||
// to leave room for the next list to appear
|
||||
func genericSegueNext(list *entry) {
|
||||
for i := range list.children {
|
||||
e := &list.children[i]
|
||||
|
||||
var yp, scale float32
|
||||
if i == list.ptr {
|
||||
yp = 0.5 - 0.3
|
||||
scale = 1.5
|
||||
} else if i < list.ptr {
|
||||
yp = 0.4 - 0.3 + 0.08*float32(i-list.ptr)
|
||||
scale = 0.5
|
||||
} else if i > list.ptr {
|
||||
yp = 0.6 - 0.3 + 0.08*float32(i-list.ptr)
|
||||
scale = 0.5
|
||||
// performance improvement
|
||||
if math.Abs(float64(i-list.ptr)) > 8 {
|
||||
continue
|
||||
}
|
||||
|
||||
menu.tweens[&e.yp] = gween.New(e.yp, yp, 0.15, ease.OutSine)
|
||||
e := &list.children[i]
|
||||
menu.tweens[&e.labelAlpha] = gween.New(e.labelAlpha, 0, 0.15, ease.OutSine)
|
||||
menu.tweens[&e.iconAlpha] = gween.New(e.iconAlpha, 0, 0.15, ease.OutSine)
|
||||
menu.tweens[&e.tagAlpha] = gween.New(e.tagAlpha, 0, 0.15, ease.OutSine)
|
||||
menu.tweens[&e.subLabelAlpha] = gween.New(e.subLabelAlpha, 0, 0.15, ease.OutSine)
|
||||
menu.tweens[&e.scale] = gween.New(e.scale, scale, 0.15, ease.OutSine)
|
||||
}
|
||||
menu.tweens[&list.cursor.alpha] = gween.New(list.cursor.alpha, 0, 0.15, ease.OutSine)
|
||||
menu.tweens[&list.cursor.yp] = gween.New(list.cursor.yp, 0.5-0.3, 0.15, ease.OutSine)
|
||||
menu.tweens[&list.alpha] = gween.New(list.alpha, 0, 0.15, ease.OutSine)
|
||||
menu.tweens[&list.y] = gween.New(list.y, -300, 0.15, ease.OutSine)
|
||||
}
|
||||
|
||||
// genericDrawCursor draws the blinking rectangular background of the active
|
||||
// menu entry
|
||||
func genericDrawCursor(list *entry) {
|
||||
w, h := menu.GetFramebufferSize()
|
||||
menu.DrawImage(menu.icons["arrow"],
|
||||
530*menu.ratio, float32(h)*list.cursor.yp-35*menu.ratio,
|
||||
70*menu.ratio, 70*menu.ratio, 1, cursorBg.Alpha(list.cursor.alpha))
|
||||
func genericDrawCursor(list *entry, i int) {
|
||||
w, _ := menu.Window.GetFramebufferSize()
|
||||
y := list.y + (270+32)*menu.ratio + list.scroll*menu.ratio + list.entryHeight*float32(i)*menu.ratio
|
||||
if menu.focus > 1 {
|
||||
blink := float32(math.Cos(menu.t))
|
||||
menu.DrawImage(
|
||||
menu.icons["selection"],
|
||||
360*menu.ratio-8*menu.ratio,
|
||||
y-8*menu.ratio,
|
||||
float32(w)-720*menu.ratio+16*menu.ratio,
|
||||
list.entryHeight*menu.ratio+16*menu.ratio,
|
||||
1, 0.15, white.Alpha(list.alpha-list.alpha*blink))
|
||||
}
|
||||
menu.DrawRect(
|
||||
550*menu.ratio, float32(h)*list.cursor.yp-50*menu.ratio,
|
||||
float32(w)-630*menu.ratio, 100*menu.ratio, 1, cursorBg.Alpha(list.cursor.alpha))
|
||||
}
|
||||
|
||||
// thumbnailDrawCursor draws the blinking rectangular background of the active
|
||||
// menu entry when there is a thumbnail
|
||||
func thumbnailDrawCursor(list *entry) {
|
||||
w, h := menu.GetFramebufferSize()
|
||||
menu.DrawImage(menu.icons["arrow"],
|
||||
500*menu.ratio, float32(h)*list.cursor.yp-50*menu.ratio,
|
||||
100*menu.ratio, 100*menu.ratio, 1, cursorBg.Alpha(list.cursor.alpha))
|
||||
menu.DrawRect(
|
||||
530*menu.ratio, float32(h)*list.cursor.yp-120*menu.ratio,
|
||||
float32(w)-630*menu.ratio, 240*menu.ratio, 0.2, cursorBg.Alpha(list.cursor.alpha))
|
||||
360*menu.ratio,
|
||||
y,
|
||||
float32(w)-720*menu.ratio,
|
||||
list.entryHeight*menu.ratio, 0.1,
|
||||
white.Alpha(list.alpha))
|
||||
}
|
||||
|
||||
// genericRender renders a vertical list of menu entries
|
||||
@@ -176,37 +166,70 @@ func thumbnailDrawCursor(list *entry) {
|
||||
func genericRender(list *entry) {
|
||||
w, h := menu.GetFramebufferSize()
|
||||
|
||||
genericDrawCursor(list)
|
||||
menu.BoldFont.SetColor(blue.Alpha(list.alpha))
|
||||
menu.BoldFont.Printf(
|
||||
360*menu.ratio,
|
||||
list.y*menu.ratio+230*menu.ratio,
|
||||
0.5*menu.ratio, list.label)
|
||||
|
||||
menu.ScissorStart(int32(530*menu.ratio), 0, int32(1310*menu.ratio), int32(h))
|
||||
menu.DrawRect(
|
||||
360*menu.ratio,
|
||||
list.y*menu.ratio+270*menu.ratio,
|
||||
float32(w)-720*menu.ratio,
|
||||
2*menu.ratio,
|
||||
0, lightGrey.Alpha(list.alpha),
|
||||
)
|
||||
|
||||
for _, e := range list.children {
|
||||
if e.yp < -0.1 || e.yp > 1.1 {
|
||||
menu.ScissorStart(
|
||||
int32(360*menu.ratio-8*menu.ratio), 0,
|
||||
int32(float32(w)-720*menu.ratio+16*menu.ratio), int32(h)-int32(272*menu.ratio+list.y*menu.ratio))
|
||||
|
||||
fontOffset := 12 * menu.ratio
|
||||
|
||||
for i, e := range list.children {
|
||||
// performance improvement
|
||||
if math.Abs(float64(i-list.ptr)) > 8 {
|
||||
continue
|
||||
}
|
||||
|
||||
fontOffset := 64 * 0.7 * menu.ratio * 0.3
|
||||
y := list.y*menu.ratio +
|
||||
(270+32)*menu.ratio +
|
||||
list.scroll*menu.ratio +
|
||||
list.entryHeight*float32(i)*menu.ratio +
|
||||
list.entryHeight/2*menu.ratio
|
||||
|
||||
menu.DrawRect(
|
||||
360*menu.ratio,
|
||||
y-1*menu.ratio+list.entryHeight/2*menu.ratio,
|
||||
float32(w)-720*menu.ratio,
|
||||
2*menu.ratio,
|
||||
0, lightGrey.Alpha(e.iconAlpha),
|
||||
)
|
||||
|
||||
if i == list.ptr {
|
||||
genericDrawCursor(list, i)
|
||||
}
|
||||
|
||||
menu.DrawImage(menu.icons[e.icon],
|
||||
610*menu.ratio-64*0.5*menu.ratio,
|
||||
float32(h)*e.yp-14*menu.ratio-64*0.5*menu.ratio+fontOffset,
|
||||
420*menu.ratio-64*0.35*menu.ratio,
|
||||
y-64*0.35*menu.ratio,
|
||||
128*menu.ratio, 128*menu.ratio,
|
||||
0.5, textColor.Alpha(e.iconAlpha))
|
||||
0.35, 0, black.Alpha(e.iconAlpha))
|
||||
|
||||
if e.labelAlpha > 0 {
|
||||
menu.Font.SetColor(textColor.Alpha(e.labelAlpha))
|
||||
menu.Font.SetColor(black.Alpha(e.labelAlpha))
|
||||
menu.Font.Printf(
|
||||
670*menu.ratio,
|
||||
float32(h)*e.yp+fontOffset,
|
||||
480*menu.ratio,
|
||||
y+fontOffset,
|
||||
0.5*menu.ratio, e.label)
|
||||
|
||||
if e.widget != nil {
|
||||
e.widget(&e)
|
||||
e.widget(list, &e, i)
|
||||
} else if e.stringValue != nil {
|
||||
lw := menu.Font.Width(0.5*menu.ratio, e.stringValue())
|
||||
menu.Font.Printf(
|
||||
float32(w)-lw-128*menu.ratio,
|
||||
float32(h)*e.yp+fontOffset,
|
||||
float32(w)-lw-400*menu.ratio,
|
||||
y+fontOffset,
|
||||
0.5*menu.ratio, e.stringValue())
|
||||
}
|
||||
}
|
||||
@@ -263,16 +286,18 @@ func askDeleteSavestateConfirmation(cb func()) {
|
||||
}
|
||||
|
||||
func genericDrawHintBar() {
|
||||
w, h := menu.GetFramebufferSize()
|
||||
menu.DrawRect(0, float32(h)-70*menu.ratio, float32(w), 70*menu.ratio, 0, lightGrey)
|
||||
w, h := menu.Window.GetFramebufferSize()
|
||||
menu.DrawRect(0, float32(h)-88*menu.ratio, float32(w), 88*menu.ratio, 0, white)
|
||||
menu.DrawRect(0, float32(h)-88*menu.ratio, float32(w), 2*menu.ratio, 0, lightGrey)
|
||||
|
||||
_, upDown, _, a, b, _, _, _, _, guide := hintIcons()
|
||||
|
||||
var stack float32
|
||||
lstack := float32(75) * menu.ratio
|
||||
rstack := float32(w) - 96*menu.ratio
|
||||
stackHintLeft(&lstack, upDown, "Navigate", h)
|
||||
stackHintRight(&rstack, a, "Ok", h)
|
||||
stackHintRight(&rstack, b, "Back", h)
|
||||
if state.CoreRunning {
|
||||
stackHint(&stack, guide, "RESUME", h)
|
||||
stackHintRight(&rstack, guide, "Resume", h)
|
||||
}
|
||||
stackHint(&stack, upDown, "NAVIGATE", h)
|
||||
stackHint(&stack, b, "BACK", h)
|
||||
stackHint(&stack, a, "OK", h)
|
||||
}
|
||||
@@ -77,15 +77,22 @@ func (s *sceneCoreDiskControl) render() {
|
||||
|
||||
func (s *sceneCoreDiskControl) drawHintBar() {
|
||||
w, h := menu.GetFramebufferSize()
|
||||
menu.DrawRect(0, float32(h)-70*menu.ratio, float32(w), 70*menu.ratio, 0, lightGrey)
|
||||
menu.DrawRect(0, float32(h)-88*menu.ratio, float32(w), 88*menu.ratio, 0, white)
|
||||
menu.DrawRect(0, float32(h)-88*menu.ratio, float32(w), 2*menu.ratio, 0, lightGrey)
|
||||
|
||||
_, upDown, leftRight, _, b, _, _, _, _, guide := hintIcons()
|
||||
_, upDown, leftRight, a, b, _, _, _, _, guide := hintIcons()
|
||||
|
||||
var stack float32
|
||||
lstack := float32(75) * menu.ratio
|
||||
rstack := float32(w) - 96*menu.ratio
|
||||
list := menu.stack[len(menu.stack)-1].Entry()
|
||||
stackHintLeft(&lstack, upDown, "Navigate", h)
|
||||
if list.children[list.ptr].callbackOK != nil {
|
||||
stackHintRight(&rstack, a, "Set", h)
|
||||
} else {
|
||||
stackHintLeft(&lstack, leftRight, "Set", h)
|
||||
}
|
||||
stackHintRight(&rstack, b, "Back", h)
|
||||
if state.CoreRunning {
|
||||
stackHint(&stack, guide, "RESUME", h)
|
||||
stackHintRight(&rstack, guide, "Resume", h)
|
||||
}
|
||||
stackHint(&stack, upDown, "NAVIGATE", h)
|
||||
stackHint(&stack, b, "BACK", h)
|
||||
stackHint(&stack, leftRight, "SET", h)
|
||||
}
|
||||
@@ -81,15 +81,17 @@ func (s *sceneCoreOptions) render() {
|
||||
|
||||
func (s *sceneCoreOptions) drawHintBar() {
|
||||
w, h := menu.GetFramebufferSize()
|
||||
menu.DrawRect(0, float32(h)-70*menu.ratio, float32(w), 70*menu.ratio, 0, lightGrey)
|
||||
menu.DrawRect(0, float32(h)-88*menu.ratio, float32(w), 88*menu.ratio, 0, white)
|
||||
menu.DrawRect(0, float32(h)-88*menu.ratio, float32(w), 2*menu.ratio, 0, lightGrey)
|
||||
|
||||
_, upDown, leftRight, _, b, _, _, _, _, guide := hintIcons()
|
||||
|
||||
var stack float32
|
||||
lstack := float32(75) * menu.ratio
|
||||
rstack := float32(w) - 96*menu.ratio
|
||||
stackHintLeft(&lstack, upDown, "Navigate", h)
|
||||
stackHintRight(&rstack, leftRight, "Set", h)
|
||||
stackHintRight(&rstack, b, "Back", h)
|
||||
if state.CoreRunning {
|
||||
stackHint(&stack, guide, "RESUME", h)
|
||||
stackHintRight(&rstack, guide, "Resume", h)
|
||||
}
|
||||
stackHint(&stack, upDown, "NAVIGATE", h)
|
||||
stackHint(&stack, b, "BACK", h)
|
||||
stackHint(&stack, leftRight, "SET", h)
|
||||
}
|
||||
@@ -47,8 +47,8 @@ func (s *sceneDialog) update(dt float32) {
|
||||
// Cancel
|
||||
if input.Released[0][libretro.DeviceIDJoypadB] == 1 {
|
||||
audio.PlayEffect(audio.Effects["cancel"])
|
||||
menu.stack[len(menu.stack)-2].segueBack()
|
||||
menu.stack = menu.stack[:len(menu.stack)-1]
|
||||
menu.focus--
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ func (s *sceneDialog) render() {
|
||||
white,
|
||||
)
|
||||
|
||||
menu.Font.SetColor(orange)
|
||||
menu.Font.SetColor(blue)
|
||||
lw1 := menu.Font.Width(0.7*menu.ratio, s.title)
|
||||
menu.Font.Printf(fw/2-lw1/2, fh/2-120*menu.ratio+20*menu.ratio, 0.7*menu.ratio, s.title)
|
||||
menu.Font.SetColor(black)
|
||||
@@ -89,7 +89,7 @@ func (s *sceneDialog) render() {
|
||||
b,
|
||||
fw/2-width/2*menu.ratio+margin*menu.ratio,
|
||||
fh/2+height/2*menu.ratio-70*menu.ratio-margin*menu.ratio,
|
||||
70*menu.ratio, 70*menu.ratio, 1.0, darkGrey)
|
||||
70*menu.ratio, 70*menu.ratio, 1.0, 0, darkGrey)
|
||||
menu.Font.Printf(
|
||||
fw/2-width/2*menu.ratio+margin*menu.ratio+70*menu.ratio,
|
||||
fh/2+height/2*menu.ratio-23*menu.ratio-margin*menu.ratio,
|
||||
@@ -100,7 +100,7 @@ func (s *sceneDialog) render() {
|
||||
a,
|
||||
fw/2+width/2*menu.ratio-150*menu.ratio-margin*menu.ratio,
|
||||
fh/2+height/2*menu.ratio-70*menu.ratio-margin*menu.ratio,
|
||||
70*menu.ratio, 70*menu.ratio, 1.0, darkGrey)
|
||||
70*menu.ratio, 70*menu.ratio, 1.0, 0, darkGrey)
|
||||
menu.Font.Printf(
|
||||
fw/2+width/2*menu.ratio-150*menu.ratio-margin*menu.ratio+70*menu.ratio,
|
||||
fh/2+height/2*menu.ratio-23*menu.ratio-margin*menu.ratio,
|
||||
|
||||
@@ -109,7 +109,7 @@ func appendNode(list *sceneExplorer, fullPath string, name string, f os.FileInfo
|
||||
|
||||
func buildExplorer(path string, exts []string, cb func(string), dirAction *entry, prettifier Prettifier) Scene {
|
||||
var list sceneExplorer
|
||||
list.label = "Explorer"
|
||||
list.label = path
|
||||
|
||||
// Display the special directory action entry.
|
||||
if dirAction != nil && dirAction.label != "" {
|
||||
|
||||
+4
-207
@@ -10,41 +10,6 @@ import (
|
||||
"github.com/libretro/ludo/state"
|
||||
)
|
||||
|
||||
type sceneHistory struct {
|
||||
entry
|
||||
}
|
||||
|
||||
func buildHistory() Scene {
|
||||
var list sceneHistory
|
||||
list.label = "History"
|
||||
|
||||
history.Load()
|
||||
for _, game := range history.List {
|
||||
game := game // needed for callbackOK
|
||||
strippedName, tags := extractTags(game.Name)
|
||||
list.children = append(list.children, entry{
|
||||
label: strippedName,
|
||||
subLabel: game.System,
|
||||
gameName: game.Name,
|
||||
path: game.Path,
|
||||
system: game.System,
|
||||
tags: tags,
|
||||
callbackOK: func() { loadHistoryEntry(&list, game) },
|
||||
callbackX: func() { askDeleteGameConfirmation(func() { deleteHistoryEntry(&list, game) }) },
|
||||
})
|
||||
}
|
||||
|
||||
if len(history.List) == 0 {
|
||||
list.children = append(list.children, entry{
|
||||
label: "Empty history",
|
||||
icon: "subsetting",
|
||||
})
|
||||
}
|
||||
|
||||
list.segueMount()
|
||||
return &list
|
||||
}
|
||||
|
||||
func loadHistoryEntry(list Scene, game history.Game) {
|
||||
if _, err := os.Stat(game.Path); os.IsNotExist(err) {
|
||||
ntf.DisplayAndLog(ntf.Error, "Menu", "Game not found.")
|
||||
@@ -74,179 +39,11 @@ func loadHistoryEntry(list Scene, game history.Game) {
|
||||
System: game.System,
|
||||
CorePath: corePath,
|
||||
})
|
||||
list.segueNext()
|
||||
menu.Push(buildQuickMenu())
|
||||
menu.tweens.FastForward() // position the elements without animating
|
||||
history.Load()
|
||||
menu.WarpToQuickMenu()
|
||||
state.MenuActive = false
|
||||
} else {
|
||||
list.segueNext()
|
||||
menu.Push(buildQuickMenu())
|
||||
}
|
||||
}
|
||||
|
||||
func removeHistoryGame(s []history.Game, game history.Game) []history.Game {
|
||||
l := []history.Game{}
|
||||
for _, g := range s {
|
||||
if g.Path != game.Path {
|
||||
l = append(l, g)
|
||||
}
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func removeHistoryEntry(s []entry, game history.Game) []entry {
|
||||
l := []entry{}
|
||||
for _, g := range s {
|
||||
if g.path != game.Path {
|
||||
l = append(l, g)
|
||||
}
|
||||
}
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
func deleteHistoryEntry(list *sceneHistory, game history.Game) {
|
||||
history.List = removeHistoryGame(history.List, game)
|
||||
history.Save()
|
||||
refreshTabs()
|
||||
list.children = removeHistoryEntry(list.children, game)
|
||||
|
||||
if len(history.List) == 0 {
|
||||
list.children = append(list.children, entry{
|
||||
label: "Empty history",
|
||||
icon: "subsetting",
|
||||
})
|
||||
}
|
||||
|
||||
if list.ptr >= len(list.children) {
|
||||
list.ptr = len(list.children) - 1
|
||||
}
|
||||
|
||||
buildIndexes(&list.entry)
|
||||
genericAnimate(&list.entry)
|
||||
}
|
||||
|
||||
// Generic stuff
|
||||
func (s *sceneHistory) Entry() *entry {
|
||||
return &s.entry
|
||||
}
|
||||
|
||||
func (s *sceneHistory) segueMount() {
|
||||
genericSegueMount(&s.entry)
|
||||
}
|
||||
|
||||
func (s *sceneHistory) segueNext() {
|
||||
genericSegueNext(&s.entry)
|
||||
}
|
||||
|
||||
func (s *sceneHistory) segueBack() {
|
||||
genericAnimate(&s.entry)
|
||||
}
|
||||
|
||||
func (s *sceneHistory) update(dt float32) {
|
||||
genericInput(&s.entry, dt)
|
||||
}
|
||||
|
||||
// Override rendering
|
||||
func (s *sceneHistory) render() {
|
||||
list := &s.entry
|
||||
|
||||
_, h := menu.GetFramebufferSize()
|
||||
|
||||
thumbnailDrawCursor(list)
|
||||
|
||||
menu.ScissorStart(int32(510*menu.ratio), 0, int32(1310*menu.ratio), int32(h))
|
||||
|
||||
for i, e := range list.children {
|
||||
if e.yp < -0.1 || e.yp > 1.1 {
|
||||
freeThumbnail(list, i)
|
||||
continue
|
||||
}
|
||||
|
||||
fontOffset := 64 * 0.7 * menu.ratio * 0.3
|
||||
|
||||
if e.labelAlpha > 0 {
|
||||
drawThumbnail(
|
||||
list, i,
|
||||
e.system, e.gameName,
|
||||
680*menu.ratio-85*e.scale*menu.ratio,
|
||||
float32(h)*e.yp-14*menu.ratio-64*e.scale*menu.ratio+fontOffset,
|
||||
170*menu.ratio, 128*menu.ratio,
|
||||
e.scale, white.Alpha(e.iconAlpha),
|
||||
)
|
||||
menu.DrawBorder(
|
||||
680*menu.ratio-85*e.scale*menu.ratio,
|
||||
float32(h)*e.yp-14*menu.ratio-64*e.scale*menu.ratio+fontOffset,
|
||||
170*menu.ratio*e.scale, 128*menu.ratio*e.scale, 0.02/e.scale,
|
||||
textColor.Alpha(e.iconAlpha))
|
||||
if e.path == state.GamePath && e.path != "" {
|
||||
menu.DrawCircle(
|
||||
680*menu.ratio,
|
||||
float32(h)*e.yp-14*menu.ratio+fontOffset,
|
||||
90*menu.ratio*e.scale,
|
||||
black.Alpha(e.iconAlpha))
|
||||
menu.DrawImage(menu.icons["resume"],
|
||||
680*menu.ratio-25*e.scale*menu.ratio,
|
||||
float32(h)*e.yp-14*menu.ratio-25*e.scale*menu.ratio+fontOffset,
|
||||
50*menu.ratio, 50*menu.ratio,
|
||||
e.scale, white.Alpha(e.iconAlpha))
|
||||
}
|
||||
|
||||
// Offset on Y to vertically center label + sublabel if there is a sublabel
|
||||
slOffset := float32(0)
|
||||
if e.subLabel != "" {
|
||||
slOffset = 30 * menu.ratio * e.subLabelAlpha
|
||||
}
|
||||
|
||||
menu.Font.SetColor(textColor.Alpha(e.labelAlpha))
|
||||
stack := 840 * menu.ratio
|
||||
menu.Font.Printf(
|
||||
840*menu.ratio,
|
||||
float32(h)*e.yp+fontOffset-slOffset,
|
||||
0.5*menu.ratio, e.label)
|
||||
stack += float32(int(menu.Font.Width(0.5*menu.ratio, e.label)))
|
||||
stack += 10
|
||||
|
||||
for _, tag := range e.tags {
|
||||
if _, ok := menu.icons[tag]; ok {
|
||||
stack += 20
|
||||
menu.DrawImage(
|
||||
menu.icons[tag],
|
||||
stack, float32(h)*e.yp-22*menu.ratio-slOffset,
|
||||
60*menu.ratio, 44*menu.ratio, 1.0, white.Alpha(e.tagAlpha))
|
||||
menu.DrawBorder(stack, float32(h)*e.yp-22*menu.ratio-slOffset,
|
||||
60*menu.ratio, 44*menu.ratio, 0.05/menu.ratio, black.Alpha(e.tagAlpha/4))
|
||||
stack += 60 * menu.ratio
|
||||
}
|
||||
}
|
||||
|
||||
menu.Font.SetColor(mediumGrey.Alpha(e.subLabelAlpha))
|
||||
menu.Font.Printf(
|
||||
840*menu.ratio,
|
||||
float32(h)*e.yp+fontOffset+60*menu.ratio-slOffset,
|
||||
0.5*menu.ratio, e.subLabel)
|
||||
}
|
||||
}
|
||||
|
||||
menu.ScissorEnd()
|
||||
}
|
||||
|
||||
func (s *sceneHistory) drawHintBar() {
|
||||
w, h := menu.GetFramebufferSize()
|
||||
menu.DrawRect(0, float32(h)-70*menu.ratio, float32(w), 70*menu.ratio, 0, lightGrey)
|
||||
|
||||
_, upDown, _, a, b, x, _, _, _, guide := hintIcons()
|
||||
|
||||
var stack float32
|
||||
if state.CoreRunning {
|
||||
stackHint(&stack, guide, "RESUME", h)
|
||||
}
|
||||
stackHint(&stack, upDown, "NAVIGATE", h)
|
||||
stackHint(&stack, b, "BACK", h)
|
||||
stackHint(&stack, a, "RUN", h)
|
||||
|
||||
list := menu.stack[len(menu.stack)-1].Entry()
|
||||
if list.children[list.ptr].callbackX != nil {
|
||||
stackHint(&stack, x, "DELETE", h)
|
||||
menu.WarpToQuickMenu()
|
||||
state.MenuActive = false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,401 @@
|
||||
package menu
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"github.com/libretro/ludo/audio"
|
||||
"github.com/libretro/ludo/history"
|
||||
"github.com/libretro/ludo/input"
|
||||
"github.com/libretro/ludo/libretro"
|
||||
"github.com/libretro/ludo/playlists"
|
||||
"github.com/libretro/ludo/state"
|
||||
"github.com/libretro/ludo/utils"
|
||||
|
||||
"github.com/tanema/gween"
|
||||
"github.com/tanema/gween/ease"
|
||||
)
|
||||
|
||||
type sceneHome struct {
|
||||
entry
|
||||
yptr int
|
||||
yscroll float32
|
||||
xscrolls []float32
|
||||
xptrs []int
|
||||
}
|
||||
|
||||
func buildHome() Scene {
|
||||
var list sceneHome
|
||||
list.label = "Home"
|
||||
|
||||
cat := 0
|
||||
history.Load()
|
||||
if len(history.List) > 0 {
|
||||
list.children = append(list.children, entry{
|
||||
label: "Recently played",
|
||||
})
|
||||
list.xscrolls = append(list.xscrolls, 0)
|
||||
list.xptrs = append(list.xptrs, 0)
|
||||
|
||||
for _, game := range history.List {
|
||||
game := game
|
||||
strippedName, tags := extractTags(game.Name)
|
||||
list.children[cat].children = append(list.children[cat].children, entry{
|
||||
label: strippedName,
|
||||
gameName: game.Name,
|
||||
tags: tags,
|
||||
subLabel: game.System,
|
||||
system: game.System,
|
||||
callbackOK: func() {
|
||||
loadHistoryEntry(&list, game)
|
||||
},
|
||||
})
|
||||
}
|
||||
cat++
|
||||
}
|
||||
|
||||
playlists.Load()
|
||||
|
||||
// To store the keys in slice in sorted order
|
||||
var keys []string
|
||||
for k := range playlists.Playlists {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, path := range keys {
|
||||
path := path
|
||||
filename := utils.FileName(path)
|
||||
|
||||
list.children = append(list.children, entry{
|
||||
label: filename,
|
||||
})
|
||||
list.xscrolls = append(list.xscrolls, 0)
|
||||
list.xptrs = append(list.xptrs, 0)
|
||||
|
||||
for _, game := range playlists.Playlists[path] {
|
||||
game := game
|
||||
strippedName, tags := extractTags(game.Name)
|
||||
list.children[cat].children = append(list.children[cat].children, entry{
|
||||
label: strippedName,
|
||||
gameName: game.Name,
|
||||
path: game.Path,
|
||||
tags: tags,
|
||||
icon: utils.FileName(path) + "-content",
|
||||
subLabel: filename,
|
||||
system: filename,
|
||||
callbackOK: func() { loadPlaylistEntry(&list, filename, game) },
|
||||
})
|
||||
}
|
||||
cat++
|
||||
}
|
||||
|
||||
if len(list.children) == 0 {
|
||||
menu.focus--
|
||||
}
|
||||
|
||||
list.segueMount()
|
||||
|
||||
return &list
|
||||
}
|
||||
|
||||
func (s *sceneHome) Entry() *entry {
|
||||
return &s.entry
|
||||
}
|
||||
|
||||
func (s *sceneHome) segueMount() {
|
||||
s.alpha = 0
|
||||
for j := range s.children {
|
||||
s.xscrolls[j] = 0
|
||||
}
|
||||
s.y = 300
|
||||
|
||||
for j := range s.children {
|
||||
ve := &s.children[j]
|
||||
ve.labelAlpha = 0
|
||||
ve.height = 504 + 136
|
||||
|
||||
for i := range ve.children {
|
||||
e := &s.children[j].children[i]
|
||||
|
||||
if i == s.xptrs[j] {
|
||||
e.labelAlpha = 1
|
||||
e.iconAlpha = 1
|
||||
e.scale = 2.1
|
||||
e.borderAlpha = 0
|
||||
} else if i < s.xptrs[j] {
|
||||
e.labelAlpha = 0
|
||||
e.iconAlpha = 0
|
||||
e.scale = 1
|
||||
e.borderAlpha = 0
|
||||
} else {
|
||||
e.labelAlpha = 0
|
||||
e.iconAlpha = 1
|
||||
e.scale = 1
|
||||
e.borderAlpha = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s.animate()
|
||||
}
|
||||
|
||||
func (s *sceneHome) segueBack() {
|
||||
s.animate()
|
||||
}
|
||||
|
||||
func (s *sceneHome) animate() {
|
||||
for j := range s.children {
|
||||
ve := &s.children[j]
|
||||
|
||||
labelAlpha := float32(1)
|
||||
if j < s.yptr {
|
||||
labelAlpha = 0
|
||||
}
|
||||
menu.tweens[&ve.labelAlpha] = gween.New(ve.labelAlpha, labelAlpha, 0.15, ease.OutSine)
|
||||
menu.tweens[&ve.height] = gween.New(ve.height, 504+136, 0.15, ease.OutSine)
|
||||
|
||||
for i := range ve.children {
|
||||
e := &s.children[j].children[i]
|
||||
|
||||
var labelAlpha, iconAlpha, scale, borderAlpha float32
|
||||
if i == s.xptrs[j] {
|
||||
labelAlpha = 1
|
||||
iconAlpha = 1
|
||||
scale = 2.1
|
||||
borderAlpha = 1
|
||||
} else {
|
||||
labelAlpha = 0
|
||||
iconAlpha = 1
|
||||
scale = 1
|
||||
borderAlpha = 0
|
||||
}
|
||||
if j < s.yptr {
|
||||
labelAlpha = 0
|
||||
iconAlpha = 0
|
||||
borderAlpha = 0
|
||||
}
|
||||
|
||||
menu.tweens[&e.labelAlpha] = gween.New(e.labelAlpha, labelAlpha, 0.15, ease.OutSine)
|
||||
menu.tweens[&e.iconAlpha] = gween.New(e.iconAlpha, iconAlpha, 0.15, ease.OutSine)
|
||||
menu.tweens[&e.borderAlpha] = gween.New(e.borderAlpha, borderAlpha, 0.15, ease.OutSine)
|
||||
menu.tweens[&e.scale] = gween.New(e.scale, scale, 0.15, ease.OutSine)
|
||||
}
|
||||
}
|
||||
|
||||
for j := range s.children {
|
||||
menu.tweens[&s.xscrolls[j]] = gween.New(s.xscrolls[j], float32(s.xptrs[j]*(320+32)), 0.15, ease.OutSine)
|
||||
}
|
||||
|
||||
vst := float32(0)
|
||||
for j := range s.children {
|
||||
if j == s.yptr {
|
||||
break
|
||||
}
|
||||
vst += 504 + 136
|
||||
}
|
||||
|
||||
menu.tweens[&s.yscroll] = gween.New(s.yscroll, vst, 0.15, ease.OutSine)
|
||||
menu.tweens[&s.alpha] = gween.New(s.alpha, 1, 0.15, ease.OutSine)
|
||||
menu.tweens[&s.y] = gween.New(s.y, 0, 0.15, ease.OutSine)
|
||||
}
|
||||
|
||||
func (s *sceneHome) segueNext() {
|
||||
menu.tweens[&s.alpha] = gween.New(s.alpha, 0, 0.15, ease.OutSine)
|
||||
menu.tweens[&s.y] = gween.New(s.y, -300, 0.15, ease.OutSine)
|
||||
|
||||
for j := range s.children {
|
||||
ve := &s.children[j]
|
||||
for i := range ve.children {
|
||||
e := &s.children[j].children[i]
|
||||
menu.tweens[&e.iconAlpha] = gween.New(e.iconAlpha, 0, 0.15, ease.OutSine)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sceneHome) update(dt float32) {
|
||||
// Empty state
|
||||
if len(s.children) == 0 {
|
||||
menu.focus--
|
||||
return
|
||||
}
|
||||
|
||||
// Right
|
||||
repeatRight(dt, input.NewState[0][libretro.DeviceIDJoypadRight] == 1, func() {
|
||||
if s.xptrs[s.yptr] < len(s.children[s.yptr].children)-1 {
|
||||
s.xptrs[s.yptr]++
|
||||
audio.PlayEffect(audio.Effects["down"])
|
||||
menu.t = 0
|
||||
s.animate()
|
||||
}
|
||||
})
|
||||
|
||||
// Left
|
||||
repeatLeft(dt, input.NewState[0][libretro.DeviceIDJoypadLeft] == 1, func() {
|
||||
if s.xptrs[s.yptr] > 0 {
|
||||
s.xptrs[s.yptr]--
|
||||
audio.PlayEffect(audio.Effects["up"])
|
||||
menu.t = 0
|
||||
s.animate()
|
||||
}
|
||||
})
|
||||
|
||||
// Down
|
||||
repeatDown(dt, input.NewState[0][libretro.DeviceIDJoypadDown] == 1, func() {
|
||||
if s.yptr < len(s.children)-1 {
|
||||
s.yptr++
|
||||
audio.PlayEffect(audio.Effects["down"])
|
||||
menu.t = 0
|
||||
s.animate()
|
||||
}
|
||||
})
|
||||
|
||||
// Up
|
||||
repeatUp(dt, input.NewState[0][libretro.DeviceIDJoypadUp] == 1, func() {
|
||||
if s.yptr > 0 {
|
||||
s.yptr--
|
||||
audio.PlayEffect(audio.Effects["up"])
|
||||
menu.t = 0
|
||||
s.animate()
|
||||
} else if s.yptr == 0 && len(menu.stack) > 1 {
|
||||
audio.PlayEffect(audio.Effects["cancel"])
|
||||
menu.stack[len(menu.stack)-2].segueBack()
|
||||
menu.focus--
|
||||
menu.t = 0
|
||||
}
|
||||
})
|
||||
|
||||
// OK
|
||||
if input.Released[0][libretro.DeviceIDJoypadA] == 1 {
|
||||
if len(s.children) > 0 && s.children[s.yptr].children[s.xptrs[s.yptr]].callbackOK != nil {
|
||||
audio.PlayEffect(audio.Effects["ok"])
|
||||
s.segueNext()
|
||||
s.children[s.yptr].children[s.xptrs[s.yptr]].callbackOK()
|
||||
}
|
||||
}
|
||||
|
||||
// Cancel
|
||||
if input.Released[0][libretro.DeviceIDJoypadB] == 1 {
|
||||
if len(menu.stack) > 1 {
|
||||
audio.PlayEffect(audio.Effects["cancel"])
|
||||
menu.stack[len(menu.stack)-2].segueBack()
|
||||
menu.focus--
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s sceneHome) render() {
|
||||
vst := float32(0)
|
||||
for j, ve := range s.children {
|
||||
ve := ve
|
||||
|
||||
menu.BoldFont.SetColor(blue.Alpha(ve.labelAlpha * s.alpha))
|
||||
menu.BoldFont.Printf(
|
||||
96*menu.ratio,
|
||||
s.y*menu.ratio+230*menu.ratio+vst*menu.ratio-s.yscroll*menu.ratio,
|
||||
0.5*menu.ratio, ve.label)
|
||||
|
||||
y := s.y + 272 + vst - s.yscroll
|
||||
|
||||
vst += ve.height
|
||||
|
||||
// performance improvement
|
||||
if math.Abs(float64(j-s.yptr)) > 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
stackWidth := float32(96)
|
||||
for i, e := range ve.children {
|
||||
x := -s.xscrolls[j] + stackWidth
|
||||
|
||||
stackWidth += 320*e.scale + e.margin + 32
|
||||
|
||||
// performance improvement
|
||||
if math.Abs(float64(i-s.xptrs[j])) > 4 {
|
||||
freeThumbnail(&ve, i)
|
||||
continue
|
||||
}
|
||||
|
||||
if menu.focus == 2 && j == s.yptr && i == s.xptrs[s.yptr] {
|
||||
blink := float32(math.Cos(menu.t))
|
||||
menu.DrawImage(
|
||||
menu.icons["selection"],
|
||||
x*menu.ratio-8*menu.ratio,
|
||||
y*menu.ratio-8*menu.ratio,
|
||||
320*e.scale*menu.ratio+16*menu.ratio, 240*e.scale*menu.ratio+16*menu.ratio,
|
||||
1, 0.1, white.Alpha((e.borderAlpha-blink)*s.alpha))
|
||||
}
|
||||
|
||||
drawThumbnail(
|
||||
&ve, i,
|
||||
e.system, e.gameName,
|
||||
x*menu.ratio,
|
||||
y*menu.ratio,
|
||||
320*e.scale*menu.ratio, 240*e.scale*menu.ratio,
|
||||
1, white.Alpha(e.iconAlpha*s.alpha))
|
||||
|
||||
menu.DrawImage(
|
||||
menu.icons["border"],
|
||||
x*menu.ratio,
|
||||
y*menu.ratio,
|
||||
320*e.scale*menu.ratio, 240*e.scale*menu.ratio,
|
||||
1, 0.07, white.Alpha(e.iconAlpha*s.alpha))
|
||||
|
||||
menu.BoldFont.SetColor(black.Alpha(e.labelAlpha * s.alpha))
|
||||
menu.BoldFont.Printf(
|
||||
(x+672+32)*menu.ratio,
|
||||
(y+360)*menu.ratio,
|
||||
0.7*menu.ratio, e.label)
|
||||
|
||||
menu.BoldFont.SetColor(mediumGrey.Alpha(e.labelAlpha * s.alpha))
|
||||
menu.BoldFont.Printf(
|
||||
(x+672+32)*menu.ratio,
|
||||
(y+430)*menu.ratio,
|
||||
0.5*menu.ratio, e.subLabel)
|
||||
|
||||
stack := (x + 672 + 32) * menu.ratio
|
||||
for _, tag := range e.tags {
|
||||
if _, ok := menu.icons[tag]; ok {
|
||||
menu.DrawRect(stack-1*menu.ratio, (y+500-35)*menu.ratio-1*menu.ratio,
|
||||
48*menu.ratio+2*menu.ratio, 35*menu.ratio+2*menu.ratio, 0.22,
|
||||
mediumGrey.Alpha(e.labelAlpha*s.alpha))
|
||||
menu.DrawImage(
|
||||
menu.icons[tag],
|
||||
stack, (y+500-35)*menu.ratio,
|
||||
48*menu.ratio, 35*menu.ratio, 1.0, 0.2,
|
||||
white.Alpha(e.labelAlpha*s.alpha))
|
||||
stack += 48 * menu.ratio
|
||||
stack += 24 * menu.ratio
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(s.children) == 0 {
|
||||
w, h := menu.Window.GetFramebufferSize()
|
||||
menu.BoldFont.SetColor(black)
|
||||
msg := "Welcome to Ludo, please scan your collection."
|
||||
msgw := menu.BoldFont.Width(0.5*menu.ratio, msg)
|
||||
menu.BoldFont.Printf(
|
||||
float32(w)/2-msgw/2,
|
||||
float32(h)/2,
|
||||
0.5*menu.ratio, msg)
|
||||
}
|
||||
}
|
||||
|
||||
func (s sceneHome) drawHintBar() {
|
||||
w, h := menu.Window.GetFramebufferSize()
|
||||
menu.DrawRect(0, float32(h)-88*menu.ratio, float32(w), 88*menu.ratio, 0, white)
|
||||
menu.DrawRect(0, float32(h)-88*menu.ratio, float32(w), 2*menu.ratio, 0, lightGrey)
|
||||
|
||||
arrows, _, _, a, b, _, _, _, _, guide := hintIcons()
|
||||
|
||||
lstack := float32(75) * menu.ratio
|
||||
rstack := float32(w) - 96*menu.ratio
|
||||
stackHintLeft(&lstack, arrows, "Navigate", h)
|
||||
stackHintRight(&rstack, a, "Run", h)
|
||||
stackHintRight(&rstack, b, "Back", h)
|
||||
if state.CoreRunning {
|
||||
stackHintRight(&rstack, guide, "Resume", h)
|
||||
}
|
||||
}
|
||||
+12
-11
@@ -157,8 +157,7 @@ func (s *sceneKeyboard) render() {
|
||||
ttw := 10 * ksp
|
||||
|
||||
// Background
|
||||
menu.DrawRect(0, 0, float32(w), float32(h), 0,
|
||||
white.Alpha(s.alpha))
|
||||
menu.DrawRect(0, 0, float32(w), float32(h), 0, white.Alpha(s.alpha))
|
||||
|
||||
// Label
|
||||
menu.Font.SetColor(black)
|
||||
@@ -169,7 +168,7 @@ func (s *sceneKeyboard) render() {
|
||||
|
||||
// Value
|
||||
menu.DrawRect(float32(w)/2-ttw/2, s.y+float32(h)*0.25-ksz/2, ttw, ksz, 0,
|
||||
video.Color{R: 0.95, G: 0.95, B: 0.95, A: 1})
|
||||
lightGrey)
|
||||
menu.Font.Printf(
|
||||
float32(w)/2-ttw/2+ksz/4,
|
||||
s.y+float32(h)*0.25-ksz/2+ksz*0.62,
|
||||
@@ -205,15 +204,17 @@ func (s *sceneKeyboard) render() {
|
||||
|
||||
func (s *sceneKeyboard) drawHintBar() {
|
||||
w, h := menu.GetFramebufferSize()
|
||||
menu.DrawRect(0, float32(h)-70*menu.ratio, float32(w), 70*menu.ratio, 0, lightGrey)
|
||||
menu.DrawRect(0, float32(h)-88*menu.ratio, float32(w), 88*menu.ratio, 0, white)
|
||||
menu.DrawRect(0, float32(h)-88*menu.ratio, float32(w), 2*menu.ratio, 0, lightGrey)
|
||||
|
||||
arrows, _, _, a, b, x, y, start, _, _ := hintIcons()
|
||||
|
||||
var stack float32
|
||||
stackHint(&stack, arrows, "SELECT", h)
|
||||
stackHint(&stack, b, "BACK", h)
|
||||
stackHint(&stack, x, "SHIFT", h)
|
||||
stackHint(&stack, y, "DELETE", h)
|
||||
stackHint(&stack, a, "INSERT", h)
|
||||
stackHint(&stack, start, "DONE", h)
|
||||
lstack := float32(75) * menu.ratio
|
||||
rstack := float32(w) - 96*menu.ratio
|
||||
stackHintLeft(&lstack, arrows, "Select", h)
|
||||
stackHintRight(&rstack, start, "Done", h)
|
||||
stackHintRight(&rstack, a, "Insert", h)
|
||||
stackHintRight(&rstack, y, "Delete", h)
|
||||
stackHintRight(&rstack, x, "Shift", h)
|
||||
stackHintRight(&rstack, b, "Back", h)
|
||||
}
|
||||
+19
-57
@@ -6,8 +6,10 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/libretro/ludo/core"
|
||||
"github.com/libretro/ludo/dat"
|
||||
"github.com/libretro/ludo/history"
|
||||
ntf "github.com/libretro/ludo/notifications"
|
||||
"github.com/libretro/ludo/scanner"
|
||||
"github.com/libretro/ludo/settings"
|
||||
"github.com/libretro/ludo/state"
|
||||
"github.com/libretro/ludo/utils"
|
||||
@@ -61,21 +63,10 @@ func prettifyCoreName(in string) string {
|
||||
|
||||
func buildMainMenu() Scene {
|
||||
var list sceneMain
|
||||
list.label = "Main Menu"
|
||||
list.label = "Manual Menu"
|
||||
|
||||
usr, _ := user.Current()
|
||||
|
||||
if state.CoreRunning {
|
||||
list.children = append(list.children, entry{
|
||||
label: "Quick Menu",
|
||||
icon: "subsetting",
|
||||
callbackOK: func() {
|
||||
list.segueNext()
|
||||
menu.Push(buildQuickMenu())
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
list.children = append(list.children, entry{
|
||||
label: "Load Core",
|
||||
icon: "subsetting",
|
||||
@@ -110,43 +101,6 @@ func buildMainMenu() Scene {
|
||||
},
|
||||
})
|
||||
|
||||
if state.LudOS {
|
||||
list.children = append(list.children, entry{
|
||||
label: "Updater",
|
||||
icon: "subsetting",
|
||||
callbackOK: func() {
|
||||
list.segueNext()
|
||||
menu.Push(buildUpdater())
|
||||
},
|
||||
})
|
||||
|
||||
list.children = append(list.children, entry{
|
||||
label: "Reboot",
|
||||
icon: "subsetting",
|
||||
callbackOK: func() {
|
||||
askQuitConfirmation(func() { cleanReboot() })
|
||||
},
|
||||
})
|
||||
|
||||
list.children = append(list.children, entry{
|
||||
label: "Shutdown",
|
||||
icon: "subsetting",
|
||||
callbackOK: func() {
|
||||
askQuitConfirmation(func() { cleanShutdown() })
|
||||
},
|
||||
})
|
||||
} else {
|
||||
list.children = append(list.children, entry{
|
||||
label: "Quit",
|
||||
icon: "subsetting",
|
||||
callbackOK: func() {
|
||||
askQuitConfirmation(func() {
|
||||
menu.SetShouldClose(true)
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
list.segueMount()
|
||||
|
||||
return &list
|
||||
@@ -165,15 +119,23 @@ func coreExplorerCb(path string) {
|
||||
func gameExplorerCb(path string) {
|
||||
if err := core.LoadGame(path); err != nil {
|
||||
ntf.DisplayAndLog(ntf.Error, "Core", err.Error())
|
||||
return
|
||||
} else {
|
||||
scanner.ScanFile(path, func(game dat.Game) {
|
||||
name := game.Name
|
||||
if name == "" {
|
||||
name = utils.FileName(path)
|
||||
}
|
||||
history.Push(history.Game{
|
||||
Path: path,
|
||||
Name: name,
|
||||
System: game.System,
|
||||
CorePath: state.CorePath,
|
||||
})
|
||||
history.Load()
|
||||
menu.WarpToQuickMenu()
|
||||
})
|
||||
state.MenuActive = false
|
||||
}
|
||||
history.Push(history.Game{
|
||||
Path: path,
|
||||
Name: utils.FileName(path),
|
||||
CorePath: state.CorePath,
|
||||
})
|
||||
menu.WarpToQuickMenu()
|
||||
state.MenuActive = false
|
||||
}
|
||||
|
||||
// Shutdown the operating system
|
||||
|
||||
+5
-196
@@ -12,44 +12,8 @@ import (
|
||||
"github.com/libretro/ludo/playlists"
|
||||
"github.com/libretro/ludo/settings"
|
||||
"github.com/libretro/ludo/state"
|
||||
"github.com/libretro/ludo/utils"
|
||||
)
|
||||
|
||||
type scenePlaylist struct {
|
||||
entry
|
||||
}
|
||||
|
||||
func buildPlaylist(path string) Scene {
|
||||
var list scenePlaylist
|
||||
list.label = utils.FileName(path)
|
||||
|
||||
for _, game := range playlists.Playlists[path] {
|
||||
game := game // needed for callbackOK
|
||||
strippedName, tags := extractTags(game.Name)
|
||||
list.children = append(list.children, entry{
|
||||
label: strippedName,
|
||||
gameName: game.Name,
|
||||
path: game.Path,
|
||||
tags: tags,
|
||||
icon: utils.FileName(path) + "-content",
|
||||
callbackOK: func() { loadPlaylistEntry(&list, list.label, game) },
|
||||
callbackX: func() { askDeleteGameConfirmation(func() { deletePlaylistEntry(&list, path, game) }) },
|
||||
})
|
||||
}
|
||||
|
||||
if len(playlists.Playlists[path]) == 0 {
|
||||
list.children = append(list.children, entry{
|
||||
label: "Empty playlist",
|
||||
icon: "subsetting",
|
||||
})
|
||||
}
|
||||
|
||||
buildIndexes(&list.entry)
|
||||
|
||||
list.segueMount()
|
||||
return &list
|
||||
}
|
||||
|
||||
// Index first letters of entries to allow quick jump to the next or previous
|
||||
// letter
|
||||
func buildIndexes(list *entry) {
|
||||
@@ -83,7 +47,7 @@ func extractTags(name string) (string, []string) {
|
||||
return name, tags
|
||||
}
|
||||
|
||||
func loadPlaylistEntry(list *scenePlaylist, playlist string, game playlists.Game) {
|
||||
func loadPlaylistEntry(list Scene, playlist string, game playlists.Game) {
|
||||
if _, err := os.Stat(game.Path); os.IsNotExist(err) {
|
||||
ntf.DisplayAndLog(ntf.Error, "Menu", "Game not found.")
|
||||
return
|
||||
@@ -114,167 +78,12 @@ func loadPlaylistEntry(list *scenePlaylist, playlist string, game playlists.Game
|
||||
System: playlist,
|
||||
CorePath: corePath,
|
||||
})
|
||||
list.segueNext()
|
||||
menu.Push(buildQuickMenu())
|
||||
menu.tweens.FastForward() // position the elements without animating
|
||||
history.Load()
|
||||
menu.WarpToQuickMenu()
|
||||
state.MenuActive = false
|
||||
} else {
|
||||
list.segueNext()
|
||||
menu.Push(buildQuickMenu())
|
||||
}
|
||||
}
|
||||
|
||||
func removePlaylistGame(s []playlists.Game, game playlists.Game) []playlists.Game {
|
||||
l := []playlists.Game{}
|
||||
for _, g := range s {
|
||||
if g.Path != game.Path {
|
||||
l = append(l, g)
|
||||
}
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func removePlaylistEntry(s []entry, game playlists.Game) []entry {
|
||||
l := []entry{}
|
||||
for _, g := range s {
|
||||
if g.path != game.Path {
|
||||
l = append(l, g)
|
||||
}
|
||||
}
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
func deletePlaylistEntry(list *scenePlaylist, path string, game playlists.Game) {
|
||||
playlists.Playlists[path] = removePlaylistGame(playlists.Playlists[path], game)
|
||||
playlists.Save(path)
|
||||
refreshTabs()
|
||||
list.children = removePlaylistEntry(list.children, game)
|
||||
|
||||
if len(playlists.Playlists[path]) == 0 {
|
||||
list.children = append(list.children, entry{
|
||||
label: "Empty playlist",
|
||||
icon: "subsetting",
|
||||
})
|
||||
}
|
||||
|
||||
if list.ptr >= len(list.children) {
|
||||
list.ptr = len(list.children) - 1
|
||||
}
|
||||
|
||||
buildIndexes(&list.entry)
|
||||
genericAnimate(&list.entry)
|
||||
}
|
||||
|
||||
// Generic stuff
|
||||
func (s *scenePlaylist) Entry() *entry {
|
||||
return &s.entry
|
||||
}
|
||||
|
||||
func (s *scenePlaylist) segueMount() {
|
||||
genericSegueMount(&s.entry)
|
||||
}
|
||||
|
||||
func (s *scenePlaylist) segueNext() {
|
||||
genericSegueNext(&s.entry)
|
||||
}
|
||||
|
||||
func (s *scenePlaylist) segueBack() {
|
||||
genericAnimate(&s.entry)
|
||||
}
|
||||
|
||||
func (s *scenePlaylist) update(dt float32) {
|
||||
genericInput(&s.entry, dt)
|
||||
}
|
||||
|
||||
// Override rendering
|
||||
func (s *scenePlaylist) render() {
|
||||
list := &s.entry
|
||||
|
||||
_, h := menu.GetFramebufferSize()
|
||||
|
||||
thumbnailDrawCursor(list)
|
||||
|
||||
menu.ScissorStart(int32(510*menu.ratio), 0, int32(1310*menu.ratio), int32(h))
|
||||
|
||||
for i, e := range list.children {
|
||||
if e.yp < -0.1 || e.yp > 1.1 {
|
||||
freeThumbnail(list, i)
|
||||
continue
|
||||
}
|
||||
|
||||
fontOffset := 64 * 0.7 * menu.ratio * 0.3
|
||||
|
||||
if e.labelAlpha > 0 {
|
||||
drawThumbnail(
|
||||
list, i,
|
||||
list.label, e.gameName,
|
||||
680*menu.ratio-85*e.scale*menu.ratio,
|
||||
float32(h)*e.yp-14*menu.ratio-64*e.scale*menu.ratio+fontOffset,
|
||||
170*menu.ratio, 128*menu.ratio,
|
||||
e.scale, white.Alpha(e.iconAlpha),
|
||||
)
|
||||
menu.DrawBorder(
|
||||
680*menu.ratio-85*e.scale*menu.ratio,
|
||||
float32(h)*e.yp-14*menu.ratio-64*e.scale*menu.ratio+fontOffset,
|
||||
170*menu.ratio*e.scale, 128*menu.ratio*e.scale, 0.02/e.scale,
|
||||
textColor.Alpha(e.iconAlpha))
|
||||
if e.path == state.GamePath && e.path != "" {
|
||||
menu.DrawCircle(
|
||||
680*menu.ratio,
|
||||
float32(h)*e.yp-14*menu.ratio+fontOffset,
|
||||
90*menu.ratio*e.scale,
|
||||
black.Alpha(e.iconAlpha))
|
||||
menu.DrawImage(menu.icons["resume"],
|
||||
680*menu.ratio-25*e.scale*menu.ratio,
|
||||
float32(h)*e.yp-14*menu.ratio-25*e.scale*menu.ratio+fontOffset,
|
||||
50*menu.ratio, 50*menu.ratio,
|
||||
e.scale, white.Alpha(e.iconAlpha))
|
||||
}
|
||||
|
||||
menu.Font.SetColor(textColor.Alpha(e.labelAlpha))
|
||||
stack := 840 * menu.ratio
|
||||
menu.Font.Printf(
|
||||
840*menu.ratio,
|
||||
float32(h)*e.yp+fontOffset,
|
||||
0.5*menu.ratio, e.label)
|
||||
stack += float32(int(menu.Font.Width(0.5*menu.ratio, e.label)))
|
||||
stack += 10
|
||||
|
||||
for _, tag := range e.tags {
|
||||
if _, ok := menu.icons[tag]; ok {
|
||||
stack += 20
|
||||
menu.DrawImage(
|
||||
menu.icons[tag],
|
||||
stack, float32(h)*e.yp-22*menu.ratio,
|
||||
60*menu.ratio, 44*menu.ratio, 1.0, white.Alpha(e.tagAlpha))
|
||||
menu.DrawBorder(stack, float32(h)*e.yp-22*menu.ratio,
|
||||
60*menu.ratio, 44*menu.ratio, 0.05/menu.ratio, black.Alpha(e.tagAlpha/4))
|
||||
stack += 60 * menu.ratio
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
menu.ScissorEnd()
|
||||
}
|
||||
|
||||
func (s *scenePlaylist) drawHintBar() {
|
||||
w, h := menu.GetFramebufferSize()
|
||||
menu.DrawRect(0, float32(h)-70*menu.ratio, float32(w), 70*menu.ratio, 0, lightGrey)
|
||||
|
||||
_, upDown, _, a, b, x, _, _, _, guide := hintIcons()
|
||||
|
||||
var stack float32
|
||||
if state.CoreRunning {
|
||||
stackHint(&stack, guide, "RESUME", h)
|
||||
}
|
||||
stackHint(&stack, upDown, "NAVIGATE", h)
|
||||
stackHint(&stack, b, "BACK", h)
|
||||
stackHint(&stack, a, "RUN", h)
|
||||
|
||||
list := menu.stack[len(menu.stack)-1].Entry()
|
||||
if list.children[list.ptr].callbackX != nil {
|
||||
stackHint(&stack, x, "DELETE", h)
|
||||
menu.WarpToQuickMenu()
|
||||
state.MenuActive = false
|
||||
}
|
||||
}
|
||||
+10
-1
@@ -58,7 +58,7 @@ func buildQuickMenu() Scene {
|
||||
|
||||
list.children = append(list.children, entry{
|
||||
label: "Options",
|
||||
icon: "subsetting",
|
||||
icon: "options",
|
||||
callbackOK: func() {
|
||||
list.segueNext()
|
||||
menu.Push(buildCoreOptions())
|
||||
@@ -76,6 +76,15 @@ func buildQuickMenu() Scene {
|
||||
})
|
||||
}
|
||||
|
||||
list.children = append(list.children, entry{
|
||||
label: "Settings",
|
||||
icon: "subsetting",
|
||||
callbackOK: func() {
|
||||
list.segueNext()
|
||||
menu.Push(buildSettings())
|
||||
},
|
||||
})
|
||||
|
||||
list.segueMount()
|
||||
|
||||
return &list
|
||||
|
||||
+64
-28
@@ -1,6 +1,7 @@
|
||||
package menu
|
||||
|
||||
import (
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
@@ -20,6 +21,7 @@ type sceneSavestates struct {
|
||||
func buildSavestates() Scene {
|
||||
var list sceneSavestates
|
||||
list.label = "Savestates"
|
||||
list.entryHeight = 160
|
||||
|
||||
list.children = append(list.children, entry{
|
||||
label: "Save State",
|
||||
@@ -117,71 +119,105 @@ func deleteSavestateEntry(list *sceneSavestates, path string) {
|
||||
// Override rendering
|
||||
func (s *sceneSavestates) render() {
|
||||
list := &s.entry
|
||||
w, h := menu.GetFramebufferSize()
|
||||
|
||||
_, h := menu.GetFramebufferSize()
|
||||
menu.BoldFont.SetColor(blue.Alpha(list.alpha))
|
||||
menu.BoldFont.Printf(
|
||||
360*menu.ratio,
|
||||
list.y*menu.ratio+230*menu.ratio,
|
||||
0.5*menu.ratio, list.label)
|
||||
|
||||
thumbnailDrawCursor(list)
|
||||
menu.DrawRect(
|
||||
360*menu.ratio,
|
||||
list.y*menu.ratio+270*menu.ratio,
|
||||
float32(w)-720*menu.ratio,
|
||||
2*menu.ratio,
|
||||
0, lightGrey,
|
||||
)
|
||||
|
||||
menu.ScissorStart(
|
||||
int32(360*menu.ratio-8*menu.ratio), 0,
|
||||
int32(float32(w)-720*menu.ratio+16*menu.ratio), int32(h)-int32(272*menu.ratio+list.y*menu.ratio))
|
||||
|
||||
fontOffset := 12 * menu.ratio
|
||||
|
||||
for i, e := range list.children {
|
||||
if e.yp < -0.1 || e.yp > 1.1 {
|
||||
// performance improvement
|
||||
if math.Abs(float64(i-list.ptr)) > 8 {
|
||||
freeThumbnail(list, i)
|
||||
continue
|
||||
}
|
||||
|
||||
fontOffset := 64 * 0.7 * menu.ratio * 0.3
|
||||
y := list.y*menu.ratio +
|
||||
(270+32)*menu.ratio +
|
||||
list.scroll*menu.ratio +
|
||||
list.entryHeight*float32(i)*menu.ratio +
|
||||
list.entryHeight/2*menu.ratio
|
||||
|
||||
menu.DrawRect(
|
||||
360*menu.ratio,
|
||||
y-1*menu.ratio+list.entryHeight/2*menu.ratio,
|
||||
float32(w)-720*menu.ratio,
|
||||
2*menu.ratio,
|
||||
0, lightGrey,
|
||||
)
|
||||
|
||||
if i == list.ptr {
|
||||
genericDrawCursor(list, i)
|
||||
}
|
||||
|
||||
if e.labelAlpha > 0 {
|
||||
drawSavestateThumbnail(
|
||||
list, i,
|
||||
filepath.Join(settings.Current.ScreenshotsDirectory, utils.FileName(e.path)+".png"),
|
||||
680*menu.ratio-85*e.scale*menu.ratio,
|
||||
float32(h)*e.yp-14*menu.ratio-64*e.scale*menu.ratio+fontOffset,
|
||||
480*menu.ratio-85*1*menu.ratio,
|
||||
y-64*menu.ratio,
|
||||
170*menu.ratio, 128*menu.ratio,
|
||||
e.scale, textColor.Alpha(e.iconAlpha),
|
||||
1, white.Alpha(e.iconAlpha),
|
||||
)
|
||||
menu.DrawBorder(
|
||||
680*menu.ratio-85*e.scale*menu.ratio,
|
||||
float32(h)*e.yp-14*menu.ratio-64*e.scale*menu.ratio+fontOffset,
|
||||
170*menu.ratio*e.scale, 128*menu.ratio*e.scale, 0.02/e.scale,
|
||||
textColor.Alpha(e.iconAlpha))
|
||||
if i == 0 {
|
||||
menu.DrawImage(menu.icons["savestate"],
|
||||
680*menu.ratio-25*e.scale*menu.ratio,
|
||||
float32(h)*e.yp-14*menu.ratio-25*e.scale*menu.ratio+fontOffset,
|
||||
480*menu.ratio-25*1*menu.ratio,
|
||||
y-50/2*menu.ratio,
|
||||
50*menu.ratio, 50*menu.ratio,
|
||||
e.scale, textColor.Alpha(e.iconAlpha))
|
||||
1, 0, white.Alpha(e.iconAlpha))
|
||||
}
|
||||
|
||||
menu.Font.SetColor(textColor.Alpha(e.labelAlpha))
|
||||
menu.Font.SetColor(black.Alpha(e.labelAlpha))
|
||||
menu.Font.Printf(
|
||||
840*menu.ratio,
|
||||
float32(h)*e.yp+fontOffset,
|
||||
600*menu.ratio,
|
||||
y+fontOffset,
|
||||
0.5*menu.ratio, e.label)
|
||||
}
|
||||
}
|
||||
|
||||
menu.ScissorEnd()
|
||||
}
|
||||
|
||||
func (s *sceneSavestates) drawHintBar() {
|
||||
w, h := menu.GetFramebufferSize()
|
||||
menu.DrawRect(0, float32(h)-70*menu.ratio, float32(w), 70*menu.ratio, 0, lightGrey)
|
||||
menu.DrawRect(0, float32(h)-88*menu.ratio, float32(w), 88*menu.ratio, 0, white)
|
||||
menu.DrawRect(0, float32(h)-88*menu.ratio, float32(w), 2*menu.ratio, 0, lightGrey)
|
||||
|
||||
ptr := menu.stack[len(menu.stack)-1].Entry().ptr
|
||||
|
||||
_, upDown, _, a, b, x, _, _, _, guide := hintIcons()
|
||||
|
||||
var stack float32
|
||||
if state.CoreRunning {
|
||||
stackHint(&stack, guide, "RESUME", h)
|
||||
}
|
||||
stackHint(&stack, upDown, "NAVIGATE", h)
|
||||
stackHint(&stack, b, "BACK", h)
|
||||
lstack := float32(75) * menu.ratio
|
||||
rstack := float32(w) - 96*menu.ratio
|
||||
stackHintLeft(&lstack, upDown, "Navigate", h)
|
||||
if ptr == 0 {
|
||||
stackHint(&stack, a, "SAVE", h)
|
||||
stackHintRight(&rstack, a, "Save", h)
|
||||
} else {
|
||||
stackHint(&stack, a, "LOAD", h)
|
||||
stackHintRight(&rstack, a, "Load", h)
|
||||
}
|
||||
stackHintRight(&rstack, b, "Back", h)
|
||||
if state.CoreRunning {
|
||||
stackHintRight(&rstack, guide, "Resume", h)
|
||||
}
|
||||
|
||||
list := menu.stack[len(menu.stack)-1].Entry()
|
||||
if list.children[list.ptr].callbackX != nil {
|
||||
stackHint(&stack, x, "DELETE", h)
|
||||
stackHintRight(&rstack, x, "Delete", h)
|
||||
}
|
||||
}
|
||||
+24
-22
@@ -126,33 +126,33 @@ func dirExplorerCb(path string, f *structs.Field) {
|
||||
}
|
||||
|
||||
// Widgets to display settings values
|
||||
var widgets = map[string]func(*entry){
|
||||
var widgets = map[string]func(*entry, *entry, int){
|
||||
|
||||
// On/Off switch for boolean settings
|
||||
"switch": func(e *entry) {
|
||||
"switch": func(list, e *entry, i int) {
|
||||
icon := "off"
|
||||
if e.value().(bool) {
|
||||
icon = "on"
|
||||
}
|
||||
w, h := menu.GetFramebufferSize()
|
||||
fbw, _ := menu.GetFramebufferSize()
|
||||
menu.DrawImage(menu.icons[icon],
|
||||
float32(w)-128*menu.ratio-128*menu.ratio,
|
||||
float32(h)*e.yp-64*1.25*menu.ratio,
|
||||
float32(fbw)-400*menu.ratio-128*menu.ratio,
|
||||
list.y+(270+32)*menu.ratio+list.scroll*menu.ratio+100*float32(i)*menu.ratio+50*menu.ratio-64*1.25*menu.ratio,
|
||||
128*menu.ratio, 128*menu.ratio,
|
||||
1.25, textColor.Alpha(e.iconAlpha))
|
||||
1.25, 0, black.Alpha(e.iconAlpha))
|
||||
},
|
||||
|
||||
// Range widget for audio volume and similat float settings
|
||||
"range": func(e *entry) {
|
||||
fbw, fbh := menu.GetFramebufferSize()
|
||||
x := float32(fbw) - 128*menu.ratio - 175*menu.ratio
|
||||
y := float32(fbh)*e.yp - 4*menu.ratio
|
||||
"range": func(list, e *entry, i int) {
|
||||
fbw, _ := menu.GetFramebufferSize()
|
||||
x := float32(fbw) - 400*menu.ratio - 175*menu.ratio
|
||||
y := list.y + (270+32)*menu.ratio + list.scroll*menu.ratio + 100*float32(i)*menu.ratio + 50*menu.ratio - 4*menu.ratio
|
||||
w := 175 * menu.ratio
|
||||
h := 8 * menu.ratio
|
||||
menu.DrawRect(x, y, w, h, 0.9, textColor.Alpha(e.iconAlpha/4))
|
||||
menu.DrawRect(x, y, w, h, 0.9, black.Alpha(e.iconAlpha/4))
|
||||
w = 175 * menu.ratio * e.value().(float32)
|
||||
menu.DrawRect(x, y, w, h, 0.9, textColor.Alpha(e.iconAlpha))
|
||||
menu.DrawCircle(x+w, y+4*menu.ratio, 38*menu.ratio, textColor.Alpha(e.iconAlpha))
|
||||
menu.DrawRect(x, y, w, h, 0.9, black.Alpha(e.iconAlpha))
|
||||
menu.DrawCircle(x+w, y+4*menu.ratio, 38*menu.ratio, black.Alpha(e.iconAlpha))
|
||||
},
|
||||
}
|
||||
|
||||
@@ -274,20 +274,22 @@ func (s *sceneSettings) render() {
|
||||
|
||||
func (s *sceneSettings) drawHintBar() {
|
||||
w, h := menu.GetFramebufferSize()
|
||||
menu.DrawRect(0, float32(h)-70*menu.ratio, float32(w), 70*menu.ratio, 0, lightGrey)
|
||||
menu.DrawRect(0, float32(h)-88*menu.ratio, float32(w), 88*menu.ratio, 0, white)
|
||||
menu.DrawRect(0, float32(h)-88*menu.ratio, float32(w), 2*menu.ratio, 0, lightGrey)
|
||||
|
||||
_, upDown, leftRight, a, b, _, _, _, _, guide := hintIcons()
|
||||
|
||||
var stack float32
|
||||
lstack := float32(75) * menu.ratio
|
||||
rstack := float32(w) - 96*menu.ratio
|
||||
list := menu.stack[len(menu.stack)-1].Entry()
|
||||
if state.CoreRunning {
|
||||
stackHint(&stack, guide, "RESUME", h)
|
||||
}
|
||||
stackHint(&stack, upDown, "NAVIGATE", h)
|
||||
stackHint(&stack, b, "BACK", h)
|
||||
stackHintLeft(&lstack, upDown, "Navigate", h)
|
||||
if list.children[list.ptr].callbackOK != nil {
|
||||
stackHint(&stack, a, "SET", h)
|
||||
stackHintRight(&rstack, a, "Set", h)
|
||||
} else {
|
||||
stackHint(&stack, leftRight, "SET", h)
|
||||
stackHintLeft(&lstack, leftRight, "Set", h)
|
||||
}
|
||||
stackHintRight(&rstack, b, "Back", h)
|
||||
if state.CoreRunning {
|
||||
stackHintRight(&rstack, guide, "Resume", h)
|
||||
}
|
||||
}
|
||||
+136
-247
@@ -1,24 +1,15 @@
|
||||
package menu
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"math"
|
||||
"os/user"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/libretro/ludo/audio"
|
||||
"github.com/libretro/ludo/input"
|
||||
"github.com/libretro/ludo/libretro"
|
||||
ntf "github.com/libretro/ludo/notifications"
|
||||
"github.com/libretro/ludo/playlists"
|
||||
"github.com/libretro/ludo/scanner"
|
||||
"github.com/libretro/ludo/state"
|
||||
"github.com/libretro/ludo/utils"
|
||||
"github.com/libretro/ludo/video"
|
||||
colorful "github.com/lucasb-eyer/go-colorful"
|
||||
|
||||
"github.com/tanema/gween"
|
||||
"github.com/tanema/gween/ease"
|
||||
)
|
||||
|
||||
type sceneTabs struct {
|
||||
@@ -27,304 +18,202 @@ type sceneTabs struct {
|
||||
|
||||
func buildTabs() Scene {
|
||||
var list sceneTabs
|
||||
list.label = "Ludo"
|
||||
list.label = "Tabs"
|
||||
|
||||
list.children = append(list.children, entry{
|
||||
label: "Main Menu",
|
||||
subLabel: "Load cores and games manually",
|
||||
icon: "main",
|
||||
icon: "tab-collection",
|
||||
callbackOK: func() {
|
||||
menu.stack = menu.stack[:len(menu.stack)-1]
|
||||
menu.Push(buildHome())
|
||||
menu.focus--
|
||||
},
|
||||
})
|
||||
|
||||
list.children = append(list.children, entry{
|
||||
icon: "file",
|
||||
callbackOK: func() {
|
||||
menu.stack = menu.stack[:len(menu.stack)-1]
|
||||
menu.Push(buildMainMenu())
|
||||
menu.focus--
|
||||
},
|
||||
})
|
||||
|
||||
list.children = append(list.children, entry{
|
||||
label: "Settings",
|
||||
subLabel: "Configure Ludo",
|
||||
icon: "setting",
|
||||
icon: "tab-settings",
|
||||
callbackOK: func() {
|
||||
menu.stack = menu.stack[:len(menu.stack)-1]
|
||||
menu.Push(buildSettings())
|
||||
menu.focus--
|
||||
},
|
||||
})
|
||||
|
||||
list.children = append(list.children, entry{
|
||||
label: "History",
|
||||
subLabel: "Play again",
|
||||
icon: "history",
|
||||
callbackOK: func() {
|
||||
menu.Push(buildHistory())
|
||||
},
|
||||
})
|
||||
|
||||
list.children = append(list.children, getPlaylists()...)
|
||||
|
||||
list.children = append(list.children, entry{
|
||||
label: "Add games",
|
||||
subLabel: "Scan your collection",
|
||||
icon: "add",
|
||||
icon: "tab-scan",
|
||||
callbackOK: func() {
|
||||
usr, _ := user.Current()
|
||||
menu.stack = menu.stack[:len(menu.stack)-1]
|
||||
menu.Push(buildExplorer(usr.HomeDir, nil,
|
||||
func(path string) {
|
||||
scanner.ScanDir(path, refreshTabs)
|
||||
},
|
||||
func(path string) { scanner.ScanDir(path, nil) },
|
||||
&entry{
|
||||
label: "<Scan this directory>",
|
||||
icon: "scan",
|
||||
},
|
||||
nil,
|
||||
))
|
||||
menu.focus--
|
||||
},
|
||||
})
|
||||
|
||||
if state.LudOS {
|
||||
list.children = append(list.children, entry{
|
||||
icon: "tab-updater",
|
||||
callbackOK: func() {
|
||||
menu.stack = menu.stack[:len(menu.stack)-1]
|
||||
menu.Push(buildUpdater())
|
||||
menu.focus--
|
||||
},
|
||||
})
|
||||
|
||||
list.children = append(list.children, entry{
|
||||
icon: "tab-reboot",
|
||||
callbackOK: func() {
|
||||
askQuitConfirmation(func() { cleanReboot() })
|
||||
},
|
||||
})
|
||||
|
||||
list.children = append(list.children, entry{
|
||||
icon: "tab-shutdown",
|
||||
callbackOK: func() {
|
||||
askQuitConfirmation(func() { cleanShutdown() })
|
||||
},
|
||||
})
|
||||
} else {
|
||||
list.children = append(list.children, entry{
|
||||
icon: "tab-quit",
|
||||
callbackOK: func() {
|
||||
askQuitConfirmation(func() {
|
||||
menu.SetShouldClose(true)
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
list.segueMount()
|
||||
|
||||
return &list
|
||||
}
|
||||
|
||||
// refreshTabs is called after playlist scanning is complete. It inserts the new
|
||||
// playlists in the tabs, and makes sure that all the icons are positioned and
|
||||
// sized properly.
|
||||
func refreshTabs() {
|
||||
e := menu.stack[0].Entry()
|
||||
l := len(e.children)
|
||||
pls := getPlaylists()
|
||||
|
||||
// This assumes that the 3 first tabs are not playlists, and that the last
|
||||
// tab is the scanner.
|
||||
e.children = append(e.children[:3], append(pls, e.children[l-1:]...)...)
|
||||
|
||||
// Update which tab is the active tab after the refresh
|
||||
if e.ptr >= 3 {
|
||||
e.ptr += len(pls) - (l - 4)
|
||||
}
|
||||
|
||||
// Ensure new icons are styled properly
|
||||
for i := range e.children {
|
||||
if i == e.ptr {
|
||||
e.children[i].iconAlpha = 1
|
||||
e.children[i].scale = 0.75
|
||||
e.children[i].width = 500
|
||||
} else if i < e.ptr {
|
||||
e.children[i].iconAlpha = 1
|
||||
e.children[i].scale = 0.25
|
||||
e.children[i].width = 128
|
||||
} else if i > e.ptr {
|
||||
e.children[i].iconAlpha = 1
|
||||
e.children[i].scale = 0.25
|
||||
e.children[i].width = 128
|
||||
}
|
||||
}
|
||||
|
||||
// Adapt the tabs scroll value
|
||||
if len(menu.stack) == 1 {
|
||||
menu.scroll = float32(e.ptr * 128)
|
||||
} else {
|
||||
e.children[e.ptr].margin = 1360
|
||||
menu.scroll = float32(e.ptr*128 + 680)
|
||||
}
|
||||
func (s *sceneTabs) Entry() *entry {
|
||||
return &s.entry
|
||||
}
|
||||
|
||||
// getPlaylists browse the filesystem for CSV files, parse them and returns
|
||||
// a list of menu entries. It is used in the tabs, but could be used somewhere
|
||||
// else too.
|
||||
func getPlaylists() []entry {
|
||||
playlists.Load()
|
||||
|
||||
// To store the keys in slice in sorted order
|
||||
var keys []string
|
||||
for k := range playlists.Playlists {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
var pls []entry
|
||||
for _, path := range keys {
|
||||
path := path
|
||||
filename := utils.FileName(path)
|
||||
count := playlists.Count(path)
|
||||
label := playlists.ShortName(filename)
|
||||
pls = append(pls, entry{
|
||||
label: label,
|
||||
subLabel: fmt.Sprintf("%d Games", count),
|
||||
icon: filename,
|
||||
callbackOK: func() {
|
||||
menu.Push(buildPlaylist(path))
|
||||
},
|
||||
callbackX: func() { askDeletePlaylistConfirmation(func() { deletePlaylist(path) }) },
|
||||
})
|
||||
}
|
||||
return pls
|
||||
func (s *sceneTabs) segueMount() {
|
||||
s.animate()
|
||||
}
|
||||
|
||||
func deletePlaylist(path string) {
|
||||
err := os.Remove(path)
|
||||
if err != nil {
|
||||
ntf.DisplayAndLog(ntf.Error, "Menu", "Could not delete playlist: %s", err.Error())
|
||||
return
|
||||
}
|
||||
menu.stack[0].Entry().ptr++
|
||||
delete(playlists.Playlists, path)
|
||||
refreshTabs()
|
||||
func (s *sceneTabs) segueBack() {
|
||||
s.animate()
|
||||
}
|
||||
|
||||
func (tabs *sceneTabs) Entry() *entry {
|
||||
return &tabs.entry
|
||||
func (s *sceneTabs) animate() {
|
||||
}
|
||||
|
||||
func (tabs *sceneTabs) segueMount() {
|
||||
for i := range tabs.children {
|
||||
e := &tabs.children[i]
|
||||
|
||||
if i == tabs.ptr {
|
||||
e.labelAlpha = 1
|
||||
e.iconAlpha = 1
|
||||
e.scale = 0.75
|
||||
e.width = 500
|
||||
} else if i < tabs.ptr {
|
||||
e.labelAlpha = 0
|
||||
e.iconAlpha = 1
|
||||
e.scale = 0.25
|
||||
e.width = 128
|
||||
} else if i > tabs.ptr {
|
||||
e.labelAlpha = 0
|
||||
e.iconAlpha = 1
|
||||
e.scale = 0.25
|
||||
e.width = 128
|
||||
}
|
||||
}
|
||||
|
||||
tabs.animate()
|
||||
// left tabs are never removed, we don't need to implement this callback
|
||||
func (s *sceneTabs) segueNext() {
|
||||
}
|
||||
|
||||
func (tabs *sceneTabs) segueBack() {
|
||||
tabs.animate()
|
||||
}
|
||||
|
||||
func (tabs *sceneTabs) animate() {
|
||||
for i := range tabs.children {
|
||||
e := &tabs.children[i]
|
||||
|
||||
var labelAlpha, scale, width float32
|
||||
if i == tabs.ptr {
|
||||
labelAlpha = 1
|
||||
scale = 0.75
|
||||
width = 500
|
||||
} else if i < tabs.ptr {
|
||||
labelAlpha = 0
|
||||
scale = 0.25
|
||||
width = 128
|
||||
} else if i > tabs.ptr {
|
||||
labelAlpha = 0
|
||||
scale = 0.25
|
||||
width = 128
|
||||
}
|
||||
|
||||
menu.tweens[&e.labelAlpha] = gween.New(e.labelAlpha, labelAlpha, 0.15, ease.OutSine)
|
||||
menu.tweens[&e.iconAlpha] = gween.New(e.iconAlpha, 1, 0.15, ease.OutSine)
|
||||
menu.tweens[&e.scale] = gween.New(e.scale, scale, 0.15, ease.OutSine)
|
||||
menu.tweens[&e.width] = gween.New(e.width, width, 0.15, ease.OutSine)
|
||||
menu.tweens[&e.margin] = gween.New(e.margin, 0, 0.15, ease.OutSine)
|
||||
}
|
||||
menu.tweens[&menu.scroll] = gween.New(menu.scroll, float32(tabs.ptr*128), 0.15, ease.OutSine)
|
||||
}
|
||||
|
||||
func (tabs *sceneTabs) segueNext() {
|
||||
cur := &tabs.children[tabs.ptr]
|
||||
menu.tweens[&cur.margin] = gween.New(cur.margin, 1360, 0.15, ease.OutSine)
|
||||
menu.tweens[&menu.scroll] = gween.New(menu.scroll, menu.scroll+680, 0.15, ease.OutSine)
|
||||
for i := range tabs.children {
|
||||
e := &tabs.children[i]
|
||||
if i != tabs.ptr {
|
||||
menu.tweens[&e.iconAlpha] = gween.New(e.iconAlpha, 0, 0.15, ease.OutSine)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (tabs *sceneTabs) update(dt float32) {
|
||||
// Right
|
||||
repeatRight(dt, input.NewState[0][libretro.DeviceIDJoypadRight] == 1, func() {
|
||||
tabs.ptr++
|
||||
if tabs.ptr >= len(tabs.children) {
|
||||
tabs.ptr = 0
|
||||
}
|
||||
audio.PlayEffect(audio.Effects["down"])
|
||||
tabs.animate()
|
||||
})
|
||||
|
||||
func (s *sceneTabs) update(dt float32) {
|
||||
// Left
|
||||
repeatLeft(dt, input.NewState[0][libretro.DeviceIDJoypadLeft] == 1, func() {
|
||||
tabs.ptr--
|
||||
if tabs.ptr < 0 {
|
||||
tabs.ptr = len(tabs.children) - 1
|
||||
s.ptr--
|
||||
if s.ptr < 0 {
|
||||
s.ptr = 0
|
||||
} else {
|
||||
audio.PlayEffect(audio.Effects["up"])
|
||||
menu.t = 0
|
||||
s.animate()
|
||||
}
|
||||
audio.PlayEffect(audio.Effects["up"])
|
||||
tabs.animate()
|
||||
})
|
||||
|
||||
// Right
|
||||
repeatRight(dt, input.NewState[0][libretro.DeviceIDJoypadRight] == 1, func() {
|
||||
s.ptr++
|
||||
if s.ptr >= len(s.children) {
|
||||
s.ptr = len(s.children) - 1
|
||||
} else {
|
||||
audio.PlayEffect(audio.Effects["down"])
|
||||
menu.t = 0
|
||||
s.animate()
|
||||
}
|
||||
})
|
||||
|
||||
// Down
|
||||
repeatDown(dt, input.NewState[0][libretro.DeviceIDJoypadDown] == 1, func() {
|
||||
audio.PlayEffect(audio.Effects["ok"])
|
||||
menu.t = 0
|
||||
menu.focus++
|
||||
s.animate()
|
||||
})
|
||||
|
||||
// OK
|
||||
if input.Released[0][libretro.DeviceIDJoypadA] == 1 {
|
||||
if tabs.children[tabs.ptr].callbackOK != nil {
|
||||
audio.PlayEffect(audio.Effects["ok"])
|
||||
tabs.segueNext()
|
||||
tabs.children[tabs.ptr].callbackOK()
|
||||
}
|
||||
}
|
||||
|
||||
// X
|
||||
if input.Released[0][libretro.DeviceIDJoypadX] == 1 {
|
||||
if tabs.children[tabs.ptr].callbackX != nil {
|
||||
tabs.children[tabs.ptr].callbackX()
|
||||
}
|
||||
audio.PlayEffect(audio.Effects["ok"])
|
||||
s.children[s.ptr].callbackOK()
|
||||
menu.t = 0
|
||||
menu.focus++
|
||||
s.animate()
|
||||
}
|
||||
}
|
||||
|
||||
func (tabs sceneTabs) render() {
|
||||
_, h := menu.GetFramebufferSize()
|
||||
func (s sceneTabs) render() {
|
||||
w, _ := menu.Window.GetFramebufferSize()
|
||||
|
||||
stackWidth := 710 * menu.ratio
|
||||
for i, e := range tabs.children {
|
||||
now := time.Now().Format("3:04PM")
|
||||
nowWidth := menu.BoldFont.Width(0.5*menu.ratio, now)
|
||||
menu.BoldFont.SetColor(black)
|
||||
menu.BoldFont.Printf(
|
||||
float32(w)-96*menu.ratio-nowWidth,
|
||||
90*menu.ratio, 0.5*menu.ratio, now)
|
||||
|
||||
cf := colorful.Hcl(float64(i)*20, 0.5, 0.5)
|
||||
c := video.Color{R: float32(cf.R), G: float32(cf.B), B: float32(cf.G), A: e.iconAlpha}
|
||||
if menu.focus > 2 {
|
||||
return
|
||||
}
|
||||
|
||||
x := -menu.scroll*menu.ratio + stackWidth + e.width/2*menu.ratio
|
||||
spacing := float32(96 + 32)
|
||||
totalWidth := spacing * float32(len(s.children)) * menu.ratio
|
||||
|
||||
stackWidth += e.width*menu.ratio + e.margin*menu.ratio
|
||||
|
||||
if e.labelAlpha > 0 {
|
||||
menu.Font.SetColor(c.Alpha(e.labelAlpha))
|
||||
lw := menu.Font.Width(0.5*menu.ratio, e.label)
|
||||
menu.Font.Printf(x-lw/2, float32(int(float32(h)/2+250*menu.ratio)), 0.5*menu.ratio, e.label)
|
||||
lw = menu.Font.Width(0.4*menu.ratio, e.subLabel)
|
||||
menu.Font.Printf(x-lw/2, float32(int(float32(h)/2+330*menu.ratio)), 0.4*menu.ratio, e.subLabel)
|
||||
for i, e := range s.children {
|
||||
if i == s.ptr && menu.focus == 1 {
|
||||
blink := float32(math.Cos(menu.t))
|
||||
menu.DrawImage(menu.icons["selection"],
|
||||
float32(w)/2-totalWidth/2+float32(i)*spacing*menu.ratio+96*menu.ratio/2-8*menu.ratio,
|
||||
32*menu.ratio-8*menu.ratio,
|
||||
96*menu.ratio+16*menu.ratio, 96*menu.ratio+16*menu.ratio, 1, 1,
|
||||
white.Alpha(1-blink))
|
||||
}
|
||||
|
||||
menu.DrawImage(menu.icons["hexagon"],
|
||||
x-220*e.scale*menu.ratio, float32(h)/2-220*e.scale*menu.ratio,
|
||||
440*menu.ratio, 440*menu.ratio, e.scale, c)
|
||||
|
||||
menu.DrawImage(menu.icons["circle"],
|
||||
float32(w)/2-totalWidth/2+float32(i)*spacing*menu.ratio+96*menu.ratio/2,
|
||||
32*menu.ratio,
|
||||
96*menu.ratio, 96*menu.ratio, 1, 0, white)
|
||||
menu.DrawImage(menu.icons[e.icon],
|
||||
x-128*e.scale*menu.ratio, float32(h)/2-128*e.scale*menu.ratio,
|
||||
256*menu.ratio, 256*menu.ratio, e.scale, white.Alpha(e.iconAlpha))
|
||||
float32(w)/2-totalWidth/2+float32(i)*spacing*menu.ratio+96*menu.ratio/2+24*menu.ratio,
|
||||
56*menu.ratio,
|
||||
48*menu.ratio, 48*menu.ratio, 1, 0, blue)
|
||||
}
|
||||
}
|
||||
|
||||
func (tabs sceneTabs) drawHintBar() {
|
||||
w, h := menu.GetFramebufferSize()
|
||||
menu.DrawRect(0, float32(h)-70*menu.ratio, float32(w), 70*menu.ratio, 0, lightGrey)
|
||||
func (s *sceneTabs) drawHintBar() {
|
||||
w, h := menu.Window.GetFramebufferSize()
|
||||
menu.DrawRect(0, float32(h)-88*menu.ratio, float32(w), 88*menu.ratio, 0, white)
|
||||
menu.DrawRect(0, float32(h)-88*menu.ratio, float32(w), 2*menu.ratio, 0, lightGrey)
|
||||
|
||||
_, _, leftRight, a, _, x, _, _, _, guide := hintIcons()
|
||||
_, _, leftRight, a, _, _, _, _, _, guide := hintIcons()
|
||||
|
||||
var stack float32
|
||||
lstack := float32(75) * menu.ratio
|
||||
rstack := float32(w) - 96*menu.ratio
|
||||
stackHintLeft(&lstack, leftRight, "Navigate", h)
|
||||
stackHintRight(&rstack, a, "Ok", h)
|
||||
if state.CoreRunning {
|
||||
stackHint(&stack, guide, "RESUME", h)
|
||||
}
|
||||
stackHint(&stack, leftRight, "NAVIGATE", h)
|
||||
stackHint(&stack, a, "OPEN", h)
|
||||
|
||||
list := menu.stack[0].Entry()
|
||||
if list.children[list.ptr].callbackX != nil {
|
||||
stackHint(&stack, x, "DELETE", h)
|
||||
stackHintRight(&rstack, guide, "Resume", h)
|
||||
}
|
||||
}
|
||||
@@ -16,11 +16,11 @@ type sceneUpdater struct {
|
||||
|
||||
func buildUpdater() Scene {
|
||||
var list sceneUpdater
|
||||
list.label = "Updater Menu"
|
||||
list.label = "Update LudOS"
|
||||
|
||||
list.children = append(list.children, entry{
|
||||
label: "Checking updates",
|
||||
icon: "reload",
|
||||
icon: "reset",
|
||||
})
|
||||
|
||||
list.segueMount()
|
||||
@@ -83,11 +83,11 @@ func (s *sceneUpdater) update(dt float32) {
|
||||
if ludos.IsDownloading() {
|
||||
s.children[0].label = fmt.Sprintf(
|
||||
"Downloading update %.0f%%%%", ludos.GetProgress()*100)
|
||||
s.children[0].icon = "reload"
|
||||
s.children[0].icon = "reset"
|
||||
s.children[0].callbackOK = nil
|
||||
} else if ludos.IsDone() {
|
||||
s.children[0].label = "Reboot and upgrade"
|
||||
s.children[0].icon = "reload"
|
||||
s.children[0].label = "Reboot and update"
|
||||
s.children[0].icon = "reset"
|
||||
s.children[0].callbackOK = func() {
|
||||
cmd := exec.Command("/usr/sbin/shutdown", "-r", "now")
|
||||
core.UnloadGame()
|
||||
|
||||
+10
-8
@@ -15,7 +15,7 @@ func buildWiFi() Scene {
|
||||
|
||||
list.children = append(list.children, entry{
|
||||
label: "Looking for networks",
|
||||
icon: "reload",
|
||||
icon: "reset",
|
||||
})
|
||||
|
||||
list.segueMount()
|
||||
@@ -32,7 +32,7 @@ func buildWiFi() Scene {
|
||||
network := network
|
||||
list.children = append(list.children, entry{
|
||||
label: network.SSID,
|
||||
icon: "menu_network",
|
||||
icon: "wifi",
|
||||
stringValue: func() string { return ludos.NetworkStatus(network) },
|
||||
callbackOK: func() {
|
||||
list.segueNext()
|
||||
@@ -85,15 +85,17 @@ func (s *sceneWiFi) render() {
|
||||
}
|
||||
|
||||
func (s *sceneWiFi) drawHintBar() {
|
||||
w, h := menu.GetFramebufferSize()
|
||||
menu.DrawRect(0, float32(h)-70*menu.ratio, float32(w), 70*menu.ratio, 0, lightGrey)
|
||||
w, h := menu.Window.GetFramebufferSize()
|
||||
menu.DrawRect(0, float32(h)-88*menu.ratio, float32(w), 88*menu.ratio, 0, white)
|
||||
menu.DrawRect(0, float32(h)-88*menu.ratio, float32(w), 2*menu.ratio, 0, lightGrey)
|
||||
|
||||
_, upDown, _, a, b, _, _, _, _, _ := hintIcons()
|
||||
|
||||
var stack float32
|
||||
stackHint(&stack, upDown, "NAVIGATE", h)
|
||||
stackHint(&stack, b, "BACK", h)
|
||||
lstack := float32(75) * menu.ratio
|
||||
rstack := float32(w) - 96*menu.ratio
|
||||
stackHintLeft(&lstack, upDown, "Navigate", h)
|
||||
if s.children[0].callbackOK != nil {
|
||||
stackHint(&stack, a, "CONNECT", h)
|
||||
stackHintRight(&rstack, a, "Connect", h)
|
||||
}
|
||||
stackHintRight(&rstack, b, "Back", h)
|
||||
}
|
||||
+7
-12
@@ -14,6 +14,9 @@ import (
|
||||
|
||||
// Downloads a thumbnail from the web and cache it to the local filesystem.
|
||||
func downloadThumbnail(list *entry, i int, url, folderPath, path string) {
|
||||
if i >= len(list.children) {
|
||||
return
|
||||
}
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
list.children[i].thumbnail = menu.icons["img-broken"]
|
||||
@@ -61,7 +64,7 @@ func scrubIllegalChars(str string) string {
|
||||
}
|
||||
|
||||
// Draws a thumbnail in the playlist scene.
|
||||
func drawThumbnail(list *entry, i int, system, gameName string, x, y, w, h, scale float32, color video.Color) {
|
||||
func drawThumbnail(list *entry, i int, system, gameName string, x, y, w, h, scale float32, c video.Color) {
|
||||
folderPath := filepath.Join(settings.Current.ThumbnailsDirectory, system, "Named_Snaps")
|
||||
legalName := scrubIllegalChars(gameName)
|
||||
path := filepath.Join(folderPath, legalName+".png")
|
||||
@@ -76,26 +79,18 @@ func drawThumbnail(list *entry, i int, system, gameName string, x, y, w, h, scal
|
||||
}
|
||||
}
|
||||
|
||||
menu.DrawImage(
|
||||
list.children[i].thumbnail,
|
||||
x, y, w, h, scale,
|
||||
color,
|
||||
)
|
||||
menu.DrawThumbnail(list.children[i].thumbnail, x, y, w, h, scale, 0.07, c)
|
||||
}
|
||||
|
||||
// Draws a thumbnail in the savestates scene.
|
||||
func drawSavestateThumbnail(list *entry, i int, path string, x, y, w, h, scale float32, color video.Color) {
|
||||
func drawSavestateThumbnail(list *entry, i int, path string, x, y, w, h, scale float32, c video.Color) {
|
||||
if list.children[i].thumbnail == 0 {
|
||||
if _, err := os.Stat(path); !os.IsNotExist(err) {
|
||||
list.children[i].thumbnail = video.NewImage(path)
|
||||
}
|
||||
}
|
||||
|
||||
menu.DrawImage(
|
||||
list.children[i].thumbnail,
|
||||
x, y, w, h, scale,
|
||||
color,
|
||||
)
|
||||
menu.DrawThumbnail(list.children[i].thumbnail, x, y, w, h, scale, 0, c)
|
||||
}
|
||||
|
||||
func freeThumbnail(list *entry, i int) {
|
||||
|
||||
+29
-5
@@ -70,11 +70,27 @@ func ScanDir(dir string, doneCb func()) {
|
||||
f.Close()
|
||||
i++
|
||||
}
|
||||
doneCb()
|
||||
if doneCb != nil {
|
||||
doneCb()
|
||||
}
|
||||
n.Update(ntf.Success, "Done scanning. %d new games found.", i)
|
||||
}()
|
||||
}
|
||||
|
||||
// ScanFile scans a single file
|
||||
func ScanFile(path string, doneCb func(dat.Game)) {
|
||||
dir := filepath.Dir(path)
|
||||
roms := []string{path}
|
||||
games := make(chan (dat.Game))
|
||||
go Scan(dir, roms, games, nil)
|
||||
go func() {
|
||||
game := <-games
|
||||
if doneCb != nil {
|
||||
doneCb(game)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Returns the checksum and headerless checksum of a ROM
|
||||
func checksumHeaderless(rom *zip.File, headerSize uint) (uint32, uint32, error) {
|
||||
h, err := rom.Open()
|
||||
@@ -109,7 +125,9 @@ func Scan(dir string, roms []string, games chan (dat.Game), n *ntf.Notification)
|
||||
// Open the ZIP archive
|
||||
z, err := zip.OpenReader(f)
|
||||
if err != nil {
|
||||
n.Update(ntf.Error, err.Error())
|
||||
if n != nil {
|
||||
n.Update(ntf.Error, err.Error())
|
||||
}
|
||||
continue
|
||||
}
|
||||
for _, rom := range z.File {
|
||||
@@ -127,14 +145,18 @@ func Scan(dir string, roms []string, games chan (dat.Game), n *ntf.Notification)
|
||||
} else if rom.CRC32 > 0 {
|
||||
// Look for a matching game entry in the database
|
||||
state.DB.FindByCRC(f, rom.Name, rom.CRC32, games)
|
||||
n.Update(ntf.Info, strconv.Itoa(i)+"/"+strconv.Itoa(len(roms))+" "+f)
|
||||
if n != nil {
|
||||
n.Update(ntf.Info, strconv.Itoa(i)+"/"+strconv.Itoa(len(roms))+" "+f)
|
||||
}
|
||||
}
|
||||
}
|
||||
z.Close()
|
||||
case ".cue":
|
||||
// Look for a matching game entry in the database
|
||||
state.DB.FindByROMName(f, filepath.Base(f), 0, games)
|
||||
n.Update(ntf.Info, strconv.Itoa(i)+"/"+strconv.Itoa(len(roms))+" "+f)
|
||||
if n != nil {
|
||||
n.Update(ntf.Info, strconv.Itoa(i)+"/"+strconv.Itoa(len(roms))+" "+f)
|
||||
}
|
||||
case ".32x", "a52", ".a78", ".col", ".crt", ".d64", ".pce", ".fds", ".gb", ".gba", ".gbc", ".gen", ".gg", ".ipf", ".j64", ".jag", ".lnx", ".md", ".n64", ".nes", ".ngc", ".nds", ".rom", ".sfc", ".sg", ".smc", ".smd", ".sms", ".ws", ".wsc":
|
||||
bytes, err := ioutil.ReadFile(f)
|
||||
if err != nil {
|
||||
@@ -143,7 +165,9 @@ func Scan(dir string, roms []string, games chan (dat.Game), n *ntf.Notification)
|
||||
}
|
||||
CRC32 := crc32.ChecksumIEEE(bytes)
|
||||
state.DB.FindByCRC(f, utils.FileName(f), CRC32, games)
|
||||
n.Update(ntf.Info, strconv.Itoa(i)+"/"+strconv.Itoa(len(roms))+" "+f)
|
||||
if n != nil {
|
||||
n.Update(ntf.Info, strconv.Itoa(i)+"/"+strconv.Itoa(len(roms))+" "+f)
|
||||
}
|
||||
}
|
||||
}
|
||||
close(games)
|
||||
|
||||
@@ -20,7 +20,7 @@ uniform vec4 color;
|
||||
COMPAT_VARYING vec2 fragTexCoord;
|
||||
|
||||
vec4 demultiply(vec4 c) {
|
||||
return vec4(c.rgb/c.a, c.a);
|
||||
return vec4(c.rgb/c.a, max(0.0, c.a));
|
||||
}
|
||||
|
||||
void main() {
|
||||
|
||||
+2
-2
@@ -231,8 +231,8 @@ func LoadFont(file string, scale int32, windowWidth int, windowHeight int) (*Fon
|
||||
}
|
||||
|
||||
// SetColor allows you to set the text color to be used when you draw the text
|
||||
func (f *Font) SetColor(color Color) {
|
||||
f.color = color
|
||||
func (f *Font) SetColor(c Color) {
|
||||
f.color = c
|
||||
}
|
||||
|
||||
// UpdateResolution passes the new framebuffer size to the font shader
|
||||
|
||||
+39
-3
@@ -66,12 +66,14 @@ func rotateUV(va []float32, rot uint) []float32 {
|
||||
}
|
||||
|
||||
// DrawImage draws an image with x, y, w, h
|
||||
func (video *Video) DrawImage(image uint32, x, y, w, h float32, scale float32, c Color) {
|
||||
func (video *Video) DrawImage(image uint32, x, y, w, h, scale, r float32, c Color) {
|
||||
|
||||
va := video.vertexArray(x, y, w, h, scale)
|
||||
|
||||
gl.UseProgram(video.demulProgram)
|
||||
gl.Uniform4f(gl.GetUniformLocation(video.demulProgram, gl.Str("color\x00")), c.R, c.G, c.B, c.A)
|
||||
gl.UseProgram(video.roundedProgram)
|
||||
gl.Uniform4f(gl.GetUniformLocation(video.roundedProgram, gl.Str("color\x00")), c.R, c.G, c.B, c.A)
|
||||
gl.Uniform1f(gl.GetUniformLocation(video.roundedProgram, gl.Str("radius\x00")), r)
|
||||
gl.Uniform2f(gl.GetUniformLocation(video.roundedProgram, gl.Str("size\x00")), w, h)
|
||||
gl.Enable(gl.BLEND)
|
||||
gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
|
||||
bindVertexArray(video.vao)
|
||||
@@ -86,6 +88,31 @@ func (video *Video) DrawImage(image uint32, x, y, w, h float32, scale float32, c
|
||||
gl.Disable(gl.BLEND)
|
||||
}
|
||||
|
||||
// DrawThumbnail draws an image with x, y, w, h
|
||||
func (video *Video) DrawThumbnail(image uint32, x, y, w, h, scale, r float32, c Color) {
|
||||
|
||||
va := video.vertexArray(x, y, w, h, scale)
|
||||
|
||||
gl.UseProgram(video.roundedProgram)
|
||||
gl.Uniform4f(gl.GetUniformLocation(video.roundedProgram, gl.Str("color\x00")), c.R, c.G, c.B, c.A)
|
||||
gl.Uniform1f(gl.GetUniformLocation(video.roundedProgram, gl.Str("radius\x00")), r)
|
||||
gl.Uniform2f(gl.GetUniformLocation(video.roundedProgram, gl.Str("size\x00")), w, h)
|
||||
gl.Enable(gl.BLEND)
|
||||
gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
|
||||
bindVertexArray(video.vao)
|
||||
gl.ActiveTexture(gl.TEXTURE0)
|
||||
gl.BindTexture(gl.TEXTURE_2D, image)
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
|
||||
gl.BindBuffer(gl.ARRAY_BUFFER, video.vbo)
|
||||
gl.BufferData(gl.ARRAY_BUFFER, len(va)*4, gl.Ptr(va), gl.STATIC_DRAW)
|
||||
gl.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)
|
||||
bindVertexArray(0)
|
||||
gl.BindTexture(gl.TEXTURE_2D, 0)
|
||||
gl.UseProgram(0)
|
||||
gl.Disable(gl.BLEND)
|
||||
}
|
||||
|
||||
// ScissorStart starts a GL scissor box, don't forget to call ScissorEnd
|
||||
func (video *Video) ScissorStart(x, y, w, h int32) {
|
||||
gl.Enable(gl.SCISSOR_TEST)
|
||||
@@ -148,10 +175,13 @@ func (video *Video) DrawRect(x, y, w, h, r float32, c Color) {
|
||||
gl.Enable(gl.BLEND)
|
||||
gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
|
||||
bindVertexArray(video.vao)
|
||||
gl.ActiveTexture(gl.TEXTURE0)
|
||||
gl.BindTexture(gl.TEXTURE_2D, video.white)
|
||||
gl.BindBuffer(gl.ARRAY_BUFFER, video.vbo)
|
||||
gl.BufferData(gl.ARRAY_BUFFER, len(va)*4, gl.Ptr(va), gl.STATIC_DRAW)
|
||||
gl.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)
|
||||
bindVertexArray(0)
|
||||
gl.BindTexture(gl.TEXTURE_2D, 0)
|
||||
gl.UseProgram(0)
|
||||
gl.Disable(gl.BLEND)
|
||||
}
|
||||
@@ -219,3 +249,9 @@ func NewImage(file string) uint32 {
|
||||
|
||||
return textureLoad(rgba)
|
||||
}
|
||||
|
||||
func newWhite() uint32 {
|
||||
rgba := image.NewRGBA(image.Rect(0, 0, 8, 8))
|
||||
draw.Draw(rgba, rgba.Bounds(), image.White, image.Point{0, 0}, draw.Src)
|
||||
return textureLoad(rgba)
|
||||
}
|
||||
@@ -14,6 +14,7 @@ out vec4 COMPAT_FRAGCOLOR;
|
||||
#define COMPAT_FRAGCOLOR gl_FragColor
|
||||
#endif
|
||||
|
||||
uniform sampler2D Texture;
|
||||
uniform vec4 color;
|
||||
uniform float radius;
|
||||
uniform vec2 size;
|
||||
@@ -24,11 +25,15 @@ float udRoundBox(vec2 p, vec2 b, float r) {
|
||||
return length(max(abs(p)-b+r,0.0))-r;
|
||||
}
|
||||
|
||||
vec4 demultiply(vec4 c) {
|
||||
return vec4(c.rgb/c.a, c.a);
|
||||
}
|
||||
|
||||
void main() {
|
||||
float ratio = size.x / size.y;
|
||||
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, c.a * (1.0-smoothstep(0.00002,0.0001,b)));
|
||||
COMPAT_FRAGCOLOR = demultiply(COMPAT_TEXTURE(Texture, fragTexCoord)) * vec4(c.r, c.g, c.b, c.a * (1.0-smoothstep(0.0001,0.001,b)));
|
||||
}
|
||||
` + "\x00"
|
||||
+12
-3
@@ -17,10 +17,12 @@ import (
|
||||
|
||||
// Video holds the state of the video package
|
||||
type Video struct {
|
||||
Window *glfw.Window
|
||||
Geom libretro.GameGeometry
|
||||
Font *Font
|
||||
Window *glfw.Window
|
||||
Geom libretro.GameGeometry
|
||||
Font *Font
|
||||
BoldFont *Font
|
||||
|
||||
white uint32 // white texture for sampler2D
|
||||
program uint32 // current program used for the game quad
|
||||
defaultProgram uint32 // default program used for the game quad
|
||||
sharpBilinearProgram uint32 // sharp bilinear program used for the game quad
|
||||
@@ -123,6 +125,11 @@ func (video *Video) Configure(fullscreen bool) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
boldFontPath := filepath.Join(settings.Current.AssetsDirectory, "boldfont.ttf")
|
||||
video.BoldFont, err = LoadFont(boldFontPath, int32(36*2), fbw, fbh)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Configure the vertex and fragment shaders
|
||||
video.defaultProgram, err = newProgram(vertexShader, defaultFragmentShader)
|
||||
@@ -206,6 +213,8 @@ func (video *Video) Configure(fullscreen bool) {
|
||||
|
||||
video.coreRatioViewport(fbw, fbh)
|
||||
|
||||
video.white = newWhite()
|
||||
|
||||
if e := gl.GetError(); e != gl.NO_ERROR {
|
||||
log.Printf("[Video] OpenGL error: %d\n", e)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user