UI resizing tweaks

This commit is contained in:
dvorakmortonxd 2026-09-11 21:50:54 +02:00
commit a6094bc780
5 changed files with 111 additions and 17 deletions

View file

@ -990,7 +990,7 @@ const TypedQ trans_p10[] = {
"Těší mě."},
{"gut", "dobrý|dobře|good", "dobrý"},
{"Hallo!", "ahoj|hello", "Ahoj!"},
{"heißen (Infinitiv), er heißt",
{"heißen (Infinitiv)",
"jmenovat se|jmenuje se|to be called|to be named", "jmenovat se"},
{"Ich heiße Jens.",
"jmenuji se jens|jmenuju se jens|my name is jens", "Jmenuji se Jens."},
@ -1022,7 +1022,7 @@ const TypedQ trans_p11[] = {
"poblíž, v blízkosti"},
{"das Spiel, -e", "hra|game", "hra"},
{"wo", "kde|where", "kde"},
{"wohnen (Infinitiv), er wohnt", "bydlet|bydlí|to live", "bydlet"},
{"wohnen (Infinitiv)", "bydlet|bydlí|to live", "bydlet"},
{"Wo wohnst du?",
"kde bydlíš|kde bydlíte|where do you live", "Kde bydlíš?"},
{"Ich wohne in Prag.", "bydlím v praze|i live in prague", "Bydlím v Praze."},
@ -1063,7 +1063,7 @@ const TypedQ trans_p13[] = {
{"auch", "také|taky|also|too", "také"},
{"aus", "z|ze|from", "z"},
{"die Band, -s", "skupina|kapela|band", "(hudební) skupina"},
{"chatten (Infinitiv), er chattet", "chatovat|chatuje|to chat", "chatovat"},
{"chatten (Infinitiv)", "chatovat|chatuje|to chat", "chatovat"},
{"das Computerspiel, -e", "počítačová hra|computer game", "počítačová hra"},
{"die/das E-Mail, -s", "e-mail|email", "e-mail"},
{"das Englisch", "angličtina|anglicky|english", "angličtina, anglicky"},
@ -1077,7 +1077,7 @@ const TypedQ trans_p13[] = {
{"der Fußball", "fotbal|football|soccer", "fotbal"},
{"Er ist Fußballfanatiker.", "je fotbalový fanatik|he is a football fanatic",
"Je fotbalový fanatik."},
{"gehen (Infinitiv), er geht", "jít|jde|chodit|to go", "jít"},
{"gehen (Infinitiv)", "jít|jde|chodit|to go", "jít"},
{"Ich gehe gern auf Konzerte.",
"rád chodím na koncerty|ráda chodím na koncerty|i like going to concerts",
"Rád(a) chodím na koncerty."},
@ -1085,34 +1085,34 @@ const TypedQ trans_p13[] = {
{"die Gitarre, -n", "kytara|guitar", "kytara"},
{"das Handy, -s", "mobil|mobilní telefon|mobile phone", "mobil"},
{"das Hobby, -s", "koníček|hobby", "koníček"},
{"hören (Infinitiv), er hört", "poslouchat|poslouchá|slyšet|to listen|to hear",
{"hören (Infinitiv)", "poslouchat|poslouchá|slyšet|to listen|to hear",
"poslouchat"},
{"Er hört gern Musik.", "rád poslouchá hudbu|he likes listening to music",
"Rád poslouchá hudbu."},
{"im Internet surfen (Infinitiv), er surft im Internet",
{"im Internet surfen (Infinitiv)",
"surfovat na internetu|to surf the internet", "surfovat na internetu"},
{"immer", "pořád|neustále|vždy|vždycky|always", "pořád, neustále"},
{"das Jahr, -e", "rok|year", "rok"},
{"kein/keine/kein", "žádný|žádná|žádné|no|none", "žádný/žádná/žádné"},
{"das Klavier, -e", "klavír|piano", "klavír"},
{"kommen (Infinitiv, aus), er kommt",
{"kommen (Infinitiv, aus)",
"přicházet|přijít|přichází|pochází|to come|to come from",
"přicházet, pocházet (z)"},
{"Er kommt aus Wien.",
"pochází z vídně|he comes from vienna|he is from vienna",
"Pochází z Vídně."},
{"das Konzert, -e", "koncert|concert", "koncert"},
{"lernen (Infinitiv), er lernt", "učit se|učí se|to learn", "učit se"},
{"liegen (Infinitiv), er liegt", "ležet|leží|to lie|to be located", "ležet"},
{"lernen (Infinitiv)", "učit se|učí se|to learn", "učit se"},
{"liegen (Infinitiv)", "ležet|leží|to lie|to be located", "ležet"},
{"der Nachname, -n", "příjmení|surname|last name", "příjmení"},
{"nicht weit von", "nedaleko od|not far from", "nedaleko od"},
{"reisen (Infinitiv), er reist", "cestovat|cestuje|to travel", "cestovat"},
{"schreiben (Infinitiv), er schreibt", "psát|píše|to write", "psát"},
{"reisen (Infinitiv)", "cestovat|cestuje|to travel", "cestovat"},
{"schreiben (Infinitiv)", "psát|píše|to write", "psát"},
{"die Schule, -n", "škola|school", "škola"},
{"spielen (Infinitiv), er spielt", "hrát|hraje|to play", "hrát"},
{"spielen (Infinitiv)", "hrát|hraje|to play", "hrát"},
{"Sie spielt Gitarre.", "hraje na kytaru|she plays the guitar",
"Hraje na kytaru."},
{"tanzen (Infinitiv), er tanzt", "tancovat|tančí|tancuje|to dance", "tancovat"},
{"tanzen (Infinitiv)", "tancovat|tančí|tancuje|to dance", "tancovat"},
{"der Vorname, -n", "křestní jméno|first name", "křestní jméno"},
{"der Wohnort, -e", "bydliště|místo bydliště|place of residence",
"bydliště"},
@ -1121,13 +1121,13 @@ const TypedQ trans_p13[] = {
const TypedQ trans_p14[] = {
{"falsch", "nesprávně|chybně|špatně|wrong|incorrect", "nesprávně, chybně"},
{"die Flöte, -n", "flétna|flute", "flétna"},
{"haben (Infinitiv), er hat", "mít|má|to have", "mít"},
{"haben (Infinitiv)", "mít|má|to have", "mít"},
{"das Instrument, -e", "nástroj|instrument", "(hudební) nástroj"},
{"jetzt", "teď|nyní|now", "teď"},
{"machen (Infinitiv), er macht", "dělat|dělá|to do|to make", "dělat"},
{"machen (Infinitiv)", "dělat|dělá|to do|to make", "dělat"},
{"richtig", "správně|správný|correct|right", "správně"},
{"der Sport", "sport", "sport"},
{"Sport treiben (Infinitiv), er treibt Sport",
{"Sport treiben (Infinitiv)",
"sportovat|sportuje|provozovat sport|to do sport", "sportovat"},
{"das Squash", "squash", "squash"},
{"das/der Yoga", "jóga|yoga", "jóga"},

View file

@ -16,6 +16,8 @@ UiLang app_lang = LANG_CS;
ThemePalette app_theme;
double app_ui_scale = 1.0;
const char *theme_names[THEME_COUNT] = {
"Catppuccin",
"Nord",

43
main.c
View file

@ -126,6 +126,47 @@ GtkWidget *build_exercise_page(UnitCtx *u, int n) {
}
}
static int ui_scale_last_w = -1;
static guint ui_scale_timer = 0;
static int ui_scale_pending_w = 0;
static gboolean ui_scale_apply(gpointer data) {
double scale;
(void)data;
ui_scale_timer = 0;
scale = 1.0 + (ui_scale_pending_w - 760) / 1900.0;
if (scale < 1.0)
scale = 1.0;
if (scale > 1.5)
scale = 1.5;
scale = (double)((int)(scale * 20.0 + 0.5)) / 20.0;
if (scale != app_ui_scale) {
app_ui_scale = scale;
apply_theme();
}
return G_SOURCE_REMOVE;
}
/* Grow the interface's text as the window gets wider, so it stays comfortable
* when maximised on a large or high-DPI display. Debounced to avoid reloading
* the stylesheet on every frame while dragging. */
static gboolean ui_scale_tick(GtkWidget *w, GdkFrameClock *clock,
gpointer data) {
int width = gtk_widget_get_allocated_width(w);
(void)clock;
(void)data;
if (width < 200 || width == ui_scale_last_w)
return G_SOURCE_CONTINUE;
ui_scale_last_w = width;
ui_scale_pending_w = width;
if (ui_scale_timer)
g_source_remove(ui_scale_timer);
ui_scale_timer = g_timeout_add(120, ui_scale_apply, NULL);
return G_SOURCE_CONTINUE;
}
void activate(GtkApplication *app, gpointer user_data) {
GtkWindow *window;
GtkWidget *headerbar;
@ -243,6 +284,8 @@ void activate(GtkApplication *app, gpointer user_data) {
apply_theme();
apply_language();
gtk_widget_add_tick_callback(GTK_WIDGET(window), ui_scale_tick, NULL, NULL);
gtk_window_present(window);
}

View file

@ -524,6 +524,7 @@ extern ThemeId app_theme_id;
extern ColorMode app_color_mode;
extern UiLang app_lang;
extern ThemePalette app_theme;
extern double app_ui_scale;
extern const char *theme_names[THEME_COUNT];
extern GtkWidget *theme_swatch_areas[THEME_COUNT];
extern UnitCtx units[NUM_UNITS];

50
theme.c
View file

@ -188,6 +188,42 @@ char *read_css_file(void) {
return contents;
}
typedef struct {
double scale;
} FontScale;
static gboolean scale_font_size_eval(const GMatchInfo *info, GString *result,
gpointer user_data) {
const FontScale *fs = user_data;
char *space = g_match_info_fetch(info, 1);
char *num = g_match_info_fetch(info, 2);
int px = (int)g_ascii_strtoll(num, NULL, 10);
int scaled = (int)(px * fs->scale + 0.5);
g_string_append_printf(result, "font-size:%s%dpx", space, scaled);
g_free(space);
g_free(num);
return FALSE;
}
/* Scale every explicit `font-size: Npx` declaration so the interface grows on
* large (e.g. maximised / high-DPI) windows. Layout metrics stay untouched. */
static char *scale_font_sizes(const char *css, double scale) {
GRegex *re;
char *out;
FontScale fs;
if (scale <= 1.0001)
return g_strdup(css);
fs.scale = scale;
re = g_regex_new("font-size:([ \t]*)([0-9]+)px", 0, 0, NULL);
out = g_regex_replace_eval(re, css, -1, 0, 0,
scale_font_size_eval, &fs, NULL);
g_regex_unref(re);
return out;
}
/* The palette is data-driven, so its @define-color bindings are generated
* here and prepended to the rules loaded from the external stylesheet. */
char *build_theme_css(const ThemePalette *p) {
@ -217,10 +253,22 @@ char *build_theme_css(const ThemePalette *p) {
p->success, p->success2, p->warning, p->error, p->on_accent,
p->node, p->locked_bg, p->locked_border);
char *rules = read_css_file();
char *css = g_strconcat(palette, rules, NULL);
char *scaled = scale_font_sizes(rules, app_ui_scale);
char *base;
char *css;
if (app_ui_scale > 1.0001)
base = g_strdup_printf("window { font-size: %dpx; }\n",
(int)(14.0 * app_ui_scale + 0.5));
else
base = g_strdup("");
css = g_strconcat(palette, base, scaled, NULL);
g_free(palette);
g_free(rules);
g_free(scaled);
g_free(base);
return css;
}