Add Load Dialog Interface

This commit is contained in:
Joshua de Reeper 2026-05-19 23:15:55 +02:00 committed by deReeperJosh
parent 4dd4d40684
commit 29ffab52f7
8 changed files with 326 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 521 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

View File

@ -53,6 +53,10 @@ namespace rsx::overlays::home_menu
return "gauge-solid.png";
case fa_icon::usb:
return "usb-solid.png";
case fa_icon::file:
return "file-solid.png";
case fa_icon::folder:
return "folder-solid.png";
case fa_icon::bug:
return "bug-solid.png";
}

View File

@ -26,6 +26,8 @@ namespace rsx::overlays::home_menu
settings_sliders,
settings_gauge,
usb,
file,
folder,
bug,
};

View File

@ -4,6 +4,7 @@
#include "Emu/Io/Skylander.h"
#include "Emu/System.h"
#include "Emu/VFS.h"
#include "Utilities/File.h"
namespace rsx
{
@ -12,6 +13,8 @@ namespace rsx
static constexpr u16 sky_list_y = 85;
static constexpr u16 sky_list_h = 540;
static std::string last_skylander_path = "";
skylander_dialog::skylander_list_entry::skylander_list_entry(std::string name)
{
std::unique_ptr<overlay_element> label_text = std::make_unique<label>(name);
@ -99,6 +102,7 @@ namespace rsx
break;
case pad_button::cross:
// load
load_skylander(m_list->get_selected_index());
break;
case pad_button::square:
// clear
@ -252,5 +256,277 @@ namespace rsx
}
}
}
void skylander_dialog::load_skylander(u8 slot)
{
ensure(slot < 8);
Emu.CallFromMainThread([this, slot]()
{
if (auto manager = g_fxo->try_get<rsx::overlays::display_manager>())
{
manager->create<rsx::overlays::skylander_load_dialog>(slot)->show([this](s32 status)
{
if (status == selection_code::ok)
m_list_dirty = true;
});
}
});
}
skylander_load_dialog::skylander_file_list_entry::skylander_file_list_entry(skylander_file_type type, std::string file_name)
: file_name(file_name), type(type)
{
if (type == skylander_file_type::folder)
{
icon_data = rsx::overlays::resource_config::load_icon("home/32/folder-solid.png");
}
else
{
icon_data = rsx::overlays::resource_config::load_icon("home/32/file-solid.png");
}
std::unique_ptr<overlay_element> image = std::make_unique<image_view>(30, 30);
static_cast<image_view*>(image.get())->set_raw_image(icon_data.get());
std::unique_ptr<overlay_element> label_text = std::make_unique<label>(file_name);
label_text->set_size(400, 30);
label_text->set_font("Arial", 16);
label_text->set_wrap_text(true);
label_text->back_color.a = 0.f;
this->pack_padding = 15;
this->add_element(image);
this->add_element(label_text);
}
skylander_load_dialog::skylander_load_dialog(u8 slot) : slot(slot)
{
m_allow_input_on_pause = true;
m_dim_background = std::make_unique<overlay_element>();
m_dim_background->set_size(virtual_width, virtual_height);
m_dim_background->back_color.a = 0.9f;
m_description = std::make_unique<label>();
m_description->set_font("Arial", 20);
m_description->set_pos(20, 37);
m_description->set_text("Emulated Skylander Portal");
m_description->auto_resize();
m_description->back_color.a = 0.f;
fade_animation.duration_sec = 0.15f;
return_code = selection_code::canceled;
}
void skylander_load_dialog::update(u64 timestamp_us)
{
if (fade_animation.active)
{
fade_animation.update(timestamp_us);
}
}
void skylander_load_dialog::on_button_pressed(pad_button button_press, bool is_auto_repeat)
{
if (fade_animation.active)
return;
bool close_dialog = false;
switch (button_press)
{
case pad_button::circle:
play_sound(sound_effect::cancel);
close_dialog = true;
break;
case pad_button::cross:
// load
play_sound(sound_effect::accept);
if (m_list && !m_list->m_items.empty())
{
auto selected_entry = static_cast<const skylander_file_list_entry*>(m_list->get_selected_entry());
rsx_log.notice("Selected skylander file entry: %s (type: %d)", selected_entry->get_file_name(), static_cast<u8>(selected_entry->get_type()));
if (selected_entry->get_type() == skylander_file_type::folder)
{
if (selected_entry->get_file_name() == "..")
{
auto last_slash_pos = last_skylander_path.find_last_of('/', last_skylander_path.length() - 2);
if (last_slash_pos != std::string::npos)
{
last_skylander_path = last_skylander_path.substr(0, last_slash_pos + 1);
}
}
else
{
last_skylander_path += selected_entry->get_file_name() + "/";
}
m_list_dirty = true;
}
else if (selected_entry->get_type() == skylander_file_type::file)
{
rsx_log.notice("Loading skylander from file: %s", last_skylander_path + selected_entry->get_file_name());
fs::file sky_file(last_skylander_path + selected_entry->get_file_name(), fs::read + fs::write + fs::lock);
if (!sky_file)
{
rsx_log.error("Failed to open skylander file: %s", last_skylander_path + selected_entry->get_file_name());
}
std::array<u8, 0x40 * 0x10> data;
if (sky_file.read(data.data(), data.size()) != data.size())
{
rsx_log.error("Failed to read skylander file: %s", last_skylander_path + selected_entry->get_file_name());
}
if (const auto skylander = g_skyportal.get_skylander(slot))
{
const auto& [portal_slot, sky_id, sky_var] = skylander.value();
g_skyportal.remove_skylander(portal_slot);
}
g_skyportal.load_skylander(slot, data.data(), std::move(sky_file));
return_code = selection_code::ok;
close_dialog = true;
}
}
break;
case pad_button::dpad_up:
case pad_button::ls_up:
m_list->select_previous();
break;
case pad_button::dpad_down:
case pad_button::ls_down:
m_list->select_next();
break;
case pad_button::L1:
m_list->select_previous(10);
break;
case pad_button::R1:
m_list->select_next(10);
break;
default:
rsx_log.trace("[ui] Button %d pressed", static_cast<u8>(button_press));
break;
}
if (close_dialog)
{
fade_animation.current = color4f(1.f);
fade_animation.end = color4f(0.f);
fade_animation.active = true;
fade_animation.on_finish = [this]
{
close(true, true);
};
}
// Play a sound unless this is a fast auto repeat which would induce a nasty noise
else if (!is_auto_repeat || m_auto_repeat_ms_interval >= m_auto_repeat_ms_interval_default)
{
play_sound(sound_effect::cursor);
}
}
compiled_resource skylander_load_dialog::get_compiled()
{
if (!visible)
{
return {};
}
compiled_resource result;
result.add(m_dim_background->get_compiled());
if (m_list_dirty.exchange(false))
{
reload();
}
if (m_list)
{
result.add(m_list->get_compiled());
}
result.add(m_description->get_compiled());
fade_animation.apply(result);
return result;
}
void skylander_load_dialog::show(std::function<void(s32 status)> on_close)
{
visible = false;
this->on_close = std::move(on_close);
fade_animation.current = color4f(0.f);
fade_animation.end = color4f(1.f);
fade_animation.active = true;
visible = true;
const auto notify = std::make_shared<atomic_t<u32>>(0);
auto& overlayman = g_fxo->get<display_manager>();
overlayman.attach_thread_input(
uid, "Skylander load dialog",
[notify]()
{
*notify = true;
notify->notify_one();
});
while (!Emu.IsStopped() && !*notify)
{
notify->wait(0, atomic_wait_timeout{1'000'000});
}
}
void skylander_load_dialog::reload()
{
s32 selected_index = 0;
std::vector<std::unique_ptr<overlay_element>> entries;
std::string skylanders_dir = vfs::get("/dev_hdd0");
if (!last_skylander_path.empty())
{
skylanders_dir = last_skylander_path;
}
else
{
skylanders_dir = skylanders_dir.substr(0, skylanders_dir.find("dev_hdd0"));
last_skylander_path = skylanders_dir;
}
for (auto&& dir_entry : fs::dir{skylanders_dir})
{
if (dir_entry.is_directory)
{
entries.emplace_back(std::make_unique<skylander_file_list_entry>(skylander_file_type::folder, dir_entry.name));
}
else if (dir_entry.name.ends_with(".sky") || dir_entry.name.ends_with(".bin") || dir_entry.name.ends_with(".dump"))
{
entries.emplace_back(std::make_unique<skylander_file_list_entry>(skylander_file_type::file, dir_entry.name));
}
}
// Recreate list
if (m_list)
{
status_flags |= status_bits::invalidate_image_cache;
}
m_list = std::make_unique<list_view>(virtual_width - 2 * 20, sky_list_h);
m_list->set_pos(20, sky_list_y);
m_list->set_cancel_only(false);
for (auto& entry : entries)
{
m_list->add_entry(entry);
}
if (!m_list->m_items.empty())
{
m_list->select_entry(selected_index);
}
m_description->set_text("Select Skylander File");
m_description->auto_resize();
}
} // namespace overlays
} // namespace rsx

