use super::selection::EntitySelection;
use super::undo::UndoHistory;
use crate::ecs::gizmos::{GizmoDragMode, GizmoMode, GizmoState};
use crate::prelude::*;
#[derive(Clone, Copy, PartialEq, Eq, Default)]
pub enum CoordinateSpace {
#[default]
World,
Local,
}
#[derive(Clone, Default)]
pub struct MarqueeState {
pub active: bool,
pub start_position: egui::Pos2,
pub current_position: egui::Pos2,
}
impl MarqueeState {
pub fn start(&mut self, position: egui::Pos2) {
self.active = true;
self.start_position = position;
self.current_position = position;
}
pub fn update(&mut self, position: egui::Pos2) {
self.current_position = position;
}
pub fn finish(&mut self) -> Option<egui::Rect> {
if self.active {
self.active = false;
Some(egui::Rect::from_two_pos(
self.start_position,
self.current_position,
))
} else {
None
}
}
pub fn cancel(&mut self) {
self.active = false;
}
pub fn rect(&self) -> Option<egui::Rect> {
if self.active {
Some(egui::Rect::from_two_pos(
self.start_position,
self.current_position,
))
} else {
None
}
}
}
#[derive(Clone)]
pub struct SnapSettings {
pub translation_snap: f32,
pub rotation_snap_degrees: f32,
pub scale_snap: f32,
pub enabled: bool,
}
impl Default for SnapSettings {
fn default() -> Self {
Self {
translation_snap: 0.25,
rotation_snap_degrees: 15.0,
scale_snap: 0.1,
enabled: false,
}
}
}
impl SnapSettings {
pub fn snap_translation(&self, value: f32) -> f32 {
if self.enabled && self.translation_snap > 0.0 {
(value / self.translation_snap).round() * self.translation_snap
} else {
value
}
}
pub fn snap_rotation(&self, degrees: f32) -> f32 {
if self.enabled && self.rotation_snap_degrees > 0.0 {
(degrees / self.rotation_snap_degrees).round() * self.rotation_snap_degrees
} else {
degrees
}
}
pub fn snap_scale(&self, value: f32) -> f32 {
if self.enabled && self.scale_snap > 0.0 {
(value / self.scale_snap).round() * self.scale_snap
} else {
value
}
}
}
#[derive(Clone, Copy, PartialEq, Default)]
pub enum TransformOperation {
#[default]
None,
Grab,
Rotate,
Scale,
}
#[derive(Clone, Copy, PartialEq, Default)]
pub enum TransformConstraint {
#[default]
None,
X,
Y,
Z,
}
pub struct ModalTransformState {
pub operation: TransformOperation,
pub constraint: TransformConstraint,
pub initial_transforms: std::collections::HashMap<Entity, LocalTransform>,
pub start_mouse_pos: Vec2,
pub accumulated_delta: Vec2,
pub numeric_input: String,
pub numeric_value: Option<f32>,
}
impl Default for ModalTransformState {
fn default() -> Self {
Self {
operation: TransformOperation::None,
constraint: TransformConstraint::None,
initial_transforms: std::collections::HashMap::new(),
start_mouse_pos: Vec2::zeros(),
accumulated_delta: Vec2::zeros(),
numeric_input: String::new(),
numeric_value: None,
}
}
}
pub struct GizmoInteraction {
pub active: Option<GizmoState>,
pub mode: GizmoMode,
pub hover_axis: Option<Vec3>,
pub drag_mode: GizmoDragMode,
pub camera_distance: f32,
pub drag_initial_transforms: std::collections::HashMap<Entity, LocalTransform>,
}
impl GizmoInteraction {
pub fn is_dragging(&self) -> bool {
!matches!(self.drag_mode, GizmoDragMode::None)
}
}
impl Default for GizmoInteraction {
fn default() -> Self {
Self {
active: None,
mode: GizmoMode::GlobalTranslation,
hover_axis: None,
drag_mode: GizmoDragMode::None,
camera_distance: 10.0,
drag_initial_transforms: std::collections::HashMap::new(),
}
}
}
#[derive(Default)]
pub struct TransformEdit {
pub pending: Option<(Entity, LocalTransform)>,
pub modal: ModalTransformState,
}
#[derive(Default)]
pub struct EditorContext {
pub gizmo_interaction: GizmoInteraction,
pub transform_edit: TransformEdit,
pub selection: EntitySelection,
pub marquee: MarqueeState,
pub coordinate_space: CoordinateSpace,
pub snap_settings: SnapSettings,
pub undo_history: UndoHistory,
}
impl EditorContext {
pub fn gizmo_root(&self) -> Option<Entity> {
self.gizmo_interaction.active.as_ref().map(|g| g.root)
}
pub fn capture_selection_transforms(
&self,
world: &World,
) -> std::collections::HashMap<Entity, LocalTransform> {
let mut transforms = std::collections::HashMap::new();
for &entity in self.selection.iter() {
if let Some(transform) = world.get_local_transform(entity) {
transforms.insert(entity, *transform);
}
}
transforms
}
pub fn begin_selection_transform_tracking(&mut self, world: &World) {
if self.selection.len() == 1
&& let Some(&entity) = self.selection.iter().next()
&& let Some(transform) = world.get_local_transform(entity)
{
super::undo::begin_transform_change(
&mut self.transform_edit.pending,
entity,
*transform,
);
}
}
pub fn commit_selection_transforms(
&mut self,
world: &World,
initial_transforms: std::collections::HashMap<Entity, LocalTransform>,
description: &str,
) -> bool {
let count = self.selection.len();
if count == 1 {
if let Some(entity) = self.selection.primary()
&& let Some(transform) = world.get_local_transform(entity)
{
super::undo::commit_transform_change(
&mut self.transform_edit.pending,
&mut self.undo_history,
entity,
*transform,
);
return true;
}
false
} else if count > 1 {
let transforms: Vec<(Entity, LocalTransform, LocalTransform)> = self
.selection
.iter()
.filter_map(|&entity| {
let old = initial_transforms.get(&entity)?;
let new = world.get_local_transform(entity)?;
Some((entity, *old, *new))
})
.collect();
super::undo::push_bulk_transform_change(
&mut self.undo_history,
transforms,
description,
);
true
} else {
false
}
}
}