Added a back button after you clik "Pokračuj" and refreshed the menu with the units

This commit is contained in:
Martin Dvořák 2026-09-03 21:15:09 +02:00
commit 58487c24d5

View file

@ -24,10 +24,24 @@ static double path_y_at(double t) {
return mid + PATH_AMP * sin(t * phase); return mid + PATH_AMP * sin(t * phase);
} }
typedef struct { double r, g, b; } Rgb;
static Rgb color_from_hex(unsigned int hex) {
Rgb c;
c.r = (double)((hex >> 16) & 0xFF) / 255.0;
c.g = (double)((hex >> 8) & 0xFF) / 255.0;
c.b = (double)(hex & 0xFF) / 255.0;
return c;
}
static void on_continue_clicked(GtkButton *button, gpointer user_data) { static void on_continue_clicked(GtkButton *button, gpointer user_data) {
gtk_stack_set_visible_child_name(main_stack, "roadmap"); gtk_stack_set_visible_child_name(main_stack, "roadmap");
} }
static void on_back_clicked(GtkButton *button, gpointer user_data) {
gtk_stack_set_visible_child_name(main_stack, "welcome");
}
static gboolean on_window_key_pressed(GtkEventControllerKey *controller, static gboolean on_window_key_pressed(GtkEventControllerKey *controller,
guint keyval, guint keycode, guint keyval, guint keycode,
GdkModifierType state, GdkModifierType state,
@ -94,11 +108,56 @@ static GtkWidget *build_welcome_page(void) {
return box; return box;
} }
static void draw_node_halo(cairo_t *cr, double cx, double cy,
double radius, double r, double g, double b,
double alpha) {
cairo_pattern_t *grad;
grad = cairo_pattern_create_radial(cx, cy, 6.0, cx, cy, radius);
cairo_pattern_add_color_stop_rgba(grad, 0.0, r, g, b, alpha);
cairo_pattern_add_color_stop_rgba(grad, 0.55, r, g, b, alpha * 0.30);
cairo_pattern_add_color_stop_rgba(grad, 1.0, r, g, b, 0.0);
cairo_set_source(cr, grad);
cairo_paint(cr);
cairo_pattern_destroy(grad);
}
static void draw_finish_flag(cairo_t *cr, double cx, double cy) {
double base_y = cy - NODE_SIZE / 2.0;
double top_y = base_y - 84.0;
double mid_y = top_y + 16.0;
cairo_new_path(cr);
cairo_move_to(cr, cx - 2.0, base_y);
cairo_line_to(cr, cx + 2.0, base_y);
cairo_line_to(cr, cx + 2.0, top_y);
cairo_line_to(cr, cx - 2.0, top_y);
cairo_close_path(cr);
cairo_set_source_rgb(cr, 0.804, 0.820, 0.875);
cairo_fill(cr);
cairo_new_path(cr);
cairo_arc(cr, cx, top_y - 4.0, 5.0, 0.0, 2.0 * G_PI);
cairo_set_source_rgb(cr, 0.969, 0.698, 0.671);
cairo_fill(cr);
cairo_new_path(cr);
cairo_move_to(cr, cx, top_y + 2.0);
cairo_line_to(cr, cx + 42.0, mid_y);
cairo_line_to(cr, cx, top_y + 30.0);
cairo_close_path(cr);
cairo_set_source_rgb(cr, 0.796, 0.651, 0.969);
cairo_fill(cr);
}
static void draw_rail(GtkDrawingArea *area, cairo_t *cr, static void draw_rail(GtkDrawingArea *area, cairo_t *cr,
int width, int height, gpointer user_data) { int width, int height, gpointer user_data) {
const int segments = (NUM_UNITS - 1) * 60; const int segments = (NUM_UNITS - 1) * 60;
const double t_end = (double)(NUM_UNITS - 1); const double t_end = (double)(NUM_UNITS - 1);
const double t_lit = (double)(FIRST_LOCKED - 1); const double t_lit = (double)(FIRST_LOCKED - 1);
const Rgb mauve = color_from_hex(0xcba6f7);
const Rgb blue = color_from_hex(0x89b4fa);
double x0 = path_x_at(0);
double x1 = path_x_at(NUM_UNITS - 1);
int k; int k;
(void)area; (void)area;
@ -112,6 +171,10 @@ static void draw_rail(GtkDrawingArea *area, cairo_t *cr,
cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND); cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
cairo_set_line_join(cr, CAIRO_LINE_JOIN_ROUND); cairo_set_line_join(cr, CAIRO_LINE_JOIN_ROUND);
draw_node_halo(cr, path_x_at(FIRST_LOCKED - 1),
path_y_at(FIRST_LOCKED - 1),
NODE_SIZE * 1.05, mauve.r, mauve.g, mauve.b, 0.18);
cairo_new_path(cr); cairo_new_path(cr);
cairo_move_to(cr, path_x_at(0), path_y_at(0)); cairo_move_to(cr, path_x_at(0), path_y_at(0));
for (k = 1; k <= segments; k++) { for (k = 1; k <= segments; k++) {
@ -119,28 +182,65 @@ static void draw_rail(GtkDrawingArea *area, cairo_t *cr,
cairo_line_to(cr, path_x_at(t), path_y_at(t)); cairo_line_to(cr, path_x_at(t), path_y_at(t));
} }
cairo_set_line_width(cr, 16); cairo_set_line_width(cr, 16);
cairo_set_source_rgb(cr, 0.27059, 0.27843, 0.35294); cairo_set_source_rgb(cr, 0.153, 0.157, 0.235);
cairo_stroke(cr); cairo_stroke(cr);
if (t_lit > 0.0) { if (t_lit > 0.0) {
int lit_segments = (int)(t_lit / t_end * (double)segments + 0.5); int lit_segments;
cairo_pattern_t *grad;
lit_segments = (int)(t_lit / t_end * (double)segments + 0.5);
cairo_new_path(cr); cairo_new_path(cr);
cairo_move_to(cr, path_x_at(0), path_y_at(0)); cairo_move_to(cr, path_x_at(0), path_y_at(0));
for (k = 1; k <= lit_segments; k++) { for (k = 1; k <= lit_segments; k++) {
double t = t_end * (double)k / (double)segments; double t = t_end * (double)k / (double)segments;
cairo_line_to(cr, path_x_at(t), path_y_at(t)); cairo_line_to(cr, path_x_at(t), path_y_at(t));
} }
cairo_set_source_rgba(cr, 0.79608, 0.65098, 0.96863, 0.20);
cairo_set_line_width(cr, 36); grad = cairo_pattern_create_linear(x0, 0.0, x1, 0.0);
cairo_pattern_add_color_stop_rgba(grad, 0.0,
mauve.r, mauve.g, mauve.b, 0.16);
cairo_pattern_add_color_stop_rgba(grad, 1.0,
blue.r, blue.g, blue.b, 0.16);
cairo_set_source(cr, grad);
cairo_set_line_width(cr, 34);
cairo_stroke_preserve(cr); cairo_stroke_preserve(cr);
cairo_set_source_rgb(cr, 0.79608, 0.65098, 0.96863); cairo_pattern_destroy(grad);
grad = cairo_pattern_create_linear(x0, 0.0, x1, 0.0);
cairo_pattern_add_color_stop_rgba(grad, 0.0,
mauve.r, mauve.g, mauve.b, 1.0);
cairo_pattern_add_color_stop_rgba(grad, 1.0,
blue.r, blue.g, blue.b, 1.0);
cairo_set_source(cr, grad);
cairo_set_line_width(cr, 16); cairo_set_line_width(cr, 16);
cairo_stroke(cr); cairo_stroke_preserve(cr);
cairo_pattern_destroy(grad);
} }
{
double start_t = t_lit > 0.0 ? t_lit : 0.0;
int first_seg = (int)(start_t / t_end * (double)segments + 0.5);
cairo_set_source_rgba(cr, 0.651, 0.678, 0.784, 0.17);
cairo_set_dash(cr, (double[]){1.0, 26.0}, 2, 0.0);
cairo_new_path(cr);
cairo_move_to(cr, path_x_at(start_t), path_y_at(start_t));
for (k = first_seg; k <= segments; k++) {
double t = t_end * (double)k / (double)segments;
cairo_line_to(cr, path_x_at(t), path_y_at(t));
}
cairo_stroke(cr);
cairo_set_dash(cr, NULL, 0, 0.0);
}
draw_finish_flag(cr, path_x_at(NUM_UNITS - 1),
path_y_at(NUM_UNITS - 1));
} }
static void add_path_node(GtkFixed *fixed, int index) { static void add_path_node(GtkFixed *fixed, int index) {
int num = index + 1; int num = index + 1;
gboolean done = index < FIRST_LOCKED - 1;
gboolean current = index == FIRST_LOCKED - 1;
gboolean locked = index >= FIRST_LOCKED; gboolean locked = index >= FIRST_LOCKED;
double cx = path_x_at(index); double cx = path_x_at(index);
double cy = path_y_at(index); double cy = path_y_at(index);
@ -153,7 +253,11 @@ static void add_path_node(GtkFixed *fixed, int index) {
gtk_widget_add_css_class(card, "unit-node"); gtk_widget_add_css_class(card, "unit-node");
gtk_widget_set_sensitive(card, !locked); gtk_widget_set_sensitive(card, !locked);
gtk_widget_set_size_request(card, (int)NODE_SIZE, (int)NODE_SIZE); gtk_widget_set_size_request(card, (int)NODE_SIZE, (int)NODE_SIZE);
if (locked) if (done)
gtk_widget_add_css_class(card, "done");
else if (current)
gtk_widget_add_css_class(card, "current");
else
gtk_widget_add_css_class(card, "locked"); gtk_widget_add_css_class(card, "locked");
vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2); vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2);
@ -167,7 +271,13 @@ static void add_path_node(GtkFixed *fixed, int index) {
gtk_widget_add_css_class(number, "unit-number"); gtk_widget_add_css_class(number, "unit-number");
gtk_box_append(GTK_BOX(vbox), number); gtk_box_append(GTK_BOX(vbox), number);
if (locked) { if (done) {
GtkWidget *icon;
icon = gtk_image_new_from_icon_name("object-select-symbolic");
gtk_image_set_pixel_size(GTK_IMAGE(icon), 18);
gtk_widget_add_css_class(icon, "state-icon");
gtk_box_append(GTK_BOX(vbox), icon);
} else if (locked) {
GtkWidget *lock; GtkWidget *lock;
lock = gtk_image_new_from_icon_name("system-lock-screen-symbolic"); lock = gtk_image_new_from_icon_name("system-lock-screen-symbolic");
gtk_image_set_pixel_size(GTK_IMAGE(lock), 16); gtk_image_set_pixel_size(GTK_IMAGE(lock), 16);
@ -175,17 +285,36 @@ static void add_path_node(GtkFixed *fixed, int index) {
gtk_box_append(GTK_BOX(vbox), lock); gtk_box_append(GTK_BOX(vbox), lock);
} }
text = g_strdup_printf("Jednotka %d%s", num, locked ? " (zamčeno)" : ""); if (done)
text = g_strdup_printf("Jednotka %d · dokončeno", num);
else if (current)
text = g_strdup_printf("Jednotka %d · pokračovat", num);
else
text = g_strdup_printf("Jednotka %d · zamčeno", num);
gtk_widget_set_tooltip_text(card, text); gtk_widget_set_tooltip_text(card, text);
g_free(text); g_free(text);
gtk_fixed_put(fixed, card, gtk_fixed_put(fixed, card,
(int)(cx - NODE_SIZE / 2.0), (int)(cx - NODE_SIZE / 2.0),
(int)(cy - NODE_SIZE / 2.0)); (int)(cy - NODE_SIZE / 2.0));
if (current) {
GtkWidget *cap;
cap = gtk_label_new(NULL);
gtk_label_set_markup(GTK_LABEL(cap),
"<span color=\"#89b4fa\">▶</span> Pokračuj zde");
gtk_widget_add_css_class(cap, "node-caption");
gtk_fixed_put(fixed, cap,
(int)(cx + NODE_SIZE / 2.0 + 14.0),
(int)(cy - 30.0));
}
} }
static GtkWidget *build_roadmap_page(void) { static GtkWidget *build_roadmap_page(void) {
GtkWidget *page; GtkWidget *page;
GtkWidget *top;
GtkWidget *back;
GtkWidget *center;
GtkWidget *heading; GtkWidget *heading;
GtkWidget *sub; GtkWidget *sub;
GtkWidget *scroll; GtkWidget *scroll;
@ -197,29 +326,45 @@ static GtkWidget *build_roadmap_page(void) {
canvas_w = (int)(path_x_at(NUM_UNITS - 1) + PATH_MARGIN); canvas_w = (int)(path_x_at(NUM_UNITS - 1) + PATH_MARGIN);
page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4); page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_widget_set_hexpand(page, TRUE); gtk_widget_set_hexpand(page, TRUE);
gtk_widget_set_vexpand(page, TRUE); gtk_widget_set_vexpand(page, TRUE);
gtk_widget_set_margin_start(page, 32); gtk_widget_set_margin_start(page, 32);
gtk_widget_set_margin_end(page, 32); gtk_widget_set_margin_end(page, 32);
gtk_widget_set_margin_top(page, 28); gtk_widget_set_margin_top(page, 24);
gtk_widget_set_margin_bottom(page, 24); gtk_widget_set_margin_bottom(page, 24);
top = gtk_center_box_new();
gtk_widget_set_margin_top(top, 2);
gtk_widget_set_margin_bottom(top, 6);
gtk_box_append(GTK_BOX(page), top);
back = gtk_button_new_from_icon_name("go-previous-symbolic");
gtk_widget_add_css_class(back, "back-btn");
gtk_widget_set_valign(back, GTK_ALIGN_CENTER);
gtk_widget_set_tooltip_text(back, "Zpět");
g_signal_connect(back, "clicked", G_CALLBACK(on_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("Učební plán"); heading = gtk_label_new("Učební plán");
gtk_widget_set_halign(heading, GTK_ALIGN_CENTER); gtk_widget_set_halign(heading, GTK_ALIGN_CENTER);
gtk_widget_add_css_class(heading, "roadmap-title"); gtk_widget_add_css_class(heading, "roadmap-title");
gtk_box_append(GTK_BOX(page), heading); gtk_box_append(GTK_BOX(center), heading);
sub = gtk_label_new("Vyberte jednotku na cestě a začněte procvičovat."); sub = gtk_label_new("Vyberte jednotku na cestě a začněte procvičovat.");
gtk_widget_set_halign(sub, GTK_ALIGN_CENTER); gtk_widget_set_halign(sub, GTK_ALIGN_CENTER);
gtk_widget_add_css_class(sub, "roadmap-sub"); gtk_widget_add_css_class(sub, "roadmap-sub");
gtk_box_append(GTK_BOX(page), sub); gtk_box_append(GTK_BOX(center), sub);
scroll = gtk_scrolled_window_new(); scroll = gtk_scrolled_window_new();
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
GTK_POLICY_AUTOMATIC, GTK_POLICY_NEVER); GTK_POLICY_AUTOMATIC, GTK_POLICY_NEVER);
gtk_widget_set_vexpand(scroll, TRUE); gtk_widget_set_vexpand(scroll, TRUE);
gtk_widget_set_margin_top(scroll, 18); gtk_widget_set_margin_top(scroll, 14);
gtk_box_append(GTK_BOX(page), scroll); gtk_box_append(GTK_BOX(page), scroll);
wrap = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); wrap = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
@ -344,28 +489,74 @@ static void activate(GtkApplication *app, gpointer user_data) {
" color: #a6adc8;" " color: #a6adc8;"
" font-size: 14px;" " font-size: 14px;"
"}" "}"
".back-btn {"
" min-width: 42px;"
" min-height: 42px;"
" padding: 0;"
" border-radius: 999px;"
" background-color: rgba(49, 50, 68, 0.9);"
" border: 1px solid #45475a;"
" color: #cdd6f4;"
" box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);"
" transition: background-color 150ms ease, color 150ms ease,"
" border-color 150ms ease, box-shadow 150ms ease;"
"}"
".back-btn:hover {"
" background-color: #45475a;"
" border-color: #585b70;"
" color: #cba6f7;"
" box-shadow: 0 6px 20px rgba(203, 166, 247, 0.35);"
"}"
".back-btn:active {"
" background-color: #313244;"
" box-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);"
"}"
".unit-node {" ".unit-node {"
" background-image: linear-gradient(135deg, #cba6f7 0%, #b4befe 100%);" " background-image: none;"
" color: #1e1e2e;" " background-color: #2b2d40;"
" border: none;" " color: #cdd6f4;"
" border: 2px solid #3b3e56;"
" border-radius: 999px;" " border-radius: 999px;"
" padding: 0;" " padding: 0;"
" box-shadow: 0 4px 16px rgba(203, 166, 247, 0.35), 0 0 0 1px rgba(203, 166, 247, 0.25);" " box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);"
" transition: transform 150ms ease, box-shadow 150ms ease;" " transition: transform 150ms ease, box-shadow 150ms ease,"
" border-color 150ms ease;"
"}" "}"
".unit-node:hover {" ".unit-node.done {"
" transform: translateY(-3px);" " background-image: linear-gradient(135deg, #a6e3a1 0%, #94e2d5 100%);"
" box-shadow: 0 10px 26px rgba(203, 166, 247, 0.55), 0 0 0 2px rgba(203, 166, 247, 0.35);" " border-color: #a6e3a1;"
" color: #1e1e2e;"
" box-shadow: 0 4px 16px rgba(166, 227, 161, 0.35);"
"}" "}"
".unit-node:active {" ".unit-node.current {"
" transform: translateY(0px);" " background-image: linear-gradient(135deg, #cba6f7 0%, #b4befe 55%, #89b4fa 100%);"
" box-shadow: 0 4px 12px rgba(203, 166, 247, 0.45);" " border-color: #cba6f7;"
" color: #1e1e2e;"
" box-shadow: 0 4px 18px rgba(203, 166, 247, 0.5),"
" 0 0 0 4px rgba(30, 30, 46, 0.9),"
" 0 0 0 7px rgba(203, 166, 247, 0.45);"
"}"
".unit-node.done:hover,"
".unit-node.current:hover {"
" transform: translateY(-3px) scale(1.05);"
"}"
".unit-node.done:hover {"
" box-shadow: 0 8px 22px rgba(166, 227, 161, 0.5);"
"}"
".unit-node.current:hover {"
" box-shadow: 0 8px 26px rgba(203, 166, 247, 0.65),"
" 0 0 0 4px rgba(30, 30, 46, 0.9),"
" 0 0 0 7px rgba(203, 166, 247, 0.55);"
"}"
".unit-node.done:active,"
".unit-node.current:active {"
" transform: translateY(0px) scale(0.98);"
"}" "}"
".unit-node.locked {" ".unit-node.locked {"
" background-image: none;" " background-image: none;"
" background-color: #313244;" " background-color: #24263a;"
" color: #a6adc8;" " border: 2px solid #383b52;"
" border: 1px solid #45475a;" " color: #6c7086;"
" box-shadow: none;" " box-shadow: none;"
"}" "}"
".unit-node.locked:hover {" ".unit-node.locked:hover {"
@ -376,12 +567,35 @@ static void activate(GtkApplication *app, gpointer user_data) {
" font-size: 24px;" " font-size: 24px;"
" font-weight: 800;" " font-weight: 800;"
"}" "}"
".unit-node.locked .unit-number {" ".unit-node.done .unit-number,"
" color: #a6adc8;" ".unit-node.current .unit-number {"
" color: #1e1e2e;"
"}" "}"
".lock-icon {" ".unit-node.locked .unit-number {"
" color: #6c7086;" " color: #6c7086;"
"}" "}"
".state-icon {"
" color: #1e1e2e;"
"}"
".unit-node.locked .lock-icon {"
" color: #585b70;"
"}"
".node-caption {"
" background-color: rgba(30, 30, 46, 0.88);"
" border: 1px solid #45475a;"
" border-radius: 999px;"
" padding: 7px 15px;"
" color: #cdd6f4;"
" font-size: 13px;"
" font-weight: 700;"
" box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);"
"}"
"tooltip {"
" background-color: #181825;"
" border: 1px solid #45475a;"
" border-radius: 10px;"
" color: #cdd6f4;"
"}"
); );
// Apply CSS to the window // Apply CSS to the window