feat: add configuration management and panel rendering

- Introduced a new `Config` struct for loading application configuration from a TOML file.
- Added support for keybindings and panel configurations, including status widgets.
- Implemented `PtyTerminal` for handling pseudo-terminal interactions.
- Created a rendering module to manage the display of application windows, panels, and dropdowns.
- Refactored the main application loop to utilize the new configuration and rendering logic.
- Removed legacy code related to event handling and rendering from `main.rs`.
This commit is contained in:
Björn Blomberg
2026-03-27 17:01:06 +01:00
parent d7c70baf35
commit 4440ad79bd
15 changed files with 2154 additions and 177 deletions

143
src/config.rs Normal file
View File

@@ -0,0 +1,143 @@
use serde::Deserialize;
use std::fs;
#[derive(Deserialize, Debug, Clone, Default)]
pub struct Config {
#[serde(default, rename = "panel")]
pub panels: Vec<PanelConfig>,
#[serde(default, rename = "keybind")]
pub keybinds: Vec<KeybindConfig>,
}
/// En tangentbords-genväg definierad i config
#[derive(Deserialize, Debug, Clone)]
pub struct KeybindConfig {
/// T.ex. "ctrl+space", "alt+f2", "ctrl+t"
pub key: String,
/// "global" = alltid, "wm" = bara när ingen terminal är fokuserad
#[serde(default)]
pub scope: KeybindScope,
pub action: MenuAction,
}
#[derive(Deserialize, Debug, Clone, Default, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum KeybindScope {
/// Aktiveras alltid, oavsett fokus
Global,
/// Aktiveras bara när ingen terminal-ruta är fokuserad
#[default]
Wm,
}
#[derive(Deserialize, Debug, Clone)]
pub struct PanelConfig {
pub position: PanelPosition,
#[serde(default, rename = "item")]
pub items: Vec<MenuItem>,
#[serde(default, rename = "status")]
pub status_widgets: Vec<StatusWidget>,
/// Länk till extern fil med panel-definition
pub file: Option<String>,
}
#[derive(Deserialize, Debug, Clone, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum PanelPosition {
Top,
Bottom,
Left,
Right,
}
#[derive(Deserialize, Debug, Clone)]
pub struct MenuItem {
pub label: String,
pub action: MenuAction,
}
#[derive(Deserialize, Debug, Clone)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum MenuAction {
Exit,
SpawnTerminal {
shell: Option<String>,
},
RunScript {
path: String,
},
RunProgram {
command: String,
#[serde(default)]
args: Vec<String>,
},
Submenu {
#[serde(default, rename = "item")]
items: Vec<MenuItem>,
},
/// Öppnar Tui-run kommandodialog
SpawnRunDialog,
}
/// En status-widget i panelen: kör ett kommando periodiskt och renderar output
#[derive(Deserialize, Debug, Clone)]
pub struct StatusWidget {
/// Shell-kommando som producerar en sträng (t.ex. `date '+%H:%M:%S'`)
pub command: String,
/// Hur ofta kommandot körs, i sekunder
#[serde(default = "default_interval")]
pub interval: u64,
/// Hur många tecken brett blocket är
pub width: u16,
/// Dockas mot höger eller vänster i panelen
#[serde(default)]
pub align: StatusAlign,
}
#[derive(Deserialize, Debug, Clone, Default, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum StatusAlign {
Left,
#[default]
Right,
}
fn default_interval() -> u64 {
10
}
impl Config {
pub fn load(path: &str) -> anyhow::Result<Config> {
let content = fs::read_to_string(path)?;
let mut config: Config = toml::from_str(&content)?;
for panel in &mut config.panels {
if let Some(file) = panel.file.clone() {
let c = fs::read_to_string(&file)
.map_err(|e| anyhow::anyhow!("Kunde inte läsa {}: {}", file, e))?;
let ext: ExternalPanelConfig = toml::from_str(&c)
.map_err(|e| anyhow::anyhow!("Parse-fel i {}: {}", file, e))?;
panel.items = ext.items;
panel.status_widgets = ext.status_widgets;
}
}
Ok(config)
}
pub fn load_or_default(path: &str) -> Config {
match Config::load(path) {
Ok(c) => c,
Err(e) => {
eprintln!("Varning: kunde inte ladda {}: {}", path, e);
Config::default()
}
}
}
}
#[derive(Deserialize)]
struct ExternalPanelConfig {
#[serde(default, rename = "item")]
items: Vec<MenuItem>,
#[serde(default, rename = "status")]
status_widgets: Vec<StatusWidget>,
}