diff options
| -rw-r--r-- | app/include/turns/app/widgets/turn_order_view.hpp | 7 | ||||
| -rw-r--r-- | app/include/turns/app/windows/tracker.hpp | 10 | ||||
| -rw-r--r-- | app/src/application.cpp | 5 | ||||
| -rw-r--r-- | app/src/widgets/turn_order_view.cpp | 15 | ||||
| -rw-r--r-- | app/src/windows/tracker.cpp | 102 | ||||
| -rw-r--r-- | domain/include/turns/domain/turn_order.hpp | 47 | ||||
| -rw-r--r-- | domain/src/turn_order.cpp | 142 | ||||
| -rw-r--r-- | res/ui.cmb | 4 |
8 files changed, 287 insertions, 45 deletions
diff --git a/app/include/turns/app/widgets/turn_order_view.hpp b/app/include/turns/app/widgets/turn_order_view.hpp index 0122f4a..022b51c 100644 --- a/app/include/turns/app/widgets/turn_order_view.hpp +++ b/app/include/turns/app/widgets/turn_order_view.hpp @@ -2,19 +2,19 @@ #define TURNS_APP_WIDGETS_TURN_ORDER_VIEW_HPP #include "turns/app/widgets/template_widget.hpp" -#include "turns/domain/turn_order.hpp" -#include "turns/domain/participant.hpp" #include "turns/domain/disposition.hpp" +#include "turns/domain/participant.hpp" +#include "turns/domain/turn_order.hpp" #include <array> #include <glibmm/object.h> #include <glibmm/propertyproxy.h> #include <glibmm/refptr.h> +#include <glibmm/ustring.h> #include <gtkmm/listbox.h> #include <gtkmm/scrolledwindow.h> #include <gtkmm/widget.h> -#include <glibmm/ustring.h> namespace turns::app::widgets { @@ -29,6 +29,7 @@ namespace turns::app::widgets auto get_model() const noexcept -> Glib::RefPtr<domain::turn_order>; private: + auto handle_active_participant_changed() -> void; auto handle_create_row(Glib::RefPtr<Glib::Object> const item) -> Gtk::Widget *; Glib::RefPtr<domain::turn_order> m_model; diff --git a/app/include/turns/app/windows/tracker.hpp b/app/include/turns/app/windows/tracker.hpp index b9b45a7..292d6d5 100644 --- a/app/include/turns/app/windows/tracker.hpp +++ b/app/include/turns/app/windows/tracker.hpp @@ -1,8 +1,8 @@ #ifndef TURNS_APP_WINDOWS_TRACKER_HPP #define TURNS_APP_WINDOWS_TRACKER_HPP -#include "turns/domain/turn_order.hpp" #include "turns/app/widgets/turn_order_view.hpp" +#include "turns/domain/turn_order.hpp" #include <adwaita.h> #include <giomm/simpleaction.h> @@ -13,11 +13,12 @@ #include <glibmm/variant.h> #include <gtkmm/applicationwindow.h> #include <gtkmm/builder.h> +#include <gtkmm/button.h> #include <gtkmm/listbox.h> +#include <gtkmm/revealer.h> #include <gtkmm/scrolledwindow.h> #include <gtkmm/stack.h> #include <gtkmm/widget.h> -#include <gtkmm/revealer.h> namespace turns::app::windows { @@ -30,11 +31,16 @@ namespace turns::app::windows auto handle_add_participant() -> void; auto handle_delete_participant(Glib::VariantBase param) -> void; auto handle_edit_participant(Glib::VariantBase param) -> void; + auto handle_start() -> void; + auto handle_stop() -> void; + + auto setup_actions() -> void; AdwApplicationWindow * m_adw; Gtk::Revealer * m_controls; Gtk::Widget * m_empty; Gtk::Stack * m_stack; + Gtk::Button * m_start; AdwWindowTitle * m_title; widgets::turn_order_view * m_turn_order; Glib::PropertyProxy<Glib::ustring> m_subtitle; diff --git a/app/src/application.cpp b/app/src/application.cpp index efa1ce0..bf49a6e 100644 --- a/app/src/application.cpp +++ b/app/src/application.cpp @@ -61,8 +61,11 @@ namespace turns::app adw_style_manager_set_color_scheme(style_manager, ADW_COLOR_SCHEME_PREFER_LIGHT); m_application->add_action("quit", sigc::mem_fun(*this, &application::handle_action_quit)); - m_application->set_accel_for_action("app.quit", "<ctrl>q"); + m_application->set_accel_for_action("app.quit", "<Primary>q"); m_application->set_accel_for_action("win.clear", "<Primary>x"); + m_application->set_accel_for_action("win.next", "<Primary>space"); + m_application->set_accel_for_action("win.previous", "<Primary>BackSpace"); + m_application->set_accel_for_action("win.add_participant", "<Primary>a"); register_derived_widgets(); } diff --git a/app/src/widgets/turn_order_view.cpp b/app/src/widgets/turn_order_view.cpp index 700dfd3..c2b4afa 100644 --- a/app/src/widgets/turn_order_view.cpp +++ b/app/src/widgets/turn_order_view.cpp @@ -4,6 +4,7 @@ #include "turns/domain/participant.hpp" #include "turns/lang/messages.hpp" +#include <algorithm> #include <format> #include <sigc++/functors/mem_fun.h> @@ -23,6 +24,7 @@ namespace turns::app::widgets , m_view{get_widget<Gtk::ListBox>("view")} { 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)); } auto turn_order_view::get_model() const noexcept -> Glib::RefPtr<domain::turn_order> @@ -30,6 +32,19 @@ namespace turns::app::widgets return m_model; } + 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<domain::turn_order::active_participant_type>::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<Glib::Object> const item) -> Gtk::Widget * { auto participant = std::dynamic_pointer_cast<domain::participant>(item); diff --git a/app/src/windows/tracker.cpp b/app/src/windows/tracker.cpp index 5af405b..7712539 100644 --- a/app/src/windows/tracker.cpp +++ b/app/src/windows/tracker.cpp @@ -30,34 +30,23 @@ namespace turns::app::windows , m_controls{builder->get_widget<Gtk::Revealer>("controls")} , m_empty(builder->get_widget<Gtk::Widget>("empty")) , m_stack{builder->get_widget<Gtk::Stack>("stack")} + , m_start{builder->get_widget<Gtk::Button>("start")} , m_title(ADW_WINDOW_TITLE(builder->get_widget<Gtk::Widget>("title")->gobj())) , m_turn_order{Gtk::make_managed<widgets::turn_order_view>()} , m_subtitle{Glib::wrap(GTK_WIDGET(m_title)), "subtitle"} { - m_stack->add(*m_turn_order); - - auto clear_action = add_action("clear", sigc::mem_fun(*m_turn_order->get_model(), &domain::turn_order::clear)); - add_action("add_participant", sigc::mem_fun(*this, &tracker::handle_add_participant)); - add_action_with_parameter("delete", Glib::VARIANT_TYPE_INT32, sigc::mem_fun(*this, &tracker::handle_delete_participant)); - add_action_with_parameter("edit", Glib::VARIANT_TYPE_INT32, sigc::mem_fun(*this, &tracker::handle_edit_participant)); - auto start_action = add_action("start", sigc::mem_fun(*m_turn_order->get_model(), &domain::turn_order::start)); - - Glib::Binding::bind_property(m_turn_order->get_model()->property_empty(), - clear_action->property_enabled(), - Glib::Binding::Flags::SYNC_CREATE | Glib::Binding::Flags::INVERT_BOOLEAN); + setup_actions(); - Glib::Binding::bind_property(m_turn_order->get_model()->property_empty(), - m_controls->property_reveal_child(), - Glib::Binding::Flags::SYNC_CREATE | Glib::Binding::Flags::INVERT_BOOLEAN); + m_stack->add(*m_turn_order); Glib::Binding::bind_property(m_turn_order->get_model()->property_empty(), m_stack->property_visible_child(), Glib::Binding::Flags::SYNC_CREATE, [this](auto empty) { return empty ? m_empty : m_turn_order; }); - Glib::Binding::bind_property(m_turn_order->get_model()->property_empty(), - start_action->property_enabled(), - Glib::Binding::Flags::SYNC_CREATE | Glib::Binding::Flags::INVERT_BOOLEAN); + Glib::Binding::bind_property(m_turn_order->get_model()->property_running(), + m_controls->property_reveal_child(), + Glib::Binding::Flags::SYNC_CREATE); // clang-format off Glib::Binding::bind_property(m_turn_order->get_model()->property_empty(), @@ -91,4 +80,83 @@ namespace turns::app::windows dialog->present(this); } + auto tracker::handle_stop() -> void + { + m_turn_order->get_model()->stop(); + } + + auto tracker::setup_actions() -> void + { + // win.add_participant + // depends-on: turn_order:state == stopped + { + auto action = add_action("add_participant", sigc::mem_fun(*this, &tracker::handle_add_participant)); + + Glib::Binding::bind_property(m_turn_order->get_model()->property_running(), + action->property_enabled(), + Glib::Binding::Flags::SYNC_CREATE | Glib::Binding::Flags::INVERT_BOOLEAN); + } + + // win.clear + // depends-on: turn_order:empty == false + { + auto action = add_action("clear", sigc::mem_fun(*m_turn_order->get_model(), &domain::turn_order::clear)); + + Glib::Binding::bind_property(m_turn_order->get_model()->property_empty(), + action->property_enabled(), + Glib::Binding::Flags::SYNC_CREATE | Glib::Binding::Flags::INVERT_BOOLEAN); + } + + // win.next + // depends-on: turn_order:state == running + { + auto action = add_action("next", sigc::mem_fun(*m_turn_order->get_model(), &domain::turn_order::next)); + + Glib::Binding::bind_property(m_turn_order->get_model()->property_running(), + action->property_enabled(), + Glib::Binding::Flags::SYNC_CREATE); + } + + // win.previous + // depends-on: turn_order:has_previous == true + { + auto action = add_action("previous", sigc::mem_fun(*m_turn_order->get_model(), &domain::turn_order::previous)); + + Glib::Binding::bind_property(m_turn_order->get_model()->property_has_previous(), + action->property_enabled(), + Glib::Binding::Flags::SYNC_CREATE); + } + + // win.start + // depends-on: turn_order:empty == false + { + auto action = add_action("start", sigc::mem_fun(*m_turn_order->get_model(), &domain::turn_order::start)); + + Glib::Binding::bind_property(m_turn_order->get_model()->property_empty(), + action->property_enabled(), + Glib::Binding::Flags::SYNC_CREATE | Glib::Binding::Flags::INVERT_BOOLEAN); + + Glib::Binding::bind_property(m_turn_order->get_model()->property_running(), + m_start->property_visible(), + Glib::Binding::Flags::SYNC_CREATE | Glib::Binding::Flags::INVERT_BOOLEAN); + } + + // win.stop + // depends-on: turn_order:running == true + { + auto action = add_action("stop", sigc::mem_fun(*this, &tracker::handle_stop)); + + Glib::Binding::bind_property(m_turn_order->get_model()->property_running(), + action->property_enabled(), + Glib::Binding::Flags::SYNC_CREATE); + } + + // win.delete + // win.edit + { + add_action_with_parameter("delete", Glib::VARIANT_TYPE_INT32, sigc::mem_fun(*this, &tracker::handle_delete_participant)); + add_action_with_parameter("edit", Glib::VARIANT_TYPE_INT32, sigc::mem_fun(*this, &tracker::handle_edit_participant)); + } + } + } // namespace turns::app::windows diff --git a/domain/include/turns/domain/turn_order.hpp b/domain/include/turns/domain/turn_order.hpp index d3f74eb..59438d6 100644 --- a/domain/include/turns/domain/turn_order.hpp +++ b/domain/include/turns/domain/turn_order.hpp @@ -14,31 +14,56 @@ namespace turns::domain struct turn_order : Glib::Object { + using active_participant_type = unsigned int; + using empty_type = bool; + using has_next_type = bool; + using has_previous_type = bool; + using running_type = bool; + using round_type = unsigned int; + auto static create() -> Glib::RefPtr<turn_order>; turn_order(); - auto add(Glib::ustring const & name, float priority, disposition disposition) -> void; + /** Modifiers */ + auto add(Glib::ustring const & name, float priority, disposition disposition) -> void; auto clear() -> void; - - auto empty() const noexcept -> bool; - - auto get(unsigned int index) -> Glib::RefPtr<participant>; - + auto next() -> void; + auto previous() -> void; auto remove(unsigned int index) -> void; - - auto size() -> unsigned int; - + auto reset() -> void; auto start() -> void; + auto stop() -> void; + + /** Querries */ + auto active_participant() const noexcept -> active_participant_type; + auto empty() const noexcept -> empty_type; + auto get(unsigned int index) const noexcept -> Glib::RefPtr<participant>; auto list_model() -> Glib::RefPtr<Gio::ListModel>; + auto round() const noexcept -> round_type; + auto running() const noexcept -> running_type; + auto size() const noexcept -> unsigned int; - auto property_empty() const -> Glib::PropertyProxy_ReadOnly<bool>; + /** Properties */ + + auto property_active_participant() const -> Glib::PropertyProxy_ReadOnly<active_participant_type>; + auto property_empty() const -> Glib::PropertyProxy_ReadOnly<empty_type>; + auto property_has_next() const -> Glib::PropertyProxy_ReadOnly<has_next_type>; + auto property_has_previous() const -> Glib::PropertyProxy_ReadOnly<has_previous_type>; + auto property_running() const -> Glib::PropertyProxy_ReadOnly<running_type>; + auto property_round() const -> Glib::PropertyProxy_ReadOnly<round_type>; private: Glib::RefPtr<Gio::ListStore<participant>> m_model; - Glib::Property<bool> m_empty; + + Glib::Property<active_participant_type> m_active_participant; + Glib::Property<empty_type> m_empty; + Glib::Property<has_next_type> m_has_next; + Glib::Property<has_previous_type> m_has_previous; + Glib::Property<running_type> m_running; + Glib::Property<round_type> m_round; }; } // namespace turns::domain diff --git a/domain/src/turn_order.cpp b/domain/src/turn_order.cpp index 990e9c1..595b55d 100644 --- a/domain/src/turn_order.cpp +++ b/domain/src/turn_order.cpp @@ -3,6 +3,7 @@ #include "turns/domain/participant.hpp" #include <compare> +#include <limits> #include <typeinfo> #include <glibmm/binding.h> @@ -39,50 +40,121 @@ namespace turns::domain turn_order::turn_order() : Glib::ObjectBase{typeid(turn_order)} , m_model{Gio::ListStore<participant>::create()} + , m_active_participant(*this, "active_participant", std::numeric_limits<active_participant_type>::max()) , 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} { Glib::Binding::bind_property(m_model->property_n_items(), m_empty.get_proxy(), Glib::Binding::Flags::DEFAULT, [](auto n) { return n == 0; }); } + /** 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) { - m_model->insert_sorted(participant, comparator); + auto position = m_model->insert_sorted(participant, comparator); participant->property_priority().signal_changed().connect([this] { m_model->sort(comparator); }); + + if (m_active_participant != std::numeric_limits<active_participant_type>::max() && position <= m_active_participant) + { + m_active_participant = m_active_participant + 1; + } } } - auto turn_order::get(unsigned int index) -> Glib::RefPtr<participant> + auto turn_order::clear() -> void { - return m_model->get_item(index); + m_model->remove_all(); + m_active_participant = std::numeric_limits<active_participant_type>::max(); + m_has_next = false; + m_has_previous = false; + m_running = false; } - auto turn_order::list_model() -> Glib::RefPtr<Gio::ListModel> + auto turn_order::next() -> void { - return m_model; + m_active_participant = (m_active_participant + 1) % size(); + if (!m_active_participant) + { + m_round = round() + 1; + } + m_has_previous = m_active_participant || m_round; } - auto turn_order::size() -> unsigned int + auto turn_order::previous() -> void { - return m_model->get_n_items(); + if (!m_has_previous) + { + return; + } + + if (m_active_participant) + { + m_active_participant = m_active_participant - 1; + } + else if (m_round) + { + m_round = round() - 1; + m_active_participant = size() - 1; + } + + m_has_previous = m_active_participant || m_round; } auto turn_order::remove(unsigned int index) -> void { m_model->remove(index); + if (empty()) + { + m_active_participant = std::numeric_limits<active_participant_type>::max(); + m_has_next = false; + m_has_previous = false; + m_running = false; + } + else if (m_active_participant >= size() - 1) + { + m_active_participant = size() - 1; + } + else if (index <= m_active_participant) + { + m_active_participant = m_active_participant - 1; + } } - auto turn_order::clear() -> void + auto turn_order::reset() -> void { - m_model->remove_all(); + m_running = false; + m_active_participant = 0; } auto turn_order::start() -> void { + if (m_active_participant == std::numeric_limits<active_participant_type>::max()) + { + m_active_participant = 0; + } + m_running = true; + m_has_next = 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 @@ -90,9 +162,61 @@ namespace turns::domain return m_empty; } + auto turn_order::get(unsigned int index) const noexcept -> Glib::RefPtr<participant> + { + return m_model->get_item(index); + } + + auto turn_order::list_model() -> Glib::RefPtr<Gio::ListModel> + { + return m_model; + } + + auto turn_order::round() const noexcept -> round_type + { + return m_round; + } + + auto turn_order::running() const noexcept -> running_type + { + return m_running; + } + + auto turn_order::size() const noexcept -> unsigned int + { + return m_model->get_n_items(); + } + + /** Properties */ + + auto turn_order::property_active_participant() const -> Glib::PropertyProxy_ReadOnly<active_participant_type> + { + return m_active_participant.get_proxy(); + } + auto turn_order::property_empty() const -> Glib::PropertyProxy_ReadOnly<bool> { return m_empty.get_proxy(); } + auto turn_order::property_has_next() const -> Glib::PropertyProxy_ReadOnly<has_next_type> + { + return m_has_next.get_proxy(); + } + + auto turn_order::property_has_previous() const -> Glib::PropertyProxy_ReadOnly<has_previous_type> + { + return m_has_previous.get_proxy(); + } + + auto turn_order::property_running() const -> Glib::PropertyProxy_ReadOnly<running_type> + { + return m_running.get_proxy(); + } + + auto turn_order::property_round() const -> Glib::PropertyProxy_ReadOnly<round_type> + { + return m_round.get_proxy(); + } + } // namespace turns::domain
\ No newline at end of file @@ -92,7 +92,7 @@ (1,22,"GtkActionable","action-name","win.previous",None,None,None,None,None,None,None,None,None), (1,22,"GtkButton","icon-name","media-skip-backward-symbolic",None,None,None,None,None,None,None,None,None), (1,22,"GtkWidget","tooltip-markup","Previous participant",None,None,None,None,None,None,None,None,None), - (1,23,"GtkActionable","action-name","win.end",None,None,None,None,None,None,None,None,None), + (1,23,"GtkActionable","action-name","win.stop",None,None,None,None,None,None,None,None,None), (1,23,"GtkButton","icon-name","media-playback-stop-symbolic",None,None,None,None,None,None,None,None,None), (1,23,"GtkWidget","tooltip-markup","End turn order",None,None,None,None,None,None,None,None,None), (1,24,"GtkActionable","action-name","win.next",None,None,None,None,None,None,None,None,None), @@ -122,8 +122,8 @@ (2,12,"GtkAdjustment","lower","-1000.0",None,None,None,None,None,None,None,None,None), (2,12,"GtkAdjustment","step-increment","1.0",None,None,None,None,None,None,None,None,None), (2,12,"GtkAdjustment","upper","1000.0",None,None,None,None,None,None,None,None,None), - (3,1,"GtkListBoxRow","activatable","False",None,None,None,None,None,None,None,None,None), (3,1,"GtkListBoxRow","child",None,None,None,None,None,2,None,None,None,None), + (3,1,"GtkListBoxRow","selectable","False",None,None,None,None,None,None,None,None,None), (3,1,"GtkWidget","valign","center",None,None,None,None,None,None,None,None,None), (3,4,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None), (3,4,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None), |
