Files

196 lines
5.9 KiB
C++

/*
* PS5 Vulkan Template - the Samples menu, drawn with the UI kit in the title's theme.
*
* With the UI module, ps5_run_launcher (launcher.cpp) hands over to this menu:
* the samples proven on the console in a themed list, the focused one described
* beside it, and Back (to the start screen) or Quit at the foot. Like the menu it
* replaces, it is a program on the base class that ends once a choice is made.
*
* Copyright (C) 2026 Mihawk
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "kit.hpp"
namespace {
using hui::gfx::Color;
class SamplesMenu : public ps5ui::KitExample
{
public:
int chosen{ -2 }; // -2: still choosing, -1: the last row, else an index in ps5Samples
int selected;
std::string message;
const char *leave;
std::vector<int> entries; // ps5Samples indices, in menu order
const hui::ui::Theme &theme = ps5ui::active_theme();
hui::ui::ListView list;
hui::gfx::DrawList scene;
hui::ui::Feedback feedback;
float clock{ 0.0f };
SamplesMenu(int selected, const std::string &message, const char *leave)
: selected(selected), message(message), leave(leave)
{
title = PS5_TITLE_NAME;
name = "samplesmenu";
settings.overlay = false;
ps5.ownOverlay = true;
for (size_t i = 0; i < ps5SampleCount; i++) {
if (ps5Samples[i].inMenu) {
entries.push_back((int)i);
}
}
}
void prepare() override
{
VulkanExampleBase::prepare();
prepareKit({ .music = false, .covers = false });
std::vector<hui::ui::ListItem> rows;
int focus = 0;
for (size_t row = 0; row < entries.size(); row++) {
const Ps5Sample &sample = ps5Samples[entries[row]];
hui::ui::ListItem item;
item.title = sample.title;
item.subtitle = sample.description;
item.tag = entries[row];
rows.push_back(item);
if (entries[row] == selected) {
focus = (int)row;
}
}
hui::ui::ListItem last;
last.title = leave;
last.tag = -1;
rows.push_back(last);
list.style.theme = theme;
list.style.panel = true;
list.style.row_height = 84.0f;
list.style.title_size = 26.0f;
list.style.subtitle_size = 19.0f;
list.set_items(rows);
list.set_bounds({ 120.0f, 230.0f, 1060.0f, 760.0f });
list.set_focus(focus);
list.enter();
prepared = true;
}
void update(float dt)
{
feedback.clear();
hui::InputFrame input = kit.input();
if (ps5.frameBudget) {
input = kit.scripted("launcher", ps5.framesDrawn);
}
clock += dt;
const hui::ui::Event event = list.handle(input, feedback);
if (event == hui::ui::Event::activated) {
chosen = list.items()[list.focus()].tag;
} else if (event == hui::ui::Event::cancelled || input.is_pressed(hui::Action::back)) {
chosen = -1;
}
list.update(dt);
kit.play(feedback, theme.sounds);
kit.tick(dt);
}
void compose()
{
scene.clear();
hui::ui::Canvas canvas{ scene, kit.fonts, 0, clock };
hui::ui::Painter paint(scene, kit.fonts, theme);
const bool fromStart = strcmp(leave, "Back") == 0;
paint.heading(fromStart ? "Samples" : PS5_TITLE_NAME, 120.0f, 150.0f, 64.0f, paint.page_text());
char device[160];
snprintf(device, sizeof(device), "%s%s, Vulkan %u.%u.%u, %ux%u", fromStart ? PS5_TITLE_NAME ": " : "",
deviceProperties.deviceName, VK_API_VERSION_MAJOR(deviceProperties.apiVersion),
VK_API_VERSION_MINOR(deviceProperties.apiVersion), VK_API_VERSION_PATCH(deviceProperties.apiVersion),
width, height);
paint.body(device, 122.0f, 196.0f, 22.0f, paint.page_text_muted());
list.draw(canvas);
// The focused sample, described beside the list
const hui::gfx::Rect info{ 1240.0f, 230.0f, 560.0f, 520.0f };
paint.panel(info);
const hui::ui::ListItem &item = list.items()[list.focus()];
paint.heading(item.title, info.x + 36.0f, info.y + 78.0f, 40.0f, theme.text);
if (!item.subtitle.empty()) {
float baseline = info.y + 130.0f;
for (const std::string &line : kit.fonts.regular.font->wrap(item.subtitle, 24.0f, info.w - 72.0f)) {
paint.body(line, info.x + 36.0f, baseline, 24.0f, theme.text_muted);
baseline += 34.0f;
}
hui::ui::Theme panelInk = theme;
panelInk.page_text_muted = theme.text_muted; // the hints sit on the panel, not the page
ps5ui::draw_hints(scene, kit.fonts, panelInk,
{ { hui::ui::Button::cross, "Start" }, { hui::ui::Button::options, "Back here, from the sample" } }, info.x + 36.0f, info.y + info.h - 52.0f, -1, 30.0f);
} else {
paint.body(fromStart ? "Back to the start screen" : "Close the title", info.x + 36.0f, info.y + 130.0f, 24.0f, theme.text_muted);
}
if (!message.empty()) {
float baseline = info.y + info.h + 50.0f;
for (const std::string &line : kit.fonts.regular.font->wrap(message, 22.0f, info.w)) {
paint.body(line, info.x, baseline, 22.0f, theme.danger);
baseline += 30.0f;
}
}
ps5ui::draw_hints(scene, kit.fonts, theme,
{ { hui::ui::Button::dpad, "Move" }, { hui::ui::Button::cross, "Start" }, { hui::ui::Button::circle, "Back" } }, 120.0f, 1040.0f);
hui::gfx::BackdropSpec backdrop = theme.backdrop;
backdrop.time = clock;
kit.renderer.begin();
kit.renderer.backdrop(backdrop);
kit.renderer.draw(scene);
}
void render() override
{
if (!prepared) {
return;
}
prepareFrame();
update(std::min(frameTimer, 0.05f));
compose();
buildKitCommandBuffer();
submitFrame();
if (chosen != -2) {
quit = true;
}
}
~SamplesMenu() override
{
if (device) {
vkDeviceWaitIdle(device);
}
}
};
} // namespace
int ps5_run_kit_launcher(int selected, const std::string &message, uint32_t frameBudget,
const std::string &screenshotPath, const char *leave)
{
SamplesMenu *menu = new SamplesMenu(selected, message, leave);
menu->benchmark.active = frameBudget > 0;
menu->ps5.frameBudget = frameBudget;
menu->ps5.screenshotPath = screenshotPath;
int chosen = -1;
try {
menu->initVulkan();
menu->prepare();
menu->renderLoop();
chosen = frameBudget ? -1 : menu->chosen;
} catch (const std::exception &e) {
say("samples menu: %s", e.what());
}
if (menu->vulkanDevice) {
vkDeviceWaitIdle(menu->vulkanDevice->logicalDevice);
}
delete menu;
return chosen;
}