Configuration crate
- Write config library crate - Add comments and default values - Update UI binary to add config loading
This commit is contained in:
parent
8343dc18f2
commit
77627207d8
46
crates/popcorn-conf/src/icon.rs
Normal file
46
crates/popcorn-conf/src/icon.rs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
/// Icon styling configuration.
|
||||||
|
/// Corresponds to `[icon]` in the config file.
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct IconConfig {
|
||||||
|
/// Icon size in pixels.
|
||||||
|
#[serde(default = "def_size")]
|
||||||
|
pub size: u32,
|
||||||
|
|
||||||
|
/// Icon color in AARRGGBB hex format.
|
||||||
|
#[serde(default = "def_color")]
|
||||||
|
pub color: String,
|
||||||
|
|
||||||
|
/// Icon font family name.
|
||||||
|
#[serde(default = "def_font")]
|
||||||
|
pub font: String,
|
||||||
|
|
||||||
|
/// Default glyph to use when no icon is supplied.
|
||||||
|
#[serde(default = "def_icon")]
|
||||||
|
pub default_icon: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn def_size() -> u32 {
|
||||||
|
35
|
||||||
|
}
|
||||||
|
fn def_color() -> String {
|
||||||
|
"ffffffff".into()
|
||||||
|
}
|
||||||
|
fn def_font() -> String {
|
||||||
|
"IosevkaTermSlab Nerd Font Mono".into()
|
||||||
|
}
|
||||||
|
fn def_icon() -> String {
|
||||||
|
"".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for IconConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
size: def_size(),
|
||||||
|
color: def_color(),
|
||||||
|
font: def_font(),
|
||||||
|
default_icon: def_icon(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,15 +1,66 @@
|
|||||||
|
mod icon;
|
||||||
|
mod percent_value;
|
||||||
|
mod ui;
|
||||||
|
mod window;
|
||||||
|
|
||||||
|
pub use icon::IconConfig;
|
||||||
|
pub use percent_value::PercentValueConfig;
|
||||||
|
pub use ui::UiConfig;
|
||||||
|
pub use window::WindowConfig;
|
||||||
|
|
||||||
|
use dirs::config_dir;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
/// Errors returned by Popcorn configuration loading.
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum ConfigError {
|
||||||
|
#[error("I/O error: {0}")]
|
||||||
|
Io(std::io::Error),
|
||||||
|
|
||||||
|
#[error("TOML parse error: {0}")]
|
||||||
|
Toml(toml::de::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Complete Popcorn configuration.
|
||||||
|
/// Combines window, UI, percent text, and icon styling.
|
||||||
|
#[derive(Debug, Deserialize, Default)]
|
||||||
|
pub struct PopcornConfig {
|
||||||
|
#[serde(default)]
|
||||||
|
pub window: WindowConfig,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
pub ui: UiConfig,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
pub percent_value: PercentValueConfig,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
pub icon: IconConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PopcornConfig {
|
||||||
|
/// Load the Popcorn configuration from:
|
||||||
|
/// `~/.config/candywidgets/popcorn/config.toml`
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// - `ConfigError::Io` if the file cannot be read
|
||||||
|
/// - `ConfigError::Toml` if the syntax is invalid
|
||||||
|
pub fn load() -> Result<Self, ConfigError> {
|
||||||
|
let path = config_dir()
|
||||||
|
.unwrap_or_else(|| PathBuf::from("."))
|
||||||
|
.join("candywidgets/popcorn/config.toml");
|
||||||
|
|
||||||
|
let text = std::fs::read_to_string(path).map_err(ConfigError::Io)?;
|
||||||
|
let cfg: PopcornConfig = toml::from_str(&text).map_err(ConfigError::Toml)?;
|
||||||
|
Ok(cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load configuration.\
|
||||||
|
/// If the file is missing or broken, fall back to built-in defaults.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn add(left: u64, right: u64) -> u64 {
|
pub fn load_or_default() -> Self {
|
||||||
left + right
|
Self::load().unwrap_or_default()
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn it_works() {
|
|
||||||
let result = add(2, 2);
|
|
||||||
assert_eq!(result, 4);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
38
crates/popcorn-conf/src/percent_value.rs
Normal file
38
crates/popcorn-conf/src/percent_value.rs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
/// Configuration for percentage text styling.
|
||||||
|
/// Corresponds to `[percent_value]`.
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct PercentValueConfig {
|
||||||
|
/// Font size in pixels.
|
||||||
|
#[serde(default = "def_font_size")]
|
||||||
|
pub font_size: u32,
|
||||||
|
|
||||||
|
/// Font color in AARRGGBB hex.
|
||||||
|
#[serde(default = "def_color")]
|
||||||
|
pub font_color: String,
|
||||||
|
|
||||||
|
/// Font family name.
|
||||||
|
#[serde(default = "def_font")]
|
||||||
|
pub font: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn def_font_size() -> u32 {
|
||||||
|
35
|
||||||
|
}
|
||||||
|
fn def_color() -> String {
|
||||||
|
"ffffffff".into()
|
||||||
|
}
|
||||||
|
fn def_font() -> String {
|
||||||
|
"Iosevka Extrabold".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PercentValueConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
font_size: def_font_size(),
|
||||||
|
font_color: def_color(),
|
||||||
|
font: def_font(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
63
crates/popcorn-conf/src/ui.rs
Normal file
63
crates/popcorn-conf/src/ui.rs
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
/// Configuration for UI geometry and base colors.
|
||||||
|
/// Corresponds to `[ui]`.
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct UiConfig {
|
||||||
|
/// Popup width in pixels.
|
||||||
|
#[serde(default = "def_width")]
|
||||||
|
pub width: u32,
|
||||||
|
|
||||||
|
/// Popup height in pixels.
|
||||||
|
#[serde(default = "def_height")]
|
||||||
|
pub height: u32,
|
||||||
|
|
||||||
|
/// Corner radius in pixels.
|
||||||
|
#[serde(default = "def_radius")]
|
||||||
|
pub border_radius: u32,
|
||||||
|
|
||||||
|
/// Horizontal padding around icon and text.
|
||||||
|
#[serde(default = "def_padding")]
|
||||||
|
pub padding: u32,
|
||||||
|
|
||||||
|
/// Background color in AARRGGBB hex format.
|
||||||
|
#[serde(default = "def_bg")]
|
||||||
|
pub bg_col: String,
|
||||||
|
|
||||||
|
/// Fallback fill color when none is provided via CLI.
|
||||||
|
#[serde(default = "def_fill")]
|
||||||
|
pub default_fill_color: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn def_width() -> u32 {
|
||||||
|
200
|
||||||
|
}
|
||||||
|
const fn def_height() -> u32 {
|
||||||
|
50
|
||||||
|
}
|
||||||
|
const fn def_radius() -> u32 {
|
||||||
|
5
|
||||||
|
}
|
||||||
|
const fn def_padding() -> u32 {
|
||||||
|
7
|
||||||
|
}
|
||||||
|
|
||||||
|
fn def_bg() -> String {
|
||||||
|
"675f5f5f".into()
|
||||||
|
}
|
||||||
|
fn def_fill() -> String {
|
||||||
|
"ff397979".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for UiConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
width: def_width(),
|
||||||
|
height: def_height(),
|
||||||
|
border_radius: def_radius(),
|
||||||
|
padding: def_padding(),
|
||||||
|
bg_col: def_bg(),
|
||||||
|
default_fill_color: def_fill(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
38
crates/popcorn-conf/src/window.rs
Normal file
38
crates/popcorn-conf/src/window.rs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
/// Window placement and timeout settings.
|
||||||
|
/// Corresponds to `[window]`.
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct WindowConfig {
|
||||||
|
/// Auto-hide timeout in milliseconds.
|
||||||
|
#[serde(default = "default_timeout")]
|
||||||
|
pub popup_timeout: u64,
|
||||||
|
|
||||||
|
/// X position of the popup.
|
||||||
|
#[serde(default = "default_pos_x")]
|
||||||
|
pub pos_x: i32,
|
||||||
|
|
||||||
|
/// Y position of the popup.
|
||||||
|
#[serde(default = "default_pos_y")]
|
||||||
|
pub pos_y: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn default_timeout() -> u64 {
|
||||||
|
1300
|
||||||
|
}
|
||||||
|
const fn default_pos_x() -> i32 {
|
||||||
|
1046
|
||||||
|
}
|
||||||
|
const fn default_pos_y() -> i32 {
|
||||||
|
36
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for WindowConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
popup_timeout: default_timeout(),
|
||||||
|
pos_x: default_pos_x(),
|
||||||
|
pos_y: default_pos_y(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,6 @@
|
|||||||
use crate::args::OsdArgs;
|
use crate::args::OsdArgs;
|
||||||
|
use popcorn_conf::PopcornConfig;
|
||||||
|
|
||||||
use i_slint_backend_winit::{
|
use i_slint_backend_winit::{
|
||||||
Backend,
|
Backend,
|
||||||
winit::{
|
winit::{
|
||||||
@ -9,14 +11,9 @@ use i_slint_backend_winit::{
|
|||||||
use slint::{Color, LogicalPosition, LogicalSize, SharedString};
|
use slint::{Color, LogicalPosition, LogicalSize, SharedString};
|
||||||
slint::include_modules!();
|
slint::include_modules!();
|
||||||
|
|
||||||
// Helper function to simplify color values (A, R, G, B)
|
/// Convert an AARRGGBB hex string into a Slint Color.
|
||||||
fn argb_col(color_hex: u32) -> Color {
|
|
||||||
Color::from_argb_encoded(color_hex)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_hex_color(s: &str) -> Result<Color, String> {
|
fn parse_hex_color(s: &str) -> Result<Color, String> {
|
||||||
let clean = s.trim().trim_start_matches('#');
|
let clean = s.trim().trim_start_matches('#');
|
||||||
|
|
||||||
let raw = u32::from_str_radix(clean, 16).map_err(|_| "invalid hex color")?;
|
let raw = u32::from_str_radix(clean, 16).map_err(|_| "invalid hex color")?;
|
||||||
|
|
||||||
if clean.len() == 8 {
|
if clean.len() == 8 {
|
||||||
@ -26,91 +23,77 @@ fn parse_hex_color(s: &str) -> Result<Color, String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set Slint properties from config data
|
/// Apply all UI properties to the Slint component.
|
||||||
fn set_ui_props(ui: &OSDpopup, args: &OsdArgs) -> Result<(), Box<dyn std::error::Error>> {
|
/// Uses the config for static styling and CLI args for dynamic values.
|
||||||
// Config values
|
#[allow(clippy::cast_precision_loss, clippy::cast_possible_wrap)]
|
||||||
let popup_width = 200;
|
fn set_ui_props(
|
||||||
let popup_height = 50;
|
ui: &OSDpopup,
|
||||||
let popup_border_radius = 5;
|
cfg: &PopcornConfig,
|
||||||
let popup_padding = 7;
|
args: &OsdArgs,
|
||||||
let window_position_x = 1146;
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let window_position_y = 35;
|
// Window placement from config
|
||||||
|
|
||||||
let osd_bg_color = argb_col(0x_67_5f_5f_5f);
|
|
||||||
let fill_color = if let Some(c) = args.color.as_deref() {
|
|
||||||
parse_hex_color(c)?
|
|
||||||
} else {
|
|
||||||
argb_col(0x_ff_12_7a_9b)
|
|
||||||
};
|
|
||||||
|
|
||||||
let value_font_size = 35;
|
|
||||||
let value_font_color = argb_col(0x_ff_ff_ff_ff);
|
|
||||||
let value_font = "Iosevka Extrabold";
|
|
||||||
let percent_value = i32::from(args.value.unwrap_or(0));
|
|
||||||
|
|
||||||
let icon_size = value_font_size;
|
|
||||||
let icon_font_color = value_font_color;
|
|
||||||
let icon_font = "IosevkaTermSlab Nerd Font Mono";
|
|
||||||
let icon_glyph = args.icon.as_deref().unwrap_or("");
|
|
||||||
|
|
||||||
// Config values -> Slint component properties
|
|
||||||
ui.set_popup_width(popup_width);
|
|
||||||
ui.set_popup_height(popup_height);
|
|
||||||
ui.set_popup_border_radius(popup_border_radius);
|
|
||||||
ui.set_popup_padding(popup_padding);
|
|
||||||
|
|
||||||
ui.set_osd_bg_color(osd_bg_color);
|
|
||||||
ui.set_fill_color(fill_color);
|
|
||||||
|
|
||||||
ui.set_value_font_size(value_font_size);
|
|
||||||
ui.set_value_font_color(value_font_color);
|
|
||||||
ui.set_value_font(SharedString::from(value_font));
|
|
||||||
ui.set_percent_value(percent_value);
|
|
||||||
|
|
||||||
ui.set_icon_size(icon_size);
|
|
||||||
ui.set_icon_font_color(icon_font_color);
|
|
||||||
ui.set_icon_font(SharedString::from(icon_font));
|
|
||||||
ui.set_icon_glyph(SharedString::from(icon_glyph));
|
|
||||||
|
|
||||||
// Window size (width, height): pixels
|
|
||||||
#[allow(clippy::cast_precision_loss)]
|
|
||||||
ui.window()
|
|
||||||
.set_size(LogicalSize::new(popup_width as f32, popup_height as f32));
|
|
||||||
|
|
||||||
// Window position (x,y): pixels
|
|
||||||
#[allow(clippy::cast_precision_loss)]
|
|
||||||
ui.window().set_position(LogicalPosition::new(
|
ui.window().set_position(LogicalPosition::new(
|
||||||
window_position_x as f32,
|
cfg.window.pos_x as f32,
|
||||||
window_position_y as f32,
|
cfg.window.pos_y as f32,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
// Window size from config
|
||||||
|
ui.window()
|
||||||
|
.set_size(LogicalSize::new(cfg.ui.width as f32, cfg.ui.height as f32));
|
||||||
|
|
||||||
|
// Container geometry
|
||||||
|
ui.set_popup_width(cfg.ui.width as i32);
|
||||||
|
ui.set_popup_height(cfg.ui.height as i32);
|
||||||
|
ui.set_popup_border_radius(cfg.ui.border_radius as i32);
|
||||||
|
ui.set_popup_padding(cfg.ui.padding as i32);
|
||||||
|
|
||||||
|
// Background color
|
||||||
|
ui.set_osd_bg_color(parse_hex_color(&cfg.ui.bg_col)?);
|
||||||
|
|
||||||
|
// Fill color: CLI overrides config fallback
|
||||||
|
let fill_color_hex = args.color.as_deref().unwrap_or(&cfg.ui.default_fill_color);
|
||||||
|
ui.set_fill_color(parse_hex_color(fill_color_hex)?);
|
||||||
|
|
||||||
|
// Percentage value styling
|
||||||
|
ui.set_value_font_size(cfg.percent_value.font_size as i32);
|
||||||
|
ui.set_value_font_color(parse_hex_color(&cfg.percent_value.font_color)?);
|
||||||
|
ui.set_value_font(SharedString::from(cfg.percent_value.font.as_str()));
|
||||||
|
ui.set_percent_value(i32::from(args.value.unwrap_or(0)));
|
||||||
|
|
||||||
|
// Icon styling
|
||||||
|
ui.set_icon_size(cfg.icon.size as i32);
|
||||||
|
ui.set_icon_font_color(parse_hex_color(&cfg.icon.color)?);
|
||||||
|
ui.set_icon_font(SharedString::from(cfg.icon.font.as_str()));
|
||||||
|
|
||||||
|
// Icon glyph: CLI overrides config fallback
|
||||||
|
let icon_glyph = args
|
||||||
|
.icon
|
||||||
|
.as_deref()
|
||||||
|
.unwrap_or(cfg.icon.default_icon.as_str());
|
||||||
|
|
||||||
|
ui.set_icon_glyph(SharedString::from(icon_glyph));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create and show the Window UI
|
/// Create and display the OSD popup window.
|
||||||
|
/// Loads config, applies X11 window attributes, sets UI props, and runs the Slint event loop.
|
||||||
pub fn show_popup(args: &OsdArgs) -> Result<(), Box<dyn std::error::Error>> {
|
pub fn show_popup(args: &OsdArgs) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
// Closure that adjusts winit WindowAttributes before Slint creates the window.
|
let cfg = PopcornConfig::load_or_default();
|
||||||
let window_attrs = |attrs: WindowAttributes| {
|
|
||||||
// Mark the X11 window as a Dock so the WM doesn't treat it as a normal window. Window type `Notification` didn't work. Fix later.
|
|
||||||
attrs.with_x11_window_type(vec![WindowType::Dock])
|
|
||||||
};
|
|
||||||
|
|
||||||
// Build a Slint backend that applies this attribute hook to all windows.
|
// Mark the window as a dock-type override window so WMs treat it like an OSD layer.
|
||||||
|
let window_attrs = |attrs: WindowAttributes| attrs.with_x11_window_type(vec![WindowType::Dock]);
|
||||||
|
|
||||||
|
// Build and activate backend with X11 window-attribute hook.
|
||||||
let backend = Backend::builder()
|
let backend = Backend::builder()
|
||||||
.with_window_attributes_hook(window_attrs) // Register the hook
|
.with_window_attributes_hook(window_attrs)
|
||||||
.build()?; // Construct backend
|
.build()?;
|
||||||
|
|
||||||
// Activate this customized backend for all Slint window creation and events.
|
|
||||||
slint::platform::set_platform(Box::new(backend))?;
|
slint::platform::set_platform(Box::new(backend))?;
|
||||||
|
|
||||||
// Create window
|
// Build UI and apply all properties
|
||||||
let ui = OSDpopup::new()?;
|
let ui = OSDpopup::new()?;
|
||||||
|
set_ui_props(&ui, &cfg, args)?;
|
||||||
|
|
||||||
// Send the data to the UI as properties
|
|
||||||
let _ = set_ui_props(&ui, args);
|
|
||||||
|
|
||||||
// Run the UI
|
|
||||||
ui.run()?;
|
ui.run()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user