Skip to content

Commit

Permalink
Enable scroll_view widget
Browse files Browse the repository at this point in the history
  • Loading branch information
nicoburns committed Nov 28, 2023
1 parent 738dfeb commit 7a39d6d
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 73 deletions.
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
29 changes: 14 additions & 15 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,22 @@ 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>,
app_state: &mut T,
) -> EventResult<A> {
) -> MessageResult<A> {
let tl = &id_path[1..];
self.child.event(tl, &mut state.1, event, app_state)
self.child.message(tl, &mut state.1, event, app_state)
}
}
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
143 changes: 87 additions & 56 deletions src/widget/scroll_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,14 @@
//! There's a lot more functionality in the Druid version, including
//! control over scrolling axes, ability to scroll to content, etc.
use druid_shell::{
kurbo::{Affine, Point, Rect, Size, Vec2},
piet::RenderContext,
};
use crate::Axis;
use vello::kurbo::{Affine, Point, Size};
use vello::peniko::Mix;
use vello::SceneBuilder;

use crate::Widget;
use super::{BoxConstraints, Widget};

use super::{
contexts::LifeCycleCx, EventCx, LayoutCx, LifeCycle, PaintCx, Pod, PreparePaintCx, RawEvent,
UpdateCx,
};
use super::{contexts::LifeCycleCx, Event, EventCx, LayoutCx, LifeCycle, PaintCx, Pod, UpdateCx};

pub struct ScrollView {
child: Pod,
Expand All @@ -47,41 +44,26 @@ impl ScrollView {
}
}

// TODO: scroll bars
impl Widget for ScrollView {
fn event(&mut self, cx: &mut EventCx, event: &RawEvent) {
// TODO: scroll wheel + click-drag on scroll bars
let offset = Vec2::new(0.0, self.offset);
let child_event = match event {
RawEvent::MouseDown(mouse_event) => {
let mut mouse_event = mouse_event.clone();
mouse_event.pos += offset;
RawEvent::MouseDown(mouse_event)
}
RawEvent::MouseUp(mouse_event) => {
let mut mouse_event = mouse_event.clone();
mouse_event.pos += offset;
RawEvent::MouseUp(mouse_event)
}
RawEvent::MouseMove(mouse_event) => {
let mut mouse_event = mouse_event.clone();
mouse_event.pos += offset;
RawEvent::MouseMove(mouse_event)
}
RawEvent::MouseWheel(mouse_event) => {
let mut mouse_event = mouse_event.clone();
mouse_event.pos += offset;
RawEvent::MouseWheel(mouse_event)
}
_ => event.clone(),
};
self.child.event(cx, &child_event);
fn event(&mut self, cx: &mut EventCx, event: &Event) {
// Pass event through to child
self.child.event(cx, event);

// Handle scroll wheel events
if !cx.is_handled() {
if let RawEvent::MouseWheel(mouse) = event {
let new_offset = (self.offset + mouse.wheel_delta.y).max(0.0);
if let Event::MouseWheel(mouse) = event {
let max_offset = (self.child.get_size().height - cx.size().height).max(0.0);
let new_offset = (self.offset + mouse.wheel_delta.y).max(0.0).min(max_offset);
if new_offset != self.offset {
self.offset = new_offset;
let new_origin = Point {
x: 0.0,
y: -self.offset,
};
self.child.set_origin(cx.widget_state, new_origin);
cx.set_handled(true);
// TODO: request paint
cx.request_paint();
}
}
}
Expand All @@ -95,28 +77,77 @@ impl Widget for ScrollView {
self.child.update(cx);
}

fn measure(&mut self, cx: &mut LayoutCx) -> (Size, Size) {
let _ = self.child.measure(cx);
(Size::ZERO, Size::new(1e9, 1e9))
fn compute_max_intrinsic(&mut self, axis: Axis, cx: &mut LayoutCx, bc: &BoxConstraints) -> f64 {
match axis {
Axis::Horizontal => {
if bc.min().width.is_sign_negative() {
0.0
} else {
let length =
self.child
.compute_max_intrinsic(axis, cx, &bc.unbound_max_height());
length.min(bc.max().width).max(bc.min().width)
}
}
Axis::Vertical => {
if bc.min().height.is_sign_negative() {
0.0
} else {
let length =
self.child
.compute_max_intrinsic(axis, cx, &bc.unbound_max_height());
length.min(bc.max().height).max(bc.min().height)
}
}
}
}

fn layout(&mut self, cx: &mut LayoutCx, proposed_size: Size) -> Size {
let child_proposed = Size::new(proposed_size.width, 1e9);
let child_size = self.child.layout(cx, child_proposed);
Size::new(child_size.width, proposed_size.height)
fn layout(&mut self, cx: &mut LayoutCx, bc: &BoxConstraints) -> Size {
cx.request_paint();

let cbc = BoxConstraints::new(
Size {
width: 0.0,
height: 0.0,
},
Size {
width: bc.max().width,
height: f64::INFINITY,
},
);
let child_size = self.child.layout(cx, &cbc);
let size = Size {
width: child_size.width.min(bc.max().width),
height: child_size.height.min(bc.max().height),
};

// Ensure that scroll offset is within bounds
let max_offset = (child_size.height - size.height).max(0.0);
if max_offset < self.offset {
self.offset = max_offset;
let new_origin = Point {
x: 0.0,
y: -self.offset,
};
self.child.set_origin(cx.widget_state, new_origin);
}

size
}

fn prepare_paint(&mut self, cx: &mut PreparePaintCx, visible: Rect) {
let child_visible = visible + Vec2::new(0.0, self.offset);
self.child.prepare_paint(cx, child_visible);
fn paint(&mut self, cx: &mut PaintCx, builder: &mut SceneBuilder) {
builder.push_layer(Mix::Normal, 1.0, Affine::IDENTITY, &cx.size().to_rect());
self.child.paint(cx, builder);
builder.pop_layer();
}

fn paint(&mut self, cx: &mut PaintCx) {
cx.with_save(|cx| {
let size = cx.size();
cx.clip(size.to_rect());
cx.transform(Affine::translate((0.0, -self.offset)));
self.child.paint_raw(cx);
});
fn accessibility(&mut self, cx: &mut super::AccessCx) {
self.child.accessibility(cx);

if cx.is_requested() {
let mut builder = accesskit::NodeBuilder::new(accesskit::Role::ScrollView);
builder.set_children(vec![self.child.id().into()]);
cx.push_node(builder);
}
}
}

0 comments on commit 7a39d6d

Please sign in to comment.