split screen mode for fs. fix game dump. scrolling text for fs, progress, menu base. display icon when dumping.
This commit is contained in:
@@ -35,6 +35,7 @@ struct BaseSource {
|
|||||||
virtual Result Read(const std::string& path, void* buf, s64 off, s64 size, u64* bytes_read) = 0;
|
virtual Result Read(const std::string& path, void* buf, s64 off, s64 size, u64* bytes_read) = 0;
|
||||||
virtual auto GetName(const std::string& path) const -> std::string = 0;
|
virtual auto GetName(const std::string& path) const -> std::string = 0;
|
||||||
virtual auto GetSize(const std::string& path) const -> s64 = 0;
|
virtual auto GetSize(const std::string& path) const -> s64 = 0;
|
||||||
|
virtual auto GetIcon(const std::string& path) const -> int { return 0; }
|
||||||
};
|
};
|
||||||
|
|
||||||
// called after dump has finished.
|
// called after dump has finished.
|
||||||
|
|||||||
@@ -60,6 +60,14 @@ struct List final : Object {
|
|||||||
return m_page;
|
return m_page;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto SetPageJump(bool enable) {
|
||||||
|
m_page_jump = enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto GetPageJump() const {
|
||||||
|
return m_page_jump;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
auto Draw(NVGcontext* vg, Theme* theme) -> void override {}
|
auto Draw(NVGcontext* vg, Theme* theme) -> void override {}
|
||||||
auto ClampX(float x, s64 count) const -> float;
|
auto ClampX(float x, s64 count) const -> float;
|
||||||
@@ -85,6 +93,7 @@ private:
|
|||||||
float m_y_prog{};
|
float m_y_prog{};
|
||||||
|
|
||||||
Layout m_layout{Layout::GRID};
|
Layout m_layout{Layout::GRID};
|
||||||
|
bool m_page_jump{true};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace sphaira::ui
|
} // namespace sphaira::ui
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "ui/menus/menu_base.hpp"
|
#include "ui/menus/menu_base.hpp"
|
||||||
|
#include "ui/scrolling_text.hpp"
|
||||||
#include "ui/list.hpp"
|
#include "ui/list.hpp"
|
||||||
#include "nro.hpp"
|
#include "nro.hpp"
|
||||||
#include "fs.hpp"
|
#include "fs.hpp"
|
||||||
@@ -33,6 +34,11 @@ enum class SelectedType {
|
|||||||
Delete,
|
Delete,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class ViewSide {
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
};
|
||||||
|
|
||||||
enum SortType {
|
enum SortType {
|
||||||
SortType_Size,
|
SortType_Size,
|
||||||
SortType_Alphabetical,
|
SortType_Alphabetical,
|
||||||
@@ -56,6 +62,10 @@ struct FsEntry {
|
|||||||
auto IsAssoc() const -> bool {
|
auto IsAssoc() const -> bool {
|
||||||
return flags & FsEntryFlag_Assoc;
|
return flags & FsEntryFlag_Assoc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto IsSame(const FsEntry& e) const {
|
||||||
|
return root == e.root && type == e.type;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// roughly 1kib in size per entry
|
// roughly 1kib in size per entry
|
||||||
@@ -145,11 +155,15 @@ struct FsDirCollection {
|
|||||||
|
|
||||||
using FsDirCollections = std::vector<FsDirCollection>;
|
using FsDirCollections = std::vector<FsDirCollection>;
|
||||||
|
|
||||||
struct Menu final : MenuBase {
|
struct Menu;
|
||||||
Menu(const std::vector<NroEntry>& nro_entries);
|
|
||||||
~Menu();
|
struct FsView final : Widget {
|
||||||
|
friend class Menu;
|
||||||
|
|
||||||
|
FsView(Menu* menu, ViewSide side);
|
||||||
|
FsView(Menu* menu, const fs::FsPath& path, const FsEntry& entry, ViewSide side);
|
||||||
|
~FsView();
|
||||||
|
|
||||||
auto GetShortTitle() const -> const char* override { return "Files"; };
|
|
||||||
void Update(Controller* controller, TouchInfo* touch) override;
|
void Update(Controller* controller, TouchInfo* touch) override;
|
||||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||||
void OnFocusGained() override;
|
void OnFocusGained() override;
|
||||||
@@ -158,6 +172,16 @@ struct Menu final : MenuBase {
|
|||||||
return fs::AppendPath(root_path, file_path);
|
return fs::AppendPath(root_path, file_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto GetFs() {
|
||||||
|
return m_fs.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& GetFsEntry() const {
|
||||||
|
return m_fs_entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetSide(ViewSide side);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void SetIndex(s64 index);
|
void SetIndex(s64 index);
|
||||||
void InstallForwarder();
|
void InstallForwarder();
|
||||||
@@ -169,10 +193,6 @@ private:
|
|||||||
|
|
||||||
auto Scan(const fs::FsPath& new_path, bool is_walk_up = false) -> Result;
|
auto Scan(const fs::FsPath& new_path, bool is_walk_up = false) -> Result;
|
||||||
|
|
||||||
void LoadAssocEntriesPath(const fs::FsPath& path);
|
|
||||||
void LoadAssocEntries();
|
|
||||||
auto FindFileAssocFor() -> std::vector<FileAssocEntry>;
|
|
||||||
|
|
||||||
auto GetNewPath(const FileEntry& entry) const -> fs::FsPath {
|
auto GetNewPath(const FileEntry& entry) const -> fs::FsPath {
|
||||||
return GetNewPath(m_path, entry.name);
|
return GetNewPath(m_path, entry.name);
|
||||||
}
|
}
|
||||||
@@ -201,39 +221,6 @@ private:
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddSelectedEntries(SelectedType type) {
|
|
||||||
auto entries = GetSelectedEntries();
|
|
||||||
if (entries.empty()) {
|
|
||||||
// log_write("%s with no selected files\n", __PRETTY_FUNCTION__);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_selected_type = type;
|
|
||||||
m_selected_files = entries;
|
|
||||||
m_selected_path = m_path;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ResetSelection() {
|
|
||||||
m_selected_files.clear();
|
|
||||||
m_selected_count = 0;
|
|
||||||
m_selected_type = SelectedType::None;
|
|
||||||
m_selected_path = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
auto HasTypeInSelectedEntries(FsDirEntryType type) const -> bool {
|
|
||||||
if (!m_selected_count) {
|
|
||||||
return GetEntry().type == type;
|
|
||||||
} else {
|
|
||||||
for (auto&p : m_selected_files) {
|
|
||||||
if (p.type == type) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto GetEntry(u32 index) -> FileEntry& {
|
auto GetEntry(u32 index) -> FileEntry& {
|
||||||
return m_entries[m_entries_current[index]];
|
return m_entries[m_entries_current[index]];
|
||||||
}
|
}
|
||||||
@@ -257,13 +244,15 @@ private:
|
|||||||
void Sort();
|
void Sort();
|
||||||
void SortAndFindLastFile();
|
void SortAndFindLastFile();
|
||||||
void SetIndexFromLastFile(const LastFile& last_file);
|
void SetIndexFromLastFile(const LastFile& last_file);
|
||||||
void UpdateSubheading();
|
|
||||||
|
|
||||||
void OnDeleteCallback();
|
void OnDeleteCallback();
|
||||||
void OnPasteCallback();
|
void OnPasteCallback();
|
||||||
void OnRenameCallback();
|
void OnRenameCallback();
|
||||||
auto CheckIfUpdateFolder() -> Result;
|
auto CheckIfUpdateFolder() -> Result;
|
||||||
|
|
||||||
|
static auto get_collection(fs::Fs* fs, const fs::FsPath& path, const fs::FsPath& parent_name, FsDirCollection& out, bool inc_file, bool inc_dir, bool inc_size) -> Result;
|
||||||
|
static auto get_collections(fs::Fs* fs, const fs::FsPath& path, const fs::FsPath& parent_name, FsDirCollections& out, bool inc_size = false) -> Result;
|
||||||
|
|
||||||
auto get_collection(const fs::FsPath& path, const fs::FsPath& parent_name, FsDirCollection& out, bool inc_file, bool inc_dir, bool inc_size) -> Result;
|
auto get_collection(const fs::FsPath& path, const fs::FsPath& parent_name, FsDirCollection& out, bool inc_file, bool inc_dir, bool inc_size) -> Result;
|
||||||
auto get_collections(const fs::FsPath& path, const fs::FsPath& parent_name, FsDirCollections& out, bool inc_size = false) -> Result;
|
auto get_collections(const fs::FsPath& path, const fs::FsPath& parent_name, FsDirCollections& out, bool inc_size = false) -> Result;
|
||||||
|
|
||||||
@@ -276,9 +265,9 @@ private:
|
|||||||
void DisplayHash(hash::Type type);
|
void DisplayHash(hash::Type type);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr inline const char* INI_SECTION = "filebrowser";
|
Menu* m_menu{};
|
||||||
|
ViewSide m_side{};
|
||||||
|
|
||||||
const std::vector<NroEntry>& m_nro_entries;
|
|
||||||
std::unique_ptr<fs::Fs> m_fs{};
|
std::unique_ptr<fs::Fs> m_fs{};
|
||||||
FsEntry m_fs_entry{};
|
FsEntry m_fs_entry{};
|
||||||
fs::FsPath m_path{};
|
fs::FsPath m_path{};
|
||||||
@@ -291,23 +280,122 @@ private:
|
|||||||
std::unique_ptr<List> m_list{};
|
std::unique_ptr<List> m_list{};
|
||||||
std::optional<fs::FsPath> m_daybreak_path{};
|
std::optional<fs::FsPath> m_daybreak_path{};
|
||||||
|
|
||||||
// search options
|
// this keeps track of the highlighted file before opening a folder
|
||||||
// show files [X]
|
// if the user presses B to go back to the previous dir
|
||||||
// show folders [X]
|
// this vector is popped, then, that entry is checked if it still exists
|
||||||
// recursive (slow) [ ]
|
// if it does, the index becomes that file.
|
||||||
|
std::vector<LastFile> m_previous_highlighted_file{};
|
||||||
|
s64 m_index{};
|
||||||
|
s64 m_selected_count{};
|
||||||
|
ScrollingText m_scroll_name{};
|
||||||
|
|
||||||
|
bool m_is_update_folder{};
|
||||||
|
};
|
||||||
|
|
||||||
|
// contains all selected files for a command, such as copy, delete, cut etc.
|
||||||
|
struct SelectedStash {
|
||||||
|
void Add(std::shared_ptr<FsView> view, SelectedType type, const std::vector<FileEntry>& files, const fs::FsPath& path) {
|
||||||
|
if (files.empty()) {
|
||||||
|
Reset();
|
||||||
|
} else {
|
||||||
|
m_view = view;
|
||||||
|
m_type = type;
|
||||||
|
m_files = files;
|
||||||
|
m_path = path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto SameFs(FsView* view) -> bool {
|
||||||
|
if (m_view && view->GetFsEntry().IsSame(m_view->GetFsEntry())) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Type() const -> SelectedType {
|
||||||
|
return m_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Empty() const -> bool {
|
||||||
|
return m_files.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Reset() {
|
||||||
|
m_view = {};
|
||||||
|
m_type = {};
|
||||||
|
m_files = {};
|
||||||
|
m_path = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// private:
|
||||||
|
std::shared_ptr<FsView> m_view{};
|
||||||
|
std::vector<FileEntry> m_files{};
|
||||||
|
fs::FsPath m_path{};
|
||||||
|
SelectedType m_type{SelectedType::None};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Menu final : MenuBase {
|
||||||
|
friend class FsView;
|
||||||
|
|
||||||
|
Menu(const std::vector<NroEntry>& nro_entries);
|
||||||
|
~Menu();
|
||||||
|
|
||||||
|
auto GetShortTitle() const -> const char* override { return "Files"; };
|
||||||
|
void Update(Controller* controller, TouchInfo* touch) override;
|
||||||
|
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||||
|
void OnFocusGained() override;
|
||||||
|
|
||||||
|
static auto GetNewPath(const fs::FsPath& root_path, const fs::FsPath& file_path) -> fs::FsPath {
|
||||||
|
return fs::AppendPath(root_path, file_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
auto IsSplitScreen() const {
|
||||||
|
return m_split_screen;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetSplitScreen(bool enable);
|
||||||
|
|
||||||
|
void RefreshViews();
|
||||||
|
|
||||||
|
void LoadAssocEntriesPath(const fs::FsPath& path);
|
||||||
|
void LoadAssocEntries();
|
||||||
|
auto FindFileAssocFor() -> std::vector<FileAssocEntry>;
|
||||||
|
|
||||||
|
void AddSelectedEntries(SelectedType type) {
|
||||||
|
auto entries = view->GetSelectedEntries();
|
||||||
|
if (entries.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_selected.Add(view, type, entries, view->m_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResetSelection() {
|
||||||
|
m_selected.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateSubheading();
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr inline const char* INI_SECTION = "filebrowser";
|
||||||
|
|
||||||
|
const std::vector<NroEntry>& m_nro_entries;
|
||||||
|
std::shared_ptr<FsView> view{};
|
||||||
|
std::shared_ptr<FsView> view_left{};
|
||||||
|
std::shared_ptr<FsView> view_right{};
|
||||||
|
|
||||||
std::vector<FileAssocEntry> m_assoc_entries{};
|
std::vector<FileAssocEntry> m_assoc_entries{};
|
||||||
std::vector<FileEntry> m_selected_files{};
|
SelectedStash m_selected{};
|
||||||
|
|
||||||
// this keeps track of the highlighted file before opening a folder
|
// this keeps track of the highlighted file before opening a folder
|
||||||
// if the user presses B to go back to the previous dir
|
// if the user presses B to go back to the previous dir
|
||||||
// this vector is popped, then, that entry is checked if it still exists
|
// this vector is popped, then, that entry is checked if it still exists
|
||||||
// if it does, the index becomes that file.
|
// if it does, the index becomes that file.
|
||||||
std::vector<LastFile> m_previous_highlighted_file{};
|
std::vector<LastFile> m_previous_highlighted_file{};
|
||||||
fs::FsPath m_selected_path{};
|
|
||||||
s64 m_index{};
|
s64 m_index{};
|
||||||
s64 m_selected_count{};
|
s64 m_selected_count{};
|
||||||
SelectedType m_selected_type{SelectedType::None};
|
|
||||||
|
|
||||||
option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_Alphabetical};
|
option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_Alphabetical};
|
||||||
option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Descending};
|
option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Descending};
|
||||||
@@ -318,6 +406,7 @@ private:
|
|||||||
|
|
||||||
bool m_loaded_assoc_entries{};
|
bool m_loaded_assoc_entries{};
|
||||||
bool m_is_update_folder{};
|
bool m_is_update_folder{};
|
||||||
|
bool m_split_screen{};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace sphaira::ui::menu::filebrowser
|
} // namespace sphaira::ui::menu::filebrowser
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "ui/widget.hpp"
|
#include "ui/widget.hpp"
|
||||||
|
#include "ui/scrolling_text.hpp"
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace sphaira::ui::menu {
|
namespace sphaira::ui::menu {
|
||||||
@@ -33,6 +34,9 @@ private:
|
|||||||
std::string m_title_sub_heading{};
|
std::string m_title_sub_heading{};
|
||||||
std::string m_sub_heading{};
|
std::string m_sub_heading{};
|
||||||
|
|
||||||
|
ScrollingText m_scroll_title_sub_heading{};
|
||||||
|
ScrollingText m_scroll_sub_heading{};
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
struct tm m_tm{};
|
struct tm m_tm{};
|
||||||
TimeStamp m_poll_timestamp{};
|
TimeStamp m_poll_timestamp{};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "widget.hpp"
|
#include "ui/widget.hpp"
|
||||||
|
#include "ui/scrolling_text.hpp"
|
||||||
#include "fs.hpp"
|
#include "fs.hpp"
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <span>
|
#include <span>
|
||||||
@@ -28,6 +29,7 @@ struct ProgressBox final : Widget {
|
|||||||
auto NewTransfer(const std::string& transfer) -> ProgressBox&;
|
auto NewTransfer(const std::string& transfer) -> ProgressBox&;
|
||||||
auto UpdateTransfer(s64 offset, s64 size) -> ProgressBox&;
|
auto UpdateTransfer(s64 offset, s64 size) -> ProgressBox&;
|
||||||
// not const in order to avoid copy by using std::swap
|
// not const in order to avoid copy by using std::swap
|
||||||
|
auto SetImage(int image) -> ProgressBox&;
|
||||||
auto SetImageData(std::vector<u8>& data) -> ProgressBox&;
|
auto SetImageData(std::vector<u8>& data) -> ProgressBox&;
|
||||||
auto SetImageDataConst(std::span<const u8> data) -> ProgressBox&;
|
auto SetImageDataConst(std::span<const u8> data) -> ProgressBox&;
|
||||||
|
|
||||||
@@ -36,6 +38,7 @@ struct ProgressBox final : Widget {
|
|||||||
auto ShouldExitResult() -> Result;
|
auto ShouldExitResult() -> Result;
|
||||||
|
|
||||||
// helper functions
|
// helper functions
|
||||||
|
auto CopyFile(fs::Fs* fs_src, fs::Fs* fs_dst, const fs::FsPath& src, const fs::FsPath& dst) -> Result;
|
||||||
auto CopyFile(fs::Fs* fs, const fs::FsPath& src, const fs::FsPath& dst) -> Result;
|
auto CopyFile(fs::Fs* fs, const fs::FsPath& src, const fs::FsPath& dst) -> Result;
|
||||||
auto CopyFile(const fs::FsPath& src, const fs::FsPath& dst) -> Result;
|
auto CopyFile(const fs::FsPath& src, const fs::FsPath& dst) -> Result;
|
||||||
void Yield();
|
void Yield();
|
||||||
@@ -86,8 +89,13 @@ private:
|
|||||||
s64 m_speed{};
|
s64 m_speed{};
|
||||||
TimeStamp m_timestamp{};
|
TimeStamp m_timestamp{};
|
||||||
std::vector<u8> m_image_data{};
|
std::vector<u8> m_image_data{};
|
||||||
|
int m_image_pending{};
|
||||||
|
bool m_is_image_pending{};
|
||||||
// shared data end.
|
// shared data end.
|
||||||
|
|
||||||
|
ScrollingText m_scroll_title{};
|
||||||
|
ScrollingText m_scroll_transfer{};
|
||||||
|
|
||||||
int m_cpuid{};
|
int m_cpuid{};
|
||||||
int m_image{};
|
int m_image{};
|
||||||
bool m_own_image{};
|
bool m_own_image{};
|
||||||
|
|||||||
@@ -299,6 +299,7 @@ enum class Button : u64 {
|
|||||||
UP = static_cast<u64>(HidNpadButton_AnyUp),
|
UP = static_cast<u64>(HidNpadButton_AnyUp),
|
||||||
DOWN = static_cast<u64>(HidNpadButton_AnyDown),
|
DOWN = static_cast<u64>(HidNpadButton_AnyDown),
|
||||||
|
|
||||||
|
NONE = 0,
|
||||||
ANY_BUTTON = A | B | X | Y | L | R | L2 | R2 | L3 | R3 | START | SELECT,
|
ANY_BUTTON = A | B | X | Y | L | R | L2 | R2 | L3 | R3 | START | SELECT,
|
||||||
ANY_HORIZONTAL = LEFT | RIGHT,
|
ANY_HORIZONTAL = LEFT | RIGHT,
|
||||||
ANY_VERTICAL = UP | DOWN,
|
ANY_VERTICAL = UP | DOWN,
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ struct UsbTest final : usb::upload::Usb, yati::source::Stream {
|
|||||||
m_pull_offset = 0;
|
m_pull_offset = 0;
|
||||||
Stream::Reset();
|
Stream::Reset();
|
||||||
m_size = m_source->GetSize(path);
|
m_size = m_source->GetSize(path);
|
||||||
|
m_pbox->SetImage(m_source->GetIcon(path));
|
||||||
m_pbox->SetTitle(m_source->GetName(path));
|
m_pbox->SetTitle(m_source->GetName(path));
|
||||||
m_pbox->NewTransfer(m_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) {
|
for (auto path : paths) {
|
||||||
const auto file_size = source->GetSize(path);
|
const auto file_size = source->GetSize(path);
|
||||||
|
pbox->SetImage(source->GetIcon(path));
|
||||||
pbox->SetTitle(source->GetName(path));
|
pbox->SetTitle(source->GetName(path));
|
||||||
pbox->NewTransfer(path);
|
pbox->NewTransfer(path);
|
||||||
|
|
||||||
@@ -246,6 +248,7 @@ Result DumpToDevNull(ui::ProgressBox* pbox, BaseSource* source, std::span<const
|
|||||||
R_TRY(pbox->ShouldExitResult());
|
R_TRY(pbox->ShouldExitResult());
|
||||||
|
|
||||||
const auto file_size = source->GetSize(path);
|
const auto file_size = source->GetSize(path);
|
||||||
|
pbox->SetImage(source->GetIcon(path));
|
||||||
pbox->SetTitle(source->GetName(path));
|
pbox->SetTitle(source->GetName(path));
|
||||||
pbox->NewTransfer(path);
|
pbox->NewTransfer(path);
|
||||||
|
|
||||||
@@ -267,6 +270,7 @@ Result DumpToNetwork(ui::ProgressBox* pbox, const location::Entry& loc, BaseSour
|
|||||||
R_TRY(pbox->ShouldExitResult());
|
R_TRY(pbox->ShouldExitResult());
|
||||||
|
|
||||||
const auto file_size = source->GetSize(path);
|
const auto file_size = source->GetSize(path);
|
||||||
|
pbox->SetImage(source->GetIcon(path));
|
||||||
pbox->SetTitle(source->GetName(path));
|
pbox->SetTitle(source->GetName(path));
|
||||||
pbox->NewTransfer(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
|
} // namespace sphaira::dump
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ List::List(s64 row, s64 page, const Vec4& pos, const Vec4& v, const Vec2& pad)
|
|||||||
, m_v{v}
|
, m_v{v}
|
||||||
, m_pad{pad} {
|
, m_pad{pad} {
|
||||||
m_pos = pos;
|
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 {
|
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) {
|
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_up_button = GetPageJump() ? (m_row == 1 ? Button::DPAD_LEFT : Button::L2) : (Button::NONE);
|
||||||
const auto page_down_button = m_row == 1 ? Button::DPAD_RIGHT : Button::R2;
|
const auto page_down_button = GetPageJump() ? (m_row == 1 ? Button::DPAD_RIGHT : Button::R2) : (Button::NONE);
|
||||||
|
|
||||||
if (controller->GotDown(Button::DOWN)) {
|
if (controller->GotDown(Button::DOWN)) {
|
||||||
if (ScrollDown(index, m_row, count)) {
|
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;
|
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
|
// only draw if full x is in bounds
|
||||||
if (v.x + v.w > GetX() + GetW()) {
|
if (v.x + v.w > GetX() + GetW()) {
|
||||||
break;
|
break;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -129,6 +129,8 @@ struct NspEntry {
|
|||||||
s64 nsp_size{};
|
s64 nsp_size{};
|
||||||
// copy of ncm cs, it is not closed.
|
// copy of ncm cs, it is not closed.
|
||||||
NcmContentStorage cs{};
|
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.
|
// todo: benchmark manual sdcard read and decryption vs ncm.
|
||||||
Result Read(void* buf, s64 off, s64 size, u64* bytes_read) {
|
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 {
|
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){
|
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);
|
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 {
|
auto GetName(const std::string& path) const -> std::string {
|
||||||
const auto it = std::ranges::find_if(m_entries, [&path](auto& e){
|
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()) {
|
if (it != m_entries.end()) {
|
||||||
@@ -206,7 +208,7 @@ struct NspSource final : dump::BaseSource {
|
|||||||
|
|
||||||
auto GetSize(const std::string& path) const -> s64 {
|
auto GetSize(const std::string& path) const -> s64 {
|
||||||
const auto it = std::ranges::find_if(m_entries, [&path](auto& e){
|
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()) {
|
if (it != m_entries.end()) {
|
||||||
@@ -216,6 +218,18 @@ struct NspSource final : dump::BaseSource {
|
|||||||
return 0;
|
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:
|
private:
|
||||||
std::vector<NspEntry> m_entries{};
|
std::vector<NspEntry> m_entries{};
|
||||||
};
|
};
|
||||||
@@ -568,7 +582,7 @@ Result BuildNspEntries(Entry& e, u32 flags, std::vector<NspEntry>& out) {
|
|||||||
|
|
||||||
NspEntry nsp;
|
NspEntry nsp;
|
||||||
R_TRY(BuildNspEntry(e, info, nsp));
|
R_TRY(BuildNspEntry(e, info, nsp));
|
||||||
out.emplace_back(nsp);
|
out.emplace_back(nsp).icon = e.image;
|
||||||
}
|
}
|
||||||
|
|
||||||
R_UNLESS(!out.empty(), 0x1);
|
R_UNLESS(!out.empty(), 0x1);
|
||||||
|
|||||||
@@ -171,6 +171,7 @@ struct XciSource final : dump::BaseSource {
|
|||||||
// size of the entire xci.
|
// size of the entire xci.
|
||||||
s64 xci_size{};
|
s64 xci_size{};
|
||||||
Menu* menu{};
|
Menu* menu{};
|
||||||
|
int icon{};
|
||||||
|
|
||||||
Result Read(const std::string& path, void* buf, s64 off, s64 size, u64* bytes_read) override {
|
Result Read(const std::string& path, void* buf, s64 off, s64 size, u64* bytes_read) override {
|
||||||
if (path.ends_with(GetDumpTypeStr(DumpFileType_XCI))) {
|
if (path.ends_with(GetDumpTypeStr(DumpFileType_XCI))) {
|
||||||
@@ -218,6 +219,10 @@ struct XciSource final : dump::BaseSource {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto GetIcon(const std::string& path) const -> int override {
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static auto InRange(s64 off, s64 offset, s64 size) -> bool {
|
static auto InRange(s64 off, s64 offset, s64 size) -> bool {
|
||||||
return off < offset + size && off >= offset;
|
return off < offset + size && off >= offset;
|
||||||
@@ -944,6 +949,7 @@ Result Menu::DumpGames(u32 flags) {
|
|||||||
auto source = std::make_shared<XciSource>();
|
auto source = std::make_shared<XciSource>();
|
||||||
source->menu = this;
|
source->menu = this;
|
||||||
source->application_name = m_entries[m_entry_index].lang_entry.name;
|
source->application_name = m_entries[m_entry_index].lang_entry.name;
|
||||||
|
source->icon = m_icon;
|
||||||
|
|
||||||
std::vector<fs::FsPath> paths;
|
std::vector<fs::FsPath> paths;
|
||||||
if (flags & DumpFileFlag_XCI) {
|
if (flags & DumpFileFlag_XCI) {
|
||||||
|
|||||||
@@ -70,10 +70,13 @@ void MenuBase::Draw(NVGcontext* vg, Theme* theme) {
|
|||||||
|
|
||||||
nvgFontSize(vg, 28);
|
nvgFontSize(vg, 28);
|
||||||
gfx::textBounds(vg, 0, 0, bounds, m_title.c_str());
|
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) {
|
void MenuBase::SetTitle(std::string title) {
|
||||||
|
|||||||
@@ -87,11 +87,19 @@ auto ProgressBox::Draw(NVGcontext* vg, Theme* theme) -> void {
|
|||||||
const auto offset = m_offset;
|
const auto offset = m_offset;
|
||||||
const auto speed = m_speed;
|
const auto speed = m_speed;
|
||||||
const auto last_offset = m_last_offset;
|
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);
|
mutexUnlock(&m_mutex);
|
||||||
|
|
||||||
if (!image_data.empty()) {
|
if (!image_data.empty()) {
|
||||||
FreeImage();
|
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;
|
m_own_image = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,8 +116,8 @@ auto ProgressBox::Draw(NVGcontext* vg, Theme* theme) -> void {
|
|||||||
nvgSave(vg);
|
nvgSave(vg);
|
||||||
nvgIntersectScissor(vg, GetX(), GetY(), GetW(), GetH());
|
nvgIntersectScissor(vg, GetX(), GetY(), GetW(), GetH());
|
||||||
|
|
||||||
if (m_image) {
|
if (image) {
|
||||||
gfx::drawImage(vg, GetX() + 30, GetY() + 30, 128, 128, m_image, 5);
|
gfx::drawImage(vg, GetX() + 25, GetY() + 25, 120, 120, image, 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
// shapes.
|
// 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 + 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()) {
|
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);
|
nvgRestore(vg);
|
||||||
@@ -189,6 +209,14 @@ auto ProgressBox::UpdateTransfer(s64 offset, s64 size) -> ProgressBox& {
|
|||||||
return *this;
|
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& {
|
auto ProgressBox::SetImageData(std::vector<u8>& data) -> ProgressBox& {
|
||||||
mutexLock(&m_mutex);
|
mutexLock(&m_mutex);
|
||||||
std::swap(m_image_data, data);
|
std::swap(m_image_data, data);
|
||||||
@@ -219,24 +247,24 @@ auto ProgressBox::ShouldExitResult() -> Result {
|
|||||||
R_SUCCEED();
|
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;
|
fs::File src_file;
|
||||||
R_TRY(fs->OpenFile(src_path, FsOpenMode_Read, &src_file));
|
R_TRY(fs_src->OpenFile(src_path, FsOpenMode_Read, &src_file));
|
||||||
ON_SCOPE_EXIT(fs->FileClose(&src_file));
|
ON_SCOPE_EXIT(fs_src->FileClose(&src_file));
|
||||||
|
|
||||||
s64 src_size;
|
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.
|
// this can fail if it already exists so we ignore the result.
|
||||||
// if the file actually failed to be created, the result is implicitly
|
// if the file actually failed to be created, the result is implicitly
|
||||||
// handled when we try and open it for writing.
|
// 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;
|
fs::File dst_file;
|
||||||
R_TRY(fs->OpenFile(dst_path, FsOpenMode_Write, &dst_file));
|
R_TRY(fs_dst->OpenFile(dst_path, FsOpenMode_Write, &dst_file));
|
||||||
ON_SCOPE_EXIT(fs->FileClose(&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{};
|
s64 offset{};
|
||||||
std::vector<u8> buf(1024*1024*4); // 4MiB
|
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());
|
R_TRY(ShouldExitResult());
|
||||||
|
|
||||||
u64 bytes_read;
|
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();
|
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();
|
Yield();
|
||||||
|
|
||||||
UpdateTransfer(offset, src_size);
|
UpdateTransfer(offset, src_size);
|
||||||
@@ -258,6 +286,10 @@ auto ProgressBox::CopyFile(fs::Fs* fs, const fs::FsPath& src_path, const fs::FsP
|
|||||||
R_SUCCEED();
|
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 {
|
auto ProgressBox::CopyFile(const fs::FsPath& src_path, const fs::FsPath& dst_path) -> Result {
|
||||||
fs::FsNativeSd fs;
|
fs::FsNativeSd fs;
|
||||||
R_TRY(fs.GetFsOpenResult());
|
R_TRY(fs.GetFsOpenResult());
|
||||||
|
|||||||
Reference in New Issue
Block a user