Skip to content
Snippets Groups Projects
widget.rs 6.81 KiB
Newer Older
sam edelsten's avatar
sam edelsten committed
use crate::*;
use bevy::{prelude::*, window::PrimaryWindow};
use cosmic_text::Affinity;

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

pub(crate) struct WidgetPlugin;

impl Plugin for WidgetPlugin {
    fn build(&self, app: &mut App) {
        app.add_systems(Update, reshape.in_set(WidgetSet).after(InputSet))
            .add_systems(
                PostUpdate,
                (
                    (new_image_from_default, set_sprite_size_from_ui),
                    set_widget_size,
                    set_buffer_size,
                    set_padding,
                    set_x_offset,
                )
                    .chain()
                    .in_set(WidgetSet)
                    .after(TransformSystem::TransformPropagate),
            );
    }
}
/// Wrapper for a [`Vec2`] describing the horizontal and vertical padding of a widget.
/// This is set programatically, not for user modification.
/// To set a widget's padding, use [`CosmicTextAlign`]
#[derive(Component, Default, Deref, DerefMut, Debug)]
sam edelsten's avatar
sam edelsten committed
pub struct CosmicPadding(pub Vec2);

/// Wrapper for a [`Vec2`] describing the horizontal and vertical size of a widget.
/// This is set programatically, not for user modification.
/// To set a widget's size, use either it's [`Sprite`] dimensions or modify the target UI element's
/// size.
#[derive(Component, Default, Deref, DerefMut)]
sam edelsten's avatar
sam edelsten committed
pub struct CosmicWidgetSize(pub Vec2);

/// Reshapes text in a [`CosmicEditor`]
fn reshape(mut query: Query<&mut CosmicEditor>, mut font_system: ResMut<CosmicFontSystem>) {
sam edelsten's avatar
sam edelsten committed
    for mut cosmic_editor in query.iter_mut() {
        cosmic_editor.shape_as_needed(&mut font_system.0, false);
    }
}

/// Programatically sets the [`CosmicPadding`] of a widget based on it's [`CosmicTextAlign`]
fn set_padding(
sam edelsten's avatar
sam edelsten committed
    mut query: Query<
        (
            &mut CosmicPadding,
sam edelsten's avatar
sam edelsten committed
            &CosmicBuffer,
            &CosmicWidgetSize,
            Option<&CosmicEditor>,
        ),
        Or<(
            With<CosmicEditor>,
            Changed<CosmicTextAlign>,
sam edelsten's avatar
sam edelsten committed
            Changed<CosmicBuffer>,
            Changed<CosmicWidgetSize>,
        )>,
    >,
) {
    for (mut padding, position, buffer, size, editor_opt) in query.iter_mut() {
        // TODO: At least one of these clones is uneccessary
        let mut buffer = buffer.0.clone();

        if let Some(editor) = editor_opt {
            buffer = editor.with_buffer(|b| b.clone());
        }

        if !buffer.redraw() {
            continue;
        }

        padding.0 = match position {
            CosmicTextAlign::Center { padding: _ } => Vec2::new(
sam edelsten's avatar
sam edelsten committed
                get_x_offset_center(size.0.x, &buffer) as f32,
                get_y_offset_center(size.0.y, &buffer) as f32,
            ),
            CosmicTextAlign::TopLeft { padding } => Vec2::new(*padding as f32, *padding as f32),
            CosmicTextAlign::Left { padding } => Vec2::new(
sam edelsten's avatar
sam edelsten committed
                *padding as f32,
                get_y_offset_center(size.0.y, &buffer) as f32,
            ),
        }
    }
}

/// Programatically sets the [`CosmicWidgetSize`] of a widget based on it's [`Sprite`] properties
fn set_widget_size(
sam edelsten's avatar
sam edelsten committed
    mut query: Query<(&mut CosmicWidgetSize, &Sprite), Changed<Sprite>>,
    windows: Query<&Window, With<PrimaryWindow>>,
) {
    if windows.iter().len() == 0 {
        return;
    }
    // TODO: early return if sprite size is unchanged
    let scale = windows.single().scale_factor();
    for (mut size, sprite) in query.iter_mut() {
        size.0 = sprite.custom_size.unwrap().ceil() * scale;
    }
}

