diff --git a/src/.idea/.idea.Artemis/.idea/avalonia.xml b/src/.idea/.idea.Artemis/.idea/avalonia.xml index 8021c13f5..5e6d28ea6 100644 --- a/src/.idea/.idea.Artemis/.idea/avalonia.xml +++ b/src/.idea/.idea.Artemis/.idea/avalonia.xml @@ -55,7 +55,7 @@ - + diff --git a/src/Artemis.Core/Models/Profile/ProfileCategory.cs b/src/Artemis.Core/Models/Profile/ProfileCategory.cs index d6b343b19..6ecba4ae2 100644 --- a/src/Artemis.Core/Models/Profile/ProfileCategory.cs +++ b/src/Artemis.Core/Models/Profile/ProfileCategory.cs @@ -98,7 +98,10 @@ namespace Artemis.Core configuration.Category.RemoveProfileConfiguration(configuration); if (targetIndex != null) - _profileConfigurations.Insert(Math.Clamp(targetIndex.Value, 0, _profileConfigurations.Count), configuration); + { + targetIndex = Math.Clamp(targetIndex.Value, 0, _profileConfigurations.Count); + _profileConfigurations.Insert(targetIndex.Value, configuration); + } else _profileConfigurations.Add(configuration); configuration.Category = this; @@ -116,7 +119,8 @@ namespace Artemis.Core internal void RemoveProfileConfiguration(ProfileConfiguration configuration) { - if (!_profileConfigurations.Remove(configuration)) return; + if (!_profileConfigurations.Remove(configuration)) + return; for (int index = 0; index < _profileConfigurations.Count; index++) _profileConfigurations[index].Order = index; diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeView.axaml b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeView.axaml index 45e2d9141..285501a8e 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeView.axaml +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeView.axaml @@ -90,7 +90,9 @@ - + + + + + + + + - @@ -56,7 +113,7 @@ VerticalAlignment="Center" Text="{CompiledBinding ProfileCategory.Name, FallbackValue='Profile name'}" TextTrimming="CharacterEllipsis" - PointerPressed="Title_OnPointerPressed" + PointerReleased="InputElement_OnPointerReleased" Background="Transparent"> @@ -107,7 +164,8 @@ - { + private static Image? _dragAdorner; + private Point _dragStartPosition; + private Point _elementDragOffset; + private ListBox _listBox; + public SidebarCategoryView() { InitializeComponent(); + _listBox = this.Get("SidebarListBox"); + + AddHandler(DragDrop.DragEnterEvent, HandleDragEnterEvent, RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble, true); + AddHandler(DragDrop.DragOverEvent, HandleDragOver, RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble, true); + AddHandler(PointerEnterEvent, HandlePointerEnter, RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble, true); } private void InitializeComponent() @@ -17,9 +36,104 @@ namespace Artemis.UI.Screens.Sidebar AvaloniaXamlLoader.Load(this); } - private void Title_OnPointerPressed(object? sender, PointerPressedEventArgs e) + private void InputElement_OnPointerReleased(object? sender, PointerReleasedEventArgs e) { - ViewModel?.ToggleCollapsed.Execute().Subscribe(); + if (e.InitialPressMouseButton == MouseButton.Left) + ViewModel?.ToggleCollapsed.Execute().Subscribe(); } + + #region Dragging + + private void HandlePointerEnter(object? sender, PointerEventArgs e) + { + DisposeDragAdorner(); + } + + private void HandleDragEnterEvent(object? sender, DragEventArgs e) + { + CreateDragAdorner(e); + } + + private void CreateDragAdorner(DragEventArgs e) + { + if (_dragAdorner != null) + return; + + if (e.Source is not Control c) + return; + + // Get the list box item that raised the event + ListBoxItem? container = c.FindLogicalAncestorOfType(); + if (container == null) + return; + + // Take a snapshot of said tree view item and add it as an adorner + ITransform? originalTransform = container.RenderTransform; + try + { + _dragStartPosition = e.GetPosition(this.FindAncestorOfType()); + _elementDragOffset = e.GetPosition(container); + + RenderTargetBitmap renderTarget = new(new PixelSize((int) container.Bounds.Width, (int) container.Bounds.Height)); + container.RenderTransform = new TranslateTransform(container.Bounds.X * -1, container.Bounds.Y * -1); + renderTarget.Render(container); + _dragAdorner = new Image + { + Source = renderTarget, + VerticalAlignment = VerticalAlignment.Top, + HorizontalAlignment = HorizontalAlignment.Left, + Stretch = Stretch.None, + IsHitTestVisible = false + }; + AdornerLayer.GetAdornerLayer(this)!.Children.Add(_dragAdorner); + } + finally + { + container.RenderTransform = originalTransform; + } + } + + private void HandleDragOver(object? sender, DragEventArgs e) + { + UpdateDragAdorner(e.GetPosition(this.FindAncestorOfType())); + } + + private void HandleLeaveEvent(object? sender, RoutedEventArgs e) + { + // If there is currently an adorner, dispose of it + DisposeDragAdorner(); + } + + private void HandleDrop(object? sender, DragEventArgs e) + { + // If there is currently an adorner, dispose of it + DisposeDragAdorner(); + } + + private void DisposeDragAdorner() + { + if (_dragAdorner == null) + return; + + AdornerLayer.GetAdornerLayer(this)!.Children.Remove(_dragAdorner); + (_dragAdorner.Source as RenderTargetBitmap)?.Dispose(); + _dragAdorner = null; + + } + + private void UpdateDragAdorner(Point position) + { + if (_dragAdorner == null) + return; + + _dragAdorner.RenderTransform = new TranslateTransform(_dragStartPosition.X - _elementDragOffset.X, position.Y - _elementDragOffset.Y); + } + + private void InputElement_OnPointerMoved(object? sender, PointerEventArgs e) + { + DisposeDragAdorner(); + } + + #endregion } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Sidebar/SidebarCategoryViewModel.cs b/src/Artemis.UI/Screens/Sidebar/SidebarCategoryViewModel.cs index ebb6df9bf..fa6ea6a8e 100644 --- a/src/Artemis.UI/Screens/Sidebar/SidebarCategoryViewModel.cs +++ b/src/Artemis.UI/Screens/Sidebar/SidebarCategoryViewModel.cs @@ -14,6 +14,7 @@ using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services.Builders; using Artemis.UI.Shared.Services.ProfileEditor; using DynamicData; +using DynamicData.Binding; using ReactiveUI; namespace Artemis.UI.Screens.Sidebar @@ -37,13 +38,13 @@ namespace Artemis.UI.Screens.Sidebar _vmFactory = vmFactory; ProfileCategory = profileCategory; - SourceCache profileConfigurations = new(t => t.ProfileId); + SourceList profileConfigurations = new(); // Only show items when not collapsed IObservable> profileConfigurationsFilter = this.WhenAnyValue(vm => vm.IsCollapsed).Select(b => new Func(_ => !b)); profileConfigurations.Connect() - .SortBy(c => c.Order) .Filter(profileConfigurationsFilter) + .Sort(SortExpressionComparer.Ascending(c => c.Order)) .Transform(c => _vmFactory.SidebarProfileConfigurationViewModel(_sidebarViewModel, c)) .Bind(out ReadOnlyObservableCollection profileConfigurationViewModels) .Subscribe(); @@ -58,7 +59,7 @@ namespace Artemis.UI.Screens.Sidebar { // Update the list of profiles whenever the category fires events Observable.FromEventPattern(x => profileCategory.ProfileConfigurationAdded += x, x => profileCategory.ProfileConfigurationAdded -= x) - .Subscribe(e => profileConfigurations.AddOrUpdate(e.EventArgs.ProfileConfiguration)) + .Subscribe(e => profileConfigurations.Add(e.EventArgs.ProfileConfiguration)) .DisposeWith(d); Observable.FromEventPattern(x => profileCategory.ProfileConfigurationRemoved += x, x => profileCategory.ProfileConfigurationRemoved -= x) .Subscribe(e => profileConfigurations.Remove(e.EventArgs.ProfileConfiguration)) @@ -77,7 +78,7 @@ namespace Artemis.UI.Screens.Sidebar profileConfigurations.Edit(updater => { foreach (ProfileConfiguration profileConfiguration in profileCategory.ProfileConfigurations) - updater.AddOrUpdate(profileConfiguration); + updater.Add(profileConfiguration); }); } @@ -135,5 +136,16 @@ namespace Artemis.UI.Screens.Sidebar ProfileCategory.IsSuspended = !ProfileCategory.IsSuspended; _profileService.SaveProfileCategory(ProfileCategory); } + + public void AddProfileConfiguration(ProfileConfiguration profileConfiguration, int? index) + { + ProfileCategory oldCategory = profileConfiguration.Category; + ProfileCategory.AddProfileConfiguration(profileConfiguration, index); + + _profileService.SaveProfileCategory(ProfileCategory); + // If the profile moved to a new category, also save the old category + if (oldCategory != ProfileCategory) + _profileService.SaveProfileCategory(oldCategory); + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Sidebar/SidebarView.axaml b/src/Artemis.UI/Screens/Sidebar/SidebarView.axaml index 85ba0e4d9..566761bc2 100644 --- a/src/Artemis.UI/Screens/Sidebar/SidebarView.axaml +++ b/src/Artemis.UI/Screens/Sidebar/SidebarView.axaml @@ -7,6 +7,9 @@ xmlns:svg="clr-namespace:Avalonia.Svg.Skia;assembly=Avalonia.Svg.Skia" mc:Ignorable="d" d:DesignWidth="240" d:DesignHeight="450" x:Class="Artemis.UI.Screens.Sidebar.SidebarView"> + + + @@ -33,7 +36,7 @@ - +