diff --git a/Cargo.lock b/Cargo.lock index eabf8f21a2c61afafdae87a9e8bb3c7170e209ec..f2937f67313778f3c029ab74c6d0aa2964d2453b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2978,9 +2978,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2988,9 +2988,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", @@ -3015,9 +3015,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3025,9 +3025,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", @@ -3038,9 +3038,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "wayland-scanner" diff --git a/Cargo.toml b/Cargo.toml index 7dac19dd83b5ddbb18a7aae2272848fc34414f96..2e3e890e9d54223c121cff91fceb2c0507dc62d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ js-sys = "0.3.61" insta = "1.29.0" [[example]] -name = "basic_button" +name = "basic_ui" [[example]] name = "basic_sprite" diff --git a/examples/basic_button.rs b/examples/basic_button.rs deleted file mode 100644 index 9f355754c3ca76f4aab8a7e1081b0a6207781a1a..0000000000000000000000000000000000000000 --- a/examples/basic_button.rs +++ /dev/null @@ -1,67 +0,0 @@ -use bevy::{prelude::*, window::PrimaryWindow}; -use bevy_cosmic_edit::{ - create_cosmic_font_system, spawn_cosmic_edit, ActiveEditor, CosmicEditMeta, CosmicEditPlugin, - CosmicFont, CosmicFontConfig, CosmicMetrics, CosmicNode, CosmicText, CosmicTextPos, -}; -use cosmic_text::AttrsOwned; - -fn setup( - mut commands: Commands, - windows: Query<&Window, With<PrimaryWindow>>, - mut cosmic_fonts: ResMut<Assets<CosmicFont>>, -) { - let primary_window = windows.single(); - commands.spawn(Camera2dBundle::default()); - let root = commands - .spawn(NodeBundle { - style: Style { - display: Display::Flex, - width: Val::Percent(100.), - height: Val::Percent(100.), - ..default() - }, - ..default() - }) - .id(); - - let font_bytes: &[u8] = include_bytes!("../assets/fonts/VictorMono-Regular.ttf"); - let cosmic_font_config = CosmicFontConfig { - fonts_dir_path: None, - font_bytes: Some(vec![font_bytes]), - load_system_fonts: true, - }; - let font_system = create_cosmic_font_system(cosmic_font_config); - let font_system_handle: Handle<CosmicFont> = cosmic_fonts.add(CosmicFont(font_system)); - let mut attrs = cosmic_text::Attrs::new(); - attrs = attrs.family(cosmic_text::Family::Name("Victor Mono")); - attrs = attrs.color(cosmic_text::Color::rgb(0x94, 0x00, 0xD3)); - let cosmic_edit_meta = CosmicEditMeta { - text: CosmicText::OneStyle("馃榾馃榾馃榾 x => y".to_string()), - attrs: AttrsOwned::new(attrs), - text_pos: CosmicTextPos::Center, - bg: Color::WHITE, - metrics: CosmicMetrics { - font_size: 14., - line_height: 18., - scale_factor: primary_window.scale_factor() as f32, - }, - font_system_handle, - node: CosmicNode::Ui, - size: None, - readonly: false, - bg_image: None, - }; - let cosmic_edit = spawn_cosmic_edit(&mut commands, &mut cosmic_fonts, cosmic_edit_meta); - commands.entity(root).add_child(cosmic_edit); - commands.insert_resource(ActiveEditor { - entity: Some(cosmic_edit), - }); -} - -fn main() { - App::new() - .add_plugins(DefaultPlugins) - .add_plugins(CosmicEditPlugin) - .add_systems(Startup, setup) - .run(); -} diff --git a/examples/basic_sprite.rs b/examples/basic_sprite.rs index 696d26c810385be49ade2148d75ff3506e419ad6..ef0b9e3ca5113029f525bd62995bf7bc12c05e5d 100644 --- a/examples/basic_sprite.rs +++ b/examples/basic_sprite.rs @@ -1,15 +1,14 @@ use bevy::{core_pipeline::clear_color::ClearColorConfig, prelude::*, window::PrimaryWindow}; use bevy_cosmic_edit::{ - create_cosmic_font_system, spawn_cosmic_edit, ActiveEditor, CosmicEditMeta, CosmicEditPlugin, - CosmicEditSprite, CosmicFont, CosmicFontConfig, CosmicMetrics, CosmicNode, CosmicText, - CosmicTextPos, + ActiveEditor, CosmicAttrs, CosmicEditPlugin, CosmicEditSpriteBundle, CosmicFontConfig, + CosmicFontSystem, CosmicMetrics, CosmicText, CosmicTextPosition, }; use cosmic_text::AttrsOwned; fn setup( mut commands: Commands, windows: Query<&Window, With<PrimaryWindow>>, - mut cosmic_fonts: ResMut<Assets<CosmicFont>>, + mut font_system: ResMut<CosmicFontSystem>, ) { let primary_window = windows.single(); let camera_bundle = Camera2dBundle { @@ -19,49 +18,51 @@ fn setup( ..default() }; commands.spawn(camera_bundle); - let font_bytes: &[u8] = include_bytes!("../assets/fonts/VictorMono-Regular.ttf"); - let cosmic_font_config = CosmicFontConfig { - fonts_dir_path: None, - font_bytes: Some(vec![font_bytes]), - load_system_fonts: true, - }; - let font_system = create_cosmic_font_system(cosmic_font_config); - let font_system_handle: Handle<CosmicFont> = cosmic_fonts.add(CosmicFont(font_system)); + let mut attrs = cosmic_text::Attrs::new(); attrs = attrs.family(cosmic_text::Family::Name("Victor Mono")); attrs = attrs.color(cosmic_text::Color::rgb(0x94, 0x00, 0xD3)); + let scale_factor = primary_window.scale_factor() as f32; - let cosmic_edit_meta = CosmicEditMeta { - text: CosmicText::OneStyle("馃榾馃榾馃榾 x => y".to_string()), - attrs: AttrsOwned::new(attrs), - text_pos: CosmicTextPos::Center, - bg: Color::WHITE, - metrics: CosmicMetrics { + + let cosmic_edit = CosmicEditSpriteBundle { + sprite: Sprite { + custom_size: Some(Vec2::new(primary_window.width(), primary_window.height())), + ..default() + }, + cosmic_metrics: CosmicMetrics { font_size: 14., line_height: 18., scale_factor, }, - font_system_handle, - node: CosmicNode::Sprite(CosmicEditSprite { - transform: Transform { - translation: Vec3::new(0., 0., 1.), - ..default() - }, - }), - size: Some((primary_window.width(), primary_window.height())), - readonly: false, - bg_image: None, - }; - let cosmic_edit = spawn_cosmic_edit(&mut commands, &mut cosmic_fonts, cosmic_edit_meta); + text_position: CosmicTextPosition::Center, + cosmic_attrs: CosmicAttrs(AttrsOwned::new(attrs)), + ..default() + } + .set_text( + CosmicText::OneStyle("馃榾馃榾馃榾 x => y".to_string()), + AttrsOwned::new(attrs), + &mut font_system.0, + ); + + let cosmic_edit = commands.spawn(cosmic_edit).id(); + commands.insert_resource(ActiveEditor { entity: Some(cosmic_edit), }); } fn main() { + let font_bytes: &[u8] = include_bytes!("../assets/fonts/VictorMono-Regular.ttf"); + let font_config = CosmicFontConfig { + fonts_dir_path: None, + font_bytes: Some(vec![font_bytes]), + load_system_fonts: true, + }; + App::new() .add_plugins(DefaultPlugins) - .add_plugins(CosmicEditPlugin) + .add_plugins(CosmicEditPlugin { font_config }) .add_systems(Startup, setup) .run(); } diff --git a/examples/basic_ui.rs b/examples/basic_ui.rs new file mode 100644 index 0000000000000000000000000000000000000000..943700dd1abca1bc417a3f021c13d958160ef09c --- /dev/null +++ b/examples/basic_ui.rs @@ -0,0 +1,69 @@ +use bevy::{core_pipeline::clear_color::ClearColorConfig, prelude::*, window::PrimaryWindow}; +use bevy_cosmic_edit::{ + ActiveEditor, CosmicAttrs, CosmicEditPlugin, CosmicEditUiBundle, CosmicFontConfig, + CosmicFontSystem, CosmicMetrics, CosmicText, CosmicTextPosition, +}; +use cosmic_text::AttrsOwned; + +fn setup( + mut commands: Commands, + windows: Query<&Window, With<PrimaryWindow>>, + mut font_system: ResMut<CosmicFontSystem>, +) { + let primary_window = windows.single(); + let camera_bundle = Camera2dBundle { + camera_2d: Camera2d { + clear_color: ClearColorConfig::Custom(Color::WHITE), + }, + ..default() + }; + commands.spawn(camera_bundle); + + let mut attrs = cosmic_text::Attrs::new(); + attrs = attrs.family(cosmic_text::Family::Name("Victor Mono")); + attrs = attrs.color(cosmic_text::Color::rgb(0x94, 0x00, 0xD3)); + + let scale_factor = primary_window.scale_factor() as f32; + + let cosmic_edit = CosmicEditUiBundle { + style: Style { + width: Val::Percent(100.), + height: Val::Percent(100.), + ..default() + }, + cosmic_metrics: CosmicMetrics { + font_size: 14., + line_height: 18., + scale_factor, + }, + text_position: CosmicTextPosition::Center, + cosmic_attrs: CosmicAttrs(AttrsOwned::new(attrs)), + ..default() + } + .set_text( + CosmicText::OneStyle("馃榾馃榾馃榾 x => y".to_string()), + AttrsOwned::new(attrs), + &mut font_system.0, + ); + + let cosmic_edit = commands.spawn(cosmic_edit).id(); + + commands.insert_resource(ActiveEditor { + entity: Some(cosmic_edit), + }); +} + +fn main() { + let font_bytes: &[u8] = include_bytes!("../assets/fonts/VictorMono-Regular.ttf"); + let font_config = CosmicFontConfig { + fonts_dir_path: None, + font_bytes: Some(vec![font_bytes]), + load_system_fonts: true, + }; + + App::new() + .add_plugins(DefaultPlugins) + .add_plugins(CosmicEditPlugin { font_config }) + .add_systems(Startup, setup) + .run(); +} diff --git a/examples/bevy_api_testing.rs b/examples/bevy_api_testing.rs new file mode 100644 index 0000000000000000000000000000000000000000..fcb9bc8f0abac266939ecf841e7671c7db8654d7 --- /dev/null +++ b/examples/bevy_api_testing.rs @@ -0,0 +1,49 @@ +use bevy::prelude::*; +use bevy_cosmic_edit::{ + change_active_editor_sprite, change_active_editor_ui, ActiveEditor, CosmicEditPlugin, + CosmicEditSpriteBundle, CosmicEditUiBundle, +}; + +fn setup(mut commands: Commands) { + commands.spawn(Camera2dBundle::default()); + + // spawn a new CosmicEditBundle + commands.spawn(CosmicEditUiBundle { + style: Style { + // Size and position of text box + width: Val::Px(300.), + height: Val::Px(50.), + left: Val::Px(100.), + top: Val::Px(100.), + ..default() + }, + ..default() + }); + + let sprite_editor = commands + .spawn(CosmicEditSpriteBundle { + sprite: Sprite { + // Sets size of text box + custom_size: Some(Vec2::new(300., 100.)), + ..default() + }, + // Position of text box + transform: Transform::from_xyz(100., 200., 0.), + ..default() + }) + .id(); + + commands.insert_resource(ActiveEditor { + entity: Some(sprite_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/examples/font_per_widget.rs b/examples/font_per_widget.rs index 8cfd85eb5b4e907c792e8488b8f49ae55d53c9f6..50d0843d14160b3d9d57981b18dd49eec0439a10 100644 --- a/examples/font_per_widget.rs +++ b/examples/font_per_widget.rs @@ -1,15 +1,18 @@ +#![allow(clippy::type_complexity)] + use bevy::{prelude::*, window::PrimaryWindow}; +use bevy_cosmic_edit::change_active_editor_sprite; +use bevy_cosmic_edit::change_active_editor_ui; use bevy_cosmic_edit::{ - create_cosmic_font_system, get_cosmic_text, spawn_cosmic_edit, ActiveEditor, CosmicEdit, - CosmicEditMeta, CosmicEditPlugin, CosmicFont, CosmicFontConfig, CosmicMetrics, CosmicNode, - CosmicText, CosmicTextPos, + ActiveEditor, CosmicAttrs, CosmicEditPlugin, CosmicEditUiBundle, CosmicFontConfig, + CosmicFontSystem, CosmicMetrics, CosmicText, CosmicTextPosition, }; -use cosmic_text::*; +use cosmic_text::{Attrs, AttrsOwned, Family}; fn setup( mut commands: Commands, windows: Query<&Window, With<PrimaryWindow>>, - mut cosmic_fonts: ResMut<Assets<CosmicFont>>, + mut font_system: ResMut<CosmicFontSystem>, ) { commands.spawn(Camera2dBundle::default()); let root = commands @@ -24,13 +27,6 @@ fn setup( }) .id(); let primary_window = windows.single(); - let cosmic_font_config = CosmicFontConfig { - fonts_dir_path: None, - font_bytes: None, - load_system_fonts: true, - }; - let font_system = create_cosmic_font_system(cosmic_font_config); - let font_system_handle = cosmic_fonts.add(CosmicFont(font_system)); let attrs = Attrs::new(); let serif_attrs = attrs.family(Family::Serif); @@ -229,77 +225,76 @@ fn setup( )], ]; - let cosmic_edit_meta_1 = CosmicEditMeta { - text: CosmicText::MultiStyle(lines), - attrs: AttrsOwned::new(attrs), - text_pos: CosmicTextPos::Center, - metrics: CosmicMetrics { + let cosmic_edit_1 = CosmicEditUiBundle { + text_position: bevy_cosmic_edit::CosmicTextPosition::Center, + cosmic_attrs: CosmicAttrs(AttrsOwned::new(attrs)), + cosmic_metrics: CosmicMetrics { font_size: 18., line_height: 22., scale_factor: primary_window.scale_factor() as f32, }, - font_system_handle: font_system_handle.clone(), - node: CosmicNode::Ui, - size: None, - bg: bevy::prelude::Color::WHITE, - readonly: false, - bg_image: None, - }; - let cosmic_edit_1 = spawn_cosmic_edit(&mut commands, &mut cosmic_fonts, cosmic_edit_meta_1); + style: Style { + width: Val::Percent(50.), + height: Val::Percent(100.), + ..default() + }, + background_color: BackgroundColor(Color::WHITE), + ..default() + } + .set_text( + CosmicText::MultiStyle(lines), + AttrsOwned::new(attrs), + &mut font_system.0, + ); let mut attrs_2 = cosmic_text::Attrs::new(); attrs_2 = attrs_2.family(cosmic_text::Family::Name("Times New Roman")); - let cosmic_edit_meta_2 = CosmicEditMeta { - text: CosmicText::OneStyle("Widget 2.\nClick on me =>".to_string()), - attrs: AttrsOwned::new(attrs_2), - metrics: CosmicMetrics { + + let cosmic_edit_2 = CosmicEditUiBundle { + cosmic_attrs: CosmicAttrs(AttrsOwned::new(attrs_2)), + cosmic_metrics: CosmicMetrics { font_size: 14., line_height: 18., scale_factor: primary_window.scale_factor() as f32, }, - font_system_handle: font_system_handle.clone(), - node: CosmicNode::Ui, - text_pos: CosmicTextPos::Center, - size: None, - bg: bevy::prelude::Color::WHITE.with_a(0.8), - readonly: false, - bg_image: None, - }; - let cosmic_edit_2 = spawn_cosmic_edit(&mut commands, &mut cosmic_fonts, cosmic_edit_meta_2); - - commands.entity(root).add_child(cosmic_edit_1); - commands.entity(root).add_child(cosmic_edit_2); + text_position: CosmicTextPosition::Center, + background_color: BackgroundColor(Color::WHITE.with_a(0.8)), + style: Style { + width: Val::Percent(50.), + height: Val::Percent(100.), + ..default() + }, + ..default() + } + .set_text( + CosmicText::OneStyle("Widget 2.\nClick on me =>".to_string()), + AttrsOwned::new(attrs_2), + &mut font_system.0, + ); - commands.insert_resource(ActiveEditor { - entity: Some(cosmic_edit_1), + let mut id = None; + // Spawn the CosmicEditUiBundles as children of root + commands.entity(root).with_children(|parent| { + id = Some(parent.spawn(cosmic_edit_1).id()); + parent.spawn(cosmic_edit_2); }); -} -fn change_active_editor( - mut commands: Commands, - mut interaction_query: Query< - (&Interaction, &mut CosmicEdit, Entity), - (Changed<Interaction>, With<CosmicEdit>), - >, -) { - for (interaction, cosmic_edit, entity) in interaction_query.iter_mut() { - if let Interaction::Pressed = interaction { - commands.insert_resource(ActiveEditor { - entity: Some(entity), - }); - info!( - "Widget text: {}", - get_cosmic_text(&cosmic_edit.editor.buffer()) - ); - } - } + // Set active editor + commands.insert_resource(ActiveEditor { entity: id }); } fn main() { + let font_config = CosmicFontConfig { + fonts_dir_path: None, + font_bytes: None, + load_system_fonts: true, + }; + App::new() .add_plugins(DefaultPlugins) - .add_plugins(CosmicEditPlugin) + .add_plugins(CosmicEditPlugin { font_config }) .add_systems(Startup, setup) - .add_systems(Update, change_active_editor) + .add_systems(Update, change_active_editor_ui) + .add_systems(Update, change_active_editor_sprite) .run(); } diff --git a/examples/multiple_sprites.rs b/examples/multiple_sprites.rs index e89bd86255da132a08f9942adefef741cf97c588..d71e6b5860fd5f6f7bdc762183dab7da544ed4c5 100644 --- a/examples/multiple_sprites.rs +++ b/examples/multiple_sprites.rs @@ -1,18 +1,16 @@ use bevy::{core_pipeline::clear_color::ClearColorConfig, prelude::*, window::PrimaryWindow}; +use bevy_cosmic_edit::change_active_editor_sprite; +use bevy_cosmic_edit::change_active_editor_ui; use bevy_cosmic_edit::{ - create_cosmic_font_system, spawn_cosmic_edit, ActiveEditor, CosmicEdit, CosmicEditMeta, - CosmicEditPlugin, CosmicEditSprite, CosmicFont, CosmicFontConfig, CosmicMetrics, CosmicNode, - CosmicText, CosmicTextPos, + ActiveEditor, CosmicAttrs, CosmicEditPlugin, CosmicEditSpriteBundle, CosmicFontConfig, + CosmicFontSystem, CosmicMetrics, CosmicText, CosmicTextPosition, }; use cosmic_text::AttrsOwned; -#[derive(Component)] -pub struct MainCamera; - fn setup( mut commands: Commands, windows: Query<&Window, With<PrimaryWindow>>, - mut cosmic_fonts: ResMut<Assets<CosmicFont>>, + mut font_system: ResMut<CosmicFontSystem>, ) { let primary_window = windows.single(); let camera_bundle = Camera2dBundle { @@ -21,15 +19,8 @@ fn setup( }, ..default() }; - commands.spawn((camera_bundle, MainCamera)); - let font_bytes: &[u8] = include_bytes!("../assets/fonts/VictorMono-Regular.ttf"); - let cosmic_font_config = CosmicFontConfig { - fonts_dir_path: None, - font_bytes: Some(vec![font_bytes]), - load_system_fonts: true, - }; - let font_system = create_cosmic_font_system(cosmic_font_config); - let font_system_handle = cosmic_fonts.add(CosmicFont(font_system)); + commands.spawn(camera_bundle); + let mut attrs = cosmic_text::Attrs::new(); attrs = attrs.family(cosmic_text::Family::Name("Victor Mono")); attrs = attrs.color(cosmic_text::Color::rgb(0x94, 0x00, 0xD3)); @@ -38,111 +29,73 @@ fn setup( line_height: 18., scale_factor: primary_window.scale_factor() as f32, }; - let cosmic_edit_meta = CosmicEditMeta { - text: CosmicText::OneStyle("馃榾馃榾馃榾 x => y".to_string()), - attrs: AttrsOwned::new(attrs), - text_pos: CosmicTextPos::Center, - metrics: metrics.clone(), - font_system_handle: font_system_handle.clone(), - node: CosmicNode::Sprite(CosmicEditSprite { - transform: Transform { - translation: Vec3::new(-primary_window.width() / 4., 0., 1.), - ..default() - }, - }), - size: Some((primary_window.width() / 2., primary_window.height())), - bg: Color::WHITE, - readonly: false, - bg_image: None, - }; - let cosmic_edit_1 = spawn_cosmic_edit(&mut commands, &mut cosmic_fonts, cosmic_edit_meta); - let cosmic_edit_meta = CosmicEditMeta { - text: CosmicText::OneStyle("Widget_2. Click on me".to_string()), - attrs: AttrsOwned::new(attrs), - text_pos: CosmicTextPos::Center, - metrics: metrics.clone(), - font_system_handle: font_system_handle.clone(), - node: CosmicNode::Sprite(CosmicEditSprite { - transform: Transform { - translation: Vec3::new( - primary_window.width() / 4., - -primary_window.height() / 4., - 1., - ), - ..default() - }, - }), - size: Some((primary_window.width() / 2., primary_window.height() / 2.)), - bg: Color::GRAY.with_a(0.5), - readonly: false, - bg_image: None, - }; - let _ = spawn_cosmic_edit(&mut commands, &mut cosmic_fonts, cosmic_edit_meta); - let cosmic_edit_meta = CosmicEditMeta { - text: CosmicText::OneStyle("Widget_3. Click on me".to_string()), - attrs: AttrsOwned::new(attrs), - text_pos: CosmicTextPos::Center, - metrics: metrics.clone(), - font_system_handle: font_system_handle.clone(), - node: CosmicNode::Sprite(CosmicEditSprite { - transform: Transform { - translation: Vec3::new( - primary_window.width() / 4., - primary_window.height() / 4., - 1., - ), - ..default() - }, - }), - size: Some((primary_window.width() / 2., primary_window.height() / 2.)), - bg: Color::GRAY.with_a(0.8), - readonly: false, - bg_image: None, - }; - let _ = spawn_cosmic_edit(&mut commands, &mut cosmic_fonts, cosmic_edit_meta); - commands.insert_resource(ActiveEditor { - entity: Some(cosmic_edit_1), - }); -} -fn change_active_editor( - mut commands: Commands, - windows: Query<&Window, With<PrimaryWindow>>, - buttons: Res<Input<MouseButton>>, - mut cosmic_edit_query: Query<(&mut CosmicEdit, &GlobalTransform, Entity), With<CosmicEdit>>, - camera_q: Query<(&Camera, &GlobalTransform), With<MainCamera>>, -) { - let window = windows.single(); - let (camera, camera_transform) = camera_q.single(); - if buttons.just_pressed(MouseButton::Left) { - for (cosmic_edit, node_transform, entity) in &mut cosmic_edit_query.iter_mut() { - let size = (cosmic_edit.width, cosmic_edit.height); - let x_min = node_transform.affine().translation.x - size.0 / 2.; - let y_min = node_transform.affine().translation.y - size.1 / 2.; - let x_max = node_transform.affine().translation.x + size.0 / 2.; - let y_max = node_transform.affine().translation.y + size.1 / 2.; - window.cursor_position().and_then(|pos| { - if let Some(pos) = camera.viewport_to_world_2d(camera_transform, pos) { - Some({ - if x_min < pos.x && pos.x < x_max && y_min < pos.y && pos.y < y_max { - commands.insert_resource(ActiveEditor { - entity: Some(entity), - }); - }; - }) - } else { - None - } - }); - } + let cosmic_edit_1 = CosmicEditSpriteBundle { + cosmic_attrs: CosmicAttrs(AttrsOwned::new(attrs)), + cosmic_metrics: metrics.clone(), + sprite: Sprite { + custom_size: Some(Vec2 { + x: primary_window.width() / 2., + y: primary_window.height(), + }), + ..default() + }, + transform: Transform::from_translation(Vec3::new(-primary_window.width() / 4., 0., 1.)), + text_position: CosmicTextPosition::Center, + background_color: BackgroundColor(Color::ALICE_BLUE), + ..default() } + .set_text( + CosmicText::OneStyle("馃榾馃榾馃榾 x => y".to_string()), + AttrsOwned::new(attrs), + &mut font_system.0, + ); + + let cosmic_edit_2 = CosmicEditSpriteBundle { + cosmic_attrs: CosmicAttrs(AttrsOwned::new(attrs)), + cosmic_metrics: metrics, + sprite: Sprite { + custom_size: Some(Vec2 { + x: primary_window.width() / 2., + y: primary_window.height() / 2., + }), + ..default() + }, + transform: Transform::from_translation(Vec3::new( + primary_window.width() / 4., + -primary_window.height() / 4., + 1., + )), + text_position: CosmicTextPosition::Center, + background_color: BackgroundColor(Color::GRAY.with_a(0.5)), + ..default() + } + .set_text( + CosmicText::OneStyle("Widget_2. Click on me".to_string()), + AttrsOwned::new(attrs), + &mut font_system.0, + ); + + let id = commands.spawn(cosmic_edit_1).id(); + + commands.insert_resource(ActiveEditor { entity: Some(id) }); + + commands.spawn(cosmic_edit_2); } fn main() { + let font_bytes: &[u8] = include_bytes!("../assets/fonts/VictorMono-Regular.ttf"); + let font_config = CosmicFontConfig { + fonts_dir_path: None, + font_bytes: Some(vec![font_bytes]), + load_system_fonts: true, + }; + App::new() .add_plugins(DefaultPlugins) - .add_plugins(CosmicEditPlugin) + .add_plugins(CosmicEditPlugin { font_config }) .add_systems(Startup, setup) - .add_systems(Update, change_active_editor) + .add_systems(Update, change_active_editor_ui) + .add_systems(Update, change_active_editor_sprite) .run(); } diff --git a/examples/readonly.rs b/examples/readonly.rs index 7a9a79a40a55aa0a28ebec14165035dc5114471a..049d0431538cbb9aae633559d2838bd04c06b32c 100644 --- a/examples/readonly.rs +++ b/examples/readonly.rs @@ -1,14 +1,14 @@ use bevy::{prelude::*, window::PrimaryWindow}; use bevy_cosmic_edit::{ - create_cosmic_font_system, spawn_cosmic_edit, ActiveEditor, CosmicEditMeta, CosmicEditPlugin, - CosmicFont, CosmicFontConfig, CosmicMetrics, CosmicNode, CosmicText, CosmicTextPos, + ActiveEditor, CosmicAttrs, CosmicEditPlugin, CosmicEditUiBundle, CosmicFontConfig, + CosmicFontSystem, CosmicMetrics, CosmicText, CosmicTextPosition, ReadOnly, }; use cosmic_text::AttrsOwned; fn setup( mut commands: Commands, windows: Query<&Window, With<PrimaryWindow>>, - mut cosmic_fonts: ResMut<Assets<CosmicFont>>, + mut font_system: ResMut<CosmicFontSystem>, ) { let primary_window = windows.single(); commands.spawn(Camera2dBundle::default()); @@ -23,45 +23,54 @@ fn setup( ..default() }) .id(); - let font_bytes: &[u8] = include_bytes!("../assets/fonts/VictorMono-Regular.ttf"); - let cosmic_font_config = CosmicFontConfig { - fonts_dir_path: None, - font_bytes: Some(vec![font_bytes]), - load_system_fonts: true, - }; - let font_system = create_cosmic_font_system(cosmic_font_config); - let font_system_handle = cosmic_fonts.add(CosmicFont(font_system)); let mut attrs = cosmic_text::Attrs::new(); attrs = attrs.family(cosmic_text::Family::Name("Victor Mono")); attrs = attrs.color(cosmic_text::Color::rgb(0x94, 0x00, 0xD3)); - let cosmic_edit_meta = CosmicEditMeta { - text: CosmicText::OneStyle("馃榾馃榾馃榾 x => y\nRead only widget".to_string()), - attrs: AttrsOwned::new(attrs), - text_pos: CosmicTextPos::Center, - bg: Color::WHITE, - metrics: CosmicMetrics { + + let cosmic_edit = CosmicEditUiBundle { + style: Style { + width: Val::Percent(100.), + height: Val::Percent(100.), + ..default() + }, + cosmic_attrs: CosmicAttrs(AttrsOwned::new(attrs)), + text_position: CosmicTextPosition::Center, + background_color: BackgroundColor(Color::WHITE), + cosmic_metrics: CosmicMetrics { font_size: 14., line_height: 18., scale_factor: primary_window.scale_factor() as f32, }, - font_system_handle, - node: CosmicNode::Ui, - size: None, - readonly: true, - bg_image: None, - }; - let cosmic_edit = spawn_cosmic_edit(&mut commands, &mut cosmic_fonts, cosmic_edit_meta); - commands.entity(root).add_child(cosmic_edit); - commands.insert_resource(ActiveEditor { - entity: Some(cosmic_edit), + ..default() + } + .set_text( + CosmicText::OneStyle("馃榾馃榾馃榾 x => y\nRead only widget".to_string()), + AttrsOwned::new(attrs), + &mut font_system.0, + ); + + let mut id = None; + // Spawn the CosmicEditUiBundle as a child of root + commands.entity(root).with_children(|parent| { + id = Some(parent.spawn(cosmic_edit).insert(ReadOnly).id()); }); + + // Set active editor + commands.insert_resource(ActiveEditor { entity: id }); } fn main() { + let font_bytes: &[u8] = include_bytes!("../assets/fonts/VictorMono-Regular.ttf"); + let font_config = CosmicFontConfig { + fonts_dir_path: None, + font_bytes: Some(vec![font_bytes]), + load_system_fonts: true, + }; + App::new() .add_plugins(DefaultPlugins) - .add_plugins(CosmicEditPlugin) + .add_plugins(CosmicEditPlugin { font_config }) .add_systems(Startup, setup) .run(); } diff --git a/src/lib.rs b/src/lib.rs index 0c6bfb853f9ff75a63ecc5019be74572b2b83747..164865c3e40d4b753a54e5db081766746a1a76f4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +#![allow(clippy::type_complexity)] + use std::{collections::VecDeque, path::PathBuf, time::Duration}; #[path = "utils.rs"] pub mod utils; @@ -7,63 +9,351 @@ use bevy::{ asset::HandleId, input::mouse::{MouseScrollUnit, MouseWheel}, prelude::*, - reflect::{TypePath, TypeUuid}, - render::render_resource::Extent3d, + render::{render_resource::Extent3d, texture::DEFAULT_IMAGE_HANDLE}, + ui::FocusPolicy, window::{PrimaryWindow, WindowScaleFactorChanged}, }; use cosmic_text::{ - Action, AttrsList, AttrsOwned, Buffer, BufferLine, Cursor, Edit, Editor, FontSystem, Metrics, - Shaping, SwashCache, + Action, Attrs, AttrsList, AttrsOwned, Buffer, BufferLine, Cursor, Edit, Editor, FontSystem, + Metrics, Shaping, SwashCache, }; use image::{imageops::FilterType, GenericImageView}; -#[derive(Clone)] -pub struct CosmicEditUi; - -#[derive(Clone)] -pub struct CosmicEditSprite { - pub transform: Transform, -} - -#[derive(Clone)] -pub enum CosmicNode { - Ui, - Sprite(CosmicEditSprite), -} - #[derive(Clone)] pub enum CosmicText { OneStyle(String), - MultiStyle(Vec<Vec<(String, cosmic_text::AttrsOwned)>>), + MultiStyle(Vec<Vec<(String, AttrsOwned)>>), } -#[derive(Clone)] +/// Enum representing the position of the cosmic text. +#[derive(Clone, Component, Default)] +pub enum CosmicTextPosition { + #[default] + Center, + TopLeft, +} + +#[derive(Clone, Component)] pub struct CosmicMetrics { pub font_size: f32, pub line_height: f32, pub scale_factor: f32, } -/// Contains metadata for spawning cosmic edit, including text content, position, size, and style. -#[derive(Clone)] -pub struct CosmicEditMeta { - pub text: CosmicText, - pub text_pos: CosmicTextPos, - pub attrs: cosmic_text::AttrsOwned, - pub metrics: CosmicMetrics, - pub font_system_handle: Handle<CosmicFont>, - pub size: Option<(f32, f32)>, // None used for bevy-ui nodes to use parent size - pub node: CosmicNode, - pub bg: bevy::prelude::Color, - pub bg_image: Option<Handle<Image>>, - pub readonly: bool, +impl Default for CosmicMetrics { + fn default() -> Self { + Self { + font_size: 12., + line_height: 12., + scale_factor: 1., + } + } } -/// Enum representing the position of the cosmic text. -#[derive(Clone)] -pub enum CosmicTextPos { - Center, - TopLeft, +#[derive(Resource)] +pub struct CosmicFontSystem(pub FontSystem); + +#[derive(Component)] +pub struct ReadOnly; // tag component + +#[derive(Component)] +pub struct CosmicEditor(pub Editor); + +impl Default for CosmicEditor { + fn default() -> Self { + Self(Editor::new(Buffer::new_empty(Metrics::new(12., 14.)))) + } +} + +impl CosmicEditor { + pub fn set_text( + &mut self, + text: CosmicText, + attrs: AttrsOwned, + // i'd like to get the font system + attrs automagically but i'm too 3head -bytemunch + font_system: &mut FontSystem, + ) -> &mut Self { + let editor = &mut self.0; + editor.buffer_mut().lines.clear(); + match text { + CosmicText::OneStyle(text) => { + editor.buffer_mut().set_text( + font_system, + text.as_str(), + attrs.as_attrs(), + Shaping::Advanced, + ); + } + CosmicText::MultiStyle(lines) => { + for line in lines { + let mut line_text = String::new(); + let mut attrs_list = AttrsList::new(attrs.as_attrs()); + for (text, attrs) in line.iter() { + let start = line_text.len(); + line_text.push_str(text); + let end = line_text.len(); + attrs_list.add_span(start..end, attrs.as_attrs()); + } + editor.buffer_mut().lines.push(BufferLine::new( + line_text, + attrs_list, + Shaping::Advanced, + )); + } + } + } + self + } + + /// Retrieves the cosmic text content from an editor. + /// + /// # Arguments + /// + /// * none, takes the rust magic ref to self + /// + /// # Returns + /// + /// A `String` containing the cosmic text content. + pub fn get_text(&self) -> String { + let buffer = self.0.buffer(); + let mut text = String::new(); + let line_count = buffer.lines.len(); + + for (i, line) in buffer.lines.iter().enumerate() { + text.push_str(line.text()); + + if i < line_count - 1 { + text.push('\n'); + } + } + + text + } +} + +/// Adds the font system to each editor when added +fn cosmic_editor_builder( + mut added_editors: Query< + ( + &mut CosmicEditor, + &CosmicAttrs, + &CosmicMetrics, + &BackgroundColor, + Option<&ReadOnly>, + Option<&Node>, + Option<&Sprite>, + ), + Added<CosmicEditor>, + >, + mut font_system: ResMut<CosmicFontSystem>, +) { + for (mut editor, attrs, metrics, background_color, readonly, node, sprite) in + added_editors.iter_mut() + { + // keep old text if set + let mut text = editor.get_text(); + + if text.is_empty() { + text = "".into(); + editor.0.buffer_mut().set_text( + &mut font_system.0, + text.as_str(), + attrs.0.as_attrs(), + Shaping::Advanced, + ); + } + + editor.0.buffer_mut().set_metrics( + &mut font_system.0, + Metrics::new(metrics.font_size, metrics.line_height).scale(metrics.scale_factor), + ); + + if let Some(node) = node { + editor + .0 + .buffer_mut() + .set_size(&mut font_system.0, node.size().x, node.size().y) + } + + if let Some(sprite) = sprite { + if let Some(size) = sprite.custom_size { + editor + .0 + .buffer_mut() + .set_size(&mut font_system.0, size.x, size.y) + } + } + + // hide cursor on readonly buffers + let mut cursor = editor.0.cursor(); + if readonly.is_some() { + cursor.color = Some(bevy_color_to_cosmic(background_color.0)); + } + editor.0.set_cursor(cursor); + } +} + +#[derive(Component)] +pub struct CosmicAttrs(pub AttrsOwned); + +impl Default for CosmicAttrs { + fn default() -> Self { + CosmicAttrs(AttrsOwned::new(Attrs::new())) + } +} + +#[derive(Component, Default)] +pub struct CosmicBackground(pub Option<Handle<Image>>); + +#[derive(Bundle)] +pub struct CosmicEditUiBundle { + // Bevy UI bits + /// Describes the logical size of the node + pub node: Node, + /// Marker component that signals this node is a button + pub button: Button, + /// Styles which control the layout (size and position) of the node and it's children + /// In some cases these styles also affect how the node drawn/painted. + pub style: Style, + /// Describes whether and how the button has been interacted with by the input + pub interaction: Interaction, + /// Whether this node should block interaction with lower nodes + pub focus_policy: FocusPolicy, + /// The background color, which serves as a "fill" for this node + pub background_color: BackgroundColor, + /// The color of the Node's border + pub border_color: BorderColor, + /// This is used as the cosmic text canvas + pub image: UiImage, + /// The transform of the node + /// + /// This field is automatically managed by the UI layout system. + /// To alter the position of the `NodeBundle`, use the properties of the [`Style`] component. + pub transform: Transform, + /// The global transform of the node + /// + /// This field is automatically managed by the UI layout system. + /// To alter the position of the `NodeBundle`, use the properties of the [`Style`] component. + pub global_transform: GlobalTransform, + /// Describes the visibility properties of the node + pub visibility: Visibility, + /// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering + pub computed_visibility: ComputedVisibility, + /// Indicates the depth at which the node should appear in the UI + pub z_index: ZIndex, + // cosmic bits + /// cosmic-text Editor, holds the text buffer + font system + pub editor: CosmicEditor, + /// text positioning enum + pub text_position: CosmicTextPosition, + /// text metrics + pub cosmic_metrics: CosmicMetrics, + /// edit history + pub cosmic_edit_history: CosmicEditHistory, + /// text attributes + pub cosmic_attrs: CosmicAttrs, + /// bg img + pub background_image: CosmicBackground, +} + +impl CosmicEditUiBundle { + pub fn set_text( + mut self, + text: CosmicText, + attrs: AttrsOwned, + font_system: &mut FontSystem, + ) -> Self { + self.editor.set_text(text, attrs, font_system); + self + } +} + +impl Default for CosmicEditUiBundle { + fn default() -> Self { + Self { + focus_policy: FocusPolicy::Block, + node: Default::default(), + button: Default::default(), + style: Default::default(), + border_color: BorderColor(Color::NONE), + interaction: Default::default(), + background_color: Default::default(), + image: Default::default(), + transform: Default::default(), + global_transform: Default::default(), + visibility: Default::default(), + computed_visibility: Default::default(), + z_index: 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(), + } + } +} + +#[derive(Bundle)] +pub struct CosmicEditSpriteBundle { + // Bevy Sprite Bits + pub sprite: Sprite, + pub transform: Transform, + pub global_transform: GlobalTransform, + pub texture: Handle<Image>, + /// User indication of whether an entity is visible + pub visibility: Visibility, + /// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering + pub computed_visibility: ComputedVisibility, + // + pub background_color: BackgroundColor, + // cosmic bits + /// cosmic-text Editor, holds the text buffer + font system + pub editor: CosmicEditor, + /// text positioning enum + pub text_position: CosmicTextPosition, + /// text metrics + pub cosmic_metrics: CosmicMetrics, + /// edit history + pub cosmic_edit_history: CosmicEditHistory, + /// text attributes + pub cosmic_attrs: CosmicAttrs, + /// bg img + pub background_image: CosmicBackground, +} + +impl CosmicEditSpriteBundle { + pub fn set_text( + mut self, + text: CosmicText, + attrs: AttrsOwned, + font_system: &mut FontSystem, + ) -> Self { + self.editor.set_text(text, attrs, font_system); + self + } +} + +impl Default for CosmicEditSpriteBundle { + fn default() -> Self { + Self { + sprite: Default::default(), + transform: Default::default(), + global_transform: Default::default(), + 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(), + } + } } #[derive(Clone)] @@ -72,54 +362,41 @@ pub struct EditHistoryItem { pub lines: Vec<Vec<(String, AttrsOwned)>>, } -#[derive(Component)] +#[derive(Component, Default)] pub struct CosmicEditHistory { pub edits: VecDeque<EditHistoryItem>, pub current_edit: usize, } -#[derive(Component)] -pub struct CosmicEdit { - pub editor: Editor, - pub font_system: Handle<CosmicFont>, - pub attrs: cosmic_text::AttrsOwned, - pub text_pos: CosmicTextPos, - pub bg: bevy::prelude::Color, - pub bg_image: Option<Handle<Image>>, - pub width: f32, - pub height: f32, - pub font_size: f32, - pub line_height: f32, - pub readonly: bool, - pub is_ui_node: bool, -} - -#[derive(TypeUuid, TypePath)] -#[uuid = "DC6A0357-7941-4ADE-9332-24EA87E38961"] -pub struct CosmicFont(pub FontSystem); - /// Plugin struct that adds systems and initializes resources related to cosmic edit functionality. -pub struct CosmicEditPlugin; +#[derive(Default)] +pub struct CosmicEditPlugin { + pub font_config: CosmicFontConfig, +} impl Plugin for CosmicEditPlugin { fn build(&self, app: &mut App) { - app.add_systems( - Update, - ( - cosmic_edit_bevy_events, - cosmic_edit_set_redraw, - on_scale_factor_change, - cosmic_edit_redraw_buffer_ui - .before(cosmic_edit_set_redraw) - .before(on_scale_factor_change), - cosmic_edit_redraw_buffer.before(on_scale_factor_change), - ), - ) - .init_resource::<ActiveEditor>() - .add_asset::<CosmicFont>() - .insert_resource(SwashCacheState { - swash_cache: SwashCache::new(), - }); + let font_system = create_cosmic_font_system(self.font_config.clone()); + + app.add_systems(PreUpdate, cosmic_editor_builder) + .add_systems( + Update, + ( + cosmic_edit_bevy_events, + cosmic_edit_set_redraw, + on_scale_factor_change, + cosmic_edit_redraw_buffer_ui + .before(cosmic_edit_set_redraw) + .before(on_scale_factor_change), + cosmic_edit_redraw_buffer.before(on_scale_factor_change), + ), + ) + .init_resource::<ActiveEditor>() + // .add_asset::<CosmicFont>() + .insert_resource(SwashCacheState { + swash_cache: SwashCache::new(), + }) + .insert_resource(CosmicFontSystem(font_system)); } } @@ -130,19 +407,29 @@ pub struct ActiveEditor { } /// Resource struct that holds configuration options for cosmic fonts. -#[derive(Resource, Default)] +#[derive(Resource, Clone)] pub struct CosmicFontConfig { pub fonts_dir_path: Option<PathBuf>, pub font_bytes: Option<Vec<&'static [u8]>>, pub load_system_fonts: bool, // caution: this can be relatively slow } +impl Default for CosmicFontConfig { + fn default() -> Self { + Self { + load_system_fonts: true, + font_bytes: None, + fonts_dir_path: None, + } + } +} + #[derive(Resource)] struct SwashCacheState { swash_cache: SwashCache, } -pub fn create_cosmic_font_system(cosmic_font_config: CosmicFontConfig) -> FontSystem { +fn create_cosmic_font_system(cosmic_font_config: CosmicFontConfig) -> FontSystem { let locale = sys_locale::get_locale().unwrap_or_else(|| String::from("en-US")); let mut db = cosmic_text::fontdb::Database::new(); if let Some(dir_path) = cosmic_font_config.fonts_dir_path.clone() { @@ -161,22 +448,18 @@ pub fn create_cosmic_font_system(cosmic_font_config: CosmicFontConfig) -> FontSy fn on_scale_factor_change( mut scale_factor_changed: EventReader<WindowScaleFactorChanged>, - mut cosmic_edit_query: Query<&mut CosmicEdit, With<CosmicEdit>>, - mut cosmic_fonts: ResMut<Assets<CosmicFont>>, + mut cosmic_query: Query<(&mut CosmicEditor, &mut CosmicMetrics)>, + mut font_system: ResMut<CosmicFontSystem>, ) { if !scale_factor_changed.is_empty() { let new_scale_factor = scale_factor_changed.iter().last().unwrap().scale_factor as f32; - for mut cosmic_edit in &mut cosmic_edit_query.iter_mut() { - if let Some(font_system) = cosmic_fonts.get_mut(&cosmic_edit.font_system) { - let font_system = &mut font_system.0; - let metrics = Metrics::new(cosmic_edit.font_size, cosmic_edit.line_height) - .scale(new_scale_factor); - cosmic_edit - .editor - .buffer_mut() - .set_metrics(font_system, metrics); - cosmic_edit.editor.buffer_mut().set_redraw(true); - } + for (mut editor, metrics) in &mut cosmic_query.iter_mut() { + let font_system = &mut font_system.0; + let metrics = + Metrics::new(metrics.font_size, metrics.line_height).scale(new_scale_factor); + + editor.0.buffer_mut().set_metrics(font_system, metrics); + editor.0.buffer_mut().set_redraw(true); } } } @@ -217,30 +500,7 @@ pub fn get_node_cursor_pos( }) } -/// Retrieves the cosmic text content from an editor. -/// -/// # Arguments -/// -/// * `editor` - A reference to the `Editor` instance containing the text content. -/// -/// # Returns -/// -/// A `String` containing the cosmic text content. -pub fn get_cosmic_text(buffer: &Buffer) -> String { - let mut text = String::new(); - let line_count = buffer.lines.len(); - - for (i, line) in buffer.lines.iter().enumerate() { - text.push_str(line.text()); - - if i < line_count - 1 { - text.push('\n'); - } - } - - text -} - +/// Returns texts from a MultiStyle buffer pub fn get_text_spans( buffer: &Buffer, default_attrs: AttrsOwned, @@ -279,9 +539,13 @@ pub fn get_text_spans( spans } -fn save_edit_history(cosmic_edit: &mut CosmicEdit, edit_history: &mut CosmicEditHistory) { +fn save_edit_history( + editor: &mut Editor, + attrs: &AttrsOwned, + edit_history: &mut CosmicEditHistory, +) { let edits = &edit_history.edits; - let current_lines = get_text_spans(cosmic_edit.editor.buffer(), cosmic_edit.attrs.clone()); + let current_lines = get_text_spans(editor.buffer(), attrs.clone()); let current_edit = edit_history.current_edit; let mut new_edits = VecDeque::new(); new_edits.extend(edits.iter().take(current_edit + 1).cloned()); @@ -290,7 +554,7 @@ fn save_edit_history(cosmic_edit: &mut CosmicEdit, edit_history: &mut CosmicEdit new_edits.drain(0..100); } new_edits.push_back(EditHistoryItem { - cursor: cosmic_edit.editor.cursor(), + cursor: editor.cursor(), lines: current_lines, }); let len = new_edits.len(); @@ -300,7 +564,7 @@ fn save_edit_history(cosmic_edit: &mut CosmicEdit, edit_history: &mut CosmicEdit }; } -fn bevy_color_to_cosmic(color: bevy::prelude::Color) -> cosmic_text::Color { +pub fn bevy_color_to_cosmic(color: bevy::prelude::Color) -> cosmic_text::Color { cosmic_text::Color::rgba( (color.r() * 255.) as u8, (color.g() * 255.) as u8, @@ -328,6 +592,8 @@ pub fn get_x_offset(buffer: &Buffer) -> i32 { ((buffer.size().0 - text_width) / 2.0) as i32 } +#[allow(clippy::too_many_arguments, clippy::type_complexity)] +// the meat of the input management pub fn cosmic_edit_bevy_events( windows: Query<&Window, With<PrimaryWindow>>, active_editor: Res<ActiveEditor>, @@ -336,15 +602,20 @@ pub fn cosmic_edit_bevy_events( buttons: Res<Input<MouseButton>>, mut cosmic_edit_query: Query< ( - &mut CosmicEdit, + &mut CosmicEditor, &mut CosmicEditHistory, &GlobalTransform, + &CosmicAttrs, + &CosmicTextPosition, Entity, ), - With<CosmicEdit>, + With<CosmicEditor>, >, + readonly_query: Query<&ReadOnly>, + node_query: Query<&mut Node>, + sprite_query: Query<&mut Sprite>, + mut font_system: ResMut<CosmicFontSystem>, mut is_deleting: Local<bool>, - mut font_system_assets: ResMut<Assets<CosmicFont>>, mut scroll_evr: EventReader<MouseWheel>, mut edits_duration: Local<Option<Duration>>, mut undoredo_duration: Local<Option<Duration>>, @@ -353,437 +624,396 @@ 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 cosmic_edit, mut edit_history, node_transform, entity) in + for (mut editor, mut edit_history, node_transform, attrs, text_position, entity) in &mut cosmic_edit_query.iter_mut() { + let readonly = readonly_query.get(entity).is_ok(); + + let (width, height, is_ui_node) = match node_query.get(entity) { + Ok(node) => (node.size().x, node.size().y, true), + Err(_) => { + let sprite = sprite_query.get(entity).unwrap(); + let size = sprite.custom_size.unwrap(); + (size.x, size.y, false) + } + }; + + let editor = &mut editor.0; + let attrs = &attrs.0; + if active_editor.entity == Some(entity) { - if let Some(font_system) = font_system_assets.get_mut(&cosmic_edit.font_system) { - let now_ms = get_timestamp(); + let now_ms = get_timestamp(); - #[cfg(target_os = "macos")] - let command = keys.any_pressed([KeyCode::SuperLeft, KeyCode::SuperRight]); + #[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(not(target_os = "macos"))] + let command = keys.any_pressed([KeyCode::ControlLeft, KeyCode::ControlRight]); - let shift = keys.any_pressed([KeyCode::ShiftLeft, KeyCode::ShiftRight]); + let shift = keys.any_pressed([KeyCode::ShiftLeft, KeyCode::ShiftRight]); - #[cfg(target_os = "macos")] - let option = keys.any_pressed([KeyCode::AltLeft, KeyCode::AltRight]); + #[cfg(target_os = "macos")] + let option = keys.any_pressed([KeyCode::AltLeft, KeyCode::AltRight]); - // if shift key is pressed - let already_has_selection = cosmic_edit.editor.select_opt().is_some(); - if shift && !already_has_selection { - let cursor = cosmic_edit.editor.cursor(); - cosmic_edit.editor.set_select_opt(Some(cursor)); - } + // if shift key is pressed + let already_has_selection = editor.select_opt().is_some(); + if shift && !already_has_selection { + let cursor = editor.cursor(); + editor.set_select_opt(Some(cursor)); + } - #[cfg(target_os = "macos")] - let should_jump = command && option; - #[cfg(not(target_os = "macos"))] - let should_jump = command; - - if should_jump && keys.just_pressed(KeyCode::Left) { - cosmic_edit - .editor - .action(&mut font_system.0, Action::PreviousWord); - if !shift { - cosmic_edit.editor.set_select_opt(None); - } - // RETURN - return; + #[cfg(target_os = "macos")] + let should_jump = command && option; + #[cfg(not(target_os = "macos"))] + let should_jump = command; + + if should_jump && keys.just_pressed(KeyCode::Left) { + editor.action(&mut font_system.0, Action::PreviousWord); + if !shift { + editor.set_select_opt(None); } - if should_jump && keys.just_pressed(KeyCode::Right) { - cosmic_edit - .editor - .action(&mut font_system.0, Action::NextWord); - if !shift { - cosmic_edit.editor.set_select_opt(None); - } - // RETURN - return; + return; + } + if should_jump && keys.just_pressed(KeyCode::Right) { + editor.action(&mut font_system.0, Action::NextWord); + if !shift { + editor.set_select_opt(None); } - if should_jump && keys.just_pressed(KeyCode::Home) { - cosmic_edit - .editor - .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 - cosmic_edit.editor.buffer_mut().set_redraw(true); - if !shift { - cosmic_edit.editor.set_select_opt(None); - } - // RETURN - return; + return; + } + if should_jump && keys.just_pressed(KeyCode::Home) { + editor.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); + if !shift { + editor.set_select_opt(None); } - if should_jump && keys.just_pressed(KeyCode::End) { - cosmic_edit - .editor - .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 - cosmic_edit.editor.buffer_mut().set_redraw(true); - if !shift { - cosmic_edit.editor.set_select_opt(None); - } - // RETURN - return; + return; + } + if should_jump && keys.just_pressed(KeyCode::End) { + editor.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); + if !shift { + editor.set_select_opt(None); } + return; + } - if keys.just_pressed(KeyCode::Left) { - cosmic_edit.editor.action(&mut font_system.0, Action::Left); - if !shift { - cosmic_edit.editor.set_select_opt(None); - } - // RETURN - return; + if keys.just_pressed(KeyCode::Left) { + editor.action(&mut font_system.0, Action::Left); + if !shift { + editor.set_select_opt(None); } - if keys.just_pressed(KeyCode::Right) { - cosmic_edit.editor.action(&mut font_system.0, Action::Right); - if !shift { - cosmic_edit.editor.set_select_opt(None); - } - // RETURN - return; + return; + } + if keys.just_pressed(KeyCode::Right) { + editor.action(&mut font_system.0, Action::Right); + if !shift { + editor.set_select_opt(None); } - if keys.just_pressed(KeyCode::Up) { - cosmic_edit.editor.action(&mut font_system.0, Action::Up); - if !shift { - cosmic_edit.editor.set_select_opt(None); - } - // RETURN - return; + return; + } + if keys.just_pressed(KeyCode::Up) { + editor.action(&mut font_system.0, Action::Up); + if !shift { + editor.set_select_opt(None); } - if keys.just_pressed(KeyCode::Down) { - cosmic_edit.editor.action(&mut font_system.0, Action::Down); - if !shift { - cosmic_edit.editor.set_select_opt(None); - } - // RETURN - return; + return; + } + if keys.just_pressed(KeyCode::Down) { + editor.action(&mut font_system.0, Action::Down); + if !shift { + editor.set_select_opt(None); } - if !cosmic_edit.readonly && keys.just_pressed(KeyCode::Back) { - #[cfg(target_arch = "wasm32")] - cosmic_edit - .editor - .action(&mut font_system.0, Action::Backspace); - *is_deleting = true; + return; + } + + if !readonly && keys.just_pressed(KeyCode::Back) { + #[cfg(target_arch = "wasm32")] + editor.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); + } + if keys.just_pressed(KeyCode::Escape) { + editor.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 { + line: 0, + index: 0, + affinity: current_cursor.affinity, + color: current_cursor.color, + })); + return; + } + if keys.just_pressed(KeyCode::Home) { + editor.action(&mut font_system.0, Action::Home); + if !shift { + editor.set_select_opt(None); } - if !cosmic_edit.readonly && keys.just_released(KeyCode::Back) { - *is_deleting = false; + return; + } + if keys.just_pressed(KeyCode::End) { + editor.action(&mut font_system.0, Action::End); + if !shift { + editor.set_select_opt(None); } - if !cosmic_edit.readonly && keys.just_pressed(KeyCode::Delete) { - cosmic_edit - .editor - .action(&mut font_system.0, Action::Delete); + return; + } + if keys.just_pressed(KeyCode::PageUp) { + editor.action(&mut font_system.0, Action::PageUp); + if !shift { + editor.set_select_opt(None); } - if keys.just_pressed(KeyCode::Escape) { - cosmic_edit - .editor - .action(&mut font_system.0, Action::Escape); + return; + } + if keys.just_pressed(KeyCode::PageDown) { + editor.action(&mut font_system.0, Action::PageDown); + if !shift { + editor.set_select_opt(None); } - if command && keys.just_pressed(KeyCode::A) { - cosmic_edit - .editor - .action(&mut font_system.0, Action::BufferEnd); - let current_cursor = cosmic_edit.editor.cursor(); - cosmic_edit.editor.set_select_opt(Some(Cursor { - line: 0, - index: 0, - affinity: current_cursor.affinity, - color: current_cursor.color, - })); - // RETURN + return; + } + + // redo + #[cfg(not(target_os = "windows"))] + let requested_redo = command && shift && keys.just_pressed(KeyCode::Z); + #[cfg(target_os = "windows")] + let requested_redo = command && keys.just_pressed(KeyCode::Y); + + if !readonly && requested_redo { + let edits = &edit_history.edits; + if edits.is_empty() { return; } - if keys.just_pressed(KeyCode::Home) { - cosmic_edit.editor.action(&mut font_system.0, Action::Home); - if !shift { - cosmic_edit.editor.set_select_opt(None); - } - // RETURN + if edit_history.current_edit == edits.len() - 1 { return; } - if keys.just_pressed(KeyCode::End) { - cosmic_edit.editor.action(&mut font_system.0, Action::End); - if !shift { - cosmic_edit.editor.set_select_opt(None); + let idx = edit_history.current_edit + 1; + if let Some(current_edit) = edits.get(idx) { + editor.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()); + for (text, attrs) in line.iter() { + let start = line_text.len(); + line_text.push_str(text); + let end = line_text.len(); + attrs_list.add_span(start..end, attrs.as_attrs()); + } + editor.buffer_mut().lines.push(BufferLine::new( + line_text, + attrs_list, + Shaping::Advanced, + )); } - // RETURN - return; + editor.set_cursor(current_edit.cursor); + editor.buffer_mut().set_redraw(true); + edit_history.current_edit += 1; } - if keys.just_pressed(KeyCode::PageUp) { - cosmic_edit - .editor - .action(&mut font_system.0, Action::PageUp); - if !shift { - cosmic_edit.editor.set_select_opt(None); - } - // RETURN + *undoredo_duration = Some(Duration::from_millis(now_ms as u64)); + return; + } + // undo + let requested_undo = command && keys.just_pressed(KeyCode::Z); + + if !readonly && requested_undo { + let edits = &edit_history.edits; + if edits.is_empty() { return; } - if keys.just_pressed(KeyCode::PageDown) { - cosmic_edit - .editor - .action(&mut font_system.0, Action::PageDown); - if !shift { - cosmic_edit.editor.set_select_opt(None); - } - // RETURN + if edit_history.current_edit <= 1 { return; } - - // redo - #[cfg(not(target_os = "windows"))] - let requested_redo = command && shift && keys.just_pressed(KeyCode::Z); - #[cfg(target_os = "windows")] - let requested_redo = command && keys.just_pressed(KeyCode::Y); - - if !cosmic_edit.readonly && requested_redo { - let edits = &edit_history.edits; - if edits.is_empty() { - // RETURN - return; - } - if edit_history.current_edit == edits.len() - 1 { - // RETURN - return; - } - let idx = edit_history.current_edit + 1; - if let Some(current_edit) = edits.get(idx) { - cosmic_edit.editor.buffer_mut().lines.clear(); - for line in current_edit.lines.iter() { - let mut line_text = String::new(); - let mut attrs_list = AttrsList::new(cosmic_edit.attrs.as_attrs()); - for (text, attrs) in line.iter() { - let start = line_text.len(); - line_text.push_str(text); - let end = line_text.len(); - attrs_list.add_span(start..end, attrs.as_attrs()); - } - cosmic_edit.editor.buffer_mut().lines.push(BufferLine::new( - line_text, - attrs_list, - Shaping::Advanced, - )); + let idx = edit_history.current_edit - 1; + if let Some(current_edit) = edits.get(idx) { + editor.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()); + for (text, attrs) in line.iter() { + let start = line_text.len(); + line_text.push_str(text); + let end = line_text.len(); + attrs_list.add_span(start..end, attrs.as_attrs()); } - cosmic_edit.editor.set_cursor(current_edit.cursor); - cosmic_edit.editor.buffer_mut().set_redraw(true); - edit_history.current_edit += 1; + editor.buffer_mut().lines.push(BufferLine::new( + line_text, + attrs_list, + Shaping::Advanced, + )); } - *undoredo_duration = Some(Duration::from_millis(now_ms as u64)); - // RETURN - return; + editor.set_cursor(current_edit.cursor); + editor.buffer_mut().set_redraw(true); + edit_history.current_edit -= 1; } - // undo - let requested_undo = command && keys.just_pressed(KeyCode::Z); - - if !cosmic_edit.readonly && requested_undo { - let edits = &edit_history.edits; - if edits.is_empty() { - // RETURN - return; + *undoredo_duration = Some(Duration::from_millis(now_ms as u64)); + return; + } + + let mut is_clipboard = false; + #[cfg(not(target_arch = "wasm32"))] + { + if let Ok(mut clipboard) = arboard::Clipboard::new() { + if command && keys.just_pressed(KeyCode::C) { + if let Some(text) = editor.copy_selection() { + clipboard.set_text(text).unwrap(); + return; + } } - if edit_history.current_edit <= 1 { - // RETURN - return; + if !readonly && command && keys.just_pressed(KeyCode::X) { + if let Some(text) = editor.copy_selection() { + clipboard.set_text(text).unwrap(); + editor.delete_selection(); + } + is_clipboard = true; } - let idx = edit_history.current_edit - 1; - if let Some(current_edit) = edits.get(idx) { - cosmic_edit.editor.buffer_mut().lines.clear(); - for line in current_edit.lines.iter() { - let mut line_text = String::new(); - let mut attrs_list = AttrsList::new(cosmic_edit.attrs.as_attrs()); - for (text, attrs) in line.iter() { - let start = line_text.len(); - line_text.push_str(text); - let end = line_text.len(); - attrs_list.add_span(start..end, attrs.as_attrs()); + 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)); } - cosmic_edit.editor.buffer_mut().lines.push(BufferLine::new( - line_text, - attrs_list, - Shaping::Advanced, - )); } - cosmic_edit.editor.set_cursor(current_edit.cursor); - cosmic_edit.editor.buffer_mut().set_redraw(true); - edit_history.current_edit -= 1; + is_clipboard = true; } - *undoredo_duration = Some(Duration::from_millis(now_ms as u64)); - // RETURN - return; } + } + let (offset_x, offset_y) = match text_position { + CosmicTextPosition::Center => { + (get_x_offset(editor.buffer()), get_y_offset(editor.buffer())) + } + CosmicTextPosition::TopLeft => (0, 0), + }; + let point = |node_cursor_pos: (f32, f32)| { + ( + (node_cursor_pos.0 * scale_factor) as i32 - offset_x, + (node_cursor_pos.1 * scale_factor) as i32 - offset_y, + ) + }; - let mut is_clipboard = false; - #[cfg(not(target_arch = "wasm32"))] - { - if let Ok(mut clipboard) = arboard::Clipboard::new() { - if command && keys.just_pressed(KeyCode::C) { - if let Some(text) = cosmic_edit.editor.copy_selection() { - clipboard.set_text(text).unwrap(); - // RETURN - return; - } - } - if !cosmic_edit.readonly && command && keys.just_pressed(KeyCode::X) { - if let Some(text) = cosmic_edit.editor.copy_selection() { - clipboard.set_text(text).unwrap(); - cosmic_edit.editor.delete_selection(); - } - is_clipboard = true; - } - if !cosmic_edit.readonly && command && keys.just_pressed(KeyCode::V) { - if let Ok(text) = clipboard.get_text() { - for c in text.chars() { - cosmic_edit - .editor - .action(&mut font_system.0, Action::Insert(c)); - } - } - is_clipboard = true; - } + if buttons.just_pressed(MouseButton::Left) { + if let Some(node_cursor_pos) = get_node_cursor_pos( + primary_window, + node_transform, + (width, height), + is_ui_node, + camera, + camera_transform, + ) { + let (x, y) = point(node_cursor_pos); + if shift { + editor.action(&mut font_system.0, Action::Drag { x, y }); + } else { + editor.action(&mut font_system.0, Action::Click { x, y }); } } - let (offset_x, offset_y) = match cosmic_edit.text_pos { - CosmicTextPos::Center => ( - get_x_offset(cosmic_edit.editor.buffer()), - get_y_offset(cosmic_edit.editor.buffer()), - ), - CosmicTextPos::TopLeft => (0, 0), - }; - let point = |node_cursor_pos: (f32, f32)| { - ( - (node_cursor_pos.0 * scale_factor) as i32 - offset_x, - (node_cursor_pos.1 * scale_factor) as i32 - offset_y, - ) - }; - if buttons.just_pressed(MouseButton::Left) { - if let Some(node_cursor_pos) = get_node_cursor_pos( - primary_window, - node_transform, - (cosmic_edit.width, cosmic_edit.height), - cosmic_edit.is_ui_node, - camera, - camera_transform, - ) { - let (x, y) = point(node_cursor_pos); - if shift { - cosmic_edit - .editor - .action(&mut font_system.0, Action::Drag { x, y }); - } else { - cosmic_edit - .editor - .action(&mut font_system.0, Action::Click { x, y }); - } - } - // RETURN - return; + return; + } + if buttons.pressed(MouseButton::Left) { + if let Some(node_cursor_pos) = get_node_cursor_pos( + primary_window, + node_transform, + (width, height), + is_ui_node, + camera, + camera_transform, + ) { + let (x, y) = point(node_cursor_pos); + editor.action(&mut font_system.0, Action::Drag { x, y }); } - if buttons.pressed(MouseButton::Left) { - if let Some(node_cursor_pos) = get_node_cursor_pos( - primary_window, - node_transform, - (cosmic_edit.width, cosmic_edit.height), - cosmic_edit.is_ui_node, - camera, - camera_transform, - ) { - let (x, y) = point(node_cursor_pos); - cosmic_edit - .editor - .action(&mut font_system.0, Action::Drag { x, y }); + return; + } + for ev in scroll_evr.iter() { + match ev.unit { + MouseScrollUnit::Line => { + editor.action( + &mut font_system.0, + Action::Scroll { + lines: -ev.y as i32, + }, + ); } - // RETURN - return; - } - for ev in scroll_evr.iter() { - match ev.unit { - MouseScrollUnit::Line => { - cosmic_edit.editor.action( - &mut font_system.0, - Action::Scroll { - lines: -ev.y as i32, - }, - ); - } - MouseScrollUnit::Pixel => { - let line_height = cosmic_edit.editor.buffer().metrics().line_height; - cosmic_edit.editor.action( - &mut font_system.0, - Action::Scroll { - lines: -(ev.y / line_height) as i32, - }, - ); - } + MouseScrollUnit::Pixel => { + let line_height = editor.buffer().metrics().line_height; + editor.action( + &mut font_system.0, + Action::Scroll { + lines: -(ev.y / line_height) as i32, + }, + ); } } + } - if cosmic_edit.readonly { - // RETURN - return; - } + if readonly { + return; + } - let mut is_edit = is_clipboard; - let mut is_return = false; - if keys.just_pressed(KeyCode::Return) { - is_return = true; - is_edit = true; - // to have new line on wasm rather than E - cosmic_edit - .editor - .action(&mut font_system.0, Action::Insert('\n')); - } + let mut is_edit = is_clipboard; + let mut is_return = false; + 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 !(is_clipboard || is_return) { - for char_ev in char_evr.iter() { - is_edit = true; - if *is_deleting { - cosmic_edit - .editor - .action(&mut font_system.0, Action::Backspace); - } else { - cosmic_edit - .editor - .action(&mut font_system.0, Action::Insert(char_ev.char)); - } + 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)); } } + } - if !is_edit { - // RETURN - return; - } + if !is_edit { + return; + } - if let Some(last_edit_duration) = *edits_duration { - if Duration::from_millis(now_ms as u64) - last_edit_duration - > Duration::from_millis(150) - { - save_edit_history(&mut cosmic_edit, &mut edit_history); - *edits_duration = Some(Duration::from_millis(now_ms as u64)); - } - } else { - save_edit_history(&mut cosmic_edit, &mut edit_history); + if let Some(last_edit_duration) = *edits_duration { + if Duration::from_millis(now_ms as u64) - last_edit_duration + > Duration::from_millis(150) + { + save_edit_history(editor, attrs, &mut edit_history); *edits_duration = Some(Duration::from_millis(now_ms as u64)); } + } else { + save_edit_history(editor, attrs, &mut edit_history); + *edits_duration = Some(Duration::from_millis(now_ms as u64)); } } } } -fn cosmic_edit_set_redraw(mut cosmic_edit_query: Query<&mut CosmicEdit, Added<CosmicEdit>>) { - for mut cosmic_edit in cosmic_edit_query.iter_mut() { - cosmic_edit.editor.buffer_mut().set_redraw(true); +fn cosmic_edit_set_redraw(mut cosmic_edit_query: Query<&mut CosmicEditor, Added<CosmicEditor>>) { + for mut editor in cosmic_edit_query.iter_mut() { + editor.0.buffer_mut().set_redraw(true); } } +#[allow(clippy::too_many_arguments)] fn redraw_buffer_common( images: &mut ResMut<Assets<Image>>, swash_cache_state: &mut ResMut<SwashCacheState>, - cosmic_edit: &mut CosmicEdit, - img_handle: &mut Handle<Image>, - font_system_assets: &mut ResMut<Assets<CosmicFont>>, + editor: &mut Editor, + background_image: Option<Handle<Image>>, + background_color: Color, + cosmic_canvas_img_handle: &mut Handle<Image>, + text_position: &CosmicTextPosition, + font_system: &mut ResMut<CosmicFontSystem>, scale_factor: f32, original_width: f32, original_height: f32, @@ -791,96 +1021,90 @@ fn redraw_buffer_common( let width = original_width * scale_factor; let height = original_height * scale_factor; let swash_cache = &mut swash_cache_state.swash_cache; - if let Some(font_system) = font_system_assets.get_mut(&cosmic_edit.font_system) { - cosmic_edit.editor.shape_as_needed(&mut font_system.0); - if cosmic_edit.editor.buffer().redraw() { - cosmic_edit - .editor - .buffer_mut() - .set_size(&mut font_system.0, width, height); - let font_color = cosmic_text::Color::rgb(0, 0, 0); - - let mut pixels = vec![0; width as usize * height as usize * 4]; - if let Some(bg_image) = cosmic_edit.bg_image.clone() { - let image = images.get(&bg_image).unwrap(); - - let mut dynamic_image = image.clone().try_into_dynamic().unwrap(); - if image.size().x != width || image.size().y != height { - dynamic_image = dynamic_image.resize_to_fill( - width as u32, - height 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 = cosmic_edit.bg; - for pixel in pixels.chunks_exact_mut(4) { - pixel[0] = (bg.r() * 255.) as u8; // Red component - pixel[1] = (bg.g() * 255.) as u8; // Green component - pixel[2] = (bg.b() * 255.) as u8; // Blue component - pixel[3] = (bg.a() * 255.) as u8; // Alpha component + editor.shape_as_needed(&mut font_system.0); + if editor.buffer().redraw() { + editor + .buffer_mut() + .set_size(&mut font_system.0, width, height); + let font_color = cosmic_text::Color::rgb(0, 0, 0); + + let mut pixels = vec![0; width as usize * height as usize * 4]; + if let Some(bg_image) = background_image { + let image = images.get(&bg_image).unwrap(); + + let mut dynamic_image = image.clone().try_into_dynamic().unwrap(); + if image.size().x != width || image.size().y != height { + dynamic_image = + dynamic_image.resize_to_fill(width as u32, height 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 = background_color; + for pixel in pixels.chunks_exact_mut(4) { + pixel[0] = (bg.r() * 255.) as u8; // Red component + pixel[1] = (bg.g() * 255.) as u8; // Green component + pixel[2] = (bg.b() * 255.) as u8; // Blue component + pixel[3] = (bg.a() * 255.) as u8; // Alpha component + } + } - let (offset_y, offset_x) = match cosmic_edit.text_pos { - CosmicTextPos::Center => ( - get_y_offset(cosmic_edit.editor.buffer()), - get_x_offset(cosmic_edit.editor.buffer()), - ), - CosmicTextPos::TopLeft => (0, 0), - }; - cosmic_edit.editor.draw( - &mut font_system.0, - swash_cache, - font_color, - |x, y, w, h, color| { - for row in 0..h as i32 { - for col in 0..w as i32 { - draw_pixel( - &mut pixels, - width as i32, - height as i32, - x + col + offset_x, - y + row + offset_y, - color, - ); - } + let (offset_y, offset_x) = match text_position { + CosmicTextPosition::Center => { + (get_y_offset(editor.buffer()), get_x_offset(editor.buffer())) + } + CosmicTextPosition::TopLeft => (0, 0), + }; + + editor.draw( + &mut font_system.0, + swash_cache, + font_color, + |x, y, w, h, color| { + for row in 0..h as i32 { + for col in 0..w as i32 { + draw_pixel( + &mut pixels, + width as i32, + height as i32, + x + col + offset_x, + y + row + offset_y, + color, + ); } - }, - ); - cosmic_edit.editor.buffer_mut().set_redraw(false); - - if let Some(prev_image) = images.get_mut(img_handle) { - if *img_handle == bevy::render::texture::DEFAULT_IMAGE_HANDLE.typed() { - let mut prev_image = prev_image.clone(); - prev_image.data.clear(); - prev_image.data.extend_from_slice(pixels.as_slice()); - prev_image.resize(Extent3d { - width: width as u32, - height: height as u32, - depth_or_array_layers: 1, - }); - let handle_id: HandleId = HandleId::random::<Image>(); - let new_handle: Handle<Image> = Handle::weak(handle_id); - let new_handle = images.set(new_handle, prev_image); - *img_handle = new_handle; - } else { - prev_image.data.clear(); - prev_image.data.extend_from_slice(pixels.as_slice()); - prev_image.resize(Extent3d { - width: width as u32, - height: height as u32, - depth_or_array_layers: 1, - }); } + }, + ); + editor.buffer_mut().set_redraw(false); + + if let Some(prev_image) = images.get_mut(cosmic_canvas_img_handle) { + if *cosmic_canvas_img_handle == bevy::render::texture::DEFAULT_IMAGE_HANDLE.typed() { + let mut prev_image = prev_image.clone(); + prev_image.data.clear(); + prev_image.data.extend_from_slice(pixels.as_slice()); + prev_image.resize(Extent3d { + width: width as u32, + height: height as u32, + depth_or_array_layers: 1, + }); + let handle_id: HandleId = HandleId::random::<Image>(); + let new_handle: Handle<Image> = Handle::weak(handle_id); + let new_handle = images.set(new_handle, prev_image); + *cosmic_canvas_img_handle = new_handle; + } else { + prev_image.data.clear(); + prev_image.data.extend_from_slice(pixels.as_slice()); + prev_image.resize(Extent3d { + width: width as u32, + height: height as u32, + depth_or_array_layers: 1, + }); } } } @@ -890,27 +1114,41 @@ fn cosmic_edit_redraw_buffer_ui( windows: Query<&Window, With<PrimaryWindow>>, mut images: ResMut<Assets<Image>>, mut swash_cache_state: ResMut<SwashCacheState>, - mut cosmic_edit_query: Query< - (&mut CosmicEdit, &mut UiImage, &Node, &mut Visibility), - With<CosmicEdit>, - >, - mut font_system_assets: ResMut<Assets<CosmicFont>>, + mut cosmic_edit_query: Query<( + &mut CosmicEditor, + &CosmicBackground, + &BackgroundColor, + &CosmicTextPosition, + &mut UiImage, + &Node, + &mut Visibility, + )>, + mut font_system: ResMut<CosmicFontSystem>, ) { let primary_window = windows.single(); - for (mut cosmic_edit, mut img, node, mut visibility) in &mut cosmic_edit_query.iter_mut() { - if node.size().x != 0. && node.size().y != 0. { - cosmic_edit.width = node.size().x; - cosmic_edit.height = node.size().y; - } - let width = cosmic_edit.width; - let height = cosmic_edit.height; + for ( + mut editor, + background_image, + background_color, + text_position, + mut img, + node, + mut visibility, + ) in &mut cosmic_edit_query.iter_mut() + { + // provide min sizes to prevent render panic + let width = node.size().x.max(1.); + let height = node.size().y.max(1.); redraw_buffer_common( &mut images, &mut swash_cache_state, - &mut cosmic_edit, + &mut editor.0, + background_image.0.clone(), + background_color.0, &mut img.texture, - &mut font_system_assets, + text_position, + &mut font_system, primary_window.scale_factor() as f32, width, height, @@ -928,22 +1166,41 @@ fn cosmic_edit_redraw_buffer( windows: Query<&Window, With<PrimaryWindow>>, mut images: ResMut<Assets<Image>>, mut swash_cache_state: ResMut<SwashCacheState>, - mut cosmic_edit_query: Query< - (&mut CosmicEdit, &mut Handle<Image>, &mut Visibility), - With<CosmicEdit>, - >, - mut font_system_assets: ResMut<Assets<CosmicFont>>, + mut cosmic_edit_query: Query<( + &mut CosmicEditor, + &Sprite, + &CosmicBackground, + &BackgroundColor, + &CosmicTextPosition, + &mut Handle<Image>, + &mut Visibility, + )>, + mut font_system: ResMut<CosmicFontSystem>, ) { let primary_window = windows.single(); - for (mut cosmic_edit, mut handle, mut visibility) in &mut cosmic_edit_query.iter_mut() { - let width = cosmic_edit.width; - let height = cosmic_edit.height; + for ( + mut editor, + sprite, + background_image, + background_color, + text_position, + mut handle, + mut visibility, + ) in &mut cosmic_edit_query.iter_mut() + { + // provide min sizes to prevent render panic + let width = sprite.custom_size.unwrap().x.max(1.); + let height = sprite.custom_size.unwrap().y.max(1.); + redraw_buffer_common( &mut images, &mut swash_cache_state, - &mut cosmic_edit, + &mut editor.0, + background_image.0.clone(), + background_color.0, &mut handle, - &mut font_system_assets, + text_position, + &mut font_system, primary_window.scale_factor() as f32, width, height, @@ -957,143 +1214,6 @@ fn cosmic_edit_redraw_buffer( } } -pub fn cosmic_edit_set_text( - text: CosmicText, - attrs: AttrsOwned, - editor: &mut Editor, - font_system: &mut FontSystem, -) { - editor.buffer_mut().lines.clear(); - match text { - CosmicText::OneStyle(text) => { - editor.buffer_mut().set_text( - font_system, - text.as_str(), - attrs.as_attrs(), - Shaping::Advanced, - ); - } - CosmicText::MultiStyle(lines) => { - for line in lines { - let mut line_text = String::new(); - let mut attrs_list = AttrsList::new(attrs.as_attrs()); - for (text, attrs) in line.iter() { - let start = line_text.len(); - line_text.push_str(text); - let end = line_text.len(); - attrs_list.add_span(start..end, attrs.as_attrs()); - } - editor.buffer_mut().lines.push(BufferLine::new( - line_text, - attrs_list, - Shaping::Advanced, - )); - } - } - } -} - -/// Spawns a cosmic edit entity with the provided configuration. -/// -/// # Returns -/// -/// The `Entity` identifier of the spawned cosmic edit entity. -pub fn spawn_cosmic_edit( - commands: &mut Commands, - cosmic_fonts: &mut ResMut<Assets<CosmicFont>>, - cosmic_edit_meta: CosmicEditMeta, -) -> Entity { - let font_system = cosmic_fonts - .get_mut(&cosmic_edit_meta.font_system_handle) - .unwrap(); - let scale_factor = cosmic_edit_meta.metrics.scale_factor; - let font_size = cosmic_edit_meta.metrics.font_size; - let line_height = cosmic_edit_meta.metrics.line_height; - let metrics = Metrics::new(font_size, line_height).scale(scale_factor); - let buffer = Buffer::new(&mut font_system.0, metrics); - let mut editor = Editor::new(buffer); - cosmic_edit_set_text( - cosmic_edit_meta.text, - cosmic_edit_meta.attrs.clone(), - &mut editor, - &mut font_system.0, - ); - if let Some((width, height)) = cosmic_edit_meta.size { - editor.buffer_mut().set_size( - &mut font_system.0, - width * scale_factor, - height * scale_factor, - ); - } - let mut cursor = editor.cursor(); - if cosmic_edit_meta.readonly { - cursor.color = Some(bevy_color_to_cosmic(cosmic_edit_meta.bg)); - } - editor.set_cursor(cursor); - - let mut edits = VecDeque::new(); - if !cosmic_edit_meta.readonly { - edits.push_back(EditHistoryItem { - cursor, - lines: get_text_spans(editor.buffer(), cosmic_edit_meta.attrs.clone()), - }); - } - let edit_history = CosmicEditHistory { - edits, - current_edit: 0, - }; - let mut cosmic_edit_component = CosmicEdit { - editor, - font_system: cosmic_edit_meta.font_system_handle, - text_pos: cosmic_edit_meta.text_pos, - bg: cosmic_edit_meta.bg, - is_ui_node: false, - font_size, - line_height, - width: cosmic_edit_meta.size.unwrap_or((1., 1.)).0, - height: cosmic_edit_meta.size.unwrap_or((1., 1.)).1, - readonly: cosmic_edit_meta.readonly, - attrs: cosmic_edit_meta.attrs.clone(), - bg_image: cosmic_edit_meta.bg_image, - }; - match cosmic_edit_meta.node { - CosmicNode::Ui => { - cosmic_edit_component.is_ui_node = true; - let style = Style { - width: Val::Percent(100.), - height: Val::Percent(100.), - ..default() - }; - let button_bundle = ButtonBundle { - visibility: Visibility::Hidden, - focus_policy: bevy::ui::FocusPolicy::Pass, - style, - ..default() - }; - commands - .spawn((button_bundle, cosmic_edit_component, edit_history)) - .id() - } - CosmicNode::Sprite(sprite_node) => { - let sprite = SpriteBundle { - visibility: Visibility::Hidden, - sprite: Sprite { - custom_size: Some(Vec2::new( - cosmic_edit_component.width, - cosmic_edit_component.height, - )), - ..default() - }, - transform: sprite_node.transform, - ..default() - }; - commands - .spawn((sprite, cosmic_edit_component, edit_history)) - .id() - } - } -} - fn draw_pixel( buffer: &mut [u8], width: i32, @@ -1145,39 +1265,17 @@ fn draw_pixel( #[cfg(test)] mod tests { - use bevy::prelude::*; - use cosmic_text::{Attrs, AttrsOwned}; - use crate::*; fn test_spawn_cosmic_edit_system( mut commands: Commands, - mut cosmic_fonts: ResMut<Assets<CosmicFont>>, + mut font_system: ResMut<CosmicFontSystem>, ) { - let cosmic_font_config = CosmicFontConfig { - fonts_dir_path: None, - font_bytes: None, - load_system_fonts: true, - }; - let font_system = create_cosmic_font_system(cosmic_font_config); - let font_system_handle = cosmic_fonts.add(CosmicFont(font_system)); - let cosmic_edit_meta = CosmicEditMeta { - text: CosmicText::OneStyle("Blah".to_string()), - attrs: AttrsOwned::new(Attrs::new()), - metrics: CosmicMetrics { - font_size: 14., - line_height: 18., - scale_factor: 1., - }, - text_pos: CosmicTextPos::Center, - font_system_handle, - node: CosmicNode::Ui, - size: None, - bg: bevy::prelude::Color::NONE, - readonly: false, - bg_image: None, - }; - spawn_cosmic_edit(&mut commands, &mut cosmic_fonts, cosmic_edit_meta); + commands.spawn(CosmicEditUiBundle::default().set_text( + CosmicText::OneStyle("Blah".into()), + AttrsOwned::new(Attrs::new()), + &mut font_system.0, + )); } #[test] @@ -1185,6 +1283,9 @@ mod tests { let mut app = App::new(); app.add_plugins(TaskPoolPlugin::default()); app.add_plugins(AssetPlugin::default()); + app.insert_resource(CosmicFontSystem(create_cosmic_font_system( + CosmicFontConfig::default(), + ))); app.add_systems(Update, test_spawn_cosmic_edit_system); let input = Input::<KeyCode>::default(); @@ -1192,16 +1293,15 @@ mod tests { let mouse_input: Input<MouseButton> = Input::<MouseButton>::default(); app.insert_resource(mouse_input); app.add_asset::<Image>(); - app.add_asset::<CosmicFont>(); app.add_event::<ReceivedCharacter>(); app.update(); - let mut text_nodes_query = app.world.query::<&CosmicEdit>(); - for node in text_nodes_query.iter(&app.world) { - insta::assert_debug_snapshot!(node - .editor + let mut text_nodes_query = app.world.query::<&CosmicEditor>(); + for cosmic_editor in text_nodes_query.iter(&app.world) { + insta::assert_debug_snapshot!(cosmic_editor + .0 .buffer() .lines .iter() diff --git a/src/utils.rs b/src/utils.rs index 8728dda5ac6469cbbf08a5ab5a6496b20edabfeb..b3d72d9f081f61ad21e7b53265798d03cc2381b4 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -10,3 +10,59 @@ pub fn get_timestamp() -> f64 { let duration = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); duration.as_millis() as f64 } + +use crate::{ActiveEditor, CosmicEditor, ReadOnly}; +use bevy::{prelude::*, ui::Interaction, window::PrimaryWindow}; + +// util fns for examples +// +pub fn change_active_editor_ui( + mut commands: Commands, + mut interaction_query: Query< + (&Interaction, Entity), + ( + Changed<Interaction>, + (With<CosmicEditor>, Without<ReadOnly>), + ), + >, +) { + for (interaction, entity) in interaction_query.iter_mut() { + if let Interaction::Pressed = interaction { + commands.insert_resource(ActiveEditor { + entity: Some(entity), + }); + } + } +} + +pub fn change_active_editor_sprite( + mut commands: Commands, + windows: Query<&Window, With<PrimaryWindow>>, + buttons: Res<Input<MouseButton>>, + mut cosmic_edit_query: Query< + (&mut Sprite, &GlobalTransform, Entity), + (With<CosmicEditor>, Without<ReadOnly>), + >, + camera_q: Query<(&Camera, &GlobalTransform)>, +) { + let window = windows.single(); + let (camera, camera_transform) = camera_q.single(); + if buttons.just_pressed(MouseButton::Left) { + for (sprite, node_transform, entity) in &mut cosmic_edit_query.iter_mut() { + let size = sprite.custom_size.unwrap_or(Vec2::new(1., 1.)); + let x_min = node_transform.affine().translation.x - size.x / 2.; + let y_min = node_transform.affine().translation.y - size.y / 2.; + let x_max = node_transform.affine().translation.x + size.x / 2.; + let y_max = node_transform.affine().translation.y + size.y / 2.; + if let Some(pos) = window.cursor_position() { + if let Some(pos) = camera.viewport_to_world_2d(camera_transform, pos) { + if x_min < pos.x && pos.x < x_max && y_min < pos.y && pos.y < y_max { + commands.insert_resource(ActiveEditor { + entity: Some(entity), + }); + }; + } + }; + } + } +}