mirror of
https://github.com/pepaskopec-blip/Graduately.git
synced 2026-10-27 15:14:35 +00:00
The swap helper now detaches from the app, downloads start when a newer VERSION appears, and checks keep running so a just-published build is not missed. Co-authored-by: Cursor <cursoragent@cursor.com>
361 lines
14 KiB
C
361 lines
14 KiB
C
#include "maturita.h"
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Settings */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
static GtkWidget *settings_popover;
|
|
|
|
void settings_open(void) {
|
|
if (settings_popover)
|
|
gtk_popover_popup(GTK_POPOVER(settings_popover));
|
|
}
|
|
|
|
void draw_theme_swatch(GtkDrawingArea *area, cairo_t *cr,
|
|
int width, int height, gpointer data) {
|
|
ThemeId id = (ThemeId)GPOINTER_TO_INT(data);
|
|
ThemePalette p = theme_palette(id, app_color_mode);
|
|
Rgb base = color_from_hex(p.base);
|
|
Rgb a1 = color_from_hex(p.accent);
|
|
Rgb a2 = color_from_hex(p.accent3);
|
|
Rgb ok = color_from_hex(p.success);
|
|
Rgb accent = color_from_hex(app_theme.accent);
|
|
gboolean selected = (app_theme_id == id);
|
|
double size = MIN(width, height);
|
|
double cx = width / 2.0;
|
|
double cy = height / 2.0;
|
|
double r = size / 2.0 - (selected ? 3.0 : 1.0);
|
|
double dot = MAX(3.5, r * 0.26);
|
|
double gap = dot * 2.05;
|
|
|
|
(void)area;
|
|
|
|
if (selected) {
|
|
cairo_new_path(cr);
|
|
cairo_arc(cr, cx, cy, size / 2.0 - 0.5, 0.0, 2.0 * G_PI);
|
|
cairo_set_source_rgb(cr, accent.r, accent.g, accent.b);
|
|
cairo_set_line_width(cr, 2.2);
|
|
cairo_stroke(cr);
|
|
}
|
|
|
|
cairo_new_path(cr);
|
|
cairo_arc(cr, cx, cy, r, 0.0, 2.0 * G_PI);
|
|
cairo_set_source_rgb(cr, base.r, base.g, base.b);
|
|
cairo_fill(cr);
|
|
|
|
cairo_new_path(cr);
|
|
cairo_arc(cr, cx - gap, cy, dot, 0.0, 2.0 * G_PI);
|
|
cairo_set_source_rgb(cr, a1.r, a1.g, a1.b);
|
|
cairo_fill(cr);
|
|
|
|
cairo_new_path(cr);
|
|
cairo_arc(cr, cx, cy, dot, 0.0, 2.0 * G_PI);
|
|
cairo_set_source_rgb(cr, a2.r, a2.g, a2.b);
|
|
cairo_fill(cr);
|
|
|
|
cairo_new_path(cr);
|
|
cairo_arc(cr, cx + gap, cy, dot, 0.0, 2.0 * G_PI);
|
|
cairo_set_source_rgb(cr, ok.r, ok.g, ok.b);
|
|
cairo_fill(cr);
|
|
}
|
|
|
|
void on_mode_toggled(GtkToggleButton *btn, gpointer user_data) {
|
|
ColorMode mode = (ColorMode)GPOINTER_TO_INT(user_data);
|
|
|
|
if (!gtk_toggle_button_get_active(btn))
|
|
return;
|
|
if (app_color_mode == mode)
|
|
return;
|
|
app_color_mode = mode;
|
|
apply_theme();
|
|
save_settings();
|
|
}
|
|
|
|
void on_theme_toggled(GtkToggleButton *btn, gpointer user_data) {
|
|
ThemeId id = (ThemeId)GPOINTER_TO_INT(user_data);
|
|
|
|
if (!gtk_toggle_button_get_active(btn))
|
|
return;
|
|
if (app_theme_id == id)
|
|
return;
|
|
app_theme_id = id;
|
|
apply_theme();
|
|
save_settings();
|
|
}
|
|
|
|
void on_lang_toggled(GtkToggleButton *btn, gpointer user_data) {
|
|
UiLang lang = (UiLang)GPOINTER_TO_INT(user_data);
|
|
|
|
if (!gtk_toggle_button_get_active(btn))
|
|
return;
|
|
if (app_lang == lang)
|
|
return;
|
|
app_lang = lang;
|
|
apply_language();
|
|
save_settings();
|
|
}
|
|
|
|
GtkWidget *make_lang_chip(const char *label, UiLang lang,
|
|
GtkToggleButton *group) {
|
|
GtkWidget *btn = gtk_toggle_button_new_with_label(label);
|
|
|
|
gtk_widget_add_css_class(btn, "mode-chip");
|
|
gtk_widget_set_hexpand(btn, TRUE);
|
|
if (group)
|
|
gtk_toggle_button_set_group(GTK_TOGGLE_BUTTON(btn), group);
|
|
g_signal_connect(btn, "toggled", G_CALLBACK(on_lang_toggled),
|
|
GINT_TO_POINTER(lang));
|
|
return btn;
|
|
}
|
|
|
|
GtkWidget *make_mode_chip(const char *label, ColorMode mode,
|
|
GtkToggleButton *group) {
|
|
GtkWidget *btn = gtk_toggle_button_new();
|
|
|
|
i18n_bind(btn, label, 1);
|
|
gtk_widget_add_css_class(btn, "mode-chip");
|
|
gtk_widget_set_hexpand(btn, TRUE);
|
|
if (group)
|
|
gtk_toggle_button_set_group(GTK_TOGGLE_BUTTON(btn), group);
|
|
g_signal_connect(btn, "toggled", G_CALLBACK(on_mode_toggled),
|
|
GINT_TO_POINTER(mode));
|
|
return btn;
|
|
}
|
|
|
|
GtkWidget *make_theme_card(ThemeId id, GtkToggleButton *group) {
|
|
GtkWidget *btn;
|
|
GtkWidget *vbox;
|
|
GtkWidget *swatch;
|
|
GtkWidget *name;
|
|
|
|
btn = gtk_toggle_button_new();
|
|
gtk_widget_add_css_class(btn, "theme-card");
|
|
gtk_widget_set_hexpand(btn, TRUE);
|
|
if (group)
|
|
gtk_toggle_button_set_group(GTK_TOGGLE_BUTTON(btn), group);
|
|
|
|
vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
|
|
gtk_widget_set_halign(vbox, GTK_ALIGN_CENTER);
|
|
gtk_button_set_child(GTK_BUTTON(btn), vbox);
|
|
|
|
swatch = gtk_drawing_area_new();
|
|
gtk_widget_set_size_request(swatch, 44, 44);
|
|
gtk_widget_set_halign(swatch, GTK_ALIGN_CENTER);
|
|
gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(swatch),
|
|
draw_theme_swatch,
|
|
GINT_TO_POINTER(id), NULL);
|
|
theme_swatch_areas[id] = swatch;
|
|
gtk_box_append(GTK_BOX(vbox), swatch);
|
|
|
|
name = gtk_label_new(theme_names[id]);
|
|
gtk_widget_add_css_class(name, "theme-card-name");
|
|
gtk_widget_set_halign(name, GTK_ALIGN_CENTER);
|
|
gtk_box_append(GTK_BOX(vbox), name);
|
|
|
|
g_signal_connect(btn, "toggled", G_CALLBACK(on_theme_toggled),
|
|
GINT_TO_POINTER(id));
|
|
return btn;
|
|
}
|
|
|
|
void on_settings_clicked(GtkButton *button, gpointer user_data) {
|
|
GtkPopover *popover = GTK_POPOVER(user_data);
|
|
|
|
(void)button;
|
|
update_on_settings_open();
|
|
gtk_popover_popup(popover);
|
|
}
|
|
|
|
GtkWidget *build_stats_button(void) {
|
|
GtkWidget *btn;
|
|
GtkWidget *icon;
|
|
|
|
btn = gtk_button_new();
|
|
gtk_widget_add_css_class(btn, "settings-btn");
|
|
gtk_widget_add_css_class(btn, "flat");
|
|
i18n_bind(btn, "stats", 2);
|
|
gtk_widget_set_valign(btn, GTK_ALIGN_CENTER);
|
|
gtk_widget_set_focus_on_click(btn, FALSE);
|
|
|
|
icon = gtk_drawing_area_new();
|
|
gtk_widget_set_size_request(icon, 18, 18);
|
|
gtk_widget_set_can_target(icon, FALSE);
|
|
gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(icon),
|
|
draw_stats_icon, NULL, NULL);
|
|
gtk_button_set_child(GTK_BUTTON(btn), icon);
|
|
g_signal_connect_swapped(btn, "state-flags-changed",
|
|
G_CALLBACK(gtk_widget_queue_draw), icon);
|
|
g_signal_connect(btn, "clicked", G_CALLBACK(on_stats_clicked), NULL);
|
|
|
|
return btn;
|
|
}
|
|
|
|
GtkWidget *build_settings_button(void) {
|
|
GtkWidget *btn;
|
|
GtkWidget *icon;
|
|
GtkWidget *popover;
|
|
GtkWidget *box;
|
|
GtkWidget *title;
|
|
GtkWidget *label;
|
|
GtkWidget *mode_row;
|
|
GtkWidget *lang_row;
|
|
GtkWidget *theme_grid;
|
|
GtkWidget *dark_btn;
|
|
GtkWidget *light_btn;
|
|
GtkWidget *cs_btn;
|
|
GtkWidget *en_btn;
|
|
GtkWidget *first_theme = NULL;
|
|
int i;
|
|
|
|
btn = gtk_button_new();
|
|
gtk_widget_add_css_class(btn, "settings-btn");
|
|
gtk_widget_add_css_class(btn, "flat");
|
|
i18n_bind(btn, "settings", 2);
|
|
gtk_widget_set_valign(btn, GTK_ALIGN_CENTER);
|
|
gtk_widget_set_focus_on_click(btn, FALSE);
|
|
|
|
icon = gtk_drawing_area_new();
|
|
gtk_widget_set_size_request(icon, 18, 18);
|
|
gtk_widget_set_can_target(icon, FALSE);
|
|
gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(icon),
|
|
draw_settings_icon, NULL, NULL);
|
|
gtk_button_set_child(GTK_BUTTON(btn), icon);
|
|
g_signal_connect_swapped(btn, "state-flags-changed",
|
|
G_CALLBACK(gtk_widget_queue_draw), icon);
|
|
|
|
popover = gtk_popover_new();
|
|
settings_popover = popover;
|
|
gtk_widget_add_css_class(popover, "settings-popover");
|
|
gtk_popover_set_has_arrow(GTK_POPOVER(popover), TRUE);
|
|
gtk_popover_set_position(GTK_POPOVER(popover), GTK_POS_BOTTOM);
|
|
gtk_widget_set_parent(popover, btn);
|
|
g_signal_connect_swapped(btn, "destroy",
|
|
G_CALLBACK(gtk_widget_unparent), popover);
|
|
g_signal_connect(btn, "clicked", G_CALLBACK(on_settings_clicked), popover);
|
|
|
|
box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 14);
|
|
gtk_widget_add_css_class(box, "settings-box");
|
|
|
|
title = gtk_label_new(NULL);
|
|
i18n_bind(title, "settings_title", 0);
|
|
gtk_widget_set_halign(title, GTK_ALIGN_START);
|
|
gtk_widget_add_css_class(title, "settings-title");
|
|
gtk_box_append(GTK_BOX(box), title);
|
|
|
|
label = gtk_label_new(NULL);
|
|
i18n_bind(label, "mode", 0);
|
|
gtk_widget_set_halign(label, GTK_ALIGN_START);
|
|
gtk_widget_add_css_class(label, "settings-label");
|
|
gtk_widget_set_margin_top(label, 4);
|
|
gtk_box_append(GTK_BOX(box), label);
|
|
|
|
mode_row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
|
|
gtk_widget_add_css_class(mode_row, "settings-seg");
|
|
gtk_box_append(GTK_BOX(box), mode_row);
|
|
|
|
dark_btn = make_mode_chip("mode_dark", MODE_DARK, NULL);
|
|
light_btn = make_mode_chip("mode_light", MODE_LIGHT,
|
|
GTK_TOGGLE_BUTTON(dark_btn));
|
|
g_signal_handlers_block_matched(dark_btn, G_SIGNAL_MATCH_FUNC,
|
|
0, 0, NULL, on_mode_toggled, NULL);
|
|
g_signal_handlers_block_matched(light_btn, G_SIGNAL_MATCH_FUNC,
|
|
0, 0, NULL, on_mode_toggled, NULL);
|
|
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(dark_btn),
|
|
app_color_mode == MODE_DARK);
|
|
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(light_btn),
|
|
app_color_mode == MODE_LIGHT);
|
|
g_signal_handlers_unblock_matched(dark_btn, G_SIGNAL_MATCH_FUNC,
|
|
0, 0, NULL, on_mode_toggled, NULL);
|
|
g_signal_handlers_unblock_matched(light_btn, G_SIGNAL_MATCH_FUNC,
|
|
0, 0, NULL, on_mode_toggled, NULL);
|
|
gtk_box_append(GTK_BOX(mode_row), dark_btn);
|
|
gtk_box_append(GTK_BOX(mode_row), light_btn);
|
|
|
|
label = gtk_label_new(NULL);
|
|
i18n_bind(label, "theme", 0);
|
|
gtk_widget_set_halign(label, GTK_ALIGN_START);
|
|
gtk_widget_add_css_class(label, "settings-label");
|
|
gtk_widget_set_margin_top(label, 2);
|
|
gtk_box_append(GTK_BOX(box), label);
|
|
|
|
theme_grid = gtk_flow_box_new();
|
|
gtk_widget_add_css_class(theme_grid, "theme-grid");
|
|
gtk_flow_box_set_selection_mode(GTK_FLOW_BOX(theme_grid),
|
|
GTK_SELECTION_NONE);
|
|
gtk_flow_box_set_min_children_per_line(GTK_FLOW_BOX(theme_grid), 3);
|
|
gtk_flow_box_set_max_children_per_line(GTK_FLOW_BOX(theme_grid), 3);
|
|
gtk_flow_box_set_column_spacing(GTK_FLOW_BOX(theme_grid), 8);
|
|
gtk_flow_box_set_row_spacing(GTK_FLOW_BOX(theme_grid), 8);
|
|
gtk_flow_box_set_homogeneous(GTK_FLOW_BOX(theme_grid), TRUE);
|
|
gtk_widget_set_hexpand(theme_grid, TRUE);
|
|
gtk_box_append(GTK_BOX(box), theme_grid);
|
|
|
|
for (i = 0; i < THEME_COUNT; i++) {
|
|
GtkWidget *card = make_theme_card(
|
|
(ThemeId)i,
|
|
first_theme ? GTK_TOGGLE_BUTTON(first_theme) : NULL);
|
|
if (!first_theme)
|
|
first_theme = card;
|
|
if (app_theme_id == (ThemeId)i) {
|
|
g_signal_handlers_block_matched(card, G_SIGNAL_MATCH_FUNC,
|
|
0, 0, NULL, on_theme_toggled, NULL);
|
|
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(card), TRUE);
|
|
g_signal_handlers_unblock_matched(card, G_SIGNAL_MATCH_FUNC,
|
|
0, 0, NULL, on_theme_toggled, NULL);
|
|
}
|
|
gtk_flow_box_insert(GTK_FLOW_BOX(theme_grid), card, -1);
|
|
}
|
|
|
|
label = gtk_label_new(NULL);
|
|
i18n_bind(label, "language", 0);
|
|
gtk_widget_set_halign(label, GTK_ALIGN_START);
|
|
gtk_widget_add_css_class(label, "settings-label");
|
|
gtk_widget_set_margin_top(label, 4);
|
|
gtk_box_append(GTK_BOX(box), label);
|
|
|
|
lang_row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
|
|
gtk_widget_add_css_class(lang_row, "settings-seg");
|
|
gtk_box_append(GTK_BOX(box), lang_row);
|
|
|
|
cs_btn = make_lang_chip("Čeština", LANG_CS, NULL);
|
|
en_btn = make_lang_chip("English", LANG_EN, GTK_TOGGLE_BUTTON(cs_btn));
|
|
g_signal_handlers_block_matched(cs_btn, G_SIGNAL_MATCH_FUNC,
|
|
0, 0, NULL, on_lang_toggled, NULL);
|
|
g_signal_handlers_block_matched(en_btn, G_SIGNAL_MATCH_FUNC,
|
|
0, 0, NULL, on_lang_toggled, NULL);
|
|
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cs_btn),
|
|
app_lang == LANG_CS);
|
|
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(en_btn),
|
|
app_lang == LANG_EN);
|
|
g_signal_handlers_unblock_matched(cs_btn, G_SIGNAL_MATCH_FUNC,
|
|
0, 0, NULL, on_lang_toggled, NULL);
|
|
g_signal_handlers_unblock_matched(en_btn, G_SIGNAL_MATCH_FUNC,
|
|
0, 0, NULL, on_lang_toggled, NULL);
|
|
gtk_box_append(GTK_BOX(lang_row), cs_btn);
|
|
gtk_box_append(GTK_BOX(lang_row), en_btn);
|
|
|
|
label = gtk_label_new(NULL);
|
|
i18n_bind(label, "updates", 0);
|
|
gtk_widget_set_halign(label, GTK_ALIGN_START);
|
|
gtk_widget_add_css_class(label, "settings-label");
|
|
gtk_widget_set_margin_top(label, 4);
|
|
gtk_box_append(GTK_BOX(box), label);
|
|
gtk_box_append(GTK_BOX(box), build_update_box());
|
|
|
|
{
|
|
GtkWidget *sw = gtk_scrolled_window_new();
|
|
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
|
|
GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
|
|
/* Tall enough for every section, including updates at the bottom; GTK
|
|
* still shrinks the popover to fit the window and scrolls the rest. */
|
|
gtk_scrolled_window_set_max_content_height(GTK_SCROLLED_WINDOW(sw), 820);
|
|
gtk_scrolled_window_set_has_frame(GTK_SCROLLED_WINDOW(sw), FALSE);
|
|
gtk_scrolled_window_set_propagate_natural_width(GTK_SCROLLED_WINDOW(sw), TRUE);
|
|
gtk_scrolled_window_set_propagate_natural_height(GTK_SCROLLED_WINDOW(sw), TRUE);
|
|
gtk_widget_set_hexpand(sw, FALSE);
|
|
gtk_widget_set_vexpand(sw, FALSE);
|
|
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(sw), box);
|
|
gtk_popover_set_child(GTK_POPOVER(popover), sw);
|
|
}
|
|
|
|
return btn;
|
|
}
|