mirror of https://github.com/linebender/xilem
TMP
This commit is contained in:
parent
9c397da91f
commit
54f83dbb6c
|
@ -182,7 +182,7 @@ impl From<PointerButton> for PointerButtons {
|
||||||
// TODO - Touchpad, Touch, AxisMotion
|
// TODO - Touchpad, Touch, AxisMotion
|
||||||
// TODO - How to handle CursorEntered?
|
// TODO - How to handle CursorEntered?
|
||||||
// Note to self: Events like "pointerenter", "pointerleave" are handled differently at the Widget level. But that's weird because WidgetPod can distribute them. Need to think about this again.
|
// Note to self: Events like "pointerenter", "pointerleave" are handled differently at the Widget level. But that's weird because WidgetPod can distribute them. Need to think about this again.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum PointerEvent {
|
pub enum PointerEvent {
|
||||||
PointerDown(PointerButton, PointerState),
|
PointerDown(PointerButton, PointerState),
|
||||||
PointerUp(PointerButton, PointerState),
|
PointerUp(PointerButton, PointerState),
|
||||||
|
@ -198,7 +198,7 @@ pub enum PointerEvent {
|
||||||
|
|
||||||
// TODO - Clipboard Paste?
|
// TODO - Clipboard Paste?
|
||||||
// TODO skip is_synthetic=true events
|
// TODO skip is_synthetic=true events
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum TextEvent {
|
pub enum TextEvent {
|
||||||
KeyboardKey(KeyEvent, ModifiersState),
|
KeyboardKey(KeyEvent, ModifiersState),
|
||||||
Ime(Ime),
|
Ime(Ime),
|
||||||
|
@ -207,13 +207,13 @@ pub enum TextEvent {
|
||||||
FocusChange(bool),
|
FocusChange(bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct AccessEvent {
|
pub struct AccessEvent {
|
||||||
pub action: accesskit::Action,
|
pub action: accesskit::Action,
|
||||||
pub data: Option<accesskit::ActionData>,
|
pub data: Option<accesskit::ActionData>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct PointerState {
|
pub struct PointerState {
|
||||||
// TODO
|
// TODO
|
||||||
// pub device_id: DeviceId,
|
// pub device_id: DeviceId,
|
||||||
|
@ -242,7 +242,7 @@ pub enum WindowTheme {
|
||||||
/// may occur during an [`on_event`](crate::Widget::on_event) pass, if some
|
/// may occur during an [`on_event`](crate::Widget::on_event) pass, if some
|
||||||
/// widget has been added then.
|
/// widget has been added then.
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
#[allow(variant_size_differences)]
|
#[allow(variant_size_differences)]
|
||||||
pub enum Update {
|
pub enum Update {
|
||||||
/// Sent to a `Widget` when it is added to the widget tree. This should be
|
/// Sent to a `Widget` when it is added to the widget tree. This should be
|
||||||
|
|
|
@ -64,3 +64,30 @@ pub(crate) fn run_update_anim_pass(root: &mut RenderRoot, elapsed_ns: u64) {
|
||||||
elapsed_ns,
|
elapsed_ns,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- MARK: TESTS ---
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::testing::{Record, Recording, TestHarness, TestWidgetExt as _};
|
||||||
|
use crate::widget::SizedBox;
|
||||||
|
use crate::WidgetId;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_update_anim_pass() {
|
||||||
|
let record = Recording::default();
|
||||||
|
|
||||||
|
let id = WidgetId::next();
|
||||||
|
let widget = SizedBox::new_with_id(SizedBox::empty().record(&record), id);
|
||||||
|
|
||||||
|
let mut harness = TestHarness::create(widget);
|
||||||
|
|
||||||
|
record.clear();
|
||||||
|
|
||||||
|
harness.edit_widget(id, |mut widget| {
|
||||||
|
widget.ctx.request_anim_frame();
|
||||||
|
});
|
||||||
|
harness.animate_ms(20);
|
||||||
|
|
||||||
|
assert_eq!(record.next(), Some(Record::AnimFrame(20_000_000)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -93,3 +93,120 @@ pub(crate) fn run_compose_pass(root: &mut RenderRoot) {
|
||||||
Vec2::ZERO,
|
Vec2::ZERO,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- MARK: TESTS ---
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use smallvec::smallvec;
|
||||||
|
use vello::kurbo::{Point, Size};
|
||||||
|
|
||||||
|
use crate::testing::{
|
||||||
|
widget_ids, ModularWidget, Record, Recording, TestHarness, TestWidgetExt as _,
|
||||||
|
};
|
||||||
|
use crate::widget::SizedBox;
|
||||||
|
use crate::WidgetPod;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_compose_pass() {
|
||||||
|
let record = Recording::default();
|
||||||
|
let [parent_id, recorder_id] = widget_ids();
|
||||||
|
let inner = SizedBox::new_with_id(SizedBox::empty().record(&record), recorder_id);
|
||||||
|
let parent = ModularWidget::new((WidgetPod::new(inner), Point::ZERO, Vec2::ZERO))
|
||||||
|
.layout_fn(|state, ctx, bc| {
|
||||||
|
let (child, pos, _) = state;
|
||||||
|
ctx.run_layout(child, bc);
|
||||||
|
ctx.place_child(child, *pos);
|
||||||
|
Size::ZERO
|
||||||
|
})
|
||||||
|
.compose_fn(|state, ctx| {
|
||||||
|
let (child, _, translation) = state;
|
||||||
|
ctx.set_child_translation(child, *translation);
|
||||||
|
})
|
||||||
|
.register_children_fn(move |state, ctx| {
|
||||||
|
let (child, _, _) = state;
|
||||||
|
ctx.register_child(child);
|
||||||
|
})
|
||||||
|
.children_fn(|(child, _, _)| smallvec![child.id()]);
|
||||||
|
let root = SizedBox::new_with_id(parent, parent_id);
|
||||||
|
|
||||||
|
let mut harness = TestHarness::create(root);
|
||||||
|
record.clear();
|
||||||
|
|
||||||
|
harness.edit_widget(parent_id, |mut widget| {
|
||||||
|
// TODO - Find better way to express this
|
||||||
|
let mut widget = widget.downcast::<ModularWidget<(WidgetPod<SizedBox>, Point, Vec2)>>();
|
||||||
|
widget.widget.state.1 = Point::new(30., 30.);
|
||||||
|
widget.ctx.request_layout();
|
||||||
|
});
|
||||||
|
assert_eq!(
|
||||||
|
record.drain(),
|
||||||
|
vec![
|
||||||
|
Record::Layout(Size::new(400., 400.)),
|
||||||
|
Record::Compose(Point::new(30., 30.)),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
harness.edit_widget(parent_id, |mut widget| {
|
||||||
|
// TODO - Find better way to express this
|
||||||
|
let mut widget = widget.downcast::<ModularWidget<(WidgetPod<SizedBox>, Point, Vec2)>>();
|
||||||
|
widget.widget.state.2 = Vec2::new(8., 8.);
|
||||||
|
widget.ctx.request_compose();
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO - Should changing a parent transform call the child's compose method?
|
||||||
|
assert_eq!(record.drain(), vec![]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_move_text_input() {
|
||||||
|
let record = Recording::default();
|
||||||
|
let [parent_id, recorder_id] = widget_ids();
|
||||||
|
let inner = SizedBox::new_with_id(SizedBox::empty().record(&record), recorder_id);
|
||||||
|
let parent = ModularWidget::new((WidgetPod::new(inner), Point::ZERO, Vec2::ZERO))
|
||||||
|
.layout_fn(|state, ctx, bc| {
|
||||||
|
let (child, pos, _) = state;
|
||||||
|
ctx.run_layout(child, bc);
|
||||||
|
ctx.place_child(child, *pos);
|
||||||
|
Size::ZERO
|
||||||
|
})
|
||||||
|
.compose_fn(|state, ctx| {
|
||||||
|
let (child, _, translation) = state;
|
||||||
|
ctx.set_child_translation(child, *translation);
|
||||||
|
})
|
||||||
|
.register_children_fn(move |state, ctx| {
|
||||||
|
let (child, _, _) = state;
|
||||||
|
ctx.register_child(child);
|
||||||
|
})
|
||||||
|
.children_fn(|(child, _, _)| smallvec![child.id()]);
|
||||||
|
let root = SizedBox::new_with_id(parent, parent_id);
|
||||||
|
|
||||||
|
let mut harness = TestHarness::create(root);
|
||||||
|
record.clear();
|
||||||
|
|
||||||
|
harness.edit_widget(parent_id, |mut widget| {
|
||||||
|
// TODO - Find better way to express this
|
||||||
|
let mut widget = widget.downcast::<ModularWidget<(WidgetPod<SizedBox>, Point, Vec2)>>();
|
||||||
|
widget.widget.state.1 = Point::new(30., 30.);
|
||||||
|
widget.ctx.request_layout();
|
||||||
|
});
|
||||||
|
assert_eq!(
|
||||||
|
record.drain(),
|
||||||
|
vec![
|
||||||
|
Record::Layout(Size::new(400., 400.)),
|
||||||
|
Record::Compose(Point::new(30., 30.)),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
harness.edit_widget(parent_id, |mut widget| {
|
||||||
|
// TODO - Find better way to express this
|
||||||
|
let mut widget = widget.downcast::<ModularWidget<(WidgetPod<SizedBox>, Point, Vec2)>>();
|
||||||
|
widget.widget.state.2 = Vec2::new(8., 8.);
|
||||||
|
widget.ctx.request_compose();
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO - Should changing a parent transform call the child's compose method?
|
||||||
|
assert_eq!(record.drain(), vec![]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ use std::collections::VecDeque;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use accesskit::{NodeBuilder, Role};
|
use accesskit::{NodeBuilder, Role};
|
||||||
use smallvec::SmallVec;
|
use smallvec::{smallvec, SmallVec};
|
||||||
use tracing::trace_span;
|
use tracing::trace_span;
|
||||||
use vello::Scene;
|
use vello::Scene;
|
||||||
use widget::widget::get_child_at_pos;
|
use widget::widget::get_child_at_pos;
|
||||||
|
@ -43,7 +43,7 @@ pub const REPLACE_CHILD: Selector = Selector::new("masonry-test.replace-child");
|
||||||
///
|
///
|
||||||
/// This widget is generic over its state, which is passed in at construction time.
|
/// This widget is generic over its state, which is passed in at construction time.
|
||||||
pub struct ModularWidget<S> {
|
pub struct ModularWidget<S> {
|
||||||
state: S,
|
pub state: S,
|
||||||
accepts_pointer_interaction: bool,
|
accepts_pointer_interaction: bool,
|
||||||
accepts_focus: bool,
|
accepts_focus: bool,
|
||||||
accepts_text_input: bool,
|
accepts_text_input: bool,
|
||||||
|
@ -102,18 +102,18 @@ pub struct Recording(Rc<RefCell<VecDeque<Record>>>);
|
||||||
/// A recording of a method call on a widget.
|
/// A recording of a method call on a widget.
|
||||||
///
|
///
|
||||||
/// Each member of the enum corresponds to one of the methods on `Widget`.
|
/// Each member of the enum corresponds to one of the methods on `Widget`.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Record {
|
pub enum Record {
|
||||||
PE(PointerEvent),
|
PointerEvent(PointerEvent),
|
||||||
TE(TextEvent),
|
TextEvent(TextEvent),
|
||||||
AE(AccessEvent),
|
AccessEvent(AccessEvent),
|
||||||
AF(u64),
|
AnimFrame(u64),
|
||||||
RC,
|
RegisterChildren,
|
||||||
U(Update),
|
Update(Update),
|
||||||
Layout(Size),
|
Layout(Size),
|
||||||
Compose,
|
Compose(Point),
|
||||||
Paint,
|
Paint,
|
||||||
Access,
|
Accessibilty,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// External trait implemented for all widgets.
|
/// External trait implemented for all widgets.
|
||||||
|
@ -161,6 +161,22 @@ impl<S> ModularWidget<S> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<W: Widget> ModularWidget<WidgetPod<W>> {
|
||||||
|
pub fn new_parent(child: W) -> ModularWidget<WidgetPod<W>> {
|
||||||
|
let child = WidgetPod::new(child);
|
||||||
|
ModularWidget::new(child)
|
||||||
|
.register_children_fn(move |child, ctx| {
|
||||||
|
ctx.register_child(child);
|
||||||
|
})
|
||||||
|
.layout_fn(move |child, ctx, bc| {
|
||||||
|
let size = ctx.run_layout(child, bc);
|
||||||
|
ctx.place_child(child, Point::ZERO);
|
||||||
|
size
|
||||||
|
})
|
||||||
|
.children_fn(|child| smallvec![child.id()])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Builder methods.
|
/// Builder methods.
|
||||||
///
|
///
|
||||||
/// Each method takes a flag which is then returned by the matching Widget method.
|
/// Each method takes a flag which is then returned by the matching Widget method.
|
||||||
|
@ -508,32 +524,32 @@ impl Recording {
|
||||||
#[warn(clippy::missing_trait_methods)]
|
#[warn(clippy::missing_trait_methods)]
|
||||||
impl<W: Widget> Widget for Recorder<W> {
|
impl<W: Widget> Widget for Recorder<W> {
|
||||||
fn on_pointer_event(&mut self, ctx: &mut EventCtx, event: &event::PointerEvent) {
|
fn on_pointer_event(&mut self, ctx: &mut EventCtx, event: &event::PointerEvent) {
|
||||||
self.recording.push(Record::PE(event.clone()));
|
self.recording.push(Record::PointerEvent(event.clone()));
|
||||||
self.child.on_pointer_event(ctx, event);
|
self.child.on_pointer_event(ctx, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_text_event(&mut self, ctx: &mut EventCtx, event: &event::TextEvent) {
|
fn on_text_event(&mut self, ctx: &mut EventCtx, event: &event::TextEvent) {
|
||||||
self.recording.push(Record::TE(event.clone()));
|
self.recording.push(Record::TextEvent(event.clone()));
|
||||||
self.child.on_text_event(ctx, event);
|
self.child.on_text_event(ctx, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_access_event(&mut self, ctx: &mut EventCtx, event: &AccessEvent) {
|
fn on_access_event(&mut self, ctx: &mut EventCtx, event: &AccessEvent) {
|
||||||
self.recording.push(Record::AE(event.clone()));
|
self.recording.push(Record::AccessEvent(event.clone()));
|
||||||
self.child.on_access_event(ctx, event);
|
self.child.on_access_event(ctx, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_anim_frame(&mut self, ctx: &mut UpdateCtx, interval: u64) {
|
fn on_anim_frame(&mut self, ctx: &mut UpdateCtx, interval: u64) {
|
||||||
self.recording.push(Record::AF(interval));
|
self.recording.push(Record::AnimFrame(interval));
|
||||||
self.child.on_anim_frame(ctx, interval);
|
self.child.on_anim_frame(ctx, interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_children(&mut self, ctx: &mut RegisterCtx) {
|
fn register_children(&mut self, ctx: &mut RegisterCtx) {
|
||||||
self.recording.push(Record::RC);
|
self.recording.push(Record::RegisterChildren);
|
||||||
self.child.register_children(ctx);
|
self.child.register_children(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, ctx: &mut UpdateCtx, event: &Update) {
|
fn update(&mut self, ctx: &mut UpdateCtx, event: &Update) {
|
||||||
self.recording.push(Record::U(event.clone()));
|
self.recording.push(Record::Update(event.clone()));
|
||||||
self.child.update(ctx, event);
|
self.child.update(ctx, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -544,7 +560,7 @@ impl<W: Widget> Widget for Recorder<W> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compose(&mut self, ctx: &mut ComposeCtx) {
|
fn compose(&mut self, ctx: &mut ComposeCtx) {
|
||||||
self.recording.push(Record::Compose);
|
self.recording.push(Record::Compose(ctx.window_origin()));
|
||||||
self.child.compose(ctx);
|
self.child.compose(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -558,7 +574,7 @@ impl<W: Widget> Widget for Recorder<W> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn accessibility(&mut self, ctx: &mut AccessCtx, node: &mut NodeBuilder) {
|
fn accessibility(&mut self, ctx: &mut AccessCtx, node: &mut NodeBuilder) {
|
||||||
self.recording.push(Record::Access);
|
self.recording.push(Record::Accessibilty);
|
||||||
self.child.accessibility(ctx, node);
|
self.child.accessibility(ctx, node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,24 +1,12 @@
|
||||||
// Copyright 2022 the Xilem Authors
|
// Copyright 2022 the Xilem Authors
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use smallvec::smallvec;
|
|
||||||
|
|
||||||
use crate::testing::{ModularWidget, TestHarness, TestWidgetExt};
|
use crate::testing::{ModularWidget, TestHarness, TestWidgetExt};
|
||||||
use crate::widget::Flex;
|
use crate::widget::Flex;
|
||||||
use crate::{Point, PointerButton, Size, Update, Widget, WidgetId, WidgetPod};
|
use crate::{Point, PointerButton, Size, Update, Widget, WidgetId, WidgetPod};
|
||||||
|
|
||||||
fn make_parent_widget<W: Widget>(child: W) -> ModularWidget<WidgetPod<W>> {
|
fn make_parent_widget<W: Widget>(child: W) -> ModularWidget<WidgetPod<W>> {
|
||||||
let child = WidgetPod::new(child);
|
ModularWidget::new_parent(child)
|
||||||
ModularWidget::new(child)
|
|
||||||
.register_children_fn(move |child, ctx| {
|
|
||||||
ctx.register_child(child);
|
|
||||||
})
|
|
||||||
.layout_fn(move |child, ctx, bc| {
|
|
||||||
let size = ctx.run_layout(child, bc);
|
|
||||||
ctx.place_child(child, Point::ZERO);
|
|
||||||
size
|
|
||||||
})
|
|
||||||
.children_fn(|child| smallvec![child.id()])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(FALSE)]
|
#[cfg(FALSE)]
|
||||||
|
|
|
@ -11,7 +11,7 @@ use crate::*;
|
||||||
fn next_pointer_event(recording: &Recording) -> Option<PointerEvent> {
|
fn next_pointer_event(recording: &Recording) -> Option<PointerEvent> {
|
||||||
while let Some(event) = recording.next() {
|
while let Some(event) = recording.next() {
|
||||||
match event {
|
match event {
|
||||||
Record::PE(event) => {
|
Record::PointerEvent(event) => {
|
||||||
return Some(event);
|
return Some(event);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
@ -27,7 +27,7 @@ fn is_hovered(harness: &TestHarness, id: WidgetId) -> bool {
|
||||||
fn next_hovered_changed(recording: &Recording) -> Option<bool> {
|
fn next_hovered_changed(recording: &Recording) -> Option<bool> {
|
||||||
while let Some(event) = recording.next() {
|
while let Some(event) = recording.next() {
|
||||||
match event {
|
match event {
|
||||||
Record::U(Update::HoveredChanged(hovered)) => return Some(hovered),
|
Record::Update(Update::HoveredChanged(hovered)) => return Some(hovered),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue