From 4ec6a2ae12b6adb843c0777649ff45a741ca6cbc Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Tue, 23 Jul 2024 15:08:19 +0200 Subject: domain: redesign turn_order --- .vscode/settings.json | 3 +- app/include/turns/app/widgets/turn_order_view.hpp | 1 - .../turns/app/windows/participant_editor.hpp | 6 +- app/src/widgets/participant_row.cpp | 19 +- app/src/widgets/turn_order_view.cpp | 20 +- app/src/windows/participant_editor.cpp | 12 +- app/src/windows/tracker.cpp | 26 +- app/tests/windows/participant_editor.cpp | 4 +- domain/CMakeLists.txt | 2 + domain/include/turns/domain/participant.hpp | 46 ++- domain/include/turns/domain/turn_order.hpp | 111 +++--- domain/src/participant.cpp | 76 +--- domain/src/turn_order.cpp | 237 +++++------ domain/tests/participant.cpp | 90 +---- domain/tests/register_types.cpp | 13 + domain/tests/turn_order.cpp | 434 +++++---------------- test_support/CMakeLists.txt | 1 + test_support/src/glib_main.cpp | 9 + 18 files changed, 390 insertions(+), 720 deletions(-) create mode 100644 domain/tests/register_types.cpp diff --git a/.vscode/settings.json b/.vscode/settings.json index 3073d74..b1a65b9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,7 +9,8 @@ "gobj", "refptr", "sigc", - "ustring" + "ustring", + "vfunc" ], "editor.tabSize": 2, diff --git a/app/include/turns/app/widgets/turn_order_view.hpp b/app/include/turns/app/widgets/turn_order_view.hpp index 7b7556f..15524a9 100644 --- a/app/include/turns/app/widgets/turn_order_view.hpp +++ b/app/include/turns/app/widgets/turn_order_view.hpp @@ -27,7 +27,6 @@ namespace turns::app::widgets explicit turn_order_view(Glib::RefPtr model = {}); private: - auto handle_active_participant_changed() -> void; auto handle_create_row(Glib::RefPtr const item) -> Gtk::Widget *; Glib::RefPtr m_model; diff --git a/app/include/turns/app/windows/participant_editor.hpp b/app/include/turns/app/windows/participant_editor.hpp index 764b9ba..23d0569 100644 --- a/app/include/turns/app/windows/participant_editor.hpp +++ b/app/include/turns/app/windows/participant_editor.hpp @@ -21,9 +21,9 @@ namespace turns::app::windows struct participant_editor : Gtk::Widget { - using signal_finished_type = sigc::signal().get_name()), - decltype(std::declval().get_priority()), - decltype(std::declval().get_disposition()))>; + using signal_finished_type = sigc::signal().name().get_value()), + decltype(std::declval().priority().get_value()), + decltype(std::declval().disposition().get_value()))>; participant_editor(BaseObjectType * base, Glib::RefPtr const builder, Glib::RefPtr obj = {}); diff --git a/app/src/widgets/participant_row.cpp b/app/src/widgets/participant_row.cpp index 3494834..87cc217 100644 --- a/app/src/widgets/participant_row.cpp +++ b/app/src/widgets/participant_row.cpp @@ -73,14 +73,14 @@ namespace turns::app::widgets if (participant) { - Glib::Binding::bind_property(participant->property_name(), m_title->property_label(), Glib::Binding::Flags::SYNC_CREATE); + Glib::Binding::bind_property(participant->name(), m_title->property_label(), Glib::Binding::Flags::SYNC_CREATE); - Glib::Binding::bind_property(participant->property_priority(), + Glib::Binding::bind_property(participant->priority(), m_subtitle->property_label(), Glib::Binding::Flags::SYNC_CREATE, [](auto n) { return std::vformat(_(lang::priority_number), std::make_format_args(n)); }); - Glib::Binding::bind_property(participant->property_disposition(), + Glib::Binding::bind_property(participant->disposition(), m_toggle_defeated->property_css_classes(), Glib::Binding::Flags::SYNC_CREATE, [this](auto value) { @@ -92,6 +92,19 @@ namespace turns::app::widgets classes.push_back(css_class_for(value)); return classes; }); + + Glib::Binding::bind_property(participant->is_active(), property_css_classes(), Glib::Binding::Flags::SYNC_CREATE, [this](auto value) { + auto classes = get_css_classes(); + if (!value) + { + std::erase(classes, "active-participant"); + } + else + { + classes.push_back("active-participant"); + } + return classes; + }); } } diff --git a/app/src/widgets/turn_order_view.cpp b/app/src/widgets/turn_order_view.cpp index ccc6d25..67e0afa 100644 --- a/app/src/widgets/turn_order_view.cpp +++ b/app/src/widgets/turn_order_view.cpp @@ -25,35 +25,21 @@ namespace turns::app::widgets { if (model) { - m_view->bind_model(m_model->list_model(), sigc::mem_fun(*this, &turn_order_view::handle_create_row)); - m_model->property_active_participant().signal_changed().connect( - sigc::mem_fun(*this, &turn_order_view::handle_active_participant_changed)); + m_view->bind_model(m_model, sigc::mem_fun(*this, &turn_order_view::handle_create_row)); } } - auto turn_order_view::handle_active_participant_changed() -> void - { - std::ranges::for_each(m_view->get_children(), [](auto c) { c->remove_css_class("active-participant"); }); - - auto index = m_model->active_participant(); - if (index != std::numeric_limits::max()) - { - auto row = m_view->get_row_at_index(index); - row->add_css_class("active-participant"); - row->grab_focus(); - } - } auto turn_order_view::handle_create_row(Glib::RefPtr const item) -> Gtk::Widget * { auto participant = std::dynamic_pointer_cast(item); auto row = Gtk::make_managed(participant); - Glib::Binding::bind_property(m_model->property_running(), + Glib::Binding::bind_property(m_model->is_running(), row->property_delete_enabled(), Glib::Binding::Flags::SYNC_CREATE | Glib::Binding::Flags::INVERT_BOOLEAN); - Glib::Binding::bind_property(m_model->property_running(), + Glib::Binding::bind_property(m_model->is_running(), row->property_edit_enabled(), Glib::Binding::Flags::SYNC_CREATE | Glib::Binding::Flags::INVERT_BOOLEAN); diff --git a/app/src/windows/participant_editor.cpp b/app/src/windows/participant_editor.cpp index 1c97442..0b35c72 100644 --- a/app/src/windows/participant_editor.cpp +++ b/app/src/windows/participant_editor.cpp @@ -42,9 +42,9 @@ namespace turns::app::windows if (m_participant) { - gtk_editable_set_text(GTK_EDITABLE(m_name), m_participant->property_name().get_value().c_str()); - adw_spin_row_set_value(m_priority, m_participant->property_priority().get_value()); - adw_combo_row_set_selected(m_disposition, static_cast(m_participant->get_disposition())); + gtk_editable_set_text(GTK_EDITABLE(m_name), m_participant->name().get_value().c_str()); + adw_spin_row_set_value(m_priority, m_participant->priority()); + adw_combo_row_set_selected(m_disposition, static_cast(m_participant->disposition().get_value())); } } @@ -66,9 +66,9 @@ namespace turns::app::windows if (m_participant) { - m_participant->set_name(name); - m_participant->set_priority(priority); - m_participant->set_disposition(disposition); + m_participant->name() = name; + m_participant->priority() = priority; + m_participant->disposition() = disposition; } m_signal_finished.emit(name, priority, disposition); diff --git a/app/src/windows/tracker.cpp b/app/src/windows/tracker.cpp index b7520dd..11f4642 100644 --- a/app/src/windows/tracker.cpp +++ b/app/src/windows/tracker.cpp @@ -51,16 +51,16 @@ namespace turns::app::windows m_stack->add(*m_turn_order_view); // clang-format off - Glib::Binding::bind_property(m_turn_order->property_empty(), + Glib::Binding::bind_property(m_turn_order->is_empty(), m_stack->property_visible_child(), Glib::Binding::Flags::SYNC_CREATE, [this](auto empty) { return empty ? m_empty : m_turn_order_view; }); - Glib::Binding::bind_property(m_turn_order->property_running(), + Glib::Binding::bind_property(m_turn_order->is_running(), m_controls->property_reveal_child(), Glib::Binding::Flags::SYNC_CREATE); - Glib::Binding::bind_property(m_turn_order->property_empty(), + Glib::Binding::bind_property(m_turn_order->is_empty(), m_subtitle, Glib::Binding::Flags::SYNC_CREATE, [](auto empty) { return empty ? _(lang::no_active_turn_order) : ""; }); @@ -84,7 +84,7 @@ namespace turns::app::windows { static_cast(param); auto index = Glib::VariantBase::cast_dynamic>(param); - auto participant = m_turn_order->get(index.get()); + auto participant = m_turn_order->get_typed_object(index.get()); auto [lifeline, dialog] = editor_for(participant); dialog->present(this); } @@ -112,17 +112,17 @@ namespace turns::app::windows { auto action = add_action("add_participant", sigc::mem_fun(*this, &tracker::handle_add_participant)); - Glib::Binding::bind_property(m_turn_order->property_running(), + Glib::Binding::bind_property(m_turn_order->is_running(), action->property_enabled(), Glib::Binding::Flags::SYNC_CREATE | Glib::Binding::Flags::INVERT_BOOLEAN); } // win.clear - // depends-on: turn_order:empty == false + // depends-on: turn_order:is_empty == false { auto action = add_action("clear", sigc::mem_fun(*m_turn_order, &domain::turn_order::clear)); - Glib::Binding::bind_property(m_turn_order->property_empty(), + Glib::Binding::bind_property(m_turn_order->is_empty(), action->property_enabled(), Glib::Binding::Flags::SYNC_CREATE | Glib::Binding::Flags::INVERT_BOOLEAN); } @@ -132,7 +132,7 @@ namespace turns::app::windows { auto action = add_action("next", sigc::mem_fun(*m_turn_order, &domain::turn_order::next)); - Glib::Binding::bind_property(m_turn_order->property_running(), action->property_enabled(), Glib::Binding::Flags::SYNC_CREATE); + Glib::Binding::bind_property(m_turn_order->is_running(), action->property_enabled(), Glib::Binding::Flags::SYNC_CREATE); } // win.previous @@ -140,19 +140,19 @@ namespace turns::app::windows { auto action = add_action("previous", sigc::mem_fun(*m_turn_order, &domain::turn_order::previous)); - Glib::Binding::bind_property(m_turn_order->property_has_previous(), action->property_enabled(), Glib::Binding::Flags::SYNC_CREATE); + Glib::Binding::bind_property(m_turn_order->has_previous(), action->property_enabled(), Glib::Binding::Flags::SYNC_CREATE); } // win.start - // depends-on: turn_order:empty == false + // depends-on: turn_order:is_empty == false { auto action = add_action("start", sigc::mem_fun(*m_turn_order, &domain::turn_order::start)); - Glib::Binding::bind_property(m_turn_order->property_empty(), + Glib::Binding::bind_property(m_turn_order->is_running(), action->property_enabled(), Glib::Binding::Flags::SYNC_CREATE | Glib::Binding::Flags::INVERT_BOOLEAN); - Glib::Binding::bind_property(m_turn_order->property_running(), + Glib::Binding::bind_property(m_turn_order->is_running(), m_start->property_visible(), Glib::Binding::Flags::SYNC_CREATE | Glib::Binding::Flags::INVERT_BOOLEAN); } @@ -162,7 +162,7 @@ namespace turns::app::windows { auto action = add_action("stop", sigc::mem_fun(*this, &tracker::handle_stop)); - Glib::Binding::bind_property(m_turn_order->property_running(), action->property_enabled(), Glib::Binding::Flags::SYNC_CREATE); + Glib::Binding::bind_property(m_turn_order->is_running(), action->property_enabled(), Glib::Binding::Flags::SYNC_CREATE); } // win.delete diff --git a/app/tests/windows/participant_editor.cpp b/app/tests/windows/participant_editor.cpp index 5808740..9f73861 100644 --- a/app/tests/windows/participant_editor.cpp +++ b/app/tests/windows/participant_editor.cpp @@ -95,13 +95,13 @@ namespace turns::app::windows::tests SECTION("has its name field set according to its participant") { auto widget = GTK_EDITABLE(builder->get_widget("name")->gobj()); - REQUIRE(gtk_editable_get_text(widget) == participant->get_name()); + REQUIRE(gtk_editable_get_text(widget) == participant->name().get_value()); } SECTION("has its priority field set according to its participant") { auto widget = ADW_SPIN_ROW(builder->get_widget("priority")->gobj()); - REQUIRE(adw_spin_row_get_value(widget) == participant->get_priority()); + REQUIRE(adw_spin_row_get_value(widget) == participant->priority()); } SECTION("allows binding to the finished signal") diff --git a/domain/CMakeLists.txt b/domain/CMakeLists.txt index 58a7900..ec9eb62 100644 --- a/domain/CMakeLists.txt +++ b/domain/CMakeLists.txt @@ -37,6 +37,8 @@ target_link_options("domain" PRIVATE # Tests add_executable("domain-tests" + "tests/register_types.cpp" + "tests/disposition.cpp" "tests/participant.cpp" "tests/turn_order.cpp" diff --git a/domain/include/turns/domain/participant.hpp b/domain/include/turns/domain/participant.hpp index d845c77..b51425d 100644 --- a/domain/include/turns/domain/participant.hpp +++ b/domain/include/turns/domain/participant.hpp @@ -13,34 +13,44 @@ namespace turns::domain { - struct participant : Glib::Object { auto static create(Glib::ustring name, float priority, disposition disposition) -> Glib::RefPtr; + participant(); participant(Glib::ustring name, float priority, disposition disposition); auto operator<=>(participant const & other) const noexcept -> std::partial_ordering; - auto property_disposition() -> Glib::PropertyProxy; - auto property_disposition() const -> Glib::PropertyProxy_ReadOnly; - auto get_disposition() const noexcept -> disposition; - auto set_disposition(disposition value) -> void; - - auto property_name() -> Glib::PropertyProxy; - auto property_name() const -> Glib::PropertyProxy_ReadOnly; - auto get_name() const -> Glib::ustring; - auto set_name(Glib::ustring value) -> void; - - auto property_priority() -> Glib::PropertyProxy; - auto property_priority() const -> Glib::PropertyProxy_ReadOnly; - auto get_priority() const noexcept -> float; - auto set_priority(float value) -> void; + template + auto disposition(this Self && self) + { + return self.m_disposition.get_proxy(); + } + + template + auto is_active(this Self && self) + { + return self.m_is_active.get_proxy(); + } + + template + auto name(this Self && self) + { + return self.m_name.get_proxy(); + } + + template + auto priority(this Self && self) + { + return self.m_priority.get_proxy(); + } private: - Glib::Property m_disposition; - Glib::Property m_name; - Glib::Property m_priority; + Glib::Property m_disposition{*this, "disposition", domain::disposition::neutral}; + Glib::Property m_is_active{*this, "active", false}; + Glib::Property m_name{*this, "name", ""}; + Glib::Property m_priority{*this, "priority", 0.0f}; }; } // namespace turns::domain diff --git a/domain/include/turns/domain/turn_order.hpp b/domain/include/turns/domain/turn_order.hpp index 3b42562..ca44b62 100644 --- a/domain/include/turns/domain/turn_order.hpp +++ b/domain/include/turns/domain/turn_order.hpp @@ -4,9 +4,12 @@ #include "turns/domain/disposition.hpp" #include "turns/domain/participant.hpp" +#include #include +#include +#include -#include +#include #include #include #include @@ -14,91 +17,67 @@ namespace turns::domain { - struct turn_order : Glib::Object + struct turn_order : Gio::ListModel, + Glib::Object { + using value_type = Glib::RefPtr; + using container_type = std::vector; + using iterator = container_type::iterator; + using const_iterator = container_type::const_iterator; + using active_participant_type = unsigned int; - using empty_type = bool; + using is_empty_type = bool; using has_next_type = bool; using has_previous_type = bool; - using running_type = bool; - using round_type = unsigned int; + using is_running_type = bool; + using round_number_type = unsigned int; auto static constexpr invalid_participant_index = std::numeric_limits::max(); + auto static constexpr invalid_round_number = std::numeric_limits::max(); - auto static create() -> Glib::RefPtr; - + /** Life-time */ turn_order(); - /** Modifiers */ + auto static create() -> Glib::RefPtr; + + /** Properties */ + auto is_empty() const -> Glib::PropertyProxy_ReadOnly; + auto has_next() const -> Glib::PropertyProxy_ReadOnly; + auto has_previous() const -> Glib::PropertyProxy_ReadOnly; + auto is_running() const -> Glib::PropertyProxy_ReadOnly; + auto round_number() const -> Glib::PropertyProxy_ReadOnly; + /** Element Modifications */ auto add(Glib::ustring const & name, float priority, disposition disposition) -> void; auto clear() -> void; + auto remove(unsigned index) -> void; + + /** Turn Modification */ auto next() -> void; auto previous() -> void; - auto remove(unsigned int index) -> void; - auto reset() -> void; auto start() -> void; auto stop() -> void; - /** Querries */ - - /** - * Get the index of the currently active participant of this turn order, if any. - * - * @returns an unsigned integer in the range [0, size()) if there is an active participant, or turn_order::invalid_participant_index otherwise. - */ - auto active_participant() const noexcept -> active_participant_type; - - /** - * Check if this turn order is empty. - */ - auto empty() const noexcept -> empty_type; - - /** - * Get the actor at the specified position in this turn order. - * - * @return a valid pointer to a participant object if the index was valid, nullptr otherwise. - */ - auto get(unsigned int index) const noexcept -> Glib::RefPtr; - - /** - * Get the underlying list model, to be used with list views. - */ - auto list_model() -> Glib::RefPtr; - - /** - * Get the current round. - */ - auto round() const noexcept -> round_type; - - /** - * Check if this turn order is currently running. - */ - auto running() const noexcept -> running_type; - - /** - * Get the size of this turn order - */ - auto size() const noexcept -> unsigned int; + private: + auto get_item_type_vfunc() -> GType override; + auto get_n_items_vfunc() -> unsigned override; + auto get_item_vfunc(unsigned position) -> void * override; - /** Properties */ + /** Signal handlers */ + auto handle_priority_changed(value_type entry) -> void; - auto property_active_participant() const -> Glib::PropertyProxy_ReadOnly; - auto property_empty() const -> Glib::PropertyProxy_ReadOnly; - auto property_has_next() const -> Glib::PropertyProxy_ReadOnly; - auto property_has_previous() const -> Glib::PropertyProxy_ReadOnly; - auto property_running() const -> Glib::PropertyProxy_ReadOnly; - auto property_round() const -> Glib::PropertyProxy_ReadOnly; + /** Data management */ + auto find(value_type entry) const -> const_iterator; + auto insert(value_type entry) -> const_iterator; - private: - Glib::RefPtr> m_model; - - Glib::Property m_active_participant; - Glib::Property m_empty; - Glib::Property m_has_next; - Glib::Property m_has_previous; - Glib::Property m_running; - Glib::Property m_round; + container_type m_data{}; + std::optional m_active{}; + + Glib::Property m_has_next{*this, "has-next", false}; + Glib::Property m_has_previous{*this, "has-previous", false}; + Glib::Property m_is_empty{*this, "is-empty", true}; + Glib::Property m_is_running{*this, "is-running", false}; + Glib::Property m_round_number{*this, "round-number", invalid_round_number}; }; } // namespace turns::domain diff --git a/domain/src/participant.cpp b/domain/src/participant.cpp index 5265eb3..6f0efb1 100644 --- a/domain/src/participant.cpp +++ b/domain/src/participant.cpp @@ -3,87 +3,33 @@ #include #include +#include #include namespace turns::domain { - - auto participant::create(Glib::ustring name, float priority, disposition disposition) -> Glib::RefPtr + auto participant::create(Glib::ustring name, float priority, domain::disposition disposition) -> Glib::RefPtr { return Glib::make_refptr_for_instance(new participant{name, priority, disposition}); } - participant::participant(Glib::ustring name, float priority, disposition disposition) + participant::participant() : Glib::ObjectBase{typeid(participant)} - , m_disposition{*this, "disposition", disposition} - , m_name{*this, "name", name} - , m_priority{*this, "priority", priority} - { - } - - auto participant::operator<=>(participant const & other) const noexcept -> std::partial_ordering - { - return m_priority <=> other.m_priority; - } - - auto participant::get_disposition() const noexcept -> disposition - { - return m_disposition; - } - - auto participant::set_disposition(disposition value) -> void - { - m_disposition = value; - } - - auto participant::get_name() const -> Glib::ustring - { - return m_name; - } - - auto participant::set_name(Glib::ustring value) -> void - { - m_name = value; - } - - auto participant::get_priority() const noexcept -> float - { - return m_priority; - } - - auto participant::set_priority(float value) -> void + , Glib::Object{} { - m_priority = value; } - auto participant::property_disposition() -> Glib::PropertyProxy + participant::participant(Glib::ustring name, float priority, domain::disposition disposition) + : participant() { - return m_disposition.get_proxy(); + m_name = name; + m_priority = priority; + m_disposition = disposition; } - auto participant::property_disposition() const -> Glib::PropertyProxy_ReadOnly - { - return m_disposition.get_proxy(); - } - - auto participant::property_name() -> Glib::PropertyProxy - { - return m_name.get_proxy(); - } - - auto participant::property_name() const -> Glib::PropertyProxy_ReadOnly - { - return m_name.get_proxy(); - } - - auto participant::property_priority() -> Glib::PropertyProxy - { - return m_priority.get_proxy(); - } - - auto participant::property_priority() const -> Glib::PropertyProxy_ReadOnly + auto participant::operator<=>(participant const & other) const noexcept -> std::partial_ordering { - return m_priority.get_proxy(); + return m_priority <=> other.m_priority; } } // namespace turns::domain \ No newline at end of file diff --git a/domain/src/turn_order.cpp b/domain/src/turn_order.cpp index 61ccdca..0f8b6e8 100644 --- a/domain/src/turn_order.cpp +++ b/domain/src/turn_order.cpp @@ -2,11 +2,11 @@ #include "turns/domain/participant.hpp" +#include #include #include #include -#include #include namespace turns::domain @@ -32,194 +32,203 @@ namespace turns::domain }; } // namespace + /** Construction */ + + turn_order::turn_order() + : Glib::ObjectBase{typeid(turn_order)} + , Gio::ListModel{} + { + } + auto turn_order::create() -> Glib::RefPtr { return Glib::make_refptr_for_instance(new turn_order{}); } - turn_order::turn_order() - : Glib::ObjectBase{typeid(turn_order)} - , m_model{Gio::ListStore::create()} - , m_active_participant(*this, "active_participant", invalid_participant_index) - , m_empty{*this, "empty", true} - , m_has_next{*this, "has-next", false} - , m_has_previous{*this, "has-previous", false} - , m_running{*this, "running", false} - , m_round{*this, "round", 0} + /** Queries */ + + auto turn_order::is_empty() const -> Glib::PropertyProxy_ReadOnly + { + return m_is_empty.get_proxy(); + } + + auto turn_order::has_next() const -> Glib::PropertyProxy_ReadOnly + { + return m_has_next.get_proxy(); + } + + auto turn_order::has_previous() const -> Glib::PropertyProxy_ReadOnly + { + return m_has_previous.get_proxy(); + } + + auto turn_order::is_running() const -> Glib::PropertyProxy_ReadOnly { - Glib::Binding::bind_property(m_model->property_n_items(), m_empty.get_proxy(), Glib::Binding::Flags::DEFAULT, [](auto n) { - return n == 0; - }); + return m_is_running.get_proxy(); + } + + auto turn_order::round_number() const -> Glib::PropertyProxy_ReadOnly + { + return m_round_number.get_proxy(); } /** Modifiers */ auto turn_order::add(Glib::ustring const & name, float priority, disposition disposition) -> void { - auto participant = participant::create(name, priority, disposition); - if (auto [found, index] = m_model->find(participant, equal_comparator); !found) - { - auto position = m_model->insert_sorted(participant, comparator); - participant->property_priority().signal_changed().connect([this] { m_model->sort(comparator); }); + auto entry = participant::create(name, priority, disposition); + entry->priority().signal_changed().connect(sigc::bind(sigc::mem_fun(*this, &turn_order::handle_priority_changed), entry)); + auto position = std::distance(m_data.cbegin(), insert(entry)); + items_changed(position, 0, 1); - if (m_active_participant != invalid_participant_index && position <= m_active_participant) - { - m_active_participant = m_active_participant + 1; - } + if (get_n_items() == 1) + { + m_is_empty = false; + m_has_next = true; } } auto turn_order::clear() -> void { - m_model->remove_all(); - m_active_participant = invalid_participant_index; + m_is_running = false; + m_is_empty = true; m_has_next = false; m_has_previous = false; - m_running = false; + m_active.reset(); + m_round_number = invalid_round_number; + + auto old_size = get_n_items(); + m_data.clear(); + items_changed(0, old_size, 0); } auto turn_order::next() -> void { - m_active_participant = (m_active_participant + 1) % size(); - if (!m_active_participant) + auto old_active = *m_active; + m_active = m_active.transform([this](auto index) { return (index + 1) % get_n_items(); }); + + m_has_previous = true; + m_data[old_active]->is_active() = false; + m_data[*m_active]->is_active() = true; + + if (m_active == 0) { - m_round = round() + 1; + m_round_number = m_round_number + 1; } - m_has_previous = m_active_participant || m_round; } auto turn_order::previous() -> void { - if (!m_has_previous) + if (!(m_has_previous && m_is_running)) { return; } - if (m_active_participant) - { - m_active_participant = m_active_participant - 1; - } - else if (m_round) + auto old_active = *m_active; + m_active = m_active.transform([this](auto index) { return index ? index - 1 : get_n_items() - 1; }); + + m_has_previous = m_round_number > 0 || m_active > 0; + m_data[old_active]->is_active() = false; + m_data[*m_active]->is_active() = true; + + if (m_active == 0) { - m_round = round() - 1; - m_active_participant = size() - 1; + m_round_number = m_round_number - 1; } - - m_has_previous = m_active_participant || m_round; } - auto turn_order::remove(unsigned int index) -> void + auto turn_order::remove(unsigned index) -> void { - m_model->remove(index); - if (empty()) + if (index >= get_n_items()) { - m_active_participant = invalid_participant_index; - m_has_next = false; - m_has_previous = false; - m_running = false; + return; } - else if (m_active_participant != invalid_participant_index) + + auto position = m_data.begin() + index; + m_data.erase(position); + items_changed(index, 1, 0); + if (get_n_items() == 0) { - if (m_active_participant > size() - 1) - { - m_active_participant = size() - 1; - } - else if (index <= m_active_participant) - { - m_active_participant = m_active_participant - 1; - } + m_is_empty = true; + m_is_running = false; + m_has_next = false; } } - auto turn_order::reset() -> void - { - m_running = false; - m_active_participant = 0; - } - auto turn_order::start() -> void { - if (m_active_participant == invalid_participant_index) + if (!m_active) { - m_active_participant = 0; + m_active = 0; + m_data[*m_active]->is_active() = true; } - m_running = true; - m_has_next = true; + if (m_round_number == invalid_round_number) + { + m_round_number = 0; + } + m_is_running = true; } auto turn_order::stop() -> void { - m_running = false; - m_has_next = false; - } - - /** Querries */ - - auto turn_order::active_participant() const noexcept -> active_participant_type - { - return m_active_participant; - } - - auto turn_order::empty() const noexcept -> bool - { - return m_empty; - } - - auto turn_order::get(unsigned int index) const noexcept -> Glib::RefPtr - { - return m_model->get_item(index); + m_is_running = false; } - auto turn_order::list_model() -> Glib::RefPtr - { - return m_model; - } + /** ListModel implementation */ - auto turn_order::round() const noexcept -> round_type + auto turn_order::get_item_type_vfunc() -> GType { - return m_round; + return participant::get_type(); } - auto turn_order::running() const noexcept -> running_type + auto turn_order::get_n_items_vfunc() -> unsigned { - return m_running; + return m_data.size(); } - auto turn_order::size() const noexcept -> unsigned int + auto turn_order::get_item_vfunc(unsigned position) -> void * { - return m_model->get_n_items(); + if (position >= get_n_items()) + { + return nullptr; + } + auto item = m_data[position]; + item->reference(); + return item->gobj(); } - /** Properties */ + /** Signal handlers */ - auto turn_order::property_active_participant() const -> Glib::PropertyProxy_ReadOnly + auto turn_order::handle_priority_changed(value_type entry) -> void { - return m_active_participant.get_proxy(); - } - - auto turn_order::property_empty() const -> Glib::PropertyProxy_ReadOnly - { - return m_empty.get_proxy(); - } + auto original_position = find(entry); + auto original_index = distance(m_data.cbegin(), original_position); + auto target_position = std::ranges::upper_bound(m_data, entry, comparator); + if (original_position == target_position) + { + return; + } - auto turn_order::property_has_next() const -> Glib::PropertyProxy_ReadOnly - { - return m_has_next.get_proxy(); + m_data.erase(original_position); + auto inserted_position = insert(entry); + items_changed(0, get_n_items(), get_n_items()); + if (m_active == original_index) + { + m_active = distance(m_data.cbegin(), inserted_position); + m_has_previous = m_round_number > 0 || m_active > 0; + } } - auto turn_order::property_has_previous() const -> Glib::PropertyProxy_ReadOnly - { - return m_has_previous.get_proxy(); - } + /** Data management */ - auto turn_order::property_running() const -> Glib::PropertyProxy_ReadOnly + auto turn_order::find(value_type entry) const -> const_iterator { - return m_running.get_proxy(); + return std::ranges::find(m_data, entry); } - auto turn_order::property_round() const -> Glib::PropertyProxy_ReadOnly + auto turn_order::insert(value_type entry) -> const_iterator { - return m_round.get_proxy(); + return m_data.insert(std::ranges::upper_bound(m_data, entry, comparator), entry); } } // namespace turns::domain \ No newline at end of file diff --git a/domain/tests/participant.cpp b/domain/tests/participant.cpp index dd244f4..e4e185c 100644 --- a/domain/tests/participant.cpp +++ b/domain/tests/participant.cpp @@ -22,123 +22,63 @@ namespace turns::domain::tests REQUIRE(participant::create(constructed_name, constructed_priority, constructed_disposition)); } - SECTION("allows access to its disposition via the associated accessors") + SECTION("allows access to its disposition") { SECTION("allowing to get it") { - REQUIRE(instance.get_disposition() == constructed_disposition); + REQUIRE(instance.disposition() == constructed_disposition); } SECTION("allowing to get it via a constant object") { auto const & cref = instance; - REQUIRE(cref.get_disposition() == constructed_disposition); + REQUIRE(cref.disposition() == constructed_disposition); } SECTION("allowing to set it") { - instance.set_disposition(disposition::hostile); - REQUIRE(instance.get_disposition() == disposition::hostile); + instance.disposition() = disposition::hostile; + REQUIRE(instance.disposition() == disposition::hostile); } } - SECTION("allows access to its disposition via the associated property") + SECTION("allows access to its name") { SECTION("allowing to get it") { - REQUIRE(instance.property_disposition() == constructed_disposition); + REQUIRE(instance.name() == constructed_name); } SECTION("allowing to get it via a constant object") { auto const & cref = instance; - REQUIRE(cref.property_disposition() == constructed_disposition); + REQUIRE(cref.name() == constructed_name); } SECTION("allowing to set it") { - instance.property_disposition() = disposition::hostile; - REQUIRE(instance.get_disposition() == disposition::hostile); + instance.name() = "replaced"; + REQUIRE(instance.name() == "replaced"); } } - SECTION("allows access to its name via the associated accessors") + SECTION("allows access to its priority") { SECTION("allowing to get it") { - REQUIRE(instance.get_name() == constructed_name); + REQUIRE(instance.priority() == constructed_priority); } SECTION("allowing to get it via a constant object") { auto const & cref = instance; - REQUIRE(cref.get_name() == constructed_name); + REQUIRE(cref.priority() == constructed_priority); } SECTION("allowing to set it") { - instance.set_name("replaced"); - REQUIRE(instance.get_name() == "replaced"); - } - } - - SECTION("allows access to its name via the associated property") - { - SECTION("allowing to get it") - { - REQUIRE(instance.property_name() == constructed_name); - } - - SECTION("allowing to get it via a constant object") - { - auto const & cref = instance; - REQUIRE(cref.property_name() == constructed_name); - } - - SECTION("allowing to set it") - { - instance.property_name() = "replaced"; - REQUIRE(instance.get_name() == "replaced"); - } - } - - SECTION("allows access to its priority via the associated accessors") - { - SECTION("allowing to get it") - { - REQUIRE(instance.get_priority() == constructed_priority); - } - - SECTION("allowing to get it via a constant object") - { - auto const & cref = instance; - REQUIRE(cref.get_priority() == constructed_priority); - } - - SECTION("allowing to set it") - { - instance.set_priority(3); - REQUIRE(instance.get_priority() == 3); - } - } - - SECTION("allows access to its priority via the associated property") - { - SECTION("allowing to get it") - { - REQUIRE(instance.property_priority() == constructed_priority); - } - - SECTION("allowing to get it via a constant object") - { - auto const & cref = instance; - REQUIRE(cref.property_priority() == constructed_priority); - } - - SECTION("allowing to set it") - { - instance.property_priority() = 4; - REQUIRE(instance.get_priority() == 4); + instance.priority() = 4; + REQUIRE(instance.priority() == 4); } } diff --git a/domain/tests/register_types.cpp b/domain/tests/register_types.cpp new file mode 100644 index 0000000..de8bb52 --- /dev/null +++ b/domain/tests/register_types.cpp @@ -0,0 +1,13 @@ +#include "turns/domain/participant.hpp" +#include "turns/domain/turn_order.hpp" + +namespace turns::tests +{ + + auto register_types() -> void + { + static_cast(domain::participant{}); + static_cast(domain::turn_order{}); + } + +} // namespace turns::tests \ No newline at end of file diff --git a/domain/tests/turn_order.cpp b/domain/tests/turn_order.cpp index de1689c..eb05581 100644 --- a/domain/tests/turn_order.cpp +++ b/domain/tests/turn_order.cpp @@ -1,459 +1,221 @@ #include "turns/domain/turn_order.hpp" +#include "turns/domain/participant.hpp" + #include -#include +#include namespace turns::domain::tests { - SCENARIO("Queries on a fresh turn_order instance", "[turn_order]") { GIVEN("an empty turn_order") { auto instance = turn_order::create(); - THEN("active_participant() is turn_order::invalid_participant_index") + THEN("get_n_items() returns 0") { - REQUIRE(instance->active_participant() == turn_order::invalid_participant_index); + auto str = Gio::ListStore::create(); + REQUIRE(instance->get_n_items() == str->get_n_items()); } - THEN("empty() is true") + THEN("get_type() returns participant::get_type()") { - REQUIRE(instance->empty()); + REQUIRE(instance->get_item_type() == participant::get_type()); } - THEN("get() returns a nullptr") + THEN("get_typed_object(0) returns nullptr") { - REQUIRE(instance->get(0) == nullptr); + REQUIRE(instance->get_typed_object(0) == nullptr); } - THEN("list_model() returns a non-null pointer") + THEN("has_next() returns false") { - REQUIRE(instance->list_model()); + REQUIRE_FALSE(instance->has_next()); } - THEN("round() returns 0") + THEN("has_previous() returns false") { - REQUIRE(instance->round() == 0); + REQUIRE_FALSE(instance->has_previous()); } - THEN("running() returns false") + THEN("is_empty() returns true") { - REQUIRE_FALSE(instance->running()); + REQUIRE(instance->is_empty()); } - THEN("size() returns 0") + THEN("is_running() returns false") { - REQUIRE(instance->size() == 0); + REQUIRE_FALSE(instance->is_running()); } - THEN("size() returns the same value as list_model()->get_n_item()") + THEN("round_number() returns invalid_round_number") { - REQUIRE(instance->size() == instance->list_model()->get_n_items()); + REQUIRE(instance->round_number() == turn_order::invalid_round_number); } + } + } - WHEN("accessing turn_order:active-participant") - { - THEN("get() returns turn_order::invalid_participant_index") - { - REQUIRE(instance->property_active_participant().get_value() == turn_order::invalid_participant_index); - } + SCENARIO("Adding participants") + { + auto instance = turn_order::create(); - THEN("get_object() returns a pointer to the instance") - { - REQUIRE(instance->property_active_participant().get_object() == instance.get()); - } + GIVEN("a participant has been added to a turn_order") + { + instance->add("Participant #0", 0, disposition::neutral); - THEN("get_name() returns \"active-participant\"") - { - REQUIRE(instance->property_active_participant().get_name() == Glib::ustring{"active-participant"}); - } + THEN("get_n_items() returns 1") + { + REQUIRE(instance->get_n_items() == 1); } - WHEN("accessing turn_order:empty") + THEN("get_typed_object(0) returns a non-null pointer") { - THEN("get() returns true") - { - REQUIRE(instance->property_empty().get_value()); - } - - THEN("get_object() returns a pointer to the instance") - { - REQUIRE(instance->property_empty().get_object() == instance.get()); - } - - THEN("get_name() returns \"empty\"") - { - REQUIRE(instance->property_empty().get_name() == Glib::ustring{"empty"}); - } + REQUIRE(instance->get_typed_object(0) != nullptr); } - WHEN("accessing turn_order:has-next") + THEN("has_next() returns true") { - THEN("get() returns false") - { - REQUIRE_FALSE(instance->property_has_next().get_value()); - } - - THEN("get_object() returns a pointer to the instance") - { - REQUIRE(instance->property_has_next().get_object() == instance.get()); - } + REQUIRE(instance->has_next()); + } - THEN("get_name() returns \"has-next\"") - { - REQUIRE(instance->property_has_next().get_name() == Glib::ustring{"has-next"}); - } + THEN("has_previous() returns false") + { + REQUIRE_FALSE(instance->has_previous()); } - WHEN("accessing turn_order:has-previous") + THEN("is_empty() returns false") { - THEN("get() returns false") - { - REQUIRE_FALSE(instance->property_has_previous().get_value()); - } + REQUIRE_FALSE(instance->is_empty()); + } - THEN("get_object() returns a pointer to the instance") - { - REQUIRE(instance->property_has_previous().get_object() == instance.get()); - } + THEN("is_running() returns false") + { + REQUIRE_FALSE(instance->is_running()); + } - THEN("get_name() returns \"has-previous\"") - { - REQUIRE(instance->property_has_previous().get_name() == Glib::ustring{"has-previous"}); - } + THEN("round_number() returns invalid_round_number") + { + REQUIRE(instance->round_number() == turn_order::invalid_round_number); } - WHEN("accessing turn_order:running") + WHEN("the turn_order is start()ed") { - THEN("get() returns false") + instance->start(); + + THEN("get_n_items() still returns 1") { - REQUIRE_FALSE(instance->property_running().get_value()); + REQUIRE(instance->get_n_items() == 1); } - THEN("get_object() returns a pointer to the instance") + THEN("get_typed_object(0) still returns a non-null pointer") { - REQUIRE(instance->property_running().get_object() == instance.get()); + REQUIRE(instance->get_typed_object(0) != nullptr); } - THEN("get_name() returns \"running\"") + THEN("has_next() still returns true") { - REQUIRE(instance->property_running().get_name() == Glib::ustring{"running"}); + REQUIRE(instance->has_next()); } - } - WHEN("accessing turn_order:running") - { - THEN("get() returns 0") + THEN("has_previous() still returns false") { - REQUIRE(instance->property_round().get_value() == 0); + REQUIRE_FALSE(instance->has_previous()); } - THEN("get_object() returns a pointer to the instance") + THEN("is_empty() still returns false") { - REQUIRE(instance->property_round().get_object() == instance.get()); + REQUIRE_FALSE(instance->is_empty()); } - THEN("get_name() returns \"round\"") + THEN("is_running() returns true") { - REQUIRE(instance->property_round().get_name() == Glib::ustring{"round"}); + REQUIRE(instance->is_running()); } - } - } - } - - SCENARIO("Modification of an empty turn_order", "[turn_order]") - { - auto instance = turn_order::create(); - - GIVEN("a single participant was added") - { - auto constexpr priority = 12.0f; - instance->add("Participant #0", priority, disposition::friendly); - AND_GIVEN("the turn_order was never started") - { - WHEN("no other modification occurs") + THEN("round_number() returns 0") { - THEN("active_participant() returns turn_order::invalid_participant_index") - { - REQUIRE(instance->active_participant() == turn_order::invalid_participant_index); - } - - THEN("empty() returns false") - { - REQUIRE_FALSE(instance->empty()); - } - - THEN("get() returns a valid pointer for index 0") - { - REQUIRE(instance->get(0)); - } - - THEN("get() returns a nullptr for an index greater than 0") - { - REQUIRE(instance->get(1) == nullptr); - } - - THEN("round() returns 0") - { - REQUIRE(instance->round() == 0); - } - - THEN("running() returns false") - { - REQUIRE_FALSE(instance->running()); - } - - THEN("size() returns 1") - { - REQUIRE(instance->size() == 1); - } - - THEN("turn_order:has-next is false") - { - REQUIRE(instance->property_has_next() == false); - } - - THEN("turn_order:has-previous is false") - { - REQUIRE(instance->property_has_previous() == false); - } - - THEN("turn_order:running is false") - { - REQUIRE(instance->property_running() == false); - } + REQUIRE(instance->round_number() == 0); } - WHEN("the participant is removed again") + AND_WHEN("invoking previous()") { - instance->remove(0); - - THEN("active_participant() returns turn_order::invalid_participant_index") - { - REQUIRE(instance->active_participant() == turn_order::invalid_participant_index); - } - - THEN("empty() returns true") - { - REQUIRE(instance->empty()); - } + instance->previous(); - THEN("get() returns a nullptr for index 0") + THEN("get_n_items() still returns 1") { - REQUIRE(instance->get(0) == nullptr); + REQUIRE(instance->get_n_items() == 1); } - THEN("round() returns 0") + THEN("get_typed_object(0) still returns a non-null pointer") { - REQUIRE(instance->round() == 0); + REQUIRE(instance->get_typed_object(0) != nullptr); } - THEN("running() returns false") + THEN("has_next() still returns true") { - REQUIRE_FALSE(instance->running()); + REQUIRE(instance->has_next()); } - THEN("size() returns 0") + THEN("has_previous() still returns false") { - REQUIRE(instance->size() == 0); + REQUIRE_FALSE(instance->has_previous()); } - THEN("turn_order:has-next is false") + THEN("is_empty() still returns false") { - REQUIRE(instance->property_has_next() == false); + REQUIRE_FALSE(instance->is_empty()); } - THEN("turn_order:has-previous is false") + THEN("is_running() returns true") { - REQUIRE(instance->property_has_previous() == false); + REQUIRE(instance->is_running()); } - THEN("turn_order:running is false") + THEN("round_number() returns 0") { - REQUIRE(instance->property_running() == false); + REQUIRE(instance->round_number() == 0); } } - WHEN("another participant with the same priority is added") + AND_WHEN("invoking next()") { - instance->add("Participant #1", priority, disposition::friendly); - - THEN("active_participant() returns turn_order::invalid_participant_index") - { - REQUIRE(instance->active_participant() == turn_order::invalid_participant_index); - } - - THEN("empty() returns false") - { - REQUIRE_FALSE(instance->empty()); - } - - THEN("get() returns a valid pointer for index 0") - { - REQUIRE(instance->get(0)); - } - - THEN("get() returns a valid pointer for index 1") - { - REQUIRE(instance->get(1)); - } - - THEN("get() returns a nullptr for an index greater than 1") - { - REQUIRE(instance->get(2) == nullptr); - } - - THEN("get(0) returns the participant that was added first") - { - REQUIRE(instance->get(0)->get_name() == "Participant #0"); - } - - THEN("get(1) returns the participant that was added second") - { - REQUIRE(instance->get(1)->get_name() == "Participant #1"); - } - - THEN("round() returns 0") - { - REQUIRE(instance->round() == 0); - } + instance->next(); - THEN("running() returns false") + THEN("get_n_items() still returns 1") { - REQUIRE_FALSE(instance->running()); + REQUIRE(instance->get_n_items() == 1); } - THEN("size() returns 2") + THEN("get_typed_object(0) still returns a non-null pointer") { - REQUIRE(instance->size() == 2); + REQUIRE(instance->get_typed_object(0) != nullptr); } - THEN("turn_order:has-next is false") + THEN("has_next() still returns true") { - REQUIRE(instance->property_has_next() == false); + REQUIRE(instance->has_next()); } - THEN("turn_order:has-previous is false") + THEN("has_previous() returns true") { - REQUIRE(instance->property_has_previous() == false); + REQUIRE(instance->has_previous()); } - THEN("turn_order:running is false") + THEN("is_empty() still returns false") { - REQUIRE(instance->property_running() == false); + REQUIRE_FALSE(instance->is_empty()); } - AND_WHEN("the participant at index 0 is removed") + THEN("is_running() returns true") { - instance->remove(0); - - THEN("active_participant() returns turn_order::invalid_participant_index") - { - REQUIRE(instance->active_participant() == turn_order::invalid_participant_index); - } - - THEN("empty() returns false") - { - REQUIRE_FALSE(instance->empty()); - } - - THEN("get() returns a valid pointer for index 0") - { - REQUIRE(instance->get(0)); - } - - THEN("get() returns a nullptr for an index greater than 0") - { - REQUIRE(instance->get(1) == nullptr); - } - - THEN("get(0) returns the participant that was added second") - { - REQUIRE(instance->get(0)->get_name() == "Participant #1"); - } - - THEN("round() returns 0") - { - REQUIRE(instance->round() == 0); - } - - THEN("running() returns false") - { - REQUIRE_FALSE(instance->running()); - } - - THEN("size() returns 1") - { - REQUIRE(instance->size() == 1); - } - - THEN("turn_order:has-next is false") - { - REQUIRE(instance->property_has_next() == false); - } - - THEN("turn_order:has-previous is false") - { - REQUIRE(instance->property_has_previous() == false); - } - - THEN("turn_order:running is false") - { - REQUIRE(instance->property_running() == false); - } + REQUIRE(instance->is_running()); } - AND_WHEN("the turn_order is cleared") + THEN("round_number() returns 1") { - instance->clear(); - - THEN("active_participant() returns turn_order::invalid_participant_index") - { - REQUIRE(instance->active_participant() == turn_order::invalid_participant_index); - } - - THEN("empty() returns true") - { - REQUIRE(instance->empty()); - } - - THEN("get() returns a nullptr pointer for index 0") - { - REQUIRE(instance->get(0) == nullptr); - } - - THEN("round() returns 0") - { - REQUIRE(instance->round() == 0); - } - - THEN("running() returns false") - { - REQUIRE_FALSE(instance->running()); - } - - THEN("size() returns 0") - { - REQUIRE(instance->size() == 0); - } - - THEN("turn_order:has-next is false") - { - REQUIRE(instance->property_has_next() == false); - } - - THEN("turn_order:has-previous is false") - { - REQUIRE(instance->property_has_previous() == false); - } - - THEN("turn_order:running is false") - { - REQUIRE(instance->property_running() == false); - } + REQUIRE(instance->round_number() == 1); } } } diff --git a/test_support/CMakeLists.txt b/test_support/CMakeLists.txt index ce77a90..447032d 100644 --- a/test_support/CMakeLists.txt +++ b/test_support/CMakeLists.txt @@ -18,6 +18,7 @@ target_compile_definitions("test_support-glib" PUBLIC target_link_libraries("test_support-glib" PUBLIC "PkgConfig::glibmm" + "PkgConfig::giomm" ) # GTK test support diff --git a/test_support/src/glib_main.cpp b/test_support/src/glib_main.cpp index d224bad..b9dc858 100644 --- a/test_support/src/glib_main.cpp +++ b/test_support/src/glib_main.cpp @@ -1,10 +1,19 @@ #include +#include #include +namespace turns::tests +{ + auto register_types() -> void; +} // namespace turns::tests + auto main(int argc, char * argv[]) -> int { + Gio::init(); Glib::init(); + turns::tests::register_types(); + return Catch::Session().run(argc, argv); } \ No newline at end of file -- cgit v1.2.3