summaryrefslogtreecommitdiff
path: root/lib/nes/app
diff options
context:
space:
mode:
authorHannes Schulze <haschu0103@outlook.de>2025-02-06 18:24:59 +0100
committerHannes Schulze <haschu0103@outlook.de>2025-02-06 18:24:59 +0100
commitd060e3a50c987d10799ccc739bdbf9d7d7a13c10 (patch)
tree4b5585383ada85608b57bcf76e5828817962fddf /lib/nes/app
parentdbed1c0fa5599b1b96c221b89b10f1e350351645 (diff)
implement settings
Diffstat (limited to 'lib/nes/app')
-rw-r--r--lib/nes/app/CMakeLists.txt3
-rw-r--r--lib/nes/app/application.cc41
-rw-r--r--lib/nes/app/application.hh18
-rw-r--r--lib/nes/app/graphics/renderer.cc16
-rw-r--r--lib/nes/app/graphics/renderer.hh7
-rw-r--r--lib/nes/app/graphics/tiles/CMakeLists.txt7
-rw-r--r--lib/nes/app/graphics/tiles/symbol_at.pngbin0 -> 156 bytes
-rw-r--r--lib/nes/app/graphics/tiles/symbol_backslash.pngbin0 -> 133 bytes
-rw-r--r--lib/nes/app/graphics/tiles/symbol_brace_close.pngbin0 -> 148 bytes
-rw-r--r--lib/nes/app/graphics/tiles/symbol_brace_open.pngbin0 -> 147 bytes
-rw-r--r--lib/nes/app/graphics/tiles/symbol_hat.pngbin0 -> 142 bytes
-rw-r--r--lib/nes/app/graphics/tiles/symbol_pipe.pngbin0 -> 141 bytes
-rw-r--r--lib/nes/app/graphics/tiles/symbol_underscore.pngbin0 -> 136 bytes
-rw-r--r--lib/nes/app/input/input-device-controller.hh4
-rw-r--r--lib/nes/app/input/input-device-keyboard.hh2
-rw-r--r--lib/nes/app/input/input-device.hh5
-rw-r--r--lib/nes/app/input/input-manager.cc43
-rw-r--r--lib/nes/app/input/input-manager.hh5
-rw-r--r--lib/nes/app/preferences.hh21
-rw-r--r--lib/nes/app/ui/screen-settings.cc101
-rw-r--r--lib/nes/app/ui/screen-settings.hh51
21 files changed, 279 insertions, 45 deletions
diff --git a/lib/nes/app/CMakeLists.txt b/lib/nes/app/CMakeLists.txt
index c0d6909..df0769e 100644
--- a/lib/nes/app/CMakeLists.txt
+++ b/lib/nes/app/CMakeLists.txt
@@ -4,7 +4,8 @@ target_sources(
application.hh
application.cc
action.hh
- file-browser.hh)
+ file-browser.hh
+ preferences.hh)
add_subdirectory(input)
add_subdirectory(graphics)
diff --git a/lib/nes/app/application.cc b/lib/nes/app/application.cc
index 01d3f0b..015e641 100644
--- a/lib/nes/app/application.cc
+++ b/lib/nes/app/application.cc
@@ -21,15 +21,25 @@ namespace nes::app
popup->render(r);
}
+ if (preferences_.get_fps_counter())
+ {
+ r.render_rect(30, 29, 2, 1, color::fixed_black);
+ auto const attrs = text_attributes{}
+ .set_max_width(2)
+ .set_alignment(text_alignment::right);
+ r.render_text_format(32, 29, color::fixed_white, attrs, "{}", fps_counter.get_fps());
+ }
+
base.switch_buffers();
}
application::application(display& display, input_device_keyboard& keyboard, file_browser& file_browser)
- : display_{ display }
- , input_manager_{ keyboard }
+ : input_manager_{ keyboard }
, file_browser_{ file_browser }
+ , display_{ preferences_, display }
, screen_title_{ keyboard }
, screen_browser_{ keyboard, file_browser }
+ , screen_settings_{ input_manager_, preferences_ }
, screen_error_{ keyboard }
, screen_confirm_quit_{ keyboard }
{
@@ -38,6 +48,8 @@ namespace nes::app
auto application::frame(std::chrono::microseconds const elapsed_time) -> void
{
+ display_.fps_counter.frame(elapsed_time);
+
if (display_.popup)
{
auto const a = display_.popup->process_events();
@@ -99,21 +111,18 @@ namespace nes::app
}
case action::type::go_to_title:
{
- console_.clear();
- display_.screen = &screen_title_;
- display_.popup = nullptr;
+ go_to_screen(&screen_title_);
break;
}
case action::type::go_to_browser:
+ case action::type::confirm_quit:
{
- console_.clear();
- display_.screen = &screen_browser_;
- display_.popup = nullptr;
+ go_to_screen(&screen_browser_);
break;
}
case action::type::go_to_settings:
{
- // TODO
+ go_to_screen(&screen_settings_);
break;
}
case action::type::go_to_help:
@@ -132,13 +141,6 @@ namespace nes::app
display_.screen = nullptr;
break;
}
- case action::type::confirm_quit:
- {
- display_.popup = nullptr;
- display_.screen = &screen_browser_;
- console_.clear();
- break;
- }
case action::type::launch_game:
{
u8 buffer[sys::cartridge::max_file_size];
@@ -176,4 +178,11 @@ namespace nes::app
screen_error_.set_action(action);
display_.popup = &screen_error_;
}
+
+ auto application::go_to_screen(screen* screen) -> void
+ {
+ console_.clear();
+ display_.screen = screen;
+ display_.popup = nullptr;
+ }
} // namespace nes::app \ No newline at end of file
diff --git a/lib/nes/app/application.hh b/lib/nes/app/application.hh
index 66b5206..c23f654 100644
--- a/lib/nes/app/application.hh
+++ b/lib/nes/app/application.hh
@@ -3,12 +3,15 @@
#include "nes/app/input/input-manager.hh"
#include "nes/app/ui/screen-title.hh"
#include "nes/app/ui/screen-browser.hh"
+#include "nes/app/ui/screen-settings.hh"
#include "nes/app/ui/screen-freeze.hh"
#include "nes/app/ui/screen-error.hh"
#include "nes/app/ui/screen-confirm-quit.hh"
+#include "nes/app/preferences.hh"
#include "nes/sys/nes.hh"
#include "nes/common/containers/box.hh"
#include "nes/common/display.hh"
+#include "nes/common/fps-counter.hh"
#include "nes/common/types.hh"
#include <chrono>
@@ -28,13 +31,17 @@ namespace nes::app
/// buffers.
class display_proxy final : public display
{
+ preferences& preferences_;
+
public:
+ fps_counter fps_counter;
display& base;
screen* popup{ nullptr };
screen* screen{ nullptr };
- explicit display_proxy(display& base)
- : base{ base }
+ explicit display_proxy(preferences& preferences, display& base)
+ : preferences_{ preferences }
+ , base{ base }
{
}
@@ -43,18 +50,20 @@ namespace nes::app
auto switch_buffers() -> void override;
};
- display_proxy display_;
input_manager input_manager_;
+ preferences preferences_;
file_browser& file_browser_;
+ display_proxy display_;
box<sys::nes> console_{};
screen_title screen_title_;
screen_browser screen_browser_;
+ screen_settings screen_settings_;
screen_freeze screen_freeze_;
screen_error screen_error_;
screen_confirm_quit screen_confirm_quit_;
public:
- explicit application(display&, input_device_keyboard&, file_browser& file_browser);
+ explicit application(display&, input_device_keyboard&, file_browser&);
application(application const&) = delete;
application(application&&) = delete;
@@ -70,5 +79,6 @@ namespace nes::app
private:
auto handle_action(action const&) -> void;
auto show_error(std::string_view message, status error, action const& action = action::close_popup()) -> void;
+ auto go_to_screen(screen*) -> void;
};
} // namespace nes::app \ No newline at end of file
diff --git a/lib/nes/app/graphics/renderer.cc b/lib/nes/app/graphics/renderer.cc
index 564125b..4f0d562 100644
--- a/lib/nes/app/graphics/renderer.cc
+++ b/lib/nes/app/graphics/renderer.cc
@@ -64,7 +64,7 @@ namespace nes::app
}
auto renderer::render_text(
- i32 x, i32 const y, std::string_view const str, color const c, text_attributes const attributes) -> void
+ i32 x, i32 const y, std::string_view const str, color const c, text_attributes const attributes) -> u32
{
// Enforce max length
char buffer[width];
@@ -121,9 +121,10 @@ namespace nes::app
// Draw the text
for (auto i = u32{ 0 }; i < length; ++i)
{
- auto const character = static_cast<u32>(buffer[i]);
- render_mask_tile(x + static_cast<i32>(i), y, resolve_character(character), c);
+ render_mask_tile(x + static_cast<i32>(i), y, resolve_character(buffer[i]), c);
}
+
+ return length;
}
auto renderer::render_rect(i32 const x, i32 const y, u32 const width, u32 const height, color const c) -> void
@@ -217,7 +218,7 @@ namespace nes::app
return rgb::from_hex(0x000000);
}
- auto renderer::resolve_character(u32 const c) const -> mask_tile
+ auto renderer::resolve_character(char const c) const -> mask_tile
{
switch (c)
{
@@ -260,6 +261,10 @@ namespace nes::app
case '9': return tiles::digit_9;
case '&': return tiles::symbol_ampersand;
case '\'': return tiles::symbol_apostrophe;
+ case '@': return tiles::symbol_at;
+ case '\\': return tiles::symbol_backslash;
+ case '{': return tiles::symbol_brace_open;
+ case '}': return tiles::symbol_brace_close;
case '[': return tiles::symbol_bracket_open;
case ']': return tiles::symbol_bracket_close;
case ':': return tiles::symbol_colon;
@@ -269,18 +274,21 @@ namespace nes::app
case '!': return tiles::symbol_exclamation_mark;
case '>': return tiles::symbol_greater;
case '#': return tiles::symbol_hashtag;
+ case '^': return tiles::symbol_hat;
case '<': return tiles::symbol_less;
case '-': return tiles::symbol_minus;
case '(': return tiles::symbol_paren_open;
case ')': return tiles::symbol_paren_close;
case '%': return tiles::symbol_percent;
case '.': return tiles::symbol_period;
+ case '|': return tiles::symbol_pipe;
case '+': return tiles::symbol_plus;
case '?': return tiles::symbol_question_mark;
case '"': return tiles::symbol_quotation_mark;
case ';': return tiles::symbol_semicolon;
case '/': return tiles::symbol_slash;
case '*': return tiles::symbol_star;
+ case '_': return tiles::symbol_underscore;
default: return tiles::symbol_unknown;
}
}
diff --git a/lib/nes/app/graphics/renderer.hh b/lib/nes/app/graphics/renderer.hh
index d4d7cc4..2e24ad3 100644
--- a/lib/nes/app/graphics/renderer.hh
+++ b/lib/nes/app/graphics/renderer.hh
@@ -46,7 +46,7 @@ namespace nes::app
/// Render an image at the given tile coordinates.
auto render_image(i32 x, i32 y, image_view) -> void;
/// Render a string of text at the given tile coordinates (one tile per character).
- auto render_text(i32 x, i32 y, std::string_view, color, text_attributes = text_attributes{}) -> void;
+ auto render_text(i32 x, i32 y, std::string_view, color, text_attributes = text_attributes{}) -> u32;
template<typename... Args>
auto render_text_format(
@@ -55,8 +55,7 @@ namespace nes::app
{
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());
+ return render_text(x, y, buffer.get_result(), c, attrs);
}
template<typename... Args>
@@ -75,6 +74,6 @@ namespace nes::app
private:
auto resolve_color(color) const -> rgb;
- auto resolve_character(u32) const -> mask_tile;
+ auto resolve_character(char) const -> mask_tile;
};
} // namespace nes::app \ No newline at end of file
diff --git a/lib/nes/app/graphics/tiles/CMakeLists.txt b/lib/nes/app/graphics/tiles/CMakeLists.txt
index eced5ca..5dced68 100644
--- a/lib/nes/app/graphics/tiles/CMakeLists.txt
+++ b/lib/nes/app/graphics/tiles/CMakeLists.txt
@@ -40,6 +40,10 @@ nes_generate_tiles(
digit_8.png
digit_9.png
symbol_ampersand.png
+ symbol_at.png
+ symbol_backslash.png
+ symbol_brace_open.png
+ symbol_brace_close.png
symbol_apostrophe.png
symbol_bracket_open.png
symbol_bracket_close.png
@@ -50,18 +54,21 @@ nes_generate_tiles(
symbol_exclamation_mark.png
symbol_greater.png
symbol_hashtag.png
+ symbol_hat.png
symbol_less.png
symbol_minus.png
symbol_paren_open.png
symbol_paren_close.png
symbol_percent.png
symbol_period.png
+ symbol_pipe.png
symbol_plus.png
symbol_question_mark.png
symbol_quotation_mark.png
symbol_semicolon.png
symbol_slash.png
symbol_star.png
+ symbol_underscore.png
symbol_unknown.png)
nes_generate_tiles(
diff --git a/lib/nes/app/graphics/tiles/symbol_at.png b/lib/nes/app/graphics/tiles/symbol_at.png
new file mode 100644
index 0000000..e399054
--- /dev/null
+++ b/lib/nes/app/graphics/tiles/symbol_at.png
Binary files differ
diff --git a/lib/nes/app/graphics/tiles/symbol_backslash.png b/lib/nes/app/graphics/tiles/symbol_backslash.png
new file mode 100644
index 0000000..8d98ef0
--- /dev/null
+++ b/lib/nes/app/graphics/tiles/symbol_backslash.png
Binary files differ
diff --git a/lib/nes/app/graphics/tiles/symbol_brace_close.png b/lib/nes/app/graphics/tiles/symbol_brace_close.png
new file mode 100644
index 0000000..bb24228
--- /dev/null
+++ b/lib/nes/app/graphics/tiles/symbol_brace_close.png
Binary files differ
diff --git a/lib/nes/app/graphics/tiles/symbol_brace_open.png b/lib/nes/app/graphics/tiles/symbol_brace_open.png
new file mode 100644
index 0000000..32bdb99
--- /dev/null
+++ b/lib/nes/app/graphics/tiles/symbol_brace_open.png
Binary files differ
diff --git a/lib/nes/app/graphics/tiles/symbol_hat.png b/lib/nes/app/graphics/tiles/symbol_hat.png
new file mode 100644
index 0000000..ab21714
--- /dev/null
+++ b/lib/nes/app/graphics/tiles/symbol_hat.png
Binary files differ
diff --git a/lib/nes/app/graphics/tiles/symbol_pipe.png b/lib/nes/app/graphics/tiles/symbol_pipe.png
new file mode 100644
index 0000000..9db3443
--- /dev/null
+++ b/lib/nes/app/graphics/tiles/symbol_pipe.png
Binary files differ
diff --git a/lib/nes/app/graphics/tiles/symbol_underscore.png b/lib/nes/app/graphics/tiles/symbol_underscore.png
new file mode 100644
index 0000000..1823c95
--- /dev/null
+++ b/lib/nes/app/graphics/tiles/symbol_underscore.png
Binary files differ
diff --git a/lib/nes/app/input/input-device-controller.hh b/lib/nes/app/input/input-device-controller.hh
index 69dd2a8..93147cd 100644
--- a/lib/nes/app/input/input-device-controller.hh
+++ b/lib/nes/app/input/input-device-controller.hh
@@ -2,7 +2,6 @@
#include "nes/app/input/input-device.hh"
#include "nes/common/containers/list.hh"
-#include <string_view>
namespace nes::app
{
@@ -23,9 +22,6 @@ namespace nes::app
/// controller already selected.
virtual auto is_reliable() const -> bool = 0;
- /// Human-readable name for the controller.
- virtual auto get_name() const -> std::string_view = 0;
-
/// Get the (persistent) player index for the controller, if already set before.
virtual auto get_index() const -> index = 0;
diff --git a/lib/nes/app/input/input-device-keyboard.hh b/lib/nes/app/input/input-device-keyboard.hh
index 23ee65c..ef89c59 100644
--- a/lib/nes/app/input/input-device-keyboard.hh
+++ b/lib/nes/app/input/input-device-keyboard.hh
@@ -43,6 +43,8 @@ namespace nes::app
return res;
}
+ auto get_name() const -> std::string_view final { return "Keyboard"; }
+
protected:
explicit input_device_keyboard() = default;
};
diff --git a/lib/nes/app/input/input-device.hh b/lib/nes/app/input/input-device.hh
index cc03057..008825d 100644
--- a/lib/nes/app/input/input-device.hh
+++ b/lib/nes/app/input/input-device.hh
@@ -1,6 +1,7 @@
#pragma once
#include "nes/sys/types/button-mask.hh"
+#include <string_view>
namespace nes::app
{
@@ -15,8 +16,12 @@ namespace nes::app
auto operator=(input_device const&) -> input_device& = delete;
auto operator=(input_device&&) -> input_device& = delete;
+ /// Read the controller button state.
virtual auto read_buttons() -> sys::button_mask = 0;
+ /// Human-readable name for the device.
+ virtual auto get_name() const -> std::string_view = 0;
+
protected:
explicit input_device() = default;
};
diff --git a/lib/nes/app/input/input-manager.cc b/lib/nes/app/input/input-manager.cc
index ceb6690..e88ac84 100644
--- a/lib/nes/app/input/input-manager.cc
+++ b/lib/nes/app/input/input-manager.cc
@@ -15,6 +15,7 @@ namespace nes::app
return res;
}
+ auto get_name() const -> std::string_view override { return "(None)"; }
auto read_buttons() -> sys::button_mask override { return sys::button_mask{}; }
};
} // namespace
@@ -36,6 +37,10 @@ namespace nes::app
// This controller was previously used and assigned an index -> replace the current device with this
// (known) controller.
auto const i = static_cast<u32>(index);
+ if (keyboard_index_ == index)
+ {
+ keyboard_index_ = input_device_controller::index::unused;
+ }
if (inputs_[i].controller)
{
inputs_[i].controller->set_index(input_device_controller::index::unused);
@@ -56,6 +61,43 @@ namespace nes::app
fill_unassigned_inputs();
}
+ auto input_manager::toggle_input(u32 const i) -> void
+ {
+ // If currently none/keyboard, use the first available controller.
+ auto curr_was_prev = inputs_[i].controller == nullptr;
+ if (curr_was_prev && keyboard_index_ == input_device_controller::index::unused)
+ {
+ // If currently none and keyboard is available, switch to keyboard.
+ keyboard_index_ = static_cast<input_device_controller::index>(i);
+ inputs_[i].controller = nullptr;
+ inputs_[i].device = keyboard_;
+ return;
+ }
+
+ // Reset old controller index
+ if (inputs_[i].controller) { inputs_[i].controller->set_index(input_device_controller::index::unused); }
+ if (keyboard_index_ == static_cast<input_device_controller::index>(i)) { keyboard_index_ = input_device_controller::index::unused; }
+
+ for (auto& controller : controllers_)
+ {
+ if (&controller == inputs_[i].controller)
+ {
+ curr_was_prev = true;
+ }
+ else if (curr_was_prev && controller.get_index() == input_device_controller::index::unused)
+ {
+ inputs_[i].controller = &controller;
+ inputs_[i].controller->set_index(static_cast<input_device_controller::index>(i));
+ inputs_[i].device = &controller;
+ return;
+ }
+ }
+
+ // No successor found, switch to none.
+ inputs_[i].controller = nullptr;
+ inputs_[i].device = &input_device_dummy::get_instance();
+ }
+
auto input_manager::fill_unassigned_inputs() -> void
{
auto it = controllers_.begin();
@@ -87,6 +129,7 @@ namespace nes::app
// We're all out of controllers -> fall back to the keyboard (if this is the first missing controller)
// or nothing.
inputs_[i].device = fallback;
+ if (fallback == keyboard_) { keyboard_index_ = static_cast<input_device_controller::index>(i); }
fallback = &input_device_dummy::get_instance(); // Only use the keyboard as a fallback once.
}
}
diff --git a/lib/nes/app/input/input-manager.hh b/lib/nes/app/input/input-manager.hh
index b0b60e8..7a63326 100644
--- a/lib/nes/app/input/input-manager.hh
+++ b/lib/nes/app/input/input-manager.hh
@@ -16,6 +16,7 @@ namespace nes::app
static constexpr auto input_count = u32{ 2 };
input_device_keyboard* keyboard_;
+ input_device_controller::index keyboard_index_{ input_device_controller::index::unused };
list<input_device_controller> controllers_;
struct
{
@@ -34,6 +35,9 @@ namespace nes::app
auto get_input_1() const -> input_device& { return *inputs_[0].device; }
auto get_input_2() const -> input_device& { return *inputs_[1].device; }
+ auto toggle_input_1() -> void { toggle_input(0); }
+ auto toggle_input_2() -> void { toggle_input(1); }
+
auto get_keyboard() const -> input_device_keyboard& { return *keyboard_; }
auto get_controllers() const -> list<input_device_controller> const& { return controllers_; }
@@ -41,6 +45,7 @@ namespace nes::app
auto remove_controller(input_device_controller&) -> void;
private:
+ auto toggle_input(u32) -> void;
auto fill_unassigned_inputs() -> void;
};
} // namespace nes::app \ No newline at end of file
diff --git a/lib/nes/app/preferences.hh b/lib/nes/app/preferences.hh
new file mode 100644
index 0000000..0c13755
--- /dev/null
+++ b/lib/nes/app/preferences.hh
@@ -0,0 +1,21 @@
+#pragma once
+
+namespace nes::app
+{
+ /// Manages application preferences.
+ class preferences
+ {
+ bool fps_counter_{ false };
+
+ public:
+ explicit preferences() = default;
+
+ preferences(preferences const&) = delete;
+ preferences(preferences&&) = delete;
+ auto operator=(preferences const&) -> preferences& = delete;
+ auto operator=(preferences&&) -> preferences& = delete;
+
+ auto get_fps_counter() const -> bool { return fps_counter_; }
+ auto set_fps_counter(bool const v) -> void { fps_counter_ = v; }
+ };
+} // namespace nes::app \ No newline at end of file
diff --git a/lib/nes/app/ui/screen-settings.cc b/lib/nes/app/ui/screen-settings.cc
index 4d32662..98070d7 100644
--- a/lib/nes/app/ui/screen-settings.cc
+++ b/lib/nes/app/ui/screen-settings.cc
@@ -1,5 +1,98 @@
-//
-// Created by Hannes Schulze on 06.02.25.
-//
+#include "nes/app/ui/screen-settings.hh"
+#include "nes/app/input/input-manager.hh"
+#include "nes/app/preferences.hh"
-#include "screen-settings.hh"
+namespace nes::app
+{
+ auto screen_settings::selection_impl::render_item(
+ renderer& renderer, item const& item, i32 const x, i32 const y, u32 const width, color const c) const -> void
+ {
+ auto const render = [&](std::string_view const label, std::string_view const value)
+ {
+ auto const label_length = renderer.render_text(x, y, label, c);
+ auto const value_attrs = text_attributes{}
+ .set_alignment(text_alignment::right)
+ .set_ellipsize_mode(ellipsize_mode::end)
+ .set_max_width(width - label_length - 2);
+ renderer.render_text(x + static_cast<i32>(width), y, value, color::foreground_secondary, value_attrs);
+ };
+ switch (item)
+ {
+ case item::controller_1:
+ render("Controller 1:", input_manager_.get_input_1().get_name());
+ break;
+ case item::controller_2:
+ render("Controller 2:", input_manager_.get_input_2().get_name());
+ break;
+ case item::fps_counter:
+ render("FPS Counter:", preferences_.get_fps_counter() ? "Yes" : "No");
+ break;
+ }
+ }
+
+ auto screen_settings::selection_impl::load_page(item (&items)[page_size], u32) -> u32
+ {
+ items[0] = item::controller_1;
+ items[1] = item::controller_2;
+ items[2] = item::fps_counter;
+ return 3;
+ }
+
+ screen_settings::screen_settings(input_manager& input_manager, preferences& preferences)
+ : input_manager_{ input_manager }
+ , preferences_{ preferences }
+ , selection_{ input_manager, preferences }
+ {
+ selection_.set_page_count(1);
+ }
+
+ auto screen_settings::render(renderer& renderer) -> void
+ {
+ renderer.render_fill(color::background_primary);
+ renderer.render_rect(0, 0, 32, 3, color::background_secondary);
+
+ 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());
+
+ renderer.render_text(
+ 2, 1, "Settings", color::foreground_primary,
+ text_attributes{}.set_max_width(26 - page_length).set_ellipsize_mode(ellipsize_mode::middle));
+
+ selection_.render(renderer);
+ }
+
+ auto screen_settings::process_events() -> action
+ {
+ while (auto const event = input_manager_.get_keyboard().poll_event())
+ {
+ if (event == input_event::key_down(key::space))
+ {
+ selection_.go_next();
+ }
+ else if (event == input_event::key_down(key::arrow_up))
+ {
+ selection_.go_up();
+ }
+ else if (event == input_event::key_down(key::arrow_down))
+ {
+ selection_.go_down();
+ }
+ else if (event == input_event::key_down(key::backspace))
+ {
+ return action::go_to_title();
+ }
+ else if (event == input_event::key_down(key::enter))
+ {
+ switch (*selection_.get_selected())
+ {
+ case item::controller_1: input_manager_.toggle_input_1(); break;
+ case item::controller_2: input_manager_.toggle_input_2(); break;
+ case item::fps_counter: preferences_.set_fps_counter(!preferences_.get_fps_counter()); break;
+ }
+ }
+ }
+
+ return action{};
+ }
+} // namespace nes::app \ No newline at end of file
diff --git a/lib/nes/app/ui/screen-settings.hh b/lib/nes/app/ui/screen-settings.hh
index 8f510ca..8910c5c 100644
--- a/lib/nes/app/ui/screen-settings.hh
+++ b/lib/nes/app/ui/screen-settings.hh
@@ -1,16 +1,51 @@
-//
-// Created by Hannes Schulze on 06.02.25.
-//
+#pragma once
-#ifndef SCREEN_SETTINGS_HH
-#define SCREEN_SETTINGS_HH
+#include "nes/app/ui/screen.hh"
+#include "nes/app/ui/selection.hh"
+namespace nes::app
+{
+ class input_manager;
+ class preferences;
+ /// Screen used to configure various emulator options.
+ class screen_settings final : public screen
+ {
+ static constexpr auto page_size = u32{ 8 };
-class screen_settings {
+ enum class item
+ {
+ controller_1,
+ controller_2,
+ fps_counter,
+ };
-};
+ class selection_impl final : public selection<item, page_size>
+ {
+ input_manager& input_manager_;
+ preferences& preferences_;
+ public:
+ explicit selection_impl(input_manager& input_manager, preferences& preferences)
+ : selection{ 2, 5, 28 }
+ , input_manager_{ input_manager }
+ , preferences_{ preferences }
+ {
+ }
+ protected:
+ auto render_item(renderer&, item const&, i32 x, i32 y, u32 width, color) const -> void override;
+ auto load_page(item(&)[page_size], u32 page) -> u32 override;
+ };
-#endif //SCREEN_SETTINGS_HH
+ input_manager& input_manager_;
+ preferences& preferences_;
+ selection_impl selection_;
+
+ public:
+ explicit screen_settings(input_manager&, preferences&);
+
+ auto render(renderer&) -> void override;
+ auto process_events() -> action override;
+ };
+} // namespace nes::app \ No newline at end of file