mirror of
https://github.com/pepaskopec-blip/Graduately.git
synced 2026-10-27 15:14:35 +00:00
added in sections to the statistics
This commit is contained in:
parent
9bb04cd864
commit
7b289f0a87
1 changed files with 227 additions and 64 deletions
291
maturita.c
291
maturita.c
|
|
@ -7,6 +7,7 @@
|
|||
#define NUM_UNITS 10
|
||||
#define NUM_UNLOCKED 3 /* first NUM_UNLOCKED units (index 0..2) are open */
|
||||
#define MAX_UNIT_EX 20 /* largest exercise count any unit may have */
|
||||
#define NUM_SUBJECTS 13
|
||||
|
||||
#define PROGRESS_DIR "progress"
|
||||
#define PROGRESS_U1 "progress/unit1.conf"
|
||||
|
|
@ -129,6 +130,12 @@ typedef struct {
|
|||
|
||||
static UnitCtx units[NUM_UNITS] = {0};
|
||||
|
||||
/* Subject catalogue shared by the subjects page and the statistics page.
|
||||
* The full definitions live in the "Subjects page" section further down. */
|
||||
static const char *sub_keys[NUM_SUBJECTS];
|
||||
static const int sub_unit_start[NUM_SUBJECTS];
|
||||
static const int sub_unit_count[NUM_SUBJECTS];
|
||||
|
||||
/* Short German bubble titles, indexed 1..n_ex (entry 0 unused). */
|
||||
static const char *const u1_ex_names[MAX_UNIT_EX + 1] = {
|
||||
NULL,
|
||||
|
|
@ -502,12 +509,11 @@ static const TrEntry tr_ui[] = {
|
|||
{"hm_guess", "Hádejte písmeno", "Guess a letter"},
|
||||
{"stats", "Statistiky", "Statistics"},
|
||||
{"stats_title", "Statistiky", "Statistics"},
|
||||
{"stats_sub", "Přehled vašeho pokroku v němčině.",
|
||||
"Overview of your German learning progress."},
|
||||
{"stats_sub", "Přehled vašeho pokroku.", "Overview of your progress."},
|
||||
{"stats_ex_label", "Cvičení", "Exercises"},
|
||||
{"stats_pct_label", "Hotovo", "Done"},
|
||||
{"stats_units_label", "Jednotky", "Units"},
|
||||
{"stats_section", "Podle jednotek", "By unit"},
|
||||
{"stats_section", "Předměty", "Subjects"},
|
||||
{"stats_locked", "Zamčeno", "Locked"},
|
||||
{"stats_complete", "Hotovo", "Complete"},
|
||||
{"stats_ex_fmt", "%d / %d", "%d / %d"},
|
||||
|
|
@ -1677,6 +1683,45 @@ static char *build_theme_css(const ThemePalette *p) {
|
|||
" letter-spacing: 0.8px;"
|
||||
" margin-bottom: 10px;"
|
||||
"}"
|
||||
".stats-subject {"
|
||||
" margin-bottom: 20px;"
|
||||
"}"
|
||||
".stats-subject-name {"
|
||||
" color: @fg_text;"
|
||||
" font-size: 16px;"
|
||||
" font-weight: 800;"
|
||||
"}"
|
||||
".stats-subject-count {"
|
||||
" color: @fg_subtext;"
|
||||
" font-size: 13px;"
|
||||
" font-weight: 700;"
|
||||
"}"
|
||||
".stats-subject-count.locked {"
|
||||
" color: @fg_overlay;"
|
||||
"}"
|
||||
".stats-subject-bar,"
|
||||
".stats-subject-bar trough {"
|
||||
" min-height: 12px;"
|
||||
" border-radius: 999px;"
|
||||
" background-color: alpha(@bg_surface1, 0.85);"
|
||||
" border: none;"
|
||||
" padding: 0;"
|
||||
"}"
|
||||
".stats-subject-bar {"
|
||||
" margin: 4px 0 0 0;"
|
||||
"}"
|
||||
".stats-subject-bar progress {"
|
||||
" min-height: 10px;"
|
||||
" border-radius: 999px;"
|
||||
" background-image: linear-gradient(90deg, @accent 0%%, @accent2 100%%);"
|
||||
" background-color: @accent;"
|
||||
" border: none;"
|
||||
"}"
|
||||
".stats-units {"
|
||||
" margin-top: 8px;"
|
||||
" border-left: 2px solid alpha(@fg_overlay, 0.22);"
|
||||
" padding-left: 12px;"
|
||||
"}"
|
||||
".stats-unit-row {"
|
||||
" background-color: alpha(@bg_surface0, 0.72);"
|
||||
" border: 1px solid @bg_surface1;"
|
||||
|
|
@ -2236,6 +2281,16 @@ typedef struct {
|
|||
GtkWidget *pct_value;
|
||||
GtkWidget *units_value;
|
||||
GtkWidget *overall_bar;
|
||||
|
||||
/* one progress block per subject (index matches sub_keys[]) */
|
||||
struct {
|
||||
GtkWidget *name; /* subject heading label */
|
||||
GtkWidget *count; /* "13 / 47" or the locked text */
|
||||
GtkWidget *bar; /* per-subject overall progress bar */
|
||||
GtkWidget *units_box; /* holds the unit rows, NULL if no units */
|
||||
} subj[NUM_SUBJECTS];
|
||||
|
||||
/* per-unit rows, indexed by the global units[] index */
|
||||
GtkWidget *unit_count[NUM_UNITS];
|
||||
GtkWidget *unit_badge[NUM_UNITS];
|
||||
GtkWidget *unit_bar[NUM_UNITS];
|
||||
|
|
@ -2257,31 +2312,48 @@ static int unit_done_count(const UnitCtx *u) {
|
|||
return n;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
int done_ex;
|
||||
int total_ex;
|
||||
int done_units;
|
||||
int open_units;
|
||||
} ProgressSum;
|
||||
|
||||
/* Sum progress over units[first .. first+n-1]. Locked placeholder units and
|
||||
* the finish node (no playable exercises) are ignored. */
|
||||
static 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++;
|
||||
}
|
||||
}
|
||||
|
||||
static void refresh_stats_ui(void) {
|
||||
int done_ex = 0;
|
||||
int total_ex = 0;
|
||||
int done_units = 0;
|
||||
int open_units = 0;
|
||||
ProgressSum all = {0};
|
||||
int pct;
|
||||
char *tmp;
|
||||
|
||||
if (!stats_ui.ex_value)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < NUM_UNLOCKED; i++) {
|
||||
UnitCtx *u = &units[i];
|
||||
int d = unit_done_count(u);
|
||||
for (int s = 0; s < NUM_SUBJECTS; s++)
|
||||
progress_for_units(sub_unit_start[s], sub_unit_count[s], &all);
|
||||
|
||||
done_ex += d;
|
||||
total_ex += u->n_ex;
|
||||
open_units++;
|
||||
if (u->n_ex > 0 && d == u->n_ex)
|
||||
done_units++;
|
||||
}
|
||||
pct = all.total_ex > 0 ? (all.done_ex * 100) / all.total_ex : 0;
|
||||
|
||||
pct = total_ex > 0 ? (done_ex * 100) / total_ex : 0;
|
||||
|
||||
tmp = g_strdup_printf(tr("stats_ex_fmt"), done_ex, total_ex);
|
||||
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);
|
||||
|
||||
|
|
@ -2289,13 +2361,36 @@ static void refresh_stats_ui(void) {
|
|||
gtk_label_set_text(GTK_LABEL(stats_ui.pct_value), tmp);
|
||||
g_free(tmp);
|
||||
|
||||
tmp = g_strdup_printf(tr("stats_units_fmt"), done_units, open_units);
|
||||
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),
|
||||
total_ex > 0 ? (double)done_ex / (double)total_ex : 0.0);
|
||||
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];
|
||||
|
|
@ -2392,6 +2487,69 @@ static GtkWidget *stats_metric_card(const char *label_key, GtkWidget **value_out
|
|||
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). */
|
||||
static 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;
|
||||
}
|
||||
|
||||
static GtkWidget *build_stats_page(void) {
|
||||
GtkWidget *outer;
|
||||
GtkWidget *scroll;
|
||||
|
|
@ -2476,64 +2634,60 @@ static GtkWidget *build_stats_page(void) {
|
|||
list = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||
gtk_box_append(GTK_BOX(page), list);
|
||||
|
||||
for (int i = 0; i < NUM_UNITS; i++) {
|
||||
GtkWidget *row;
|
||||
for (int s = 0; s < NUM_SUBJECTS; s++) {
|
||||
GtkWidget *subject;
|
||||
GtkWidget *head;
|
||||
GtkWidget *left;
|
||||
GtkWidget *idx;
|
||||
GtkWidget *name;
|
||||
GtkWidget *right;
|
||||
GtkWidget *count;
|
||||
GtkWidget *badge;
|
||||
GtkWidget *bar;
|
||||
char num[8];
|
||||
gboolean has_units = sub_unit_count[s] > 0;
|
||||
|
||||
row = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
|
||||
gtk_widget_add_css_class(row, "stats-unit-row");
|
||||
gtk_box_append(GTK_BOX(list), row);
|
||||
stats_ui.unit_row[i] = row;
|
||||
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(row), head);
|
||||
gtk_box_append(GTK_BOX(subject), head);
|
||||
|
||||
left = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
|
||||
gtk_widget_set_hexpand(left, TRUE);
|
||||
gtk_box_append(GTK_BOX(head), left);
|
||||
|
||||
g_snprintf(num, sizeof(num), "%d", i + 1);
|
||||
idx = gtk_label_new(num);
|
||||
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(units[i].title);
|
||||
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-unit-name");
|
||||
gtk_box_append(GTK_BOX(left), name);
|
||||
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;
|
||||
|
||||
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[i] = 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[i] = count;
|
||||
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-unit-bar");
|
||||
gtk_widget_add_css_class(bar, "stats-subject-bar");
|
||||
gtk_widget_set_hexpand(bar, TRUE);
|
||||
gtk_box_append(GTK_BOX(row), bar);
|
||||
stats_ui.unit_bar[i] = bar;
|
||||
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;
|
||||
|
|
@ -3119,7 +3273,6 @@ static GtkWidget *build_roadmap_page(void) {
|
|||
/* Subjects page ("Předměty") - serpentine subject map */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
#define NUM_SUBJECTS 13
|
||||
#define SUB_BUBBLE NODE_SIZE /* round node, matches .unit-node */
|
||||
#define SUB_MX 110.0
|
||||
#define SUB_MY 150.0
|
||||
|
|
@ -3144,6 +3297,16 @@ static const char *sub_keys[NUM_SUBJECTS] = {
|
|||
"Programové vybavení",
|
||||
};
|
||||
|
||||
/* Slice of the global `units[]` table that each subject owns. A start of -1
|
||||
* (or a count of 0) means the subject has no content yet; such subjects show
|
||||
* as locked on the statistics page until their units are added. */
|
||||
static const int sub_unit_start[NUM_SUBJECTS] = {
|
||||
0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
};
|
||||
static const int sub_unit_count[NUM_SUBJECTS] = {
|
||||
NUM_UNITS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
};
|
||||
|
||||
static GtkWidget *sub_scroll;
|
||||
static GtkWidget *sub_fixed;
|
||||
static GtkWidget *sub_rail;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue