split screen mode for fs. fix game dump. scrolling text for fs, progress, menu base. display icon when dumping.

This commit is contained in:
ITotalJustice
2025-05-24 21:55:10 +01:00
parent d43ca37875
commit 15721b8e8a
13 changed files with 964 additions and 623 deletions

View File

@@ -66,6 +66,7 @@ struct UsbTest final : usb::upload::Usb, yati::source::Stream {
m_pull_offset = 0;
Stream::Reset();
m_size = m_source->GetSize(path);
m_pbox->SetImage(m_source->GetIcon(path));
m_pbox->SetTitle(m_source->GetName(path));
m_pbox->NewTransfer(m_path);
}
@@ -107,6 +108,7 @@ Result DumpToFile(ui::ProgressBox* pbox, fs::Fs* fs, const fs::FsPath& root, Bas
for (auto path : paths) {
const auto file_size = source->GetSize(path);
pbox->SetImage(source->GetIcon(path));
pbox->SetTitle(source->GetName(path));
pbox->NewTransfer(path);
@@ -246,6 +248,7 @@ Result DumpToDevNull(ui::ProgressBox* pbox, BaseSource* source, std::span<const
R_TRY(pbox->ShouldExitResult());
const auto file_size = source->GetSize(path);
pbox->SetImage(source->GetIcon(path));
pbox->SetTitle(source->GetName(path));
pbox->NewTransfer(path);
@@ -267,6 +270,7 @@ Result DumpToNetwork(ui::ProgressBox* pbox, const location::Entry& loc, BaseSour
R_TRY(pbox->ShouldExitResult());
const auto file_size = source->GetSize(path);
pbox->SetImage(source->GetIcon(path));
pbox->SetTitle(source->GetName(path));
pbox->NewTransfer(path);
@@ -375,8 +379,4 @@ void Dump(std::shared_ptr<BaseSource> source, const std::vector<fs::FsPath>& pat
));
}
void DumpSingle(std::shared_ptr<BaseSource> source, const fs::FsPath& path, OnExit on_exit, DumpLocationType type) {
}
} // namespace sphaira::dump

View File

@@ -12,7 +12,8 @@ List::List(s64 row, s64 page, const Vec4& pos, const Vec4& v, const Vec2& pad)
, m_v{v}
, m_pad{pad} {
m_pos = pos;
SetScrollBarPos(SCREEN_WIDTH - 50, 100, SCREEN_HEIGHT-200);
// SetScrollBarPos(SCREEN_WIDTH - 50, 100, SCREEN_HEIGHT-200);
SetScrollBarPos(pos.x + pos.w, 100, SCREEN_HEIGHT-200);
}
auto List::ClampX(float x, s64 count) const -> float {
@@ -165,8 +166,8 @@ void List::OnUpdateHome(Controller* controller, TouchInfo* touch, s64 index, s64
}
void List::OnUpdateGrid(Controller* controller, TouchInfo* touch, s64 index, s64 count, TouchCallback callback) {
const auto page_up_button = m_row == 1 ? Button::DPAD_LEFT : Button::L2;
const auto page_down_button = m_row == 1 ? Button::DPAD_RIGHT : Button::R2;
const auto page_up_button = GetPageJump() ? (m_row == 1 ? Button::DPAD_LEFT : Button::L2) : (Button::NONE);
const auto page_down_button = GetPageJump() ? (m_row == 1 ? Button::DPAD_RIGHT : Button::R2) : (Button::NONE);
if (controller->GotDown(Button::DOWN)) {
if (ScrollDown(index, m_row, count)) {
@@ -277,7 +278,11 @@ void List::DrawGrid(NVGcontext* vg, Theme* theme, s64 count, Callback callback)
const auto x = v.x;
for (; i < count; i++, v.x += v.w + m_pad.x) {
for (s64 row = 0; i < count; row++, i++, v.x += v.w + m_pad.x) {
if (row >= m_row) {
break;
}
// only draw if full x is in bounds
if (v.x + v.w > GetX() + GetW()) {
break;

File diff suppressed because it is too large Load Diff

View File

@@ -129,6 +129,8 @@ struct NspEntry {
s64 nsp_size{};
// copy of ncm cs, it is not closed.
NcmContentStorage cs{};
// copy of the icon, if invalid, it will use the default icon.
int icon{};
// todo: benchmark manual sdcard read and decryption vs ncm.
Result Read(void* buf, s64 off, s64 size, u64* bytes_read) {
@@ -185,7 +187,7 @@ struct NspSource final : dump::BaseSource {
Result Read(const std::string& path, void* buf, s64 off, s64 size, u64* bytes_read) override {
const auto it = std::ranges::find_if(m_entries, [&path](auto& e){
return path == e.path;
return path.find(e.path.s) != path.npos;
});
R_UNLESS(it != m_entries.end(), 0x1);
@@ -194,7 +196,7 @@ struct NspSource final : dump::BaseSource {
auto GetName(const std::string& path) const -> std::string {
const auto it = std::ranges::find_if(m_entries, [&path](auto& e){
return path == e.path;
return path.find(e.path.s) != path.npos;
});
if (it != m_entries.end()) {
@@ -206,7 +208,7 @@ struct NspSource final : dump::BaseSource {
auto GetSize(const std::string& path) const -> s64 {
const auto it = std::ranges::find_if(m_entries, [&path](auto& e){
return path == e.path;
return path.find(e.path.s) != path.npos;
});
if (it != m_entries.end()) {
@@ -216,6 +218,18 @@ struct NspSource final : dump::BaseSource {
return 0;
}
auto GetIcon(const std::string& path) const -> int override {
const auto it = std::ranges::find_if(m_entries, [&path](auto& e){
return path.find(e.path.s) != path.npos;
});
if (it != m_entries.end()) {
return it->icon;
}
return App::GetDefaultImage();
}
private:
std::vector<NspEntry> m_entries{};
};
@@ -568,7 +582,7 @@ Result BuildNspEntries(Entry& e, u32 flags, std::vector<NspEntry>& out) {
NspEntry nsp;
R_TRY(BuildNspEntry(e, info, nsp));
out.emplace_back(nsp);
out.emplace_back(nsp).icon = e.image;
}
R_UNLESS(!out.empty(), 0x1);

View File

@@ -171,6 +171,7 @@ struct XciSource final : dump::BaseSource {
// size of the entire xci.
s64 xci_size{};
Menu* menu{};
int icon{};
Result Read(const std::string& path, void* buf, s64 off, s64 size, u64* bytes_read) override {
if (path.ends_with(GetDumpTypeStr(DumpFileType_XCI))) {
@@ -218,6 +219,10 @@ struct XciSource final : dump::BaseSource {
return 0;
}
auto GetIcon(const std::string& path) const -> int override {
return icon;
}
private:
static auto InRange(s64 off, s64 offset, s64 size) -> bool {
return off < offset + size && off >= offset;
@@ -944,6 +949,7 @@ Result Menu::DumpGames(u32 flags) {
auto source = std::make_shared<XciSource>();
source->menu = this;
source->application_name = m_entries[m_entry_index].lang_entry.name;
source->icon = m_icon;
std::vector<fs::FsPath> paths;
if (flags & DumpFileFlag_XCI) {

View File

@@ -70,10 +70,13 @@ void MenuBase::Draw(NVGcontext* vg, Theme* theme) {
nvgFontSize(vg, 28);
gfx::textBounds(vg, 0, 0, bounds, m_title.c_str());
gfx::drawTextArgs(vg, 80, start_y, 28.f, NVG_ALIGN_LEFT | NVG_ALIGN_BOTTOM, theme->GetColour(ThemeEntryID_TEXT), m_title.c_str());
gfx::drawTextArgs(vg, 80 + (bounds[2] - bounds[0]) + 10, start_y, 16, NVG_ALIGN_LEFT | NVG_ALIGN_BOTTOM, theme->GetColour(ThemeEntryID_TEXT_INFO), m_title_sub_heading.c_str());
gfx::drawTextArgs(vg, 80, 685.f, 18, NVG_ALIGN_LEFT, theme->GetColour(ThemeEntryID_TEXT), "%s", m_sub_heading.c_str());
const auto text_w = SCREEN_WIDTH / 2 - 30;
const auto title_sub_x = 80 + (bounds[2] - bounds[0]) + 10;
gfx::drawTextArgs(vg, 80, start_y, 28.f, NVG_ALIGN_LEFT | NVG_ALIGN_BOTTOM, theme->GetColour(ThemeEntryID_TEXT), m_title.c_str());
m_scroll_title_sub_heading.Draw(vg, true, title_sub_x, start_y, text_w - title_sub_x, 16, NVG_ALIGN_LEFT | NVG_ALIGN_BOTTOM, theme->GetColour(ThemeEntryID_TEXT_INFO), m_title_sub_heading.c_str());
m_scroll_sub_heading.Draw(vg, true, 80, 685, text_w - 80, 18, NVG_ALIGN_LEFT, theme->GetColour(ThemeEntryID_TEXT), m_sub_heading.c_str());
}
void MenuBase::SetTitle(std::string title) {

View File

@@ -87,11 +87,19 @@ auto ProgressBox::Draw(NVGcontext* vg, Theme* theme) -> void {
const auto offset = m_offset;
const auto speed = m_speed;
const auto last_offset = m_last_offset;
auto image = m_image;
if (m_is_image_pending) {
FreeImage();
image = m_image = m_image_pending;
m_image_pending = 0;
m_is_image_pending = false;
}
mutexUnlock(&m_mutex);
if (!image_data.empty()) {
FreeImage();
m_image = nvgCreateImageMem(vg, 0, image_data.data(), image_data.size());
image = m_image = nvgCreateImageMem(vg, 0, image_data.data(), image_data.size());
m_own_image = true;
}
@@ -108,8 +116,8 @@ auto ProgressBox::Draw(NVGcontext* vg, Theme* theme) -> void {
nvgSave(vg);
nvgIntersectScissor(vg, GetX(), GetY(), GetW(), GetH());
if (m_image) {
gfx::drawImage(vg, GetX() + 30, GetY() + 30, 128, 128, m_image, 5);
if (image) {
gfx::drawImage(vg, GetX() + 25, GetY() + 25, 120, 120, image, 5);
}
// shapes.
@@ -152,9 +160,21 @@ auto ProgressBox::Draw(NVGcontext* vg, Theme* theme) -> void {
}
gfx::drawTextArgs(vg, center_x, m_pos.y + 40, 24, NVG_ALIGN_CENTER | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT), m_action.c_str());
gfx::drawTextArgs(vg, center_x, m_pos.y + 100, 22, NVG_ALIGN_CENTER | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT), title.c_str());
const auto draw_text = [&](ScrollingText& scroll, const std::string& txt, float y, float size, float pad, ThemeEntryID id){
float bounds[4];
nvgFontSize(vg, size);
gfx::textBounds(vg, 0, 0, bounds, txt.c_str());
const auto min_x = GetX() + pad;
const auto title_x = std::max(min_x, center_x - (bounds[2] - bounds[0]) / 2);
scroll.Draw(vg, true, title_x, y, GetW() - pad * 2, size, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, theme->GetColour(id), txt.c_str());
};
draw_text(m_scroll_title, title, m_pos.y + 100, 22, 160, ThemeEntryID_TEXT);
if (!transfer.empty()) {
gfx::drawTextArgs(vg, center_x, m_pos.y + 150, 18, NVG_ALIGN_CENTER | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT_INFO), "%s", transfer.c_str());
draw_text(m_scroll_transfer, transfer, m_pos.y + 160, 18, 30, ThemeEntryID_TEXT_INFO);
}
nvgRestore(vg);
@@ -189,6 +209,14 @@ auto ProgressBox::UpdateTransfer(s64 offset, s64 size) -> ProgressBox& {
return *this;
}
auto ProgressBox::SetImage(int image) -> ProgressBox& {
mutexLock(&m_mutex);
m_image_pending = image;
m_is_image_pending = true;
mutexUnlock(&m_mutex);
return *this;
}
auto ProgressBox::SetImageData(std::vector<u8>& data) -> ProgressBox& {
mutexLock(&m_mutex);
std::swap(m_image_data, data);
@@ -219,24 +247,24 @@ auto ProgressBox::ShouldExitResult() -> Result {
R_SUCCEED();
}
auto ProgressBox::CopyFile(fs::Fs* fs, const fs::FsPath& src_path, const fs::FsPath& dst_path) -> Result {
auto ProgressBox::CopyFile(fs::Fs* fs_src, fs::Fs* fs_dst, const fs::FsPath& src_path, const fs::FsPath& dst_path) -> Result {
fs::File src_file;
R_TRY(fs->OpenFile(src_path, FsOpenMode_Read, &src_file));
ON_SCOPE_EXIT(fs->FileClose(&src_file));
R_TRY(fs_src->OpenFile(src_path, FsOpenMode_Read, &src_file));
ON_SCOPE_EXIT(fs_src->FileClose(&src_file));
s64 src_size;
R_TRY(fs->FileGetSize(&src_file, &src_size));
R_TRY(fs_src->FileGetSize(&src_file, &src_size));
// this can fail if it already exists so we ignore the result.
// if the file actually failed to be created, the result is implicitly
// handled when we try and open it for writing.
fs->CreateFile(dst_path, src_size, 0);
fs_dst->CreateFile(dst_path, src_size, 0);
fs::File dst_file;
R_TRY(fs->OpenFile(dst_path, FsOpenMode_Write, &dst_file));
ON_SCOPE_EXIT(fs->FileClose(&dst_file));
R_TRY(fs_dst->OpenFile(dst_path, FsOpenMode_Write, &dst_file));
ON_SCOPE_EXIT(fs_dst->FileClose(&dst_file));
R_TRY(fs->FileSetSize(&dst_file, src_size));
R_TRY(fs_dst->FileSetSize(&dst_file, src_size));
s64 offset{};
std::vector<u8> buf(1024*1024*4); // 4MiB
@@ -245,10 +273,10 @@ auto ProgressBox::CopyFile(fs::Fs* fs, const fs::FsPath& src_path, const fs::FsP
R_TRY(ShouldExitResult());
u64 bytes_read;
R_TRY(fs->FileRead(&src_file, offset, buf.data(), buf.size(), 0, &bytes_read));
R_TRY(fs_src->FileRead(&src_file, offset, buf.data(), buf.size(), 0, &bytes_read));
Yield();
R_TRY(fs->FileWrite(&dst_file, offset, buf.data(), bytes_read, FsWriteOption_None));
R_TRY(fs_dst->FileWrite(&dst_file, offset, buf.data(), bytes_read, FsWriteOption_None));
Yield();
UpdateTransfer(offset, src_size);
@@ -258,6 +286,10 @@ auto ProgressBox::CopyFile(fs::Fs* fs, const fs::FsPath& src_path, const fs::FsP
R_SUCCEED();
}
auto ProgressBox::CopyFile(fs::Fs* fs, const fs::FsPath& src_path, const fs::FsPath& dst_path) -> Result {
return CopyFile(fs, fs, src_path, dst_path);
}
auto ProgressBox::CopyFile(const fs::FsPath& src_path, const fs::FsPath& dst_path) -> Result {
fs::FsNativeSd fs;
R_TRY(fs.GetFsOpenResult());