diff --git a/src/SharpIDE.Godot/DiAutoload.cs b/src/SharpIDE.Godot/DiAutoload.cs index 39242594..82f7deac 100644 --- a/src/SharpIDE.Godot/DiAutoload.cs +++ b/src/SharpIDE.Godot/DiAutoload.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Logging; using SharpIDE.Application; using SharpIDE.Godot.Features.ActivityListener; +using SharpIDE.Godot.Features.Tools; namespace SharpIDE.Godot; @@ -23,6 +24,7 @@ public override void _EnterTree() services.AddApplication(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddHttpClient(); diff --git a/src/SharpIDE.Godot/Features/BottomPanel/BottomPanelManager.cs b/src/SharpIDE.Godot/Features/BottomPanel/BottomPanelManager.cs deleted file mode 100644 index d195205b..00000000 --- a/src/SharpIDE.Godot/Features/BottomPanel/BottomPanelManager.cs +++ /dev/null @@ -1,72 +0,0 @@ -using Godot; -using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence; -using SharpIDE.Godot.Features.Build; -using SharpIDE.Godot.Features.Debug_; -using SharpIDE.Godot.Features.IdeDiagnostics; -using SharpIDE.Godot.Features.Nuget; -using SharpIDE.Godot.Features.Problems; -using SharpIDE.Godot.Features.Run; -using SharpIDE.Godot.Features.TestExplorer; - -namespace SharpIDE.Godot.Features.BottomPanel; - -public partial class BottomPanelManager : Panel -{ - private RunPanel _runPanel = null!; - private DebugPanel _debugPanel = null!; - private BuildPanel _buildPanel = null!; - private ProblemsPanel _problemsPanel = null!; - private IdeDiagnosticsPanel _ideDiagnosticsPanel = null!; - private NugetPanel _nugetPanel = null!; - private TestExplorerPanel _testExplorerPanel = null!; - - private Dictionary _panelTypeMap = []; - - public override void _Ready() - { - _runPanel = GetNode("%RunPanel"); - _debugPanel = GetNode("%DebugPanel"); - _buildPanel = GetNode("%BuildPanel"); - _problemsPanel = GetNode("%ProblemsPanel"); - _ideDiagnosticsPanel = GetNode("%IdeDiagnosticsPanel"); - _nugetPanel = GetNode("%NugetPanel"); - _testExplorerPanel = GetNode("%TestExplorerPanel"); - - _panelTypeMap = new Dictionary - { - { BottomPanelType.Run, _runPanel }, - { BottomPanelType.Debug, _debugPanel }, - { BottomPanelType.Build, _buildPanel }, - { BottomPanelType.Problems, _problemsPanel }, - { BottomPanelType.IdeDiagnostics, _ideDiagnosticsPanel }, - { BottomPanelType.Nuget, _nugetPanel }, - { BottomPanelType.TestExplorer, _testExplorerPanel } - }; - - GodotGlobalEvents.Instance.BottomPanelTabSelected.Subscribe(OnBottomPanelTabSelected); - } - - public override void _ExitTree() - { - GodotGlobalEvents.Instance.BottomPanelTabSelected.Subscribe(OnBottomPanelTabSelected); - } - - private async Task OnBottomPanelTabSelected(BottomPanelType? type) - { - await this.InvokeAsync(() => - { - if (type == null) - { - GodotGlobalEvents.Instance.BottomPanelVisibilityChangeRequested.InvokeParallelFireAndForget(false); - } - else - { - GodotGlobalEvents.Instance.BottomPanelVisibilityChangeRequested.InvokeParallelFireAndForget(true); - } - foreach (var kvp in _panelTypeMap) - { - kvp.Value.Visible = kvp.Key == type; - } - }); - } -} \ No newline at end of file diff --git a/src/SharpIDE.Godot/Features/BottomPanel/BottomPanelManager.cs.uid b/src/SharpIDE.Godot/Features/BottomPanel/BottomPanelManager.cs.uid deleted file mode 100644 index 10be36fc..00000000 --- a/src/SharpIDE.Godot/Features/BottomPanel/BottomPanelManager.cs.uid +++ /dev/null @@ -1 +0,0 @@ -uid://cvvgp42r3nml8 diff --git a/src/SharpIDE.Godot/Features/BottomPanel/BottomPanelType.cs b/src/SharpIDE.Godot/Features/BottomPanel/BottomPanelType.cs deleted file mode 100644 index 71e01e04..00000000 --- a/src/SharpIDE.Godot/Features/BottomPanel/BottomPanelType.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace SharpIDE.Godot.Features.BottomPanel; - -public enum BottomPanelType -{ - Run, - Debug, - Build, - Problems, - IdeDiagnostics, - Nuget, - TestExplorer -} \ No newline at end of file diff --git a/src/SharpIDE.Godot/Features/BottomPanel/BottomPanelType.cs.uid b/src/SharpIDE.Godot/Features/BottomPanel/BottomPanelType.cs.uid deleted file mode 100644 index d61f8df1..00000000 --- a/src/SharpIDE.Godot/Features/BottomPanel/BottomPanelType.cs.uid +++ /dev/null @@ -1 +0,0 @@ -uid://dyoci88kqk2r1 diff --git a/src/SharpIDE.Godot/Features/CustomControls/InvertedVSplitContainer.cs b/src/SharpIDE.Godot/Features/CustomControls/InvertedVSplitContainer.cs deleted file mode 100644 index 81f7d778..00000000 --- a/src/SharpIDE.Godot/Features/CustomControls/InvertedVSplitContainer.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Godot; - -namespace SharpIDE.Godot.Features.CustomControls; - -[GlobalClass, Tool] -public partial class InvertedVSplitContainer : VSplitContainer -{ - [Export] - private int _invertedOffset = 200; - private bool _invertedCollapsed = false; - - public void InvertedSetCollapsed(bool collapsed) - { - if (_invertedCollapsed == collapsed) return; - _invertedCollapsed = collapsed; - if (collapsed) - { - SplitOffset = (int)Size.Y + 100; - DraggingEnabled = false; - } - else - { - SplitOffset = (int)Size.Y - _invertedOffset; - DraggingEnabled = true; - } - } - - public override void _Ready() - { - Dragged += OnDragged; - } - - private void OnDragged(long offset) - { - _invertedOffset = (int)Size.Y - SplitOffset; - } - - public override void _Notification(int what) - { - if (what == NotificationResized) - { - SplitOffset = (int)Size.Y - _invertedOffset; - } - } -} \ No newline at end of file diff --git a/src/SharpIDE.Godot/Features/CustomControls/InvertedVSplitContainer.cs.uid b/src/SharpIDE.Godot/Features/CustomControls/InvertedVSplitContainer.cs.uid deleted file mode 100644 index 7ac56340..00000000 --- a/src/SharpIDE.Godot/Features/CustomControls/InvertedVSplitContainer.cs.uid +++ /dev/null @@ -1 +0,0 @@ -uid://kvnhndc3l6ih diff --git a/src/SharpIDE.Godot/Features/Layout/DropZone.cs b/src/SharpIDE.Godot/Features/Layout/DropZone.cs new file mode 100644 index 00000000..eb39a5b9 --- /dev/null +++ b/src/SharpIDE.Godot/Features/Layout/DropZone.cs @@ -0,0 +1,13 @@ +using Godot; + +namespace SharpIDE.Godot.Features.Layout; + +public partial class DropZone : Control +{ + public ColorRect Highlight { get; set; } = null!; + + public override void _Ready() + { + Highlight = GetNode("Highlight"); + } +} diff --git a/src/SharpIDE.Godot/Features/Layout/DropZone.cs.uid b/src/SharpIDE.Godot/Features/Layout/DropZone.cs.uid new file mode 100644 index 00000000..34beefdb --- /dev/null +++ b/src/SharpIDE.Godot/Features/Layout/DropZone.cs.uid @@ -0,0 +1 @@ +uid://dh8sh21lx7p34 diff --git a/src/SharpIDE.Godot/Features/Layout/IdeLayoutState.cs b/src/SharpIDE.Godot/Features/Layout/IdeLayoutState.cs new file mode 100644 index 00000000..a47ab172 --- /dev/null +++ b/src/SharpIDE.Godot/Features/Layout/IdeLayoutState.cs @@ -0,0 +1,31 @@ +using SharpIDE.Godot.Features.Tools; + +namespace SharpIDE.Godot.Features.Layout; + +public sealed record IdeLayoutState +{ + public static readonly IdeLayoutState Default = new() + { + SidebarTools = new Dictionary> + { + [ToolAnchor.LeftTop] = + [ + new IdeToolState(IdeToolId.SolutionExplorer, ToolAnchor.LeftTop, IsActive: true) + ], + [ToolAnchor.RightTop] = [], + [ToolAnchor.BottomLeft] = + [ + new IdeToolState(IdeToolId.Problems, ToolAnchor.BottomLeft, IsActive: false), + new IdeToolState(IdeToolId.Run, ToolAnchor.BottomLeft, IsActive: false), + new IdeToolState(IdeToolId.Debug, ToolAnchor.BottomLeft, IsActive: false), + new IdeToolState(IdeToolId.Build, ToolAnchor.BottomLeft, IsActive: false), + new IdeToolState(IdeToolId.Nuget, ToolAnchor.BottomLeft, IsActive: false), + new IdeToolState(IdeToolId.TestExplorer, ToolAnchor.BottomLeft, IsActive: false), + new IdeToolState(IdeToolId.IdeDiagnostics, ToolAnchor.BottomLeft, IsActive: false) + ], + [ToolAnchor.BottomRight] = [] + } + }; + + public Dictionary> SidebarTools { get; init; } = []; +} \ No newline at end of file diff --git a/src/SharpIDE.Godot/Features/Layout/IdeLayoutState.cs.uid b/src/SharpIDE.Godot/Features/Layout/IdeLayoutState.cs.uid new file mode 100644 index 00000000..24a69d92 --- /dev/null +++ b/src/SharpIDE.Godot/Features/Layout/IdeLayoutState.cs.uid @@ -0,0 +1 @@ +uid://c1hjlyj0augv1 diff --git a/src/SharpIDE.Godot/Features/Layout/IdeMainLayout.cs b/src/SharpIDE.Godot/Features/Layout/IdeMainLayout.cs new file mode 100644 index 00000000..75d7480e --- /dev/null +++ b/src/SharpIDE.Godot/Features/Layout/IdeMainLayout.cs @@ -0,0 +1,247 @@ +using Godot; + +using SharpIDE.Godot.Features.Layout.Resources; +using SharpIDE.Godot.Features.Tools; + +namespace SharpIDE.Godot.Features.Layout; + +public partial class IdeMainLayout : Control +{ + private readonly Dictionary _anchorStateMap = []; + private readonly Dictionary _toolButtonMap = []; + + [Inject] + private readonly SharpIdeToolManager _toolManager = null!; + + private Dictionary _toolStateMap = []; + + private Sidebar _leftSidebar = null!; + private Sidebar _rightSidebar = null!; + private Control _bottomArea = null!; + private ToolDragOverlay _toolDragOverlay = null!; + private IdeLayoutState _layout = null!; + + public override void _Ready() + { + _leftSidebar = GetNode("%LeftSidebar"); + _rightSidebar = GetNode("%RightSidebar"); + _bottomArea = GetNode("%BottomArea"); + _toolDragOverlay = GetNode("%ToolDragOverlay"); + + _anchorStateMap[ToolAnchor.LeftTop] = new AnchorState( + _leftSidebar.TopTools, + GetNode("%LeftTopToolArea"), + new ButtonGroup { AllowUnpress = true }); + _anchorStateMap[ToolAnchor.RightTop] = new AnchorState( + _rightSidebar.TopTools, + GetNode("%RightTopToolArea"), + new ButtonGroup { AllowUnpress = true }); + _anchorStateMap[ToolAnchor.BottomLeft] = new AnchorState( + _leftSidebar.BottomTools, + GetNode("%BottomLeftToolArea"), + new ButtonGroup { AllowUnpress = true }); + _anchorStateMap[ToolAnchor.BottomRight] = new AnchorState( + _rightSidebar.BottomTools, + GetNode("%BottomRightToolArea"), + new ButtonGroup { AllowUnpress = true }); + + // TODO: Load layout from persistence + _layout = IdeLayoutState.Default; + + InitializeLayout(_layout); + + GodotGlobalEvents.Instance.IdeToolExternallyActivated.Subscribe(tool => + { + CallDeferred( + nameof(OnIdeToolExternallyActivated), + Variant.From(tool)); + return Task.CompletedTask; + }); + + _toolDragOverlay.ToolMoveRequested += OnToolMoveRequested; + } + + /// + public override void _Notification(int what) + { + switch ((long)what) + { + case NotificationDragBegin: + _leftSidebar.Visible = true; + _rightSidebar.Visible = true; + _toolDragOverlay.Visible = true; + break; + case NotificationDragEnd: + _toolDragOverlay.Visible = false; + ApplySidebarVisibility(); + break; + } + } + + private void OnIdeToolExternallyActivated(IdeToolId toolId) + { + if (!_toolStateMap.TryGetValue(toolId, out var toolState)) + { + GD.PrintErr($"Externally activated tool '{toolId}' is not part of layout."); + return; + } + + var anchor = toolState.Anchor; + DeactivateTools(anchor); + ActivateTool(toolId); + } + + private void OnToolMoveRequested(object? _, ToolMoveData moveData) + { + MoveTool(moveData.ToolId, moveData.Anchor, moveData.Index); + } + + private void InitializeLayout(IdeLayoutState layoutState) + { + _toolStateMap = layoutState.SidebarTools.Values.SelectMany(x => x).ToDictionary(state => state.ToolId); + + foreach (var anchorState in _anchorStateMap.Values) + { + anchorState.SidebarTools.RemoveChildren(); + } + + foreach (var toolState in _toolStateMap.Values) + { + var anchorState = _anchorStateMap[toolState.Anchor]; + + var button = CreateToolButton(toolState); + _toolButtonMap[toolState.ToolId] = button; + + anchorState.SidebarTools.AddChild(button); + + if (toolState.IsActive) + { + var toolControl = _toolManager.GetControl(toolState.ToolId); + + anchorState.ToolArea.ShowTool(toolControl); + } + } + + ApplySidebarVisibility(); + ApplyBottomAreaVisibility(); + } + + private void ActivateTool(IdeToolId toolId) + { + SetToolActive(toolId, true); + _toolButtonMap[toolId].SetPressedNoSignal(true); + ApplyToolVisibility(toolId); + } + + private void DeactivateTool(IdeToolId toolId) + { + SetToolActive(toolId, false); + _toolButtonMap[toolId].SetPressedNoSignal(false); + ApplyToolVisibility(toolId); + } + + private void DeactivateTools(ToolAnchor anchor) + { + foreach (var toolState in _toolStateMap.Values.Where(t => t.Anchor == anchor)) + { + DeactivateTool(toolState.ToolId); + } + } + + private void MoveTool(IdeToolId toolId, ToolAnchor targetAnchor, int anchorToolIndex) + { + var toolState = _toolStateMap[toolId]; + var targetAnchorState = _anchorStateMap[targetAnchor]; + var toolButton = _toolButtonMap[toolState.ToolId]; + + var originAnchor = toolState.Anchor; + var originAnchorState = _anchorStateMap[originAnchor]; + + _layout.SidebarTools[toolState.Anchor].Remove(toolState); + _layout.SidebarTools[targetAnchor].Insert(anchorToolIndex, toolState); + toolState.Anchor = targetAnchor; + + if (originAnchor == targetAnchor) + { + targetAnchorState.SidebarTools.MoveChild(toolButton, anchorToolIndex); + return; + } + + if (toolButton.GetParent() is { } buttonParent) + { + buttonParent.RemoveChild(toolButton); + } + + var toolControl = _toolManager.GetControl(toolId); + + if (ReferenceEquals(originAnchorState.ToolArea.CurrentTool, toolControl)) + { + originAnchorState.ToolArea.HideTool(); + } + + toolButton.ButtonGroup = targetAnchorState.ButtonGroup; + targetAnchorState.SidebarTools.AddChild(toolButton); + targetAnchorState.SidebarTools.MoveChild(toolButton, anchorToolIndex); + + if (toolButton.ButtonPressed) + { + OnIdeToolExternallyActivated(toolState.ToolId); + } + } + + private void SetToolActive(IdeToolId toolId, bool isActive) + { + _toolStateMap[toolId].IsActive = isActive; + } + + private ToolButton CreateToolButton(IdeToolState toolState) + { + var toolButton = Scenes.ToolButton.Instantiate(); + + toolButton.ToolId = toolState.ToolId; + + var icon = IdeToolDescriptors.Descriptors[toolState.ToolId].Icon; + + toolButton.SetButtonIcon(icon); + toolButton.Toggled += toggledOn => + { + SetToolActive(toolState.ToolId, toggledOn); + ApplyToolVisibility(toolState.ToolId); + }; + toolButton.ButtonGroup = _anchorStateMap[toolState.Anchor].ButtonGroup; + toolButton.ButtonPressed = toolState.IsActive; + + return toolButton; + } + + private void ApplyToolVisibility(IdeToolId toolId) + { + var toolState = _toolStateMap[toolId]; + var toolArea = _anchorStateMap[toolState.Anchor].ToolArea; + var toolControl = _toolManager.GetControl(toolId); + + if (toolState.IsActive) + { + toolArea.ShowTool(toolControl); + } + else + { + toolArea.HideTool(); + } + + ApplyBottomAreaVisibility(); + } + + private void ApplySidebarVisibility() + { + _leftSidebar.Visible = _toolStateMap.Values.Any(toolState => toolState.Anchor.IsLeft()); + _rightSidebar.Visible = _toolStateMap.Values.Any(toolState => toolState.Anchor.IsRight()); + } + + private void ApplyBottomAreaVisibility() + { + _bottomArea.Visible = _toolStateMap.Values.Any(toolState => toolState.Anchor.IsBottom() && toolState.IsActive); + } + + private sealed record AnchorState(Control SidebarTools, ToolArea ToolArea, ButtonGroup ButtonGroup); +} diff --git a/src/SharpIDE.Godot/Features/Layout/IdeMainLayout.cs.uid b/src/SharpIDE.Godot/Features/Layout/IdeMainLayout.cs.uid new file mode 100644 index 00000000..f864a61b --- /dev/null +++ b/src/SharpIDE.Godot/Features/Layout/IdeMainLayout.cs.uid @@ -0,0 +1 @@ +uid://bu3a3xbolcijl diff --git a/src/SharpIDE.Godot/Features/Layout/IdeMainLayout.tscn b/src/SharpIDE.Godot/Features/Layout/IdeMainLayout.tscn new file mode 100644 index 00000000..f2346d73 --- /dev/null +++ b/src/SharpIDE.Godot/Features/Layout/IdeMainLayout.tscn @@ -0,0 +1,183 @@ +[gd_scene load_steps=7 format=3 uid="uid://bbdbcqg3jys2b"] + +[ext_resource type="Script" uid="uid://bu3a3xbolcijl" path="res://Features/Layout/IdeMainLayout.cs" id="1_tq23w"] +[ext_resource type="PackedScene" uid="uid://cy2a1p2twwni0" path="res://Features/Layout/Sidebar.tscn" id="2_e53sf"] +[ext_resource type="PackedScene" uid="uid://b8wqkwa7sjrid" path="res://Features/Layout/ToolArea.tscn" id="3_cxuiw"] +[ext_resource type="PackedScene" uid="uid://c5dlwgcx3ubyp" path="res://Features/CodeEditor/CodeEditorPanel.tscn" id="4_kocsp"] +[ext_resource type="PackedScene" uid="uid://f1hvt1gn1adf" path="res://Features/Layout/ToolDragOverlay.tscn" id="5_e53sf"] +[ext_resource type="Script" uid="uid://dh8sh21lx7p34" path="res://Features/Layout/DropZone.cs" id="6_yv27d"] + +[node name="IdeMainLayout" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +script = ExtResource("1_tq23w") + +[node name="Root" type="HBoxContainer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="LeftSidebar" parent="Root" instance=ExtResource("2_e53sf")] +unique_name_in_owner = true +layout_mode = 2 + +[node name="CenterArea" type="VSplitContainer" parent="Root"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_constants/separation = 0 + +[node name="TopArea" type="HSplitContainer" parent="Root/CenterArea"] +layout_mode = 2 +size_flags_vertical = 3 +size_flags_stretch_ratio = 2.0 +theme_override_constants/separation = 0 + +[node name="LeftTopToolArea" parent="Root/CenterArea/TopArea" instance=ExtResource("3_cxuiw")] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="HSplitContainer" type="HSplitContainer" parent="Root/CenterArea/TopArea"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 5.0 +theme_override_constants/separation = 0 + +[node name="CodeEditor" parent="Root/CenterArea/TopArea/HSplitContainer" instance=ExtResource("4_kocsp")] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 5.0 + +[node name="RightTopToolArea" parent="Root/CenterArea/TopArea/HSplitContainer" instance=ExtResource("3_cxuiw")] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="BottomArea" type="HSplitContainer" parent="Root/CenterArea"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_vertical = 3 +theme_override_constants/separation = 0 +theme_override_constants/minimum_grab_thickness = 6 + +[node name="BottomLeftToolArea" parent="Root/CenterArea/BottomArea" instance=ExtResource("3_cxuiw")] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="BottomRightToolArea" parent="Root/CenterArea/BottomArea" instance=ExtResource("3_cxuiw")] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="RightSidebar" parent="Root" instance=ExtResource("2_e53sf")] +unique_name_in_owner = true +layout_mode = 2 + +[node name="ToolDragOverlay" parent="." node_paths=PackedStringArray("LeftSidebar", "RightSidebar", "LeftTopZone", "RightTopZone", "BottomLeftZone", "BottomRightZone") instance=ExtResource("5_e53sf")] +unique_name_in_owner = true +visible = false +layout_mode = 1 +mouse_filter = 1 +LeftSidebar = NodePath("../Root/LeftSidebar") +RightSidebar = NodePath("../Root/RightSidebar") +LeftTopZone = NodePath("LeftTopDropZone") +RightTopZone = NodePath("RightTopDropZone") +BottomLeftZone = NodePath("BottomLeftDropZone") +BottomRightZone = NodePath("BottomRightDropZone") + +[node name="LeftTopDropZone" type="Control" parent="ToolDragOverlay"] +layout_mode = 1 +anchor_right = 0.2 +anchor_bottom = 0.5 +size_flags_horizontal = 0 +size_flags_vertical = 0 +mouse_filter = 1 +script = ExtResource("6_yv27d") + +[node name="Highlight" type="ColorRect" parent="ToolDragOverlay/LeftTopDropZone"] +visible = false +layout_mode = 1 +anchors_preset = -1 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 50.227203 +mouse_filter = 2 +color = Color(0, 0.7268333, 0.89, 0.5137255) + +[node name="RightTopDropZone" type="Control" parent="ToolDragOverlay"] +layout_mode = 1 +anchor_left = 0.8 +anchor_right = 1.0 +anchor_bottom = 0.5 +size_flags_horizontal = 0 +size_flags_vertical = 0 +mouse_filter = 1 +script = ExtResource("6_yv27d") + +[node name="Highlight" type="ColorRect" parent="ToolDragOverlay/RightTopDropZone"] +visible = false +layout_mode = 1 +anchors_preset = -1 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_right = -49.99678 +mouse_filter = 2 +color = Color(0, 0.7254902, 0.8901961, 0.5137255) + +[node name="BottomLeftDropZone" type="Control" parent="ToolDragOverlay"] +layout_mode = 1 +anchor_top = 0.5 +anchor_right = 0.2 +anchor_bottom = 1.0 +grow_vertical = 0 +size_flags_horizontal = 0 +size_flags_vertical = 0 +mouse_filter = 1 +script = ExtResource("6_yv27d") + +[node name="Highlight" type="ColorRect" parent="ToolDragOverlay/BottomLeftDropZone"] +visible = false +layout_mode = 1 +anchors_preset = -1 +anchor_top = 0.349 +anchor_right = 2.4740002 +anchor_bottom = 1.0 +offset_left = 50.227203 +offset_top = -0.07600403 +mouse_filter = 2 +color = Color(0, 0.7254902, 0.8901961, 0.5137255) + +[node name="BottomRightDropZone" type="Control" parent="ToolDragOverlay"] +layout_mode = 1 +anchor_left = 0.8 +anchor_top = 0.5 +anchor_right = 1.0 +anchor_bottom = 1.0 +size_flags_horizontal = 0 +size_flags_vertical = 0 +mouse_filter = 1 +script = ExtResource("6_yv27d") + +[node name="Highlight" type="ColorRect" parent="ToolDragOverlay/BottomRightDropZone"] +visible = false +layout_mode = 1 +anchors_preset = -1 +anchor_left = -1.475 +anchor_top = 0.34600002 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_top = -0.104003906 +offset_right = -49.99678 +mouse_filter = 2 +color = Color(0, 0.7254902, 0.8901961, 0.5137255) diff --git a/src/SharpIDE.Godot/Features/Layout/IdeToolState.cs b/src/SharpIDE.Godot/Features/Layout/IdeToolState.cs new file mode 100644 index 00000000..05d45adf --- /dev/null +++ b/src/SharpIDE.Godot/Features/Layout/IdeToolState.cs @@ -0,0 +1,24 @@ +using SharpIDE.Godot.Features.Tools; + +namespace SharpIDE.Godot.Features.Layout; + +public sealed record IdeToolState( + IdeToolId ToolId, + ToolAnchor Anchor, + bool IsActive) +{ + /// + /// The ID of the tool. + /// + public IdeToolId ToolId { get; init; } = ToolId; + + /// + /// The current anchor of the tool. + /// + public ToolAnchor Anchor { get; set; } = Anchor; + + /// + /// Indicates if the tool is active. + /// + public bool IsActive { get; set; } = IsActive; +} \ No newline at end of file diff --git a/src/SharpIDE.Godot/Features/Layout/IdeToolState.cs.uid b/src/SharpIDE.Godot/Features/Layout/IdeToolState.cs.uid new file mode 100644 index 00000000..6ec2dfda --- /dev/null +++ b/src/SharpIDE.Godot/Features/Layout/IdeToolState.cs.uid @@ -0,0 +1 @@ +uid://7cd4qvu71evb diff --git a/src/SharpIDE.Godot/Features/Layout/Resources/Icons.cs b/src/SharpIDE.Godot/Features/Layout/Resources/Icons.cs new file mode 100644 index 00000000..31b9e4ad --- /dev/null +++ b/src/SharpIDE.Godot/Features/Layout/Resources/Icons.cs @@ -0,0 +1,15 @@ +using Godot; + +namespace SharpIDE.Godot.Features.Layout.Resources; + +public static class Icons +{ + public static readonly Texture2D SolutionExplorer = ResourceLoader.Load("uid://ccj0dw81x3bkc"); + public static readonly Texture2D Problems = ResourceLoader.Load("uid://uukf1nwjhthv"); + public static readonly Texture2D Run = ResourceLoader.Load("uid://cre7q0efp4vrq"); + public static readonly Texture2D Debug = ResourceLoader.Load("uid://butisxqww0boc"); + public static readonly Texture2D Build = ResourceLoader.Load("uid://b0170ypw8uf3a"); + public static readonly Texture2D Nuget = ResourceLoader.Load("uid://b5ih61vdjv5e6"); + public static readonly Texture2D TestExplorer = ResourceLoader.Load("uid://dged1mm438qli"); + public static readonly Texture2D IdeDiagnostics = ResourceLoader.Load("uid://dx8bt0adxpqgy"); +} \ No newline at end of file diff --git a/src/SharpIDE.Godot/Features/Layout/Resources/Icons.cs.uid b/src/SharpIDE.Godot/Features/Layout/Resources/Icons.cs.uid new file mode 100644 index 00000000..4f3f8556 --- /dev/null +++ b/src/SharpIDE.Godot/Features/Layout/Resources/Icons.cs.uid @@ -0,0 +1 @@ +uid://sn5652n26arl diff --git a/src/SharpIDE.Godot/Features/LeftSideBar/Resources/Ide.svg b/src/SharpIDE.Godot/Features/Layout/Resources/Ide.svg similarity index 100% rename from src/SharpIDE.Godot/Features/LeftSideBar/Resources/Ide.svg rename to src/SharpIDE.Godot/Features/Layout/Resources/Ide.svg diff --git a/src/SharpIDE.Godot/Features/Layout/Resources/Ide.svg.import b/src/SharpIDE.Godot/Features/Layout/Resources/Ide.svg.import new file mode 100644 index 00000000..cad226e8 --- /dev/null +++ b/src/SharpIDE.Godot/Features/Layout/Resources/Ide.svg.import @@ -0,0 +1,18 @@ +[remap] + +importer="svg" +type="DPITexture" +uid="uid://dx8bt0adxpqgy" +path="res://.godot/imported/Ide.svg-db83dc3a832f21820b0a91f0283367d7.dpitex" + +[deps] + +source_file="res://Features/Layout/Resources/Ide.svg" +dest_files=["res://.godot/imported/Ide.svg-db83dc3a832f21820b0a91f0283367d7.dpitex"] + +[params] + +base_scale=1.0 +saturation=1.0 +color_map={} +compress=true diff --git a/src/SharpIDE.Godot/Features/LeftSideBar/Resources/Nuget.svg b/src/SharpIDE.Godot/Features/Layout/Resources/Nuget.svg similarity index 100% rename from src/SharpIDE.Godot/Features/LeftSideBar/Resources/Nuget.svg rename to src/SharpIDE.Godot/Features/Layout/Resources/Nuget.svg diff --git a/src/SharpIDE.Godot/Features/Layout/Resources/Nuget.svg.import b/src/SharpIDE.Godot/Features/Layout/Resources/Nuget.svg.import new file mode 100644 index 00000000..b41f5bbb --- /dev/null +++ b/src/SharpIDE.Godot/Features/Layout/Resources/Nuget.svg.import @@ -0,0 +1,18 @@ +[remap] + +importer="svg" +type="DPITexture" +uid="uid://b5ih61vdjv5e6" +path="res://.godot/imported/Nuget.svg-36d5b7c20fcd38a4c8b8f31da0e7b29f.dpitex" + +[deps] + +source_file="res://Features/Layout/Resources/Nuget.svg" +dest_files=["res://.godot/imported/Nuget.svg-36d5b7c20fcd38a4c8b8f31da0e7b29f.dpitex"] + +[params] + +base_scale=0.5 +saturation=1.0 +color_map={} +compress=true diff --git a/src/SharpIDE.Godot/Features/Layout/Resources/Scenes.cs b/src/SharpIDE.Godot/Features/Layout/Resources/Scenes.cs new file mode 100644 index 00000000..a7ea8690 --- /dev/null +++ b/src/SharpIDE.Godot/Features/Layout/Resources/Scenes.cs @@ -0,0 +1,16 @@ +using Godot; + +namespace SharpIDE.Godot.Features.Layout.Resources; + +public static class Scenes +{ + public static readonly PackedScene SolutionExplorer = ResourceLoader.Load("uid://cy1bb32g7j7dr"); + public static readonly PackedScene Problems = ResourceLoader.Load("uid://tqpmww430cor"); + public static readonly PackedScene Run = ResourceLoader.Load("uid://bcoytt3bw0gpe"); + public static readonly PackedScene Debug = ResourceLoader.Load("uid://dkjips8oudqou"); + public static readonly PackedScene Build = ResourceLoader.Load("uid://co6dkhdolriej"); + public static readonly PackedScene Nuget = ResourceLoader.Load("uid://duyxg107nfh2f"); + public static readonly PackedScene TestExplorer = ResourceLoader.Load("uid://hwdok1kch3b3"); + public static readonly PackedScene IdeDiagnostics = ResourceLoader.Load("uid://b0tjuqq3bca5e"); + public static readonly PackedScene ToolButton = ResourceLoader.Load("uid://gcpcsulb43in"); +} \ No newline at end of file diff --git a/src/SharpIDE.Godot/Features/Layout/Resources/Scenes.cs.uid b/src/SharpIDE.Godot/Features/Layout/Resources/Scenes.cs.uid new file mode 100644 index 00000000..4b1ce3d1 --- /dev/null +++ b/src/SharpIDE.Godot/Features/Layout/Resources/Scenes.cs.uid @@ -0,0 +1 @@ +uid://cdcpmps0kcbou diff --git a/src/SharpIDE.Godot/Features/LeftSideBar/Resources/SidebarDebug.svg b/src/SharpIDE.Godot/Features/Layout/Resources/SidebarDebug.svg similarity index 100% rename from src/SharpIDE.Godot/Features/LeftSideBar/Resources/SidebarDebug.svg rename to src/SharpIDE.Godot/Features/Layout/Resources/SidebarDebug.svg diff --git a/src/SharpIDE.Godot/Features/Layout/Resources/SidebarDebug.svg.import b/src/SharpIDE.Godot/Features/Layout/Resources/SidebarDebug.svg.import new file mode 100644 index 00000000..cc9f0c05 --- /dev/null +++ b/src/SharpIDE.Godot/Features/Layout/Resources/SidebarDebug.svg.import @@ -0,0 +1,18 @@ +[remap] + +importer="svg" +type="DPITexture" +uid="uid://butisxqww0boc" +path="res://.godot/imported/SidebarDebug.svg-6f1c0e3358c8db682eb49c5e398a09f7.dpitex" + +[deps] + +source_file="res://Features/Layout/Resources/SidebarDebug.svg" +dest_files=["res://.godot/imported/SidebarDebug.svg-6f1c0e3358c8db682eb49c5e398a09f7.dpitex"] + +[params] + +base_scale=3.0 +saturation=1.0 +color_map={} +compress=true diff --git a/src/SharpIDE.Godot/Features/LeftSideBar/Resources/SidebarFolder.svg b/src/SharpIDE.Godot/Features/Layout/Resources/SidebarFolder.svg similarity index 100% rename from src/SharpIDE.Godot/Features/LeftSideBar/Resources/SidebarFolder.svg rename to src/SharpIDE.Godot/Features/Layout/Resources/SidebarFolder.svg diff --git a/src/SharpIDE.Godot/Features/Layout/Resources/SidebarFolder.svg.import b/src/SharpIDE.Godot/Features/Layout/Resources/SidebarFolder.svg.import new file mode 100644 index 00000000..52411f65 --- /dev/null +++ b/src/SharpIDE.Godot/Features/Layout/Resources/SidebarFolder.svg.import @@ -0,0 +1,18 @@ +[remap] + +importer="svg" +type="DPITexture" +uid="uid://ccj0dw81x3bkc" +path="res://.godot/imported/SidebarFolder.svg-586a9184693147c28205b4a51a3408ad.dpitex" + +[deps] + +source_file="res://Features/Layout/Resources/SidebarFolder.svg" +dest_files=["res://.godot/imported/SidebarFolder.svg-586a9184693147c28205b4a51a3408ad.dpitex"] + +[params] + +base_scale=3.0 +saturation=1.0 +color_map={} +compress=true diff --git a/src/SharpIDE.Godot/Features/LeftSideBar/Resources/SidebarProblem.svg b/src/SharpIDE.Godot/Features/Layout/Resources/SidebarProblem.svg similarity index 100% rename from src/SharpIDE.Godot/Features/LeftSideBar/Resources/SidebarProblem.svg rename to src/SharpIDE.Godot/Features/Layout/Resources/SidebarProblem.svg diff --git a/src/SharpIDE.Godot/Features/Layout/Resources/SidebarProblem.svg.import b/src/SharpIDE.Godot/Features/Layout/Resources/SidebarProblem.svg.import new file mode 100644 index 00000000..090fcce2 --- /dev/null +++ b/src/SharpIDE.Godot/Features/Layout/Resources/SidebarProblem.svg.import @@ -0,0 +1,18 @@ +[remap] + +importer="svg" +type="DPITexture" +uid="uid://uukf1nwjhthv" +path="res://.godot/imported/SidebarProblem.svg-980e6a0d910f0b1501b62cb690eab8a9.dpitex" + +[deps] + +source_file="res://Features/Layout/Resources/SidebarProblem.svg" +dest_files=["res://.godot/imported/SidebarProblem.svg-980e6a0d910f0b1501b62cb690eab8a9.dpitex"] + +[params] + +base_scale=3.0 +saturation=1.0 +color_map={} +compress=true diff --git a/src/SharpIDE.Godot/Features/LeftSideBar/Resources/SidebarRun.svg b/src/SharpIDE.Godot/Features/Layout/Resources/SidebarRun.svg similarity index 100% rename from src/SharpIDE.Godot/Features/LeftSideBar/Resources/SidebarRun.svg rename to src/SharpIDE.Godot/Features/Layout/Resources/SidebarRun.svg diff --git a/src/SharpIDE.Godot/Features/Layout/Resources/SidebarRun.svg.import b/src/SharpIDE.Godot/Features/Layout/Resources/SidebarRun.svg.import new file mode 100644 index 00000000..00fdc578 --- /dev/null +++ b/src/SharpIDE.Godot/Features/Layout/Resources/SidebarRun.svg.import @@ -0,0 +1,18 @@ +[remap] + +importer="svg" +type="DPITexture" +uid="uid://cre7q0efp4vrq" +path="res://.godot/imported/SidebarRun.svg-26d166c514c7b031a00337f17cd264a9.dpitex" + +[deps] + +source_file="res://Features/Layout/Resources/SidebarRun.svg" +dest_files=["res://.godot/imported/SidebarRun.svg-26d166c514c7b031a00337f17cd264a9.dpitex"] + +[params] + +base_scale=3.0 +saturation=1.0 +color_map={} +compress=true diff --git a/src/SharpIDE.Godot/Features/LeftSideBar/Resources/SidebarTestExplorer.svg b/src/SharpIDE.Godot/Features/Layout/Resources/SidebarTestExplorer.svg similarity index 100% rename from src/SharpIDE.Godot/Features/LeftSideBar/Resources/SidebarTestExplorer.svg rename to src/SharpIDE.Godot/Features/Layout/Resources/SidebarTestExplorer.svg diff --git a/src/SharpIDE.Godot/Features/Layout/Resources/SidebarTestExplorer.svg.import b/src/SharpIDE.Godot/Features/Layout/Resources/SidebarTestExplorer.svg.import new file mode 100644 index 00000000..88d7fa1e --- /dev/null +++ b/src/SharpIDE.Godot/Features/Layout/Resources/SidebarTestExplorer.svg.import @@ -0,0 +1,18 @@ +[remap] + +importer="svg" +type="DPITexture" +uid="uid://dged1mm438qli" +path="res://.godot/imported/SidebarTestExplorer.svg-5c5b2ef57b1a79102a5a2c7eae95c756.dpitex" + +[deps] + +source_file="res://Features/Layout/Resources/SidebarTestExplorer.svg" +dest_files=["res://.godot/imported/SidebarTestExplorer.svg-5c5b2ef57b1a79102a5a2c7eae95c756.dpitex"] + +[params] + +base_scale=0.5 +saturation=1.0 +color_map={} +compress=true diff --git a/src/SharpIDE.Godot/Features/LeftSideBar/Resources/Terminal.svg b/src/SharpIDE.Godot/Features/Layout/Resources/Terminal.svg similarity index 100% rename from src/SharpIDE.Godot/Features/LeftSideBar/Resources/Terminal.svg rename to src/SharpIDE.Godot/Features/Layout/Resources/Terminal.svg diff --git a/src/SharpIDE.Godot/Features/Layout/Resources/Terminal.svg.import b/src/SharpIDE.Godot/Features/Layout/Resources/Terminal.svg.import new file mode 100644 index 00000000..950c3c77 --- /dev/null +++ b/src/SharpIDE.Godot/Features/Layout/Resources/Terminal.svg.import @@ -0,0 +1,18 @@ +[remap] + +importer="svg" +type="DPITexture" +uid="uid://b0170ypw8uf3a" +path="res://.godot/imported/Terminal.svg-027637379ad9c5f527454469295af8af.dpitex" + +[deps] + +source_file="res://Features/Layout/Resources/Terminal.svg" +dest_files=["res://.godot/imported/Terminal.svg-027637379ad9c5f527454469295af8af.dpitex"] + +[params] + +base_scale=3.0 +saturation=1.0 +color_map={} +compress=true diff --git a/src/SharpIDE.Godot/Features/LeftSideBar/Resources/LeftSideBarButtonStyleNormal.tres b/src/SharpIDE.Godot/Features/Layout/Resources/ToolButtonStyleNormal.tres similarity index 100% rename from src/SharpIDE.Godot/Features/LeftSideBar/Resources/LeftSideBarButtonStyleNormal.tres rename to src/SharpIDE.Godot/Features/Layout/Resources/ToolButtonStyleNormal.tres diff --git a/src/SharpIDE.Godot/Features/LeftSideBar/Resources/LeftSideBarButtonStylePressed.tres b/src/SharpIDE.Godot/Features/Layout/Resources/ToolButtonStylePressed.tres similarity index 100% rename from src/SharpIDE.Godot/Features/LeftSideBar/Resources/LeftSideBarButtonStylePressed.tres rename to src/SharpIDE.Godot/Features/Layout/Resources/ToolButtonStylePressed.tres diff --git a/src/SharpIDE.Godot/Features/Layout/Sidebar.cs b/src/SharpIDE.Godot/Features/Layout/Sidebar.cs new file mode 100644 index 00000000..88639d91 --- /dev/null +++ b/src/SharpIDE.Godot/Features/Layout/Sidebar.cs @@ -0,0 +1,35 @@ +using Godot; + +namespace SharpIDE.Godot.Features.Layout; + +public partial class Sidebar : Panel +{ + [Export] + public Vector2 ToolMinimumSize { get; set; } = Vector2.Zero; + + public Container TopTools { get; private set; } = null!; + public Container BottomTools { get; private set; } = null!; + + [Export] + public Color PreviewColor { get; set; } + + public Control ToolPreview = null!; + + public override void _Ready() + { + TopTools = GetNode("%TopTools"); + BottomTools = GetNode("%BottomTools"); + + ToolPreview = CreateToolPreview(); + } + + private ColorRect CreateToolPreview() + { + var rect = new ColorRect(); + rect.Color = PreviewColor; + rect.CustomMinimumSize = ToolMinimumSize; + rect.SizeFlagsHorizontal = SizeFlags.Fill; + rect.SizeFlagsVertical = SizeFlags.Fill; + return rect; + } +} diff --git a/src/SharpIDE.Godot/Features/Layout/Sidebar.cs.uid b/src/SharpIDE.Godot/Features/Layout/Sidebar.cs.uid new file mode 100644 index 00000000..27636882 --- /dev/null +++ b/src/SharpIDE.Godot/Features/Layout/Sidebar.cs.uid @@ -0,0 +1 @@ +uid://d3uqv7eerpt1p diff --git a/src/SharpIDE.Godot/Features/Layout/Sidebar.tscn b/src/SharpIDE.Godot/Features/Layout/Sidebar.tscn new file mode 100644 index 00000000..02334074 --- /dev/null +++ b/src/SharpIDE.Godot/Features/Layout/Sidebar.tscn @@ -0,0 +1,42 @@ +[gd_scene load_steps=2 format=3 uid="uid://cy2a1p2twwni0"] + +[ext_resource type="Script" uid="uid://d3uqv7eerpt1p" path="res://Features/Layout/Sidebar.cs" id="1_4ivyu"] + +[node name="Sidebar" type="Panel"] +custom_minimum_size = Vector2(50, 0) +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_vertical = 3 +script = ExtResource("1_4ivyu") +ToolMinimumSize = Vector2(40, 40) +PreviewColor = Color(0, 0.7268333, 0.89, 0.6901961) + +[node name="Margins" type="MarginContainer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 5 +theme_override_constants/margin_top = 5 +theme_override_constants/margin_right = 5 +theme_override_constants/margin_bottom = 5 + +[node name="VBoxContainer" type="VBoxContainer" parent="Margins"] +layout_mode = 2 + +[node name="TopTools" type="VBoxContainer" parent="Margins/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="Control" type="Control" parent="Margins/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="BottomTools" type="VBoxContainer" parent="Margins/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 diff --git a/src/SharpIDE.Godot/Features/Layout/ToolAnchor.cs b/src/SharpIDE.Godot/Features/Layout/ToolAnchor.cs new file mode 100644 index 00000000..82e41b44 --- /dev/null +++ b/src/SharpIDE.Godot/Features/Layout/ToolAnchor.cs @@ -0,0 +1,10 @@ +namespace SharpIDE.Godot.Features.Layout; + +public enum ToolAnchor +{ + None = 0, + LeftTop = 1, + RightTop = 2, + BottomLeft = 3, + BottomRight = 4 +} \ No newline at end of file diff --git a/src/SharpIDE.Godot/Features/Layout/ToolAnchor.cs.uid b/src/SharpIDE.Godot/Features/Layout/ToolAnchor.cs.uid new file mode 100644 index 00000000..162b1c45 --- /dev/null +++ b/src/SharpIDE.Godot/Features/Layout/ToolAnchor.cs.uid @@ -0,0 +1 @@ +uid://dauviio1vuhws diff --git a/src/SharpIDE.Godot/Features/Layout/ToolAnchorExtensions.cs b/src/SharpIDE.Godot/Features/Layout/ToolAnchorExtensions.cs new file mode 100644 index 00000000..1810eeb3 --- /dev/null +++ b/src/SharpIDE.Godot/Features/Layout/ToolAnchorExtensions.cs @@ -0,0 +1,27 @@ +namespace SharpIDE.Godot.Features.Layout; + +public static class ToolAnchorExtensions +{ + extension(ToolAnchor anchor) + { + public bool IsLeft() + { + return anchor is ToolAnchor.LeftTop or ToolAnchor.BottomLeft; + } + + public bool IsRight() + { + return anchor is ToolAnchor.RightTop or ToolAnchor.BottomRight; + } + + public bool IsTop() + { + return anchor is ToolAnchor.LeftTop or ToolAnchor.RightTop; + } + + public bool IsBottom() + { + return anchor is ToolAnchor.BottomLeft or ToolAnchor.BottomRight; + } + } +} \ No newline at end of file diff --git a/src/SharpIDE.Godot/Features/Layout/ToolAnchorExtensions.cs.uid b/src/SharpIDE.Godot/Features/Layout/ToolAnchorExtensions.cs.uid new file mode 100644 index 00000000..d1609248 --- /dev/null +++ b/src/SharpIDE.Godot/Features/Layout/ToolAnchorExtensions.cs.uid @@ -0,0 +1 @@ +uid://nmwvva8a4w56 diff --git a/src/SharpIDE.Godot/Features/Layout/ToolArea.cs b/src/SharpIDE.Godot/Features/Layout/ToolArea.cs new file mode 100644 index 00000000..feec5739 --- /dev/null +++ b/src/SharpIDE.Godot/Features/Layout/ToolArea.cs @@ -0,0 +1,47 @@ +using Godot; + +namespace SharpIDE.Godot.Features.Layout; + +public partial class ToolArea : Control +{ + public Control? CurrentTool { get; private set; } + + public override void _Ready() + { + Visible = false; + } + + public void ShowTool(Control tool) + { + if (ReferenceEquals(CurrentTool, tool)) + { + return; + } + + if (CurrentTool is not null) + { + HideTool(); + } + + if (tool.GetParent() is ToolArea parent) + { + parent.HideTool(); + } + + CurrentTool = tool; + + AddChild(CurrentTool); + Visible = true; + } + + public void HideTool() + { + if (CurrentTool is not null) + { + RemoveChild(CurrentTool); + } + + CurrentTool = null; + Visible = false; + } +} diff --git a/src/SharpIDE.Godot/Features/Layout/ToolArea.cs.uid b/src/SharpIDE.Godot/Features/Layout/ToolArea.cs.uid new file mode 100644 index 00000000..98271ac5 --- /dev/null +++ b/src/SharpIDE.Godot/Features/Layout/ToolArea.cs.uid @@ -0,0 +1 @@ +uid://diqwg1bxg3tlp diff --git a/src/SharpIDE.Godot/Features/Layout/ToolArea.tscn b/src/SharpIDE.Godot/Features/Layout/ToolArea.tscn new file mode 100644 index 00000000..4bf5132d --- /dev/null +++ b/src/SharpIDE.Godot/Features/Layout/ToolArea.tscn @@ -0,0 +1,11 @@ +[gd_scene load_steps=2 format=3 uid="uid://b8wqkwa7sjrid"] + +[ext_resource type="Script" uid="uid://diqwg1bxg3tlp" path="res://Features/Layout/ToolArea.cs" id="1_03jti"] + +[node name="ToolArea" type="Panel"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_03jti") diff --git a/src/SharpIDE.Godot/Features/Layout/ToolButton.cs b/src/SharpIDE.Godot/Features/Layout/ToolButton.cs new file mode 100644 index 00000000..5d2f40e0 --- /dev/null +++ b/src/SharpIDE.Godot/Features/Layout/ToolButton.cs @@ -0,0 +1,55 @@ +using Godot; + +using SharpIDE.Godot.Features.Tools; + +namespace SharpIDE.Godot.Features.Layout; + +public partial class ToolButton : Button +{ + public IdeToolId ToolId { get; set; } + + [Export] + public Color DragPreviewColor { get; set; } + + /// + public override Variant _GetDragData(Vector2 atPosition) + { + SetDragPreview(CreateDragPreview()); + + // We disable the button when dragging to ensure the Hide() does not toggle it. + Disabled = true; + Hide(); + + return Variant.From(ToolId); + } + + /// + public override void _Notification(int what) + { + switch ((long) what) + { + case NotificationDragEnd: + Disabled = false; + Show(); + break; + } + } + + private Control CreateDragPreview() + { + var rect = new ColorRect(); + rect.Size = Size; + rect.Color = DragPreviewColor; + + var icon = new TextureRect + { + Texture = Icon, + ExpandMode = TextureRect.ExpandModeEnum.FitWidthProportional, + }; + + icon.SetAnchorsPreset(LayoutPreset.FullRect); + + rect.AddChild(icon); + return rect; + } +} diff --git a/src/SharpIDE.Godot/Features/Layout/ToolButton.cs.uid b/src/SharpIDE.Godot/Features/Layout/ToolButton.cs.uid new file mode 100644 index 00000000..b9473051 --- /dev/null +++ b/src/SharpIDE.Godot/Features/Layout/ToolButton.cs.uid @@ -0,0 +1 @@ +uid://14orxapuy7mg diff --git a/src/SharpIDE.Godot/Features/Layout/ToolButton.tscn b/src/SharpIDE.Godot/Features/Layout/ToolButton.tscn new file mode 100644 index 00000000..8e3a218f --- /dev/null +++ b/src/SharpIDE.Godot/Features/Layout/ToolButton.tscn @@ -0,0 +1,23 @@ +[gd_scene load_steps=5 format=3 uid="uid://gcpcsulb43in"] + +[ext_resource type="Script" uid="uid://14orxapuy7mg" path="res://Features/Layout/ToolButton.cs" id="1_8ncbp"] +[ext_resource type="StyleBox" uid="uid://cosaurtj574yc" path="res://Features/Layout/Resources/ToolButtonStyleNormal.tres" id="1_83dai"] +[ext_resource type="StyleBox" uid="uid://d26wbe6o067ko" path="res://Features/Layout/Resources/ToolButtonStylePressed.tres" id="2_m4fy1"] + +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_83dai"] + +[node name="ToolButton" type="Button"] +custom_minimum_size = Vector2(40, 40) +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_styles/normal = ExtResource("1_83dai") +theme_override_styles/pressed = ExtResource("2_m4fy1") +theme_override_styles/focus = SubResource("StyleBoxEmpty_83dai") +toggle_mode = true +icon_alignment = 1 +expand_icon = true +script = ExtResource("1_8ncbp") +DragPreviewColor = Color(0.36, 0.36, 0.36, 0.7294118) diff --git a/src/SharpIDE.Godot/Features/Layout/ToolDragOverlay.cs b/src/SharpIDE.Godot/Features/Layout/ToolDragOverlay.cs new file mode 100644 index 00000000..73efb6eb --- /dev/null +++ b/src/SharpIDE.Godot/Features/Layout/ToolDragOverlay.cs @@ -0,0 +1,205 @@ +using System.Diagnostics.CodeAnalysis; + +using Godot; + +using SharpIDE.Godot.Features.Tools; + +namespace SharpIDE.Godot.Features.Layout; + +public sealed record ToolMoveData(IdeToolId ToolId, ToolAnchor Anchor, int Index); + +public partial class ToolDragOverlay : Control +{ + private readonly Dictionary _dropZoneAnchorMap = []; + private readonly Dictionary _sidebarMap = []; + + [Export] + public Sidebar LeftSidebar { get; set; } = null!; + + [Export] + public Sidebar RightSidebar { get; set; } = null!; + + [Export] + public DropZone LeftTopZone { get; set; } = null!; + + [Export] + public DropZone RightTopZone { get; set; } = null!; + + [Export] + public DropZone BottomLeftZone { get; set; } = null!; + + [Export] + public DropZone BottomRightZone { get; set; } = null!; + + public event EventHandler? ToolMoveRequested; + + public override void _Ready() + { + _dropZoneAnchorMap[LeftTopZone] = ToolAnchor.LeftTop; + _dropZoneAnchorMap[RightTopZone] = ToolAnchor.RightTop; + _dropZoneAnchorMap[BottomLeftZone] = ToolAnchor.BottomLeft; + _dropZoneAnchorMap[BottomRightZone] = ToolAnchor.BottomRight; + + _sidebarMap[ToolAnchor.LeftTop] = LeftSidebar; + _sidebarMap[ToolAnchor.RightTop] = RightSidebar; + _sidebarMap[ToolAnchor.BottomLeft] = LeftSidebar; + _sidebarMap[ToolAnchor.BottomRight] = RightSidebar; + + VisibilityChanged += OnVisibilityChanged; + } + + /// + public override bool _CanDropData(Vector2 pos, Variant data) + { + if (data.VariantType is not Variant.Type.Int) + { + return false; + } + + HideToolPreviews(); + HideDropZoneHighlights(); + + var mousePosition = GetGlobalMousePosition(); + + if (TryGetAnchorAndZoneAtPosition(mousePosition, out var anchor, out var dropZone)) + { + ShowGhostPreview(anchor.Value, mousePosition); + dropZone.Highlight.Show(); + return true; + } + + return false; + } + + /// + public override void _DropData(Vector2 _, Variant data) + { + var mousePosition = GetGlobalMousePosition(); + + if (TryGetAnchorAndZoneAtPosition(mousePosition, out var anchor, out var _)) + { + RaiseToolDropped( + data.As(), + anchor.Value, + CalculateInsertionIndex(GetSidebarTools(anchor.Value), mousePosition, preview: false)); + } + } + + private void OnVisibilityChanged() + { + if (!Visible) + { + HideToolPreviews(); + HideDropZoneHighlights(); + } + } + + private void RaiseToolDropped(IdeToolId toolId, ToolAnchor anchor, int index) + { + ToolMoveRequested?.Invoke( + sender: this, + new ToolMoveData( + toolId, + anchor, + index)); + } + + private bool TryGetAnchorAndZoneAtPosition( + Vector2 position, + [NotNullWhen(true)] out ToolAnchor? anchor, + [NotNullWhen(true)] out DropZone? dropZone) + { + anchor = null; + dropZone = null; + + foreach (var (zone, zoneAnchor) in _dropZoneAnchorMap) + { + if (InDropZone(zone, position)) + { + anchor = zoneAnchor; + dropZone = zone; + return true; + } + } + + return false; + } + + private void ShowGhostPreview(ToolAnchor anchor, Vector2 mousePosition) + { + var sidebar = _sidebarMap[anchor]; + var tools = GetSidebarTools(anchor); + var previewIndex = CalculateInsertionIndex(tools, mousePosition, preview: true); + + if (!ReferenceEquals(sidebar.ToolPreview.GetParent(), tools)) + { + tools.AddChild(sidebar.ToolPreview); + } + + tools.MoveChild(sidebar.ToolPreview, previewIndex); + } + + private void HideToolPreviews() + { + if (LeftSidebar.ToolPreview.GetParent() is { } leftParent) + { + leftParent.RemoveChild(LeftSidebar.ToolPreview); + } + + if (RightSidebar.ToolPreview.GetParent() is { } rightParent) + { + rightParent.RemoveChild(RightSidebar.ToolPreview); + } + } + + private void HideDropZoneHighlights() + { + foreach (var zone in _dropZoneAnchorMap.Keys) + { + zone.Highlight.Hide(); + } + } + + private Container GetSidebarTools(ToolAnchor anchor) + { + var sidebar = _sidebarMap[anchor]; + + return anchor switch + { + ToolAnchor.LeftTop or ToolAnchor.RightTop => sidebar.TopTools, + ToolAnchor.BottomLeft or ToolAnchor.BottomRight => sidebar.BottomTools, + _ => throw new ArgumentException($"No tools to show preview in for anchor '{anchor}'.", nameof(anchor)) + }; + } + + private static int CalculateInsertionIndex(Container tools, Vector2 mousePosition, bool preview) + { + var children = tools.GetChildren() + .OfType() + + // When in preview we don't ignore the disabled button (button being dragged) + // in order to insert this preview in the correct index. + // When calculating the index for the actual insertion we ignore the button being dragged. + .Where(button => preview || !button.Disabled) + .Index() + .ToList(); + + foreach (var (index, child) in children) + { + var rect = child.GetGlobalRect(); + var midpoint = rect.Position.Y + rect.Size.Y / 2.0f; + + if (mousePosition.Y < midpoint) + { + return index; + } + } + + return children.Count; + } + + private static bool InDropZone(Control dropZone, Vector2 position) + { + return dropZone.GetGlobalRect().HasPoint(position); + } +} diff --git a/src/SharpIDE.Godot/Features/Layout/ToolDragOverlay.cs.uid b/src/SharpIDE.Godot/Features/Layout/ToolDragOverlay.cs.uid new file mode 100644 index 00000000..a169154f --- /dev/null +++ b/src/SharpIDE.Godot/Features/Layout/ToolDragOverlay.cs.uid @@ -0,0 +1 @@ +uid://dc57hptoukgk1 diff --git a/src/SharpIDE.Godot/Features/Layout/ToolDragOverlay.tscn b/src/SharpIDE.Godot/Features/Layout/ToolDragOverlay.tscn new file mode 100644 index 00000000..7370c9f7 --- /dev/null +++ b/src/SharpIDE.Godot/Features/Layout/ToolDragOverlay.tscn @@ -0,0 +1,13 @@ +[gd_scene load_steps=2 format=3 uid="uid://f1hvt1gn1adf"] + +[ext_resource type="Script" uid="uid://dc57hptoukgk1" path="res://Features/Layout/ToolDragOverlay.cs" id="1_cwqxu"] + +[node name="ToolDockOverlay" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 2 +script = ExtResource("1_cwqxu") diff --git a/src/SharpIDE.Godot/Features/LeftSideBar/LeftBottomSidebarButtonGroup.tres b/src/SharpIDE.Godot/Features/LeftSideBar/LeftBottomSidebarButtonGroup.tres deleted file mode 100644 index 5c90e98f..00000000 --- a/src/SharpIDE.Godot/Features/LeftSideBar/LeftBottomSidebarButtonGroup.tres +++ /dev/null @@ -1,4 +0,0 @@ -[gd_resource type="ButtonGroup" format=3 uid="uid://c2nmo2x3va0gi"] - -[resource] -allow_unpress = true diff --git a/src/SharpIDE.Godot/Features/LeftSideBar/LeftSideBar.cs b/src/SharpIDE.Godot/Features/LeftSideBar/LeftSideBar.cs deleted file mode 100644 index 103e60fd..00000000 --- a/src/SharpIDE.Godot/Features/LeftSideBar/LeftSideBar.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Godot; -using SharpIDE.Godot.Features.BottomPanel; - -namespace SharpIDE.Godot.Features.LeftSideBar; - -public partial class LeftSideBar : Panel -{ - private Button _slnExplorerButton = null!; - // These are in a ButtonGroup, which handles mutual exclusivity of being toggled on - private Button _problemsButton = null!; - private Button _runButton = null!; - private Button _buildButton = null!; - private Button _debugButton = null!; - private Button _ideDiagnosticsButton = null!; - private Button _nugetButton = null!; - private Button _testExplorerButton = null!; - - public override void _Ready() - { - _slnExplorerButton = GetNode