diff --git a/data/interface.ui b/data/interface.ui index da1e0ecdb82427d0d5c7c57dbed3b8e2c7dd3969..d786320d0ec80d3f57d3ebda0c8e9c1c6c2b46e5 100644 --- a/data/interface.ui +++ b/data/interface.ui @@ -10,6 +10,11 @@ <property name="upper">200</property> <property name="step_increment">1</property> </object> + <object class="RugbyListStore" id="list_store"> + <binding name="score"> + <lookup name="value">scorespin</lookup> + </binding> + </object> <menu id="menu"> <section> <item> @@ -30,23 +35,20 @@ <object class="GtkScrolledWindow"> <property name="height_request">600</property> <child> - <object class="GtkViewport"> - <child> - <object class="GtkListBox" id="listbox"> - <property name="selection_mode">none</property> - <style> - <class name="rich-list"/> - </style> - <child type="placeholder"> - <object class="GtkLabel"> - <property name="label">No possibilities for this score</property> - <style> - <class name="dim-label"/> - </style> - </object> - </child> + <object class="GtkListView" id="listview"> + <property name="model"> + <object class="GtkNoSelection"> + <property name="model">list_store</property> + </object> + </property> + <property name="factory"> + <object class="GtkBuilderListItemFactory"> + <property name="resource">/uk/me/bcowan/rugby/score-item.ui</property> </object> - </child> + </property> + <style> + <class name="rich-list"/> + </style> </object> </child> </object> diff --git a/data/rugby.gresource.xml b/data/rugby.gresource.xml index 67d6f74fe206552dd81b5c40759079754a3fd8c3..d0aed4a219471f4288fe38a4bd629208c1a8d8f8 100644 --- a/data/rugby.gresource.xml +++ b/data/rugby.gresource.xml @@ -8,5 +8,6 @@ <gresource prefix="/uk/me/bcowan/rugby"> <file preprocess="xml-stripblanks" compressed="true">interface.ui</file> <file preprocess="xml-stripblanks" compressed="true">prefs.ui</file> + <file preprocess="xml-stripblanks" compressed="true">score-item.ui</file> </gresource> </gresources> diff --git a/data/score-item.ui b/data/score-item.ui new file mode 100644 index 0000000000000000000000000000000000000000..219a0f53217683e2badc5467d8402aee0ec2a665 --- /dev/null +++ b/data/score-item.ui @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + SPDX-FileCopyrightText: 2021 Bruce Cowan <bruce@bcowan.me.uk> + + SPDX-License-Identifier: GPL-3.0-or-later +--> +<interface> + <requires lib="gtk" version="4.0"/> + <template class="GtkListItem"> + <property name="child"> + <object class="RugbyPossibilityWidget"> + <binding name="possibility"> + <lookup name="item">GtkListItem</lookup> + </binding> + <binding name="tooltip-text"> + <closure type="gchararray" function="item_tooltip_cb"> + <lookup name="item">GtkListItem</lookup> + </closure> + </binding> + </object> + </property> + </template> +</interface> diff --git a/src/meson.build b/src/meson.build index d01b95b3041a93c58629c5e6e3ba104ec7388d06..903b613070a080daac79da23eb0ac349ee8b2962 100644 --- a/src/meson.build +++ b/src/meson.build @@ -6,7 +6,7 @@ sources = [ 'rugby-app-window.c', 'rugby-list-store.c', 'rugby-possibility.c', - 'rugby-possibility-row.c', + 'rugby-possibility-widget.c', 'rugby-pref-window.c', ] diff --git a/src/rugby-app-window.c b/src/rugby-app-window.c index 35a591f680938a95fec1aa78758626154a091632..5d58a94a3432cda850298908e2b5beabb4ea3d55 100644 --- a/src/rugby-app-window.c +++ b/src/rugby-app-window.c @@ -5,73 +5,86 @@ */ #include "config.h" -#include "rugby-app-window.h" +#include "rugby-app-window.h" #include "rugby-list-store.h" -#include "rugby-possibility.h" -#include "rugby-possibility-row.h" +#include "rugby-possibility-widget.h" + +#include <glib/gi18n.h> struct _RugbyAppWindow { GtkApplicationWindow parent; - - RugbyListStore *store; - - GtkWidget *scorespin; - GtkWidget *listbox; }; G_DEFINE_TYPE (RugbyAppWindow, rugby_app_window, GTK_TYPE_APPLICATION_WINDOW) -static GtkWidget * -listbox_widget_func ( gpointer item, - G_GNUC_UNUSED gpointer user_data) +static char * +item_tooltip_cb (GtkListItem *item) { - RugbyPossibility *possibility = RUGBY_POSSIBILITY (item); - - return rugby_possibility_row_new (possibility); + RugbyPossibility *possibility = gtk_list_item_get_item (item); + + int tries, utries, kicks; + GString *tooltip = g_string_new (NULL); + + if (!possibility) + return NULL; + + g_object_get (possibility, + "tries", &tries, + "utries", &utries, + "kicks", &kicks, + NULL); + + if ((tries + utries) > 0) + { + if (tries == 1 && utries == 0) + g_string_printf (tooltip, "1 converted try"); + else if (tries == 0 && utries == 1) + g_string_printf (tooltip, "1 unconverted try"); + else + g_string_append_printf (tooltip, + ngettext ("%d try, %d converted", + "%d tries, %d converted", + tries + utries), + tries + utries, tries); + + if (kicks > 0) + g_string_append_printf (tooltip, + ngettext (", %d kick", + ", %d kicks", + kicks), + kicks); + } + else if (kicks > 0) + { + g_string_append_printf (tooltip, + ngettext ("%d kick", + "%d kicks", kicks), + kicks); + } + + return g_string_free (tooltip, FALSE); } static void rugby_app_window_init (RugbyAppWindow *self) { gtk_widget_init_template (GTK_WIDGET (self)); - - self->store = rugby_list_store_new (); - gtk_list_box_bind_model (GTK_LIST_BOX (self->listbox), - G_LIST_MODEL (self->store), - listbox_widget_func, - NULL, - NULL); - - g_object_bind_property (self->scorespin, "value", - self->store, "score", - G_BINDING_DEFAULT); -} - -static void -rugby_app_window_dispose (GObject *object) -{ - RugbyAppWindow *self = RUGBY_APP_WINDOW (object); - - g_clear_object (&self->store); - - G_OBJECT_CLASS (rugby_app_window_parent_class)->dispose (object); } static void rugby_app_window_class_init (RugbyAppWindowClass *klass) { - GObjectClass *obj_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - obj_class->dispose = rugby_app_window_dispose; + g_type_ensure (RUGBY_TYPE_POSSIBILITY_WIDGET); + g_type_ensure (RUGBY_TYPE_LIST_STORE); gtk_widget_class_set_template_from_resource (widget_class, "/uk/me/bcowan/rugby/interface.ui"); - gtk_widget_class_bind_template_child (widget_class, RugbyAppWindow, scorespin); - gtk_widget_class_bind_template_child (widget_class, RugbyAppWindow, listbox); + gtk_widget_class_bind_template_callback (widget_class, item_tooltip_cb); } RugbyAppWindow * diff --git a/src/rugby-possibility-row.c b/src/rugby-possibility-row.c deleted file mode 100644 index 452fb6ce3be6acb4348fdd217ae722f8d17c7de8..0000000000000000000000000000000000000000 --- a/src/rugby-possibility-row.c +++ /dev/null @@ -1,239 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2018-2021 Bruce Cowan <bruce@bcowan.me.uk> - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#include "config.h" - -#include "rugby-possibility-row.h" - -#include <glib/gi18n.h> - -struct _RugbyPossibilityRow -{ - GtkListBoxRow parent_instance; - - GSettings *settings; - RugbyPossibility *possibility; -}; - -G_DEFINE_TYPE (RugbyPossibilityRow, rugby_possibility_row, GTK_TYPE_LIST_BOX_ROW) - -enum -{ - PROP_POSSIBILITY = 1, - N_PROPS -}; - -static GParamSpec *properties[N_PROPS]; - -static void -rugby_possibility_row_dispose (GObject *object) -{ - RugbyPossibilityRow *self = RUGBY_POSSIBILITY_ROW (object); - - g_clear_object (&self->settings); - g_clear_object (&self->possibility); - - G_OBJECT_CLASS (rugby_possibility_row_parent_class)->dispose (object); -} - -static void -rugby_possibility_row_get_property (GObject *object, - unsigned prop_id, - GValue *value, - GParamSpec *pspec) -{ - RugbyPossibilityRow *self = RUGBY_POSSIBILITY_ROW (object); - - switch (prop_id) - { - case PROP_POSSIBILITY: - g_value_set_object (value, self->possibility); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -rugby_possibility_row_set_property (GObject *object, - unsigned prop_id, - const GValue *value, - GParamSpec *pspec) -{ - RugbyPossibilityRow *self = RUGBY_POSSIBILITY_ROW (object); - - switch (prop_id) - { - case PROP_POSSIBILITY: - self->possibility = g_value_dup_object (value); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -render_bar (GtkSnapshot *snapshot, - float x, - float y, - float w, - float h, - const GdkRGBA color) -{ - GskRoundedRect rounded; - gsk_rounded_rect_init_from_rect (&rounded, - &GRAPHENE_RECT_INIT (x, y, w, h), - h / 2.0); - - gtk_snapshot_push_rounded_clip (snapshot, &rounded); - gtk_snapshot_append_color (snapshot, - &color, - &GRAPHENE_RECT_INIT (x, y, w, h)); - gtk_snapshot_pop (snapshot); - - GdkRGBA black = { 0.0, 0.0, 0.0, 0.2 }; - gtk_snapshot_append_border (snapshot, - &rounded, - (float[]) { 2.0, 2.0, 2.0, 2.0 }, - (GdkRGBA[]) { black, black, black, black }); -} - -static void -rugby_possibility_row_snapshot (GtkWidget *widget, - GtkSnapshot *snapshot) -{ - RugbyPossibilityRow *self = RUGBY_POSSIBILITY_ROW (widget); - - int try_points = g_settings_get_int (self->settings, "try-points"); - int utry_points = g_settings_get_int (self->settings, "utry-points"); - int kick_points = g_settings_get_int (self->settings, "kick-points"); - - int width = gtk_widget_get_width (widget); - int height = gtk_widget_get_height (widget); - float x = 0.0; - - int tries, utries, kicks; - g_object_get (self->possibility, - "tries", &tries, - "utries", &utries, - "kicks", &kicks, - NULL); - int score = tries * try_points + utries * utry_points + kicks * kick_points; - - // Tries - float w = width / (score / (float) try_points); - - for (int i = 0; i < tries; i++) - { - // Green - render_bar (snapshot, x, 0.0, w, height, - (GdkRGBA) { 0.20, 0.82, 0.48, 1.0 }); - x += w; - } - - // Unconverted tries - w = width / (score / (float) utry_points); - for (int i = 0; i < utries; i++) - { - // Red - render_bar (snapshot, x, 0.0, w, height, - (GdkRGBA) { 0.88, 0.11, 0.14, 1.0 }); - x += w; - } - - // Unconverted kicks - w = width / (score / (float) kick_points); - for (int i = 0; i < kicks; i++) - { - // Yellow - render_bar (snapshot, x, 0.0, w, height, - (GdkRGBA) { 0.96, 0.83, 0.18, 1.0 }); - x += w; - } -} - -static void -rugby_possibility_row_constructed (GObject *obj) -{ - RugbyPossibilityRow *self = RUGBY_POSSIBILITY_ROW (obj); - int tries, utries, kicks; - g_autoptr (GString) tooltip = g_string_new (NULL); - - g_object_get (self->possibility, - "tries", &tries, - "utries", &utries, - "kicks", &kicks, - NULL); - - if ((tries + utries) > 0) - { - if (tries == 1 && utries == 0) - g_string_printf (tooltip, "1 converted try"); - else if (tries == 0 && utries == 1) - g_string_printf (tooltip, "1 unconverted try"); - else - g_string_append_printf (tooltip, - ngettext ("%d try, %d converted", - "%d tries, %d converted", - tries + utries), - tries + utries, tries); - - if (kicks > 0) - g_string_append_printf (tooltip, - ngettext (", %d kick", - ", %d kicks", - kicks), - kicks); - } - else if (kicks > 0) - { - g_string_append_printf (tooltip, - ngettext ("%d kick", - "%d kicks", kicks), - kicks); - } - - gtk_widget_set_tooltip_text (GTK_WIDGET (self), tooltip->str); - - G_OBJECT_CLASS (rugby_possibility_row_parent_class)->constructed (obj); -} - -static void -rugby_possibility_row_class_init (RugbyPossibilityRowClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - - object_class->constructed = rugby_possibility_row_constructed; - object_class->dispose = rugby_possibility_row_dispose; - object_class->get_property = rugby_possibility_row_get_property; - object_class->set_property = rugby_possibility_row_set_property; - - widget_class->snapshot = rugby_possibility_row_snapshot; - - properties[PROP_POSSIBILITY] = - g_param_spec_object ("possibility", "", "", - RUGBY_TYPE_POSSIBILITY, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); - - g_object_class_install_properties (object_class, - N_PROPS, - properties); -} - -static void -rugby_possibility_row_init (RugbyPossibilityRow *self) -{ - self->settings = g_settings_new ("uk.me.bcowan.Rugby"); -} - -GtkWidget * -rugby_possibility_row_new (RugbyPossibility *possibility) -{ - return g_object_new (RUGBY_TYPE_POSSIBILITY_ROW, - "possibility", possibility, - NULL); -} diff --git a/src/rugby-possibility-row.h b/src/rugby-possibility-row.h deleted file mode 100644 index 868f048631f947c4805141cda570a7d2da3f1589..0000000000000000000000000000000000000000 --- a/src/rugby-possibility-row.h +++ /dev/null @@ -1,21 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2018-2021 Bruce Cowan <bruce@bcowan.me.uk> - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include <gtk/gtk.h> - -#include "rugby-possibility.h" - -G_BEGIN_DECLS - -#define RUGBY_TYPE_POSSIBILITY_ROW (rugby_possibility_row_get_type()) - -G_DECLARE_FINAL_TYPE (RugbyPossibilityRow, rugby_possibility_row, RUGBY, POSSIBILITY_ROW, GtkListBoxRow) - -GtkWidget * rugby_possibility_row_new (RugbyPossibility *possibility); - -G_END_DECLS diff --git a/src/rugby-possibility-widget.c b/src/rugby-possibility-widget.c new file mode 100644 index 0000000000000000000000000000000000000000..94a1c9482d5e0f45b3dbfbdd99a1ef5296896328 --- /dev/null +++ b/src/rugby-possibility-widget.c @@ -0,0 +1,190 @@ +/* + * SPDX-FileCopyrightText: 2018-2021 Bruce Cowan <bruce@bcowan.me.uk> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "rugby-possibility-widget.h" + +struct _RugbyPossibilityWidget +{ + GtkWidget parent_instance; + + GSettings *settings; + RugbyPossibility *possibility; +}; + +G_DEFINE_TYPE (RugbyPossibilityWidget, rugby_possibility_widget, GTK_TYPE_WIDGET) + +enum +{ + PROP_POSSIBILITY = 1, + N_PROPS +}; + +static GParamSpec *properties[N_PROPS]; + +static void +rugby_possibility_widget_dispose (GObject *object) +{ + RugbyPossibilityWidget *self = RUGBY_POSSIBILITY_WIDGET (object); + + g_clear_object (&self->settings); + g_clear_object (&self->possibility); + + G_OBJECT_CLASS (rugby_possibility_widget_parent_class)->dispose (object); +} + +static void +rugby_possibility_widget_get_property (GObject *object, + unsigned prop_id, + GValue *value, + GParamSpec *pspec) +{ + RugbyPossibilityWidget *self = RUGBY_POSSIBILITY_WIDGET (object); + + switch (prop_id) + { + case PROP_POSSIBILITY: + g_value_set_object (value, self->possibility); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +rugby_possibility_widget_set_property (GObject *object, + unsigned prop_id, + const GValue *value, + GParamSpec *pspec) +{ + RugbyPossibilityWidget *self = RUGBY_POSSIBILITY_WIDGET (object); + + switch (prop_id) + { + case PROP_POSSIBILITY: + self->possibility = g_value_dup_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +render_bar (GtkSnapshot *snapshot, + float x, + float w, + float h, + const GdkRGBA color) +{ + graphene_rect_t area = GRAPHENE_RECT_INIT (x, 0.0, w, h); + + GskRoundedRect rounded; + gsk_rounded_rect_init_from_rect (&rounded, + &area, + h / 2.0); + + gtk_snapshot_push_rounded_clip (snapshot, &rounded); + gtk_snapshot_append_color (snapshot, + &color, + &area); + gtk_snapshot_pop (snapshot); + + GdkRGBA black = { 0.0, 0.0, 0.0, 0.2 }; + gtk_snapshot_append_border (snapshot, + &rounded, + (float[]) { 2.0, 2.0, 2.0, 2.0 }, + (GdkRGBA[]) { black, black, black, black }); +} + +static void +rugby_possibility_widget_snapshot (GtkWidget *widget, + GtkSnapshot *snapshot) +{ + RugbyPossibilityWidget *self = RUGBY_POSSIBILITY_WIDGET (widget); + + int try_points = g_settings_get_int (self->settings, "try-points"); + int utry_points = g_settings_get_int (self->settings, "utry-points"); + int kick_points = g_settings_get_int (self->settings, "kick-points"); + + int width = gtk_widget_get_width (widget); + int height = gtk_widget_get_height (widget); + float x = 0.0; + + int tries, utries, kicks; + g_object_get (self->possibility, + "tries", &tries, + "utries", &utries, + "kicks", &kicks, + NULL); + int score = tries * try_points + utries * utry_points + kicks * kick_points; + + // Tries + float w = width / (score / (float) try_points); + for (int i = 0; i < tries; i++) + { + // Green + render_bar (snapshot, x, w, height, + (GdkRGBA) { 0.20, 0.82, 0.48, 1.0 }); + x += w; + } + + // Unconverted tries + w = width / (score / (float) utry_points); + for (int i = 0; i < utries; i++) + { + // Red + render_bar (snapshot, x, w, height, + (GdkRGBA) { 0.88, 0.11, 0.14, 1.0 }); + x += w; + } + + // Unconverted kicks + w = width / (score / (float) kick_points); + for (int i = 0; i < kicks; i++) + { + // Yellow + render_bar (snapshot, x, w, height, + (GdkRGBA) { 0.96, 0.83, 0.18, 1.0 }); + x += w; + } +} + +static void +rugby_possibility_widget_class_init (RugbyPossibilityWidgetClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = rugby_possibility_widget_dispose; + object_class->get_property = rugby_possibility_widget_get_property; + object_class->set_property = rugby_possibility_widget_set_property; + + widget_class->snapshot = rugby_possibility_widget_snapshot; + + properties[PROP_POSSIBILITY] = + g_param_spec_object ("possibility", "", "", + RUGBY_TYPE_POSSIBILITY, + G_PARAM_READWRITE); + + g_object_class_install_properties (object_class, + N_PROPS, + properties); +} + +static void +rugby_possibility_widget_init (RugbyPossibilityWidget *self) +{ + self->settings = g_settings_new ("uk.me.bcowan.Rugby"); +} + +GtkWidget * +rugby_possibility_widget_new (RugbyPossibility *possibility) +{ + return g_object_new (RUGBY_TYPE_POSSIBILITY_WIDGET, + "possibility", possibility, + NULL); +} diff --git a/src/rugby-possibility-widget.h b/src/rugby-possibility-widget.h new file mode 100644 index 0000000000000000000000000000000000000000..3c40b81d7b55df4ac4bafbff66de35514b9486ac --- /dev/null +++ b/src/rugby-possibility-widget.h @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: 2018-2021 Bruce Cowan <bruce@bcowan.me.uk> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <gtk/gtk.h> + +#include "rugby-possibility.h" + +G_BEGIN_DECLS + +#define RUGBY_TYPE_POSSIBILITY_WIDGET (rugby_possibility_widget_get_type()) + +G_DECLARE_FINAL_TYPE (RugbyPossibilityWidget, rugby_possibility_widget, RUGBY, POSSIBILITY_WIDGET, GtkWidget) + +GtkWidget * rugby_possibility_widget_new (RugbyPossibility *possibility); + +G_END_DECLS