private void BeginToolAction(TilemapTool action)
        {
            if (this.actionTool == action) return;

            this.actionTool = action;
            this.actionBeginTile = this.activeAreaOrigin;

            // Start a tile update operation, so as long as the same tool
            // action is active, the edited tilemap won't fire any change events,
            // bug aggregate them into a single one at the end.
            this.activeTilemap.BeginUpdateTiles();

            this.TileDrawSource.BeginAction();
            this.actionTool.BeginAction();
        }
        private void EndToolAction()
        {
            if (this.actionTool == this.toolNone) return;

            this.actionTool.EndAction();
            this.TileDrawSource.EndAction();

            if (this.activeTilemap != null)
            {
                // Finish the tile update operation, which will fire all the
                // change events that were aggregated for this tool action.
                this.activeTilemap.EndUpdateTiles(0, 0, 0, 0);

                // Since the change events will likely trigger some scene changes,
                // such as renderers updating some cached values or colliders updating
                // the generated shapes, we should flag the scene as changed and also
                // trigger a re-draw.
                if (this.actionTool != this.toolSelect)
                {
                    DualityEditorApp.NotifyObjPropChanged(
                        this,
                        new ObjectSelection(this.activeTilemap),
                        TilemapsReflectionInfo.Property_Tilemap_Tiles);
                }
            }

            this.actionTool = this.toolNone;
            this.actionBeginTile = InvalidTile;
            UndoRedoManager.Finish();
        }
 protected override void OnLostFocus()
 {
     base.OnLostFocus();
     this.OverrideTool = null;
 }
 protected override void OnMouseLeave(EventArgs e)
 {
     base.OnMouseLeave(e);
     this.hoveredTile = InvalidTile;
     this.hoveredRenderer = null;
     if (this.actionTool == this.toolNone)
     {
         this.activeTool = this.toolNone;
         this.activeTilemap = null;
         this.activeAreaOrigin = InvalidTile;
         this.activeArea.ResizeClear(0, 0);
     }
     this.UpdateCursor();
     this.Invalidate();
 }
        protected override void OnKeyUp(KeyEventArgs e)
        {
            base.OnKeyUp(e);

            if (this.overrideTool != null && this.overrideTool.OverrideKey == e.KeyCode)
            {
                this.OverrideTool = null;
                e.Handled = true;
            }
        }
        protected override void OnKeyDown(KeyEventArgs e)
        {
            base.OnKeyDown(e);

            // Hotkeys for switching the currently selected tilemap
            if (e.KeyCode == Keys.Up || e.KeyCode == Keys.Down)
            {
                Tilemap[] visibleTilemaps =
                    this.QueryVisibleTilemapRenderers()
                    .OrderBy(r => (r as Component).GameObj.Transform.Pos.Z + r.BaseDepthOffset)
                    .Select(r => r.ActiveTilemap)
                    .NotNull()
                    .Distinct()
                    .ToArray();
                int selectedIndex = Array.IndexOf(visibleTilemaps, this.selectedTilemap);

                if (visibleTilemaps.Length > 0)
                {
                    if (e.KeyCode == Keys.Down)
                        selectedIndex = (selectedIndex == -1) ? (visibleTilemaps.Length - 1) : Math.Min(selectedIndex + 1, visibleTilemaps.Length - 1);
                    else if (e.KeyCode == Keys.Up)
                        selectedIndex = (selectedIndex == -1) ? 0 : Math.Max(selectedIndex - 1, 0);

                    Tilemap newSelection = visibleTilemaps[selectedIndex];
                    DualityEditorApp.Select(this, new ObjectSelection(newSelection.GameObj));
                }

                e.Handled = true;
                return;
            }
            else if (e.KeyCode == Keys.Left || e.KeyCode == Keys.Right)
            {
                DualityEditorApp.Deselect(this, ObjectSelection.Category.GameObjCmp);
                e.Handled = true;
                return;
            }

            // Check for tool-related keys
            foreach (TilemapTool tool in this.tools)
            {
                if (tool.OverrideKey == e.KeyCode)
                {
                    this.OverrideTool = tool;
                    e.Handled = true;
                    break;
                }
                else if (Control.ModifierKeys == Keys.None && tool.ShortcutKey == e.KeyCode)
                {
                    this.SelectedTool = tool;
                    e.Handled = true;
                    break;
                }
            }
        }
        private void UpdateOverrideToolFromModifierKeys()
        {
            foreach (TilemapTool tool in this.tools)
            {
                bool modifierPressed = false;
                if (tool.OverrideKey == Keys.Menu)
                    modifierPressed = (Control.ModifierKeys & Keys.Alt) != Keys.None;
                else if (tool.OverrideKey == Keys.ShiftKey)
                    modifierPressed = (Control.ModifierKeys & Keys.Shift) != Keys.None;
                else if (tool.OverrideKey == Keys.ControlKey)
                    modifierPressed = (Control.ModifierKeys & Keys.Control) != Keys.None;
                else
                    continue;

                if (this.overrideTool == null)
                {
                    if (modifierPressed)
                    {
                        this.OverrideTool = tool;
                        break;
                    }
                }
                else if (this.overrideTool == tool)
                {
                    if (!modifierPressed)
                    {
                        this.OverrideTool = null;
                        break;
                    }
                }
            }
        }
        private void UpdateActiveState()
        {
            TilemapTool lastActiveTool = this.activeTool;
            Tilemap lastActiveTilemap = this.activeTilemap;
            ICmpTilemapRenderer lastActiveRenderer = this.activeRenderer;

            // If an action is currently being performed, that action will always be the active tool
            if (this.actionTool != this.toolNone)
            {
                this.activeTool = this.actionTool;
            }
            // Otherwise, determine what action the cursor would do right now
            else
            {
                // Determine the active tool dynamically based on user input state
                if (this.hoveredRenderer == null)
                    this.activeTool = this.toolNone;
                else if (this.selectedTilemap == null)
                    this.activeTool = this.toolSelect;
                else if (this.hoveredRenderer.ActiveTilemap != this.selectedTilemap)
                    this.activeTool = this.toolSelect;
                else
                    this.activeTool = this.overrideTool ?? this.selectedTool;

                // Keep in mind on what renderer and tilemap belong to the currently active tool
                this.activeTilemap = (this.hoveredRenderer != null) ? this.hoveredRenderer.ActiveTilemap : null;
                this.activeRenderer = this.hoveredRenderer;
            }

            // Determine the area that is affected by the current action
            this.activeTool.UpdatePreview();

            // If our highlighted action changed, redraw view and update the cursor
            if (lastActiveTool != this.activeTool || lastActiveTilemap != this.activeTilemap || lastActiveRenderer != this.activeRenderer)
            {
                this.UpdateCursor();
                this.Invalidate();
            }
        }
        /// <summary>
        /// Initializes the list of available <see cref="TilemapTool"/> instances.
        /// </summary>
        private void InitAvailableTools()
        {
            if (this.tools.Count > 0) return;

            TilemapTool[] availableTools = DualityEditorApp.GetAvailDualityEditorTypes(typeof(TilemapTool))
                .Where(t => !t.IsAbstract)
                .Select(t => t.CreateInstanceOf() as TilemapTool)
                .NotNull()
                .OrderBy(t => t.SortOrder)
                .ToArray();

            this.toolNone = availableTools.OfType<NoTilemapTool>().FirstOrDefault();
            this.toolSelect = availableTools.OfType<SelectTilemapTool>().FirstOrDefault();

            this.tools.AddRange(availableTools);
            foreach (TilemapTool tool in this.tools)
            {
                tool.Environment = this;
            }
            this.tools.Remove(this.toolNone);

            this.selectedTool = this.tools.FirstOrDefault(t => t != this.toolSelect) ?? this.toolSelect;
            this.activeTool   = this.toolNone;
            this.actionTool   = this.toolNone;
        }