Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement scroll_view widget #146

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 66 additions & 63 deletions examples/taffy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ fn app_logic(state: &mut AppState) -> impl View<AppState> {
use taffy::style::{AlignItems, FlexWrap, JustifyContent};
use taffy::style_helpers::length;
use vello::peniko::Color;
use xilem::view::{button, div, flex_column, flex_row};
use xilem::view::{button, div, flex_column, flex_row, scroll_view};

const COLORS: [Color; 4] = [
Color::LIGHT_GREEN,
Expand All @@ -45,73 +45,76 @@ fn app_logic(state: &mut AppState) -> impl View<AppState> {
.with_background_color(Color::RED)
.with_style(|s| s.padding = length(20.0)),

// Body
flex_column((
scroll_view(

// Counter control buttons
flex_row((
label,
button("increase", |state: &mut AppState| {
println!("clicked increase");
state.count += 1;
}),
button("decrease", |state: &mut AppState| {
println!("clicked decrease");
if state.count > 0 {
state.count -= 1;
}
}),
button("reset", |state: &mut AppState| {
println!("clicked reset");
state.count = 1;
}),
))
.with_background_color(Color::BLUE_VIOLET)
.with_style(|s| {
s.gap.width = length(20.0);
s.padding = length(20.0);
s.justify_content = Some(JustifyContent::Start);
s.align_items = Some(AlignItems::Center);
}),

// Description text
div(String::from("The number of squares below is controlled by the counter above.\n\nTry clicking \"increase\" until the square count increases enough that the view becomes scrollable."))
.with_background_color(Color::RED)
.with_style(|s| s.padding = length(20.0)),

// Lorem Ipsum text
div(String::from("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."))
.with_background_color(Color::RED)
.with_style(|s| s.padding = length(20.0)),

// Wrapping container (number of children controlled by counter)
flex_row(
(0..state.count).map(|i| {
div(())
.with_background_color(COLORS[(i % 4) as usize])
.with_style(|s| {
s.size.width = length(200.0);
s.size.height = length(200.0);
})
}).collect::<Vec<_>>()
)
.with_background_color(Color::FOREST_GREEN)
// Body
flex_column((

// Counter control buttons
flex_row((
label,
button("increase", |state: &mut AppState| {
println!("clicked increase");
state.count += 1;
}),
button("decrease", |state: &mut AppState| {
println!("clicked decrease");
if state.count > 0 {
state.count -= 1;
}
}),
button("reset", |state: &mut AppState| {
println!("clicked reset");
state.count = 1;
}),
))
.with_background_color(Color::BLUE_VIOLET)
.with_style(|s| {
s.flex_grow = 1.0;
s.flex_wrap = FlexWrap::Wrap;
s.gap = length(20.0);
s.gap.width = length(20.0);
s.padding = length(20.0);
s.justify_content = Some(JustifyContent::Start);
s.align_items = Some(AlignItems::Center);
}),

))
.with_style(|s| {
s.gap.height = length(20.0);
s.padding.left = length(20.0);
s.padding.right = length(20.0);
s.padding.top = length(20.0);
s.padding.bottom = length(20.0);
})
.with_background_color(Color::WHITE)
// Description text
div(String::from("The number of squares below is controlled by the counter above.\n\nTry clicking \"increase\" until the square count increases enough that the view becomes scrollable."))
.with_background_color(Color::RED)
.with_style(|s| s.padding = length(20.0)),

// Lorem Ipsum text
div(String::from("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."))
.with_background_color(Color::RED)
.with_style(|s| s.padding = length(20.0)),

// Wrapping container (number of children controlled by counter)
flex_row(
(0..state.count).map(|i| {
div(())
.with_background_color(COLORS[(i % 4) as usize])
.with_style(|s| {
s.size.width = length(200.0);
s.size.height = length(200.0);
})
}).collect::<Vec<_>>()
)
.with_background_color(Color::FOREST_GREEN)
.with_style(|s| {
s.flex_grow = 1.0;
s.flex_wrap = FlexWrap::Wrap;
s.gap = length(20.0);
s.padding = length(20.0);
}),

))
.with_style(|s| {
s.gap.height = length(20.0);
s.padding.left = length(20.0);
s.padding.right = length(20.0);
s.padding.top = length(20.0);
s.padding.bottom = length(20.0);
})
.with_background_color(Color::WHITE)
)
)).with_style(|s| {
s.padding.left = length(20.0);
s.padding.right = length(20.0);
Expand Down
2 changes: 1 addition & 1 deletion src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ where
let mut layout_cx = LayoutCx::new(&mut cx_state, &mut self.root_state);
let bc = BoxConstraints::tight(self.size);
root_pod.layout(&mut layout_cx, &bc);
root_pod.set_origin(&mut layout_cx, Point::ORIGIN);
root_pod.set_origin(layout_cx.widget_state, Point::ORIGIN);
}
if root_pod
.state
Expand Down
3 changes: 2 additions & 1 deletion src/view/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
mod button;
// mod layout_observer;
// mod list;
// mod scroll_view;
mod scroll_view;
mod text;
// mod use_state;
mod linear_layout;
Expand All @@ -30,6 +30,7 @@ pub use xilem_core::{Id, IdPath, VecSplice};
pub use button::button;
pub use linear_layout::{h_stack, v_stack, LinearLayout};
pub use list::{list, List};
pub use scroll_view::{scroll_view, ScrollView};
pub use switch::switch;
pub use view::{Adapt, AdaptState, Cx, Memoize, View, ViewMarker, ViewSequence};

Expand Down
38 changes: 21 additions & 17 deletions src/view/scroll_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@

use std::{any::Any, marker::PhantomData};

use crate::{event::EventResult, id::Id, View};
use crate::{view::View, widget::ChangeFlags, MessageResult};

use super::Cx;
use xilem_core::Id;

use super::{Cx, ViewMarker, ViewSequence};

pub struct ScrollView<T, A, C> {
child: C,
Expand All @@ -36,18 +38,20 @@ impl<T, A, C> ScrollView<T, A, C> {
}
}

impl<T, A, VT: ViewSequence<T, A>> ViewMarker for ScrollView<T, A, VT> {}

impl<T, A, C: View<T, A>> View<T, A> for ScrollView<T, A, C>
where
C::Element: 'static,
{
type State = (Id, C::State);

type Element = crate::widget::scroll_view::ScrollView;
type Element = crate::widget::ScrollView;

fn build(&self, cx: &mut Cx) -> (Id, Self::State, Self::Element) {
let (id, (child_id, child_state, child_element)) =
cx.with_new_id(|cx| self.child.build(cx));
let element = crate::widget::scroll_view::ScrollView::new(child_element);
let element = crate::widget::ScrollView::new(child_element);
(id, (child_id, child_state), element)
}

Expand All @@ -58,27 +62,27 @@ where
id: &mut Id,
state: &mut Self::State,
element: &mut Self::Element,
) -> bool {
) -> ChangeFlags {
cx.with_id(*id, |cx| {
let child_element = element.child_mut().downcast_mut().unwrap();
let changed =
self.child
.rebuild(cx, &prev.child, &mut state.0, &mut state.1, child_element);
if changed {
element.child_mut().request_update();
}
changed
self.child
.rebuild(cx, &prev.child, &mut state.0, &mut state.1, child_element)
})
}

fn event(
fn message(
&self,
id_path: &[Id],
state: &mut Self::State,
event: Box<dyn Any>,
message: Box<dyn Any>,
app_state: &mut T,
) -> EventResult<A> {
let tl = &id_path[1..];
self.child.event(tl, &mut state.1, event, app_state)
) -> MessageResult<A> {
match id_path {
[child_id, rest_path @ ..] if *child_id == state.0 => {
self.child
.message(rest_path, &mut state.1, message, app_state)
}
_ => MessageResult::Stale(message),
}
}
}
13 changes: 9 additions & 4 deletions src/widget/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ pub struct Pod {
}

#[derive(Debug)]
pub(crate) struct WidgetState {
pub struct WidgetState {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, why this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is to allow set_origin to be called with multiple different context types. As set_origin is public, and now takes WidgetState as a parameter, WidgetState also needs to be public.

pub(crate) id: Id,
pub(crate) flags: PodFlags,
/// The origin of the child in the parent's coordinate space.
Expand Down Expand Up @@ -486,18 +486,23 @@ impl Pod {
/// The widget container can also call `set_origin` from other context, but calling `set_origin`
/// after the widget received [`LifeCycle::ViewContextChanged`] and before the next event results
/// in an inconsistent state of the widget tree.
pub fn set_origin(&mut self, cx: &mut LayoutCx, origin: Point) {
pub fn set_origin(&mut self, parent_state: &mut WidgetState, origin: Point) {
if origin != self.state.origin {
self.state.origin = origin;
// request paint is called on the parent instead of this widget, since this widget's
// fragment does not change.
cx.view_context_changed();
cx.request_paint();
parent_state.flags |= PodFlags::REQUEST_PAINT;
parent_state.flags |= PodFlags::VIEW_CONTEXT_CHANGED;

self.state.flags.insert(PodFlags::VIEW_CONTEXT_CHANGED);
}
}

/// Get the widgets size (as returned by the layout method)
pub fn get_size(&mut self) -> Size {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should omit get_ prefixes to stay consistent

Suggested change
pub fn get_size(&mut self) -> Size {
pub fn size(&mut self) -> Size {

self.state.size
}

// Return true if hot state has changed
fn set_hot_state(
widget: &mut dyn AnyWidget,
Expand Down
2 changes: 1 addition & 1 deletion src/widget/linear_layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ impl Widget for LinearLayout {

for (index, child) in self.children.iter_mut().enumerate() {
let size = child.layout(cx, &child_bc);
child.set_origin(cx, self.axis.pack(major_used, 0.0));
child.set_origin(cx.widget_state, self.axis.pack(major_used, 0.0));
major_used += self.axis.major(size);
if index < child_count - 1 {
major_used += self.spacing;
Expand Down
3 changes: 2 additions & 1 deletion src/widget/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ mod core;
mod linear_layout;
pub mod piet_scene_helpers;
mod raw_event;
mod scroll_view;
mod switch;
//mod scroll_view;
mod text;
#[allow(clippy::module_inception)]
mod widget;
Expand All @@ -34,6 +34,7 @@ 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 scroll_view::ScrollView;
pub use switch::Switch;
pub use text::TextWidget;
pub use widget::{AnyWidget, Widget};
Expand Down
Loading