use crate::docking::panels::DockPanel;
use crate::input::{InputCoordinator, WidgetKind};
use crate::input::core::coordinator::LayerId;
use crate::layout::{LayoutManager, LayoutNodeId, OverlayEntry, OverlayKind};
use crate::render::{RenderContext, TextAlign, TextBaseline};
use crate::types::{Rect, WidgetId, CompositeId};
use super::input::{register_input_coordinator_modal, register_layout_manager_modal};
use super::render::register_context_manager_modal;
use super::settings::ModalSettings;
use super::state::ModalState;
use super::types::{BackdropKind, ModalRenderKind, ModalView};
fn rect(x: f64, y: f64, w: f64, h: f64) -> Rect {
Rect::new(x, y, w, h)
}
struct NoopRender;
impl RenderContext for NoopRender {
fn dpr(&self) -> f64 { 1.0 }
fn set_stroke_color(&mut self, _color: &str) {}
fn set_stroke_width(&mut self, _width: f64) {}
fn set_line_dash(&mut self, _pattern: &[f64]) {}
fn set_line_cap(&mut self, _cap: &str) {}
fn set_line_join(&mut self, _join: &str) {}
fn set_fill_color(&mut self, _color: &str) {}
fn set_global_alpha(&mut self, _alpha: f64) {}
fn begin_path(&mut self) {}
fn move_to(&mut self, _x: f64, _y: f64) {}
fn line_to(&mut self, _x: f64, _y: f64) {}
fn close_path(&mut self) {}
fn rect(&mut self, _x: f64, _y: f64, _w: f64, _h: f64) {}
fn arc(&mut self, _cx: f64, _cy: f64, _r: f64, _s: f64, _e: f64) {}
fn ellipse(&mut self, _cx: f64, _cy: f64, _rx: f64, _ry: f64, _rot: f64, _s: f64, _e: f64) {}
fn quadratic_curve_to(&mut self, _cpx: f64, _cpy: f64, _x: f64, _y: f64) {}
fn bezier_curve_to(&mut self, _cp1x: f64, _cp1y: f64, _cp2x: f64, _cp2y: f64, _x: f64, _y: f64) {}
fn stroke(&mut self) {}
fn fill(&mut self) {}
fn clip(&mut self) {}
fn stroke_rect(&mut self, _x: f64, _y: f64, _w: f64, _h: f64) {}
fn fill_rect(&mut self, _x: f64, _y: f64, _w: f64, _h: f64) {}
fn set_font(&mut self, _font: &str) {}
fn set_text_align(&mut self, _align: TextAlign) {}
fn set_text_baseline(&mut self, _baseline: TextBaseline) {}
fn fill_text(&mut self, _text: &str, _x: f64, _y: f64) {}
fn stroke_text(&mut self, _text: &str, _x: f64, _y: f64) {}
fn measure_text(&self, _text: &str) -> f64 { 0.0 }
fn save(&mut self) {}
fn restore(&mut self) {}
fn translate(&mut self, _x: f64, _y: f64) {}
fn rotate(&mut self, _angle: f64) {}
fn scale(&mut self, _x: f64, _y: f64) {}
}
#[derive(Clone, Debug)]
struct DummyPanel;
impl DockPanel for DummyPanel {
fn title(&self) -> &str { "dummy" }
fn type_id(&self) -> &'static str { "dummy" }
}
fn plain_view() -> ModalView<'static> {
ModalView {
title: None,
tabs: &[],
footer_buttons: &[],
wizard_pages: &[],
backdrop: BackdropKind::None,
overflow: crate::types::OverflowMode::Clip,
resizable: false,
}
}
#[test]
fn modal_l1_registers_in_input_coordinator() {
let mut coord = InputCoordinator::new();
let mut state = ModalState::default();
let view = plain_view();
let settings = ModalSettings::default();
let kind = ModalRenderKind::Plain;
let layer = LayerId::modal();
let modal_rect = rect(100.0, 100.0, 400.0, 300.0);
let modal_id: CompositeId = register_input_coordinator_modal(
&mut coord,
"test-modal-l1",
modal_rect,
&mut state,
&view,
&settings,
&kind,
&layer,
);
assert_eq!(
coord.widget_kind(modal_id.as_widget_id()),
Some(WidgetKind::Modal),
"modal composite must be registered with kind Modal",
);
let stored = coord.widget_rect(modal_id.as_widget_id())
.expect("registered modal must have a rect");
assert_eq!(stored, modal_rect, "stored rect must equal the rect passed to registration");
let _ = register_input_coordinator_modal(
&mut coord,
"test-modal-l1-b",
rect(200.0, 200.0, 300.0, 200.0),
&mut state,
&view,
&settings,
&kind,
&layer,
);
}
#[test]
fn modal_l2_registers_via_context_manager() {
use crate::app_context::ContextManager;
use crate::app_context::layout::types::LayoutNode;
let mut ctx = ContextManager::new(LayoutNode::new("test-root"));
let mut render = NoopRender;
let mut state = ModalState::default();
let mut view = plain_view();
let settings = ModalSettings::default();
let kind = ModalRenderKind::WithHeader;
let layer = LayerId::modal();
let modal_rect = rect(50.0, 50.0, 600.0, 400.0);
register_context_manager_modal(
&mut ctx,
&mut render,
"test-modal-l2",
modal_rect,
&mut state,
&mut view,
&settings,
&kind,
&layer,
);
let id = WidgetId::new("test-modal-l2");
assert_eq!(
ctx.input.widget_kind(&id),
Some(WidgetKind::Modal),
"L2 must forward the modal registration to ctx.input",
);
let close_id = WidgetId::new("test-modal-l2:close");
assert_eq!(
ctx.input.widget_kind(&close_id),
Some(WidgetKind::CloseButton),
"WithHeader modal must register a CloseButton child",
);
}
#[test]
fn modal_l3_resolves_rect_from_layout_manager() {
let mut layout = LayoutManager::<DummyPanel>::new();
let mut render = NoopRender;
let settings = ModalSettings::default();
let kind = ModalRenderKind::Plain;
layout.solve(rect(0.0, 0.0, 1920.0, 1080.0));
let overlay_rect = rect(100.0, 100.0, 400.0, 300.0);
layout.push_overlay(OverlayEntry {
id: "test-modal-l3".to_string(),
kind: OverlayKind::Modal,
rect: overlay_rect,
anchor: None,
});
assert_eq!(
layout.rect_for_overlay("test-modal-l3"),
Some(overlay_rect),
"overlay rect must be resolvable before L3 call",
);
let result = {
let mut view = plain_view();
register_layout_manager_modal(
&mut layout,
&mut render,
LayoutNodeId::ROOT,
"test-modal-l3",
"modal-widget-l3",
overlay_rect,
None,
&mut view,
&settings,
&kind,
)
};
assert!(
result.is_some(),
"L3 must return Some(()) when the overlay slot exists",
);
let id = WidgetId::new("modal-widget-l3");
assert_eq!(
layout.ctx().input.widget_kind(&id),
Some(WidgetKind::Modal),
"L3 must propagate registration into the embedded ContextManager",
);
let missing = {
let mut view = plain_view();
register_layout_manager_modal(
&mut layout,
&mut render,
LayoutNodeId::ROOT,
"this-slot-does-not-exist",
"modal-widget-missing",
rect(0.0, 0.0, 0.0, 0.0),
None,
&mut view,
&settings,
&kind,
)
};
assert!(
missing.is_none(),
"L3 must return None when the overlay slot is absent",
);
}