diff --git a/editor/src/absm/mod.rs b/editor/src/absm/mod.rs index 3123a3e41..a9560e990 100644 --- a/editor/src/absm/mod.rs +++ b/editor/src/absm/mod.rs @@ -43,7 +43,7 @@ mod connection; mod node; mod parameter; mod segment; -mod selectable; +pub mod selectable; pub mod selection; mod socket; mod state_graph; diff --git a/editor/src/plugins/tilemap/brush.rs b/editor/src/plugins/tilemap/brush.rs index 0eef3d9ca..99ea4ce6b 100644 --- a/editor/src/plugins/tilemap/brush.rs +++ b/editor/src/plugins/tilemap/brush.rs @@ -3,14 +3,13 @@ use crate::fyrox::{ algebra::{Matrix4, Vector2, Vector3}, color::Color, }, - scene::{debug::SceneDrawingContext, tilemap::tileset::TileDefinition}, + scene::debug::SceneDrawingContext, }; -#[allow(dead_code)] // TODO -#[derive(Default)] +#[derive(Default, PartialEq, Debug, Clone)] pub struct BrushTile { - definition: TileDefinition, - local_position: Vector2, + pub tile_index: usize, + pub local_position: Vector2, } impl BrushTile { @@ -35,12 +34,12 @@ impl BrushTile { } } -#[derive(Default)] -pub struct Brush { - tiles: Vec, +#[derive(Default, PartialEq, Debug, Clone)] +pub struct TileMapBrush { + pub tiles: Vec, } -impl Brush { +impl TileMapBrush { pub fn draw_outline( &self, ctx: &mut SceneDrawingContext, diff --git a/editor/src/plugins/tilemap/mod.rs b/editor/src/plugins/tilemap/mod.rs index fd23de3d7..7447c9da9 100644 --- a/editor/src/plugins/tilemap/mod.rs +++ b/editor/src/plugins/tilemap/mod.rs @@ -4,6 +4,7 @@ pub mod panel; pub mod tile_set_import; pub mod tileset; +use crate::plugins::tilemap::palette::PaletteMessage; use crate::{ fyrox::{ core::{ @@ -25,7 +26,7 @@ use crate::{ }, interaction::{make_interaction_mode_button, InteractionMode}, plugin::EditorPlugin, - plugins::tilemap::{brush::Brush, panel::TileMapPanel, tileset::TileSetEditor}, + plugins::tilemap::{brush::TileMapBrush, panel::TileMapPanel, tileset::TileSetEditor}, scene::{controller::SceneController, GameScene, Selection}, settings::Settings, Editor, Message, @@ -55,7 +56,7 @@ fn make_button( pub struct TileMapInteractionMode { #[allow(dead_code)] tile_map: Handle, - brush: Arc>, + brush: Arc>, brush_position: Vector2, } @@ -180,7 +181,7 @@ impl InteractionMode for TileMapInteractionMode { #[derive(Default)] pub struct TileMapEditorPlugin { tile_set_editor: Option, - brush: Arc>, + brush: Arc>, panel: Option, } @@ -192,14 +193,26 @@ impl EditorPlugin for TileMapEditorPlugin { } fn on_ui_message(&mut self, message: &mut UiMessage, editor: &mut Editor) { + let ui = editor.engine.user_interfaces.first_mut(); + if let Some(tile_set_editor) = self.tile_set_editor.take() { self.tile_set_editor = tile_set_editor.handle_ui_message( message, - editor.engine.user_interfaces.first_mut(), + ui, &editor.engine.resource_manager, &editor.message_sender, ); } + + if let Some(panel) = self.panel.take() { + if let Some(PaletteMessage::Brush(brush)) = message.data() { + if message.destination() == panel.palette { + *self.brush.lock() = brush.clone(); + } + } + + self.panel = panel.handle_ui_message(message, ui); + } } fn on_update(&mut self, _editor: &mut Editor) { @@ -241,9 +254,9 @@ impl EditorPlugin for TileMapEditorPlugin { for node_handle in selection.nodes().iter() { if let Some(tile_map) = scene.graph.try_get(*node_handle) { - if tile_map.component_ref::().is_none() { + let Some(tile_map) = tile_map.component_ref::() else { continue; - } + }; entry.interaction_modes.add(TileMapInteractionMode { tile_map: *node_handle, @@ -254,6 +267,7 @@ impl EditorPlugin for TileMapEditorPlugin { self.panel = Some(TileMapPanel::new( &mut ui.build_ctx(), editor.scene_viewer.frame(), + tile_map.tile_set().cloned(), )); break; diff --git a/editor/src/plugins/tilemap/palette.rs b/editor/src/plugins/tilemap/palette.rs index e1039e383..21d5ec76b 100644 --- a/editor/src/plugins/tilemap/palette.rs +++ b/editor/src/plugins/tilemap/palette.rs @@ -1,41 +1,417 @@ -use crate::fyrox::{ - core::{pool::Handle, reflect::prelude::*, type_traits::prelude::*, visitor::prelude::*}, - gui::{ - define_widget_deref, - message::UiMessage, - widget::{Widget, WidgetBuilder}, - BuildContext, Control, UiNode, UserInterface, +use crate::{ + absm::selectable::{Selectable, SelectableMessage}, + fyrox::{ + core::{ + algebra::{Matrix3, Point2, Vector2}, + color::Color, + math::Rect, + pool::Handle, + reflect::prelude::*, + type_traits::prelude::*, + visitor::prelude::*, + }, + graph::{BaseSceneGraph, SceneGraph}, + gui::{ + brush::Brush, + define_constructor, define_widget_deref, + draw::{CommandTexture, Draw, DrawingContext}, + message::{MessageDirection, MouseButton, UiMessage}, + widget::{Widget, WidgetBuilder, WidgetMessage}, + BuildContext, Control, UiNode, UserInterface, + }, + scene::tilemap::tileset::TileSetResource, }, + plugins::tilemap::brush::{BrushTile, TileMapBrush}, }; use std::ops::{Deref, DerefMut}; +#[derive(Debug, PartialEq, Clone)] +pub enum PaletteMessage { + Brush(TileMapBrush), +} + +impl PaletteMessage { + define_constructor!(PaletteMessage:Brush => fn brush(TileMapBrush), layout: false); +} + +#[derive(Debug, Clone, PartialEq, Visit, Reflect, Default)] +pub(super) struct Entry { + pub node: Handle, + pub initial_position: Vector2, +} + +#[derive(Debug, Clone, PartialEq, Visit, Reflect, Default)] +pub(super) struct DragContext { + initial_cursor_position: Vector2, + entries: Vec, +} + #[derive(Clone, Debug, Visit, Reflect, TypeUuidProvider, ComponentProvider)] #[type_uuid(id = "5356a864-c026-4bd7-a4b1-30bacf77d8fa")] pub struct PaletteWidget { widget: Widget, tiles: Vec>, + view_position: Vector2, + zoom: f32, + tile_size: Vector2, + initial_view_position: Vector2, + click_position: Vector2, + is_dragging_view: bool, + drag_context: Option, + selection: Vec>, } define_widget_deref!(PaletteWidget); +impl PaletteWidget { + pub fn point_to_local_space(&self, point: Vector2) -> Vector2 { + self.visual_transform() + .try_inverse() + .unwrap_or_default() + .transform_point(&Point2::from(point)) + .coords + } + + pub fn update_transform(&self, ui: &UserInterface) { + let transform = + Matrix3::new_translation(&-self.view_position) * Matrix3::new_scaling(self.zoom); + + ui.send_message(WidgetMessage::layout_transform( + self.handle(), + MessageDirection::ToWidget, + transform, + )); + } + + fn make_drag_context(&self, ui: &UserInterface) -> DragContext { + DragContext { + initial_cursor_position: self.point_to_local_space(ui.cursor_position()), + entries: self + .selection + .iter() + .map(|n| Entry { + node: *n, + initial_position: ui.node(*n).actual_local_position(), + }) + .collect(), + } + } + + fn selected_tiles_to_brush(&self, ui: &UserInterface) -> TileMapBrush { + TileMapBrush { + tiles: self + .selection + .iter() + .filter_map(|h| ui.try_get_of_type::(*h)) + .map(|view| BrushTile { + tile_index: view.tile_index, + local_position: view.local_position, + }) + .collect::>(), + } + } + + fn set_selection(&mut self, new_selection: &[Handle], ui: &UserInterface) { + if self.selection != new_selection { + for &child in self + .children() + .iter() + .filter(|n| ui.node(**n).query_component::().is_some()) + { + ui.send_message( + SelectableMessage::select( + child, + MessageDirection::ToWidget, + new_selection.contains(&child), + ) + .with_handled(true), + ); + } + + self.selection = new_selection.to_vec(); + + ui.send_message(PaletteMessage::brush( + self.handle(), + MessageDirection::FromWidget, + self.selected_tiles_to_brush(ui), + )); + + // Make sure to update dragging context if we're in Drag mode. + if self.drag_context.is_some() { + self.drag_context = Some(self.make_drag_context(ui)); + } + } + } +} + impl Control for PaletteWidget { + fn measure_override(&self, ui: &UserInterface, _available_size: Vector2) -> Vector2 { + for child_handle in self.widget.children() { + ui.measure_node(*child_handle, self.tile_size); + } + + Vector2::default() + } + + fn arrange_override(&self, ui: &UserInterface, final_size: Vector2) -> Vector2 { + for &child_handle in self.widget.children() { + if let Some(tile) = ui.try_get_of_type::(child_handle) { + ui.arrange_node( + child_handle, + &Rect::new( + tile.local_position.x as f32 * self.tile_size.x, + tile.local_position.y as f32 * self.tile_size.y, + self.tile_size.x, + self.tile_size.y, + ), + ); + } + } + + final_size + } + + fn draw(&self, ctx: &mut DrawingContext) { + let grid_size = 9999.0; + + let grid_bounds = self + .widget + .bounding_rect() + .inflate(grid_size, grid_size) + .translate(Vector2::new(grid_size * 0.5, grid_size * 0.5)); + ctx.push_rect_filled(&grid_bounds, None); + ctx.commit( + self.clip_bounds(), + self.widget.background(), + CommandTexture::None, + None, + ); + + ctx.push_grid(self.zoom, self.tile_size, grid_bounds); + ctx.commit( + self.clip_bounds(), + Brush::Solid(Color::repeat_opaque(60)), + CommandTexture::None, + None, + ); + } + fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) { - self.widget.handle_routed_message(ui, message) + self.widget.handle_routed_message(ui, message); + + if let Some(SelectableMessage::Select(true)) = message.data() { + if message.direction() == MessageDirection::FromWidget && !message.handled() { + let selected_node = message.destination(); + + let new_selection = if ui.keyboard_modifiers().control { + let mut selection = self.selection.clone(); + selection.push(selected_node); + selection + } else { + vec![selected_node] + }; + + self.set_selection(&new_selection, ui); + } + } else if let Some(WidgetMessage::MouseDown { pos, button }) = message.data() { + if *button == MouseButton::Middle { + self.is_dragging_view = true; + self.click_position = *pos; + self.initial_view_position = self.view_position; + + ui.capture_mouse(self.handle()); + } else if *button == MouseButton::Left && !message.handled() { + if message.destination() != self.handle { + self.drag_context = Some(self.make_drag_context(ui)); + } else { + self.set_selection(&[], ui); + } + } + } else if let Some(WidgetMessage::MouseUp { button, pos }) = message.data() { + if *button == MouseButton::Middle { + self.is_dragging_view = false; + + ui.release_mouse_capture(); + } else if *button == MouseButton::Left { + if let Some(drag_context) = self.drag_context.take() { + if self.screen_to_local(*pos) != drag_context.initial_cursor_position {} + } + } + } else if let Some(WidgetMessage::MouseMove { pos, .. }) = message.data() { + if self.is_dragging_view { + self.view_position = self.initial_view_position + (*pos - self.click_position); + self.update_transform(ui); + } + + if let Some(drag_context) = self.drag_context.as_ref() { + for entry in drag_context.entries.iter() { + let local_cursor_pos = self.point_to_local_space(*pos); + + let new_position = entry.initial_position + + (local_cursor_pos - drag_context.initial_cursor_position); + + ui.send_message(WidgetMessage::desired_position( + entry.node, + MessageDirection::ToWidget, + new_position, + )); + } + } + } else if let Some(WidgetMessage::MouseWheel { amount, pos }) = message.data() { + let cursor_pos = (*pos - self.screen_position()).scale(self.zoom); + + self.zoom = (self.zoom + 0.1 * amount).clamp(0.2, 2.0); + + let new_cursor_pos = (*pos - self.screen_position()).scale(self.zoom); + + self.view_position -= (new_cursor_pos - cursor_pos).scale(self.zoom); + + self.update_transform(ui); + } } } pub struct PaletteWidgetBuilder { widget_builder: WidgetBuilder, + tiles: Vec>, } impl PaletteWidgetBuilder { pub fn new(widget_builder: WidgetBuilder) -> Self { - Self { widget_builder } + Self { + widget_builder, + tiles: Default::default(), + } + } + + pub fn with_tiles(mut self, tiles: Vec>) -> Self { + self.tiles = tiles; + self } pub fn build(self, ctx: &mut BuildContext) -> Handle { ctx.add_node(UiNode::new(PaletteWidget { + widget: self + .widget_builder + .with_clip_to_bounds(false) + .with_children(self.tiles.iter().cloned()) + .build(), + tiles: self.tiles, + view_position: Default::default(), + zoom: 1.0, + tile_size: Vector2::repeat(32.0), + initial_view_position: Default::default(), + click_position: Default::default(), + is_dragging_view: false, + drag_context: None, + selection: Default::default(), + })) + } +} + +#[derive(Clone, Debug, Visit, Reflect, TypeUuidProvider, ComponentProvider)] +#[type_uuid(id = "c8ff0080-fb29-480a-8a88-59ee4c58d60d")] +pub struct TileView { + widget: Widget, + #[component(include)] + selectable: Selectable, + tile_index: usize, + local_position: Vector2, + tile_set: TileSetResource, +} + +define_widget_deref!(TileView); + +impl Control for TileView { + fn draw(&self, drawing_context: &mut DrawingContext) { + let tile_set = self.tile_set.data_ref(); + if let Some(tile_definition) = tile_set.tiles.get(self.tile_index) { + if let Some(texture) = tile_definition + .material + .data_ref() + .texture("diffuseTexture") + { + drawing_context.push_rect_filled( + &self.bounding_rect(), + Some(&[ + Vector2::new( + tile_definition.uv_rect.position.x, + tile_definition.uv_rect.position.y, + ), + Vector2::new( + tile_definition.uv_rect.position.x + tile_definition.uv_rect.size.x, + tile_definition.uv_rect.position.y, + ), + Vector2::new( + tile_definition.uv_rect.position.x + tile_definition.uv_rect.size.x, + tile_definition.uv_rect.position.y - tile_definition.uv_rect.size.y, + ), + Vector2::new( + tile_definition.uv_rect.position.x, + tile_definition.uv_rect.position.y - tile_definition.uv_rect.size.y, + ), + ]), + ); + drawing_context.commit( + self.clip_bounds(), + Brush::Solid(Color::WHITE), + CommandTexture::Texture(texture.into()), + None, + ); + } + } + + if self.selectable.selected { + drawing_context.push_rect(&self.bounding_rect(), 1.0); + drawing_context.commit( + self.clip_bounds(), + (*self.foreground).clone(), + CommandTexture::None, + None, + ); + } + } + fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) { + self.widget.handle_routed_message(ui, message); + self.selectable + .handle_routed_message(self.handle, ui, message); + } +} + +pub struct TileViewBuilder { + widget_builder: WidgetBuilder, + tile_index: usize, + local_position: Vector2, + tile_set: TileSetResource, +} + +impl TileViewBuilder { + pub fn new(tile_set: TileSetResource, widget_builder: WidgetBuilder) -> Self { + Self { + widget_builder, + tile_index: 0, + local_position: Default::default(), + tile_set, + } + } + + pub fn with_position(mut self, position: Vector2) -> Self { + self.local_position = position; + self + } + + pub fn with_tile_index(mut self, index: usize) -> Self { + self.tile_index = index; + self + } + + pub fn build(self, ctx: &mut BuildContext) -> Handle { + ctx.add_node(UiNode::new(TileView { widget: self.widget_builder.build(), + selectable: Default::default(), + tile_index: self.tile_index, + local_position: self.local_position, + tile_set: self.tile_set, })) } } diff --git a/editor/src/plugins/tilemap/panel.rs b/editor/src/plugins/tilemap/panel.rs index 6ea29790c..7f3a7a617 100644 --- a/editor/src/plugins/tilemap/panel.rs +++ b/editor/src/plugins/tilemap/panel.rs @@ -1,6 +1,6 @@ use crate::{ fyrox::{ - core::pool::Handle, + core::{algebra::Vector2, pool::Handle}, gui::{ grid::GridBuilder, message::{MessageDirection, UiMessage}, @@ -8,23 +8,51 @@ use crate::{ window::{WindowBuilder, WindowMessage, WindowTitle}, BuildContext, HorizontalAlignment, Thickness, UiNode, UserInterface, VerticalAlignment, }, + scene::tilemap::tileset::TileSetResource, }, - plugins::tilemap::palette::PaletteWidgetBuilder, + plugins::tilemap::palette::{PaletteWidgetBuilder, TileViewBuilder}, }; pub struct TileMapPanel { pub window: Handle, + pub palette: Handle, } impl TileMapPanel { - pub fn new(ctx: &mut BuildContext, scene_frame: Handle) -> Self { - let content = GridBuilder::new( - WidgetBuilder::new() - .with_child(PaletteWidgetBuilder::new(WidgetBuilder::new()).build(ctx)), - ) - .build(ctx); + pub fn new( + ctx: &mut BuildContext, + scene_frame: Handle, + tile_set: Option, + ) -> Self { + let tiles = tile_set + .map(|tile_set_resource| { + let tile_set = tile_set_resource.data_ref(); + tile_set + .tiles + .iter() + .enumerate() + .map(|(index, tile)| { + let side_size = 10; - let window = WindowBuilder::new(WidgetBuilder::new().with_width(250.0).with_height(150.0)) + TileViewBuilder::new(tile_set_resource.clone(), WidgetBuilder::new()) + .with_tile_index(index) + .with_position(Vector2::new( + index as i32 % side_size, + index as i32 / side_size, + )) + .build(ctx) + }) + .collect::>() + }) + .unwrap_or_default(); + + let palette = PaletteWidgetBuilder::new(WidgetBuilder::new()) + .with_tiles(tiles) + .build(ctx); + + let content = GridBuilder::new(WidgetBuilder::new().with_child(palette)).build(ctx); + + let window = WindowBuilder::new(WidgetBuilder::new().with_width(250.0).with_height(350.0)) .open(false) .with_title(WindowTitle::text("Tile Map Control Panel")) .with_content(content) @@ -43,7 +71,7 @@ impl TileMapPanel { )) .unwrap(); - Self { window } + Self { window, palette } } pub fn destroy(self, ui: &UserInterface) {