View File

@ -19,6 +19,7 @@ namespace rsx
void reload();
void clear_skylander(u8 sky_slot);
void load_skylander(u8 sky_slot);
std::unique_ptr<overlay_element> m_dim_background;
std::unique_ptr<list_view> m_list;
@ -38,5 +39,48 @@ namespace rsx
void show();
};
enum class skylander_file_type
{
folder,
file
};
struct skylander_load_dialog : public user_interface
{
private:
struct skylander_file_list_entry : horizontal_layout
{
private:
std::unique_ptr<image_info> icon_data;
std::string file_name;
skylander_file_type type;
public:
skylander_file_list_entry(skylander_file_type type, std::string file_name);
std::string get_file_name() const { return file_name; }
skylander_file_type get_type() const { return type; }
};
void reload();
u8 slot = 0xff;
std::unique_ptr<overlay_element> m_dim_background;
std::unique_ptr<list_view> m_list;
std::unique_ptr<label> m_description;
animation_color_interpolate fade_animation;
atomic_t<bool> m_list_dirty{true};
public:
skylander_load_dialog(u8 slot);
void update(u64 timestamp_us) override;
void on_button_pressed(pad_button button_press, bool is_auto_repeat) override;
compiled_resource get_compiled() override;
void show(std::function<void(s32 status)> on_close);
};
} // namespace overlays
} // namespace rsx