oo-ide 0.0.4

∞ is a terminal IDE focused on low distraction, high usability.
Documentation
//! Accessibility rendering helpers.
//!
//! Provides utilities for:
//! - Cursor style rendering (block, underline, beam)
//! - High contrast mode detection
//! - Motion reduction settings
//! - Accessibility-friendly styling

use ratatui::style::Modifier;
use ratatui::style::Style;
use ratatui::symbols::cursor;

use crate::settings::Settings;
use crate::settings::adapters::editor;

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum CursorStyle {
    Block,
    Underline,
    Beam,
}

impl CursorStyle {
    pub fn from_str(s: &str) -> Self {
        match s.to_lowercase().as_str() {
            "underline" => Self::Underline,
            "beam" => Self::Beam,
            _ => Self::Block,
        }
    }

    pub fn to_ratatui_style(&self) -> Style {
        match self {
            Self::Block => Style::default().add_modifier(Modifier::Reverse),
            Self::Underline => Style::default().add_modifier(Modifier::Underlined),
            Self::Beam => Style::default().add_modifier(Modifier::RapidBlink),
        }
    }

    pub fn to_symbol(&self) -> &str {
        match self {
            Self::Block => cursor::BLOCK,
            Self::Underline => cursor::UNDERLINE,
            Self::Beam => cursor::BLANK,
        }
    }
}

pub fn get_cursor_style(settings: &Settings) -> CursorStyle {
    let style_str = editor::cursor_style(settings);
    CursorStyle::from_str(style_str)
}

pub fn should_cursor_blink(settings: &Settings) -> bool {
    *editor::cursor_blink(settings)
}

pub fn should_reduce_motion(settings: &Settings) -> bool {
    if *editor::reduced_motion(settings) {
        return true;
    }

    if let Ok(env) = std::env::var("REDUCED_MOTION") {
        return env == "1" || env.to_lowercase() == "true";
    }

    false
}

pub fn is_high_contrast_mode(settings: &Settings) -> bool {
    *editor::high_contrast(settings)
}

pub fn should_dim_non_focused(settings: &Settings) -> bool {
    *editor::dim_non_focused(settings)
}

pub fn get_line_spacing(settings: &Settings) -> f64 {
    *editor::line_spacing(settings)
}

pub struct AccessibilityColors {
    pub cursor_fg: ratatui::style::Color,
    pub cursor_bg: ratatui::style::Color,
    pub selection_bg: ratatui::style::Color,
    pub selection_fg: ratatui::style::Color,
    pub search_bg: ratatui::style::Color,
    pub search_current_bg: ratatui::style::Color,
}

impl AccessibilityColors {
    pub fn standard(
        selection_bg: ratatui::style::Color,
        selection_fg: ratatui::style::Color,
    ) -> Self {
        Self {
            cursor_fg: ratatui::style::Color::Reset,
            cursor_bg: ratatui::style::Color::LightYellow,
            selection_bg,
            selection_fg,
            search_bg: ratatui::style::Color::Rgb(100, 80, 0),
            search_current_bg: ratatui::style::Color::Rgb(230, 160, 0),
        }
    }

    pub fn high_contrast() -> Self {
        Self {
            cursor_fg: ratatui::style::Color::Black,
            cursor_bg: ratatui::style::Color::Yellow,
            selection_bg: ratatui::style::Color::Black,
            selection_fg: ratatui::style::Color::White,
            search_bg: ratatui::style::Color::Cyan,
            search_current_bg: ratatui::style::Color::Green,
        }
    }
}

pub fn get_accessibility_colors(
    settings: &Settings,
    theme: &crate::theme::Theme,
) -> AccessibilityColors {
    if is_high_contrast_mode(settings) {
        AccessibilityColors::high_contrast()
    } else {
        AccessibilityColors::standard(theme.selection_bg(), theme.selection_fg())
    }
}

pub fn apply_accessibility_modifiers(
    style: Style,
    settings: &Settings,
) -> Style {
    let mut result = style;

    if should_reduce_motion(settings) {
        result = result.remove_modifier(Modifier::Blinking);
        result = result.remove_modifier(Modifier::RapidBlink);
    }

    result
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn cursor_style_from_str() {
        assert_eq!(CursorStyle::from_str("block"), CursorStyle::Block);
        assert_eq!(CursorStyle::from_str("underline"), CursorStyle::Underline);
        assert_eq!(CursorStyle::from_str("beam"), CursorStyle::Beam);
        assert_eq!(CursorStyle::from_str("unknown"), CursorStyle::Block);
    }

    #[test]
    fn accessibility_colors() {
        let colors = AccessibilityColors::high_contrast();
        assert_eq!(colors.cursor_fg, ratatui::style::Color::Black);
        assert_eq!(colors.cursor_bg, ratatui::style::Color::Yellow);
    }
}