diff --git a/installer/DesktopMagic-Installer.exe b/installer/DesktopMagic-Installer.exe
index 8e0e20c..2e40156 100644
Binary files a/installer/DesktopMagic-Installer.exe and b/installer/DesktopMagic-Installer.exe differ
diff --git a/src/DesktopMagic/DataContexts/MainWindowDataContext.cs b/src/DesktopMagic/DataContexts/MainWindowDataContext.cs
index 8c3c0fd..d99941a 100644
--- a/src/DesktopMagic/DataContexts/MainWindowDataContext.cs
+++ b/src/DesktopMagic/DataContexts/MainWindowDataContext.cs
@@ -13,6 +13,7 @@ internal class MainWindowDataContext : INotifyPropertyChanged
private static DesktopMagicSettings settings = new();
private bool isLoading = true;
+ private string? pluginsSearchText;
public string Title =>
#if DEBUG
@@ -43,6 +44,16 @@ internal class MainWindowDataContext : INotifyPropertyChanged
}
}
+ public string? PluginsSearchText
+ {
+ get => pluginsSearchText;
+ set
+ {
+ pluginsSearchText = value;
+ OnPropertyChanged();
+ }
+ }
+
public bool IsAutoStartEnabled
{
get => StartupManager.IsAutoStartEnabled();
diff --git a/src/DesktopMagic/DesktopMagic.csproj b/src/DesktopMagic/DesktopMagic.csproj
index c46f32c..9141158 100644
--- a/src/DesktopMagic/DesktopMagic.csproj
+++ b/src/DesktopMagic/DesktopMagic.csproj
@@ -7,11 +7,11 @@
icon.ico
OnBuildSuccess
Stone_Red
- 1.3.2.0
+ 1.3.3.0
https://github.com/Stone-Red-Code/DesktopMagic
https://github.com/Stone-Red-Code/DesktopMagic
- 1.3.2.0
- 1.3.1.0
+ 1.3.3.0
+ 1.3.3.0
net8.0-windows7.0
win-x64
enable
diff --git a/src/DesktopMagic/Dialogs/CreatePluginDialog.xaml b/src/DesktopMagic/Dialogs/CreatePluginDialog.xaml
index c5000bc..637cb65 100644
--- a/src/DesktopMagic/Dialogs/CreatePluginDialog.xaml
+++ b/src/DesktopMagic/Dialogs/CreatePluginDialog.xaml
@@ -16,9 +16,9 @@
WindowCornerPreference="Round"
WindowBackdropType="Tabbed"
Height="230"
- Width="300"
+ Width="320"
MinHeight="160"
- MinWidth="300"
+ MinWidth="320"
WindowStartupLocation="CenterOwner"
ExtendsContentIntoTitleBar="True">
@@ -48,7 +48,8 @@
-
+
+
diff --git a/src/DesktopMagic/Dialogs/CreatePluginDialog.xaml.cs b/src/DesktopMagic/Dialogs/CreatePluginDialog.xaml.cs
index da4adb6..9669865 100644
--- a/src/DesktopMagic/Dialogs/CreatePluginDialog.xaml.cs
+++ b/src/DesktopMagic/Dialogs/CreatePluginDialog.xaml.cs
@@ -10,6 +10,8 @@ public partial class CreatePluginDialog : Wpf.Ui.Controls.FluentWindow
set => textBox.Text = value;
}
+ public bool IsWebPlugin { get; private set; }
+
public CreatePluginDialog()
{
InitializeComponent();
@@ -17,8 +19,15 @@ public partial class CreatePluginDialog : Wpf.Ui.Controls.FluentWindow
Resources.MergedDictionaries.Add(App.LanguageDictionary);
}
- private void OkButton_Click(object sender, RoutedEventArgs e)
+ private void DotNetButton_Click(object sender, RoutedEventArgs e)
{
+ IsWebPlugin = false;
+ DialogResult = true;
+ }
+
+ private void WebPluginButton_Click(object sender, RoutedEventArgs e)
+ {
+ IsWebPlugin = true;
DialogResult = true;
}
diff --git a/src/DesktopMagic/Helpers/ColorConverter.cs b/src/DesktopMagic/Helpers/ColorConverter.cs
index 667de56..85b1390 100644
--- a/src/DesktopMagic/Helpers/ColorConverter.cs
+++ b/src/DesktopMagic/Helpers/ColorConverter.cs
@@ -89,11 +89,35 @@ internal static partial class MultiColorConverter
return System.Windows.Media.Color.FromArgb(color.A, color.R, color.G, color.B);
}
+ public static System.Windows.Media.Color ConvertToMediaColor(string hex, System.Windows.Media.Color defaultColor)
+ {
+ if (TryConvertToMediaColor(hex, out System.Windows.Media.Color color))
+ {
+ return color;
+ }
+ else
+ {
+ return defaultColor;
+ }
+ }
+
public static System.Drawing.Color ConvertToSystemColor(System.Windows.Media.Color color)
{
return System.Drawing.Color.FromArgb(color.A, color.R, color.G, color.B);
}
+ public static System.Drawing.Color ConvertToSystemColor(string hex, System.Drawing.Color defaultColor)
+ {
+ if (TryConvertToSystemColor(hex, out System.Drawing.Color color))
+ {
+ return color;
+ }
+ else
+ {
+ return defaultColor;
+ }
+ }
+
[GeneratedRegex("(?:[0-9a-fA-F]{8})")]
private static partial Regex Hex8();
diff --git a/src/DesktopMagic/Helpers/WindowPos.cs b/src/DesktopMagic/Helpers/WindowPos.cs
index 95e67ff..4c13300 100644
--- a/src/DesktopMagic/Helpers/WindowPos.cs
+++ b/src/DesktopMagic/Helpers/WindowPos.cs
@@ -76,6 +76,21 @@ namespace DesktopMagic
DependencyProperty.RegisterAttached("IsHooked", typeof(WindowLockHook), typeof(WindowPos),
new PropertyMetadata(null));
+ public static void SetWindowLayer(Window window, string layer)
+ {
+ switch (layer)
+ {
+ case "Always on Bottom":
+ window.Topmost = false;
+ SendWpfWindowBack(window);
+ SendWpfWindowBack(window);
+ break;
+ case "Always on Top":
+ window.Topmost = true;
+ break;
+ }
+ }
+
private class WindowLockHook
{
private readonly Window Window;
diff --git a/src/DesktopMagic/Pages/MainPage.xaml b/src/DesktopMagic/Pages/MainPage.xaml
index 67ff6ae..e477d81 100644
--- a/src/DesktopMagic/Pages/MainPage.xaml
+++ b/src/DesktopMagic/Pages/MainPage.xaml
@@ -20,11 +20,19 @@
-
+
+
+
+
+
+
+
+
+
-
+
diff --git a/src/DesktopMagic/Pages/MainPage.xaml.cs b/src/DesktopMagic/Pages/MainPage.xaml.cs
index 6b6f56a..008bf1b 100644
--- a/src/DesktopMagic/Pages/MainPage.xaml.cs
+++ b/src/DesktopMagic/Pages/MainPage.xaml.cs
@@ -4,10 +4,12 @@ using DesktopMagic.Helpers;
using DesktopMagic.Plugins;
using DesktopMagic.Settings;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
+using System.Windows.Data;
namespace DesktopMagic.Pages;
@@ -56,8 +58,8 @@ public partial class MainPage : Page
{
Dispatcher.Invoke(() =>
{
- // Refresh the UI if needed
_dataContext.Settings = _manager.Settings;
+ ApplyPluginsFilter();
});
}
@@ -107,6 +109,38 @@ public partial class MainPage : Page
}
}
+ #region Plugin Search
+
+ private void AllPluginsSearchTextBox_TextChanged(object sender, TextChangedEventArgs e)
+ {
+ ApplyPluginsFilter();
+ }
+
+ private void ApplyPluginsFilter()
+ {
+ string? searchText = _dataContext.PluginsSearchText;
+
+ System.ComponentModel.ICollectionView view = CollectionViewSource.GetDefaultView(pluginsItemsControl.ItemsSource);
+
+ if (string.IsNullOrWhiteSpace(searchText))
+ {
+ view.Filter = null;
+ }
+ else
+ {
+ view.Filter = (item) =>
+ {
+ if (item is KeyValuePair kvp)
+ {
+ return kvp.Value.Metadata.Name.Contains(searchText, StringComparison.OrdinalIgnoreCase);
+ }
+ return false;
+ };
+ }
+ }
+
+ #endregion
+
#region Layout Management
private void LayoutsComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
diff --git a/src/DesktopMagic/Plugins/PluginManager.xaml.cs b/src/DesktopMagic/Plugins/PluginManager.xaml.cs
index bb8f83f..eedca7c 100644
--- a/src/DesktopMagic/Plugins/PluginManager.xaml.cs
+++ b/src/DesktopMagic/Plugins/PluginManager.xaml.cs
@@ -486,6 +486,15 @@ public partial class PluginManager : Page
string pluginMetadataPath = Path.Combine(pluginPath, "metadata.json");
await File.WriteAllTextAsync(pluginMetadataPath, JsonSerializer.Serialize(pluginMetadata));
+ if (inputDialog.IsWebPlugin)
+ {
+ await CreateNewWebPlugin(pluginPath, pluginName);
+ changed = true;
+ await InitializePluginManager();
+ pluginManagerDataContext.IsLoading = false;
+ return;
+ }
+
App.Logger.LogInfo($"Creating .NET project at: {pluginProjectPath}", source: "PluginManager");
string cmd = $"new classlib -n {pluginSafeName} -o {pluginProjectPath} -f net8.0 --target-framework-override net8.0-windows7";
Process process = Process.Start("dotnet", cmd);
@@ -608,6 +617,49 @@ public class {pluginSafeName}Plugin : Plugin
pluginManagerDataContext.IsLoading = false;
}
+ private async Task CreateNewWebPlugin(string pluginPath, string pluginName)
+ {
+ App.Logger.LogInfo($"Creating web plugin at: {pluginPath}", source: "PluginManager");
+
+ string mainHtml = $@"
+
+
+
+
+
+
+ {pluginName}
+
+
+";
+
+ var settings = new List>
+ {
+ new() { ["id"] = "message", ["name"] = "Greeting Message", ["type"] = "textbox", ["default"] = "Hello, World!" },
+ new() { ["id"] = "bold", ["name"] = "Bold Text", ["type"] = "checkbox", ["default"] = true },
+ new() { ["id"] = "color", ["name"] = "Text Color", ["type"] = "colorpicker", ["default"] = "#FF5722" }
+ };
+
+ string settingsJson = JsonSerializer.Serialize(settings, new JsonSerializerOptions { WriteIndented = true });
+ await File.WriteAllTextAsync(Path.Combine(pluginPath, "settings.json"), settingsJson);
+ await File.WriteAllTextAsync(Path.Combine(pluginPath, "main.html"), mainHtml);
+
+ App.Logger.LogInfo($"Successfully created web plugin: {pluginName}", source: "PluginManager");
+ }
+
private async void LogInButton_Click(object sender, RoutedEventArgs e)
{
if (pluginManagerDataContext.IsAuthenticated)
diff --git a/src/DesktopMagic/Plugins/PluginWindow.xaml.cs b/src/DesktopMagic/Plugins/PluginWindow.xaml.cs
index f6cc550..03df7bc 100644
--- a/src/DesktopMagic/Plugins/PluginWindow.xaml.cs
+++ b/src/DesktopMagic/Plugins/PluginWindow.xaml.cs
@@ -322,9 +322,8 @@ public partial class PluginWindow : Window, IPluginWindow
panel.Visibility = Visibility.Collapsed;
imageBorder.BorderThickness = new Thickness(0);
image.Margin = new Thickness(0);
- WindowPos.SendWpfWindowBack(this);
- WindowPos.SendWpfWindowBack(this);
- WindowPos.SetIsLocked(this, true);
+ WindowPos.SetWindowLayer(this, pluginClassInstance?.windowLayer.Value ?? "Always on Bottom");
+ WindowPos.SetIsLocked(window, true);
tileBar.CaptionHeight = 0;
ResizeMode = ResizeMode.NoResize;
}
@@ -558,12 +557,14 @@ public partial class PluginWindow : Window, IPluginWindow
{
SetHorizontalAlignment();
SetVerticalAlignment();
+ SetWindowLayer();
SetRotation();
SetThemeOverride();
SetThemeOverrideItems();
pluginClassInstance.horizontalAlignment.OnValueChanged += SetHorizontalAlignment;
pluginClassInstance.verticalAlignment.OnValueChanged += SetVerticalAlignment;
+ pluginClassInstance.windowLayer.OnValueChanged += SetWindowLayer;
pluginClassInstance.rotation.OnValueChanged += SetRotation;
pluginClassInstance.themeOverride.OnValueChanged += SetThemeOverride;
@@ -599,6 +600,11 @@ public partial class PluginWindow : Window, IPluginWindow
border.HorizontalAlignment = horizontalAlignment;
}
+ void SetWindowLayer()
+ {
+ WindowPos.SetWindowLayer(this, pluginClassInstance.windowLayer.Value);
+ }
+
void SetRotation()
{
image.LayoutTransform = new RotateTransform(pluginClassInstance.rotation.Value);
diff --git a/src/DesktopMagic/Plugins/WebPluginWindow.xaml.cs b/src/DesktopMagic/Plugins/WebPluginWindow.xaml.cs
index ff92680..08f0b5a 100644
--- a/src/DesktopMagic/Plugins/WebPluginWindow.xaml.cs
+++ b/src/DesktopMagic/Plugins/WebPluginWindow.xaml.cs
@@ -1,3 +1,4 @@
+using DesktopMagic.Api.Settings;
using DesktopMagic.Helpers;
using DesktopMagic.Plugins;
using DesktopMagic.Settings;
@@ -5,7 +6,11 @@ using DesktopMagic.Settings;
using Microsoft.Web.WebView2.Core;
using System;
+using System.Collections.Generic;
using System.IO;
+using System.Linq;
+using System.Text.Json;
+using System.Threading.Tasks;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media;
@@ -20,6 +25,9 @@ public partial class WebPluginWindow : Window, IPluginWindow
private readonly PluginSettings settings;
private bool isInitialized = false;
+ private FileSystemWatcher? fileWatcher;
+ private System.Timers.Timer? reloadDebounceTimer;
+ private bool isReloading = false;
public bool IsRunning { get; private set; } = true;
public PluginMetadata PluginMetadata { get; private set; }
@@ -71,6 +79,11 @@ public partial class WebPluginWindow : Window, IPluginWindow
Height = settings.Size.Y;
PluginFolderPath = pluginFolderPath;
+
+ if (pluginMetadata.SupportsUnloading && !string.IsNullOrEmpty(pluginFolderPath))
+ {
+ InitializeHotReload();
+ }
}
public void Exit()
@@ -193,6 +206,8 @@ public partial class WebPluginWindow : Window, IPluginWindow
try
{
+ LoadWebPluginSettings();
+
string userDataFolder = Path.Combine(Path.GetTempPath(), "DesktopMagic", "WebView2", PluginMetadata.Id.ToString());
CoreWebView2Environment environment = await CoreWebView2Environment.CreateAsync(null, userDataFolder);
await webView.EnsureCoreWebView2Async(environment);
@@ -202,6 +217,8 @@ public partial class WebPluginWindow : Window, IPluginWindow
webView.CoreWebView2.Settings.IsStatusBarEnabled = false;
webView.CoreWebView2.Settings.AreDefaultScriptDialogsEnabled = true;
+ _ = await webView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(GetSettingsBridgeScript());
+
string htmlUri = new Uri(htmlPath).AbsoluteUri;
webView.Source = new Uri(htmlUri);
@@ -224,6 +241,15 @@ public partial class WebPluginWindow : Window, IPluginWindow
App.Logger.LogInfo($"\"{PluginMetadata.Name}\" - Stopping web plugin", source: "WebPlugin");
IsRunning = false;
+ if (fileWatcher != null)
+ {
+ fileWatcher.EnableRaisingEvents = false;
+ fileWatcher.Dispose();
+ }
+
+ reloadDebounceTimer?.Stop();
+ reloadDebounceTimer?.Dispose();
+
try
{
if (isInitialized && webView.CoreWebView2 != null)
@@ -249,8 +275,359 @@ public partial class WebPluginWindow : Window, IPluginWindow
tileBar.CaptionHeight = ActualHeight - 10;
}
+ private bool firstLoad = true;
+
private void WebView_CoreWebView2InitializationCompleted(object sender, CoreWebView2InitializationCompletedEventArgs e)
{
- webView.CoreWebView2.DOMContentLoaded += (_, _) => ThemeChanged();
+ webView.CoreWebView2.DOMContentLoaded += (_, _) =>
+ {
+ if (firstLoad)
+ {
+ firstLoad = false;
+ if (fileWatcher != null)
+ {
+ fileWatcher.EnableRaisingEvents = true;
+ }
+ }
+
+ ThemeChanged();
+ };
+ }
+
+ private void LoadWebPluginSettings()
+ {
+ string settingsPath = Path.Combine(PluginFolderPath, "settings.json");
+ if (!File.Exists(settingsPath))
+ {
+ return;
+ }
+
+ try
+ {
+ string json = File.ReadAllText(settingsPath);
+ using JsonDocument doc = JsonDocument.Parse(json);
+ JsonElement root = doc.RootElement;
+
+ if (root.ValueKind != JsonValueKind.Array)
+ {
+ App.Logger.LogWarn($"\"{PluginMetadata.Name}\" - settings.json must be a JSON array", source: "WebPlugin");
+ return;
+ }
+
+ List settingElements = [];
+ int orderIndex = 0;
+
+ foreach (JsonElement element in root.EnumerateArray())
+ {
+ string id = element.GetProperty("id").GetString() ?? $"setting-{orderIndex}";
+ string name = element.TryGetProperty("name", out JsonElement nameElement) ? nameElement.GetString() ?? id : id;
+ string type = element.TryGetProperty("type", out JsonElement typeElement) ? typeElement.GetString() ?? "textbox" : "textbox";
+
+ Setting? setting = CreateSetting(type, element);
+ if (setting is null)
+ {
+ App.Logger.LogWarn($"\"{PluginMetadata.Name}\" - Unknown setting type \"{type}\" for \"{id}\"", source: "WebPlugin");
+ continue;
+ }
+
+ SettingElement settingElement = new(setting, id, name, orderIndex);
+
+ if (settings.Settings.Exists(e => e.Id == id))
+ {
+ SettingElement saved = settings.Settings.First(e => e.Id == id);
+ settingElement.JsonValue = saved.JsonValue;
+ }
+
+ string capturedId = id;
+ if (setting is Button button)
+ {
+ button.OnClick += () =>
+ {
+ _ = webView.Dispatcher.InvokeAsync(async () =>
+ {
+ try
+ {
+ _ = await webView.ExecuteScriptAsync($"window.desktopMagic?.onClick?.({JsonSerializer.Serialize(capturedId)})");
+ }
+ catch (Exception ex)
+ {
+ App.Logger.LogError($"\"{PluginMetadata.Name}\" - Failed to notify button click for \"{capturedId}\": {ex}", source: "WebPlugin");
+ }
+ });
+ };
+ }
+
+ setting.OnValueChanged += () =>
+ {
+ _ = webView.Dispatcher.InvokeAsync(async () =>
+ {
+ try
+ {
+ await NotifySettingChange(capturedId, setting);
+ }
+ catch (Exception ex)
+ {
+ App.Logger.LogError($"\"{PluginMetadata.Name}\" - Failed to notify setting change for \"{capturedId}\": {ex}", source: "WebPlugin");
+ }
+ });
+ };
+
+ settingElements.Add(settingElement);
+ orderIndex++;
+ }
+
+ settings.Settings = [.. settingElements.OrderBy(x => x.OrderIndex)];
+ }
+ catch (Exception ex)
+ {
+ App.Logger.LogWarn($"\"{PluginMetadata.Name}\" - Failed to load settings from settings.json: {ex.Message}", source: "WebPlugin");
+ }
+ }
+
+ private string GetSettingsBridgeScript()
+ {
+ string settingsJson = SerializeSettingsToJson();
+
+ return $@"
+ (function() {{
+ window.desktopMagic = {{}};
+ window.desktopMagic._settings = {settingsJson};
+
+ window.desktopMagic.getSettings = function() {{
+ return JSON.parse(JSON.stringify(window.desktopMagic._settings));
+ }};
+
+ window.desktopMagic.getSetting = function(id) {{
+ return window.desktopMagic._settings ? window.desktopMagic._settings[id] : undefined;
+ }};
+
+ window.desktopMagic.onSettingChanged = null;
+ window.desktopMagic.onButtonClick = null;
+
+ window.desktopMagic.dispatchSettingChanged = function(id, value) {{
+ if (window.desktopMagic._settings) {{
+ window.desktopMagic._settings[id] = value;
+ }}
+ if (typeof window.desktopMagic.onSettingChanged === 'function') {{
+ window.desktopMagic.onSettingChanged(id, value);
+ }}
+ }};
+
+ window.desktopMagic.onClick = function(id) {{
+ if (typeof window.desktopMagic.onButtonClick === 'function') {{
+ window.desktopMagic.onButtonClick(id);
+ }}
+ }};
+ }})();
+ ";
+ }
+
+ private void InitializeHotReload()
+ {
+ try
+ {
+ fileWatcher = new FileSystemWatcher(PluginFolderPath)
+ {
+ NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size,
+ EnableRaisingEvents = false
+ };
+
+ fileWatcher.Changed += OnPluginFileChanged;
+
+ reloadDebounceTimer = new System.Timers.Timer(500)
+ {
+ AutoReset = false
+ };
+ reloadDebounceTimer.Elapsed += async (s, e) =>
+ {
+ await ReloadWebPlugin();
+ };
+
+ App.Logger.LogInfo($"\"{PluginMetadata.Name}\" - Hot reload enabled", source: "WebPlugin");
+ }
+ catch (Exception ex)
+ {
+ App.Logger.LogWarn($"\"{PluginMetadata.Name}\" - Failed to initialize hot reload: {ex.Message}", source: "WebPlugin");
+ }
+ }
+
+ private void OnPluginFileChanged(object sender, FileSystemEventArgs e)
+ {
+ if (isReloading)
+ {
+ return;
+ }
+
+ string? fileName = e.Name;
+ if (string.IsNullOrEmpty(fileName))
+ {
+ return;
+ }
+
+ string ext = Path.GetExtension(fileName).ToLowerInvariant();
+ if (ext is not ".html" and not ".json" and not ".js" and not ".css")
+ {
+ return;
+ }
+
+ reloadDebounceTimer?.Stop();
+ reloadDebounceTimer?.Start();
+ }
+
+ private async Task ReloadWebPlugin()
+ {
+ if (isReloading || !IsRunning)
+ {
+ return;
+ }
+
+ isReloading = true;
+
+ try
+ {
+ App.Logger.LogInfo($"\"{PluginMetadata.Name}\" - Reloading web plugin", source: "WebPlugin");
+
+ if (fileWatcher != null)
+ {
+ fileWatcher.EnableRaisingEvents = false;
+ }
+
+ LoadWebPluginSettings();
+
+ await Dispatcher.Invoke(async () =>
+ {
+ if (isInitialized && webView.CoreWebView2 != null)
+ {
+ _ = await webView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(GetSettingsBridgeScript());
+
+ busyMask.IsBusy = true;
+
+ string htmlPath = Path.Combine(PluginFolderPath, "main.html");
+ string htmlUri = new Uri(htmlPath).AbsoluteUri;
+ webView.CoreWebView2.Navigate(htmlUri);
+
+ busyMask.IsBusy = false;
+ }
+ });
+
+ App.Logger.LogInfo($"\"{PluginMetadata.Name}\" - Web plugin reloaded", source: "WebPlugin");
+ }
+ catch (Exception ex)
+ {
+ App.Logger.LogError($"\"{PluginMetadata.Name}\" - Failed to reload web plugin: {ex}", source: "WebPlugin");
+ }
+ finally
+ {
+ isReloading = false;
+
+ if (fileWatcher != null && IsRunning)
+ {
+ fileWatcher.EnableRaisingEvents = true;
+ }
+ }
+ }
+
+ private async System.Threading.Tasks.Task NotifySettingChange(string id, Setting setting)
+ {
+ object? value = GetSettingValue(setting);
+ string serializedValue = JsonSerializer.Serialize(value);
+ string serializedId = JsonSerializer.Serialize(id);
+ _ = await webView.ExecuteScriptAsync($"window.desktopMagic?.dispatchSettingChanged?.({serializedId}, {serializedValue})");
+ }
+
+ private string SerializeSettingsToJson()
+ {
+ Dictionary dict = [];
+ foreach (SettingElement element in settings.Settings)
+ {
+ dict[element.Id] = GetSettingValue(element.Input);
+ }
+ return JsonSerializer.Serialize(dict);
+ }
+
+ private static object? GetSettingValue(Setting setting)
+ {
+ return setting switch
+ {
+ TextBox tb => tb.Value,
+ CheckBox cb => cb.Value,
+ Slider sl => sl.Value,
+ IntegerUpDown iud => iud.Value,
+ ComboBox cb => cb.Value,
+ ColorPicker cp => MultiColorConverter.ConvertToHexRgba(cp.Value),
+ Button btn => btn.Value,
+ Label lbl => lbl.Value,
+ FileSelector fs => fs.Value,
+ _ => null
+ };
+ }
+
+ private static Setting? CreateSetting(string type, JsonElement element)
+ {
+ return type.ToLowerInvariant() switch
+ {
+ "textbox" => new TextBox(
+ element.TryGetProperty("default", out JsonElement defaultValue) ? defaultValue.GetString() ?? "" : ""),
+
+ "checkbox" => new CheckBox(
+ element.TryGetProperty("default", out JsonElement defaultValue) && defaultValue.ValueKind == JsonValueKind.True),
+
+ "slider" => new Slider(
+ element.TryGetProperty("min", out JsonElement minValue) ? minValue.GetDouble() : 0,
+ element.TryGetProperty("max", out JsonElement maxValue) ? maxValue.GetDouble() : 100,
+ element.TryGetProperty("default", out JsonElement defaultValue) ? defaultValue.GetDouble() : 0),
+
+ "integer" => new IntegerUpDown(
+ element.TryGetProperty("min", out JsonElement minValue) ? minValue.GetInt32() : 0,
+ element.TryGetProperty("max", out JsonElement maxValue) ? maxValue.GetInt32() : 100,
+ element.TryGetProperty("default", out JsonElement defaultValue) ? defaultValue.GetInt32() : 0),
+
+ "combobox" => CreateComboBox(element),
+
+ "colorpicker" => new ColorPicker(
+ element.TryGetProperty("default", out JsonElement defaultValue)
+ ? MultiColorConverter.ConvertToSystemColor(defaultValue.GetString() ?? "#FFFFFFFF", System.Drawing.Color.White)
+ : System.Drawing.Color.White),
+
+ "button" => new Button(
+ element.TryGetProperty("default", out JsonElement defaultValue) ? defaultValue.GetString() ?? "" : ""),
+
+ "label" => new Label(
+ element.TryGetProperty("default", out JsonElement defaultValue) ? defaultValue.GetString() ?? "" : "",
+ element.TryGetProperty("bold", out JsonElement boldValue) && boldValue.ValueKind == JsonValueKind.True),
+
+ "file" => new FileSelector(
+ element.TryGetProperty("default", out JsonElement defaultValue) ? defaultValue.GetString() ?? "" : "",
+ element.TryGetProperty("filter", out JsonElement filterValue) ? filterValue.GetString() ?? "All Files|*.*" : "All Files|*.*",
+ element.TryGetProperty("title", out JsonElement titleValue) ? titleValue.GetString() ?? "Select File" : "Select File",
+ element.TryGetProperty("selectFolder", out JsonElement selectFolderValue) && selectFolderValue.ValueKind == JsonValueKind.True),
+
+ _ => null
+ };
+ }
+
+ private static ComboBox CreateComboBox(JsonElement element)
+ {
+ List items = [];
+ if (element.TryGetProperty("items", out JsonElement itemsValue) && itemsValue.ValueKind == JsonValueKind.Array)
+ {
+ foreach (JsonElement item in itemsValue.EnumerateArray())
+ {
+ items.Add(item.GetString() ?? "");
+ }
+ }
+
+ ComboBox comboBox = new([.. items]);
+
+ if (element.TryGetProperty("default", out JsonElement defaultValue))
+ {
+ string defaultText = defaultValue.GetString() ?? "";
+ if (!string.IsNullOrEmpty(defaultText) && items.Contains(defaultText))
+ {
+ comboBox.Value = defaultText;
+ }
+ }
+
+ return comboBox;
}
}
diff --git a/src/DesktopMagicPluginAPI/Plugin.cs b/src/DesktopMagicPluginAPI/Plugin.cs
index b6fbc01..94cbb09 100644
--- a/src/DesktopMagicPluginAPI/Plugin.cs
+++ b/src/DesktopMagicPluginAPI/Plugin.cs
@@ -23,10 +23,13 @@ public abstract class Plugin
[Setting("desktopmagic-vertical-alignment", "Vertical Alignment", -998)]
internal ComboBox verticalAlignment = new ComboBox("Center", "Top", "Bottom");
- [Setting("rotation", "Rotation", -997)]
+ [Setting("desktopmagic-window-layer", "Window Layer", -997)]
+ internal ComboBox windowLayer = new ComboBox("Always on Bottom", "Always on Top");
+
+ [Setting("desktopmagic-rotation", "Rotation", -996)]
internal IntegerUpDown rotation = new IntegerUpDown(-360, 360, 0);
- [Setting("theme-override", "Theme Override", -996)]
+ [Setting("desktopmagic-theme-override", "Theme Override", -995)]
internal ComboBox themeOverride = new ComboBox("");
private IPluginData application = null!;
diff --git a/src/MsixPackaging/Package.appxmanifest b/src/MsixPackaging/Package.appxmanifest
index c90a086..5e047ab 100644
--- a/src/MsixPackaging/Package.appxmanifest
+++ b/src/MsixPackaging/Package.appxmanifest
@@ -9,7 +9,7 @@
+ Version="1.3.3.0" />
Desktop Magic