From 8872a6240e7972d5b4beef72060ff1c9f3d98f98 Mon Sep 17 00:00:00 2001 From: b-guild Date: Sun, 9 Mar 2025 19:37:31 -0700 Subject: [PATCH 1/3] Enhanced tile set editing, copying pages --- editor/src/command/mod.rs | 25 + editor/src/plugins/tilemap/autotile.rs | 234 +++++++- editor/src/plugins/tilemap/brush_macro.rs | 58 +- editor/src/plugins/tilemap/commands.rs | 30 ++ editor/src/plugins/tilemap/macro_inspector.rs | 11 +- editor/src/plugins/tilemap/mod.rs | 5 +- editor/src/plugins/tilemap/palette.rs | 509 +++++++++++++++--- editor/src/plugins/tilemap/tile_inspector.rs | 12 +- editor/src/plugins/tilemap/tileset.rs | 2 + editor/src/plugins/tilemap/wfc.rs | 251 ++++++++- fyrox-impl/src/scene/tilemap/autotile.rs | 16 +- fyrox-impl/src/scene/tilemap/brush.rs | 9 +- fyrox-impl/src/scene/tilemap/mod.rs | 17 +- fyrox-impl/src/scene/tilemap/tile_source.rs | 18 +- fyrox-impl/src/scene/tilemap/tileset.rs | 11 + fyrox-impl/src/scene/tilemap/update.rs | 6 +- 16 files changed, 1072 insertions(+), 142 deletions(-) diff --git a/editor/src/command/mod.rs b/editor/src/command/mod.rs index 646bda0f7..32b65d058 100644 --- a/editor/src/command/mod.rs +++ b/editor/src/command/mod.rs @@ -77,17 +77,28 @@ impl dyn CommandContext + '_ { } } +/// An object that can be added to the editors [`CommandStack`] so the user +/// can execute it and revert it. pub trait CommandTrait: Debug + 'static { + /// The name that the user should see in the command stack. fn name(&mut self, context: &dyn CommandContext) -> String; + /// Perform the operation that this object represents. + /// This happens when the object is first added to the command stack, + /// and when the object is redone after being undone. fn execute(&mut self, context: &mut dyn CommandContext); + /// Undo the consequences of calling [`CommandTrait::execute`]. fn revert(&mut self, context: &mut dyn CommandContext); + /// This object is leaving the command stack, so it will never + /// be executed or reverted again. fn finalize(&mut self, _: &mut dyn CommandContext) {} } +/// An untyped command for the editor to execute or revert. #[derive(Debug)] pub struct Command(pub Box); impl Command { + /// Create a command from the given `CommandTrait` object. pub fn new(cmd: C) -> Self { Self(Box::new(cmd)) } @@ -107,6 +118,11 @@ impl DerefMut for Command { } } +/// A list of commands to execute in order as a single command. +/// The commands are reverted in reverse order. +/// Use [`CommandGroup::with_custom_name`] to give the command a +/// name. Otherwise, a name is automatically constructed by listing +/// the names of the commands in the group. #[derive(Debug, Default)] pub struct CommandGroup { commands: Vec, @@ -123,19 +139,28 @@ impl From> for CommandGroup { } impl CommandGroup { + /// Add an object of the `CommandTriat` to the group. pub fn push(&mut self, command: C) { self.commands.push(Command::new(command)) } + /// Add a `Command` to the group. + pub fn push_command(&mut self, command: Command) { + self.commands.push(command) + } + + /// Replace the automatically constructed name. pub fn with_custom_name>(mut self, name: S) -> Self { self.custom_name = name.as_ref().to_string(); self } + /// True if this group contains no commands. pub fn is_empty(&self) -> bool { self.commands.is_empty() } + /// The number of commands in the group. pub fn len(&self) -> usize { self.commands.len() } diff --git a/editor/src/plugins/tilemap/autotile.rs b/editor/src/plugins/tilemap/autotile.rs index ba88b50fd..973b1e592 100644 --- a/editor/src/plugins/tilemap/autotile.rs +++ b/editor/src/plugins/tilemap/autotile.rs @@ -44,7 +44,7 @@ use fyrox::{ }; use crate::{ - command::{Command, CommandContext, CommandTrait}, + command::{Command, CommandContext, CommandGroup, CommandTrait}, send_sync_message, }; @@ -117,7 +117,7 @@ impl TerrainSource for TileSetTerrainSource<'_, '_> { fn iter(&self) -> impl Iterator, TileTerrainId>> + '_ { self.update.iter().filter_map(|(p, v)| { - let cell_data = self.instance.cells.get(v.as_ref()?.brush_cell.as_ref()?)?; + let cell_data = self.instance.cells.get(&v.as_ref()?.source?.handle()?)?; Some(NeededTerrain { position: *p, terrain: cell_data.terrain_id, @@ -130,7 +130,7 @@ impl TerrainSource for TileSetTerrainSource<'_, '_> { let Some(el) = self.update.get(position).cloned().flatten() else { return false; }; - let Some(brush_cell) = el.brush_cell else { + let Some(brush_cell) = el.source.and_then(|s| s.handle()) else { return false; }; self.instance.cells.contains_key(&brush_cell) @@ -280,7 +280,7 @@ impl BrushMacro for AutoTileMacro { cell_set.extend(data.cells.keys()); } - fn create_cell(&self, context: &BrushMacroCell) -> Option { + fn create_cell(&self, context: &BrushMacroCellContext) -> Option { let instance = context.settings()?; let cell = context.cell?; Some(Command::new(SetCellCommand { @@ -291,7 +291,7 @@ impl BrushMacro for AutoTileMacro { })) } - fn remove_cell(&self, context: &BrushMacroCell) -> Option { + fn remove_cell(&self, context: &BrushMacroCellContext) -> Option { let instance = context.settings()?; let cell = context.cell?; Some(Command::new(SetCellCommand { @@ -302,6 +302,99 @@ impl BrushMacro for AutoTileMacro { })) } + fn move_cells( + &self, + from: Box<[TileDefinitionHandle]>, + to: Box<[TileDefinitionHandle]>, + context: &BrushMacroInstance, + ) -> Option { + let instance = context.settings::()?; + Some(Command::new(MoveCellsCommand::new( + context.brush.clone(), + from, + to, + instance, + ))) + } + + fn move_pages( + &self, + from: Box<[Vector2]>, + to: Box<[Vector2]>, + context: &BrushMacroInstance, + ) -> Option { + let instance = context.settings::()?; + Some(Command::new(MovePagesCommand::new( + context.brush.clone(), + from, + to, + instance, + ))) + } + + fn copy_cell( + &self, + source: Option, + destination: TileDefinitionHandle, + context: &BrushMacroInstance, + ) -> Option { + let instance = context.settings::()?; + let guard = instance.data_ref(); + let data = if let Some(source) = source { + guard.cells.get(&source).cloned() + } else { + None + }; + Some(Command::new(SetCellCommand { + brush: context.brush.clone(), + cell: destination, + instance: instance.clone(), + data, + })) + } + + fn copy_page( + &self, + source: Option>, + destination: Vector2, + context: &BrushMacroInstance, + ) -> Option { + let instance = context.settings::()?; + let guard = instance.data_ref(); + let used = guard + .cells + .keys() + .filter(|h| Some(h.page()) == source) + .map(|h| h.tile()) + .collect::>(); + let commands = guard + .cells + .iter() + .filter_map(|(handle, data)| { + if Some(handle.page()) == source { + let cell = TileDefinitionHandle::try_new(destination, handle.tile())?; + Some(Command::new(SetCellCommand { + brush: context.brush.clone(), + cell, + instance: instance.clone(), + data: Some(data.clone()), + })) + } else if handle.page() == destination && !used.contains(&handle.tile()) { + Some(Command::new(SetCellCommand { + brush: context.brush.clone(), + cell: *handle, + instance: instance.clone(), + data: None, + })) + } else { + None + } + }) + .collect::>(); + (!commands.is_empty()) + .then(|| Command::new(CommandGroup::from(commands).with_custom_name("Set Page Macros"))) + } + fn build_instance_editor( &mut self, context: &BrushMacroInstance, @@ -360,7 +453,7 @@ impl BrushMacro for AutoTileMacro { fn build_cell_editor( &mut self, - context: &BrushMacroCell, + context: &BrushMacroCellContext, ctx: &mut BuildContext, ) -> Option> { let instance = context.settings::().unwrap(); @@ -521,8 +614,10 @@ impl BrushMacro for AutoTileMacro { }; let frequency_property = instance.frequency_property; // Check that at least one cell in the stamp is part of this instance. - if !stamp.values().any(|StampElement { brush_cell, .. }| { - brush_cell.is_some_and(|handle| instance.cells.contains_key(&handle)) + if !stamp.values().any(|StampElement { source, .. }| { + source + .and_then(|s| s.handle()) + .is_some_and(|handle| instance.cells.contains_key(&handle)) }) { self.context.clear(); return; @@ -613,6 +708,129 @@ impl CommandTrait for SetCellCommand { } } +#[derive(Debug)] +struct MoveCellsCommand { + pub brush: TileMapBrushResource, + pub from: Box<[TileDefinitionHandle]>, + pub to: Box<[TileDefinitionHandle]>, + pub instance: Resource, + data: Box<[Option]>, +} + +impl MoveCellsCommand { + fn new( + brush: TileMapBrushResource, + from: Box<[TileDefinitionHandle]>, + to: Box<[TileDefinitionHandle]>, + instance: Resource, + ) -> Self { + Self { + data: vec![None; from.len()].into_boxed_slice(), + brush, + from, + to, + instance, + } + } + fn swap(&mut self) { + let mut instance = self.instance.data_ref(); + for (&from, data) in self.from.iter().zip(self.data.iter_mut()) { + swap_hash_map_entry(instance.cells.entry(from), data); + } + for (&to, data) in self.to.iter().zip(self.data.iter_mut()) { + swap_hash_map_entry(instance.cells.entry(to), data); + } + std::mem::swap(&mut self.from, &mut self.to); + self.brush.data_ref().change_flag.set(); + } +} + +impl CommandTrait for MoveCellsCommand { + fn name(&mut self, _context: &dyn CommandContext) -> String { + "Update Autotile Cell".into() + } + + fn execute(&mut self, _context: &mut dyn CommandContext) { + self.swap(); + } + + fn revert(&mut self, _context: &mut dyn CommandContext) { + self.swap(); + } +} + +#[derive(Debug)] +struct MovePagesCommand { + pub brush: TileMapBrushResource, + pub from: Box<[Vector2]>, + pub to: Box<[Vector2]>, + pub instance: Resource, + data: Box<[FxHashMap, CellData>]>, +} + +impl MovePagesCommand { + fn new( + brush: TileMapBrushResource, + from: Box<[Vector2]>, + to: Box<[Vector2]>, + instance: Resource, + ) -> Self { + Self { + data: vec![FxHashMap::default(); from.len()].into_boxed_slice(), + brush, + from, + to, + instance, + } + } + fn swap(&mut self) { + let mut instance = self.instance.data_ref(); + for (&from, data) in self.from.iter().zip(self.data.iter_mut()) { + swap_page(&mut instance.cells, from, data); + } + for (&to, data) in self.to.iter().zip(self.data.iter_mut()) { + swap_page(&mut instance.cells, to, data); + } + std::mem::swap(&mut self.from, &mut self.to); + self.brush.data_ref().change_flag.set(); + } +} + +fn swap_page( + map: &mut FxHashMap, + position: Vector2, + data: &mut FxHashMap, T>, +) { + let mut new_data = FxHashMap::default(); + map.retain(|h, d| { + if h.page() != position { + true + } else { + new_data.insert(h.tile(), std::mem::take(d)); + false + } + }); + map.extend( + data.drain() + .filter_map(|(p, d)| Some((TileDefinitionHandle::try_new(position, p)?, d))), + ); + *data = new_data; +} + +impl CommandTrait for MovePagesCommand { + fn name(&mut self, _context: &dyn CommandContext) -> String { + "Update Autotile Cell".into() + } + + fn execute(&mut self, _context: &mut dyn CommandContext) { + self.swap(); + } + + fn revert(&mut self, _context: &mut dyn CommandContext) { + self.swap(); + } +} + #[derive(Debug)] struct SetPatternPropCommand { pub brush: TileMapBrushResource, diff --git a/editor/src/plugins/tilemap/brush_macro.rs b/editor/src/plugins/tilemap/brush_macro.rs index 968ffa398..70413d4d7 100644 --- a/editor/src/plugins/tilemap/brush_macro.rs +++ b/editor/src/plugins/tilemap/brush_macro.rs @@ -156,8 +156,8 @@ pub struct MacroMessageContext { pub cell: Option, } -impl From for MacroMessageContext { - fn from(value: BrushMacroCell) -> Self { +impl From for MacroMessageContext { + fn from(value: BrushMacroCellContext) -> Self { Self { brush: value.brush, cell: value.cell, @@ -192,7 +192,7 @@ impl MacroMessageContext { /// such as adding a new cell to the macro, removing a cell, or building the widgets /// for editing the settings of a cell. #[derive(Debug, Clone)] -pub struct BrushMacroCell { +pub struct BrushMacroCellContext { /// The brush that has an instance of the macro. pub brush: TileMapBrushResource, /// The configuration for an instance of the macro. @@ -201,8 +201,8 @@ pub struct BrushMacroCell { pub cell: Option, } -impl From for BrushMacroInstance { - fn from(value: BrushMacroCell) -> Self { +impl From for BrushMacroInstance { + fn from(value: BrushMacroCellContext) -> Self { Self { brush: value.brush, settings: value.settings, @@ -210,7 +210,7 @@ impl From for BrushMacroInstance { } } -impl BrushMacroCell { +impl BrushMacroCellContext { /// A typed reference to the configuration resource. pub fn settings(&self) -> Option> where @@ -296,11 +296,51 @@ pub trait BrushMacro: 'static + Send + Sync { /// or no cell is selected. Adding the currently selected cell will naturally require /// the widgets that edit the data to change, but that will wait until /// [`BrushMacro::sync_cell_editors`] is called. - fn create_cell(&self, context: &BrushMacroCell) -> Option; + fn create_cell(&self, context: &BrushMacroCellContext) -> Option; /// Create a command to modify the given instances's data to remove the given cell. /// None is returned if no command is necessary, such as if the cell is already excluded /// or no cell is selected. - fn remove_cell(&self, context: &BrushMacroCell) -> Option; + fn remove_cell(&self, context: &BrushMacroCellContext) -> Option; + /// Create a command to move cell data from the given cells to the given cells. + /// The two lists of cells should always be the same length. + /// This method works on multiple cells at once because `from` and `to` may + /// share some cells in common, in which case correctly moving the cells + /// requires first removing all the cells from `from`, then inserting all + /// the cells into `to`. + fn move_cells( + &self, + from: Box<[TileDefinitionHandle]>, + to: Box<[TileDefinitionHandle]>, + context: &BrushMacroInstance, + ) -> Option; + /// Create a command to move cell data from the given pages to the given pages. + /// The two lists of pages should always be the same length. + /// This method works on multiple pages at once because `from` and `to` may + /// share some pages in common, in which case correctly moving the pages + /// requires first removing all the pages from `from`, then inserting all + /// the pages into `to`. + fn move_pages( + &self, + from: Box<[Vector2]>, + to: Box<[Vector2]>, + context: &BrushMacroInstance, + ) -> Option; + /// Create a command to modify the given instances's data to copy the given cell. + /// None is returned if no command is necessary, such as if the macro has no cell data. + fn copy_cell( + &self, + source: Option, + destination: TileDefinitionHandle, + context: &BrushMacroInstance, + ) -> Option; + /// Create a command to modify the given instances's data to copy the given page. + /// None is returned if no command is necessary, such as if the macro has no cell data. + fn copy_page( + &self, + source: Option>, + destination: Vector2, + context: &BrushMacroInstance, + ) -> Option; /// Modify the given `cell_set` to include the handles of all the cells that are part of /// the given instance of this macro. This is necessary for the brush editor to accurately /// update itself. @@ -329,7 +369,7 @@ pub trait BrushMacro: 'static + Send + Sync { /// when [`BrushMacro::sync_cell_editors`] is called. fn build_cell_editor( &mut self, - context: &BrushMacroCell, + context: &BrushMacroCellContext, ctx: &mut BuildContext, ) -> Option>; /// Send the necessary messages to update the cell editor widgets to edit the data for diff --git a/editor/src/plugins/tilemap/commands.rs b/editor/src/plugins/tilemap/commands.rs index e03ffd445..f756edda6 100644 --- a/editor/src/plugins/tilemap/commands.rs +++ b/editor/src/plugins/tilemap/commands.rs @@ -1127,13 +1127,26 @@ pub struct ModifyPageIconCommand { pub tile_set: TileSetResource, pub page: Vector2, pub icon: TileDefinitionHandle, + error: bool, } impl ModifyPageIconCommand { + pub fn new(tile_set: TileSetResource, page: Vector2, icon: TileDefinitionHandle) -> Self { + Self { + tile_set, + page, + icon, + error: false, + } + } fn swap(&mut self) { + if self.error { + return; + } let mut tile_set = self.tile_set.data_ref(); let Some(page) = &mut tile_set.pages.get_mut(&self.page) else { Log::err("Modify icon of non-existent tile page."); + self.error = true; return; }; std::mem::swap(&mut self.icon, &mut page.icon); @@ -1160,13 +1173,30 @@ pub struct ModifyBrushPageIconCommand { pub brush: TileMapBrushResource, pub page: Vector2, pub icon: TileDefinitionHandle, + error: bool, } impl ModifyBrushPageIconCommand { + pub fn new( + brush: TileMapBrushResource, + page: Vector2, + icon: TileDefinitionHandle, + ) -> Self { + Self { + brush, + page, + icon, + error: false, + } + } fn swap(&mut self) { + if self.error { + return; + } let mut brush = self.brush.data_ref(); let Some(page) = &mut brush.pages.get_mut(&self.page) else { Log::err("Modify icon of non-existent tile page."); + self.error = true; return; }; std::mem::swap(&mut self.icon, &mut page.icon); diff --git a/editor/src/plugins/tilemap/macro_inspector.rs b/editor/src/plugins/tilemap/macro_inspector.rs index 7f184debb..455ace492 100644 --- a/editor/src/plugins/tilemap/macro_inspector.rs +++ b/editor/src/plugins/tilemap/macro_inspector.rs @@ -231,7 +231,7 @@ fn make_items( }); continue; } - let brush_macro_cell = BrushMacroCell { + let brush_macro_cell = BrushMacroCellContext { brush: brush.clone(), settings: instance.settings.clone(), cell, @@ -410,15 +410,10 @@ impl MacroInspector { let settings = instance.settings.clone(); drop(brush_guard); let command = if cell_sets.cell_has_macro(cell, i) { - let cell = Some(cell); - brush_macro.remove_cell(&BrushMacroCell { - brush, - settings, - cell, - }) + brush_macro.copy_cell(None, cell, &BrushMacroInstance { brush, settings }) } else { let cell = Some(cell); - brush_macro.create_cell(&BrushMacroCell { + brush_macro.create_cell(&BrushMacroCellContext { brush, settings, cell, diff --git a/editor/src/plugins/tilemap/mod.rs b/editor/src/plugins/tilemap/mod.rs index 30246aa79..10b3e6b66 100644 --- a/editor/src/plugins/tilemap/mod.rs +++ b/editor/src/plugins/tilemap/mod.rs @@ -59,7 +59,6 @@ use fyrox::gui::style::Style; use fyrox::gui::text::TextBuilder; use fyrox::gui::{message::KeyCode, texture::TextureResource}; use fyrox::gui::{HorizontalAlignment, VerticalAlignment}; -use fyrox::scene::tilemap::brush::TileMapBrushResource; use fyrox::scene::tilemap::tileset::{TileSetPropertyLayer, ELEMENT_MATCH_HIGHLIGHT_COLOR}; use fyrox::scene::tilemap::{StampElement, TileMapEffectRef}; pub use handle_editor::*; @@ -547,7 +546,7 @@ impl TileDrawState { #[inline] pub fn update_stamp( &mut self, - brush: Option, + book: Option, tile_set: Option, tile_handle: F, ) where @@ -555,7 +554,7 @@ impl TileDrawState { { self.tile_set = tile_set; self.stamp.build( - brush, + book, self.selection .positions .iter() diff --git a/editor/src/plugins/tilemap/palette.rs b/editor/src/plugins/tilemap/palette.rs index 64b556674..e4b3a2c0d 100644 --- a/editor/src/plugins/tilemap/palette.rs +++ b/editor/src/plugins/tilemap/palette.rs @@ -22,8 +22,9 @@ //! is responsible for displaying a grid of tiles where the user may select tiles, //! drag tiles, and use drawing tools upon the tiles. +use fyrox::scene::tilemap::brush::TileMapBrushResource; use fyrox::scene::tilemap::tileset::OptionTileSet; -use fyrox::scene::tilemap::ResourceTilePosition; +use fyrox::scene::tilemap::{ResourceTilePosition, RotTileHandle}; use super::{commands::*, *}; use crate::asset::item::AssetItem; @@ -255,6 +256,10 @@ pub struct PaletteWidget { #[visit(skip)] #[reflect(hidden)] macro_cells: Option, + /// The cells which should be marked as being included in some macro. + #[visit(skip)] + #[reflect(hidden)] + macro_list: Option, #[visit(skip)] #[reflect(hidden)] colliders: Vec, @@ -371,6 +376,317 @@ fn invert_transform(trans: &Matrix3) -> Matrix3 { trans.try_inverse().unwrap_or(Matrix3::identity()) } +fn send_update_tile_set_pages( + tile_set: &TileSetResource, + update: &TransTilesUpdate, + state: &TileDrawStateRef, + sender: &mut MessageSender, +) { + match state.lock().stamp.source() { + Some(TileBook::TileSet(source)) => { + send_copy_tile_set_pages(source, update, tile_set, sender) + } + _ => send_update_tile_set_icons(update, tile_set, sender), + } +} + +fn send_copy_tile_set_pages( + source: &TileSetResource, + update: &TransTilesUpdate, + destination: &TileSetResource, + sender: &mut MessageSender, +) { + let mut commands = Vec::default(); + for (p, data) in update.iter() { + let Some(RotTileHandle { + element: + StampElement { + handle, + source: element_source, + }, + transform, + }) = data + else { + commands.push(Command::new(SetTileSetPageCommand { + tile_set: destination.clone(), + position: *p, + page: None, + })); + continue; + }; + if let Some(ResourceTilePosition::Page(source_page)) = element_source { + let source = source.data_ref(); + let mut page_data = source.get_page(*source_page).cloned(); + if let Some(page_data) = page_data.as_mut() { + let icon = page_data.icon; + let icon = source + .get_transformed_version(*transform, icon) + .unwrap_or(icon); + page_data.icon = icon; + } + commands.push(Command::new(SetTileSetPageCommand { + tile_set: destination.clone(), + position: *p, + page: page_data, + })); + } else { + let source = source.data_ref(); + let icon = source + .get_transformed_version(*transform, *handle) + .unwrap_or(*handle); + commands.push(Command::new(ModifyPageIconCommand::new( + destination.clone(), + *p, + icon, + ))); + } + } + if !commands.is_empty() { + sender.do_command(CommandGroup::from(commands).with_custom_name("Edit Tile Set Pages")); + } +} + +fn send_update_tile_set_icons( + update: &TransTilesUpdate, + destination: &TileSetResource, + sender: &mut MessageSender, +) { + let mut commands = Vec::default(); + for (p, data) in update.iter() { + let Some(RotTileHandle { + element: StampElement { handle, .. }, + transform, + }) = data + else { + commands.push(Command::new(SetTileSetPageCommand { + tile_set: destination.clone(), + position: *p, + page: None, + })); + continue; + }; + let tile_set = destination.data_ref(); + let icon = tile_set + .get_transformed_version(*transform, *handle) + .unwrap_or(*handle); + commands.push(Command::new(ModifyPageIconCommand::new( + destination.clone(), + *p, + icon, + ))); + } + if !commands.is_empty() { + sender.do_command(CommandGroup::from(commands).with_custom_name("Edit Tile Set Pages")); + } +} + +fn send_update_brush_pages( + brush: &TileMapBrushResource, + update: &TransTilesUpdate, + state: &TileDrawStateRef, + macro_list: Option<&BrushMacroListRef>, + sender: &mut MessageSender, +) { + let guard = state.lock(); + match guard.stamp.source() { + Some(TileBook::Brush(source)) => { + send_copy_brush_pages(source, update, brush, macro_list, sender) + } + _ => { + send_update_brush_icons(guard.tile_set.as_ref(), update, brush, macro_list, sender); + } + } +} + +fn send_copy_brush_pages( + source: &TileMapBrushResource, + update: &TransTilesUpdate, + destination: &TileMapBrushResource, + macro_list: Option<&BrushMacroListRef>, + sender: &mut MessageSender, +) { + let mut commands = CommandGroup::default(); + let same_brush = source == destination; + for (p, data) in update.iter() { + let Some(RotTileHandle { + element: + StampElement { + handle, + source: element_source, + }, + transform, + }) = data + else { + make_commands_to_erase_brush_page(destination, *p, macro_list, &mut commands); + continue; + }; + if let Some(ResourceTilePosition::Page(source_page)) = element_source { + let source = source.data_ref(); + let tile_set = source.tile_set(); + let mut page_data = source.pages.get(source_page).cloned(); + if let (Some(tile_set), Some(page_data)) = (tile_set, page_data.as_mut()) { + let icon = page_data.icon; + let icon = tile_set + .data_ref() + .get_transformed_version(*transform, icon) + .unwrap_or(icon); + page_data.icon = icon; + } + if same_brush { + drop(source); + make_commands_to_copy_brush_page( + destination, + *source_page, + *p, + macro_list, + &mut commands, + ); + } + commands.push(SetBrushPageCommand { + brush: destination.clone(), + position: *p, + page: page_data, + }); + } else { + let source = source.data_ref(); + let icon = if let Some(tile_set) = source.tile_set() { + tile_set + .data_ref() + .get_transformed_version(*transform, *handle) + .unwrap_or(*handle) + } else { + *handle + }; + commands.push(ModifyBrushPageIconCommand::new( + destination.clone(), + *p, + icon, + )); + } + } + if !commands.is_empty() { + sender.do_command(commands.with_custom_name("Edit Tile Set Pages")); + } +} + +fn send_update_brush_icons( + tile_set: Option<&TileSetResource>, + update: &TransTilesUpdate, + destination: &TileMapBrushResource, + macro_list: Option<&BrushMacroListRef>, + sender: &mut MessageSender, +) { + let mut commands = CommandGroup::default(); + for (p, data) in update.iter() { + let Some(RotTileHandle { + element: StampElement { handle, .. }, + transform, + }) = data + else { + make_commands_to_erase_brush_page(destination, *p, macro_list, &mut commands); + continue; + }; + let icon = if let Some(tile_set) = tile_set { + tile_set + .data_ref() + .get_transformed_version(*transform, *handle) + .unwrap_or(*handle) + } else { + *handle + }; + commands.push(ModifyBrushPageIconCommand::new( + destination.clone(), + *p, + icon, + )); + } + if !commands.is_empty() { + sender.do_command(commands.with_custom_name("Edit Tile Set Pages")); + } +} + +fn make_commands_to_erase_brush_page( + brush: &TileMapBrushResource, + page: Vector2, + macro_list: Option<&BrushMacroListRef>, + commands: &mut CommandGroup, +) { + if let Some(macro_list) = macro_list { + let macro_list = macro_list.lock(); + for instance in brush.data_ref().macros.iter() { + if let Some(m) = macro_list.get_by_uuid(&instance.macro_id) { + if let Some(command) = m.copy_page( + None, + page, + &BrushMacroInstance { + brush: brush.clone(), + settings: instance.settings.clone(), + }, + ) { + commands.push_command(command); + } + } + } + } + commands.push(SetBrushPageCommand { + brush: brush.clone(), + position: page, + page: None, + }); +} + +fn make_commands_to_copy_brush_page( + brush: &TileMapBrushResource, + from: Vector2, + to: Vector2, + macro_list: Option<&BrushMacroListRef>, + commands: &mut CommandGroup, +) { + if let Some(macro_list) = macro_list { + let macro_list = macro_list.lock(); + for instance in brush.data_ref().macros.iter() { + if let Some(m) = macro_list.get_by_uuid(&instance.macro_id) { + if let Some(command) = m.copy_page( + Some(from), + to, + &BrushMacroInstance { + brush: brush.clone(), + settings: instance.settings.clone(), + }, + ) { + commands.push_command(command); + } + } + } + } +} + +fn make_commands_to_copy_brush_tile( + brush: &TileMapBrushResource, + from: Option, + to: TileDefinitionHandle, + macro_list: Option<&BrushMacroListRef>, + commands: &mut CommandGroup, +) { + if let Some(macro_list) = macro_list { + let macro_list = macro_list.lock(); + for instance in brush.data_ref().macros.iter() { + if let Some(m) = macro_list.get_by_uuid(&instance.macro_id) { + if let Some(command) = m.copy_cell( + from, + to, + &BrushMacroInstance { + brush: brush.clone(), + settings: instance.settings.clone(), + }, + ) { + commands.push_command(command); + } + } + } + } +} + impl PaletteWidget { /// Each brush and tile set has two palette areas: the pages and the tiles within each page. /// These two areas are called stages, and each of the two stages needs to be handled separately. @@ -393,10 +709,7 @@ impl PaletteWidget { fn sync_to_state(&mut self, ui: &mut UserInterface) { let state = self.state.lock(); let drawing_mode = if self.editable { - match self.kind { - TilePaletteStage::Pages => DrawingMode::Pick, - TilePaletteStage::Tiles => state.drawing_mode, - } + state.drawing_mode } else { DrawingMode::Pick }; @@ -437,7 +750,7 @@ impl PaletteWidget { .push(ColliderHighlight::new(pos, color, tile_collider.clone())); }); } - if self.editable && self.kind == TilePaletteStage::Tiles { + if self.editable { self.overlay.clear(); self.highlight.clear(); match state.drawing_mode { @@ -525,11 +838,37 @@ impl PaletteWidget { Vector2::new((pos.x / s.x).floor() as i32, (pos.y / s.y).floor() as i32) } fn send_update(&mut self) { + match self.kind { + TilePaletteStage::Pages => self.send_update_pages(), + TilePaletteStage::Tiles => self.send_update_tiles(), + } + } + fn send_update_pages(&mut self) { + assert_eq!(self.kind, TilePaletteStage::Pages); + match &self.content { + TileBook::Empty => (), + TileBook::TileSet(tile_set) => { + send_update_tile_set_pages(tile_set, &self.update, &self.state, &mut self.sender); + } + TileBook::Brush(brush) => { + send_update_brush_pages( + brush, + &self.update, + &self.state, + self.macro_list.as_ref(), + &mut self.sender, + ); + } + } + self.update.clear(); + } + fn send_update_tiles(&mut self) { assert_eq!(self.kind, TilePaletteStage::Tiles); let Some(page) = self.page else { return; }; let state = self.state.lock(); + let source = state.stamp.source(); let source_set = state.tile_set.as_ref(); match &self.content { TileBook::Empty => (), @@ -553,12 +892,37 @@ impl PaletteWidget { .cloned() .or_else(|| resource.state().data()?.tile_set()) { + let mut commands = CommandGroup::default(); + let same_brush = if let Some(TileBook::Brush(brush)) = source.as_ref() { + brush == resource + } else { + false + }; + for (p, d) in self.update.iter() { + let from = if same_brush { + d.as_ref().and_then(|d| d.element.source?.handle()) + } else { + None + }; + let Some(to) = TileDefinitionHandle::try_new(page, *p) else { + continue; + }; + make_commands_to_copy_brush_tile( + resource, + from, + to, + self.macro_list.as_ref(), + &mut commands, + ); + } let mut source_set = TileSetRef::new(&source_set); - self.sender.do_command(SetBrushTilesCommand { + commands.push(SetBrushTilesCommand { brush: resource.clone(), page, tiles: self.update.build_tiles_update(&source_set.as_loaded()), }); + self.sender + .do_command(commands.with_custom_name("Edit Brush Tiles")); } self.update.clear(); } @@ -579,46 +943,13 @@ impl PaletteWidget { if state.selection_palette() != self.handle || !state.has_selection() { return false; } - match self.kind { - TilePaletteStage::Pages => { - let sel = state - .selection_positions() - .iter() - .copied() - .collect::>(); - drop(state); - let commands = sel - .into_iter() - .filter_map(|p| self.delete_page(p)) - .collect::>(); - self.sender - .do_command(CommandGroup::from(commands).with_custom_name("Delete Pages")); - } - TilePaletteStage::Tiles => { - for position in state.selection_positions() { - self.update.insert(*position, None); - } - drop(state); - self.send_update(); - } + for position in state.selection_positions() { + self.update.insert(*position, None); } + drop(state); + self.send_update(); true } - fn delete_page(&mut self, position: Vector2) -> Option { - match &self.content { - TileBook::Empty => None, - TileBook::TileSet(tile_set) => Some(Command::new(SetTileSetPageCommand { - tile_set: tile_set.clone(), - position, - page: None, - })), - TileBook::Brush(brush) => Some(Command::new(SetBrushPageCommand { - brush: brush.clone(), - position, - page: None, - })), - } - } fn set_page(&mut self, resource: TileBook, page: Option>, ui: &mut UserInterface) { let mut state = self.state.lock_mut("set_page"); if state.selection_palette() == self.handle { @@ -641,21 +972,21 @@ impl PaletteWidget { } fn drawing_mode(&self) -> DrawingMode { if self.editable { - match self.kind { - TilePaletteStage::Pages => DrawingMode::Pick, - TilePaletteStage::Tiles => self.current_tool, - } + self.current_tool } else { DrawingMode::Pick } } fn update_stamp(&self, state: &mut TileDrawStateGuardMut) { let page = self.page.unwrap_or_default(); - let brush = self.content.brush_ref().cloned(); - state.update_stamp(brush, self.content.get_tile_set(), |p| { - self.content - .get_stamp_element(ResourceTilePosition::new(self.stage(), page, p)) - }); + state.update_stamp( + Some(self.content.clone()), + self.content.get_tile_set(), + |p| { + self.content + .get_stamp_element(ResourceTilePosition::new(self.stage(), page, p)) + }, + ); } /// After the data changes in the tile set or the brush that the widget is displaying, /// call this method to rebuild the stamp from the currently selected tiles. This is necessary @@ -897,8 +1228,29 @@ impl PaletteWidget { .do_command(MoveTileSetPageCommand::new(tile_set, pages, offset)); } TileBook::Brush(brush) => { + let mut commands = CommandGroup::default(); + let from = pages.clone().into_boxed_slice(); + let to = pages.iter().map(|p| *p + offset).collect::>(); + if let Some(macro_list) = self.macro_list.as_ref() { + let macro_list = macro_list.lock(); + for instance in brush.data_ref().macros.iter() { + if let Some(m) = macro_list.get_by_uuid(&instance.macro_id) { + if let Some(command) = m.move_pages( + from.clone(), + to.clone(), + &BrushMacroInstance { + brush: brush.clone(), + settings: instance.settings.clone(), + }, + ) { + commands.push_command(command) + } + } + } + } + commands.push(MoveBrushPageCommand::new(brush, pages, offset)); self.sender - .do_command(MoveBrushPageCommand::new(brush, pages, offset)); + .do_command(commands.with_custom_name("Move Brush Pages")); } } } @@ -923,13 +1275,40 @@ impl PaletteWidget { .do_command(MoveTileSetTileCommand::new(tile_set, page, tiles, offset)); } TileBook::Brush(brush) => { + let mut commands = CommandGroup::default(); let data = brush.data_ref(); let tiles = tiles .filter(|p| data.has_tile_at(page, *p)) .collect::>(); + let from = tiles + .iter() + .filter_map(|p| TileDefinitionHandle::try_new(page, *p)) + .collect::>(); + let to = tiles + .iter() + .filter_map(|p| TileDefinitionHandle::try_new(page, *p + offset)) + .collect::>(); + if let Some(macro_list) = self.macro_list.as_ref() { + let macro_list = macro_list.lock(); + for instance in data.macros.iter() { + if let Some(m) = macro_list.get_by_uuid(&instance.macro_id) { + if let Some(command) = m.move_cells( + from.clone(), + to.clone(), + &BrushMacroInstance { + brush: brush.clone(), + settings: instance.settings.clone(), + }, + ) { + commands.push_command(command) + } + } + } + } drop(data); + commands.push(MoveBrushTileCommand::new(brush, page, tiles, offset)); self.sender - .do_command(MoveBrushTileCommand::new(brush, page, tiles, offset)); + .do_command(commands.with_custom_name("Move Brush Tiles")); } } } @@ -1297,10 +1676,7 @@ impl PaletteWidget { } fn is_overlay_visible(&self) -> bool { let drawing_mode = if self.editable { - match self.kind { - TilePaletteStage::Pages => DrawingMode::Pick, - TilePaletteStage::Tiles => self.state.lock().drawing_mode, - } + self.state.lock().drawing_mode } else { DrawingMode::Pick }; @@ -1497,10 +1873,7 @@ impl Control for PaletteWidget { self.commit_color(CURSOR_HIGHLIGHT_COLOR, ctx); } - if self.editable - && self.kind == TilePaletteStage::Tiles - && self.state.lock().drawing_mode == DrawingMode::Erase - { + if self.editable && self.state.lock().drawing_mode == DrawingMode::Erase { self.push_erase_area(line_thickness, ctx); self.commit_color(Color::RED, ctx); } @@ -1632,6 +2005,7 @@ pub struct PaletteWidgetBuilder { sender: MessageSender, state: TileDrawStateRef, macro_cells: Option, + macro_list: Option, kind: TilePaletteStage, editable: bool, } @@ -1651,6 +2025,7 @@ impl PaletteWidgetBuilder { sender, state, macro_cells: None, + macro_list: None, kind: TilePaletteStage::default(), editable: false, page: None, @@ -1693,6 +2068,13 @@ impl PaletteWidgetBuilder { self } + /// Giving the palette access to a list of macro cells allows it to draw outlines + /// around cells that are involved in some macros. + pub fn with_macro_list(mut self, macro_list: BrushMacroListRef) -> Self { + self.macro_list = Some(macro_list); + self + } + /// Build the [`PaletteWidget`]. pub fn build(self, ctx: &mut BuildContext) -> Handle { ctx.add_node(UiNode::new(PaletteWidget { @@ -1704,6 +2086,7 @@ impl PaletteWidgetBuilder { sender: self.sender, state: self.state, macro_cells: self.macro_cells, + macro_list: self.macro_list, overlay: PaletteOverlay::default(), content: self.tile_book, kind: self.kind, diff --git a/editor/src/plugins/tilemap/tile_inspector.rs b/editor/src/plugins/tilemap/tile_inspector.rs index 4e83d8103..4cfaf6175 100644 --- a/editor/src/plugins/tilemap/tile_inspector.rs +++ b/editor/src/plugins/tilemap/tile_inspector.rs @@ -1181,20 +1181,12 @@ impl TileInspector { TileBook::Empty => return, TileBook::TileSet(tile_set) => state .page_positions() - .map(|position| ModifyPageIconCommand { - tile_set: tile_set.clone(), - page: position, - icon, - }) + .map(|position| ModifyPageIconCommand::new(tile_set.clone(), position, icon)) .map(Command::new) .collect::>(), TileBook::Brush(brush) => state .page_positions() - .map(|position| ModifyBrushPageIconCommand { - brush: brush.clone(), - page: position, - icon, - }) + .map(|position| ModifyBrushPageIconCommand::new(brush.clone(), position, icon)) .map(Command::new) .collect::>(), }; diff --git a/editor/src/plugins/tilemap/tileset.rs b/editor/src/plugins/tilemap/tileset.rs index c820341e3..9eed2f2f2 100644 --- a/editor/src/plugins/tilemap/tileset.rs +++ b/editor/src/plugins/tilemap/tileset.rs @@ -292,6 +292,7 @@ impl TileSetEditor { .with_resource(tile_book.clone()) .with_kind(TilePaletteStage::Pages) .with_editable(true) + .with_macro_list(macro_list.clone()) .build(ctx); let tiles_palette = PaletteWidgetBuilder::new( @@ -305,6 +306,7 @@ impl TileSetEditor { .with_resource(tile_book.clone()) .with_kind(TilePaletteStage::Tiles) .with_editable(true) + .with_macro_list(macro_list.clone()) .with_macro_cells(brush_macro_cell_sets.clone()) .build(ctx); diff --git a/editor/src/plugins/tilemap/wfc.rs b/editor/src/plugins/tilemap/wfc.rs index 6be2a01e8..3bad625ee 100644 --- a/editor/src/plugins/tilemap/wfc.rs +++ b/editor/src/plugins/tilemap/wfc.rs @@ -18,6 +18,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +use std::hash::Hash; + use fyrox::{ asset::{ untyped::{ResourceKind, UntypedResource}, @@ -45,7 +47,7 @@ use fyrox::{ }; use crate::{ - command::{Command, CommandContext, CommandTrait}, + command::{Command, CommandContext, CommandGroup, CommandTrait}, send_sync_message, }; @@ -399,7 +401,7 @@ impl BrushMacro for WfcMacro { cell_set.extend(data.cells.iter()); } - fn create_cell(&self, context: &BrushMacroCell) -> Option { + fn create_cell(&self, context: &BrushMacroCellContext) -> Option { let instance = context.settings()?; let cell = context.cell?; Some(Command::new(SetCellCommand { @@ -410,7 +412,37 @@ impl BrushMacro for WfcMacro { })) } - fn remove_cell(&self, context: &BrushMacroCell) -> Option { + fn move_cells( + &self, + from: Box<[TileDefinitionHandle]>, + to: Box<[TileDefinitionHandle]>, + context: &BrushMacroInstance, + ) -> Option { + let instance = context.settings()?; + Some(Command::new(MoveCellsCommand::new( + context.brush.clone(), + from, + to, + instance, + ))) + } + + fn move_pages( + &self, + from: Box<[Vector2]>, + to: Box<[Vector2]>, + context: &BrushMacroInstance, + ) -> Option { + let instance = context.settings()?; + Some(Command::new(MovePagesCommand::new( + context.brush.clone(), + from, + to, + instance, + ))) + } + + fn remove_cell(&self, context: &BrushMacroCellContext) -> Option { let instance = context.settings()?; let cell = context.cell?; Some(Command::new(SetCellCommand { @@ -421,6 +453,69 @@ impl BrushMacro for WfcMacro { })) } + fn copy_cell( + &self, + source: Option, + destination: TileDefinitionHandle, + context: &BrushMacroInstance, + ) -> Option { + let instance = context.settings::()?; + let guard = instance.data_ref(); + let included = if let Some(source) = source { + guard.cells.contains(&source) + } else { + false + }; + Some(Command::new(SetCellCommand { + brush: context.brush.clone(), + cell: destination, + instance: instance.clone(), + included, + })) + } + + fn copy_page( + &self, + source: Option>, + destination: Vector2, + context: &BrushMacroInstance, + ) -> Option { + let instance = context.settings::()?; + let guard = instance.data_ref(); + let used = guard + .cells + .iter() + .filter(|h| Some(h.page()) == source) + .map(|h| h.tile()) + .collect::>(); + let commands = guard + .cells + .iter() + .filter_map(|handle| { + if Some(handle.page()) == source { + let cell = TileDefinitionHandle::try_new(destination, handle.tile())?; + Some(Command::new(SetCellCommand { + brush: context.brush.clone(), + cell, + instance: instance.clone(), + included: true, + })) + } else if handle.page() == destination && !used.contains(&handle.tile()) { + Some(Command::new(SetCellCommand { + brush: context.brush.clone(), + cell: *handle, + instance: instance.clone(), + included: false, + })) + } else { + None + } + }) + .collect::>(); + (!commands.is_empty()) + .then(|| Command::new(CommandGroup::from(commands).with_custom_name("Set Page Macros"))) + } + fn build_instance_editor( &mut self, context: &BrushMacroInstance, @@ -550,7 +645,7 @@ impl BrushMacro for WfcMacro { fn build_cell_editor( &mut self, - _context: &BrushMacroCell, + _context: &BrushMacroCellContext, _ctx: &mut BuildContext, ) -> Option> { None @@ -663,13 +758,11 @@ impl BrushMacro for WfcMacro { let mut propagator = TileSetWfcPropagator::default(); propagator.fill_from(constraint.deref()); for (&p, v) in update.iter() { - if let Some(StampElement { - brush_cell: Some(cell), - .. - }) = v - { - if instance.cells.contains(cell) { - propagator.add_cell(p); + if let Some(StampElement { source, .. }) = v { + if let Some(cell) = source.and_then(|s| s.handle()) { + if instance.cells.contains(&cell) { + propagator.add_cell(p); + } } } } @@ -737,6 +830,142 @@ impl CommandTrait for SetCellCommand { } } +#[derive(Debug)] +struct MoveCellsCommand { + pub brush: TileMapBrushResource, + pub from: Box<[TileDefinitionHandle]>, + pub to: Box<[TileDefinitionHandle]>, + pub instance: Resource, + data: Box<[bool]>, +} + +impl MoveCellsCommand { + fn new( + brush: TileMapBrushResource, + from: Box<[TileDefinitionHandle]>, + to: Box<[TileDefinitionHandle]>, + instance: Resource, + ) -> Self { + Self { + data: vec![false; from.len()].into_boxed_slice(), + brush, + from, + to, + instance, + } + } + fn swap(&mut self) { + let mut instance = self.instance.data_ref(); + for (&from, data) in self.from.iter().zip(self.data.iter_mut()) { + swap_set_entry(&mut instance.cells, from, data); + } + for (&to, data) in self.to.iter().zip(self.data.iter_mut()) { + swap_set_entry(&mut instance.cells, to, data); + } + std::mem::swap(&mut self.from, &mut self.to); + self.brush.data_ref().change_flag.set(); + } +} + +fn swap_set_entry(set: &mut FxHashSet, value: T, contains: &mut bool) { + let old_contains = set.contains(&value); + if old_contains == *contains { + return; + }; + if *contains { + set.insert(value); + } else { + set.remove(&value); + } + *contains = old_contains; +} + +impl CommandTrait for MoveCellsCommand { + fn name(&mut self, _context: &dyn CommandContext) -> String { + "Update Autotile Cell".into() + } + + fn execute(&mut self, _context: &mut dyn CommandContext) { + self.swap(); + } + + fn revert(&mut self, _context: &mut dyn CommandContext) { + self.swap(); + } +} + +#[derive(Debug)] +struct MovePagesCommand { + pub brush: TileMapBrushResource, + pub from: Box<[Vector2]>, + pub to: Box<[Vector2]>, + pub instance: Resource, + data: Box<[FxHashSet>]>, +} + +impl MovePagesCommand { + fn new( + brush: TileMapBrushResource, + from: Box<[Vector2]>, + to: Box<[Vector2]>, + instance: Resource, + ) -> Self { + Self { + data: vec![FxHashSet::default(); from.len()].into_boxed_slice(), + brush, + from, + to, + instance, + } + } + fn swap(&mut self) { + let mut instance = self.instance.data_ref(); + for (&from, data) in self.from.iter().zip(self.data.iter_mut()) { + swap_page(&mut instance.cells, from, data); + } + for (&to, data) in self.to.iter().zip(self.data.iter_mut()) { + swap_page(&mut instance.cells, to, data); + } + std::mem::swap(&mut self.from, &mut self.to); + self.brush.data_ref().change_flag.set(); + } +} + +fn swap_page( + set: &mut FxHashSet, + position: Vector2, + data: &mut FxHashSet>, +) { + let mut new_data = FxHashSet::default(); + set.retain(|h| { + if h.page() != position { + true + } else { + new_data.insert(h.tile()); + false + } + }); + set.extend( + data.drain() + .filter_map(|p| TileDefinitionHandle::try_new(position, p)), + ); + *data = new_data; +} + +impl CommandTrait for MovePagesCommand { + fn name(&mut self, _context: &dyn CommandContext) -> String { + "Update Autotile Cell".into() + } + + fn execute(&mut self, _context: &mut dyn CommandContext) { + self.swap(); + } + + fn revert(&mut self, _context: &mut dyn CommandContext) { + self.swap(); + } +} + #[derive(Debug)] struct SetPatternPropCommand { pub brush: TileMapBrushResource, diff --git a/fyrox-impl/src/scene/tilemap/autotile.rs b/fyrox-impl/src/scene/tilemap/autotile.rs index 808756bff..dea7568f4 100644 --- a/fyrox-impl/src/scene/tilemap/autotile.rs +++ b/fyrox-impl/src/scene/tilemap/autotile.rs @@ -325,15 +325,11 @@ impl TileSetAutoTiler { else { continue; }; - let brush_cell = update - .get(pos) - .cloned() - .flatten() - .and_then(|el| el.brush_cell); + let source = update.get(pos).cloned().flatten().and_then(|el| el.source); let handle = if handle.is_empty() { None } else { - Some(StampElement { handle, brush_cell }) + Some(StampElement { handle, source }) }; _ = update.insert(*pos, handle); } @@ -492,15 +488,11 @@ impl TileSetWfcPropagator { let Some(&handle) = value_map.get_random(rng, pat) else { continue; }; - let brush_cell = update - .get(pos) - .cloned() - .flatten() - .and_then(|el| el.brush_cell); + let source = update.get(pos).cloned().flatten().and_then(|el| el.source); let handle = if handle.is_empty() { None } else { - Some(StampElement { handle, brush_cell }) + Some(StampElement { handle, source }) }; _ = update.insert(*pos, handle); } diff --git a/fyrox-impl/src/scene/tilemap/brush.rs b/fyrox-impl/src/scene/tilemap/brush.rs index 3ac616d63..0aa8be159 100644 --- a/fyrox-impl/src/scene/tilemap/brush.rs +++ b/fyrox-impl/src/scene/tilemap/brush.rs @@ -494,16 +494,19 @@ impl TileMapBrush { /// The stamp element for the given position, if the tile in that cell is used /// to create a stamp. The [`StampElement::handle`] refers to the location of the tile within the - /// tile set, while the [`StampElement::brush_cell`] refers to the location of the tile within + /// tile set, while the [`StampElement::source`] refers to the location of the tile within /// the brush. pub fn stamp_element(&self, position: ResourceTilePosition) -> Option { match position.stage() { - TilePaletteStage::Pages => self.redirect_handle(position).map(|h| h.into()), + TilePaletteStage::Pages => self.redirect_handle(position).map(|handle| StampElement { + handle, + source: Some(position), + }), TilePaletteStage::Tiles => { let page = self.pages.get(&position.page())?; Some(StampElement { handle: *page.tiles.get(&position.stage_position())?, - brush_cell: position.handle(), + source: Some(position), }) } } diff --git a/fyrox-impl/src/scene/tilemap/mod.rs b/fyrox-impl/src/scene/tilemap/mod.rs index 2bbfefe72..bfa1dc853 100644 --- a/fyrox-impl/src/scene/tilemap/mod.rs +++ b/fyrox-impl/src/scene/tilemap/mod.rs @@ -409,6 +409,17 @@ pub enum ResourceTilePosition { Tile(Vector2, Vector2), } +impl Display for ResourceTilePosition { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ResourceTilePosition::Page(p) => write!(f, "Page({},{})", p.x, p.y), + ResourceTilePosition::Tile(page, pos) => { + write!(f, "({},{}):({},{})", page.x, page.y, pos.x, pos.y) + } + } + } +} + impl From for ResourceTilePosition { fn from(value: TileDefinitionHandle) -> Self { Self::Tile(value.page(), value.tile()) @@ -733,11 +744,7 @@ impl TileBook { pub fn get_stamp_element(&self, position: ResourceTilePosition) -> Option { match self { TileBook::Empty => None, - TileBook::TileSet(r) => r - .state() - .data()? - .redirect_handle(position) - .map(|h| h.into()), + TileBook::TileSet(r) => r.state().data()?.stamp_element(position), TileBook::Brush(r) => r.state().data()?.stamp_element(position), } } diff --git a/fyrox-impl/src/scene/tilemap/tile_source.rs b/fyrox-impl/src/scene/tilemap/tile_source.rs index 816f02786..0aa94cc17 100644 --- a/fyrox-impl/src/scene/tilemap/tile_source.rs +++ b/fyrox-impl/src/scene/tilemap/tile_source.rs @@ -273,7 +273,7 @@ impl TileRegion { /// A trait for types that can produce a TileDefinitionHandle upon demand, /// for use with drawing on tilemaps. pub trait TileSource { - /// The brush where these tiles were originally taken from. + /// The source where these tiles were originally taken from. fn brush(&self) -> Option<&TileMapBrushResource>; /// The transformation that should be applied to the tiles before they are written. fn transformation(&self) -> OrthoTransformation; @@ -376,7 +376,7 @@ pub struct Stamp { #[visit(skip)] elements: OrthoTransformMap, #[visit(skip)] - brush: Option, + source: Option, } /// Each cell of a stamp must have a tile handle and it may optionally have @@ -386,14 +386,14 @@ pub struct StampElement { /// The stamp cell's tile handle pub handle: TileDefinitionHandle, /// The brush cell that this stamp element came from. - pub brush_cell: Option, + pub source: Option, } impl From for StampElement { fn from(handle: TileDefinitionHandle) -> Self { Self { handle, - brush_cell: None, + source: None, } } } @@ -432,7 +432,7 @@ impl DerefMut for Tiles { impl TileSource for Stamp { fn brush(&self) -> Option<&TileMapBrushResource> { - self.brush.as_ref() + self.source.as_ref().and_then(|s| s.brush_ref()) } fn transformation(&self) -> OrthoTransformation { self.transform @@ -443,6 +443,10 @@ impl TileSource for Stamp { } impl Stamp { + /// The resource where this stamp was taken from. + pub fn source(&self) -> Option<&TileBook> { + self.source.as_ref() + } /// Iterate over the tile handles of the stamp. pub fn tile_iter(&self) -> impl Iterator + '_ { self.elements.values().map(|s| s.handle) @@ -479,10 +483,10 @@ impl Stamp { /// The transform is set to identity. pub fn build, StampElement)> + Clone>( &mut self, - brush: Option, + book: Option, source: I, ) { - self.brush = brush; + self.source = book; self.clear(); let mut rect = OptionTileRect::default(); for (p, _) in source.clone() { diff --git a/fyrox-impl/src/scene/tilemap/tileset.rs b/fyrox-impl/src/scene/tilemap/tileset.rs index 894e6bb09..27f0ed99a 100644 --- a/fyrox-impl/src/scene/tilemap/tileset.rs +++ b/fyrox-impl/src/scene/tilemap/tileset.rs @@ -1345,6 +1345,17 @@ pub struct TileSet { } impl TileSet { + /// The stamp element for the given position, if the tile in that cell is used + /// to create a stamp. The [`StampElement::handle`] refers to the location of the tile within the + /// tile set, while the [`StampElement::source`] refers to the location of the tile within + /// the brush. + pub fn stamp_element(&self, position: ResourceTilePosition) -> Option { + self.redirect_handle(position).map(|handle| StampElement { + handle, + source: Some(position), + }) + } + /// Iterate all valid tile handles. pub fn all_tiles(&self) -> TileSetHandleIterator { TileSetHandleIterator::new(Some(self.pages.iter())) diff --git a/fyrox-impl/src/scene/tilemap/update.rs b/fyrox-impl/src/scene/tilemap/update.rs index 2b24c6ec9..6eed42c70 100644 --- a/fyrox-impl/src/scene/tilemap/update.rs +++ b/fyrox-impl/src/scene/tilemap/update.rs @@ -618,9 +618,9 @@ impl Debug for MacroTilesUpdate { f.write_str("MacroTilesUpdate")?; for (p, v) in self.0.iter() { write!(f, " ({:2},{:2})->", p.x, p.y)?; - if let Some(StampElement { handle, brush_cell }) = v { + if let Some(StampElement { handle, source }) = v { write!(f, "{handle}")?; - if let Some(cell) = brush_cell { + if let Some(cell) = source { write!(f, "[{cell}]")?; } } else { @@ -737,7 +737,7 @@ impl TransTilesUpdate { *pos, Some(StampElement { handle, - brush_cell: element.brush_cell, + source: element.source, }), ); } else { From 86705d3952a42008899fbbb0a970a4f6dd7eccd9 Mon Sep 17 00:00:00 2001 From: b-guild Date: Sun, 9 Mar 2025 19:46:46 -0700 Subject: [PATCH 2/3] Remove redundant BrushMacro method remove_cell --- editor/src/plugins/tilemap/autotile.rs | 11 ----------- editor/src/plugins/tilemap/brush_macro.rs | 4 ---- editor/src/plugins/tilemap/wfc.rs | 11 ----------- 3 files changed, 26 deletions(-) diff --git a/editor/src/plugins/tilemap/autotile.rs b/editor/src/plugins/tilemap/autotile.rs index 973b1e592..bf33fa6cc 100644 --- a/editor/src/plugins/tilemap/autotile.rs +++ b/editor/src/plugins/tilemap/autotile.rs @@ -291,17 +291,6 @@ impl BrushMacro for AutoTileMacro { })) } - fn remove_cell(&self, context: &BrushMacroCellContext) -> Option { - let instance = context.settings()?; - let cell = context.cell?; - Some(Command::new(SetCellCommand { - brush: context.brush.clone(), - cell, - instance, - data: None, - })) - } - fn move_cells( &self, from: Box<[TileDefinitionHandle]>, diff --git a/editor/src/plugins/tilemap/brush_macro.rs b/editor/src/plugins/tilemap/brush_macro.rs index 70413d4d7..a652574f7 100644 --- a/editor/src/plugins/tilemap/brush_macro.rs +++ b/editor/src/plugins/tilemap/brush_macro.rs @@ -297,10 +297,6 @@ pub trait BrushMacro: 'static + Send + Sync { /// the widgets that edit the data to change, but that will wait until /// [`BrushMacro::sync_cell_editors`] is called. fn create_cell(&self, context: &BrushMacroCellContext) -> Option; - /// Create a command to modify the given instances's data to remove the given cell. - /// None is returned if no command is necessary, such as if the cell is already excluded - /// or no cell is selected. - fn remove_cell(&self, context: &BrushMacroCellContext) -> Option; /// Create a command to move cell data from the given cells to the given cells. /// The two lists of cells should always be the same length. /// This method works on multiple cells at once because `from` and `to` may diff --git a/editor/src/plugins/tilemap/wfc.rs b/editor/src/plugins/tilemap/wfc.rs index 3bad625ee..6825f418b 100644 --- a/editor/src/plugins/tilemap/wfc.rs +++ b/editor/src/plugins/tilemap/wfc.rs @@ -442,17 +442,6 @@ impl BrushMacro for WfcMacro { ))) } - fn remove_cell(&self, context: &BrushMacroCellContext) -> Option { - let instance = context.settings()?; - let cell = context.cell?; - Some(Command::new(SetCellCommand { - brush: context.brush.clone(), - cell, - instance, - included: false, - })) - } - fn copy_cell( &self, source: Option, From 0802062fb9051e7e5e0dc5994c4748adc5c0199f Mon Sep 17 00:00:00 2001 From: b-guild Date: Sun, 9 Mar 2025 20:05:24 -0700 Subject: [PATCH 3/3] Correction to tile map doc comment. --- fyrox-impl/src/scene/tilemap/mod.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/fyrox-impl/src/scene/tilemap/mod.rs b/fyrox-impl/src/scene/tilemap/mod.rs index bfa1dc853..2ad6397e8 100644 --- a/fyrox-impl/src/scene/tilemap/mod.rs +++ b/fyrox-impl/src/scene/tilemap/mod.rs @@ -736,11 +736,10 @@ impl TileBook { TileBook::Brush(r) => r.state().data()?.redirect_handle(position), } } - /// The StampElement for the given position in this resource. For tile sets the element's - /// [`StampElement::brush_cell`] is None. - /// For brushes, the [`StampElement::handle`] refers to the location of the tile within the - /// tile set, while the [`StampElement::brush_cell`] refers to the location of the tile within - /// the brush. + /// The StampElement for the given position in this resource. + /// For brushes, the [`StampElement::handle`] refers to the ultimate location of the tile within the + /// tile set, while the [`StampElement::source`] refers to the location of the tile within + /// the brush or tile set that was used to create the stamp. pub fn get_stamp_element(&self, position: ResourceTilePosition) -> Option { match self { TileBook::Empty => None,