summaryrefslogtreecommitdiff
path: root/lib/nes
diff options
context:
space:
mode:
authorHannes Schulze <haschu0103@outlook.de>2025-02-04 19:25:03 +0100
committerHannes Schulze <haschu0103@outlook.de>2025-02-04 19:25:03 +0100
commite791ac9a46095e0241181da89c35a7c702982c21 (patch)
treed959c79c24ae03282f1b00ec9bcb2ccf86d9c96c /lib/nes
parent0f22ed2ef160eef2da6080782943b22c9539650d (diff)
clean up path logic a bit
Diffstat (limited to 'lib/nes')
-rw-r--r--lib/nes/app/file-browser.hh4
-rw-r--r--lib/nes/app/graphics/renderer.hh8
-rw-r--r--lib/nes/app/ui/screen-browser.cc82
-rw-r--r--lib/nes/app/ui/screen-browser.hh10
-rw-r--r--lib/nes/common/CMakeLists.txt6
-rw-r--r--lib/nes/common/path-builder.cc47
-rw-r--r--lib/nes/common/path-builder.hh38
-rw-r--r--lib/nes/common/path-view.cc49
-rw-r--r--lib/nes/common/path-view.hh52
-rw-r--r--lib/nes/common/string-builder.hh13
10 files changed, 237 insertions, 72 deletions
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