Skip to content
Snippets Groups Projects
input.rs 23.5 KiB
Newer Older
sam's avatar
sam committed
#![allow(clippy::too_many_arguments, clippy::type_complexity)]

use crate::*;
sam's avatar
sam committed
use bevy::{
    input::{
        keyboard::{Key, KeyboardInput},
        mouse::{MouseMotion, MouseScrollUnit, MouseWheel},
    },
sam's avatar
sam committed
    prelude::*,
    window::PrimaryWindow,
};
sam edelsten's avatar
sam edelsten committed
use cosmic_text::{Action, Cursor, Edit, Motion, Selection};
sam edelsten's avatar
sam edelsten committed
#[cfg(target_arch = "wasm32")]
use crate::DefaultAttrs;
#[cfg(target_arch = "wasm32")]
use bevy::tasks::AsyncComputeTaskPool;
#[cfg(target_arch = "wasm32")]
use js_sys::Promise;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_futures::JsFuture;

/// System set for mouse and keyboard input events. Runs in [`PreUpdate`] and [`Update`]
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
pub struct InputSet;

pub(crate) struct InputPlugin;

impl Plugin for InputPlugin {
    fn build(&self, app: &mut App) {
        app.add_systems(PreUpdate, input_mouse.in_set(InputSet))
            .add_systems(
                Update,
                (kb_move_cursor, kb_input_text, kb_clipboard)
                    .chain()
                    .in_set(InputSet),
            )
            .insert_resource(ClickTimer(Timer::from_seconds(0.5, TimerMode::Once)));
    }
}

/// Timer for double / triple clicks
sam's avatar
sam committed
#[derive(Resource)]
pub struct ClickTimer(pub Timer);

// TODO: hide this behind #cfg wasm, depends on wasm having own copy/paste fn
/// Crossbeam channel struct for Wasm clipboard data
#[allow(dead_code)]
pub struct WasmPaste {
    text: String,
    entity: Entity,
}

/// Async channel for receiving from the clipboard in Wasm
#[derive(Resource)]
pub struct WasmPasteAsyncChannel {
    pub tx: crossbeam_channel::Sender<WasmPaste>,
    pub rx: crossbeam_channel::Receiver<WasmPaste>,
}

