diff --git a/examples/every_option.rs b/examples/every_option.rs index 1e1d6aa4e1428ba2673cf560e7322cb6b12b0a23..05b7473901c7fcacc3ad15f2e58473a9a4fc218a 100644 --- a/examples/every_option.rs +++ b/examples/every_option.rs @@ -7,7 +7,7 @@ struct TextChangeTimer(pub Timer); fn setup(mut commands: Commands, mut font_system: ResMut<CosmicFontSystem>) { commands.spawn(Camera2dBundle::default()); - let attrs = Attrs::new().color(bevy_color_to_cosmic(Color::rgb(0.27, 0.27, 0.27))); + let attrs = Attrs::new().color(Color::rgb(0.27, 0.27, 0.27).to_cosmic()); let editor = commands .spawn(CosmicEditBundle { @@ -18,14 +18,14 @@ fn setup(mut commands: Commands, mut font_system: ResMut<CosmicFontSystem>) { ), cursor_color: CursorColor(Color::GREEN), selection_color: SelectionColor(Color::PINK), - fill_color: FillColor(Color::YELLOW_GREEN), + fill_color: CosmicBackgroundColor(Color::YELLOW_GREEN), x_offset: XOffset::default(), - text_position: CosmicTextPosition::default(), - background_image: CosmicBackground::default(), + text_position: CosmicTextAlign::default(), + background_image: CosmicBackgroundImage::default(), default_attrs: DefaultAttrs(AttrsOwned::new(attrs)), - max_chars: CosmicMaxChars(15), - max_lines: CosmicMaxLines(1), - mode: CosmicMode::Wrap, + max_chars: MaxChars(15), + max_lines: MaxLines(1), + mode: CosmicWrap::Wrap, // CosmicEdit draws to this spritebundle sprite_bundle: SpriteBundle { sprite: Sprite { diff --git a/examples/font_per_widget.rs b/examples/font_per_widget.rs index a599c689134616c4a823ae38f127465e54af8639..a935e733dabb003bfe7daad77cd60e9298a65e70 100644 --- a/examples/font_per_widget.rs +++ b/examples/font_per_widget.rs @@ -66,31 +66,28 @@ fn setup(mut commands: Commands, mut font_system: ResMut<CosmicFontSystem>) { .style(FontStyle::Italic), ), ("\n", attrs), - ("R", attrs.color(bevy_color_to_cosmic(Color::RED))), - ("A", attrs.color(bevy_color_to_cosmic(Color::ORANGE))), - ("I", attrs.color(bevy_color_to_cosmic(Color::YELLOW))), - ("N", attrs.color(bevy_color_to_cosmic(Color::GREEN))), - ("B", attrs.color(bevy_color_to_cosmic(Color::BLUE))), - ("O", attrs.color(bevy_color_to_cosmic(Color::INDIGO))), - ("W ", attrs.color(bevy_color_to_cosmic(Color::PURPLE))), - ("Red ", attrs.color(bevy_color_to_cosmic(Color::RED))), - ("Orange ", attrs.color(bevy_color_to_cosmic(Color::ORANGE))), - ("Yellow ", attrs.color(bevy_color_to_cosmic(Color::YELLOW))), - ("Green ", attrs.color(bevy_color_to_cosmic(Color::GREEN))), - ("Blue ", attrs.color(bevy_color_to_cosmic(Color::BLUE))), - ("Indigo ", attrs.color(bevy_color_to_cosmic(Color::INDIGO))), - ("Violet ", attrs.color(bevy_color_to_cosmic(Color::PURPLE))), - ("U", attrs.color(bevy_color_to_cosmic(Color::PURPLE))), - ("N", attrs.color(bevy_color_to_cosmic(Color::INDIGO))), - ("I", attrs.color(bevy_color_to_cosmic(Color::BLUE))), - ("C", attrs.color(bevy_color_to_cosmic(Color::GREEN))), - ("O", attrs.color(bevy_color_to_cosmic(Color::YELLOW))), - ("R", attrs.color(bevy_color_to_cosmic(Color::ORANGE))), - ("N", attrs.color(bevy_color_to_cosmic(Color::RED))), - ( - "ηζ΄»,μΆ,ΰ€ΰ€Ώΰ€ΰ€¦ΰ€ΰ₯ π FPS", - attrs.color(bevy_color_to_cosmic(Color::RED)), - ), + ("R", attrs.color(Color::RED.to_cosmic())), + ("A", attrs.color(Color::ORANGE.to_cosmic())), + ("I", attrs.color(Color::YELLOW.to_cosmic())), + ("N", attrs.color(Color::GREEN.to_cosmic())), + ("B", attrs.color(Color::BLUE.to_cosmic())), + ("O", attrs.color(Color::INDIGO.to_cosmic())), + ("W ", attrs.color(Color::PURPLE.to_cosmic())), + ("Red ", attrs.color(Color::RED.to_cosmic())), + ("Orange ", attrs.color(Color::ORANGE.to_cosmic())), + ("Yellow ", attrs.color(Color::YELLOW.to_cosmic())), + ("Green ", attrs.color(Color::GREEN.to_cosmic())), + ("Blue ", attrs.color(Color::BLUE.to_cosmic())), + ("Indigo ", attrs.color(Color::INDIGO.to_cosmic())), + ("Violet ", attrs.color(Color::PURPLE.to_cosmic())), + ("U", attrs.color(Color::PURPLE.to_cosmic())), + ("N", attrs.color(Color::INDIGO.to_cosmic())), + ("I", attrs.color(Color::BLUE.to_cosmic())), + ("C", attrs.color(Color::GREEN.to_cosmic())), + ("O", attrs.color(Color::YELLOW.to_cosmic())), + ("R", attrs.color(Color::ORANGE.to_cosmic())), + ("N", attrs.color(Color::RED.to_cosmic())), + ("ηζ΄»,μΆ,ΰ€ΰ€Ώΰ€ΰ€¦ΰ€ΰ₯ π FPS", attrs.color(Color::RED.to_cosmic())), ]; let cosmic_edit_1 = commands @@ -106,7 +103,7 @@ fn setup(mut commands: Commands, mut font_system: ResMut<CosmicFontSystem>) { let mut attrs_2 = Attrs::new(); attrs_2 = attrs_2.family(Family::Name("Times New Roman")); - attrs_2.color_opt = Some(bevy_color_to_cosmic(Color::PURPLE)); + attrs_2.color_opt = Some(Color::PURPLE.to_cosmic()); let cosmic_edit_2 = commands .spawn(CosmicEditBundle { diff --git a/examples/image_background.rs b/examples/image_background.rs index 48152788956371873d2c1501b42c343e26ce9259..708611ba768f1d417f80b3628cbd1000dd9b4f99 100644 --- a/examples/image_background.rs +++ b/examples/image_background.rs @@ -9,9 +9,9 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) { let editor = commands .spawn(CosmicEditBundle { default_attrs: DefaultAttrs(AttrsOwned::new( - Attrs::new().color(bevy_color_to_cosmic(Color::GREEN)), + Attrs::new().color(Color::GREEN.to_cosmic()), )), - background_image: CosmicBackground(Some(bg_image_handle)), + background_image: CosmicBackgroundImage(Some(bg_image_handle)), ..default() }) .id(); diff --git a/examples/multiple_sprites.rs b/examples/multiple_sprites.rs index e5cdb5098ff9937382ec04625e808f01f10c0eb3..509f238b1a01b3b2ebaea3aac890e348423c4463 100644 --- a/examples/multiple_sprites.rs +++ b/examples/multiple_sprites.rs @@ -18,10 +18,10 @@ fn setup( let mut attrs = Attrs::new(); attrs = attrs.family(Family::Name("Victor Mono")); - attrs = attrs.color(bevy_color_to_cosmic(Color::PURPLE)); + attrs = attrs.color(Color::PURPLE.to_cosmic()); commands.spawn(CosmicEditBundle { - fill_color: FillColor(Color::ALICE_BLUE), + fill_color: CosmicBackgroundColor(Color::ALICE_BLUE), buffer: CosmicBuffer::new(&mut font_system, Metrics::new(14., 18.)).with_text( &mut font_system, "πππ x => y", @@ -42,7 +42,7 @@ fn setup( }); commands.spawn(CosmicEditBundle { - fill_color: FillColor(Color::GRAY.with_a(0.5)), + fill_color: CosmicBackgroundColor(Color::GRAY.with_a(0.5)), buffer: CosmicBuffer::new(&mut font_system, Metrics::new(14., 18.)).with_text( &mut font_system, "Widget_2. Click on me", diff --git a/examples/password.rs b/examples/password.rs index 4314c464ae098b9d823f36738c6f139f95b5a2a8..75bfac024f3cd96cd5f746e1ef203c6c817d0e1c 100644 --- a/examples/password.rs +++ b/examples/password.rs @@ -7,8 +7,8 @@ fn setup(mut commands: Commands) { // Sprite editor commands.spawn(( CosmicEditBundle { - max_lines: CosmicMaxLines(1), - mode: CosmicMode::InfiniteLine, + max_lines: MaxLines(1), + mode: CosmicWrap::InfiniteLine, sprite_bundle: SpriteBundle { // Sets size of text box sprite: Sprite { diff --git a/examples/placeholder.rs b/examples/placeholder.rs index 64192ad6f92d693aaf3747d8856b9d1924a68816..61fcd2b4a5a861f0ec0dc149dc757853843a7c43 100644 --- a/examples/placeholder.rs +++ b/examples/placeholder.rs @@ -23,10 +23,7 @@ fn setup(mut commands: Commands, mut font_system: ResMut<CosmicFontSystem>) { .with_rich_text(&mut font_system, vec![("", attrs)], attrs), ..default() }, - Placeholder::new( - "Placeholder", - attrs.color(bevy_color_to_cosmic(Color::GRAY)), - ), + Placeholder::new("Placeholder", attrs.color(Color::GRAY.to_cosmic())), )) .id(); diff --git a/examples/readonly.rs b/examples/readonly.rs index b339fa9c6b8bd76561a00d5803eca2587ce1472d..9a817fcfe48b48e1ced0ef013ec1ea7f4c1952f5 100644 --- a/examples/readonly.rs +++ b/examples/readonly.rs @@ -17,7 +17,7 @@ fn setup(mut commands: Commands, mut font_system: ResMut<CosmicFontSystem>) { let mut attrs = Attrs::new(); attrs = attrs.family(Family::Name("Victor Mono")); - attrs = attrs.color(bevy_color_to_cosmic(Color::PURPLE)); + attrs = attrs.color(Color::PURPLE.to_cosmic()); // spawn editor let cosmic_edit = commands diff --git a/examples/sprite_and_ui_clickable.rs b/examples/sprite_and_ui_clickable.rs index 68d87e407c25fc7b6ec89f4d8ddd7ee5871da6be..76c8cd70b36e677a23c6023d576119dd84e7957f 100644 --- a/examples/sprite_and_ui_clickable.rs +++ b/examples/sprite_and_ui_clickable.rs @@ -8,11 +8,11 @@ fn setup(mut commands: Commands) { let ui_editor = commands .spawn(CosmicEditBundle { default_attrs: DefaultAttrs(AttrsOwned::new( - Attrs::new().color(bevy_color_to_cosmic(Color::GREEN)), + Attrs::new().color(Color::GREEN.to_cosmic()), )), - max_lines: CosmicMaxLines(1), - mode: CosmicMode::InfiniteLine, - text_position: CosmicTextPosition::Left { padding: 5 }, + max_lines: MaxLines(1), + mode: CosmicWrap::InfiniteLine, + text_position: CosmicTextAlign::Left { padding: 5 }, ..default() }) .id(); @@ -33,8 +33,8 @@ fn setup(mut commands: Commands) { // Sprite editor commands.spawn((CosmicEditBundle { - max_lines: CosmicMaxLines(1), - mode: CosmicMode::InfiniteLine, + max_lines: MaxLines(1), + mode: CosmicWrap::InfiniteLine, sprite_bundle: SpriteBundle { // Sets size of text box sprite: Sprite { diff --git a/src/buffer.rs b/src/buffer.rs index 422c6050d047575da35068474728f86916350c7a..308705a8c49de5198ee5379179f6d2facc3436c6 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -1,10 +1,11 @@ use crate::*; use bevy::{prelude::*, window::PrimaryWindow}; +/// Set of all buffer setup functions. Runs in [`First`] #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)] pub struct BufferSet; -pub struct BufferPlugin; +pub(crate) struct BufferPlugin; impl Plugin for BufferPlugin { fn build(&self, app: &mut App) { @@ -35,7 +36,7 @@ impl BufferExtras for Buffer { /// /// # Returns /// - /// A `String` containing the cosmic text content. + /// A [`String`] containing the cosmic text content. fn get_text(&self) -> String { let mut text = String::new(); let line_count = self.lines.len(); @@ -52,6 +53,7 @@ impl BufferExtras for Buffer { } } +/// Component wrapper for [`Buffer`] #[derive(Component, Deref, DerefMut)] pub struct CosmicBuffer(pub Buffer); @@ -62,11 +64,13 @@ impl Default for CosmicBuffer { } impl<'s, 'r> CosmicBuffer { + /// Create a new buffer with a font system pub fn new(font_system: &mut FontSystem, metrics: Metrics) -> Self { Self(Buffer::new(font_system, metrics)) } // Das a lotta boilerplate just to hide the shaping argument + /// Add text to a newly created [`CosmicBuffer`] pub fn with_text( mut self, font_system: &mut FontSystem, @@ -77,6 +81,9 @@ impl<'s, 'r> CosmicBuffer { self } + /// Add rich text to a newly created [`CosmicBuffer`] + /// + /// Rich text is an iterable of `(&'s str, Attrs<'r>)` pub fn with_rich_text<I>( mut self, font_system: &mut FontSystem, @@ -91,6 +98,7 @@ impl<'s, 'r> CosmicBuffer { self } + /// Replace buffer text pub fn set_text( &mut self, font_system: &mut FontSystem, @@ -102,6 +110,9 @@ impl<'s, 'r> CosmicBuffer { self } + /// Replace buffer text with rich text + /// + /// Rich text is an iterable of `(&'s str, Attrs<'r>)` pub fn set_rich_text<I>( &mut self, font_system: &mut FontSystem, @@ -158,6 +169,7 @@ impl<'s, 'r> CosmicBuffer { } } +/// Adds a [`FontSystem`] to a newly created [`CosmicBuffer`] if one was not provided pub fn add_font_system( mut font_system: ResMut<CosmicFontSystem>, mut q: Query<&mut CosmicBuffer, Added<CosmicBuffer>>, @@ -171,6 +183,7 @@ pub fn add_font_system( } } +/// Initialises [`CosmicBuffer`] scale factor pub fn set_initial_scale( window_q: Query<&Window, With<PrimaryWindow>>, mut cosmic_query: Query<&mut CosmicBuffer, Added<CosmicBuffer>>, @@ -184,19 +197,22 @@ pub fn set_initial_scale( } } +/// Initialises new [`CosmicBuffer`] redraw flag to true pub fn set_redraw(mut q: Query<&mut CosmicBuffer, Added<CosmicBuffer>>) { for mut b in q.iter_mut() { b.set_redraw(true); } } +/// Initialises new [`CosmicEditor`] redraw flag to true pub fn set_editor_redraw(mut q: Query<&mut CosmicEditor, Added<CosmicEditor>>) { for mut b in q.iter_mut() { b.set_redraw(true); } } -pub(crate) fn swap_target_handle( +/// Sets image of UI elements to the [`CosmicBuffer`] output +pub fn swap_target_handle( source_q: Query<&Handle<Image>, With<CosmicBuffer>>, mut dest_q: Query< ( diff --git a/src/cosmic_edit.rs b/src/cosmic_edit.rs index ab13543341355ba1e778e7277f7509e68be6a6d1..3ac21ed7258744bba7f148e293ff9c5db32c9104 100644 --- a/src/cosmic_edit.rs +++ b/src/cosmic_edit.rs @@ -1,36 +1,41 @@ use crate::*; use bevy::prelude::*; +/// Enum representing text wrapping in a cosmic [`Buffer`] #[derive(Clone, Component, PartialEq, Default)] -pub enum CosmicMode { +pub enum CosmicWrap { InfiniteLine, #[default] Wrap, } -/// Enum representing the position of the cosmic text. +/// Enum representing the text alignment in a cosmic [`Buffer`] #[derive(Clone, Component)] -pub enum CosmicTextPosition { +pub enum CosmicTextAlign { Center { padding: i32 }, TopLeft { padding: i32 }, Left { padding: i32 }, } -impl Default for CosmicTextPosition { +impl Default for CosmicTextAlign { fn default() -> Self { - CosmicTextPosition::Center { padding: 5 } + CosmicTextAlign::Center { padding: 5 } } } +/// Tag component to disable writing to a [`CosmicBuffer`] +// TODO: Code example #[derive(Component)] pub struct ReadOnly; // tag component +/// Internal value used to decide what section of a [`Buffer`] to render #[derive(Component, Debug, Default)] pub struct XOffset { pub left: f32, pub width: f32, } +/// Default text attributes to be used on a [`CosmicBuffer`] #[derive(Component, Deref, DerefMut)] pub struct DefaultAttrs(pub AttrsOwned); @@ -40,45 +45,146 @@ impl Default for DefaultAttrs { } } +/// Image to be used as a buffer's background #[derive(Component, Default)] -pub struct CosmicBackground(pub Option<Handle<Image>>); +pub struct CosmicBackgroundImage(pub Option<Handle<Image>>); +/// Color to be used as a buffer's background #[derive(Component, Default, Deref)] -pub struct FillColor(pub Color); +pub struct CosmicBackgroundColor(pub Color); +/// Color to be used for the text cursor #[derive(Component, Default, Deref)] pub struct CursorColor(pub Color); +/// Color to be used as the selected text background #[derive(Component, Default, Deref)] pub struct SelectionColor(pub Color); +/// Maximum number of lines allowed in a buffer #[derive(Component, Default)] -pub struct CosmicMaxLines(pub usize); +pub struct MaxLines(pub usize); +/// Maximum number of characters allowed in a buffer +// TODO: Check this functionality with widechars; Use graphemes to test? #[derive(Component, Default)] -pub struct CosmicMaxChars(pub usize); - +pub struct MaxChars(pub usize); + +/// A pointer to an entity with a [`CosmicEditBundle`], used to apply cosmic rendering to a UI +/// element. +/// +///``` +/// # use bevy::prelude::*; +/// # use bevy_cosmic_edit::*; +/// # +/// # fn setup(mut commands: Commands) { +/// // Create a new cosmic bundle +/// let cosmic_edit = commands.spawn(CosmicEditBundle::default()).id(); +/// +/// // Spawn the target bundle +/// commands +/// .spawn(ButtonBundle { +/// style: Style { +/// width: Val::Percent(100.), +/// height: Val::Percent(100.), +/// ..default() +/// }, +/// background_color: BackgroundColor(Color::WHITE), +/// ..default() +/// }) +/// // Add the source component to the target element +/// .insert(CosmicSource(cosmic_edit)); +/// # } +/// # +/// # fn main() { +/// # App::new() +/// # .add_plugins(MinimalPlugins) +/// # .add_plugins(CosmicEditPlugin::default()) +/// # .add_systems(Startup, setup); +/// # } #[derive(Component)] pub struct CosmicSource(pub Entity); +/// A bundle containing all the required components for [`CosmicBuffer`] functionality. +/// +/// Uses an invisible [`SpriteBundle`] for rendering by default, so should either be paired with another +/// entity with a [`CosmicSource`] pointing to it's entity, or have the sprite set. +/// +/// ### UI mode +/// +///``` +/// # use bevy::prelude::*; +/// # use bevy_cosmic_edit::*; +/// # +/// # fn setup(mut commands: Commands) { +/// // Create a new cosmic bundle +/// let cosmic_edit = commands.spawn(CosmicEditBundle::default()).id(); +/// +/// // Spawn the target bundle +/// commands +/// .spawn(ButtonBundle { +/// style: Style { +/// width: Val::Percent(100.), +/// height: Val::Percent(100.), +/// ..default() +/// }, +/// background_color: BackgroundColor(Color::WHITE), +/// ..default() +/// }) +/// // Add the source component to the target element +/// .insert(CosmicSource(cosmic_edit)); +/// # } +/// # +/// # fn main() { +/// # App::new() +/// # .add_plugins(MinimalPlugins) +/// # .add_plugins(CosmicEditPlugin::default()) +/// # .add_systems(Startup, setup); +/// # } +/// ``` +/// ### Sprite mode +/// ``` +/// # use bevy::prelude::*; +/// # use bevy_cosmic_edit::*; +/// # +/// # fn setup(mut commands: Commands) { +/// // Create a new cosmic bundle +/// commands.spawn(CosmicEditBundle { +/// sprite_bundle: SpriteBundle { +/// sprite: Sprite { +/// custom_size: Some(Vec2::new(300.0, 40.0)), +/// ..default() +/// }, +/// ..default() +/// }, +/// ..default() +/// }); +/// # } +/// # +/// # fn main() { +/// # App::new() +/// # .add_plugins(MinimalPlugins) +/// # .add_plugins(CosmicEditPlugin::default()) +/// # .add_systems(Startup, setup); +/// # } #[derive(Bundle)] pub struct CosmicEditBundle { // cosmic bits pub buffer: CosmicBuffer, // render bits - pub fill_color: FillColor, + pub fill_color: CosmicBackgroundColor, pub cursor_color: CursorColor, pub selection_color: SelectionColor, pub default_attrs: DefaultAttrs, - pub background_image: CosmicBackground, + pub background_image: CosmicBackgroundImage, pub sprite_bundle: SpriteBundle, // restriction bits - pub max_lines: CosmicMaxLines, - pub max_chars: CosmicMaxChars, + pub max_lines: MaxLines, + pub max_chars: MaxChars, // layout bits pub x_offset: XOffset, - pub mode: CosmicMode, - pub text_position: CosmicTextPosition, + pub mode: CosmicWrap, + pub text_position: CosmicTextAlign, pub padding: CosmicPadding, pub widget_size: CosmicWidgetSize, } @@ -111,9 +217,11 @@ impl Default for CosmicEditBundle { } } +/// Holds the font system used internally by [`cosmic_text`] #[derive(Resource, Deref, DerefMut)] pub struct CosmicFontSystem(pub FontSystem); +/// Wrapper component for an [`Editor`] with a few helpful values for cursor blinking #[derive(Component, Deref, DerefMut)] pub struct CosmicEditor { #[deref] diff --git a/src/cursor.rs b/src/cursor.rs index e14720948a932036f3b4c9a720b50386070e89e0..04bb4e03bc0e08838ee5bef7d950a6ceb48b5fad 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -1,10 +1,26 @@ +// This will all be rewritten soon, looking toward per-widget cursor control +// Rewrite should address issue #93 too + use crate::*; use bevy::{input::mouse::MouseMotion, prelude::*, window::PrimaryWindow}; +/// System set for mouse cursor systems. Runs in [`Update`] #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)] pub struct CursorSet; -pub struct CursorPlugin { +/// Config enum for mouse cursor events. +#[derive(Default, Clone)] +pub enum CursorConfig { + /// Emit [`TextHoverIn`] and [`TextHoverOut`] events and change mouse cursor on hover + #[default] + Default, + /// Emit [`TextHoverIn`] and [`TextHoverOut`] events, but do not change the cursor + Events, + /// Ignore mouse events + None, +} + +pub(crate) struct CursorPlugin { pub change_cursor: CursorConfig, } @@ -34,6 +50,7 @@ pub struct TextHoverIn; #[derive(Event)] pub struct TextHoverOut; +/// Switches mouse cursor icon when hover events are received pub fn change_cursor( evr_hover_in: EventReader<TextHoverIn>, evr_hover_out: EventReader<TextHoverOut>, @@ -67,6 +84,8 @@ type CameraQuery<'a, 'b, 'c, 'd> = #[cfg(not(feature = "multicam"))] type CameraQuery<'a, 'b, 'c, 'd> = Query<'a, 'b, (&'c Camera, &'d GlobalTransform)>; +/// Sprite widget mouse cursor hover detection system. Sends [`TextHoverIn`] and [`TextHoverOut`] +/// events. pub fn hover_sprites( windows: Query<&Window, With<PrimaryWindow>>, mut cosmic_edit_query: Query<(&mut Sprite, &Visibility, &GlobalTransform), With<CosmicBuffer>>, @@ -112,6 +131,8 @@ pub fn hover_sprites( *last_hovered = *hovered; } +/// UI widget mouse cursor hover detection system. Sends [`TextHoverIn`] and [`TextHoverOut`] +/// events. pub fn hover_ui( mut interaction_query: Query<&Interaction, (Changed<Interaction>, With<CosmicSource>)>, mut evw_hover_in: EventWriter<TextHoverIn>, diff --git a/src/events.rs b/src/events.rs index 57aa2008ee256b13ccd5312dd1f337b206dfe296..e9ecea551bfc1fc07505ff49e6c83b3be1300a98 100644 --- a/src/events.rs +++ b/src/events.rs @@ -2,7 +2,8 @@ use bevy::prelude::*; -pub struct EventsPlugin; +/// Registers internal events +pub(crate) struct EventsPlugin; impl Plugin for EventsPlugin { fn build(&self, app: &mut App) { @@ -10,5 +11,8 @@ impl Plugin for EventsPlugin { } } +/// Text change events +/// Sent when text is changed in a cosmic buffer +/// Contains the entity on which the text was changed, and the new text as a [`String`] #[derive(Event, Debug)] pub struct CosmicTextChanged(pub (Entity, String)); diff --git a/src/focus.rs b/src/focus.rs index 2c8ffbf9f4594a4cc26796e00af288859c949a6b..522b18e34b0f7041e3fa907fd26f308f727c22b4 100644 --- a/src/focus.rs +++ b/src/focus.rs @@ -2,10 +2,11 @@ use crate::*; use bevy::prelude::*; use cosmic_text::{Edit, Editor}; +/// System set for focus systems. Runs in `PostUpdate` #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)] pub struct FocusSet; -pub struct FocusPlugin; +pub(crate) struct FocusPlugin; impl Plugin for FocusPlugin { fn build(&self, app: &mut App) { diff --git a/src/input.rs b/src/input.rs index 6e7ad8f7307c24b65190af510f2b97e74722be9a..8aae13b76cda14769991d5cf658fc0796e2e7543 100644 --- a/src/input.rs +++ b/src/input.rs @@ -19,10 +19,11 @@ use wasm_bindgen::prelude::*; #[cfg(target_arch = "wasm32")] use wasm_bindgen_futures::JsFuture; +/// System set for mouse and keyboard input events. Runs in [`PreUpdate`] and [`Update`] #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)] pub struct InputSet; -pub struct InputPlugin; +pub(crate) struct InputPlugin; impl Plugin for InputPlugin { fn build(&self, app: &mut App) { @@ -37,16 +38,19 @@ impl Plugin for InputPlugin { } } +/// Timer for double / triple clicks #[derive(Resource)] pub struct ClickTimer(pub Timer); // TODO: hide this behind #cfg wasm, depends on wasm having own copy/paste fn +/// Crossbeam channel struct for Wasm clipboard data #[allow(dead_code)] pub struct WasmPaste { text: String, entity: Entity, } +/// Async channel for receiving from the clipboard in Wasm #[derive(Resource)] pub struct WasmPasteAsyncChannel { pub tx: crossbeam_channel::Sender<WasmPaste>, @@ -61,7 +65,7 @@ pub(crate) fn input_mouse( mut editor_q: Query<( &mut CosmicEditor, &GlobalTransform, - &CosmicTextPosition, + &CosmicTextAlign, Entity, &XOffset, &mut Sprite, @@ -134,12 +138,12 @@ pub(crate) fn input_mouse( } let (padding_x, padding_y) = match text_position { - CosmicTextPosition::Center { padding: _ } => ( + CosmicTextAlign::Center { padding: _ } => ( get_x_offset_center(width * scale_factor, &buffer), get_y_offset_center(height * scale_factor, &buffer), ), - CosmicTextPosition::TopLeft { padding } => (*padding, *padding), - CosmicTextPosition::Left { padding } => ( + CosmicTextAlign::TopLeft { padding } => (*padding, *padding), + CosmicTextAlign::Left { padding } => ( *padding, get_y_offset_center(height * scale_factor, &buffer), ), @@ -392,8 +396,8 @@ pub(crate) fn kb_input_text( mut cosmic_edit_query: Query<( &mut CosmicEditor, &mut CosmicBuffer, - &CosmicMaxLines, - &CosmicMaxChars, + &MaxLines, + &MaxChars, Entity, Option<&ReadOnly>, )>, @@ -501,8 +505,8 @@ pub fn kb_clipboard( mut cosmic_edit_query: Query<( &mut CosmicEditor, &mut CosmicBuffer, - &CosmicMaxLines, - &CosmicMaxChars, + &MaxLines, + &MaxChars, Entity, Option<&ReadOnly>, )>, @@ -650,8 +654,8 @@ pub fn poll_wasm_paste( &mut CosmicEditor, &mut CosmicBuffer, &crate::DefaultAttrs, - &CosmicMaxChars, - &CosmicMaxChars, + &MaxChars, + &MaxChars, ), Without<ReadOnly>, >, diff --git a/src/lib.rs b/src/lib.rs index 49348cea4d473c48be945fee1d42e6e88045c4cb..bec8d56a5160b54d63eae8f00718f5e56f587178 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,82 @@ +//! # bevy_cosmic_edit +//! +//! Multiline text editor using [`cosmic_text`] for the [`bevy`] game engine! +//! +//! This bevy plugin provides multiline text editing for bevy apps, thanks to [cosmic_text](https://github.com/pop-os/cosmic-text) crate! +//! Emoji, ligatures, and other fancy stuff is supported! +//! +//!  +//! +//! ## Usage +//! +//! ο± *Warning: This plugin is currently in early development, and its API is subject to change.* +//! +//! ``` +//! use bevy::prelude::*; +//! use bevy_cosmic_edit::*; +//! +//! fn setup(mut commands: Commands, mut font_system: ResMut<CosmicFontSystem>) { +//! commands.spawn(Camera2dBundle::default()); +//! +//! // Text attributes +//! let font_size = 16.0; +//! let line_height = 18.0; +//! let attrs = Attrs::new() +//! .family(Family::Monospace) +//! .color(Color::DARK_GRAY.to_cosmic()) +//! .weight(FontWeight::BOLD); +//! +//! // Spawning +//! commands.spawn(CosmicEditBundle { +//! buffer: CosmicBuffer::new(&mut font_system, Metrics::new(font_size, line_height)) +//! .with_text(&mut font_system, "Hello, Cosmic!", attrs), +//! sprite_bundle: SpriteBundle { +//! sprite: Sprite { +//! custom_size: Some(Vec2::new(300.0, 40.0)), +//! ..default() +//! }, +//! ..default() +//! }, +//! ..default() +//! }); +//! } +//! +//! fn main() { +//! App::new() +//! .add_plugins(DefaultPlugins) +//! .add_plugins(CosmicEditPlugin::default()) +//! .add_systems(Startup, setup) +//! .add_systems(Update, change_active_editor_sprite) +//! .run(); +//! } +//! ``` +//! +//! Check the examples folder for much more! +//! +//! Native: +//! +//! ```shell +//! $ cargo r --example font_per_widget +//! ``` +//! +//! Wasm: +//! +//! ```shell +//! $ cargo install wasm-server-runner +//! $ RUSTFLAGS=--cfg=web_sys_unstable_apis cargo r --target wasm32-unknown-unknown --example basic_ui +//! ``` +//! +//! ## Compatibility +//! +//! | bevy | bevy_cosmic_edit | +//! | ------ | ---------------- | +//! | 0.13.0 | 0.16 - latest | +//! | 0.12.* | 0.15 | +//! | 0.11.* | 0.8 - 0.14 | +//! +//! ## License +//! +//! MIT or Apache-2.0 #![allow(clippy::type_complexity)] mod buffer; @@ -18,6 +97,7 @@ use bevy::{prelude::*, transform::TransformSystem}; pub use buffer::*; pub use cosmic_edit::*; +#[doc(no_inline)] pub use cosmic_text::{ Action, Attrs, AttrsOwned, Buffer, Color as CosmicColor, Cursor, Edit, Editor, Family, FontSystem, Metrics, Shaping, Style as FontStyle, Weight as FontWeight, @@ -71,14 +151,6 @@ impl Plugin for CosmicEditPlugin { #[derive(Component)] pub struct CosmicPrimaryCamera; -#[derive(Default, Clone)] -pub enum CursorConfig { - #[default] - Default, - Events, - None, -} - /// Resource struct that holds configuration options for cosmic fonts. #[derive(Resource, Clone)] pub struct CosmicFontConfig { diff --git a/src/password.rs b/src/password.rs index f84819e1504de9eba27eb24a7655cc978a59c124..5dcf3d573eadcd56a15b805ade3a54d4797433ee 100644 --- a/src/password.rs +++ b/src/password.rs @@ -3,8 +3,9 @@ use bevy::prelude::*; use cosmic_text::{Cursor, Edit, Selection, Shaping}; use unicode_segmentation::UnicodeSegmentation; -pub struct PasswordPlugin; +pub(crate) struct PasswordPlugin; +/// System set for password blocking systems. Runs in [`PostUpdate`] #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)] pub struct PasswordSet; @@ -27,6 +28,35 @@ impl Plugin for PasswordPlugin { } } +/// Component to be added to an entity with a [`CosmicEditBundle`] to block contents with a +/// password blocker glyph +/// +/// ``` +/// # use bevy::prelude::*; +/// # use bevy_cosmic_edit::*; +/// # +/// # fn setup(mut commands: Commands) { +/// // Create a new cosmic bundle +/// commands.spawn((CosmicEditBundle { +/// sprite_bundle: SpriteBundle { +/// sprite: Sprite { +/// custom_size: Some(Vec2::new(300.0, 40.0)), +/// ..default() +/// }, +/// ..default() +/// }, +/// ..default() +/// }, +/// Password::default() +/// )); +/// # } +/// # +/// # fn main() { +/// # App::new() +/// # .add_plugins(MinimalPlugins) +/// # .add_plugins(CosmicEditPlugin::default()) +/// # .add_systems(Startup, setup); +/// # } #[derive(Component)] pub struct Password { real_text: String, @@ -42,6 +72,13 @@ impl Default for Password { } } +impl Password { + /// New password component with custom blocker glyph + pub fn new(glyph: char) -> Self { + Self { glyph, ..default() } + } +} + fn hide_password_text( mut q: Query<( &mut Password, diff --git a/src/placeholder.rs b/src/placeholder.rs index d956760bb5ac783dd360b0fe899d804901dc7ad8..a7b4797c9cdef71ed834359144ed23c6a1f92fc9 100644 --- a/src/placeholder.rs +++ b/src/placeholder.rs @@ -2,14 +2,42 @@ use crate::*; use bevy::prelude::*; use cosmic_text::{Attrs, Edit}; +/// Component to be added to an entity with a [`CosmicEditBundle`] add placeholder text +/// +/// ``` +/// # use bevy::prelude::*; +/// # use bevy_cosmic_edit::*; +/// # fn setup(mut commands: Commands) { +/// commands.spawn((CosmicEditBundle { +/// sprite_bundle: SpriteBundle { +/// sprite: Sprite { +/// custom_size: Some(Vec2::new(300.0, 40.0)), +/// ..default() +/// }, +/// ..default() +/// }, +/// ..default() +/// }, +/// Placeholder::new("Email", Attrs::new().color(Color::GRAY.to_cosmic())), +/// )); +/// # } +/// # fn main() { +/// # App::new() +/// # .add_plugins(MinimalPlugins) +/// # .add_plugins(CosmicEditPlugin::default()) +/// # .add_systems(Startup, setup); +/// # } #[derive(Component)] pub struct Placeholder { + /// Placeholder text content pub text: &'static str, + /// Text attributes for placeholder text pub attrs: Attrs<'static>, active: bool, } impl Placeholder { + /// Create a new [`Placeholder`] component with given text and attributes pub fn new(text: impl Into<&'static str>, attrs: Attrs<'static>) -> Self { Self { active: false, @@ -23,7 +51,7 @@ impl Placeholder { } } -pub struct PlaceholderPlugin; +pub(crate) struct PlaceholderPlugin; impl Plugin for PlaceholderPlugin { fn build(&self, app: &mut App) { diff --git a/src/render.rs b/src/render.rs index ef9d04d729f65caa90e34c754ae3a8637358200f..ba3fd597324b281b88bbea9c09953b5aa63c0bc7 100644 --- a/src/render.rs +++ b/src/render.rs @@ -3,10 +3,11 @@ use bevy::{prelude::*, render::render_resource::Extent3d}; use cosmic_text::{Color, Edit, SwashCache}; use image::{imageops::FilterType, GenericImageView}; +/// System set for cosmic text rendering systems. Runs in [`PostUpdate`] #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)] pub struct RenderSet; -pub struct RenderPlugin; +pub(crate) struct RenderPlugin; impl Plugin for RenderPlugin { fn build(&self, app: &mut App) { @@ -26,7 +27,7 @@ pub(crate) struct SwashCacheState { pub swash_cache: SwashCache, } -pub fn blink_cursor(mut q: Query<&mut CosmicEditor, Without<ReadOnly>>, time: Res<Time>) { +pub(crate) fn blink_cursor(mut q: Query<&mut CosmicEditor, Without<ReadOnly>>, time: Res<Time>) { for mut e in q.iter_mut() { e.cursor_timer.tick(time.delta()); if e.cursor_timer.just_finished() { @@ -81,8 +82,8 @@ fn render_texture( Option<&mut CosmicEditor>, &mut CosmicBuffer, &DefaultAttrs, - &CosmicBackground, - &FillColor, + &CosmicBackgroundImage, + &CosmicBackgroundColor, &CursorColor, &SelectionColor, &Handle<Image>, @@ -90,7 +91,7 @@ fn render_texture( &CosmicPadding, &XOffset, Option<&ReadOnly>, - &CosmicTextPosition, + &CosmicTextAlign, )>, mut font_system: ResMut<CosmicFontSystem>, mut images: ResMut<Assets<Image>>, @@ -149,9 +150,9 @@ fn render_texture( .unwrap_or(cosmic_text::Color::rgb(0, 0, 0)); let min_pad = match position { - CosmicTextPosition::Center { padding } => *padding as f32, - CosmicTextPosition::TopLeft { padding } => *padding as f32, - CosmicTextPosition::Left { padding } => *padding as f32, + CosmicTextAlign::Center { padding } => *padding as f32, + CosmicTextAlign::TopLeft { padding } => *padding as f32, + CosmicTextAlign::Left { padding } => *padding as f32, }; let draw_closure = |x, y, w, h, color| { diff --git a/src/util.rs b/src/util.rs index 6175e52a7b5cdf74b3bce6e5af9454af830bbde6..e51860883568460be40f13a4316d8869e9e1444a 100644 --- a/src/util.rs +++ b/src/util.rs @@ -2,12 +2,30 @@ use crate::*; use bevy::{prelude::*, window::PrimaryWindow}; +/// Trait for adding color conversion from [`bevy::prelude::Color`] to [`cosmic_text::Color`] +pub trait ColorExtras { + fn to_cosmic(self) -> CosmicColor; +} + +impl ColorExtras for Color { + fn to_cosmic(self) -> CosmicColor { + CosmicColor::rgba( + (self.r() * 255.) as u8, + (self.g() * 255.) as u8, + (self.b() * 255.) as u8, + (self.a() * 255.) as u8, + ) + } +} + +/// System to unfocus editors when \[Esc\] is pressed pub fn deselect_editor_on_esc(i: Res<ButtonInput<KeyCode>>, mut focus: ResMut<FocusedWidget>) { if i.just_pressed(KeyCode::Escape) { focus.0 = None; } } +/// Function to find the location of the mouse cursor in a cosmic widget pub fn get_node_cursor_pos( window: &Window, node_transform: &GlobalTransform, @@ -44,6 +62,7 @@ pub fn get_node_cursor_pos( }) } +/// System to allow focus on click for sprite widgets pub fn change_active_editor_sprite( mut commands: Commands, windows: Query<&Window, With<PrimaryWindow>>, @@ -77,6 +96,7 @@ pub fn change_active_editor_sprite( } } +/// System to allow focus on click for UI widgets pub fn change_active_editor_ui( mut commands: Commands, mut interaction_query: Query< @@ -91,6 +111,7 @@ pub fn change_active_editor_ui( } } +/// System to print editor text content on change pub fn print_editor_text( text_inputs_q: Query<&CosmicEditor>, mut previous_value: Local<Vec<String>>, @@ -110,20 +131,13 @@ pub fn print_editor_text( } } -pub fn bevy_color_to_cosmic(color: bevy::prelude::Color) -> CosmicColor { - CosmicColor::rgba( - (color.r() * 255.) as u8, - (color.g() * 255.) as u8, - (color.b() * 255.) as u8, - (color.a() * 255.) as u8, - ) -} - +/// Calls javascript to get the current timestamp #[cfg(target_arch = "wasm32")] pub fn get_timestamp() -> f64 { js_sys::Date::now() } +/// Utility function to get the current unix timestamp #[cfg(not(target_arch = "wasm32"))] pub fn get_timestamp() -> f64 { use std::time::SystemTime; diff --git a/src/widget.rs b/src/widget.rs index e79ac22f3ff7a01b9bc923592dfcf25899b1b400..8f6ec47a25fbfd942cba32c2397db5ba3bf829d2 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -2,10 +2,11 @@ use crate::*; use bevy::{prelude::*, window::PrimaryWindow}; use cosmic_text::Affinity; +/// System set for cosmic text layout systems. Runs in [`PostUpdate`] #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)] pub struct WidgetSet; -pub struct WidgetPlugin; +pub(crate) struct WidgetPlugin; impl Plugin for WidgetPlugin { fn build(&self, app: &mut App) { @@ -26,30 +27,39 @@ impl Plugin for WidgetPlugin { } } +/// Wrapper for a [`Vec2`] describing the horizontal and vertical padding of a widget. +/// This is set programatically, not for user modification. +/// To set a widget's padding, use [`CosmicTextAlign`] #[derive(Component, Default, Deref, DerefMut, Debug)] pub struct CosmicPadding(pub Vec2); +/// Wrapper for a [`Vec2`] describing the horizontal and vertical size of a widget. +/// This is set programatically, not for user modification. +/// To set a widget's size, use either it's [`Sprite`] dimensions or modify the target UI element's +/// size. #[derive(Component, Default, Deref, DerefMut)] pub struct CosmicWidgetSize(pub Vec2); -pub fn reshape(mut query: Query<&mut CosmicEditor>, mut font_system: ResMut<CosmicFontSystem>) { +/// Reshapes text in a [`CosmicEditor`] +fn reshape(mut query: Query<&mut CosmicEditor>, mut font_system: ResMut<CosmicFontSystem>) { for mut cosmic_editor in query.iter_mut() { cosmic_editor.shape_as_needed(&mut font_system.0, false); } } -pub fn set_padding( +/// Programatically sets the [`CosmicPadding`] of a widget based on it's [`CosmicTextAlign`] +fn set_padding( mut query: Query< ( &mut CosmicPadding, - &CosmicTextPosition, + &CosmicTextAlign, &CosmicBuffer, &CosmicWidgetSize, Option<&CosmicEditor>, ), Or<( With<CosmicEditor>, - Changed<CosmicTextPosition>, + Changed<CosmicTextAlign>, Changed<CosmicBuffer>, Changed<CosmicWidgetSize>, )>, @@ -68,12 +78,12 @@ pub fn set_padding( } padding.0 = match position { - CosmicTextPosition::Center { padding: _ } => Vec2::new( + CosmicTextAlign::Center { padding: _ } => Vec2::new( get_x_offset_center(size.0.x, &buffer) as f32, get_y_offset_center(size.0.y, &buffer) as f32, ), - CosmicTextPosition::TopLeft { padding } => Vec2::new(*padding as f32, *padding as f32), - CosmicTextPosition::Left { padding } => Vec2::new( + CosmicTextAlign::TopLeft { padding } => Vec2::new(*padding as f32, *padding as f32), + CosmicTextAlign::Left { padding } => Vec2::new( *padding as f32, get_y_offset_center(size.0.y, &buffer) as f32, ), @@ -81,7 +91,8 @@ pub fn set_padding( } } -pub fn set_widget_size( +/// Programatically sets the [`CosmicWidgetSize`] of a widget based on it's [`Sprite`] properties +fn set_widget_size( mut query: Query<(&mut CosmicWidgetSize, &Sprite), Changed<Sprite>>, windows: Query<&Window, With<PrimaryWindow>>, ) { @@ -95,39 +106,41 @@ pub fn set_widget_size( } } -pub fn set_buffer_size( +/// Sets the internal [`Buffer`]'s size according to the [`CosmicWidgetSize`] and [`CosmicTextAlign`] +fn set_buffer_size( mut query: Query< ( &mut CosmicBuffer, - &CosmicMode, + &CosmicWrap, &CosmicWidgetSize, - &CosmicTextPosition, + &CosmicTextAlign, ), Or<( - Changed<CosmicMode>, + Changed<CosmicWrap>, Changed<CosmicWidgetSize>, - Changed<CosmicTextPosition>, + Changed<CosmicTextAlign>, )>, >, mut font_system: ResMut<CosmicFontSystem>, ) { for (mut buffer, mode, size, position) in query.iter_mut() { let padding_x = match position { - CosmicTextPosition::Center { padding: _ } => 0., - CosmicTextPosition::TopLeft { padding } => *padding as f32, - CosmicTextPosition::Left { padding } => *padding as f32, + CosmicTextAlign::Center { padding: _ } => 0., + CosmicTextAlign::TopLeft { padding } => *padding as f32, + CosmicTextAlign::Left { padding } => *padding as f32, }; let (buffer_width, buffer_height) = match mode { - CosmicMode::InfiniteLine => (f32::MAX, size.0.y), - CosmicMode::Wrap => (size.0.x - padding_x, size.0.y), + CosmicWrap::InfiniteLine => (f32::MAX, size.0.y), + CosmicWrap::Wrap => (size.0.x - padding_x, size.0.y), }; buffer.set_size(&mut font_system.0, buffer_width, buffer_height); } } -pub fn new_image_from_default( +/// Instantiates a new image for a [`CosmicBuffer`] +fn new_image_from_default( mut query: Query<&mut Handle<Image>, Added<CosmicBuffer>>, mut images: ResMut<Assets<Image>>, ) { @@ -136,17 +149,17 @@ pub fn new_image_from_default( } } -pub fn set_x_offset( +fn set_x_offset( mut query: Query<( &mut XOffset, - &CosmicMode, + &CosmicWrap, &CosmicEditor, &CosmicWidgetSize, - &CosmicTextPosition, + &CosmicTextAlign, )>, ) { for (mut x_offset, mode, editor, size, position) in query.iter_mut() { - if mode != &CosmicMode::InfiniteLine { + if mode != &CosmicWrap::InfiniteLine { return; } @@ -170,9 +183,9 @@ pub fn set_x_offset( } let padding_x = match position { - CosmicTextPosition::Center { padding } => *padding as f32, - CosmicTextPosition::TopLeft { padding } => *padding as f32, - CosmicTextPosition::Left { padding } => *padding as f32, + CosmicTextAlign::Center { padding } => *padding as f32, + CosmicTextAlign::TopLeft { padding } => *padding as f32, + CosmicTextAlign::Left { padding } => *padding as f32, }; if x_offset.width == 0. { @@ -192,7 +205,7 @@ pub fn set_x_offset( } } -pub fn set_sprite_size_from_ui( +fn set_sprite_size_from_ui( mut source_q: Query<&mut Sprite, With<CosmicBuffer>>, dest_q: Query<(&Node, &CosmicSource), Changed<Node>>, ) {