Skip to content

Commit 70b4455

Browse files
authored
Thumbnails (#88)
* Initial thumbnails implementation * Fix an animation bug * Fix a drawing bug * Fix another display issue * Resets thumbnails during context reset * Fix a display issue * FBA thumbnails * Active game icon * Improved layout
1 parent a92d96d commit 70b4455

7 files changed

Lines changed: 140 additions & 57 deletions

File tree

assets

menu/menu.go

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,27 +25,30 @@ type entry struct {
2525
yp, scale float32
2626
width float32
2727
label, subLabel string
28-
path string
28+
path string // full path of the rom linked to the entry
2929
labelAlpha float32
3030
icon string
3131
iconAlpha float32
3232
tagAlpha float32
33-
ptr int
34-
callbackOK func()
33+
callbackOK func() // callback executed when user presses OK
3534
value func() interface{}
3635
stringValue func() string
37-
widget func(*entry)
38-
incr func(int)
39-
tags []string
36+
widget func(*entry) // widget draw callback used in settings
37+
incr func(int) // increment callback used in settings
38+
tags []string // flags extracted from game title
39+
thumbnail uint32 // thumbnail texture id
40+
gameName string // title of the game in db, used for thumbnails
4041
cursor struct {
4142
alpha float32
4243
yp float32
4344
}
44-
children []entry
45+
children []entry // children entries
46+
ptr int // index of the active child
4547
}
4648

4749
// Scene represents a page of the UI
48-
// A Scene is typically an entry displaying its own children
50+
// A scene is typically an entry displaying its own children
51+
// A segue is a smooth transition between two scenes.
4952
type Scene interface {
5053
segueMount()
5154
segueNext()
@@ -98,8 +101,8 @@ func Render() {
98101

99102
menu := menu.stack[i]
100103
menu.render()
101-
menu.drawHintBar()
102104
}
105+
menu.stack[currentScreenIndex].drawHintBar()
103106
}
104107

105108
func genericDrawHintBar() {
@@ -141,7 +144,7 @@ func genericSegueMount(list *entry) {
141144
e.labelAlpha = 0
142145
e.iconAlpha = 0
143146
e.tagAlpha = 1
144-
e.scale = 0.5
147+
e.scale = 1.5
145148
} else if i < list.ptr {
146149
e.yp = 0.4 + 0.3 + 0.08*float32(i-list.ptr)
147150
e.labelAlpha = 0
@@ -173,7 +176,7 @@ func genericAnimate(list *entry) {
173176
labelAlpha = 1
174177
iconAlpha = 1
175178
tagAlpha = 1
176-
scale = 0.5
179+
scale = 1.5
177180
} else if i < list.ptr {
178181
yp = 0.4 + 0.08*float32(i-list.ptr)
179182
labelAlpha = 0.75
@@ -204,32 +207,32 @@ func genericSegueNext(list *entry) {
204207
for i := range list.children {
205208
e := &list.children[i]
206209

207-
var yp, labelAlpha, iconAlpha, tagAlpha, s float32
210+
var yp, labelAlpha, iconAlpha, tagAlpha, scale float32
208211
if i == list.ptr {
209212
yp = 0.5 - 0.3
210213
labelAlpha = 0
211214
iconAlpha = 0
212215
tagAlpha = 1
213-
s = 1.0
216+
scale = 1.5
214217
} else if i < list.ptr {
215218
yp = 0.4 - 0.3 + 0.08*float32(i-list.ptr)
216219
labelAlpha = 0
217220
iconAlpha = 0
218221
tagAlpha = 0
219-
s = 0.5
222+
scale = 0.5
220223
} else if i > list.ptr {
221224
yp = 0.6 - 0.3 + 0.08*float32(i-list.ptr)
222225
labelAlpha = 0
223226
iconAlpha = 0
224227
tagAlpha = 0
225-
s = 0.5
228+
scale = 0.5
226229
}
227230

228231
menu.tweens[&e.yp] = gween.New(e.yp, yp, 0.15, ease.OutSine)
229232
menu.tweens[&e.labelAlpha] = gween.New(e.labelAlpha, labelAlpha, 0.15, ease.OutSine)
230233
menu.tweens[&e.iconAlpha] = gween.New(e.iconAlpha, iconAlpha, 0.15, ease.OutSine)
231234
menu.tweens[&e.tagAlpha] = gween.New(e.tagAlpha, tagAlpha, 0.15, ease.OutSine)
232-
menu.tweens[&e.scale] = gween.New(e.scale, s, 0.15, ease.OutSine)
235+
menu.tweens[&e.scale] = gween.New(e.scale, scale, 0.15, ease.OutSine)
233236
}
234237
menu.tweens[&list.cursor.alpha] = gween.New(list.cursor.alpha, 0, 0.15, ease.OutSine)
235238
menu.tweens[&list.cursor.yp] = gween.New(list.cursor.yp, 0.5-0.3, 0.15, ease.OutSine)
@@ -272,26 +275,26 @@ func genericRender(list *entry) {
272275
}
273276

274277
vid.DrawImage(menu.icons[e.icon],
275-
610*menu.ratio-64*e.scale*menu.ratio,
276-
float32(h)*e.yp-14*menu.ratio-64*e.scale*menu.ratio+fontOffset,
278+
610*menu.ratio-64*0.5*menu.ratio,
279+
float32(h)*e.yp-14*menu.ratio-64*0.5*menu.ratio+fontOffset,
277280
128*menu.ratio, 128*menu.ratio,
278-
e.scale, color)
281+
0.5, color)
279282

280283
if e.labelAlpha > 0 {
281284
vid.Font.SetColor(color.R, color.G, color.B, e.labelAlpha)
282285
vid.Font.Printf(
283286
670*menu.ratio,
284287
float32(h)*e.yp+fontOffset,
285-
0.7*menu.ratio, e.label)
288+
0.6*menu.ratio, e.label)
286289

287290
if e.widget != nil {
288291
e.widget(&e)
289292
} else if e.stringValue != nil {
290-
lw := vid.Font.Width(0.7*menu.ratio, e.stringValue())
293+
lw := vid.Font.Width(0.6*menu.ratio, e.stringValue())
291294
vid.Font.Printf(
292295
float32(w)-lw-128*menu.ratio,
293296
float32(h)*e.yp+fontOffset,
294-
0.7*menu.ratio, e.stringValue())
297+
0.6*menu.ratio, e.stringValue())
295298
}
296299
}
297300
}
@@ -313,6 +316,12 @@ func (menu *Menu) ContextReset() {
313316
filename := utils.Filename(path)
314317
menu.icons[filename] = video.NewImage("assets/flags/" + filename + ".png")
315318
}
319+
320+
currentScreenIndex := len(menu.stack) - 1
321+
curList := menu.stack[currentScreenIndex].Entry()
322+
for i := range curList.children {
323+
curList.children[i].thumbnail = 0
324+
}
316325
}
317326

