Skip to content

Commit

Permalink
Enable Text widget (#143)
Browse files Browse the repository at this point in the history
* Fix and enable text widget

* Add more String views (`&'static str`, `Cow<'static, str>`)

---------

Co-authored-by: Philipp Mildenberger <[email protected]>
  • Loading branch information
nicoburns and Philipp-M authored Nov 9, 2023
1 parent 71d1db0 commit 1656532
Show file tree
Hide file tree
Showing 7 changed files with 175 additions and 53 deletions.
1 change: 1 addition & 0 deletions examples/hello.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ fn app_logic(data: &mut i32) -> impl View<i32> {
*data += 1;
}),
h_stack((
"Buttons: ",
button("decrease", |data| {
println!("clicked decrease");
*data -= 1;
Expand Down
5 changes: 3 additions & 2 deletions src/view/button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down
2 changes: 1 addition & 1 deletion src/view/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
102 changes: 91 additions & 11 deletions src/view/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,107 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use std::any::Any;
use std::borrow::Cow;

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<T, A> View<T, A> for String {
type State = ();

type Element = crate::widget::text::TextWidget;
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<dyn std::any::Any>,
_app_state: &mut T,
) -> xilem_core::MessageResult<A> {
xilem_core::MessageResult::Stale(message)
}
}

impl ViewMarker for &'static str {}

impl<T, A> View<T, A> 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<dyn std::any::Any>,
_app_state: &mut T,
) -> xilem_core::MessageResult<A> {
xilem_core::MessageResult::Stale(message)
}
}

impl ViewMarker for Cow<'static, str> {}

impl<T, A> View<T, A> for Cow<'static, str> {
type State = ();

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)
}

fn rebuild(
&self,
_cx: &mut Cx,
prev: &Self,
_id: &mut crate::id::Id,
_id: &mut Id,
_state: &mut Self::State,
element: &mut Self::Element,
) -> ChangeFlags {
Expand All @@ -43,13 +123,13 @@ impl<T, A> View<T, A> for String {
}
}

fn event(
fn message(
&self,
_id_path: &[crate::id::Id],
_id_path: &[xilem_core::Id],
_state: &mut Self::State,
_event: Box<dyn Any>,
message: Box<dyn std::any::Any>,
_app_state: &mut T,
) -> MessageResult<A> {
MessageResult::Stale
) -> xilem_core::MessageResult<A> {
xilem_core::MessageResult::Stale(message)
}
}
2 changes: 1 addition & 1 deletion src/widget/contexts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<'_, '_>, {
Expand Down
3 changes: 2 additions & 1 deletion src/widget/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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};
113 changes: 76 additions & 37 deletions src/widget/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,44 +12,82 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use parley::Layout;
use parley::{FontContext, Layout};
use std::borrow::Cow;
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,
text: Cow<'static, str>,
layout: Option<Layout<ParleyBrush>>,
is_wrapped: bool,
}

impl TextWidget {
pub fn new(text: String) -> TextWidget {
TextWidget {
text,
is_wrapped: false,
layout: None,
}
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
}

fn get_layout_mut(&mut self, font_cx: &mut FontContext) -> &mut Layout<ParleyBrush> {
// 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) {}

Expand All @@ -59,32 +97,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);
}
}

0 comments on commit 1656532

Please sign in to comment.