diff options
| author | Hannes Schulze <haschu0103@outlook.de> | 2025-02-04 19:25:03 +0100 |
|---|---|---|
| committer | Hannes Schulze <haschu0103@outlook.de> | 2025-02-04 19:25:03 +0100 |
| commit | e791ac9a46095e0241181da89c35a7c702982c21 (patch) | |
| tree | d959c79c24ae03282f1b00ec9bcb2ccf86d9c96c | |
| parent | 0f22ed2ef160eef2da6080782943b22c9539650d (diff) | |
clean up path logic a bit
| -rw-r--r-- | app/mac/scene.mm | 161 | ||||
| -rw-r--r-- | lib/nes/app/file-browser.hh | 4 | ||||
| -rw-r--r-- | lib/nes/app/graphics/renderer.hh | 8 | ||||
| -rw-r--r-- | lib/nes/app/ui/screen-browser.cc | 82 | ||||
| -rw-r--r-- | lib/nes/app/ui/screen-browser.hh | 10 | ||||
| -rw-r--r-- | lib/nes/common/CMakeLists.txt | 6 | ||||
| -rw-r--r-- | lib/nes/common/path-builder.cc | 47 | ||||
| -rw-r--r-- | lib/nes/common/path-builder.hh | 38 | ||||
| -rw-r--r-- | lib/nes/common/path-view.cc | 49 | ||||
| -rw-r--r-- | lib/nes/common/path-view.hh | 52 | ||||
| -rw-r--r-- | lib/nes/common/string-builder.hh | 13 |
11 files changed, 292 insertions, 178 deletions
diff --git a/app/mac/scene.mm b/app/mac/scene.mm index f8e3bb5..133fc18 100644 --- a/app/mac/scene.mm +++ b/app/mac/scene.mm @@ -6,6 +6,7 @@ #import "nes/app/file-browser.hh" #import "nes/common/display.hh" #import "nes/common/path-view.hh" +#import "nes/common/path-builder.hh" #import <memory> #import <iostream> #import <optional> @@ -358,26 +359,14 @@ namespace class file_browser_impl final : public nes::app::file_browser { - std::vector<std::string> components_; - std::vector<std::string_view> component_views_; + nes::path_buffer<256> path_; DIR* it_{ nullptr }; nes::u32 it_pos_{ 0 }; public: explicit file_browser_impl() { - if (chdir("/") == -1) - { - perror("chdir"); - std::abort(); - } - - it_ = opendir("."); - if (!it_) - { - perror("opendir"); - std::abort(); - } + reopen_dir(); } ~file_browser_impl() override @@ -387,73 +376,77 @@ namespace auto get_path() const -> nes::path_view override { - return nes::path_view{ component_views_.data(), static_cast<nes::u32>(component_views_.size()) }; - } - - auto read_item_count() -> nes::u32 override - { - while (read_next()) - ; - return it_pos_; + return path_.get_components(); } - auto read_items(nes::u32 offset, item* buffer, nes::u32 buffer_size) -> nes::u32 override + auto seek(nes::u32 const position) -> void override { - seek(offset); - auto count = nes::u32{ 0 }; - for (auto i = nes::u32{ 0 }; i < buffer_size; ++i) + if (it_pos_ > position) { - auto item = read_next(); - if (!item) { break; } - buffer[count++] = *item; + reopen_dir(); } - return count; + while (it_pos_ < position) + { + if (!read_next(nullptr)) { break; } + } } - auto navigate_up() -> void override + auto read_next(item* out_item) -> bool override { - if (components_.empty()) { return; } + if (!it_) { return false; } - components_.erase(components_.begin() + static_cast<std::ptrdiff_t>(components_.size() - 1)); - component_views_.erase(component_views_.begin() + static_cast<std::ptrdiff_t>(component_views_.size() - 1)); - auto path = std::string{}; - for (auto const component : get_path()) + while (true) { - path += "/"; - path += component; - } - path += "/"; + errno = 0; + auto entry = readdir(it_); + if (!entry) + { + if (errno) + { + perror("readdir"); + std::abort(); + } - if (chdir(path.c_str()) == -1) - { - perror("chdir"); - std::abort(); + return false; + } + + auto const name = std::string_view{ entry->d_name }; + if (name.length() > max_name_length) { continue; } + if (name[0] == '.') { continue; } + + auto type = item_type::directory; + if (entry->d_type == DT_DIR) { type = item_type::directory; } + else if (entry->d_type == DT_REG) { type = item_type::file; } + else { continue; } + + it_pos_ += 1; + if (out_item) { *out_item = item{ type, name }; } + return true; } + } + + auto navigate_up() -> void override + { + if (!path_.pop()) { return; } - reopen_dir(); + reopen_dir(); } auto navigate(std::string_view const item) -> void override { - auto item_copy = std::string{ item }; - if (chdir(item_copy.c_str()) == -1) - { - perror("chdir"); - std::abort(); - } + if (!path_.push(item)) { std::abort(); } - components_.emplace_back(std::move(item_copy)); - component_views_.clear(); - for (auto& str : components_) { component_views_.emplace_back(str); } - - reopen_dir(); + reopen_dir(); } auto load(std::string_view const item, nes::u8* buffer, nes::u32 const buffer_size) -> nes::u32 override { - auto item_copy = std::string{ item }; - auto const fd = open(item_copy.c_str(), O_RDONLY | O_NOCTTY | O_NDELAY | O_NONBLOCK); + auto file_path = path_; + if (!file_path.push(item)) { std::abort(); } + + auto const path = std::string{ file_path.get_path() }; + auto const fd = open(path.c_str(), O_RDONLY); if (fd == -1) { perror("open"); @@ -482,8 +475,10 @@ namespace private: auto reopen_dir() -> void { - closedir(it_); - it_ = opendir("."); + if (it_) { closedir(it_); } + + auto const path = std::string{ path_.get_path() }; + it_ = opendir(path.c_str()); if (!it_) { perror("opendir"); @@ -491,52 +486,6 @@ namespace } it_pos_ = 0; } - - auto seek(nes::u32 const position) -> void - { - if (it_pos_ > position) - { - reopen_dir(); - } - - while (it_pos_ < position) - { - if (!read_next()) { break; } - } - } - - auto read_next() -> std::optional<item> - { - if (!it_) { return std::nullopt; } - - while (true) - { - errno = 0; - auto entry = readdir(it_); - if (!entry) - { - if (errno) - { - perror("readdir"); - std::abort(); - } - - return std::nullopt; - } - - auto const name = std::string_view{ entry->d_name }; - if (name.length() > max_name_length) { continue; } - if (name[0] == '.') { continue; } - - auto type = item_type::directory; - if (entry->d_type == DT_DIR) { type = item_type::directory; } - else if (entry->d_type == DT_REG) { type = item_type::file; } - else { continue; } - - it_pos_ += 1; - return item{ type, name }; - } - } }; } // namespace diff --git a/lib/nes/app/file-browser.hh b/lib/nes/app/file-browser.hh index b766f74..67be794 100644 --- a/lib/nes/app/file-browser.hh +++ b/lib/nes/app/file-browser.hh @@ -46,8 +46,8 @@ namespace nes::app 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 seek(u32) -> void = 0; + virtual auto read_next(item* out_item) -> bool = 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; diff --git a/lib/nes/app/graphics/renderer.hh b/lib/nes/app/graphics/renderer.hh index 5b9ec48..cfc1889 100644 --- a/lib/nes/app/graphics/renderer.hh +++ b/lib/nes/app/graphics/renderer.hh @@ -56,10 +56,10 @@ namespace nes::app i32 const x, i32 const y, color const c, text_attributes const attrs, std::string_view const fmt, 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()); + string_buffer<width> buffer{}; + buffer.append_format(fmt, args...); + render_text(x, y, buffer.get_result(), c, attrs); + return static_cast<u32>(buffer.get_result().length()); } template<typename... Args> diff --git a/lib/nes/app/ui/screen-browser.cc b/lib/nes/app/ui/screen-browser.cc index daca18e..6a18d3c 100644 --- a/lib/nes/app/ui/screen-browser.cc +++ b/lib/nes/app/ui/screen-browser.cc @@ -3,7 +3,6 @@ #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 @@ -23,23 +22,37 @@ namespace nes::app .set_max_width(width - 2)); } - auto screen_browser::selection_impl::load_page(item (&items)[8], u32 const page) -> u32 + auto screen_browser::selection_impl::load_page(item (&items)[page_size], u32 const page) -> u32 { - auto offset = u32{ 0 }; - if (page == 0 && file_browser_.get_path().get_component_count() > 0) + auto count = u32{ 0 }; + auto position = page * page_size; + if (!file_browser_.get_path().is_empty()) { - items[0].is_parent = true; - items[0].data = file_browser::item{ file_browser::item_type::directory, "[Parent Directory]" }; - offset = 1; + if (page == 0) + { + items[count].is_parent = true; + items[count].data = file_browser::item{ file_browser::item_type::directory, "[Parent Directory]" }; + count += 1; + } + else + { + // First page has been missing an item, offset by one. + position -= 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) + + file_browser_.seek(position); + while (count < page_size) { - items[i + offset].is_parent = false; - items[i + offset].data = buffer[i]; + auto item = file_browser::item{}; + if (!file_browser_.read_next(&item)) { break; } + + items[count].is_parent = false; + items[count].data = item; + count += 1; } - return count + offset; + + return count; } screen_browser::screen_browser(input_device_keyboard& keyboard, file_browser& file_browser) @@ -47,7 +60,7 @@ namespace nes::app , file_browser_{ file_browser } , selection_{ file_browser } { - selection_.set_page_count(std::max(u32{ 1 }, (file_browser_.read_item_count() + 7) / 8)); + refresh(); } auto screen_browser::render(renderer& renderer) -> void @@ -59,16 +72,9 @@ namespace nes::app 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)); + 2, 1, file_browser_.get_path().get_path(), color::foreground_primary, + text_attributes{}.set_max_width(26 - page_length).set_ellipsize_mode(ellipsize_mode::middle)); selection_.render(renderer); } @@ -91,17 +97,27 @@ namespace nes::app } else if (event == input_event::key_down(key::backspace)) { - return action::go_to_title(); + if (navigation_steps_ > 0) + { + file_browser_.navigate_up(); + refresh(); + navigation_steps_ -= 1; + } + else + { + return action::go_to_title(); + } } else if (event == input_event::key_down(key::enter)) { - auto selected = selection_.get_selected(); + auto const 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)); + refresh(); + if (navigation_steps_ > 0) { navigation_steps_ -= 1; } } else { @@ -109,7 +125,8 @@ namespace nes::app { 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)); + refresh(); + navigation_steps_ += 1; break; case file_browser::item_type::file: return action::launch_game(selected->data.get_name()); @@ -121,4 +138,15 @@ namespace nes::app return action{}; } + + auto screen_browser::refresh() -> void + { + file_browser_.seek(0); + auto count = u32{ 0 }; + while (file_browser_.read_next(nullptr)) { count += 1; } + // Round up to get page count. + auto const page_count = (count + (page_size - 1)) / page_size; + // Always show at least one page. + selection_.set_page_count(std::max(u32{ 1 }, page_count)); + } } // 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 7a711c5..3870f02 100644 --- a/lib/nes/app/ui/screen-browser.hh +++ b/lib/nes/app/ui/screen-browser.hh @@ -11,13 +11,15 @@ namespace nes::app /// An interface which allows the user to browse the filesystem for a game. class screen_browser final : public screen { + static constexpr auto page_size = u32{ 8 }; + struct item { bool is_parent{ false }; file_browser::item data{ file_browser::item_type::directory, "" }; }; - class selection_impl final : public selection<item, 8> + class selection_impl final : public selection<item, page_size> { file_browser& file_browser_; @@ -30,17 +32,21 @@ namespace nes::app protected: 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; + auto load_page(item(&)[page_size], u32 page) -> u32 override; }; input_device_keyboard& keyboard_; file_browser& file_browser_; selection_impl selection_; + u32 navigation_steps_{ 0 }; public: explicit screen_browser(input_device_keyboard&, file_browser&); auto render(renderer&) -> void override; auto process_events() -> action override; + + private: + auto refresh() -> void; }; } // namespace nes::app
\ No newline at end of file diff --git a/lib/nes/common/CMakeLists.txt b/lib/nes/common/CMakeLists.txt index 11143d7..f0ee288 100644 --- a/lib/nes/common/CMakeLists.txt +++ b/lib/nes/common/CMakeLists.txt @@ -8,4 +8,8 @@ target_sources( box.hh display.hh string-builder.hh - string-builder.cc)
\ No newline at end of file + string-builder.cc + path-view.hh + path-view.cc + path-builder.hh + path-builder.cc)
\ No newline at end of file diff --git a/lib/nes/common/path-builder.cc b/lib/nes/common/path-builder.cc new file mode 100644 index 0000000..67190dc --- /dev/null +++ b/lib/nes/common/path-builder.cc @@ -0,0 +1,47 @@ +#include "nes/common/path-builder.hh" + +namespace nes +{ + path_builder::path_builder(char* buffer, u32 const buffer_length) + : buffer_{ buffer } + , buffer_length_{ buffer_length } + { + // Initialize the path to "/" if possible. + if (buffer_length > 0) { buffer_[actual_length_++] = '/'; } + } + + auto path_builder::push(std::string_view component) -> bool + { + // Trim leading and trailing slashes + while (!component.empty() && component[0] == '/') { component = component.substr(1); } + while (!component.empty() && component[component.length() - 1] == '/') { component = component.substr(0, component.length() - 1); } + if (component.empty() || std::find(component.begin(), component.end(), '/') != component.end()) { return false; } + + // Check if there is enough remaining capacity for "/<component>" + auto const add_leading_slash = actual_length_ == 0 || buffer_[actual_length_ - 1] != '/'; + auto const length = component.length() + (add_leading_slash ? 1 : 0); + if (actual_length_ + length > buffer_length_) { return false; } + + // Append "/<component>" + if (add_leading_slash) { buffer_[actual_length_++] = '/'; } + for (auto i = u32{ 0 }; i < component.length(); ++i) { buffer_[actual_length_++] = component[i]; } + + return true; + } + + auto path_builder::pop() -> bool + { + if (actual_length_ == 0 || buffer_[actual_length_ - 1] == '/') { return false; } + + // Find the start of the last component. + auto last_start = actual_length_ - 1; + while (last_start > 0 && buffer_[last_start] != '/') { last_start -= 1; } + + // Drop the slash if we are not at the root. + if (last_start == 0) { last_start += 1; } + + // Drop everything including the trailing slash. + actual_length_ = last_start; + return true; + } +} // namespace nes
\ No newline at end of file diff --git a/lib/nes/common/path-builder.hh b/lib/nes/common/path-builder.hh new file mode 100644 index 0000000..727d18c --- /dev/null +++ b/lib/nes/common/path-builder.hh @@ -0,0 +1,38 @@ +#pragma once + +#include "nes/common/types.hh" +#include "nes/common/path-view.hh" +#include <string_view> + +namespace nes +{ + /// A mutable path with a fixed-length buffer. + class path_builder + { + char* buffer_{ nullptr }; + u32 buffer_length_{ 0 }; + u32 actual_length_{ 0 }; + + public: + explicit path_builder(char* buffer, u32 buffer_length); + + auto get_components() const -> path_view { return path_view{ get_path() }; } + auto get_path() const -> std::string_view { return std::string_view{ buffer_, actual_length_ }; } + + auto push(std::string_view) -> bool; + auto pop() -> bool; + }; + + /// A path builder with a buffer embedded into it. + template<u32 Capacity> + class path_buffer : public path_builder + { + char buffer_[Capacity]; + + public: + explicit path_buffer() + : path_builder{ buffer_, Capacity } + { + } + }; +} // namespace nes
\ No newline at end of file diff --git a/lib/nes/common/path-view.cc b/lib/nes/common/path-view.cc new file mode 100644 index 0000000..47f198b --- /dev/null +++ b/lib/nes/common/path-view.cc @@ -0,0 +1,49 @@ +#include "nes/common/path-view.hh" + +namespace nes +{ + auto path_view::iterator::operator*() const -> std::string_view + { + return current_ ? std::string_view{ current_, component_length_ } : ""; + } + + auto path_view::iterator::operator++() -> iterator& + { + // Move forward to next component + current_ += component_length_; + remaining_ -= component_length_; + + // Skip over '/'. + while (remaining_ > 0 && *current_ == '/') + { + current_ += 1; + remaining_ -= 1; + } + + // Scan until end of component to find its length. + component_length_ = 0; + while (component_length_ < remaining_ && current_[component_length_] != '/') + { + component_length_ += 1; + } + + return *this; + } + + auto path_view::iterator::operator++(int) -> iterator + { + auto const res = *this; + ++*this; + return res; + } + + auto path_view::begin() const -> iterator + { + return ++iterator{ path_.data(), 0, static_cast<u32>(path_.size()) }; + } + + auto path_view::end() const -> iterator + { + return iterator{ path_.data() + path_.size(), 0, 0 }; + } +} // namespace nes
\ No newline at end of file diff --git a/lib/nes/common/path-view.hh b/lib/nes/common/path-view.hh index 3d670bf..7741060 100644 --- a/lib/nes/common/path-view.hh +++ b/lib/nes/common/path-view.hh @@ -5,13 +5,10 @@ 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. + /// Non-owning copy of a path, useful for iterating over its components. class path_view { - std::string_view const* components_{ nullptr }; - u32 component_count_{ 0 }; + std::string_view path_; public: /// An iterator for the path components. @@ -19,54 +16,37 @@ namespace nes { friend path_view; - std::string_view const* current_{ nullptr }; - u32 remaining_{ 0 }; + char const* current_{ nullptr }; // Start of the current component + u32 component_length_{ 0 }; // Length of the current component + u32 remaining_{ 0 }; // Number of characters remaining in the buffer, including the current component. 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; - } + auto operator*() const -> std::string_view; + auto operator++() -> iterator&; + auto operator++(int) -> iterator; private: - explicit iterator() = default; - explicit iterator(std::string_view const* current, u32 const remaining) + explicit iterator(char const* current, u32 const component_length, u32 const remaining) : current_{ current } + , component_length_{ component_length } , remaining_{ remaining } { } }; explicit path_view() = default; - - explicit path_view(std::string_view const* components, u32 const component_count) - : components_{ components } - , component_count_{ component_count } + explicit path_view(std::string_view const path) + : path_{ path } { } - 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 get_path() const -> std::string_view { return path_; } - auto begin() const -> iterator { return iterator{ components_, component_count_ }; } - auto end() const -> iterator { return iterator{ components_ + component_count_, 0 }; } + auto begin() const -> iterator; + auto end() const -> iterator; + auto is_empty() const -> bool { return begin() == end(); } }; } // namespace nes::app
\ No newline at end of file diff --git a/lib/nes/common/string-builder.hh b/lib/nes/common/string-builder.hh index fda85a4..e0bc673 100644 --- a/lib/nes/common/string-builder.hh +++ b/lib/nes/common/string-builder.hh @@ -95,4 +95,17 @@ namespace nes auto append_format(std::string_view fmt, format_arg const* args, u32 arg_count) -> string_builder&; auto append_format_arg(format_arg, std::string_view params) -> string_builder&; }; + + /// A string builder with a buffer embedded into it. + template<u32 Capacity> + class string_buffer : public string_builder + { + char buffer_[Capacity]; + + public: + explicit string_buffer() + : string_builder{ buffer_, Capacity } + { + } + }; } // namespace nes
\ No newline at end of file |