/// Sets the internal [`Buffer`]'s size according to the [`CosmicWidgetSize`] and [`CosmicTextAlign`]
fn set_buffer_size(
sam edelsten's avatar
sam edelsten committed
    mut query: Query<
        (
            &mut CosmicBuffer,
sam edelsten's avatar
sam edelsten committed
            &CosmicWidgetSize,
sam edelsten's avatar
sam edelsten committed
        ),
        Or<(
            Changed<CosmicWrap>,
sam edelsten's avatar
sam edelsten committed
            Changed<CosmicWidgetSize>,
            Changed<CosmicTextAlign>,
sam edelsten's avatar
sam edelsten committed
        )>,
    >,
    mut font_system: ResMut<CosmicFontSystem>,
) {
    for (mut buffer, mode, size, position) in query.iter_mut() {
        let padding_x = match position {
            CosmicTextAlign::Center { padding: _ } => 0.,
            CosmicTextAlign::TopLeft { padding } => *padding as f32,
            CosmicTextAlign::Left { padding } => *padding as f32,
sam edelsten's avatar
sam edelsten committed
        };

        let (buffer_width, buffer_height) = match mode {
            CosmicWrap::InfiniteLine => (f32::MAX, size.0.y),
            CosmicWrap::Wrap => (size.0.x - padding_x, size.0.y),
sam edelsten's avatar
sam edelsten committed
        };

        buffer.set_size(&mut font_system.0, buffer_width, buffer_height);
    }
}

/// Instantiates a new image for a [`CosmicBuffer`]
fn new_image_from_default(
sam edelsten's avatar
sam edelsten committed
    mut query: Query<&mut Handle<Image>, Added<CosmicBuffer>>,
    mut images: ResMut<Assets<Image>>,
) {
    for mut canvas in query.iter_mut() {
        *canvas = images.add(Image::default());
    }
}

sam edelsten's avatar
sam edelsten committed
    mut query: Query<(
        &mut XOffset,
sam edelsten's avatar
sam edelsten committed
        &CosmicEditor,
        &CosmicWidgetSize,
sam edelsten's avatar
sam edelsten committed
    )>,
) {
    for (mut x_offset, mode, editor, size, position) in query.iter_mut() {
        if mode != &CosmicWrap::InfiniteLine {
sam edelsten's avatar
sam edelsten committed
        let mut cursor_x = 0.;
        let cursor = editor.cursor();

        if let Some(line) = editor.with_buffer(|b| b.clone()).layout_runs().next() {
            for (idx, glyph) in line.glyphs.iter().enumerate() {
                if cursor.affinity == Affinity::Before {
                    if idx <= cursor.index {
sam edelsten's avatar
sam edelsten committed
                        cursor_x += glyph.w;
                    } else {
                        break;
                    }
                } else if idx < cursor.index {
                    cursor_x += glyph.w;
                } else {
                    break;
        let padding_x = match position {
            CosmicTextAlign::Center { padding } => *padding as f32,
            CosmicTextAlign::TopLeft { padding } => *padding as f32,
            CosmicTextAlign::Left { padding } => *padding as f32,
        };

        if x_offset.width == 0. {
            x_offset.width = size.x - padding_x * 2.;
        let right = x_offset.width + x_offset.left;

        if cursor_x > right {
            let diff = cursor_x - right;
            x_offset.left += diff;
        }
        if cursor_x < x_offset.left {
            let diff = x_offset.left - cursor_x;
            x_offset.left -= diff;
fn set_sprite_size_from_ui(
sam edelsten's avatar
sam edelsten committed
    mut source_q: Query<&mut Sprite, With<CosmicBuffer>>,
    dest_q: Query<(&Node, &CosmicSource), Changed<Node>>,
) {
    for (node, source) in dest_q.iter() {
        if let Ok(mut sprite) = source_q.get_mut(source.0) {
            sprite.custom_size = Some(node.size().ceil().max(Vec2::ONE));
        }
    }
}