diff --git a/examples/basic_sprite.rs b/examples/basic_sprite.rs index ee3d6d00411690ac2dd052cb7ebd880362a83c85..35d46cda28aad9cc1d9ebfc8c79b9498ebe8196a 100644 --- a/examples/basic_sprite.rs +++ b/examples/basic_sprite.rs @@ -32,7 +32,7 @@ fn setup(mut commands: Commands, windows: Query<&Window, With<PrimaryWindow>>) { }, text_position: CosmicTextPosition::Center, cosmic_attrs: CosmicAttrs(AttrsOwned::new(attrs)), - text: CosmicText::OneStyle("馃榾馃榾馃榾 x => y".to_string()), + text_setter: CosmicText::OneStyle("馃榾馃榾馃榾 x => y".to_string()), ..default() }; diff --git a/examples/basic_ui.rs b/examples/basic_ui.rs index 18267ec80d7f403f07971f0c4653c89a86f888a4..777030f763b770ab44874a28dd001f36970ac29a 100644 --- a/examples/basic_ui.rs +++ b/examples/basic_ui.rs @@ -33,7 +33,7 @@ fn setup(mut commands: Commands, windows: Query<&Window, With<PrimaryWindow>>) { }, text_position: CosmicTextPosition::Center, cosmic_attrs: CosmicAttrs(AttrsOwned::new(attrs)), - text: CosmicText::OneStyle("馃榾馃榾馃榾 x => y".to_string()), + text_setter: CosmicText::OneStyle("馃榾馃榾馃榾 x => y".to_string()), ..default() }; diff --git a/examples/every_option.rs b/examples/every_option.rs index e1c4ebdecec860908da83215c5c5a28d077c7367..73ee7be3c21919e244b446f682b05eedd60a2fed 100644 --- a/examples/every_option.rs +++ b/examples/every_option.rs @@ -44,7 +44,7 @@ fn setup(mut commands: Commands, windows: Query<&Window, With<PrimaryWindow>>) { }, max_chars: CosmicMaxChars(15), max_lines: CosmicMaxLines(1), - text: CosmicText::OneStyle("BANANA IS THE CODEWORD!".into()), + text_setter: CosmicText::OneStyle("BANANA IS THE CODEWORD!".into()), mode: CosmicMode::Wrap, }) .id(); @@ -85,7 +85,7 @@ fn text_swapper( } let editor = editor_q.single(); - println!("X OFFSET: {}", get_x_offset_center(editor.0.buffer())); + println!("X OFFSET: {}", get_x_offset_center(50., editor.0.buffer())); } fn main() { diff --git a/examples/font_per_widget.rs b/examples/font_per_widget.rs index e8871b99f63603cc2d2c6fe5679292b982533616..303862ebc33e74a835cb18f98d9ed9a3a9afbd69 100644 --- a/examples/font_per_widget.rs +++ b/examples/font_per_widget.rs @@ -221,7 +221,7 @@ fn setup(mut commands: Commands, windows: Query<&Window, With<PrimaryWindow>>) { ..default() }, background_color: BackgroundColor(Color::WHITE), - text: CosmicText::MultiStyle(lines), + text_setter: CosmicText::MultiStyle(lines), ..default() }; @@ -243,7 +243,7 @@ fn setup(mut commands: Commands, windows: Query<&Window, With<PrimaryWindow>>) { height: Val::Percent(100.), ..default() }, - text: CosmicText::OneStyle("Widget 2.\nClick on me =>".to_string()), + text_setter: CosmicText::OneStyle("Widget 2.\nClick on me =>".to_string()), ..default() }; diff --git a/examples/multiple_sprites.rs b/examples/multiple_sprites.rs index f088f8089ded2c88c47247b364db82cb391f9f0f..3fab9d47117ec3dc98430cfa93530b4202df8cdd 100644 --- a/examples/multiple_sprites.rs +++ b/examples/multiple_sprites.rs @@ -33,7 +33,7 @@ fn setup(mut commands: Commands, windows: Query<&Window, With<PrimaryWindow>>) { transform: Transform::from_translation(Vec3::new(-primary_window.width() / 4., 0., 1.)), text_position: CosmicTextPosition::Center, background_color: BackgroundColor(Color::ALICE_BLUE), - text: CosmicText::OneStyle("馃榾馃榾馃榾 x => y".to_string()), + text_setter: CosmicText::OneStyle("馃榾馃榾馃榾 x => y".to_string()), ..default() }; @@ -54,7 +54,7 @@ fn setup(mut commands: Commands, windows: Query<&Window, With<PrimaryWindow>>) { )), text_position: CosmicTextPosition::Center, background_color: BackgroundColor(Color::GRAY.with_a(0.5)), - text: CosmicText::OneStyle("Widget_2. Click on me".to_string()), + text_setter: CosmicText::OneStyle("Widget_2. Click on me".to_string()), ..default() }; diff --git a/examples/readonly.rs b/examples/readonly.rs index 9f0a56e5b99d97be024b9ae688c0367c420f802f..5247f030528b0cb76f59f5fd7389b4acaca1c966 100644 --- a/examples/readonly.rs +++ b/examples/readonly.rs @@ -34,7 +34,7 @@ fn setup(mut commands: Commands, windows: Query<&Window, With<PrimaryWindow>>) { line_height: 18., scale_factor: primary_window.scale_factor() as f32, }, - text: CosmicText::OneStyle("馃榾馃榾馃榾 x => y\nRead only widget".to_string()), + text_setter: CosmicText::OneStyle("馃榾馃榾馃榾 x => y\nRead only widget".to_string()), ..default() }; diff --git a/examples/text_input.rs b/examples/text_input.rs index d9e0807a348a0add16abd5576dd196cfc61cd867..5c83d7b9251dcf2eb0c9c3e9c6e50da749742a33 100644 --- a/examples/text_input.rs +++ b/examples/text_input.rs @@ -1,17 +1,12 @@ use bevy::{prelude::*, window::PrimaryWindow}; use bevy_cosmic_edit::*; -fn setup(mut commands: Commands, windows: Query<&Window, With<PrimaryWindow>>) { - commands.spawn(Camera2dBundle::default()); - +fn create_editable_widget(commands: &mut Commands, scale_factor: f32, text: String) -> Entity { let attrs = AttrsOwned::new(Attrs::new().color(CosmicColor::rgb(0, 0, 0))); - let primary_window = windows.single(); - - let editor = commands + commands .spawn(CosmicEditUiBundle { border_color: Color::LIME_GREEN.into(), style: Style { - // Size and position of text box border: UiRect::all(Val::Px(4.)), width: Val::Percent(20.), height: Val::Px(50.), @@ -23,23 +18,85 @@ fn setup(mut commands: Commands, windows: Query<&Window, With<PrimaryWindow>>) { cosmic_metrics: CosmicMetrics { font_size: 16., line_height: 16., - scale_factor: primary_window.scale_factor() as f32, + scale_factor, }, max_lines: CosmicMaxLines(1), - text: CosmicText::OneStyle("".into()), + text_setter: CosmicText::OneStyle(text), text_position: CosmicTextPosition::Left { padding: 20 }, mode: CosmicMode::InfiniteLine, ..default() }) - .id(); + .id() +} + +fn create_readonly_widget(commands: &mut Commands, scale_factor: f32, text: String) -> Entity { + let attrs = AttrsOwned::new(Attrs::new().color(CosmicColor::rgb(0, 0, 0))); + commands + .spawn(( + CosmicEditUiBundle { + border_color: Color::LIME_GREEN.into(), + style: Style { + border: UiRect::all(Val::Px(4.)), + width: Val::Percent(20.), + height: Val::Px(50.), + left: Val::Percent(40.), + top: Val::Px(100.), + ..default() + }, + cosmic_attrs: CosmicAttrs(attrs.clone()), + cosmic_metrics: CosmicMetrics { + font_size: 16., + line_height: 16., + scale_factor, + }, + text_setter: CosmicText::OneStyle(text), + text_position: CosmicTextPosition::Left { padding: 20 }, + mode: CosmicMode::AutoHeight, + ..default() + }, + ReadOnly, + )) + .id() +} +fn setup(mut commands: Commands, windows: Query<&Window, With<PrimaryWindow>>) { + commands.spawn(Camera2dBundle::default()); + let primary_window = windows.single(); + let editor = create_editable_widget( + &mut commands, + primary_window.scale_factor() as f32, + "".to_string(), + ); commands.insert_resource(Focus(Some(editor))); } +fn handle_enter( + mut commands: Commands, + keys: Res<Input<KeyCode>>, + mut mode: Query<(Entity, &CosmicEditor, &mut CosmicMode)>, + windows: Query<&Window, With<PrimaryWindow>>, +) { + if keys.just_pressed(KeyCode::Return) { + let scale_factor = windows.single().scale_factor() as f32; + for (entity, editor, mode) in mode.iter_mut() { + let text = editor.get_text(); + commands.entity(entity).despawn_recursive(); + if *mode == CosmicMode::AutoHeight { + let editor = create_editable_widget(&mut commands, scale_factor, text); + commands.insert_resource(Focus(Some(editor))); + } else { + let editor = create_readonly_widget(&mut commands, scale_factor, text); + commands.insert_resource(Focus(Some(editor))); + }; + } + } +} + fn main() { App::new() .add_plugins(DefaultPlugins) .add_plugins(CosmicEditPlugin::default()) + .add_systems(Update, handle_enter) .add_systems(Startup, setup) .run(); } diff --git a/src/lib.rs b/src/lib.rs index 7e60577ae41dbfcee69855b9b777bda469ab6df6..44eb743e3ad5bfec7d90ad866b983b3a881278ea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ use bevy::{ input::mouse::{MouseScrollUnit, MouseWheel}, prelude::*, render::{render_resource::Extent3d, texture::DEFAULT_IMAGE_HANDLE}, + transform::TransformSystem, ui::FocusPolicy, window::{PrimaryWindow, WindowScaleFactorChanged}, }; @@ -19,7 +20,7 @@ use cosmic_text::{ }; use image::{imageops::FilterType, GenericImageView}; -#[derive(Clone, Component, PartialEq)] +#[derive(Clone, Component, PartialEq, Debug)] pub enum CosmicText { OneStyle(String), MultiStyle(Vec<Vec<(String, AttrsOwned)>>), @@ -92,21 +93,11 @@ impl CosmicEditor { attrs: AttrsOwned, font_system: &mut FontSystem, ) -> &mut Self { - // TODO invoke trim_text here + // TODO: invoke trim_text here let editor = &mut self.0; editor.buffer_mut().lines.clear(); match text { CosmicText::OneStyle(text) => { - // TODO: this is a workaround - // if `set_text` called with text length smaller than the current buffer cosmic-text panics - // due to cursor being out of bounds. - // Should be removed once https://github.com/StaffEngineer/bevy_cosmic_edit/issues/52 fixed. - if text == *"" { - let mut current_cursor = editor.cursor(); - current_cursor.line = 0; - current_cursor.index = 0; - editor.set_cursor(current_cursor); - } editor.buffer_mut().set_text( font_system, text.as_str(), @@ -163,63 +154,19 @@ impl CosmicEditor { /// Adds the font system to each editor when added fn cosmic_editor_builder( - mut added_editors: Query< - ( - Entity, - &mut CosmicText, - &CosmicAttrs, - &CosmicMetrics, - &CosmicMaxChars, - &CosmicMaxLines, - Option<&ReadOnly>, - Option<&Node>, - Option<&Sprite>, - ), - Added<CosmicText>, - >, + mut added_editors: Query<(Entity, &CosmicMetrics), Added<CosmicText>>, mut font_system: ResMut<CosmicFontSystem>, mut commands: Commands, ) { - for (entity, text, attrs, metrics, max_chars, max_lines, readonly, node, sprite) in - added_editors.iter_mut() - { + 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), ); // buffer.set_wrap(&mut font_system.0, cosmic_text::Wrap::None); - let mut editor = Editor::new(buffer); - - if let Some(node) = node { - editor - .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 - .buffer_mut() - .set_size(&mut font_system.0, size.x, size.y) - } - } + let editor = Editor::new(buffer); - // hide cursor on readonly buffers - // TODO do this seperately, allow for readonly to be toggled - let mut cursor = editor.cursor(); - if readonly.is_some() { - cursor.color = Some(cosmic_text::Color::rgba(0, 0, 0, 0)); - } - editor.set_cursor(cursor); - - let mut editor_component = CosmicEditor(editor); - - let text = trim_text(text.to_owned(), max_chars.0, max_lines.0); - editor_component.set_text(text, attrs.0.clone(), &mut font_system.0); - - commands.entity(entity).insert(editor_component); - - // Add edit history + commands.entity(entity).insert(CosmicEditor(editor)); commands.entity(entity).insert(CosmicEditHistory::default()); commands.entity(entity).insert(XOffset(None)); } @@ -245,6 +192,15 @@ fn update_buffer_text( } } +fn update_cursor_position( + mut editor_q: Query<&mut CosmicEditor, Added<CosmicText>>, + mut font_system: ResMut<CosmicFontSystem>, +) { + for mut editor in editor_q.iter_mut() { + editor.0.action(&mut font_system.0, Action::BufferEnd); + } +} + #[derive(Component)] pub struct CosmicAttrs(pub AttrsOwned); @@ -313,7 +269,7 @@ pub struct CosmicEditUiBundle { /// How many characters are allowed in buffer, 0 for no limit pub max_chars: CosmicMaxChars, /// Setting this will update the buffer's text - pub text: CosmicText, + pub text_setter: CosmicText, /// Text input mode pub mode: CosmicMode, } @@ -340,7 +296,7 @@ impl Default for CosmicEditUiBundle { background_image: Default::default(), max_lines: Default::default(), max_chars: Default::default(), - text: Default::default(), + text_setter: Default::default(), mode: Default::default(), } } @@ -373,7 +329,7 @@ pub struct CosmicEditSpriteBundle { /// How many characters are allowed in buffer, 0 for no limit pub max_chars: CosmicMaxChars, /// Setting this will update the buffer's text - pub text: CosmicText, + pub text_setter: CosmicText, /// Text input mode pub mode: CosmicMode, } @@ -385,7 +341,7 @@ impl Default for CosmicEditSpriteBundle { transform: Default::default(), global_transform: Default::default(), texture: DEFAULT_IMAGE_HANDLE.typed(), - visibility: Visibility::Hidden, + visibility: Visibility::Visible, computed_visibility: Default::default(), background_color: Default::default(), text_position: Default::default(), @@ -394,7 +350,7 @@ impl Default for CosmicEditSpriteBundle { background_image: Default::default(), max_lines: Default::default(), max_chars: Default::default(), - text: Default::default(), + text_setter: Default::default(), mode: Default::default(), } } @@ -422,23 +378,24 @@ impl Plugin for CosmicEditPlugin { fn build(&self, app: &mut App) { let font_system = create_cosmic_font_system(self.font_config.clone()); - app.add_systems(PreUpdate, (cosmic_editor_builder, update_buffer_text)) + app.add_systems(First, cosmic_editor_builder) .add_systems( - Update, + PreUpdate, ( + (update_buffer_text, update_cursor_position).chain(), 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), blink_cursor, freeze_cursor_blink, - hide_inactive_cursor, + hide_inactive_or_readonly_cursor, clear_inactive_selection, ), ) + .add_systems( + PostUpdate, + (cosmic_edit_redraw_buffer_ui, cosmic_edit_redraw_buffer) + .after(TransformSystem::TransformPropagate), + ) + .add_systems(Last, on_scale_factor_change) .init_resource::<Focus>() .insert_resource(CursorBlinkTimer(Timer::from_seconds( 0.53, @@ -700,22 +657,26 @@ fn save_edit_history( } fn get_text_size(buffer: &Buffer) -> (f32, f32) { - let width = buffer.layout_runs().map(|run| run.line_w).reduce(f32::max); - let height = buffer.layout_runs().count() as f32 * buffer.metrics().line_height; - if width.is_none() || height == 0. { - return (1., 1.); + if buffer.layout_runs().count() == 0 { + return (0., buffer.metrics().line_height); } - (width.unwrap(), height) + let width = buffer + .layout_runs() + .map(|run| run.line_w) + .reduce(f32::max) + .unwrap(); + let height = buffer.layout_runs().count() as f32 * buffer.metrics().line_height; + (width, height) } -pub fn get_y_offset_center(buffer: &Buffer) -> i32 { +pub fn get_y_offset_center(widget_height: f32, buffer: &Buffer) -> i32 { let (_, text_height) = get_text_size(buffer); - ((buffer.size().1 - text_height) / 2.0) as i32 + ((widget_height - text_height) / 2.0) as i32 } -pub fn get_x_offset_center(buffer: &Buffer) -> i32 { +pub fn get_x_offset_center(widget_width: f32, buffer: &Buffer) -> i32 { let (text_width, _) = get_text_size(buffer); - ((buffer.size().0 - text_width) / 2.0) as i32 + ((widget_width - text_width) / 2.0) as i32 } #[allow(clippy::too_many_arguments, clippy::type_complexity)] @@ -1040,13 +1001,14 @@ fn cosmic_edit_bevy_events( } let (padding_x, padding_y) = match text_position { CosmicTextPosition::Center => ( - get_x_offset_center(editor.0.buffer()), - get_y_offset_center(editor.0.buffer()), + get_x_offset_center(width * scale_factor, editor.0.buffer()), + get_y_offset_center(height * scale_factor, editor.0.buffer()), ), CosmicTextPosition::TopLeft { padding } => (*padding, *padding), - CosmicTextPosition::Left { padding } => { - (*padding, get_y_offset_center(editor.0.buffer())) - } + CosmicTextPosition::Left { padding } => ( + *padding, + get_y_offset_center(height * scale_factor, editor.0.buffer()), + ), }; let point = |node_cursor_pos: (f32, f32)| { ( @@ -1174,12 +1136,6 @@ fn cosmic_edit_bevy_events( } } -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( mode: &CosmicMode, @@ -1200,146 +1156,136 @@ fn redraw_buffer_common( let widget_width = original_width * scale_factor; let widget_height = original_height * scale_factor; let swash_cache = &mut swash_cache_state.swash_cache; - editor.shape_as_needed(&mut font_system.0); - if editor.buffer().redraw() { - let mut cursor_x = 0.; - - if mode == &CosmicMode::InfiniteLine { - if let Some(line) = editor.buffer().layout_runs().next() { - for (idx, glyph) in line.glyphs.iter().enumerate() { - if editor.cursor().affinity == Affinity::Before { - if idx <= editor.cursor().index { - cursor_x += glyph.w; - } - } else if idx < editor.cursor().index { + + let mut cursor_x = 0.; + if mode == &CosmicMode::InfiniteLine { + if let Some(line) = editor.buffer().layout_runs().next() { + for (idx, glyph) in line.glyphs.iter().enumerate() { + if editor.cursor().affinity == Affinity::Before { + if idx <= editor.cursor().index { cursor_x += glyph.w; - } else { - break; } + } else if idx < editor.cursor().index { + cursor_x += glyph.w; + } else { + break; } } } + } - if mode == &CosmicMode::InfiniteLine && x_offset.0.is_none() && original_width > 1. { - let padding_x = match text_position { - CosmicTextPosition::Center => get_x_offset_center(editor.buffer()), - CosmicTextPosition::TopLeft { padding } => *padding, - CosmicTextPosition::Left { padding } => *padding, - }; - *x_offset = XOffset(Some((0., widget_width - 2. * padding_x as f32))); + if mode == &CosmicMode::InfiniteLine && x_offset.0.is_none() { + let padding_x = match text_position { + CosmicTextPosition::Center => get_x_offset_center(widget_width, editor.buffer()), + CosmicTextPosition::TopLeft { padding } => *padding, + CosmicTextPosition::Left { padding } => *padding, + }; + *x_offset = XOffset(Some((0., widget_width - 2. * padding_x as f32))); + } + + if let Some((x_min, x_max)) = x_offset.0 { + if cursor_x > x_max { + let diff = cursor_x - x_max; + *x_offset = XOffset(Some((x_min + diff, cursor_x))); + } + if cursor_x < x_min { + let diff = x_min - cursor_x; + *x_offset = XOffset(Some((cursor_x, x_max - diff))); } + } - if let Some((x_min, x_max)) = x_offset.0 { - if cursor_x > x_max { - let diff = cursor_x - x_max; - *x_offset = XOffset(Some((x_min + diff, cursor_x))); + let font_color = attrs + .0 + .color_opt + .unwrap_or(cosmic_text::Color::rgb(0, 0, 0)); + + let mut pixels = vec![0; widget_width as usize * widget_height as usize * 4]; + if let Some(bg_image) = background_image { + if let Some(image) = images.get(&bg_image) { + let mut dynamic_image = image.clone().try_into_dynamic().unwrap(); + if image.size().x != widget_width || image.size().y != widget_height { + dynamic_image = dynamic_image.resize_to_fill( + widget_width as u32, + widget_height as u32, + FilterType::Triangle, + ); } - if cursor_x < x_min { - let diff = x_min - cursor_x; - *x_offset = XOffset(Some((cursor_x, x_max - diff))); + 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 (padding_x, padding_y) = match text_position { + CosmicTextPosition::Center => ( + get_x_offset_center(widget_width, editor.buffer()), + get_y_offset_center(widget_height, editor.buffer()), + ), + CosmicTextPosition::TopLeft { padding } => (*padding, *padding), + CosmicTextPosition::Left { padding } => ( + *padding, + get_y_offset_center(widget_height, editor.buffer()), + ), + }; - let (buffer_width, buffer_height) = match mode { - CosmicMode::InfiniteLine => (f32::MAX, widget_height), - CosmicMode::AutoHeight => (widget_width, f32::MAX), - CosmicMode::Wrap => (widget_width, widget_height), - }; - editor - .buffer_mut() - .set_size(&mut font_system.0, buffer_width, buffer_height); - - let font_color = attrs - .0 - .color_opt - .unwrap_or(cosmic_text::Color::rgb(0, 0, 0)); - - let mut pixels = vec![0; widget_width as usize * widget_height as usize * 4]; - if let Some(bg_image) = background_image { - if let Some(image) = images.get(&bg_image) { - let mut dynamic_image = image.clone().try_into_dynamic().unwrap(); - if image.size().x != widget_width || image.size().y != widget_height { - dynamic_image = dynamic_image.resize_to_fill( - widget_width as u32, - widget_height as u32, - FilterType::Triangle, + 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, + widget_width as i32, + widget_height as i32, + x + col + padding_x - x_offset.0.unwrap_or((0., 0.)).0 as i32, + y + row + padding_y, + color, ); } - 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 (padding_y, padding_x) = match text_position { - CosmicTextPosition::Center => ( - get_y_offset_center(editor.buffer()), - get_x_offset_center(editor.buffer()), - ), - CosmicTextPosition::TopLeft { padding } => (*padding, *padding), - CosmicTextPosition::Left { padding } => { - (get_y_offset_center(editor.buffer()), *padding) - } - }; + }, + ); - 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, - widget_width as i32, - widget_height as i32, - x + col + padding_x - x_offset.0.unwrap_or((0., 0.)).0 as i32, - y + row + padding_y, - color, - ); - } - } - }, - ); - 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: widget_width as u32, - height: widget_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: widget_width as u32, - height: widget_height as u32, - depth_or_array_layers: 1, - }); - } + 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: widget_width as u32, + height: widget_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: widget_width as u32, + height: widget_height as u32, + depth_or_array_layers: 1, + }); } } + + editor.buffer_mut().set_redraw(false); } fn cosmic_edit_redraw_buffer_ui( @@ -1354,13 +1300,15 @@ fn cosmic_edit_redraw_buffer_ui( &CosmicTextPosition, &mut UiImage, &Node, - &mut Visibility, &mut XOffset, + &mut Style, &CosmicMode, )>, mut font_system: ResMut<CosmicFontSystem>, ) { let primary_window = windows.single(); + let scale = primary_window.scale_factor() as f32; + for ( mut editor, attrs, @@ -1369,14 +1317,39 @@ fn cosmic_edit_redraw_buffer_ui( text_position, mut img, node, - mut visibility, mut x_offset, + mut style, mode, ) 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.); + editor.0.shape_as_needed(&mut font_system.0); + if !editor.0.buffer().redraw() { + continue; + } + + let width = node.size().x; + let mut height = node.size().y; + let widget_height = height * scale; + let widget_width = width * scale; + + let (buffer_width, buffer_height) = match mode { + CosmicMode::InfiniteLine => (f32::MAX, widget_height), + CosmicMode::AutoHeight => (widget_width, (i32::MAX / 2) as f32), + CosmicMode::Wrap => (widget_width, 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_height = (text_size.1 / primary_window.scale_factor() as f32) + 30.; + if text_height > height { + height = text_height; + style.height = Val::Px(height); + } + } redraw_buffer_common( mode, @@ -1390,16 +1363,10 @@ fn cosmic_edit_redraw_buffer_ui( &mut img.texture, text_position, &mut font_system, - primary_window.scale_factor() as f32, + scale, width, height, ); - - if *visibility == Visibility::Hidden - && img.texture.clone() != bevy::render::texture::DEFAULT_IMAGE_HANDLE.typed() - { - *visibility = Visibility::Visible; - } } } @@ -1449,7 +1416,7 @@ fn freeze_cursor_blink( active_editor: Res<Focus>, keys: Res<Input<KeyCode>>, char_evr: EventReader<ReceivedCharacter>, - mut editor_q: Query<&mut CosmicEditor>, + mut editor_q: Query<&mut CosmicEditor, Without<ReadOnly>>, ) { let inputs = [ KeyCode::Left, @@ -1475,16 +1442,17 @@ fn freeze_cursor_blink( } } -fn hide_inactive_cursor( - mut cosmic_editor_q: Query<(Entity, &mut CosmicEditor)>, +fn hide_inactive_or_readonly_cursor( + mut cosmic_editor_q: Query<(Entity, &mut CosmicEditor, Option<&ReadOnly>)>, active_editor: Res<Focus>, ) { if !active_editor.is_changed() || active_editor.0.is_none() { return; } - for (e, mut editor) in &mut cosmic_editor_q.iter_mut() { - if e != active_editor.0.unwrap() { + for (e, mut editor, readonly_maybe) in &mut cosmic_editor_q.iter_mut() { + let readonly = readonly_maybe.is_some(); + if e != active_editor.0.unwrap() || readonly { let mut cursor = editor.0.cursor(); cursor.color = Some(cosmic_text::Color::rgba(0, 0, 0, 0)); editor.0.set_cursor(cursor); @@ -1515,18 +1483,19 @@ fn cosmic_edit_redraw_buffer( mut cosmic_edit_query: Query<( &mut CosmicEditor, &CosmicAttrs, - &Sprite, + &mut Sprite, &CosmicBackground, &BackgroundColor, &CosmicTextPosition, &mut Handle<Image>, - &mut Visibility, &mut XOffset, &CosmicMode, )>, mut font_system: ResMut<CosmicFontSystem>, ) { let primary_window = windows.single(); + let scale = primary_window.scale_factor() as f32; + for ( mut editor, attrs, @@ -1535,14 +1504,37 @@ fn cosmic_edit_redraw_buffer( background_color, text_position, mut handle, - mut visibility, mut x_offset, mode, ) 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.); + editor.0.shape_as_needed(&mut font_system.0); + if !editor.0.buffer().redraw() { + continue; + } + let width = sprite.custom_size.unwrap().x; + let mut height = sprite.custom_size.unwrap().y; + let widget_height = height * scale; + let widget_width = width * scale; + + let (buffer_width, buffer_height) = match mode { + CosmicMode::InfiniteLine => (f32::MAX, widget_height), + CosmicMode::AutoHeight => (widget_width, (i32::MAX / 2) as f32), // TODO: workaround + CosmicMode::Wrap => (widget_width, 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_height = (text_size.1 / primary_window.scale_factor() as f32) + 30.; + if text_height > height { + height = text_height; + sprite.custom_size.unwrap().y = height; + } + } redraw_buffer_common( mode, @@ -1556,16 +1548,10 @@ fn cosmic_edit_redraw_buffer( &mut handle, text_position, &mut font_system, - primary_window.scale_factor() as f32, + scale, width, height, ); - - if *visibility == Visibility::Hidden - && handle.clone() != bevy::render::texture::DEFAULT_IMAGE_HANDLE.typed() - { - *visibility = Visibility::Visible; - } } } @@ -1637,7 +1623,7 @@ mod tests { fn test_spawn_cosmic_edit_system(mut commands: Commands) { commands.spawn(CosmicEditUiBundle { - text: CosmicText::OneStyle("Blah".into()), + text_setter: CosmicText::OneStyle("Blah".into()), ..Default::default() }); }