diff options
| author | Hannes Schulze <haschu0103@outlook.de> | 2025-02-02 22:49:01 +0100 |
|---|---|---|
| committer | Hannes Schulze <haschu0103@outlook.de> | 2025-02-02 22:49:01 +0100 |
| commit | 0f22ed2ef160eef2da6080782943b22c9539650d (patch) | |
| tree | 1b43aada2d0183273e79b4d1db7b1e60dd809855 /lib/nes | |
| parent | 4fe67b36e3bbe6ea5505ed7233c245be9374f0ce (diff) | |
wip: hacky file browser
Diffstat (limited to 'lib/nes')
| -rw-r--r-- | lib/nes/app/CMakeLists.txt | 3 | ||||
| -rw-r--r-- | lib/nes/app/application.cc | 47 | ||||
| -rw-r--r-- | lib/nes/app/application.hh | 3 | ||||
| -rw-r--r-- | lib/nes/app/file-browser.hh | 58 | ||||
| -rw-r--r-- | lib/nes/app/graphics/renderer.hh | 7 | ||||
| -rw-r--r-- | lib/nes/app/ui/action.hh | 43 | ||||
| -rw-r--r-- | lib/nes/app/ui/screen-browser.cc | 79 | ||||
| -rw-r--r-- | lib/nes/app/ui/screen-browser.hh | 26 | ||||
| -rw-r--r-- | lib/nes/app/ui/screen-title.cc | 8 | ||||
| -rw-r--r-- | lib/nes/common/path-view.hh | 72 | ||||
| -rw-r--r-- | lib/nes/sys/cartridge.hh | 2 |
11 files changed, 287 insertions, 61 deletions
diff --git a/lib/nes/app/CMakeLists.txt b/lib/nes/app/CMakeLists.txt index 76ccb74..6e5aa93 100644 --- a/lib/nes/app/CMakeLists.txt +++ b/lib/nes/app/CMakeLists.txt @@ -2,7 +2,8 @@ target_sources( nes PRIVATE application.hh - application.cc) + application.cc + file-browser.hh) add_subdirectory(input) add_subdirectory(graphics) diff --git a/lib/nes/app/application.cc b/lib/nes/app/application.cc index 5f73e01..5471984 100644 --- a/lib/nes/app/application.cc +++ b/lib/nes/app/application.cc @@ -4,16 +4,10 @@ #include "nes/app/ui/screen.hh" #include "nes/app/graphics/renderer.hh" #include "ui/action.hh" -#include <fstream> #include <iostream> namespace nes::app { - namespace - { - constexpr auto file = std::string_view{ "/Users/hannes/Documents/temp/supermario.nes" }; - } // namespace - auto application::display_proxy::switch_buffers() -> void { if (screen) @@ -25,11 +19,12 @@ namespace nes::app base.switch_buffers(); } - application::application(display& display, input_device_keyboard& keyboard) + application::application(display& display, input_device_keyboard& keyboard, file_browser& file_browser) : display_{ display } , input_manager_{ keyboard } + , file_browser_{ file_browser } , screen_title_{ keyboard } - , screen_browser_{ keyboard } + , screen_browser_{ keyboard, file_browser } { display_.screen = &screen_title_; } @@ -67,40 +62,40 @@ namespace nes::app auto application::handle_action(action const a) -> void { - switch (a) + switch (a.get_type()) { - case action::none: + case action::type::none: { break; } - case action::go_to_title: + case action::type::go_to_title: { console_.clear(); display_.screen = &screen_title_; break; } - case action::go_to_browser: + case action::type::go_to_browser: { console_.clear(); display_.screen = &screen_browser_; break; } - case action::launch_game: + case action::type::go_to_settings: { - auto stream = std::ifstream{ file, std::ios::binary }; - if (!stream.good()) - { - std::cerr << "Unable to open cartridge file!" << std::endl; - std::abort(); - } - - stream.seekg(0, std::ios::end); - auto const length = stream.tellg(); - stream.seekg(0, std::ios::beg); - auto data = std::vector<u8>(static_cast<u32>(length)); - stream.read(reinterpret_cast<char*>(data.data()), length); + // TODO + break; + } + case action::type::go_to_help: + { + // TODO + break; + } + case action::type::launch_game: + { + nes::u8 buffer[sys::cartridge::max_file_size]; + auto const length = file_browser_.load(a.get_file_name(), buffer, sizeof(buffer)); - console_.emplace(display_, data.data(), static_cast<u32>(length)); + console_.emplace(display_, buffer, length); if (console_->get_status() != sys::status::success) { std::cerr << "Unable to load cartridge: " << to_string(console_->get_status()) << std::endl; diff --git a/lib/nes/app/application.hh b/lib/nes/app/application.hh index 44b99bb..724f2b6 100644 --- a/lib/nes/app/application.hh +++ b/lib/nes/app/application.hh @@ -41,12 +41,13 @@ namespace nes::app display_proxy display_; input_manager input_manager_; + file_browser& file_browser_; box<sys::nes> console_{}; screen_title screen_title_; screen_browser screen_browser_; public: - explicit application(display&, input_device_keyboard&); + explicit application(display&, input_device_keyboard&, file_browser& file_browser); application(application const&) = delete; application(application&&) = delete; diff --git a/lib/nes/app/file-browser.hh b/lib/nes/app/file-browser.hh new file mode 100644 index 0000000..b766f74 --- /dev/null +++ b/lib/nes/app/file-browser.hh @@ -0,0 +1,58 @@ +#pragma once + +#include "nes/common/types.hh" + +namespace nes +{ + class path_view; +} // namespace nes; + +namespace nes::app +{ + class file_browser + { + public: + static constexpr auto max_name_length = u32{ 64 }; + + enum class item_type + { + directory, + file, + }; + + class item + { + item_type type_{ item_type::directory }; + char name_[max_name_length + 1]{}; + + public: + item() = default; + + explicit item(item_type const type, std::string_view const name) + : type_{ type } + { + std::copy_n(name.begin(), std::min(static_cast<u32>(name.length()), max_name_length), name_); + } + + auto get_type() const -> item_type { return type_; } + auto get_name() const -> std::string_view { return name_; } + }; + + virtual ~file_browser() = default; + + file_browser(file_browser const&) = delete; + file_browser(file_browser&&) = delete; + auto operator=(file_browser const&) -> file_browser& = delete; + auto operator=(file_browser&&) -> file_browser& = delete; + + virtual auto get_path() const -> path_view = 0; + virtual auto read_item_count() -> u32 = 0; + virtual auto read_items(u32 offset, item* buffer, u32 buffer_size) -> u32 = 0; + virtual auto navigate_up() -> void = 0; + virtual auto navigate(std::string_view) -> void = 0; + virtual auto load(std::string_view, u8* buffer, u32 buffer_size) -> u32 = 0; + + protected: + explicit file_browser() = default; + }; +} // namespace nes::app
\ No newline at end of file diff --git a/lib/nes/app/graphics/renderer.hh b/lib/nes/app/graphics/renderer.hh index cc0b9af..5b9ec48 100644 --- a/lib/nes/app/graphics/renderer.hh +++ b/lib/nes/app/graphics/renderer.hh @@ -54,18 +54,19 @@ namespace nes::app template<typename... Args> auto render_text_format( i32 const x, i32 const y, color const c, text_attributes const attrs, std::string_view const fmt, - Args... args) -> void + Args... args) -> u32 { char buffer[width]; auto const str = string_builder{ buffer, sizeof(buffer) }.append_format(fmt, args...).get_result(); render_text(x, y, str, c, attrs); + return static_cast<u32>(str.length()); } template<typename... Args> auto render_text_format( - i32 const x, i32 const y, color const c, std::string_view const fmt, Args... args) -> void + i32 const x, i32 const y, color const c, std::string_view const fmt, Args... args) -> u32 { - render_text_format(x, y, c, text_attributes{}, fmt, args...); + return render_text_format(x, y, c, text_attributes{}, fmt, args...); } /// Render a rectangle at the given tile coordinates with a given size (measured in tiles). diff --git a/lib/nes/app/ui/action.hh b/lib/nes/app/ui/action.hh index 52e5ba6..b701606 100644 --- a/lib/nes/app/ui/action.hh +++ b/lib/nes/app/ui/action.hh @@ -1,15 +1,44 @@ #pragma once +#include <string_view> + namespace nes::app { /// An action for the application to process after a scene has handled its events. - enum class action + class action { - none, - go_to_title, - go_to_browser, - go_to_settings, - go_to_help, - launch_game, + public: + enum class type + { + none, + go_to_title, + go_to_browser, + go_to_settings, + go_to_help, + launch_game, + }; + + private: + type type_{ type::none }; + std::string_view file_name_; + + public: + explicit action() = default; + + static auto go_to_title() -> action { return action{ type::go_to_title, "" }; } + static auto go_to_browser() -> action { return action{ type::go_to_browser, "" }; } + static auto go_to_settings() -> action { return action{ type::go_to_settings, "" }; } + static auto go_to_help() -> action { return action{ type::go_to_help, "" }; } + static auto launch_game(std::string_view const file_name) -> action { return action{ type::launch_game, file_name }; } + + auto get_type() const -> type { return type_; } + auto get_file_name() const -> std::string_view { return file_name_; } + + private: + explicit action(type const type, std::string_view const file_name) + : type_{ type } + , file_name_{ file_name } + { + } }; } // namespace nes::app
\ No newline at end of file diff --git a/lib/nes/app/ui/screen-browser.cc b/lib/nes/app/ui/screen-browser.cc index da2d82d..daca18e 100644 --- a/lib/nes/app/ui/screen-browser.cc +++ b/lib/nes/app/ui/screen-browser.cc @@ -1,30 +1,53 @@ #include "nes/app/ui/screen-browser.hh" #include "nes/app/graphics/renderer.hh" #include "nes/app/graphics/color.hh" +#include "nes/app/graphics/tiles/icons.hh" #include "nes/app/input/input-device-keyboard.hh" #include "nes/common/string-builder.hh" +#include "nes/common/path-view.hh" namespace nes::app { auto screen_browser::selection_impl::render_item( - renderer& renderer, i32 const& item, i32 const x, i32 const y, u32 const width, color const color) const -> void + renderer& renderer, item const& item, i32 const x, i32 const y, u32 const width, + color const color) const -> void { - renderer.render_text_format(x, y, color, text_attributes{}.set_max_width(width), "Item {}", item); + switch (item.data.get_type()) + { + case file_browser::item_type::file: renderer.render_mask_tile(x, y, tiles::icon_cartridge, color); break; + case file_browser::item_type::directory: renderer.render_mask_tile(x, y, tiles::icon_directory, color); break; + } + + renderer.render_text(x + 2, y, item.data.get_name(), color, text_attributes{} + .set_ellipsize_mode(ellipsize_mode::end) + .set_max_width(width - 2)); } - auto screen_browser::selection_impl::load_page(int (&items)[5], u32 const page) -> u32 + auto screen_browser::selection_impl::load_page(item (&items)[8], u32 const page) -> u32 { - for (auto i = u32{ 0 }; i < 5; ++i) + auto offset = u32{ 0 }; + if (page == 0 && file_browser_.get_path().get_component_count() > 0) + { + items[0].is_parent = true; + items[0].data = file_browser::item{ file_browser::item_type::directory, "[Parent Directory]" }; + offset = 1; + } + file_browser::item buffer[8]; + auto const count = file_browser_.read_items(page * 8, buffer, 8 - offset); + for (auto i = u32{ 0 }; i < count; ++i) { - items[i] = static_cast<i32>(page * 5 + i); + items[i + offset].is_parent = false; + items[i + offset].data = buffer[i]; } - return 5; + return count + offset; } - screen_browser::screen_browser(input_device_keyboard& keyboard) + screen_browser::screen_browser(input_device_keyboard& keyboard, file_browser& file_browser) : keyboard_{ keyboard } + , file_browser_{ file_browser } + , selection_{ file_browser } { - selection_.set_page_count(2); + selection_.set_page_count(std::max(u32{ 1 }, (file_browser_.read_item_count() + 7) / 8)); } auto screen_browser::render(renderer& renderer) -> void @@ -32,11 +55,21 @@ namespace nes::app renderer.render_fill(color::background_primary); renderer.render_rect(0, 0, 32, 3, color::background_secondary); - renderer.render_text(2, 1, "Load Game", color::foreground_primary); - renderer.render_text_format( + auto const page_length = renderer.render_text_format( 30, 1, color::foreground_secondary, text_attributes{}.set_alignment(text_alignment::right), "Page {}/{}", selection_.get_current_page() + 1, selection_.get_page_count()); + char path_buffer[32]; + auto path_builder = string_builder{ path_buffer, sizeof(path_buffer) }; + for (auto const component : file_browser_.get_path()) + { + path_builder.append_format("> {} ", component); + } + path_builder.append_string(">"); + renderer.render_text( + 2, 1, path_builder.get_result(), color::foreground_primary, + text_attributes{}.set_max_width(26 - page_length).set_ellipsize_mode(ellipsize_mode::end)); + selection_.render(renderer); } @@ -58,14 +91,34 @@ namespace nes::app } else if (event == input_event::key_down(key::backspace)) { - return action::go_to_title; + return action::go_to_title(); } else if (event == input_event::key_down(key::enter)) { - return action::launch_game; + auto selected = selection_.get_selected(); + if (selected) + { + if (selected->is_parent) + { + file_browser_.navigate_up(); + selection_.set_page_count(std::max(u32{ 1 }, (file_browser_.read_item_count() + 7) / 8)); + } + else + { + switch (selected->data.get_type()) + { + case file_browser::item_type::directory: + file_browser_.navigate(selected->data.get_name()); + selection_.set_page_count(std::max(u32{ 1 }, (file_browser_.read_item_count() + 7) / 8)); + break; + case file_browser::item_type::file: + return action::launch_game(selected->data.get_name()); + } + } + } } } - return action::none; + return action{}; } } // namespace nes::app
\ No newline at end of file diff --git a/lib/nes/app/ui/screen-browser.hh b/lib/nes/app/ui/screen-browser.hh index eb66266..7a711c5 100644 --- a/lib/nes/app/ui/screen-browser.hh +++ b/lib/nes/app/ui/screen-browser.hh @@ -2,6 +2,7 @@ #include "nes/app/ui/screen.hh" #include "nes/app/ui/selection.hh" +#include "nes/app/file-browser.hh" namespace nes::app { @@ -10,21 +11,34 @@ namespace nes::app /// An interface which allows the user to browse the filesystem for a game. class screen_browser final : public screen { - class selection_impl final : public selection<i32, 5> + struct item { + bool is_parent{ false }; + file_browser::item data{ file_browser::item_type::directory, "" }; + }; + + class selection_impl final : public selection<item, 8> + { + file_browser& file_browser_; + public: - using selection::selection; + explicit selection_impl(file_browser& file_browser) + : selection{ 2, 5, 28 } + , file_browser_{ file_browser } + { + } protected: - auto render_item(renderer&, i32 const&, i32 x, i32 y, u32 width, color) const -> void override; - auto load_page(i32(&)[5], u32 page) -> u32 override; + auto render_item(renderer&, item const&, i32 x, i32 y, u32 width, color) const -> void override; + auto load_page(item(&)[8], u32 page) -> u32 override; }; input_device_keyboard& keyboard_; - selection_impl selection_{ 2, 5, 28 }; + file_browser& file_browser_; + selection_impl selection_; public: - explicit screen_browser(input_device_keyboard&); + explicit screen_browser(input_device_keyboard&, file_browser&); auto render(renderer&) -> void override; auto process_events() -> action override; diff --git a/lib/nes/app/ui/screen-title.cc b/lib/nes/app/ui/screen-title.cc index 32a6ea6..15cd601 100644 --- a/lib/nes/app/ui/screen-title.cc +++ b/lib/nes/app/ui/screen-title.cc @@ -61,13 +61,13 @@ namespace nes::app { switch (*selection_.get_selected()) { - case item::load_game: return action::go_to_browser; - case item::settings: return action::go_to_settings; - case item::help: return action::go_to_help; + case item::load_game: return action::go_to_browser(); + case item::settings: return action::go_to_settings(); + case item::help: return action::go_to_help(); } } } - return action::none; + return action{}; } } // namespace nes::app
\ No newline at end of file diff --git a/lib/nes/common/path-view.hh b/lib/nes/common/path-view.hh new file mode 100644 index 0000000..3d670bf --- /dev/null +++ b/lib/nes/common/path-view.hh @@ -0,0 +1,72 @@ +#pragma once + +#include "nes/common/types.hh" +#include <string_view> + +namespace nes +{ + /// Non-owning copy of a path, consisting of multiple path components stored as string views. + /// + /// This is similar in spirit to a string_view, but for collections of string_views instead of characters. + class path_view + { + std::string_view const* components_{ nullptr }; + u32 component_count_{ 0 }; + + public: + /// An iterator for the path components. + class iterator + { + friend path_view; + + std::string_view const* current_{ nullptr }; + u32 remaining_{ 0 }; + + public: + auto operator==(iterator const& other) -> bool { return current_ == other.current_; } + auto operator!=(iterator const& other) -> bool { return current_ != other.current_; } + + auto operator*() const -> std::string_view { return current_ ? *current_ : ""; } + + auto operator++() -> iterator& + { + if (remaining_ > 0) + { + current_ += 1; + remaining_ -= 1; + } + return *this; + } + + auto operator++(int) -> iterator + { + auto res = *this; + ++*this; + return res; + } + + private: + explicit iterator() = default; + explicit iterator(std::string_view const* current, u32 const remaining) + : current_{ current } + , remaining_{ remaining } + { + } + }; + + explicit path_view() = default; + + explicit path_view(std::string_view const* components, u32 const component_count) + : components_{ components } + , component_count_{ component_count } + { + } + + auto get_components() const -> std::string_view const* { return components_; } + auto get_component_count() const -> u32 { return component_count_; } + auto get_component(u32 const index) const -> std::string_view { return components_[index]; } + + auto begin() const -> iterator { return iterator{ components_, component_count_ }; } + auto end() const -> iterator { return iterator{ components_ + component_count_, 0 }; } + }; +} // namespace nes::app
\ No newline at end of file diff --git a/lib/nes/sys/cartridge.hh b/lib/nes/sys/cartridge.hh index 2cfb114..1934b8d 100644 --- a/lib/nes/sys/cartridge.hh +++ b/lib/nes/sys/cartridge.hh @@ -62,6 +62,8 @@ namespace nes::sys mapper* mapper_{ &mapper::invalid() }; public: + static constexpr auto max_file_size = u32{ max_prg_rom_size + max_chr_rom_size + 512 + header_length }; + explicit cartridge(u8 const* data, u32 length); auto get_status() const -> status { return status_; } |
