Преглед изворни кода

修改 ItemsDraggable; 尝试主题资源切换(WaaaghTheme.Resource); 尝试动画(NavigationBar); 尝试实现条件(ConditionBinding);

lintx пре 1 месец
родитељ
комит
03b0e22805

+ 8 - 0
Demo.sln

@@ -9,6 +9,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfSample", "WpfSample\WpfS
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Waaagh", "Waaagh\Waaagh.csproj", "{AF5669D4-D735-4ABD-B4A3-CC88DDF77137}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WaaaghTheme.Resources", "WaaaghTheme.Resource\WaaaghTheme.Resources.csproj", "{4C12C6CA-4670-4237-A092-70D092F126F0}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UI", "UI", "{6DAD74FF-5AAF-4941-94F4-602E20A8DE77}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -27,6 +31,10 @@ Global
 		{AF5669D4-D735-4ABD-B4A3-CC88DDF77137}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{AF5669D4-D735-4ABD-B4A3-CC88DDF77137}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{AF5669D4-D735-4ABD-B4A3-CC88DDF77137}.Release|Any CPU.Build.0 = Release|Any CPU
+		{4C12C6CA-4670-4237-A092-70D092F126F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{4C12C6CA-4670-4237-A092-70D092F126F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{4C12C6CA-4670-4237-A092-70D092F126F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{4C12C6CA-4670-4237-A092-70D092F126F0}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 13 - 0
Waaagh/App.xaml

@@ -0,0 +1,13 @@
+<Application x:Class="Waaagh.App"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:local="clr-namespace:Waaagh">
+    <Application.Resources>
+        <ResourceDictionary>
+            <ResourceDictionary.MergedDictionaries>
+                <ResourceDictionary Source="/WaaaghTheme.Resources;component/DarkTheme.xaml"/>
+            </ResourceDictionary.MergedDictionaries>
+            <SolidColorBrush x:Key="TestBrush" Color="#669999"/>
+        </ResourceDictionary>
+    </Application.Resources>
+</Application>

+ 14 - 18
Waaagh/Program.cs → Waaagh/App.xaml.cs

@@ -9,21 +9,31 @@ using Waaagh.ViewModels;
 using Waaagh.Views;
 
 namespace Waaagh {
-    public class Program {
+    public partial class App: Application {
         [STAThread]
         static public void Main(string[] args) {
             SQLitePCL.Batteries.Init();
 
-            IHost host = CreateHostBuilder(args).Build();
+            using IHost host = CreateHostBuilder(args).Build();
             host.Start();
 
-            Application application = host.Services.GetRequiredService<Application>();
+            App app = new App();
+            app.InitializeComponent();
+            app.MainWindow = host.Services.GetRequiredService<MainWindow>();
+            app.MainWindow.Visibility = Visibility.Visible;
+            app.Run();
 
 #if DEBUG
             //  DbTest(host);
 #endif
 
-            application.Run();
+        }
+
+        protected override void OnStartup(StartupEventArgs e) {
+            base.OnStartup(e);
+            // SplashScreen splash = new SplashScreen(@"\Assets\Image\琪露诺3.jpg");
+            // splash.Show(false, true);
+            // splash.Close(new TimeSpan(0, 0, 5));
         }
 
         static private IHostBuilder CreateHostBuilder(string[] args) {
@@ -49,20 +59,6 @@ namespace Waaagh {
                             DataContext = sp.GetRequiredService<MainViewModel>()
                         };
                     });
-                })
-                .ConfigureServices(services => {
-                    // Application
-                    services.AddSingleton<Application>(sp => {
-                        Application application = new Application() {
-                            MainWindow = sp.GetRequiredService<MainWindow>(),
-                        };
-                        application.Startup += (s, e) => {
-                            if (s is Application application) {
-                                application.MainWindow.Show();
-                            }
-                        };
-                        return application;
-                    });
                 });
         }
 

BIN
Waaagh/Assets/Image/八坂神奈子.jpg


BIN
Waaagh/Assets/Image/山城高岭.jpg


BIN
Waaagh/Assets/Image/琪露诺.jpg


BIN
Waaagh/Assets/Image/琪露诺2.jpg


BIN
Waaagh/Assets/Image/琪露诺3.jpg


BIN
Waaagh/Assets/Image/蕾米莉亚·斯卡蕾特.jpg


BIN
Waaagh/Assets/Image/风见幽香.jpg


BIN
Waaagh/Assets/Image/风见幽香2.jpg


+ 199 - 219
Waaagh/Behaviors/ItemsDraggable.cs

@@ -4,268 +4,248 @@ using System.Windows.Controls;
 using System.Windows.Input;
 using System.Windows.Media;
 
-namespace Waaagh.Behaviors {
-    sealed public class ItemsDraggable {
-        #region Drag Drop Data Format String
-        static readonly string DraggableDataFormat = "DraggableData";
-        static readonly string DraggableCollectionFormat = "DraggableCollection";
-        #endregion
+namespace Waaagh.Behaviors;
 
-        #region Enabled
-        static public bool GetEnabled(DependencyObject obj) {
-            return (bool)obj.GetValue(EnabledProperty);
-        }
+sealed public class ItemsDraggable {
+    #region Drag Drop Data Format String
+    static readonly string DraggableDataFormat = "DraggableData";
+    static readonly string DraggableCollectionFormat = "DraggableCollection";
+    #endregion
 
-        static public void SetEnabled(DependencyObject obj, bool value) {
-            obj.SetValue(EnabledProperty, value);
-        }
+    #region Dragging Data Object
+    private static DataObject? GetDraggingData(DependencyObject obj) {
+        return (DataObject?)obj.GetValue(DraggingDataProperty);
+    }
 
-        static public readonly DependencyProperty EnabledProperty =
-            DependencyProperty.RegisterAttached("Enabled", typeof(bool), typeof(ItemsDraggable),
-                new PropertyMetadata(false, OnEnabledChanged));
+    private static void SetDraggingData(DependencyObject obj, DataObject? value) {
+        obj.SetValue(DraggingDataProperty, value);
+    }
 
-        static private void OnEnabledChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args) {
-            if (sender is ItemsControl itemsControl == false) {
-                throw new InvalidOperationException("The attached control is not an 'ItemsControl'.");
-            }
-            itemsControl.AllowDrop = false;
-            itemsControl.MouseMove -= ItemsDraggable_MouseMove;
-            itemsControl.DragEnter -= ItemsDraggable_DragEnter;
-            itemsControl.DragOver -= ItemsDraggable_DragOver;
-            itemsControl.DragLeave -= ItemsDraggable_DragLeave;
-            itemsControl.Drop -= ItemsDraggable_Drop;
-            if (GetEnabled(itemsControl) == true) {
-                itemsControl.AllowDrop = true;
-                itemsControl.MouseMove += ItemsDraggable_MouseMove;
-                itemsControl.DragEnter += ItemsDraggable_DragEnter;
-                itemsControl.DragOver += ItemsDraggable_DragOver;
-                itemsControl.DragLeave += ItemsDraggable_DragLeave;
-                itemsControl.Drop += ItemsDraggable_Drop;
-            }
-        }
+    private static readonly DependencyProperty DraggingDataProperty = DependencyProperty.RegisterAttached(
+        "DraggingData", typeof(DataObject), typeof(ItemsDraggable), new PropertyMetadata(null));
+    #endregion
 
+    #region Enabled
+    static public bool GetEnabled(DependencyObject obj) {
+        return (bool)obj.GetValue(EnabledProperty);
+    }
 
+    static public void SetEnabled(DependencyObject obj, bool value) {
+        obj.SetValue(EnabledProperty, value);
+    }
 
-        public static Func<object, Type, IList?> GetQueryTargetCollection(DependencyObject obj) {
-            return (Func<object, Type, IList?>)obj.GetValue(QueryTargetCollectionProperty);
+    static public readonly DependencyProperty EnabledProperty = DependencyProperty.RegisterAttached(
+        "Enabled", typeof(bool), typeof(ItemsDraggable), new PropertyMetadata(false, OnEnabledChanged));
+
+    static private void OnEnabledChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args) {
+        if (sender is ItemsControl itemsControl == false) {
+            throw new InvalidOperationException("The attached control is not an 'ItemsControl'.");
+        }
+        // if (itemsControl.ItemsSource is IList == false) {
+        //     throw new InvalidOperationException("The attached control must have an 'ItemsSource' implements 'IList'.");
+        // }
+        itemsControl.AllowDrop = false;
+        itemsControl.PreviewMouseLeftButtonDown -= ItemsDraggable_PreviewMouseLeftButtonDown;
+        itemsControl.MouseMove -= ItemsDraggable_MouseMove;
+        itemsControl.DragEnter -= ItemsDraggable_DragEnter;
+        itemsControl.DragOver -= ItemsDraggable_DragOver;
+        itemsControl.DragLeave -= ItemsDraggable_DragLeave;
+        itemsControl.Drop -= ItemsDraggable_Drop;
+        if (GetEnabled(itemsControl) == true) {
+            itemsControl.AllowDrop = true;
+            itemsControl.PreviewMouseLeftButtonDown += ItemsDraggable_PreviewMouseLeftButtonDown;
+            itemsControl.MouseMove += ItemsDraggable_MouseMove;
+            itemsControl.DragEnter += ItemsDraggable_DragEnter;
+            itemsControl.DragOver += ItemsDraggable_DragOver;
+            itemsControl.DragLeave += ItemsDraggable_DragLeave;
+            itemsControl.Drop += ItemsDraggable_Drop;
         }
+    }
+    #endregion
 
-        public static void SetQueryTargetCollection(DependencyObject obj, Func<object, Type, IList?> value) {
-            obj.SetValue(QueryTargetCollectionProperty, value);
+    #region Drag Drop Event
+    private static void ItemsDraggable_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs args) {
+        if (sender is ItemsControl control == false) {
+            return;
         }
+        if (control.ItemsSource is IList == false) {
+            return;
+        }
+        if (GetElement(control, args.GetPosition(control)) is Control sourceControl == false) {
+            return;
+        }
+        if (GetParent(sourceControl) is ItemsControl sourceItemsControl == false) {
+            return;
+        }
+        DataObject data = new DataObject();
+        data.SetData(DraggableDataFormat, sourceControl.DataContext);
+        data.SetData(DraggableCollectionFormat, sourceItemsControl.ItemsSource);
+        SetDraggingData(control, data);
+    }
 
-        public static readonly DependencyProperty QueryTargetCollectionProperty =
-            DependencyProperty.RegisterAttached("QueryTargetCollection", typeof(Func<object, Type, IList?>), typeof(ItemsDraggable), new PropertyMetadata(null));
-
+    static private void ItemsDraggable_MouseMove(object sender, MouseEventArgs args) {
+        if (args.LeftButton != MouseButtonState.Pressed) {
+            return;
+        }
+        if (sender is ItemsControl source == false) {
+            return;
+        }
+        if (GetDraggingData(source) is DataObject data == false) {
+            return;
+        }
+        SetDraggingData(source, null);
+        DragDrop.DoDragDrop(source, data, DragDropEffects.Move);
+    }
 
-        #endregion
+    static private void ItemsDraggable_DragEnter(object sender, DragEventArgs args) {
 
-        #region Drag Drop Event
-        static private void ItemsDraggable_MouseMove(object sender, MouseEventArgs args) {
-            if (args.LeftButton != MouseButtonState.Pressed) {
-                return;
-            }
-            if (sender is ItemsControl source == false) {
-                return;
-            }
-            if (source.ItemsSource is IList == false) {
-                return;
-            }
-            if (GetElement(source, args.GetPosition(source)) is Control sourceControl == false) {
-                return;
-            }
-            if (GetParent(sourceControl) is ItemsControl sourceItemsControl == false) {
-                return;
-            }
+    }
 
-            DataObject data = new DataObject();
-            data.SetData(DraggableDataFormat, sourceControl.DataContext);
-            data.SetData(DraggableCollectionFormat, sourceItemsControl.ItemsSource);
-            DragDrop.DoDragDrop(sourceItemsControl, data, DragDropEffects.Move);
-        }
+    static private void ItemsDraggable_DragOver(object sender, DragEventArgs args) {
 
-        static private void ItemsDraggable_DragEnter(object sender, DragEventArgs args) {
+    }
 
-        }
+    static private void ItemsDraggable_DragLeave(object sender, DragEventArgs args) {
 
-        static private void ItemsDraggable_DragOver(object sender, DragEventArgs args) {
+    }
 
+    static private void ItemsDraggable_Drop(object sender, DragEventArgs args) {
+        if (args.Effects == DragDropEffects.None) {
+            return;
+        }
+        if (sender is ItemsControl control == false) {
+            return;
+        }
+        // 获取 放置目标
+        if (GetElement(control, args.GetPosition(control)) is Control targetControl == false) {
+            return;
+        }
+        if (GetParent(targetControl) is ItemsControl targetItemsControl == false) {
+            return;
+        }
+        object? targetData = targetControl.DataContext;
+        IList? targetCollection = targetItemsControl.ItemsSource as IList;
+        if (targetCollection == null) {
+            return;
+        }
+        // 获取 拖拽源
+        object? sourceData = args.Data.GetData(DraggableDataFormat) as object;
+        IList? sourceCollection = args.Data.GetData(DraggableCollectionFormat) as IList;
+        if (sourceData == null || sourceCollection == null) {
+            return;
         }
+        // 执行 拖拽影响
+        DoDragDropEffects(control, args.Effects, sourceCollection, sourceData, targetCollection, targetData);
+    }
+    #endregion
+
+    #region Drag Drop Effects
+    public delegate void DoEffectsCallbackHandler(object sender, DoEffectsCallbackArgs args);
 
-        static private void ItemsDraggable_DragLeave(object sender, DragEventArgs args) {
+    public class DoEffectsCallbackArgs {
+        public IList SourceCollection { get; }
+        public object SourceData { get; }
+        public IList TargetCollection { get; }
+        public object? TargetData { get; }
 
+        public DoEffectsCallbackArgs(IList sourceCollection, object sourceData, IList targetCollection, object? targetData) {
+            SourceCollection = sourceCollection;
+            SourceData = sourceData;
+            TargetCollection = targetCollection;
+            TargetData = targetData;
         }
+    }
 
-        static private void ItemsDraggable_Drop(object sender, DragEventArgs args) {
-            if (args.Effects == DragDropEffects.None) {
-                return;
-            }
-            if (sender is ItemsControl target == false) {
-                return;
+    static private void DoDragDropEffects(ItemsControl control, DragDropEffects effects, IList sourceCollection, object sourceData, IList targetCollection, object? targetData) {
+        if (sourceCollection == null || sourceData == null || targetCollection == null) {
+            return;
+        }
+        if (object.ReferenceEquals(sourceData, targetData)) {
+            return;
+        }
+        switch (effects) {
+            case DragDropEffects.Scroll: {
+                break;
             }
-            // 获取 放置目标
-            if (GetElement(target, args.GetPosition(target)) is Control targetControl == false) {
-                return;
+            case DragDropEffects.All: {
+                break;
             }
-            if (GetParent(targetControl) is ItemsControl targetItemsControl == false) {
-                return;
+            case DragDropEffects.Copy: {
+                break;
             }
-            if (targetItemsControl.ItemsSource is IList targetCollection == false) {
-                return;
+            case DragDropEffects.Move: {
+                GetDoMoveEffect(control)?.Invoke(control, new DoEffectsCallbackArgs(sourceCollection, sourceData, targetCollection, targetData));
+                break;
             }
-            // 获取 拖拽源
-            object? sourceData = args.Data.GetData(DraggableDataFormat) as object;
-            IList? sourceCollection = args.Data.GetData(DraggableCollectionFormat) as IList;
-            if (sourceData == null || sourceCollection == null) {
-                return;
+            case DragDropEffects.Link: {
+                break;
             }
-            // 判断 集合元素类型
-            object? targetData = targetControl.DataContext;
-            if (CheckCollectionElementType(sourceCollection, targetCollection) == false) {
-                IList? collection = GetQueryTargetCollection(targetItemsControl)?.Invoke(targetControl.DataContext, sourceData.GetType());
-                if (collection == null || CheckCollectionElementType(sourceCollection, collection) == false) {
-                    return;
-                }
-                targetCollection = collection;
-                targetData = null; // 置 targetData 为 null, 尝试将 sourceData 添加到 targetCollection 末尾
+            default: {
+                break;
             }
-            // 执行 拖拽影响
-            DoDragDropEffects(args.Effects, sourceCollection, sourceData, targetCollection, targetData);
         }
-        #endregion
+    }
 
-        #region Drag Drop Effects
-        static private void DoDragDropEffects(DragDropEffects effects, IList sourceCollection, object sourceData, IList targetCollection, object? targetData) {
-            if (sourceCollection == null || sourceData == null || targetCollection == null) {
-                return;
-            }
-            if (object.ReferenceEquals(sourceData, targetData)) {
-                return;
-            }
-            switch (effects) {
-                case DragDropEffects.Scroll: {
-                    break;
-                }
-                case DragDropEffects.All: {
-                    break;
-                }
-                case DragDropEffects.Copy: {
-                    break;
-                }
-                case DragDropEffects.Move: {
-                    DoMoveEffect(sourceCollection, sourceData, targetCollection, targetData);
-                    break;
-                }
-                case DragDropEffects.Link: {
-                    break;
-                }
-                default: {
-                    break;
-                }
-            }
-        }
+    #region Do Move Effect Callback
+    public static DoEffectsCallbackHandler GetDoMoveEffect(DependencyObject obj) {
+        return (DoEffectsCallbackHandler)obj.GetValue(DoMoveEffectProperty);
+    }
 
-        static private void DoMoveEffect(IList sourceCollection, object sourceData, IList targetCollection, object? targetData) {
-            int oldIndex = sourceCollection.IndexOf(sourceData);
-            int newIndex = targetCollection.IndexOf(targetData);
-            if (newIndex == -1) {
-                newIndex = targetCollection.Count - 1;
-            }
-            if (object.ReferenceEquals(sourceCollection, targetCollection)) {
-                if (oldIndex == newIndex) {
-                    return;
-                }
-                // 同个集合, 移除元素后下标可能变化
-                if (oldIndex < newIndex) {
-                    newIndex -= 1;
-                }
-            }
-            sourceCollection.RemoveAt(oldIndex);
-            targetCollection.Insert(newIndex + 1, sourceData);
-        }
+    public static void SetDoMoveEffect(DependencyObject obj, DoEffectsCallbackHandler value) {
+        obj.SetValue(DoMoveEffectProperty, value);
+    }
 
-        #endregion
+    public static readonly DependencyProperty DoMoveEffectProperty = DependencyProperty.RegisterAttached(
+        "DoMoveEffect", typeof(DoEffectsCallbackHandler), typeof(ItemsDraggable), new PropertyMetadata(null));
+    #endregion
 
-        #region Helper
-        static private bool GetDragDropEventSource(ref ItemsControl parent, out Control child, Func<IInputElement, Point> getPosition) {
-            if (parent != null) {
-                ItemContainerGenerator generator = parent.ItemContainerGenerator;
-                for (int i = generator.Items.Count - 1; i >= 0; --i) {
-                    if (generator.ContainerFromIndex(i) is Control control == false) {
-                        continue;
-                    }
-                    Point position = getPosition(control);
-                    if (position.X < 0 || position.X > control.ActualWidth ||
-                        position.Y < 0 || position.Y > control.ActualHeight) {
-                        continue;
-                    }
-                    if (control is ContentControl) {
-                        child = control;
-                        return true;
-                    }
-                    if (control is ItemsControl itemsControl) {
-                        ItemsControl prevParent = parent;
-                        parent = itemsControl;
-                        if (GetDragDropEventSource(ref parent, out child, getPosition) == false) {
-                            child = parent;
-                            parent = prevParent;
-                        }
-                        return true;
-                    }
-                }
-            }
-            child = default!;
-            return false;
-        }
+    #endregion
 
-        static private Control? GetElement(ItemsControl source, Point point) {
-            if (GetItemContainerType(source) is Type type == false) {
-                return null;
-            }
-            DependencyObject? dp = source.InputHitTest(point) as DependencyObject;
-            while (dp != null) {
-                if (dp.GetType().Equals(type)) {
-                    break;
-                }
-                dp = VisualTreeHelper.GetParent(dp);
+    #region Helper
+    static private Control? GetElement(ItemsControl source, Point point) {
+        if (GetItemContainerType(source) is Type type == false) {
+            return null;
+        }
+        DependencyObject? dp = source.InputHitTest(point) as DependencyObject;
+        while (dp != null) {
+            if (dp.GetType().Equals(type)) {
+                break;
             }
-            return dp as Control;
+            dp = VisualTreeHelper.GetParent(dp);
         }
+        return dp as Control;
+    }
 
-        static private ItemsControl? GetParent(Control control) {
-            for (DependencyObject dp = VisualTreeHelper.GetParent(control); dp != null; dp = VisualTreeHelper.GetParent(dp)) {
-                if (dp is ItemsControl parent && parent.ItemContainerGenerator.ContainerFromItem(control.DataContext) != null) {
-                    return parent;
-                }
+    static private ItemsControl? GetParent(Control control) {
+        for (DependencyObject dp = VisualTreeHelper.GetParent(control); dp != null; dp = VisualTreeHelper.GetParent(dp)) {
+            if (dp is ItemsControl parent && parent.ItemContainerGenerator.ContainerFromItem(control.DataContext) != null) {
+                return parent;
             }
-            return null;
         }
+        return null;
+    }
 
-        static private Type? GetItemContainerType(ItemsControl source) {
-            if (source == null) {
-                return null;
-            }
-            if (source is ListView) {
-                return typeof(ListViewItem);
-            }
-            else if (source is ListBox) {
-                return typeof(ListBoxItem);
-            }
-            else if (source is TreeView || source is TreeViewItem) {
-                return typeof(TreeViewItem);
-            }
-            // else if (source is GridView) {
-            //     return typeof(GridViewColumn);
-            // }
-            if (source.Items.Count > 0) {
-                return source.ItemContainerGenerator.ContainerFromIndex(0)?.GetType();
-            }
+    static private Type? GetItemContainerType(ItemsControl source) {
+        if (source == null) {
             return null;
         }
-
-        static private bool CheckCollectionElementType(IList collection1, IList collection2) {
-            return collection1.GetType().GetGenericArguments()[0] == collection2.GetType().GetGenericArguments()[0];
+        if (source is ListView) {
+            return typeof(ListViewItem);
         }
-        #endregion
+        else if (source is ListBox) {
+            return typeof(ListBoxItem);
+        }
+        else if (source is TreeView || source is TreeViewItem) {
+            return typeof(TreeViewItem);
+        }
+        // else if (source is GridView) {
+        //     return typeof(GridViewColumn);
+        // }
+        if (source.Items.Count > 0) {
+            return source.ItemContainerGenerator.ContainerFromIndex(0)?.GetType();
+        }
+        return null;
     }
+    #endregion
+
 }

+ 26 - 0
Waaagh/Converters/ConditionBinding.cs

@@ -0,0 +1,26 @@
+using System.Globalization;
+using System.Windows.Data;
+using System.Windows.Markup;
+
+namespace Waaagh.Converters {
+    internal class ConditionBinding: MarkupExtension, IValueConverter {
+        public object Target { get; set; } = true;
+        public object True { get; set; } = true;
+        public object False { get; set; } = false;
+
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
+            if (Target == null) {
+                return value == null ? True : False;
+            }
+            return Target.Equals(value) ? True : False;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
+            throw new NotImplementedException();
+        }
+
+        public override object ProvideValue(IServiceProvider serviceProvider) {
+            return this;
+        }
+    }
+}

+ 44 - 0
Waaagh/CustomControl/NavigationBar.cs

@@ -0,0 +1,44 @@
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media.Animation;
+using Jamesnet.Wpf.Animation;
+using Jamesnet.Wpf.Controls;
+
+namespace Waaagh.CustomControl {
+    public class NavigationBar: ListBox {
+        private ValueItem _vi;
+        private Storyboard _sb;
+
+        static NavigationBar() {
+            DefaultStyleKeyProperty.OverrideMetadata(typeof(NavigationBar), new FrameworkPropertyMetadata(typeof(NavigationBar)));
+        }
+
+        public override void OnApplyTemplate() {
+            base.OnApplyTemplate();
+            Grid grid = (Grid)GetTemplateChild("PART_Circle");
+
+            InitStoryboard(grid);
+        }
+
+        private void InitStoryboard(Grid circle) {
+            _vi = new();
+            _sb = new();
+
+            _vi.Mode = EasingFunctionBaseMode.QuinticEaseInOut;
+            _vi.Property = new PropertyPath(Canvas.LeftProperty);
+            _vi.Duration = new Duration(new TimeSpan(0, 0, 0, 0, 500));
+
+            Storyboard.SetTarget(_vi, circle);
+            Storyboard.SetTargetProperty(_vi, _vi.Property);
+
+            _sb.Children.Add(_vi);
+        }
+
+        protected override void OnSelectionChanged(SelectionChangedEventArgs e) {
+            base.OnSelectionChanged(e);
+
+            _vi.To = SelectedIndex * 80;
+            _sb.Begin();
+        }
+    }
+}

+ 124 - 0
Waaagh/Themes/CustomControl/NavigationBar.xaml

@@ -0,0 +1,124 @@
+<ResourceDictionary
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+    xmlns:cnvt="clr-namespace:Waaagh.Converters"
+    xmlns:cc="clr-namespace:Waaagh.CustomControl"
+    xmlns:james="https://jamesnet.dev/xaml/presentation"
+    xmlns:helper="clr-namespace:Waaagh.Helpers">
+
+    <Storyboard x:Key="Selected">
+        <james:ThickItem Mode="CubicEaseInOut" TargetName="icon" Duration="0:0:0.5" Property="Margin" To="0 -80 0 0"/>
+        <james:ThickItem Mode="CubicEaseInOut" TargetName="name" Duration="0:0:0.5" Property="Margin" To="0 45 0 0"/>
+        <james:ColorItem Mode="CubicEaseInOut" TargetName="icon" Duration="0:0:0.5" Property="Fill.Color" To="#333333"/>
+        <james:ColorItem Mode="CubicEaseInOut" TargetName="name" Duration="0:0:0.5" Property="Foreground.Color" To="#333333"/>
+    </Storyboard>
+
+    <Storyboard x:Key="UnSelected">
+        <james:ThickItem Mode="CubicEaseInOut" TargetName="icon" Duration="0:0:0.5" Property="Margin" To="0 0 0 0"/>
+        <james:ThickItem Mode="CubicEaseInOut" TargetName="name" Duration="0:0:0.5" Property="Margin" To="0 60 0 0"/>
+        <james:ColorItem Mode="CubicEaseInOut" TargetName="icon" Duration="0:0:0.5" Property="Fill.Color" To="#44333333"/>
+        <james:ColorItem Mode="CubicEaseInOut" TargetName="name" Duration="0:0:0.5" Property="Foreground.Color" To="#00000000"/>
+    </Storyboard>
+
+    <Style TargetType="{x:Type james:JamesIcon}" x:Key="Icon">
+        <Setter Property="Icon" Value="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem},Path=Tag}"/>
+        <Setter Property="Width" Value="40"/>
+        <Setter Property="Height" Value="40"/>
+        <Setter Property="Fill" Value="#44333333"/>
+    </Style>
+
+    <Style TargetType="{x:Type TextBlock}" x:Key="Name">
+        <Setter Property="Text" Value="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem},Path=Content}"/>
+        <Setter Property="HorizontalAlignment" Value="Center"/>
+        <Setter Property="FontWeight" Value="Bold"/>
+        <Setter Property="FontSize" Value="14"/>
+        <Setter Property="Foreground" Value="#00000000"/>
+        <Setter Property="Margin" Value="0 60 0 0"/>
+    </Style>
+
+    <Style TargetType="{x:Type ListBoxItem}" x:Key="MagicBarItem">
+        <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
+        <Setter Property="Background" Value="Transparent"/>
+        <Setter Property="Template">
+            <Setter.Value>
+                <ControlTemplate TargetType="{x:Type ListBoxItem}">
+                    <Grid Background="{TemplateBinding Background}">
+                        <james:JamesIcon x:Name="icon" Style="{StaticResource Icon}"/>
+                        <TextBlock x:Name="name" Style="{StaticResource Name}"/>
+                    </Grid>
+                    <ControlTemplate.Triggers>
+                        <Trigger Property="IsSelected" Value="True">
+                            <Trigger.EnterActions>
+                                <BeginStoryboard Storyboard="{StaticResource Selected}"/>
+                            </Trigger.EnterActions>
+                            <Trigger.ExitActions>
+                                <BeginStoryboard Storyboard="{StaticResource UnSelected}"/>
+                            </Trigger.ExitActions>
+                        </Trigger>
+                    </ControlTemplate.Triggers>
+                </ControlTemplate>
+            </Setter.Value>
+        </Setter>
+    </Style>
+
+    <Geometry x:Key="ArcData">
+        M0,0 L100,0 C95.167503,0 91.135628,3.4278221 90.203163,7.9846497 L90.152122,8.2704506 89.963921,9.1416779 C85.813438,27.384438 69.496498,41 50,41 30.5035,41 14.186564,27.384438 10.036079,9.1416779 L9.8478823,8.2704926 9.7968359,7.9846497 C8.8643732,3.4278221 4.8324914,0 0,0 z
+    </Geometry>
+
+    <Style TargetType="{x:Type Path}" x:Key="Arc">
+        <Setter Property="Data" Value="{StaticResource ArcData}"/>
+        <Setter Property="Width" Value="100"/>
+        <Setter Property="Height" Value="100"/>
+        <Setter Property="Fill" Value="#222222"/>
+        <Setter Property="Margin" Value="-10 40 -10 -1"/>
+    </Style>
+
+    <Style TargetType="{x:Type Border}" x:Key="Bar">
+        <Setter Property="Background" Value="#DDDDDD"/>
+        <Setter Property="Margin" Value="0 40 0 0"/>
+        <Setter Property="CornerRadius" Value="10"/>
+    </Style>
+
+    <Style TargetType="{x:Type Grid}" x:Key="Circle">
+        <Setter Property="Width" Value="80"/>
+        <Setter Property="Height" Value="80"/>
+        <Setter Property="Canvas.Left" Value="-100"/>
+    </Style>
+
+    <Style TargetType="{x:Type cc:NavigationBar}">
+        <Setter Property="ItemContainerStyle" Value="{StaticResource MagicBarItem}"/>
+        <Setter Property="SnapsToDevicePixels" Value="True"/>
+        <Setter Property="UseLayoutRounding" Value="True"/>
+        <Setter Property="Background" Value="Transparent"/>
+        <Setter Property="Width" Value="440"/>
+        <Setter Property="Height" Value="120"/>
+        <Setter Property="Template">
+            <Setter.Value>
+                <ControlTemplate TargetType="{x:Type cc:NavigationBar}">
+                    <Grid Background="{TemplateBinding Background}">
+                        <Grid.Clip>
+                            <RectangleGeometry Rect="0 0 440 120"/>
+                        </Grid.Clip>
+                        <Border Style="{StaticResource Bar}"/>
+                        <Canvas Margin="20 0 20 0">
+                            <Grid x:Name="PART_Circle" Style="{StaticResource Circle}">
+                                <Path Style="{StaticResource Arc}"/>
+                                <Ellipse Fill="#222222"/>
+                                <Ellipse Fill="CadetBlue" Margin="6"/>
+                            </Grid>
+                        </Canvas>
+                        <ItemsPresenter Margin="20 40 20 0"/>
+                    </Grid>
+                </ControlTemplate>
+            </Setter.Value>
+        </Setter>
+        <Setter Property="ItemsPanel">
+            <Setter.Value>
+                <ItemsPanelTemplate>
+                    <UniformGrid Columns="5"/>
+                </ItemsPanelTemplate>
+            </Setter.Value>
+        </Setter>
+    </Style>
+</ResourceDictionary>

+ 46 - 48
Waaagh/Themes/CustomControl/WaaaghWindow.xaml

@@ -3,7 +3,7 @@
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
     xmlns:cnvt="clr-namespace:Waaagh.Converters"
-    xmlns:custom="clr-namespace:Waaagh.CustomControl"
+    xmlns:cc="clr-namespace:Waaagh.CustomControl"
     xmlns:helper="clr-namespace:Waaagh.Helpers">
 
     <Style x:Key="TitleButtonStyle" TargetType="Button">
@@ -18,7 +18,7 @@
     <TextBlock x:Key="TestContent" Text="Test"/>
     <TextBlock x:Key="TestContent2" Text="Testsds"/>
 
-    <Style TargetType="{x:Type custom:WaaaghWindow}">
+    <Style TargetType="{x:Type cc:WaaaghWindow}">
 
         <Setter Property="WindowChrome.WindowChrome">
             <Setter.Value>
@@ -32,62 +32,60 @@
 
         <Setter Property="Template">
             <Setter.Value>
-                <ControlTemplate TargetType="{x:Type custom:WaaaghWindow}">
-                    <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
-                        <Border x:Name="LayoutRoot" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}">
-                            <DockPanel>
-                                <Border DockPanel.Dock="Top" Height="28">
-                                    <DockPanel LastChildFill="True" Background="Beige">
+                <ControlTemplate TargetType="{x:Type cc:WaaaghWindow}">
+                    <Border x:Name="LayoutRoot" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}">
+                        <DockPanel>
+                            <Border DockPanel.Dock="Top" Height="28">
+                                <DockPanel LastChildFill="True" Background="Beige">
 
 
-                                        <!--#region TitleButton-->
-                                        <Grid DockPanel.Dock="Right">
-                                            <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" WindowChrome.IsHitTestVisibleInChrome="True"
+                                    <!--#region TitleButton-->
+                                    <Grid DockPanel.Dock="Right">
+                                        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" WindowChrome.IsHitTestVisibleInChrome="True"
                                                 Visibility="{TemplateBinding TitleBarButtons, Converter={cnvt:DefaultContentVisibilityConverter}}">
-                                                <Button Style="{StaticResource TitleButtonStyle}" Command="{x:Static SystemCommands.MinimizeWindowCommand}" Content="-"/>
-                                                <Button Style="{StaticResource TitleButtonStyle}" Command="{x:Static SystemCommands.MaximizeWindowCommand}" Content="{StaticResource TestContent}"
+                                            <Button Style="{StaticResource TitleButtonStyle}" Command="{x:Static SystemCommands.MinimizeWindowCommand}" Content="-"/>
+                                            <Button Style="{StaticResource TitleButtonStyle}" Command="{x:Static SystemCommands.MaximizeWindowCommand}" Content="{StaticResource TestContent}"
                                                     Visibility="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=WindowState, Converter={cnvt:IsCollapsedConverter}, ConverterParameter={x:Static WindowState.Maximized}}"/>
-                                                <Button Style="{StaticResource TitleButtonStyle}" Command="{x:Static SystemCommands.RestoreWindowCommand}" Content="{StaticResource TestContent2}"
+                                            <Button Style="{StaticResource TitleButtonStyle}" Command="{x:Static SystemCommands.RestoreWindowCommand}" Content="{StaticResource TestContent2}"
                                                     Visibility="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=WindowState, Converter={cnvt:IsCollapsedConverter}, ConverterParameter={x:Static WindowState.Normal}}"/>
-                                                <Button Style="{StaticResource TitleButtonStyle}" Command="{x:Static SystemCommands.CloseWindowCommand}" Content="X"/>
-                                            </StackPanel>
-                                            <ItemsControl ItemsSource="{TemplateBinding TitleBarButtons}" HorizontalAlignment="Right" VerticalAlignment="Center">
-                                                <ItemsControl.ItemsPanel>
-                                                    <ItemsPanelTemplate>
-                                                        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" WindowChrome.IsHitTestVisibleInChrome="True"/>
-                                                    </ItemsPanelTemplate>
-                                                </ItemsControl.ItemsPanel>
+                                            <Button Style="{StaticResource TitleButtonStyle}" Command="{x:Static SystemCommands.CloseWindowCommand}" Content="X"/>
+                                        </StackPanel>
+                                        <ItemsControl ItemsSource="{TemplateBinding TitleBarButtons}" HorizontalAlignment="Right" VerticalAlignment="Center">
+                                            <ItemsControl.ItemsPanel>
+                                                <ItemsPanelTemplate>
+                                                    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" WindowChrome.IsHitTestVisibleInChrome="True"/>
+                                                </ItemsPanelTemplate>
+                                            </ItemsControl.ItemsPanel>
 
-                                                <ItemsControl.ItemContainerStyle>
-                                                    <Style TargetType="Button" BasedOn="{StaticResource TitleButtonStyle}"/>
-                                                </ItemsControl.ItemContainerStyle>
-                                            </ItemsControl>
-                                        </Grid>
-                                        <!--#endregion-->
+                                            <ItemsControl.ItemContainerStyle>
+                                                <Style TargetType="Button" BasedOn="{StaticResource TitleButtonStyle}"/>
+                                            </ItemsControl.ItemContainerStyle>
+                                        </ItemsControl>
+                                    </Grid>
+                                    <!--#endregion-->
 
-                                        <!--#region TitleContent-->
-                                        <Border DockPanel.Dock="Left">
-                                            <Grid>
-                                                <StackPanel Orientation="Horizontal" Visibility="{TemplateBinding TitleBarContent, Converter={cnvt:DefaultContentVisibilityConverter}}">
-                                                    <Image Source="{TemplateBinding Icon}"/>
-                                                    <TextBlock Text="{TemplateBinding Title}" VerticalAlignment="Center"/>
-                                                </StackPanel>
-                                                <ContentPresenter ContentSource="TitleBarContent"/>
-                                            </Grid>
-                                        </Border>
-                                        <!--#endregion-->
+                                    <!--#region TitleContent-->
+                                    <Border DockPanel.Dock="Left">
+                                        <Grid>
+                                            <StackPanel Orientation="Horizontal" Visibility="{TemplateBinding TitleBarContent, Converter={cnvt:DefaultContentVisibilityConverter}}">
+                                                <Image Source="{TemplateBinding Icon}"/>
+                                                <TextBlock Text="{TemplateBinding Title}" VerticalAlignment="Center"/>
+                                            </StackPanel>
+                                            <ContentPresenter ContentSource="TitleBarContent"/>
+                                        </Grid>
+                                    </Border>
+                                    <!--#endregion-->
 
-                                    </DockPanel>
-                                </Border>
+                                </DockPanel>
+                            </Border>
 
-                                <Border Background="{TemplateBinding Background}">
-                                    <AdornerDecorator>
-                                        <ContentPresenter/>
-                                    </AdornerDecorator>
-                                </Border>
+                            <Border Background="{TemplateBinding Background}">
+                                <AdornerDecorator>
+                                    <ContentPresenter/>
+                                </AdornerDecorator>
+                            </Border>
 
-                            </DockPanel>
-                        </Border>
+                        </DockPanel>
                     </Border>
 
                     <ControlTemplate.Triggers>

+ 1 - 1
Waaagh/Themes/Generic.xaml

@@ -5,6 +5,6 @@
 
     <ResourceDictionary.MergedDictionaries>
         <ResourceDictionary Source="Waaagh;component/Themes/CustomControl/WaaaghWindow.xaml"/>
+        <ResourceDictionary Source="Waaagh;component/Themes/CustomControl/NavigationBar.xaml"/>
     </ResourceDictionary.MergedDictionaries>
-
 </ResourceDictionary>

+ 3 - 2
Waaagh/Views/MainWindow.xaml

@@ -29,8 +29,9 @@
             <RowDefinition Height="32"/>
         </Grid.RowDefinitions>
 
-        <Border Grid.Row="1">
-            <TextBlock FontSize="32" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Binding Greeting}"/>
+        <Border Grid.Row="1" Background="{StaticResource BackgroundBrush}">
+            <TextBlock FontSize="32" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Binding Greeting}"
+                Foreground="{StaticResource TestBrush}"/>
         </Border>
     </Grid>
 

+ 53 - 0
Waaagh/Waaagh.csproj

@@ -8,8 +8,20 @@
 		<UseWPF>true</UseWPF>
 	</PropertyGroup>
 
+	<ItemGroup>
+	  <ApplicationDefinition Remove="App.xaml" />
+	</ItemGroup>
+
 	<ItemGroup>
 	  <None Remove="appsettings.json" />
+	  <None Remove="Assets\Image\八坂神奈子.jpg" />
+	  <None Remove="Assets\Image\山城高岭.jpg" />
+	  <None Remove="Assets\Image\琪露诺.jpg" />
+	  <None Remove="Assets\Image\琪露诺2.jpg" />
+	  <None Remove="Assets\Image\琪露诺3.jpg" />
+	  <None Remove="Assets\Image\蕾米莉亚·斯卡蕾特.jpg" />
+	  <None Remove="Assets\Image\风见幽香.jpg" />
+	  <None Remove="Assets\Image\风见幽香2.jpg" />
 	</ItemGroup>
 
 	<ItemGroup>
@@ -20,6 +32,7 @@
 
 	<ItemGroup>
 		<PackageReference Include="CommunityToolkit.Mvvm" Version="8.3.2" />
+		<PackageReference Include="Jamesnet.Wpf" Version="1.0.0.110" />
 		<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.0" />
 		<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.0">
 		  <PrivateAssets>all</PrivateAssets>
@@ -35,8 +48,48 @@
 		<Folder Include="Views\Units\" />
 	</ItemGroup>
 
+	<ItemGroup>
+	  <Page Include="App.xaml">
+	    <Generator>MSBuild:Compile</Generator>
+	  </Page>
+	</ItemGroup>
+
 	<ItemGroup>
 		<ProjectReference Include="..\FakeLibrary\FakeLibrary.csproj" />
+		<ProjectReference Include="..\WaaaghTheme.Resource\WaaaghTheme.Resources.csproj" />
+	</ItemGroup>
+
+	<ItemGroup>
+	  <Resource Include="Assets\Image\八坂神奈子.jpg">
+	    <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+	  </Resource>
+	  <Resource Include="Assets\Image\山城高岭.jpg">
+	    <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+	  </Resource>
+	  <Resource Include="Assets\Image\琪露诺.jpg">
+	    <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+	  </Resource>
+	  <Resource Include="Assets\Image\琪露诺2.jpg">
+	    <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+	  </Resource>
+	  <Resource Include="Assets\Image\琪露诺3.jpg">
+	    <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+	  </Resource>
+	  <Resource Include="Assets\Image\蕾米莉亚·斯卡蕾特.jpg">
+	    <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+	  </Resource>
+	  <Resource Include="Assets\Image\风见幽香.jpg">
+	    <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+	  </Resource>
+	  <Resource Include="Assets\Image\风见幽香2.jpg">
+	    <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+	  </Resource>
+	</ItemGroup>
+
+	<ItemGroup>
+	  <ApplicationDefinition Update="App.xaml">
+	    <SubType>Designer</SubType>
+	  </ApplicationDefinition>
 	</ItemGroup>
 
 	<ItemGroup>

+ 5 - 0
WaaaghTheme.Resource/DarkTheme.xaml

@@ -0,0 +1,5 @@
+<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+    <SolidColorBrush x:Key="BackgroundBrush" Color="Coral"/>
+    <SolidColorBrush x:Key="ForegroundBrush" Color="LightPink"/>
+</ResourceDictionary>

+ 5 - 0
WaaaghTheme.Resource/LightTheme.xaml

@@ -0,0 +1,5 @@
+<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+    <SolidColorBrush x:Key="BackgroundBrush" Color="LightPink"/>
+    <SolidColorBrush x:Key="ForegroundBrush" Color="Coral"/>
+</ResourceDictionary>

+ 10 - 0
WaaaghTheme.Resource/WaaaghTheme.Resources.csproj

@@ -0,0 +1,10 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+	<PropertyGroup>
+		<TargetFramework>net8.0-windows</TargetFramework>
+		<Nullable>enable</Nullable>
+		<UseWPF>true</UseWPF>
+		<ImplicitUsings>enable</ImplicitUsings>
+	</PropertyGroup>
+
+</Project>

+ 5 - 1
WpfSample/App.xaml

@@ -4,6 +4,10 @@
              xmlns:local="clr-namespace:WpfSample"
              StartupUri="MainWindow.xaml">
     <Application.Resources>
-         
+        <ResourceDictionary>
+            <ResourceDictionary.MergedDictionaries>
+                <ResourceDictionary Source="/WaaaghTheme.Resources;component/DarkTheme.xaml"/>
+            </ResourceDictionary.MergedDictionaries>
+        </ResourceDictionary>
     </Application.Resources>
 </Application>

+ 1 - 1
WpfSample/App.xaml.cs

@@ -2,6 +2,6 @@
 
 namespace WpfSample {
     public partial class App: Application {
-    }
 
+    }
 }

+ 0 - 25
WpfSample/AttachedProperty.cs

@@ -1,25 +0,0 @@
-using System.Windows;
-using System.Windows.Controls;
-
-namespace WpfSample {
-    static class PropADemo {
-        public static string GetText(DependencyObject obj) {
-            return (string)obj.GetValue(TextProperty);
-        }
-
-        public static void SetText(DependencyObject obj, string value) {
-            obj.SetValue(TextProperty, value);
-        }
-
-        public static readonly DependencyProperty TextProperty =
-            DependencyProperty.RegisterAttached("Text", typeof(string), typeof(PropADemo),
-                new PropertyMetadata(string.Empty, OnTextChanged));
-
-        private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
-            if (d is Control control) {
-
-            }
-        }
-
-    }
-}

+ 108 - 47
WpfSample/MainWindow.xaml

@@ -1,54 +1,115 @@
-<Window x:Class="WpfSample.MainWindow"
+<cc:WaaaghWindow x:Class="WpfSample.MainWindow"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         xmlns:local="clr-namespace:WpfSample"
         xmlns:vm="clr-namespace:WpfSample.ViewModels"
+        xmlns:attach="clr-namespace:Waaagh.Behaviors;assembly=Waaagh"
+        xmlns:cc="clr-namespace:Waaagh.CustomControl;assembly=Waaagh"
+        xmlns:james="https://jamesnet.dev/xaml/presentation"
         mc:Ignorable="d"
-        Title="MainWindow" Height="450" Width="800"
-        BorderThickness="0" Background="Coral">
-
-    <Grid>
-
-        <Grid.RowDefinitions>
-            <RowDefinition Height="36"/>
-            <RowDefinition Height="*"/>
-        </Grid.RowDefinitions>
-
-        <StackPanel Grid.Row="0" Orientation="Horizontal">
-            <Button Command="{Binding AddItemCommand}" Content="Add"/>
-            <TextBlock x:Name="tb1" local:PropADemo.Text="Hello World"/>
-            <TextBlock x:Name="tb2" local:PropADemo.Text="Greeting"/>
-        </StackPanel>
-
-        <TreeView Grid.Row="1" ItemsSource="{Binding Groups}">
-            <TreeView.Resources>
-                <HierarchicalDataTemplate DataType="{x:Type vm:DemoGroupViewModel}" ItemsSource="{Binding Items}">
-                    <Border Background="Coral">
-                        <TextBlock Text="{Binding Message}"/>
-                    </Border>
-                </HierarchicalDataTemplate>
-
-                <HierarchicalDataTemplate DataType="{x:Type vm:DemoItemViewModel}">
-                    <Border Background="#669999">
-                        <TextBlock Text="{Binding Message}"/>
-                    </Border>
-                </HierarchicalDataTemplate>
-            </TreeView.Resources>
-
-            <TreeView.ItemContainerStyle>
-                <Style TargetType="TreeViewItem">
-                    <Setter Property="AllowDrop" Value="True"/>
-                    <EventSetter Event="MouseMove" Handler="TreeViewItem_MouseMove"/>
-                    <EventSetter Event="DragEnter" Handler="TreeViewItem_DragEnter"/>
-                    <EventSetter Event="DragOver" Handler="TreeViewItem_DragOver"/>
-                    <EventSetter Event="DragLeave" Handler="TreeViewItem_DragLeave"/>
-                    <EventSetter Event="Drop" Handler="TreeViewItem_Drop"/>
-                </Style>
-            </TreeView.ItemContainerStyle>
-
-        </TreeView>
-
-    </Grid>
-</Window>
+        Title="MainWindow" Height="450" Width="800">
+
+    <Window.DataContext>
+        <vm:MainViewModel/>
+    </Window.DataContext>
+
+    <TabControl>
+        <TabItem Header="TEst">
+            <cc:NavigationBar>
+                <ListBoxItem Content="Microsoft" Tag="{x:Static james:IconType.Microsoft}"/>
+                <ListBoxItem Content="Apple" Tag="{x:Static james:IconType.Apple}"/>
+                <ListBoxItem Content="Google" Tag="{x:Static james:IconType.Google}"/>
+                <ListBoxItem Content="Facebook" Tag="{x:Static james:IconType.Facebook}"/>
+                <ListBoxItem Content="Instagram" Tag="{x:Static james:IconType.Instagram}"/>
+            </cc:NavigationBar>
+        </TabItem>
+
+        <TabItem Header="Animation">
+            <Rectangle x:Name="TestRectangle" Width="100" Height="100" Fill="Coral">
+                <Rectangle.Triggers>
+                    <EventTrigger RoutedEvent="Rectangle.Loaded">
+                        <BeginStoryboard>
+                            <Storyboard>
+                                <DoubleAnimation Storyboard.TargetName="TestRectangle" Storyboard.TargetProperty="Opacity"
+                                    Duration="0:0:2" From="1.0" To="0.0" AutoReverse="True" RepeatBehavior="Forever"/>
+                            </Storyboard>
+                        </BeginStoryboard>
+                    </EventTrigger>
+                </Rectangle.Triggers>
+            </Rectangle>
+        </TabItem>
+
+        <TabItem Header="WaaaghTheme">
+            <Grid ShowGridLines="True">
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="*"/>
+                    <RowDefinition Height="32"/>
+                </Grid.RowDefinitions>
+
+                <Border Grid.Row="0" Background="{DynamicResource BackgroundBrush}">
+                    <TextBlock FontSize="32" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center" Text="Greeting" Foreground="{DynamicResource ForegroundBrush}"/>
+                </Border>
+
+                <StackPanel Grid.Row="1" Orientation="Horizontal">
+                    <Button Content="Dark" Click="DarkThemeButton_Click"/>
+                    <Button Content="Light" Click="LightThemeButton_Click"/>
+                </StackPanel>
+            </Grid>
+        </TabItem>
+
+        <TabItem Header="ItemsDraggable">
+            <Grid>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="36"/>
+                    <RowDefinition Height="*"/>
+                </Grid.RowDefinitions>
+
+                <StackPanel Grid.Row="0" Orientation="Horizontal">
+                    <Button Command="{Binding AddGroupCommand}" Content="AddGroup"/>
+                    <Button Command="{Binding AddItemCommand}" Content="AddItem"/>
+                </StackPanel>
+
+                <UniformGrid Grid.Row="1" Columns="4">
+                    <UniformGrid.Resources>
+                        <HierarchicalDataTemplate DataType="{x:Type vm:DemoGroupViewModel}" ItemsSource="{Binding Items}">
+                            <Border Background="Coral">
+                                <TextBlock>
+                                    <TextBlock.Text>
+                                        <MultiBinding StringFormat="{}{0}-{1}">
+                                            <Binding Path="Index"/>
+                                            <Binding Path="Name"/>
+                                        </MultiBinding>
+                                    </TextBlock.Text>
+                                </TextBlock>
+                            </Border>
+                        </HierarchicalDataTemplate>
+                        <HierarchicalDataTemplate DataType="{x:Type vm:DemoItemViewModel}">
+                            <Border Background="#669999">
+                                <TextBlock Text="{Binding Message}"/>
+                            </Border>
+                        </HierarchicalDataTemplate>
+                    </UniformGrid.Resources>
+
+                    <TreeView ItemsSource="{Binding Groups}"
+                        attach:ItemsDraggable.Enabled="True"
+                        attach:ItemsDraggable.DoMoveEffect="{Binding DoMoveEffectFunc}"/>
+
+                    <TreeView ItemsSource="{Binding Groups}"
+                        attach:ItemsDraggable.Enabled="True"
+                        attach:ItemsDraggable.DoMoveEffect="{Binding DoMoveEffectFunc}"/>
+
+                    <TreeView ItemsSource="{Binding Groups2}"
+                        attach:ItemsDraggable.Enabled="True"
+                        attach:ItemsDraggable.DoMoveEffect="{Binding DoMoveEffectFunc}"/>
+
+                    <TreeView ItemsSource="{Binding Groups2}"
+                        attach:ItemsDraggable.Enabled="True"
+                        attach:ItemsDraggable.DoMoveEffect="{Binding DoMoveEffectFunc}"/>
+                </UniformGrid>
+            </Grid>
+        </TabItem>
+
+    </TabControl>
+</cc:WaaaghWindow>

+ 19 - 22
WpfSample/MainWindow.xaml.cs

@@ -1,34 +1,31 @@
 using System.Windows;
-using System.Windows.Input;
-using WpfSample.ViewModels;
+using Waaagh.CustomControl;
 
 namespace WpfSample {
-    public partial class MainWindow: Window {
+    public partial class MainWindow: WaaaghWindow {
+        ResourceDictionary _dark;
+        ResourceDictionary _light;
+
         public MainWindow() {
+            _dark = new() {
+                Source = new Uri("/WaaaghTheme.Resources;component/DarkTheme.xaml", UriKind.Relative),
+            };
+            _light = new() {
+                Source = new Uri("/WaaaghTheme.Resources;component/LightTheme.xaml", UriKind.Relative),
+            };
             InitializeComponent();
-            DataContext = new MainViewModel();
-        }
-
-        private void TreeViewItem_MouseMove(object sender, MouseEventArgs e) {
-            if (sender is FrameworkElement element && e.LeftButton == MouseButtonState.Pressed) {
-                DragDrop.DoDragDrop(element, element.DataContext, DragDropEffects.Move);
-            }
         }
 
-        private void TreeViewItem_DragEnter(object sender, DragEventArgs e) {
-
+        private void DarkThemeButton_Click(object sender, RoutedEventArgs e) {
+            Application.Current.Resources.MergedDictionaries.Remove(_dark);
+            Application.Current.Resources.MergedDictionaries.Remove(_light);
+            Application.Current.Resources.MergedDictionaries.Add(_dark);
         }
 
-        private void TreeViewItem_DragOver(object sender, DragEventArgs e) {
-
-        }
-
-        private void TreeViewItem_DragLeave(object sender, DragEventArgs e) {
-
-        }
-
-        private void TreeViewItem_Drop(object sender, DragEventArgs e) {
-
+        private void LightThemeButton_Click(object sender, RoutedEventArgs e) {
+            Application.Current.Resources.MergedDictionaries.Remove(_dark);
+            Application.Current.Resources.MergedDictionaries.Remove(_light);
+            Application.Current.Resources.MergedDictionaries.Add(_light);
         }
     }
 }

+ 2 - 2
WpfSample/ViewModels/DemoGroupViewModel.cs

@@ -12,15 +12,15 @@ namespace WpfSample.ViewModels {
                     Value = $"V{random.Next()}",
                 });
             }
+            Index = random.Next(1, 109);
         }
 
         [ObservableProperty]
-        [NotifyPropertyChangedFor(nameof(Message))]
         private string name = string.Empty;
 
         public ObservableCollection<DemoItemViewModel> Items { get; } = new ObservableCollection<DemoItemViewModel>();
 
-        public string Message => $"{Name}-{Items.Count}";
+        public int Index { get; }
 
         [RelayCommand]
         private void AddItem() {

+ 45 - 2
WpfSample/ViewModels/MainViewModel.cs

@@ -1,6 +1,8 @@
-using System.Collections.ObjectModel;
+using System.Collections;
+using System.Collections.ObjectModel;
 using CommunityToolkit.Mvvm.ComponentModel;
 using CommunityToolkit.Mvvm.Input;
+using Waaagh.Behaviors;
 
 namespace WpfSample.ViewModels {
     internal partial class MainViewModel: ObservableObject {
@@ -11,20 +13,61 @@ namespace WpfSample.ViewModels {
                     Name = $"G{i}",
                 });
             }
+            for (int i = random.Next(1, 10); i >= 0; --i) {
+                Groups2.Add(new DemoGroupViewModel() {
+                    Name = $"G{i}",
+                });
+            }
         }
 
         [ObservableProperty]
         private string message = string.Empty;
 
-        public ObservableCollection<DemoGroupViewModel> Groups { get; set; } = new ObservableCollection<DemoGroupViewModel>();
+        public ObservableCollection<DemoGroupViewModel> Groups { get; } = new ObservableCollection<DemoGroupViewModel>();
+        public ObservableCollection<DemoGroupViewModel> Groups2 { get; } = new ObservableCollection<DemoGroupViewModel>();
 
         class Test {
             public string Name { get; set; } = string.Empty;
         }
 
+        [RelayCommand]
+        private void AddGroup() {
+            Groups.Add(new DemoGroupViewModel() {
+                Name = $"GX",
+            });
+            Groups2.Add(new DemoGroupViewModel() {
+                Name = $"GY",
+            });
+        }
+
         [RelayCommand]
         private void AddItem() {
             Groups[0].AddItemCommand?.Execute(null);
         }
+
+        public ItemsDraggable.DoEffectsCallbackHandler DoMoveEffectFunc { get; } = (object sender, ItemsDraggable.DoEffectsCallbackArgs args) => {
+            object sourceData = args.SourceData;
+            object? targetData = args.TargetData;
+            IList sourceCollection = args.SourceCollection;
+            IList targetCollection = args.TargetCollection;
+
+            int oldIndex = sourceCollection.IndexOf(sourceData);
+            int newIndex = targetCollection.IndexOf(targetData);
+            if (newIndex == -1) {
+                newIndex = targetCollection.Count - 1;
+            }
+
+            if (object.ReferenceEquals(sourceCollection, targetCollection)) {
+                if (oldIndex == newIndex) {
+                    return;
+                }
+                // 同个集合, 移除元素后下标可能变化
+                if (oldIndex < newIndex) {
+                    newIndex -= 1;
+                }
+            }
+            sourceCollection.RemoveAt(oldIndex);
+            targetCollection.Insert(newIndex + 1, sourceData);
+        };
     }
 }

+ 17 - 11
WpfSample/WpfSample.csproj

@@ -1,15 +1,21 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
-  <PropertyGroup>
-    <OutputType>WinExe</OutputType>
-    <TargetFramework>net8.0-windows</TargetFramework>
-    <Nullable>enable</Nullable>
-    <ImplicitUsings>enable</ImplicitUsings>
-    <UseWPF>true</UseWPF>
-  </PropertyGroup>
-
-  <ItemGroup>
-    <PackageReference Include="CommunityToolkit.Mvvm" Version="8.3.2" />
-  </ItemGroup>
+	<PropertyGroup>
+		<OutputType>WinExe</OutputType>
+		<TargetFramework>net8.0-windows</TargetFramework>
+		<Nullable>enable</Nullable>
+		<ImplicitUsings>enable</ImplicitUsings>
+		<UseWPF>true</UseWPF>
+	</PropertyGroup>
+
+	<ItemGroup>
+		<PackageReference Include="CommunityToolkit.Mvvm" Version="8.3.2" />
+		<PackageReference Include="Jamesnet.Wpf" Version="1.0.0.110" />
+	</ItemGroup>
+
+	<ItemGroup>
+		<ProjectReference Include="..\FakeLibrary\FakeLibrary.csproj" />
+		<ProjectReference Include="..\Waaagh\Waaagh.csproj" />
+	</ItemGroup>
 
 </Project>