diff --git a/src/Jamesnet.Core/AppBootstrapper.cs b/src/Jamesnet.Core/AppBootstrapper.cs new file mode 100644 index 0000000..e0fa4d8 --- /dev/null +++ b/src/Jamesnet.Core/AppBootstrapper.cs @@ -0,0 +1,37 @@ +namespace Jamesnet.Core; + +public abstract class AppBootstrapper +{ + protected readonly IContainer Container; + protected readonly ILayerManager Layer; + protected readonly IViewModelMapper ViewModelMapper; + + protected AppBootstrapper() + { + Container = new Container(); + Layer = new LayerManager(); + ViewModelMapper = new ViewModelMapper(); + ContainerProvider.SetContainer(Container); + ConfigureContainer(); + } + + protected virtual void ConfigureContainer() + { + Container.RegisterInstance(Container); + Container.RegisterInstance(Layer); + Container.RegisterInstance(ViewModelMapper); + Container.RegisterSingleton(); + } + + protected abstract void RegisterViewModels(); + protected abstract void RegisterDependencies(); + + public void Run() + { + RegisterViewModels(); + RegisterDependencies(); + OnStartup(); + } + + protected abstract void OnStartup(); +} diff --git a/src/Jamesnet.Core/BaseResourceLoader.cs b/src/Jamesnet.Core/BaseResourceLoader.cs new file mode 100644 index 0000000..0f446f2 --- /dev/null +++ b/src/Jamesnet.Core/BaseResourceLoader.cs @@ -0,0 +1,43 @@ +using System.Reflection; + +namespace Jamesnet.Core; + +public abstract class BaseResourceLoader +{ + protected abstract string AssemblyName { get; } + protected abstract string ResourcePath { get; } + protected abstract IEnumerable ConvertToItems(YamlData rawData); + protected abstract TResult OrganizeItems(IEnumerable items); + + public TResult LoadAndOrganize() + { + Assembly assembly = Assembly.Load(AssemblyName); + YamlData rawData = LoadYamlData(assembly, ResourcePath); + IEnumerable items = ConvertToItems(rawData); + return OrganizeItems(items); + } + + private YamlData LoadYamlData(Assembly assembly, string resourcePath) + { + YamlData yamlData = new YamlData(); + + object result = YamlConverter.ParseResource(assembly, resourcePath); + IEnumerable data = result as IEnumerable; + + if (data == null) + { + throw new InvalidOperationException("YamlConverter.ParseResource did not return an IEnumerable"); + } + + foreach (object item in data) + { + IDictionary dict = item as IDictionary; + if (dict != null) + { + yamlData.Add(new YamlItem(dict)); + } + } + + return yamlData; + } +} diff --git a/src/Jamesnet.Core/Container.cs b/src/Jamesnet.Core/Container.cs new file mode 100644 index 0000000..fe44dd2 --- /dev/null +++ b/src/Jamesnet.Core/Container.cs @@ -0,0 +1,100 @@ +namespace Jamesnet.Core; + +public class Container : IContainer +{ + private readonly Dictionary<(Type, string), Func> _registrations = new Dictionary<(Type, string), Func>(); + + public void Register() where TImplementation : TInterface + { + Register(null); + } + + public void Register(string name) where TImplementation : TInterface + { + _registrations[(typeof(TInterface), name)] = () => CreateInstance(typeof(TImplementation)); + } + + public void RegisterSingleton() where TImplementation : TInterface + { + RegisterSingleton(null); + } + + public void RegisterSingleton(string name) where TImplementation : TInterface + { + var lazy = new Lazy(() => CreateInstance(typeof(TImplementation))); + _registrations[(typeof(TInterface), name)] = () => lazy.Value; + } + + public void RegisterSingleton(string name) + { + var lazy = new Lazy(() => CreateInstance(typeof(TImplementation))); + _registrations[(typeof(TImplementation), name)] = () => lazy.Value; + } + + public void RegisterInstance(TInterface instance) + { + RegisterInstance(instance, null); + } + + public void RegisterInstance(TInterface instance, string name) + { + _registrations[(typeof(TInterface), name)] = () => instance; + } + + public T Resolve() + { + return Resolve(null); + } + + public T Resolve(string name) + { + return (T)Resolve(typeof(T), name); + } + + public object Resolve(Type type) + { + return Resolve(type, null); + } + + public object Resolve(Type type, string name) + { + if (_registrations.TryGetValue((type, name), out var creator)) + { + return creator(); + } + if (!type.IsAbstract && !type.IsInterface) + { + return CreateInstance(type); + } + throw new InvalidOperationException($"Type {type} has not been registered."); + } + + public bool TryResolve(out T result) + { + return TryResolve(null, out result); + } + + public bool TryResolve(string name, out T result) + { + if (_registrations.TryGetValue((typeof(T), name), out var creator)) + { + result = (T)creator(); + return true; + } + if (!typeof(T).IsAbstract && !typeof(T).IsInterface) + { + result = (T)CreateInstance(typeof(T)); + return true; + } + result = default; + return false; + } + + private object CreateInstance(Type type) + { + var constructors = type.GetConstructors(); + var constructor = constructors.FirstOrDefault(c => c.GetParameters().Length > 0) ?? constructors.First(); + var parameters = constructor.GetParameters().Select(p => Resolve(p.ParameterType)).ToArray(); + return constructor.Invoke(parameters); + } +} diff --git a/src/Jamesnet.Core/ContainerProvider.cs b/src/Jamesnet.Core/ContainerProvider.cs new file mode 100644 index 0000000..64e3399 --- /dev/null +++ b/src/Jamesnet.Core/ContainerProvider.cs @@ -0,0 +1,20 @@ +namespace Jamesnet.Core; + +public static class ContainerProvider +{ + private static IContainer _container; + + public static void SetContainer(IContainer container) + { + _container = container; + } + + public static IContainer GetContainer() + { + if (_container == null) + { + throw new InvalidOperationException("IContainer has not been set. Make sure to call ContainerProvider.SetContainer in your App class."); + } + return _container; + } +} diff --git a/src/Jamesnet.Core/DefaultViewModelInitializer.cs b/src/Jamesnet.Core/DefaultViewModelInitializer.cs new file mode 100644 index 0000000..2bf8886 --- /dev/null +++ b/src/Jamesnet.Core/DefaultViewModelInitializer.cs @@ -0,0 +1,46 @@ +namespace Jamesnet.Core; + +public class DefaultViewModelInitializer : IViewModelInitializer +{ + private readonly IContainer _container; + private readonly IViewModelMapper _viewModelMapper; + + public DefaultViewModelInitializer(IContainer container, IViewModelMapper viewModelMapper) + { + _container = container; + _viewModelMapper = viewModelMapper; + } + + public void InitializeViewModel(IView view) + { + var viewType = view.GetType(); + var viewModelType = _viewModelMapper.GetViewModelType(viewType); + + if (viewModelType != null) + { + var viewModel = CreateViewModel(viewModelType); + view.DataContext = viewModel; + } + } + + private object CreateViewModel(Type viewModelType) + { + try + { + var constructor = viewModelType.GetConstructors() + .OrderByDescending(c => c.GetParameters().Length) + .First(); + + var parameters = constructor.GetParameters() + .Select(p => _container.Resolve(p.ParameterType)) + .ToArray(); + + return constructor.Invoke(parameters); + } + catch (Exception ex) + { + throw new InvalidOperationException($"Failed to create ViewModel of type {viewModelType.Name}.", ex); + } + } + +} diff --git a/src/Jamesnet.Core/IContainer.cs b/src/Jamesnet.Core/IContainer.cs new file mode 100644 index 0000000..c027cc9 --- /dev/null +++ b/src/Jamesnet.Core/IContainer.cs @@ -0,0 +1,18 @@ +namespace Jamesnet.Core; + +public interface IContainer +{ + void Register() where TImplementation : TInterface; + void Register(string name) where TImplementation : TInterface; + void RegisterSingleton() where TImplementation : TInterface; + void RegisterSingleton(string name) where TImplementation : TInterface; + void RegisterSingleton(string name); + void RegisterInstance(TInterface instance); + void RegisterInstance(TInterface instance, string name); + T Resolve(); + T Resolve(string name); + object Resolve(Type type); + object Resolve(Type type, string name); + bool TryResolve(out T result); + bool TryResolve(string name, out T result); +} diff --git a/src/Jamesnet.Core/ILayer.cs b/src/Jamesnet.Core/ILayer.cs new file mode 100644 index 0000000..e576ca9 --- /dev/null +++ b/src/Jamesnet.Core/ILayer.cs @@ -0,0 +1,6 @@ +namespace Jamesnet.Core; + +public interface ILayer +{ + object Content { get; set; } +} diff --git a/src/Jamesnet.Core/ILayerManager.cs b/src/Jamesnet.Core/ILayerManager.cs new file mode 100644 index 0000000..341d7a4 --- /dev/null +++ b/src/Jamesnet.Core/ILayerManager.cs @@ -0,0 +1,10 @@ +namespace Jamesnet.Core; + +public interface ILayerManager +{ + void Register(string layerName, ILayer layer); + void Add(string layerName, IView view); + void Show(string layerName, IView view); + void Hide(string layerName); + void Mapping(string layerName, IView view); +} diff --git a/src/Jamesnet.Core/IView.cs b/src/Jamesnet.Core/IView.cs new file mode 100644 index 0000000..3bb5b29 --- /dev/null +++ b/src/Jamesnet.Core/IView.cs @@ -0,0 +1,6 @@ +namespace Jamesnet.Core; + +public interface IView +{ + object DataContext { get; set; } +} diff --git a/src/Jamesnet.Core/IViewLoadable.cs b/src/Jamesnet.Core/IViewLoadable.cs new file mode 100644 index 0000000..b3d6635 --- /dev/null +++ b/src/Jamesnet.Core/IViewLoadable.cs @@ -0,0 +1,6 @@ +namespace Jamesnet.Core; + +public interface IViewLoadable +{ + void Loaded(); +} diff --git a/src/Jamesnet.Core/IViewModelInitializer.cs b/src/Jamesnet.Core/IViewModelInitializer.cs new file mode 100644 index 0000000..04280a4 --- /dev/null +++ b/src/Jamesnet.Core/IViewModelInitializer.cs @@ -0,0 +1,6 @@ +namespace Jamesnet.Core; + +public interface IViewModelInitializer +{ + void InitializeViewModel(IView view); +} diff --git a/src/Jamesnet.Core/IViewModelMapper.cs b/src/Jamesnet.Core/IViewModelMapper.cs new file mode 100644 index 0000000..221fb51 --- /dev/null +++ b/src/Jamesnet.Core/IViewModelMapper.cs @@ -0,0 +1,7 @@ +namespace Jamesnet.Core; + +public interface IViewModelMapper +{ + void Register() where TView : IView where TViewModel : class; + Type GetViewModelType(Type viewType); +} diff --git a/src/Jamesnet.Core/InjectAttribute.cs b/src/Jamesnet.Core/InjectAttribute.cs new file mode 100644 index 0000000..6870137 --- /dev/null +++ b/src/Jamesnet.Core/InjectAttribute.cs @@ -0,0 +1,4 @@ +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)] +public class InjectAttribute : Attribute +{ +} diff --git a/src/Jamesnet.Core/Jamesnet.Core.csproj b/src/Jamesnet.Core/Jamesnet.Core.csproj new file mode 100644 index 0000000..fa71b7a --- /dev/null +++ b/src/Jamesnet.Core/Jamesnet.Core.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/src/Jamesnet.Core/LayerManager.cs b/src/Jamesnet.Core/LayerManager.cs new file mode 100644 index 0000000..404865e --- /dev/null +++ b/src/Jamesnet.Core/LayerManager.cs @@ -0,0 +1,75 @@ +namespace Jamesnet.Core; + +public class LayerManager : ILayerManager +{ + private readonly Dictionary _layers = new Dictionary(); + private readonly Dictionary> _layerViews = new Dictionary>(); + private readonly Dictionary _layerViewMappings = new Dictionary(); + + public void Register(string layerName, ILayer layer) + { + Console.WriteLine($"LayerManager.Register called with layerName: {layerName}"); + + if (!_layers.ContainsKey(layerName)) + { + _layers[layerName] = layer; + _layerViews[layerName] = new List(); + if (_layerViewMappings.TryGetValue(layerName, out var view)) + { + Show(layerName, view); + } + } + } + + public void Show(string layerName, IView view) + { + if (!_layers.TryGetValue(layerName, out var layer)) + { + throw new InvalidOperationException($"Layer not registered: {layerName}"); + } + + if (view == null) + { + layer.Content = null; + return; + } + + if (!_layerViews[layerName].Contains(view)) + { + Add(layerName, view); + } + + layer.Content = view; + } + + public void Add(string layerName, IView view) + { + if (!_layers.ContainsKey(layerName)) + { + throw new InvalidOperationException($"Layer not registered: {layerName}"); + } + + if (!_layerViews.ContainsKey(layerName)) + { + _layerViews[layerName] = new List(); + } + + if (!_layerViews[layerName].Contains(view)) + { + _layerViews[layerName].Add(view); + } + } + + public void Hide(string layerName) + { + if (_layers.TryGetValue(layerName, out var layer)) + { + layer.Content = null; + } + } + + public void Mapping(string layerName, IView view) + { + _layerViewMappings[layerName] = view; + } +} diff --git a/src/Jamesnet.Core/RelayCommand.cs b/src/Jamesnet.Core/RelayCommand.cs new file mode 100644 index 0000000..1c458cf --- /dev/null +++ b/src/Jamesnet.Core/RelayCommand.cs @@ -0,0 +1,61 @@ +using System.Windows.Input; + +namespace Jamesnet.Core; + +public class RelayCommand : ICommand +{ + private readonly Action _execute; + private readonly Func _canExecute; + + public RelayCommand(Action execute, Func canExecute = null) + { + _execute = execute ?? throw new ArgumentNullException(nameof(execute)); + _canExecute = canExecute; + } + + public event EventHandler CanExecuteChanged; + + public bool CanExecute(object parameter) + { + return _canExecute == null || _canExecute((T)parameter); + } + + public void Execute(object parameter) + { + _execute((T)parameter); + } + + public void RaiseCanExecuteChanged() + { + CanExecuteChanged?.Invoke(this, EventArgs.Empty); + } +} + +public class RelayCommand : ICommand +{ + private readonly Action _execute; + private readonly Func _canExecute; + + public RelayCommand(Action execute, Func canExecute = null) + { + _execute = execute ?? throw new ArgumentNullException(nameof(execute)); + _canExecute = canExecute; + } + + public event EventHandler CanExecuteChanged; + + public bool CanExecute(object parameter) + { + return _canExecute == null || _canExecute(); + } + + public void Execute(object parameter) + { + _execute(); + } + + public void RaiseCanExecuteChanged() + { + CanExecuteChanged?.Invoke(this, EventArgs.Empty); + } +} diff --git a/src/Jamesnet.Core/StringExtensions.cs b/src/Jamesnet.Core/StringExtensions.cs new file mode 100644 index 0000000..173d01e --- /dev/null +++ b/src/Jamesnet.Core/StringExtensions.cs @@ -0,0 +1,15 @@ +using System.Globalization; + +namespace Jamesnet.Core; + +public static class StringExtensions +{ + public static string ToPascal(this string input) + { + if (string.IsNullOrEmpty(input)) + return input; + + TextInfo textInfo = new CultureInfo("en-US", false).TextInfo; + return textInfo.ToTitleCase(input.ToLower()).Replace(" ", ""); + } +} diff --git a/src/Jamesnet.Core/ViewModelBase.cs b/src/Jamesnet.Core/ViewModelBase.cs new file mode 100644 index 0000000..cc0091e --- /dev/null +++ b/src/Jamesnet.Core/ViewModelBase.cs @@ -0,0 +1,25 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace Jamesnet.Core; + +public class ViewModelBase : INotifyPropertyChanged +{ + public event PropertyChangedEventHandler? PropertyChanged; + + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + protected bool SetProperty(ref T storage, T value, Action? callback = null, [CallerMemberName] string propertyName = null) + { + if (EqualityComparer.Default.Equals(storage, value)) + return false; + + storage = value; + OnPropertyChanged(propertyName); + callback?.Invoke(); + return true; + } +} diff --git a/src/Jamesnet.Core/ViewModelMapper.cs b/src/Jamesnet.Core/ViewModelMapper.cs new file mode 100644 index 0000000..a9cae44 --- /dev/null +++ b/src/Jamesnet.Core/ViewModelMapper.cs @@ -0,0 +1,16 @@ +namespace Jamesnet.Core; + +public class ViewModelMapper : IViewModelMapper +{ + private readonly Dictionary _mappings = new Dictionary(); + + public void Register() where TView : IView where TViewModel : class + { + _mappings[typeof(TView)] = typeof(TViewModel); + } + + public Type GetViewModelType(Type viewType) + { + return _mappings.TryGetValue(viewType, out var viewModelType) ? viewModelType : null; + } +} diff --git a/src/Jamesnet.Core/YamlConverter.cs b/src/Jamesnet.Core/YamlConverter.cs new file mode 100644 index 0000000..3f3e33b --- /dev/null +++ b/src/Jamesnet.Core/YamlConverter.cs @@ -0,0 +1,60 @@ +using System.Reflection; + +namespace Jamesnet.Core +{ + public static class YamlConverter + { + public static IEnumerable> Parse(string content) + { + return ParseYamlContent(content); + } + + public static IEnumerable> ParseFile(string filePath) + { + string content = File.ReadAllText(filePath); + return Parse(content); + } + + public static IEnumerable> ParseResource(Assembly assembly, string resourcePath) + { + using (Stream stream = assembly.GetManifestResourceStream(resourcePath)) + using (StreamReader reader = new StreamReader(stream)) + { + string content = reader.ReadToEnd(); + return Parse(content); + } + } + + private static IEnumerable> ParseYamlContent(string content) + { + var lines = content.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None) + .Where(line => !string.IsNullOrWhiteSpace(line) && !line.TrimStart().StartsWith("#")); + + var currentItem = new Dictionary(); + foreach (var line in lines) + { + if (line.TrimStart().StartsWith("-")) + { + if (currentItem.Count > 0) + { + yield return currentItem; + currentItem = new Dictionary(); + } + } + + if (line.Contains(':')) + { + var parts = line.Split(new[] { ':' }, 2); + var key = parts[0].Trim().TrimStart('-', ' '); + var value = parts[1].Trim(); + currentItem[key] = value; + } + } + + if (currentItem.Count > 0) + { + yield return currentItem; + } + } + } +} diff --git a/src/Jamesnet.Core/YamlData.cs b/src/Jamesnet.Core/YamlData.cs new file mode 100644 index 0000000..7bc0b31 --- /dev/null +++ b/src/Jamesnet.Core/YamlData.cs @@ -0,0 +1,7 @@ +namespace Jamesnet.Core; + +public class YamlData : List +{ + public YamlData() : base() { } + public YamlData(IEnumerable collection) : base(collection) { } +} diff --git a/src/Jamesnet.Core/YamlExtensions.cs b/src/Jamesnet.Core/YamlExtensions.cs new file mode 100644 index 0000000..85412a8 --- /dev/null +++ b/src/Jamesnet.Core/YamlExtensions.cs @@ -0,0 +1,20 @@ +namespace Jamesnet.Core; + +public static class YamlExtensions +{ + public static T GetValue(this IReadOnlyDictionary dict, string key, T defaultValue = default) + { + if (dict.TryGetValue(key, out string value)) + { + try + { + return (T)Convert.ChangeType(value, typeof(T)); + } + catch + { + return defaultValue; + } + } + return defaultValue; + } +} diff --git a/src/Jamesnet.Core/YamlItem.cs b/src/Jamesnet.Core/YamlItem.cs new file mode 100644 index 0000000..f78e80e --- /dev/null +++ b/src/Jamesnet.Core/YamlItem.cs @@ -0,0 +1,18 @@ +namespace Jamesnet.Core; + +public class YamlItem : Dictionary +{ + public YamlItem() : base() { } + + public YamlItem(IDictionary dictionary) : base(dictionary) { } + + public T GetValue(string key) + { + string value; + if (TryGetValue(key, out value)) + { + return (T)Convert.ChangeType(value, typeof(T)); + } + return default(T); + } +} diff --git a/src/Jamesnet.WinUI3/CustomWrapPanel.cs b/src/Jamesnet.WinUI3/CustomWrapPanel.cs new file mode 100644 index 0000000..71fca1c --- /dev/null +++ b/src/Jamesnet.WinUI3/CustomWrapPanel.cs @@ -0,0 +1,102 @@ +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using System; +using Windows.Foundation; + +namespace Jamesnet.WinUI3 +{ + + public class CustomWrapPanel : Panel + { + public static readonly DependencyProperty HorizontalSpacingProperty = + DependencyProperty.Register(nameof(HorizontalSpacing), typeof(double), typeof(CustomWrapPanel), + new PropertyMetadata(0.0, OnSpacingChanged)); + + public static readonly DependencyProperty VerticalSpacingProperty = + DependencyProperty.Register(nameof(VerticalSpacing), typeof(double), typeof(CustomWrapPanel), + new PropertyMetadata(0.0, OnSpacingChanged)); + + public double HorizontalSpacing + { + get => (double)GetValue(HorizontalSpacingProperty); + set => SetValue(HorizontalSpacingProperty, value); + } + + public double VerticalSpacing + { + get => (double)GetValue(VerticalSpacingProperty); + set => SetValue(VerticalSpacingProperty, value); + } + + private static void OnSpacingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is CustomWrapPanel panel) + { + panel.InvalidateMeasure(); + } + } + + protected override Size MeasureOverride(Size availableSize) + { + double curLineWidth = 0; + double curLineHeight = 0; + double panelWidth = 0; + double panelHeight = 0; + bool isFirstInLine = true; + + foreach (UIElement child in Children) + { + child.Measure(availableSize); + Size childSize = child.DesiredSize; + + if (curLineWidth + childSize.Width > availableSize.Width && !isFirstInLine) + { + panelWidth = Math.Max(panelWidth, curLineWidth - HorizontalSpacing); + panelHeight += curLineHeight + VerticalSpacing; + curLineWidth = childSize.Width; + curLineHeight = childSize.Height; + isFirstInLine = true; + } + else + { + curLineWidth += childSize.Width + (isFirstInLine ? 0 : HorizontalSpacing); + curLineHeight = Math.Max(curLineHeight, childSize.Height); + isFirstInLine = false; + } + } + + panelWidth = Math.Max(panelWidth, curLineWidth); + panelHeight += curLineHeight; + + return new Size(panelWidth, panelHeight); + } + + protected override Size ArrangeOverride(Size finalSize) + { + double curX = 0; + double curY = 0; + double curLineHeight = 0; + bool isFirstInLine = true; + + foreach (UIElement child in Children) + { + Size childSize = child.DesiredSize; + + if (curX + childSize.Width > finalSize.Width && !isFirstInLine) + { + curY += curLineHeight + VerticalSpacing; + curX = 0; + curLineHeight = 0; + isFirstInLine = true; + } + + child.Arrange(new Rect(curX, curY, childSize.Width, childSize.Height)); + curX += childSize.Width + (isFirstInLine ? 0 : HorizontalSpacing); + curLineHeight = Math.Max(curLineHeight, childSize.Height); + isFirstInLine = false; + } + + return finalSize; + } + } +} \ No newline at end of file diff --git a/src/Jamesnet.WinUI3/Jamesnet.WinUI3.csproj b/src/Jamesnet.WinUI3/Jamesnet.WinUI3.csproj new file mode 100644 index 0000000..df7887f --- /dev/null +++ b/src/Jamesnet.WinUI3/Jamesnet.WinUI3.csproj @@ -0,0 +1,20 @@ + + + net8.0-windows10.0.19041.0 + 10.0.17763.0 + Jamesnet.WinUI3 + win-x86;win-x64;win-arm64 + win10-x86;win10-x64;win10-arm64 + true + 10.0.19041.38 + + + + + + + + + + + \ No newline at end of file diff --git a/src/Jamesnet.WinUI3/RecursiveControl.cs b/src/Jamesnet.WinUI3/RecursiveControl.cs new file mode 100644 index 0000000..03c51f9 --- /dev/null +++ b/src/Jamesnet.WinUI3/RecursiveControl.cs @@ -0,0 +1,107 @@ +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using System.Collections; + +namespace Jamesnet.WinUI3; + +public class RecursiveControl : ListBoxItem +{ + public RecursiveControl() + { + DefaultStyleKey = typeof(RecursiveControl); + } + + public object ItemsSource + { + get => GetValue(ItemsSourceProperty); + set => SetValue(ItemsSourceProperty, value); + } + + public static readonly DependencyProperty ItemsSourceProperty = + DependencyProperty.Register(nameof(ItemsSource), typeof(object), typeof(RecursiveControl), new PropertyMetadata(null, OnItemsSourceChanged)); + + public string ItemsBindingPath + { + get => (string)GetValue(ItemsBindingPathProperty); + set => SetValue(ItemsBindingPathProperty, value); + } + + public static readonly DependencyProperty ItemsBindingPathProperty = + DependencyProperty.Register(nameof(ItemsBindingPath), typeof(string), typeof(RecursiveControl), new PropertyMetadata(null)); + + private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((RecursiveControl)d).GenerateItems(); + } + + public bool IsExpanded + { + get => (bool)GetValue(IsExpandedProperty); + set => SetValue(IsExpandedProperty, value); + } + + public static readonly DependencyProperty IsExpandedProperty = DependencyProperty.Register(nameof(IsExpanded), typeof(bool), typeof(RecursiveControl), new PropertyMetadata(true, OnIsExpandedChanged)); + + private static void OnIsExpandedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((RecursiveControl)d).UpdateChildrenVisibility(); + } + + private Panel _itemsPanel; + + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + _itemsPanel = GetTemplateChild("PART_ItemsPanel") as Panel; + UpdateChildrenVisibility(); + SetItemsSourceFromDataContext(); + } + + private void SetItemsSourceFromDataContext() + { + if (DataContext == null || string.IsNullOrEmpty(ItemsBindingPath)) return; + + var dataContextType = DataContext.GetType(); + var property = dataContextType.GetProperty(ItemsBindingPath); + + if (property != null && typeof(IEnumerable).IsAssignableFrom(property.PropertyType)) + { + var value = property.GetValue(DataContext); + if (value is IEnumerable enumerable) + { + ItemsSource = enumerable; + } + } + } + + private void GenerateItems() + { + if (_itemsPanel == null || ItemsSource == null) return; + + _itemsPanel.Children.Clear(); + + foreach (var item in ItemsSource as IEnumerable) + { + var container = GetContainerForItemOverride(); + + if (container is FrameworkElement fe) + { + fe.DataContext = item; + _itemsPanel.Children.Add(fe); + } + } + } + + protected virtual DependencyObject GetContainerForItemOverride() + { + return new RecursiveControl(); + } + + private void UpdateChildrenVisibility() + { + if (_itemsPanel != null) + { + _itemsPanel.Visibility = IsExpanded ? Visibility.Visible : Visibility.Collapsed; + } + } +} diff --git a/src/Jamesnet.WinUI3/WinUI3Layer.cs b/src/Jamesnet.WinUI3/WinUI3Layer.cs new file mode 100644 index 0000000..eafe465 --- /dev/null +++ b/src/Jamesnet.WinUI3/WinUI3Layer.cs @@ -0,0 +1,67 @@ +using Jamesnet.Core; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using System; + +namespace Jamesnet.WinUI3 +{ + public class WinUI3Layer : ContentControl, ILayer + { + public static readonly DependencyProperty LayerNameProperty = + DependencyProperty.Register(nameof(LayerName), typeof(string), typeof(WinUI3Layer), new PropertyMetadata(null, OnLayerNameChanged)); + private bool _isRegistered = false; + public string LayerName + { + get => (string)GetValue(LayerNameProperty); + set => SetValue(LayerNameProperty, value); + } + + + public WinUI3Layer() + { + DefaultStyleKey = typeof(WinUI3Layer); + Loaded += WinUI3Layer_Loaded; + } + private void WinUI3Layer_Loaded(object sender, RoutedEventArgs e) + { + RegisterToLayerManager(); + } + private void RegisterToLayerManager() + { + System.Diagnostics.Debug.WriteLine($"RegisterToLayerManager called for {LayerName}"); + if (string.IsNullOrEmpty(LayerName) || _isRegistered) + { + System.Diagnostics.Debug.WriteLine($"Early return for {LayerName}. IsRegistered: {_isRegistered}"); + return; + } + try + { + var container = ContainerProvider.GetContainer(); + System.Diagnostics.Debug.WriteLine($"Container retrieved for {LayerName}"); + var layerManager = container.Resolve(); + if (layerManager != null) + { + layerManager.Register(LayerName, this); + _isRegistered = true; + System.Diagnostics.Debug.WriteLine($"Layer {LayerName} registered successfully"); + } + else + { + System.Diagnostics.Debug.WriteLine($"LayerManager is null for {LayerName}"); + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Error in RegisterToLayerManager for {LayerName}: {ex}"); + } + } + private static void OnLayerNameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is WinUI3Layer layer) + { + layer._isRegistered = false; // Reset registration status when LayerName changes + layer.RegisterToLayerManager(); + } + } + } +} \ No newline at end of file diff --git a/src/Jamesnet.WinUI3/WinUI3View.cs b/src/Jamesnet.WinUI3/WinUI3View.cs new file mode 100644 index 0000000..e552afc --- /dev/null +++ b/src/Jamesnet.WinUI3/WinUI3View.cs @@ -0,0 +1,40 @@ +using Jamesnet.Core; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; + +namespace Jamesnet.WinUI3; + +public class WinUI3View : ContentControl, IView +{ + private bool _viewModelInitialized = false; + + public WinUI3View() + { + DefaultStyleKey = typeof(WinUI3View); + InitializeViewModel(); + Loaded += OnLoaded; + } + + private void OnLoaded(object sender, RoutedEventArgs e) + { + if (_viewModelInitialized && DataContext is IViewLoadable loadable) + { + loadable.Loaded(); + } + Loaded -= OnLoaded; + } + + public void InitializeViewModel() + { + var initializer = ContainerProvider.GetContainer().Resolve(); + initializer.InitializeViewModel(this); + + _viewModelInitialized = DataContext != null; + + OnViewModelInitialized(); + } + + protected virtual void OnViewModelInitialized() + { + } +} diff --git a/src/Leagueoflegends.Clash/Leagueoflegends.Clash.csproj b/src/Leagueoflegends.Clash/Leagueoflegends.Clash.csproj new file mode 100644 index 0000000..5b9cec3 --- /dev/null +++ b/src/Leagueoflegends.Clash/Leagueoflegends.Clash.csproj @@ -0,0 +1,20 @@ + + + net8.0-windows10.0.19041.0 + 10.0.17763.0 + Leagueoflegends.Clash + win-x86;win-x64;win-arm64 + win10-x86;win10-x64;win10-arm64 + true + 10.0.19041.38 + + + + + + + + + + + \ No newline at end of file diff --git a/src/Leagueoflegends.Clash/Local/Converters/ShowMenuConverter.cs b/src/Leagueoflegends.Clash/Local/Converters/ShowMenuConverter.cs new file mode 100644 index 0000000..2900804 --- /dev/null +++ b/src/Leagueoflegends.Clash/Local/Converters/ShowMenuConverter.cs @@ -0,0 +1,18 @@ +using Leagueoflegends.Support.Local.Models; +using Microsoft.UI.Xaml.Data; +using System; + +namespace Leagueoflegends.Clash.Local.Converters; + +public class ShowMenuConverter : IValueConverter +{ + public object Convert(object value, Type targetType, object parameter, string language) + { + return value is MenuModel menu && menu.Name.Equals(parameter); + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + throw new NotImplementedException(); + } +} diff --git a/src/Leagueoflegends.Clash/Local/Datas/ScheduleDataLoader.cs b/src/Leagueoflegends.Clash/Local/Datas/ScheduleDataLoader.cs new file mode 100644 index 0000000..3411e99 --- /dev/null +++ b/src/Leagueoflegends.Clash/Local/Datas/ScheduleDataLoader.cs @@ -0,0 +1,31 @@ +using Jamesnet.Core; +using Leagueoflegends.Support.Local.Datas; +using Leagueoflegends.Support.Local.Models; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Leagueoflegends.Clash.Local.Datas; + +public class ScheduleDataLoader : BaseResourceLoader>, IScheduleDataLoader +{ + protected override string AssemblyName => "Leagueoflegends.Support"; + protected override string ResourcePath => "Leagueoflegends.Support.Datas.Schedules.yml"; + + public List LoadSchedules() => LoadAndOrganize(); + + protected override IEnumerable ConvertToItems(YamlData rawData) + { + return rawData.Select(item => new Schedule + { + Title = item.GetValue("title"), + Day = item.GetValue("day"), + Timestamp = item.GetValue("timestamp") + }); + } + + protected override List OrganizeItems(IEnumerable schedules) + { + return schedules.ToList(); + } +} diff --git a/src/Leagueoflegends.Clash/Local/ViewModels/HubContentViewModel.cs b/src/Leagueoflegends.Clash/Local/ViewModels/HubContentViewModel.cs new file mode 100644 index 0000000..b6d613f --- /dev/null +++ b/src/Leagueoflegends.Clash/Local/ViewModels/HubContentViewModel.cs @@ -0,0 +1,56 @@ +using Jamesnet.Core; +using Leagueoflegends.Support.Local.Datas; +using Leagueoflegends.Support.Local.Models; +using Leagueoflegends.Support.Local.Services; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Leagueoflegends.Clash.Local.ViewModels; + +public class HubContentViewModel : ViewModelBase +{ + private readonly IMenuManager _menu; + private readonly IScheduleDataLoader _scheduleData; + + private MenuModel _currentTabMenu; + private List _tabMenus; + private List _schedules; + + public MenuModel CurrentTabMenu + { + get => _currentTabMenu; + set => SetProperty(ref _currentTabMenu, value, CurrentTabMenuChanged); + } + + private void CurrentTabMenuChanged() + { + Console.WriteLine($"RiotTabMenu Changed: {CurrentTabMenu?.Name}"); + } + + public List TabMenus + { + get => _tabMenus; + set => SetProperty(ref _tabMenus, value); + } + + public List Schedules + { + get => _schedules; + set => SetProperty(ref _schedules, value); + } + + public HubContentViewModel(IMenuManager subMenu, IScheduleDataLoader scheduleData) + { + _menu = subMenu; + _scheduleData = scheduleData; + LoadSchedules(); + } + + private void LoadSchedules() + { + TabMenus = _menu.GetMenuByCategory("HUB"); + CurrentTabMenu = TabMenus.FirstOrDefault(); + Schedules = _scheduleData.LoadSchedules(); + } +} diff --git a/src/Leagueoflegends.Clash/Themes/Generic.xaml b/src/Leagueoflegends.Clash/Themes/Generic.xaml new file mode 100644 index 0000000..77b9698 --- /dev/null +++ b/src/Leagueoflegends.Clash/Themes/Generic.xaml @@ -0,0 +1,7 @@ + + + + + diff --git a/src/Leagueoflegends.Clash/Themes/Views/HubContent.xaml b/src/Leagueoflegends.Clash/Themes/Views/HubContent.xaml new file mode 100644 index 0000000..bee58e9 --- /dev/null +++ b/src/Leagueoflegends.Clash/Themes/Views/HubContent.xaml @@ -0,0 +1,26 @@ + + + + + + diff --git a/src/Leagueoflegends.Clash/UI/Views/HubContent.cs b/src/Leagueoflegends.Clash/UI/Views/HubContent.cs new file mode 100644 index 0000000..0e9452d --- /dev/null +++ b/src/Leagueoflegends.Clash/UI/Views/HubContent.cs @@ -0,0 +1,11 @@ +using Jamesnet.WinUI3; + +namespace Leagueoflegends.Clash.UI.Views; + +public class HubContent : WinUI3View +{ + public HubContent() + { + DefaultStyleKey = typeof(HubContent); + } +} diff --git a/src/Leagueoflegends.Collection/Leagueoflegends.Collection.csproj b/src/Leagueoflegends.Collection/Leagueoflegends.Collection.csproj new file mode 100644 index 0000000..72112cf --- /dev/null +++ b/src/Leagueoflegends.Collection/Leagueoflegends.Collection.csproj @@ -0,0 +1,20 @@ + + + net8.0-windows10.0.19041.0 + 10.0.17763.0 + Leagueoflegends.Collection + win-x86;win-x64;win-arm64 + win10-x86;win10-x64;win10-arm64 + true + 10.0.19041.38 + + + + + + + + + + + \ No newline at end of file diff --git a/src/Leagueoflegends.Collection/Local/Datas/ChampStatsDataLoader.cs b/src/Leagueoflegends.Collection/Local/Datas/ChampStatsDataLoader.cs new file mode 100644 index 0000000..e36e3c3 --- /dev/null +++ b/src/Leagueoflegends.Collection/Local/Datas/ChampStatsDataLoader.cs @@ -0,0 +1,44 @@ +using Jamesnet.Core; +using Leagueoflegends.Support.Local.Datas; +using Leagueoflegends.Support.Local.Models; +using System.Collections.Generic; +using System.Linq; + +namespace Leagueoflegends.Collection.Local.Datas; + +public class ChampStatsDataLoader : BaseResourceLoader>, IChampStatsDataLoader +{ + protected override string AssemblyName => "Leagueoflegends.Support"; + protected override string ResourcePath => "Leagueoflegends.Support.Datas.PersonalChampStats.yml"; + + public List LoadChampionStats() => LoadAndOrganize(); + + public Dictionary> GetStatsByPosition() + { + var allChampions = LoadAndOrganize(); + return allChampions + .GroupBy(c => c.Position) + .ToDictionary( + g => g.Key, + g => g.OrderBy(c => c.Name).ToList() + ); + } + + protected override IEnumerable ConvertToItems(YamlData rawData) + { + return rawData.Select(item => new ChampionStats + { + Seq = item.GetValue("seq"), + Name = item.GetValue("name"), + Mastery = item.GetValue("mastery"), + Achievements = item.GetValue("achievements"), + Position = item.GetValue("position"), + ImageUrl = $"ms-appx:///Leagueoflegends.Support/Images/Portraits/portrait_{item.GetValue("name").Replace(" ", "").Replace("&", "").Replace(".", "").Replace("'", "").ToLower()}.jpg" + }); + } + + protected override List OrganizeItems(IEnumerable championStats) + { + return championStats.OrderBy(c => c.Seq).ToList(); + } +} diff --git a/src/Leagueoflegends.Collection/Local/Datas/OptionDataLoader.cs b/src/Leagueoflegends.Collection/Local/Datas/OptionDataLoader.cs new file mode 100644 index 0000000..fea867e --- /dev/null +++ b/src/Leagueoflegends.Collection/Local/Datas/OptionDataLoader.cs @@ -0,0 +1,35 @@ +using Jamesnet.Core; +using Leagueoflegends.Support.Local.Datas; +using Leagueoflegends.Support.Local.Models; +using System.Collections.Generic; +using System.Linq; + +namespace Leagueoflegends.Collection.Local.Datas; + +public class OptionDataLoader : BaseResourceLoader>, IOptionDataLoader +{ + protected override string AssemblyName => "Leagueoflegends.Support"; + protected override string ResourcePath => "Leagueoflegends.Support.Datas.Options.yml"; + + public List