Skip to content
Snippets Groups Projects
lib.rs 45.5 KiB
Newer Older
                    } else {
                        editor.action(&mut font_system.0, Action::Insert(char_ev.char));
StaffEngineer's avatar
StaffEngineer committed
                    }
                }
StaffEngineer's avatar
StaffEngineer committed

            if !is_edit {
                // RETURN
                return;
            }
StaffEngineer's avatar
StaffEngineer committed

            if let Some(last_edit_duration) = *edits_duration {
                if Duration::from_millis(now_ms as u64) - last_edit_duration
                    > Duration::from_millis(150)
                {
                    save_edit_history(editor, attrs, &mut edit_history);
StaffEngineer's avatar
StaffEngineer committed
                    *edits_duration = Some(Duration::from_millis(now_ms as u64));
                }
            } else {
                save_edit_history(editor, attrs, &mut edit_history);
                *edits_duration = Some(Duration::from_millis(now_ms as u64));
fn cosmic_edit_set_redraw(mut cosmic_edit_query: Query<&mut CosmicEditor, Added<CosmicEditor>>) {
    for mut editor in cosmic_edit_query.iter_mut() {
        editor.0.buffer_mut().set_redraw(true);
StaffEngineer's avatar
StaffEngineer committed
    }
}

#[allow(clippy::too_many_arguments)]
StaffEngineer's avatar
StaffEngineer committed
fn redraw_buffer_common(
    images: &mut ResMut<Assets<Image>>,
    swash_cache_state: &mut ResMut<SwashCacheState>,
    editor: &mut Editor,
    background_image: Option<Handle<Image>>,
    background_color: Color,
    cosmic_canvas_img_handle: &mut Handle<Image>,
    text_position: &CosmicTextPosition,
    font_system: &mut ResMut<CosmicFontSystem>,
StaffEngineer's avatar
StaffEngineer committed
    scale_factor: f32,
    original_width: f32,
    original_height: f32,
) {
    let width = original_width * scale_factor;
    let height = original_height * scale_factor;
    let swash_cache = &mut swash_cache_state.swash_cache;
    editor.shape_as_needed(&mut font_system.0);
    if editor.buffer().redraw() {
        editor
            .buffer_mut()
            .set_size(&mut font_system.0, width, height);
        let font_color = cosmic_text::Color::rgb(0, 0, 0);

        let mut pixels = vec![0; width as usize * height as usize * 4];
        if let Some(bg_image) = background_image {
            let image = images.get(&bg_image).unwrap();

            let mut dynamic_image = image.clone().try_into_dynamic().unwrap();
            if image.size().x != width || image.size().y != height {
                dynamic_image =
                    dynamic_image.resize_to_fill(width as u32, height as u32, FilterType::Triangle);
            }
            for (i, (_, _, rgba)) in dynamic_image.pixels().enumerate() {
                if let Some(p) = pixels.get_mut(i * 4..(i + 1) * 4) {
                    p[0] = rgba[0];
                    p[1] = rgba[1];
                    p[2] = rgba[2];
                    p[3] = rgba[3];
StaffEngineer's avatar
StaffEngineer committed
                }
            }
        } else {
            let bg = background_color;
            for pixel in pixels.chunks_exact_mut(4) {
                pixel[0] = (bg.r() * 255.) as u8; // Red component
                pixel[1] = (bg.g() * 255.) as u8; // Green component
                pixel[2] = (bg.b() * 255.) as u8; // Blue component
                pixel[3] = (bg.a() * 255.) as u8; // Alpha component
            }
        }
StaffEngineer's avatar
StaffEngineer committed

        let (offset_y, offset_x) = match text_position {
            CosmicTextPosition::Center => {
                (get_y_offset(editor.buffer()), get_x_offset(editor.buffer()))
            }
            CosmicTextPosition::TopLeft => (0, 0),
        };

        editor.draw(
            &mut font_system.0,
            swash_cache,
            font_color,
            |x, y, w, h, color| {
                for row in 0..h as i32 {
                    for col in 0..w as i32 {
                        draw_pixel(
                            &mut pixels,
                            width as i32,
                            height as i32,
                            x + col + offset_x,
                            y + row + offset_y,
                            color,
                        );
StaffEngineer's avatar
StaffEngineer committed
                    }
                }
            },
        );
        editor.buffer_mut().set_redraw(false);

        if let Some(prev_image) = images.get_mut(cosmic_canvas_img_handle) {
            if *cosmic_canvas_img_handle == bevy::render::texture::DEFAULT_IMAGE_HANDLE.typed() {
                let mut prev_image = prev_image.clone();
                prev_image.data.clear();
                prev_image.data.extend_from_slice(pixels.as_slice());
                prev_image.resize(Extent3d {
                    width: width as u32,
                    height: height as u32,
                    depth_or_array_layers: 1,
                });
                let handle_id: HandleId = HandleId::random::<Image>();
                let new_handle: Handle<Image> = Handle::weak(handle_id);
                let new_handle = images.set(new_handle, prev_image);
                *cosmic_canvas_img_handle = new_handle;
            } else {
                prev_image.data.clear();
                prev_image.data.extend_from_slice(pixels.as_slice());
                prev_image.resize(Extent3d {
                    width: width as u32,
                    height: height as u32,
                    depth_or_array_layers: 1,
                });
StaffEngineer's avatar
StaffEngineer committed
            }
        }
    }
}

fn cosmic_edit_redraw_buffer_ui(
    windows: Query<&Window, With<PrimaryWindow>>,
    mut images: ResMut<Assets<Image>>,
    mut swash_cache_state: ResMut<SwashCacheState>,
    mut cosmic_edit_query: Query<(
        &mut CosmicEditor,
        &CosmicBackground,
        &BackgroundColor,
        &CosmicTextPosition,
        &mut UiImage,
        &Node,
        &mut Visibility,
    )>,
    mut font_system: ResMut<CosmicFontSystem>,
StaffEngineer's avatar
StaffEngineer committed
) {
    let primary_window = windows.single();
    for (
        mut editor,
        background_image,
        background_color,
        text_position,
        mut img,
        node,
        mut visibility,
    ) in &mut cosmic_edit_query.iter_mut()
    {
        // provide min sizes to prevent render panic
        let width = node.size().x.max(1.);
        let height = node.size().y.max(1.);
StaffEngineer's avatar
StaffEngineer committed

        redraw_buffer_common(
            &mut images,
            &mut swash_cache_state,
            &mut editor.0,
            background_image.0.clone(),
            background_color.0,
StaffEngineer's avatar
StaffEngineer committed
            &mut img.texture,
            text_position,
            &mut font_system,
StaffEngineer's avatar
StaffEngineer committed
            primary_window.scale_factor() as f32,
            width,
            height,
        );

        if *visibility == Visibility::Hidden
            && img.texture.clone() != bevy::render::texture::DEFAULT_IMAGE_HANDLE.typed()
        {
            *visibility = Visibility::Visible;
        }
    }
}

fn cosmic_edit_redraw_buffer(
    windows: Query<&Window, With<PrimaryWindow>>,
    mut images: ResMut<Assets<Image>>,
    mut swash_cache_state: ResMut<SwashCacheState>,
    mut cosmic_edit_query: Query<(
        &mut CosmicEditor,
        &Sprite,
        &CosmicBackground,
        &BackgroundColor,
        &CosmicTextPosition,
        &mut Handle<Image>,
        &mut Visibility,
    )>,
    mut font_system: ResMut<CosmicFontSystem>,
StaffEngineer's avatar
StaffEngineer committed
) {
    let primary_window = windows.single();
    for (
        mut editor,
        sprite,
        background_image,
        background_color,
        text_position,
        mut handle,
        mut visibility,
    ) in &mut cosmic_edit_query.iter_mut()
    {
        // provide min sizes to prevent render panic
        let width = sprite.custom_size.unwrap().x.max(1.);
        let height = sprite.custom_size.unwrap().y.max(1.);

StaffEngineer's avatar
StaffEngineer committed
        redraw_buffer_common(
            &mut images,
            &mut swash_cache_state,
            &mut editor.0,
            background_image.0.clone(),
            background_color.0,
StaffEngineer's avatar
StaffEngineer committed
            &mut handle,
            text_position,
            &mut font_system,
StaffEngineer's avatar
StaffEngineer committed
            primary_window.scale_factor() as f32,
            width,
            height,
        );

        if *visibility == Visibility::Hidden
            && handle.clone() != bevy::render::texture::DEFAULT_IMAGE_HANDLE.typed()
        {
            *visibility = Visibility::Visible;
        }
    }
}

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: CosmicTextPosition::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<_>>());
//         }
//     }
// }