318327
// fastForwardTweens finishes all the current animations in the queue.

menu/scene_playlist.go

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ func buildPlaylist(path string) Scene {
2727
strippedName, tags := extractTags(game.Name)
2828
list.children = append(list.children, entry{
2929
label: strippedName,
30+
gameName: game.Name,
3031
path: game.Path,
3132
tags: tags,
3233
icon: utils.Filename(path) + "-content",
@@ -103,9 +104,7 @@ func (s *screenPlaylist) render() {
103104

104105
_, h := vid.Window.GetFramebufferSize()
105106

106-
drawCursor(list)
107-
108-
for _, e := range list.children {
107+
for i, e := range list.children {
109108
if e.yp < -0.1 || e.yp > 1.1 {
110109
continue
111110
}
@@ -117,25 +116,35 @@ func (s *screenPlaylist) render() {
117116
color = video.Color{R: 1, G: 1, B: 1, A: e.iconAlpha}
118117
}
119118

120-
icon := menu.icons[e.icon]
121-
if e.path == state.Global.GamePath {
122-
icon = menu.icons["resume"]
123-
}
124-
125-
vid.DrawImage(icon,
126-
610*menu.ratio-64*e.scale*menu.ratio,
127-
float32(h)*e.yp-14*menu.ratio-64*e.scale*menu.ratio+fontOffset,
128-
128*menu.ratio, 128*menu.ratio,
129-
e.scale, color)
130-
131119
if e.labelAlpha > 0 {
120+
drawThumbnail(
121+
list, i,
122+
list.label, e.gameName,
123+
680*menu.ratio-85*e.scale*menu.ratio,
124+
float32(h)*e.yp-14*menu.ratio-64*e.scale*menu.ratio+fontOffset,
125+
170*menu.ratio, 128*menu.ratio,
126+
e.scale,
127+
)
128+
vid.DrawBorder(
129+
680*menu.ratio-85*e.scale*menu.ratio,
130+
float32(h)*e.yp-14*menu.ratio-64*e.scale*menu.ratio+fontOffset,
131+
170*menu.ratio*e.scale, 128*menu.ratio*e.scale, 0.02/e.scale,
132+
video.Color{R: color.R, G: color.G, B: color.B, A: 0.75})
133+
if e.path == state.Global.GamePath {
134+
vid.DrawImage(menu.icons["resume"],
135+
680*menu.ratio-64*e.scale*menu.ratio,
136+
float32(h)*e.yp-14*menu.ratio-64*e.scale*menu.ratio+fontOffset,
137+
128*menu.ratio, 128*menu.ratio,
138+
e.scale, video.Color{R: 1, G: 1, B: 1, A: 1})
139+
}
140+
132141
vid.Font.SetColor(color.R, color.G, color.B, e.labelAlpha)
133-
stack := 670 * menu.ratio
142+
stack := 840 * menu.ratio
134143
vid.Font.Printf(
135-
670*menu.ratio,
144+
840*menu.ratio,
136145
float32(h)*e.yp+fontOffset,
137-
0.7*menu.ratio, e.label)
138-
stack += float32(int(vid.Font.Width(0.7*menu.ratio, e.label)))
146+
0.6*menu.ratio, e.label)
147+
stack += float32(int(vid.Font.Width(0.6*menu.ratio, e.label)))
139148
stack += 10
140149

141150
for _, tag := range e.tags {

menu/scene_tabs.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,8 @@ func refreshTabs() {
9393
if i == e.ptr {
9494
e.children[i].yp = 0.5
9595
e.children[i].iconAlpha = 1
96-
e.children[i].scale = 1
97-
e.children[i].width = 600
96+
e.children[i].scale = 0.75
97+
e.children[i].width = 500
9898
} else if i < e.ptr {
9999
e.children[i].yp = 0.5
100100
e.children[i].iconAlpha = 1
@@ -113,7 +113,7 @@ func refreshTabs() {
113113
menu.scroll = float32(e.ptr * 128)
114114
} else {
115115
e.children[e.ptr].width = 5200
116-
menu.scroll = float32(e.ptr*128 + 2980)
116+
menu.scroll = float32(e.ptr*128 + 3030)
117117
}
118118
}
119119

@@ -182,8 +182,8 @@ func (tabs *screenTabs) segueMount() {
182182
e.yp = 0.5
183183
e.labelAlpha = 1
184184
e.iconAlpha = 1
185-
e.scale = 1
186-
e.width = 600
185+
e.scale = 0.75
186+
e.width = 500
187187
} else if i < tabs.ptr {
188188
e.yp = 0.5
189189
e.labelAlpha = 0
@@ -215,8 +215,8 @@ func (tabs *screenTabs) animate() {
215215
yp = 0.5
216216
labelAlpha = 1
217217
iconAlpha = 1
218-
scale = 1
219-
width = 600
218+
scale = 0.75
219+
width = 500
220220
} else if i < tabs.ptr {
221221
yp = 0.5
222222
labelAlpha = 0
@@ -243,7 +243,7 @@ func (tabs *screenTabs) animate() {
243243
func (tabs *screenTabs) segueNext() {
244244
cur := &tabs.children[tabs.ptr]
245245
menu.tweens[&cur.width] = gween.New(cur.width, 5200, 0.15, ease.OutSine)
246-
menu.tweens[&menu.scroll] = gween.New(menu.scroll, menu.scroll+2980, 0.15, ease.OutSine)
246+
menu.tweens[&menu.scroll] = gween.New(menu.scroll, menu.scroll+3030, 0.15, ease.OutSine)
247247
}
248248

249249
func (tabs *screenTabs) update() {
@@ -283,7 +283,7 @@ func (tabs *screenTabs) update() {
283283
func (tabs screenTabs) render() {
284284
_, h := vid.Window.GetFramebufferSize()
285285

286-
stackWidth := 660 * menu.ratio
286+
stackWidth := 710 * menu.ratio
287287
for i, e := range tabs.children {
288288

289289
c := colorful.Hcl(float64(i)*20, 0.5, 0.5)
@@ -294,10 +294,10 @@ func (tabs screenTabs) render() {
294294

295295
if e.labelAlpha > 0 {
296296
vid.Font.SetColor(float32(c.R), float32(c.B), float32(c.G), e.labelAlpha)
297-
lw := vid.Font.Width(0.7*menu.ratio, e.label)
298-
vid.Font.Printf(x-lw/2, float32(h)*e.yp+310*menu.ratio, 0.7*menu.ratio, e.label)
297+
lw := vid.Font.Width(0.6*menu.ratio, e.label)
298+
vid.Font.Printf(x-lw/2, float32(h)*e.yp+250*menu.ratio, 0.6*menu.ratio, e.label)
299299
lw = vid.Font.Width(0.4*menu.ratio, e.subLabel)
300-
vid.Font.Printf(x-lw/2, float32(h)*e.yp+390*menu.ratio, 0.4*menu.ratio, e.subLabel)
300+
vid.Font.Printf(x-lw/2, float32(h)*e.yp+330*menu.ratio, 0.4*menu.ratio, e.subLabel)
301301
}
302302

303303
vid.DrawImage(menu.icons["hexagon"],

menu/thumbnail.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package menu
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"net/http"
7+
"os"
8+
"os/user"
9+
10+
"github.com/libretro/ludo/video"
11+
)
12+
13+
func downloadThumbnail(list *entry, i int, url, folderPath, path string) {
14+
resp, err := http.Get(url)
15+
if err != nil {
16+
fmt.Println(err)
17+
list.children[i].thumbnail = menu.icons["img-broken"]
18+
return
19+
}
20+
defer resp.Body.Close()
21+
22+
if resp.StatusCode != 200 {
23+
list.children[i].thumbnail = menu.icons["img-broken"]
24+
return
25+
}
26+
27+
err = os.MkdirAll(folderPath, os.ModePerm)
28+
if err != nil {
29+
fmt.Println(err)
30+
list.children[i].thumbnail = menu.icons["img-broken"]
31+
return
32+
}
33+
34+
out, err := os.Create(path)
35+
if err != nil {
36+
fmt.Println(err)
37+
list.children[i].thumbnail = menu.icons["img-broken"]
38+
return
39+
}
40+
defer out.Close()
41+
42+
io.Copy(out, resp.Body)
43+
}
44+
45+
func drawThumbnail(list *entry, i int, system, gameName string, x, y, w, h, scale float32) {
46+
usr, _ := user.Current()
47+
folderPath := usr.HomeDir + "/.ludo/thumbnails/" + system + "/Named_Snaps/"
48+
path := folderPath + gameName + ".png"
49+
url := "http://thumbnails.libretro.com/" + system + "/Named_Snaps/" + gameName + ".png"
50+
51+
if list.children[i].thumbnail == 0 || list.children[i].thumbnail == menu.icons["img-dl"] {
52+
if _, err := os.Stat(path); !os.IsNotExist(err) {
53+
list.children[i].thumbnail = video.NewImage(path)
54+
} else if list.children[i].thumbnail != menu.icons["img-dl"] {
55+
list.children[i].thumbnail = menu.icons["img-dl"]
56+
go downloadThumbnail(list, i, url, folderPath, path)
57+
}
58+
}
59+
60+
vid.DrawImage(
61+
list.children[i].thumbnail,
62+
x, y, w, h, scale,
63+
video.Color{R: 1, G: 1, B: 1, A: 1},
64+
)
65+
}

video/gfx.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ func (video *Video) drawTextureQuad(image uint32, x1, y1, x2, y2, x3, y3, x4, y4
110110
gl.Enable(gl.BLEND)
111111
gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
112112
gl.BindVertexArray(video.vao)
113+
gl.ActiveTexture(gl.TEXTURE0)
113114
gl.BindTexture(gl.TEXTURE_2D, image)
114115
gl.BindBuffer(gl.ARRAY_BUFFER, video.vbo)
115116
gl.BufferData(gl.ARRAY_BUFFER, len(va)*4, gl.Ptr(va), gl.STATIC_DRAW)
@@ -192,6 +193,7 @@ func textureLoad(rgba *image.RGBA) uint32 {
192193
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
193194
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
194195
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
196+
gl.PixelStorei(gl.UNPACK_ROW_LENGTH, 0)
195197
gl.TexImage2D(
196198
gl.TEXTURE_2D,
197199
0,

video/video.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -311,10 +311,8 @@ func (video *Video) Refresh(data unsafe.Pointer, width int32, height int32, pitc
311311
gl.BindTexture(gl.TEXTURE_2D, video.texID)
312312
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, width, height, 0, video.pixType, video.pixFmt, nil)
313313

314-
if pitch != video.pitch {
315-
video.pitch = pitch
316-
gl.PixelStorei(gl.UNPACK_ROW_LENGTH, video.pitch/video.bpp)
317-
}
314+
video.pitch = pitch
315+
gl.PixelStorei(gl.UNPACK_ROW_LENGTH, video.pitch/video.bpp)
318316

319317
if data != nil {
320318
gl.TexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, video.pixType, video.pixFmt, data)

0 commit comments

Comments
 (0)