diff --git a/examples/bevy_api_testing.rs b/examples/bevy_api_testing.rs index 6f1e9ae672cc5af9d5352daee3172c8301bced69..f51ff48c77107186d6925db5da1fa5e6cf9bc194 100644 --- a/examples/bevy_api_testing.rs +++ b/examples/bevy_api_testing.rs @@ -18,6 +18,7 @@ fn setup(mut commands: Commands) { Attrs::new().color(bevy_color_to_cosmic(Color::GREEN)), )), max_lines: CosmicMaxLines(1), + placeholder_setter: PlaceholderText(CosmicText::OneStyle("Place held :)".into())), ..default() }); diff --git a/examples/every_option.rs b/examples/every_option.rs index 431906892ac73b7248cc820fdcf0bf830faa3c03..c2330f32a43203c6436fb5c6316965fb489339dd 100644 --- a/examples/every_option.rs +++ b/examples/every_option.rs @@ -47,6 +47,10 @@ fn setup(mut commands: Commands, windows: Query<&Window, With<PrimaryWindow>>) { max_lines: CosmicMaxLines(1), text_setter: CosmicText::OneStyle("BANANA IS THE CODEWORD!".into()), mode: CosmicMode::Wrap, + placeholder_setter: PlaceholderText(CosmicText::OneStyle("Placeholder".into())), + placeholder_attrs: PlaceholderAttrs(AttrsOwned::new( + Attrs::new().color(CosmicColor::rgb(88, 88, 88)), + )), }) .id(); diff --git a/examples/text_input.rs b/examples/text_input.rs index 75d5ee3dc874e5f0c12e68b260fed7ab5e75463c..f8e6773072243914595ff35be3b4a0f00e33527c 100644 --- a/examples/text_input.rs +++ b/examples/text_input.rs @@ -4,6 +4,8 @@ use bevy_cosmic_edit::*; fn create_editable_widget(commands: &mut Commands, scale_factor: f32, text: String) -> Entity { let attrs = AttrsOwned::new(Attrs::new().color(bevy_color_to_cosmic(Color::hex("4d4d4d").unwrap()))); + let placeholder_attrs = + AttrsOwned::new(Attrs::new().color(bevy_color_to_cosmic(Color::hex("#e6e6e6").unwrap()))); commands .spawn(CosmicEditUiBundle { border_color: Color::hex("#ededed").unwrap().into(), @@ -25,6 +27,8 @@ fn create_editable_widget(commands: &mut Commands, scale_factor: f32, text: Stri text_setter: CosmicText::OneStyle(text), text_position: CosmicTextPosition::Left { padding: 20 }, mode: CosmicMode::InfiniteLine, + placeholder_setter: PlaceholderText(CosmicText::OneStyle("Type something...".into())), + placeholder_attrs: PlaceholderAttrs(placeholder_attrs.clone()), ..default() }) .id() diff --git a/src/lib.rs b/src/lib.rs index d86b6bbdeb40bafd5d01302edad5374ebc054285..cb9113f2ba8b10ad3c6ad1a44ef13b3f5b8653f0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -190,6 +190,20 @@ pub struct CosmicMaxChars(pub usize); #[derive(Component, Default)] pub struct FillColor(pub Color); +#[derive(Component)] +pub struct Placeholder(pub CosmicEditor); + +#[derive(Component, Default)] +pub struct PlaceholderText(pub CosmicText); + +#[derive(Component)] +pub struct PlaceholderAttrs(pub AttrsOwned); + +impl Default for PlaceholderAttrs { + fn default() -> Self { + Self(AttrsOwned::new(Attrs::new())) + } +} #[derive(Bundle)] pub struct CosmicEditUiBundle { // Bevy UI bits @@ -245,6 +259,9 @@ pub struct CosmicEditUiBundle { pub text_setter: CosmicText, /// Text input mode pub mode: CosmicMode, + /// Setting this will update the placeholder text + pub placeholder_setter: PlaceholderText, + pub placeholder_attrs: PlaceholderAttrs, } impl Default for CosmicEditUiBundle { @@ -272,6 +289,8 @@ impl Default for CosmicEditUiBundle { text_setter: Default::default(), mode: Default::default(), background_color: BackgroundColor(Color::WHITE), + placeholder_setter: Default::default(), + placeholder_attrs: Default::default(), } } } @@ -387,36 +406,43 @@ impl Plugin for CosmicEditPlugin { fn build(&self, app: &mut App) { let font_system = create_cosmic_font_system(self.font_config.clone()); - app.add_systems(First, (cosmic_editor_builder, on_scale_factor_change)) - .add_systems(PreUpdate, update_buffer_text) - .add_systems( - Update, - ( - input_kb, - input_mouse, - blink_cursor, - freeze_cursor_blink, - hide_inactive_or_readonly_cursor, - clear_inactive_selection, - ), - ) - .add_systems( - PostUpdate, - (cosmic_edit_redraw_buffer_ui, cosmic_edit_redraw_buffer) - .after(TransformSystem::TransformPropagate), - ) - .init_resource::<Focus>() - .insert_resource(CursorBlinkTimer(Timer::from_seconds( - 0.53, - TimerMode::Repeating, - ))) - .insert_resource(CursorVisibility(true)) - .insert_resource(SwashCacheState { - swash_cache: SwashCache::new(), - }) - .insert_resource(CosmicFontSystem(font_system)) - .insert_resource(ClickTimer(Timer::from_seconds(0.5, TimerMode::Once))) - .add_event::<CosmicTextChanged>(); + app.add_systems( + First, + ( + cosmic_editor_builder, + placeholder_builder, + on_scale_factor_change, + ), + ) + .add_systems(PreUpdate, (update_buffer_text, update_placeholder_text)) + .add_systems( + Update, + ( + input_kb, + input_mouse, + blink_cursor, + freeze_cursor_blink, + hide_inactive_or_readonly_cursor, + clear_inactive_selection, + ), + ) + .add_systems( + PostUpdate, + (cosmic_edit_redraw_buffer_ui, cosmic_edit_redraw_buffer) + .after(TransformSystem::TransformPropagate), + ) + .init_resource::<Focus>() + .insert_resource(CursorBlinkTimer(Timer::from_seconds( + 0.53, + TimerMode::Repeating, + ))) + .insert_resource(CursorVisibility(true)) + .insert_resource(SwashCacheState { + swash_cache: SwashCache::new(), + }) + .insert_resource(CosmicFontSystem(font_system)) + .insert_resource(ClickTimer(Timer::from_seconds(0.5, TimerMode::Once))) + .add_event::<CosmicTextChanged>(); match self.change_cursor { CursorConfig::Default => { @@ -479,6 +505,23 @@ fn cosmic_editor_builder( } } +fn placeholder_builder( + mut added_editors: Query<(Entity, &CosmicMetrics), Added<PlaceholderText>>, + mut font_system: ResMut<CosmicFontSystem>, + mut commands: Commands, +) { + for (entity, metrics) in added_editors.iter_mut() { + let buffer = Buffer::new( + &mut font_system.0, + Metrics::new(metrics.font_size, metrics.line_height).scale(metrics.scale_factor), + ); + + let editor = CosmicEditor(Editor::new(buffer)); + + commands.entity(entity).insert(Placeholder(editor)); + } +} + 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(); @@ -572,6 +615,21 @@ fn update_buffer_text( } } +/// Updates editor buffer when text component changes +fn update_placeholder_text( + mut editor_q: Query< + (&mut Placeholder, &mut PlaceholderText, &PlaceholderAttrs), + Changed<PlaceholderText>, + >, + mut font_system: ResMut<CosmicFontSystem>, +) { + for (mut editor, text, attrs) in editor_q.iter_mut() { + editor + .0 + .set_text(text.0.to_owned(), attrs.0.clone(), &mut font_system.0); + } +} + fn trim_text(text: CosmicText, max_chars: usize, max_lines: usize) -> CosmicText { if max_chars == 0 && max_lines == 0 { // no limits, no work to do @@ -722,30 +780,41 @@ fn blink_cursor( time: Res<Time>, active_editor: ResMut<Focus>, mut cosmic_editor_q: Query<&mut CosmicEditor, Without<ReadOnly>>, + mut placeholder_editor_q: Query<&mut Placeholder, Without<ReadOnly>>, ) { if let Some(e) = active_editor.0 { - if let Ok(mut editor) = cosmic_editor_q.get_mut(e) { - timer.0.tick(time.delta()); - if !timer.0.just_finished() && !active_editor.is_changed() { - return; - } - visibility.0 = !visibility.0; + timer.0.tick(time.delta()); + if !timer.0.just_finished() && !active_editor.is_changed() { + return; + } + visibility.0 = !visibility.0; - // always start cursor visible on focus - if active_editor.is_changed() { - visibility.0 = true; - timer.0.set_elapsed(Duration::ZERO); - } + // always start cursor visible on focus + if active_editor.is_changed() { + visibility.0 = true; + timer.0.set_elapsed(Duration::ZERO); + } - let mut cursor = editor.0.cursor(); - let new_color = if visibility.0 { - None - } else { - Some(cosmic_text::Color::rgba(0, 0, 0, 0)) - }; + let new_color = if visibility.0 { + None + } else { + Some(cosmic_text::Color::rgba(0, 0, 0, 0)) + }; + + if let Ok(mut editor) = cosmic_editor_q.get_mut(e) { + let editor = &mut editor.0; + let mut cursor = editor.cursor(); cursor.color = new_color; - editor.0.set_cursor(cursor); - editor.0.buffer_mut().set_redraw(true); + editor.set_cursor(cursor); + editor.buffer_mut().set_redraw(true); + } + + if let Ok(mut placeholder) = placeholder_editor_q.get_mut(e) { + let placeholder = &mut placeholder.0 .0; + let mut cursor_p = placeholder.cursor(); + cursor_p.color = new_color; + placeholder.set_cursor(cursor_p); + placeholder.buffer_mut().set_redraw(true); } } } @@ -784,6 +853,7 @@ fn freeze_cursor_blink( fn hide_inactive_or_readonly_cursor( mut cosmic_editor_q_readonly: Query<&mut CosmicEditor, With<ReadOnly>>, + mut cosmic_editor_q_placeholder: Query<(Entity, &mut Placeholder, Option<&ReadOnly>)>, mut cosmic_editor_q_editable: Query<(Entity, &mut CosmicEditor), Without<ReadOnly>>, active_editor: Res<Focus>, ) { @@ -798,6 +868,16 @@ fn hide_inactive_or_readonly_cursor( return; } + for (e, mut editor, readonly_opt) in &mut cosmic_editor_q_placeholder.iter_mut() { + if e != active_editor.0.unwrap() || readonly_opt.is_some() { + let editor = &mut editor.0; + let mut cursor = editor.0.cursor(); + cursor.color = Some(cosmic_text::Color::rgba(0, 0, 0, 0)); + editor.0.set_cursor(cursor); + editor.0.buffer_mut().set_redraw(true); + } + } + for (e, mut editor) in &mut cosmic_editor_q_editable.iter_mut() { if e != active_editor.0.unwrap() { let mut cursor = editor.0.cursor(); @@ -829,6 +909,7 @@ fn cosmic_edit_redraw_buffer_ui( mut swash_cache_state: ResMut<SwashCacheState>, mut cosmic_edit_query: Query<( &mut CosmicEditor, + Option<&mut Placeholder>, &CosmicAttrs, &CosmicBackground, &FillColor, @@ -846,6 +927,7 @@ fn cosmic_edit_redraw_buffer_ui( for ( mut editor, + mut placeholder_opt, attrs, background_image, fill_color, @@ -857,8 +939,20 @@ fn cosmic_edit_redraw_buffer_ui( mode, ) in &mut cosmic_edit_query.iter_mut() { - editor.0.shape_as_needed(&mut font_system.0); - if !editor.0.buffer().redraw() { + let editor = if editor.get_text().is_empty() && placeholder_opt.is_some() { + let placeholder = &mut placeholder_opt.as_mut().unwrap().0 .0; + let mut cursor = placeholder.cursor(); + cursor.index = 0; + placeholder.set_cursor(cursor); + placeholder.buffer_mut().set_redraw(true); + *x_offset = XOffset(None); + placeholder + } else { + &mut editor.0 + }; + + editor.shape_as_needed(&mut font_system.0); + if !editor.buffer().redraw() { continue; } @@ -879,12 +973,11 @@ fn cosmic_edit_redraw_buffer_ui( CosmicMode::Wrap => (widget_width - padding_x, widget_height), }; editor - .0 .buffer_mut() .set_size(&mut font_system.0, buffer_width, buffer_height); if mode == &CosmicMode::AutoHeight { - let text_size = get_text_size(editor.0.buffer()); + let text_size = get_text_size(editor.buffer()); let text_height = (text_size.1 + 30.) / primary_window.scale_factor() as f32; if text_height > height { height = text_height.ceil(); @@ -897,7 +990,7 @@ fn cosmic_edit_redraw_buffer_ui( &mut x_offset, &mut images, &mut swash_cache_state, - &mut editor.0, + editor, attrs, background_image.0.clone(), fill_color.0,