use std::cell::{Cell, RefCell};
use std::fmt;
use std::rc::Rc;
use kurbo::Rect;
use crate::event::PassBracket;
use crate::insets::WindowInsets;
use crate::widget::ChildPod;
thread_local! {
static NEXT_OVERLAY_KEY: Cell<u64> = const { Cell::new(1) };
static OVERLAY_REGISTRY: Cell<Vec<OverlayEntry>> = const { Cell::new(Vec::new()) };
static OVERLAY_PASS_OPEN: Cell<bool> = const { Cell::new(false) };
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct OverlayKey(u64);
impl OverlayKey {
pub fn next() -> Self {
OverlayKey(NEXT_OVERLAY_KEY.with(|next| {
let id = next.get();
next.set(id.wrapping_add(1));
id
}))
}
pub fn value(self) -> u64 {
self.0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum OverlayBand {
Floating,
Tooltip,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum OverlayInput {
Interactive,
Transparent,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum OutsideTap {
Ignore,
Notify {
consume: bool,
},
}
pub type OverlayPod = Rc<RefCell<ChildPod>>;
#[derive(Clone)]
pub struct OverlayEntry {
pub key: OverlayKey,
pub band: OverlayBand,
pub input: OverlayInput,
pub outside_tap: OutsideTap,
pub window_rect: Rect,
pub pod: OverlayPod,
pub insets: WindowInsets,
}
impl fmt::Debug for OverlayEntry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("OverlayEntry")
.field("key", &self.key)
.field("band", &self.band)
.field("input", &self.input)
.field("outside_tap", &self.outside_tap)
.field("window_rect", &self.window_rect)
.field("insets", &self.insets)
.field("pod", &"<pod>")
.finish()
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct OverlayHit {
pub key: OverlayKey,
pub band: OverlayBand,
pub input: OverlayInput,
pub outside_tap: OutsideTap,
pub window_rect: Rect,
}
impl OverlayHit {
pub(crate) fn of(entry: &OverlayEntry) -> Self {
Self {
key: entry.key,
band: entry.band,
input: entry.input,
outside_tap: entry.outside_tap,
window_rect: entry.window_rect,
}
}
pub(crate) fn contains(&self, point: kurbo::Point) -> bool {
point.x >= self.window_rect.x0
&& point.x < self.window_rect.x1
&& point.y >= self.window_rect.y0
&& point.y < self.window_rect.y1
}
}
pub(crate) struct OverlayPaintPass(PassBracket<Vec<OverlayEntry>>);
impl OverlayPaintPass {
pub(crate) fn enter() -> Self {
OverlayPaintPass(PassBracket::enter(&OVERLAY_REGISTRY, &OVERLAY_PASS_OPEN))
}
pub(crate) fn take(&self) -> Vec<OverlayEntry> {
self.0.take()
}
}
pub(crate) fn register(entry: OverlayEntry) {
OVERLAY_REGISTRY.with(|registry| {
let mut entries = registry.take();
entries.push(entry);
registry.set(entries);
});
}
pub(crate) fn sort_into_paint_order(entries: &mut [OverlayEntry]) {
entries.sort_by_key(|entry| entry.band);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::layout::BoxConstraints;
use crate::widget::{LayoutCtx, PaintCtx, PaintScene, Widget};
use kurbo::{Point, Size};
struct StubWidget;
impl Widget for StubWidget {
fn layout(&mut self, _ctx: &mut LayoutCtx, _bc: &BoxConstraints) -> Size {
Size::ZERO
}
fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
}
fn entry(key: OverlayKey, band: OverlayBand, rect: Rect) -> OverlayEntry {
OverlayEntry {
key,
band,
input: OverlayInput::Interactive,
outside_tap: OutsideTap::Ignore,
window_rect: rect,
pod: Rc::new(RefCell::new(ChildPod::new(Box::new(StubWidget)))),
insets: WindowInsets::default(),
}
}
#[test]
fn keys_are_distinct_and_stable() {
let first = OverlayKey::next();
let second = OverlayKey::next();
assert_ne!(first, second, "two owners never share an identity");
let stored = first;
assert_eq!(stored, first);
assert_ne!(stored, second);
}
#[test]
fn paint_order_puts_floating_below_tooltip_and_is_stable_within_a_band() {
let (a, b, c, d) = (
OverlayKey::next(),
OverlayKey::next(),
OverlayKey::next(),
OverlayKey::next(),
);
let rect = Rect::new(0.0, 0.0, 10.0, 10.0);
let mut entries = vec![
entry(a, OverlayBand::Tooltip, rect),
entry(b, OverlayBand::Floating, rect),
entry(c, OverlayBand::Tooltip, rect),
entry(d, OverlayBand::Floating, rect),
];
sort_into_paint_order(&mut entries);
assert_eq!(
entries.iter().map(|e| e.key).collect::<Vec<_>>(),
vec![b, d, a, c],
"Floating first (in registration order), then Tooltip (in registration order)"
);
}
#[test]
fn a_hit_rect_is_half_open_on_its_far_edges() {
let hit = OverlayHit::of(&entry(
OverlayKey::next(),
OverlayBand::Floating,
Rect::new(10.0, 20.0, 110.0, 60.0),
));
assert!(
hit.contains(Point::new(10.0, 20.0)),
"near edges are inside"
);
assert!(hit.contains(Point::new(109.9, 59.9)));
assert!(!hit.contains(Point::new(110.0, 40.0)));
assert!(!hit.contains(Point::new(60.0, 60.0)));
assert!(!hit.contains(Point::new(9.9, 40.0)));
}
#[test]
fn a_hit_keeps_the_routing_fields_and_drops_the_pod() {
let key = OverlayKey::next();
let mut source = entry(key, OverlayBand::Tooltip, Rect::new(1.0, 2.0, 3.0, 4.0));
source.input = OverlayInput::Transparent;
source.outside_tap = OutsideTap::Notify { consume: true };
let hit = OverlayHit::of(&source);
assert_eq!(hit.key, key);
assert_eq!(hit.band, OverlayBand::Tooltip);
assert_eq!(hit.input, OverlayInput::Transparent);
assert_eq!(hit.outside_tap, OutsideTap::Notify { consume: true });
assert_eq!(hit.window_rect, source.window_rect);
assert_eq!(Rc::strong_count(&source.pod), 1);
}
#[test]
fn the_registry_is_pass_scoped_and_preserves_registration_order() {
let (first, second) = (OverlayKey::next(), OverlayKey::next());
let rect = Rect::new(0.0, 0.0, 10.0, 10.0);
let pass = OverlayPaintPass::enter();
assert!(pass.take().is_empty(), "a fresh pass starts empty");
register(entry(first, OverlayBand::Floating, rect));
register(entry(second, OverlayBand::Floating, rect));
let drained = pass.take();
assert_eq!(
drained.iter().map(|e| e.key).collect::<Vec<_>>(),
vec![first, second],
"registration order is preserved"
);
assert!(pass.take().is_empty(), "and the drain empties the registry");
drop(pass);
register(entry(first, OverlayBand::Floating, rect));
let next = OverlayPaintPass::enter();
assert!(
next.take().is_empty(),
"a stray registration does not survive into the next pass"
);
}
#[test]
fn a_nested_pass_hands_the_enclosing_passs_registrations_back() {
let (outer_key, inner_key) = (OverlayKey::next(), OverlayKey::next());
let rect = Rect::new(0.0, 0.0, 10.0, 10.0);
let outer = OverlayPaintPass::enter();
register(entry(outer_key, OverlayBand::Floating, rect));
{
let inner = OverlayPaintPass::enter();
assert!(inner.take().is_empty(), "the inner pass starts empty");
register(entry(inner_key, OverlayBand::Floating, rect));
assert_eq!(
inner.take().iter().map(|e| e.key).collect::<Vec<_>>(),
vec![inner_key]
);
}
assert_eq!(
outer.take().iter().map(|e| e.key).collect::<Vec<_>>(),
vec![outer_key],
"the enclosing pass's registrations are handed back intact"
);
}
#[test]
fn debug_prints_the_routing_fields_without_the_pod() {
let rendered = format!(
"{:?}",
entry(
OverlayKey::next(),
OverlayBand::Floating,
Rect::new(0.0, 0.0, 10.0, 10.0)
)
);
assert!(rendered.contains("Floating"), "{rendered}");
assert!(rendered.contains("Interactive"), "{rendered}");
assert!(rendered.contains("<pod>"), "{rendered}");
}
}