diff --git a/src/calculate_nodes.rs b/src/calculate_nodes.rs index 4b7ccc82b76595ba3394e4894b0bab355e38ce9a..31c6d07b6c806cdacfe8b3c61f64245b56602ae4 100644 --- a/src/calculate_nodes.rs +++ b/src/calculate_nodes.rs @@ -219,6 +219,7 @@ fn create_primitive( font, properties, text_layout, + word_wrap, .. } => { // --- Bind to Font Asset --- // @@ -247,6 +248,11 @@ fn create_primitive( parent_layout.height - border_y, ); + // TODO: Fix this hack. + if !*word_wrap { + properties.max_size.0 = 100000.0; + } + needs_layout = false; if properties.max_size.0 == 0.0 || properties.max_size.1 == 0.0 { diff --git a/src/render_primitive.rs b/src/render_primitive.rs index 799af3e6c2b5e75e9c7a570ebc45969408790848..3a3d95caa8abaaaf5669ddc4233137b2e7ad4df4 100644 --- a/src/render_primitive.rs +++ b/src/render_primitive.rs @@ -25,6 +25,7 @@ pub enum RenderPrimitive { text_layout: TextLayout, layout: Rect, properties: TextProperties, + word_wrap: bool, }, Image { border_radius: Corner<f32>, @@ -103,7 +104,11 @@ impl From<&KStyle> for RenderPrimitive { border: style.border.resolve(), layout: Rect::default(), }, - RenderCommand::Text { content, alignment } => Self::Text { + RenderCommand::Text { + content, + alignment, + word_wrap, + } => Self::Text { color: style.color.resolve(), content, font, @@ -115,6 +120,7 @@ impl From<&KStyle> for RenderPrimitive { alignment, ..Default::default() }, + word_wrap, }, RenderCommand::Image { handle } => Self::Image { border_radius: style.border_radius.resolve(), diff --git a/src/styles/render_command.rs b/src/styles/render_command.rs index d2f242cfddebe561acc679d19109649026de81aa..eafb32c58690efa6c2da550465f31c745f29d591 100644 --- a/src/styles/render_command.rs +++ b/src/styles/render_command.rs @@ -13,6 +13,7 @@ pub enum RenderCommand { Text { content: String, alignment: Alignment, + word_wrap: bool, }, Image { handle: Handle<Image>, diff --git a/src/widgets/text.rs b/src/widgets/text.rs index 790272daeeb34bf4bded21fd338e99a08804e0bd..a7f8ce2dfbd2ced1c328ccd55565dedc81aeb980 100644 --- a/src/widgets/text.rs +++ b/src/widgets/text.rs @@ -33,6 +33,9 @@ pub struct TextProps { pub alignment: Alignment, /// Custom styles to pass in. pub user_styles: KStyle, + /// Basic word wrapping. + /// Defautls to true + pub word_wrap: bool, } impl Default for TextProps { @@ -44,6 +47,7 @@ impl Default for TextProps { show_cursor: false, size: -1.0, alignment: Alignment::Start, + word_wrap: true, user_styles: Default::default(), } } @@ -84,6 +88,7 @@ pub fn text_render( render_command: StyleProp::Value(RenderCommand::Text { content: text.content.clone(), alignment: text.alignment, + word_wrap: text.word_wrap, }), font: if let Some(ref font) = text.font { StyleProp::Value(font.clone()) diff --git a/src/widgets/text_box.rs b/src/widgets/text_box.rs index 8bf53c00891b88dd5b77c2fae9ec3a0719086b3f..bcce5c1648f4f228241c33b796dac73b382357d7 100644 --- a/src/widgets/text_box.rs +++ b/src/widgets/text_box.rs @@ -12,7 +12,7 @@ use crate::{ on_layout::OnLayout, prelude::{KChildren, KayakWidgetContext, OnChange}, render::font::FontMapping, - styles::{Edge, KStyle, RenderCommand, StyleProp, Units}, + styles::{Edge, KPositionType, KStyle, RenderCommand, StyleProp, Units}, widget::Widget, widget_state::WidgetState, widgets::{ @@ -22,6 +22,8 @@ use crate::{ Focusable, DEFAULT_FONT, }; +use super::ElementBundle; + /// Props used by the [`TextBox`] widget #[derive(Component, PartialEq, Default, Debug, Clone)] pub struct TextBoxProps { @@ -44,6 +46,7 @@ pub struct TextBoxState { pub cursor_position: usize, pub cursor_visible: bool, pub cursor_last_update: Instant, + pub current_value: String, } impl Default for TextBoxState { @@ -55,6 +58,7 @@ impl Default for TextBoxState { cursor_position: Default::default(), cursor_visible: Default::default(), cursor_last_update: Instant::now(), + current_value: String::new(), } } } @@ -96,18 +100,34 @@ pub fn text_box_render( In((widget_context, entity)): In<(KayakWidgetContext, Entity)>, mut commands: Commands, mut query: Query<(&mut KStyle, &TextBoxProps, &mut OnEvent, &OnChange)>, - state_query: Query<&TextBoxState>, + mut state_query: ParamSet<(Query<&TextBoxState>, Query<&mut TextBoxState>)>, + font_assets: Res<Assets<KayakFont>>, + font_mapping: Res<FontMapping>, ) -> bool { if let Ok((mut styles, text_box, mut on_event, on_change)) = query.get_mut(entity) { let state_entity = widget_context.use_state::<TextBoxState>( &mut commands, entity, TextBoxState { + current_value: text_box.value.clone(), ..TextBoxState::default() }, ); - if let Ok(state) = state_query.get(state_entity) { + let mut is_different = false; + if let Ok(state) = state_query.p0().get(state_entity) { + if state.current_value != text_box.value { + is_different = true; + } + } + + if is_different { + if let Ok(mut state) = state_query.p1().get_mut(state_entity) { + state.current_value = text_box.value.clone(); + } + } + + if let Ok(state) = state_query.p0().get(state_entity) { *styles = KStyle::default() // Required styles .with_style(KStyle { @@ -140,9 +160,7 @@ pub fn text_box_render( ..Default::default() }; - let current_value = text_box.value.clone(); let cloned_on_change = on_change.clone(); - let style_font = styles.font.clone(); *on_event = OnEvent::new( @@ -185,40 +203,26 @@ pub fn text_box_render( } } EventType::CharInput { c } => { - let mut current_value = current_value.clone(); - let cloned_on_change = cloned_on_change.clone(); - if let Ok(state) = state_query.get(state_entity) { + if let Ok(mut state) = state_query.get_mut(state_entity) { + let cloned_on_change = cloned_on_change.clone(); if !state.focused { return (event_dispatcher_context, event); } - } else { - return (event_dispatcher_context, event); - } - if is_backspace(c) { - if !current_value.is_empty() { - if let Ok(mut state) = state_query.get_mut(state_entity) { + let cursor_pos = state.cursor_position; + if is_backspace(c) { + if !state.current_value.is_empty() { // TODO: This doesn't respect graphemes! - current_value.remove(state.cursor_position - 1); + state.current_value.remove(cursor_pos - 1); state.cursor_position -= 1; } - } - } else if !c.is_control() { - if let Ok(mut state) = state_query.get_mut(state_entity) { + } else if !c.is_control() { // TODO: This doesn't respect graphemes! - current_value.insert(state.cursor_position, c); + state.current_value.insert(cursor_pos, c); state.cursor_position += 1; } - } - if let Ok(mut state) = state_query.get_mut(state_entity) { // Update graphemes - set_graphemes( - &mut state, - &font_assets, - &font_mapping, - &style_font, - ¤t_value, - ); + set_graphemes(&mut state, &font_assets, &font_mapping, &style_font); set_new_cursor_position( &mut state, @@ -226,22 +230,15 @@ pub fn text_box_render( &font_mapping, &style_font, ); + cloned_on_change.set_value(state.current_value.clone()); + event.add_system(cloned_on_change); } - - cloned_on_change.set_value(current_value); - event.add_system(cloned_on_change); } EventType::Focus => { if let Ok(mut state) = state_query.get_mut(state_entity) { state.focused = true; // Update graphemes - set_graphemes( - &mut state, - &font_assets, - &font_mapping, - &style_font, - ¤t_value, - ); + set_graphemes(&mut state, &font_assets, &font_mapping, &style_font); state.cursor_position = state.graphemes.len(); @@ -266,7 +263,7 @@ pub fn text_box_render( let cursor_styles = KStyle { background_color: Color::rgba(0.933, 0.745, 0.745, 1.0).into(), - position_type: crate::styles::KPositionType::SelfDirected.into(), + position_type: KPositionType::SelfDirected.into(), top: Units::Pixels(5.0).into(), left: Units::Pixels(state.cursor_x).into(), width: Units::Pixels(2.0).into(), @@ -274,6 +271,51 @@ pub fn text_box_render( ..Default::default() }; + let text_styles = KStyle { + top: Units::Stretch(1.0).into(), + bottom: Units::Stretch(1.0).into(), + ..Default::default() + }; + + let shift = if let Some(layout) = widget_context.get_layout(entity) { + let font_handle = match &styles.font { + StyleProp::Value(font) => font_mapping.get_handle(font.clone()).unwrap(), + _ => font_mapping.get_handle(DEFAULT_FONT.into()).unwrap(), + }; + if let Some(font) = font_assets.get(&font_handle) { + let string_to_cursor = state.graphemes[0..state.cursor_position].join(""); + let measurement = font.measure( + &string_to_cursor, + TextProperties { + font_size: 14.0, + line_height: 18.0, + max_size: (10000.0, 18.0), + alignment: kayak_font::Alignment::Start, + tab_size: 4, + }, + ); + if measurement.size().0 > layout.width { + (layout.width - measurement.size().0) - 20.0 + } else { + 0.0 + } + } else { + 0.0 + } + } else { + 0.0 + }; + + let scroll_styles = KStyle { + position_type: KPositionType::SelfDirected.into(), + padding_left: StyleProp::Value(Units::Stretch(0.0)), + padding_right: StyleProp::Value(Units::Stretch(0.0)), + padding_bottom: StyleProp::Value(Units::Stretch(1.0)), + padding_top: StyleProp::Value(Units::Stretch(1.0)), + left: Units::Pixels(shift).into(), + ..Default::default() + }; + let parent_id = Some(entity); rsx! { <BackgroundBundle styles={background_styles}> @@ -281,30 +323,27 @@ pub fn text_box_render( height: Units::Pixels(26.0).into(), padding_left: StyleProp::Value(Units::Stretch(0.0)), padding_right: StyleProp::Value(Units::Stretch(0.0)), - padding_bottom: StyleProp::Value(Units::Stretch(1.0)), - padding_top: StyleProp::Value(Units::Stretch(1.0)), ..Default::default() }}> - <TextWidgetBundle - text={TextProps { - content: text_box.value.clone(), - size: 14.0, - line_height: Some(18.0), - user_styles: KStyle { - top: Units::Stretch(1.0).into(), - bottom: Units::Stretch(1.0).into(), + <ElementBundle styles={scroll_styles}> + <TextWidgetBundle + text={TextProps { + content: text_box.value.clone(), + size: 14.0, + line_height: Some(18.0), + user_styles: text_styles, + word_wrap: false, ..Default::default() - }, - ..Default::default() - }} - /> - { - if state.focused && state.cursor_visible { - constructor! { - <BackgroundBundle styles={cursor_styles} /> + }} + /> + { + if state.focused && state.cursor_visible { + constructor! { + <BackgroundBundle styles={cursor_styles} /> + } } } - } + </ElementBundle> </ClipBundle> </BackgroundBundle> } @@ -326,7 +365,6 @@ fn set_graphemes( font_assets: &Res<Assets<KayakFont>>, font_mapping: &FontMapping, style_font: &StyleProp<String>, - current_value: &String, ) { let font_handle = match style_font { StyleProp::Value(font) => font_mapping.get_handle(font.clone()).unwrap(), @@ -335,7 +373,7 @@ fn set_graphemes( if let Some(font) = font_assets.get(&font_handle) { state.graphemes = font - .get_graphemes(¤t_value) + .get_graphemes(&state.current_value) .iter() .map(|s| s.to_string()) .collect::<Vec<_>>();