diff --git a/docs/configure/core/styling.md b/docs/configure/core/styling.md index 707ca558c..dfd612d4b 100644 --- a/docs/configure/core/styling.md +++ b/docs/configure/core/styling.md @@ -1,3 +1,26 @@ # Styling [!INCLUDE [Styling](../../_includes/core/styling.md)] + +## Backdrops + +Different Whim windows can support custom backdrops. They will generally be associated with a `backdrop` key in the YAML/JSON configuration. The following backdrops are available: + +- `none`: No backdrop +- `acrylic`: An [acrylic backdrop](https://docs.microsoft.com/en-us/windows/apps/design/style/acrylic) +- `acrylic_thin`: A more transparent Acrylic backdrop - based on the Acrylic backdrop + +| Type | Description | WinUI Documentation | +| -------------- | ----------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | +| `none` | No backdrop | N/A | +| `acrylic` | A translucent texture that blurs the content behind it. | [Acrylic material](https://docs.microsoft.com/en-us/windows/apps/design/style/acrylic) | +| `acrylic_thin` | A more transparent version of the Acrylic backdrop. | N/A | +| `mica` | An opaque, dynamic material that incorpoates theme and the desktop wallpaper. Mica has better performance than Acrylic. | [Mica material](https://learn.microsoft.com/en-us/windows/apps/design/style/mica) | +| `mica_alt` | A variant of Mica with stronger tinting of the user's background color. | [Mica alt material](https://learn.microsoft.com/en-us/windows/apps/design/style/mica) | + +### Configuration + +| Property | Description | +| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `type` | The type of backdrop to use. | +| `always_show_backdrop` | By default, WinUI will disable the backdrop when the window loses focus. Whim overrides this setting. Set this to false to disable the backdrop when the window loses focus. | diff --git a/docs/configure/plugins/bar.md b/docs/configure/plugins/bar.md index 52c0cf826..9e4b3c84c 100644 --- a/docs/configure/plugins/bar.md +++ b/docs/configure/plugins/bar.md @@ -1,7 +1,86 @@ # Bar Plugin -👷🏗️🚧 +The `BarPlugin` adds a configurable bar at the top of each monitor. -## Tree Layout +![Bar demo](../../images/bar-demo.png) -👷🏗️🚧 +```yaml +plugins: + bar: + left_components: + entries: + - type: workspace_widget + + center_components: + entries: + - type: focused_window_widget + shorten_title: true + + right_components: + entries: + - type: battery_widget + - type: active_layout_widget + - type: date_time_widget + format: HH:mm:ss, dd MMM yyyy + - type: tree_layout_widget +``` + +## Configuration + +| Property | Description | +| ------------------- | ------------------------------------------------------------------------------- | +| `is_enabled` | Whether the plugin is enabled | +| `height` | The height of the bar in pixels. | +| `backdrop` | The backdrop to use for the bar - see [Backdrops](../core/styling.md#backdrops) | +| `left_components` | The widgets to display on the left side of the bar. | +| `center_components` | The widgets to display in the center of the bar. | +| `right_components` | The widgets to display on the right side of the bar. | + +## Components + +The `left_components`, `center_components`, and `right_components` properties have lists of components under the `entries` key. Each component has a `type` key that specifies the type of widget to use. The following widgets are available: + +- [Configuration](#configuration) +- [Components](#components) +- [Widgets](#widgets) + - [Active Layout Widget](#active-layout-widget) + - [Battery Widget](#battery-widget) + - [Date Time Widget](#date-time-widget) + - [Focused Window Widget](#focused-window-widget) + - [Workspace Widget](#workspace-widget) + - [Tree Layout Widget](#tree-layout-widget) + +## Widgets + +### Active Layout Widget + +The `ActiveLayoutWidget` displays the name of the current layout. + +### Battery Widget + +The `BatteryWidget` displays the battery percentage and status. + +### Date Time Widget + +The `DateTimeWidget` displays the current date and time. + +| Property | Description | +| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `interval` | The interval in milliseconds to update the date and time. | +| `format` | The format to display the date and time in. For more, see [Custom date and time format strings](https://docs.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings) | + +### Focused Window Widget + +The `FocusedWindowWidget` displays the title of the focused window. + +| Property | Description | +| --------------- | ------------------------------------------- | +| `shorten_title` | Whether to shorten the title of the window. | + +### Workspace Widget + +The `WorkspaceWidget` displays the name of the current workspace. + +### Tree Layout Widget + +The `TreeLayoutWidget` displays the direction to add windows in the tree layout engine on the current workspace. This will only show if the current layout in the workspace is a tree layout. diff --git a/docs/configure/plugins/command-palette.md b/docs/configure/plugins/command-palette.md index 5df1a8632..60b4b0155 100644 --- a/docs/configure/plugins/command-palette.md +++ b/docs/configure/plugins/command-palette.md @@ -16,12 +16,13 @@ plugins: ## Configuration -| Property | Description | -| -------------------- | -------------------------------------------------------------------------------- | -| `is_enabled` | Whether the plugin is enabled. Defaults to `true`. | -| `max_height_percent` | The maximum height of the command palette as a percentage of the monitor height. | -| `max_width_pixels` | The maximum width of the command palette in pixels. | -| `y_position_percent` | The y position of the command palette as a percentage of the monitor height. | +| Property | Description | +| -------------------- | -------------------------------------------------------------------------------------------- | +| `is_enabled` | Whether the plugin is enabled. Defaults to `true`. | +| `backdrop` | The backdrop to use for the command palette - see [Backdrops](../core/styling.md#backdrops). | +| `max_height_percent` | The maximum height of the command palette as a percentage of the monitor height. | +| `max_width_pixels` | The maximum width of the command palette in pixels. | +| `y_position_percent` | The y position of the command palette as a percentage of the monitor height. | [!INCLUDE [Commands](../../_includes/plugins/command-palette.md)] diff --git a/docs/script/plugins/bar.md b/docs/script/plugins/bar.md index 3d13cfb19..da21ec30b 100644 --- a/docs/script/plugins/bar.md +++ b/docs/script/plugins/bar.md @@ -1,6 +1,6 @@ # Bar Plugin -The adds a configurable bar at the top of a screen. +The adds a configurable bar at the top of each monitor. ![Bar demo](../../images/bar-demo.png) diff --git a/src/Whim.Bar/ActiveLayout/ActiveLayoutWidget.xaml.cs b/src/Whim.Bar/ActiveLayout/ActiveLayoutWidget.xaml.cs index b2bb4d19c..f5f1a853d 100644 --- a/src/Whim.Bar/ActiveLayout/ActiveLayoutWidget.xaml.cs +++ b/src/Whim.Bar/ActiveLayout/ActiveLayoutWidget.xaml.cs @@ -24,8 +24,15 @@ public ActiveLayoutWidget(IContext config, IMonitor monitor) /// /// Creates a new bar component which contains the active layout widget. /// - public static BarComponent CreateComponent() - { - return new BarComponent((context, monitor, window) => new ActiveLayoutWidget(context, monitor)); - } + public static BarComponent CreateComponent() => new ActiveLayoutComponent(); +} + +/// +/// The bar component for the active layout widget. +/// +public record ActiveLayoutComponent : BarComponent +{ + /// + public override UserControl CreateWidget(IContext context, IMonitor monitor, Microsoft.UI.Xaml.Window window) => + new ActiveLayoutWidget(context, monitor); } diff --git a/src/Whim.Bar/BarConfig.cs b/src/Whim.Bar/BarConfig.cs index c9a7b41b0..8fa99e114 100644 --- a/src/Whim.Bar/BarConfig.cs +++ b/src/Whim.Bar/BarConfig.cs @@ -7,10 +7,20 @@ namespace Whim.Bar; /// -/// Delegate for creating a component. -/// A component will subscribe to if it has resources to dispose. +/// A component to display in the bar. /// -public delegate UserControl BarComponent(IContext context, IMonitor monitor, Microsoft.UI.Xaml.Window window); +public abstract record BarComponent +{ + /// + /// Delegate for creating a component. + /// A component will subscribe to if it has resources to dispose. + /// + /// + /// + /// + /// + public abstract UserControl CreateWidget(IContext context, IMonitor monitor, Microsoft.UI.Xaml.Window window); +} /// /// Configuration for the bar plugin. @@ -21,9 +31,18 @@ namespace Whim.Bar; /// /// Creates a new bar configuration. /// -/// The components to display on the left side of the bar. -/// The components to display in the center of the bar. -/// The components to display on the right side of the bar. +/// +/// The components to display on the left side of the bar. Changes to this list will be respected until the +/// bar is initialized. +/// +/// +/// The components to display in the center of the bar. Changes to this list will be respected until the +/// bar is initialized. +/// +/// +/// The components to display on the right side of the bar. Changes to this list will be respected until the +/// bar is initialized. +/// public class BarConfig( IList leftComponents, IList centerComponents, @@ -34,19 +53,25 @@ IList rightComponents public event PropertyChangedEventHandler? PropertyChanged; /// - /// The components to display on the left side of the bar. + /// The components to display in the center of the bar. This will be set to the provided list, until + /// the bar is initialized. /// - internal IList LeftComponents = leftComponents; + public IReadOnlyList LeftComponents { get; private set; } = + (IReadOnlyList)leftComponents; /// - /// The components to display in the center of the bar. + /// The components to display in the center of the bar. This will be set to the provided list, until + /// the bar is initialized. /// - internal IList CenterComponents = centerComponents; + public IReadOnlyList CenterComponents { get; private set; } = + (IReadOnlyList)centerComponents; /// - /// The components to display on the right side of the bar. + /// The components to display in the center of the bar. This will be set to the provided list, until + /// the bar is initialized. /// - internal IList RightComponents = rightComponents; + public IReadOnlyList RightComponents { get; private set; } = + (IReadOnlyList)rightComponents; private int _height = GetHeightFromResourceDictionary() ?? 30; @@ -85,6 +110,17 @@ protected virtual void OnPropertyChanged(string propertyName) /// public WindowBackdropConfig Backdrop { get; set; } = new(BackdropType.Mica, AlwaysShowBackdrop: true); + /// + /// Freezes the configuration, making it immutable. + /// + /// + internal void Initialize() + { + LeftComponents = new List(LeftComponents); + CenterComponents = new List(CenterComponents); + RightComponents = new List(RightComponents); + } + private static int? GetHeightFromResourceDictionary() { try diff --git a/src/Whim.Bar/BarPlugin.cs b/src/Whim.Bar/BarPlugin.cs index 466fe3b1f..4c4886b18 100644 --- a/src/Whim.Bar/BarPlugin.cs +++ b/src/Whim.Bar/BarPlugin.cs @@ -14,7 +14,6 @@ namespace Whim.Bar; public class BarPlugin(IContext context, BarConfig barConfig) : IBarPlugin { private readonly IContext _context = context; - private readonly BarConfig _barConfig = barConfig; private readonly Dictionary _monitorBarMap = []; private bool _disposedValue; @@ -24,12 +23,17 @@ public class BarPlugin(IContext context, BarConfig barConfig) : IBarPlugin /// public string Name => "whim.bar"; + /// + public BarConfig Config => barConfig; + /// public void PreInitialize() { _context.MonitorManager.MonitorsChanged += MonitorManager_MonitorsChanged; _context.FilterManager.AddTitleMatchFilter("Whim Bar"); - _context.WorkspaceManager.AddProxyLayoutEngine(layout => new BarLayoutEngine(_barConfig, layout)); + _context.WorkspaceManager.AddProxyLayoutEngine(layout => new BarLayoutEngine(Config, layout)); + + Config.Initialize(); } /// @@ -37,7 +41,7 @@ public void PostInitialize() { foreach (IMonitor monitor in _context.MonitorManager) { - BarWindow barWindow = new(_context, _barConfig, monitor); + BarWindow barWindow = new(_context, Config, monitor); _monitorBarMap[monitor] = barWindow; } @@ -57,7 +61,7 @@ private void MonitorManager_MonitorsChanged(object? sender, MonitorsChangedEvent // Add the new monitors foreach (IMonitor monitor in e.AddedMonitors) { - BarWindow barWindow = new(_context, _barConfig, monitor); + BarWindow barWindow = new(_context, Config, monitor); _monitorBarMap[monitor] = barWindow; } diff --git a/src/Whim.Bar/BarWindow.xaml.cs b/src/Whim.Bar/BarWindow.xaml.cs index edda4ad25..02ecf3275 100644 --- a/src/Whim.Bar/BarWindow.xaml.cs +++ b/src/Whim.Bar/BarWindow.xaml.cs @@ -50,9 +50,11 @@ public BarWindow(IContext context, BarConfig barConfig, IMonitor monitor) _backdropController = new(this, barConfig.Backdrop); // Set up the bar. - LeftPanel.Children.AddRange(_barConfig.LeftComponents.Select(c => c(_context, _monitor, this))); - CenterPanel.Children.AddRange(_barConfig.CenterComponents.Select(c => c(_context, _monitor, this))); - RightPanel.Children.AddRange(_barConfig.RightComponents.Select(c => c(_context, _monitor, this))); + LeftPanel.Children.AddRange(_barConfig.LeftComponents.Select(c => c.CreateWidget(_context, _monitor, this))); + CenterPanel.Children.AddRange( + _barConfig.CenterComponents.Select(c => c.CreateWidget(_context, _monitor, this)) + ); + RightPanel.Children.AddRange(_barConfig.RightComponents.Select(c => c.CreateWidget(_context, _monitor, this))); } internal void UpdateRect() diff --git a/src/Whim.Bar/Battery/BatteryWidget.xaml.cs b/src/Whim.Bar/Battery/BatteryWidget.xaml.cs index eaaa41f2b..0a3bb3e2e 100644 --- a/src/Whim.Bar/Battery/BatteryWidget.xaml.cs +++ b/src/Whim.Bar/Battery/BatteryWidget.xaml.cs @@ -21,8 +21,15 @@ internal BatteryWidget(IContext context, IMonitor monitor) /// /// Creates a new bar component which contains the active layout widget. /// - public static BarComponent CreateComponent() - { - return new BarComponent((context, monitor, window) => new BatteryWidget(context, monitor)); - } + public static BarComponent CreateComponent() => new BatteryComponent(); +} + +/// +/// The bar component for the active layout widget. +/// +public record BatteryComponent : BarComponent +{ + /// + public override UserControl CreateWidget(IContext context, IMonitor monitor, Microsoft.UI.Xaml.Window window) => + new BatteryWidget(context, monitor); } diff --git a/src/Whim.Bar/DateTime/DateTimeWidget.xaml.cs b/src/Whim.Bar/DateTime/DateTimeWidget.xaml.cs index d9e9dc9c1..f907f80dc 100644 --- a/src/Whim.Bar/DateTime/DateTimeWidget.xaml.cs +++ b/src/Whim.Bar/DateTime/DateTimeWidget.xaml.cs @@ -30,8 +30,22 @@ internal DateTimeWidget(int interval, string format) /// /// /// - public static BarComponent CreateComponent(int interval = 100, string format = "HH:mm:ss dd-MMM-yyyy") - { - return new BarComponent((context, monitor, window) => new DateTimeWidget(interval, format)); - } + public static BarComponent CreateComponent(int interval = 100, string format = "HH:mm:ss dd-MMM-yyyy") => + new DateTimeComponent(interval, format); +} + +/// +/// The bar component for the date time widget. +/// +/// +/// The interval in milliseconds to update the date time. +/// +/// +/// The date time format. See Custom date and time format strings. +/// +public record DateTimeComponent(int IntervalMs, string Format) : BarComponent +{ + /// + public override UserControl CreateWidget(IContext context, IMonitor monitor, Microsoft.UI.Xaml.Window window) => + new DateTimeWidget(IntervalMs, Format); } diff --git a/src/Whim.Bar/FocusedWindow/FocusedWindowWidget.xaml.cs b/src/Whim.Bar/FocusedWindow/FocusedWindowWidget.xaml.cs index 172964e3f..d7cc26b11 100644 --- a/src/Whim.Bar/FocusedWindow/FocusedWindowWidget.xaml.cs +++ b/src/Whim.Bar/FocusedWindow/FocusedWindowWidget.xaml.cs @@ -27,12 +27,8 @@ internal FocusedWindowWidget(IContext context, IMonitor monitor, Func /// The function to get the title of the window. Defaults to . /// - public static BarComponent CreateComponent(Func? getTitle = null) - { - return new BarComponent( - (context, monitor, window) => new FocusedWindowWidget(context, monitor, getTitle ?? GetTitle) - ); - } + public static BarComponent CreateComponent(Func? getTitle = null) => + new FocusedWindowComponent(getTitle); /// /// Gets the full title of the window. @@ -59,3 +55,16 @@ public static string GetShortTitle(IWindow window) /// public static string? GetProcessName(IWindow window) => window.ProcessFileName?.Replace(".exe", ""); } + +/// +/// The bar component for the focused window widget. +/// +/// +/// The function to get the title of the window. Defaults to . +/// +public record FocusedWindowComponent(Func? GetTitle) : BarComponent +{ + /// + public override UserControl CreateWidget(IContext context, IMonitor monitor, Microsoft.UI.Xaml.Window window) => + new FocusedWindowWidget(context, monitor, GetTitle ?? FocusedWindowWidget.GetTitle); +} diff --git a/src/Whim.Bar/IBarPlugin.cs b/src/Whim.Bar/IBarPlugin.cs index 0cb90f5a2..9b91794a8 100644 --- a/src/Whim.Bar/IBarPlugin.cs +++ b/src/Whim.Bar/IBarPlugin.cs @@ -6,4 +6,10 @@ namespace Whim.Bar; /// BarPlugin displays an interactive bar at the top of the screen for Whim. It can be configured /// with various s to display on the left, center, and right sides of the bar. /// -public interface IBarPlugin : IPlugin, IDisposable { } +public interface IBarPlugin : IPlugin, IDisposable +{ + /// + /// The configuration for the bar. + /// + BarConfig Config { get; } +} diff --git a/src/Whim.Bar/Whim.Bar.csproj b/src/Whim.Bar/Whim.Bar.csproj index 49a058df5..9b7aca24c 100644 --- a/src/Whim.Bar/Whim.Bar.csproj +++ b/src/Whim.Bar/Whim.Bar.csproj @@ -44,6 +44,7 @@ + \ No newline at end of file diff --git a/src/Whim.Bar/Workspace/WorkspaceWidget.xaml.cs b/src/Whim.Bar/Workspace/WorkspaceWidget.xaml.cs index c5d9f5f6e..d8afee72b 100644 --- a/src/Whim.Bar/Workspace/WorkspaceWidget.xaml.cs +++ b/src/Whim.Bar/Workspace/WorkspaceWidget.xaml.cs @@ -32,10 +32,7 @@ private void Window_Closed(object? sender, Microsoft.UI.Xaml.WindowEventArgs e) /// /// Create the workspace widget bar component. /// - public static BarComponent CreateComponent() - { - return new BarComponent((context, monitor, window) => new WorkspaceWidget(context, monitor, window)); - } + public static BarComponent CreateComponent() => new WorkspaceComponent(); /// protected virtual void Dispose(bool disposing) @@ -62,3 +59,13 @@ public void Dispose() GC.SuppressFinalize(this); } } + +/// +/// The bar component for the workspace widget. +/// +public record WorkspaceComponent : BarComponent +{ + /// + public override UserControl CreateWidget(IContext context, IMonitor monitor, Microsoft.UI.Xaml.Window window) => + new WorkspaceWidget(context, monitor, window); +} diff --git a/src/Whim.TreeLayout.Bar/TreeLayoutBarPlugin.cs b/src/Whim.TreeLayout.Bar/TreeLayoutBarPlugin.cs index 8d3d0fb03..ee6c26c0d 100644 --- a/src/Whim.TreeLayout.Bar/TreeLayoutBarPlugin.cs +++ b/src/Whim.TreeLayout.Bar/TreeLayoutBarPlugin.cs @@ -1,4 +1,5 @@ using System.Text.Json; +using Microsoft.UI.Xaml.Controls; using Whim.Bar; namespace Whim.TreeLayout.Bar; @@ -32,12 +33,7 @@ public void PostInitialize() { } /// Create the tree layout engine bar component. /// /// - public BarComponent CreateComponent() - { - return new BarComponent( - (context, monitor, window) => new TreeLayoutEngineWidget(context, _plugin, monitor, window) - ); - } + public BarComponent CreateComponent() => new TreeLayoutComponent(_plugin); /// public void LoadState(JsonElement state) { } @@ -45,3 +41,13 @@ public void LoadState(JsonElement state) { } /// public JsonElement? SaveState() => null; } + +/// +/// The tree layout engine component for the bar. +/// +public record TreeLayoutComponent(ITreeLayoutPlugin Plugin) : BarComponent +{ + /// + public override UserControl CreateWidget(IContext context, IMonitor monitor, Microsoft.UI.Xaml.Window window) => + new TreeLayoutEngineWidget(context, Plugin, monitor, window); +} diff --git a/src/Whim.Yaml.Tests/Plugins/YamlLoader_LoadBarPluginTests.cs b/src/Whim.Yaml.Tests/Plugins/YamlLoader_LoadBarPluginTests.cs new file mode 100644 index 000000000..69f68084c --- /dev/null +++ b/src/Whim.Yaml.Tests/Plugins/YamlLoader_LoadBarPluginTests.cs @@ -0,0 +1,703 @@ +using FluentAssertions; +using NSubstitute; +using Whim.Bar; +using Whim.TestUtils; +using Whim.TreeLayout; +using Whim.TreeLayout.Bar; +using Xunit; + +namespace Whim.Yaml.Tests; + +public class YamlLoader_LoadBarPluginTests +{ + private static BarPlugin GetBarPlugin(IContext ctx, int idx = 0) + { + BarPlugin plugin = (ctx.PluginManager.ReceivedCalls().ElementAt(idx).GetArguments()[0] as BarPlugin)!; + plugin.Config.Initialize(); + return plugin; + } + + public static TheoryData BarIsEnabledConfig => + new() + { + // YAML, is_enabled: true + { + """ + plugins: + bar: + is_enabled: true + """, + // Is YAML + true + }, + // JSON, is_enabled: true + { + """ + { + "plugins": { + "bar": { + "is_enabled": true + } + } + } + """, + // Is JSON + false + }, + }; + + [Theory, MemberAutoSubstituteData(nameof(BarIsEnabledConfig))] + public void LoadBarPlugin_IsEnabled(string schema, bool isYaml, IContext ctx) + { + // Given a valid config with the bar plugin + YamlLoaderTestUtils.SetupFileConfig(ctx, schema, isYaml); + + // When loading the config + bool result = YamlLoader.Load(ctx); + + // Then the result is true, and the bar plugin is loaded + Assert.True(result); + ctx.PluginManager.Received(1).AddPlugin(Arg.Is(p => p.Config.Height == 30)); + } + + public static TheoryData BarIsNotEnabledConfig => + new() + { + // YAML, is_enabled: false + { + """ + plugins: + bar: + is_enabled: false + """, + true + }, + // JSON, is_enabled: false + { + """ + { + "plugins": { + "bar": { + "is_enabled": false + } + } + } + """, + false + }, + }; + + [Theory, MemberAutoSubstituteData(nameof(BarIsNotEnabledConfig))] + public void LoadBarPlugin_IsNotEnabled(string schema, bool isYaml, IContext ctx) + { + // Given a valid config with the bar plugin that is not enabled + YamlLoaderTestUtils.SetupFileConfig(ctx, schema, isYaml); + + // When loading the config + bool result = YamlLoader.Load(ctx); + + // Then the result is true, and the bar plugin is not loaded + Assert.True(result); + ctx.PluginManager.DidNotReceive().AddPlugin(Arg.Any()); + } + + public static TheoryData BarPluginIsNotValidConfig => + new() + { + // YAML + { + """ + plugins: + bar: + height: "not an int" + """, + true + }, + // JSON + { + """ + { + "plugins": { + "bar": { + "height": "not an int" + } + } + } + """, + false + }, + }; + + [Theory, MemberAutoSubstituteData(nameof(BarPluginIsNotValidConfig))] + public void LoadBarPlugin_ConfigIsNotValid_PluginIsNotLoaded(string schema, bool isYaml, IContext ctx) + { + // Given a config with the bar plugin that is not valid + YamlLoaderTestUtils.SetupFileConfig(ctx, schema, isYaml); + + // When loading the config + bool result = YamlLoader.Load(ctx); + + // Then the result is false, and the bar plugin is not loaded + Assert.True(result); + ctx.PluginManager.DidNotReceive().AddPlugin(Arg.Any()); + } + + public static TheoryData BarPluginHeightConfig => + new() + { + // YAML + { + """ + plugins: + bar: + height: 42 + """, + true + }, + // JSON + { + """ + { + "plugins": { + "bar": { + "height": 42 + } + } + } + """, + false + }, + }; + + [Theory, MemberAutoSubstituteData(nameof(BarPluginHeightConfig))] + public void LoadBarPlugin_ConfigHasHeight(string schema, bool isYaml, IContext ctx) + { + // Given a valid config with the bar plugin and a height + YamlLoaderTestUtils.SetupFileConfig(ctx, schema, isYaml); + + // When loading the config + bool result = YamlLoader.Load(ctx); + + // Then the result is true, and the bar plugin has the height + Assert.True(result); + ctx.PluginManager.Received(1).AddPlugin(Arg.Is(p => p.Config.Height == 42)); + } + + public static TheoryData ActiveLayoutBarWidgetConfig => + new() + { + // YAML + { + """ + plugins: + bar: + left_components: + entries: + - type: active_layout_widget + """, + true + }, + // JSON + { + """ + { + "plugins": { + "bar": { + "left_components": { + "entries": [ + { + "type": "active_layout_widget" + } + ] + } + } + } + } + """, + false + }, + }; + + [Theory, MemberAutoSubstituteData(nameof(ActiveLayoutBarWidgetConfig))] + public void LoadBarPlugin_ConfigHasActiveLayoutWidget(string schema, bool isYaml, IContext ctx) + { + // Given a valid config with the bar plugin and an active layout widget + YamlLoaderTestUtils.SetupFileConfig(ctx, schema, isYaml); + + // When loading the config + bool result = YamlLoader.Load(ctx); + + // Then the result is true, and the bar plugin has the active layout widget + Assert.True(result); + ctx.PluginManager.Received(1).AddPlugin(Arg.Is(p => p.Config.LeftComponents.Count == 1)); + + ActiveLayoutComponent activeLayoutComponent = (ActiveLayoutComponent)GetBarPlugin(ctx).Config.LeftComponents[0]; + Assert.Equal(new ActiveLayoutComponent(), activeLayoutComponent); + } + + public static TheoryData BatteryBarWidgetConfig => + new() + { + // YAML + { + """ + plugins: + bar: + left_components: + entries: + - type: battery_widget + """, + true + }, + // JSON + { + """ + { + "plugins": { + "bar": { + "left_components": { + "entries": [ + { + "type": "battery_widget" + } + ] + } + } + } + } + """, + false + }, + }; + + [Theory, MemberAutoSubstituteData(nameof(BatteryBarWidgetConfig))] + public void LoadBarPlugin_ConfigHasBatteryWidget(string schema, bool isYaml, IContext ctx) + { + // Given a valid config with the bar plugin and a battery widget + YamlLoaderTestUtils.SetupFileConfig(ctx, schema, isYaml); + + // When loading the config + bool result = YamlLoader.Load(ctx); + + // Then the result is true, and the bar plugin has the battery widget + Assert.True(result); + ctx.PluginManager.Received(1).AddPlugin(Arg.Is(p => p.Config.LeftComponents.Count == 1)); + + BatteryComponent batteryComponent = (BatteryComponent)GetBarPlugin(ctx).Config.LeftComponents[0]; + Assert.Equal(new BatteryComponent(), batteryComponent); + } + + public static TheoryData DateAndTimeBarWidgetConfig => + new() + { + // YAML, 1000ms, full date + { + """ + plugins: + bar: + left_components: + entries: + - type: date_time_widget + format: "yyyy-MM-dd HH:mm:ss" + """, + true, + 1000, + "yyyy-MM-dd HH:mm:ss" + }, + // JSON, 1000ms, full date + { + """ + { + "plugins": { + "bar": { + "left_components": { + "entries": [ + { + "type": "date_time_widget", + "format": "yyyy-MM-dd HH:mm:ss" + } + ] + } + } + } + } + """, + false, + 1000, + "yyyy-MM-dd HH:mm:ss" + }, + // YAML, 500ms, short date + { + """ + plugins: + bar: + left_components: + entries: + - type: date_time_widget + interval: 500 + format: "yyyy-MM-dd" + """, + true, + 500, + "yyyy-MM-dd" + }, + // JSON, 500ms, short date + { + """ + { + "plugins": { + "bar": { + "left_components": { + "entries": [ + { + "type": "date_time_widget", + "interval": 500, + "format": "yyyy-MM-dd" + } + ] + } + } + } + } + """, + false, + 500, + "yyyy-MM-dd" + }, + }; + + [Theory, MemberAutoSubstituteData(nameof(DateAndTimeBarWidgetConfig))] + public void LoadBarPlugin_ConfigHasDateTimeBarWidget( + string schema, + bool isYaml, + int interval, + string format, + IContext ctx + ) + { + // Given a valid config with the bar plugin and a date and time widget + YamlLoaderTestUtils.SetupFileConfig(ctx, schema, isYaml); + + // When loading the config + bool result = YamlLoader.Load(ctx); + + // Then the result is true, and the bar plugin has the date and time widget + Assert.True(result); + ctx.PluginManager.Received(1).AddPlugin(Arg.Is(p => p.Config.LeftComponents.Count == 1)); + + DateTimeComponent dateTimeComponent = (DateTimeComponent)GetBarPlugin(ctx).Config.LeftComponents[0]; + Assert.Equal(new DateTimeComponent(interval, format), dateTimeComponent); + } + + public static TheoryData FocusedWindowBarWidgetConfig => + new() + { + // YAML, show full name + { + """ + plugins: + bar: + left_components: + entries: + - type: focused_window_widget + shorten_title: false + """, + true, + false + }, + // JSON, show full name + { + """ + { + "plugins": { + "bar": { + "left_components": { + "entries": [ + { + "type": "focused_window_widget", + "shorten_title": false + } + ] + } + } + } + } + """, + false, + false + }, + // YAML, shorten title + { + """ + plugins: + bar: + left_components: + entries: + - type: focused_window_widget + shorten_title: true + """, + true, + true + }, + // JSON, shorten title + { + """ + { + "plugins": { + "bar": { + "left_components": { + "entries": [ + { + "type": "focused_window_widget", + "shorten_title": true + } + ] + } + } + } + } + """, + false, + true + }, + // YAML, show full name (default) + { + """ + plugins: + bar: + left_components: + entries: + - type: focused_window_widget + """, + true, + false + }, + }; + + [Theory, MemberAutoSubstituteData(nameof(FocusedWindowBarWidgetConfig))] + public void LoadBarPlugin_ConfigHasFocusedWindowWidget(string schema, bool isYaml, bool shortenTitle, IContext ctx) + { + // Given a valid config with the bar plugin and a focused window widget + YamlLoaderTestUtils.SetupFileConfig(ctx, schema, isYaml); + + // When loading the config + bool result = YamlLoader.Load(ctx); + + // Then the result is true, and the bar plugin has the focused window widget + Assert.True(result); + ctx.PluginManager.Received(1).AddPlugin(Arg.Is(p => p.Config.LeftComponents.Count == 1)); + + FocusedWindowComponent focusedWindowComponent = (FocusedWindowComponent) + GetBarPlugin(ctx).Config.LeftComponents[0]; + Assert.IsType(focusedWindowComponent); + + // Verify the title type. + string longTitle = "Window title | Very Long Application Name"; + string shortTitle = "Window title"; + string expectedTitle = shortenTitle ? shortTitle : longTitle; + + var focusedWindow = Substitute.For(); + focusedWindow.Title.Returns(longTitle); + + Assert.Equal(expectedTitle, focusedWindowComponent.GetTitle!(focusedWindow)); + } + + public static TheoryData WorkspaceBarWidgetConfig => + new() + { + // YAML + { + """ + plugins: + bar: + left_components: + entries: + - type: workspace_widget + """, + true + }, + // JSON + { + """ + { + "plugins": { + "bar": { + "left_components": { + "entries": [ + { + "type": "workspace_widget" + } + ] + } + } + } + } + """, + false + }, + }; + + [Theory, MemberAutoSubstituteData(nameof(WorkspaceBarWidgetConfig))] + public void LoadBarPlugin_ConfigHasWorkspaceWidget(string schema, bool isYaml, IContext ctx) + { + // Given a valid config with the bar plugin and a workspace widget + YamlLoaderTestUtils.SetupFileConfig(ctx, schema, isYaml); + + // When loading the config + bool result = YamlLoader.Load(ctx); + + // Then the result is true, and the bar plugin has the workspace widget + Assert.True(result); + ctx.PluginManager.Received(1).AddPlugin(Arg.Is(p => p.Config.LeftComponents.Count == 1)); + + WorkspaceComponent workspaceComponent = (WorkspaceComponent)GetBarPlugin(ctx).Config.LeftComponents[0]; + Assert.Equal(new WorkspaceComponent(), workspaceComponent); + } + + public static TheoryData TreeLayoutEngineBarWidgetConfig => + new() + { + // YAML + { + """ + plugins: + bar: + left_components: + entries: + - type: tree_layout_widget + """, + true + }, + // JSON + { + """ + { + "plugins": { + "bar": { + "left_components": { + "entries": [ + { + "type": "tree_layout_widget" + } + ] + } + } + } + } + """, + false + }, + }; + + [Theory, MemberAutoSubstituteData(nameof(TreeLayoutEngineBarWidgetConfig))] + public void LoadBarPlugin_ConfigHasTreeLayoutWidget(string schema, bool isYaml, IContext ctx) + { + // Given a valid config with the bar plugin and a tree layout engine widget + YamlLoaderTestUtils.SetupFileConfig(ctx, schema, isYaml); + + // When loading the config + bool result = YamlLoader.Load(ctx); + + // Then the result is true, and the bar plugin has the tree layout engine widget + Assert.True(result); + ctx.PluginManager.Received(1).AddPlugin(Arg.Is(p => p.Config.LeftComponents.Count == 1)); + + TreeLayoutComponent treeLayoutEngineComponent = (TreeLayoutComponent) + GetBarPlugin(ctx, 4).Config.LeftComponents[0]; + + new TreeLayoutComponent(new TreeLayoutPlugin(ctx)).Should().BeEquivalentTo(treeLayoutEngineComponent); + } + + public static TheoryData MultipleBarComponentsConfig => + new() + { + // YAML + { + """ + plugins: + bar: + left_components: + entries: + - type: active_layout_widget + center_components: + entries: + - type: date_time_widget + format: "yyyy-MM-dd HH:mm:ss" + right_components: + entries: + - type: focused_window_widget + - type: battery_widget + """, + true + }, + // JSON + { + """ + { + "plugins": { + "bar": { + "left_components": { + "entries": [ + { + "type": "active_layout_widget" + } + ] + }, + "center_components": { + "entries": [ + { + "type": "date_time_widget", + "format": "yyyy-MM-dd HH:mm:ss" + } + ] + }, + "right_components": { + "entries": [ + { + "type": "focused_window_widget" + }, + { + "type": "battery_widget" + } + ] + } + } + } + } + """, + false + }, + }; + + [Theory, MemberAutoSubstituteData(nameof(MultipleBarComponentsConfig))] + public void LoadBarPlugin_ConfigHasMultipleComponents(string schema, bool isYaml, IContext ctx) + { + // Given a valid config with the bar plugin and multiple components + YamlLoaderTestUtils.SetupFileConfig(ctx, schema, isYaml); + + // When loading the config + bool result = YamlLoader.Load(ctx); + + // Then the result is true, and the bar plugin has the multiple components + Assert.True(result); + ctx.PluginManager.Received(1).AddPlugin(Arg.Any()); + + BarPlugin plugin = GetBarPlugin(ctx); + + Assert.Single(plugin.Config.LeftComponents); + Assert.Single(plugin.Config.CenterComponents); + Assert.Equal(2, plugin.Config.RightComponents.Count); + + ActiveLayoutComponent activeLayoutComponent = (ActiveLayoutComponent)plugin.Config.LeftComponents[0]; + Assert.Equal(new ActiveLayoutComponent(), activeLayoutComponent); + + DateTimeComponent dateTimeComponent = (DateTimeComponent)plugin.Config.CenterComponents[0]; + Assert.Equal(new DateTimeComponent(1000, "yyyy-MM-dd HH:mm:ss"), dateTimeComponent); + + FocusedWindowComponent focusedWindowComponent = (FocusedWindowComponent)plugin.Config.RightComponents[0]; + Assert.Equal(new FocusedWindowComponent(FocusedWindowWidget.GetTitle), focusedWindowComponent); + + BatteryComponent batteryComponent = (BatteryComponent)plugin.Config.RightComponents[1]; + Assert.Equal(new BatteryComponent(), batteryComponent); + } +} diff --git a/src/Whim.Yaml.Tests/Plugins/YamlLoader_CommandPalettePluginTests.cs b/src/Whim.Yaml.Tests/Plugins/YamlLoader_LoadCommandPalettePluginTests.cs similarity index 99% rename from src/Whim.Yaml.Tests/Plugins/YamlLoader_CommandPalettePluginTests.cs rename to src/Whim.Yaml.Tests/Plugins/YamlLoader_LoadCommandPalettePluginTests.cs index f101bd8b0..e7f176bca 100644 --- a/src/Whim.Yaml.Tests/Plugins/YamlLoader_CommandPalettePluginTests.cs +++ b/src/Whim.Yaml.Tests/Plugins/YamlLoader_LoadCommandPalettePluginTests.cs @@ -5,7 +5,7 @@ namespace Whim.Yaml.Tests; -public class YamlPluginLoader_CommandPalettePluginTests +public class YamlPluginLoader_LoadCommandPalettePluginTests { private const int _defaultMaxHeightPercent = 40; private const int _defaultMaxWidthPixels = 800; diff --git a/src/Whim.Yaml.Tests/Plugins/YamlPluginLoader_GapsPluginTests.cs b/src/Whim.Yaml.Tests/Plugins/YamlLoader_LoadGapsPluginTests.cs similarity index 99% rename from src/Whim.Yaml.Tests/Plugins/YamlPluginLoader_GapsPluginTests.cs rename to src/Whim.Yaml.Tests/Plugins/YamlLoader_LoadGapsPluginTests.cs index 0d08c372b..59fafc02a 100644 --- a/src/Whim.Yaml.Tests/Plugins/YamlPluginLoader_GapsPluginTests.cs +++ b/src/Whim.Yaml.Tests/Plugins/YamlLoader_LoadGapsPluginTests.cs @@ -5,7 +5,7 @@ namespace Whim.Yaml.Tests; -public class YamlPluginLoader_GapsPluginTests +public class YamlLoader_LoadGapsPluginTests { private const int _defaultOuterGap = 0; private const int _defaultInnerGap = 10; diff --git a/src/Whim.Yaml.Tests/Plugins/YamlLoader_SliceLayoutPluginTests.cs b/src/Whim.Yaml.Tests/Plugins/YamlLoader_LoadSliceLayoutPluginTests.cs similarity index 98% rename from src/Whim.Yaml.Tests/Plugins/YamlLoader_SliceLayoutPluginTests.cs rename to src/Whim.Yaml.Tests/Plugins/YamlLoader_LoadSliceLayoutPluginTests.cs index 5d1186d25..7325bafaf 100644 --- a/src/Whim.Yaml.Tests/Plugins/YamlLoader_SliceLayoutPluginTests.cs +++ b/src/Whim.Yaml.Tests/Plugins/YamlLoader_LoadSliceLayoutPluginTests.cs @@ -5,7 +5,7 @@ namespace Whim.Yaml.Tests; -public class YamlPluginLoader_SliceLayoutPluginTests +public class YamlPluginLoader_LoadSliceLayoutPluginTests { public static TheoryData SliceLayoutConfig => new() diff --git a/src/Whim.Yaml.Tests/Plugins/YamlLoader_TreeLayoutPluginTests.cs b/src/Whim.Yaml.Tests/Plugins/YamlLoader_LoadTreeLayoutPluginTests.cs similarity index 100% rename from src/Whim.Yaml.Tests/Plugins/YamlLoader_TreeLayoutPluginTests.cs rename to src/Whim.Yaml.Tests/Plugins/YamlLoader_LoadTreeLayoutPluginTests.cs diff --git a/src/Whim.Yaml/Whim.Yaml.csproj b/src/Whim.Yaml/Whim.Yaml.csproj index 654909140..4192cf820 100644 --- a/src/Whim.Yaml/Whim.Yaml.csproj +++ b/src/Whim.Yaml/Whim.Yaml.csproj @@ -30,12 +30,14 @@ + + diff --git a/src/Whim.Yaml/YamlBarPluginLoader.cs b/src/Whim.Yaml/YamlBarPluginLoader.cs new file mode 100644 index 000000000..ef447d641 --- /dev/null +++ b/src/Whim.Yaml/YamlBarPluginLoader.cs @@ -0,0 +1,161 @@ +using System.Diagnostics.CodeAnalysis; +using Corvus.Json; +using Whim.Bar; +using Whim.TreeLayout; +using Whim.TreeLayout.Bar; + +namespace Whim.Yaml; + +internal static class YamlBarPluginLoader +{ + [SuppressMessage( + "Reliability", + "CA2000:Dispose objects before losing scope", + Justification = "Items will be disposed by the context where appropriate." + )] + public static void LoadBarPlugin(IContext ctx, Schema schema) + { + var bar = schema.Plugins.Bar; + + if (!bar.IsValid()) + { + Logger.Debug("Bar plugin is not valid."); + return; + } + + if (bar.IsEnabled.AsOptional() is { } isEnabled && !isEnabled) + { + Logger.Debug("Bar plugin is not enabled."); + return; + } + + List leftComponents = GetBarComponents(ctx, bar.LeftComponents); + List centerComponents = GetBarComponents(ctx, bar.CenterComponents); + List rightComponents = GetBarComponents(ctx, bar.RightComponents); + + BarConfig config = new(leftComponents, centerComponents, rightComponents); + + if (bar.Height.AsOptional() is { } height) + { + config.Height = (int)height; + } + + if (bar.Backdrop.AsOptional() is { } backdrop) + { + config.Backdrop = YamlLoaderUtils.ParseWindowBackdropConfig(backdrop); + } + + ctx.PluginManager.AddPlugin(new BarPlugin(ctx, config)); + } + + private static List GetBarComponents(IContext ctx, Schema.DefsRequiredEntries componentsWrapper) + { + if (componentsWrapper.Entries.AsOptional() is not { } entries) + { + return []; + } + + List components = []; + + foreach (var entry in entries) + { + entry.Match( + (in Schema.AWidgetToDisplayTheActiveLayout widget) => + { + components.Add(CreateActiveLayoutBarWidget(ctx, widget)); + return null; + }, + (in Schema.AWidgetToDisplayTheBatteryStatus widget) => + { + components.Add(CreateBatteryBarWidget(ctx, widget)); + return null; + }, + (in Schema.AWidgetToDisplayTheDateAndTime widget) => + { + components.Add(CreateDateTimeBarWidget(ctx, widget)); + return null; + }, + (in Schema.AWidgetToDisplayTheFocusedWindow widget) => + { + components.Add(CreateFocusedWindowBarWidget(ctx, widget)); + return null; + }, + (in Schema.AWidgetToDisplayTheWorkspace widget) => + { + components.Add(CreateWorkspaceBarWidget(ctx, widget)); + return null; + }, + (in Schema.DefsRequiredType2 widget) => + { + components.Add(CreateTreeLayoutEngineBarWidget(ctx, widget)); + return null; + }, + (in Schema.DefsRequiredType _) => + { + return null; + } + ); + } + + return components; + } + + private static BarComponent CreateActiveLayoutBarWidget(IContext ctx, Schema.AWidgetToDisplayTheActiveLayout widget) + { + return ActiveLayoutWidget.CreateComponent(); + } + + private static BarComponent CreateBatteryBarWidget(IContext ctx, Schema.AWidgetToDisplayTheBatteryStatus widget) + { + return BatteryWidget.CreateComponent(); + } + + private static BarComponent CreateDateTimeBarWidget(IContext ctx, Schema.AWidgetToDisplayTheDateAndTime widget) + { + int interval = (int?)widget.Interval.AsOptional() ?? 1000; + string format = (string?)widget.Format.AsOptional() ?? "yyyy-MM-dd HH:mm:ss"; + + return DateTimeWidget.CreateComponent(interval, format); + } + + private static BarComponent CreateFocusedWindowBarWidget( + IContext ctx, + Schema.AWidgetToDisplayTheFocusedWindow widget + ) + { + bool shortenTitle = widget.ShortenTitle.AsOptional() ?? false; + Func getTitle = shortenTitle + ? FocusedWindowWidget.GetShortTitle + : FocusedWindowWidget.GetTitle; + + return FocusedWindowWidget.CreateComponent(getTitle); + } + + private static BarComponent CreateWorkspaceBarWidget(IContext ctx, Schema.AWidgetToDisplayTheWorkspace widget) + { + return WorkspaceWidget.CreateComponent(); + } + + private static BarComponent CreateTreeLayoutEngineBarWidget(IContext ctx, Schema.DefsRequiredType2 widget) + { + if ( + ctx.PluginManager.LoadedPlugins.FirstOrDefault(p => p.Name == "whim.tree_layout") + is not TreeLayoutPlugin treeLayoutPlugin + ) + { + treeLayoutPlugin = new TreeLayoutPlugin(ctx); + ctx.PluginManager.AddPlugin(treeLayoutPlugin); + } + + if ( + ctx.PluginManager.LoadedPlugins.FirstOrDefault(p => p.Name == "whim.tree_layout.bar") + is not TreeLayoutBarPlugin treeLayoutBarPlugin + ) + { + treeLayoutBarPlugin = new TreeLayoutBarPlugin(treeLayoutPlugin); + ctx.PluginManager.AddPlugin(treeLayoutBarPlugin); + } + + return treeLayoutBarPlugin.CreateComponent(); + } +} diff --git a/src/Whim.Yaml/YamlPluginLoader.cs b/src/Whim.Yaml/YamlPluginLoader.cs index d376fd251..baa6721fd 100644 --- a/src/Whim.Yaml/YamlPluginLoader.cs +++ b/src/Whim.Yaml/YamlPluginLoader.cs @@ -31,6 +31,9 @@ public static void LoadPlugins(IContext ctx, Schema schema) LoadUpdaterPlugin(ctx, schema); LoadSliceLayoutPlugin(ctx, schema); LoadTreeLayoutPlugin(ctx, schema); + + // Load the bar plugin last, since it has dependencies on the TreeLayout plugin. + YamlBarPluginLoader.LoadBarPlugin(ctx, schema); } private static void LoadGapsPlugin(IContext ctx, Schema schema) diff --git a/src/Whim.Yaml/schema.json b/src/Whim.Yaml/schema.json index f31b4fa0e..27e5bda64 100644 --- a/src/Whim.Yaml/schema.json +++ b/src/Whim.Yaml/schema.json @@ -68,6 +68,11 @@ "plugins": { "type": "object", "properties": { + "bar": { + "$ref": "#/$defs/BarPlugin", + "description": "Plugin to add a bar" + }, + "gaps": { "$ref": "#/$defs/GapsPlugin", "description": "Plugin to add gaps between windows" @@ -286,11 +291,11 @@ "properties": { "command": { "type": "string", - "description": "The identifier of the command to run. For more, see https://dalyisaac.github.io/Whim/docs/customize/commands.html" + "description": "The identifier of the command to run. For more, see https://dalyisaac.github.io/Whim/configure/core/commands.html" }, "keybind": { "type": "string", - "description": "The keybind to trigger the command. For more, see https://dalyisaac.github.io/Whim/docs/customize/keybinds.html" + "description": "The keybind to trigger the command. For more, see https://dalyisaac.github.io/Whim/configure/core/keybinds.html" } } }, @@ -415,9 +420,166 @@ } }, + "BarPlugin": { + "type": "object", + "description": "Plugin to add a bar. For more, see https://dalyisaac.github.io/Whim/configure/plugins/bar.html", + "additionalProperties": false, + "properties": { + "is_enabled": { + "type": "boolean", + "description": "Whether the plugin is enabled", + "default": true + }, + "height": { + "type": "integer", + "description": "The height of the bar in pixels", + "default": 30 + }, + "backdrop": { + "$ref": "#/$defs/WindowBackdropConfig", + "description": "The backdrop to use for the bar" + }, + "left_components": { + "$ref": "#/$defs/BarWidgetList", + "description": "The widgets to display on the left side of the bar" + }, + "center_components": { + "$ref": "#/$defs/BarWidgetList", + "description": "The widgets to display in the center of the bar" + }, + "right_components": { + "$ref": "#/$defs/BarWidgetList", + "description": "The widgets to display on the right side of the bar" + } + } + }, + + "BarWidgetList": { + "type": "object", + "required": ["entries"], + "properties": { + "entries": { + "type": "array", + "items": { "$ref": "#/$defs/Widget" }, + "description": "Widgets to display on the bar" + } + } + }, + + "Widget": { + "required": ["type"], + "anyOf": [ + { + "$ref": "#/$defs/ActiveLayoutWidget" + }, + { + "$ref": "#/$defs/BatteryWidget" + }, + { + "$ref": "#/$defs/DateTimeWidget" + }, + { + "$ref": "#/$defs/FocusedWindowWidget" + }, + { + "$ref": "#/$defs/WorkspaceWidget" + }, + { + "$ref": "#/$defs/TreeLayoutEngineWidget" + } + ] + }, + + "ActiveLayoutWidget": { + "type": "object", + "description": "A widget to display the active layout", + "additionalProperties": false, + "required": ["type"], + "properties": { + "type": { + "const": "active_layout_widget" + } + } + }, + + "BatteryWidget": { + "type": "object", + "description": "A widget to display the battery status", + "additionalProperties": false, + "required": ["type"], + "properties": { + "type": { + "const": "battery_widget" + } + } + }, + + "DateTimeWidget": { + "type": "object", + "description": "A widget to display the date and time", + "additionalProperties": false, + "required": ["type"], + "properties": { + "type": { + "const": "date_time_widget" + }, + "interval": { + "type": "integer", + "description": "The interval in milliseconds to update the date and time", + "default": 1000 + }, + "format": { + "type": "string", + "description": "The format to display the date and time in. For more, see https://docs.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings", + "default": "HH:mm:ss dd-MMM-yyyy" + } + } + }, + + "FocusedWindowWidget": { + "type": "object", + "description": "A widget to display the focused window", + "additionalProperties": false, + "required": ["type"], + "properties": { + "type": { + "const": "focused_window_widget" + }, + "shorten_title": { + "type": "boolean", + "description": "Whether to shorten the title of the focused window", + "default": false + } + } + }, + + "WorkspaceWidget": { + "type": "object", + "description": "A widget to display the workspace", + "additionalProperties": false, + "required": ["type"], + "properties": { + "type": { + "const": "workspace_widget" + } + } + }, + + "TreeLayoutEngineWidget": { + "type": "object", + "description": "A widget to display the direction to add windows in the tree layout engine on the current workspace", + "additionalProperties": false, + "required": ["type"], + "properties": { + "type": { + "const": "tree_layout_widget" + } + } + }, + "GapsPlugin": { "type": "object", - "description": "Plugin to add gaps between windows. For more, see https://dalyisaac.github.io/Whim/docs/plugins/gaps.html", + "description": "Plugin to add gaps between windows. For more, see https://dalyisaac.github.io/Whim/configure/plugins/gaps.html", "additionalProperties": false, "properties": { "is_enabled": { @@ -450,7 +612,7 @@ "CommandPalettePlugin": { "type": "object", - "description": "Plugin to add a command palette. For more, see https://dalyisaac.github.io/Whim/docs/plugins/command-palette.html", + "description": "Plugin to add a command palette. For more, see https://dalyisaac.github.io/Whim/configure/plugins/command-palette.html", "additionalProperties": false, "properties": { "is_enabled": { @@ -482,7 +644,7 @@ "FocusIndicatorPlugin": { "type": "object", - "description": "Plugin to add a focus indicator. For more, see https://dalyisaac.github.io/Whim/docs/plugins/focus-indicator.html", + "description": "Plugin to add a focus indicator. For more, see https://https://dalyisaac.github.io/Whim/configure/plugins/command-palette.html", "additionalProperties": false, "properties": { "is_enabled": { @@ -515,7 +677,7 @@ "LayoutPreviewPlugin": { "type": "object", - "description": "Plugin to add a layout preview. For more, see https://dalyisaac.github.io/Whim/docs/plugins/layout-preview.html", + "description": "Plugin to add a layout preview. For more, see https://dalyisaac.github.io/Whim/configure/plugins/layout-preview.html", "additionalProperties": false, "properties": { "is_enabled": { @@ -528,7 +690,7 @@ "UpdaterPlugin": { "type": "object", - "description": "Plugin to add an updater. For more, see https://dalyisaac.github.io/Whim/docs/plugins/updater.html", + "description": "Plugin to add an updater. For more, see https://dalyisaac.github.io/Whim/configure/plugins/updater.html", "additionalProperties": false, "properties": { "is_enabled": { @@ -579,7 +741,7 @@ "SliceLayoutPlugin": { "type": "object", - "description": "Plugin to add a slice layout. For more, see https://dalyisaac.github.io/Whim/docs/plugins/slice-layout.html", + "description": "Plugin to add a slice layout. For more, see https://dalyisaac.github.io/Whim/configure/plugins/slice-layout.html", "additionalProperties": false, "properties": { "is_enabled": { @@ -592,7 +754,7 @@ "TreeLayoutPlugin": { "type": "object", - "description": "Plugin to add a tree layout. For more, see https://dalyisaac.github.io/Whim/docs/plugins/tree-layout.html", + "description": "Plugin to add a tree layout. For more, see https://dalyisaac.github.io/Whim/configure/plugins/tree-layout.html", "additionalProperties": false, "properties": { "is_enabled": {