diff --git a/editor/resources/brush.png b/editor/resources/brush.png new file mode 100644 index 000000000..15efa68dd Binary files /dev/null and b/editor/resources/brush.png differ diff --git a/editor/resources/eraser.png b/editor/resources/eraser.png new file mode 100644 index 000000000..09be4b914 Binary files /dev/null and b/editor/resources/eraser.png differ diff --git a/editor/resources/fill.png b/editor/resources/fill.png new file mode 100644 index 000000000..555f6eba1 Binary files /dev/null and b/editor/resources/fill.png differ diff --git a/editor/resources/grid-icon.png b/editor/resources/grid-icon.png index 3b83eeea1..4cb9c4968 100644 Binary files a/editor/resources/grid-icon.png and b/editor/resources/grid-icon.png differ diff --git a/editor/resources/pipette.png b/editor/resources/pipette.png new file mode 100644 index 000000000..195e55beb Binary files /dev/null and b/editor/resources/pipette.png differ diff --git a/editor/resources/rect_fill.png b/editor/resources/rect_fill.png new file mode 100644 index 000000000..3f45aea59 Binary files /dev/null and b/editor/resources/rect_fill.png differ diff --git a/editor/src/interaction/mod.rs b/editor/src/interaction/mod.rs index 7c00d61d3..abd4ca1af 100644 --- a/editor/src/interaction/mod.rs +++ b/editor/src/interaction/mod.rs @@ -148,7 +148,17 @@ pub trait InteractionMode: BaseInteractionMode { fn on_drop(&mut self, _engine: &mut Engine) {} - fn on_hot_key( + fn on_hot_key_pressed( + &mut self, + #[allow(unused_variables)] hotkey: &HotKey, + #[allow(unused_variables)] controller: &mut dyn SceneController, + #[allow(unused_variables)] engine: &mut Engine, + #[allow(unused_variables)] settings: &Settings, + ) -> bool { + false + } + + fn on_hot_key_released( &mut self, #[allow(unused_variables)] hotkey: &HotKey, #[allow(unused_variables)] controller: &mut dyn SceneController, @@ -283,6 +293,16 @@ impl InteractionModeContainer { .map(|mode| &mut **mode) } + pub fn of_type(&self) -> Option<&T> { + self.get(&T::type_uuid()) + .and_then(|mode| mode.as_any().downcast_ref()) + } + + pub fn of_type_mut(&mut self) -> Option<&mut T> { + self.get_mut(&T::type_uuid()) + .and_then(|mode| mode.as_any_mut().downcast_mut()) + } + pub fn drain(&mut self) -> impl Iterator> + '_ { self.try_notify_changed(); self.container.drain(..) diff --git a/editor/src/interaction/terrain.rs b/editor/src/interaction/terrain.rs index b9269e299..90e63196f 100644 --- a/editor/src/interaction/terrain.rs +++ b/editor/src/interaction/terrain.rs @@ -448,7 +448,7 @@ impl InteractionMode for TerrainInteractionMode { )); } - fn on_hot_key( + fn on_hot_key_pressed( &mut self, hotkey: &HotKey, _controller: &mut dyn SceneController, diff --git a/editor/src/lib.rs b/editor/src/lib.rs index 26dd11474..51b738c02 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -991,7 +991,7 @@ impl Editor { .current_interaction_mode .and_then(|current_mode| scene.interaction_modes.get_mut(¤t_mode)) { - processed |= current_interaction_mode.on_hot_key( + processed |= current_interaction_mode.on_hot_key_pressed( &hot_key, &mut *scene.controller, engine, @@ -1125,6 +1125,25 @@ impl Editor { } } } + } else if let Some(WidgetMessage::KeyUp(key)) = message.data() { + let hot_key = HotKey::Some { + code: *key, + modifiers, + }; + + if let Some(scene) = self.scenes.current_scene_entry_mut() { + if let Some(current_interaction_mode) = scene + .current_interaction_mode + .and_then(|current_mode| scene.interaction_modes.get_mut(¤t_mode)) + { + current_interaction_mode.on_hot_key_released( + &hot_key, + &mut *scene.controller, + engine, + &self.settings, + ); + } + } } } diff --git a/editor/src/plugins/tilemap/mod.rs b/editor/src/plugins/tilemap/mod.rs index 4ff1d4c58..3a49ed1df 100644 --- a/editor/src/plugins/tilemap/mod.rs +++ b/editor/src/plugins/tilemap/mod.rs @@ -5,7 +5,6 @@ mod preview; pub mod tile_set_import; pub mod tileset; -use crate::plugins::tilemap::preview::TileSetPreview; use crate::{ command::SetPropertyCommand, fyrox::{ @@ -27,6 +26,7 @@ use crate::{ scene::{ debug::Line, node::Node, + tilemap::tileset::TileSet, tilemap::{brush::TileMapBrush, TileMap, Tiles}, Scene, }, @@ -34,12 +34,16 @@ use crate::{ interaction::{make_interaction_mode_button, InteractionMode}, message::MessageSender, plugin::EditorPlugin, - plugins::tilemap::{palette::PaletteMessage, panel::TileMapPanel, tileset::TileSetEditor}, + plugins::tilemap::{ + palette::PaletteMessage, panel::TileMapPanel, preview::TileSetPreview, + tileset::TileSetEditor, + }, scene::{commands::GameSceneContext, controller::SceneController, GameScene, Selection}, settings::Settings, Editor, Message, }; -use fyrox::scene::tilemap::tileset::TileSet; +use fyrox::gui::key::HotKey; +use fyrox::gui::message::KeyCode; use std::sync::Arc; fn make_button( @@ -60,6 +64,18 @@ fn make_button( .build(ctx) } +pub enum DrawingMode { + Draw, + Erase, + FloodFill, + Pick { + click_grid_position: Option>, + }, + RectFill { + click_grid_position: Option>, + }, +} + struct InteractionContext { previous_tiles: Tiles, } @@ -67,12 +83,13 @@ struct InteractionContext { #[derive(TypeUuidProvider)] #[type_uuid(id = "33fa8ef9-a29c-45d4-a493-79571edd870a")] pub struct TileMapInteractionMode { - #[allow(dead_code)] tile_map: Handle, brush: Arc>, brush_position: Vector2, interaction_context: Option, sender: MessageSender, + #[allow(dead_code)] + drawing_mode: DrawingMode, } impl TileMapInteractionMode { @@ -286,6 +303,70 @@ impl InteractionMode for TileMapInteractionMode { fn uuid(&self) -> Uuid { Self::type_uuid() } + + fn on_hot_key_pressed( + &mut self, + hotkey: &HotKey, + _controller: &mut dyn SceneController, + _engine: &mut Engine, + _settings: &Settings, + ) -> bool { + if let HotKey::Some { code, .. } = hotkey { + match *code { + KeyCode::AltLeft => { + self.drawing_mode = DrawingMode::Pick { + click_grid_position: None, + }; + return true; + } + KeyCode::ShiftLeft => { + self.drawing_mode = DrawingMode::Erase; + return true; + } + KeyCode::ControlLeft => { + self.drawing_mode = DrawingMode::RectFill { + click_grid_position: None, + }; + return true; + } + _ => (), + } + } + false + } + + fn on_hot_key_released( + &mut self, + hotkey: &HotKey, + _controller: &mut dyn SceneController, + _engine: &mut Engine, + _settings: &Settings, + ) -> bool { + if let HotKey::Some { code, .. } = hotkey { + match *code { + KeyCode::AltLeft => { + if matches!(self.drawing_mode, DrawingMode::Pick { .. }) { + self.drawing_mode = DrawingMode::Draw; + return true; + } + } + KeyCode::ShiftLeft => { + if matches!(self.drawing_mode, DrawingMode::Erase) { + self.drawing_mode = DrawingMode::Draw; + return true; + } + } + KeyCode::ControlLeft => { + if matches!(self.drawing_mode, DrawingMode::RectFill { .. }) { + self.drawing_mode = DrawingMode::Draw; + return true; + } + } + _ => (), + } + } + false + } } #[derive(Default)] @@ -359,10 +440,11 @@ impl EditorPlugin for TileMapEditorPlugin { } } - let tile_map = editor - .scenes - .current_scene_entry_mut() - .and_then(|entry| entry.controller.downcast_mut::()) + let editor_scene_entry = editor.scenes.current_scene_entry_mut(); + + let tile_map = editor_scene_entry + .as_ref() + .and_then(|entry| entry.controller.downcast_ref::()) .and_then(|scene| { editor.engine.scenes[scene.scene] .graph @@ -375,14 +457,22 @@ impl EditorPlugin for TileMapEditorPlugin { self.tile_map, tile_map, &editor.message_sender, + editor_scene_entry, ); } } - fn on_update(&mut self, _editor: &mut Editor) { + fn on_update(&mut self, editor: &mut Editor) { if let Some(tile_set_editor) = self.tile_set_editor.as_mut() { tile_set_editor.update(); } + + if let Some(panel) = self.panel.as_mut() { + panel.update( + editor.engine.user_interfaces.first(), + editor.scenes.current_scene_entry_ref(), + ); + } } fn on_message(&mut self, message: &Message, editor: &mut Editor) { @@ -430,6 +520,7 @@ impl EditorPlugin for TileMapEditorPlugin { brush_position: Default::default(), interaction_context: None, sender: editor.message_sender.clone(), + drawing_mode: DrawingMode::Draw, }); self.panel = Some(TileMapPanel::new( diff --git a/editor/src/plugins/tilemap/panel.rs b/editor/src/plugins/tilemap/panel.rs index 79b3d7d97..273b05f24 100644 --- a/editor/src/plugins/tilemap/panel.rs +++ b/editor/src/plugins/tilemap/panel.rs @@ -1,12 +1,14 @@ -use crate::plugins::tilemap::commands::AddBrushTileCommand; use crate::{ asset::item::AssetItem, command::{Command, CommandGroup, SetPropertyCommand}, fyrox::{ core::{algebra::Vector2, pool::Handle, Uuid}, - graph::{BaseSceneGraph, SceneGraphNode}, + fxhash::FxHashSet, + graph::{BaseSceneGraph, SceneGraph, SceneGraphNode}, gui::{ border::BorderBuilder, + button::{Button, ButtonMessage}, + decorator::DecoratorMessage, dropdown_list::{DropdownListBuilder, DropdownListMessage}, grid::{Column, GridBuilder, Row}, message::{MessageDirection, UiMessage}, @@ -25,23 +27,32 @@ use crate::{ }, }, }, - gui::make_dropdown_list_option, + gui::{make_dropdown_list_option, make_image_button_with_tooltip}, + load_image, message::MessageSender, plugins::tilemap::{ - commands::{MoveBrushTilesCommand, RemoveBrushTileCommand, SetBrushTilesCommand}, + commands::{ + AddBrushTileCommand, MoveBrushTilesCommand, RemoveBrushTileCommand, + SetBrushTilesCommand, + }, palette::{ BrushTileViewBuilder, PaletteMessage, PaletteWidget, PaletteWidgetBuilder, TileViewMessage, }, + DrawingMode, TileMapInteractionMode, }, - scene::commands::GameSceneContext, + scene::{commands::GameSceneContext, container::EditorSceneEntry}, }; -use fyrox::fxhash::FxHashSet; pub struct TileMapPanel { pub window: Handle, pub palette: Handle, active_brush_selector: Handle, + draw_button: Handle, + erase_button: Handle, + flood_fill_button: Handle, + pick_button: Handle, + rect_fill_button: Handle, } fn generate_tiles( @@ -96,14 +107,62 @@ impl TileMapPanel { .build(ctx); let active_brush_selector = - DropdownListBuilder::new(WidgetBuilder::new().with_width(250.0)) + DropdownListBuilder::new(WidgetBuilder::new().with_width(250.0).with_height(20.0)) .with_opt_selected(selected_brush_index(tile_map)) .with_items(make_brush_entries(tile_map, ctx)) .build(ctx); + let width = 20.0; + let height = 20.0; + let draw_button = make_image_button_with_tooltip( + ctx, + width, + height, + load_image(include_bytes!("../../../resources/brush.png")), + "Draw with active brush.", + Some(0), + ); + let erase_button = make_image_button_with_tooltip( + ctx, + width, + height, + load_image(include_bytes!("../../../resources/eraser.png")), + "Erase with active brush.", + Some(0), + ); + let flood_fill_button = make_image_button_with_tooltip( + ctx, + width, + height, + load_image(include_bytes!("../../../resources/fill.png")), + "Flood fill with random tiles from current brush.", + Some(0), + ); + let pick_button = make_image_button_with_tooltip( + ctx, + width, + height, + load_image(include_bytes!("../../../resources/pipette.png")), + "Pick tiles for drawing from the tile map.", + Some(0), + ); + let rect_fill_button = make_image_button_with_tooltip( + ctx, + width, + height, + load_image(include_bytes!("../../../resources/rect_fill.png")), + "Fill the rectangle using the current brush.", + Some(0), + ); + let toolbar = WrapPanelBuilder::new( WidgetBuilder::new() .on_row(0) + .with_child(draw_button) + .with_child(erase_button) + .with_child(flood_fill_button) + .with_child(pick_button) + .with_child(rect_fill_button) .with_child(active_brush_selector), ) .with_orientation(Orientation::Horizontal) @@ -112,12 +171,12 @@ impl TileMapPanel { let content = GridBuilder::new(WidgetBuilder::new().with_child(toolbar).with_child( BorderBuilder::new(WidgetBuilder::new().on_row(1).with_child(palette)).build(ctx), )) - .add_row(Row::strict(23.0)) + .add_row(Row::auto()) .add_row(Row::stretch()) .add_column(Column::stretch()) .build(ctx); - let window = WindowBuilder::new(WidgetBuilder::new().with_width(250.0).with_height(350.0)) + let window = WindowBuilder::new(WidgetBuilder::new().with_width(250.0).with_height(400.0)) .open(false) .with_title(WindowTitle::text("Tile Map Control Panel")) .with_content(content) @@ -140,6 +199,11 @@ impl TileMapPanel { window, palette, active_brush_selector, + draw_button, + erase_button, + flood_fill_button, + pick_button, + rect_fill_button, } } @@ -157,6 +221,7 @@ impl TileMapPanel { tile_map_handle: Handle, tile_map: Option<&TileMap>, sender: &MessageSender, + editor_scene: Option<&mut EditorSceneEntry>, ) -> Option { if let Some(WindowMessage::Close) = message.data() { if message.destination() == self.window { @@ -282,11 +347,89 @@ impl TileMapPanel { } } } + } else if let Some(ButtonMessage::Click) = message.data() { + if let Some(interaction_mode) = editor_scene.and_then(|entry| { + entry + .interaction_modes + .of_type_mut::() + }) { + if message.destination() == self.draw_button { + interaction_mode.drawing_mode = DrawingMode::Draw; + } else if message.destination() == self.erase_button { + interaction_mode.drawing_mode = DrawingMode::Erase; + } else if message.destination() == self.flood_fill_button { + interaction_mode.drawing_mode = DrawingMode::FloodFill; + } else if message.destination() == self.rect_fill_button { + interaction_mode.drawing_mode = DrawingMode::RectFill { + click_grid_position: Default::default(), + }; + } else if message.destination() == self.pick_button { + interaction_mode.drawing_mode = DrawingMode::Pick { + click_grid_position: Default::default(), + }; + } + } } Some(self) } + pub fn update(&self, ui: &UserInterface, editor_scene: Option<&EditorSceneEntry>) { + if let Some(interaction_mode) = editor_scene + .and_then(|entry| entry.interaction_modes.of_type::()) + { + fn highlight_tool_button(button: Handle, highlight: bool, ui: &UserInterface) { + let decorator = *ui.try_get_of_type::