diff --git a/Directory.Packages.props b/Directory.Packages.props index 19f6fe46..e55bb4d6 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -9,6 +9,7 @@ + diff --git a/Emerald/App.xaml.cs b/Emerald/App.xaml.cs index 25e5d31b..4c4e74a9 100644 --- a/Emerald/App.xaml.cs +++ b/Emerald/App.xaml.cs @@ -12,7 +12,7 @@ public App() this.InitializeComponent(); } - protected Window? MainWindow { get; private set; } + public Window? MainWindow { get; private set; } protected IHost? Host { get; private set; } protected override void OnLaunched(LaunchActivatedEventArgs args) @@ -31,6 +31,7 @@ protected override void OnLaunched(LaunchActivatedEventArgs args) ); MainWindow = builder.Window; + #if DEBUG MainWindow.EnableHotReload(); #endif @@ -48,6 +49,9 @@ protected override void OnLaunched(LaunchActivatedEventArgs args) // Place the frame in the current Window MainWindow.Content = rootFrame; } +#if WINDOWS + var mica = Emerald.Uno.Helpers.WindowManager.IntializeWindow(MainWindow); +#endif if (rootFrame.Content == null) { @@ -59,4 +63,9 @@ protected override void OnLaunched(LaunchActivatedEventArgs args) // Ensure the current window is active MainWindow.Activate(); } + + /// + /// Gets the current instance in use + /// + public new static App Current => (App)Application.Current; } diff --git a/Emerald/Assets/icon.png b/Emerald/Assets/icon.png new file mode 100644 index 00000000..94000d94 Binary files /dev/null and b/Emerald/Assets/icon.png differ diff --git a/Emerald/Emerald.csproj b/Emerald/Emerald.csproj index 5e96e83c..77654f5f 100644 --- a/Emerald/Emerald.csproj +++ b/Emerald/Emerald.csproj @@ -41,7 +41,20 @@ + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + MSBuild:Compile + + diff --git a/Emerald/Helpers/Extensions.cs b/Emerald/Helpers/Extensions.cs new file mode 100644 index 00000000..34d20225 --- /dev/null +++ b/Emerald/Helpers/Extensions.cs @@ -0,0 +1,155 @@ +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.Windows.ApplicationModel.Resources; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using Windows.System.Diagnostics; + +namespace Emerald.Uno.Helpers; + +public static class Extensions +{ + private static readonly ConcurrentDictionary cachedResources = new(); + + //public static void ShowAt(this TeachingTip tip, FrameworkElement element, TeachingTipPlacementMode placement = TeachingTipPlacementMode.Auto, bool closeWhenClick = true, bool addToMainGrid = true) + //{ + // if (addToMainGrid) + // (App.Current.MainWindow.Content as Grid).Children.Add(tip); + + // tip.Target = element; + // tip.PreferredPlacement = placement; + // tip.IsOpen = true; + // if (closeWhenClick) + // { + // tip.ActionButtonClick += (_, _) => tip.IsOpen = false; + // tip.CloseButtonClick += (_, _) => tip.IsOpen = false; + // } + //} + + public static int GetMemoryGB() + { + SystemMemoryUsageReport systemMemoryUsageReport = SystemDiagnosticInfo.GetForCurrentSystem().MemoryUsage.GetReport(); + + long memkb = Convert.ToInt64(systemMemoryUsageReport.TotalPhysicalSizeInBytes); + return Convert.ToInt32(memkb / Math.Pow(1024, 3)); + } + + public static string KiloFormat(this int num) + { + if (num >= 100000000) + return (num / 1000000).ToString("#,0M"); + + if (num >= 10000000) + return (num / 1000000).ToString("0.#") + "M"; + + if (num >= 100000) + return (num / 1000).ToString("#,0K"); + + if (num >= 1000) + return (num / 100).ToString("0.#") + "K"; + + return num.ToString("#,0"); + } + + //public static ContentDialog ToContentDialog(this UIElement content, string title, string closebtnText = null, ContentDialogButton defaultButton = ContentDialogButton.Close) + //{ + // ContentDialog dialog = new() + // { + // XamlRoot = App.Current.MainWindow.Content.XamlRoot, + // Style = Application.Current.Resources["DefaultContentDialogStyle"] as Style, + // Title = title, + // CloseButtonText = closebtnText, + // DefaultButton = defaultButton, + // Content = content, + // RequestedTheme = (ElementTheme)Settings.SettingsSystem.Settings.App.Appearance.Theme + // }; + // return dialog; + //} + + public static int Remove(this ObservableCollection coll, Func condition) + { + var itemsToRemove = coll.Where(condition).ToList(); + + foreach (var itemToRemove in itemsToRemove) + { + coll.Remove(itemToRemove); + } + return itemsToRemove.Count; + } + + public static int Remove(this List coll, Func condition) + { + var itemsToRemove = coll.Where(condition).ToList(); + + foreach (var itemToRemove in itemsToRemove) + { + coll.Remove(itemToRemove); + } + + return itemsToRemove.Count; + } + public static void AddRange(this ObservableCollection cll, IEnumerable items) + { + foreach (var item in items) + cll.Add(item); + } + + public static string ToBinaryString(this string str) + { + var binary = ""; + foreach (char ch in str) + { + binary += Convert.ToString((int)ch, 2); + } + return binary; + } + + public static string ToMD5(this string s) + { + StringBuilder sb = new(); + byte[] hashValue = MD5.HashData(Encoding.UTF8.GetBytes(s)); + + foreach (byte b in hashValue) + { + sb.Append($"{b:X2}"); + } + + return sb.ToString(); + } + + public static string Localize(this string resourceKey) + { + try + { + string s = Windows.ApplicationModel.Resources.ResourceLoader + .GetForViewIndependentUse() + .GetString(resourceKey); + + return string.IsNullOrEmpty(s) ? resourceKey : s; + } + catch + { + return resourceKey; + } + } + + //public static string Localize(this Core.Localized resourceKey) => + // resourceKey.ToString().Localize(); + + //public static Models.Account ToAccount(this CmlLib.Core.Auth.MSession session, bool plusCount = true) + //{ + // bool isOffline = session.AccessToken == "access_token"; + // return new Models.Account(session.Username, isOffline ? null : session.AccessToken, isOffline ? null : session.UUID, plusCount ? MainWindow.HomePage.AccountsPage.AllCount++ : 0, false, session.ClientToken); + //} + + //public static CmlLib.Core.Auth.MSession ToMSession(this Models.Account account) + //{ + // bool isOffline = account.UUID == null; + // return new CmlLib.Core.Auth.MSession(account.UserName, isOffline ? "access_token" : account.AccessToken, isOffline ? Guid.NewGuid().ToString().Replace("-", "") : account.UUID) { ClientToken = account.ClientToken }; + //} +} diff --git a/Emerald/Helpers/MarkupExtensions/ConditionString.cs b/Emerald/Helpers/MarkupExtensions/ConditionString.cs new file mode 100644 index 00000000..b73a2c3c --- /dev/null +++ b/Emerald/Helpers/MarkupExtensions/ConditionString.cs @@ -0,0 +1,21 @@ +using Microsoft.UI.Xaml.Markup; + +namespace Emerald.Uno.Helpers; + +[MarkupExtensionReturnType(ReturnType = typeof(string))] +public class ConditionString : MarkupExtension +{ + public string TrueString { get; set; } + + public string FalseString { get; set; } + + public bool Condition { get; set; } + + public string Result + => Condition ? TrueString : FalseString; + + public override string ToString() + => Result; + + protected override object ProvideValue() => Result; +} diff --git a/Emerald/Helpers/MarkupExtensions/FontIcon.cs b/Emerald/Helpers/MarkupExtensions/FontIcon.cs new file mode 100644 index 00000000..e9d03396 --- /dev/null +++ b/Emerald/Helpers/MarkupExtensions/FontIcon.cs @@ -0,0 +1,14 @@ +using Microsoft.UI.Xaml.Markup; + +namespace Emerald.Uno.Helpers; + +[MarkupExtensionReturnType(ReturnType = typeof(Microsoft.UI.Xaml.Controls.FontIcon))] +public sealed class FontIcon : MarkupExtension +{ + public string Glyph { get; set; } + + public int FontSize { get; set; } = 16; + + protected override object ProvideValue() + => new Microsoft.UI.Xaml.Controls.FontIcon() { Glyph = Glyph, FontSize = FontSize }; +} diff --git a/Emerald/Helpers/MarkupExtensions/LocalizeString.cs b/Emerald/Helpers/MarkupExtensions/LocalizeString.cs new file mode 100644 index 00000000..31b5689c --- /dev/null +++ b/Emerald/Helpers/MarkupExtensions/LocalizeString.cs @@ -0,0 +1,12 @@ +using Microsoft.UI.Xaml.Markup; +using CommunityToolkit.Mvvm; +namespace Emerald.Uno.Helpers; + +[MarkupExtensionReturnType(ReturnType = typeof(string))] +public sealed class Localize : MarkupExtension +{ + public string Name { get; set; } + + protected override object ProvideValue() + => Name.Localize(); +} diff --git a/Emerald/Helpers/WindowManager.cs b/Emerald/Helpers/WindowManager.cs new file mode 100644 index 00000000..5e16c5ca --- /dev/null +++ b/Emerald/Helpers/WindowManager.cs @@ -0,0 +1,176 @@ +using Microsoft.UI; +using Microsoft.UI.Composition; +using Microsoft.UI.Composition.SystemBackdrops; +using Microsoft.UI.Windowing; +using Microsoft.UI.Xaml; +using System; +using System.Runtime.InteropServices; +using Windows.ApplicationModel; +using WinRT; +using WinRT.Interop; + +namespace Emerald.Uno.Helpers +{ +#if WINDOWS + public static class WindowManager + { + /// + /// Add mica and the icon to the + /// + public static MicaBackground IntializeWindow(Window window) + { + var s = new MicaBackground(window); + s.TrySetMicaBackdrop(); + return s; + } + + /// + /// Sets the customized titlebar if supported + /// + /// + public static void SetTitleBar(Window window, UIElement AppTitleBar) + { + FrameworkElement RootUI = (FrameworkElement)window.Content; + if (AppWindowTitleBar.IsCustomizationSupported()) + { + var titlebar = window.AppWindow.TitleBar; + titlebar.ExtendsContentIntoTitleBar = true; + + void SetColor(ElementTheme acualTheme) + { + titlebar.ButtonBackgroundColor = titlebar.ButtonInactiveBackgroundColor = titlebar.ButtonPressedBackgroundColor = Colors.Transparent; + switch (acualTheme) + { + case ElementTheme.Dark: + titlebar.ButtonHoverBackgroundColor = Colors.Black; + titlebar.ButtonForegroundColor = Colors.White; + titlebar.ButtonHoverForegroundColor = Colors.White; + titlebar.ButtonPressedForegroundColor = Colors.Silver; + break; + case ElementTheme.Light: + titlebar.ButtonHoverBackgroundColor = Colors.White; + titlebar.ButtonForegroundColor = Colors.Black; + titlebar.ButtonHoverForegroundColor = Colors.Black; + titlebar.ButtonPressedForegroundColor = Colors.DarkGray; + break; + } + } + + RootUI.ActualThemeChanged += (s, _) => SetColor(s.ActualTheme); + window.SetTitleBar(AppTitleBar); + SetColor(RootUI.ActualTheme); + } + else + { + window.ExtendsContentIntoTitleBar = true; + window.SetTitleBar(AppTitleBar); + } + } + } + + public class WindowsSystemDispatcherQueueHelper + { + private object? _dispatcherQueueController; + + [StructLayout(LayoutKind.Sequential)] + internal struct DispatcherQueueOptions + { + internal int dwSize; + internal int threadType; + internal int apartmentType; + } + + [DllImport("CoreMessaging.dll")] + private static extern int CreateDispatcherQueueController([In] DispatcherQueueOptions options, [In, Out, MarshalAs(UnmanagedType.IUnknown)] ref object? dispatcherQueueController); + + public void EnsureWindowsSystemDispatcherQueueController() + { + if (Windows.System.DispatcherQueue.GetForCurrentThread() != null) + { + // one already exists, so we'll just use it. + return; + } + + if (_dispatcherQueueController == null) + { + DispatcherQueueOptions options; + options.dwSize = Marshal.SizeOf(typeof(DispatcherQueueOptions)); + options.threadType = 2; + options.apartmentType = 2; + + CreateDispatcherQueueController(options, ref _dispatcherQueueController); + } + } + } + + public class MicaBackground + { + private readonly Window _window; + public readonly MicaController MicaController = new(); + private SystemBackdropConfiguration _backdropConfiguration = new(); + private readonly WindowsSystemDispatcherQueueHelper _dispatcherQueueHelper = new(); + + public MicaBackground(Window window) + { + _window = window; + } + + public bool TrySetMicaBackdrop() + { + if (MicaController.IsSupported()) + { + _dispatcherQueueHelper.EnsureWindowsSystemDispatcherQueueController(); + _window.Activated += WindowOnActivated; + _window.Closed += WindowOnClosed; + ((FrameworkElement)_window.Content).ActualThemeChanged += MicaBackground_ActualThemeChanged; + _backdropConfiguration.IsInputActive = true; + + _backdropConfiguration.Theme = _window.Content switch + { + FrameworkElement { ActualTheme: ElementTheme.Dark } => SystemBackdropTheme.Dark, + FrameworkElement { ActualTheme: ElementTheme.Light } => SystemBackdropTheme.Light, + FrameworkElement { ActualTheme: ElementTheme.Default } => SystemBackdropTheme.Default, + _ => throw new InvalidOperationException("Unknown theme") + }; + + MicaController.AddSystemBackdropTarget(_window.As()); + MicaController.SetSystemBackdropConfiguration(_backdropConfiguration); + + return true; + } + + return false; + } + + private void MicaBackground_ActualThemeChanged(FrameworkElement sender, object args) + { + if (_backdropConfiguration != null) + { + SetConfigurationSourceTheme(); + } + } + + private void SetConfigurationSourceTheme() + { + switch (((FrameworkElement)_window.Content).ActualTheme) + { + case ElementTheme.Dark: _backdropConfiguration.Theme = SystemBackdropTheme.Dark; break; + case ElementTheme.Light: _backdropConfiguration.Theme = SystemBackdropTheme.Light; break; + case ElementTheme.Default: _backdropConfiguration.Theme = SystemBackdropTheme.Default; break; + } + } + + private void WindowOnClosed(object sender, WindowEventArgs args) + { + MicaController.Dispose(); + _window.Activated -= WindowOnActivated; + _backdropConfiguration = null!; + } + + private void WindowOnActivated(object sender, WindowActivatedEventArgs args) + { + _backdropConfiguration.IsInputActive = args.WindowActivationState is not WindowActivationState.Deactivated; + } + } +#endif +} diff --git a/Emerald/MainPage.xaml b/Emerald/MainPage.xaml index 7145924d..68ff87d4 100644 --- a/Emerald/MainPage.xaml +++ b/Emerald/MainPage.xaml @@ -3,12 +3,169 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:Emerald" xmlns:utu="using:Uno.Toolkit.UI" - Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> - - - + xmlns:models="using:Emerald.Uno.Models" + xmlns:uc="using:Emerald.Uno.UserControls" + xmlns:helpers="using:Emerald.Uno.Helpers"> + + + + + + + + + + + + + + + +