yofi 0.2.1

minimalistic menu for wayland
Documentation
use std::cell::RefCell;
use std::collections::HashMap;
use std::path::Path;
use std::rc::Rc;

use once_cell::unsync::Lazy;

use super::*;
use crate::desktop::IconConfig;
use crate::draw::{BgParams, InputTextParams, ListParams};
use crate::font::{Font, FontBackend, InnerFont};
use crate::icon::Icon;
use crate::window::Params as WindowParams;

macro_rules! select_conf {
    ($config:ident, $inner:ident, $field:ident) => {
        $config
            .$inner
            .$field
            .as_ref()
            .or_else(|| $config.$field.as_ref())
    };
}

impl<'a> From<&'a Config> for InputTextParams<'a> {
    fn from(config: &'a Config) -> InputTextParams<'a> {
        let font_color = select_conf!(config, input_text, font_color)
            .copied()
            .unwrap_or(DEFAULT_FONT_COLOR);

        InputTextParams {
            font: select_conf!(config, input_text, font)
                .map(font_by_name)
                .unwrap_or_else(default_font),
            font_size: select_conf!(config, input_text, font_size)
                .copied()
                .unwrap_or(DEFAULT_FONT_SIZE),
            bg_color: select_conf!(config, input_text, bg_color)
                .copied()
                .unwrap_or(DEFAULT_INPUT_BG_COLOR),
            font_color,
            prompt_color: config.input_text.prompt_color.unwrap_or_else(|| {
                let [r, g, b, a] = font_color.to_rgba();
                Color::from_rgba(r, g, b, (a / 4).wrapping_mul(3))
            }),
            prompt: config.input_text.prompt.as_deref(),
            password: config.input_text.password,
            margin: config.input_text.margin.clone(),
            padding: config.input_text.padding.clone(),
            radius: config.input_text.corner_radius.clone(),
        }
    }
}

impl<'a> From<&'a Config> for ListParams {
    fn from(config: &'a Config) -> ListParams {
        ListParams {
            font: select_conf!(config, list_items, font)
                .map(font_by_name)
                .unwrap_or_else(default_font),
            font_size: select_conf!(config, list_items, font_size)
                .copied()
                .unwrap_or(DEFAULT_FONT_SIZE),
            font_color: select_conf!(config, list_items, font_color)
                .copied()
                .unwrap_or(DEFAULT_FONT_COLOR),
            selected_font_color: config
                .list_items
                .selected_font_color
                .unwrap_or(DEFAULT_SELECTED_FONT_COLOR),
            match_color: config.list_items.match_color,
            icon_size: config.icon.as_ref().map(|c| c.size),
            fallback_icon: config
                .icon
                .as_ref()
                .and_then(|i| i.fallback_icon_path.as_ref())
                .map(Icon::new),
            margin: config.list_items.margin.clone(),
            hide_actions: config.list_items.hide_actions,
            action_left_margin: config.list_items.action_left_margin,
            item_spacing: config.list_items.item_spacing,
            icon_spacing: config.list_items.icon_spacing,
        }
    }
}

impl<'a> From<&'a Config> for BgParams {
    fn from(config: &'a Config) -> BgParams {
        let border = match (config.bg_border_color, config.bg_border_width) {
            (None, None) => None,
            (Some(c), Some(w)) => Some((c, w)),
            (Some(c), None) => Some((c, DEFAULT_BG_BORDER_WIDTH)),
            (None, Some(w)) => Some((DEFAULT_BG_BORDER_COLOR, w)),
        };
        BgParams {
            width: config.width,
            height: config.height,
            radius: config.corner_radius.clone(),
            color: config.bg_color.unwrap_or(DEFAULT_BG_COLOR),
            border,
        }
    }
}

impl<'a> From<&'a Config> for WindowParams {
    fn from(config: &'a Config) -> WindowParams {
        WindowParams {
            width: config.width,
            height: config.height,
            force_window: config.force_window,
            window_offsets: config.window_offsets,
            scale: config.scale,
        }
    }
}

impl<'a> From<&'a Config> for Option<IconConfig> {
    fn from(config: &'a Config) -> Option<IconConfig> {
        config.icon.as_ref().map(|c| IconConfig {
            icon_size: c.size,
            theme: c.theme.clone(),
        })
    }
}

fn default_font() -> Font {
    std::thread_local! {
        static DEFAULT_FONT: Lazy<Font> = Lazy::new(|| Rc::new(InnerFont::default()));
    }
    DEFAULT_FONT.with(|f| Rc::clone(f))
}

fn font_by_name(name: impl AsRef<str>) -> Font {
    std::thread_local! {
        static LOADED_FONTS: RefCell<HashMap<String, Font>> = RefCell::new(HashMap::new());
    }

    let name = name.as_ref();

    if let Some(font) = LOADED_FONTS.with(|fonts| fonts.borrow().get(name).cloned()) {
        return font;
    }

    let path = Path::new(name);
    let font = if path.is_absolute() && path.exists() {
        InnerFont::font_by_path(path)
    } else {
        InnerFont::font_by_name(name)
    };
    let font = Rc::new(font.unwrap_or_else(|e| panic!("cannot find font {}: {}", name, e)));
    LOADED_FONTS.with(|fonts| fonts.borrow_mut().insert(name.to_owned(), font.clone()));
    font
}