Merge pull request #748 from b-guild/tilemapdata

Improved Tile Set Editing
This commit is contained in:
Dmitry Stepanov
2025-03-10 19:14:15 +03:00
committed by GitHub
16 changed files with 1062 additions and 159 deletions
+25
View File
@@ -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<dyn CommandTrait>);
impl Command {
/// Create a command from the given `CommandTrait` object.
pub fn new<C: CommandTrait>(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<Command>,
@@ -123,19 +139,28 @@ impl From<Vec<Command>> for CommandGroup {
}
impl CommandGroup {
/// Add an object of the `CommandTriat` to the group.
pub fn push<C: CommandTrait>(&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<S: AsRef<str>>(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()
}
+220 -13
View File
@@ -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<Item = NeededTerrain<Vector2<i32>, 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<Command> {
fn create_cell(&self, context: &BrushMacroCellContext) -> Option<Command> {
let instance = context.settings()?;
let cell = context.cell?;
Some(Command::new(SetCellCommand {
@@ -291,17 +291,99 @@ impl BrushMacro for AutoTileMacro {
}))
}
fn remove_cell(&self, context: &BrushMacroCell) -> Option<Command> {
let instance = context.settings()?;
let cell = context.cell?;
fn move_cells(
&self,
from: Box<[TileDefinitionHandle]>,
to: Box<[TileDefinitionHandle]>,
context: &BrushMacroInstance,
) -> Option<Command> {
let instance = context.settings::<AutoTileInstance>()?;
Some(Command::new(MoveCellsCommand::new(
context.brush.clone(),
from,
to,
instance,
)))
}
fn move_pages(
&self,
from: Box<[Vector2<i32>]>,
to: Box<[Vector2<i32>]>,
context: &BrushMacroInstance,
) -> Option<Command> {
let instance = context.settings::<AutoTileInstance>()?;
Some(Command::new(MovePagesCommand::new(
context.brush.clone(),
from,
to,
instance,
)))
}
fn copy_cell(
&self,
source: Option<TileDefinitionHandle>,
destination: TileDefinitionHandle,
context: &BrushMacroInstance,
) -> Option<Command> {
let instance = context.settings::<AutoTileInstance>()?;
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,
instance,
data: None,
cell: destination,
instance: instance.clone(),
data,
}))
}
fn copy_page(
&self,
source: Option<Vector2<i32>>,
destination: Vector2<i32>,
context: &BrushMacroInstance,
) -> Option<Command> {
let instance = context.settings::<AutoTileInstance>()?;
let guard = instance.data_ref();
let used = guard
.cells
.keys()
.filter(|h| Some(h.page()) == source)
.map(|h| h.tile())
.collect::<Vec<_>>();
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::<Vec<_>>();
(!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 +442,7 @@ impl BrushMacro for AutoTileMacro {
fn build_cell_editor(
&mut self,
context: &BrushMacroCell,
context: &BrushMacroCellContext,
ctx: &mut BuildContext,
) -> Option<Handle<UiNode>> {
let instance = context.settings::<AutoTileInstance>().unwrap();
@@ -521,8 +603,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 +697,129 @@ impl CommandTrait for SetCellCommand {
}
}
#[derive(Debug)]
struct MoveCellsCommand {
pub brush: TileMapBrushResource,
pub from: Box<[TileDefinitionHandle]>,
pub to: Box<[TileDefinitionHandle]>,
pub instance: Resource<AutoTileInstance>,
data: Box<[Option<CellData>]>,
}
impl MoveCellsCommand {
fn new(
brush: TileMapBrushResource,
from: Box<[TileDefinitionHandle]>,
to: Box<[TileDefinitionHandle]>,
instance: Resource<AutoTileInstance>,
) -> 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<i32>]>,
pub to: Box<[Vector2<i32>]>,
pub instance: Resource<AutoTileInstance>,
data: Box<[FxHashMap<Vector2<i32>, CellData>]>,
}
impl MovePagesCommand {
fn new(
brush: TileMapBrushResource,
from: Box<[Vector2<i32>]>,
to: Box<[Vector2<i32>]>,
instance: Resource<AutoTileInstance>,
) -> 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<T: Default>(
map: &mut FxHashMap<TileDefinitionHandle, T>,
position: Vector2<i32>,
data: &mut FxHashMap<Vector2<i32>, 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,
+48 -12
View File
@@ -156,8 +156,8 @@ pub struct MacroMessageContext {
pub cell: Option<TileDefinitionHandle>,
}
impl From<BrushMacroCell> for MacroMessageContext {
fn from(value: BrushMacroCell) -> Self {
impl From<BrushMacroCellContext> 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<TileDefinitionHandle>,
}
impl From<BrushMacroCell> for BrushMacroInstance {
fn from(value: BrushMacroCell) -> Self {
impl From<BrushMacroCellContext> for BrushMacroInstance {
fn from(value: BrushMacroCellContext) -> Self {
Self {
brush: value.brush,
settings: value.settings,
@@ -210,7 +210,7 @@ impl From<BrushMacroCell> for BrushMacroInstance {
}
}
impl BrushMacroCell {
impl BrushMacroCellContext {
/// A typed reference to the configuration resource.
pub fn settings<T>(&self) -> Option<Resource<T>>
where
@@ -296,11 +296,47 @@ 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<Command>;
/// 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<Command>;
fn create_cell(&self, context: &BrushMacroCellContext) -> Option<Command>;
/// 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<Command>;
/// 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<i32>]>,
to: Box<[Vector2<i32>]>,
context: &BrushMacroInstance,
) -> Option<Command>;
/// 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<TileDefinitionHandle>,
destination: TileDefinitionHandle,
context: &BrushMacroInstance,
) -> Option<Command>;
/// 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<Vector2<i32>>,
destination: Vector2<i32>,
context: &BrushMacroInstance,
) -> Option<Command>;
/// 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 +365,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<Handle<UiNode>>;
/// Send the necessary messages to update the cell editor widgets to edit the data for
+30
View File
@@ -1127,13 +1127,26 @@ pub struct ModifyPageIconCommand {
pub tile_set: TileSetResource,
pub page: Vector2<i32>,
pub icon: TileDefinitionHandle,
error: bool,
}
impl ModifyPageIconCommand {
pub fn new(tile_set: TileSetResource, page: Vector2<i32>, 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<i32>,
pub icon: TileDefinitionHandle,
error: bool,
}
impl ModifyBrushPageIconCommand {
pub fn new(
brush: TileMapBrushResource,
page: Vector2<i32>,
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);
@@ -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,
+2 -3
View File
@@ -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<F>(
&mut self,
brush: Option<TileMapBrushResource>,
book: Option<TileBook>,
tile_set: Option<TileSetResource>,
tile_handle: F,
) where
@@ -555,7 +554,7 @@ impl TileDrawState {
{
self.tile_set = tile_set;
self.stamp.build(
brush,
book,
self.selection
.positions
.iter()
+446 -63
View File
@@ -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<MacroCellSetListRef>,
/// The cells which should be marked as being included in some macro.
#[visit(skip)]
#[reflect(hidden)]
macro_list: Option<BrushMacroListRef>,
#[visit(skip)]
#[reflect(hidden)]
colliders: Vec<ColliderHighlight>,
@@ -371,6 +376,317 @@ fn invert_transform(trans: &Matrix3<f32>) -> Matrix3<f32> {
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<i32>,
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<i32>,
to: Vector2<i32>,
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<TileDefinitionHandle>,
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::<Vec<_>>();
drop(state);
let commands = sel
.into_iter()
.filter_map(|p| self.delete_page(p))
.collect::<Vec<_>>();
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<i32>) -> Option<Command> {
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<Vector2<i32>>, 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::<Box<_>>();
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::<Vec<_>>();
let from = tiles
.iter()
.filter_map(|p| TileDefinitionHandle::try_new(page, *p))
.collect::<Box<_>>();
let to = tiles
.iter()
.filter_map(|p| TileDefinitionHandle::try_new(page, *p + offset))
.collect::<Box<_>>();
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<MacroCellSetListRef>,
macro_list: Option<BrushMacroListRef>,
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<UiNode> {
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,
+2 -10
View File
@@ -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::<Vec<_>>(),
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::<Vec<_>>(),
};
+2
View File
@@ -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);
+233 -15
View File
@@ -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<Command> {
fn create_cell(&self, context: &BrushMacroCellContext) -> Option<Command> {
let instance = context.settings()?;
let cell = context.cell?;
Some(Command::new(SetCellCommand {
@@ -410,17 +412,99 @@ impl BrushMacro for WfcMacro {
}))
}
fn remove_cell(&self, context: &BrushMacroCell) -> Option<Command> {
fn move_cells(
&self,
from: Box<[TileDefinitionHandle]>,
to: Box<[TileDefinitionHandle]>,
context: &BrushMacroInstance,
) -> Option<Command> {
let instance = context.settings()?;
let cell = context.cell?;
Some(Command::new(MoveCellsCommand::new(
context.brush.clone(),
from,
to,
instance,
)))
}
fn move_pages(
&self,
from: Box<[Vector2<i32>]>,
to: Box<[Vector2<i32>]>,
context: &BrushMacroInstance,
) -> Option<Command> {
let instance = context.settings()?;
Some(Command::new(MovePagesCommand::new(
context.brush.clone(),
from,
to,
instance,
)))
}
fn copy_cell(
&self,
source: Option<TileDefinitionHandle>,
destination: TileDefinitionHandle,
context: &BrushMacroInstance,
) -> Option<Command> {
let instance = context.settings::<WfcInstance>()?;
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,
instance,
included: false,
cell: destination,
instance: instance.clone(),
included,
}))
}
fn copy_page(
&self,
source: Option<Vector2<i32>>,
destination: Vector2<i32>,
context: &BrushMacroInstance,
) -> Option<Command> {
let instance = context.settings::<WfcInstance>()?;
let guard = instance.data_ref();
let used = guard
.cells
.iter()
.filter(|h| Some(h.page()) == source)
.map(|h| h.tile())
.collect::<Vec<_>>();
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::<Vec<_>>();
(!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 +634,7 @@ impl BrushMacro for WfcMacro {
fn build_cell_editor(
&mut self,
_context: &BrushMacroCell,
_context: &BrushMacroCellContext,
_ctx: &mut BuildContext,
) -> Option<Handle<UiNode>> {
None
@@ -663,13 +747,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 +819,142 @@ impl CommandTrait for SetCellCommand {
}
}
#[derive(Debug)]
struct MoveCellsCommand {
pub brush: TileMapBrushResource,
pub from: Box<[TileDefinitionHandle]>,
pub to: Box<[TileDefinitionHandle]>,
pub instance: Resource<WfcInstance>,
data: Box<[bool]>,
}
impl MoveCellsCommand {
fn new(
brush: TileMapBrushResource,
from: Box<[TileDefinitionHandle]>,
to: Box<[TileDefinitionHandle]>,
instance: Resource<WfcInstance>,
) -> 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<T: Hash + Eq>(set: &mut FxHashSet<T>, 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<i32>]>,
pub to: Box<[Vector2<i32>]>,
pub instance: Resource<WfcInstance>,
data: Box<[FxHashSet<Vector2<i32>>]>,
}
impl MovePagesCommand {
fn new(
brush: TileMapBrushResource,
from: Box<[Vector2<i32>]>,
to: Box<[Vector2<i32>]>,
instance: Resource<WfcInstance>,
) -> 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<TileDefinitionHandle>,
position: Vector2<i32>,
data: &mut FxHashSet<Vector2<i32>>,
) {
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,
+4 -12
View File
@@ -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);
}
+6 -3
View File
@@ -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<StampElement> {
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),
})
}
}
+16 -10
View File
@@ -409,6 +409,17 @@ pub enum ResourceTilePosition {
Tile(Vector2<i32>, Vector2<i32>),
}
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<TileDefinitionHandle> for ResourceTilePosition {
fn from(value: TileDefinitionHandle) -> Self {
Self::Tile(value.page(), value.tile())
@@ -725,19 +736,14 @@ 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<StampElement> {
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),
}
}
+11 -7
View File
@@ -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<StampElement>,
#[visit(skip)]
brush: Option<TileMapBrushResource>,
source: Option<TileBook>,
}
/// 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<TileDefinitionHandle>,
pub source: Option<ResourceTilePosition>,
}
impl From<TileDefinitionHandle> 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<Item = TileDefinitionHandle> + '_ {
self.elements.values().map(|s| s.handle)
@@ -479,10 +483,10 @@ impl Stamp {
/// The transform is set to identity.
pub fn build<I: Iterator<Item = (Vector2<i32>, StampElement)> + Clone>(
&mut self,
brush: Option<TileMapBrushResource>,
book: Option<TileBook>,
source: I,
) {
self.brush = brush;
self.source = book;
self.clear();
let mut rect = OptionTileRect::default();
for (p, _) in source.clone() {
+11
View File
@@ -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<StampElement> {
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()))
+3 -3
View File
@@ -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 {