mirror of
https://github.com/pepaskopec-blip/Graduately.git
synced 2026-10-27 15:14:35 +00:00
Split the UI code into multiple files
This commit is contained in:
parent
cc18cb100d
commit
56fe5ae6fc
23 changed files with 10140 additions and 9559 deletions
399
stats.c
Normal file
399
stats.c
Normal file
|
|
@ -0,0 +1,399 @@
|
|||
#include "maturita.h"
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Statistics */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
GtkWidget *make_back_button(const char *target);
|
||||
void on_nav_clicked(GtkButton *button, gpointer user_data);
|
||||
|
||||
|
||||
StatsUi stats_ui;
|
||||
char *stats_return_page;
|
||||
|
||||
int unit_done_count(const UnitCtx *u) {
|
||||
int n = 0;
|
||||
|
||||
if (!u)
|
||||
return 0;
|
||||
for (int i = 1; i <= u->n_ex; i++) {
|
||||
if (u->done[i])
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
/* Sum progress over units[first .. first+n-1]. Locked placeholder units and
|
||||
* the finish node (no playable exercises) are ignored. */
|
||||
void progress_for_units(int first, int n, ProgressSum *out) {
|
||||
for (int j = 0; j < n; j++) {
|
||||
UnitCtx *u;
|
||||
int d;
|
||||
|
||||
if (first < 0 || first + j >= NUM_UNITS)
|
||||
continue;
|
||||
u = &units[first + j];
|
||||
if (!u->unlocked || u->n_ex <= 0)
|
||||
continue;
|
||||
d = unit_done_count(u);
|
||||
out->done_ex += d;
|
||||
out->total_ex += u->n_ex;
|
||||
out->open_units++;
|
||||
if (d == u->n_ex)
|
||||
out->done_units++;
|
||||
}
|
||||
}
|
||||
|
||||
void refresh_stats_ui(void) {
|
||||
ProgressSum all = {0};
|
||||
int pct;
|
||||
char *tmp;
|
||||
|
||||
if (!stats_ui.ex_value)
|
||||
return;
|
||||
|
||||
for (int s = 0; s < NUM_SUBJECTS; s++)
|
||||
progress_for_units(sub_unit_start[s], sub_unit_count[s], &all);
|
||||
|
||||
pct = all.total_ex > 0 ? (all.done_ex * 100) / all.total_ex : 0;
|
||||
|
||||
tmp = g_strdup_printf(tr("stats_ex_fmt"), all.done_ex, all.total_ex);
|
||||
gtk_label_set_text(GTK_LABEL(stats_ui.ex_value), tmp);
|
||||
g_free(tmp);
|
||||
|
||||
tmp = g_strdup_printf(tr("stats_pct_fmt"), pct);
|
||||
gtk_label_set_text(GTK_LABEL(stats_ui.pct_value), tmp);
|
||||
g_free(tmp);
|
||||
|
||||
tmp = g_strdup_printf(tr("stats_units_fmt"), all.done_units,
|
||||
all.open_units);
|
||||
gtk_label_set_text(GTK_LABEL(stats_ui.units_value), tmp);
|
||||
g_free(tmp);
|
||||
|
||||
gtk_progress_bar_set_fraction(
|
||||
GTK_PROGRESS_BAR(stats_ui.overall_bar),
|
||||
all.total_ex > 0 ? (double)all.done_ex / (double)all.total_ex : 0.0);
|
||||
|
||||
for (int s = 0; s < NUM_SUBJECTS; s++) {
|
||||
GtkWidget *count = stats_ui.subj[s].count;
|
||||
GtkWidget *bar = stats_ui.subj[s].bar;
|
||||
ProgressSum sp = {0};
|
||||
|
||||
if (!count || !bar)
|
||||
continue;
|
||||
progress_for_units(sub_unit_start[s], sub_unit_count[s], &sp);
|
||||
if (sub_unit_count[s] > 0) {
|
||||
tmp = g_strdup_printf(tr("stats_ex_fmt"), sp.done_ex, sp.total_ex);
|
||||
gtk_label_set_text(GTK_LABEL(count), tmp);
|
||||
g_free(tmp);
|
||||
gtk_progress_bar_set_fraction(
|
||||
GTK_PROGRESS_BAR(bar),
|
||||
sp.total_ex > 0 ? (double)sp.done_ex / (double)sp.total_ex
|
||||
: 0.0);
|
||||
} else {
|
||||
gtk_label_set_text(GTK_LABEL(count), tr("stats_locked"));
|
||||
gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(bar), 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < NUM_UNITS; i++) {
|
||||
UnitCtx *u = &units[i];
|
||||
GtkWidget *count = stats_ui.unit_count[i];
|
||||
GtkWidget *badge = stats_ui.unit_badge[i];
|
||||
GtkWidget *bar = stats_ui.unit_bar[i];
|
||||
GtkWidget *row = stats_ui.unit_row[i];
|
||||
gboolean unlocked = u->unlocked && u->n_ex > 0;
|
||||
int d = unlocked ? unit_done_count(u) : 0;
|
||||
gboolean complete = unlocked && d == u->n_ex;
|
||||
|
||||
if (!count || !bar || !row)
|
||||
continue;
|
||||
|
||||
gtk_widget_remove_css_class(row, "done");
|
||||
gtk_widget_remove_css_class(row, "locked");
|
||||
gtk_widget_remove_css_class(bar, "done");
|
||||
|
||||
if (!unlocked) {
|
||||
gtk_widget_add_css_class(row, "locked");
|
||||
gtk_label_set_text(GTK_LABEL(count), tr("stats_locked"));
|
||||
gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(bar), 0.0);
|
||||
if (badge)
|
||||
gtk_widget_set_visible(badge, FALSE);
|
||||
continue;
|
||||
}
|
||||
|
||||
tmp = g_strdup_printf(tr("stats_ex_fmt"), d, u->n_ex);
|
||||
gtk_label_set_text(GTK_LABEL(count), tmp);
|
||||
g_free(tmp);
|
||||
gtk_progress_bar_set_fraction(
|
||||
GTK_PROGRESS_BAR(bar),
|
||||
u->n_ex > 0 ? (double)d / (double)u->n_ex : 0.0);
|
||||
|
||||
if (complete) {
|
||||
gtk_widget_add_css_class(row, "done");
|
||||
gtk_widget_add_css_class(bar, "done");
|
||||
if (badge) {
|
||||
gtk_label_set_text(GTK_LABEL(badge), tr("stats_complete"));
|
||||
gtk_widget_set_visible(badge, TRUE);
|
||||
}
|
||||
} else if (badge) {
|
||||
gtk_widget_set_visible(badge, FALSE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void on_stats_back_clicked(GtkButton *button, gpointer user_data) {
|
||||
const char *target = stats_return_page;
|
||||
|
||||
(void)button;
|
||||
(void)user_data;
|
||||
if (!target || !target[0])
|
||||
target = "subjects";
|
||||
gtk_stack_set_visible_child_name(main_stack, target);
|
||||
}
|
||||
|
||||
void on_stats_clicked(GtkButton *button, gpointer user_data) {
|
||||
const char *cur;
|
||||
|
||||
(void)button;
|
||||
(void)user_data;
|
||||
|
||||
cur = gtk_stack_get_visible_child_name(main_stack);
|
||||
if (cur && g_strcmp0(cur, "stats") != 0) {
|
||||
g_free(stats_return_page);
|
||||
stats_return_page = g_strdup(cur);
|
||||
}
|
||||
refresh_stats_ui();
|
||||
gtk_stack_set_visible_child_name(main_stack, "stats");
|
||||
}
|
||||
|
||||
GtkWidget *stats_metric_card(const char *label_key, GtkWidget **value_out) {
|
||||
GtkWidget *card;
|
||||
GtkWidget *value;
|
||||
GtkWidget *label;
|
||||
|
||||
card = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||
gtk_widget_add_css_class(card, "stats-metric");
|
||||
gtk_widget_set_hexpand(card, TRUE);
|
||||
|
||||
value = gtk_label_new("0");
|
||||
gtk_widget_set_halign(value, GTK_ALIGN_START);
|
||||
gtk_widget_add_css_class(value, "stats-metric-value");
|
||||
gtk_box_append(GTK_BOX(card), value);
|
||||
|
||||
label = gtk_label_new(NULL);
|
||||
i18n_bind(label, label_key, 0);
|
||||
gtk_widget_set_halign(label, GTK_ALIGN_START);
|
||||
gtk_widget_add_css_class(label, "stats-metric-label");
|
||||
gtk_box_append(GTK_BOX(card), label);
|
||||
|
||||
*value_out = value;
|
||||
return card;
|
||||
}
|
||||
|
||||
/* Build one unit row (index, name, badge, count, bar) for the stats list.
|
||||
* `num` is the row number shown (1-based within its subject). */
|
||||
GtkWidget *make_unit_stats_row(UnitCtx *u, int num, int global) {
|
||||
GtkWidget *row;
|
||||
GtkWidget *head;
|
||||
GtkWidget *left;
|
||||
GtkWidget *idx;
|
||||
GtkWidget *name;
|
||||
GtkWidget *right;
|
||||
GtkWidget *count;
|
||||
GtkWidget *badge;
|
||||
GtkWidget *bar;
|
||||
char text[8];
|
||||
|
||||
row = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
|
||||
gtk_widget_add_css_class(row, "stats-unit-row");
|
||||
stats_ui.unit_row[global] = row;
|
||||
|
||||
head = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10);
|
||||
gtk_box_append(GTK_BOX(row), head);
|
||||
|
||||
left = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
|
||||
gtk_widget_set_hexpand(left, TRUE);
|
||||
gtk_box_append(GTK_BOX(head), left);
|
||||
|
||||
g_snprintf(text, sizeof(text), "%d", num);
|
||||
idx = gtk_label_new(text);
|
||||
gtk_widget_add_css_class(idx, "stats-unit-index");
|
||||
gtk_widget_set_valign(idx, GTK_ALIGN_CENTER);
|
||||
gtk_box_append(GTK_BOX(left), idx);
|
||||
|
||||
name = gtk_label_new(u->title);
|
||||
gtk_widget_set_halign(name, GTK_ALIGN_START);
|
||||
gtk_widget_set_hexpand(name, TRUE);
|
||||
gtk_label_set_xalign(GTK_LABEL(name), 0.0);
|
||||
gtk_label_set_ellipsize(GTK_LABEL(name), PANGO_ELLIPSIZE_END);
|
||||
gtk_widget_add_css_class(name, "stats-unit-name");
|
||||
gtk_box_append(GTK_BOX(left), name);
|
||||
|
||||
right = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
|
||||
gtk_widget_set_valign(right, GTK_ALIGN_CENTER);
|
||||
gtk_box_append(GTK_BOX(head), right);
|
||||
|
||||
badge = gtk_label_new(NULL);
|
||||
gtk_widget_add_css_class(badge, "stats-unit-badge");
|
||||
gtk_widget_set_visible(badge, FALSE);
|
||||
gtk_box_append(GTK_BOX(right), badge);
|
||||
stats_ui.unit_badge[global] = badge;
|
||||
|
||||
count = gtk_label_new("");
|
||||
gtk_widget_add_css_class(count, "stats-unit-count");
|
||||
gtk_box_append(GTK_BOX(right), count);
|
||||
stats_ui.unit_count[global] = count;
|
||||
|
||||
bar = gtk_progress_bar_new();
|
||||
gtk_widget_add_css_class(bar, "stats-unit-bar");
|
||||
gtk_widget_set_hexpand(bar, TRUE);
|
||||
gtk_box_append(GTK_BOX(row), bar);
|
||||
stats_ui.unit_bar[global] = bar;
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
GtkWidget *build_stats_page(void) {
|
||||
GtkWidget *outer;
|
||||
GtkWidget *scroll;
|
||||
GtkWidget *page;
|
||||
GtkWidget *top;
|
||||
GtkWidget *back;
|
||||
GtkWidget *center;
|
||||
GtkWidget *heading;
|
||||
GtkWidget *sub;
|
||||
GtkWidget *summary;
|
||||
GtkWidget *section;
|
||||
GtkWidget *list;
|
||||
|
||||
outer = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||
gtk_widget_add_css_class(outer, "stats-page");
|
||||
|
||||
top = gtk_center_box_new();
|
||||
gtk_widget_set_margin_top(top, 2);
|
||||
gtk_widget_set_margin_bottom(top, 6);
|
||||
gtk_box_append(GTK_BOX(outer), top);
|
||||
|
||||
back = make_back_button("subjects");
|
||||
g_signal_handlers_disconnect_matched(back, G_SIGNAL_MATCH_FUNC,
|
||||
0, 0, NULL, on_nav_clicked, NULL);
|
||||
g_signal_connect(back, "clicked", G_CALLBACK(on_stats_back_clicked), NULL);
|
||||
gtk_center_box_set_start_widget(GTK_CENTER_BOX(top), back);
|
||||
|
||||
center = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
|
||||
gtk_widget_set_valign(center, GTK_ALIGN_CENTER);
|
||||
gtk_center_box_set_center_widget(GTK_CENTER_BOX(top), center);
|
||||
|
||||
heading = gtk_label_new(NULL);
|
||||
i18n_bind(heading, "stats_title", 0);
|
||||
gtk_widget_set_halign(heading, GTK_ALIGN_CENTER);
|
||||
gtk_widget_add_css_class(heading, "roadmap-title");
|
||||
gtk_box_append(GTK_BOX(center), heading);
|
||||
|
||||
sub = gtk_label_new(NULL);
|
||||
i18n_bind(sub, "stats_sub", 0);
|
||||
gtk_widget_set_halign(sub, GTK_ALIGN_CENTER);
|
||||
gtk_widget_add_css_class(sub, "roadmap-sub");
|
||||
gtk_label_set_wrap(GTK_LABEL(sub), TRUE);
|
||||
gtk_box_append(GTK_BOX(center), sub);
|
||||
|
||||
scroll = gtk_scrolled_window_new();
|
||||
gtk_widget_set_vexpand(scroll, TRUE);
|
||||
gtk_widget_set_hexpand(scroll, TRUE);
|
||||
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
|
||||
GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
|
||||
gtk_box_append(GTK_BOX(outer), scroll);
|
||||
|
||||
page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||
gtk_widget_set_halign(page, GTK_ALIGN_FILL);
|
||||
gtk_widget_set_margin_start(page, 4);
|
||||
gtk_widget_set_margin_end(page, 4);
|
||||
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroll), page);
|
||||
|
||||
summary = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10);
|
||||
gtk_widget_add_css_class(summary, "stats-summary");
|
||||
gtk_widget_set_halign(summary, GTK_ALIGN_FILL);
|
||||
gtk_box_append(GTK_BOX(page), summary);
|
||||
|
||||
gtk_box_append(GTK_BOX(summary),
|
||||
stats_metric_card("stats_ex_label", &stats_ui.ex_value));
|
||||
gtk_box_append(GTK_BOX(summary),
|
||||
stats_metric_card("stats_pct_label", &stats_ui.pct_value));
|
||||
gtk_box_append(GTK_BOX(summary),
|
||||
stats_metric_card("stats_units_label",
|
||||
&stats_ui.units_value));
|
||||
|
||||
stats_ui.overall_bar = gtk_progress_bar_new();
|
||||
gtk_widget_add_css_class(stats_ui.overall_bar, "stats-overall-bar");
|
||||
gtk_widget_set_hexpand(stats_ui.overall_bar, TRUE);
|
||||
gtk_box_append(GTK_BOX(page), stats_ui.overall_bar);
|
||||
|
||||
section = gtk_label_new(NULL);
|
||||
i18n_bind(section, "stats_section", 0);
|
||||
gtk_widget_set_halign(section, GTK_ALIGN_START);
|
||||
gtk_widget_add_css_class(section, "stats-section");
|
||||
gtk_box_append(GTK_BOX(page), section);
|
||||
|
||||
list = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||
gtk_box_append(GTK_BOX(page), list);
|
||||
|
||||
for (int s = 0; s < NUM_SUBJECTS; s++) {
|
||||
GtkWidget *subject;
|
||||
GtkWidget *head;
|
||||
GtkWidget *name;
|
||||
GtkWidget *count;
|
||||
GtkWidget *bar;
|
||||
gboolean has_units = sub_unit_count[s] > 0;
|
||||
|
||||
subject = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||
gtk_widget_add_css_class(subject, "stats-subject");
|
||||
gtk_box_append(GTK_BOX(list), subject);
|
||||
|
||||
head = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10);
|
||||
gtk_box_append(GTK_BOX(subject), head);
|
||||
|
||||
name = gtk_label_new(NULL);
|
||||
i18n_bind(name, sub_keys[s], 0);
|
||||
gtk_widget_set_halign(name, GTK_ALIGN_START);
|
||||
gtk_widget_set_hexpand(name, TRUE);
|
||||
gtk_label_set_xalign(GTK_LABEL(name), 0.0);
|
||||
gtk_label_set_ellipsize(GTK_LABEL(name), PANGO_ELLIPSIZE_END);
|
||||
gtk_widget_add_css_class(name, "stats-subject-name");
|
||||
if (!has_units)
|
||||
gtk_widget_add_css_class(name, "unit-name-locked");
|
||||
gtk_box_append(GTK_BOX(head), name);
|
||||
stats_ui.subj[s].name = name;
|
||||
|
||||
count = gtk_label_new(NULL);
|
||||
gtk_widget_add_css_class(count, "stats-subject-count");
|
||||
if (!has_units)
|
||||
gtk_widget_add_css_class(count, "locked");
|
||||
gtk_box_append(GTK_BOX(head), count);
|
||||
stats_ui.subj[s].count = count;
|
||||
|
||||
bar = gtk_progress_bar_new();
|
||||
gtk_widget_add_css_class(bar, "stats-subject-bar");
|
||||
gtk_widget_set_hexpand(bar, TRUE);
|
||||
gtk_box_append(GTK_BOX(subject), bar);
|
||||
stats_ui.subj[s].bar = bar;
|
||||
|
||||
if (has_units) {
|
||||
GtkWidget *units_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||
|
||||
gtk_widget_add_css_class(units_box, "stats-units");
|
||||
gtk_box_append(GTK_BOX(subject), units_box);
|
||||
stats_ui.subj[s].units_box = units_box;
|
||||
|
||||
for (int j = 0; j < sub_unit_count[s]; j++) {
|
||||
int gi = sub_unit_start[s] + j;
|
||||
|
||||
gtk_box_append(GTK_BOX(units_box),
|
||||
make_unit_stats_row(&units[gi], j + 1, gi));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return outer;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue