revue 2.71.1

A Vue-style TUI framework for Rust with CSS styling
Documentation
//! Zen Mode widget for distraction-free content viewing
//!
//! Provides a fullscreen overlay that hides all other UI elements,
//! focusing only on the wrapped content with optional padding and styling.
//!
//! Inspired by eilmeldung's ArticleContentDistractionFree mode.

use crate::render::Cell;
use crate::style::Color;
use crate::widget::traits::{RenderContext, View, WidgetProps};
use crate::{impl_props_builders, impl_styled_view};

/// Zen Mode wrapper for distraction-free content display
///
/// Wraps any View and provides a toggle between normal and fullscreen modes.
/// In zen mode, the content fills the entire screen with configurable padding.
///
/// # Example
///
/// ```rust,ignore
/// use revue::widget::{zen, text, Text};
///
/// let content = Text::new("Focus on this content...");
/// let mut zen_view = zen(content)
///     .padding(4)
///     .bg(Color::rgb(20, 20, 30));
///
/// // Toggle zen mode
/// zen_view.toggle();
///
/// // Check state
/// if zen_view.is_enabled() {
///     // Renders fullscreen
/// }
/// ```
pub struct ZenMode {
    /// Child content view
    content: Box<dyn View>,
    /// Whether zen mode is active
    enabled: bool,
    /// Horizontal padding in zen mode
    padding_x: u16,
    /// Vertical padding in zen mode
    padding_y: u16,
    /// Background color in zen mode
    bg_color: Color,
    /// Optional overlay opacity (0.0-1.0) for dimming
    dim_opacity: f32,
    /// Whether to center content vertically
    center_vertical: bool,
    /// Widget properties
    props: WidgetProps,
}

impl ZenMode {
    /// Create a new zen mode wrapper
    pub fn new(content: impl View + 'static) -> Self {
        Self {
            content: Box::new(content),
            enabled: false,
            padding_x: 4,
            padding_y: 2,
            bg_color: Color::rgb(15, 15, 25),
            dim_opacity: 0.0,
            center_vertical: false,
            props: WidgetProps::new(),
        }
    }

    /// Set horizontal and vertical padding
    pub fn padding(mut self, padding: u16) -> Self {
        self.padding_x = padding;
        self.padding_y = padding;
        self
    }

    /// Set horizontal padding
    pub fn padding_x(mut self, padding: u16) -> Self {
        self.padding_x = padding;
        self
    }

    /// Set vertical padding
    pub fn padding_y(mut self, padding: u16) -> Self {
        self.padding_y = padding;
        self
    }

    /// Set background color
    pub fn bg(mut self, color: Color) -> Self {
        self.bg_color = color;
        self
    }

    /// Set dim opacity for transition effect (0.0 = no dim, 1.0 = full dim)
    pub fn dim(mut self, opacity: f32) -> Self {
        self.dim_opacity = opacity.clamp(0.0, 1.0);
        self
    }

    /// Center content vertically in zen mode
    pub fn center(mut self) -> Self {
        self.center_vertical = true;
        self
    }

    /// Enable zen mode
    pub fn enable(&mut self) {
        self.enabled = true;
    }

    /// Disable zen mode
    pub fn disable(&mut self) {
        self.enabled = false;
    }

    /// Toggle zen mode
    pub fn toggle(&mut self) {
        self.enabled = !self.enabled;
    }

    /// Check if zen mode is enabled
    pub fn is_enabled(&self) -> bool {
        self.enabled
    }

    /// Set enabled state
    pub fn set_enabled(&mut self, enabled: bool) {
        self.enabled = enabled;
    }

    /// Get reference to inner content
    pub fn content(&self) -> &dyn View {
        self.content.as_ref()
    }

    /// Get mutable reference to inner content
    pub fn content_mut(&mut self) -> &mut dyn View {
        self.content.as_mut()
    }

    // Getters for testing
    #[doc(hidden)]
    pub fn get_padding_x(&self) -> u16 {
        self.padding_x
    }

    #[doc(hidden)]
    pub fn get_padding_y(&self) -> u16 {
        self.padding_y
    }

    #[doc(hidden)]
    pub fn get_bg_color(&self) -> Color {
        self.bg_color
    }

    #[doc(hidden)]
    pub fn get_dim_opacity(&self) -> f32 {
        self.dim_opacity
    }

    #[doc(hidden)]
    pub fn get_center_vertical(&self) -> bool {
        self.center_vertical
    }
}

impl Default for ZenMode {
    fn default() -> Self {
        Self::new(super::text(""))
    }
}

impl View for ZenMode {
    fn render(&self, ctx: &mut RenderContext) {
        let area = ctx.area;

        if self.enabled {
            // Zen mode: fill background and render content with padding
            for y in 0..area.height {
                for x in 0..area.width {
                    let mut cell = Cell::new(' ');
                    cell.bg = Some(self.bg_color);
                    ctx.set(x, y, cell);
                }
            }

            // Calculate padded area
            let content_width = area.width.saturating_sub(self.padding_x * 2);
            let content_height = area.height.saturating_sub(self.padding_y * 2);

            if content_width > 0 && content_height > 0 {
                let content_area = ctx.sub_area(
                    self.padding_x,
                    self.padding_y,
                    content_width,
                    content_height,
                );

                let mut sub_ctx = RenderContext::new(ctx.buffer, content_area);
                self.content.render(&mut sub_ctx);
            }
        } else {
            // Normal mode: just render content in full area
            self.content.render(ctx);
        }
    }

    crate::impl_view_meta!("ZenMode");
}

impl_styled_view!(ZenMode);
impl_props_builders!(ZenMode);

/// Helper function to create a zen mode wrapper
pub fn zen(content: impl View + 'static) -> ZenMode {
    ZenMode::new(content)
}

/// Helper function to create a zen mode wrapper with dark theme
pub fn zen_dark(content: impl View + 'static) -> ZenMode {
    ZenMode::new(content).bg(Color::rgb(15, 15, 25)).padding(4)
}

/// Helper function to create a zen mode wrapper with light theme
pub fn zen_light(content: impl View + 'static) -> ZenMode {
    ZenMode::new(content)
        .bg(Color::rgb(250, 250, 250))
        .padding(4)
}