use crate::systems::retained_ui::viewport_controls::{self, ViewportControlsHandles};
use nightshade::prelude::*;
const OUTLINE_COLOR: Vec4 = Vec4::new(1.0, 0.647, 0.0, 1.0);
const OUTLINE_WIDTH: f32 = 3.0;
#[derive(Default, Clone)]
pub struct ViewportHandles {
pub tile_container: Entity,
pub viewport_pane: TileId,
pub viewport_content: Entity,
pub notes_pane: TileId,
pub notes_content: Entity,
pub outline: Entity,
pub controls: Option<ViewportControlsHandles>,
pub extra_camera_tiles: Vec<ExtraCameraTile>,
}
#[derive(Clone, Copy)]
pub struct ExtraCameraTile {
pub pane_id: TileId,
pub camera_entity: Entity,
pub content_entity: Entity,
pub controls: ViewportControlsHandles,
}
pub fn configure_viewport_pane(world: &mut World, content_entity: Entity) {
world.ui.remove_components(
content_entity,
nightshade::ecs::world::UI_NODE_COLOR | nightshade::ecs::world::UI_NODE_CONTENT,
);
if let Some(node) = world.ui.get_ui_layout_node_mut(content_entity) {
node.flow_layout = None;
node.clip_content = false;
}
}
pub fn build(tree: &mut UiTreeBuilder) -> ViewportHandles {
let tile_container = tree.add_tile_container(vec2(0.0, 0.0));
let world = tree.world_mut();
world.ui.remove_components(
tile_container,
nightshade::ecs::world::UI_NODE_COLOR
| nightshade::ecs::world::UI_NODE_CONTENT
| nightshade::ecs::world::UI_THEME_BINDING,
);
if let Some(node) = world.ui.get_ui_layout_node_mut(tile_container) {
node.clip_content = false;
}
let (viewport_pane, viewport_content) =
ui_tile_add_pane(world, tile_container, "Viewport").expect("viewport pane");
configure_viewport_pane(world, viewport_content);
let (notes_pane, notes_content) =
ui_tile_add_pane(world, tile_container, "Notes").expect("notes pane");
let mut sub_tree = UiTreeBuilder::from_parent(world, notes_content);
sub_tree.add_label("Drag tabs to rearrange. Click a tab to focus.");
sub_tree.add_label("");
sub_tree.add_label("The Viewport tab is transparent and reports its rect to");
sub_tree.add_label("active_viewport_rect, so the camera and picking use");
sub_tree.add_label("the visible area, not the full window.");
sub_tree.finish_subtree();
let outline = tree
.add_node()
.window(Ab(vec2(0.0, 0.0)), Ab(vec2(0.0, 0.0)), Anchor::TopLeft)
.with_rect(0.0, OUTLINE_WIDTH, OUTLINE_COLOR)
.color_raw::<UiBase>(vec4(0.0, 0.0, 0.0, 0.0))
.with_layer(UiLayer::DockedPanels)
.with_depth(UiDepthMode::Set(15.0))
.entity();
ui_set_visible(tree.world_mut(), outline, false);
let controls = tree
.world_mut()
.resources
.active_camera
.map(|camera_entity| {
viewport_controls::ensure_shading(tree.world_mut(), camera_entity);
let world_mut = tree.world_mut();
if !world_mut
.core
.entity_has_components(camera_entity, nightshade::ecs::world::VIEWPORT_UPDATE_MODE)
{
world_mut
.core
.add_components(camera_entity, nightshade::ecs::world::VIEWPORT_UPDATE_MODE);
world_mut.core.set_viewport_update_mode(
camera_entity,
nightshade::ecs::camera::components::ViewportUpdateMode::WhenDirty,
);
}
viewport_controls::build(tree, camera_entity)
});
ViewportHandles {
tile_container,
viewport_pane,
viewport_content,
notes_pane,
notes_content,
outline,
controls,
extra_camera_tiles: Vec::new(),
}
}
pub fn update(
world: &mut World,
handles: &super::UiHandles,
extra_camera_tiles: &[ExtraCameraTile],
) {
let viewport_size = world
.resources
.window
.cached_viewport_size
.map(|(width, height)| vec2(width as f32, height as f32));
let Some(viewport_size) = viewport_size else {
world.resources.window.active_viewport_rect = None;
world.resources.window.camera_tile_rects.clear();
world.resources.user_interface.required_cameras.clear();
ui_set_visible(world, handles.viewport.outline, false);
return;
};
let reserved = ui_reserved_areas(world).clone();
let dpi = world.resources.window.cached_scale_factor.max(0.0001);
let center_min = vec2(reserved.left, reserved.top);
let center_size = vec2(
(viewport_size.x - reserved.left - reserved.right).max(0.0),
(viewport_size.y - reserved.top - reserved.bottom).max(0.0),
);
if let Some(node) = world
.ui
.get_ui_layout_node_mut(handles.viewport.tile_container)
&& let Some(nightshade::ecs::ui::layout_types::UiLayoutType::Window(window)) =
node.base_layout.as_mut()
{
window.position = nightshade::ecs::ui::units::Ab(center_min / dpi).into();
window.size = nightshade::ecs::ui::units::Ab(center_size / dpi).into();
}
let viewport_active = ui_tile_active_pane(
world,
handles.viewport.tile_container,
handles.viewport.viewport_pane,
);
let active_pane_rect = pane_rect(
world,
handles.viewport.tile_container,
handles.viewport.viewport_pane,
);
let camera_tile_rects = &mut world.resources.window.camera_tile_rects;
camera_tile_rects.clear();
let active_camera = world.resources.active_camera;
if let Some(camera) = active_camera {
let pane_layout = world
.ui
.get_ui_layout_node(handles.viewport.viewport_content)
.map(|node| (node.computed_rect, node.visible));
if let Some((rect, visible)) = pane_layout
&& visible
&& rect.width() > 0.0
&& rect.height() > 0.0
{
world.resources.window.camera_tile_rects.insert(
camera,
ViewportRect {
x: rect.min.x,
y: rect.min.y,
width: rect.width(),
height: rect.height(),
},
);
}
}
for tile in extra_camera_tiles {
if Some(tile.camera_entity) == active_camera {
continue;
}
let pane_layout = world
.ui
.get_ui_layout_node(tile.content_entity)
.map(|node| (node.computed_rect, node.visible));
if let Some((rect, visible)) = pane_layout
&& visible
&& rect.width() > 0.0
&& rect.height() > 0.0
{
world.resources.window.camera_tile_rects.insert(
tile.camera_entity,
ViewportRect {
x: rect.min.x,
y: rect.min.y,
width: rect.width(),
height: rect.height(),
},
);
}
}
if let Some(controls) = handles.viewport.controls {
viewport_controls::update(
world,
&controls,
world
.ui
.get_ui_layout_node(handles.viewport.viewport_content)
.filter(|node| node.visible)
.map(|node| node.computed_rect),
);
viewport_controls::sync(world, &controls);
}
for tile in extra_camera_tiles {
viewport_controls::update(
world,
&tile.controls,
world
.ui
.get_ui_layout_node(tile.content_entity)
.filter(|node| node.visible)
.map(|node| node.computed_rect),
);
viewport_controls::sync(world, &tile.controls);
}
let required = &mut world.resources.user_interface.required_cameras;
required.clear();
if let Some(active) = active_camera
&& world
.resources
.window
.camera_tile_rects
.contains_key(&active)
{
required.push(active);
}
for camera in world.resources.window.camera_tile_rects.keys() {
if Some(*camera) != active_camera {
required.push(*camera);
}
}
if viewport_active {
if let Some(camera) = active_camera
&& let Some(rect) = world
.resources
.window
.camera_tile_rects
.get(&camera)
.copied()
{
world.resources.window.active_viewport_rect = Some(rect);
} else {
world.resources.window.active_viewport_rect = None;
}
} else {
world.resources.window.active_viewport_rect = None;
}
if let Some(rect) = active_pane_rect
&& rect.width() > 1.0
&& rect.height() > 1.0
{
ui_set_visible(world, handles.viewport.outline, true);
if let Some(node) = world.ui.get_ui_layout_node_mut(handles.viewport.outline)
&& let Some(nightshade::ecs::ui::layout_types::UiLayoutType::Window(window)) =
node.base_layout.as_mut()
{
window.position = nightshade::ecs::ui::units::Ab(rect.min / dpi).into();
window.size = nightshade::ecs::ui::units::Ab(rect.size() / dpi).into();
}
} else {
ui_set_visible(world, handles.viewport.outline, false);
}
}
fn pane_rect(
world: &World,
container: Entity,
pane_id: TileId,
) -> Option<nightshade::ecs::ui::types::Rect> {
let data = world.ui.get_ui_tile_container(container)?;
data.rects.get(pane_id.0).copied()
}