use std::cell::Cell;
use std::rc::Rc;
use teksilo_canvas::{Point, Rect, SizeProposal};
use crate::overlay::{DismissBehavior, OverlayId, OverlayPlacement, OverlayRequest};
use crate::test_widgets::{FillWidget, StackWidget};
use crate::widget_builder::{HandlerSet, WidgetBuilder};
use crate::widget_id::WidgetId;
use crate::widget_tree::WidgetTree;
#[derive(Debug)]
struct Row(Vec<WidgetId>);
impl crate::widget::Widget for Row {
fn layout_response(
&self,
proposal: SizeProposal,
_ctx: &crate::widget::LayoutContext,
) -> crate::widget::LayoutResponse {
proposal.resolve(0.0, 0.0).into()
}
fn place_children(
&self,
bounds: Rect,
_proposal: SizeProposal,
children: &mut [crate::widget::WidgetPlacement],
_ctx: &crate::widget::LayoutContext,
) {
let width = bounds.width / children.len().max(1) as f32;
for (i, child) in children.iter_mut().enumerate() {
child.origin = Point::new(bounds.x + width * i as f32, bounds.y);
child.size = teksilo_canvas::Size::new(width, bounds.height);
}
}
fn children(&self) -> Vec<WidgetId> {
self.0.clone()
}
}
#[derive(Debug)]
struct Chrome {
child: Option<WidgetId>,
taps: Rc<Cell<u32>>,
}
impl crate::widget::Widget for Chrome {
fn build(&mut self, ctx: &mut crate::build_context::BuildContext) -> Vec<WidgetId> {
let taps = self.taps.clone();
ctx.apply_self_handlers(
HandlerSet::new()
.window_chrome()
.on_tap(move |_, _| taps.set(taps.get() + 1)),
);
self.child.into_iter().collect()
}
fn layout_response(
&self,
proposal: SizeProposal,
_ctx: &crate::widget::LayoutContext,
) -> crate::widget::LayoutResponse {
proposal.resolve(0.0, 0.0).into()
}
fn place_children(
&self,
bounds: Rect,
_proposal: SizeProposal,
children: &mut [crate::widget::WidgetPlacement],
_ctx: &crate::widget::LayoutContext,
) {
for child in children.iter_mut() {
child.origin = bounds.origin();
child.size = bounds.size();
}
}
fn children(&self) -> Vec<WidgetId> {
self.child.into_iter().collect()
}
}
struct Window {
tree: WidgetTree,
surface: WidgetId,
surface_taps: Rc<Cell<u32>>,
app_control: WidgetId,
app_control_taps: Rc<Cell<u32>>,
close: WidgetId,
close_taps: Rc<Cell<u32>>,
page_control: WidgetId,
page_taps: Rc<Cell<u32>>,
panel: WidgetId,
scrim: WidgetId,
}
impl Window {
fn new() -> Self {
let mut tree = WidgetTree::new();
let counter = || Rc::new(Cell::new(0));
let (surface_taps, app_control_taps, close_taps, page_taps) =
(counter(), counter(), counter(), counter());
let tapped = |taps: &Rc<Cell<u32>>| {
let taps = taps.clone();
move |_: &crate::gesture::TapEvent, _: &mut crate::widget::EventContext| {
taps.set(taps.get() + 1)
}
};
let title = tree.add(FillWidget::new().label("Untitled"));
let surface = tree.add(Chrome {
child: Some(title),
taps: surface_taps.clone(),
});
let app_control = tree.add(FillWidget::new().on_tap(tapped(&app_control_taps)));
let dead_zone = tree.add(
StackWidget::new()
.child(app_control)
.gesture_dead_zone(true),
);
let second_surface = tree.add(Chrome {
child: Some(dead_zone),
taps: Rc::new(Cell::new(0)),
});
let close = tree.add(Chrome {
child: None,
taps: close_taps.clone(),
});
let page_control = tree.add(FillWidget::new().on_tap(tapped(&page_taps)));
tree.add(Row(vec![surface, second_surface, close, page_control]));
let panel = tree.add(FillWidget::new());
tree.set_dormant(panel);
let scrim = tree.add(FillWidget::new());
tree.set_dormant(scrim);
tree.layout(SizeProposal::exact(400.0, 300.0));
Self {
tree,
surface,
surface_taps,
app_control,
app_control_taps,
close,
close_taps,
page_control,
page_taps,
panel,
scrim,
}
}
fn overlay(&mut self, content: WidgetId, placement: OverlayPlacement) -> OverlayId {
self.tree.activate(content);
self.tree.show_overlay(OverlayRequest::new(
content,
self.page_control,
placement,
DismissBehavior::Manual,
))
}
fn open_modal(&mut self) {
let scrim = self.overlay(self.scrim, OverlayPlacement::FullViewport);
let modal = self.overlay(self.panel, OverlayPlacement::Centered);
self.tree
.overlay_manager_mut()
.set_parent_overlay(scrim, Some(modal));
self.tree.layout(SizeProposal::exact(400.0, 300.0));
}
fn centre(&self, id: WidgetId) -> Point {
self.tree.bounds(id).center()
}
}
#[test]
fn a_window_button_takes_a_press_through_a_modal_scrim() {
let mut window = Window::new();
window.open_modal();
assert_eq!(
window.tree.hit_test(window.centre(window.close)),
Some(window.close)
);
window.tree.click(window.close);
assert_eq!(window.close_taps.get(), 1, "the window button must work");
}
#[test]
fn the_page_stays_behind_the_scrim() {
let mut window = Window::new();
window.open_modal();
assert_eq!(
window.tree.hit_test(window.centre(window.page_control)),
Some(window.scrim)
);
window.tree.click(window.page_control);
assert_eq!(window.page_taps.get(), 0);
}
#[test]
fn app_content_on_a_drag_surface_is_the_surface() {
let mut window = Window::new();
window.open_modal();
assert_eq!(
window.tree.hit_test(window.centre(window.surface)),
Some(window.surface)
);
window.tree.click(window.surface);
assert_eq!(window.surface_taps.get(), 1);
}
#[test]
fn an_app_control_in_the_title_bar_stays_blocked() {
let mut window = Window::new();
window.open_modal();
assert_eq!(
window.tree.hit_test(window.centre(window.app_control)),
Some(window.scrim)
);
window.tree.click(window.app_control);
assert_eq!(window.app_control_taps.get(), 0);
}
#[test]
fn a_modal_without_a_scrim_leaves_the_chrome_live() {
let mut window = Window::new();
window.overlay(window.panel, OverlayPlacement::Centered);
window.tree.layout(SizeProposal::exact(400.0, 300.0));
assert_eq!(
window.tree.hit_test(window.centre(window.close)),
Some(window.close)
);
assert_eq!(
window.tree.hit_test(window.centre(window.page_control)),
None
);
assert_eq!(
window.tree.hit_test(window.centre(window.app_control)),
None
);
}
#[test]
fn an_ordinary_full_window_layer_covers_the_chrome() {
let mut window = Window::new();
window.overlay(window.scrim, OverlayPlacement::FullViewport);
window.tree.layout(SizeProposal::exact(400.0, 300.0));
assert_eq!(
window.tree.hit_test(window.centre(window.close)),
Some(window.scrim)
);
}
#[test]
fn the_scrim_is_not_over_the_chrome() {
let mut window = Window::new();
window.open_modal();
let full = Rect::new(0.0, 0.0, 400.0, 300.0);
let rects = window.tree.overlay_rects_for_view();
assert!(rects.all.contains(&full), "a web view still stands down");
assert!(!rects.over_chrome.contains(&full));
}
#[test]
fn a_pass_through_layer_covers_only_what_takes_a_press() {
let mut window = Window::new();
let handle = window.tree.add(FillWidget::new());
let hidden = window.tree.add(FillWidget::new());
let layer = window
.tree
.add(Row(vec![handle, hidden]).event_pass_through(true));
window.tree.set_dormant(layer);
window.overlay(layer, OverlayPlacement::FullViewport);
window.tree.set_dormant(hidden);
window.tree.layout(SizeProposal::exact(400.0, 300.0));
let handle_bounds = window.tree.bounds(handle);
assert!(handle_bounds.width > 0.0 && handle_bounds.height > 0.0);
let rects = window.tree.overlay_rects_for_view();
assert_eq!(rects.over_chrome, vec![handle_bounds]);
assert_eq!(rects.all, vec![handle_bounds]);
window.tree.set_dormant(handle);
window.tree.layout(SizeProposal::exact(400.0, 300.0));
let rects = window.tree.overlay_rects_for_view();
assert_eq!(
rects.over_chrome,
Vec::<Rect>::new(),
"an empty layer covers nothing"
);
assert_eq!(rects.all, Vec::<Rect>::new());
}