diff --git a/examples/restricted_input.rs b/examples/restricted_input.rs new file mode 100644 index 0000000000000000000000000000000000000000..18de1076cb28630c8dcab218529f648c4d999ff4 --- /dev/null +++ b/examples/restricted_input.rs @@ -0,0 +1,48 @@ +use bevy::prelude::*; +use bevy_cosmic_edit::{ + change_active_editor_sprite, change_active_editor_ui, ActiveEditor, CosmicAttrs, + CosmicEditPlugin, CosmicEditUiBundle, CosmicMaxChars, CosmicMaxLines, CosmicMetrics, +}; +use cosmic_text::{Attrs, AttrsOwned}; + +fn setup(mut commands: Commands) { + commands.spawn(Camera2dBundle::default()); + + let attrs = AttrsOwned::new(Attrs::new().color(cosmic_text::Color::rgb(69, 69, 69))); + + let editor = commands + .spawn(CosmicEditUiBundle { + style: Style { + // Size and position of text box + width: Val::Percent(20.), + height: Val::Px(50.), + left: Val::Percent(40.), + top: Val::Px(100.), + ..default() + }, + cosmic_attrs: CosmicAttrs(attrs.clone()), + cosmic_metrics: CosmicMetrics { + font_size: 16., + line_height: 16., + ..Default::default() + }, + max_chars: CosmicMaxChars(15), + max_lines: CosmicMaxLines(1), + ..default() + }) + .id(); + + commands.insert_resource(ActiveEditor { + entity: Some(editor), + }); +} + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_plugins(CosmicEditPlugin::default()) + .add_systems(Startup, setup) + .add_systems(Update, change_active_editor_ui) + .add_systems(Update, change_active_editor_sprite) + .run(); +} diff --git a/src/lib.rs b/src/lib.rs index ce3c3489063e023eaf44397a714002f5cc07fde6..c1b5dbb2fbac38f87a35782da20ca66a68cba863 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,6 +33,7 @@ pub enum CosmicTextPosition { TopLeft, } +// TODO docs #[derive(Clone, Component)] pub struct CosmicMetrics { pub font_size: f32, @@ -77,6 +78,7 @@ impl CosmicEditor { editor.buffer_mut().lines.clear(); match text { CosmicText::OneStyle(text) => { + // TODO get access to max_chars and max_lines here, implement limits editor.buffer_mut().set_text( font_system, text.as_str(), @@ -202,6 +204,12 @@ impl Default for CosmicAttrs { #[derive(Component, Default)] pub struct CosmicBackground(pub Option<Handle<Image>>); +#[derive(Component, Default)] +pub struct CosmicMaxLines(pub usize); + +#[derive(Component, Default)] +pub struct CosmicMaxChars(pub usize); + #[derive(Bundle)] pub struct CosmicEditUiBundle { // Bevy UI bits @@ -251,6 +259,10 @@ pub struct CosmicEditUiBundle { pub cosmic_attrs: CosmicAttrs, /// bg img pub background_image: CosmicBackground, + /// How many lines are allowed in buffer, 0 for no limit + pub max_lines: CosmicMaxLines, + /// How many characters are allowed in buffer, 0 for no limit + pub max_chars: CosmicMaxChars, } impl CosmicEditUiBundle { @@ -287,6 +299,8 @@ impl Default for CosmicEditUiBundle { cosmic_edit_history: Default::default(), cosmic_attrs: Default::default(), background_image: Default::default(), + max_lines: Default::default(), + max_chars: Default::default(), } } } @@ -317,6 +331,10 @@ pub struct CosmicEditSpriteBundle { pub cosmic_attrs: CosmicAttrs, /// bg img pub background_image: CosmicBackground, + /// How many lines are allowed in buffer, 0 for no limit + pub max_lines: CosmicMaxLines, + /// How many characters are allowed in buffer, 0 for no limit + pub max_chars: CosmicMaxChars, } impl CosmicEditSpriteBundle { @@ -340,15 +358,15 @@ impl Default for CosmicEditSpriteBundle { texture: DEFAULT_IMAGE_HANDLE.typed(), visibility: Visibility::Hidden, computed_visibility: Default::default(), - // background_color: Default::default(), - // editor: Default::default(), text_position: Default::default(), cosmic_metrics: Default::default(), cosmic_edit_history: Default::default(), cosmic_attrs: Default::default(), background_image: Default::default(), + max_lines: Default::default(), + max_chars: Default::default(), } } } @@ -608,6 +626,8 @@ pub fn cosmic_edit_bevy_events( &GlobalTransform, &CosmicAttrs, &CosmicTextPosition, + &CosmicMaxLines, + &CosmicMaxChars, Entity, ), With<CosmicEditor>, @@ -625,8 +645,16 @@ pub fn cosmic_edit_bevy_events( let primary_window = windows.single(); let scale_factor = primary_window.scale_factor() as f32; let (camera, camera_transform) = camera_q.iter().find(|(c, _)| c.is_active).unwrap(); - for (mut editor, mut edit_history, node_transform, attrs, text_position, entity) in - &mut cosmic_edit_query.iter_mut() + for ( + mut editor, + mut edit_history, + node_transform, + attrs, + text_position, + max_lines, + max_chars, + entity, + ) in &mut cosmic_edit_query.iter_mut() { let readonly = readonly_query.get(entity).is_ok(); @@ -639,7 +667,6 @@ pub fn cosmic_edit_bevy_events( } }; - let editor = &mut editor.0; let attrs = &attrs.0; if active_editor.entity == Some(entity) { @@ -657,10 +684,10 @@ pub fn cosmic_edit_bevy_events( let option = keys.any_pressed([KeyCode::AltLeft, KeyCode::AltRight]); // if shift key is pressed - let already_has_selection = editor.select_opt().is_some(); + let already_has_selection = editor.0.select_opt().is_some(); if shift && !already_has_selection { - let cursor = editor.cursor(); - editor.set_select_opt(Some(cursor)); + let cursor = editor.0.cursor(); + editor.0.set_select_opt(Some(cursor)); } #[cfg(target_os = "macos")] @@ -669,87 +696,87 @@ pub fn cosmic_edit_bevy_events( let should_jump = command; if should_jump && keys.just_pressed(KeyCode::Left) { - editor.action(&mut font_system.0, Action::PreviousWord); + editor.0.action(&mut font_system.0, Action::PreviousWord); if !shift { - editor.set_select_opt(None); + editor.0.set_select_opt(None); } return; } if should_jump && keys.just_pressed(KeyCode::Right) { - editor.action(&mut font_system.0, Action::NextWord); + editor.0.action(&mut font_system.0, Action::NextWord); if !shift { - editor.set_select_opt(None); + editor.0.set_select_opt(None); } return; } if should_jump && keys.just_pressed(KeyCode::Home) { - editor.action(&mut font_system.0, Action::BufferStart); + editor.0.action(&mut font_system.0, Action::BufferStart); // there's a bug with cosmic text where it doesn't update the visual cursor for this action // TODO: fix upstream - editor.buffer_mut().set_redraw(true); + editor.0.buffer_mut().set_redraw(true); if !shift { - editor.set_select_opt(None); + editor.0.set_select_opt(None); } return; } if should_jump && keys.just_pressed(KeyCode::End) { - editor.action(&mut font_system.0, Action::BufferEnd); + editor.0.action(&mut font_system.0, Action::BufferEnd); // there's a bug with cosmic text where it doesn't update the visual cursor for this action // TODO: fix upstream - editor.buffer_mut().set_redraw(true); + editor.0.buffer_mut().set_redraw(true); if !shift { - editor.set_select_opt(None); + editor.0.set_select_opt(None); } return; } if keys.just_pressed(KeyCode::Left) { - editor.action(&mut font_system.0, Action::Left); + editor.0.action(&mut font_system.0, Action::Left); if !shift { - editor.set_select_opt(None); + editor.0.set_select_opt(None); } return; } if keys.just_pressed(KeyCode::Right) { - editor.action(&mut font_system.0, Action::Right); + editor.0.action(&mut font_system.0, Action::Right); if !shift { - editor.set_select_opt(None); + editor.0.set_select_opt(None); } return; } if keys.just_pressed(KeyCode::Up) { - editor.action(&mut font_system.0, Action::Up); + editor.0.action(&mut font_system.0, Action::Up); if !shift { - editor.set_select_opt(None); + editor.0.set_select_opt(None); } return; } if keys.just_pressed(KeyCode::Down) { - editor.action(&mut font_system.0, Action::Down); + editor.0.action(&mut font_system.0, Action::Down); if !shift { - editor.set_select_opt(None); + editor.0.set_select_opt(None); } return; } if !readonly && keys.just_pressed(KeyCode::Back) { #[cfg(target_arch = "wasm32")] - editor.action(&mut font_system.0, Action::Backspace); + editor.0.action(&mut font_system.0, Action::Backspace); *is_deleting = true; } if !readonly && keys.just_released(KeyCode::Back) { *is_deleting = false; } if !readonly && keys.just_pressed(KeyCode::Delete) { - editor.action(&mut font_system.0, Action::Delete); + editor.0.action(&mut font_system.0, Action::Delete); } if keys.just_pressed(KeyCode::Escape) { - editor.action(&mut font_system.0, Action::Escape); + editor.0.action(&mut font_system.0, Action::Escape); } if command && keys.just_pressed(KeyCode::A) { - editor.action(&mut font_system.0, Action::BufferEnd); - let current_cursor = editor.cursor(); - editor.set_select_opt(Some(Cursor { + editor.0.action(&mut font_system.0, Action::BufferEnd); + let current_cursor = editor.0.cursor(); + editor.0.set_select_opt(Some(Cursor { line: 0, index: 0, affinity: current_cursor.affinity, @@ -758,30 +785,30 @@ pub fn cosmic_edit_bevy_events( return; } if keys.just_pressed(KeyCode::Home) { - editor.action(&mut font_system.0, Action::Home); + editor.0.action(&mut font_system.0, Action::Home); if !shift { - editor.set_select_opt(None); + editor.0.set_select_opt(None); } return; } if keys.just_pressed(KeyCode::End) { - editor.action(&mut font_system.0, Action::End); + editor.0.action(&mut font_system.0, Action::End); if !shift { - editor.set_select_opt(None); + editor.0.set_select_opt(None); } return; } if keys.just_pressed(KeyCode::PageUp) { - editor.action(&mut font_system.0, Action::PageUp); + editor.0.action(&mut font_system.0, Action::PageUp); if !shift { - editor.set_select_opt(None); + editor.0.set_select_opt(None); } return; } if keys.just_pressed(KeyCode::PageDown) { - editor.action(&mut font_system.0, Action::PageDown); + editor.0.action(&mut font_system.0, Action::PageDown); if !shift { - editor.set_select_opt(None); + editor.0.set_select_opt(None); } return; } @@ -802,7 +829,7 @@ pub fn cosmic_edit_bevy_events( } let idx = edit_history.current_edit + 1; if let Some(current_edit) = edits.get(idx) { - editor.buffer_mut().lines.clear(); + editor.0.buffer_mut().lines.clear(); for line in current_edit.lines.iter() { let mut line_text = String::new(); let mut attrs_list = AttrsList::new(attrs.as_attrs()); @@ -812,14 +839,14 @@ pub fn cosmic_edit_bevy_events( let end = line_text.len(); attrs_list.add_span(start..end, attrs.as_attrs()); } - editor.buffer_mut().lines.push(BufferLine::new( + editor.0.buffer_mut().lines.push(BufferLine::new( line_text, attrs_list, Shaping::Advanced, )); } - editor.set_cursor(current_edit.cursor); - editor.buffer_mut().set_redraw(true); + editor.0.set_cursor(current_edit.cursor); + editor.0.buffer_mut().set_redraw(true); edit_history.current_edit += 1; } *undoredo_duration = Some(Duration::from_millis(now_ms as u64)); @@ -838,7 +865,7 @@ pub fn cosmic_edit_bevy_events( } let idx = edit_history.current_edit - 1; if let Some(current_edit) = edits.get(idx) { - editor.buffer_mut().lines.clear(); + editor.0.buffer_mut().lines.clear(); for line in current_edit.lines.iter() { let mut line_text = String::new(); let mut attrs_list = AttrsList::new(attrs.as_attrs()); @@ -848,14 +875,14 @@ pub fn cosmic_edit_bevy_events( let end = line_text.len(); attrs_list.add_span(start..end, attrs.as_attrs()); } - editor.buffer_mut().lines.push(BufferLine::new( + editor.0.buffer_mut().lines.push(BufferLine::new( line_text, attrs_list, Shaping::Advanced, )); } - editor.set_cursor(current_edit.cursor); - editor.buffer_mut().set_redraw(true); + editor.0.set_cursor(current_edit.cursor); + editor.0.buffer_mut().set_redraw(true); edit_history.current_edit -= 1; } *undoredo_duration = Some(Duration::from_millis(now_ms as u64)); @@ -867,22 +894,32 @@ pub fn cosmic_edit_bevy_events( { if let Ok(mut clipboard) = arboard::Clipboard::new() { if command && keys.just_pressed(KeyCode::C) { - if let Some(text) = editor.copy_selection() { + if let Some(text) = editor.0.copy_selection() { clipboard.set_text(text).unwrap(); return; } } if !readonly && command && keys.just_pressed(KeyCode::X) { - if let Some(text) = editor.copy_selection() { + if let Some(text) = editor.0.copy_selection() { clipboard.set_text(text).unwrap(); - editor.delete_selection(); + editor.0.delete_selection(); } is_clipboard = true; } if !readonly && command && keys.just_pressed(KeyCode::V) { if let Ok(text) = clipboard.get_text() { for c in text.chars() { - editor.action(&mut font_system.0, Action::Insert(c)); + if max_chars.0 == 0 || editor.get_text().len() < max_chars.0 { + if c == 0xA as char { + if max_lines.0 == 0 + || editor.0.buffer().lines.len() < max_lines.0 + { + editor.0.action(&mut font_system.0, Action::Insert(c)); + } + } else { + editor.0.action(&mut font_system.0, Action::Insert(c)); + } + } } } is_clipboard = true; @@ -890,9 +927,10 @@ pub fn cosmic_edit_bevy_events( } } let (offset_x, offset_y) = match text_position { - CosmicTextPosition::Center => { - (get_x_offset(editor.buffer()), get_y_offset(editor.buffer())) - } + CosmicTextPosition::Center => ( + get_x_offset(editor.0.buffer()), + get_y_offset(editor.0.buffer()), + ), CosmicTextPosition::TopLeft => (0, 0), }; let point = |node_cursor_pos: (f32, f32)| { @@ -913,9 +951,9 @@ pub fn cosmic_edit_bevy_events( ) { let (x, y) = point(node_cursor_pos); if shift { - editor.action(&mut font_system.0, Action::Drag { x, y }); + editor.0.action(&mut font_system.0, Action::Drag { x, y }); } else { - editor.action(&mut font_system.0, Action::Click { x, y }); + editor.0.action(&mut font_system.0, Action::Click { x, y }); } } return; @@ -931,9 +969,9 @@ pub fn cosmic_edit_bevy_events( ) { let (x, y) = point(node_cursor_pos); if active_editor.is_changed() && !shift { - editor.action(&mut font_system.0, Action::Click { x, y }); + editor.0.action(&mut font_system.0, Action::Click { x, y }); } else { - editor.action(&mut font_system.0, Action::Drag { x, y }); + editor.0.action(&mut font_system.0, Action::Drag { x, y }); } } return; @@ -941,7 +979,7 @@ pub fn cosmic_edit_bevy_events( for ev in scroll_evr.iter() { match ev.unit { MouseScrollUnit::Line => { - editor.action( + editor.0.action( &mut font_system.0, Action::Scroll { lines: -ev.y as i32, @@ -949,8 +987,8 @@ pub fn cosmic_edit_bevy_events( ); } MouseScrollUnit::Pixel => { - let line_height = editor.buffer().metrics().line_height; - editor.action( + let line_height = editor.0.buffer().metrics().line_height; + editor.0.action( &mut font_system.0, Action::Scroll { lines: -(ev.y / line_height) as i32, @@ -965,9 +1003,10 @@ pub fn cosmic_edit_bevy_events( } // fix for issue #8 - if let Some(select) = editor.select_opt() { - if editor.cursor().line == select.line && editor.cursor().index == select.index { - editor.set_select_opt(None); + if let Some(select) = editor.0.select_opt() { + if editor.0.cursor().line == select.line && editor.0.cursor().index == select.index + { + editor.0.set_select_opt(None); } } @@ -976,17 +1015,23 @@ pub fn cosmic_edit_bevy_events( if keys.just_pressed(KeyCode::Return) { is_return = true; is_edit = true; - // to have new line on wasm rather than E - editor.action(&mut font_system.0, Action::Insert('\n')); + if (max_lines.0 == 0 || editor.0.buffer().lines.len() < max_lines.0) + && (max_chars.0 == 0 || editor.get_text().len() < max_chars.0) + { + // to have new line on wasm rather than E + editor.0.action(&mut font_system.0, Action::Insert('\n')); + } } if !(is_clipboard || is_return) { for char_ev in char_evr.iter() { is_edit = true; if *is_deleting { - editor.action(&mut font_system.0, Action::Backspace); - } else { - editor.action(&mut font_system.0, Action::Insert(char_ev.char)); + editor.0.action(&mut font_system.0, Action::Backspace); + } else if max_chars.0 == 0 || editor.get_text().len() < max_chars.0 { + editor + .0 + .action(&mut font_system.0, Action::Insert(char_ev.char)); } } } @@ -999,11 +1044,11 @@ pub fn cosmic_edit_bevy_events( if Duration::from_millis(now_ms as u64) - last_edit_duration > Duration::from_millis(150) { - save_edit_history(editor, attrs, &mut edit_history); + save_edit_history(&mut editor.0, attrs, &mut edit_history); *edits_duration = Some(Duration::from_millis(now_ms as u64)); } } else { - save_edit_history(editor, attrs, &mut edit_history); + save_edit_history(&mut editor.0, attrs, &mut edit_history); *edits_duration = Some(Duration::from_millis(now_ms as u64)); } }