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

use crate::{cosmic_edit::ScrollEnabled, prelude::*};
use bevy::ecs::{component::ComponentId, world::DeferredWorld};

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

sam edelsten's avatar
sam edelsten committed
pub(crate) fn plugin(app: &mut App) {
    app.add_systems(PreUpdate, scroll::scroll.in_set(InputSet))
        .add_systems(
            Update,
            (
                keyboard::kb_move_cursor,
                keyboard::kb_input_text,
                clipboard::kb_clipboard,
sam edelsten's avatar
sam edelsten committed
                    cursor_icon::update_cursor_icon,
                    cursor_visibility::update_cursor_visibility,
                ),
sam edelsten's avatar
sam edelsten committed
                .chain()
                .in_set(InputSet),
        )
        .add_event::<hover::TextHoverIn>()
        .add_event::<hover::TextHoverOut>()
        .add_event::<CosmicTextChanged>()
        .register_type::<hover::TextHoverIn>()
        .register_type::<hover::TextHoverOut>()
        .register_type::<CosmicTextChanged>();
sam edelsten's avatar
sam edelsten committed
    #[cfg(target_arch = "wasm32")]
    {
        let (tx, rx) = crossbeam_channel::bounded::<clipboard::WasmPaste>(1);
        app.insert_resource(clipboard::WasmPasteAsyncChannel { tx, rx })
            .add_systems(Update, clipboard::poll_wasm_paste);
/// Text change events
///
/// Sent when text is changed in a cosmic buffer
/// Contains the entity on which the text was changed, and the new text as a [`String`]
#[derive(Event, Reflect, Debug)]
pub struct CosmicTextChanged(pub (Entity, String));

/// First variant is least important, last is most important
#[derive(Component, Default, Debug)]
#[require(ScrollEnabled)]
#[component(on_add = add_event_handlers)]
pub(crate) enum InputState {
    #[default]
    Idle,
    Hovering,
    Dragging {
        initial_buffer_coord: Vec2,
    },
fn add_event_handlers(
    mut world: DeferredWorld,
    targeted_entity: Entity,
    _component_id: ComponentId,
sam's avatar
sam committed
) {
    let mut observers = [
sam edelsten's avatar
sam edelsten committed
        Observer::new(click::handle_focused_click.pipe(render_implementations::debug_error)),
        Observer::new(drag::handle_dragstart.pipe(render_implementations::debug_error)),
        Observer::new(drag::handle_drag_continue),
        Observer::new(drag::handle_dragend),
        Observer::new(hover::handle_hover_start),
        Observer::new(hover::handle_hover_continue),
        Observer::new(hover::handle_hover_end),
        Observer::new(cancel::handle_cancel),
    ];
    for observer in &mut observers {
        observer.watch_entity(targeted_entity);
    world.commands().spawn_batch(observers);
// todo: avoid these warnings on ReadOnly
fn warn_no_editor_on_picking_event(job: &'static str) {
    debug!(
        note = "This is a false alarm for ReadOnly buffers",
        note = "Please only use the `InputState` component on entities with a `CosmicEditor` component",
        note = "`CosmicEditor` components should be automatically added to focussed `CosmicEditBuffer` entities",
        "Failed to get editor from picking event while {job}"
    );
pub mod cancel {
    use crate::prelude::*;
    use super::{warn_no_editor_on_picking_event, InputState};
    impl InputState {
        /// `Cancel` event handler
        pub fn cancel(&mut self) {
            trace!("Cancelling a pointer");
            *self = InputState::Idle;
    pub(super) fn handle_cancel(
        trigger: Trigger<Pointer<Cancel>>,
        mut editor: Query<&mut InputState, With<CosmicEditBuffer>>,
    ) {
        let Ok(mut input_state) = editor.get_mut(trigger.target) else {
            warn_no_editor_on_picking_event("handling cursor `Cancel` event");
sam's avatar
sam committed
            return;
        input_state.cancel();