sam's avatar
sam committed
pub(crate) fn input_mouse(
sam's avatar
sam committed
    windows: Query<&Window, With<PrimaryWindow>>,
sam edelsten's avatar
sam edelsten committed
    active_editor: Res<FocusedWidget>,
sam edelsten's avatar
sam edelsten committed
    keys: Res<ButtonInput<KeyCode>>,
    buttons: Res<ButtonInput<MouseButton>>,
    mut editor_q: Query<(
sam's avatar
sam committed
        &mut CosmicEditor,
        &GlobalTransform,
sam's avatar
sam committed
        Entity,
        &XOffset,
        &mut Sprite,
        Option<&ScrollDisabled>,
sam's avatar
sam committed
    )>,
    node_q: Query<(&Node, &GlobalTransform, &CosmicSource)>,
sam's avatar
sam committed
    mut font_system: ResMut<CosmicFontSystem>,
    mut scroll_evr: EventReader<MouseWheel>,
    camera_q: Query<(&Camera, &GlobalTransform)>,
    mut click_timer: ResMut<ClickTimer>,
    mut click_count: Local<usize>,
    time: Res<Time>,
    evr_mouse_motion: EventReader<MouseMotion>,
sam's avatar
sam committed
) {
sam's avatar
sam committed
    click_timer.0.tick(time.delta());

    let Some(active_editor_entity) = active_editor.0 else {
sam's avatar
sam committed
        return;
sam's avatar
sam committed
    if click_timer.0.finished() || !evr_mouse_motion.is_empty() {
        *click_count = 0;
    }

    if buttons.just_pressed(MouseButton::Left) {
        click_timer.0.reset();
        *click_count += 1;
    }

    if *click_count > 3 {
        *click_count = 0;
    }

    let Ok(primary_window) = windows.get_single() else {
sam edelsten's avatar
sam edelsten committed
    let scale_factor = primary_window.scale_factor();
    let Some((camera, camera_transform)) = camera_q.iter().find(|(c, _)| c.is_active) else {
        return;
    };
    if let Ok((
        mut editor,
        sprite_transform,
        text_position,
        entity,
        x_offset,
        sprite,
        scroll_disabled,
    )) = editor_q.get_mut(active_editor_entity)
sam edelsten's avatar
sam edelsten committed
        let buffer = editor.with_buffer(|b| b.clone());

        let mut is_ui_node = false;
        let mut transform = sprite_transform;
        let sprite_size = sprite.custom_size.expect("Must specify Sprite.custom_size");
        let (mut width, mut height) = (sprite_size.x, sprite_size.y);

        // TODO: this is bad loop nesting, rethink system with relationships in mind
        for (node, node_transform, source) in node_q.iter() {
            if source.0 != entity {
                continue;
            is_ui_node = true;
            transform = node_transform;
            width = node.size().x;
            height = node.size().y;
        }
sam's avatar
sam committed

        let shift = keys.any_pressed([KeyCode::ShiftLeft, KeyCode::ShiftRight]);

        // if shift key is pressed
sam edelsten's avatar
sam edelsten committed
        let already_has_selection = editor.selection() != Selection::None;
sam's avatar
sam committed
        if shift && !already_has_selection {
sam edelsten's avatar
sam edelsten committed
            let cursor = editor.cursor();
            editor.set_selection(Selection::Normal(cursor));
sam's avatar
sam committed
        }

        let (padding_x, padding_y) = match text_position {
            CosmicTextAlign::Center { padding: _ } => (
sam edelsten's avatar
sam edelsten committed
                get_x_offset_center(width * scale_factor, &buffer),
                get_y_offset_center(height * scale_factor, &buffer),
            CosmicTextAlign::TopLeft { padding } => (*padding, *padding),
            CosmicTextAlign::Left { padding } => (
sam's avatar
sam committed
                *padding,
sam edelsten's avatar
sam edelsten committed
                get_y_offset_center(height * scale_factor, &buffer),
        let point = |node_cursor_pos: Vec2| {
                (node_cursor_pos.x * scale_factor) as i32 - padding_x,
                (node_cursor_pos.y * scale_factor) as i32 - padding_y,
sam's avatar
sam committed
            )
        };

        if buttons.just_pressed(MouseButton::Left) {
sam edelsten's avatar
sam edelsten committed
            editor.cursor_visible = true;
            editor.cursor_timer.reset();

sam's avatar
sam committed
            if let Some(node_cursor_pos) = get_node_cursor_pos(
                primary_window,
                transform,
                Vec2::new(width, height),
sam's avatar
sam committed
                is_ui_node,
                camera,
                camera_transform,
            ) {
                let (mut x, y) = point(node_cursor_pos);
                x += x_offset.left as i32;
sam's avatar
sam committed
                if shift {
sam edelsten's avatar
sam edelsten committed
                    editor.action(&mut font_system.0, Action::Drag { x, y });
sam's avatar
sam committed
                } else {
sam's avatar
sam committed
                    match *click_count {
                        1 => {
sam edelsten's avatar
sam edelsten committed
                            editor.action(&mut font_system.0, Action::Click { x, y });
sam's avatar
sam committed
                        }
                        2 => {
                            // select word
sam edelsten's avatar
sam edelsten committed
                            editor.action(&mut font_system.0, Action::Motion(Motion::LeftWord));
                            let cursor = editor.cursor();
                            editor.set_selection(Selection::Normal(cursor));
                            editor.action(&mut font_system.0, Action::Motion(Motion::RightWord));
sam's avatar
sam committed
                        }
                        3 => {
                            // select paragraph
sam edelsten's avatar
sam edelsten committed
                            editor
                                .action(&mut font_system.0, Action::Motion(Motion::ParagraphStart));
                            let cursor = editor.cursor();
                            editor.set_selection(Selection::Normal(cursor));
                            editor.action(&mut font_system.0, Action::Motion(Motion::ParagraphEnd));
sam's avatar
sam committed

        if buttons.pressed(MouseButton::Left) && *click_count == 0 {
sam's avatar
sam committed
            if let Some(node_cursor_pos) = get_node_cursor_pos(
                primary_window,
                transform,
                Vec2::new(width, height),
sam's avatar
sam committed
                is_ui_node,
                camera,
                camera_transform,
            ) {
                let (mut x, y) = point(node_cursor_pos);
                x += x_offset.left as i32;
sam's avatar
sam committed
                if active_editor.is_changed() && !shift {
sam edelsten's avatar
sam edelsten committed
                    editor.action(&mut font_system.0, Action::Click { x, y });
sam's avatar
sam committed
                } else {
sam edelsten's avatar
sam edelsten committed
                    editor.action(&mut font_system.0, Action::Drag { x, y });
        if scroll_disabled.is_none() {
            for ev in scroll_evr.read() {
                match ev.unit {
                    MouseScrollUnit::Line => {
                        editor.action(
                            &mut font_system.0,
                            Action::Scroll {
                                lines: -ev.y as i32,
                            },
                        );
                    }
                    MouseScrollUnit::Pixel => {
                        let line_height = buffer.metrics().line_height;
                        editor.action(
                            &mut font_system.0,
                            Action::Scroll {
                                lines: -(ev.y / line_height) as i32,
                            },
                        );
                    }
pub fn kb_move_cursor(
sam edelsten's avatar
sam edelsten committed
    active_editor: Res<FocusedWidget>,
sam edelsten's avatar
sam edelsten committed
    keys: Res<ButtonInput<KeyCode>>,
    mut cosmic_edit_query: Query<(&mut CosmicEditor,)>,
sam's avatar
sam committed
    mut font_system: ResMut<CosmicFontSystem>,
    let Some(active_editor_entity) = active_editor.0 else {
        return;
    };
    if let Ok((mut editor,)) = cosmic_edit_query.get_mut(active_editor_entity) {
sam edelsten's avatar
sam edelsten committed
        if keys.get_just_pressed().len() != 0 {
            editor.cursor_visible = true;
            editor.cursor_timer.reset();
        }
        let command = keypress_command(&keys);
        #[cfg(target_arch = "wasm32")]
        let command = if web_sys::window()
            .unwrap()
            .navigator()
            .user_agent()
            .unwrap_or("NoUA".into())
            .contains("Macintosh")
        {
            keys.any_pressed([KeyCode::SuperLeft, KeyCode::SuperRight])
        } else {
            command
        };

sam's avatar
sam committed
        #[cfg(target_os = "macos")]
        let option = keys.any_pressed([KeyCode::AltLeft, KeyCode::AltRight]);

        let shift = keys.any_pressed([KeyCode::ShiftLeft, KeyCode::ShiftRight]);

sam's avatar
sam committed
        // if shift key is pressed
sam edelsten's avatar
sam edelsten committed
        let already_has_selection = editor.selection() != Selection::None;
sam's avatar
sam committed
        if shift && !already_has_selection {
sam edelsten's avatar
sam edelsten committed
            let cursor = editor.cursor();
            editor.set_selection(Selection::Normal(cursor));
sam's avatar
sam committed
        }

        #[cfg(target_os = "macos")]
        let should_jump = command && option;
        #[cfg(not(target_os = "macos"))]
        let should_jump = command;

sam edelsten's avatar
sam edelsten committed
        if should_jump && keys.just_pressed(KeyCode::ArrowLeft) {
sam edelsten's avatar
sam edelsten committed
            editor.action(&mut font_system.0, Action::Motion(Motion::PreviousWord));
sam's avatar
sam committed
            if !shift {
sam edelsten's avatar
sam edelsten committed
                editor.set_selection(Selection::None);
sam edelsten's avatar
sam edelsten committed
        if should_jump && keys.just_pressed(KeyCode::ArrowRight) {
sam edelsten's avatar
sam edelsten committed
            editor.action(&mut font_system.0, Action::Motion(Motion::NextWord));
sam's avatar
sam committed
            if !shift {
sam edelsten's avatar
sam edelsten committed
                editor.set_selection(Selection::None);
sam's avatar
sam committed
            }
            return;
        }
        if should_jump && keys.just_pressed(KeyCode::Home) {
sam edelsten's avatar
sam edelsten committed
            editor.action(&mut font_system.0, Action::Motion(Motion::BufferStart));
sam's avatar
sam committed
            if !shift {
sam edelsten's avatar
sam edelsten committed
                editor.set_selection(Selection::None);
sam's avatar
sam committed
            }
            return;
        }
        if should_jump && keys.just_pressed(KeyCode::End) {
sam edelsten's avatar
sam edelsten committed
            editor.action(&mut font_system.0, Action::Motion(Motion::BufferEnd));
sam's avatar
sam committed
            if !shift {
sam edelsten's avatar
sam edelsten committed
                editor.set_selection(Selection::None);
sam edelsten's avatar
sam edelsten committed
        if keys.just_pressed(KeyCode::ArrowLeft) {
sam edelsten's avatar
sam edelsten committed
            editor.action(&mut font_system.0, Action::Motion(Motion::Left));
sam's avatar
sam committed
            if !shift {
sam edelsten's avatar
sam edelsten committed
                editor.set_selection(Selection::None);
sam edelsten's avatar
sam edelsten committed
        if keys.just_pressed(KeyCode::ArrowRight) {
sam edelsten's avatar
sam edelsten committed
            editor.action(&mut font_system.0, Action::Motion(Motion::Right));
sam's avatar
sam committed
            if !shift {
sam edelsten's avatar
sam edelsten committed
                editor.set_selection(Selection::None);
sam edelsten's avatar
sam edelsten committed
        if keys.just_pressed(KeyCode::ArrowUp) {
sam edelsten's avatar
sam edelsten committed
            editor.action(&mut font_system.0, Action::Motion(Motion::Up));
sam's avatar
sam committed
            if !shift {
sam edelsten's avatar
sam edelsten committed
                editor.set_selection(Selection::None);
sam edelsten's avatar
sam edelsten committed
        if keys.just_pressed(KeyCode::ArrowDown) {
sam edelsten's avatar
sam edelsten committed
            editor.action(&mut font_system.0, Action::Motion(Motion::Down));
sam's avatar
sam committed
            if !shift {
sam edelsten's avatar
sam edelsten committed
                editor.set_selection(Selection::None);
        if keys.just_pressed(KeyCode::Escape) {
            editor.action(&mut font_system.0, Action::Escape);
        }
        if command && keys.just_pressed(KeyCode::KeyA) {
            editor.action(&mut font_system.0, Action::Motion(Motion::BufferEnd));
            let current_cursor = editor.cursor();
            editor.set_selection(Selection::Normal(Cursor {
                line: 0,
                index: 0,
                affinity: current_cursor.affinity,
            }));
            return;
        }
        if keys.just_pressed(KeyCode::Home) {
            editor.action(&mut font_system.0, Action::Motion(Motion::Home));
            if !shift {
                editor.set_selection(Selection::None);
            }
            return;
        }
        if keys.just_pressed(KeyCode::End) {
            editor.action(&mut font_system.0, Action::Motion(Motion::End));
            if !shift {
                editor.set_selection(Selection::None);
            }
            return;
        }
        if keys.just_pressed(KeyCode::PageUp) {
            editor.action(&mut font_system.0, Action::Motion(Motion::PageUp));
            if !shift {
                editor.set_selection(Selection::None);
            }
            return;
        }
        if keys.just_pressed(KeyCode::PageDown) {
            editor.action(&mut font_system.0, Action::Motion(Motion::PageDown));
            if !shift {
                editor.set_selection(Selection::None);
            }
        }
    }
}

pub(crate) fn kb_input_text(
    active_editor: Res<FocusedWidget>,
    keys: Res<ButtonInput<KeyCode>>,
    mut char_evr: EventReader<KeyboardInput>,
    mut cosmic_edit_query: Query<(
        &mut CosmicEditor,
        &mut CosmicBuffer,
        Entity,
        Option<&ReadOnly>,
    )>,
    mut evw_changed: EventWriter<CosmicTextChanged>,
    mut font_system: ResMut<CosmicFontSystem>,
    mut is_deleting: Local<bool>,
) {
    let Some(active_editor_entity) = active_editor.0 else {
        return;
    };

    if let Ok((mut editor, buffer, max_lines, max_chars, entity, readonly_opt)) =
        cosmic_edit_query.get_mut(active_editor_entity)
    {
        let command = keypress_command(&keys);
        if keys.get_just_pressed().len() != 0 {
            editor.cursor_visible = true;
            editor.cursor_timer.reset();
        }
        let readonly = readonly_opt.is_some();
sam edelsten's avatar
sam edelsten committed
        if keys.just_pressed(KeyCode::Backspace) & !readonly {
            // fix for issue #8
sam edelsten's avatar
sam edelsten committed
            let select = editor.selection();
            match select {
                Selection::Line(cursor) => {
                    if editor.cursor().line == cursor.line && editor.cursor().index == cursor.index
                    {
                        editor.set_selection(Selection::None);
                    }
                }
                Selection::Normal(cursor) => {
                    if editor.cursor().line == cursor.line && editor.cursor().index == cursor.index
                    {
                        editor.set_selection(Selection::None);
                    }
                }
                Selection::Word(cursor) => {
                    if editor.cursor().line == cursor.line && editor.cursor().index == cursor.index
                    {
                        editor.set_selection(Selection::None);
                    }
                }
                Selection::None => {}
sam's avatar
sam committed
            *is_deleting = true;
        }
sam edelsten's avatar
sam edelsten committed
        if keys.just_released(KeyCode::Backspace) {
sam's avatar
sam committed
            *is_deleting = false;
        }
        if keys.just_pressed(KeyCode::Delete) && !readonly {
sam edelsten's avatar
sam edelsten committed
            editor.action(&mut font_system.0, Action::Delete);
            editor.with_buffer_mut(|b| b.set_redraw(true));
sam's avatar
sam committed
            return;
        }

        let mut is_edit = false;
        let mut is_return = false;
        if keys.just_pressed(KeyCode::Enter) {
            is_return = true;
            if (max_lines.0 == 0 || buffer.lines.len() < max_lines.0)
                && (max_chars.0 == 0 || buffer.get_text().len() < max_chars.0)
            {
                // to have new line on wasm rather than E
                is_edit = true;
                editor.action(&mut font_system.0, Action::Insert('\n'));

        if !is_return {
            for char_ev in char_evr.read() {
                is_edit = true;
                if *is_deleting {
                    editor.action(&mut font_system.0, Action::Backspace);
                } else if !command
                    && (max_chars.0 == 0 || buffer.get_text().len() < max_chars.0)
                    && matches!(char_ev.state, bevy::input::ButtonState::Pressed)
                {
                    match &char_ev.logical_key {
                        Key::Character(char) => {
                            let b = char.as_bytes();
                            for c in b {
                                let c: char = (*c).into();
                                editor.action(&mut font_system.0, Action::Insert(c));
                            }
                        Key::Space => {
                            editor.action(&mut font_system.0, Action::Insert(' '));
                        }
                        _ => (),
        evw_changed.send(CosmicTextChanged((
            entity,
            editor.with_buffer_mut(|b| b.get_text()),
        )));
    }
}

pub fn kb_clipboard(
    active_editor: Res<FocusedWidget>,
    keys: Res<ButtonInput<KeyCode>>,
    mut evw_changed: EventWriter<CosmicTextChanged>,
    mut font_system: ResMut<CosmicFontSystem>,
    mut cosmic_edit_query: Query<(
        &mut CosmicEditor,
        &mut CosmicBuffer,
        Entity,
        Option<&ReadOnly>,
    )>,
sam edelsten's avatar
sam edelsten committed
    _channel: Option<Res<WasmPasteAsyncChannel>>,
) {
    let Some(active_editor_entity) = active_editor.0 else {
        return;
    };

    if let Ok((mut editor, buffer, max_lines, max_chars, entity, readonly_opt)) =
        cosmic_edit_query.get_mut(active_editor_entity)
    {
        let command = keypress_command(&keys);

        let readonly = readonly_opt.is_some();

sam's avatar
sam committed
        let mut is_clipboard = false;
        #[cfg(not(target_arch = "wasm32"))]
        {
            if let Ok(mut clipboard) = arboard::Clipboard::new() {
sam edelsten's avatar
sam edelsten committed
                if command && keys.just_pressed(KeyCode::KeyC) {
sam edelsten's avatar
sam edelsten committed
                    if let Some(text) = editor.copy_selection() {
sam's avatar
sam committed
                        clipboard.set_text(text).unwrap();
                        return;
                    }
                }
sam edelsten's avatar
sam edelsten committed
                if command && keys.just_pressed(KeyCode::KeyX) && !readonly {
sam edelsten's avatar
sam edelsten committed
                    if let Some(text) = editor.copy_selection() {
sam's avatar
sam committed
                        clipboard.set_text(text).unwrap();
sam edelsten's avatar
sam edelsten committed
                        editor.delete_selection();
sam's avatar
sam committed
                    }
                    is_clipboard = true;
                }
sam edelsten's avatar
sam edelsten committed
                if command && keys.just_pressed(KeyCode::KeyV) && !readonly {
sam's avatar
sam committed
                    if let Ok(text) = clipboard.get_text() {
                        for c in text.chars() {
sam edelsten's avatar
sam edelsten committed
                            if max_chars.0 == 0 || buffer.get_text().len() < max_chars.0 {
sam's avatar
sam committed
                                if c == 0xA as char {
sam edelsten's avatar
sam edelsten committed
                                    if max_lines.0 == 0 || buffer.lines.len() < max_lines.0 {
                                        editor.action(&mut font_system.0, Action::Insert(c));
sam's avatar
sam committed
                                    }
                                } else {
sam edelsten's avatar
sam edelsten committed
                                    editor.action(&mut font_system.0, Action::Insert(c));
        #[cfg(target_arch = "wasm32")]
        {
sam edelsten's avatar
sam edelsten committed
            if command && keys.just_pressed(KeyCode::KeyC) {
sam edelsten's avatar
sam edelsten committed
                if let Some(text) = editor.copy_selection() {
                    write_clipboard_wasm(text.as_str());
                    return;
                }
            }

sam edelsten's avatar
sam edelsten committed
            if command && keys.just_pressed(KeyCode::KeyX) && !readonly {
sam edelsten's avatar
sam edelsten committed
                if let Some(text) = editor.copy_selection() {
                    write_clipboard_wasm(text.as_str());
sam edelsten's avatar
sam edelsten committed
                    editor.delete_selection();
                }
                is_clipboard = true;
            }
sam edelsten's avatar
sam edelsten committed
            if command && keys.just_pressed(KeyCode::KeyV) && !readonly {
                let tx = _channel.unwrap().tx.clone();
                let _task = AsyncComputeTaskPool::get().spawn(async move {
                    let promise = read_clipboard_wasm();

                    let result = JsFuture::from(promise).await;

                    if let Ok(js_text) = result {
                        if let Some(text) = js_text.as_string() {
                            let _ = tx.try_send(WasmPaste { text, entity });
                        }
                    }
                });

                return;
            }
        }

sam edelsten's avatar
sam edelsten committed
        evw_changed.send(CosmicTextChanged((entity, buffer.get_text())));
sam edelsten's avatar
sam edelsten committed
fn keypress_command(keys: &ButtonInput<KeyCode>) -> bool {
    #[cfg(target_os = "macos")]
    let command = keys.any_pressed([KeyCode::SuperLeft, KeyCode::SuperRight]);

    #[cfg(not(target_os = "macos"))]
    let command = keys.any_pressed([KeyCode::ControlLeft, KeyCode::ControlRight]);

    #[cfg(target_arch = "wasm32")]
    let command = if web_sys::window()
        .unwrap()
        .navigator()
        .user_agent()
        .unwrap_or("NoUA".into())
        .contains("Macintosh")
    {
        keys.any_pressed([KeyCode::SuperLeft, KeyCode::SuperRight])
    } else {
        command
    };

    command
}

#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub fn write_clipboard_wasm(text: &str) {
    let clipboard = web_sys::window().unwrap().navigator().clipboard();
    let _result = clipboard.write_text(text);
}

#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub fn read_clipboard_wasm() -> Promise {
    let clipboard = web_sys::window().unwrap().navigator().clipboard();
    clipboard.read_text()
}

#[cfg(target_arch = "wasm32")]
pub fn poll_wasm_paste(
    channel: Res<WasmPasteAsyncChannel>,
    mut editor_q: Query<
        (
            &mut CosmicEditor,
sam edelsten's avatar
sam edelsten committed
            &mut CosmicBuffer,
            &crate::DefaultAttrs,
        ),
        Without<ReadOnly>,
    >,
    mut evw_changed: EventWriter<CosmicTextChanged>,
    mut font_system: ResMut<CosmicFontSystem>,
) {
    let inlet = channel.rx.try_recv();
    match inlet {
        Ok(inlet) => {
            let entity = inlet.entity;
sam edelsten's avatar
sam edelsten committed
            if let Ok((mut editor, mut buffer, attrs, max_chars, max_lines)) =
                editor_q.get_mut(entity)
            {
                let text = inlet.text;
                let attrs = &attrs.0;
                for c in text.chars() {
sam edelsten's avatar
sam edelsten committed
                    if max_chars.0 == 0 || buffer.get_text().len() < max_chars.0 {
                        if c == 0xA as char {
sam edelsten's avatar
sam edelsten committed
                            if max_lines.0 == 0 || buffer.lines.len() < max_lines.0 {
                                editor.action(&mut font_system.0, Action::Insert(c));
                            }
                        } else {
sam edelsten's avatar
sam edelsten committed
                            editor.action(&mut font_system.0, Action::Insert(c));
sam edelsten's avatar
sam edelsten committed
                evw_changed.send(CosmicTextChanged((entity, buffer.get_text())));