Skip to content

Commit 4a2c334

Browse files
Very basic Rich Text Support (#9733)
* Add is_markdown property * Add builtins.slint line * Add parse_markdown function * Formatting * Add strikethrough * Assume that end tags always match and don't contain any additional info * Layout rich text * Formatting * Handle code blocks, hackily do indentation and list points * Remove dbg! * Apply a few suggestions * Write unit tests * Do a 3rd layer of bullet point indentation * Test nested lists * Add markdown text component * Add a paragraph_from_text function * Remove MarkdownText if SLINT_ENABLE_EXPERIMENTAL_FEATURES is not set * Mark as experimental * [autofix.ci] apply automated fixes * Add cfg flag to test --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent a61602a commit 4a2c334

File tree

10 files changed

+598
-72
lines changed

10 files changed

+598
-72
lines changed

api/cpp/cbindgen.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,7 @@ fn gen_corelib(
304304
"Flickable",
305305
"SimpleText",
306306
"ComplexText",
307+
"MarkdownText",
307308
"Path",
308309
"WindowItem",
309310
"TextInput",

internal/compiler/builtins.slint

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,18 @@ component ComplexText inherits SimpleText {
120120

121121
export { ComplexText as Text }
122122

123+
export component MarkdownText inherits SimpleText {
124+
in property <string> font-family;
125+
in property <bool> font-italic;
126+
in property <TextOverflow> overflow;
127+
in property <TextWrap> wrap;
128+
in property <length> letter-spacing;
129+
in property <brush> stroke;
130+
in property <length> stroke-width;
131+
in property <TextStrokeStyle> stroke-style;
132+
//-default_size_binding:implicit_size
133+
}
134+
123135
export component TouchArea {
124136
in property <bool> enabled: true;
125137
out property <bool> pressed;

internal/compiler/passes/embed_glyphs.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -559,7 +559,7 @@ pub fn collect_font_sizes_used(
559559
.to_string()
560560
.as_str()
561561
{
562-
"TextInput" | "Text" | "SimpleText" | "ComplexText" => {
562+
"TextInput" | "Text" | "SimpleText" | "ComplexText" | "MarkdownText" => {
563563
if let Some(font_size) = try_extract_font_size_from_element(elem, "font-size") {
564564
add_font_size(font_size)
565565
}

internal/compiler/typeregister.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,8 @@ impl TypeRegister {
585585
register.elements.remove("DropArea").unwrap();
586586
register.types.remove("DropEvent").unwrap(); // Also removed in xtask/src/slintdocs.rs
587587

588+
register.elements.remove("MarkdownText").unwrap();
589+
588590
Rc::new(RefCell::new(register))
589591
}
590592

internal/core/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ raw-window-handle-06 = ["dep:raw-window-handle-06"]
6363

6464
experimental = []
6565

66+
experimental-rich-text = ["dep:pulldown-cmark"]
67+
6668
unstable-wgpu-26 = ["dep:wgpu-26"]
6769
unstable-wgpu-27 = ["dep:wgpu-27"]
6870

@@ -106,6 +108,7 @@ integer-sqrt = { version = "0.1.5" }
106108
bytemuck = { workspace = true, optional = true, features = ["derive"] }
107109
sys-locale = { version = "0.3.2", optional = true }
108110
parley = { version = "0.6.0", optional = true }
111+
pulldown-cmark = { version = "0.13.0", optional = true }
109112

110113
image = { workspace = true, optional = true, default-features = false }
111114
clru = { workspace = true, optional = true }

internal/core/item_rendering.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,7 @@ pub trait RenderText {
281281
fn overflow(self: Pin<&Self>) -> TextOverflow;
282282
fn letter_spacing(self: Pin<&Self>) -> LogicalLength;
283283
fn stroke(self: Pin<&Self>) -> (Brush, LogicalLength, TextStrokeStyle);
284+
fn is_markdown(self: Pin<&Self>) -> bool;
284285
}
285286

286287
impl RenderText for (SharedString, Brush) {
@@ -321,6 +322,10 @@ impl RenderText for (SharedString, Brush) {
321322
fn stroke(self: Pin<&Self>) -> (Brush, LogicalLength, TextStrokeStyle) {
322323
Default::default()
323324
}
325+
326+
fn is_markdown(self: Pin<&Self>) -> bool {
327+
false
328+
}
324329
}
325330

326331
/// Trait used to render each items.

internal/core/items.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1727,6 +1727,10 @@ declare_item_vtable! {
17271727
fn slint_get_ComplexTextVTable() -> ComplexTextVTable for ComplexText
17281728
}
17291729

1730+
declare_item_vtable! {
1731+
fn slint_get_MarkdownTextVTable() -> MarkdownTextVTable for MarkdownText
1732+
}
1733+
17301734
declare_item_vtable! {
17311735
fn slint_get_SimpleTextVTable() -> SimpleTextVTable for SimpleText
17321736
}

internal/core/items/text.rs

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,10 @@ impl RenderText for ComplexText {
200200
fn stroke(self: Pin<&Self>) -> (Brush, LogicalLength, TextStrokeStyle) {
201201
(self.stroke(), self.stroke_width(), self.stroke_style())
202202
}
203+
204+
fn is_markdown(self: Pin<&Self>) -> bool {
205+
false
206+
}
203207
}
204208

205209
impl ComplexText {
@@ -215,6 +219,189 @@ impl ComplexText {
215219
}
216220
}
217221

222+
/// The implementation of the `Text` element
223+
#[repr(C)]
224+
#[derive(FieldOffsets, Default, SlintElement)]
225+
#[pin]
226+
pub struct MarkdownText {
227+
pub width: Property<LogicalLength>,
228+
pub height: Property<LogicalLength>,
229+
pub text: Property<SharedString>,
230+
pub font_size: Property<LogicalLength>,
231+
pub font_weight: Property<i32>,
232+
pub color: Property<Brush>,
233+
pub horizontal_alignment: Property<TextHorizontalAlignment>,
234+
pub vertical_alignment: Property<TextVerticalAlignment>,
235+
236+
pub font_family: Property<SharedString>,
237+
pub font_italic: Property<bool>,
238+
pub wrap: Property<TextWrap>,
239+
pub overflow: Property<TextOverflow>,
240+
pub letter_spacing: Property<LogicalLength>,
241+
pub stroke: Property<Brush>,
242+
pub stroke_width: Property<LogicalLength>,
243+
pub stroke_style: Property<TextStrokeStyle>,
244+
pub cached_rendering_data: CachedRenderingData,
245+
}
246+
247+
impl Item for MarkdownText {
248+
fn init(self: Pin<&Self>, _self_rc: &ItemRc) {}
249+
250+
fn layout_info(
251+
self: Pin<&Self>,
252+
orientation: Orientation,
253+
window_adapter: &Rc<dyn WindowAdapter>,
254+
self_rc: &ItemRc,
255+
) -> LayoutInfo {
256+
text_layout_info(
257+
self,
258+
&self_rc,
259+
window_adapter,
260+
orientation,
261+
Self::FIELD_OFFSETS.width.apply_pin(self),
262+
)
263+
}
264+
265+
fn input_event_filter_before_children(
266+
self: Pin<&Self>,
267+
_: &MouseEvent,
268+
_window_adapter: &Rc<dyn WindowAdapter>,
269+
_self_rc: &ItemRc,
270+
) -> InputEventFilterResult {
271+
InputEventFilterResult::ForwardAndIgnore
272+
}
273+
274+
fn input_event(
275+
self: Pin<&Self>,
276+
_: &MouseEvent,
277+
_window_adapter: &Rc<dyn WindowAdapter>,
278+
_self_rc: &ItemRc,
279+
) -> InputEventResult {
280+
InputEventResult::EventIgnored
281+
}
282+
283+
fn capture_key_event(
284+
self: Pin<&Self>,
285+
_: &KeyEvent,
286+
_window_adapter: &Rc<dyn WindowAdapter>,
287+
_self_rc: &ItemRc,
288+
) -> KeyEventResult {
289+
KeyEventResult::EventIgnored
290+
}
291+
292+
fn key_event(
293+
self: Pin<&Self>,
294+
_: &KeyEvent,
295+
_window_adapter: &Rc<dyn WindowAdapter>,
296+
_self_rc: &ItemRc,
297+
) -> KeyEventResult {
298+
KeyEventResult::EventIgnored
299+
}
300+
301+
fn focus_event(
302+
self: Pin<&Self>,
303+
_: &FocusEvent,
304+
_window_adapter: &Rc<dyn WindowAdapter>,
305+
_self_rc: &ItemRc,
306+
) -> FocusEventResult {
307+
FocusEventResult::FocusIgnored
308+
}
309+
310+
fn render(
311+
self: Pin<&Self>,
312+
backend: &mut &mut dyn ItemRenderer,
313+
self_rc: &ItemRc,
314+
size: LogicalSize,
315+
) -> RenderingResult {
316+
(*backend).draw_text(self, self_rc, size, &self.cached_rendering_data);
317+
RenderingResult::ContinueRenderingChildren
318+
}
319+
320+
fn bounding_rect(
321+
self: core::pin::Pin<&Self>,
322+
_window_adapter: &Rc<dyn WindowAdapter>,
323+
_self_rc: &ItemRc,
324+
geometry: LogicalRect,
325+
) -> LogicalRect {
326+
geometry
327+
}
328+
329+
fn clips_children(self: Pin<&Self>) -> bool {
330+
false
331+
}
332+
}
333+
334+
impl ItemConsts for MarkdownText {
335+
const cached_rendering_data_offset: const_field_offset::FieldOffset<
336+
MarkdownText,
337+
CachedRenderingData,
338+
> = MarkdownText::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection();
339+
}
340+
341+
impl RenderText for MarkdownText {
342+
fn target_size(self: Pin<&Self>) -> LogicalSize {
343+
LogicalSize::from_lengths(self.width(), self.height())
344+
}
345+
346+
fn text(self: Pin<&Self>) -> SharedString {
347+
self.text()
348+
}
349+
350+
fn font_request(self: Pin<&Self>, self_rc: &ItemRc) -> FontRequest {
351+
WindowItem::resolved_font_request(
352+
self_rc,
353+
self.font_family(),
354+
self.font_weight(),
355+
self.font_size(),
356+
self.letter_spacing(),
357+
self.font_italic(),
358+
)
359+
}
360+
361+
fn color(self: Pin<&Self>) -> Brush {
362+
self.color()
363+
}
364+
365+
fn alignment(
366+
self: Pin<&Self>,
367+
) -> (super::TextHorizontalAlignment, super::TextVerticalAlignment) {
368+
(self.horizontal_alignment(), self.vertical_alignment())
369+
}
370+
371+
fn wrap(self: Pin<&Self>) -> TextWrap {
372+
self.wrap()
373+
}
374+
375+
fn overflow(self: Pin<&Self>) -> TextOverflow {
376+
self.overflow()
377+
}
378+
379+
fn letter_spacing(self: Pin<&Self>) -> LogicalLength {
380+
self.letter_spacing()
381+
}
382+
383+
fn stroke(self: Pin<&Self>) -> (Brush, LogicalLength, TextStrokeStyle) {
384+
(self.stroke(), self.stroke_width(), self.stroke_style())
385+
}
386+
387+
fn is_markdown(self: Pin<&Self>) -> bool {
388+
true
389+
}
390+
}
391+
392+
impl MarkdownText {
393+
pub fn font_metrics(
394+
self: Pin<&Self>,
395+
window_adapter: &Rc<dyn WindowAdapter>,
396+
self_rc: &ItemRc,
397+
) -> FontMetrics {
398+
let window_inner = WindowInner::from_pub(window_adapter.window());
399+
let scale_factor = ScaleFactor::new(window_inner.scale_factor());
400+
let font_request = self.font_request(self_rc);
401+
window_adapter.renderer().font_metrics(font_request, scale_factor)
402+
}
403+
}
404+
218405
/// The implementation of the `Text` element
219406
#[repr(C)]
220407
#[derive(FieldOffsets, Default, SlintElement)]
@@ -371,6 +558,10 @@ impl RenderText for SimpleText {
371558
fn stroke(self: Pin<&Self>) -> (Brush, LogicalLength, TextStrokeStyle) {
372559
Default::default()
373560
}
561+
562+
fn is_markdown(self: Pin<&Self>) -> bool {
563+
false
564+
}
374565
}
375566

376567
impl SimpleText {

0 commit comments

Comments
 (0)