From ae5286e7608c6a1e145bb7441c56d52fddb5a3ab Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Wed, 8 Nov 2023 23:05:33 +0000 Subject: [PATCH] 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..7576058b1 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::Nop } } 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..40e33d0cd 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.trim(), 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); + } }