cu-bevymon 0.15.0

A Bevy-backed Ratatui monitor for Copper.
Documentation
use bevy::prelude::*;
use bevy::ui::{Node as UiNode, UiRect, UiTargetCamera, widget::ViewportNode};

use crate::{CuBevyMonFocusBorder, CuBevyMonPanel, CuBevyMonSurface, CuBevyMonSurfaceNode};

#[derive(Component)]
pub struct CuBevyMonSplitRoot;

#[derive(Component)]
pub struct CuBevyMonSplitSimPanel;

#[derive(Component)]
pub struct CuBevyMonSplitMonitorPanel;

#[derive(Clone, Copy, Debug)]
pub struct CuBevyMonSplitStyle {
    pub outer_padding_px: f32,
    pub panel_gap_px: f32,
    pub sim_panel_percent: f32,
    pub monitor_panel_percent: f32,
    pub monitor_panel_inset_px: f32,
    pub focused_border_color: Color,
    pub unfocused_border_color: Color,
    pub focused_border_width_px: f32,
    pub unfocused_border_width_px: f32,
    pub monitor_background: Color,
}

impl Default for CuBevyMonSplitStyle {
    fn default() -> Self {
        Self {
            outer_padding_px: 12.0,
            panel_gap_px: 12.0,
            sim_panel_percent: 67.0,
            monitor_panel_percent: 33.0,
            monitor_panel_inset_px: 4.0,
            focused_border_color: Color::srgb(0.38, 0.40, 0.43),
            unfocused_border_color: Color::BLACK,
            focused_border_width_px: 1.0,
            unfocused_border_width_px: 1.0,
            monitor_background: Color::srgba(0.04, 0.05, 0.08, 0.98),
        }
    }
}

pub struct CuBevyMonSplitLayoutConfig {
    pub scene_camera: Entity,
    pub ui_camera: Option<Entity>,
    pub style: CuBevyMonSplitStyle,
}

impl CuBevyMonSplitLayoutConfig {
    pub fn new(scene_camera: Entity) -> Self {
        Self {
            scene_camera,
            ui_camera: None,
            style: CuBevyMonSplitStyle::default(),
        }
    }

    pub fn with_ui_camera(mut self, ui_camera: Entity) -> Self {
        self.ui_camera = Some(ui_camera);
        self
    }

    pub fn with_style(mut self, style: CuBevyMonSplitStyle) -> Self {
        self.style = style;
        self
    }
}

#[derive(Clone, Copy, Debug)]
pub struct CuBevyMonSplitLayoutEntities {
    pub root: Entity,
    pub sim_panel: Entity,
    pub monitor_panel: Entity,
    pub monitor_texture: Entity,
}

pub fn spawn_split_layout(
    commands: &mut Commands,
    monitor_texture: Handle<Image>,
    config: CuBevyMonSplitLayoutConfig,
) -> CuBevyMonSplitLayoutEntities {
    let style = config.style;
    let sim_border = CuBevyMonFocusBorder::new(CuBevyMonSurface::Sim)
        .with_colors(style.focused_border_color, style.unfocused_border_color)
        .with_widths(
            style.focused_border_width_px,
            style.unfocused_border_width_px,
        );
    let monitor_border = CuBevyMonFocusBorder::new(CuBevyMonSurface::Monitor)
        .with_colors(style.focused_border_color, style.unfocused_border_color)
        .with_widths(
            style.focused_border_width_px,
            style.unfocused_border_width_px,
        );

    let mut root_builder = commands.spawn((
        UiNode {
            width: Val::Percent(100.0),
            height: Val::Percent(100.0),
            flex_direction: FlexDirection::Row,
            padding: UiRect::all(Val::Px(style.outer_padding_px)),
            column_gap: Val::Px(style.panel_gap_px),
            ..default()
        },
        BackgroundColor(Color::NONE),
        CuBevyMonSplitRoot,
    ));
    if let Some(ui_camera) = config.ui_camera {
        root_builder.insert(UiTargetCamera(ui_camera));
    }

    let mut sim_panel = None;
    let mut monitor_panel = None;
    let mut monitor_texture_node = None;
    let root = root_builder
        .with_children(|parent| {
            sim_panel = Some(
                parent
                    .spawn((
                        UiNode {
                            height: Val::Percent(100.0),
                            min_width: Val::Px(0.0),
                            flex_basis: Val::Px(0.0),
                            flex_grow: style.sim_panel_percent,
                            position_type: PositionType::Relative,
                            border: UiRect::all(Val::Px(style.unfocused_border_width_px)),
                            ..default()
                        },
                        ViewportNode::new(config.scene_camera),
                        BackgroundColor(Color::NONE),
                        BorderColor::all(style.unfocused_border_color),
                        CuBevyMonSurfaceNode(CuBevyMonSurface::Sim),
                        sim_border,
                        CuBevyMonSplitSimPanel,
                    ))
                    .id(),
            );

            monitor_panel = Some(
                parent
                    .spawn((
                        UiNode {
                            height: Val::Percent(100.0),
                            min_width: Val::Px(0.0),
                            flex_basis: Val::Px(0.0),
                            flex_grow: style.monitor_panel_percent,
                            border: UiRect::all(Val::Px(style.unfocused_border_width_px)),
                            padding: UiRect::all(Val::Px(style.monitor_panel_inset_px)),
                            ..default()
                        },
                        BackgroundColor(style.monitor_background),
                        BorderColor::all(style.unfocused_border_color),
                        CuBevyMonSurfaceNode(CuBevyMonSurface::Monitor),
                        monitor_border,
                        CuBevyMonSplitMonitorPanel,
                    ))
                    .with_children(|panel| {
                        monitor_texture_node = Some(
                            panel
                                .spawn((
                                    ImageNode::new(monitor_texture.clone()),
                                    UiNode {
                                        width: Val::Percent(100.0),
                                        height: Val::Percent(100.0),
                                        ..default()
                                    },
                                    BackgroundColor(Color::BLACK),
                                    CuBevyMonPanel,
                                ))
                                .id(),
                        );
                    })
                    .id(),
            );
        })
        .id();

    CuBevyMonSplitLayoutEntities {
        root,
        sim_panel: sim_panel.expect("split layout missing sim panel"),
        monitor_panel: monitor_panel.expect("split layout missing monitor panel"),
        monitor_texture: monitor_texture_node.expect("split layout missing monitor texture"),
    }
}