diff options
-rw-r--r-- | src/bootstrap.cpp | 6 | ||||
-rw-r--r-- | src/engine/data/style.hpp | 20 | ||||
-rw-r--r-- | src/engine/graphics/component_renderer.cpp | 50 | ||||
-rw-r--r-- | src/engine/graphics/component_renderer.hpp | 5 | ||||
-rw-r--r-- | src/game/components/statusline.cpp | 77 | ||||
-rw-r--r-- | src/game/components/statusline.hpp | 7 | ||||
-rw-r--r-- | src/game/game.cpp | 9 | ||||
-rw-r--r-- | src/game/status_manager.cpp | 10 | ||||
-rw-r--r-- | src/game/status_manager.hpp | 4 | ||||
-rw-r--r-- | src/interfaces/component.hpp | 12 | ||||
-rw-r--r-- | src/interfaces/statusline.hpp | 8 | ||||
-rw-r--r-- | src/util/ranges.hpp | 47 | ||||
-rw-r--r-- | src/util/ranges_impl.hpp | 60 |
13 files changed, 286 insertions, 29 deletions
diff --git a/src/bootstrap.cpp b/src/bootstrap.cpp index 0b9d972..12f7369 100644 --- a/src/bootstrap.cpp +++ b/src/bootstrap.cpp @@ -79,6 +79,12 @@ auto bootstrap() noexcept -> yacppdic::DIContainer return std::make_unique<Matrix<char>>(bounds); }); + di_container.bind<IMatrixFactory<ComponentElement>>().to_factory( + [](const Bounds &bounds) + { + return std::make_unique<Matrix<ComponentElement>>(bounds); + }); + di_container.bind<IStatusLineFactory>().to_factory( [&di_container](const Bounds &size) { diff --git a/src/engine/data/style.hpp b/src/engine/data/style.hpp new file mode 100644 index 0000000..50c1faa --- /dev/null +++ b/src/engine/data/style.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include <cstdint> +#include <optional> + +class Style +{ +public: + // Colors + std::optional<uint32_t> fg_color{}; + std::optional<uint32_t> bg_color{}; + + // Toggles + bool bold = false; + bool reset_before = false; + + // Spacing + uint32_t padding_left = 0U; + uint32_t padding_right = 0U; +}; diff --git a/src/engine/graphics/component_renderer.cpp b/src/engine/graphics/component_renderer.cpp index 1ab4c08..eb024bc 100644 --- a/src/engine/graphics/component_renderer.cpp +++ b/src/engine/graphics/component_renderer.cpp @@ -23,21 +23,39 @@ void ComponentRenderer::render( _cursor_controller->hide(); _cursor_controller->move_to(position); - const auto component_matrix = component->get(); - - const auto foreground_color = component->get_foreground_color(); - const auto background_color = component->get_background_color(); + _use_component_colors(component); - fmt::print( - "{}{}", - get_background_esc_seq(background_color), - get_foreground_esc_seq(foreground_color)); + const auto component_matrix = component->get(); for (const auto &row : *component_matrix) { - for (const auto &col : row) + for (const auto &element : row) { - std::cout.put(col); + if (element.style.reset_before) + { + fmt::print(RESET_ALL_MODES, fmt::arg("esc", ESC)); + _use_component_colors(component); + } + + const auto opt_element_fg_color = element.style.fg_color; + const auto opt_element_bg_color = element.style.bg_color; + + if (opt_element_fg_color.has_value()) + { + fmt::print("{}", get_foreground_esc_seq(opt_element_fg_color.value())); + } + + if (opt_element_bg_color.has_value()) + { + fmt::print("{}", get_background_esc_seq(opt_element_bg_color.value())); + } + + if (element.style.bold) + { + fmt::print(SET_BOLD_MODE, fmt::arg("esc", ESC)); + } + + std::cout.put(element.value); } const auto current_pos = _cursor_controller->where(); @@ -53,3 +71,15 @@ void ComponentRenderer::render( _cursor_controller->move_to(previous_pos); _cursor_controller->show(); } + +void ComponentRenderer::_use_component_colors( + const std::shared_ptr<IComponent> &component) noexcept +{ + const auto foreground_color = component->get_foreground_color(); + const auto background_color = component->get_background_color(); + + fmt::print( + "{}{}", + get_background_esc_seq(background_color), + get_foreground_esc_seq(foreground_color)); +} diff --git a/src/engine/graphics/component_renderer.hpp b/src/engine/graphics/component_renderer.hpp index 4f53e07..ff1bc47 100644 --- a/src/engine/graphics/component_renderer.hpp +++ b/src/engine/graphics/component_renderer.hpp @@ -8,6 +8,8 @@ #include <memory> +constexpr auto SET_BOLD_MODE = "{esc}[1m"; + class ComponentRenderer : public IComponentRenderer { public: @@ -20,4 +22,7 @@ public: private: std::shared_ptr<ICursorController> _cursor_controller; + + static void + _use_component_colors(const std::shared_ptr<IComponent> &component) noexcept; }; diff --git a/src/game/components/statusline.cpp b/src/game/components/statusline.cpp index 5b59a53..1892f86 100644 --- a/src/game/components/statusline.cpp +++ b/src/game/components/statusline.cpp @@ -3,15 +3,17 @@ #include "engine/data/bounds.hpp" #include "engine/data/vector2.hpp" #include "util/color.hpp" +#include "util/ranges.hpp" #include <fmt/color.h> +#include <optional> #include <utility> StatusLine::StatusLine(std::shared_ptr<ComponentMatrix> component_matrix) noexcept : _component_matrix(std::move(component_matrix)), _need_render(false) { - _component_matrix->fill(' '); + _component_matrix->fill(ComponentElement({.value = ' '})); } auto StatusLine::get() const noexcept -> const std::shared_ptr<ComponentMatrix> & @@ -39,11 +41,16 @@ auto StatusLine::get_background_color() const noexcept -> uint32_t return STATUSLINE_COLOR; } -void StatusLine::set_status( +void StatusLine::set_section_status( StatusLineSection section, const std::string_view &status, int32_t start) noexcept { + if (status.length() == 0U) + { + return; + } + _clear_section(section, start); auto section_start = _get_section_start_x(section); @@ -56,11 +63,60 @@ void StatusLine::set_status( const auto status_len = static_cast<int32_t>(status.length()); + const auto section_style = + _section_styles.contains(section) ? _section_styles.at(section) : Style(); + + auto is_style_applied = false; + + if (section_style.padding_left > 0U) + { + auto padding_remaining = section_style.padding_left; + + _component_matrix->set(pos, {.value = ' ', .style = section_style}); + padding_remaining--; + + for (auto index : IotaView(0U, padding_remaining)) + { + pos += Vector2::right(); + + _component_matrix->set(pos, {.value = ' '}); + } + + is_style_applied = true; + } + + pos += Vector2::right(); + + auto status_offset_start = 0U; + + // Apply the section style to the first status character if it hasn't + // already been applied + if (!is_style_applied) + { + _component_matrix->set(pos, {.value = status[0], .style = section_style}); + status_offset_start++; + pos += Vector2::right(); + + is_style_applied = true; + } + + const auto final_status_length = + status_len <= section_length ? status_len : section_length; + _matrix_write_string( pos, - status_len <= section_length - ? status - : status.substr(0U, static_cast<uint32_t>(section_length))); + status.substr(status_offset_start, static_cast<uint32_t>(final_status_length))); + + pos = pos.to_direction(Vector2::right(), final_status_length); + + for (auto index : IotaView(0U, section_style.padding_right)) + { + _component_matrix->set(pos, {.value = ' '}); + + pos += Vector2::right(); + } + + _component_matrix->set(pos, {.value = ' ', .style = {.reset_before = true}}); set_need_render(true); } @@ -70,6 +126,11 @@ void StatusLine::set_section_length(StatusLineSection section, int32_t length) n _section_lengths[section] = length; } +void StatusLine::set_section_style(StatusLineSection section, const Style &style) noexcept +{ + _section_styles[section] = style; +} + void StatusLine::_matrix_write_string( const Vector2 &position, const std::string_view &str) noexcept @@ -78,7 +139,7 @@ void StatusLine::_matrix_write_string( for (const auto &character : str) { - _component_matrix->set(working_pos, character); + _component_matrix->set(working_pos, {.value = character}); working_pos += Vector2::right(); } @@ -110,9 +171,9 @@ void StatusLine::_clear_section(StatusLineSection section, int32_t start) noexce auto section_length = _section_lengths[section]; - for (auto index = 0; index < section_length - start; index++) + for (auto index : IotaView(0, section_length - start)) { - _component_matrix->set(pos, ' '); + _component_matrix->set(pos, ComponentElement({.value = ' '})); pos += Vector2::right(); } diff --git a/src/game/components/statusline.hpp b/src/game/components/statusline.hpp index 8c1969b..e009174 100644 --- a/src/game/components/statusline.hpp +++ b/src/game/components/statusline.hpp @@ -3,6 +3,7 @@ #include "interfaces/matrix.hpp" #include "interfaces/statusline.hpp" +#include "engine/data/style.hpp" #include "engine/data/vector2.hpp" #include <memory> @@ -26,16 +27,20 @@ public: auto get_background_color() const noexcept -> uint32_t override; - void set_status( + void set_section_status( StatusLineSection section, const std::string_view &status, int32_t start) noexcept override; void set_section_length(StatusLineSection section, int32_t length) noexcept override; + void + set_section_style(StatusLineSection section, const Style &style) noexcept override; + private: std::shared_ptr<ComponentMatrix> _component_matrix; std::unordered_map<StatusLineSection, int32_t> _section_lengths; + std::unordered_map<StatusLineSection, Style> _section_styles; bool _need_render; void diff --git a/src/game/game.cpp b/src/game/game.cpp index e969fe1..fd53841 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -80,13 +80,20 @@ void Game::on_start() statusline->set_section_length(StatusLineSection::G, statusline_section_g_length); statusline->set_section_length(StatusLineSection::H, statusline_section_h_length); + statusline->set_section_style( + StatusLineSection::A, + {.bg_color = static_cast<uint32_t>(fmt::color::green), + .bold = true, + .padding_left = 1U, + .padding_right = 1U}); + _scene->register_component(statusline, Vector2({.x = 0, .y = 1})); _status_manager->bind(statusline); _minimum_cursor_pos_y = 2; - _status_manager->set_section_title(StatusLineSection::A, " "); + _status_manager->set_section_title(StatusLineSection::A, ""); _status_manager->set_section_title(StatusLineSection::B, "X: "); _status_manager->set_section_title(StatusLineSection::C, "Y: "); _status_manager->set_section_title(StatusLineSection::D, "Paused: "); diff --git a/src/game/status_manager.cpp b/src/game/status_manager.cpp index 36add4c..e4838cc 100644 --- a/src/game/status_manager.cpp +++ b/src/game/status_manager.cpp @@ -13,23 +13,23 @@ void StatusManager::set_section_title( const StatusLineSection §ion, const std::string_view &title) noexcept { - if (_title_lengths.contains(section)) + if (_section_title_lengths.contains(section)) { fmt::print(stderr, "Error: can't set statusbar section title more than once"); return; } - _statusline->set_status(section, title); + _statusline->set_section_status(section, title); - _title_lengths[section] = static_cast<int>(title.length()); + _section_title_lengths[section] = static_cast<int>(title.length()); } void StatusManager::set_section_body( const StatusLineSection §ion, const std::string_view &body) noexcept { - auto section_title_length = _title_lengths[section]; + auto section_title_length = _section_title_lengths[section]; - _statusline->set_status(section, body, section_title_length + 1); + _statusline->set_section_status(section, body, section_title_length + 1); } diff --git a/src/game/status_manager.hpp b/src/game/status_manager.hpp index 85faf3c..8224003 100644 --- a/src/game/status_manager.hpp +++ b/src/game/status_manager.hpp @@ -6,8 +6,8 @@ #include <yacppdic/auto_wirable.hpp> #include <memory> +#include <optional> #include <string_view> -#include <unordered_map> class StatusManager : public IStatusManager, public yacppdic::AutoWirable<IStatusManager, StatusManager> @@ -26,5 +26,5 @@ public: private: std::shared_ptr<IStatusLine> _statusline; - std::unordered_map<StatusLineSection, int32_t> _title_lengths; + std::unordered_map<StatusLineSection, int32_t> _section_title_lengths; }; diff --git a/src/interfaces/component.hpp b/src/interfaces/component.hpp index 7e1b132..2f62c95 100644 --- a/src/interfaces/component.hpp +++ b/src/interfaces/component.hpp @@ -2,13 +2,23 @@ #include "interfaces/matrix.hpp" +#include "engine/data/style.hpp" + #include <memory> +#include <optional> + +class ComponentElement +{ +public: + char value{}; + Style style{}; +}; // NOLINTNEXTLINE(cppcoreguidelines-special-member-functions) class IComponent { public: - using ComponentMatrix = IMatrix<char>; + using ComponentMatrix = IMatrix<ComponentElement>; virtual ~IComponent() = default; diff --git a/src/interfaces/statusline.hpp b/src/interfaces/statusline.hpp index ae25fd9..0634d2e 100644 --- a/src/interfaces/statusline.hpp +++ b/src/interfaces/statusline.hpp @@ -3,9 +3,12 @@ #include "interfaces/component.hpp" #include "engine/data/bounds.hpp" +#include "engine/data/style.hpp" #include <yacppdic/factory.hpp> +#include <memory> +#include <optional> #include <string_view> enum StatusLineSection @@ -25,13 +28,16 @@ class IStatusLine : public IComponent { public: // NOLINTNEXTLINE(google-default-arguments) - virtual void set_status( + virtual void set_section_status( StatusLineSection section, const std::string_view &status, int32_t start = 1) noexcept = 0; virtual void set_section_length(StatusLineSection section, int32_t length) noexcept = 0; + + virtual void + set_section_style(StatusLineSection section, const Style &style) noexcept = 0; }; using IStatusLineFactory = diff --git a/src/util/ranges.hpp b/src/util/ranges.hpp new file mode 100644 index 0000000..c47c7b5 --- /dev/null +++ b/src/util/ranges.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include <concepts> +#include <iterator> + +template <std::weakly_incrementable Value> +class IotaViewIterator +{ +public: + constexpr explicit IotaViewIterator(Value value) noexcept; + + constexpr auto operator++() noexcept -> const IotaViewIterator &; + constexpr auto operator++(int) noexcept -> IotaViewIterator; + + constexpr auto operator*() const noexcept -> Value; + + constexpr auto operator==(const IotaViewIterator &rhs) const noexcept -> bool; + constexpr auto operator!=(const IotaViewIterator &rhs) const noexcept -> bool; + +private: + Value _value; +}; + +/** + * A range factory that generates a sequence of elements by repeatedly incrementing an + * initial value. + * + * This class was created because C++20 ranges is a complete shitshow in Clang. + * https://github.com/llvm/llvm-project/issues/52696 + */ +template <std::weakly_incrementable Value, std::semiregular Bound> +requires std::equality_comparable_with<Value, Bound> && std::copyable<Value> +class IotaView +{ +public: + constexpr IotaView(Value value, Bound bound) noexcept; + + [[nodiscard]] constexpr auto begin() const noexcept -> IotaViewIterator<Value>; + + [[nodiscard]] constexpr auto end() const noexcept -> IotaViewIterator<Value>; + +private: + Value _value; + Bound _bound; +}; + +#include "ranges_impl.hpp" diff --git a/src/util/ranges_impl.hpp b/src/util/ranges_impl.hpp new file mode 100644 index 0000000..b18a11c --- /dev/null +++ b/src/util/ranges_impl.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include "ranges.hpp" + +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define IOTA_VIEW_ITERATOR(return_type) \ + template <std::weakly_incrementable Value> \ + constexpr return_type IotaViewIterator<Value> + +IOTA_VIEW_ITERATOR()::IotaViewIterator(Value value) noexcept : _value(value) {} + +IOTA_VIEW_ITERATOR(auto)::operator++() noexcept -> const IotaViewIterator & +{ + ++_value; + + return *this; +} + +IOTA_VIEW_ITERATOR(auto)::operator++(int) noexcept -> IotaViewIterator +{ + auto copy = *this; + + ++(*this); + + return copy; +} + +IOTA_VIEW_ITERATOR(auto)::operator*() const noexcept -> Value +{ + return _value; +} + +IOTA_VIEW_ITERATOR(auto)::operator==(const IotaViewIterator &rhs) const noexcept -> bool +{ + return _value == rhs._value; +} + +IOTA_VIEW_ITERATOR(auto)::operator!=(const IotaViewIterator &rhs) const noexcept -> bool +{ + return !(*this == rhs); +} + +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define IOTA_VIEW(return_type) \ + template <std::weakly_incrementable Value, std::semiregular Bound> \ + requires std::equality_comparable_with<Value, Bound> && std::copyable<Value> \ + constexpr return_type IotaView<Value, Bound> + +IOTA_VIEW()::IotaView(Value value, Bound bound) noexcept : _value(value), _bound(bound) {} + +IOTA_VIEW(auto)::begin() const noexcept -> IotaViewIterator<Value> +{ + return IotaViewIterator(_value); +} + +IOTA_VIEW(auto)::end() const noexcept -> IotaViewIterator<Value> +{ + return IotaViewIterator(_bound); +} + |