summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/mac/scene.mm149
-rw-r--r--lib/nes/app/application.cc4
-rw-r--r--lib/nes/app/input/CMakeLists.txt6
-rw-r--r--lib/nes/app/input/input-buffer.cc56
-rw-r--r--lib/nes/app/input/input-buffer.hh41
-rw-r--r--lib/nes/app/input/input-device-keyboard.hh35
-rw-r--r--lib/nes/app/input/input-device.hh23
-rw-r--r--lib/nes/app/input/input-event.hh46
-rw-r--r--lib/nes/app/input/input-manager.cc9
-rw-r--r--lib/nes/app/input/key.cc88
-rw-r--r--lib/nes/app/input/key.hh90
-rw-r--r--lib/nes/app/input/modifier-mask.hh39
-rw-r--r--lib/nes/app/ui/screen-title.cc29
-rw-r--r--lib/nes/app/ui/screen-title.hh8
-rw-r--r--lib/nes/common/list.hh11
15 files changed, 571 insertions, 63 deletions
diff --git a/app/mac/scene.mm b/app/mac/scene.mm
index 247a097..bf1a61b 100644
--- a/app/mac/scene.mm
+++ b/app/mac/scene.mm
@@ -2,6 +2,7 @@
#import "nes/app/application.hh"
#import "nes/app/input/input-device-keyboard.hh"
#import "nes/app/input/input-device-controller.hh"
+#import "nes/app/input/input-buffer.hh"
#import "nes/common/display.hh"
#import <memory>
#import <optional>
@@ -67,25 +68,128 @@ namespace
class input_device_keyboard_impl final : public nes::app::input_device_keyboard
{
- GCKeyboardInput* profile_;
+ GCKeyboardInput* profile_{ nil };
+ nes::app::input_buffer buffer_;
public:
explicit input_device_keyboard_impl(GCKeyboardInput* profile)
- : profile_{ profile }
{
+ set_profile(profile);
}
- auto set_profile(GCKeyboardInput* profile) -> void { profile_ = profile; }
+ ~input_device_keyboard_impl()
+ {
+ set_profile(nil);
+ }
- auto read_key_up() -> bool override { return [[profile_ buttonForKeyCode:GCKeyCodeUpArrow] isPressed]; }
- auto read_key_down() -> bool override { return [[profile_ buttonForKeyCode:GCKeyCodeDownArrow] isPressed]; }
- auto read_key_left() -> bool override { return [[profile_ buttonForKeyCode:GCKeyCodeLeftArrow] isPressed]; }
- auto read_key_right() -> bool override { return [[profile_ buttonForKeyCode:GCKeyCodeRightArrow] isPressed]; }
- auto read_key_a() -> bool override { return [[profile_ buttonForKeyCode:GCKeyCodeKeyZ] isPressed]; }
- auto read_key_b() -> bool override { return [[profile_ buttonForKeyCode:GCKeyCodeKeyX] isPressed]; }
- auto read_key_select() -> bool override { return [[profile_ buttonForKeyCode:GCKeyCodeSpacebar] isPressed]; }
- auto read_key_start() -> bool override { return [[profile_ buttonForKeyCode:GCKeyCodeReturnOrEnter] isPressed]; }
- auto read_key_escape() -> bool override { return [[profile_ buttonForKeyCode:GCKeyCodeEscape] isPressed]; }
+ auto set_profile(GCKeyboardInput* profile) -> void
+ {
+ [profile_ setKeyChangedHandler:nil];
+ profile_ = profile;
+ [profile_ setKeyChangedHandler:^(GCKeyboardInput*, GCControllerButtonInput*, GCKeyCode key_code, BOOL pressed) {
+ handle_key_changed(key_code, pressed);
+ }];
+ }
+
+ auto read_key(nes::app::key const key) -> bool override { return buffer_.read_key(key); }
+ auto poll_event() -> nes::app::input_event override { return buffer_.poll_event(); }
+
+ private:
+ auto handle_key_changed(GCKeyCode const key_code, BOOL const pressed) -> void
+ {
+ auto const key = convert_key_code(key_code);
+ if (key)
+ {
+ if (pressed)
+ {
+ buffer_.key_down(*key);
+ }
+ else
+ {
+ buffer_.key_up(*key);
+ }
+ }
+ }
+
+ auto convert_key_code(GCKeyCode const key_code) -> std::optional<nes::app::key>
+ {
+ if (key_code == GCKeyCodeZero) { return nes::app::key::digit_0; }
+ if (key_code == GCKeyCodeOne) { return nes::app::key::digit_1; }
+ if (key_code == GCKeyCodeTwo) { return nes::app::key::digit_2; }
+ if (key_code == GCKeyCodeThree) { return nes::app::key::digit_3; }
+ if (key_code == GCKeyCodeFour) { return nes::app::key::digit_4; }
+ if (key_code == GCKeyCodeFive) { return nes::app::key::digit_5; }
+ if (key_code == GCKeyCodeSix) { return nes::app::key::digit_6; }
+ if (key_code == GCKeyCodeSeven) { return nes::app::key::digit_7; }
+ if (key_code == GCKeyCodeEight) { return nes::app::key::digit_8; }
+ if (key_code == GCKeyCodeNine) { return nes::app::key::digit_9; }
+ if (key_code == GCKeyCodeKeyA) { return nes::app::key::letter_a; }
+ if (key_code == GCKeyCodeKeyB) { return nes::app::key::letter_b; }
+ if (key_code == GCKeyCodeKeyC) { return nes::app::key::letter_c; }
+ if (key_code == GCKeyCodeKeyD) { return nes::app::key::letter_d; }
+ if (key_code == GCKeyCodeKeyE) { return nes::app::key::letter_e; }
+ if (key_code == GCKeyCodeKeyF) { return nes::app::key::letter_f; }
+ if (key_code == GCKeyCodeKeyG) { return nes::app::key::letter_g; }
+ if (key_code == GCKeyCodeKeyH) { return nes::app::key::letter_h; }
+ if (key_code == GCKeyCodeKeyI) { return nes::app::key::letter_i; }
+ if (key_code == GCKeyCodeKeyJ) { return nes::app::key::letter_j; }
+ if (key_code == GCKeyCodeKeyK) { return nes::app::key::letter_k; }
+ if (key_code == GCKeyCodeKeyL) { return nes::app::key::letter_l; }
+ if (key_code == GCKeyCodeKeyM) { return nes::app::key::letter_m; }
+ if (key_code == GCKeyCodeKeyN) { return nes::app::key::letter_n; }
+ if (key_code == GCKeyCodeKeyO) { return nes::app::key::letter_o; }
+ if (key_code == GCKeyCodeKeyP) { return nes::app::key::letter_p; }
+ if (key_code == GCKeyCodeKeyQ) { return nes::app::key::letter_q; }
+ if (key_code == GCKeyCodeKeyR) { return nes::app::key::letter_r; }
+ if (key_code == GCKeyCodeKeyS) { return nes::app::key::letter_s; }
+ if (key_code == GCKeyCodeKeyT) { return nes::app::key::letter_t; }
+ if (key_code == GCKeyCodeKeyU) { return nes::app::key::letter_u; }
+ if (key_code == GCKeyCodeKeyV) { return nes::app::key::letter_v; }
+ if (key_code == GCKeyCodeKeyW) { return nes::app::key::letter_w; }
+ if (key_code == GCKeyCodeKeyX) { return nes::app::key::letter_x; }
+ if (key_code == GCKeyCodeKeyY) { return nes::app::key::letter_y; }
+ if (key_code == GCKeyCodeKeyZ) { return nes::app::key::letter_z; }
+ if (key_code == GCKeyCodeEscape) { return nes::app::key::escape; }
+ if (key_code == GCKeyCodeHyphen) { return nes::app::key::dash; }
+ if (key_code == GCKeyCodeEqualSign) { return nes::app::key::equal; }
+ if (key_code == GCKeyCodeDeleteOrBackspace) { return nes::app::key::backspace; }
+ if (key_code == GCKeyCodeTab) { return nes::app::key::tab; }
+ if (key_code == GCKeyCodeReturnOrEnter) { return nes::app::key::enter; }
+ if (key_code == GCKeyCodeSemicolon) { return nes::app::key::semicolon; }
+ if (key_code == GCKeyCodeQuote) { return nes::app::key::apostrophe; }
+ if (key_code == GCKeyCodeBackslash) { return nes::app::key::backslash; }
+ if (key_code == GCKeyCodeComma) { return nes::app::key::comma; }
+ if (key_code == GCKeyCodePeriod) { return nes::app::key::period; }
+ if (key_code == GCKeyCodeSlash) { return nes::app::key::slash; }
+ if (key_code == GCKeyCodeSpacebar) { return nes::app::key::space; }
+ if (key_code == GCKeyCodeCapsLock) { return nes::app::key::caps_lock; }
+ if (key_code == GCKeyCodeOpenBracket) { return nes::app::key::bracket_open; }
+ if (key_code == GCKeyCodeCloseBracket) { return nes::app::key::bracket_close; }
+ if (key_code == GCKeyCodeLeftControl) { return nes::app::key::control_left; }
+ if (key_code == GCKeyCodeRightControl) { return nes::app::key::control_right; }
+ if (key_code == GCKeyCodeLeftShift) { return nes::app::key::shift_left; }
+ if (key_code == GCKeyCodeRightShift) { return nes::app::key::shift_right; }
+ if (key_code == GCKeyCodeLeftAlt) { return nes::app::key::alt_left; }
+ if (key_code == GCKeyCodeRightAlt) { return nes::app::key::alt_right; }
+ if (key_code == GCKeyCodeLeftArrow) { return nes::app::key::arrow_left; }
+ if (key_code == GCKeyCodeRightArrow) { return nes::app::key::arrow_right; }
+ if (key_code == GCKeyCodeUpArrow) { return nes::app::key::arrow_up; }
+ if (key_code == GCKeyCodeDownArrow) { return nes::app::key::arrow_down; }
+ if (key_code == GCKeyCodeF1) { return nes::app::key::f1; }
+ if (key_code == GCKeyCodeF2) { return nes::app::key::f2; }
+ if (key_code == GCKeyCodeF3) { return nes::app::key::f3; }
+ if (key_code == GCKeyCodeF4) { return nes::app::key::f4; }
+ if (key_code == GCKeyCodeF5) { return nes::app::key::f5; }
+ if (key_code == GCKeyCodeF6) { return nes::app::key::f6; }
+ if (key_code == GCKeyCodeF7) { return nes::app::key::f7; }
+ if (key_code == GCKeyCodeF8) { return nes::app::key::f8; }
+ if (key_code == GCKeyCodeF9) { return nes::app::key::f9; }
+ if (key_code == GCKeyCodeF10) { return nes::app::key::f10; }
+ if (key_code == GCKeyCodeF11) { return nes::app::key::f11; }
+ if (key_code == GCKeyCodeF12) { return nes::app::key::f12; }
+
+ return std::nullopt;
+ }
};
class input_device_controller_impl final : public nes::app::input_device_controller
@@ -134,14 +238,19 @@ namespace
}
}
- auto read_key_up() -> bool override { return [[[profile_ dpad] up] isPressed]; }
- auto read_key_down() -> bool override { return [[[profile_ dpad] down] isPressed]; }
- auto read_key_left() -> bool override { return [[[profile_ dpad] left] isPressed]; }
- auto read_key_right() -> bool override { return [[[profile_ dpad] right] isPressed]; }
- auto read_key_a() -> bool override { return [[profile_ buttonA] isPressed]; }
- auto read_key_b() -> bool override { return [[profile_ buttonB] isPressed]; }
- auto read_key_select() -> bool override { return [[profile_ leftTrigger] isPressed]; }
- auto read_key_start() -> bool override { return [[profile_ rightTrigger] isPressed]; }
+ auto read_buttons() -> nes::sys::button_mask override
+ {
+ auto res = nes::sys::button_mask{};
+ if ([[[profile_ dpad] up] isPressed]) { res.add(nes::sys::buttons::up); }
+ if ([[[profile_ dpad] down] isPressed]) { res.add(nes::sys::buttons::down); }
+ if ([[[profile_ dpad] left] isPressed]) { res.add(nes::sys::buttons::left); }
+ if ([[[profile_ dpad] right] isPressed]) { res.add(nes::sys::buttons::right); }
+ if ([[profile_ buttonA] isPressed]) { res.add(nes::sys::buttons::a); }
+ if ([[profile_ buttonB] isPressed]) { res.add(nes::sys::buttons::b); }
+ if ([[profile_ leftTrigger] isPressed]) { res.add(nes::sys::buttons::select); }
+ if ([[profile_ rightTrigger] isPressed]) { res.add(nes::sys::buttons::start); }
+ return res;
+ }
};
} // namespace
diff --git a/lib/nes/app/application.cc b/lib/nes/app/application.cc
index 264e07a..b18e6d6 100644
--- a/lib/nes/app/application.cc
+++ b/lib/nes/app/application.cc
@@ -43,8 +43,8 @@ namespace nes::app
if (console_)
{
- console_->ref_controller_1().set_pressed(input_manager_.get_input_1().read_mask());
- console_->ref_controller_2().set_pressed(input_manager_.get_input_2().read_mask());
+ console_->ref_controller_1().set_pressed(input_manager_.get_input_1().read_buttons());
+ console_->ref_controller_2().set_pressed(input_manager_.get_input_2().read_buttons());
// The scene is rendered by the console after the PPU requests a new frame (thus calling
// display_proxy::switch_buffers).
diff --git a/lib/nes/app/input/CMakeLists.txt b/lib/nes/app/input/CMakeLists.txt
index 7150745..632bfcd 100644
--- a/lib/nes/app/input/CMakeLists.txt
+++ b/lib/nes/app/input/CMakeLists.txt
@@ -1,6 +1,12 @@
target_sources(
nes
PRIVATE
+ key.hh
+ key.cc
+ modifier-mask.hh
+ input-event.hh
+ input-buffer.hh
+ input-buffer.cc
input-device.hh
input-device-keyboard.hh
input-device-controller.hh
diff --git a/lib/nes/app/input/input-buffer.cc b/lib/nes/app/input/input-buffer.cc
new file mode 100644
index 0000000..e8da51c
--- /dev/null
+++ b/lib/nes/app/input/input-buffer.cc
@@ -0,0 +1,56 @@
+#include "nes/app/input/input-buffer.hh"
+#include "nes/app/input/input-event.hh"
+
+namespace nes::app
+{
+ auto input_buffer::key_down(key const k) -> void
+ {
+ set_pressed(k, true);
+ }
+
+ auto input_buffer::key_up(key const k) -> void
+ {
+ set_pressed(k, false);
+ }
+
+ auto input_buffer::read_key(key const k) -> bool
+ {
+ return get_event(k).pressed;
+ }
+
+ auto input_buffer::poll_event() -> input_event
+ {
+ auto it = event_queue_.begin();
+ if (it == event_queue_.end()) { return input_event{}; }
+
+ auto const event = *it;
+ auto const k = get_key(*event);
+ auto const pressed = event->pressed;
+ event_queue_.remove(it);
+
+ return pressed ? input_event::key_down(k) : input_event::key_up(k);
+ }
+
+ auto input_buffer::get_event(key const k) -> event&
+ {
+ return key_events_[static_cast<u32>(k)];
+ }
+
+ auto input_buffer::get_key(event const& ev) -> key
+ {
+ return static_cast<key>(&ev - key_events_);
+ }
+
+ auto input_buffer::set_pressed(key const k, bool const pressed) -> void
+ {
+ auto& event = get_event(k);
+ if (event.pressed == pressed) { return; }
+ event.pressed = pressed;
+
+ // Remove the event if it was not previously fetched (skipping it entirely), otherwise add a new event.
+ if (!event_queue_.remove(event))
+ {
+ event_queue_.append(event);
+ }
+ }
+} // namespace nes::app \ No newline at end of file
diff --git a/lib/nes/app/input/input-buffer.hh b/lib/nes/app/input/input-buffer.hh
new file mode 100644
index 0000000..68fca6b
--- /dev/null
+++ b/lib/nes/app/input/input-buffer.hh
@@ -0,0 +1,41 @@
+#pragma once
+
+#include "nes/app/input/key.hh"
+#include "nes/common/list.hh"
+
+namespace nes::app
+{
+ class input_event;
+
+ class input_buffer
+ {
+ struct event final : list<event>::node
+ {
+ bool pressed{ false };
+ };
+
+ event key_events_[key_count]{};
+ list<event> event_queue_;
+
+ public:
+ explicit input_buffer() = default;
+
+ input_buffer(input_buffer const&) = delete;
+ input_buffer(input_buffer&&) = delete;
+ auto operator=(input_buffer const&) -> input_buffer& = delete;
+ auto operator=(input_buffer&&) -> input_buffer& = delete;
+
+ /// Record a key-down event.
+ auto key_down(key) -> void;
+ /// Record a key-up event.
+ auto key_up(key) -> void;
+
+ auto read_key(key) -> bool;
+ auto poll_event() -> input_event;
+
+ private:
+ auto get_event(key) -> event&;
+ auto get_key(event const&) -> key;
+ auto set_pressed(key, bool) -> void;
+ };
+} // namespace nes::app \ No newline at end of file
diff --git a/lib/nes/app/input/input-device-keyboard.hh b/lib/nes/app/input/input-device-keyboard.hh
index dddcab6..23ee65c 100644
--- a/lib/nes/app/input/input-device-keyboard.hh
+++ b/lib/nes/app/input/input-device-keyboard.hh
@@ -1,6 +1,9 @@
#pragma once
#include "nes/app/input/input-device.hh"
+#include "nes/app/input/modifier-mask.hh"
+#include "nes/app/input/input-event.hh"
+#include "nes/app/input/key.hh"
namespace nes::app
{
@@ -8,7 +11,37 @@ namespace nes::app
class input_device_keyboard : public input_device
{
public:
- virtual auto read_key_escape() -> bool = 0;
+ /// Read the current state of a key.
+ virtual auto read_key(key) -> bool = 0;
+
+ /// Poll for events.
+ ///
+ /// As long as there are any buffered key-down or key-up events, they will be returned. After that, this
+ /// function will return input_event::type::none until there is another event.
+ virtual auto poll_event() -> input_event = 0;
+
+ auto read_buttons() -> sys::button_mask final
+ {
+ auto res = sys::button_mask{};
+ if (read_key(key::letter_w)) { res.add(sys::buttons::up); }
+ if (read_key(key::letter_a)) { res.add(sys::buttons::left); }
+ if (read_key(key::letter_s)) { res.add(sys::buttons::down); }
+ if (read_key(key::letter_d)) { res.add(sys::buttons::right); }
+ if (read_key(key::letter_k)) { res.add(sys::buttons::b); }
+ if (read_key(key::letter_l)) { res.add(sys::buttons::a); }
+ if (read_key(key::space)) { res.add(sys::buttons::select); }
+ if (read_key(key::enter)) { res.add(sys::buttons::start); }
+ return res;
+ }
+
+ auto read_modifiers() -> modifier_mask
+ {
+ auto res = modifier_mask{};
+ if (read_key(key::control_left) || read_key(key::control_right)) { res.add(modifiers::control); }
+ if (read_key(key::shift_left) || read_key(key::shift_right)) { res.add(modifiers::shift); }
+ if (read_key(key::alt_left) || read_key(key::alt_right)) { res.add(modifiers::alt); }
+ return res;
+ }
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 8e54f61..cc03057 100644
--- a/lib/nes/app/input/input-device.hh
+++ b/lib/nes/app/input/input-device.hh
@@ -15,28 +15,7 @@ namespace nes::app
auto operator=(input_device const&) -> input_device& = delete;
auto operator=(input_device&&) -> input_device& = delete;
- virtual auto read_key_up() -> bool = 0;
- virtual auto read_key_down() -> bool = 0;
- virtual auto read_key_left() -> bool = 0;
- virtual auto read_key_right() -> bool = 0;
- virtual auto read_key_a() -> bool = 0;
- virtual auto read_key_b() -> bool = 0;
- virtual auto read_key_select() -> bool = 0;
- virtual auto read_key_start() -> bool = 0;
-
- auto read_mask() -> sys::button_mask
- {
- auto res = sys::button_mask{};
- if (read_key_up()) { res.add(sys::buttons::up); }
- if (read_key_down()) { res.add(sys::buttons::down); }
- if (read_key_left()) { res.add(sys::buttons::left); }
- if (read_key_right()) { res.add(sys::buttons::right); }
- if (read_key_a()) { res.add(sys::buttons::a); }
- if (read_key_b()) { res.add(sys::buttons::b); }
- if (read_key_select()) { res.add(sys::buttons::select); }
- if (read_key_start()) { res.add(sys::buttons::start); }
- return res;
- }
+ virtual auto read_buttons() -> sys::button_mask = 0;
protected:
explicit input_device() = default;
diff --git a/lib/nes/app/input/input-event.hh b/lib/nes/app/input/input-event.hh
new file mode 100644
index 0000000..90e67b0
--- /dev/null
+++ b/lib/nes/app/input/input-event.hh
@@ -0,0 +1,46 @@
+#pragma once
+
+#include "nes/app/input/key.hh"
+
+namespace nes::app
+{
+ class input_event
+ {
+ public:
+ enum class type
+ {
+ none,
+ key_down,
+ key_up,
+ };
+
+ private:
+ type type_{ type::none };
+ key key_{};
+
+ public:
+ constexpr explicit input_event() = default;
+
+ static constexpr auto key_down(key const key) -> input_event { return input_event{ type::key_down, key }; }
+ static constexpr auto key_up(key const key) -> input_event { return input_event{ type::key_up, key }; }
+
+ constexpr operator bool() const { return type_ != type::none; }
+
+ constexpr auto get_type() const -> type { return type_; }
+ constexpr auto get_key() const -> key { return key_; }
+
+ private:
+ constexpr explicit input_event(type const type, key const key)
+ : type_{ type }
+ , key_{ key }
+ {
+ }
+ };
+
+ constexpr auto operator==(input_event const lhs, input_event const rhs)
+ {
+ return lhs.get_type() == rhs.get_type() && lhs.get_key() == rhs.get_key();
+ }
+
+ constexpr auto operator!=(input_event const lhs, input_event const rhs) { return !(lhs == rhs); }
+} // namespace nes::app \ No newline at end of file
diff --git a/lib/nes/app/input/input-manager.cc b/lib/nes/app/input/input-manager.cc
index 8131283..1af0f5b 100644
--- a/lib/nes/app/input/input-manager.cc
+++ b/lib/nes/app/input/input-manager.cc
@@ -15,14 +15,7 @@ namespace nes::app
return res;
}
- auto read_key_up() -> bool override { return false; }
- auto read_key_down() -> bool override { return false; }
- auto read_key_left() -> bool override { return false; }
- auto read_key_right() -> bool override { return false; }
- auto read_key_a() -> bool override { return false; }
- auto read_key_b() -> bool override { return false; }
- auto read_key_select() -> bool override { return false; }
- auto read_key_start() -> bool override { return false; }
+ auto read_buttons() -> sys::button_mask override { return sys::button_mask{}; }
};
} // namespace
diff --git a/lib/nes/app/input/key.cc b/lib/nes/app/input/key.cc
new file mode 100644
index 0000000..9cd7104
--- /dev/null
+++ b/lib/nes/app/input/key.cc
@@ -0,0 +1,88 @@
+#include "nes/app/input/key.hh"
+#include "nes/app/input/modifier-mask.hh"
+
+namespace nes::app
+{
+ auto to_ascii(key const k, modifier_mask const m) -> char
+ {
+ switch (k)
+ {
+ case key::digit_0: return m.contains(modifiers::shift) ? ')' : '0';
+ case key::digit_1: return m.contains(modifiers::shift) ? '!' : '1';
+ case key::digit_2: return m.contains(modifiers::shift) ? '@' : '2';
+ case key::digit_3: return m.contains(modifiers::shift) ? '#' : '3';
+ case key::digit_4: return m.contains(modifiers::shift) ? '$' : '4';
+ case key::digit_5: return m.contains(modifiers::shift) ? '%' : '5';
+ case key::digit_6: return m.contains(modifiers::shift) ? '^' : '6';
+ case key::digit_7: return m.contains(modifiers::shift) ? '&' : '7';
+ case key::digit_8: return m.contains(modifiers::shift) ? '*' : '8';
+ case key::digit_9: return m.contains(modifiers::shift) ? '(' : '9';
+ case key::letter_a: return m.contains(modifiers::shift) ? 'A' : 'a';
+ case key::letter_b: return m.contains(modifiers::shift) ? 'B' : 'b';
+ case key::letter_c: return m.contains(modifiers::shift) ? 'C' : 'c';
+ case key::letter_d: return m.contains(modifiers::shift) ? 'D' : 'd';
+ case key::letter_e: return m.contains(modifiers::shift) ? 'E' : 'e';
+ case key::letter_f: return m.contains(modifiers::shift) ? 'F' : 'f';
+ case key::letter_g: return m.contains(modifiers::shift) ? 'G' : 'g';
+ case key::letter_h: return m.contains(modifiers::shift) ? 'H' : 'h';
+ case key::letter_i: return m.contains(modifiers::shift) ? 'I' : 'i';
+ case key::letter_j: return m.contains(modifiers::shift) ? 'J' : 'j';
+ case key::letter_k: return m.contains(modifiers::shift) ? 'K' : 'k';
+ case key::letter_l: return m.contains(modifiers::shift) ? 'L' : 'l';
+ case key::letter_m: return m.contains(modifiers::shift) ? 'M' : 'm';
+ case key::letter_n: return m.contains(modifiers::shift) ? 'N' : 'n';
+ case key::letter_o: return m.contains(modifiers::shift) ? 'O' : 'o';
+ case key::letter_p: return m.contains(modifiers::shift) ? 'P' : 'p';
+ case key::letter_q: return m.contains(modifiers::shift) ? 'Q' : 'q';
+ case key::letter_r: return m.contains(modifiers::shift) ? 'R' : 'r';
+ case key::letter_s: return m.contains(modifiers::shift) ? 'S' : 's';
+ case key::letter_t: return m.contains(modifiers::shift) ? 'T' : 't';
+ case key::letter_u: return m.contains(modifiers::shift) ? 'U' : 'u';
+ case key::letter_v: return m.contains(modifiers::shift) ? 'V' : 'v';
+ case key::letter_w: return m.contains(modifiers::shift) ? 'W' : 'w';
+ case key::letter_x: return m.contains(modifiers::shift) ? 'X' : 'x';
+ case key::letter_y: return m.contains(modifiers::shift) ? 'Y' : 'y';
+ case key::letter_z: return m.contains(modifiers::shift) ? 'Z' : 'z';
+ case key::dash: return m.contains(modifiers::shift) ? '_' : '-';
+ case key::equal: return m.contains(modifiers::shift) ? '+' : '=';
+ case key::semicolon: return m.contains(modifiers::shift) ? ':' : ';';
+ case key::apostrophe: return m.contains(modifiers::shift) ? '"' : '\'';
+ case key::backslash: return m.contains(modifiers::shift) ? '|' : '\\';
+ case key::comma: return m.contains(modifiers::shift) ? '<' : ',';
+ case key::period: return m.contains(modifiers::shift) ? '>' : '.';
+ case key::slash: return m.contains(modifiers::shift) ? '?' : '/';
+ case key::bracket_open: return m.contains(modifiers::shift) ? '{' : '[';
+ case key::bracket_close: return m.contains(modifiers::shift) ? '}' : ']';
+ case key::tab: return '\t';
+ case key::enter: return '\n';
+ case key::space: return ' ';
+ case key::escape: return 0;
+ case key::backspace: return 0;
+ case key::caps_lock: return 0;
+ case key::control_left: return 0;
+ case key::control_right: return 0;
+ case key::shift_left: return 0;
+ case key::shift_right: return 0;
+ case key::alt_left: return 0;
+ case key::alt_right: return 0;
+ case key::arrow_left: return 0;
+ case key::arrow_right: return 0;
+ case key::arrow_up: return 0;
+ case key::arrow_down: return 0;
+ case key::f1: return 0;
+ case key::f2: return 0;
+ case key::f3: return 0;
+ case key::f4: return 0;
+ case key::f5: return 0;
+ case key::f6: return 0;
+ case key::f7: return 0;
+ case key::f8: return 0;
+ case key::f9: return 0;
+ case key::f10: return 0;
+ case key::f11: return 0;
+ case key::f12: return 0;
+ }
+
+ return 0;
+ }
+} // namespace nes::app \ No newline at end of file
diff --git a/lib/nes/app/input/key.hh b/lib/nes/app/input/key.hh
new file mode 100644
index 0000000..3156e65
--- /dev/null
+++ b/lib/nes/app/input/key.hh
@@ -0,0 +1,90 @@
+#pragma once
+
+#include "nes/common/types.hh"
+
+namespace nes::app
+{
+ class modifier_mask;
+
+ enum class key
+ {
+ digit_0,
+ digit_1,
+ digit_2,
+ digit_3,
+ digit_4,
+ digit_5,
+ digit_6,
+ digit_7,
+ digit_8,
+ digit_9,
+ letter_a,
+ letter_b,
+ letter_c,
+ letter_d,
+ letter_e,
+ letter_f,
+ letter_g,
+ letter_h,
+ letter_i,
+ letter_j,
+ letter_k,
+ letter_l,
+ letter_m,
+ letter_n,
+ letter_o,
+ letter_p,
+ letter_q,
+ letter_r,
+ letter_s,
+ letter_t,
+ letter_u,
+ letter_v,
+ letter_w,
+ letter_x,
+ letter_y,
+ letter_z,
+ escape,
+ dash,
+ equal,
+ backspace,
+ tab,
+ enter,
+ semicolon,
+ apostrophe,
+ backslash,
+ comma,
+ period,
+ slash,
+ space,
+ caps_lock,
+ bracket_open,
+ bracket_close,
+ control_left,
+ control_right,
+ shift_left,
+ shift_right,
+ alt_left,
+ alt_right,
+ arrow_left,
+ arrow_right,
+ arrow_up,
+ arrow_down,
+ f1,
+ f2,
+ f3,
+ f4,
+ f5,
+ f6,
+ f7,
+ f8,
+ f9,
+ f10,
+ f11,
+ f12,
+ };
+
+ inline constexpr auto key_count = static_cast<u32>(key::f12) + 1;
+
+ auto to_ascii(key, modifier_mask) -> char;
+} // namespace nes::app \ No newline at end of file
diff --git a/lib/nes/app/input/modifier-mask.hh b/lib/nes/app/input/modifier-mask.hh
new file mode 100644
index 0000000..e18cddb
--- /dev/null
+++ b/lib/nes/app/input/modifier-mask.hh
@@ -0,0 +1,39 @@
+#pragma once
+
+#include "nes/common/types.hh"
+
+namespace nes::app
+{
+ /// Keyboard modifiers.
+ class modifier_mask
+ {
+ u8 raw_value_{ 0 };
+
+ public:
+ constexpr explicit modifier_mask() = default;
+
+ static constexpr auto from_raw_value(u8 const raw_value) -> modifier_mask
+ {
+ return modifier_mask{ raw_value };
+ }
+
+ constexpr auto add(modifier_mask const other) -> void { raw_value_ |= other.raw_value_; }
+ constexpr auto remove(modifier_mask const other) -> void { raw_value_ ^= other.raw_value_; }
+ constexpr auto contains(modifier_mask const other) const -> bool { return (raw_value_ & other.raw_value_) == other.raw_value_; }
+ constexpr auto is_empty() const -> bool { return raw_value_ == 0; }
+ constexpr auto get_raw_value() const -> u8 { return raw_value_; }
+
+ private:
+ constexpr explicit modifier_mask(u8 const raw_value)
+ : raw_value_{ raw_value }
+ {
+ }
+ };
+
+ namespace modifiers
+ {
+ inline constexpr auto control = modifier_mask::from_raw_value(1 << 0);
+ inline constexpr auto shift = modifier_mask::from_raw_value(1 << 1);
+ inline constexpr auto alt = modifier_mask::from_raw_value(1 << 2);
+ } // namespace modifiers
+} // namespace nes::app \ No newline at end of file
diff --git a/lib/nes/app/ui/screen-title.cc b/lib/nes/app/ui/screen-title.cc
index f1e4f98..5a2f757 100644
--- a/lib/nes/app/ui/screen-title.cc
+++ b/lib/nes/app/ui/screen-title.cc
@@ -2,7 +2,6 @@
#include "nes/app/graphics/renderer.hh"
#include "nes/app/graphics/color.hh"
#include "nes/app/graphics/tiles/banner.hh"
-#include "nes/app/graphics/tiles/icons.hh"
#include "nes/app/input/input-manager.hh"
namespace nes::app
@@ -15,17 +14,35 @@ namespace nes::app
auto screen_title::render(renderer& renderer) -> void
{
renderer.render_fill(color::background_primary);
- renderer.render_text(10, 16, "Hello, world!", color::foreground_primary);
- renderer.render_mask_tile(14, 18, tiles::icon_directory, color::accent_primary);
- renderer.render_mask_tile(16, 18, tiles::icon_cartridge, color::accent_primary);
renderer.render_image(2, 4, tiles::banner);
+
+ if (selected_item_ == item::load_game) { renderer.render_text(10, 16, ">", color::accent_primary); }
+ renderer.render_text(12, 16, "Load Game", selected_item_ == item::load_game ? color::accent_primary : color::foreground_primary);
+
+ if (selected_item_ == item::settings) { renderer.render_text(10, 19, ">", color::accent_primary); }
+ renderer.render_text(12, 19, "Settings", selected_item_ == item::settings ? color::accent_primary : color::foreground_primary);
+
+ if (selected_item_ == item::help) { renderer.render_text(10, 22, ">", color::accent_primary); }
+ renderer.render_text(12, 22, "Help", selected_item_ == item::help ? color::accent_primary : color::foreground_primary);
}
auto screen_title::process_events() -> action
{
- if (input_manager_.get_keyboard().read_key_start())
+ while (auto event = input_manager_.get_keyboard().poll_event())
{
- return action::load_game;
+ if (event == input_event::key_down(key::space))
+ {
+ switch (selected_item_)
+ {
+ case item::load_game: selected_item_ = item::settings; break;
+ case item::settings: selected_item_ = item::help; break;
+ case item::help: selected_item_ = item::load_game; break;
+ }
+ }
+ else if (event == input_event::key_down(key::enter))
+ {
+ return action::load_game;
+ }
}
return action::none;
diff --git a/lib/nes/app/ui/screen-title.hh b/lib/nes/app/ui/screen-title.hh
index a8dfdc5..2ebc78a 100644
--- a/lib/nes/app/ui/screen-title.hh
+++ b/lib/nes/app/ui/screen-title.hh
@@ -11,7 +11,15 @@ namespace nes::app
/// Presents options to load games, adjust settings, and more.
class screen_title final : public screen
{
+ enum class item
+ {
+ load_game,
+ settings,
+ help,
+ };
+
input_manager& input_manager_;
+ item selected_item_{ item::load_game };
public:
explicit screen_title(input_manager&);
diff --git a/lib/nes/common/list.hh b/lib/nes/common/list.hh
index d7a7d9b..b1fddf0 100644
--- a/lib/nes/common/list.hh
+++ b/lib/nes/common/list.hh
@@ -106,10 +106,10 @@ namespace nes
}
/// Remove an item from the list.
- auto remove(T& item) -> void
+ auto remove(T& item) -> bool
{
node& item_node = item;
- if (!item_node.list_) { return; }
+ if (!item_node.list_) { return false; }
if (head_ == &item) { head_ = item_node.next_; }
if (tail_ == &item) { tail_ = item_node.prev_; }
@@ -119,12 +119,15 @@ namespace nes
item_node.next_ = nullptr;
item_node.prev_ = nullptr;
item_node.list_ = nullptr;
+
+ return true;
}
/// Remove an item from the list by its iterator.
- auto remove(iterator it) -> void
+ auto remove(iterator it) -> bool
{
- if (it != end()) { remove(**it); }
+ if (it != end()) { return remove(**it); }
+ return false;
}
};
} // namespace nes \ No newline at end of file