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 @@
|
||||
#[must_use]
|
||||
pub fn add(left: u64, right: u64) -> u64 {
|
||||
left + right
|
||||
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),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
/// Complete Popcorn configuration.
|
||||
/// Combines window, UI, percent text, and icon styling.
|
||||
#[derive(Debug, Deserialize, Default)]
|
||||
pub struct PopcornConfig {
|
||||
#[serde(default)]
|
||||
pub window: WindowConfig,
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let result = add(2, 2);
|
||||
assert_eq!(result, 4);
|
||||
#[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]
|
||||
pub fn load_or_default() -> Self {
|
||||
Self::load().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
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 popcorn_conf::PopcornConfig;
|
||||
|
||||
use i_slint_backend_winit::{
|
||||
Backend,
|
||||
winit::{
|
||||
@ -9,14 +11,9 @@ use i_slint_backend_winit::{
|
||||
use slint::{Color, LogicalPosition, LogicalSize, SharedString};
|
||||
slint::include_modules!();
|
||||
|
||||
// Helper function to simplify color values (A, R, G, B)
|
||||
fn argb_col(color_hex: u32) -> Color {
|
||||
Color::from_argb_encoded(color_hex)
|
||||
}
|
||||
|
||||
/// Convert an AARRGGBB hex string into a Slint Color.
|
||||
fn parse_hex_color(s: &str) -> Result<Color, String> {
|
||||
let clean = s.trim().trim_start_matches('#');
|
||||
|
||||
let raw = u32::from_str_radix(clean, 16).map_err(|_| "invalid hex color")?;
|
||||
|
||||
if clean.len() == 8 {
|
||||
@ -26,91 +23,77 @@ fn parse_hex_color(s: &str) -> Result<Color, String> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Set Slint properties from config data
|
||||
fn set_ui_props(ui: &OSDpopup, args: &OsdArgs) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Config values
|
||||
let popup_width = 200;
|
||||
let popup_height = 50;
|
||||
let popup_border_radius = 5;
|
||||
let popup_padding = 7;
|
||||
let window_position_x = 1146;
|
||||
let window_position_y = 35;
|
||||
|
||||
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)]
|
||||
/// Apply all UI properties to the Slint component.
|
||||
/// Uses the config for static styling and CLI args for dynamic values.
|
||||
#[allow(clippy::cast_precision_loss, clippy::cast_possible_wrap)]
|
||||
fn set_ui_props(
|
||||
ui: &OSDpopup,
|
||||
cfg: &PopcornConfig,
|
||||
args: &OsdArgs,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Window placement from config
|
||||
ui.window().set_position(LogicalPosition::new(
|
||||
window_position_x as f32,
|
||||
window_position_y as f32,
|
||||
cfg.window.pos_x 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(())
|
||||
}
|
||||
|
||||
/// 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>> {
|
||||
// Closure that adjusts winit WindowAttributes before Slint creates the window.
|
||||
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])
|
||||
};
|
||||
let cfg = PopcornConfig::load_or_default();
|
||||
|
||||
// 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()
|
||||
.with_window_attributes_hook(window_attrs) // Register the hook
|
||||
.build()?; // Construct backend
|
||||
|
||||
// Activate this customized backend for all Slint window creation and events.
|
||||
.with_window_attributes_hook(window_attrs)
|
||||
.build()?;
|
||||
slint::platform::set_platform(Box::new(backend))?;
|
||||
|
||||
// Create window
|
||||
// Build UI and apply all properties
|
||||
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()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user