summaryrefslogtreecommitdiff
path: root/lib/nes/app
diff options
context:
space:
mode:
authorHannes Schulze <haschu0103@outlook.de>2025-02-04 23:39:24 +0100
committerHannes Schulze <haschu0103@outlook.de>2025-02-04 23:39:24 +0100
commit64308c9e520dbd8e72b675d0ba064ecdaf9bd494 (patch)
treeff8dc176d62340df9565e424f19b9d6349c4e8b8 /lib/nes/app
parent79553ab1fa3d707612e80dd03bf95ca65fa0c411 (diff)
add error handling to ui
Diffstat (limited to 'lib/nes/app')
-rw-r--r--lib/nes/app/CMakeLists.txt1
-rw-r--r--lib/nes/app/action.hh (renamed from lib/nes/app/ui/action.hh)29
-rw-r--r--lib/nes/app/application.cc48
-rw-r--r--lib/nes/app/application.hh4
-rw-r--r--lib/nes/app/file-browser.hh2
-rw-r--r--lib/nes/app/graphics/renderer.cc2
-rw-r--r--lib/nes/app/graphics/renderer.hh9
-rw-r--r--lib/nes/app/ui/CMakeLists.txt5
-rw-r--r--lib/nes/app/ui/screen-browser.cc22
-rw-r--r--lib/nes/app/ui/screen-browser.hh2
-rw-r--r--lib/nes/app/ui/screen-error.cc38
-rw-r--r--lib/nes/app/ui/screen-error.hh27
-rw-r--r--lib/nes/app/ui/screen.hh2
13 files changed, 148 insertions, 43 deletions
diff --git a/lib/nes/app/CMakeLists.txt b/lib/nes/app/CMakeLists.txt
index 6e5aa93..c0d6909 100644
--- a/lib/nes/app/CMakeLists.txt
+++ b/lib/nes/app/CMakeLists.txt
@@ -3,6 +3,7 @@ target_sources(
PRIVATE
application.hh
application.cc
+ action.hh
file-browser.hh)
add_subdirectory(input)
diff --git a/lib/nes/app/ui/action.hh b/lib/nes/app/action.hh
index 66f77f8..f63c17d 100644
--- a/lib/nes/app/ui/action.hh
+++ b/lib/nes/app/action.hh
@@ -1,6 +1,6 @@
#pragma once
-#include "nes/common/status.hh"
+#include "../common/status.hh"
#include <string_view>
namespace nes::app
@@ -16,6 +16,7 @@ namespace nes::app
go_to_browser,
go_to_settings,
go_to_help,
+ close_popup,
launch_game,
show_error,
};
@@ -23,35 +24,41 @@ namespace nes::app
private:
type type_{ type::none };
std::string_view file_name_;
+ std::string_view message_;
status error_{ status::success };
public:
explicit action() = default;
- static auto go_to_title() -> action { return action{ type::go_to_title, "", status::success }; }
- static auto go_to_browser() -> action { return action{ type::go_to_browser, "", status::success }; }
- static auto go_to_settings() -> action { return action{ type::go_to_settings, "", status::success }; }
- static auto go_to_help() -> action { return action{ type::go_to_help, "", status::success }; }
+ static auto go_to_title() -> action { return action{ type::go_to_title }; }
+ static auto go_to_browser() -> action { return action{ type::go_to_browser }; }
+ static auto go_to_settings() -> action { return action{ type::go_to_settings }; }
+ static auto go_to_help() -> action { return action{ type::go_to_help }; }
+ static auto close_popup() -> action { return action{ type::close_popup }; }
static auto launch_game(std::string_view const file_name) -> action
{
- return action{ type::launch_game, file_name, status::success };
+ auto res = action{ type::launch_game };
+ res.file_name_ = file_name;
+ return res;
}
- static auto show_error(status const error)
+ static auto show_error(std::string_view const message, status const error) -> action
{
- return action{ type::show_error, "", error };
+ auto res = action{ type::show_error };
+ res.message_ = message;
+ res.error_ = error;
+ return res;
}
auto get_type() const -> type { return type_; }
auto get_file_name() const -> std::string_view { return file_name_; }
+ auto get_message() const -> std::string_view { return message_; }
auto get_error() const -> status { return error_; }
private:
- explicit action(type const type, std::string_view const file_name, status const error)
+ explicit action(type const type)
: type_{ type }
- , file_name_{ file_name }
- , error_{ error }
{
}
};
diff --git a/lib/nes/app/application.cc b/lib/nes/app/application.cc
index 6b7253e..ac81840 100644
--- a/lib/nes/app/application.cc
+++ b/lib/nes/app/application.cc
@@ -3,19 +3,25 @@
#include "nes/app/input/input-device-controller.hh"
#include "nes/app/ui/screen.hh"
#include "nes/app/graphics/renderer.hh"
-#include "ui/action.hh"
+#include "nes/app/action.hh"
#include <iostream>
namespace nes::app
{
auto application::display_proxy::switch_buffers() -> void
{
+ auto r = renderer{ base };
+
if (screen)
{
- auto r = renderer{ base };
screen->render(r);
}
+ if (popup)
+ {
+ popup->render(r);
+ }
+
base.switch_buffers();
}
@@ -25,13 +31,19 @@ namespace nes::app
, file_browser_{ file_browser }
, screen_title_{ keyboard }
, screen_browser_{ keyboard, file_browser }
+ , screen_error_{ keyboard }
{
display_.screen = &screen_title_;
}
auto application::frame(std::chrono::microseconds const elapsed_time) -> void
{
- if (display_.screen)
+ if (display_.popup)
+ {
+ auto const a = display_.popup->process_events();
+ handle_action(a);
+ }
+ else if (display_.screen)
{
auto const a = display_.screen->process_events();
handle_action(a);
@@ -48,9 +60,12 @@ namespace nes::app
if (console_->get_status() != status::success)
{
+ // TODO: Present as error popup
std::cerr << "Invalid state: " << to_string(console_->get_status()) << std::endl;
std::abort();
}
+
+ // TODO: Handle escape key
}
else
{
@@ -72,12 +87,14 @@ namespace nes::app
{
console_.clear();
display_.screen = &screen_title_;
+ display_.popup = nullptr;
break;
}
case action::type::go_to_browser:
{
console_.clear();
display_.screen = &screen_browser_;
+ display_.popup = nullptr;
break;
}
case action::type::go_to_settings:
@@ -90,31 +107,46 @@ namespace nes::app
// TODO
break;
}
+ case action::type::close_popup:
+ {
+ display_.popup = nullptr;
+ break;
+ }
case action::type::launch_game:
{
u8 buffer[sys::cartridge::max_file_size];
auto length = u32{ 0 };
if (auto const s = file_browser_.load(a.get_file_name(), buffer, &length); s != status::success)
{
- std::cerr << "Unable to open cartridge file: " << to_string(console_->get_status()) << std::endl;
- std::abort();
+ show_error("Unable to open file", s);
+ break;
}
console_.emplace(display_, span{ buffer, length });
if (console_->get_status() != status::success)
{
- std::cerr << "Unable to load cartridge: " << to_string(console_->get_status()) << std::endl;
- std::abort();
+ show_error("Unable to load cartridge", console_->get_status());
+ console_.clear();
+ break;
}
display_.screen = nullptr;
+ display_.popup = nullptr;
break;
}
case action::type::show_error:
{
- // TODO
+ show_error(a.get_message(), a.get_error());
break;
}
}
}
+
+ auto application::show_error(std::string_view const message, status const error) -> void
+ {
+ screen_error_.set_message(message);
+ screen_error_.set_error(error);
+ display_.popup = &screen_error_;
+ }
+
} // namespace nes::app \ No newline at end of file
diff --git a/lib/nes/app/application.hh b/lib/nes/app/application.hh
index 724f2b6..81e6516 100644
--- a/lib/nes/app/application.hh
+++ b/lib/nes/app/application.hh
@@ -3,6 +3,7 @@
#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-error.hh"
#include "nes/sys/nes.hh"
#include "nes/common/box.hh"
#include "nes/common/display.hh"
@@ -27,6 +28,7 @@ namespace nes::app
{
public:
display& base;
+ screen* popup{ nullptr };
screen* screen{ nullptr };
explicit display_proxy(display& base)
@@ -45,6 +47,7 @@ namespace nes::app
box<sys::nes> console_{};
screen_title screen_title_;
screen_browser screen_browser_;
+ screen_error screen_error_;
public:
explicit application(display&, input_device_keyboard&, file_browser& file_browser);
@@ -62,5 +65,6 @@ namespace nes::app
private:
auto handle_action(action) -> void;
+ auto show_error(std::string_view message, status error) -> void;
};
} // namespace nes::app \ No newline at end of file
diff --git a/lib/nes/app/file-browser.hh b/lib/nes/app/file-browser.hh
index ef5343c..d956c80 100644
--- a/lib/nes/app/file-browser.hh
+++ b/lib/nes/app/file-browser.hh
@@ -30,7 +30,7 @@ namespace nes::app
char name_[max_name_length + 1]{};
public:
- item() = default;
+ explicit item() = default;
explicit item(item_type const type, std::string_view const name)
: type_{ type }
diff --git a/lib/nes/app/graphics/renderer.cc b/lib/nes/app/graphics/renderer.cc
index 6429a52..d512e71 100644
--- a/lib/nes/app/graphics/renderer.cc
+++ b/lib/nes/app/graphics/renderer.cc
@@ -129,7 +129,7 @@ namespace nes::app
i32 const x, i32 const y, u32 const width, u32 const height, color const c) -> void
{
if (c == color::transparent) { return; }
- if (x >= static_cast<i32>(width) || y >= static_cast<i32>(height)) { return; }
+ if (x >= static_cast<i32>(renderer::width) || y >= static_cast<i32>(renderer::height)) { return; }
auto const resolved = resolve_color(c);
auto const x_stop = std::min(x + static_cast<i32>(width), static_cast<i32>(renderer::width));
diff --git a/lib/nes/app/graphics/renderer.hh b/lib/nes/app/graphics/renderer.hh
index cfc1889..9b764a6 100644
--- a/lib/nes/app/graphics/renderer.hh
+++ b/lib/nes/app/graphics/renderer.hh
@@ -20,8 +20,8 @@ namespace nes::app
/// Renders tiles and other primitive shapes onto a display.
///
- /// The renderer sees the display as a canvas divided into 8x8 tiles. All elements (except shadows and borders) must
- /// be arranged on this 8x8 grid.
+ /// The renderer sees the display as a canvas divided into 8x8 tiles. All elements must be arranged on this 8x8
+ /// grid.
class renderer
{
display& display_;
@@ -37,11 +37,6 @@ namespace nes::app
auto operator=(renderer const&) -> renderer& = delete;
auto operator=(renderer&&) -> renderer& = delete;
- /// If set, add a drop shadow behind newly drawn opaque elements.
- auto set_shadow(u32 x_px, u32 y_px, color) -> void;
- /// If set, add an inner border inside newly drawn opaque elements.
- auto set_border(u32 px, color) -> void;
-
/// Render a colored tile at the given tile coordinates.
auto render_image_tile(i32 x, i32 y, image_tile const&) -> void;
/// Render an alpha-only tile at the given tile coordinates with the given color.
diff --git a/lib/nes/app/ui/CMakeLists.txt b/lib/nes/app/ui/CMakeLists.txt
index 0980e97..de42744 100644
--- a/lib/nes/app/ui/CMakeLists.txt
+++ b/lib/nes/app/ui/CMakeLists.txt
@@ -1,10 +1,11 @@
target_sources(
nes
PRIVATE
- action.hh
selection.hh
screen.hh
screen-title.hh
screen-title.cc
screen-browser.hh
- screen-browser.cc) \ No newline at end of file
+ screen-browser.cc
+ screen-error.hh
+ screen-error.cc) \ No newline at end of file
diff --git a/lib/nes/app/ui/screen-browser.cc b/lib/nes/app/ui/screen-browser.cc
index 14ddc23..a82e96d 100644
--- a/lib/nes/app/ui/screen-browser.cc
+++ b/lib/nes/app/ui/screen-browser.cc
@@ -11,13 +11,13 @@ namespace nes::app
renderer& renderer, item const& item, i32 const x, i32 const y, u32 const width,
color const color) const -> void
{
- switch (item.data.get_type())
+ switch (item.base.get_type())
{
case file_browser::item_type::file: renderer.render_mask_tile(x, y, tiles::icon_cartridge, color); break;
case file_browser::item_type::directory: renderer.render_mask_tile(x, y, tiles::icon_directory, color); break;
}
- renderer.render_text(x + 2, y, item.data.get_name(), color, text_attributes{}
+ renderer.render_text(x + 2, y, item.base.get_name(), color, text_attributes{}
.set_ellipsize_mode(ellipsize_mode::end)
.set_max_width(width - 2));
}
@@ -31,7 +31,7 @@ namespace nes::app
if (page == 0)
{
items[count].is_parent = true;
- items[count].data = file_browser::item{ file_browser::item_type::directory, "[Parent Directory]" };
+ items[count].base = file_browser::item{ file_browser::item_type::directory, "[Parent Directory]" };
count += 1;
}
else
@@ -48,7 +48,7 @@ namespace nes::app
if (!file_browser_.read_next(&item)) { break; }
items[count].is_parent = false;
- items[count].data = item;
+ items[count].base = item;
count += 1;
}
@@ -101,7 +101,7 @@ namespace nes::app
{
if (auto const s = file_browser_.navigate_up(); s != status::success)
{
- return action::show_error(s);
+ return action::show_error("Unable to navigate to directory", s);
}
refresh();
navigation_steps_ -= 1;
@@ -120,23 +120,23 @@ namespace nes::app
{
if (auto const s = file_browser_.navigate_up(); s != status::success)
{
- return action::show_error(s);
+ return action::show_error("Unable to navigate to directory", s);
}
refresh();
if (navigation_steps_ > 0) { navigation_steps_ -= 1; }
}
- else if (selected->data.get_type() == file_browser::item_type::directory)
+ else if (selected->base.get_type() == file_browser::item_type::directory)
{
- if (auto const s = file_browser_.navigate(selected->data.get_name()); s != status::success)
+ if (auto const s = file_browser_.navigate(selected->base.get_name()); s != status::success)
{
- return action::show_error(s);
+ return action::show_error("Unable to navigate to directory", s);
}
refresh();
navigation_steps_ += 1;
}
- else if (selected->data.get_type() == file_browser::item_type::file)
+ else if (selected->base.get_type() == file_browser::item_type::file)
{
- return action::launch_game(selected->data.get_name());
+ return action::launch_game(selected->base.get_name());
}
}
}
diff --git a/lib/nes/app/ui/screen-browser.hh b/lib/nes/app/ui/screen-browser.hh
index 3870f02..0ccf0b6 100644
--- a/lib/nes/app/ui/screen-browser.hh
+++ b/lib/nes/app/ui/screen-browser.hh
@@ -16,7 +16,7 @@ namespace nes::app
struct item
{
bool is_parent{ false };
- file_browser::item data{ file_browser::item_type::directory, "" };
+ file_browser::item base{};
};
class selection_impl final : public selection<item, page_size>
diff --git a/lib/nes/app/ui/screen-error.cc b/lib/nes/app/ui/screen-error.cc
new file mode 100644
index 0000000..67a3244
--- /dev/null
+++ b/lib/nes/app/ui/screen-error.cc
@@ -0,0 +1,38 @@
+#include "nes/app/ui/screen-error.hh"
+#include "nes/app/graphics/renderer.hh"
+#include "nes/app/graphics/color.hh"
+#include "nes/app/input/input-device-keyboard.hh"
+
+namespace nes::app
+{
+ screen_error::screen_error(input_device_keyboard& keyboard)
+ : keyboard_{ keyboard }
+ {
+ }
+
+ auto screen_error::render(renderer& renderer) -> void
+ {
+ // TODO: Nicer box
+ renderer.render_rect(2, 9, 28, 5, color::background_secondary);
+
+ auto const attrs = text_attributes{}
+ .set_alignment(text_alignment::center)
+ .set_ellipsize_mode(ellipsize_mode::end)
+ .set_max_width(26);
+ renderer.render_text_format(16, 10, color::foreground_primary, attrs, "{}:", message_);
+ renderer.render_text(16, 12, to_string(error_), color::foreground_primary, attrs);
+ }
+
+ auto screen_error::process_events() -> action
+ {
+ while (auto const event = keyboard_.poll_event())
+ {
+ if (event == input_event::key_down(key::enter))
+ {
+ return action::close_popup();
+ }
+ }
+
+ return action{};
+ }
+} // namespace nes::app \ No newline at end of file
diff --git a/lib/nes/app/ui/screen-error.hh b/lib/nes/app/ui/screen-error.hh
new file mode 100644
index 0000000..ec877e1
--- /dev/null
+++ b/lib/nes/app/ui/screen-error.hh
@@ -0,0 +1,27 @@
+#pragma once
+
+#include "nes/app/ui/screen.hh"
+
+namespace nes::app
+{
+ class input_device_keyboard;
+
+ /// An error popup.
+ class screen_error final : public screen
+ {
+ input_device_keyboard& keyboard_;
+ std::string_view message_;
+ status error_{ status::success };
+
+ public:
+ explicit screen_error(input_device_keyboard&);
+
+ auto get_message() const -> std::string_view { return message_; }
+ auto get_error() const -> status { return error_; }
+ auto set_message(std::string_view const v) { message_ = v; }
+ auto set_error(status const v) { error_ = v; }
+
+ auto render(renderer&) -> void override;
+ auto process_events() -> action override;
+ };
+} // namespace nes::app \ No newline at end of file
diff --git a/lib/nes/app/ui/screen.hh b/lib/nes/app/ui/screen.hh
index 07e9d6f..6d30b7a 100644
--- a/lib/nes/app/ui/screen.hh
+++ b/lib/nes/app/ui/screen.hh
@@ -1,6 +1,6 @@
#pragma once
-#include "nes/app/ui/action.hh"
+#include "nes/app/action.hh"
namespace nes::app
{