From 4b6606147407fe4ee5308e7a4036d41140b6af06 Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Thu, 10 Oct 2024 22:24:26 +0100 Subject: [PATCH] Upgrade to vello 0.3, parley 0.2, peniko 0.2 and usvg 0.43 --- Cargo.toml | 9 +- packages/blitz-dom/Cargo.toml | 3 +- packages/blitz-dom/src/document.rs | 209 ++++++++++++++++-- packages/blitz-dom/src/layout/construct.rs | 38 ++-- packages/blitz-dom/src/layout/mod.rs | 9 - packages/blitz-dom/src/node.rs | 16 +- packages/blitz-dom/src/stylo_to_parley.rs | 17 +- packages/blitz-renderer-vello/src/renderer.rs | 5 +- .../src/renderer/render.rs | 86 +++---- packages/dioxus-blitz/Cargo.toml | 1 + .../src/documents/dioxus_document.rs | 21 +- 11 files changed, 279 insertions(+), 135 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 27688be0..bd401323 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,13 +26,14 @@ dioxus-devtools = { git = "https://github.com/dioxuslabs/dioxus", rev = "9ffd4b8 taffy = { git = "https://github.com/dioxuslabs/taffy", rev = "950a0eb1322f15e5d1083f4793b55d52061718de" } # Linebender dependencies -peniko = { version = "0.1" } -vello = { git = "https://github.com/linebender/vello", rev = "aaa9f5f2d0f21f3d038501ea0cf32c989d97aab3", package = "vello", features = [ "wgpu" ] } -vello_svg = { git = "https://github.com/cfraz89/vello_svg", rev = "fc29d4ebf8d6aaee980b203f39ef2c73fe43c017" } -parley = { git = "https://github.com/nicoburns/parley", rev = "186b6e991d08731c0588dc0b247564cbba1c0435" } +peniko = { version = "0.2" } +vello = { version = "0.3", features = [ "wgpu" ] } +vello_svg = { version = "0.4" } +parley = { version = "0.2" } # Other dependencies tokio = { version = "1.25.0", features = ["full"] } +clipboard-rs = { version = "0.2.1" } tracing = "0.1.40" wgpu = "22.1.0" diff --git a/packages/blitz-dom/Cargo.toml b/packages/blitz-dom/Cargo.toml index 64f9180f..b15b5c36 100644 --- a/packages/blitz-dom/Cargo.toml +++ b/packages/blitz-dom/Cargo.toml @@ -17,6 +17,7 @@ style_dom = { workspace = true } taffy = { workspace = true } parley = { workspace = true } peniko = { workspace = true } +clipboard-rs = { workspace = true } tracing = { workspace = true, optional = true } slab = "0.4.9" app_units = "0.7.5" @@ -29,5 +30,5 @@ url = { version = "2.5.0", features = ["serde"] } data-url = "0.3.1" image = "0.25.2" winit = { version = "0.30.4", default-features = false } -usvg = "0.42.0" +usvg = "0.43.0" woff = "0.3.3" \ No newline at end of file diff --git a/packages/blitz-dom/src/document.rs b/packages/blitz-dom/src/document.rs index bdda761a..2a71c8e8 100644 --- a/packages/blitz-dom/src/document.rs +++ b/packages/blitz-dom/src/document.rs @@ -3,14 +3,15 @@ use crate::node::{ImageData, NodeSpecificData, TextBrush}; use crate::{ElementNodeData, Node, NodeData, TextNodeData, Viewport}; use app_units::Au; use html5ever::local_name; +use parley::PlainEditorOp; use peniko::kurbo; use string_cache::Atom; use style::attr::{AttrIdentifier, AttrValue}; use style::values::computed::Overflow; use style::values::GenericAtomIdent; +use winit::keyboard::{Key, NamedKey}; // use quadtree_rs::Quadtree; use crate::util::Resource; -use parley::editor::{PointerButton, TextEvent}; use selectors::{matching::QuirksMode, Element}; use slab::Slab; use std::any::Any; @@ -79,7 +80,7 @@ pub struct Document { /// We pin the tree to a guarantee to the nodes it creates that the tree is stable in memory. /// /// There is no way to create the tree - publicly or privately - that would invalidate that invariant. - pub(crate) nodes: Box>, + pub nodes: Box>, pub(crate) guard: SharedRwLock, @@ -110,10 +111,10 @@ pub struct Document { pub(crate) nodes_to_stylesheet: BTreeMap, /// A Parley font context - pub(crate) font_ctx: parley::FontContext, + pub font_ctx: parley::FontContext, /// A Parley layout context - pub(crate) layout_ctx: parley::LayoutContext, + pub layout_ctx: parley::LayoutContext, /// The node which is currently hovered (if any) pub(crate) hover_node_id: Option, @@ -130,7 +131,7 @@ impl DocumentLike for Document { let event = dbg!(event); match event.data { - EventData::Click { x, y, mods } => { + EventData::Click { x, y, .. } => { let hit = self.hit(x, y); if let Some(hit) = hit { assert!(hit.node_id == event.target); @@ -154,10 +155,10 @@ impl DocumentLike for Document { { let x = (hit.x - content_box_offset.x) as f64 * self.viewport.scale_f64(); let y = (hit.y - content_box_offset.y) as f64 * self.viewport.scale_f64(); - text_input_data.editor.pointer_down( - kurbo::Point { x, y }, - mods, - PointerButton::Primary, + text_input_data.editor.transact( + &mut self.font_ctx, + &mut self.layout_ctx, + [PlainEditorOp::MoveToPoint(x as f32, y as f32)], ); self.set_focus_to(hit.node_id); @@ -190,31 +191,191 @@ impl DocumentLike for Document { return; } + if !event.state.is_pressed() { + return; + } + #[allow(unused)] + let shift = mods.state().shift_key(); + #[allow(unused)] + let action_mod = { + if cfg!(target_os = "macos") { + mods.state().super_key() + } else { + mods.state().control_key() + } + }; + let node = &mut self.nodes[node_id]; let text_input_data = node .raw_dom_data .downcast_element_mut() .and_then(|el| el.text_input_data_mut()); + if let Some(input_data) = text_input_data { - let text_event = TextEvent::KeyboardKey(event, mods.state()); - input_data.editor.text_event(&text_event); + macro_rules! transact { + ($op:expr) => {{ + input_data.editor.transact( + &mut self.font_ctx, + &mut self.layout_ctx, + [$op], + ); + }}; + } + + match event.logical_key { + #[cfg(not(target_os = "android"))] + Key::Character(c) + if action_mod && matches!(c.as_str(), "c" | "x" | "v") => + { + use clipboard_rs::{Clipboard, ClipboardContext}; + use parley::layout::editor::ActiveText; + + match c.to_lowercase().as_str() { + "c" => { + if let ActiveText::Selection(text) = + input_data.editor.active_text() + { + let cb = ClipboardContext::new().unwrap(); + cb.set_text(text.to_owned()).ok(); + } + } + "x" => { + if let ActiveText::Selection(text) = + input_data.editor.active_text() + { + let cb = ClipboardContext::new().unwrap(); + cb.set_text(text.to_owned()).ok(); + transact!(PlainEditorOp::DeleteSelection) + } + } + "v" => { + let cb = ClipboardContext::new().unwrap(); + let text = cb.get_text().unwrap_or_default(); + transact!(PlainEditorOp::InsertOrReplaceSelection( + text.into(), + )) + } + _ => unreachable!(), + } + } + Key::Character(c) + if action_mod && matches!(c.to_lowercase().as_str(), "a") => + { + if shift { + transact!(PlainEditorOp::CollapseSelection) + } else { + transact!(PlainEditorOp::SelectAll) + } + } + Key::Named(NamedKey::ArrowLeft) => { + if action_mod { + if shift { + transact!(PlainEditorOp::SelectWordLeft) + } else { + transact!(PlainEditorOp::MoveWordLeft) + } + } else if shift { + transact!(PlainEditorOp::SelectLeft) + } else { + transact!(PlainEditorOp::MoveLeft) + } + } + Key::Named(NamedKey::ArrowRight) => { + if action_mod { + if shift { + transact!(PlainEditorOp::SelectWordRight) + } else { + transact!(PlainEditorOp::MoveWordRight) + } + } else if shift { + transact!(PlainEditorOp::SelectRight) + } else { + transact!(PlainEditorOp::MoveRight) + } + } + Key::Named(NamedKey::ArrowUp) => { + if shift { + transact!(PlainEditorOp::SelectUp) + } else { + transact!(PlainEditorOp::MoveUp) + } + } + Key::Named(NamedKey::ArrowDown) => { + if shift { + transact!(PlainEditorOp::SelectDown) + } else { + transact!(PlainEditorOp::MoveDown) + } + } + Key::Named(NamedKey::Home) => { + if action_mod { + if shift { + transact!(PlainEditorOp::SelectToTextStart) + } else { + transact!(PlainEditorOp::MoveToTextStart) + } + } else if shift { + transact!(PlainEditorOp::SelectToLineStart) + } else { + transact!(PlainEditorOp::MoveToLineStart) + } + } + Key::Named(NamedKey::End) => { + if action_mod { + if shift { + transact!(PlainEditorOp::SelectToTextEnd) + } else { + transact!(PlainEditorOp::MoveToTextEnd) + } + } else if shift { + transact!(PlainEditorOp::SelectToLineEnd) + } else { + transact!(PlainEditorOp::MoveToLineEnd) + } + } + Key::Named(NamedKey::Delete) => { + if action_mod { + transact!(PlainEditorOp::DeleteWord) + } else { + transact!(PlainEditorOp::Delete) + } + } + Key::Named(NamedKey::Backspace) => { + if action_mod { + transact!(PlainEditorOp::BackdeleteWord) + } else { + transact!(PlainEditorOp::Backdelete) + } + } + Key::Named(NamedKey::Enter) => { + transact!(PlainEditorOp::InsertOrReplaceSelection("\n".into())) + } + Key::Named(NamedKey::Space) => { + transact!(PlainEditorOp::InsertOrReplaceSelection(" ".into())) + } + Key::Character(s) => { + transact!(PlainEditorOp::InsertOrReplaceSelection(s.into())) + } + _ => {} + }; println!("Sent text event to {}", node_id); } } } - EventData::Ime(ime_event) => { - if let Some(node_id) = self.focus_node_id { - let node = &mut self.nodes[node_id]; - let text_input_data = node - .raw_dom_data - .downcast_element_mut() - .and_then(|el| el.text_input_data_mut()); - if let Some(input_data) = text_input_data { - let text_event = TextEvent::Ime(ime_event); - input_data.editor.text_event(&text_event); - println!("Sent ime event to {}", node_id); - } - } + EventData::Ime(_ime_event) => { + // FIXME: Implement IME events on top of PlainEditor + // if let Some(node_id) = self.focus_node_id { + // let node = &mut self.nodes[node_id]; + // let text_input_data = node + // .raw_dom_data + // .downcast_element_mut() + // .and_then(|el| el.text_input_data_mut()); + // if let Some(input_data) = text_input_data { + // let text_event = TextEvent::Ime(ime_event); + // input_data.editor.text_event(&text_event); + // println!("Sent ime event to {}", node_id); + // } + // } } EventData::Hover => {} } diff --git a/packages/blitz-dom/src/layout/construct.rs b/packages/blitz-dom/src/layout/construct.rs index a35504f8..48edbad4 100644 --- a/packages/blitz-dom/src/layout/construct.rs +++ b/packages/blitz-dom/src/layout/construct.rs @@ -2,11 +2,7 @@ use core::str; use std::sync::Arc; use html5ever::{local_name, namespace_url, ns, QualName}; -use parley::{ - builder::TreeBuilder, - style::{FontStack, WhiteSpaceCollapse}, - InlineBox, -}; +use parley::{FontStack, InlineBox, PlainEditorOp, StyleProperty, TreeBuilder, WhiteSpaceCollapse}; use slab::Slab; use style::{ data::ElementData, @@ -329,7 +325,7 @@ fn marker_for_style(list_style_type: ListStyleType, index: usize) -> Option Option> { - let bullet_font = Some(FontStack::Source("Bullet, monospace, sans-serif")); + let bullet_font = Some("Bullet, monospace, sans-serif".into()); match list_style_type { ListStyleType::Disc | ListStyleType::Circle @@ -497,19 +493,23 @@ fn create_text_editor(doc: &mut Document, input_element_id: usize, is_multiline: let element = &mut node.raw_dom_data.downcast_element_mut().unwrap(); if !matches!(element.node_specific_data, NodeSpecificData::TextInput(_)) { - let initial_value = element - .attr(local_name!("value")) - .unwrap_or(" ") - .to_string(); - - let mut text_input_data = TextInputData::new(initial_value, 16.0, is_multiline); - text_input_data - .editor - .set_text_size(parley_style.font_size * doc.viewport.scale()); - text_input_data - .editor - .set_line_height(parley_style.line_height); - text_input_data.editor.set_brush(parley_style.brush); + let mut text_input_data = TextInputData::new(is_multiline); + let text: Arc = Arc::from(element.attr(local_name!("value")).unwrap_or(" ")); + let styles: Arc<[_]> = Arc::from([ + StyleProperty::FontSize(parley_style.font_size), + StyleProperty::LineHeight(parley_style.line_height), + StyleProperty::Brush(parley_style.brush), + ]); + text_input_data.editor.transact( + &mut doc.font_ctx, + &mut doc.layout_ctx, + [ + PlainEditorOp::SetText(text), + PlainEditorOp::SetScale(doc.viewport.scale_f64() as f32), + PlainEditorOp::SetWidth(10000.0), + PlainEditorOp::SetDefaultStyle(styles), + ], + ); element.node_specific_data = NodeSpecificData::TextInput(text_input_data); } } diff --git a/packages/blitz-dom/src/layout/mod.rs b/packages/blitz-dom/src/layout/mod.rs index 3ff0becf..b9584ad1 100644 --- a/packages/blitz-dom/src/layout/mod.rs +++ b/packages/blitz-dom/src/layout/mod.rs @@ -156,15 +156,6 @@ impl LayoutPartialTree for Document { return taffy::LayoutOutput::HIDDEN; } - // Perform text layout for text inputs - if inputs.run_mode == taffy::RunMode::PerformLayout { - if let Some(input_data) = element_data.text_input_data_mut() { - input_data - .editor - .rebuild(&mut tree.font_ctx, &mut tree.layout_ctx); - } - } - // todo: need to handle shadow roots by actually descending into them if *element_data.name.local == *"input" { match element_data.attr(local_name!("type")) { diff --git a/packages/blitz-dom/src/node.rs b/packages/blitz-dom/src/node.rs index 7abd64e5..c7954386 100644 --- a/packages/blitz-dom/src/node.rs +++ b/packages/blitz-dom/src/node.rs @@ -486,17 +486,23 @@ impl ImageData { } } -#[derive(Clone)] pub struct TextInputData { /// A parley TextEditor instance - pub editor: Box>, + pub editor: Box>, /// Whether the input is a singleline or multiline input pub is_multiline: bool, } +// FIXME: Implement Clone for PlainEditor +impl Clone for TextInputData { + fn clone(&self) -> Self { + TextInputData::new(self.is_multiline) + } +} + impl TextInputData { - pub fn new(text: String, text_size: f32, is_multiline: bool) -> Self { - let editor = Box::new(parley::editor::TextEditor::new(text, text_size)); + pub fn new(is_multiline: bool) -> Self { + let editor = Box::new(parley::PlainEditor::default()); Self { editor, is_multiline, @@ -567,7 +573,7 @@ impl std::fmt::Debug for ListItemLayout { } } -pub type TextBrush = parley::editor::TextBrush; +pub type TextBrush = peniko::Brush; #[derive(Clone)] pub struct TextLayout { diff --git a/packages/blitz-dom/src/stylo_to_parley.rs b/packages/blitz-dom/src/stylo_to_parley.rs index 73e0b26f..fd4a9596 100644 --- a/packages/blitz-dom/src/stylo_to_parley.rs +++ b/packages/blitz-dom/src/stylo_to_parley.rs @@ -1,4 +1,6 @@ //! Conversion functions from Stylo types to Parley types +use std::borrow::Cow; + use crate::node::TextBrush; use crate::util::ToPenikoColor; @@ -72,7 +74,7 @@ pub(crate) fn style(style: &stylo::ComputedValues) -> parley::TextStyle<'static, } // TODO: fix leak! - break 'ret parley::FontFamily::Named(name.to_string().leak()); + break 'ret parley::FontFamily::Named(Cow::Owned(name.to_string())); } } stylo::SingleFontFamily::Generic(generic) => { @@ -89,9 +91,6 @@ pub(crate) fn style(style: &stylo::ComputedValues) -> parley::TextStyle<'static, }) .collect(); - // TODO: fix leak! - let families = Box::leak(families.into_boxed_slice()); - // Convert text colour let color = itext_styles.color.as_peniko(); @@ -100,19 +99,19 @@ pub(crate) fn style(style: &stylo::ComputedValues) -> parley::TextStyle<'static, .text_decoration_color .as_absolute() .map(ToPenikoColor::as_peniko) - .map(|color| TextBrush::Normal(peniko::Brush::Solid(color))); + .map(peniko::Brush::Solid); parley::TextStyle { // font_stack: parley::FontStack::Single(FontFamily::Generic(GenericFamily::SystemUi)), - font_stack: parley::FontStack::List(families), + font_stack: parley::FontStack::List(Cow::Owned(families)), font_size, font_stretch: Default::default(), font_style, font_weight, - font_variations: parley::FontSettings::List(&[]), - font_features: parley::FontSettings::List(&[]), + font_variations: parley::FontSettings::List(Cow::Borrowed(&[])), + font_features: parley::FontSettings::List(Cow::Borrowed(&[])), locale: Default::default(), - brush: TextBrush::Normal(peniko::Brush::Solid(color)), + brush: peniko::Brush::Solid(color), has_underline: itext_styles.text_decorations_in_effect.underline, underline_offset: Default::default(), underline_size: Default::default(), diff --git a/packages/blitz-renderer-vello/src/renderer.rs b/packages/blitz-renderer-vello/src/renderer.rs index 8ee17159..0d9d3c65 100644 --- a/packages/blitz-renderer-vello/src/renderer.rs +++ b/packages/blitz-renderer-vello/src/renderer.rs @@ -6,9 +6,8 @@ use blitz_dom::{Document, Viewport}; use std::num::NonZeroUsize; use std::sync::Arc; use vello::{ - block_on_wgpu, peniko::Color, - util::{RenderContext, RenderSurface}, + util::{block_on_wgpu, RenderContext, RenderSurface}, AaSupport, RenderParams, Renderer as VelloRenderer, RendererOptions, Scene, }; use wgpu::{ @@ -147,7 +146,6 @@ where width: state.surface.config.width, height: state.surface.config.height, antialiasing_method: vello::AaConfig::Msaa16, - debug: vello::DebugLayers::none(), }; // Regenerate the vello scene @@ -229,7 +227,6 @@ pub async fn render_to_buffer(dom: &Document, viewport: Viewport) -> Vec { width, height, antialiasing_method: vello::AaConfig::Area, - debug: vello::DebugLayers::none(), }; renderer .render_to_texture(device, queue, &scene, &view, &render_params) diff --git a/packages/blitz-renderer-vello/src/renderer/render.rs b/packages/blitz-renderer-vello/src/renderer/render.rs index dad2f712..99b9ec01 100644 --- a/packages/blitz-renderer-vello/src/renderer/render.rs +++ b/packages/blitz-renderer-vello/src/renderer/render.rs @@ -12,6 +12,7 @@ use blitz_dom::node::{ }; use blitz_dom::{local_name, Document, Node}; +use parley::Line; use style::{ dom::TElement, properties::{ @@ -51,7 +52,7 @@ use vello::kurbo::{BezPath, Cap, Join}; use vello::peniko::Gradient; use vello::{ kurbo::{Affine, Point, Rect, Shape, Stroke, Vec2}, - peniko::{self, Brush, Color, Fill, Mix}, + peniko::{self, Color, Fill, Mix}, Scene, }; use vello_svg::usvg; @@ -448,23 +449,21 @@ impl VelloSceneGenerator<'_> { // Render the text in text inputs if let Some(input_data) = cx.text_input { - let text_layout = input_data.editor.layout(); - - // Render text - cx.stroke_text(scene, text_layout, pos); - - // Render caret - let cursor_line = input_data.editor.get_cursor_line(); let transform = Affine::translate((pos.x * self.scale, pos.y * self.scale)); - if let Some(line) = cursor_line { - scene.stroke( - &Stroke::new(2.), - transform, - &Brush::Solid(Color::BLACK), - None, - &line, - ); + + // Render selection/caret + for rect in input_data.editor.selection_geometry().iter() { + scene.fill(Fill::NonZero, transform, Color::STEEL_BLUE, None, &rect); } + if let Some(cursor) = input_data.editor.selection_strong_geometry(1.5) { + scene.fill(Fill::NonZero, transform, Color::BLACK, None, &cursor); + }; + if let Some(cursor) = input_data.editor.selection_weak_geometry(1.5) { + scene.fill(Fill::NonZero, transform, Color::DARK_GRAY, None, &cursor); + }; + + // Render text + cx.stroke_text(scene, input_data.editor.lines(), pos); } else if let Some(ListItemLayout { marker, position: ListItemLayoutPosition::Outside(layout), @@ -496,7 +495,7 @@ impl VelloSceneGenerator<'_> { x: pos.x + x_offset as f64, y: pos.y + y_offset as f64, }; - cx.stroke_text(scene, layout, pos); + cx.stroke_text(scene, layout.lines(), pos); } if element.is_inline_root { @@ -511,7 +510,7 @@ impl VelloSceneGenerator<'_> { }); // Render text - cx.stroke_text(scene, &text_layout.layout, pos); + cx.stroke_text(scene, text_layout.layout.lines(), pos); // Render inline boxes for line in text_layout.layout.lines() { @@ -617,14 +616,20 @@ struct ElementCx<'a> { } impl ElementCx<'_> { - fn stroke_text(&self, scene: &mut Scene, text_layout: &parley::Layout, pos: Point) { + fn stroke_text<'a>( + &self, + scene: &mut Scene, + lines: impl Iterator>, + pos: Point, + ) { let transform = Affine::translate((pos.x * self.scale, pos.y * self.scale)); - for line in text_layout.lines() { + for line in lines { for item in line.items() { if let PositionedLayoutItem::GlyphRun(glyph_run) = item { let mut x = glyph_run.offset(); let y = glyph_run.baseline(); + let run = glyph_run.run(); let font = run.font(); let font_size = run.font_size(); @@ -640,35 +645,10 @@ impl ElementCx<'_> { .map(|coord| vello::skrifa::instance::NormalizedCoord::from_bits(*coord)) .collect::>(); - let text_brush = match &style.brush { - TextBrush::Normal(text_brush) => text_brush, - TextBrush::Highlight { text, fill } => { - scene.fill( - Fill::EvenOdd, - transform, - fill, - None, - &Rect::from_origin_size( - ( - glyph_run.offset() as f64, - // The y coordinate is on the baseline. We want to draw from the top of the line - // (Note that we are in a y-down coordinate system) - (y - metrics.ascent - metrics.leading) as f64, - ), - ( - glyph_run.advance() as f64, - (metrics.ascent + metrics.descent + metrics.leading) as f64, - ), - ), - ); - - text - } - }; - scene .draw_glyphs(font) - .brush(text_brush) + .brush(&style.brush) + .hint(true) .transform(transform) .glyph_transform(glyph_xform) .font_size(font_size) @@ -679,7 +659,8 @@ impl ElementCx<'_> { let gx = x + glyph.x; let gy = y - glyph.y; x += glyph.advance; - vello::glyph::Glyph { + + vello::Glyph { id: glyph.id as _, x: gx, y: gy, @@ -692,13 +673,7 @@ impl ElementCx<'_> { let w = glyph_run.advance() as f64; let y = (glyph_run.baseline() - offset + size / 2.0) as f64; let line = vello::kurbo::Line::new((x, y), (x + w, y)); - scene.stroke( - &Stroke::new(size as f64), - transform, - brush.text_brush(), - None, - &line, - ) + scene.stroke(&Stroke::new(size as f64), transform, brush, None, &line) }; if let Some(underline) = &style.underline { @@ -770,6 +745,7 @@ impl ElementCx<'_> { format: peniko::Format::Rgba8, width, height, + alpha: 255, extend: peniko::Extend::Pad, }; diff --git a/packages/dioxus-blitz/Cargo.toml b/packages/dioxus-blitz/Cargo.toml index 97d61067..c8e4a085 100644 --- a/packages/dioxus-blitz/Cargo.toml +++ b/packages/dioxus-blitz/Cargo.toml @@ -25,6 +25,7 @@ dioxus-cli-config = { workspace = true, optional = true } dioxus-devtools = { workspace = true, optional = true } futures-util = "0.3.30" vello = { workspace = true } +parley = { workspace = true } wgpu = { workspace = true } style = { workspace = true } tracing = { workspace = true, optional = true } diff --git a/packages/dioxus-blitz/src/documents/dioxus_document.rs b/packages/dioxus-blitz/src/documents/dioxus_document.rs index 6dd04a23..cc614514 100644 --- a/packages/dioxus-blitz/src/documents/dioxus_document.rs +++ b/packages/dioxus-blitz/src/documents/dioxus_document.rs @@ -4,6 +4,7 @@ use std::{ any::Any, collections::{HashMap, HashSet}, rc::Rc, + sync::Arc, }; use blitz_dom::{ @@ -23,6 +24,7 @@ use dioxus::{ prelude::set_event_converter, }; use futures_util::{pin_mut, FutureExt}; +use parley::PlainEditorOp; use rustc_hash::FxHashMap; use style::{ data::{ElementData, ElementStyles}, @@ -350,7 +352,7 @@ impl DioxusDocument { }; let value = match &element_node_data.node_specific_data { NodeSpecificData::CheckboxInput(checked) => checked.to_string(), - NodeSpecificData::TextInput(input_data) => input_data.editor.layout.text().clone(), + NodeSpecificData::TextInput(input_data) => input_data.editor.text().to_string(), _ => element_node_data .attr(local_name!("value")) .unwrap_or_default() @@ -735,7 +737,7 @@ impl WriteMutations for MutationWriter<'_> { self.doc.snapshot_node(node_id); - let node = self.doc.get_node_mut(node_id).unwrap(); + let node = &mut self.doc.nodes[node_id]; let stylo_element_data = &mut *node.stylo_element_data.borrow_mut(); if let Some(data) = stylo_element_data { @@ -750,8 +752,13 @@ impl WriteMutations for MutationWriter<'_> { else if let AttributeValue::Text(val) = value { // Update text input value if let Some(input_data) = element.text_input_data_mut() { - if input_data.editor.text() != val { - input_data.editor.set_text(val.clone()); + let val: &str = val; + if &*input_data.editor.text() != val { + input_data.editor.transact( + &mut self.doc.font_ctx, + &mut self.doc.layout_ctx, + [PlainEditorOp::SetText(Arc::from(val))], + ); } } @@ -782,7 +789,11 @@ impl WriteMutations for MutationWriter<'_> { if let AttributeValue::None = value { // Update text input value if let Some(input_data) = element.text_input_data_mut() { - input_data.editor.set_text("".to_string()); + input_data.editor.transact( + &mut self.doc.font_ctx, + &mut self.doc.layout_ctx, + [PlainEditorOp::SetText(Arc::from(""))], + ); } // FIXME: check namespace