Compare commits

...
49 Commits
Author SHA1 Message Date
Jean-André Santoni 8ad9611123 More fixes 2021-08-24 12:23:01 +07:00
Jean-André Santoni bca097e333 Fix empty start 2021-08-24 12:19:50 +07:00
Jean-André Santoni 189c56d008 Menu focus fixes 2021-08-23 13:54:10 +07:00
Jean-André Santoni fc332586a6 Merge remote-tracking branch 'origin/master' into kivutar/newdesign 2021-08-23 13:28:57 +07:00
Jean-André Santoni 98966a71c7 Merge remote-tracking branch 'origin/master' into kivutar/newdesign 2021-07-16 18:27:06 +07:00
Jean-André Santoni e23af93404 Merge remote-tracking branch 'origin/master' into kivutar/newdesign 2021-07-14 21:08:24 +07:00
Jean-André Santoni 437ca0fa92 Merge branch 'master' into kivutar/newdesign 2021-05-30 14:46:44 +07:00
Jean-André Santoni ce142a4972 Merge branch 'master' into kivutar/newdesign 2021-05-24 10:22:08 +07:00
Jean-André Santoni 61f399ed68 Add core disc options 2021-05-24 10:21:30 +07:00
Jean-André Santoni 1e98126d08 Adapt disk control interface 2021-05-24 10:11:29 +07:00
Jean-André Santoni 1483700d44 N64 on Windows (#424)
* N64 on Windows

* Fix
2021-05-24 10:11:29 +07:00
Jean-André Santoni 654bc37bd6 Update dependencies 2021-05-24 10:10:33 +07:00
Jean-André Santoni eb522a6115 Merge branch 'master' into kivutar/newdesign 2021-05-20 18:42:31 +07:00
Jean-André Santoni 7303547284 Merge branch 'master' into kivutar/newdesign 2021-05-14 20:25:33 +07:00
Jean-André Santoni 876a072fc2 Merge branch 'master' into kivutar/newdesign 2021-01-28 10:48:48 +07:00
Jean-André Santoni dcb50a2c47 Free thumbnails 2020-08-15 15:57:52 +07:00
Jean-André Santoni 465ec1729f Add a way to manually load cores and games 2020-08-15 15:44:09 +07:00
Jean-André Santoni 58f9aeef6b Fix some linter messages 2020-08-15 15:29:04 +07:00
Jean-André Santoni bf47149392 Get rid of go-colorful 2020-08-15 11:10:41 +07:00
Jean-André Santoni a0a36d55e7 Improve transitions 2020-08-11 22:43:59 +07:00
Jean-André Santoni 013da42c81 Fix loading a game from cli 2020-08-11 19:58:20 +07:00
Jean-André Santoni b3a5cfcb38 Fix icons coloring 2020-08-08 14:09:43 +07:00
Jean-André Santoni be63145460 Fix wifi icons 2020-08-07 18:12:54 +07:00
Jean-André Santoni df3d86e92f Performance improvement 2020-08-07 18:00:37 +07:00
Jean-André Santoni 669da8fa40 Fixes 2020-08-07 17:41:36 +07:00
Jean-André Santoni 55040f528d Use a color palette 2020-08-07 16:40:57 +07:00
Jean-André Santoni 20dc029003 New scrolling mode 2020-08-07 15:14:28 +07:00
Jean-André Santoni 3742fc21cf Fix modals 2020-08-07 10:58:14 +07:00
Jean-André Santoni d0c2786900 Display flags 2020-08-06 21:51:52 +07:00
Jean-André Santoni 4551641c87 Make background transparent only on quick menu 2020-08-05 23:35:18 +07:00
Jean-André Santoni 784c76bf10 Fix savestates and hide tabs 2020-08-05 14:06:52 +07:00
Jean-André Santoni 75582663c3 Fixes 2020-08-05 13:49:43 +07:00
Jean-André Santoni fce5b93d2c Update icons for LudOS 2020-08-05 13:42:29 +07:00
Jean-André Santoni 2a07b39372 Display list title 2020-08-05 13:28:13 +07:00
Jean-André Santoni 6f1655e5f5 Display time 2020-08-04 16:06:56 +07:00
Jean-André Santoni aa41965711 Fix savestates scene 2020-08-04 15:42:57 +07:00
Jean-André Santoni c2945fa6e0 Improve generic cursor 2020-08-04 14:32:13 +07:00
Jean-André Santoni 117417f9d5 Fixes 2020-08-04 14:17:08 +07:00
Jean-André Santoni c3b23e756a Recenter generic lists 2020-08-04 12:55:56 +07:00
Jean-André Santoni 8087e65cd7 Make tabs work 2020-08-04 12:29:56 +07:00
Jean-André Santoni cab234a1ff Display tabs 2020-08-04 11:45:15 +07:00
Jean-André Santoni 3cf76e948a Code cleaning 2020-08-04 11:08:58 +07:00
Jean-André Santoni ba40c54544 Tweaks 2020-08-03 22:48:40 +07:00
Jean-André Santoni 2a6433045e Rounded corners for thumbnails 2020-08-03 19:58:40 +07:00
Jean-André Santoni 0ecad7cf3c Allow stacking hints to the right too 2020-08-03 16:27:35 +07:00
Jean-André Santoni 1011d9975f Can load games 2020-08-03 15:55:41 +07:00
Jean-André Santoni 9c516b7fdf Perf improvement 2020-08-03 14:30:37 +07:00
Jean-André Santoni 1b82ca1aae Thumbnails and bold fonts 2020-08-02 22:54:11 +07:00
Jean-André Santoni 5d7a07479c Home page layout 2020-08-02 20:15:50 +07:00
31 changed files with 1014 additions and 960 deletions

No files matched your search

+1 -1
Submodule assets updated: e72d3737fb...98b3c0a088.
+19 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
}
-2
View File
@@ -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() {
+1
View File
@@ -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
View File
@@ -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)
}
+14 -7
View File
@@ -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)
}
+8 -6
View File
@@ -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)
}
+4 -4
View File
@@ -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,
+1 -1
View File
@@ -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
View File
@@ -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
}
}
+401
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
}
}
+5 -5
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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)
}
+6 -1
View File
@@ -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
View File
@@ -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)
}