623 lines
25 KiB
Rust
623 lines
25 KiB
Rust
//! Unified viewport overlay chrome (toolbars, badges, HUD).
|
|
|
|
use bevy::prelude::*;
|
|
use bevy_egui::{egui, EguiTextureHandle, EguiUserTextures};
|
|
use bevy_inspector_egui::bevy_inspector::hierarchy::SelectedEntities;
|
|
use egui_phosphor_icons::icons;
|
|
use transform_gizmo_bevy::prelude::GizmoOptions;
|
|
|
|
use crate::assets::operators::{
|
|
apply_material_operator, apply_texture_operator, place_asset_operator, place_subasset_operator,
|
|
};
|
|
use crate::assets::{AssetSelection, AssetSubAssetKind, EditorAssets};
|
|
use crate::gizmos::{EditorGizmoMode, EditorGizmoSpace};
|
|
use crate::render_target::ViewportRenderTarget;
|
|
use crate::selection::ViewportClick;
|
|
use crate::state::PlayPossession;
|
|
use crate::viewport::actor_icons::ActorIconSettings;
|
|
use crate::viewport::brush_edit::BrushEditMode;
|
|
use crate::viewport::{
|
|
snap_translation, viewport_ground_position, EditorViewportMode, ViewportDisplayMode,
|
|
ViewportSettings,
|
|
};
|
|
|
|
use super::helpers::{
|
|
focus_editor_camera_on_selection, viewport_recall_bookmark, viewport_save_bookmark,
|
|
};
|
|
use super::theme::viewport_toolbar_frame;
|
|
use super::widgets::{icon_button_small, panel_toolbar_row, tool_button};
|
|
|
|
#[derive(Resource, Default)]
|
|
pub struct ViewportUiState {
|
|
pub options_open: bool,
|
|
pub shortcuts_open: bool,
|
|
}
|
|
|
|
const VIEWPORT_TOOLTIP: &str = "RMB + WASD/QE: fly | MMB: pan | Scroll: dolly\nW/E/R: gizmo | X: world/local | F: focus | G: game view | Ctrl+G: grid\nTab: cycle overlapping picks | Play/Pause/Stop: main toolbar (F5 / F6)";
|
|
|
|
pub fn viewport_tab_ui(
|
|
world: &mut World,
|
|
ui: &mut egui::Ui,
|
|
viewport_rect: &mut egui::Rect,
|
|
pointer_in_viewport: &mut bool,
|
|
viewport_pointer_pos: &mut Option<egui::Pos2>,
|
|
selected_entities: &SelectedEntities,
|
|
playing: bool,
|
|
_cursor_grabbed: bool,
|
|
) {
|
|
let rect = ui.available_rect_before_wrap();
|
|
*viewport_rect = rect;
|
|
|
|
if rect.width() <= 1.0 || rect.height() <= 1.0 {
|
|
*pointer_in_viewport = false;
|
|
*viewport_pointer_pos = None;
|
|
return;
|
|
}
|
|
|
|
let clean_game_view = world.resource::<ViewportDisplayMode>().clean_game_view;
|
|
let response = ui.allocate_rect(rect, egui::Sense::HOVER);
|
|
*viewport_pointer_pos = response
|
|
.interact_pointer_pos()
|
|
.or_else(|| response.hover_pos())
|
|
.filter(|pos| rect.contains(*pos));
|
|
*pointer_in_viewport = viewport_pointer_pos.is_some();
|
|
|
|
let primary_clicked = ui.input(|input| {
|
|
input.pointer.button_clicked(egui::PointerButton::Primary) && !input.modifiers.alt
|
|
});
|
|
if primary_clicked && !clean_game_view {
|
|
if let Some(pos) = response
|
|
.interact_pointer_pos()
|
|
.or_else(|| response.hover_pos())
|
|
.filter(|pos| rect.contains(*pos))
|
|
{
|
|
world.resource_mut::<ViewportClick>().0 = Some(pos);
|
|
}
|
|
}
|
|
|
|
if let Some(target) = world
|
|
.get_resource::<ViewportRenderTarget>()
|
|
.and_then(|target| target.0.clone())
|
|
{
|
|
let mut textures = world.resource_mut::<EguiUserTextures>();
|
|
let texture_id = textures
|
|
.image_id(target.image.id())
|
|
.unwrap_or_else(|| textures.add_image(EguiTextureHandle::Strong(target.image.clone())));
|
|
|
|
let painter = ui.painter();
|
|
painter.rect_filled(rect, 0.0, egui::Color32::BLACK);
|
|
painter.image(
|
|
texture_id,
|
|
rect,
|
|
egui::Rect::from_min_max(egui::pos2(0.0, 0.0), egui::pos2(1.0, 1.0)),
|
|
egui::Color32::WHITE,
|
|
);
|
|
} else {
|
|
ui.allocate_rect(rect, egui::Sense::hover());
|
|
ui.scope_builder(egui::UiBuilder::new().max_rect(rect), |ui| {
|
|
ui.centered_and_justified(|ui| {
|
|
ui.label(egui::RichText::new("Viewport").weak());
|
|
});
|
|
});
|
|
}
|
|
|
|
if clean_game_view {
|
|
if playing {
|
|
game_view_overlay(world, ui.ctx(), rect, playing);
|
|
}
|
|
return;
|
|
}
|
|
|
|
scene_view_overlay_toolbar(world, ui.ctx(), rect);
|
|
scene_view_mode_badge(world, ui.ctx(), rect);
|
|
scene_view_brush_mode_badge(world, ui.ctx(), rect);
|
|
scene_view_gi_badge(world, ui.ctx(), rect);
|
|
scene_view_volume_hud(world, ui.ctx(), rect);
|
|
viewport_asset_drop_ui(world, ui, rect, selected_entities);
|
|
}
|
|
|
|
fn scene_view_mode_badge(world: &World, ctx: &egui::Context, scene_rect: egui::Rect) {
|
|
let Some(mode) = world.get_resource::<EditorViewportMode>() else {
|
|
return;
|
|
};
|
|
let mode = *mode;
|
|
let (label, color) = match mode {
|
|
EditorViewportMode::Lit => ("Lit", egui::Color32::from_rgb(120, 200, 120)),
|
|
EditorViewportMode::Unlit => ("Unlit", egui::Color32::from_rgb(200, 200, 120)),
|
|
EditorViewportMode::Collider => ("Collider", egui::Color32::from_rgb(220, 140, 80)),
|
|
};
|
|
|
|
egui::Area::new(egui::Id::new("scene_view_mode_badge"))
|
|
.fixed_pos(scene_rect.right_bottom() + egui::vec2(-72.0, -22.0))
|
|
.interactable(false)
|
|
.show(ctx, |ui| {
|
|
egui::Frame::NONE
|
|
.fill(egui::Color32::from_black_alpha(160))
|
|
.inner_margin(egui::Margin::symmetric(8, 4))
|
|
.show(ui, |ui| {
|
|
ui.colored_label(color, label);
|
|
});
|
|
});
|
|
|
|
if mode == EditorViewportMode::Collider {
|
|
egui::Area::new(egui::Id::new("scene_view_collider_warning"))
|
|
.fixed_pos(scene_rect.left_bottom() + egui::vec2(8.0, -28.0))
|
|
.interactable(false)
|
|
.show(ctx, |ui| {
|
|
ui.colored_label(
|
|
egui::Color32::from_rgb(255, 180, 100),
|
|
"Meshes hidden - switch to Lit (sun icon) for shading",
|
|
);
|
|
});
|
|
}
|
|
}
|
|
|
|
fn scene_view_brush_mode_badge(world: &World, ctx: &egui::Context, scene_rect: egui::Rect) {
|
|
let Some(mode) = world.get_resource::<BrushEditMode>().copied() else {
|
|
return;
|
|
};
|
|
if !mode.is_element_mode() {
|
|
return;
|
|
}
|
|
egui::Area::new(egui::Id::new("scene_view_brush_mode_badge"))
|
|
.fixed_pos(scene_rect.right_bottom() + egui::vec2(-150.0, -22.0))
|
|
.interactable(false)
|
|
.show(ctx, |ui| {
|
|
egui::Frame::new()
|
|
.fill(egui::Color32::from_rgba_unmultiplied(35, 44, 50, 220))
|
|
.corner_radius(egui::CornerRadius::same(3))
|
|
.inner_margin(egui::Margin::symmetric(6, 2))
|
|
.show(ui, |ui| {
|
|
ui.label(
|
|
egui::RichText::new(format!("Brush: {}", mode.label()))
|
|
.color(egui::Color32::from_rgb(170, 220, 255))
|
|
.small(),
|
|
);
|
|
});
|
|
});
|
|
}
|
|
|
|
fn scene_view_gi_badge(world: &World, ctx: &egui::Context, scene_rect: egui::Rect) {
|
|
let Some(profile) = world.get_resource::<settings::ActiveCameraRenderProfile>() else {
|
|
return;
|
|
};
|
|
let (label, color) = match profile.gi_path {
|
|
settings::GiPath::Forward => ("Forward", egui::Color32::from_rgb(140, 180, 255)),
|
|
settings::GiPath::SolariDeferred => ("Solari", egui::Color32::from_rgb(255, 170, 90)),
|
|
};
|
|
egui::Area::new(egui::Id::new("scene_view_gi_badge"))
|
|
.fixed_pos(scene_rect.right_bottom() + egui::vec2(-148.0, -22.0))
|
|
.interactable(false)
|
|
.show(ctx, |ui| {
|
|
egui::Frame::NONE
|
|
.fill(egui::Color32::from_black_alpha(160))
|
|
.inner_margin(egui::Margin::symmetric(8, 4))
|
|
.show(ui, |ui| {
|
|
ui.colored_label(color, label);
|
|
});
|
|
});
|
|
}
|
|
|
|
fn scene_view_volume_hud(world: &World, ctx: &egui::Context, scene_rect: egui::Rect) {
|
|
let Some(contributions) = world.get_resource::<settings::ActiveVolumeContribution>() else {
|
|
return;
|
|
};
|
|
let Some(primary) = contributions.entries.first() else {
|
|
return;
|
|
};
|
|
egui::Area::new(egui::Id::new("scene_view_volume_hud"))
|
|
.fixed_pos(scene_rect.left_bottom() + egui::vec2(8.0, -56.0))
|
|
.interactable(false)
|
|
.show(ctx, |ui| {
|
|
egui::Frame::NONE
|
|
.fill(egui::Color32::from_black_alpha(150))
|
|
.inner_margin(egui::Margin::symmetric(8, 4))
|
|
.show(ui, |ui| {
|
|
ui.label(format!(
|
|
"Volume: {} ({:.0}%, {} overrides)",
|
|
primary.label,
|
|
primary.weight * 100.0,
|
|
primary.override_count
|
|
));
|
|
});
|
|
});
|
|
}
|
|
|
|
fn scene_view_overlay_toolbar(world: &mut World, ctx: &egui::Context, scene_rect: egui::Rect) {
|
|
let mut options_open = world.resource::<ViewportUiState>().options_open;
|
|
let mut shortcuts_open = world.resource::<ViewportUiState>().shortcuts_open;
|
|
|
|
egui::Area::new(egui::Id::new("scene_view_toolbar"))
|
|
.fixed_pos(scene_rect.left_top() + egui::vec2(6.0, 6.0))
|
|
.interactable(true)
|
|
.show(ctx, |ui| {
|
|
viewport_toolbar_frame().show(ui, |ui| {
|
|
panel_toolbar_row(ui, |ui| {
|
|
let gizmo_mode = *world.resource::<crate::gizmos::EditorGizmoMode>();
|
|
if tool_button(
|
|
ui,
|
|
icons::ARROWS_OUT_CARDINAL,
|
|
gizmo_mode == EditorGizmoMode::Translate,
|
|
"Translate (W)",
|
|
)
|
|
.clicked()
|
|
{
|
|
super::helpers::set_gizmo_mode(world, EditorGizmoMode::Translate);
|
|
}
|
|
if tool_button(
|
|
ui,
|
|
icons::ARROWS_CLOCKWISE,
|
|
gizmo_mode == EditorGizmoMode::Rotate,
|
|
"Rotate (E)",
|
|
)
|
|
.clicked()
|
|
{
|
|
super::helpers::set_gizmo_mode(world, EditorGizmoMode::Rotate);
|
|
}
|
|
if tool_button(
|
|
ui,
|
|
icons::ARROWS_IN_LINE_HORIZONTAL,
|
|
gizmo_mode == EditorGizmoMode::Scale,
|
|
"Scale (R)",
|
|
)
|
|
.clicked()
|
|
{
|
|
super::helpers::set_gizmo_mode(world, EditorGizmoMode::Scale);
|
|
}
|
|
|
|
ui.separator();
|
|
|
|
let space = *world.resource::<EditorGizmoSpace>();
|
|
if tool_button(
|
|
ui,
|
|
icons::GLOBE,
|
|
space == EditorGizmoSpace::World,
|
|
"World space (X)",
|
|
)
|
|
.clicked()
|
|
{
|
|
super::helpers::set_gizmo_space(world, EditorGizmoSpace::World);
|
|
}
|
|
if tool_button(
|
|
ui,
|
|
icons::CUBE,
|
|
space == EditorGizmoSpace::Local,
|
|
"Local space (X)",
|
|
)
|
|
.clicked()
|
|
{
|
|
super::helpers::set_gizmo_space(world, EditorGizmoSpace::Local);
|
|
}
|
|
|
|
ui.separator();
|
|
|
|
let viewport_mode = *world.resource::<EditorViewportMode>();
|
|
if tool_button(
|
|
ui,
|
|
icons::SUN,
|
|
viewport_mode == EditorViewportMode::Lit,
|
|
"Lit shading (WYSIWYG)",
|
|
)
|
|
.clicked()
|
|
{
|
|
*world.resource_mut::<EditorViewportMode>() = EditorViewportMode::Lit;
|
|
}
|
|
if tool_button(
|
|
ui,
|
|
icons::PAINT_BRUSH,
|
|
viewport_mode == EditorViewportMode::Unlit,
|
|
"Unlit base color (debug albedo)",
|
|
)
|
|
.clicked()
|
|
{
|
|
*world.resource_mut::<EditorViewportMode>() = EditorViewportMode::Unlit;
|
|
}
|
|
if tool_button(
|
|
ui,
|
|
icons::BOUNDING_BOX,
|
|
viewport_mode == EditorViewportMode::Collider,
|
|
"Collider view (hide meshes)",
|
|
)
|
|
.clicked()
|
|
{
|
|
*world.resource_mut::<EditorViewportMode>() = EditorViewportMode::Collider;
|
|
}
|
|
|
|
ui.separator();
|
|
|
|
if let Some(mut settings) = world.get_resource_mut::<ViewportSettings>() {
|
|
let grid = settings.show_grid;
|
|
if tool_button(ui, icons::GRID_FOUR, grid, "Toggle grid (Ctrl+G)").clicked()
|
|
{
|
|
settings.show_grid = !settings.show_grid;
|
|
}
|
|
let snap = settings.snap_translate;
|
|
if tool_button(ui, icons::MAGNET, snap, "Snap translation").clicked() {
|
|
settings.snap_translate = !settings.snap_translate;
|
|
}
|
|
}
|
|
|
|
if let Some(mut visualizers) =
|
|
world.get_resource_mut::<crate::visualizers::EditorVisualizationSettings>()
|
|
{
|
|
let enabled = visualizers.enabled;
|
|
if tool_button(ui, icons::EYE, enabled, "Toggle scene visualizers")
|
|
.clicked()
|
|
{
|
|
visualizers.enabled = !visualizers.enabled;
|
|
}
|
|
}
|
|
|
|
let clean_game_view = world.resource::<ViewportDisplayMode>().clean_game_view;
|
|
if tool_button(
|
|
ui,
|
|
icons::GAME_CONTROLLER,
|
|
clean_game_view,
|
|
"Game view overlay (G)",
|
|
)
|
|
.clicked()
|
|
{
|
|
world.resource_mut::<ViewportDisplayMode>().clean_game_view =
|
|
!clean_game_view;
|
|
}
|
|
|
|
if icon_button_small(ui, icons::CROSSHAIR, "Focus selection (F)").clicked() {
|
|
focus_editor_camera_on_selection(world);
|
|
}
|
|
|
|
if icon_button_small(ui, icons::GEAR, "Viewport options").clicked() {
|
|
options_open = !options_open;
|
|
}
|
|
if icon_button_small(ui, icons::QUESTION, VIEWPORT_TOOLTIP).clicked() {
|
|
shortcuts_open = !shortcuts_open;
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
if options_open {
|
|
scene_options_popover(world, ctx, scene_rect, &mut options_open);
|
|
}
|
|
if shortcuts_open {
|
|
viewport_shortcuts_window(ctx, &mut shortcuts_open);
|
|
}
|
|
|
|
world.resource_mut::<ViewportUiState>().options_open = options_open;
|
|
world.resource_mut::<ViewportUiState>().shortcuts_open = shortcuts_open;
|
|
}
|
|
|
|
fn scene_options_popover(
|
|
world: &mut World,
|
|
ctx: &egui::Context,
|
|
scene_rect: egui::Rect,
|
|
open: &mut bool,
|
|
) {
|
|
egui::Area::new(egui::Id::new("scene_view_options"))
|
|
.fixed_pos(scene_rect.left_top() + egui::vec2(6.0, 44.0))
|
|
.show(ctx, |ui| {
|
|
viewport_toolbar_frame().show(ui, |ui| {
|
|
ui.set_min_width(220.0);
|
|
ui.heading("Viewport Options");
|
|
if let Some(mut display) = world.get_resource_mut::<ViewportDisplayMode>() {
|
|
ui.checkbox(&mut display.clean_game_view, "Game view overlay");
|
|
ui.small("G toggles game view; Ctrl+G toggles grid");
|
|
}
|
|
if let Some(mut settings) = world.get_resource_mut::<ViewportSettings>() {
|
|
ui.separator();
|
|
ui.checkbox(&mut settings.show_grid, "Show grid");
|
|
ui.checkbox(&mut settings.snap_translate, "Snap translation");
|
|
ui.add(
|
|
egui::Slider::new(&mut settings.snap_increment, 0.25..=4.0)
|
|
.text("Snap step"),
|
|
);
|
|
}
|
|
if let Some(mut gizmo_options) = world.get_resource_mut::<GizmoOptions>() {
|
|
ui.separator();
|
|
ui.add(
|
|
egui::Slider::new(&mut gizmo_options.visuals.gizmo_size, 40.0..=160.0)
|
|
.text("Gizmo size"),
|
|
);
|
|
}
|
|
if let Some(mut mode) = world.get_resource_mut::<EditorViewportMode>() {
|
|
ui.separator();
|
|
ui.label("Shading");
|
|
ui.horizontal(|ui| {
|
|
ui.selectable_value(&mut *mode, EditorViewportMode::Lit, "Lit");
|
|
ui.selectable_value(&mut *mode, EditorViewportMode::Unlit, "Unlit");
|
|
ui.selectable_value(&mut *mode, EditorViewportMode::Collider, "Colliders");
|
|
});
|
|
}
|
|
let mut actor_icons_active = false;
|
|
if let Some(mut visualizers) =
|
|
world.get_resource_mut::<crate::visualizers::EditorVisualizationSettings>()
|
|
{
|
|
ui.separator();
|
|
ui.checkbox(&mut visualizers.enabled, "Show visualizers");
|
|
ui.add_enabled_ui(visualizers.enabled, |ui| {
|
|
ui.checkbox(&mut visualizers.show_actor_icons, "Actor root icons");
|
|
ui.add_enabled_ui(visualizers.show_actor_icons, |ui| {
|
|
ui.horizontal_wrapped(|ui| {
|
|
ui.checkbox(&mut visualizers.show_actor_icon_actor, "Actors");
|
|
ui.checkbox(&mut visualizers.show_actor_icon_mesh, "Meshes");
|
|
ui.checkbox(&mut visualizers.show_actor_icon_light, "Lights");
|
|
ui.checkbox(&mut visualizers.show_actor_icon_physics, "Physics");
|
|
ui.checkbox(&mut visualizers.show_actor_icon_gameplay, "Gameplay");
|
|
ui.checkbox(&mut visualizers.show_actor_icon_volume, "Volumes");
|
|
ui.checkbox(
|
|
&mut visualizers.show_actor_icon_prefab_model,
|
|
"Prefabs/models",
|
|
);
|
|
ui.checkbox(&mut visualizers.show_actor_icon_debug, "Warnings");
|
|
});
|
|
});
|
|
actor_icons_active = visualizers.enabled && visualizers.show_actor_icons;
|
|
ui.separator();
|
|
ui.checkbox(&mut visualizers.show_colliders, "Colliders");
|
|
ui.checkbox(&mut visualizers.show_lights, "Lights");
|
|
ui.checkbox(&mut visualizers.show_spawns, "Player spawns");
|
|
ui.checkbox(
|
|
&mut visualizers.show_prefab_model_anchors,
|
|
"Prefab/model anchors",
|
|
);
|
|
ui.checkbox(&mut visualizers.show_runtime, "Runtime player/cameras");
|
|
ui.checkbox(&mut visualizers.pickable_proxies, "Selectable proxies");
|
|
});
|
|
}
|
|
if actor_icons_active {
|
|
if let Some(mut icon_settings) = world.get_resource_mut::<ActorIconSettings>() {
|
|
ui.add(
|
|
egui::Slider::new(
|
|
&mut icon_settings.size_pixels,
|
|
ActorIconSettings::MIN_SIZE_PIXELS
|
|
..=ActorIconSettings::MAX_SIZE_PIXELS,
|
|
)
|
|
.text("Icon size"),
|
|
);
|
|
}
|
|
}
|
|
ui.horizontal(|ui| {
|
|
if ui.button("Save bookmark").clicked() {
|
|
viewport_save_bookmark(world);
|
|
}
|
|
if ui.button("Recall bookmark").clicked() {
|
|
viewport_recall_bookmark(world);
|
|
}
|
|
});
|
|
ui.small("Ctrl+Shift+1 save | Ctrl+Shift+2 recall");
|
|
if ui.button("Close").clicked() {
|
|
*open = false;
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
fn viewport_shortcuts_window(ctx: &egui::Context, open: &mut bool) {
|
|
egui::Window::new("Viewport Shortcuts")
|
|
.open(open)
|
|
.default_width(320.0)
|
|
.show(ctx, |ui| {
|
|
ui.label("Viewport navigation");
|
|
ui.small("RMB + WASD/QE - fly camera");
|
|
ui.small("MMB drag - pan");
|
|
ui.small("Scroll - dolly");
|
|
ui.separator();
|
|
ui.label("Tools");
|
|
ui.small("W / E / R - translate / rotate / scale gizmo");
|
|
ui.small("X - toggle world / local space");
|
|
ui.small("G - toggle game view overlay");
|
|
ui.small("Ctrl+G - toggle grid");
|
|
ui.small("F - focus selection");
|
|
ui.small("Tab - cycle overlapping selection at click");
|
|
ui.small("F5 - play / stop (main toolbar)");
|
|
ui.small("F6 - pause / resume (while playing)");
|
|
ui.small("F8 - possess / eject (while playing)");
|
|
ui.small("Esc - deselect / free cursor");
|
|
});
|
|
}
|
|
|
|
fn game_view_overlay(world: &World, ctx: &egui::Context, rect: egui::Rect, playing: bool) {
|
|
egui::Area::new(egui::Id::new("game_view_hud"))
|
|
.fixed_pos(rect.left_top() + egui::vec2(6.0, 6.0))
|
|
.interactable(false)
|
|
.show(ctx, |ui| {
|
|
viewport_toolbar_frame().show(ui, |ui| {
|
|
if !playing {
|
|
ui.label(egui::RichText::new("Game View").strong());
|
|
ui.small("Press F5 or Play to run here.");
|
|
return;
|
|
}
|
|
|
|
ui.label(egui::RichText::new("Playing").strong());
|
|
let possession = *world.resource::<PlayPossession>();
|
|
let possess_label = match possession {
|
|
PlayPossession::Possessed => "Possessed",
|
|
PlayPossession::Ejected => "Ejected (Scene camera)",
|
|
};
|
|
ui.small(possess_label);
|
|
ui.small("F5 stop | F6 pause | F8 possess/eject | Esc free cursor");
|
|
});
|
|
});
|
|
}
|
|
|
|
fn viewport_asset_drop_ui(
|
|
world: &mut World,
|
|
ui: &mut egui::Ui,
|
|
viewport_rect: egui::Rect,
|
|
selected: &SelectedEntities,
|
|
) {
|
|
let pointer_released = ui.input(|input| input.pointer.any_released());
|
|
if !pointer_released {
|
|
return;
|
|
}
|
|
|
|
let pointer_pos = ui.input(|input| input.pointer.hover_pos());
|
|
if !pointer_pos.is_some_and(|pos| viewport_rect.contains(pos)) {
|
|
world.resource_mut::<EditorAssets>().clear_drag();
|
|
return;
|
|
}
|
|
|
|
let selection = {
|
|
let assets = world.resource::<EditorAssets>();
|
|
assets.dragging_selection().cloned()
|
|
};
|
|
world.resource_mut::<EditorAssets>().clear_drag();
|
|
|
|
let Some(selection) = selection else {
|
|
return;
|
|
};
|
|
let placement = viewport_ground_position(world, pointer_pos.unwrap(), viewport_rect)
|
|
.map(|pos| snap_translation(pos, world.resource::<ViewportSettings>()))
|
|
.unwrap_or(Vec3::ZERO);
|
|
if matches!(
|
|
selection,
|
|
AssetSelection::SubAsset {
|
|
kind: AssetSubAssetKind::Mesh,
|
|
..
|
|
}
|
|
) {
|
|
place_subasset_operator(world, selection, placement);
|
|
return;
|
|
}
|
|
|
|
if let AssetSelection::SubAsset {
|
|
kind: AssetSubAssetKind::Texture,
|
|
source_path: Some(path),
|
|
label,
|
|
..
|
|
} = &selection
|
|
{
|
|
let asset = crate::assets::EditorAsset {
|
|
label: label.clone(),
|
|
path: Some(path.clone()),
|
|
folder_path: std::path::Path::new(path)
|
|
.parent()
|
|
.map(|parent| parent.to_string_lossy().replace('\\', "/"))
|
|
.unwrap_or_else(|| crate::assets::ASSETS_ROOT.to_string()),
|
|
kind: crate::assets::EditorAssetKind::Texture,
|
|
};
|
|
apply_texture_operator(world, asset, selected);
|
|
return;
|
|
}
|
|
|
|
let asset = {
|
|
let assets = world.resource::<EditorAssets>();
|
|
assets.asset_for_selection(&selection).cloned()
|
|
};
|
|
let Some(asset) = asset else {
|
|
return;
|
|
};
|
|
match asset.kind {
|
|
crate::assets::EditorAssetKind::Texture => {
|
|
apply_texture_operator(world, asset, selected);
|
|
return;
|
|
}
|
|
crate::assets::EditorAssetKind::Material => {
|
|
apply_material_operator(world, asset, selected);
|
|
return;
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
place_asset_operator(world, asset, placement);
|
|
}
|