mod clipboard;
mod contacts;
mod cursor;
mod file_drop;
mod focus_ime;
mod focus_traversal;
mod hover;
mod lifecycle;
mod overlay;
mod platform_views;
mod scale;
mod selection_toolbar;
mod semantics;
mod shortcuts;
use super::*;
use std::cell::{Cell, RefCell};
use std::rc::Rc;
use kurbo::{Point, Rect, Size};
use crate::anim::FrameTime;
use crate::event::{
CursorIcon, EditCommand, EventCtx, EventOutcome, EventResult, FileDropEvent, FileDropPhase,
ImeContentType, ImeState, InputEvent, Key, KeyEvent, Modifiers, NamedKey, OverlayEventKind,
PointerButton, PointerEvent, PointerId, PointerPhase, ScrollDelta,
};
use crate::layout::BoxConstraints;
use crate::overlay::{OutsideTap, OverlayEntry, OverlayInput, OverlayKey};
use crate::semantics::{ROOT_NODE_ID, SemanticsCtx, SemanticsUpdate};
use crate::view::{BuildCtx, ChangeFlags, View};
use crate::widget::{LayoutCtx, PaintCtx, PaintScene, PlatformViewFrame};
#[derive(Default)]
pub(super) struct AppState {
label: String,
}
pub(super) struct TextWidget {
text: String,
}
impl crate::widget::Widget for TextWidget {
fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
let intrinsic = Size::new(self.text.len() as f64 * 8.0, 16.0);
bc.constrain(intrinsic)
}
fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut dyn PaintScene) {
scene.draw_text(ctx.origin(), &self.text);
}
}
pub(super) struct MockTextView {
text: String,
}
impl View<AppState> for MockTextView {
type Element = TextWidget;
fn build(&self, _ctx: &mut BuildCtx<'_>) -> Self::Element {
TextWidget {
text: self.text.clone(),
}
}
fn rebuild(
&self,
prev: &Self,
element: &mut Self::Element,
_ctx: &mut BuildCtx<'_>,
) -> ChangeFlags {
if prev.text != self.text {
element.text = self.text.clone();
ChangeFlags::PAINT
} else {
ChangeFlags::NONE
}
}
}
#[derive(Default)]
pub(super) struct RecordingScene {
texts: Vec<(Point, String)>,
}
impl PaintScene for RecordingScene {
fn fill_rect(&mut self, _origin: Point, _size: Size, _color: peniko::Color) {}
fn draw_text(&mut self, origin: Point, text: &str) {
self.texts.push((origin, text.to_string()));
}
fn draw_scene_texture(&mut self, _id: u64, _dest: Rect) {}
}
pub(super) fn build(state: &mut AppState) -> MockTextView {
MockTextView {
text: state.label.clone(),
}
}
#[derive(Default)]
pub(super) struct ClickState {
clicks: u32,
}
pub(super) fn pointer(phase: PointerPhase, x: f64, y: f64) -> InputEvent {
InputEvent::Pointer(crate::event::PointerEvent {
phase,
position: Point::new(x, y),
button: crate::event::PointerButton::Primary,
})
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum Leaf {
Top,
Bottom,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum OwnerReaction {
Nothing,
Capture,
Focus,
}
#[derive(Clone, Debug, PartialEq)]
pub(super) struct SurfaceSpec {
key: OverlayKey,
label: &'static str,
rect: Rect,
band: crate::overlay::OverlayBand,
input: OverlayInput,
outside_tap: OutsideTap,
register: bool,
on_down: OwnerReaction,
on_move: OwnerReaction,
on_up: OwnerReaction,
pod_claims_focus: bool,
pod_publishes_ime: bool,
pod_publishes_ime_on_event: bool,
}
impl SurfaceSpec {
fn floating(label: &'static str, rect: Rect) -> Self {
Self {
key: OverlayKey::next(),
label,
rect,
band: crate::overlay::OverlayBand::Floating,
input: OverlayInput::Interactive,
outside_tap: OutsideTap::Ignore,
register: true,
on_down: OwnerReaction::Nothing,
on_move: OwnerReaction::Nothing,
on_up: OwnerReaction::Nothing,
pod_claims_focus: false,
pod_publishes_ime: false,
pod_publishes_ime_on_event: false,
}
}
}
#[derive(Default)]
pub(super) struct OverlayLog {
owner: Vec<InputEvent>,
pod: Vec<(&'static str, InputEvent)>,
sibling: Vec<InputEvent>,
sibling_focus: Vec<bool>,
pod_paint_focus: Vec<bool>,
sibling_paint_focus: Vec<bool>,
child_window_size: Option<Size>,
}
pub(super) type Log = std::rc::Rc<std::cell::RefCell<OverlayLog>>;
pub(super) struct OverlayContentWidget {
label: &'static str,
log: Log,
claims_focus: bool,
publishes_ime: bool,
publishes_ime_on_event: bool,
}
impl OverlayContentWidget {
fn surface() -> ImeState {
ImeState {
active: true,
content_type: ImeContentType::Normal,
..Default::default()
}
}
}
impl crate::widget::Widget for OverlayContentWidget {
fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
bc.constrain(Size::new(80.0, 30.0))
}
fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut dyn PaintScene) {
scene.draw_text(ctx.origin(), self.label);
self.log.borrow_mut().pod_paint_focus.push(ctx.has_focus());
if self.publishes_ime {
ctx.publish_ime_state(Self::surface());
}
}
fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
self.log.borrow_mut().pod.push((self.label, event.clone()));
if self.publishes_ime_on_event {
ctx.publish_ime_state(Self::surface());
}
if self.claims_focus
&& let InputEvent::Pointer(pointer) = event
&& pointer.phase == PointerPhase::Down
{
ctx.request_focus();
}
EventResult::Handled
}
}
pub(super) struct OwnedSurface {
spec: SurfaceSpec,
pod: crate::overlay::OverlayPod,
}
pub(super) struct OverlayOwnerWidget {
surfaces: Vec<OwnedSurface>,
log: Log,
}
impl OverlayOwnerWidget {
fn new(specs: &[SurfaceSpec], log: Log) -> Self {
let mut owner = Self {
surfaces: Vec::new(),
log,
};
owner.sync(specs);
owner
}
fn sync(&mut self, specs: &[SurfaceSpec]) {
self.surfaces
.retain(|s| specs.iter().any(|n| n.key == s.spec.key));
for spec in specs {
match self.surfaces.iter_mut().find(|s| s.spec.key == spec.key) {
Some(live) => live.spec = spec.clone(),
None => {
let log = std::rc::Rc::clone(&self.log);
self.surfaces.push(OwnedSurface {
spec: spec.clone(),
pod: std::rc::Rc::new(std::cell::RefCell::new(
crate::widget::ChildPod::new(Box::new(OverlayContentWidget {
label: spec.label,
log,
claims_focus: spec.pod_claims_focus,
publishes_ime: spec.pod_publishes_ime,
publishes_ime_on_event: spec.pod_publishes_ime_on_event,
})),
)),
});
}
}
}
}
}
impl crate::widget::Widget for OverlayOwnerWidget {
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
self.log.borrow_mut().child_window_size = Some(ctx.window_size());
let window = BoxConstraints::loose(ctx.window_size());
for surface in &mut self.surfaces {
surface.pod.borrow_mut().layout_child(ctx, &window);
}
bc.constrain(Size::new(40.0, 20.0))
}
fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut dyn PaintScene) {
scene.draw_text(ctx.origin(), "owner");
for surface in &self.surfaces {
if !surface.spec.register {
continue;
}
ctx.register_overlay(OverlayEntry {
key: surface.spec.key,
band: surface.spec.band,
input: surface.spec.input,
outside_tap: surface.spec.outside_tap,
keys: crate::overlay::OverlayKeys::Pass,
window_rect: surface.spec.rect,
pod: std::rc::Rc::clone(&surface.pod),
insets: ctx.window_insets(),
});
}
}
fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
let InputEvent::Overlay(overlay) = event else {
self.log.borrow_mut().owner.push(event.clone());
return EventResult::Ignored;
};
let Some(surface) = self.surfaces.iter_mut().find(|s| s.spec.key == overlay.key) else {
return EventResult::Ignored;
};
self.log.borrow_mut().owner.push(event.clone());
match &overlay.kind {
OverlayEventKind::Pointer(pointer) => {
let reaction = match pointer.phase {
PointerPhase::Down => Some(surface.spec.on_down),
PointerPhase::Move => Some(surface.spec.on_move),
PointerPhase::Up => Some(surface.spec.on_up),
_ => None,
};
match reaction {
Some(OwnerReaction::Capture) => ctx.capture_pointer(),
Some(OwnerReaction::Focus) => ctx.request_focus(),
Some(OwnerReaction::Nothing) | None => {}
}
let local = InputEvent::Pointer(PointerEvent {
position: pointer.position - surface.spec.rect.origin().to_vec2(),
..*pointer
});
surface.pod.borrow_mut().event_child(ctx, &local);
}
OverlayEventKind::Scroll { position, delta } => {
let local = InputEvent::Scroll {
position: *position - surface.spec.rect.origin().to_vec2(),
delta: *delta,
};
surface.pod.borrow_mut().event_child(ctx, &local);
}
OverlayEventKind::Scale {
focal,
phase,
scale_delta,
velocity,
} => {
let local = InputEvent::Scale(crate::event::ScaleEvent {
phase: *phase,
scale_delta: *scale_delta,
focal: *focal - surface.spec.rect.origin().to_vec2(),
velocity: *velocity,
});
surface.pod.borrow_mut().event_child(ctx, &local);
}
OverlayEventKind::OutsideDown => {}
OverlayEventKind::Shortcut(id) => {
surface
.pod
.borrow_mut()
.event_child(ctx, &InputEvent::Shortcut(*id));
}
OverlayEventKind::Key(key) => {
surface
.pod
.borrow_mut()
.event_child(ctx, &InputEvent::Key(key.clone()));
}
}
EventResult::Ignored
}
}
pub(super) struct SiblingLeafWidget {
log: Log,
publish: Option<crate::selection_toolbar::SelectionToolbarRequest>,
}
impl SiblingLeafWidget {
fn surface() -> ImeState {
ImeState {
active: true,
content_type: ImeContentType::Password,
..Default::default()
}
}
}
impl crate::widget::Widget for SiblingLeafWidget {
fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
bc.constrain(Size::new(60.0, 40.0))
}
fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut dyn PaintScene) {
scene.draw_text(ctx.origin(), "sibling");
self.log
.borrow_mut()
.sibling_paint_focus
.push(ctx.has_focus());
if ctx.has_focus() {
ctx.publish_ime_state(Self::surface());
}
if let Some(request) = self.publish {
ctx.publish_selection_toolbar(request);
}
}
fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
{
let mut log = self.log.borrow_mut();
log.sibling.push(event.clone());
log.sibling_focus.push(ctx.has_focus());
}
match event {
InputEvent::Pointer(pointer) if pointer.phase == PointerPhase::Down => {
ctx.request_focus();
ctx.publish_ime_state(Self::surface());
EventResult::Handled
}
InputEvent::Scroll { .. } => {
ctx.request_focus();
ctx.publish_ime_state(Self::surface());
EventResult::Handled
}
_ => EventResult::Ignored,
}
}
}
pub(super) struct OverlayRootWidget {
owner: crate::widget::ChildPod,
sibling: crate::widget::ChildPod,
}
impl crate::widget::Widget for OverlayRootWidget {
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
self.owner.layout_child(ctx, bc);
self.owner.set_origin(Point::new(0.0, 0.0));
self.sibling.layout_child(ctx, bc);
self.sibling.set_origin(Point::new(0.0, 100.0));
bc.constrain(Size::new(200.0, 200.0))
}
fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut dyn PaintScene) {
self.owner.paint_child(ctx, scene);
self.sibling.paint_child(ctx, scene);
}
fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
if event.is_broadcast() {
self.owner.event_child(ctx, event);
self.sibling.event_child(ctx, event);
return EventResult::Ignored;
}
let ends = matches!(
event,
InputEvent::Pointer(p)
if matches!(p.phase, PointerPhase::Up | PointerPhase::Cancel)
);
for (pod, _) in [(&mut self.owner, 0), (&mut self.sibling, 1)] {
if pod.is_active() {
let result = pod.event_child(ctx, event);
if ends {
pod.set_active(false);
}
return result;
}
}
if event.is_focus_routed() {
if self.sibling.holds_live_focus() {
return self.sibling.event_child(ctx, event);
}
if self.owner.holds_live_focus() {
return self.owner.event_child(ctx, event);
}
return EventResult::Ignored;
}
let position = event.position();
if self.sibling.contains(position) {
return self.sibling.event_child(ctx, event);
}
if self.owner.contains(position) {
return self.owner.event_child(ctx, event);
}
EventResult::Ignored
}
}
pub(super) struct OverlayRootView {
specs: Vec<SurfaceSpec>,
publish: Option<crate::selection_toolbar::SelectionToolbarRequest>,
log: Log,
}
impl View<OverlayState> for OverlayRootView {
type Element = OverlayRootWidget;
fn build(&self, _ctx: &mut BuildCtx<'_>) -> Self::Element {
OverlayRootWidget {
owner: crate::widget::ChildPod::new(Box::new(OverlayOwnerWidget::new(
&self.specs,
std::rc::Rc::clone(&self.log),
))),
sibling: crate::widget::ChildPod::new(Box::new(SiblingLeafWidget {
log: std::rc::Rc::clone(&self.log),
publish: self.publish,
})),
}
}
fn rebuild(
&self,
prev: &Self,
element: &mut Self::Element,
_ctx: &mut BuildCtx<'_>,
) -> ChangeFlags {
element
.owner
.widget_mut()
.downcast_mut::<OverlayOwnerWidget>()
.expect("the owner keeps its type")
.sync(&self.specs);
element
.sibling
.widget_mut()
.downcast_mut::<SiblingLeafWidget>()
.expect("the sibling keeps its type")
.publish = self.publish;
if prev.specs != self.specs || prev.publish != self.publish {
ChangeFlags::PAINT
} else {
ChangeFlags::NONE
}
}
}
pub(super) struct OverlayState {
specs: Vec<SurfaceSpec>,
publish: Option<crate::selection_toolbar::SelectionToolbarRequest>,
log: Log,
}
pub(super) fn overlay_logic(state: &mut OverlayState) -> OverlayRootView {
OverlayRootView {
specs: state.specs.clone(),
publish: state.publish,
log: std::rc::Rc::clone(&state.log),
}
}
pub(super) struct OverlayHarness {
root: RenderRoot<OverlayState, OverlayRootView>,
state: OverlayState,
log: Log,
}
impl OverlayHarness {
fn new(specs: Vec<SurfaceSpec>) -> Self {
let log: Log = std::rc::Rc::new(std::cell::RefCell::new(OverlayLog::default()));
let mut harness = Self {
root: RenderRoot::new(),
state: OverlayState {
specs,
publish: None,
log: std::rc::Rc::clone(&log),
},
log,
};
harness.frame();
harness
}
fn frame(&mut self) -> RecordingScene {
self.root.rebuild(&mut overlay_logic, &mut self.state);
self.root.layout(Size::new(200.0, 200.0));
let mut scene = RecordingScene::default();
self.root.paint(&mut scene, FrameTime::ZERO);
scene
}
fn dispatch(&mut self, event: &InputEvent) -> EventOutcome {
self.root.event(&mut self.state, event)
}
fn down(&mut self, x: f64, y: f64) -> EventOutcome {
self.dispatch(&pointer(PointerPhase::Down, x, y))
}
fn clear_log(&mut self) {
let mut log = self.log.borrow_mut();
log.owner.clear();
log.pod.clear();
log.sibling.clear();
log.sibling_focus.clear();
log.pod_paint_focus.clear();
log.sibling_paint_focus.clear();
}
fn branch_focus(&self) -> (bool, bool) {
let log = self.log.borrow();
(
*log.pod_paint_focus
.last()
.expect("the pod painted at least once"),
*log.sibling_paint_focus
.last()
.expect("the sibling painted at least once"),
)
}
fn owner_overlays(&self) -> Vec<(OverlayKey, OverlayEventKind)> {
self.log
.borrow()
.owner
.iter()
.filter_map(|event| match event {
InputEvent::Overlay(o) => Some((o.key, o.kind.clone())),
_ => None,
})
.collect()
}
fn sibling_pointers(&self) -> Vec<PointerEvent> {
self.log
.borrow()
.sibling
.iter()
.filter_map(|event| match event {
InputEvent::Pointer(p) => Some(*p),
_ => None,
})
.collect()
}
}