Add Technické vybavení unit 1: von Neumann architecture with quiz.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
8f72a8e1bf
commit
47831c512b
10 changed files with 976 additions and 5 deletions
765
hw.c
Normal file
765
hw.c
Normal file
|
|
@ -0,0 +1,765 @@
|
||||||
|
#include "maturita.h"
|
||||||
|
|
||||||
|
/* ---- Technical equipment (Technické vybavení) --------------------- */
|
||||||
|
|
||||||
|
NetLesson hw_lessons[HW_LESSONS] = {
|
||||||
|
{ .n_slides = HW_SLIDES, .unit_page = "hwunit1", .ex_page = "hwex1" },
|
||||||
|
};
|
||||||
|
|
||||||
|
void hw_rail_theme_reset(void);
|
||||||
|
void refresh_hw_completion_ui(void);
|
||||||
|
|
||||||
|
GtkWidget *hw_scroll;
|
||||||
|
GtkWidget *hw_fixed;
|
||||||
|
GtkWidget *hw_rail;
|
||||||
|
GtkWidget *hw_nodes[HW_UNITS];
|
||||||
|
GtkWidget *hw_labels[HW_UNITS];
|
||||||
|
double hw_cx[HW_UNITS];
|
||||||
|
double hw_cy[HW_UNITS];
|
||||||
|
int hw_cols = 1;
|
||||||
|
int hw_rows = 1;
|
||||||
|
int hw_cw = (int)(2.0 * ROAD_MX + (HW_UNITS - 1) * PATH_SPAC);
|
||||||
|
int hw_ch = (int)(2.0 * ROAD_MY);
|
||||||
|
guint hw_idle;
|
||||||
|
double hw_last_avail = -1.0;
|
||||||
|
int hw_last_cols = -1;
|
||||||
|
int hw_last_rows = -1;
|
||||||
|
int hw_last_cw = -1;
|
||||||
|
int hw_last_ch = -1;
|
||||||
|
cairo_surface_t *hw_rail_cache;
|
||||||
|
int hw_cache_w;
|
||||||
|
int hw_cache_h;
|
||||||
|
|
||||||
|
void hw_point(double t, double *ox, double *oy) {
|
||||||
|
int k = (int)t;
|
||||||
|
double u = t - (double)k;
|
||||||
|
double u2, u3;
|
||||||
|
double p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y;
|
||||||
|
|
||||||
|
if (k < 0) { k = 0; u = 0.0; }
|
||||||
|
if (k >= HW_UNITS - 1) { k = HW_UNITS - 2; u = 1.0; }
|
||||||
|
|
||||||
|
p1x = hw_cx[k]; p1y = hw_cy[k];
|
||||||
|
p2x = hw_cx[k + 1]; p2y = hw_cy[k + 1];
|
||||||
|
if (k - 1 >= 0) { p0x = hw_cx[k - 1]; p0y = hw_cy[k - 1]; }
|
||||||
|
else { p0x = p1x - (p2x - p1x); p0y = p1y - (p2y - p1y); }
|
||||||
|
if (k + 2 < HW_UNITS) { p3x = hw_cx[k + 2]; p3y = hw_cy[k + 2]; }
|
||||||
|
else { p3x = p2x + (p2x - p1x); p3y = p2y + (p2y - p1y); }
|
||||||
|
|
||||||
|
u2 = u * u;
|
||||||
|
u3 = u2 * u;
|
||||||
|
*ox = 0.5 * (2.0 * p1x + (-p0x + p2x) * u
|
||||||
|
+ (2.0 * p0x - 5.0 * p1x + 4.0 * p2x - p3x) * u2
|
||||||
|
+ (-p0x + 3.0 * p1x - 3.0 * p2x + p3x) * u3);
|
||||||
|
*oy = 0.5 * (2.0 * p1y + (-p0y + p2y) * u
|
||||||
|
+ (2.0 * p0y - 5.0 * p1y + 4.0 * p2y - p3y) * u2
|
||||||
|
+ (-p0y + 3.0 * p1y - 3.0 * p2y + p3y) * u3);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hw_path(cairo_t *cr, double t0, double t1) {
|
||||||
|
const double steps_per_unit = 24.0;
|
||||||
|
int n = (int)((t1 - t0) * steps_per_unit);
|
||||||
|
double x, y;
|
||||||
|
int s;
|
||||||
|
|
||||||
|
if (n < 1)
|
||||||
|
n = 1;
|
||||||
|
hw_point(t0, &x, &y);
|
||||||
|
cairo_move_to(cr, x, y);
|
||||||
|
for (s = 1; s <= n; s++) {
|
||||||
|
hw_point(t0 + (t1 - t0) * (double)s / (double)n, &x, &y);
|
||||||
|
cairo_line_to(cr, x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void hw_geometry(double avail) {
|
||||||
|
const double phase = 2.0 * G_PI * 1.7 / (double)(HW_UNITS - 1);
|
||||||
|
int rows;
|
||||||
|
int r, c, i;
|
||||||
|
|
||||||
|
for (rows = 1; rows <= HW_UNITS; rows++) {
|
||||||
|
int cols = (HW_UNITS + rows - 1) / rows;
|
||||||
|
if (2.0 * ROAD_MX + (cols - 1) * PATH_SPAC <= avail + 1.0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (rows > HW_UNITS)
|
||||||
|
rows = HW_UNITS;
|
||||||
|
hw_rows = rows;
|
||||||
|
hw_cols = (HW_UNITS + rows - 1) / rows;
|
||||||
|
if (hw_cols < 1)
|
||||||
|
hw_cols = 1;
|
||||||
|
hw_cw = (int)(2.0 * ROAD_MX + (hw_cols - 1) * PATH_SPAC);
|
||||||
|
hw_ch = (int)(2.0 * ROAD_MY + (rows - 1) * ROAD_GAP);
|
||||||
|
|
||||||
|
for (r = 0; r < rows; r++) {
|
||||||
|
int base = r * hw_cols;
|
||||||
|
int len = MIN(hw_cols, HW_UNITS - base);
|
||||||
|
int fwd = (r % 2) == 0;
|
||||||
|
for (c = 0; c < len; c++) {
|
||||||
|
i = base + c;
|
||||||
|
int cc = fwd ? c : (hw_cols - 1 - c);
|
||||||
|
hw_cx[i] = ROAD_MX + cc * PATH_SPAC;
|
||||||
|
hw_cy[i] = ROAD_MY + r * ROAD_GAP
|
||||||
|
+ ROAD_WAVE * sin((double)i * phase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void hw_apply_layout(void) {
|
||||||
|
if (!hw_fixed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (hw_cols == hw_last_cols && hw_rows == hw_last_rows &&
|
||||||
|
hw_cw == hw_last_cw && hw_ch == hw_last_ch)
|
||||||
|
return;
|
||||||
|
|
||||||
|
hw_last_cols = hw_cols;
|
||||||
|
hw_last_rows = hw_rows;
|
||||||
|
hw_last_cw = hw_cw;
|
||||||
|
hw_last_ch = hw_ch;
|
||||||
|
if (hw_rail_cache) {
|
||||||
|
cairo_surface_destroy(hw_rail_cache);
|
||||||
|
hw_rail_cache = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < HW_UNITS; i++) {
|
||||||
|
if (!hw_nodes[i] || !hw_labels[i])
|
||||||
|
continue;
|
||||||
|
gtk_fixed_move(GTK_FIXED(hw_fixed), hw_nodes[i],
|
||||||
|
(int)(hw_cx[i] - NODE_SIZE / 2.0),
|
||||||
|
(int)(hw_cy[i] - NODE_SIZE / 2.0));
|
||||||
|
gtk_fixed_move(GTK_FIXED(hw_fixed), hw_labels[i],
|
||||||
|
(int)(hw_cx[i] - (PATH_SPAC - 20.0) / 2.0),
|
||||||
|
(int)(hw_cy[i] + NODE_SIZE / 2.0 + 10.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
gtk_widget_set_size_request(hw_fixed, hw_cw, hw_ch);
|
||||||
|
gtk_widget_set_size_request(hw_rail, hw_cw, hw_ch);
|
||||||
|
gtk_fixed_move(GTK_FIXED(hw_fixed), hw_rail, 0, 0);
|
||||||
|
gtk_widget_queue_draw(hw_rail);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hw_relayout(void) {
|
||||||
|
GtkAdjustment *hadj;
|
||||||
|
double avail;
|
||||||
|
|
||||||
|
if (!hw_scroll)
|
||||||
|
return;
|
||||||
|
hadj = gtk_scrolled_window_get_hadjustment(
|
||||||
|
GTK_SCROLLED_WINDOW(hw_scroll));
|
||||||
|
avail = gtk_adjustment_get_page_size(hadj);
|
||||||
|
if (avail < 1.0)
|
||||||
|
return;
|
||||||
|
if (fabs(avail - hw_last_avail) < 1.0)
|
||||||
|
return;
|
||||||
|
hw_last_avail = avail;
|
||||||
|
hw_geometry(avail);
|
||||||
|
hw_apply_layout();
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean hw_relayout_idle(gpointer data) {
|
||||||
|
(void)data;
|
||||||
|
hw_idle = 0;
|
||||||
|
hw_relayout();
|
||||||
|
return G_SOURCE_REMOVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void hw_relayout_later(void) {
|
||||||
|
if (hw_idle == 0)
|
||||||
|
hw_idle = g_idle_add(hw_relayout_idle, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hw_adjust_notify(GtkAdjustment *adj, GParamSpec *ps,
|
||||||
|
gpointer data) {
|
||||||
|
(void)adj;
|
||||||
|
(void)ps;
|
||||||
|
(void)data;
|
||||||
|
hw_relayout_later();
|
||||||
|
}
|
||||||
|
|
||||||
|
void hw_render_rail_to(cairo_t *cr) {
|
||||||
|
const double t_end = (double)(HW_UNITS - 1);
|
||||||
|
const Rgb mauve = color_from_hex(app_theme.accent);
|
||||||
|
const Rgb rail = color_from_hex(app_theme.rail);
|
||||||
|
const Rgb muted = color_from_hex(app_theme.subtext);
|
||||||
|
double hx, hy;
|
||||||
|
|
||||||
|
cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
|
||||||
|
cairo_paint(cr);
|
||||||
|
cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
|
||||||
|
cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
|
||||||
|
cairo_set_line_join(cr, CAIRO_LINE_JOIN_ROUND);
|
||||||
|
|
||||||
|
hw_point(0.0, &hx, &hy);
|
||||||
|
draw_node_halo(cr, hx, hy, NODE_SIZE * 1.05,
|
||||||
|
mauve.r, mauve.g, mauve.b, 0.18);
|
||||||
|
|
||||||
|
cairo_new_path(cr);
|
||||||
|
hw_path(cr, 0.0, t_end);
|
||||||
|
cairo_set_line_width(cr, 16);
|
||||||
|
cairo_set_source_rgb(cr, rail.r, rail.g, rail.b);
|
||||||
|
cairo_stroke(cr);
|
||||||
|
|
||||||
|
cairo_set_source_rgba(cr, muted.r, muted.g, muted.b, 0.17);
|
||||||
|
cairo_set_dash(cr, (double[]){2.0, 40.0}, 2, 0.0);
|
||||||
|
cairo_new_path(cr);
|
||||||
|
hw_path(cr, 0.0, t_end);
|
||||||
|
cairo_stroke(cr);
|
||||||
|
cairo_set_dash(cr, NULL, 0, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hw_draw_rail(GtkDrawingArea *area, cairo_t *cr,
|
||||||
|
int width, int height, gpointer data) {
|
||||||
|
(void)area;
|
||||||
|
(void)width;
|
||||||
|
(void)height;
|
||||||
|
(void)data;
|
||||||
|
|
||||||
|
if (!hw_rail_cache || hw_cache_w != hw_cw || hw_cache_h != hw_ch) {
|
||||||
|
cairo_t *rcr;
|
||||||
|
|
||||||
|
if (hw_rail_cache)
|
||||||
|
cairo_surface_destroy(hw_rail_cache);
|
||||||
|
hw_cache_w = hw_cw;
|
||||||
|
hw_cache_h = hw_ch;
|
||||||
|
if (hw_cache_w < 1)
|
||||||
|
hw_cache_w = 1;
|
||||||
|
if (hw_cache_h < 1)
|
||||||
|
hw_cache_h = 1;
|
||||||
|
hw_rail_cache = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
|
||||||
|
hw_cache_w, hw_cache_h);
|
||||||
|
rcr = cairo_create(hw_rail_cache);
|
||||||
|
hw_render_rail_to(rcr);
|
||||||
|
cairo_destroy(rcr);
|
||||||
|
}
|
||||||
|
|
||||||
|
cairo_set_source_surface(cr, hw_rail_cache, 0, 0);
|
||||||
|
cairo_paint(cr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hw_rail_theme_reset(void) {
|
||||||
|
if (hw_rail_cache) {
|
||||||
|
cairo_surface_destroy(hw_rail_cache);
|
||||||
|
hw_rail_cache = NULL;
|
||||||
|
}
|
||||||
|
if (hw_rail)
|
||||||
|
gtk_widget_queue_draw(hw_rail);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hw_add_node(GtkFixed *fixed, int index) {
|
||||||
|
int num = index + 1;
|
||||||
|
gboolean locked = index >= HW_LESSONS;
|
||||||
|
GtkWidget *card;
|
||||||
|
GtkWidget *vbox;
|
||||||
|
GtkWidget *number;
|
||||||
|
GtkWidget *icon;
|
||||||
|
GtkWidget *name;
|
||||||
|
char *text;
|
||||||
|
static const char *unit_keys[HW_LESSONS] = {
|
||||||
|
"hw_unit1"
|
||||||
|
};
|
||||||
|
|
||||||
|
card = gtk_button_new();
|
||||||
|
gtk_widget_add_css_class(card, "unit-node");
|
||||||
|
gtk_widget_set_can_focus(card, FALSE);
|
||||||
|
gtk_widget_set_sensitive(card, !locked);
|
||||||
|
gtk_widget_set_size_request(card, (int)NODE_SIZE, (int)NODE_SIZE);
|
||||||
|
|
||||||
|
if (locked)
|
||||||
|
gtk_widget_add_css_class(card, "locked");
|
||||||
|
else
|
||||||
|
gtk_widget_add_css_class(card, "current");
|
||||||
|
|
||||||
|
vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2);
|
||||||
|
gtk_widget_set_halign(vbox, GTK_ALIGN_CENTER);
|
||||||
|
gtk_widget_set_valign(vbox, GTK_ALIGN_CENTER);
|
||||||
|
gtk_button_set_child(GTK_BUTTON(card), vbox);
|
||||||
|
|
||||||
|
text = g_strdup_printf("%d", num);
|
||||||
|
number = gtk_label_new(text);
|
||||||
|
g_free(text);
|
||||||
|
gtk_widget_add_css_class(number, "unit-number");
|
||||||
|
gtk_box_append(GTK_BOX(vbox), number);
|
||||||
|
|
||||||
|
if (locked) {
|
||||||
|
icon = icon_area_new(draw_lock_icon, 0.3451, 0.3569, 0.4392, 16);
|
||||||
|
gtk_box_append(GTK_BOX(vbox), icon);
|
||||||
|
} else {
|
||||||
|
icon = icon_area_new(draw_check_icon, 0.1176, 0.1176, 0.1804, 18);
|
||||||
|
gtk_widget_set_visible(icon, FALSE);
|
||||||
|
gtk_box_append(GTK_BOX(vbox), icon);
|
||||||
|
hw_lessons[index].done_icon = icon;
|
||||||
|
g_signal_connect(card, "clicked", G_CALLBACK(net_open_unit),
|
||||||
|
&hw_lessons[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
hw_nodes[index] = card;
|
||||||
|
gtk_fixed_put(fixed, card, 0, 0);
|
||||||
|
|
||||||
|
name = gtk_label_new(NULL);
|
||||||
|
gtk_widget_set_size_request(name, (int)(PATH_SPAC - 20.0), -1);
|
||||||
|
gtk_widget_set_halign(name, GTK_ALIGN_CENTER);
|
||||||
|
gtk_label_set_justify(GTK_LABEL(name), GTK_JUSTIFY_CENTER);
|
||||||
|
gtk_label_set_wrap(GTK_LABEL(name), TRUE);
|
||||||
|
gtk_widget_add_css_class(name, "unit-name");
|
||||||
|
if (index < HW_LESSONS) {
|
||||||
|
i18n_bind(name, unit_keys[index], 0);
|
||||||
|
} else {
|
||||||
|
gtk_widget_add_css_class(name, "unit-name-locked");
|
||||||
|
}
|
||||||
|
hw_labels[index] = name;
|
||||||
|
gtk_fixed_put(fixed, name, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
GtkWidget *build_hwmap_page(void) {
|
||||||
|
GtkWidget *page;
|
||||||
|
GtkWidget *scroll;
|
||||||
|
GtkWidget *wrap;
|
||||||
|
GtkWidget *fixed;
|
||||||
|
GtkWidget *rail;
|
||||||
|
GtkAdjustment *ha;
|
||||||
|
GtkAdjustment *va;
|
||||||
|
|
||||||
|
hw_geometry(1000.0);
|
||||||
|
|
||||||
|
page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||||
|
gtk_widget_set_hexpand(page, TRUE);
|
||||||
|
gtk_widget_set_vexpand(page, TRUE);
|
||||||
|
gtk_widget_set_margin_start(page, 32);
|
||||||
|
gtk_widget_set_margin_end(page, 32);
|
||||||
|
gtk_widget_set_margin_top(page, 24);
|
||||||
|
gtk_widget_set_margin_bottom(page, 24);
|
||||||
|
|
||||||
|
gtk_box_append(GTK_BOX(page),
|
||||||
|
top_bar("subjects", "Technické vybavení", "hw_sub"));
|
||||||
|
|
||||||
|
scroll = gtk_scrolled_window_new();
|
||||||
|
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
|
||||||
|
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
|
||||||
|
gtk_widget_set_vexpand(scroll, TRUE);
|
||||||
|
gtk_widget_set_margin_top(scroll, 14);
|
||||||
|
gtk_box_append(GTK_BOX(page), scroll);
|
||||||
|
hw_scroll = scroll;
|
||||||
|
|
||||||
|
wrap = gtk_center_box_new();
|
||||||
|
gtk_widget_set_vexpand(wrap, TRUE);
|
||||||
|
gtk_widget_set_hexpand(wrap, TRUE);
|
||||||
|
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroll), wrap);
|
||||||
|
|
||||||
|
fixed = gtk_fixed_new();
|
||||||
|
gtk_widget_set_halign(fixed, GTK_ALIGN_CENTER);
|
||||||
|
gtk_widget_set_valign(fixed, GTK_ALIGN_CENTER);
|
||||||
|
gtk_widget_set_size_request(fixed, hw_cw, hw_ch);
|
||||||
|
gtk_center_box_set_center_widget(GTK_CENTER_BOX(wrap), fixed);
|
||||||
|
hw_fixed = fixed;
|
||||||
|
|
||||||
|
rail = gtk_drawing_area_new();
|
||||||
|
gtk_widget_set_size_request(rail, hw_cw, hw_ch);
|
||||||
|
gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(rail), hw_draw_rail,
|
||||||
|
NULL, NULL);
|
||||||
|
gtk_fixed_put(GTK_FIXED(fixed), rail, 0, 0);
|
||||||
|
hw_rail = rail;
|
||||||
|
|
||||||
|
for (int i = 0; i < HW_UNITS; i++)
|
||||||
|
hw_add_node(GTK_FIXED(fixed), i);
|
||||||
|
|
||||||
|
ha = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(scroll));
|
||||||
|
va = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(scroll));
|
||||||
|
g_signal_connect(ha, "notify::page-size",
|
||||||
|
G_CALLBACK(hw_adjust_notify), NULL);
|
||||||
|
g_signal_connect(va, "notify::page-size",
|
||||||
|
G_CALLBACK(hw_adjust_notify), NULL);
|
||||||
|
|
||||||
|
hw_relayout_later();
|
||||||
|
refresh_hw_completion_ui();
|
||||||
|
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void hw_slide_apply(NetLesson *L);
|
||||||
|
void hw_slide_prev(GtkButton *button, gpointer data);
|
||||||
|
void hw_slide_next(GtkButton *button, gpointer data);
|
||||||
|
|
||||||
|
/* ---- Unit 1: Architektura počítače -------------------------------- */
|
||||||
|
|
||||||
|
static GtkWidget *hw_note_host;
|
||||||
|
static int hw_note_last_w = -1;
|
||||||
|
|
||||||
|
static void hw_rescale_notes_at(int w) {
|
||||||
|
int body, head, kick;
|
||||||
|
|
||||||
|
if (w < 160)
|
||||||
|
w = 160;
|
||||||
|
body = w / 46;
|
||||||
|
if (body < 14) body = 14;
|
||||||
|
if (body > 26) body = 26;
|
||||||
|
head = body + 14;
|
||||||
|
if (head > 46) head = 46;
|
||||||
|
kick = body - 3;
|
||||||
|
if (kick < 11) kick = 11;
|
||||||
|
for (int i = 0; i < HW_LESSONS; i++)
|
||||||
|
net_rescale_lesson_notes(&hw_lessons[i], body, head, kick);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean hw_note_tick(GtkWidget *w, GdkFrameClock *clock,
|
||||||
|
gpointer data) {
|
||||||
|
int width = gtk_widget_get_allocated_width(w);
|
||||||
|
|
||||||
|
(void)clock;
|
||||||
|
(void)data;
|
||||||
|
if (width != hw_note_last_w) {
|
||||||
|
hw_note_last_w = width;
|
||||||
|
hw_rescale_notes_at(width);
|
||||||
|
}
|
||||||
|
return G_SOURCE_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static GtkWidget *build_hw_unit_page(NetLesson *L, const char *title_key,
|
||||||
|
const char *sub_key,
|
||||||
|
const NetSlide *slides, guint n_slides) {
|
||||||
|
GtkWidget *page;
|
||||||
|
GtkWidget *scroll;
|
||||||
|
GtkWidget *nav;
|
||||||
|
|
||||||
|
net_lesson_notes_ensure(L);
|
||||||
|
net_notes_target = L;
|
||||||
|
L->n_slides = n_slides;
|
||||||
|
|
||||||
|
page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||||
|
gtk_widget_set_margin_start(page, 28);
|
||||||
|
gtk_widget_set_margin_end(page, 28);
|
||||||
|
gtk_widget_set_margin_top(page, 20);
|
||||||
|
gtk_widget_set_margin_bottom(page, 20);
|
||||||
|
|
||||||
|
gtk_box_append(GTK_BOX(page), top_bar("hwmap", title_key, sub_key));
|
||||||
|
|
||||||
|
scroll = gtk_scrolled_window_new();
|
||||||
|
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
|
||||||
|
GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
|
||||||
|
gtk_widget_set_vexpand(scroll, TRUE);
|
||||||
|
gtk_widget_set_margin_top(scroll, 10);
|
||||||
|
gtk_box_append(GTK_BOX(page), scroll);
|
||||||
|
|
||||||
|
L->stack = gtk_stack_new();
|
||||||
|
gtk_stack_set_transition_type(GTK_STACK(L->stack),
|
||||||
|
GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT);
|
||||||
|
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroll), L->stack);
|
||||||
|
hw_note_host = L->stack;
|
||||||
|
|
||||||
|
for (guint s = 0; s < n_slides; s++) {
|
||||||
|
GtkWidget *stage = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||||
|
GtkWidget *card;
|
||||||
|
char name[16];
|
||||||
|
|
||||||
|
gtk_widget_set_vexpand(stage, TRUE);
|
||||||
|
g_snprintf(name, sizeof(name), "hw_slide%u", s);
|
||||||
|
gtk_stack_add_named(GTK_STACK(L->stack), stage, name);
|
||||||
|
|
||||||
|
card = gtk_box_new(GTK_ORIENTATION_VERTICAL, 16);
|
||||||
|
gtk_widget_add_css_class(card, "notes-card");
|
||||||
|
gtk_widget_set_valign(card, GTK_ALIGN_CENTER);
|
||||||
|
gtk_box_append(GTK_BOX(stage), card);
|
||||||
|
|
||||||
|
net_note_kicker(card, slides[s].kicker);
|
||||||
|
net_note_title(card, slides[s].title);
|
||||||
|
for (int i = 0; i < 8 && slides[s].line[i]; i++)
|
||||||
|
net_note_line(card, slides[s].line[i], FALSE);
|
||||||
|
if (slides[s].tip)
|
||||||
|
net_note_line(card, slides[s].tip, TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
gtk_widget_add_tick_callback(L->stack, hw_note_tick, NULL, NULL);
|
||||||
|
hw_note_last_w = -1;
|
||||||
|
hw_rescale_notes_at(0);
|
||||||
|
net_notes_target = NULL;
|
||||||
|
|
||||||
|
nav = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10);
|
||||||
|
gtk_box_set_homogeneous(GTK_BOX(nav), TRUE);
|
||||||
|
gtk_widget_set_margin_top(nav, 10);
|
||||||
|
gtk_box_append(GTK_BOX(page), nav);
|
||||||
|
|
||||||
|
L->prev_btn = gtk_button_new_with_label(tr("net_slide_prev"));
|
||||||
|
gtk_widget_add_css_class(L->prev_btn, "btn-primary");
|
||||||
|
g_signal_connect(L->prev_btn, "clicked", G_CALLBACK(hw_slide_prev), L);
|
||||||
|
gtk_box_append(GTK_BOX(nav), L->prev_btn);
|
||||||
|
|
||||||
|
L->next_btn = gtk_button_new_with_label(tr("net_slide_next"));
|
||||||
|
gtk_widget_add_css_class(L->next_btn, "btn-primary");
|
||||||
|
g_signal_connect(L->next_btn, "clicked", G_CALLBACK(hw_slide_next), L);
|
||||||
|
gtk_box_append(GTK_BOX(nav), L->next_btn);
|
||||||
|
|
||||||
|
L->idx = 0;
|
||||||
|
hw_slide_apply(L);
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
|
void hw_slide_apply(NetLesson *L) {
|
||||||
|
char name[16];
|
||||||
|
|
||||||
|
if (!L || !L->stack)
|
||||||
|
return;
|
||||||
|
g_snprintf(name, sizeof(name), "hw_slide%u", L->idx);
|
||||||
|
gtk_stack_set_visible_child_name(GTK_STACK(L->stack), name);
|
||||||
|
if (L->prev_btn) {
|
||||||
|
gtk_button_set_label(GTK_BUTTON(L->prev_btn), tr("net_slide_prev"));
|
||||||
|
gtk_widget_set_sensitive(L->prev_btn, L->idx > 0);
|
||||||
|
}
|
||||||
|
if (L->next_btn) {
|
||||||
|
gtk_button_set_label(
|
||||||
|
GTK_BUTTON(L->next_btn),
|
||||||
|
L->idx + 1 >= L->n_slides ? tr("net_slide_start")
|
||||||
|
: tr("net_slide_next"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void hw_slide_prev(GtkButton *button, gpointer data) {
|
||||||
|
NetLesson *L = data;
|
||||||
|
(void)button;
|
||||||
|
if (!L) return;
|
||||||
|
if (L->idx > 0) {
|
||||||
|
L->idx--;
|
||||||
|
hw_slide_apply(L);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void hw_slide_next(GtkButton *button, gpointer data) {
|
||||||
|
NetLesson *L = data;
|
||||||
|
(void)button;
|
||||||
|
if (!L) return;
|
||||||
|
if (L->idx + 1 < L->n_slides) {
|
||||||
|
L->idx++;
|
||||||
|
hw_slide_apply(L);
|
||||||
|
} else {
|
||||||
|
gtk_stack_set_visible_child_name(main_stack, L->ex_page);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void hw_lessons_apply_lang(void) {
|
||||||
|
for (int i = 0; i < HW_LESSONS; i++)
|
||||||
|
hw_slide_apply(&hw_lessons[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
GtkWidget *build_hw_unit1_page(void) {
|
||||||
|
static const NetSlide slides[HW_SLIDES] = {
|
||||||
|
{
|
||||||
|
"1 / 3 • Úvod", "Architektura počítače",
|
||||||
|
"Architektura popisuje, z jakých částí se počítač skládá "
|
||||||
|
"a jak spolu spolupracují.",
|
||||||
|
{
|
||||||
|
"Architektura počítače je základní model, podle kterého "
|
||||||
|
"jsou počítače navrhovány.",
|
||||||
|
"Nejznámější model je Von Neumannovo schéma.",
|
||||||
|
"Používá se dodnes u většiny běžných počítačů.",
|
||||||
|
NULL,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"2 / 3 • Osobnost", "John von Neumann",
|
||||||
|
"Tip: zapamatuj si jméno – podle něj se jmenuje celé schéma.",
|
||||||
|
{
|
||||||
|
"John von Neumann byl americký matematik.",
|
||||||
|
"Pracoval na poli digitálních počítačů a operační teorie "
|
||||||
|
"kvantové mechaniky.",
|
||||||
|
"Je autorem Von Neumannovy algebry.",
|
||||||
|
"Byl tvůrcem teorie her.",
|
||||||
|
"Podílel se na vývoji atomové a vodíkové bomby.",
|
||||||
|
NULL,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"3 / 3 • Schéma", "Von Neumannovo schéma",
|
||||||
|
"ALS = aritmeticko-logická jednotka",
|
||||||
|
{
|
||||||
|
"Von Neumannovo schéma má pět hlavních částí:",
|
||||||
|
"ALS – aritmeticko-logická jednotka (výpočty a logika)",
|
||||||
|
"Řadič – řídí činnost ostatních částí",
|
||||||
|
"Operační paměť – uchovává program i data",
|
||||||
|
"Vstupní jednotka – přivádí data do počítače",
|
||||||
|
"Výstupní jednotka – předává výsledky ven",
|
||||||
|
NULL,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return build_hw_unit_page(&hw_lessons[0], "hw_unit1", "hw_unit1_sub",
|
||||||
|
slides, HW_SLIDES);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Quiz --------------------------------------------------------- */
|
||||||
|
|
||||||
|
static void hw_mcq_check(GtkButton *button, gpointer data) {
|
||||||
|
NetMcqCtx *ctx = data;
|
||||||
|
int ok = 0;
|
||||||
|
|
||||||
|
(void)button;
|
||||||
|
for (int i = 0; i < ctx->n; i++) {
|
||||||
|
int active = -1;
|
||||||
|
for (int o = 0; o < ctx->n_opts; o++) {
|
||||||
|
GtkToggleButton *tb = ctx->toggles[i * ctx->n_opts + o];
|
||||||
|
gtk_widget_remove_css_class(GTK_WIDGET(tb), "ok");
|
||||||
|
gtk_widget_remove_css_class(GTK_WIDGET(tb), "wrong");
|
||||||
|
if (gtk_toggle_button_get_active(tb))
|
||||||
|
active = o;
|
||||||
|
}
|
||||||
|
if (active == ctx->qs[i].correct) {
|
||||||
|
ok++;
|
||||||
|
gtk_widget_add_css_class(
|
||||||
|
GTK_WIDGET(ctx->toggles[i * ctx->n_opts + active]), "ok");
|
||||||
|
} else if (active >= 0) {
|
||||||
|
gtk_widget_add_css_class(
|
||||||
|
GTK_WIDGET(ctx->toggles[i * ctx->n_opts + active]), "wrong");
|
||||||
|
}
|
||||||
|
if (ctx->hints && ctx->hints[i])
|
||||||
|
gtk_widget_set_visible(ctx->hints[i], TRUE);
|
||||||
|
}
|
||||||
|
if (ok == ctx->n) {
|
||||||
|
set_feedback(ctx->feedback, TRUE, tr("feedback_ok"));
|
||||||
|
mark_hw_done(ctx->lesson_id);
|
||||||
|
} else {
|
||||||
|
set_feedback(ctx->feedback, FALSE, tr("feedback_retry_short"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static GtkWidget *build_hw_mcq_page(int lesson_id, const char *back_page,
|
||||||
|
const char *title_key,
|
||||||
|
const char *heading_key,
|
||||||
|
const ChoiceQ *qs, const char **hints,
|
||||||
|
int n) {
|
||||||
|
GtkWidget *page;
|
||||||
|
GtkWidget *scroll;
|
||||||
|
GtkWidget *body;
|
||||||
|
GtkWidget *check;
|
||||||
|
GtkWidget *btns;
|
||||||
|
GtkWidget *head;
|
||||||
|
GtkWidget *intro;
|
||||||
|
NetMcqCtx *ctx = g_new0(NetMcqCtx, 1);
|
||||||
|
int n_opts = qs[0].n_options;
|
||||||
|
|
||||||
|
page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||||
|
gtk_widget_set_margin_start(page, 32);
|
||||||
|
gtk_widget_set_margin_end(page, 32);
|
||||||
|
gtk_widget_set_margin_top(page, 24);
|
||||||
|
gtk_widget_set_margin_bottom(page, 24);
|
||||||
|
|
||||||
|
gtk_box_append(GTK_BOX(page), top_bar(back_page, title_key, NULL));
|
||||||
|
|
||||||
|
scroll = gtk_scrolled_window_new();
|
||||||
|
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
|
||||||
|
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
|
||||||
|
gtk_widget_set_vexpand(scroll, TRUE);
|
||||||
|
gtk_widget_set_margin_top(scroll, 12);
|
||||||
|
gtk_box_append(GTK_BOX(page), scroll);
|
||||||
|
|
||||||
|
body = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10);
|
||||||
|
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroll), body);
|
||||||
|
|
||||||
|
head = gtk_label_new(NULL);
|
||||||
|
i18n_bind(head, heading_key, 0);
|
||||||
|
gtk_widget_set_halign(head, GTK_ALIGN_START);
|
||||||
|
gtk_label_set_wrap(GTK_LABEL(head), TRUE);
|
||||||
|
gtk_widget_set_margin_top(head, 6);
|
||||||
|
gtk_widget_add_css_class(head, "ex-sub");
|
||||||
|
gtk_box_append(GTK_BOX(body), head);
|
||||||
|
|
||||||
|
intro = gtk_label_new(NULL);
|
||||||
|
i18n_bind(intro, "net_quiz_intro", 0);
|
||||||
|
gtk_widget_set_halign(intro, GTK_ALIGN_START);
|
||||||
|
gtk_label_set_wrap(GTK_LABEL(intro), TRUE);
|
||||||
|
gtk_widget_add_css_class(intro, "ex-prompt");
|
||||||
|
gtk_box_append(GTK_BOX(body), intro);
|
||||||
|
|
||||||
|
ctx->qs = qs;
|
||||||
|
ctx->n = n;
|
||||||
|
ctx->n_opts = n_opts;
|
||||||
|
ctx->lesson_id = lesson_id;
|
||||||
|
ctx->toggles = g_new0(GtkToggleButton *, n * n_opts);
|
||||||
|
ctx->hints = g_new0(GtkWidget *, n);
|
||||||
|
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
GtkWidget *prompt;
|
||||||
|
GtkWidget *row;
|
||||||
|
GtkToggleButton *first = NULL;
|
||||||
|
char *qtext;
|
||||||
|
|
||||||
|
qtext = g_strdup_printf("%d.) %s", i + 1, qs[i].prompt);
|
||||||
|
prompt = gtk_label_new(qtext);
|
||||||
|
g_free(qtext);
|
||||||
|
gtk_widget_set_halign(prompt, GTK_ALIGN_START);
|
||||||
|
gtk_label_set_wrap(GTK_LABEL(prompt), TRUE);
|
||||||
|
gtk_widget_add_css_class(prompt, "ex-prompt");
|
||||||
|
gtk_widget_set_margin_top(prompt, 8);
|
||||||
|
gtk_box_append(GTK_BOX(body), prompt);
|
||||||
|
|
||||||
|
row = gtk_flow_box_new();
|
||||||
|
gtk_flow_box_set_selection_mode(GTK_FLOW_BOX(row), GTK_SELECTION_NONE);
|
||||||
|
gtk_flow_box_set_min_children_per_line(GTK_FLOW_BOX(row), n_opts);
|
||||||
|
gtk_flow_box_set_max_children_per_line(GTK_FLOW_BOX(row), n_opts);
|
||||||
|
gtk_widget_set_halign(row, GTK_ALIGN_START);
|
||||||
|
gtk_box_append(GTK_BOX(body), row);
|
||||||
|
|
||||||
|
for (int o = 0; o < n_opts; o++) {
|
||||||
|
GtkWidget *tb = gtk_toggle_button_new_with_label(qs[i].options[o]);
|
||||||
|
gtk_widget_add_css_class(tb, "pill");
|
||||||
|
gtk_toggle_button_set_group(GTK_TOGGLE_BUTTON(tb), first);
|
||||||
|
if (!first)
|
||||||
|
first = GTK_TOGGLE_BUTTON(tb);
|
||||||
|
gtk_flow_box_append(GTK_FLOW_BOX(row), tb);
|
||||||
|
ctx->toggles[i * n_opts + o] = GTK_TOGGLE_BUTTON(tb);
|
||||||
|
}
|
||||||
|
ctx->hints[i] = meaning_add(body, hints[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
btns = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
|
||||||
|
gtk_widget_set_margin_top(btns, 12);
|
||||||
|
gtk_box_append(GTK_BOX(body), btns);
|
||||||
|
|
||||||
|
check = gtk_button_new();
|
||||||
|
i18n_bind(check, "check", 1);
|
||||||
|
gtk_widget_add_css_class(check, "pill");
|
||||||
|
gtk_widget_set_halign(check, GTK_ALIGN_START);
|
||||||
|
gtk_box_append(GTK_BOX(btns), check);
|
||||||
|
g_signal_connect(check, "clicked", G_CALLBACK(hw_mcq_check), ctx);
|
||||||
|
|
||||||
|
ctx->feedback = gtk_label_new("");
|
||||||
|
gtk_widget_set_halign(ctx->feedback, GTK_ALIGN_START);
|
||||||
|
gtk_box_append(GTK_BOX(body), ctx->feedback);
|
||||||
|
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
|
GtkWidget *build_hw_unit1_exercise_page(void) {
|
||||||
|
static const ChoiceQ qs[] = {
|
||||||
|
{"Kdo je autorem Von Neumannova schématu?",
|
||||||
|
{"Alan Turing", "John von Neumann", "Bill Gates", "Steve Jobs"},
|
||||||
|
4, 1},
|
||||||
|
{"Čím byl John von Neumann?",
|
||||||
|
{"Americký matematik", "Německý fyzik", "Anglický chemik",
|
||||||
|
"Francouzský lékař"},
|
||||||
|
4, 0},
|
||||||
|
{"Co znamená zkratka ALS?",
|
||||||
|
{"Automatická logická síť", "Aritmeticko-logická jednotka",
|
||||||
|
"Analogová linková sestava", "Adresová listová sekce"},
|
||||||
|
4, 1},
|
||||||
|
{"Která část Von Neumannova schématu řídí ostatní části?",
|
||||||
|
{"Operační paměť", "Vstupní jednotka", "Řadič", "Výstupní jednotka"},
|
||||||
|
4, 2},
|
||||||
|
{"Kde se podle Von Neumannova schématu uchovává program i data?",
|
||||||
|
{"Jen na pevném disku", "V operační paměti", "Jen ve vstupní jednotce",
|
||||||
|
"Jen v ALS"},
|
||||||
|
4, 1},
|
||||||
|
{"Čím se John von Neumann také zabýval?",
|
||||||
|
{"Jen malířstvím", "Teorií her", "Jen botanikou", "Jen hudbou"},
|
||||||
|
4, 1},
|
||||||
|
};
|
||||||
|
static const char *hints[] = {
|
||||||
|
"John von Neumann – americký matematik",
|
||||||
|
"Americký matematik s objevy v digitálních počítačích",
|
||||||
|
"ALS = aritmeticko-logická jednotka",
|
||||||
|
"Řadič řídí činnost ostatních částí",
|
||||||
|
"Operační paměť uchovává program i data",
|
||||||
|
"Byl tvůrcem teorie her",
|
||||||
|
};
|
||||||
|
return build_hw_mcq_page(0, "hwunit1", "hw_ex1_title", "hw_quiz1_head",
|
||||||
|
qs, hints, 6);
|
||||||
|
}
|
||||||
22
i18n.c
22
i18n.c
|
|
@ -281,6 +281,15 @@ const TrEntry tr_ui[] = {
|
||||||
{"net_slide_prev", "Zpět", "Back"},
|
{"net_slide_prev", "Zpět", "Back"},
|
||||||
{"net_slide_start", "Přejít na cvičení", "Go to the exercise"},
|
{"net_slide_start", "Přejít na cvičení", "Go to the exercise"},
|
||||||
{"net_solution", "Ukázat řešení", "Show solution"},
|
{"net_solution", "Ukázat řešení", "Show solution"},
|
||||||
|
{"hw_sub", "Vyberte lekci na cestě a začněte procvičovat.",
|
||||||
|
"Pick a unit on the path and start practicing."},
|
||||||
|
{"hw_unit1", "Architektura počítače", "Computer architecture"},
|
||||||
|
{"hw_unit1_sub", "3 snímky o Von Neumannovi a schématu a pak kvíz.",
|
||||||
|
"3 slides about von Neumann and the model, then a quiz."},
|
||||||
|
{"hw_ex1_title", "Kvíz: architektura počítače",
|
||||||
|
"Quiz: computer architecture"},
|
||||||
|
{"hw_quiz1_head", "Kvíz k architektuře počítače",
|
||||||
|
"Quiz on computer architecture"},
|
||||||
{NULL, NULL, NULL}
|
{NULL, NULL, NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -917,6 +926,17 @@ const TrEntry tr_content[] = {
|
||||||
{"Ahoj! (při loučení)", NULL, "Bye!"},
|
{"Ahoj! (při loučení)", NULL, "Bye!"},
|
||||||
{"shledání", NULL, "reunion"},
|
{"shledání", NULL, "reunion"},
|
||||||
{"Na shledanou!", NULL, "Goodbye!"},
|
{"Na shledanou!", NULL, "Goodbye!"},
|
||||||
|
{"John von Neumann – americký matematik", NULL,
|
||||||
|
"John von Neumann – American mathematician"},
|
||||||
|
{"Americký matematik s objevy v digitálních počítačích", NULL,
|
||||||
|
"American mathematician with discoveries in digital computers"},
|
||||||
|
{"ALS = aritmeticko-logická jednotka", NULL,
|
||||||
|
"ALU = arithmetic-logic unit"},
|
||||||
|
{"Řadič řídí činnost ostatních částí", NULL,
|
||||||
|
"The control unit directs the other parts"},
|
||||||
|
{"Operační paměť uchovává program i data", NULL,
|
||||||
|
"Main memory stores both the program and data"},
|
||||||
|
{"Byl tvůrcem teorie her", NULL, "He created game theory"},
|
||||||
{NULL, NULL, NULL}
|
{NULL, NULL, NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1006,6 +1026,7 @@ void refresh_welcome_heading(void) {
|
||||||
|
|
||||||
void refresh_stats_ui(void);
|
void refresh_stats_ui(void);
|
||||||
void net_lessons_apply_lang(void);
|
void net_lessons_apply_lang(void);
|
||||||
|
void hw_lessons_apply_lang(void);
|
||||||
void net_rail_theme_reset(void);
|
void net_rail_theme_reset(void);
|
||||||
|
|
||||||
void apply_language(void) {
|
void apply_language(void) {
|
||||||
|
|
@ -1017,4 +1038,5 @@ void apply_language(void) {
|
||||||
refresh_welcome_heading();
|
refresh_welcome_heading();
|
||||||
refresh_stats_ui();
|
refresh_stats_ui();
|
||||||
net_lessons_apply_lang();
|
net_lessons_apply_lang();
|
||||||
|
hw_lessons_apply_lang();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
48
icons.c
48
icons.c
|
|
@ -94,6 +94,54 @@ void draw_wifi_icon(GtkDrawingArea *area, cairo_t *cr,
|
||||||
cairo_fill(cr);
|
cairo_fill(cr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* CPU / chip icon for Technické vybavení. */
|
||||||
|
void draw_chip_icon(GtkDrawingArea *area, cairo_t *cr,
|
||||||
|
int width, int height, gpointer data) {
|
||||||
|
const double s = MIN(width, height);
|
||||||
|
const double cx = width / 2.0;
|
||||||
|
const double cy = height / 2.0;
|
||||||
|
const double body = s * 0.42;
|
||||||
|
const double pin_len = s * 0.12;
|
||||||
|
const double pin_w = s * 0.055;
|
||||||
|
const Rgb c = mix_rgb(color_from_hex(app_theme.on_accent),
|
||||||
|
color_from_hex(app_theme.accent2), 0.22);
|
||||||
|
|
||||||
|
(void)area;
|
||||||
|
(void)data;
|
||||||
|
|
||||||
|
cairo_set_source_rgb(cr, c.r, c.g, c.b);
|
||||||
|
|
||||||
|
cairo_rectangle(cr, cx - body / 2.0, cy - body / 2.0, body, body);
|
||||||
|
cairo_fill(cr);
|
||||||
|
|
||||||
|
cairo_set_line_cap(cr, CAIRO_LINE_CAP_SQUARE);
|
||||||
|
cairo_set_line_width(cr, pin_w);
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
double t = (i + 0.5) / 4.0;
|
||||||
|
double x = cx - body / 2.0 + t * body;
|
||||||
|
double y = cy - body / 2.0 + t * body;
|
||||||
|
|
||||||
|
cairo_move_to(cr, x, cy - body / 2.0);
|
||||||
|
cairo_line_to(cr, x, cy - body / 2.0 - pin_len);
|
||||||
|
cairo_move_to(cr, x, cy + body / 2.0);
|
||||||
|
cairo_line_to(cr, x, cy + body / 2.0 + pin_len);
|
||||||
|
cairo_move_to(cr, cx - body / 2.0, y);
|
||||||
|
cairo_line_to(cr, cx - body / 2.0 - pin_len, y);
|
||||||
|
cairo_move_to(cr, cx + body / 2.0, y);
|
||||||
|
cairo_line_to(cr, cx + body / 2.0 + pin_len, y);
|
||||||
|
}
|
||||||
|
cairo_stroke(cr);
|
||||||
|
|
||||||
|
{
|
||||||
|
Rgb hole = color_from_hex(app_theme.crust);
|
||||||
|
double core = body * 0.34;
|
||||||
|
|
||||||
|
cairo_set_source_rgb(cr, hole.r, hole.g, hole.b);
|
||||||
|
cairo_rectangle(cr, cx - core / 2.0, cy - core / 2.0, core, core);
|
||||||
|
cairo_fill(cr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void draw_back_icon(GtkDrawingArea *area, cairo_t *cr,
|
void draw_back_icon(GtkDrawingArea *area, cairo_t *cr,
|
||||||
int width, int height, gpointer data) {
|
int width, int height, gpointer data) {
|
||||||
const double size = 15.0; /* fixed logical icon size */
|
const double size = 15.0; /* fixed logical icon size */
|
||||||
|
|
|
||||||
3
main.c
3
main.c
|
|
@ -171,6 +171,9 @@ void activate(GtkApplication *app, gpointer user_data) {
|
||||||
gtk_stack_add_named(main_stack, roadmap_page, "roadmap");
|
gtk_stack_add_named(main_stack, roadmap_page, "roadmap");
|
||||||
gtk_stack_add_named(main_stack, build_stats_page(), "stats");
|
gtk_stack_add_named(main_stack, build_stats_page(), "stats");
|
||||||
gtk_stack_add_named(main_stack, build_netmap_page(), "netmap");
|
gtk_stack_add_named(main_stack, build_netmap_page(), "netmap");
|
||||||
|
gtk_stack_add_named(main_stack, build_hwmap_page(), "hwmap");
|
||||||
|
gtk_stack_add_named(main_stack, build_hw_unit1_page(), "hwunit1");
|
||||||
|
gtk_stack_add_named(main_stack, build_hw_unit1_exercise_page(), "hwex1");
|
||||||
{
|
{
|
||||||
typedef GtkWidget *(*NetBuilder)(void);
|
typedef GtkWidget *(*NetBuilder)(void);
|
||||||
static const struct {
|
static const struct {
|
||||||
|
|
|
||||||
22
maturita.h
22
maturita.h
|
|
@ -12,6 +12,7 @@
|
||||||
#define MAX_UNIT_EX 20 /* largest exercise count any unit may have */
|
#define MAX_UNIT_EX 20 /* largest exercise count any unit may have */
|
||||||
#define NUM_SUBJECTS 13
|
#define NUM_SUBJECTS 13
|
||||||
#define NET_SUBJ 1 /* index of "Správa počítačových sítí" */
|
#define NET_SUBJ 1 /* index of "Správa počítačových sítí" */
|
||||||
|
#define HW_SUBJ 11 /* index of "Technické vybavení" */
|
||||||
#define CSS_FILE "style.css"
|
#define CSS_FILE "style.css"
|
||||||
#define PROGRESS_DIR "progress"
|
#define PROGRESS_DIR "progress"
|
||||||
#define PROGRESS_U1 "progress/unit1.conf"
|
#define PROGRESS_U1 "progress/unit1.conf"
|
||||||
|
|
@ -19,6 +20,7 @@
|
||||||
#define PROGRESS_U3 "progress/unit3.conf"
|
#define PROGRESS_U3 "progress/unit3.conf"
|
||||||
#define SETTINGS_FILE "progress/settings.conf"
|
#define SETTINGS_FILE "progress/settings.conf"
|
||||||
#define PROGRESS_NET "progress/net.conf"
|
#define PROGRESS_NET "progress/net.conf"
|
||||||
|
#define PROGRESS_HW "progress/hw.conf"
|
||||||
#define NODE_SIZE 88.0
|
#define NODE_SIZE 88.0
|
||||||
#define PATH_SPAC 240.0
|
#define PATH_SPAC 240.0
|
||||||
#define ROAD_MX 150.0 /* horizontal canvas margin */
|
#define ROAD_MX 150.0 /* horizontal canvas margin */
|
||||||
|
|
@ -56,6 +58,9 @@
|
||||||
#define NET9_SLIDES 4
|
#define NET9_SLIDES 4
|
||||||
#define NET10_SLIDES 3
|
#define NET10_SLIDES 3
|
||||||
#define NET_LESSONS 10
|
#define NET_LESSONS 10
|
||||||
|
#define HW_UNITS 10
|
||||||
|
#define HW_LESSONS 1
|
||||||
|
#define HW_SLIDES 3
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
THEME_CATPPUCCIN = 0,
|
THEME_CATPPUCCIN = 0,
|
||||||
|
|
@ -668,6 +673,9 @@ extern const U3Fill ex13_u3_rows[];
|
||||||
extern const U3Kw ex14_u3_qs[];
|
extern const U3Kw ex14_u3_qs[];
|
||||||
extern const U3Let ex15_u3_rows[];
|
extern const U3Let ex15_u3_rows[];
|
||||||
extern NetLesson net_lessons[NET_LESSONS];
|
extern NetLesson net_lessons[NET_LESSONS];
|
||||||
|
extern NetLesson *net_notes_target;
|
||||||
|
extern NetLesson hw_lessons[HW_LESSONS];
|
||||||
|
extern GtkWidget *hw_nodes[HW_UNITS];
|
||||||
extern GtkWidget *net_note_host;
|
extern GtkWidget *net_note_host;
|
||||||
extern int net_note_last_w;
|
extern int net_note_last_w;
|
||||||
extern GtkWidget *net_scroll;
|
extern GtkWidget *net_scroll;
|
||||||
|
|
@ -745,6 +753,20 @@ void net_save_progress(void);
|
||||||
void mark_net_done(int lesson_id);
|
void mark_net_done(int lesson_id);
|
||||||
void refresh_net_completion_ui(void);
|
void refresh_net_completion_ui(void);
|
||||||
void progress_for_net(ProgressSum *out);
|
void progress_for_net(ProgressSum *out);
|
||||||
|
void hw_load_progress(void);
|
||||||
|
void hw_save_progress(void);
|
||||||
|
void mark_hw_done(int lesson_id);
|
||||||
|
void refresh_hw_completion_ui(void);
|
||||||
|
void progress_for_hw(ProgressSum *out);
|
||||||
|
void hw_rail_theme_reset(void);
|
||||||
|
void hw_lessons_apply_lang(void);
|
||||||
|
GtkWidget *build_hwmap_page(void);
|
||||||
|
GtkWidget *build_hw_unit1_page(void);
|
||||||
|
GtkWidget *build_hw_unit1_exercise_page(void);
|
||||||
|
void draw_chip_icon(GtkDrawingArea *area, cairo_t *cr,
|
||||||
|
int width, int height, gpointer data);
|
||||||
|
void net_lesson_notes_ensure(NetLesson *L);
|
||||||
|
void net_rescale_lesson_notes(NetLesson *L, int body, int head, int kick);
|
||||||
GtkWidget *make_back_button(const char *target);
|
GtkWidget *make_back_button(const char *target);
|
||||||
void on_nav_clicked(GtkButton *button, gpointer user_data);
|
void on_nav_clicked(GtkButton *button, gpointer user_data);
|
||||||
int unit_done_count(const UnitCtx *u);
|
int unit_done_count(const UnitCtx *u);
|
||||||
|
|
|
||||||
2
net.c
2
net.c
|
|
@ -40,7 +40,7 @@ void net_heading(GtkWidget *box, const char *text) {
|
||||||
/* Note-card slides with text that scales with the window. */
|
/* Note-card slides with text that scales with the window. */
|
||||||
GtkWidget *net_note_host;
|
GtkWidget *net_note_host;
|
||||||
int net_note_last_w = -1;
|
int net_note_last_w = -1;
|
||||||
static NetLesson *net_notes_target;
|
NetLesson *net_notes_target;
|
||||||
|
|
||||||
void net_lesson_notes_ensure(NetLesson *L) {
|
void net_lesson_notes_ensure(NetLesson *L) {
|
||||||
if (L->note_kicks)
|
if (L->note_kicks)
|
||||||
|
|
|
||||||
93
progress.c
93
progress.c
|
|
@ -53,6 +53,7 @@ void load_progress(void) {
|
||||||
for (int i = 0; i < NUM_UNLOCKED; i++)
|
for (int i = 0; i < NUM_UNLOCKED; i++)
|
||||||
unit_load_progress(&units[i]);
|
unit_load_progress(&units[i]);
|
||||||
net_load_progress();
|
net_load_progress();
|
||||||
|
hw_load_progress();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Redraw the exercise rails of every unit that already has a page. */
|
/* Redraw the exercise rails of every unit that already has a page. */
|
||||||
|
|
@ -124,6 +125,7 @@ void refresh_completion_ui(void) {
|
||||||
|
|
||||||
all_rails_redraw();
|
all_rails_redraw();
|
||||||
refresh_net_completion_ui();
|
refresh_net_completion_ui();
|
||||||
|
refresh_hw_completion_ui();
|
||||||
refresh_stats_ui();
|
refresh_stats_ui();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -225,3 +227,94 @@ void progress_for_net(ProgressSum *out) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void hw_load_progress(void) {
|
||||||
|
GKeyFile *kf = g_key_file_new();
|
||||||
|
GError *err = NULL;
|
||||||
|
|
||||||
|
if (!g_key_file_load_from_file(kf, PROGRESS_HW, G_KEY_FILE_NONE, &err)) {
|
||||||
|
if (err)
|
||||||
|
g_error_free(err);
|
||||||
|
g_key_file_free(kf);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < HW_LESSONS; i++) {
|
||||||
|
gchar key[8];
|
||||||
|
|
||||||
|
g_snprintf(key, sizeof(key), "%d", i + 1);
|
||||||
|
hw_lessons[i].done = g_key_file_get_boolean(kf, "done", key, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_key_file_free(kf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hw_save_progress(void) {
|
||||||
|
GKeyFile *kf = g_key_file_new();
|
||||||
|
|
||||||
|
for (int i = 0; i < HW_LESSONS; i++) {
|
||||||
|
gchar key[8];
|
||||||
|
|
||||||
|
g_snprintf(key, sizeof(key), "%d", i + 1);
|
||||||
|
g_key_file_set_boolean(kf, "done", key, hw_lessons[i].done);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_mkdir_with_parents(PROGRESS_DIR, 0755) != 0) {
|
||||||
|
g_key_file_free(kf);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
gchar *data = g_key_file_to_data(kf, NULL, NULL);
|
||||||
|
g_key_file_free(kf);
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
GError *err = NULL;
|
||||||
|
g_file_set_contents(PROGRESS_HW, data, -1, &err);
|
||||||
|
if (err)
|
||||||
|
g_error_free(err);
|
||||||
|
g_free(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void refresh_hw_completion_ui(void) {
|
||||||
|
for (int i = 0; i < HW_LESSONS; i++) {
|
||||||
|
GtkWidget *node = hw_nodes[i];
|
||||||
|
GtkWidget *icon = hw_lessons[i].done_icon;
|
||||||
|
|
||||||
|
if (!node)
|
||||||
|
continue;
|
||||||
|
if (hw_lessons[i].done) {
|
||||||
|
gtk_widget_remove_css_class(node, "current");
|
||||||
|
gtk_widget_add_css_class(node, "done");
|
||||||
|
if (icon)
|
||||||
|
gtk_widget_set_visible(icon, TRUE);
|
||||||
|
} else {
|
||||||
|
gtk_widget_remove_css_class(node, "done");
|
||||||
|
gtk_widget_add_css_class(node, "current");
|
||||||
|
if (icon)
|
||||||
|
gtk_widget_set_visible(icon, FALSE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void mark_hw_done(int lesson_id) {
|
||||||
|
if (lesson_id < 0 || lesson_id >= HW_LESSONS)
|
||||||
|
return;
|
||||||
|
if (hw_lessons[lesson_id].done)
|
||||||
|
return;
|
||||||
|
hw_lessons[lesson_id].done = TRUE;
|
||||||
|
hw_save_progress();
|
||||||
|
refresh_hw_completion_ui();
|
||||||
|
refresh_stats_ui();
|
||||||
|
}
|
||||||
|
|
||||||
|
void progress_for_hw(ProgressSum *out) {
|
||||||
|
out->total_ex += HW_LESSONS;
|
||||||
|
out->open_units += HW_LESSONS;
|
||||||
|
for (int i = 0; i < HW_LESSONS; i++) {
|
||||||
|
if (hw_lessons[i].done) {
|
||||||
|
out->done_ex++;
|
||||||
|
out->done_units++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
10
stats.c
10
stats.c
|
|
@ -56,6 +56,8 @@ void refresh_stats_ui(void) {
|
||||||
for (int s = 0; s < NUM_SUBJECTS; s++) {
|
for (int s = 0; s < NUM_SUBJECTS; s++) {
|
||||||
if (s == NET_SUBJ)
|
if (s == NET_SUBJ)
|
||||||
progress_for_net(&all);
|
progress_for_net(&all);
|
||||||
|
else if (s == HW_SUBJ)
|
||||||
|
progress_for_hw(&all);
|
||||||
else
|
else
|
||||||
progress_for_units(sub_unit_start[s], sub_unit_count[s], &all);
|
progress_for_units(sub_unit_start[s], sub_unit_count[s], &all);
|
||||||
}
|
}
|
||||||
|
|
@ -83,12 +85,15 @@ void refresh_stats_ui(void) {
|
||||||
GtkWidget *count = stats_ui.subj[s].count;
|
GtkWidget *count = stats_ui.subj[s].count;
|
||||||
GtkWidget *bar = stats_ui.subj[s].bar;
|
GtkWidget *bar = stats_ui.subj[s].bar;
|
||||||
ProgressSum sp = {0};
|
ProgressSum sp = {0};
|
||||||
gboolean has_content = (s == NET_SUBJ) || sub_unit_count[s] > 0;
|
gboolean has_content = (s == NET_SUBJ) || (s == HW_SUBJ)
|
||||||
|
|| sub_unit_count[s] > 0;
|
||||||
|
|
||||||
if (!count || !bar)
|
if (!count || !bar)
|
||||||
continue;
|
continue;
|
||||||
if (s == NET_SUBJ)
|
if (s == NET_SUBJ)
|
||||||
progress_for_net(&sp);
|
progress_for_net(&sp);
|
||||||
|
else if (s == HW_SUBJ)
|
||||||
|
progress_for_hw(&sp);
|
||||||
else
|
else
|
||||||
progress_for_units(sub_unit_start[s], sub_unit_count[s], &sp);
|
progress_for_units(sub_unit_start[s], sub_unit_count[s], &sp);
|
||||||
if (has_content) {
|
if (has_content) {
|
||||||
|
|
@ -353,7 +358,8 @@ GtkWidget *build_stats_page(void) {
|
||||||
GtkWidget *name;
|
GtkWidget *name;
|
||||||
GtkWidget *count;
|
GtkWidget *count;
|
||||||
GtkWidget *bar;
|
GtkWidget *bar;
|
||||||
gboolean has_units = (s == NET_SUBJ) || sub_unit_count[s] > 0;
|
gboolean has_units = (s == NET_SUBJ) || (s == HW_SUBJ)
|
||||||
|
|| sub_unit_count[s] > 0;
|
||||||
gboolean has_deutsch_units = sub_unit_count[s] > 0;
|
gboolean has_deutsch_units = sub_unit_count[s] > 0;
|
||||||
|
|
||||||
subject = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
subject = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||||
|
|
|
||||||
15
subjects.c
15
subjects.c
|
|
@ -5,7 +5,7 @@
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
|
||||||
/* Index 0 (Deutsch) and NET_SUBJ (computer networks) are unlocked. */
|
/* Index 0 (Deutsch), NET_SUBJ (networks) and HW_SUBJ (hardware) are unlocked. */
|
||||||
const char *sub_keys[NUM_SUBJECTS] = {
|
const char *sub_keys[NUM_SUBJECTS] = {
|
||||||
"Deutsch",
|
"Deutsch",
|
||||||
"Správa počítačových sítí",
|
"Správa počítačových sítí",
|
||||||
|
|
@ -326,6 +326,17 @@ GtkWidget *build_subjects_page(void) {
|
||||||
g_object_set_data_full(G_OBJECT(card), "target",
|
g_object_set_data_full(G_OBJECT(card), "target",
|
||||||
g_strdup("netmap"), g_free);
|
g_strdup("netmap"), g_free);
|
||||||
g_signal_connect(card, "clicked", G_CALLBACK(on_nav_clicked), NULL);
|
g_signal_connect(card, "clicked", G_CALLBACK(on_nav_clicked), NULL);
|
||||||
|
} else if (i == HW_SUBJ) {
|
||||||
|
GtkWidget *chip = gtk_drawing_area_new();
|
||||||
|
gtk_widget_add_css_class(card, "current");
|
||||||
|
gtk_widget_set_size_request(chip, (int)SUB_BUBBLE,
|
||||||
|
(int)SUB_BUBBLE);
|
||||||
|
gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(chip),
|
||||||
|
draw_chip_icon, NULL, NULL);
|
||||||
|
gtk_button_set_child(GTK_BUTTON(card), chip);
|
||||||
|
g_object_set_data_full(G_OBJECT(card), "target",
|
||||||
|
g_strdup("hwmap"), g_free);
|
||||||
|
g_signal_connect(card, "clicked", G_CALLBACK(on_nav_clicked), NULL);
|
||||||
} else {
|
} else {
|
||||||
GtkWidget *lock;
|
GtkWidget *lock;
|
||||||
gtk_widget_add_css_class(card, "locked");
|
gtk_widget_add_css_class(card, "locked");
|
||||||
|
|
@ -345,7 +356,7 @@ GtkWidget *build_subjects_page(void) {
|
||||||
gtk_label_set_justify(GTK_LABEL(lbl), GTK_JUSTIFY_CENTER);
|
gtk_label_set_justify(GTK_LABEL(lbl), GTK_JUSTIFY_CENTER);
|
||||||
gtk_label_set_wrap(GTK_LABEL(lbl), TRUE);
|
gtk_label_set_wrap(GTK_LABEL(lbl), TRUE);
|
||||||
gtk_widget_add_css_class(lbl, "unit-name");
|
gtk_widget_add_css_class(lbl, "unit-name");
|
||||||
if (i != 0 && i != NET_SUBJ)
|
if (i != 0 && i != NET_SUBJ && i != HW_SUBJ)
|
||||||
gtk_widget_add_css_class(lbl, "unit-name-locked");
|
gtk_widget_add_css_class(lbl, "unit-name-locked");
|
||||||
sub_labels[i] = lbl;
|
sub_labels[i] = lbl;
|
||||||
gtk_fixed_put(GTK_FIXED(fixed), lbl, 0, 0);
|
gtk_fixed_put(GTK_FIXED(fixed), lbl, 0, 0);
|
||||||
|
|
|
||||||
1
theme.c
1
theme.c
|
|
@ -359,4 +359,5 @@ void apply_theme(void) {
|
||||||
gtk_widget_queue_draw(theme_swatch_areas[i]);
|
gtk_widget_queue_draw(theme_swatch_areas[i]);
|
||||||
}
|
}
|
||||||
net_rail_theme_reset();
|
net_rail_theme_reset();
|
||||||
|
hw_rail_theme_reset();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue