From 987493bcc5190c91c0ed57a57cf67366957beac5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Andr=C3=A9=20Santoni?= Date: Sun, 9 Jun 2019 21:17:57 +0700 Subject: [PATCH 01/13] Ask user's confirmation before exiting. Closes #202 --- input/actions.go | 39 ------------------------- input/input.go | 12 ++++++++ main.go | 2 +- menu/input.go | 32 +++++++++++++++++++++ menu/scene_dialog.go | 68 ++++++++++++++++++++++++++++++++++++++++++++ menu/scene_main.go | 53 +++++++++++++++++++++++++--------- 6 files changed, 153 insertions(+), 53 deletions(-) delete mode 100644 input/actions.go create mode 100644 menu/scene_dialog.go diff --git a/input/actions.go b/input/actions.go deleted file mode 100644 index ca811c7b..00000000 --- a/input/actions.go +++ /dev/null @@ -1,39 +0,0 @@ -package input - -import ( - "github.com/libretro/ludo/libretro" - "github.com/libretro/ludo/settings" - "github.com/libretro/ludo/state" -) - -const ( - // ActionMenuToggle toggles the menu UI - ActionMenuToggle uint32 = libretro.DeviceIDJoypadR3 + 1 - // ActionFullscreenToggle switches between fullscreen and windowed mode - ActionFullscreenToggle uint32 = libretro.DeviceIDJoypadR3 + 2 - // ActionShouldClose will cause the program to shutdown - ActionShouldClose uint32 = libretro.DeviceIDJoypadR3 + 3 - // ActionLast is used for iterating - ActionLast uint32 = libretro.DeviceIDJoypadR3 + 4 -) - -// ProcessActions checks if certain keys are pressed and perform corresponding actions -func ProcessActions() { - // Toggle the menu if ActionMenuToggle is pressed - if Released[0][ActionMenuToggle] && state.Global.CoreRunning { - state.Global.MenuActive = !state.Global.MenuActive - } - - // Toggle fullscreen if ActionFullscreenToggle is pressed - if Released[0][ActionFullscreenToggle] { - settings.Current.VideoFullscreen = !settings.Current.VideoFullscreen - vid.Reconfigure(settings.Current.VideoFullscreen) - menu.ContextReset() - settings.Save() - } - - // Close if ActionShouldClose is pressed - if Pressed[0][ActionShouldClose] { - vid.Window.SetShouldClose(true) - } -} diff --git a/input/input.go b/input/input.go index 481de4f8..b7cbda8f 100644 --- a/input/input.go +++ b/input/input.go @@ -35,6 +35,18 @@ var ( Pressed inputstate // keys just pressed during this frame ) +// Hot keys +const ( + // ActionMenuToggle toggles the menu UI + ActionMenuToggle uint32 = libretro.DeviceIDJoypadR3 + 1 + // ActionFullscreenToggle switches between fullscreen and windowed mode + ActionFullscreenToggle uint32 = libretro.DeviceIDJoypadR3 + 2 + // ActionShouldClose will cause the program to shutdown + ActionShouldClose uint32 = libretro.DeviceIDJoypadR3 + 3 + // ActionLast is used for iterating + ActionLast uint32 = libretro.DeviceIDJoypadR3 + 4 +) + // joystickCallback is triggered when a joypad is plugged. func joystickCallback(joy int, event int) { switch glfw.MonitorEvent(event) { diff --git a/main.go b/main.go index adf9f845..bc1f8eb7 100644 --- a/main.go +++ b/main.go @@ -29,6 +29,7 @@ func runLoop(vid *video.Video, m *menu.Menu) { currTime = time.Now() dt := float32(currTime.Sub(prevTime)) / 1000000000 glfw.PollEvents() + m.ProcessHotkeys() ntf.Process(dt) vid.ResizeViewport() if !state.Global.MenuActive { @@ -48,7 +49,6 @@ func runLoop(vid *video.Video, m *menu.Menu) { vid.Render() m.Render(dt) } - input.ProcessActions() m.RenderNotifications() glfw.SwapInterval(1) vid.Window.SwapBuffers() diff --git a/menu/input.go b/menu/input.go index 96d3547f..e432bbb0 100644 --- a/menu/input.go +++ b/menu/input.go @@ -3,6 +3,8 @@ package menu import ( "github.com/libretro/ludo/input" "github.com/libretro/ludo/libretro" + "github.com/libretro/ludo/settings" + "github.com/libretro/ludo/state" ) var ( @@ -100,3 +102,33 @@ func genericInput(list *entry, dt float32) { } } } + +// ProcessHotkeys checks if certain keys are pressed and perform corresponding actions +func (m *Menu) ProcessHotkeys() { + // Disable all hot keys on the exit dialog + currentScene := m.stack[len(m.stack)-1] + if currentScene.Entry().label == "Exit Dialog" { + return + } + + // Toggle the menu if ActionMenuToggle is pressed + if input.Released[0][input.ActionMenuToggle] && state.Global.CoreRunning { + state.Global.MenuActive = !state.Global.MenuActive + } + + // Toggle fullscreen if ActionFullscreenToggle is pressed + if input.Released[0][input.ActionFullscreenToggle] { + settings.Current.VideoFullscreen = !settings.Current.VideoFullscreen + vid.Reconfigure(settings.Current.VideoFullscreen) + m.ContextReset() + settings.Save() + } + + // Close if ActionShouldClose is pressed, but display a confirmation dialog + // in case a game is running + if input.Pressed[0][input.ActionShouldClose] { + askConfirmation(func() { + vid.Window.SetShouldClose(true) + }) + } +} diff --git a/menu/scene_dialog.go b/menu/scene_dialog.go new file mode 100644 index 00000000..a6ad965c --- /dev/null +++ b/menu/scene_dialog.go @@ -0,0 +1,68 @@ +package menu + +import ( + "github.com/libretro/ludo/input" + "github.com/libretro/ludo/libretro" + "github.com/libretro/ludo/video" +) + +type sceneDialog struct { + entry +} + +func buildDialog(callbackOK func()) Scene { + var list sceneDialog + list.label = "Exit Dialog" + list.callbackOK = callbackOK + return &list +} + +func (s *sceneDialog) Entry() *entry { + return &s.entry +} + +func (s *sceneDialog) segueMount() { +} + +func (s *sceneDialog) segueNext() { +} + +func (s *sceneDialog) segueBack() { +} + +func (s *sceneDialog) update(dt float32) { + // OK + if input.Released[0][libretro.DeviceIDJoypadA] { + s.callbackOK() + } + + // Cancel + if input.Released[0][libretro.DeviceIDJoypadB] { + menu.stack[len(menu.stack)-2].segueBack() + menu.stack = menu.stack[:len(menu.stack)-1] + } +} + +func (s *sceneDialog) render() { + w, h := vid.Window.GetFramebufferSize() + vid.DrawRect(0, 0, float32(w), float32(h), 0, video.Color{R: 0.5, G: 0, B: 0, A: 1}) + vid.Font.SetColor(1, 1, 1, 1) + msg1 := "A game is currently running." + lw1 := vid.Font.Width(0.6*menu.ratio, msg1) + vid.Font.Printf(float32(w)/2-lw1/2, float32(h)/2-60*menu.ratio, 0.6*menu.ratio, msg1) + msg2 := "If you have not saved yet, your progress will be lost." + lw2 := vid.Font.Width(0.6*menu.ratio, msg2) + vid.Font.Printf(float32(w)/2-lw2/2, float32(h)/2, 0.6*menu.ratio, msg2) + msg3 := "Do you want to exit Ludo anyway?" + lw3 := vid.Font.Width(0.6*menu.ratio, msg3) + vid.Font.Printf(float32(w)/2-lw3/2, float32(h)/2+60*menu.ratio, 0.6*menu.ratio, msg3) +} + +func (s *sceneDialog) drawHintBar() { + w, h := vid.Window.GetFramebufferSize() + vid.DrawRect(0, float32(h)-70*menu.ratio, float32(w), 70*menu.ratio, 0, video.Color{R: 0.75, G: 0.75, B: 0.75, A: 1}) + + var stack float32 + stackHint(&stack, "key-z", "NO", h) + stackHint(&stack, "key-x", "YES", h) +} diff --git a/menu/scene_main.go b/menu/scene_main.go index 24a7776d..fa2a791c 100644 --- a/menu/scene_main.go +++ b/menu/scene_main.go @@ -16,6 +16,37 @@ type sceneMain struct { entry } +func cleanShutdown() { + cmd := exec.Command("/usr/sbin/shutdown", "-P", "now") + core.UnloadGame() + err := cmd.Run() + if err != nil { + ntf.DisplayAndLog(ntf.Error, "Menu", err.Error()) + } +} + +func cleanReboot() { + cmd := exec.Command("/usr/sbin/shutdown", "-r", "now") + core.UnloadGame() + err := cmd.Run() + if err != nil { + ntf.DisplayAndLog(ntf.Error, "Menu", err.Error()) + } +} + +func askConfirmation(cb func()) { + if state.Global.CoreRunning { + if !state.Global.MenuActive { + state.Global.MenuActive = true + } + menu.Push(buildDialog(func() { + cb() + })) + } else { + cb() + } +} + func buildMainMenu() Scene { var list sceneMain list.label = "Main Menu" @@ -79,12 +110,9 @@ func buildMainMenu() Scene { label: "Reboot", icon: "subsetting", callbackOK: func() { - cmd := exec.Command("/usr/sbin/shutdown", "-r", "now") - core.UnloadGame() - err := cmd.Run() - if err != nil { - ntf.DisplayAndLog(ntf.Error, "Menu", err.Error()) - } + askConfirmation(func() { + cleanReboot() + }) }, }) @@ -92,12 +120,9 @@ func buildMainMenu() Scene { label: "Shutdown", icon: "subsetting", callbackOK: func() { - cmd := exec.Command("/usr/sbin/shutdown", "-P", "now") - core.UnloadGame() - err := cmd.Run() - if err != nil { - ntf.DisplayAndLog(ntf.Error, "Menu", err.Error()) - } + askConfirmation(func() { + cleanShutdown() + }) }, }) } else { @@ -105,7 +130,9 @@ func buildMainMenu() Scene { label: "Quit", icon: "subsetting", callbackOK: func() { - vid.Window.SetShouldClose(true) + askConfirmation(func() { + vid.Window.SetShouldClose(true) + }) }, }) } From 356d645b67c1643c65d8a2101add6ad340f848b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Andr=C3=A9=20Santoni?= Date: Mon, 10 Jun 2019 22:01:31 +0700 Subject: [PATCH 02/13] Improve layout --- menu/scene_dialog.go | 65 ++++++++++++++++++++++++++++-------- video/rounded_frag_shader.go | 2 +- 2 files changed, 52 insertions(+), 15 deletions(-) diff --git a/menu/scene_dialog.go b/menu/scene_dialog.go index a6ad965c..7bf04b34 100644 --- a/menu/scene_dialog.go +++ b/menu/scene_dialog.go @@ -45,24 +45,61 @@ func (s *sceneDialog) update(dt float32) { func (s *sceneDialog) render() { w, h := vid.Window.GetFramebufferSize() - vid.DrawRect(0, 0, float32(w), float32(h), 0, video.Color{R: 0.5, G: 0, B: 0, A: 1}) - vid.Font.SetColor(1, 1, 1, 1) + fw := float32(w) + fh := float32(h) + vid.DrawRect(0, 0, fw, fh, 0, video.Color{R: 0, G: 0, B: 0, A: 0.85}) + + var width float32 = 1000 + var height float32 = 400 + + vid.DrawRect( + fw/2-width/2*menu.ratio, + fh/2-height/2*menu.ratio, + width*menu.ratio, + height*menu.ratio, + 0.05, + video.Color{R: 1, G: 1, B: 1, A: 1}, + ) + + vid.Font.SetColor(0.8, 0.4, 0.1, 1) msg1 := "A game is currently running." - lw1 := vid.Font.Width(0.6*menu.ratio, msg1) - vid.Font.Printf(float32(w)/2-lw1/2, float32(h)/2-60*menu.ratio, 0.6*menu.ratio, msg1) + lw1 := vid.Font.Width(0.7*menu.ratio, msg1) + vid.Font.Printf(fw/2-lw1/2, fh/2-120*menu.ratio+20*menu.ratio, 0.7*menu.ratio, msg1) + vid.Font.SetColor(0, 0, 0, 1) msg2 := "If you have not saved yet, your progress will be lost." - lw2 := vid.Font.Width(0.6*menu.ratio, msg2) - vid.Font.Printf(float32(w)/2-lw2/2, float32(h)/2, 0.6*menu.ratio, msg2) + lw2 := vid.Font.Width(0.5*menu.ratio, msg2) + vid.Font.Printf(fw/2-lw2/2, fh/2-30*menu.ratio+20*menu.ratio, 0.5*menu.ratio, msg2) msg3 := "Do you want to exit Ludo anyway?" - lw3 := vid.Font.Width(0.6*menu.ratio, msg3) - vid.Font.Printf(float32(w)/2-lw3/2, float32(h)/2+60*menu.ratio, 0.6*menu.ratio, msg3) + lw3 := vid.Font.Width(0.5*menu.ratio, msg3) + vid.Font.Printf(fw/2-lw3/2, fh/2+30*menu.ratio+20*menu.ratio, 0.5*menu.ratio, msg3) + + c := video.Color{R: 0.25, G: 0.25, B: 0.25, A: 1} + vid.Font.SetColor(0.25, 0.25, 0.25, 1.0) + + var margin float32 = 15 + + vid.DrawImage( + menu.icons["key-z"], + 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, c) + vid.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, + 0.5*menu.ratio, + "NO") + + vid.DrawImage( + menu.icons["key-x"], + 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, c) + vid.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, + 0.5*menu.ratio, + "YES") } func (s *sceneDialog) drawHintBar() { - w, h := vid.Window.GetFramebufferSize() - vid.DrawRect(0, float32(h)-70*menu.ratio, float32(w), 70*menu.ratio, 0, video.Color{R: 0.75, G: 0.75, B: 0.75, A: 1}) - - var stack float32 - stackHint(&stack, "key-z", "NO", h) - stackHint(&stack, "key-x", "YES", h) } diff --git a/video/rounded_frag_shader.go b/video/rounded_frag_shader.go index e648d0f9..d9717cd5 100644 --- a/video/rounded_frag_shader.go +++ b/video/rounded_frag_shader.go @@ -29,6 +29,6 @@ void main() { vec2 halfRes = vec2(0.5*ratio, 0.5); float b = udRoundBox(fragTexCoord*vec2(ratio,1.0) - halfRes, halfRes, min(halfRes.x,halfRes.y)*radius); vec4 c = min(color, vec4(1.0, 1.0, 1.0, 1.0)); - COMPAT_FRAGCOLOR = vec4(c.r, c.g, c.b, c.a * (1.0-smoothstep(0.00001,0.01,b))); + COMPAT_FRAGCOLOR = vec4(c.r, c.g, c.b, c.a * (1.0-smoothstep(0.00002,0.0001,b))); } ` + "\x00" From 9b696af3888991ae1c4cfb48998e44d8e9613395 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Andr=C3=A9=20Santoni?= Date: Tue, 11 Jun 2019 00:17:33 +0700 Subject: [PATCH 03/13] Remove useless code --- core/core.go | 2 +- input/input.go | 10 +--------- main.go | 2 +- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/core/core.go b/core/core.go index b3a6df34..fcf9b2b3 100644 --- a/core/core.go +++ b/core/core.go @@ -163,7 +163,7 @@ func LoadGame(gamePath string) error { vid.Window.SetTitle("Ludo - " + si.LibraryName) } - input.Init(vid, menu) + input.Init(vid) audio.Init(int32(avi.Timing.SampleRate)) if state.Global.Core.AudioCallback != nil { state.Global.Core.AudioCallback.SetState(true) diff --git a/input/input.go b/input/input.go index b7cbda8f..52d800f8 100644 --- a/input/input.go +++ b/input/input.go @@ -59,19 +59,11 @@ func joystickCallback(joy int, event int) { } } -// ContextReseter is an interface to to allow reloading icons after the -// window is recreated when switching fullscreen -type ContextReseter interface { - ContextReset() -} - var vid *video.Video -var menu ContextReseter // Init initializes the input package -func Init(v *video.Video, m ContextReseter) { +func Init(v *video.Video) { vid = v - menu = m glfw.SetJoystickCallback(joystickCallback) } diff --git a/main.go b/main.go index bc1f8eb7..8e1cdab7 100644 --- a/main.go +++ b/main.go @@ -102,7 +102,7 @@ func main() { core.Init(vid, m) - input.Init(vid, m) + input.Init(vid) if len(state.Global.CorePath) > 0 { err := core.Load(state.Global.CorePath) From 69e3efe4b21fc69169a4f03ad144d9de74cf185a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Andr=C3=A9=20Santoni?= Date: Fri, 14 Jun 2019 22:53:59 +0700 Subject: [PATCH 04/13] Add basic widgets --- core/core.go | 2 - menu/hints.go | 31 ++++++++ menu/scene_dialog.go | 98 +++++++++++------------ menu/scene_tabs.go | 45 ++++++++--- menu/widgets.go | 181 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 295 insertions(+), 62 deletions(-) create mode 100644 menu/widgets.go diff --git a/core/core.go b/core/core.go index fcf9b2b3..c545b853 100644 --- a/core/core.go +++ b/core/core.go @@ -30,7 +30,6 @@ type MenuInterface interface { } var vid *video.Video -var menu MenuInterface // Options holds the settings for the current core var Options *options.Options @@ -39,7 +38,6 @@ var Options *options.Options // Call Init before calling other functions of this package. func Init(v *video.Video, m MenuInterface) { vid = v - menu = m ticker := time.NewTicker(time.Second) go func() { for range ticker.C { diff --git a/menu/hints.go b/menu/hints.go index 61cd86e2..71b25918 100644 --- a/menu/hints.go +++ b/menu/hints.go @@ -12,3 +12,34 @@ func stackHint(stack *float32, icon, label string, h int) { vid.Font.Printf(*stack, float32(h)-23*menu.ratio, 0.5*menu.ratio, label) *stack += vid.Font.Width(0.5*menu.ratio, label) } + +func HintBar(_ *Props, children ...func()) func() { + w, h := vid.Window.GetFramebufferSize() + return HBox(&Props{ + Y: float32(h) - 70*menu.ratio, + Width: float32(w), + Height: 70 * menu.ratio, + Color: video.Color{R: 0.75, G: 0.75, B: 0.75, A: 1}, + }, + children..., + ) +} + +func Hint(props *Props, icon string, title string) func() { + darkGrey := video.Color{R: 0.25, G: 0.25, B: 0.25, A: 1} + return HBox(props, + Box(&Props{Width: 15}), + Image(&Props{ + Width: 70 * menu.ratio, + Height: 70 * menu.ratio, + Scale: 1, + Color: darkGrey, + }, menu.icons[icon]), + Label(&Props{ + Height: 70 * menu.ratio, + Scale: 0.5 * menu.ratio, + Color: darkGrey, + }, title), + Box(&Props{Width: 15}), + ) +} diff --git a/menu/scene_dialog.go b/menu/scene_dialog.go index 7bf04b34..e83dd218 100644 --- a/menu/scene_dialog.go +++ b/menu/scene_dialog.go @@ -47,58 +47,54 @@ func (s *sceneDialog) render() { w, h := vid.Window.GetFramebufferSize() fw := float32(w) fh := float32(h) - vid.DrawRect(0, 0, fw, fh, 0, video.Color{R: 0, G: 0, B: 0, A: 0.85}) - var width float32 = 1000 - var height float32 = 400 - - vid.DrawRect( - fw/2-width/2*menu.ratio, - fh/2-height/2*menu.ratio, - width*menu.ratio, - height*menu.ratio, - 0.05, - video.Color{R: 1, G: 1, B: 1, A: 1}, - ) - - vid.Font.SetColor(0.8, 0.4, 0.1, 1) - msg1 := "A game is currently running." - lw1 := vid.Font.Width(0.7*menu.ratio, msg1) - vid.Font.Printf(fw/2-lw1/2, fh/2-120*menu.ratio+20*menu.ratio, 0.7*menu.ratio, msg1) - vid.Font.SetColor(0, 0, 0, 1) - msg2 := "If you have not saved yet, your progress will be lost." - lw2 := vid.Font.Width(0.5*menu.ratio, msg2) - vid.Font.Printf(fw/2-lw2/2, fh/2-30*menu.ratio+20*menu.ratio, 0.5*menu.ratio, msg2) - msg3 := "Do you want to exit Ludo anyway?" - lw3 := vid.Font.Width(0.5*menu.ratio, msg3) - vid.Font.Printf(fw/2-lw3/2, fh/2+30*menu.ratio+20*menu.ratio, 0.5*menu.ratio, msg3) - - c := video.Color{R: 0.25, G: 0.25, B: 0.25, A: 1} - vid.Font.SetColor(0.25, 0.25, 0.25, 1.0) - - var margin float32 = 15 - - vid.DrawImage( - menu.icons["key-z"], - 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, c) - vid.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, - 0.5*menu.ratio, - "NO") - - vid.DrawImage( - menu.icons["key-x"], - 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, c) - vid.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, - 0.5*menu.ratio, - "YES") + width := 1000 * menu.ratio + height := 400 * menu.ratio + + white := video.Color{R: 1, G: 1, B: 1, A: 1} + black := video.Color{R: 0, G: 0, B: 0, A: 1} + warningTitle := video.Color{R: 0.8, G: 0.4, B: 0.1, A: 1} + + // Background + Box(&Props{Width: fw, Height: fh, Color: video.Color{R: 0, G: 0, B: 0, A: 0.85}}, + // Dialog + VBox(&Props{ + X: fw/2 - width/2, + Y: fh/2 - height/2, + Width: width, + Height: height, + BorderRadius: 0.05, + Color: white, + }, + // Title + Label(&Props{ + TextAlign: "center", + Scale: 0.7 * menu.ratio, + Color: warningTitle, + Height: 150 * menu.ratio, + }, "A game is currently running"), + // Messages + Label(&Props{ + TextAlign: "center", + Scale: 0.5 * menu.ratio, + Color: black, + Height: 60 * menu.ratio, + }, "If you have not saved yet, your progress will be lost."), + Label(&Props{ + TextAlign: "center", + Scale: 0.5 * menu.ratio, + Color: black, + Height: 60 * menu.ratio, + }, "Do you want to exit Ludo anyway?"), + Box(&Props{Height: 40 * menu.ratio}), + Box(&Props{}, + // The NO Hint + Hint(&Props{}, "key-z", "NO"), + // The YES Hint + Hint(&Props{X: width - 175*menu.ratio}, "key-x", "YES"), + ), + ), + )() } func (s *sceneDialog) drawHintBar() { diff --git a/menu/scene_tabs.go b/menu/scene_tabs.go index 1461a83a..1f544c74 100644 --- a/menu/scene_tabs.go +++ b/menu/scene_tabs.go @@ -283,16 +283,43 @@ func (tabs sceneTags) render() { x-128*e.scale*menu.ratio, float32(h)*e.yp-128*e.scale*menu.ratio, 256*menu.ratio, 256*menu.ratio, e.scale, video.Color{R: 1, G: 1, B: 1, A: e.iconAlpha}) } + + red := video.Color{R: 1, G: 0, B: 0, A: 1} + green := video.Color{R: 0, G: 1, B: 0, A: 1} + blue := video.Color{R: 0, G: 0, B: 1, A: 1} + black := video.Color{R: 0, G: 0, B: 0, A: 1} + white := video.Color{R: 1, G: 1, B: 1, A: 1} + + Box(&Props{X: 20, Y: 20, Width: 600, Height: 600, Color: red}, + VBox(&Props{X: 50, Y: 50, Width: 500, Height: 500, Color: green}, + VBox(&Props{Width: 50, Height: 50, Color: blue}, + VBox(&Props{Width: 10, Height: 10, Color: white}), + VBox(&Props{Width: 10, Height: 10, Color: green}), + ), + HBox(&Props{Width: 100, Height: 100, Color: black}, + VBox(&Props{Width: 25, Height: 25, Color: black}), + Box(&Props{Width: 25, Height: 25, Color: red}), + Box(&Props{Width: 25, Height: 25, Color: black}), + VBox(&Props{Width: 25, Height: 25, Color: red}, + Image(&Props{Width: 25, Height: 25, Scale: 1, Color: white}, menu.icons["key-x"]), + Image(&Props{Width: 25, Height: 25, Scale: 1, Color: white}, menu.icons["key-x"]), + ), + ), + HBox(&Props{Width: 50, Height: 50, Color: blue}, + Label(&Props{Scale: 0.5, Height: 50, Color: white}, "hi"), + Image(&Props{Width: 50, Height: 50, Scale: 1, Color: white}, menu.icons["key-x"]), + Label(&Props{Scale: 0.5, Height: 50, Color: white}, "hi"), + Image(&Props{Width: 50, Height: 50, Scale: 1, Color: white}, menu.icons["key-x"]), + Label(&Props{Scale: 0.5, Height: 50, Color: white}, "hi"), + ), + ), + )() } func (tabs sceneTags) drawHintBar() { - w, h := vid.Window.GetFramebufferSize() - vid.DrawRect(0, float32(h)-70*menu.ratio, float32(w), 70*menu.ratio, 0, video.Color{R: 0.75, G: 0.75, B: 0.75, A: 1}) - - var stack float32 - if state.Global.CoreRunning { - stackHint(&stack, "key-p", "RESUME", h) - } - stackHint(&stack, "key-left-right", "NAVIGATE", h) - stackHint(&stack, "key-x", "OPEN", h) + HintBar(&Props{}, + Hint(&Props{Hidden: !state.Global.CoreRunning}, "key-p", "RESUME"), + Hint(&Props{}, "key-left-right", "NAVIGATE"), + Hint(&Props{}, "key-x", "OPEN"), + )() } diff --git a/menu/widgets.go b/menu/widgets.go new file mode 100644 index 00000000..d0e233a4 --- /dev/null +++ b/menu/widgets.go @@ -0,0 +1,181 @@ +package menu + +import ( + "github.com/libretro/ludo/video" +) + +type Direction uint8 + +const ( + Vertical Direction = iota + Horizontal +) + +// Props are the properties of a widget +type Props struct { + X, Y float32 + Width, Height float32 + BorderRadius float32 + Scale float32 + TextAlign string + ContentWidth float32 + ContentHeight float32 + Direction Direction + Color video.Color + Hidden bool +} + +type propsStack []*Props + +func (s *propsStack) Push(v *Props) *propsStack { + *s = append(*s, v) + return s +} + +func (s *propsStack) Pop() { + l := len(*s) + *s = (*s)[:l-1] +} + +func (s *propsStack) Last() *Props { + if len(*s) == 0 { + return nil + } + return (*s)[len(*s)-1] +} + +func (s *propsStack) Sum() *Props { + sum := Props{} + for _, t := range *s { + sum.X += t.X + sum.Y += t.Y + } + return &sum +} + +var wStack = &propsStack{} +var tStack = &propsStack{} + +// Box is a basic widget container +func Box(props *Props, children ...func()) func() { + return func() { + if props.Hidden { + return + } + parent := wStack.Last() + self := wStack.Push(props).Last() + tsum := tStack.Push(&Props{}).Sum() + sum := wStack.Sum() + vid.DrawRect(sum.X+tsum.X, sum.Y+tsum.Y, props.Width, props.Height, props.BorderRadius, props.Color) + for _, child := range children { + child() + } + updateParent(parent, maxf32(props.Width, self.ContentWidth), maxf32(props.Height, self.ContentHeight)) + tStack.Pop() + wStack.Pop() + } +} + +// HBox is an horizontal widget container +func HBox(props *Props, children ...func()) func() { + return func() { + if props.Hidden { + return + } + props.Direction = Horizontal + parent := wStack.Last() + self := wStack.Push(props).Last() + tsum := tStack.Push(&Props{}).Sum() + sum := wStack.Sum() + vid.DrawRect(sum.X+tsum.X, sum.Y+tsum.Y, props.Width, props.Height, props.BorderRadius, props.Color) + for _, child := range children { + tStack.Push(&Props{X: wStack.Last().ContentWidth}) + child() + tStack.Pop() + } + updateParent(parent, maxf32(props.Width, self.ContentWidth), maxf32(props.Height, self.ContentHeight)) + tStack.Pop() + wStack.Pop() + } +} + +// VBox is a vertical widget container +func VBox(props *Props, children ...func()) func() { + return func() { + if props.Hidden { + return + } + props.Direction = Vertical + parent := wStack.Last() + self := wStack.Push(props).Last() + tsum := tStack.Push(&Props{}).Sum() + sum := wStack.Sum() + vid.DrawRect(sum.X+tsum.X, sum.Y+tsum.Y, props.Width, props.Height, props.BorderRadius, props.Color) + for _, child := range children { + tStack.Push(&Props{Y: wStack.Last().ContentHeight}) + child() + tStack.Pop() + } + updateParent(parent, maxf32(props.Width, self.ContentWidth), maxf32(props.Height, self.ContentHeight)) + tStack.Pop() + wStack.Pop() + } +} + +// Label a widget is used to draw some text +func Label(props *Props, msg string) func() { + return func() { + if props.Hidden { + return + } + parent := wStack.Last() + self := wStack.Push(props).Last() + tsum := tStack.Push(&Props{}).Sum() + sum := wStack.Sum() + vid.Font.SetColor(props.Color.R, props.Color.G, props.Color.B, props.Color.A) + textWidth := vid.Font.Width(props.Scale, msg) + switch props.TextAlign { + case "center": + vid.Font.Printf(sum.X+tsum.X+parent.Width/2-textWidth/2, sum.Y+tsum.Y+self.Height*0.67, props.Scale, msg) + default: + vid.Font.Printf(sum.X+tsum.X, sum.Y+tsum.Y+self.Height*0.67, props.Scale, msg) + } + updateParent(parent, textWidth, maxf32(props.Height, self.ContentHeight)) + tStack.Pop() + wStack.Pop() + } +} + +// Image is a widget used to display an image +func Image(props *Props, image uint32) func() { + return func() { + if props.Hidden { + return + } + parent := wStack.Last() + sum := wStack.Push(props).Sum() + tsum := tStack.Push(&Props{}).Sum() + vid.DrawImage(image, sum.X+tsum.X, sum.Y+tsum.Y, props.Width, props.Height, props.Scale, props.Color) + updateParent(parent, props.Width, props.Height) + tStack.Pop() + wStack.Pop() + } +} + +func updateParent(parent *Props, w, h float32) { + if parent != nil { + switch parent.Direction { + case Horizontal: + parent.ContentWidth += w + case Vertical: + parent.ContentHeight += h + } + } +} + +func maxf32(a, b float32) float32 { + if b > a { + return b + } + return a +} From fe23690446eb09c1f08ce18177646d6aca2b8102 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Andr=C3=A9=20Santoni?= Date: Fri, 14 Jun 2019 23:03:03 +0700 Subject: [PATCH 05/13] Convert more code --- menu/hints.go | 3 ++- menu/scene_savestates.go | 22 +++++++--------------- menu/scene_settings.go | 23 ++++++++--------------- 3 files changed, 17 insertions(+), 31 deletions(-) diff --git a/menu/hints.go b/menu/hints.go index 71b25918..141215a5 100644 --- a/menu/hints.go +++ b/menu/hints.go @@ -13,13 +13,14 @@ func stackHint(stack *float32, icon, label string, h int) { *stack += vid.Font.Width(0.5*menu.ratio, label) } -func HintBar(_ *Props, children ...func()) func() { +func HintBar(props *Props, children ...func()) func() { w, h := vid.Window.GetFramebufferSize() return HBox(&Props{ Y: float32(h) - 70*menu.ratio, Width: float32(w), Height: 70 * menu.ratio, Color: video.Color{R: 0.75, G: 0.75, B: 0.75, A: 1}, + Hidden: props.Hidden, }, children..., ) diff --git a/menu/scene_savestates.go b/menu/scene_savestates.go index 243c1478..176fec5e 100644 --- a/menu/scene_savestates.go +++ b/menu/scene_savestates.go @@ -135,20 +135,12 @@ func (s *sceneSavestates) render() { } func (s *sceneSavestates) drawHintBar() { - w, h := vid.Window.GetFramebufferSize() - vid.DrawRect(0, float32(h)-70*menu.ratio, float32(w), 70*menu.ratio, 0, video.Color{R: 0.75, G: 0.75, B: 0.75, A: 1}) - ptr := menu.stack[len(menu.stack)-1].Entry().ptr - - var stack float32 - if state.Global.CoreRunning { - stackHint(&stack, "key-p", "RESUME", h) - } - stackHint(&stack, "key-up-down", "NAVIGATE", h) - stackHint(&stack, "key-z", "BACK", h) - if ptr == 0 { - stackHint(&stack, "key-x", "SAVE", h) - } else { - stackHint(&stack, "key-x", "LOAD", h) - } + HintBar(&Props{}, + Hint(&Props{Hidden: !state.Global.CoreRunning}, "key-p", "RESUME"), + Hint(&Props{}, "key-up-down", "NAVIGATE"), + Hint(&Props{}, "key-z", "BACK"), + Hint(&Props{Hidden: ptr != 0}, "key-x", "SAVE"), + Hint(&Props{Hidden: ptr == 0}, "key-x", "LOAD"), + )() } diff --git a/menu/scene_settings.go b/menu/scene_settings.go index 7cfe0b06..797d471b 100644 --- a/menu/scene_settings.go +++ b/menu/scene_settings.go @@ -246,20 +246,13 @@ func (s *sceneSettings) render() { } func (s *sceneSettings) drawHintBar() { - w, h := vid.Window.GetFramebufferSize() - vid.DrawRect(0, float32(h)-70*menu.ratio, float32(w), 70*menu.ratio, 0, video.Color{R: 0.75, G: 0.75, B: 0.75, A: 1}) - - var stack float32 - if state.Global.CoreRunning { - stackHint(&stack, "key-p", "RESUME", h) - } - stackHint(&stack, "key-up-down", "NAVIGATE", h) - stackHint(&stack, "key-z", "BACK", h) - list := menu.stack[len(menu.stack)-1].Entry() - if list.children[list.ptr].callbackOK != nil { - stackHint(&stack, "key-x", "SET", h) - } else { - stackHint(&stack, "key-left-right", "SET", h) - } + cb := list.children[list.ptr].callbackOK + HintBar(&Props{}, + Hint(&Props{Hidden: !state.Global.CoreRunning}, "key-p", "RESUME"), + Hint(&Props{}, "key-up-down", "NAVIGATE"), + Hint(&Props{}, "key-z", "BACK"), + Hint(&Props{Hidden: cb == nil}, "key-x", "SET"), + Hint(&Props{Hidden: cb != nil}, "key-left-right", "SET"), + )() } From a92eb0172755ee6796445e661ac0f7e1415b9227 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Andr=C3=A9=20Santoni?= Date: Sun, 16 Jun 2019 13:43:28 +0700 Subject: [PATCH 06/13] Convert more code --- menu/scene.go | 19 +++++++------------ menu/scene_core_options.go | 17 ++++++----------- menu/scene_playlist.go | 16 ++++++---------- menu/scene_settings.go | 3 +-- 4 files changed, 20 insertions(+), 35 deletions(-) diff --git a/menu/scene.go b/menu/scene.go index a5ebe20a..c0b516c4 100644 --- a/menu/scene.go +++ b/menu/scene.go @@ -179,6 +179,7 @@ func drawCursor(list *entry) { // It also display values of settings if we are displaying a settings scene func genericRender(list *entry) { w, h := vid.Window.GetFramebufferSize() + fontOffset := 64 * 0.7 * menu.ratio * 0.3 drawCursor(list) @@ -187,8 +188,6 @@ func genericRender(list *entry) { continue } - fontOffset := 64 * 0.7 * menu.ratio * 0.3 - color := video.Color{R: 0, G: 0, B: 0, A: e.iconAlpha} if state.Global.CoreRunning { color = video.Color{R: 1, G: 1, B: 1, A: e.iconAlpha} @@ -221,14 +220,10 @@ func genericRender(list *entry) { } func genericDrawHintBar() { - w, h := vid.Window.GetFramebufferSize() - vid.DrawRect(0, float32(h)-70*menu.ratio, float32(w), 70*menu.ratio, 0, video.Color{R: 0.75, G: 0.75, B: 0.75, A: 1}) - - var stack float32 - if state.Global.CoreRunning { - stackHint(&stack, "key-p", "RESUME", h) - } - stackHint(&stack, "key-up-down", "NAVIGATE", h) - stackHint(&stack, "key-z", "BACK", h) - stackHint(&stack, "key-x", "OK", h) + HintBar(&Props{}, + Hint(&Props{Hidden: !state.Global.CoreRunning}, "key-p", "RESUME"), + Hint(&Props{}, "key-up-down", "NAVIGATE"), + Hint(&Props{}, "key-z", "BACK"), + Hint(&Props{}, "key-x", "OK"), + )() } diff --git a/menu/scene_core_options.go b/menu/scene_core_options.go index b4da1750..eca7aa95 100644 --- a/menu/scene_core_options.go +++ b/menu/scene_core_options.go @@ -5,7 +5,6 @@ import ( "github.com/libretro/ludo/core" "github.com/libretro/ludo/state" - "github.com/libretro/ludo/video" ) type sceneCoreOptions struct { @@ -69,14 +68,10 @@ func (s *sceneCoreOptions) render() { } func (s *sceneCoreOptions) drawHintBar() { - w, h := vid.Window.GetFramebufferSize() - vid.DrawRect(0, float32(h)-70*menu.ratio, float32(w), 70*menu.ratio, 0, video.Color{R: 0.75, G: 0.75, B: 0.75, A: 1}) - - var stack float32 - if state.Global.CoreRunning { - stackHint(&stack, "key-p", "RESUME", h) - } - stackHint(&stack, "key-up-down", "NAVIGATE", h) - stackHint(&stack, "key-z", "BACK", h) - stackHint(&stack, "key-left-right", "SET", h) + HintBar(&Props{}, + Hint(&Props{Hidden: !state.Global.CoreRunning}, "key-p", "RESUME"), + Hint(&Props{}, "key-up-down", "NAVIGATE"), + Hint(&Props{}, "key-z", "BACK"), + Hint(&Props{}, "key-left-right", "SET"), + )() } diff --git a/menu/scene_playlist.go b/menu/scene_playlist.go index f42e941c..d766b359 100644 --- a/menu/scene_playlist.go +++ b/menu/scene_playlist.go @@ -179,14 +179,10 @@ func (s *scenePlaylist) render() { } func (s *scenePlaylist) drawHintBar() { - w, h := vid.Window.GetFramebufferSize() - vid.DrawRect(0, float32(h)-70*menu.ratio, float32(w), 70*menu.ratio, 0, video.Color{R: 0.75, G: 0.75, B: 0.75, A: 1}) - - var stack float32 - if state.Global.CoreRunning { - stackHint(&stack, "key-p", "RESUME", h) - } - stackHint(&stack, "key-up-down", "NAVIGATE", h) - stackHint(&stack, "key-z", "BACK", h) - stackHint(&stack, "key-x", "RUN", h) + HintBar(&Props{}, + Hint(&Props{Hidden: !state.Global.CoreRunning}, "key-p", "RESUME"), + Hint(&Props{}, "key-up-down", "NAVIGATE"), + Hint(&Props{}, "key-z", "BACK"), + Hint(&Props{}, "key-x", "RUN"), + )() } diff --git a/menu/scene_settings.go b/menu/scene_settings.go index 797d471b..3fcbe303 100644 --- a/menu/scene_settings.go +++ b/menu/scene_settings.go @@ -246,8 +246,7 @@ func (s *sceneSettings) render() { } func (s *sceneSettings) drawHintBar() { - list := menu.stack[len(menu.stack)-1].Entry() - cb := list.children[list.ptr].callbackOK + cb := s.children[s.ptr].callbackOK HintBar(&Props{}, Hint(&Props{Hidden: !state.Global.CoreRunning}, "key-p", "RESUME"), Hint(&Props{}, "key-up-down", "NAVIGATE"), From 0404944e4e9981bf9a21a9f8c15edbcd883ad181 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Andr=C3=A9=20Santoni?= Date: Sun, 16 Jun 2019 23:13:49 +0700 Subject: [PATCH 07/13] Remove test code --- menu/scene_tabs.go | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/menu/scene_tabs.go b/menu/scene_tabs.go index 1f544c74..0ed6961b 100644 --- a/menu/scene_tabs.go +++ b/menu/scene_tabs.go @@ -283,37 +283,6 @@ func (tabs sceneTags) render() { x-128*e.scale*menu.ratio, float32(h)*e.yp-128*e.scale*menu.ratio, 256*menu.ratio, 256*menu.ratio, e.scale, video.Color{R: 1, G: 1, B: 1, A: e.iconAlpha}) } - - red := video.Color{R: 1, G: 0, B: 0, A: 1} - green := video.Color{R: 0, G: 1, B: 0, A: 1} - blue := video.Color{R: 0, G: 0, B: 1, A: 1} - black := video.Color{R: 0, G: 0, B: 0, A: 1} - white := video.Color{R: 1, G: 1, B: 1, A: 1} - - Box(&Props{X: 20, Y: 20, Width: 600, Height: 600, Color: red}, - VBox(&Props{X: 50, Y: 50, Width: 500, Height: 500, Color: green}, - VBox(&Props{Width: 50, Height: 50, Color: blue}, - VBox(&Props{Width: 10, Height: 10, Color: white}), - VBox(&Props{Width: 10, Height: 10, Color: green}), - ), - HBox(&Props{Width: 100, Height: 100, Color: black}, - VBox(&Props{Width: 25, Height: 25, Color: black}), - Box(&Props{Width: 25, Height: 25, Color: red}), - Box(&Props{Width: 25, Height: 25, Color: black}), - VBox(&Props{Width: 25, Height: 25, Color: red}, - Image(&Props{Width: 25, Height: 25, Scale: 1, Color: white}, menu.icons["key-x"]), - Image(&Props{Width: 25, Height: 25, Scale: 1, Color: white}, menu.icons["key-x"]), - ), - ), - HBox(&Props{Width: 50, Height: 50, Color: blue}, - Label(&Props{Scale: 0.5, Height: 50, Color: white}, "hi"), - Image(&Props{Width: 50, Height: 50, Scale: 1, Color: white}, menu.icons["key-x"]), - Label(&Props{Scale: 0.5, Height: 50, Color: white}, "hi"), - Image(&Props{Width: 50, Height: 50, Scale: 1, Color: white}, menu.icons["key-x"]), - Label(&Props{Scale: 0.5, Height: 50, Color: white}, "hi"), - ), - ), - )() } func (tabs sceneTags) drawHintBar() { From d8ebaaf204c8ff93109ca797b3a6107d0a8f6d84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Andr=C3=A9=20Santoni?= Date: Mon, 17 Jun 2019 00:20:51 +0700 Subject: [PATCH 08/13] Rewrite tabs --- menu/scene_tabs.go | 101 +++++++++++++++++++++++++-------------------- menu/widgets.go | 2 +- 2 files changed, 58 insertions(+), 45 deletions(-) diff --git a/menu/scene_tabs.go b/menu/scene_tabs.go index 0ed6961b..bb7e18ab 100644 --- a/menu/scene_tabs.go +++ b/menu/scene_tabs.go @@ -18,12 +18,12 @@ import ( "github.com/tanema/gween/ease" ) -type sceneTags struct { +type sceneTabs struct { entry } func buildTabs() Scene { - var list sceneTags + var list sceneTabs list.label = "Ludo" list.children = append(list.children, entry{ @@ -88,17 +88,14 @@ func refreshTabs() { // Ensure new icons are styled properly for i := range e.children { if i == e.ptr { - e.children[i].yp = 0.5 e.children[i].iconAlpha = 1 e.children[i].scale = 0.75 e.children[i].width = 500 } else if i < e.ptr { - e.children[i].yp = 0.5 e.children[i].iconAlpha = 1 e.children[i].scale = 0.25 e.children[i].width = 128 } else if i > e.ptr { - e.children[i].yp = 0.5 e.children[i].iconAlpha = 1 e.children[i].scale = 0.25 e.children[i].width = 128 @@ -144,28 +141,25 @@ func getPlaylists() []entry { return pls } -func (tabs *sceneTags) Entry() *entry { +func (tabs *sceneTabs) Entry() *entry { return &tabs.entry } -func (tabs *sceneTags) segueMount() { +func (tabs *sceneTabs) segueMount() { for i := range tabs.children { e := &tabs.children[i] if i == tabs.ptr { - e.yp = 0.5 e.labelAlpha = 1 e.iconAlpha = 1 e.scale = 0.75 e.width = 500 } else if i < tabs.ptr { - e.yp = 0.5 e.labelAlpha = 0 e.iconAlpha = 1 e.scale = 0.25 e.width = 128 } else if i > tabs.ptr { - e.yp = 0.5 e.labelAlpha = 0 e.iconAlpha = 1 e.scale = 0.25 @@ -176,36 +170,32 @@ func (tabs *sceneTags) segueMount() { tabs.animate() } -func (tabs *sceneTags) segueBack() { +func (tabs *sceneTabs) segueBack() { tabs.animate() } -func (tabs *sceneTags) animate() { +func (tabs *sceneTabs) animate() { for i := range tabs.children { e := &tabs.children[i] - var yp, labelAlpha, iconAlpha, scale, width float32 + var labelAlpha, iconAlpha, scale, width float32 if i == tabs.ptr { - yp = 0.5 labelAlpha = 1 iconAlpha = 1 scale = 0.75 width = 500 } else if i < tabs.ptr { - yp = 0.5 labelAlpha = 0 iconAlpha = 1 scale = 0.25 width = 128 } else if i > tabs.ptr { - yp = 0.5 labelAlpha = 0 iconAlpha = 1 scale = 0.25 width = 128 } - menu.tweens[&e.yp] = gween.New(e.yp, yp, 0.15, ease.OutSine) 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.scale] = gween.New(e.scale, scale, 0.15, ease.OutSine) @@ -215,7 +205,7 @@ func (tabs *sceneTags) animate() { menu.tweens[&menu.scroll] = gween.New(menu.scroll, float32(tabs.ptr*128), 0.15, ease.OutSine) } -func (tabs *sceneTags) segueNext() { +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) @@ -227,7 +217,7 @@ func (tabs *sceneTags) segueNext() { } } -func (tabs *sceneTags) update(dt float32) { +func (tabs *sceneTabs) update(dt float32) { // Right repeatRight(dt, input.NewState[0][libretro.DeviceIDJoypadRight], func() { tabs.ptr++ @@ -255,37 +245,60 @@ func (tabs *sceneTags) update(dt float32) { } } -func (tabs sceneTags) render() { +// Tab is a widget that draws the homepage hexagon plus title +func Tab(props *Props, i int, e entry) func() { + c := colorful.Hcl(float64(i)*20, 0.5, 0.5) + return Box(props, + Box(&Props{Width: e.width * menu.ratio}, + Image(&Props{ + X: e.width/2*menu.ratio - 220*e.scale*menu.ratio, + Y: -220 * e.scale * menu.ratio, + Width: 440 * menu.ratio, + Height: 440 * menu.ratio, + Scale: e.scale, + Color: video.Color{R: float32(c.R), G: float32(c.B), B: float32(c.G), A: e.iconAlpha}, + }, menu.icons["hexagon"]), + Image(&Props{ + X: e.width/2*menu.ratio - 128*e.scale*menu.ratio, + Y: -128 * e.scale * menu.ratio, + Width: 256 * menu.ratio, + Height: 256 * menu.ratio, + Scale: e.scale, + Color: video.Color{R: 1, G: 1, B: 1, A: e.iconAlpha}, + }, menu.icons[e.icon]), + Label(&Props{ + Y: 250 * menu.ratio, + TextAlign: "center", + Scale: 0.6 * menu.ratio, + Color: video.Color{R: float32(c.R), G: float32(c.B), B: float32(c.G), A: e.labelAlpha}, + }, e.label), + Label(&Props{ + Y: 330 * menu.ratio, + TextAlign: "center", + Scale: 0.4 * menu.ratio, + Color: video.Color{R: float32(c.R), G: float32(c.B), B: float32(c.G), A: e.labelAlpha}, + }, e.subLabel), + ), + ) +} + +func (tabs sceneTabs) render() { _, h := vid.Window.GetFramebufferSize() - stackWidth := 710 * menu.ratio + var children []func() for i, e := range tabs.children { - - c := colorful.Hcl(float64(i)*20, 0.5, 0.5) - - x := -menu.scroll*menu.ratio + stackWidth + e.width/2*menu.ratio - - stackWidth += e.width*menu.ratio + e.margin*menu.ratio - - if e.labelAlpha > 0 { - vid.Font.SetColor(float32(c.R), float32(c.B), float32(c.G), e.labelAlpha) - lw := vid.Font.Width(0.6*menu.ratio, e.label) - vid.Font.Printf(x-lw/2, float32(h)*e.yp+250*menu.ratio, 0.6*menu.ratio, e.label) - lw = vid.Font.Width(0.4*menu.ratio, e.subLabel) - vid.Font.Printf(x-lw/2, float32(h)*e.yp+330*menu.ratio, 0.4*menu.ratio, e.subLabel) - } - - vid.DrawImage(menu.icons["hexagon"], - x-220*e.scale*menu.ratio, float32(h)*e.yp-220*e.scale*menu.ratio, - 440*menu.ratio, 440*menu.ratio, e.scale, video.Color{R: float32(c.R), G: float32(c.B), B: float32(c.G), A: e.iconAlpha}) - - vid.DrawImage(menu.icons[e.icon], - x-128*e.scale*menu.ratio, float32(h)*e.yp-128*e.scale*menu.ratio, - 256*menu.ratio, 256*menu.ratio, e.scale, video.Color{R: 1, G: 1, B: 1, A: e.iconAlpha}) + children = append(children, Tab(&Props{ + Y: float32(h) / 2, + Width: e.width*menu.ratio + e.margin*menu.ratio, + }, i, e)) } + + HBox(&Props{ + X: 710*menu.ratio - menu.scroll*menu.ratio, + }, children...)() } -func (tabs sceneTags) drawHintBar() { +func (tabs sceneTabs) drawHintBar() { HintBar(&Props{}, Hint(&Props{Hidden: !state.Global.CoreRunning}, "key-p", "RESUME"), Hint(&Props{}, "key-left-right", "NAVIGATE"), diff --git a/menu/widgets.go b/menu/widgets.go index d0e233a4..ce2ddc83 100644 --- a/menu/widgets.go +++ b/menu/widgets.go @@ -125,7 +125,7 @@ func VBox(props *Props, children ...func()) func() { // Label a widget is used to draw some text func Label(props *Props, msg string) func() { return func() { - if props.Hidden { + if props.Hidden || props.Color.A == 0 { return } parent := wStack.Last() From 5329d5f1bc044423f0cbef3d46fb6cdfdca82915 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Andr=C3=A9=20Santoni?= Date: Sun, 23 Jun 2019 19:23:55 +0700 Subject: [PATCH 09/13] Remove stackHint --- menu/hints.go | 13 ++----------- menu/scene_wifi.go | 15 +++++---------- 2 files changed, 7 insertions(+), 21 deletions(-) diff --git a/menu/hints.go b/menu/hints.go index 141215a5..56549fa9 100644 --- a/menu/hints.go +++ b/menu/hints.go @@ -2,17 +2,7 @@ package menu import "github.com/libretro/ludo/video" -// Used to easily compose different hint bars based on the context. -func stackHint(stack *float32, icon, label string, h int) { - c := video.Color{R: 0.25, G: 0.25, B: 0.25, A: 1} - vid.Font.SetColor(0.25, 0.25, 0.25, 1.0) - *stack += 30 * menu.ratio - vid.DrawImage(menu.icons[icon], *stack, float32(h)-70*menu.ratio, 70*menu.ratio, 70*menu.ratio, 1.0, c) - *stack += 70 * menu.ratio - vid.Font.Printf(*stack, float32(h)-23*menu.ratio, 0.5*menu.ratio, label) - *stack += vid.Font.Width(0.5*menu.ratio, label) -} - +// HintBar is the bar showing at the bottom of the screen func HintBar(props *Props, children ...func()) func() { w, h := vid.Window.GetFramebufferSize() return HBox(&Props{ @@ -26,6 +16,7 @@ func HintBar(props *Props, children ...func()) func() { ) } +// Hint is a widget combining an icon and a label func Hint(props *Props, icon string, title string) func() { darkGrey := video.Color{R: 0.25, G: 0.25, B: 0.25, A: 1} return HBox(props, diff --git a/menu/scene_wifi.go b/menu/scene_wifi.go index 9019d8b5..1912db0f 100644 --- a/menu/scene_wifi.go +++ b/menu/scene_wifi.go @@ -3,7 +3,6 @@ package menu import ( "github.com/libretro/ludo/ludos" ntf "github.com/libretro/ludo/notifications" - "github.com/libretro/ludo/video" ) type sceneWiFi struct { @@ -87,13 +86,9 @@ func (s *sceneWiFi) render() { } func (s *sceneWiFi) drawHintBar() { - w, h := vid.Window.GetFramebufferSize() - vid.DrawRect(0, float32(h)-70*menu.ratio, float32(w), 70*menu.ratio, 0, video.Color{R: 0.75, G: 0.75, B: 0.75, A: 1}) - - var stack float32 - stackHint(&stack, "key-up-down", "NAVIGATE", h) - stackHint(&stack, "key-z", "BACK", h) - if s.children[0].callbackOK != nil { - stackHint(&stack, "key-x", "CONNECT", h) - } + HintBar(&Props{}, + Hint(&Props{}, "key-up-down", "NAVIGATE"), + Hint(&Props{}, "key-x", "BACK"), + Hint(&Props{Hidden: s.children[0].callbackOK == nil}, "key-x", "CONNECT"), + )() } From c36cabda18ae2627d1bd4592ba745b54db06c6ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Andr=C3=A9=20Santoni?= Date: Sun, 23 Jun 2019 19:29:17 +0700 Subject: [PATCH 10/13] Comments --- menu/widgets.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/menu/widgets.go b/menu/widgets.go index ce2ddc83..d5c582df 100644 --- a/menu/widgets.go +++ b/menu/widgets.go @@ -4,10 +4,13 @@ import ( "github.com/libretro/ludo/video" ) +// Direction of the children widgets in a container type Direction uint8 const ( + // Vertical means that the children will stack on top of each others Vertical Direction = iota + // Horizontal means that the children will stack from left to right Horizontal ) From cdb0eca4ef8da0f0cf9fef3ce239b000f1b6adba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Andr=C3=A9=20Santoni?= Date: Sat, 5 Sep 2020 21:24:01 +0700 Subject: [PATCH 11/13] Toasts --- menu/notifications.go | 56 +++++++++++++++++++++++++------------------ menu/widgets.go | 7 ++++++ 2 files changed, 40 insertions(+), 23 deletions(-) diff --git a/menu/notifications.go b/menu/notifications.go index 2b909002..70249ae8 100644 --- a/menu/notifications.go +++ b/menu/notifications.go @@ -27,29 +27,39 @@ func (m *Menu) RenderNotifications() { var h float32 = 75 stack := h for _, n := range ntf.List() { - fading := n.Duration * 4 - if fading > 1 { - fading = 1 - } - offset := fading*h - h - lw := vid.Font.Width(0.5*m.ratio, n.Message) - fg := severityFgColor[n.Severity] - bg := severityBgColor[n.Severity] - vid.DrawRect( - 25*m.ratio, - (stack+offset-46)*m.ratio, - lw+40*m.ratio, - 70*m.ratio, - 0.25, - video.Color{R: float32(bg.R), G: float32(bg.G), B: float32(bg.B), A: fading}, - ) - vid.Font.SetColor(float32(fg.R), float32(fg.G), float32(fg.B), fading) - vid.Font.Printf( - 45*m.ratio, - (stack+offset)*m.ratio, - 0.5*m.ratio, - n.Message, - ) + offset := minf32(n.Duration*4, 1)*h - h + lw := vid.Font.Width(0.4*menu.ratio, n.Message) + + Toast(&Props{ + X: 25 * menu.ratio, + Y: (stack + offset - 46) * menu.ratio, + Width: lw + 70*menu.ratio + 20*menu.ratio, + Height: 70 * menu.ratio, + BorderRadius: 0.25, + }, n)() + stack += h + offset } } + +// Toast can render a notification +func Toast(props *Props, n *ntf.Notification) func() { + fg := severityFgColor[n.Severity] + bg := severityBgColor[n.Severity] + alpha := minf32(n.Duration*4, 1) + props.Color = video.Color{R: float32(bg.R), G: float32(bg.G), B: float32(bg.B), A: alpha} + + return HBox(props, + Image(&Props{ + Width: props.Height, + Height: props.Height, + Scale: 1, + Color: video.Color{R: float32(fg.R), G: float32(fg.G), B: float32(fg.B), A: alpha}, + }, menu.icons["core-infos"]), + Label(&Props{ + Height: props.Height, + Scale: 0.4 * menu.ratio, + Color: video.Color{R: float32(fg.R), G: float32(fg.G), B: float32(fg.B), A: alpha}, + }, n.Message), + ) +} diff --git a/menu/widgets.go b/menu/widgets.go index d5c582df..a5e8b91d 100644 --- a/menu/widgets.go +++ b/menu/widgets.go @@ -182,3 +182,10 @@ func maxf32(a, b float32) float32 { } return a } + +func minf32(a, b float32) float32 { + if b < a { + return b + } + return a +} From a66883f1c7360995ead698ba18ac1b730780e063 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Andr=C3=A9=20Santoni?= Date: Sat, 5 Sep 2020 22:03:28 +0700 Subject: [PATCH 12/13] Cleaning --- menu/notifications.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/menu/notifications.go b/menu/notifications.go index 70249ae8..1b6d9478 100644 --- a/menu/notifications.go +++ b/menu/notifications.go @@ -24,21 +24,20 @@ var severityBgColor = map[ntf.Severity]colorful.Color{ func (m *Menu) RenderNotifications() { fbw, fbh := vid.Window.GetFramebufferSize() vid.Font.UpdateResolution(fbw, fbh) - var h float32 = 75 - stack := h + stack := float32(0) for _, n := range ntf.List() { - offset := minf32(n.Duration*4, 1)*h - h + offset := minf32(n.Duration*4, 1) * 80 lw := vid.Font.Width(0.4*menu.ratio, n.Message) Toast(&Props{ X: 25 * menu.ratio, - Y: (stack + offset - 46) * menu.ratio, + Y: (stack + offset - 80 + 25) * menu.ratio, Width: lw + 70*menu.ratio + 20*menu.ratio, Height: 70 * menu.ratio, BorderRadius: 0.25, }, n)() - stack += h + offset + stack += offset } } From 7791c486dac592b95c9d6dbca82e385a6e739b9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Andr=C3=A9=20Santoni?= Date: Sat, 5 Sep 2020 22:21:51 +0700 Subject: [PATCH 13/13] Cleaning --- menu/scene_keyboard.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/menu/scene_keyboard.go b/menu/scene_keyboard.go index 838563da..6cae4fe1 100644 --- a/menu/scene_keyboard.go +++ b/menu/scene_keyboard.go @@ -20,19 +20,19 @@ type sceneKeyboard struct { } var layouts = [][]string{ - []string{ + { "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "a", "s", "d", "f", "g", "h", "j", "k", "l", "@", "z", "x", "c", "v", "b", "n", "m", " ", "-", ".", }, - []string{ + { "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "A", "S", "D", "F", "G", "H", "J", "K", "L", "+", "Z", "X", "C", "V", "B", "N", "M", " ", "_", "/", }, - []string{ + { "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "!", "\"", "#", "$", "%%", "&", "'", "*", "(", ")", "+", ",", "-", "~", "/", ":", ";", "=", "<", ">",