From 630ded59da207e8ac61dc43020c54f392cb3cb6e Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Wed, 8 Nov 2023 23:05:33 +0000 Subject: [PATCH 1/2] Fix and enable text widget --- examples/hello.rs | 1 + src/view/button.rs | 5 +- src/view/mod.rs | 2 +- src/view/text.rs | 25 +++++----- src/widget/contexts.rs | 2 +- src/widget/mod.rs | 3 +- src/widget/text.rs | 106 ++++++++++++++++++++++++++++------------- 7 files changed, 93 insertions(+), 51 deletions(-) diff --git a/examples/hello.rs b/examples/hello.rs index f1c78fe88..747018586 100644 --- a/examples/hello.rs +++ b/examples/hello.rs @@ -16,6 +16,7 @@ fn app_logic(data: &mut i32) -> impl View { *data += 1; }), h_stack(( + String::from("Buttons: "), button("decrease", |data| { println!("clicked decrease"); *data -= 1; diff --git a/src/view/button.rs b/src/view/button.rs index 93a12a3c7..a5ad7cda6 100644 --- a/src/view/button.rs +++ b/src/view/button.rs @@ -14,8 +14,9 @@ use std::any::Any; -use crate::view::ViewMarker; -use crate::{view::Id, widget::ChangeFlags, MessageResult}; +use crate::view::{Id, ViewMarker}; +use crate::widget::ChangeFlags; +use crate::MessageResult; use super::{Cx, View}; diff --git a/src/view/mod.rs b/src/view/mod.rs index 958f6acc2..86c066d3a 100644 --- a/src/view/mod.rs +++ b/src/view/mod.rs @@ -17,7 +17,7 @@ mod button; // mod layout_observer; // mod list; // mod scroll_view; -// mod text; +mod text; // mod use_state; mod linear_layout; mod list; diff --git a/src/view/text.rs b/src/view/text.rs index 240a7392f..861da2020 100644 --- a/src/view/text.rs +++ b/src/view/text.rs @@ -12,19 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::any::Any; - -use crate::{event::MessageResult, id::Id, widget::ChangeFlags}; +use crate::view::{Id, ViewMarker}; +use crate::widget::ChangeFlags; use super::{Cx, View}; +impl ViewMarker for String {} + impl View for String { type State = (); - type Element = crate::widget::text::TextWidget; + type Element = crate::widget::TextWidget; - fn build(&self, cx: &mut Cx) -> (Id, Self::State, Self::Element) { - let (id, element) = cx.with_new_id(|_| crate::widget::text::TextWidget::new(self.clone())); + fn build(&self, cx: &mut Cx) -> (crate::view::Id, Self::State, Self::Element) { + let (id, element) = cx.with_new_id(|_| crate::widget::TextWidget::new(self.clone())); (id, (), element) } @@ -32,7 +33,7 @@ impl View for String { &self, _cx: &mut Cx, prev: &Self, - _id: &mut crate::id::Id, + _id: &mut Id, _state: &mut Self::State, element: &mut Self::Element, ) -> ChangeFlags { @@ -43,13 +44,13 @@ impl View for String { } } - fn event( + fn message( &self, - _id_path: &[crate::id::Id], + _id_path: &[xilem_core::Id], _state: &mut Self::State, - _event: Box, + message: Box, _app_state: &mut T, - ) -> MessageResult { - MessageResult::Stale + ) -> xilem_core::MessageResult { + xilem_core::MessageResult::Stale(message) } } diff --git a/src/widget/contexts.rs b/src/widget/contexts.rs index 34f16486a..eb5277a79 100644 --- a/src/widget/contexts.rs +++ b/src/widget/contexts.rs @@ -321,7 +321,7 @@ impl_context_method!( } } ); -// Methods on all contexts besides LayoutCx. +// Methods on LayoutCx and PaintCx // // These Methods return information about the widget impl_context_method!(LayoutCx<'_, '_>, PaintCx<'_, '_>, { diff --git a/src/widget/mod.rs b/src/widget/mod.rs index 2ec7572b0..102154567 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -22,7 +22,7 @@ mod linear_layout; mod piet_scene_helpers; mod raw_event; //mod scroll_view; -//mod text; +mod text; #[allow(clippy::module_inception)] mod widget; @@ -33,4 +33,5 @@ pub use button::Button; pub use contexts::{AccessCx, CxState, EventCx, LayoutCx, LifeCycleCx, PaintCx, UpdateCx}; pub use linear_layout::LinearLayout; pub use raw_event::{Event, LifeCycle, MouseEvent, ViewContext}; +pub use text::TextWidget; pub use widget::{AnyWidget, Widget}; diff --git a/src/widget/text.rs b/src/widget/text.rs index aab65f673..98563895e 100644 --- a/src/widget/text.rs +++ b/src/widget/text.rs @@ -12,44 +12,81 @@ // See the License for the specific language governing permissions and // limitations under the License. -use parley::Layout; +use parley::{FontContext, Layout}; use vello::{ - kurbo::{Affine, Point, Size}, + kurbo::{Affine, Size}, peniko::{Brush, Color}, - SceneBuilder, SceneFragment, + SceneBuilder, }; use crate::text::ParleyBrush; use super::{ - align::{FirstBaseline, LastBaseline, SingleAlignment, VertAlignment}, - contexts::LifeCycleCx, - AlignCx, ChangeFlags, EventCx, LayoutCx, LifeCycle, PaintCx, RawEvent, UpdateCx, Widget, + contexts::LifeCycleCx, BoxConstraints, ChangeFlags, Event, EventCx, LayoutCx, LifeCycle, + PaintCx, UpdateCx, Widget, }; pub struct TextWidget { text: String, layout: Option>, - is_wrapped: bool, } impl TextWidget { pub fn new(text: String) -> TextWidget { - TextWidget { - text, - is_wrapped: false, - layout: None, - } + TextWidget { text, layout: None } } pub fn set_text(&mut self, text: String) -> ChangeFlags { self.text = text; ChangeFlags::LAYOUT | ChangeFlags::PAINT } + + fn get_layout_mut(&mut self, font_cx: &mut FontContext) -> &mut Layout { + // Ensure Parley layout is initialised + if self.layout.is_none() { + let mut lcx = parley::LayoutContext::new(); + let mut layout_builder = lcx.ranged_builder(font_cx, &self.text, 1.0); + layout_builder.push_default(&parley::style::StyleProperty::Brush(ParleyBrush( + Brush::Solid(Color::rgb8(255, 255, 255)), + ))); + self.layout = Some(layout_builder.build()); + } + + self.layout.as_mut().unwrap() + } + + fn layout_text(&mut self, font_cx: &mut FontContext, bc: &BoxConstraints) -> Size { + // Compute max_advance from box constraints + let max_advance = if bc.max().width.is_finite() { + Some(bc.max().width as f32) + } else if bc.min().width.is_sign_negative() { + Some(0.0) + } else { + None + }; + + // Layout text + let layout = self.get_layout_mut(font_cx); + layout.break_all_lines(max_advance, parley::layout::Alignment::Start); + + // // Debug print + // println!( + // "max: {:?}. w: {}, h: {}", + // max_advance, + // layout.width(), + // layout.height() + // ); + + // Return dimensions + Size { + width: layout.width() as f64, + height: layout.height() as f64, + } + } } impl Widget for TextWidget { - fn event(&mut self, _cx: &mut EventCx, _event: &RawEvent) {} + fn event(&mut self, _cx: &mut EventCx, _event: &Event) {} fn lifecycle(&mut self, _cx: &mut LifeCycleCx, _event: &LifeCycle) {} @@ -59,32 +96,33 @@ impl Widget for TextWidget { cx.request_layout(); } - fn measure(&mut self, cx: &mut LayoutCx) -> (Size, Size) { - let min_size = Size::ZERO; - let max_size = Size::new(50.0, 50.0); - self.is_wrapped = false; - (min_size, max_size) + fn compute_max_intrinsic( + &mut self, + axis: crate::Axis, + cx: &mut LayoutCx, + bc: &super::BoxConstraints, + ) -> f64 { + let size = self.layout_text(cx.font_cx(), bc); + match axis { + crate::Axis::Horizontal => size.width, + crate::Axis::Vertical => size.height, + } } - fn layout(&mut self, cx: &mut LayoutCx, proposed_size: Size) -> Size { - let mut lcx = parley::LayoutContext::new(); - let mut layout_builder = lcx.ranged_builder(cx.font_cx(), &self.text, 1.0); - layout_builder.push_default(&parley::style::StyleProperty::Brush(ParleyBrush( - Brush::Solid(Color::rgb8(255, 255, 255)), - ))); - let mut layout = layout_builder.build(); - // Question for Chad: is this needed? - layout.break_all_lines(None, parley::layout::Alignment::Start); - self.layout = Some(layout); - cx.widget_state.max_size + fn layout(&mut self, cx: &mut LayoutCx, bc: &BoxConstraints) -> Size { + cx.request_paint(); + self.layout_text(cx.font_cx(), bc) } - fn align(&self, cx: &mut AlignCx, alignment: SingleAlignment) {} - - fn paint(&mut self, cx: &mut PaintCx, builder: &mut SceneBuilder) { + fn paint(&mut self, _cx: &mut PaintCx, builder: &mut SceneBuilder) { if let Some(layout) = &self.layout { - let transform = Affine::translate((40.0, 40.0)); - crate::text::render_text(builder, transform, &layout); + crate::text::render_text(builder, Affine::IDENTITY, layout); } } + + fn accessibility(&mut self, cx: &mut super::AccessCx) { + let mut builder = accesskit::NodeBuilder::new(accesskit::Role::StaticText); + builder.set_value(self.text.clone()); + cx.push_node(builder); + } } From 77ffca3be313c85e6ad0bb9a537b509e4f985ab8 Mon Sep 17 00:00:00 2001 From: Philipp Mildenberger Date: Thu, 9 Nov 2023 20:17:30 +0100 Subject: [PATCH 2/2] Add more String views (`&'static str`, `Cow<'static, str>`) --- examples/hello.rs | 2 +- src/view/text.rs | 79 ++++++++++++++++++++++++++++++++++++++++++++++ src/widget/text.rs | 7 ++-- 3 files changed, 84 insertions(+), 4 deletions(-) diff --git a/examples/hello.rs b/examples/hello.rs index 747018586..464501d1c 100644 --- a/examples/hello.rs +++ b/examples/hello.rs @@ -16,7 +16,7 @@ fn app_logic(data: &mut i32) -> impl View { *data += 1; }), h_stack(( - String::from("Buttons: "), + "Buttons: ", button("decrease", |data| { println!("clicked decrease"); *data -= 1; diff --git a/src/view/text.rs b/src/view/text.rs index 861da2020..1ec4fd482 100644 --- a/src/view/text.rs +++ b/src/view/text.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::borrow::Cow; + use crate::view::{Id, ViewMarker}; use crate::widget::ChangeFlags; @@ -24,6 +26,83 @@ impl View for String { type Element = crate::widget::TextWidget; + fn build(&self, cx: &mut Cx) -> (crate::view::Id, Self::State, Self::Element) { + let (id, element) = + cx.with_new_id(|_| crate::widget::TextWidget::new(Cow::from(self.clone()))); + (id, (), element) + } + + fn rebuild( + &self, + _cx: &mut Cx, + prev: &Self, + _id: &mut Id, + _state: &mut Self::State, + element: &mut Self::Element, + ) -> ChangeFlags { + if prev != self { + element.set_text(Cow::from(self.clone())) + } else { + ChangeFlags::empty() + } + } + + fn message( + &self, + _id_path: &[xilem_core::Id], + _state: &mut Self::State, + message: Box, + _app_state: &mut T, + ) -> xilem_core::MessageResult { + xilem_core::MessageResult::Stale(message) + } +} + +impl ViewMarker for &'static str {} + +impl View for &'static str { + type State = (); + + type Element = crate::widget::TextWidget; + + fn build(&self, cx: &mut Cx) -> (crate::view::Id, Self::State, Self::Element) { + let (id, element) = cx.with_new_id(|_| crate::widget::TextWidget::new(Cow::from(*self))); + (id, (), element) + } + + fn rebuild( + &self, + _cx: &mut Cx, + prev: &Self, + _id: &mut Id, + _state: &mut Self::State, + element: &mut Self::Element, + ) -> ChangeFlags { + if prev != self { + element.set_text(Cow::from(*self)) + } else { + ChangeFlags::empty() + } + } + + fn message( + &self, + _id_path: &[xilem_core::Id], + _state: &mut Self::State, + message: Box, + _app_state: &mut T, + ) -> xilem_core::MessageResult { + xilem_core::MessageResult::Stale(message) + } +} + +impl ViewMarker for Cow<'static, str> {} + +impl View for Cow<'static, str> { + type State = (); + + type Element = crate::widget::TextWidget; + fn build(&self, cx: &mut Cx) -> (crate::view::Id, Self::State, Self::Element) { let (id, element) = cx.with_new_id(|_| crate::widget::TextWidget::new(self.clone())); (id, (), element) diff --git a/src/widget/text.rs b/src/widget/text.rs index 98563895e..b14b4ba5f 100644 --- a/src/widget/text.rs +++ b/src/widget/text.rs @@ -13,6 +13,7 @@ // limitations under the License. use parley::{FontContext, Layout}; +use std::borrow::Cow; use vello::{ kurbo::{Affine, Size}, peniko::{Brush, Color}, @@ -27,16 +28,16 @@ use super::{ }; pub struct TextWidget { - text: String, + text: Cow<'static, str>, layout: Option>, } impl TextWidget { - pub fn new(text: String) -> TextWidget { + pub fn new(text: Cow<'static, str>) -> TextWidget { TextWidget { text, layout: None } } - pub fn set_text(&mut self, text: String) -> ChangeFlags { + pub fn set_text(&mut self, text: Cow<'static, str>) -> ChangeFlags { self.text = text; ChangeFlags::LAYOUT | ChangeFlags::PAINT }