yofi 0.2.0

minimalistic menu for wayland
use std::ffi::CString;
use std::path::PathBuf;

use defaults::Defaults;
use serde::Deserialize;

use crate::style::{Margin, Padding};
use crate::Color;

const DEFAULT_CONFIG_NAME: &str = concat!(crate::prog_name!(), ".config");

const DEFAULT_ICON_SIZE: u16 = 16;
const DEFAULT_FONT_SIZE: u16 = 24;

const DEFAULT_FONT_COLOR: Color = Color::from_rgba(0xf8, 0xf8, 0xf2, 0xff);
const DEFAULT_BG_COLOR: Color = Color::from_rgba(0x27, 0x28, 0x22, 0xee);
const DEFAULT_INPUT_BG_COLOR: Color = Color::from_rgba(0x75, 0x71, 0x5e, 0xc0);
const DEFAULT_SELECTED_FONT_COLOR: Color = Color::from_rgba(0xa6, 0xe2, 0x2e, 0xff);

mod params;

#[derive(Defaults, Deserialize)]
#[serde(default)]
pub struct Config {
    #[def = "400"]
    width: u32,
    #[def = "512"]
    height: u32,
    #[def = "false"]
    force_window: bool,
    window_offsets: Option<(i32, i32)>,
    scale: Option<u16>,
    term: Option<String>,
    font: Option<String>,
    font_size: Option<u16>,
    bg_color: Option<Color>,
    font_color: Option<Color>,

    icon: Option<Icon>,

    input_text: InputText,
    list_items: ListItems,
}

impl Config {
    pub fn disable_icons(&mut self) {
        self.icon = None;
    }

    pub fn set_prompt(&mut self, prompt: String) {
        self.input_text.prompt = Some(prompt);
    }

    pub fn set_password(&mut self) {
        self.input_text.password = true;
    }
}

#[derive(Defaults, Deserialize)]
#[serde(default)]
struct InputText {
    font: Option<String>,
    font_size: Option<u16>,
    bg_color: Option<Color>,
    font_color: Option<Color>,
    prompt_color: Option<Color>,
    prompt: Option<String>,
    password: bool,
    #[def = "Margin::all(5.0)"]
    margin: Margin,
    #[def = "Padding::from_pair(1.7, -4.0)"]
    padding: Padding,
}

#[derive(Defaults, Deserialize)]
#[serde(default)]
struct ListItems {
    font: Option<String>,
    font_size: Option<u16>,
    font_color: Option<Color>,
    selected_font_color: Option<Color>,
    match_color: Option<Color>,
    #[def = "Margin { top: 10.0, ..Margin::from_pair(5.0, 15.0) }"]
    margin: Margin,
    #[def = "false"]
    hide_actions: bool,
    #[def = "60.0"]
    action_left_margin: f32,
    #[def = "2.0"]
    item_spacing: f32,
    #[def = "10.0"]
    icon_spacing: f32,
}

#[derive(Defaults, Deserialize)]
#[serde(default)]
struct Icon {
    #[def = "DEFAULT_ICON_SIZE"]
    size: u16,
    theme: Option<String>,
    fallback_icon_path: Option<PathBuf>,
}

fn config_path() -> PathBuf {
    xdg::BaseDirectories::with_prefix(crate::prog_name!())
        .expect("failed to get xdg dirs")
        .place_config_file(DEFAULT_CONFIG_NAME)
        .expect("cannot create configuration directory")
}

impl Config {
    pub fn load(path: Option<PathBuf>) -> Self {
        std::fs::read_to_string(path.unwrap_or_else(config_path))
            .map(|config_content| {
                toml::from_str(&config_content).unwrap_or_else(|e| panic!("Invalid config: {}", e))
            })
            .unwrap_or_default()
    }

    pub fn param<T>(&self) -> T
    where
        T: for<'a> From<&'a Self>,
    {
        self.into()
    }

    pub fn terminal_command(&self) -> Vec<CString> {
        if let Some(cmd) = self.term.as_ref() {
            shlex::split(cmd)
                .unwrap()
                .into_iter()
                .map(|s| CString::new(s).unwrap())
                .collect::<Vec<_>>()
        } else {
            vec![]
        }
    }
}