summaryrefslogtreecommitdiff
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
parent0f22ed2ef160eef2da6080782943b22c9539650d (diff)
clean up path logic a bit
-rw-r--r--app/mac/scene.mm161
-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
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