/* * SPDX-FileCopyrightText: 2025 Felix Morgner * SPDX-License-Identifier: LGPL-2.1-only */ #include "turns-turn-order.h" #include "turns-participant.h" #include #include #include #include enum { PROP_EMPTY = 1, PROP_PARTICIPANT_COUNT, PROP_RUNNING, PROP_ROUND_PROGRESS, PROP_SORT_MODE, N_PROPERTIES, }; struct _TurnsTurnOrder { GObject parent_instance; gsize active; GPtrArray * participants; gboolean running; TurnsTurnOrderSortMode sort_mode; }; static GParamSpec * properties[N_PROPERTIES] = { nullptr, }; static void turns_turn_order_list_model_init(GListModelInterface * iface); G_DEFINE_FINAL_TYPE_WITH_CODE(TurnsTurnOrder, turns_turn_order, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE(G_TYPE_LIST_MODEL, turns_turn_order_list_model_init)) static gint compare_participant(void const * lhs, void const * rhs, void * sort_mode) { auto left_priority = turns_participant_get_priority(lhs); auto right_priority = turns_participant_get_priority(rhs); auto result = 0; if (left_priority < right_priority) { result = -1; } else if (left_priority > right_priority) { result = 1; } if ((TurnsTurnOrderSortMode)(long long)sort_mode == TURNS_TURN_ORDER_SORT_MODE_DESCENDING) { return result * -1; } return result; } static void sort_participants(TurnsTurnOrder * self) { auto const sort_mode = turns_turn_order_get_sort_mode(self); g_ptr_array_sort_values_with_data(self->participants, &compare_participant, (gpointer)sort_mode); auto const participant_count = turns_turn_order_get_participant_count(self); g_list_model_items_changed(G_LIST_MODEL(self), 0, participant_count, participant_count); } static void clear_participants(TurnsTurnOrder * self) { g_return_if_fail(TURNS_IS_TURN_ORDER(self)); g_ptr_array_unref(self->participants); self->participants = g_ptr_array_new_full(5, g_object_unref); g_object_notify_by_pspec(G_OBJECT(self), properties[PROP_EMPTY]); } static void set_running(TurnsTurnOrder * self, gboolean value) { g_return_if_fail(TURNS_IS_TURN_ORDER(self)); if (self->running == value) { return; } self->running = value; g_object_notify_by_pspec(G_OBJECT(self), properties[PROP_RUNNING]); } static void handle_participant_property_changed(TurnsParticipant const *, GParamSpec *, TurnsTurnOrder * self) { sort_participants(self); } static void turns_turn_order_get_property(GObject * self, guint id, GValue * value, GParamSpec * specification) { auto instance = TURNS_TURN_ORDER(self); switch (id) { case PROP_EMPTY: g_value_set_boolean(value, turns_turn_order_get_empty(instance)); return; case PROP_PARTICIPANT_COUNT: g_value_set_uint64(value, turns_turn_order_get_participant_count(instance)); return; case PROP_RUNNING: g_value_set_boolean(value, turns_turn_order_get_running(instance)); return; case PROP_ROUND_PROGRESS: g_value_set_float(value, turns_turn_order_get_round_progress(instance)); return; case PROP_SORT_MODE: g_value_set_enum(value, turns_turn_order_get_sort_mode(instance)); return; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(self, id, specification); } } static void turns_turn_order_set_property(GObject * self, guint id, GValue const * value, GParamSpec * specification) { auto instance = TURNS_TURN_ORDER(self); switch (id) { case PROP_SORT_MODE: turns_turn_order_set_sort_mode(instance, g_value_get_enum(value)); return; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(self, id, specification); } } static void turns_turn_order_init(TurnsTurnOrder * self) { self->active = 0; self->participants = g_ptr_array_new_full(5, g_object_unref); self->running = false; self->sort_mode = TURNS_TURN_ORDER_SORT_MODE_DESCENDING; } static void turns_turn_order_finalize(GObject * self) { auto instance = TURNS_TURN_ORDER(self); g_ptr_array_unref(instance->participants); G_OBJECT_CLASS(turns_turn_order_parent_class)->finalize(self); } static void turns_turn_order_class_init(TurnsTurnOrderClass * klass) { auto object_class = G_OBJECT_CLASS(klass); object_class->finalize = turns_turn_order_finalize; object_class->get_property = turns_turn_order_get_property; object_class->set_property = turns_turn_order_set_property; properties[PROP_EMPTY] = g_param_spec_boolean("empty", "Empty", "Whether the turn order is empty.", TRUE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY); properties[PROP_PARTICIPANT_COUNT] = g_param_spec_uint64("participant-count", "Participant count", "The number of participants in the turn order.", 0, UINT64_MAX, 0, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY); properties[PROP_RUNNING] = g_param_spec_boolean("running", "Running", "Whether or not the turn order is running (e.g. has been started)", false, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY); properties[PROP_ROUND_PROGRESS] = g_param_spec_float("round-progress", "Round Progress", "The current progress of the round.", 0.0f, 1.0f, 0.0f, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY); properties[PROP_SORT_MODE] = g_param_spec_enum("sort-mode", "Sort Mode", "The sort mode applied to the turn order", TURNS_TYPE_TURN_ORDER_SORT_MODE, TURNS_TURN_ORDER_SORT_MODE_DESCENDING, G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); g_object_class_install_properties(object_class, N_PROPERTIES, properties); } static gpointer turns_turn_order_list_model_get_item(GListModel * self, guint position) { g_return_val_if_fail(position < turns_turn_order_get_participant_count(TURNS_TURN_ORDER(self)), nullptr); return g_object_ref(TURNS_TURN_ORDER(self)->participants->pdata[position]); } static guint turns_turn_order_list_model_get_n_items(GListModel * self) { return turns_turn_order_get_participant_count(TURNS_TURN_ORDER(self)); } static GType turns_turn_order_list_model_get_item_type(GListModel * self) { (void)self; return TURNS_TYPE_PARTICIPANT; } static void turns_turn_order_list_model_init(GListModelInterface * iface) { iface->get_item = turns_turn_order_list_model_get_item; iface->get_item_type = turns_turn_order_list_model_get_item_type; iface->get_n_items = turns_turn_order_list_model_get_n_items; } TurnsTurnOrder * turns_turn_order_new() { return TURNS_TURN_ORDER(g_object_new(TURNS_TYPE_TURN_ORDER, nullptr)); } void turns_turn_order_add(TurnsTurnOrder * self, TurnsParticipant * participant) { g_return_if_fail(participant != nullptr); g_signal_connect(participant, "notify::priority", (GCallback)&handle_participant_property_changed, self); auto was_empty = self->participants->len == 0; g_ptr_array_add(self->participants, g_object_ref(participant)); sort_participants(self); auto position = 0u; g_ptr_array_find(self->participants, participant, &position); if (was_empty && !turns_turn_order_get_empty(self)) { g_object_notify_by_pspec(G_OBJECT(self), properties[PROP_EMPTY]); } g_list_model_items_changed(G_LIST_MODEL(self), position, 0, 1); } void turns_turn_order_clear(TurnsTurnOrder * self) { g_return_if_fail(TURNS_IS_TURN_ORDER(self)); auto old_size = turns_turn_order_get_participant_count(self); clear_participants(self); g_object_freeze_notify(G_OBJECT(self)); set_running(self, false); g_object_thaw_notify(G_OBJECT(self)); g_list_model_items_changed(G_LIST_MODEL(self), 0, old_size, 0); } void turns_turn_order_remove_at(TurnsTurnOrder * self, guint position) { g_return_if_fail(TURNS_IS_TURN_ORDER(self)); g_return_if_fail(position < turns_turn_order_get_participant_count(self)); auto element = TURNS_PARTICIPANT(self->participants->pdata[position]); g_return_if_fail(TURNS_IS_PARTICIPANT(element)); g_ptr_array_remove_index(self->participants, position); if (turns_turn_order_get_empty(self)) { g_object_notify_by_pspec(G_OBJECT(self), properties[PROP_EMPTY]); set_running(self, false); } g_list_model_items_changed(G_LIST_MODEL(self), position, 1, 0); } gboolean turns_turn_order_get_empty(TurnsTurnOrder const * self) { g_return_val_if_fail(TURNS_IS_TURN_ORDER((TurnsTurnOrder *)self), true); return self->participants->len == 0; } gsize turns_turn_order_get_participant_count(TurnsTurnOrder const * self) { g_return_val_if_fail(TURNS_IS_TURN_ORDER((TurnsTurnOrder *)self), 0); return self->participants->len; } gboolean turns_turn_order_get_running(TurnsTurnOrder const * self) { g_return_val_if_fail(TURNS_IS_TURN_ORDER((TurnsTurnOrder *)self), false); return self->running; } gfloat turns_turn_order_get_round_progress(TurnsTurnOrder const * self) { g_return_val_if_fail(TURNS_IS_TURN_ORDER((TurnsTurnOrder *)self), 0.0f); auto participant_count = turns_turn_order_get_participant_count(self); if (participant_count == 0) { return 0.0f; } return ((gfloat)(self->active)) / ((gfloat)participant_count); } TurnsTurnOrderSortMode turns_turn_order_get_sort_mode(TurnsTurnOrder const * self) { g_return_val_if_fail(TURNS_IS_TURN_ORDER((TurnsTurnOrder *)self), TURNS_TURN_ORDER_SORT_MODE_ASCENDING); return self->sort_mode; } void turns_turn_order_set_sort_mode(TurnsTurnOrder * self, TurnsTurnOrderSortMode sort_mode) { g_return_if_fail(TURNS_IS_TURN_ORDER(self)); if (sort_mode == self->sort_mode) { return; } self->sort_mode = sort_mode; g_object_notify_by_pspec(G_OBJECT(self), properties[PROP_SORT_MODE]); sort_participants(self); }