use crate::core::{Bounds, Pixels, Point, Size};
use crate::view::any_element::AnyElement;
use crate::view::context::ElementContext;
use crate::view::element::{Element, ParentElement};
use crate::view::style::{Direction, Length, Style};
use crate::view::text_layout::compute_layout;
use smallvec::SmallVec;
use taffy::{AvailableSpace, NodeId};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Placement {
Below,
Right,
}
pub struct DeferredOverlay {
children: SmallVec<[AnyElement; 2]>,
anchor: Bounds,
placement: Placement,
origin: Point,
within: Option<Bounds>,
}
pub struct Anchored {
target: Option<Bounds>,
placement: Placement,
within: Option<Bounds>,
children: SmallVec<[AnyElement; 2]>,
node: Option<NodeId>,
}
pub fn anchored() -> Anchored {
Anchored { target: None, placement: Placement::Below, within: None, children: SmallVec::new(), node: None }
}
impl Anchored {
pub fn anchor_to(mut self, bounds: Bounds) -> Self {
self.target = Some(bounds);
self
}
pub fn set_anchor(&mut self, bounds: Bounds) {
self.target = Some(bounds);
}
pub fn set_within(&mut self, bounds: Bounds) {
self.within = Some(bounds);
}
pub fn within(mut self, bounds: Bounds) -> Self {
self.within = Some(bounds);
self
}
pub fn position(self, point: Point) -> Self {
self.anchor_to(Bounds::new(point, Size::default()))
}
pub fn placement(mut self, placement: Placement) -> Self {
self.placement = placement;
self
}
}
impl ParentElement for Anchored {
fn extend_children(&mut self, children: impl IntoIterator<Item = AnyElement>) {
self.children.extend(children);
}
}
impl Element for Anchored {
fn request_layout(&mut self, cx: &mut ElementContext) -> NodeId {
let style = Style {
absolute: Some(true),
width: Some(Length::Px(Pixels::ZERO)),
height: Some(Length::Px(Pixels::ZERO)),
..Default::default()
};
let node = cx.request_node(&style, &[]);
self.node = Some(node);
node
}
fn prepaint(&mut self, cx: &mut ElementContext) {
let anchor = match (self.target, self.node) {
(Some(target), _) => target,
(None, Some(node)) => cx.node_bounds(node),
(None, None) => return,
};
let children = std::mem::take(&mut self.children);
cx.frame.deferred.push(DeferredOverlay {
children,
anchor,
placement: self.placement,
origin: Point::default(),
within: self.within,
});
}
fn paint(&mut self, _cx: &mut ElementContext) {}
}
pub fn place(anchor: Bounds, placement: Placement, size: Size, viewport: Size) -> Point {
place_in(anchor, placement, size, Bounds::new(Point::default(), viewport))
}
pub fn place_in(anchor: Bounds, placement: Placement, size: Size, area: Bounds) -> Point {
let (width, height) = (size.width.0, size.height.0);
let (left, top, right, bottom) = (area.origin.x.0, area.origin.y.0, area.right().0, area.bottom().0);
let (mut x, mut y) = match placement {
Placement::Below => (anchor.origin.x.0, anchor.bottom().0),
Placement::Right => (anchor.right().0, anchor.origin.y.0),
};
if placement == Placement::Below && y + height > bottom && anchor.origin.y.0 - height >= top {
y = anchor.origin.y.0 - height;
}
if placement == Placement::Right && x + width > right {
x = anchor.origin.x.0 - width;
}
x = x.min(right - width).max(left);
y = y.min(bottom - height).max(top);
Point::new(Pixels(x), Pixels(y))
}
pub(crate) fn prepaint_deferred(cx: &mut ElementContext, viewport: Size) -> Vec<DeferredOverlay> {
let mut prepared = Vec::new();
loop {
let pending = std::mem::take(&mut cx.frame.deferred);
if pending.is_empty() {
return prepared;
}
for mut overlay in pending {
let child_nodes: Vec<NodeId> = overlay.children.iter_mut().map(|child| child.request_layout(cx)).collect();
let root_style = Style { direction: Some(Direction::Column), ..Default::default() };
let root = cx.request_node(&root_style, &child_nodes);
let available = taffy::Size { width: AvailableSpace::MaxContent, height: AvailableSpace::MaxContent };
if let Err(error) = compute_layout(cx.frame, cx.text, cx.shape_cache, root, available) {
eprintln!("falló el layout de un overlay: {error}");
continue;
}
let size = cx
.frame
.taffy
.layout(root)
.map_or(Size::default(), |layout| Size::new(Pixels(layout.size.width), Pixels(layout.size.height)));
let area = overlay.within.unwrap_or_else(|| Bounds::new(Point::default(), viewport));
overlay.origin = place_in(overlay.anchor, overlay.placement, size, area);
let origin = overlay.origin;
cx.without_mask(|cx| {
cx.with_origin(origin, |cx| overlay.children.iter_mut().for_each(|child| child.prepaint(cx)))
});
prepared.push(overlay);
}
}
}
pub(crate) fn paint_deferred(cx: &mut ElementContext, overlays: &mut [DeferredOverlay]) {
for overlay in overlays {
let origin = overlay.origin;
cx.without_mask(|cx| {
cx.with_origin(origin, |cx| overlay.children.iter_mut().for_each(|child| child.paint(cx)))
});
}
}
#[cfg(test)]
mod tests {
use super::*;
fn bounds(x: f32, y: f32, width: f32, height: f32) -> Bounds {
Bounds::new(Point::new(Pixels(x), Pixels(y)), Size::new(Pixels(width), Pixels(height)))
}
#[test]
fn flips_above_when_there_is_no_room_below() {
let viewport = Size::new(Pixels(800.0), Pixels(600.0));
let origin = place(
bounds(100.0, 580.0, 10.0, 20.0),
Placement::Below,
Size::new(Pixels(200.0), Pixels(100.0)),
viewport,
);
assert_eq!(origin, Point::new(Pixels(100.0), Pixels(480.0)));
}
#[test]
fn stays_inside_the_panel_that_opened_it_and_not_the_whole_window() {
let panel = bounds(0.0, 0.0, 800.0, 400.0);
let size = Size::new(Pixels(200.0), Pixels(100.0));
let flipped = place_in(bounds(100.0, 390.0, 10.0, 20.0), Placement::Below, size, panel);
assert_eq!(flipped, Point::new(Pixels(100.0), Pixels(290.0)));
let too_tall =
place_in(bounds(100.0, 50.0, 10.0, 20.0), Placement::Below, Size::new(Pixels(200.0), Pixels(500.0)), panel);
assert_eq!(too_tall.y, Pixels(0.0), "si no cabe en ningún lado, se pega al borde superior del panel");
let beside =
place_in(bounds(790.0, 390.0, 10.0, 20.0), Placement::Below, size, bounds(0.0, 100.0, 800.0, 300.0));
assert!(beside.x.0 + 200.0 <= 800.0 && beside.y.0 + 100.0 <= 400.0 && beside.y.0 >= 100.0);
}
#[test]
fn stays_inside_the_right_edge() {
let viewport = Size::new(Pixels(800.0), Pixels(600.0));
let origin =
place(bounds(750.0, 10.0, 10.0, 20.0), Placement::Below, Size::new(Pixels(200.0), Pixels(100.0)), viewport);
assert_eq!(origin.x, Pixels(600.0));
}
}