Skip to content
Snippets Groups Projects
lib.rs 43.9 KiB
Newer Older
StaffEngineer's avatar
StaffEngineer committed
pub fn spawn_cosmic_edit(
    commands: &mut Commands,
    cosmic_fonts: &mut ResMut<Assets<CosmicFont>>,
    cosmic_edit_meta: CosmicEditMeta,
) -> Entity {
    let font_system = cosmic_fonts
        .get_mut(&cosmic_edit_meta.font_system_handle)
        .unwrap();
    let scale_factor = cosmic_edit_meta.metrics.scale_factor;
    let font_size = cosmic_edit_meta.metrics.font_size;
    let line_height = cosmic_edit_meta.metrics.line_height;
    let metrics = Metrics::new(font_size, line_height).scale(scale_factor);
    let buffer = Buffer::new(&mut font_system.0, metrics);
    let mut editor = Editor::new(buffer);
    cosmic_edit_set_text(
        cosmic_edit_meta.text,
        cosmic_edit_meta.attrs.clone(),
        &mut editor,
        &mut font_system.0,
    );
    if let Some((width, height)) = cosmic_edit_meta.size {
        editor.buffer_mut().set_size(
            &mut font_system.0,
            width * scale_factor,
            height * scale_factor,
        );
    }
    let mut cursor = editor.cursor();
    if cosmic_edit_meta.readonly {
        cursor.color = Some(bevy_color_to_cosmic(cosmic_edit_meta.bg));
    }
    editor.set_cursor(cursor);

    let mut edits = VecDeque::new();
    if !cosmic_edit_meta.readonly {
        edits.push_back(EditHistoryItem {
            cursor,
            lines: get_text_spans(editor.buffer(), cosmic_edit_meta.attrs.clone()),
        });
    }
    let edit_history = CosmicEditHistory {
        edits,
        current_edit: 0,
    };
    let mut cosmic_edit_component = CosmicEdit {
        editor,
        font_system: cosmic_edit_meta.font_system_handle,
        text_pos: cosmic_edit_meta.text_pos,
        bg: cosmic_edit_meta.bg,
        is_ui_node: false,
        font_size,
        line_height,
        width: cosmic_edit_meta.size.unwrap_or((1., 1.)).0,
        height: cosmic_edit_meta.size.unwrap_or((1., 1.)).1,
        readonly: cosmic_edit_meta.readonly,
        attrs: cosmic_edit_meta.attrs.clone(),
        bg_image: cosmic_edit_meta.bg_image,
    };
    match cosmic_edit_meta.node {
        CosmicNode::Ui => {
            cosmic_edit_component.is_ui_node = true;
            let style = Style {
                width: Val::Percent(100.),
                height: Val::Percent(100.),
                ..default()
            };
            let button_bundle = ButtonBundle {
                visibility: Visibility::Hidden,
                focus_policy: bevy::ui::FocusPolicy::Pass,
                style,
                ..default()
            };
            commands
                .spawn((button_bundle, cosmic_edit_component, edit_history))
                .id()
        }
        CosmicNode::Sprite(sprite_node) => {
            let sprite = SpriteBundle {
                visibility: Visibility::Hidden,
                sprite: Sprite {
                    custom_size: Some(Vec2::new(
                        cosmic_edit_component.width,
                        cosmic_edit_component.height,
                    )),
                    ..default()
                },
                transform: sprite_node.transform,
                ..default()
            };
            commands
                .spawn((sprite, cosmic_edit_component, edit_history))
                .id()
        }
    }
}

fn draw_pixel(
    buffer: &mut [u8],
    width: i32,
    height: i32,
    x: i32,
    y: i32,
    color: cosmic_text::Color,
) {
    let alpha = (color.0 >> 24) & 0xFF;
    if alpha == 0 {
        // Do not draw if alpha is zero
        return;
    }

    if y < 0 || y >= height {
        // Skip if y out of bounds
        return;
    }

    if x < 0 || x >= width {
        // Skip if x out of bounds
        return;
    }

    let offset = (y as usize * width as usize + x as usize) * 4;

    let mut current = buffer[offset + 2] as u32
        | (buffer[offset + 1] as u32) << 8
        | (buffer[offset] as u32) << 16
        | (buffer[offset + 3] as u32) << 24;

    if alpha >= 255 || current == 0 {
        // Alpha is 100% or current is null, replace with no blending
        current = color.0;
    } else {
        // Alpha blend with current value
        let n_alpha = 255 - alpha;
        let rb = ((n_alpha * (current & 0x00FF00FF)) + (alpha * (color.0 & 0x00FF00FF))) >> 8;
        let ag = (n_alpha * ((current & 0xFF00FF00) >> 8))
            + (alpha * (0x01000000 | ((color.0 & 0x0000FF00) >> 8)));
        current = (rb & 0x00FF00FF) | (ag & 0xFF00FF00);
    }

    buffer[offset + 2] = current as u8;
    buffer[offset + 1] = (current >> 8) as u8;
    buffer[offset] = (current >> 16) as u8;
    buffer[offset + 3] = (current >> 24) as u8;
}

#[cfg(test)]
mod tests {
    use bevy::prelude::*;
    use cosmic_text::{Attrs, AttrsOwned};

    use crate::*;

    fn test_spawn_cosmic_edit_system(
        mut commands: Commands,
        mut cosmic_fonts: ResMut<Assets<CosmicFont>>,
    ) {
        let cosmic_font_config = CosmicFontConfig {
            fonts_dir_path: None,
            font_bytes: None,
            load_system_fonts: true,
        };
        let font_system = create_cosmic_font_system(cosmic_font_config);
        let font_system_handle = cosmic_fonts.add(CosmicFont(font_system));
        let cosmic_edit_meta = CosmicEditMeta {
            text: CosmicText::OneStyle("Blah".to_string()),
            attrs: AttrsOwned::new(Attrs::new()),
            metrics: CosmicMetrics {
                font_size: 14.,
                line_height: 18.,
                scale_factor: 1.,
            },
            text_pos: CosmicTextPos::Center,
            font_system_handle,
            node: CosmicNode::Ui,
            size: None,
            bg: bevy::prelude::Color::NONE,
            readonly: false,
            bg_image: None,
        };
        spawn_cosmic_edit(&mut commands, &mut cosmic_fonts, cosmic_edit_meta);
    }

    #[test]
    fn test_spawn_cosmic_edit() {
        let mut app = App::new();
        app.add_plugins(TaskPoolPlugin::default());
        app.add_plugins(AssetPlugin::default());
        app.add_systems(Update, test_spawn_cosmic_edit_system);

        let input = Input::<KeyCode>::default();
        app.insert_resource(input);
        let mouse_input: Input<MouseButton> = Input::<MouseButton>::default();
        app.insert_resource(mouse_input);
        app.add_asset::<Image>();
        app.add_asset::<CosmicFont>();

        app.add_event::<ReceivedCharacter>();

        app.update();

        let mut text_nodes_query = app.world.query::<&CosmicEdit>();
        for node in text_nodes_query.iter(&app.world) {
            insta::assert_debug_snapshot!(node
                .editor
                .buffer()
                .lines
                .iter()
                .map(|line| line.text())
                .collect::<Vec<_>>());
        }
    }
}