use concinnity_core::components::FrameInput;
use concinnity_core::components::Sprite;
use concinnity_core::components::TextInput;
use concinnity_core::components::TextLabel;
use concinnity_core::ecs::World;
use concinnity_core::ecs::asset_id::AssetId;
use super::fixtures::{
behavior, behavior_session, close_rect_of, entry, hook, seed_tree, select_behavior, set_input,
title_rect_of, world_with_fields, world_with_input,
};
use crate::editor::behavior;
use crate::editor::behavior::panel::{BehaviorAction, ViewMode};
use crate::editor::hook::{Drag, FormTarget};
use crate::editor::hud;
use crate::editor::inject;
use crate::frame_hook::FrameHook;
use crate::editor::panels::form_panel;
use crate::editor::panels::assets_panel;
use crate::editor::panels::preview;
use crate::editor::panels::registry::PanelKey;
use crate::editor::hook::tests::fixtures::entry_target;
use crate::editor::widget;
#[test]
fn title_bar_drag_moves_and_clamps_the_assets_panel() {
let mut h = hook(Vec::new());
h.open[PanelKey::Assets] = true;
let vp = [1280.0, 720.0];
let start = h.origin(PanelKey::Assets, vp);
let mut world = world_with_input(FrameInput {
viewport: vp,
mouse_x: start[0] + 10.0,
mouse_y: start[1] + 10.0,
left_click: true,
left_button_down: true,
..Default::default()
});
h.tick(&mut world);
assert!(h.drag.is_some(), "the title press starts a drag");
set_input(
&mut world,
FrameInput {
viewport: vp,
mouse_x: 400.0,
mouse_y: 150.0,
left_button_down: true,
..Default::default()
},
);
h.tick(&mut world);
assert_eq!(h.origin(PanelKey::Assets, vp), [390.0, 140.0]);
set_input(
&mut world,
FrameInput {
viewport: vp,
mouse_x: -500.0,
mouse_y: -500.0,
left_button_down: true,
..Default::default()
},
);
h.tick(&mut world);
assert_eq!(
h.origin(PanelKey::Assets, vp),
[0.0, hud::BAR_H],
"never partially off screen or under the top bar"
);
set_input(
&mut world,
FrameInput {
viewport: vp,
left_button_down: false,
..Default::default()
},
);
h.tick(&mut world);
assert!(h.drag.is_none(), "release ends the drag");
assert_eq!(h.origin(PanelKey::Assets, vp), [0.0, hud::BAR_H]);
}
#[test]
fn title_bar_drag_moves_and_clamps_the_preview_panel() {
let mut h = hook(Vec::new());
let vp = [1280.0, 720.0];
let start = h.origin(PanelKey::Preview, vp);
let mut world = world_with_input(FrameInput {
viewport: vp,
mouse_x: start[0] + 5.0,
mouse_y: start[1] + 5.0,
left_click: true,
left_button_down: true,
..Default::default()
});
h.tick(&mut world);
assert!(h.drag.is_some());
set_input(
&mut world,
FrameInput {
viewport: vp,
mouse_x: 5000.0,
mouse_y: 5000.0,
left_button_down: true,
..Default::default()
},
);
h.tick(&mut world);
let size = preview::size();
assert_eq!(
h.origin(PanelKey::Preview, vp),
[vp[0] - size[0], vp[1] - size[1]],
"stops flush with the bottom-right corner"
);
}
#[test]
fn edit_panel_drags_by_its_title_bar() {
let mut h = hook(vec![entry("lamp", "PointLight")]);
let mut world = World::new();
inject::editor_hud(&mut world);
h.open[PanelKey::Assets] = true;
let target = entry_target(&h, 0);
h.open_form(&mut world, "PointLight".to_string(), target);
let vp = [1280.0, 720.0];
let fo = h.origin(PanelKey::Edit, vp);
world.add_component(FrameInput {
viewport: vp,
mouse_x: fo[0] + 12.0,
mouse_y: fo[1] + 8.0,
left_click: true,
left_button_down: true,
..Default::default()
});
h.tick(&mut world);
assert!(h.drag.is_some(), "the form title press starts a drag");
set_input(
&mut world,
FrameInput {
viewport: vp,
mouse_x: 112.0,
mouse_y: 208.0,
left_button_down: true,
..Default::default()
},
);
h.tick(&mut world);
assert_eq!(h.origin(PanelKey::Edit, vp), [100.0, 200.0]);
assert_eq!(
h.origin(PanelKey::Assets, vp),
assets_panel::default_origin(vp[0]),
"the Assets panel did not move"
);
}
#[test]
fn focusing_a_panel_moves_it_to_the_front() {
let mut h = hook(Vec::new());
let panels = h.panel_order.len();
assert_eq!(h.panel_order.last().copied(), Some(PanelKey::Worlds));
h.focus_panel(PanelKey::Assets);
assert_eq!(h.panel_order.last().copied(), Some(PanelKey::Assets));
assert_eq!(h.panel_order.len(), panels, "no duplicates");
h.focus_panel(PanelKey::Assets);
assert_eq!(h.panel_order.last().copied(), Some(PanelKey::Assets));
assert_eq!(h.panel_order.len(), panels);
}
#[test]
fn publish_layers_ranks_panels_below_the_top_bar() {
let mut h = hook(Vec::new());
h.focus_panel(PanelKey::Edit); let layers = h.compute_layers();
let layer = |id| *layers.get(&id).expect("id mapped");
let edit = layer(form_panel::EDIT_BG);
let assets = layer(assets_panel::PANEL_BG);
let preview = layer(preview::PANEL_BG);
assert!(
edit > assets && edit > preview,
"the frontmost panel outranks the others"
);
assert!(
layer(hud::SAVE_BUTTON) > edit,
"the top bar sits above every panel"
);
assert_eq!(layer(form_panel::NAME_INPUT), edit);
}
#[test]
fn a_panel_press_brings_it_to_the_front() {
let mut h = hook(vec![entry("lamp", "PointLight")]);
let mut world = world_with_fields();
h.open[PanelKey::Assets] = true;
let vp = [1280.0, 720.0];
let po = h.origin(PanelKey::Assets, vp);
let t = widget::title_rect(po, assets_panel::PANEL_W);
let claimed = h.try_panel_press(
PanelKey::Assets,
t[0] + t[2] * 0.5,
t[1] + t[3] * 0.5,
vp,
&mut world,
);
assert!(claimed, "the press was claimed by the Assets panel");
assert_eq!(h.panel_order.last().copied(), Some(PanelKey::Assets));
assert!(h.drag.is_some(), "a title-bar press starts a drag");
}
#[test]
fn edit_form_title_bar_x_closes_the_form() {
let mut h = hook(vec![entry("lamp", "PointLight")]);
let mut world = world_with_fields();
h.open[PanelKey::Assets] = true;
let target = entry_target(&h, 0);
h.open_form(&mut world, "PointLight".to_string(), target);
assert!(h.form_open());
let vp = [1280.0, 720.0];
let x = form_panel::close_rect(h.origin(PanelKey::Edit, vp), form_panel::EDIT_W);
let claimed = h.try_panel_press(PanelKey::Edit, x[0] + 5.0, x[1] + 5.0, vp, &mut world);
assert!(claimed, "the X press was claimed");
assert!(!h.form_open(), "the X closed the form");
assert!(h.drag.is_none(), "the X did not start a drag");
}
#[test]
fn every_panel_title_bar_x_closes_it() {
let vp = [1280.0, 720.0];
let mut world = world_with_fields();
let mut h = hook(Vec::new());
let px = close_rect_of(&h, PanelKey::Preview, vp);
assert!(h.try_panel_press(PanelKey::Preview, px[0] + 5.0, px[1] + 5.0, vp, &mut world));
assert!(
!h.open[PanelKey::Preview] && h.drag.is_none(),
"Preview X closed it, no drag"
);
let mut h = hook(Vec::new());
h.open[PanelKey::Assets] = true;
let ax = close_rect_of(&h, PanelKey::Assets, vp);
assert!(h.try_panel_press(PanelKey::Assets, ax[0] + 5.0, ax[1] + 5.0, vp, &mut world));
assert!(
!h.open[PanelKey::Assets] && h.drag.is_none(),
"Assets X closed it, no drag"
);
let mut h = hook(Vec::new());
h.open[PanelKey::View] = true;
let vx = close_rect_of(&h, PanelKey::View, vp);
assert!(h.try_panel_press(PanelKey::View, vx[0] + 5.0, vx[1] + 5.0, vp, &mut world));
assert!(
!h.open[PanelKey::View] && h.drag.is_none(),
"View X closed it, no drag"
);
let mut h = hook(Vec::new());
h.open[PanelKey::Templates] = true;
let tx = close_rect_of(&h, PanelKey::Templates, vp);
assert!(h.try_panel_press(
PanelKey::Templates,
tx[0] + 5.0,
tx[1] + 5.0,
vp,
&mut world
));
assert!(
!h.open[PanelKey::Templates] && h.drag.is_none(),
"Templates X closed it, no drag"
);
}
#[test]
fn a_hidden_panel_is_not_interactive() {
let mut h = hook(Vec::new());
let vp = [1280.0, 720.0];
let mut world = world_with_fields();
let pt = title_rect_of(&h, PanelKey::Preview, vp);
assert!(h.try_panel_press(PanelKey::Preview, pt[0] + 5.0, pt[1] + 5.0, vp, &mut world));
h.drag = None;
h.open[PanelKey::Preview] = false;
assert!(!h.try_panel_press(PanelKey::Preview, pt[0] + 5.0, pt[1] + 5.0, vp, &mut world));
let vt = title_rect_of(&h, PanelKey::View, vp);
assert!(!h.try_panel_press(PanelKey::View, vt[0] + 5.0, vt[1] + 5.0, vp, &mut world));
h.open[PanelKey::View] = true;
assert!(h.try_panel_press(PanelKey::View, vt[0] + 5.0, vt[1] + 5.0, vp, &mut world));
}
#[test]
fn the_behavior_panel_leaves_the_other_panels_pressable_in_every_view() {
let (mut h, mut world) = behavior_session(vec![behavior(
"chase",
serde_json::json!({"on": "tick", "do": [{"hide": {"target": "self"}}]}),
)]);
let vp = [1280.0, 720.0];
h.open[PanelKey::View] = true;
h.focus_panel(PanelKey::Behavior);
for mode in [ViewMode::Outline, ViewMode::Chart, ViewMode::Overview] {
h.behavior.mode = mode;
for key in [PanelKey::Preview, PanelKey::View] {
let title = title_rect_of(&h, key, vp);
let (mx, my) = (title[0] + 5.0, title[1] + 5.0);
assert!(
!h.try_panel_press(PanelKey::Behavior, mx, my, vp, &mut world),
"{mode:?}: Behavior swallowed a press meant for {key:?}"
);
h.drag = None;
assert!(
h.try_panel_press(key, mx, my, vp, &mut world),
"{mode:?}: {key:?} never saw the press"
);
h.drag = None;
h.focus_panel(PanelKey::Behavior);
}
}
}
#[test]
fn templates_panel_press_drags_and_focuses() {
let mut h = hook(Vec::new());
h.open[PanelKey::Templates] = true;
let vp = [1280.0, 720.0];
let mut world = world_with_fields();
let t = title_rect_of(&h, PanelKey::Templates, vp);
assert!(h.try_panel_press(
PanelKey::Templates,
t[0] + t[2] * 0.5,
t[1] + t[3] * 0.5,
vp,
&mut world
));
assert!(h.drag.is_some(), "a title-bar press starts a drag");
assert_eq!(h.panel_order.last().copied(), Some(PanelKey::Templates));
}
#[test]
fn tick_lays_out_the_open_panel_in_every_state() {
let sprite_visible = |w: &World, id: AssetId| w.get_by_id::<Sprite>(id).unwrap().visible;
let label = |w: &World, id: AssetId| w.get_by_id::<TextLabel>(id).unwrap().clone();
let mut world = World::new();
inject::editor_hud(&mut world);
world.add_component(FrameInput {
viewport: [1280.0, 720.0],
mouse_x: 1200.0,
mouse_y: 300.0,
..Default::default()
});
let mut h = hook(vec![entry("a", "PointLight"), entry("b", "Decal")]);
h.open[PanelKey::Assets] = true;
seed_tree(&mut h, Vec::new());
h.tick(&mut world);
assert!(
sprite_visible(&world, assets_panel::PANEL_BG),
"panel bg shown"
);
let row0 = label(&world, assets_panel::name_label(0));
assert!(
row0.visible && row0.content.starts_with("- World"),
"first row is the World group header, got {:?}",
row0.content
);
h.assets.picker_open = true;
h.tick(&mut world);
assert!(
sprite_visible(&world, assets_panel::PICKER_BG),
"picker backing shown"
);
assert!(
world
.get_by_id::<TextInput>(assets_panel::SEARCH_INPUT)
.unwrap()
.visible
);
h.assets.picker_open = false;
h.assets.row_menu = Some(h.handle_for("a"));
h.tick(&mut world);
assert!(
sprite_visible(&world, assets_panel::MENU_BG),
"row menu shown"
);
assert_eq!(
label(&world, assets_panel::MENU_DELETE_LABEL).content,
"Delete"
);
h.assets.row_menu = None;
h.open_form(&mut world, "PointLight".to_string(), FormTarget::New);
h.tick(&mut world);
assert!(
sprite_visible(&world, form_panel::APPLY_BG),
"confirm button shown"
);
assert_eq!(
label(&world, form_panel::TITLE_LABEL).content,
"New PointLight"
);
assert_eq!(label(&world, form_panel::APPLY_LABEL).content, "Add");
assert!(
world
.get_by_id::<TextInput>(form_panel::NAME_INPUT)
.unwrap()
.visible,
"the name heading shows"
);
assert!(
label(&world, assets_panel::name_label(0)).visible,
"the tree stays visible beside the form"
);
h.open[PanelKey::Assets] = false;
h.form.close();
h.tick(&mut world);
assert!(
!sprite_visible(&world, assets_panel::PANEL_BG),
"panel bg hidden"
);
assert!(
!sprite_visible(&world, form_panel::EDIT_BG),
"form panel hidden"
);
}
#[test]
fn scroll_moves_each_regions_offset() {
let world = world_with_fields();
let mut h = hook(
(0..20)
.map(|i| entry(&format!("log{i}"), "TextLabel"))
.collect(),
);
seed_tree(&mut h, Vec::new());
h.assets.row_menu = Some(h.handle_for("log0"));
h.scroll_tree(1.0, &world);
assert!(h.assets.scroll > 0, "a closed picker scrolls the tree");
assert!(
h.assets.row_menu.is_none(),
"scrolling dismisses an open row menu"
);
h.scroll_tree(-1.0, &world);
assert_eq!(h.assets.scroll, 0, "scrolling back up clamps at the top");
h.assets.picker_open = true;
let before = h.assets.scroll;
h.scroll_tree(1.0, &world);
assert_eq!(
h.assets.scroll, before,
"the tree stays put while the picker is open"
);
assert!(h.assets.picker_scroll > 0, "the picker's own list scrolled");
h.assets.picker_open = false;
h.templates.detail = Some(0);
h.scroll_template_list(1.0);
}
#[test]
fn drive_drag_parks_each_secondary_panel() {
let vp = [1280.0, 720.0];
let held = FrameInput {
left_button_down: true,
mouse_x: 220.0,
mouse_y: 160.0,
..Default::default()
};
let mut h = hook(Vec::new());
h.drag = Some(Drag {
key: PanelKey::View,
grab: [10.0, 10.0],
});
h.drive_drag(&held, vp);
assert!(
h.positions[PanelKey::View].is_some(),
"the View panel follows the cursor"
);
let mut h = hook(Vec::new());
h.drag = Some(Drag {
key: PanelKey::Templates,
grab: [10.0, 10.0],
});
h.drive_drag(&held, vp);
assert!(
h.positions[PanelKey::Templates].is_some(),
"the Templates panel follows"
);
let mut h = hook(Vec::new());
h.templates.detail = Some(0);
h.drag = Some(Drag {
key: PanelKey::TemplateDetail,
grab: [10.0, 10.0],
});
h.drive_drag(&held, vp);
assert!(
h.positions[PanelKey::TemplateDetail].is_some(),
"the Template detail panel follows"
);
}
#[test]
fn an_open_overlay_layers_above_its_panel_and_below_the_one_in_front() {
let (mut h, mut world) = behavior_session(vec![behavior(
"chase",
serde_json::json!({"on": "tick", "do": [{"save": {}}]}),
)]);
let flat = h.compute_layers();
let panel_layer = flat[&behavior::panel::PANEL_BG];
assert_eq!(flat[&behavior::panel::DROP_BG], panel_layer);
select_behavior(&mut h, &mut world, "do");
h.apply_behavior_action(BehaviorAction::Palette, &mut world, [0.0, 0.0]);
let open = h.compute_layers();
for id in behavior::panel::palette_ids() {
assert!(
open[&id] > open[&behavior::panel::PANEL_BG],
"{id:?} does not draw above its own panel",
);
assert!(
open[&id] > open[&behavior::panel::row_label(0)],
"{id:?} does not draw above the rows it covers",
);
}
h.focus_panel(PanelKey::Preview);
let stacked = h.compute_layers();
let front = stacked[&preview::PANEL_BG];
for id in behavior::panel::palette_ids() {
assert!(stacked[&id] < front, "{id:?} escaped above the front panel");
}
}