diff --git a/api/node/rust/interpreter/value.rs b/api/node/rust/interpreter/value.rs index 4b2bb0db15e..2a675e223a3 100644 --- a/api/node/rust/interpreter/value.rs +++ b/api/node/rust/interpreter/value.rs @@ -25,6 +25,7 @@ pub enum JsValueType { Struct, Brush, Image, + StyledText, } impl From for JsValueType { @@ -37,6 +38,7 @@ impl From for JsValueType { slint_interpreter::ValueType::Struct => JsValueType::Struct, slint_interpreter::ValueType::Brush => JsValueType::Brush, slint_interpreter::ValueType::Image => JsValueType::Image, + slint_interpreter::ValueType::StyledText => JsValueType::StyledText, _ => JsValueType::Void, } } @@ -290,7 +292,8 @@ pub fn to_value(env: &Env, unknown: JsUnknown, typ: &Type) -> Result { | Type::Easing | Type::PathData | Type::LayoutCache - | Type::ElementReference => Err(napi::Error::from_reason("reason")), + | Type::ElementReference + | Type::StyledText => Err(napi::Error::from_reason("reason")), } } diff --git a/internal/compiler/builtin_macros.rs b/internal/compiler/builtin_macros.rs index 03f6fa3115e..392e39c5d72 100644 --- a/internal/compiler/builtin_macros.rs +++ b/internal/compiler/builtin_macros.rs @@ -343,7 +343,12 @@ fn to_debug_string( Type::Float32 | Type::Int32 => expr.maybe_convert_to(Type::String, node, diag), Type::String => expr, // TODO - Type::Color | Type::Brush | Type::Image | Type::Easing | Type::Array(_) => { + Type::Color + | Type::Brush + | Type::Image + | Type::Easing + | Type::StyledText + | Type::Array(_) => { Expression::StringLiteral("".into()) } Type::Duration diff --git a/internal/compiler/expression_tree.rs b/internal/compiler/expression_tree.rs index 0cb92d3fd32..66f81a5e761 100644 --- a/internal/compiler/expression_tree.rs +++ b/internal/compiler/expression_tree.rs @@ -1427,6 +1427,7 @@ impl Expression { Expression::EnumerationValue(enumeration.clone().default_value()) } Type::ComponentFactory => Expression::EmptyComponentFactory, + Type::StyledText => Expression::Invalid, } } diff --git a/internal/compiler/generator/rust.rs b/internal/compiler/generator/rust.rs index 61721577b99..c1a4dc47a4a 100644 --- a/internal/compiler/generator/rust.rs +++ b/internal/compiler/generator/rust.rs @@ -91,6 +91,7 @@ pub fn rust_primitive_type(ty: &Type) -> Option { Type::Percent => Some(quote!(f32)), Type::Bool => Some(quote!(bool)), Type::Image => Some(quote!(sp::Image)), + Type::StyledText => Some(quote!(sp::StyledText)), Type::Struct(s) => { struct_name_to_tokens(&s.name).or_else(|| { let elem = diff --git a/internal/compiler/langtype.rs b/internal/compiler/langtype.rs index 3dbaac5f95d..8dae242d7a7 100644 --- a/internal/compiler/langtype.rs +++ b/internal/compiler/langtype.rs @@ -64,6 +64,8 @@ pub enum Type { /// This is a `SharedArray` LayoutCache, + + StyledText, } impl core::cmp::PartialEq for Type { @@ -104,6 +106,7 @@ impl core::cmp::PartialEq for Type { Type::UnitProduct(a) => matches!(other, Type::UnitProduct(b) if a == b), Type::ElementReference => matches!(other, Type::ElementReference), Type::LayoutCache => matches!(other, Type::LayoutCache), + Type::StyledText => matches!(other, Type::StyledText), } } } @@ -178,6 +181,7 @@ impl Display for Type { } Type::ElementReference => write!(f, "element ref"), Type::LayoutCache => write!(f, "layout cache"), + Type::StyledText => write!(f, "styled-text"), } } } @@ -213,6 +217,7 @@ impl Type { | Self::Array(_) | Self::Brush | Self::InferredProperty + | Self::StyledText ) } @@ -314,6 +319,7 @@ impl Type { Type::UnitProduct(_) => None, Type::ElementReference => None, Type::LayoutCache => None, + Type::StyledText => None, } } diff --git a/internal/compiler/llr/expression.rs b/internal/compiler/llr/expression.rs index c131c061e5f..5c2a6f8da01 100644 --- a/internal/compiler/llr/expression.rs +++ b/internal/compiler/llr/expression.rs @@ -267,6 +267,7 @@ impl Expression { Expression::EnumerationValue(enumeration.clone().default_value()) } Type::ComponentFactory => Expression::EmptyComponentFactory, + Type::StyledText => return None, }) } diff --git a/internal/compiler/typeregister.rs b/internal/compiler/typeregister.rs index 8f4c496de9d..ba3df4d6b0f 100644 --- a/internal/compiler/typeregister.rs +++ b/internal/compiler/typeregister.rs @@ -397,6 +397,7 @@ impl TypeRegister { register.insert_type(Type::Angle); register.insert_type(Type::Brush); register.insert_type(Type::Rem); + register.insert_type(Type::StyledText); register.types.insert("Point".into(), logical_point_type().into()); BUILTIN.with(|e| e.enums.fill_register(&mut register)); diff --git a/internal/core/Cargo.toml b/internal/core/Cargo.toml index 214183ff57b..29cfe4e3ff7 100644 --- a/internal/core/Cargo.toml +++ b/internal/core/Cargo.toml @@ -44,6 +44,10 @@ std = [ "dep:sys-locale", "dep:webbrowser", "shared-fontique", + "dep:pulldown-cmark", + "dep:thiserror", + "dep:htmlparser", + "i-slint-common/color-parsing", ] # Unsafe feature meaning that there is only one core running and all thread_local are static. # You can only enable this feature if you are sure that any API of this crate is only called @@ -68,7 +72,7 @@ raw-window-handle-06 = ["dep:raw-window-handle-06"] experimental = [] -experimental-rich-text = ["dep:pulldown-cmark", "dep:htmlparser", "dep:thiserror", "i-slint-common/color-parsing"] +experimental-rich-text = [] unstable-wgpu-26 = ["dep:wgpu-26"] unstable-wgpu-27 = ["dep:wgpu-27"] diff --git a/internal/core/api.rs b/internal/core/api.rs index 88066bb90a1..f9f0371619e 100644 --- a/internal/core/api.rs +++ b/internal/core/api.rs @@ -15,6 +15,9 @@ use crate::window::{WindowAdapter, WindowInner}; use alloc::boxed::Box; use alloc::string::String; +mod styled_text; +pub use styled_text::*; + /// A position represented in the coordinate space of logical pixels. That is the space before applying /// a display device specific scale factor. #[derive(Debug, Default, Copy, Clone, PartialEq)] diff --git a/internal/core/api/styled_text.rs b/internal/core/api/styled_text.rs new file mode 100644 index 00000000000..fda3ad3bd1b --- /dev/null +++ b/internal/core/api/styled_text.rs @@ -0,0 +1,547 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 + +#[derive(Clone, Debug, PartialEq)] +/// Styles that can be applied to text spans +#[allow(missing_docs, dead_code)] +pub(crate) enum Style { + Emphasis, + Strong, + Strikethrough, + Code, + Link, + Underline, + Color(crate::Color), +} + +#[derive(Clone, Debug, PartialEq)] +/// A style and a text span +pub(crate) struct FormattedSpan { + /// Span of text to style + pub(crate) range: core::ops::Range, + /// The style to apply + pub(crate) style: Style, +} + +#[cfg(feature = "std")] +#[derive(Clone, Debug)] +enum ListItemType { + Ordered(u64), + Unordered, +} + +/// A section of styled text, split up by a linebreak +#[derive(Clone, Debug, PartialEq)] +pub(crate) struct StyledTextParagraph { + /// The raw paragraph text + pub(crate) text: alloc::string::String, + /// Formatting styles and spans + pub(crate) formatting: alloc::vec::Vec, + /// Locations of clickable links within the paragraph + pub(crate) links: alloc::vec::Vec<(core::ops::Range, alloc::string::String)>, +} + +/// Error type returned by `StyledText::parse` +#[cfg(feature = "std")] +#[derive(Debug, thiserror::Error)] +#[non_exhaustive] +pub enum StyledTextError<'a> { + /// Spans are unbalanced: stack already empty when popped + #[error("Spans are unbalanced: stack already empty when popped")] + Pop, + /// Spans are unbalanced: stack contained items at end of function + #[error("Spans are unbalanced: stack contained items at end of function")] + NotEmpty, + /// Paragraph not started + #[error("Paragraph not started")] + ParagraphNotStarted, + /// Unimplemented markdown tag + #[error("Unimplemented: {:?}", .0)] + UnimplementedTag(pulldown_cmark::Tag<'a>), + /// Unimplemented markdown event + #[error("Unimplemented: {:?}", .0)] + UnimplementedEvent(pulldown_cmark::Event<'a>), + /// Unimplemented html event + #[error("Unimplemented: {}", .0)] + UnimplementedHtmlEvent(alloc::string::String), + /// Unimplemented html tag + #[error("Unimplemented html tag: {}", .0)] + UnimplementedHtmlTag(alloc::string::String), + /// Unimplemented html attribute + #[error("Unexpected {} attribute in html {}", .0, .1)] + UnexpectedAttribute(alloc::string::String, alloc::string::String), + /// Missing color attribute in html + #[error("Missing color attribute in html {}", .0)] + MissingColor(alloc::string::String), + /// Closing html tag doesn't match the opening tag + #[error("Closing html tag doesn't match the opening tag. Expected {}, got {}", .0, .1)] + ClosingTagMismatch(&'a str, alloc::string::String), +} + +/// Styled text that has been parsed and seperated into paragraphs +#[derive(Debug, PartialEq, Clone, Default)] +pub struct StyledText { + /// Paragraphs of styled text + pub(crate) paragraphs: alloc::vec::Vec, +} + +#[cfg(feature = "std")] +impl StyledText { + fn begin_paragraph(&mut self, indentation: u32, list_item_type: Option) { + let mut text = alloc::string::String::with_capacity(indentation as usize * 4); + for _ in 0..indentation { + text.push_str(" "); + } + match list_item_type { + Some(ListItemType::Unordered) => { + if indentation % 3 == 0 { + text.push_str("• ") + } else if indentation % 3 == 1 { + text.push_str("◦ ") + } else { + text.push_str("▪ ") + } + } + Some(ListItemType::Ordered(num)) => text.push_str(&alloc::format!("{}. ", num)), + None => {} + }; + self.paragraphs.push(StyledTextParagraph { + text, + formatting: Default::default(), + links: Default::default(), + }); + } + + /// Parse a markdown string as styled text + pub fn parse(string: &str) -> Result> { + let parser = + pulldown_cmark::Parser::new_ext(string, pulldown_cmark::Options::ENABLE_STRIKETHROUGH); + + let mut styled_text = StyledText::default(); + let mut list_state_stack: alloc::vec::Vec> = alloc::vec::Vec::new(); + let mut style_stack = alloc::vec::Vec::new(); + let mut current_url = None; + + for event in parser { + let indentation = list_state_stack.len().saturating_sub(1) as _; + + match event { + pulldown_cmark::Event::SoftBreak | pulldown_cmark::Event::HardBreak => { + styled_text.begin_paragraph(indentation, None); + } + pulldown_cmark::Event::End(pulldown_cmark::TagEnd::List(_)) => { + if list_state_stack.pop().is_none() { + return Err(StyledTextError::Pop); + } + } + pulldown_cmark::Event::End( + pulldown_cmark::TagEnd::Paragraph | pulldown_cmark::TagEnd::Item, + ) => {} + pulldown_cmark::Event::Start(tag) => { + let style = match tag { + pulldown_cmark::Tag::Paragraph => { + styled_text.begin_paragraph(indentation, None); + continue; + } + pulldown_cmark::Tag::Item => { + styled_text.begin_paragraph( + indentation, + Some(match list_state_stack.last().copied() { + Some(Some(index)) => ListItemType::Ordered(index), + _ => ListItemType::Unordered, + }), + ); + if let Some(state) = list_state_stack.last_mut() { + *state = state.map(|state| state + 1); + } + continue; + } + pulldown_cmark::Tag::List(index) => { + list_state_stack.push(index); + continue; + } + pulldown_cmark::Tag::Strong => Style::Strong, + pulldown_cmark::Tag::Emphasis => Style::Emphasis, + pulldown_cmark::Tag::Strikethrough => Style::Strikethrough, + pulldown_cmark::Tag::Link { dest_url, .. } => { + current_url = Some(dest_url); + Style::Link + } + + pulldown_cmark::Tag::Heading { .. } + | pulldown_cmark::Tag::Image { .. } + | pulldown_cmark::Tag::DefinitionList + | pulldown_cmark::Tag::DefinitionListTitle + | pulldown_cmark::Tag::DefinitionListDefinition + | pulldown_cmark::Tag::TableHead + | pulldown_cmark::Tag::TableRow + | pulldown_cmark::Tag::TableCell + | pulldown_cmark::Tag::HtmlBlock + | pulldown_cmark::Tag::Superscript + | pulldown_cmark::Tag::Subscript + | pulldown_cmark::Tag::Table(_) + | pulldown_cmark::Tag::MetadataBlock(_) + | pulldown_cmark::Tag::BlockQuote(_) + | pulldown_cmark::Tag::CodeBlock(_) + | pulldown_cmark::Tag::FootnoteDefinition(_) => { + return Err(StyledTextError::UnimplementedTag(tag)); + } + }; + + style_stack.push(( + style, + styled_text + .paragraphs + .last() + .ok_or(StyledTextError::ParagraphNotStarted)? + .text + .len(), + )); + } + pulldown_cmark::Event::Text(text) => { + styled_text + .paragraphs + .last_mut() + .ok_or(StyledTextError::ParagraphNotStarted)? + .text + .push_str(&text); + } + pulldown_cmark::Event::End(_) => { + let (style, start) = if let Some(value) = style_stack.pop() { + value + } else { + return Err(StyledTextError::Pop); + }; + + let paragraph = styled_text + .paragraphs + .last_mut() + .ok_or(StyledTextError::ParagraphNotStarted)?; + let end = paragraph.text.len(); + + if let Some(url) = current_url.take() { + paragraph.links.push((start..end, url.into())); + } + + paragraph.formatting.push(FormattedSpan { range: start..end, style }); + } + pulldown_cmark::Event::Code(text) => { + let paragraph = styled_text + .paragraphs + .last_mut() + .ok_or(StyledTextError::ParagraphNotStarted)?; + let start = paragraph.text.len(); + paragraph.text.push_str(&text); + paragraph.formatting.push(FormattedSpan { + range: start..paragraph.text.len(), + style: Style::Code, + }); + } + pulldown_cmark::Event::InlineHtml(html) => { + if html.starts_with(" "", + Style::Underline => "", + other => std::unreachable!( + "Got unexpected closing style {:?} with html {}. This error should have been caught earlier.", + other, + html + ), + }; + + if (&*html) != expected_tag { + return Err(StyledTextError::ClosingTagMismatch( + expected_tag, + (&*html).into(), + )); + } + + let paragraph = styled_text + .paragraphs + .last_mut() + .ok_or(StyledTextError::ParagraphNotStarted)?; + let end = paragraph.text.len(); + paragraph.formatting.push(FormattedSpan { range: start..end, style }); + } else { + let mut expecting_color_attribute = false; + + for token in htmlparser::Tokenizer::from(&*html) { + match token { + Ok(htmlparser::Token::ElementStart { local: tag_type, .. }) => { + match &*tag_type { + "u" => { + style_stack.push(( + Style::Underline, + styled_text + .paragraphs + .last() + .ok_or(StyledTextError::ParagraphNotStarted)? + .text + .len(), + )); + } + "font" => { + expecting_color_attribute = true; + } + _ => { + return Err(StyledTextError::UnimplementedHtmlTag( + (&*tag_type).into(), + )); + } + } + } + Ok(htmlparser::Token::Attribute { + local: key, + value: Some(value), + .. + }) => match &*key { + "color" => { + if !expecting_color_attribute { + return Err(StyledTextError::UnexpectedAttribute( + (&*key).into(), + (&*html).into(), + )); + } + expecting_color_attribute = false; + + let value = + i_slint_common::color_parsing::parse_color_literal( + &*value, + ) + .or_else(|| { + i_slint_common::color_parsing::named_colors() + .get(&*value) + .copied() + }) + .expect("invalid color value"); + + style_stack.push(( + Style::Color(crate::Color::from_argb_encoded(value)), + styled_text + .paragraphs + .last() + .ok_or(StyledTextError::ParagraphNotStarted)? + .text + .len(), + )); + } + _ => { + return Err(StyledTextError::UnexpectedAttribute( + (&*key).into(), + (&*html).into(), + )); + } + }, + Ok(htmlparser::Token::ElementEnd { .. }) => {} + _ => { + return Err(StyledTextError::UnimplementedHtmlEvent( + alloc::format!("{:?}", token), + )); + } + } + } + + if expecting_color_attribute { + return Err(StyledTextError::MissingColor((&*html).into())); + } + } + } + pulldown_cmark::Event::Rule + | pulldown_cmark::Event::TaskListMarker(_) + | pulldown_cmark::Event::FootnoteReference(_) + | pulldown_cmark::Event::InlineMath(_) + | pulldown_cmark::Event::DisplayMath(_) + | pulldown_cmark::Event::Html(_) => { + return Err(StyledTextError::UnimplementedEvent(event)); + } + } + } + + if !style_stack.is_empty() { + return Err(StyledTextError::NotEmpty); + } + + Ok(styled_text) + } +} + +#[test] +fn markdown_parsing() { + assert_eq!( + StyledText::parse("hello *world*").unwrap().paragraphs, + [StyledTextParagraph { + text: "hello world".into(), + formatting: alloc::vec![FormattedSpan { range: 6..11, style: Style::Emphasis }], + links: alloc::vec![] + }] + ); + + assert_eq!( + StyledText::parse( + " +- line 1 +- line 2 + " + ) + .unwrap() + .paragraphs, + [ + StyledTextParagraph { + text: "• line 1".into(), + formatting: alloc::vec![], + links: alloc::vec![] + }, + StyledTextParagraph { + text: "• line 2".into(), + formatting: alloc::vec![], + links: alloc::vec![] + } + ] + ); + + assert_eq!( + StyledText::parse( + " +1. a +2. b +4. c + " + ) + .unwrap() + .paragraphs, + [ + StyledTextParagraph { + text: "1. a".into(), + formatting: alloc::vec![], + links: alloc::vec![] + }, + StyledTextParagraph { + text: "2. b".into(), + formatting: alloc::vec![], + links: alloc::vec![] + }, + StyledTextParagraph { + text: "3. c".into(), + formatting: alloc::vec![], + links: alloc::vec![] + } + ] + ); + + assert_eq!( + StyledText::parse( + " +Normal _italic_ **strong** ~~strikethrough~~ `code` +new *line* +" + ) + .unwrap() + .paragraphs, + [ + StyledTextParagraph { + text: "Normal italic strong strikethrough code".into(), + formatting: alloc::vec![ + FormattedSpan { range: 7..13, style: Style::Emphasis }, + FormattedSpan { range: 14..20, style: Style::Strong }, + FormattedSpan { range: 21..34, style: Style::Strikethrough }, + FormattedSpan { range: 35..39, style: Style::Code } + ], + links: alloc::vec![] + }, + StyledTextParagraph { + text: "new line".into(), + formatting: alloc::vec![FormattedSpan { range: 4..8, style: Style::Emphasis },], + links: alloc::vec![] + } + ] + ); + + assert_eq!( + StyledText::parse( + " +- root + - child + - grandchild + - great grandchild +" + ) + .unwrap() + .paragraphs, + [ + StyledTextParagraph { + text: "• root".into(), + formatting: alloc::vec![], + links: alloc::vec![] + }, + StyledTextParagraph { + text: " ◦ child".into(), + formatting: alloc::vec![], + links: alloc::vec![] + }, + StyledTextParagraph { + text: " ▪ grandchild".into(), + formatting: alloc::vec![], + links: alloc::vec![] + }, + StyledTextParagraph { + text: " • great grandchild".into(), + formatting: alloc::vec![], + links: alloc::vec![] + }, + ] + ); + + assert_eq!( + StyledText::parse("hello [*world*](https://example.com)").unwrap().paragraphs, + [StyledTextParagraph { + text: "hello world".into(), + formatting: alloc::vec![ + FormattedSpan { range: 6..11, style: Style::Emphasis }, + FormattedSpan { range: 6..11, style: Style::Link } + ], + links: alloc::vec![(6..11, "https://example.com".into())] + }] + ); + + assert_eq!( + StyledText::parse("hello world").unwrap().paragraphs, + [StyledTextParagraph { + text: "hello world".into(), + formatting: alloc::vec![FormattedSpan { range: 0..11, style: Style::Underline },], + links: alloc::vec![] + }] + ); + + assert_eq!( + StyledText::parse(r#"hello world"#).unwrap().paragraphs, + [StyledTextParagraph { + text: "hello world".into(), + formatting: alloc::vec![FormattedSpan { + range: 0..11, + style: Style::Color(crate::Color::from_rgb_u8(0, 0, 255)) + },], + links: alloc::vec![] + }] + ); + + assert_eq!( + StyledText::parse(r#"hello world"#).unwrap().paragraphs, + [StyledTextParagraph { + text: "hello world".into(), + formatting: alloc::vec![ + FormattedSpan { + range: 0..11, + style: Style::Color(crate::Color::from_rgb_u8(255, 0, 0)) + }, + FormattedSpan { range: 0..11, style: Style::Underline }, + ], + links: alloc::vec![] + }] + ); +} diff --git a/internal/core/rtti.rs b/internal/core/rtti.rs index 7d6fb159250..b9db495a0c3 100644 --- a/internal/core/rtti.rs +++ b/internal/core/rtti.rs @@ -54,6 +54,7 @@ macro_rules! declare_ValueType_2 { crate::items::MenuEntry, crate::items::DropEvent, crate::model::ModelRc, + crate::api::StyledText, $(crate::items::$Name,)* ]; }; diff --git a/internal/interpreter/api.rs b/internal/interpreter/api.rs index 76aa1a3251f..c13025ffa46 100644 --- a/internal/interpreter/api.rs +++ b/internal/interpreter/api.rs @@ -63,6 +63,8 @@ pub enum ValueType { Brush, /// Correspond to `image` type in .slint. Image, + /// Correspond to `styled-text` type in .slint. + StyledText, /// The type is not a public type but something internal. #[doc(hidden)] Other = -1, @@ -87,6 +89,7 @@ impl From for ValueType { LangType::Struct { .. } => Self::Struct, LangType::Void => Self::Void, LangType::Image => Self::Image, + LangType::StyledText => Self::StyledText, _ => Self::Other, } } @@ -140,6 +143,8 @@ pub enum Value { #[doc(hidden)] /// Correspond to the `component-factory` type in .slint ComponentFactory(ComponentFactory) = 12, + /// Correspond to the `styled-text` type in .slint + StyledText(i_slint_core::api::StyledText) = 13, } impl Value { @@ -185,6 +190,9 @@ impl PartialEq for Value { Value::ComponentFactory(lhs) => { matches!(other, Value::ComponentFactory(rhs) if lhs == rhs) } + Value::StyledText(lhs) => { + matches!(other, Value::StyledText(rhs) if lhs == rhs) + } } } } @@ -209,6 +217,7 @@ impl std::fmt::Debug for Value { Value::EnumerationValue(n, v) => write!(f, "Value::EnumerationValue({n:?}, {v:?})"), Value::LayoutCache(v) => write!(f, "Value::LayoutCache({v:?})"), Value::ComponentFactory(factory) => write!(f, "Value::ComponentFactory({factory:?})"), + Value::StyledText(text) => write!(f, "Value::StyledText({text:?})"), } } } @@ -251,6 +260,7 @@ declare_value_conversion!(PathData => [PathData]); declare_value_conversion!(EasingCurve => [i_slint_core::animations::EasingCurve]); declare_value_conversion!(LayoutCache => [SharedVector] ); declare_value_conversion!(ComponentFactory => [ComponentFactory] ); +declare_value_conversion!(StyledText => [i_slint_core::api::StyledText] ); /// Implement From / TryFrom for Value that convert a `struct` to/from `Value::Struct` macro_rules! declare_value_struct_conversion { diff --git a/internal/interpreter/dynamic_item_tree.rs b/internal/interpreter/dynamic_item_tree.rs index 65f92339329..4d1ec8d3d8b 100644 --- a/internal/interpreter/dynamic_item_tree.rs +++ b/internal/interpreter/dynamic_item_tree.rs @@ -14,7 +14,7 @@ use i_slint_compiler::{generator, object_tree, parser, CompilerConfiguration}; use i_slint_core::accessibility::{ AccessibilityAction, AccessibleStringProperty, SupportedAccessibilityAction, }; -use i_slint_core::api::LogicalPosition; +use i_slint_core::api::{LogicalPosition, StyledText}; use i_slint_core::component_factory::ComponentFactory; use i_slint_core::item_tree::{ IndexRange, ItemRc, ItemTree, ItemTreeNode, ItemTreeRef, ItemTreeRefPin, ItemTreeVTable, @@ -1261,7 +1261,7 @@ pub(crate) fn generate_item_tree<'id>( } Type::LayoutCache => property_info::>(), Type::Function { .. } | Type::Callback { .. } => return None, - + Type::StyledText => property_info::(), // These can't be used in properties Type::Invalid | Type::Void diff --git a/internal/interpreter/eval.rs b/internal/interpreter/eval.rs index f60ab172d38..6e22a66de40 100644 --- a/internal/interpreter/eval.rs +++ b/internal/interpreter/eval.rs @@ -1732,6 +1732,7 @@ fn check_value_type(value: &mut Value, ty: &Type) -> bool { } Type::LayoutCache => matches!(value, Value::LayoutCache(_)), Type::ComponentFactory => matches!(value, Value::ComponentFactory(_)), + Type::StyledText => matches!(value, Value::StyledText(_)), } } @@ -2032,6 +2033,7 @@ pub fn default_value_for_type(ty: &Type) -> Value { | Type::Function { .. } => { panic!("There can't be such property") } + Type::StyledText => Value::StyledText(Default::default()), } }