aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/bootstrap.cpp6
-rw-r--r--src/engine/data/style.hpp20
-rw-r--r--src/engine/graphics/component_renderer.cpp50
-rw-r--r--src/engine/graphics/component_renderer.hpp5
-rw-r--r--src/game/components/statusline.cpp77
-rw-r--r--src/game/components/statusline.hpp7
-rw-r--r--src/game/game.cpp9
-rw-r--r--src/game/status_manager.cpp10
-rw-r--r--src/game/status_manager.hpp4
-rw-r--r--src/interfaces/component.hpp12
-rw-r--r--src/interfaces/statusline.hpp8
-rw-r--r--src/util/ranges.hpp47
-rw-r--r--src/util/ranges_impl.hpp60
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 &section,
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 &section,
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);
+}
+