566 lines
19 KiB
C++
566 lines
19 KiB
C++
/*
|
|
* settings.cpp
|
|
* MontaukOS Terminal - settings panel
|
|
*
|
|
* Copyright (c) 2026 Daniel Hammer
|
|
*/
|
|
|
|
#include "settings.hpp"
|
|
|
|
#include <montauk/config.h>
|
|
#include <montauk/heap.h>
|
|
#include <montauk/string.h>
|
|
#include <montauk/syscall.h>
|
|
#include <gui/gui.hpp>
|
|
#include <gui/canvas.hpp>
|
|
#include <gui/mtk.hpp>
|
|
#include <gui/standalone.hpp>
|
|
#include <gui/truetype.hpp>
|
|
|
|
extern "C" {
|
|
#include <stdio.h>
|
|
}
|
|
|
|
using namespace gui;
|
|
|
|
namespace termset {
|
|
|
|
// ==== Themes ====
|
|
|
|
struct ThemeEntry {
|
|
const char* id; // stable key written to the config file
|
|
const char* name; // shown in the panel
|
|
TermPalette palette;
|
|
};
|
|
|
|
// Palette order is the ANSI one: black, red, green, yellow, blue, magenta,
|
|
// cyan, white, then the eight bright variants.
|
|
static const ThemeEntry kThemes[] = {
|
|
{
|
|
"montauk-dark", "Montauk Dark",
|
|
TERM_PALETTE_DEFAULT
|
|
},
|
|
{
|
|
"pure-black", "Pure Black",
|
|
{
|
|
Color::from_hex(0x000000), Color::from_hex(0xFFFFFF),
|
|
Color::from_hex(0xFFFFFF),
|
|
{
|
|
Color::from_hex(0x000000), Color::from_hex(0xD00000),
|
|
Color::from_hex(0x00C000), Color::from_hex(0xC0C000),
|
|
Color::from_hex(0x4060E0), Color::from_hex(0xC000C0),
|
|
Color::from_hex(0x00C0C0), Color::from_hex(0xE5E5E5),
|
|
Color::from_hex(0x7F7F7F), Color::from_hex(0xFF4040),
|
|
Color::from_hex(0x40FF40), Color::from_hex(0xFFFF40),
|
|
Color::from_hex(0x6080FF), Color::from_hex(0xFF40FF),
|
|
Color::from_hex(0x40FFFF), Color::from_hex(0xFFFFFF),
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"solarized-dark", "Solarized Dark",
|
|
{
|
|
Color::from_hex(0x002B36), Color::from_hex(0x839496),
|
|
Color::from_hex(0x93A1A1),
|
|
{
|
|
Color::from_hex(0x073642), Color::from_hex(0xDC322F),
|
|
Color::from_hex(0x859900), Color::from_hex(0xB58900),
|
|
Color::from_hex(0x268BD2), Color::from_hex(0xD33682),
|
|
Color::from_hex(0x2AA198), Color::from_hex(0xEEE8D5),
|
|
Color::from_hex(0x586E75), Color::from_hex(0xCB4B16),
|
|
Color::from_hex(0x93A1A1), Color::from_hex(0x657B83),
|
|
Color::from_hex(0x839496), Color::from_hex(0x6C71C4),
|
|
Color::from_hex(0x93A1A1), Color::from_hex(0xFDF6E3),
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"solarized-light", "Solarized Light",
|
|
{
|
|
Color::from_hex(0xFDF6E3), Color::from_hex(0x657B83),
|
|
Color::from_hex(0x586E75),
|
|
{
|
|
Color::from_hex(0x073642), Color::from_hex(0xDC322F),
|
|
Color::from_hex(0x859900), Color::from_hex(0xB58900),
|
|
Color::from_hex(0x268BD2), Color::from_hex(0xD33682),
|
|
Color::from_hex(0x2AA198), Color::from_hex(0xEEE8D5),
|
|
Color::from_hex(0x586E75), Color::from_hex(0xCB4B16),
|
|
Color::from_hex(0x93A1A1), Color::from_hex(0x657B83),
|
|
Color::from_hex(0x839496), Color::from_hex(0x6C71C4),
|
|
Color::from_hex(0x93A1A1), Color::from_hex(0xFDF6E3),
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"gruvbox-dark", "Gruvbox Dark",
|
|
{
|
|
Color::from_hex(0x282828), Color::from_hex(0xEBDBB2),
|
|
Color::from_hex(0xEBDBB2),
|
|
{
|
|
Color::from_hex(0x282828), Color::from_hex(0xCC241D),
|
|
Color::from_hex(0x98971A), Color::from_hex(0xD79921),
|
|
Color::from_hex(0x458588), Color::from_hex(0xB16286),
|
|
Color::from_hex(0x689D6A), Color::from_hex(0xA89984),
|
|
Color::from_hex(0x928374), Color::from_hex(0xFB4934),
|
|
Color::from_hex(0xB8BB26), Color::from_hex(0xFABD2F),
|
|
Color::from_hex(0x83A598), Color::from_hex(0xD3869B),
|
|
Color::from_hex(0x8EC07C), Color::from_hex(0xEBDBB2),
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"nord", "Nord",
|
|
{
|
|
Color::from_hex(0x2E3440), Color::from_hex(0xD8DEE9),
|
|
Color::from_hex(0xD8DEE9),
|
|
{
|
|
Color::from_hex(0x3B4252), Color::from_hex(0xBF616A),
|
|
Color::from_hex(0xA3BE8C), Color::from_hex(0xEBCB8B),
|
|
Color::from_hex(0x81A1C1), Color::from_hex(0xB48EAD),
|
|
Color::from_hex(0x88C0D0), Color::from_hex(0xE5E9F0),
|
|
Color::from_hex(0x4C566A), Color::from_hex(0xD08770),
|
|
Color::from_hex(0xB9D4A0), Color::from_hex(0xF0D399),
|
|
Color::from_hex(0x8FA8CE), Color::from_hex(0xC3A0BB),
|
|
Color::from_hex(0x8FBCBB), Color::from_hex(0xECEFF4),
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"dracula", "Dracula",
|
|
{
|
|
Color::from_hex(0x282A36), Color::from_hex(0xF8F8F2),
|
|
Color::from_hex(0xF8F8F2),
|
|
{
|
|
Color::from_hex(0x21222C), Color::from_hex(0xFF5555),
|
|
Color::from_hex(0x50FA7B), Color::from_hex(0xF1FA8C),
|
|
Color::from_hex(0xBD93F9), Color::from_hex(0xFF79C6),
|
|
Color::from_hex(0x8BE9FD), Color::from_hex(0xF8F8F2),
|
|
Color::from_hex(0x6272A4), Color::from_hex(0xFF6E6E),
|
|
Color::from_hex(0x69FF94), Color::from_hex(0xFFFFA5),
|
|
Color::from_hex(0xD6ACFF), Color::from_hex(0xFF92DF),
|
|
Color::from_hex(0xA4FFFF), Color::from_hex(0xFFFFFF),
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"paper-light", "Paper Light",
|
|
{
|
|
Color::from_hex(0xFFFFFF), Color::from_hex(0x33333A),
|
|
Color::from_hex(0x33333A),
|
|
{
|
|
Color::from_hex(0x2E3436), Color::from_hex(0xC01C28),
|
|
Color::from_hex(0x26A269), Color::from_hex(0xA2734C),
|
|
Color::from_hex(0x12488B), Color::from_hex(0xA347BA),
|
|
Color::from_hex(0x2AA1B3), Color::from_hex(0x8B8E8F),
|
|
Color::from_hex(0x5E5C64), Color::from_hex(0xF66151),
|
|
Color::from_hex(0x33D17A), Color::from_hex(0xE9AD0C),
|
|
Color::from_hex(0x2A7BDE), Color::from_hex(0xC061CB),
|
|
Color::from_hex(0x33C7DE), Color::from_hex(0x3D3846),
|
|
}
|
|
}
|
|
},
|
|
};
|
|
|
|
static constexpr int kThemeCount = (int)(sizeof(kThemes) / sizeof(kThemes[0]));
|
|
|
|
static constexpr int FONT_SIZE_MIN = 8;
|
|
static constexpr int FONT_SIZE_DEFAULT = 18;
|
|
static constexpr int FONT_SIZE_MAX = 64;
|
|
static constexpr int FONT_SIZE_STEP = 2;
|
|
|
|
// ==== Panel state ====
|
|
|
|
static constexpr int PANEL_W = 380;
|
|
static constexpr int PANEL_H = 452;
|
|
|
|
static constexpr int PAD = 16;
|
|
static constexpr int CARD_PAD = 4;
|
|
static constexpr int ROW_H = 34;
|
|
static constexpr int ROW_GAP = 2;
|
|
static constexpr int STEP_BTN_W = 32;
|
|
static constexpr int SWATCH = 12;
|
|
static constexpr int SWATCH_GAP = 4;
|
|
static constexpr int SWATCH_N = 6;
|
|
|
|
struct Panel {
|
|
int win_id;
|
|
uint32_t* pixels;
|
|
int width;
|
|
int height;
|
|
bool open;
|
|
int mouse_x;
|
|
int mouse_y;
|
|
int theme_index;
|
|
Callbacks cb;
|
|
};
|
|
|
|
static Panel g_panel = {-1, nullptr, PANEL_W, PANEL_H, false, -1, -1, 0, {nullptr, nullptr}};
|
|
|
|
// The console session takes over the whole framebuffer with no desktop around
|
|
// it, so it defaults to the plain black palette rather than the windowed
|
|
// terminal's lighter grey.
|
|
static constexpr const char* kConsoleDefaultTheme = "pure-black";
|
|
|
|
static bool g_console_session = false;
|
|
|
|
// ==== Preferences ====
|
|
|
|
static void current_user(char* out, int cap) {
|
|
if (montauk::getuser(out, cap) <= 0 || !out[0])
|
|
montauk::strcpy(out, "default");
|
|
}
|
|
|
|
static int theme_index_by_id(const char* id) {
|
|
for (int i = 0; i < kThemeCount; i++) {
|
|
if (montauk::streq(kThemes[i].id, id)) return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int clamp_font_size(int size) {
|
|
if (size < FONT_SIZE_MIN) return FONT_SIZE_MIN;
|
|
if (size > FONT_SIZE_MAX) return FONT_SIZE_MAX;
|
|
return size;
|
|
}
|
|
|
|
static void save_prefs() {
|
|
char user[64];
|
|
current_user(user, sizeof(user));
|
|
|
|
montauk::toml::Doc doc = montauk::config::load_user(user, "terminal");
|
|
// The console session has no panel, so its theme is a default rather than a
|
|
// choice -- writing it back would let a zoom in the console silently retheme
|
|
// every windowed terminal. Leaving the key untouched round-trips whatever
|
|
// the user actually picked.
|
|
if (!g_console_session)
|
|
montauk::config::set_string(&doc, "appearance.theme",
|
|
kThemes[g_panel.theme_index].id);
|
|
montauk::config::set_int(&doc, "appearance.font_size", fonts::TERM_SIZE);
|
|
montauk::config::save_user(user, "terminal", &doc);
|
|
doc.destroy();
|
|
}
|
|
|
|
void load_prefs(bool console_session) {
|
|
g_console_session = console_session;
|
|
|
|
char user[64];
|
|
current_user(user, sizeof(user));
|
|
|
|
montauk::toml::Doc doc = montauk::config::load_user(user, "terminal");
|
|
int idx = theme_index_by_id(doc.get_string("appearance.theme", ""));
|
|
int size = (int)doc.get_int("appearance.font_size", fonts::TERM_SIZE);
|
|
doc.destroy();
|
|
|
|
// A saved theme is an explicit choice and wins in either mode; with none
|
|
// saved, the full-screen console starts on Pure Black and the windowed
|
|
// terminal on the default palette.
|
|
if (idx < 0) {
|
|
idx = console_session ? theme_index_by_id(kConsoleDefaultTheme) : 0;
|
|
if (idx < 0) idx = 0;
|
|
}
|
|
|
|
g_panel.theme_index = idx;
|
|
g_term_palette = kThemes[idx].palette;
|
|
fonts::TERM_SIZE = clamp_font_size(size);
|
|
}
|
|
|
|
// ==== Applying changes ====
|
|
|
|
static void select_theme(int idx) {
|
|
if (idx < 0 || idx >= kThemeCount || idx == g_panel.theme_index) return;
|
|
|
|
TermPalette from = g_term_palette;
|
|
g_panel.theme_index = idx;
|
|
g_term_palette = kThemes[idx].palette;
|
|
if (g_panel.cb.theme_changed)
|
|
g_panel.cb.theme_changed(from, g_term_palette);
|
|
save_prefs();
|
|
}
|
|
|
|
static void step_font_size(int delta) {
|
|
int size = clamp_font_size(fonts::TERM_SIZE + delta);
|
|
if (size == fonts::TERM_SIZE) return;
|
|
|
|
fonts::TERM_SIZE = size;
|
|
if (g_panel.cb.font_changed)
|
|
g_panel.cb.font_changed();
|
|
save_prefs();
|
|
render();
|
|
}
|
|
|
|
void zoom_font(int direction) {
|
|
step_font_size(direction * FONT_SIZE_STEP);
|
|
}
|
|
|
|
// ==== Layout ====
|
|
//
|
|
// Sections are a muted label over a bordered card, matching the rest of the
|
|
// system settings apps. The window titlebar already names the panel, so there
|
|
// is deliberately no heading inside it.
|
|
|
|
struct Layout {
|
|
Rect theme_card;
|
|
Rect theme_rows[kThemeCount];
|
|
Rect font_minus;
|
|
Rect font_plus;
|
|
Rect font_value;
|
|
Rect reset_btn;
|
|
Rect close_btn;
|
|
int theme_label_y;
|
|
int font_label_y;
|
|
};
|
|
|
|
static Layout compute_layout() {
|
|
Layout lo = {};
|
|
int fh = system_font_height();
|
|
int content_w = g_panel.width - PAD * 2;
|
|
int y = PAD;
|
|
|
|
lo.theme_label_y = y;
|
|
y += fh + 6;
|
|
|
|
lo.theme_card = {PAD, y, content_w,
|
|
kThemeCount * ROW_H + (kThemeCount - 1) * ROW_GAP + CARD_PAD * 2};
|
|
int row_y = y + CARD_PAD;
|
|
for (int i = 0; i < kThemeCount; i++) {
|
|
lo.theme_rows[i] = {lo.theme_card.x + CARD_PAD, row_y,
|
|
content_w - CARD_PAD * 2, ROW_H};
|
|
row_y += ROW_H + ROW_GAP;
|
|
}
|
|
y += lo.theme_card.h + 20;
|
|
|
|
// Font size is a single setting, so it reads as a plain row -- label left,
|
|
// stepper right-aligned to the same content edge as the card above --
|
|
// rather than a one-row card.
|
|
int ctrl_h = 28;
|
|
lo.font_label_y = y + (ctrl_h - fh) / 2;
|
|
lo.font_plus = {PAD + content_w - STEP_BTN_W, y, STEP_BTN_W, ctrl_h};
|
|
lo.font_value = {lo.font_plus.x - 6 - 56, y, 56, ctrl_h};
|
|
lo.font_minus = {lo.font_value.x - 6 - STEP_BTN_W, y, STEP_BTN_W, ctrl_h};
|
|
|
|
int btn_h = 30;
|
|
int btn_y = g_panel.height - PAD - btn_h;
|
|
lo.close_btn = {g_panel.width - PAD - 90, btn_y, 90, btn_h};
|
|
lo.reset_btn = {PAD, btn_y, 140, btn_h};
|
|
return lo;
|
|
}
|
|
|
|
// ==== Rendering ====
|
|
|
|
// Background, four representative ANSI colors and the foreground, drawn as a
|
|
// mini strip so each theme is identifiable without applying it. Every swatch
|
|
// gets a hairline border -- without it a white background swatch vanishes into
|
|
// the card on the light themes.
|
|
static void draw_theme_preview(Canvas& c, const TermPalette& p, int x, int y,
|
|
const mtk::Theme& theme) {
|
|
Color strip[SWATCH_N] = {
|
|
p.bg, p.ansi[1], p.ansi[2], p.ansi[4], p.ansi[5], p.fg
|
|
};
|
|
for (int i = 0; i < SWATCH_N; i++) {
|
|
int sx = x + i * (SWATCH + SWATCH_GAP);
|
|
mtk::draw_rounded_frame(c, {sx, y, SWATCH, SWATCH}, 3, strip[i],
|
|
mtk::mix(theme.border, strip[i], 80));
|
|
}
|
|
}
|
|
|
|
void render() {
|
|
if (!g_panel.open || g_panel.win_id < 0 || !g_panel.pixels) return;
|
|
|
|
Canvas c(g_panel.pixels, g_panel.width, g_panel.height);
|
|
mtk::Theme theme = mtk::make_theme();
|
|
c.fill(theme.window_bg);
|
|
|
|
Layout lo = compute_layout();
|
|
int fh = system_font_height();
|
|
|
|
c.text(PAD, lo.theme_label_y, "Theme", theme.text_subtle);
|
|
mtk::draw_rounded_frame(c, lo.theme_card, 6, theme.surface_alt, theme.border);
|
|
|
|
int preview_w = SWATCH_N * (SWATCH + SWATCH_GAP) - SWATCH_GAP;
|
|
for (int i = 0; i < kThemeCount; i++) {
|
|
const Rect& row = lo.theme_rows[i];
|
|
bool selected = (i == g_panel.theme_index);
|
|
bool hovered = row.contains(g_panel.mouse_x, g_panel.mouse_y);
|
|
|
|
if (selected)
|
|
mtk::draw_list_row(c, row, true, false, theme);
|
|
else if (hovered)
|
|
c.fill_rounded_rect(row.x, row.y, row.w, row.h, theme.radius_md,
|
|
theme.surface_hover);
|
|
|
|
c.text(row.x + 10, row.y + (row.h - fh) / 2, kThemes[i].name,
|
|
selected ? theme.text_inverse : theme.text);
|
|
draw_theme_preview(c, kThemes[i].palette,
|
|
row.x + row.w - 10 - preview_w,
|
|
row.y + (row.h - SWATCH) / 2, theme);
|
|
}
|
|
|
|
c.text(PAD, lo.font_label_y, "Font size", theme.text);
|
|
|
|
char value[16];
|
|
snprintf(value, sizeof(value), "%d px", fonts::TERM_SIZE);
|
|
int vw = text_width(value);
|
|
c.text(lo.font_value.x + (lo.font_value.w - vw) / 2,
|
|
lo.font_value.y + (lo.font_value.h - fh) / 2, value, theme.text);
|
|
|
|
bool can_shrink = fonts::TERM_SIZE > FONT_SIZE_MIN;
|
|
bool can_grow = fonts::TERM_SIZE < FONT_SIZE_MAX;
|
|
mtk::draw_button(c, lo.font_minus, "-", mtk::BUTTON_SECONDARY,
|
|
mtk::widget_state(false,
|
|
can_shrink && lo.font_minus.contains(
|
|
g_panel.mouse_x, g_panel.mouse_y),
|
|
can_shrink),
|
|
theme);
|
|
mtk::draw_button(c, lo.font_plus, "+", mtk::BUTTON_SECONDARY,
|
|
mtk::widget_state(false,
|
|
can_grow && lo.font_plus.contains(
|
|
g_panel.mouse_x, g_panel.mouse_y),
|
|
can_grow),
|
|
theme);
|
|
|
|
mtk::draw_button(c, lo.reset_btn, "Reset defaults", mtk::BUTTON_SECONDARY,
|
|
mtk::widget_state(false,
|
|
lo.reset_btn.contains(g_panel.mouse_x,
|
|
g_panel.mouse_y)),
|
|
theme);
|
|
mtk::draw_button(c, lo.close_btn, "Close", mtk::BUTTON_PRIMARY,
|
|
mtk::widget_state(false,
|
|
lo.close_btn.contains(g_panel.mouse_x,
|
|
g_panel.mouse_y)),
|
|
theme);
|
|
|
|
montauk::win_present(g_panel.win_id);
|
|
}
|
|
|
|
// ==== Input ====
|
|
|
|
static void reset_defaults() {
|
|
select_theme(0);
|
|
int target = FONT_SIZE_DEFAULT;
|
|
if (target != fonts::TERM_SIZE)
|
|
step_font_size(target - fonts::TERM_SIZE);
|
|
}
|
|
|
|
static void handle_mouse(const montauk::abi::WinEvent& ev) {
|
|
g_panel.mouse_x = ev.mouse.x;
|
|
g_panel.mouse_y = ev.mouse.y;
|
|
|
|
bool pressed = (ev.mouse.buttons & 1) && !(ev.mouse.prev_buttons & 1);
|
|
if (!pressed) return;
|
|
|
|
Layout lo = compute_layout();
|
|
|
|
for (int i = 0; i < kThemeCount; i++) {
|
|
if (lo.theme_rows[i].contains(g_panel.mouse_x, g_panel.mouse_y)) {
|
|
select_theme(i);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (lo.font_minus.contains(g_panel.mouse_x, g_panel.mouse_y)) {
|
|
step_font_size(-FONT_SIZE_STEP);
|
|
} else if (lo.font_plus.contains(g_panel.mouse_x, g_panel.mouse_y)) {
|
|
step_font_size(FONT_SIZE_STEP);
|
|
} else if (lo.reset_btn.contains(g_panel.mouse_x, g_panel.mouse_y)) {
|
|
reset_defaults();
|
|
} else if (lo.close_btn.contains(g_panel.mouse_x, g_panel.mouse_y)) {
|
|
close();
|
|
}
|
|
}
|
|
|
|
static void handle_key(const montauk::abi::KeyEvent& key) {
|
|
if (!key.pressed) return;
|
|
|
|
if (key.scancode == 0x01) { // Escape
|
|
close();
|
|
return;
|
|
}
|
|
if (key.ascii == '+' || key.ascii == '=') {
|
|
step_font_size(FONT_SIZE_STEP);
|
|
} else if (key.ascii == '-') {
|
|
step_font_size(-FONT_SIZE_STEP);
|
|
} else if (key.scancode == 0x48) { // Up
|
|
select_theme(g_panel.theme_index - 1);
|
|
} else if (key.scancode == 0x50) { // Down
|
|
select_theme(g_panel.theme_index + 1);
|
|
}
|
|
}
|
|
|
|
// ==== Lifecycle ====
|
|
|
|
void init(const Callbacks& cb) {
|
|
g_panel.cb = cb;
|
|
}
|
|
|
|
bool is_open() {
|
|
return g_panel.open;
|
|
}
|
|
|
|
void open() {
|
|
if (g_panel.open) {
|
|
render();
|
|
return;
|
|
}
|
|
|
|
montauk::abi::WinCreateResult wres;
|
|
if (montauk::win_create("Terminal Settings", PANEL_W, PANEL_H, &wres) < 0
|
|
|| wres.id < 0)
|
|
return;
|
|
|
|
g_panel.win_id = wres.id;
|
|
g_panel.pixels = (uint32_t*)(uintptr_t)wres.pixelVa;
|
|
g_panel.width = PANEL_W;
|
|
g_panel.height = PANEL_H;
|
|
g_panel.mouse_x = -1;
|
|
g_panel.mouse_y = -1;
|
|
g_panel.open = true;
|
|
render();
|
|
}
|
|
|
|
void close() {
|
|
if (!g_panel.open) return;
|
|
if (g_panel.win_id >= 0)
|
|
montauk::win_destroy(g_panel.win_id);
|
|
g_panel.win_id = -1;
|
|
g_panel.pixels = nullptr;
|
|
g_panel.open = false;
|
|
}
|
|
|
|
void poll() {
|
|
if (!g_panel.open || g_panel.win_id < 0) return;
|
|
|
|
bool redraw = false;
|
|
montauk::abi::WinEvent ev;
|
|
int r;
|
|
while ((r = montauk::win_poll(g_panel.win_id, &ev)) > 0) {
|
|
if (ev.type == 3) {
|
|
close();
|
|
return;
|
|
}
|
|
|
|
if (ev.type == 0) {
|
|
handle_key(ev.key);
|
|
} else if (ev.type == 1) {
|
|
handle_mouse(ev);
|
|
} else if (ev.type == 2) {
|
|
g_panel.width = ev.resize.w;
|
|
g_panel.height = ev.resize.h;
|
|
g_panel.pixels = (uint32_t*)(uintptr_t)montauk::win_resize(
|
|
g_panel.win_id, ev.resize.w, ev.resize.h);
|
|
}
|
|
redraw = true;
|
|
|
|
if (!g_panel.open) return;
|
|
}
|
|
|
|
if (r < 0) {
|
|
close();
|
|
return;
|
|
}
|
|
|
|
if (redraw)
|
|
render();
|
|
}
|
|
|
|
} // namespace termset
|