use crate::docking::panels::{PanelDockingManager, PanelRect, DockPanel};
use crate::core::types::Rect;
use crate::app_context::{ContextManager, layout::types::LayoutNode};
use super::chrome_slot::ChromeSlot;
use super::edge_panels::EdgePanels;
use super::overlay_stack::{OverlayStack, OverlayEntry};
use super::tree::LayoutTree;
use super::z_layers::ZLayerTable;
use super::types::{OverlayKind, LayoutSolved};
use super::solve::solve_layout;
fn panel_rect_from_rect(r: Rect) -> PanelRect {
PanelRect::new(r.x as f32, r.y as f32, r.width as f32, r.height as f32)
}
fn panel_rect_to_rect(pr: PanelRect) -> Rect {
Rect::new(pr.x as f64, pr.y as f64, pr.width as f64, pr.height as f64)
}
pub struct LayoutManager<P: DockPanel> {
chrome: ChromeSlot,
edges: EdgePanels,
panels: PanelDockingManager<P>,
overlays: OverlayStack,
z_layers: ZLayerTable,
tree: LayoutTree,
last_solved: Option<LayoutSolved>,
last_window: Option<Rect>,
ctx: ContextManager,
}
impl<P: DockPanel> LayoutManager<P> {
pub fn new() -> Self {
Self {
chrome: ChromeSlot::default(),
edges: EdgePanels::new(),
panels: PanelDockingManager::new(),
overlays: OverlayStack::new(),
z_layers: ZLayerTable::default(),
tree: LayoutTree::new(),
last_solved: None,
last_window: None,
ctx: ContextManager::new(LayoutNode::new("__layout_root__")),
}
}
pub fn chrome(&self) -> &ChromeSlot {
&self.chrome
}
pub fn chrome_mut(&mut self) -> &mut ChromeSlot {
&mut self.chrome
}
pub fn edges(&self) -> &EdgePanels {
&self.edges
}
pub fn edges_mut(&mut self) -> &mut EdgePanels {
&mut self.edges
}
pub fn overlays(&self) -> &OverlayStack {
&self.overlays
}
pub fn overlays_mut(&mut self) -> &mut OverlayStack {
&mut self.overlays
}
pub fn z_layers(&self) -> &ZLayerTable {
&self.z_layers
}
pub fn z_layers_mut(&mut self) -> &mut ZLayerTable {
&mut self.z_layers
}
pub fn ctx(&self) -> &ContextManager {
&self.ctx
}
pub fn ctx_mut(&mut self) -> &mut ContextManager {
&mut self.ctx
}
pub fn tree(&self) -> &LayoutTree {
&self.tree
}
pub fn panels(&self) -> &PanelDockingManager<P> {
&self.panels
}
pub fn panels_mut(&mut self) -> &mut PanelDockingManager<P> {
&mut self.panels
}
pub fn solve(&mut self, window: Rect) -> &LayoutSolved {
let solved = solve_layout(window, &self.chrome, &self.edges, &mut self.tree);
let dock_pr = panel_rect_from_rect(solved.dock_area);
self.panels.layout(dock_pr);
self.last_solved = Some(solved);
self.last_window = Some(window);
self.last_solved.as_ref()
.expect("last_solved is Some — we just assigned it")
}
pub fn last_solved(&self) -> Option<&LayoutSolved> {
self.last_solved.as_ref()
}
pub fn last_window(&self) -> Option<Rect> {
self.last_window
}
pub fn rect_for_chrome(&self) -> Option<Rect> {
self.last_solved.as_ref().and_then(|s| s.chrome)
}
pub fn rect_for_dock_area(&self) -> Option<Rect> {
self.last_solved.as_ref().map(|s| s.dock_area)
}
pub fn rect_for_floating_area(&self) -> Option<Rect> {
self.last_solved.as_ref().map(|s| s.floating_area)
}
pub fn rect_for_overlay(&self, id: &str) -> Option<Rect> {
self.overlays.get(id).map(|e| e.rect)
}
pub fn rect_for_edge_slot(&self, id: &str) -> Option<Rect> {
let solved = self.last_solved.as_ref()?;
let slot = self.edges.get(id)?;
use super::types::EdgeSide;
let visible: Vec<_> = self.edges.slots_for(slot.side).collect();
let idx = visible.iter().position(|s| s.id == id)?;
let rects = match slot.side {
EdgeSide::Top => &solved.edges.top,
EdgeSide::Bottom => &solved.edges.bottom,
EdgeSide::Left => &solved.edges.left,
EdgeSide::Right => &solved.edges.right,
};
rects.get(idx).copied()
}
pub fn rect_for(&self, slot_id: &str) -> Option<Rect> {
if slot_id == "chrome" {
return self.rect_for_chrome();
}
if let Some(r) = self.rect_for_edge_slot(slot_id) {
return Some(r);
}
if let Some(r) = self.rect_for_overlay(slot_id) {
return Some(r);
}
if let Some(pr) = self.panels.rect_for_leaf_str(slot_id) {
return Some(panel_rect_to_rect(pr));
}
None
}
pub fn push_overlay(&mut self, entry: OverlayEntry) {
self.overlays.push(entry);
}
pub fn clear_overlays(&mut self) {
self.overlays.clear();
}
pub fn overlays_in_draw_order(&mut self) -> &[OverlayEntry] {
self.overlays.sort_by_z(&self.z_layers);
self.overlays.entries()
}
pub fn z_for(&self, kind: OverlayKind) -> i32 {
self.z_layers.z_for(kind)
}
}
impl<P: DockPanel> Default for LayoutManager<P> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::layout::{EdgeSlot, EdgeSide};
#[derive(Clone, Debug)]
struct DummyPanel;
impl DockPanel for DummyPanel {
fn title(&self) -> &str { "dummy" }
fn type_id(&self) -> &'static str { "dummy" }
}
fn rect(x: f64, y: f64, w: f64, h: f64) -> Rect {
Rect::new(x, y, w, h)
}
#[test]
fn solve_chrome_only() {
let mut lm = LayoutManager::<DummyPanel>::new();
let solved = lm.solve(rect(0.0, 0.0, 1920.0, 1080.0));
let chrome = solved.chrome.expect("chrome should be Some");
assert_eq!(chrome.y, 0.0);
assert_eq!(chrome.height, 32.0);
let dock = solved.dock_area;
assert_eq!(dock.y, 32.0);
assert_eq!(dock.height, 1048.0);
assert_eq!(dock.width, 1920.0);
}
#[test]
fn solve_with_edges() {
let mut lm = LayoutManager::<DummyPanel>::new();
lm.edges_mut().add(EdgeSlot {
id: "toolbar".to_string(),
side: EdgeSide::Top,
thickness: 40.0,
visible: true,
order: 0,
});
lm.edges_mut().add(EdgeSlot {
id: "sidebar".to_string(),
side: EdgeSide::Left,
thickness: 200.0,
visible: true,
order: 0,
});
let solved = lm.solve(rect(0.0, 0.0, 1920.0, 1080.0));
let dock = solved.dock_area;
assert_eq!(dock.x, 200.0, "dock starts after sidebar");
assert_eq!(dock.y, 72.0, "dock starts after chrome+toolbar");
assert_eq!(dock.width, 1720.0);
assert_eq!(dock.height, 1008.0);
}
#[test]
fn solve_chrome_hidden() {
let mut lm = LayoutManager::<DummyPanel>::new();
lm.chrome_mut().visible = false;
let solved = lm.solve(rect(0.0, 0.0, 800.0, 600.0));
assert!(solved.chrome.is_none(), "hidden chrome yields None");
assert_eq!(solved.dock_area.y, 0.0);
assert_eq!(solved.dock_area.height, 600.0);
}
#[test]
fn z_table_default_ordering() {
let lm = LayoutManager::<DummyPanel>::new();
assert!(lm.z_for(OverlayKind::Modal) < lm.z_for(OverlayKind::ContextMenu));
assert!(lm.z_for(OverlayKind::ContextMenu) < lm.z_for(OverlayKind::Tooltip));
}
#[test]
fn z_table_override() {
let mut lm = LayoutManager::<DummyPanel>::new();
lm.z_layers_mut().set(OverlayKind::Modal, 10);
assert_eq!(lm.z_for(OverlayKind::Modal), 10);
}
#[test]
fn overlay_sort_by_z() {
use super::super::overlay_stack::OverlayEntry;
let mut lm = LayoutManager::<DummyPanel>::new();
lm.push_overlay(OverlayEntry { id: "tip".to_string(), kind: OverlayKind::Tooltip, rect: rect(0.0,0.0,1.0,1.0), anchor: None });
lm.push_overlay(OverlayEntry { id: "dd".to_string(), kind: OverlayKind::Dropdown, rect: rect(0.0,0.0,1.0,1.0), anchor: None });
lm.push_overlay(OverlayEntry { id: "m".to_string(), kind: OverlayKind::Modal, rect: rect(0.0,0.0,1.0,1.0), anchor: None });
let ordered = lm.overlays_in_draw_order();
assert_eq!(ordered[0].id, "dd");
assert_eq!(ordered[1].id, "m");
assert_eq!(ordered[2].id, "tip");
}
#[test]
fn panels_accessor_compiles() {
let mut lm = LayoutManager::<DummyPanel>::new();
let _panels: &mut PanelDockingManager<DummyPanel> = lm.panels_mut();
}
}