Skip to content

Commit

Permalink
WIP: Wire Portal through in xilem
Browse files Browse the repository at this point in the history
  • Loading branch information
xorgy committed Aug 28, 2024
1 parent 251f42a commit 8e2dddd
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 30 deletions.
7 changes: 4 additions & 3 deletions masonry/examples/to_do_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ impl AppDriver for Driver {
fn on_action(&mut self, ctx: &mut DriverCtx<'_>, _widget_id: WidgetId, action: Action) {
match action {
Action::ButtonPressed(_) => {
let mut root: WidgetMut<RootWidget<Portal<Flex>>> = ctx.get_root();
let mut root = root.get_element();
let mut flex = root.child_mut();
let mut root: WidgetMut<RootWidget<Portal>> = ctx.get_root();
let mut element = root.get_element();
let mut root_mut = element.child_mut();
let mut flex = root_mut.downcast::<Flex>();
flex.add_child(Label::new(self.next_task.clone()));

let mut first_row = flex.child_mut(0).unwrap();
Expand Down
9 changes: 9 additions & 0 deletions masonry/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,13 @@ pub struct AccessEvent {
pub data: Option<accesskit::ActionData>,
}

#[derive(Debug, Clone)]
pub enum PointerType {
Mouse,
Touch,
Pen,
}

#[derive(Debug, Clone)]
pub struct PointerState {
// TODO
Expand All @@ -228,6 +235,7 @@ pub struct PointerState {
pub count: u8,
pub focus: bool,
pub force: Option<Force>,
pub pointer_type: PointerType,
}

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -487,6 +495,7 @@ impl PointerState {
count: 0,
focus: false,
force: None,
pointer_type: PointerType::Mouse,
}
}
}
Expand Down
61 changes: 43 additions & 18 deletions masonry/src/widget/portal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,21 @@ use crate::{
LifeCycleCtx, PaintCtx, PointerEvent, StatusChange, TextEvent, Widget, WidgetId, WidgetPod,
};

struct FlickGesture {}

// TODO - refactor - see https://github.com/linebender/xilem/issues/366
// TODO - rename "Portal" to "ScrollPortal"?
// Conceptually, a Portal is a Widget giving a restricted view of a child widget
// Imagine a very large widget, and a rect that represents the part of the widget we see
pub struct Portal<W: Widget> {
child: WidgetPod<W>,
pub struct Portal {
child: WidgetPod<Box<dyn Widget>>,
// TODO - differentiate between the "explicit" viewport pos determined
// by user input, and the computed viewport pos that may change based
// on re-layouts
// TODO - rename
viewport_pos: Point,
#[allow(dead_code)]
flick_gesture: Option<FlickGesture>,
// TODO - test how it looks like
constrain_horizontal: bool,
constrain_vertical: bool,
Expand All @@ -39,11 +43,28 @@ pub struct Portal<W: Widget> {
}

// --- MARK: BUILDERS ---
impl<W: Widget> Portal<W> {
pub fn new(child: W) -> Self {
impl Portal {
pub fn new(child: impl Widget) -> Self {
Portal {
child: WidgetPod::new(child).boxed(),
viewport_pos: Point::ORIGIN,
flick_gesture: None,
constrain_horizontal: false,
constrain_vertical: false,
must_fill: false,
// TODO - remove
scrollbar_horizontal: WidgetPod::new(ScrollBar::new(Axis::Horizontal, 1.0, 1.0)),
scrollbar_horizontal_visible: false,
scrollbar_vertical: WidgetPod::new(ScrollBar::new(Axis::Vertical, 1.0, 1.0)),
scrollbar_vertical_visible: false,
}
}

pub fn new_pod(child: WidgetPod<Box<dyn Widget>>) -> Self {
Portal {
child: WidgetPod::new(child),
child,
viewport_pos: Point::ORIGIN,
flick_gesture: None,
constrain_horizontal: false,
constrain_vertical: false,
must_fill: false,
Expand Down Expand Up @@ -124,7 +145,7 @@ fn compute_pan_range(mut viewport: Range<f64>, target: Range<f64>) -> Range<f64>
viewport
}

impl<W: Widget> Portal<W> {
impl Portal {
// TODO - rename
fn set_viewport_pos_raw(&mut self, portal_size: Size, content_size: Size, pos: Point) -> bool {
let viewport_max_pos =
Expand All @@ -144,8 +165,8 @@ impl<W: Widget> Portal<W> {
}

// --- MARK: WIDGETMUT ---
impl<W: Widget> WidgetMut<'_, Portal<W>> {
pub fn child_mut(&mut self) -> WidgetMut<'_, W> {
impl WidgetMut<'_, Portal> {
pub fn child_mut(&mut self) -> WidgetMut<'_, Box<dyn Widget>> {
self.ctx.get_mut(&mut self.widget.child)
}

Expand Down Expand Up @@ -237,9 +258,9 @@ impl<W: Widget> WidgetMut<'_, Portal<W>> {
}

// --- MARK: IMPL WIDGET ---
impl<W: Widget> Widget for Portal<W> {
impl Widget for Portal {
fn on_pointer_event(&mut self, ctx: &mut EventCtx, event: &PointerEvent) {
const SCROLLING_SPEED: f64 = 10.0;
const SCROLLING_SPEED: f64 = 120.0;

let portal_size = ctx.size();
let content_size = ctx.get_raw_ref(&mut self.child).ctx().layout_rect().size();
Expand Down Expand Up @@ -401,7 +422,7 @@ impl<W: Widget> Widget for Portal<W> {
fn accessibility(&mut self, ctx: &mut AccessCtx) {
// TODO - Double check this code
// Not sure about these values
if false {
if true {
ctx.current_node().set_scroll_x(self.viewport_pos.x);
ctx.current_node().set_scroll_y(self.viewport_pos.y);
ctx.current_node().set_scroll_x_min(0.0);
Expand All @@ -420,10 +441,14 @@ impl<W: Widget> Widget for Portal<W> {
}

ctx.current_node().set_clips_children();
ctx.current_node()
.push_child(self.scrollbar_horizontal.id().into());
ctx.current_node()
.push_child(self.scrollbar_vertical.id().into());
// if self.scrollbar_horizontal_visible {
// ctx.current_node()
// .push_child(self.scrollbar_horizontal.id().into());
// }
// if self.scrollbar_vertical_visible {
// ctx.current_node()
// .push_child(self.scrollbar_vertical.id().into());
// }
}

fn children_ids(&self) -> SmallVec<[WidgetId; 16]> {
Expand Down Expand Up @@ -497,23 +522,23 @@ mod tests {
assert_render_snapshot!(harness, "button_list_no_scroll");

harness.edit_root_widget(|mut portal| {
let mut portal = portal.downcast::<Portal<Flex>>();
let mut portal = portal.downcast::<Portal>();
portal.set_viewport_pos(Point::new(0.0, 130.0))
});

assert_render_snapshot!(harness, "button_list_scrolled");

let item_3_rect = harness.get_widget(item_3_id).state().layout_rect();
harness.edit_root_widget(|mut portal| {
let mut portal = portal.downcast::<Portal<Flex>>();
let mut portal = portal.downcast::<Portal>();
portal.pan_viewport_to(item_3_rect);
});

assert_render_snapshot!(harness, "button_list_scroll_to_item_3");

let item_13_rect = harness.get_widget(item_13_id).state().layout_rect();
harness.edit_root_widget(|mut portal| {
let mut portal = portal.downcast::<Portal<Flex>>();
let mut portal = portal.downcast::<Portal>();
portal.pan_viewport_to(item_13_rect);
});

Expand Down
6 changes: 3 additions & 3 deletions xilem/examples/mason.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use std::time::Duration;
use xilem::{
tokio::time,
view::{
button, button_any_pointer, checkbox, flex, label, prose, task, textbox, Axis,
button, button_any_pointer, checkbox, flex, label, portal, prose, task, textbox, Axis,
FlexExt as _, FlexSpacer,
},
Color, EventLoop, EventLoopBuilder, TextAlignment, WidgetView, Xilem,
Expand Down Expand Up @@ -60,7 +60,7 @@ fn app_logic(data: &mut AppData) -> impl WidgetView<AppData> {
});

fork(
flex((
portal(flex((
flex((
label("Label")
.brush(Color::REBECCA_PURPLE)
Expand Down Expand Up @@ -91,7 +91,7 @@ fn app_logic(data: &mut AppData) -> impl WidgetView<AppData> {
button("Decrement", |data: &mut AppData| data.count -= 1),
button("Reset", |data: &mut AppData| data.count = 0),
flex((fizz_buzz_flex_sequence, flex_sequence)).direction(axis),
)),
))),
// The following `task` view only exists whilst the example is in the "active" state, so
// the updates it performs will only be running whilst we are in that state.
data.active.then(|| {
Expand Down
4 changes: 2 additions & 2 deletions xilem/examples/to_do_mvc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// On Windows platform, don't show a console when opening the app.
#![windows_subsystem = "windows"]

use xilem::view::{button, checkbox, flex, textbox, Axis};
use xilem::view::{button, checkbox, flex, portal, textbox, Axis};
use xilem::{EventLoop, WidgetView, Xilem};

struct Task {
Expand Down Expand Up @@ -66,7 +66,7 @@ fn app_logic(task_list: &mut TaskList) -> impl WidgetView<TaskList> {
})
.collect::<Vec<_>>();

flex((first_line, tasks))
portal(flex((first_line, tasks)))
}

fn main() {
Expand Down
9 changes: 5 additions & 4 deletions xilem/examples/variable_clock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ use time::{error::IndeterminateOffset, macros::format_description, OffsetDateTim
use winit::error::EventLoopError;
use xilem::{
view::{
button, flex, label, prose, sized_box, task, variable_label, Axis, FlexExt, FlexSpacer,
button, flex, label, portal, prose, sized_box, task, variable_label, Axis, FlexExt,
FlexSpacer,
},
Color, EventLoop, EventLoopBuilder, WidgetView, Xilem,
};
Expand All @@ -38,14 +39,14 @@ struct TimeZone {
}

fn app_logic(data: &mut Clocks) -> impl WidgetView<Clocks> {
let view = flex((
let view = portal(flex((
// HACK: We add a spacer at the top for Android. See https://github.com/rust-windowing/winit/issues/2308
FlexSpacer::Fixed(40.),
local_time(data),
controls(),
// TODO: When we get responsive layouts, move this into a two-column view.
TIMEZONES.iter().map(|it| it.view(data)).collect::<Vec<_>>(),
));
flex(TIMEZONES.iter().map(|it| it.view(data)).collect::<Vec<_>>()),
)));
fork(
view,
task(
Expand Down
3 changes: 3 additions & 0 deletions xilem/src/view/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ pub use checkbox::*;
mod flex;
pub use flex::*;

mod portal;
pub use portal::*;

mod sized_box;
pub use sized_box::*;

Expand Down
82 changes: 82 additions & 0 deletions xilem/src/view/portal.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright 2024 the Xilem Authors
// SPDX-License-Identifier: Apache-2.0

use std::marker::PhantomData;

use masonry::widget;
use xilem_core::ViewMarker;

use crate::{
core::{Mut, View, ViewId},
Pod, ViewCtx, WidgetView,
};

/// A scrollable widget portal
pub fn portal<State, Action, V>(inner: V) -> Portal<V, State, Action>
where
V: WidgetView<State, Action>,
{
Portal {
inner,
phantom: PhantomData,
}
}

pub struct Portal<V, State, Action = ()> {
inner: V,
phantom: PhantomData<fn() -> (State, Action)>,
}

impl<V, State, Action> Portal<V, State, Action> {}

impl<V, State, Action> ViewMarker for Portal<V, State, Action> {}
impl<V, State, Action> View<State, Action, ViewCtx> for Portal<V, State, Action>
where
State: 'static,
Action: 'static,
V: WidgetView<State, Action>,
{
type Element = Pod<widget::Portal>;
type ViewState = V::ViewState;

fn build(&self, ctx: &mut ViewCtx) -> (Self::Element, Self::ViewState) {
let (child, child_state) = self.inner.build(ctx);
let widget = widget::Portal::new_pod(child.inner.boxed()).content_must_fill(true);
(Pod::new(widget), child_state)
}

fn rebuild<'el>(
&self,
prev: &Self,
view_state: &mut Self::ViewState,
ctx: &mut ViewCtx,
mut element: Mut<'el, Self::Element>,
) -> Mut<'el, Self::Element> {
{
let mut child = element.child_mut();
self.inner
.rebuild(&prev.inner, view_state, ctx, child.downcast());
}
element
}

fn teardown(
&self,
view_state: &mut Self::ViewState,
ctx: &mut ViewCtx,
mut element: Mut<'_, Self::Element>,
) {
let mut child = element.child_mut();
self.inner.teardown(view_state, ctx, child.downcast());
}

fn message(
&self,
view_state: &mut Self::ViewState,
id_path: &[ViewId],
message: xilem_core::DynMessage,
app_state: &mut State,
) -> crate::MessageResult<Action> {
self.inner.message(view_state, id_path, message, app_state)
}
}

0 comments on commit 8e2dddd

Please sign in to comment.