Skip to content
Snippets Groups Projects
render.rs 6.88 KiB
Newer Older
use crate::*;
sam edelsten's avatar
sam edelsten committed
use bevy::{prelude::*, render::render_resource::Extent3d};
use cosmic_text::{Color, Edit, SwashCache};
use image::{imageops::FilterType, GenericImageView};

/// System set for cosmic text rendering systems. Runs in [`PostUpdate`]
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
pub struct RenderSet;

pub(crate) struct RenderPlugin;

impl Plugin for RenderPlugin {
    fn build(&self, app: &mut App) {
        app.insert_resource(SwashCacheState {
            swash_cache: SwashCache::new(),
        })
        .add_systems(Update, blink_cursor)
        .add_systems(
            PostUpdate,
            (render_texture,).in_set(RenderSet).after(WidgetSet),
        );
    }
}

#[derive(Resource)]
pub(crate) struct SwashCacheState {
    pub swash_cache: SwashCache,
}

pub(crate) fn blink_cursor(mut q: Query<&mut CosmicEditor, Without<ReadOnly>>, time: Res<Time>) {
sam edelsten's avatar
sam edelsten committed
    for mut e in q.iter_mut() {
        e.cursor_timer.tick(time.delta());
        if e.cursor_timer.just_finished() {
            e.cursor_visible = !e.cursor_visible;
            e.set_redraw(true);
sam edelsten's avatar
sam edelsten committed
fn draw_pixel(buffer: &mut [u8], width: i32, height: i32, x: i32, y: i32, color: Color) {
    let a_a = color.a() as u32;
    if a_a == 0 {
        // Do not draw if alpha is zero
sam edelsten's avatar
sam edelsten committed

    if y < 0 || y >= height {
        // Skip if y out of bounds
        return;
sam edelsten's avatar
sam edelsten committed
    if x < 0 || x >= width {
        // Skip if x out of bounds
        return;
    }
sam edelsten's avatar
sam edelsten committed
    let offset = (y as usize * width as usize + x as usize) * 4;
    let bg = bevy::prelude::Color::srgba_u8(
sam edelsten's avatar
sam edelsten committed
        buffer[offset],
        buffer[offset + 1],
        buffer[offset + 2],
        buffer[offset + 3],
    );
sam edelsten's avatar
sam edelsten committed
    // TODO: if alpha is 100% or bg is empty skip blending

    let fg = Srgba::rgba_u8(color.r(), color.g(), color.b(), color.a());
    let premul = (fg * fg.alpha).with_alpha(color.a() as f32 / 255.0);
    let out = premul + (bg.to_srgba() * (1.0 - fg.alpha));
    buffer[offset + 2] = (out.blue * 255.0) as u8;
    buffer[offset + 1] = (out.green * 255.0) as u8;
    buffer[offset] = (out.red * 255.0) as u8;
    buffer[offset + 3] = (out.alpha * 255.0) as u8;
fn render_texture(
    mut query: Query<(
sam edelsten's avatar
sam edelsten committed
        Option<&mut CosmicEditor>,
        &mut CosmicBuffer,
        &DefaultAttrs,
        &CosmicBackgroundImage,
        &CosmicBackgroundColor,
sam edelsten's avatar
sam edelsten committed
        &CursorColor,
        &SelectionColor,
        Option<&SelectedTextColor>,
        &Handle<Image>,
        &CosmicWidgetSize,
        &CosmicPadding,
        &XOffset,
sam edelsten's avatar
sam edelsten committed
        Option<&ReadOnly>,
    )>,
    mut font_system: ResMut<CosmicFontSystem>,
    mut images: ResMut<Assets<Image>>,
    mut swash_cache_state: ResMut<SwashCacheState>,
) {
sam edelsten's avatar
sam edelsten committed
    for (
        editor,
        mut buffer,
        attrs,
        background_image,
        fill_color,
        cursor_color,
        selection_color,
        selected_text_color_option,
sam edelsten's avatar
sam edelsten committed
        canvas,
        size,
        padding,
        x_offset,
        readonly_opt,
        position,
sam edelsten's avatar
sam edelsten committed
    ) in query.iter_mut()
        // Draw background
        let mut pixels = vec![0; size.0.x as usize * size.0.y as usize * 4];
        if let Some(bg_image) = background_image.0.clone() {
            if let Some(image) = images.get(&bg_image) {
                let mut dynamic_image = image.clone().try_into_dynamic().unwrap();
                if image.size().x != size.0.x as u32 || image.size().y != size.0.y as u32 {
                    dynamic_image = dynamic_image.resize_to_fill(
                        size.0.x as u32,
                        size.0.y 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];
                    }
                }
            }
        } else {
            let bg = fill_color.0.to_cosmic();
            for pixel in pixels.chunks_exact_mut(4) {
                pixel[0] = bg.r(); // Red component
                pixel[1] = bg.g(); // Green component
                pixel[2] = bg.b(); // Blue component
                pixel[3] = bg.a(); // Alpha component
            }
        }

        let font_color = attrs
            .0
            .color_opt
            .unwrap_or(cosmic_text::Color::rgb(0, 0, 0));

        let min_pad = match position {
            CosmicTextAlign::Center { padding } => *padding as f32,
            CosmicTextAlign::TopLeft { padding } => *padding as f32,
            CosmicTextAlign::Left { padding } => *padding as f32,
sam edelsten's avatar
sam edelsten committed
        let draw_closure = |x, y, w, h, color| {
            for row in 0..h as i32 {
                for col in 0..w as i32 {
                    draw_pixel(
                        &mut pixels,
                        size.0.x as i32,
                        size.0.y as i32,
                        x + col + padding.x.max(min_pad) as i32 - x_offset.left as i32,
                        y + row + padding.y as i32,
sam edelsten's avatar
sam edelsten committed
                        color,
                    );
sam edelsten's avatar
sam edelsten committed
        // Draw glyphs
        if let Some(mut editor) = editor {
            if !editor.redraw() {
sam edelsten's avatar
sam edelsten committed
                continue;
            let cursor_color = cursor_color.0;
sam edelsten's avatar
sam edelsten committed
            let cursor_opacity = if editor.cursor_visible && readonly_opt.is_none() {
                cursor_color.alpha()
sam edelsten's avatar
sam edelsten committed
            } else {
            let cursor_color = cursor_color.with_alpha(cursor_opacity).to_cosmic();
            let selection_color = selection_color.0.to_cosmic();

            let selected_text_color = selected_text_color_option
                .map(|selected_text_color| selected_text_color.0.to_cosmic())
                .unwrap_or(font_color);
sam edelsten's avatar
sam edelsten committed
            editor.draw(
                &mut font_system.0,
sam edelsten's avatar
sam edelsten committed
                &mut swash_cache_state.swash_cache,
                font_color,
                cursor_color,
                selection_color,
                selected_text_color,
sam edelsten's avatar
sam edelsten committed
                draw_closure,
sam edelsten's avatar
sam edelsten committed
            editor.set_redraw(false);
        } else {
            if !buffer.redraw() {
                continue;
sam edelsten's avatar
sam edelsten committed
            buffer.draw(
                &mut font_system.0,
                &mut swash_cache_state.swash_cache,
                font_color,
                draw_closure,
            );
            buffer.set_redraw(false);
sam edelsten's avatar
sam edelsten committed
        if let Some(prev_image) = images.get_mut(canvas) {
            prev_image.data.clear();
            prev_image.data.extend_from_slice(pixels.as_slice());
            prev_image.resize(Extent3d {
                width: size.0.x as u32,
                height: size.0.y as u32,
                depth_or_array_layers: 1,
            });