Skip to content

Commit 82f077b

Browse files
committed
feat(settings): redesign SettingsDialog with NavigationView and extensibility API
- Replace TabControl layout with NavigationView grouped headers (Appearance, Performance, Logging, Info) with colored accent sections - Add 6 rich content pages: Theme, Status Bar, Rendering, Animations, Log Settings, System Info — using SharpDesk.Settings design pattern - Add SettingsRegistrationService with RegisterSettingsGroup/RegisterSettingsPage API for plugins and application code to add custom settings pages - Add AnimationManager.IsEnabled runtime toggle — disabling cancels active animations and instant-completes new ones - Add Actions sidebar category to Start menu for user-registered actions - Add gradient background and responsive NavigationView to settings window - DemoApp registers sample custom settings group via extensibility API
1 parent 49d39f5 commit 82f077b

13 files changed

Lines changed: 653 additions & 191 deletions

File tree

Examples/DemoApp/Program.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using SharpConsoleUI;
2+
using SharpConsoleUI.Builders;
23
using SharpConsoleUI.Configuration;
34
using SharpConsoleUI.Core;
45
using SharpConsoleUI.Drivers;
@@ -34,6 +35,21 @@ static async Task<int> Main(string[] args)
3435

3536
windowSystem.StatusBarStateService.TopStatus = "SharpConsoleUI Demo | Ctrl+T: Theme Selector";
3637

38+
// Register a sample custom settings group
39+
windowSystem.RegisterSettingsGroup("Demo", new Color(255, 200, 100), group =>
40+
{
41+
group.AddPage("About Demo", icon: "★", subtitle: "Demo application info",
42+
content: panel =>
43+
{
44+
panel.AddControl(Controls.Markup()
45+
.AddLine("[bold rgb(255,200,100)]Demo Application[/]")
46+
.AddEmptyLine()
47+
.AddLine("[dim]This is a sample custom settings page[/]")
48+
.AddLine("[dim]registered via the extensibility API.[/]")
49+
.Build());
50+
});
51+
});
52+
3753
LauncherWindow.Create(windowSystem);
3854

3955
windowSystem.Run();

SharpConsoleUI/Animation/AnimationManager.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,22 @@ public sealed class AnimationManager
1010
{
1111
private readonly List<IAnimation> _animations = new();
1212
private readonly object _lock = new();
13+
private bool _isEnabled = true;
14+
15+
/// <summary>
16+
/// Gets or sets whether animations are enabled. When disabled, new animations
17+
/// complete instantly (onUpdate called with final value, onComplete called).
18+
/// </summary>
19+
public bool IsEnabled
20+
{
21+
get => _isEnabled;
22+
set
23+
{
24+
_isEnabled = value;
25+
if (!value)
26+
CancelAll();
27+
}
28+
}
1329

1430
/// <summary>Number of currently running animations.</summary>
1531
public int ActiveCount
@@ -167,6 +183,16 @@ private IAnimation AddTween<T>(
167183
Action<T>? onUpdate,
168184
Action? onComplete)
169185
{
186+
// When disabled, call onUpdate with final value and onComplete immediately
187+
if (!_isEnabled)
188+
{
189+
onUpdate?.Invoke(to);
190+
onComplete?.Invoke();
191+
var noOp = new Tween<T>(from, to, TimeSpan.Zero, EasingFunctions.Linear, interpolator, null, null);
192+
noOp.Cancel();
193+
return noOp;
194+
}
195+
170196
var tween = new Tween<T>(
171197
from, to, duration,
172198
easing ?? EasingFunctions.EaseInOut,

SharpConsoleUI/Configuration/ControlDefaults.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -835,5 +835,11 @@ public static class ControlDefaults
835835
/// U+29C9: Two Joined Squares — rendered via MarkupParser for correct width handling.
836836
/// </summary>
837837
public const string StartMenuPluginsIcon = "\u29C9";
838+
839+
/// <summary>
840+
/// Icon for the "Actions" category in the Start menu sidebar.
841+
/// U+25B6: Black Right-Pointing Triangle — rendered via MarkupParser for correct width handling.
842+
/// </summary>
843+
public const string StartMenuActionsIcon = "\u25B6";
838844
}
839845
}

SharpConsoleUI/ConsoleWindowSystem.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ public class ConsoleWindowSystem
7777
private readonly InputStateService _inputStateService;
7878
private readonly NotificationStateService _notificationStateService;
7979
private readonly StatusBarStateService _statusBarStateService;
80+
private readonly SettingsRegistrationService _settingsRegistrationService = new();
8081
private readonly Core.DesktopPortalService _desktopPortalService;
8182

8283
// Plugin system
@@ -187,6 +188,7 @@ public ConsoleWindowSystem(IConsoleDriver driver, ITheme theme, PluginConfigurat
187188

188189
// Initialize options with environment variable fallback
189190
_options = options ?? ConsoleWindowSystemOptions.Create();
191+
Animations.IsEnabled = _options.EnableAnimations;
190192

191193
// Initialize state services BEFORE driver.Initialize() call
192194
_cursorStateService = new CursorStateService(_consoleDriver);
@@ -373,6 +375,11 @@ public IConsoleDriver ConsoleDriver
373375
/// </summary>
374376
public StatusBarStateService StatusBarStateService => _statusBarStateService;
375377

378+
/// <summary>
379+
/// Gets the settings registration service for adding custom settings pages.
380+
/// </summary>
381+
public SettingsRegistrationService SettingsRegistrationService => _settingsRegistrationService;
382+
376383
/// <summary>
377384
/// Gets the desktop portal service for managing desktop-level overlay portals.
378385
/// </summary>
@@ -918,5 +925,29 @@ private void UpdateStatusBarBounds()
918925

919926
#endregion
920927

928+
#region Settings Registration
929+
930+
/// <summary>
931+
/// Registers a settings group with multiple pages, shown in the Settings dialog.
932+
/// </summary>
933+
/// <param name="name">The display name of the settings group.</param>
934+
/// <param name="accentColor">The accent color used for this group in the navigation sidebar.</param>
935+
/// <param name="configure">Action that configures the group's pages via a builder.</param>
936+
public void RegisterSettingsGroup(string name, Color accentColor, Action<SettingsGroupBuilder> configure)
937+
=> _settingsRegistrationService.RegisterGroup(name, accentColor, configure);
938+
939+
/// <summary>
940+
/// Registers a single settings page under the "Extensions" group in the Settings dialog.
941+
/// </summary>
942+
/// <param name="name">The display name of the settings page.</param>
943+
/// <param name="icon">Optional icon character or string shown beside the page name.</param>
944+
/// <param name="subtitle">Optional subtitle shown below the page name.</param>
945+
/// <param name="content">Factory that populates the page content panel.</param>
946+
public void RegisterSettingsPage(string name, string? icon = null,
947+
string? subtitle = null, Action<ScrollablePanelControl>? content = null)
948+
=> _settingsRegistrationService.RegisterPage(name, icon, subtitle, content);
949+
950+
#endregion
951+
921952
}
922953
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
using SharpConsoleUI.Controls;
2+
3+
namespace SharpConsoleUI.Core;
4+
5+
/// <summary>
6+
/// Registration for a single settings page within a group.
7+
/// </summary>
8+
public record SettingsPageRegistration(
9+
string Name,
10+
string? Icon,
11+
string? Subtitle,
12+
Action<ScrollablePanelControl> ContentFactory);
13+
14+
/// <summary>
15+
/// Registration for a settings group containing one or more pages.
16+
/// </summary>
17+
public record SettingsGroupRegistration(
18+
string Name,
19+
Color AccentColor,
20+
List<SettingsPageRegistration> Pages);
21+
22+
/// <summary>
23+
/// Builder for configuring pages within a settings group during registration.
24+
/// </summary>
25+
public sealed class SettingsGroupBuilder
26+
{
27+
internal readonly List<SettingsPageRegistration> Pages = new();
28+
29+
/// <summary>
30+
/// Adds a page to this settings group.
31+
/// </summary>
32+
/// <param name="name">The display name of the settings page.</param>
33+
/// <param name="icon">Optional icon character or string shown beside the page name.</param>
34+
/// <param name="subtitle">Optional subtitle shown below the page name.</param>
35+
/// <param name="content">Factory that populates the page content panel.</param>
36+
/// <returns>This builder for method chaining.</returns>
37+
public SettingsGroupBuilder AddPage(string name, string? icon = null,
38+
string? subtitle = null, Action<ScrollablePanelControl>? content = null)
39+
{
40+
Pages.Add(new SettingsPageRegistration(name, icon, subtitle, content ?? (_ => { })));
41+
return this;
42+
}
43+
}
44+
45+
/// <summary>
46+
/// Stores custom settings group and page registrations for the Settings dialog.
47+
/// </summary>
48+
public sealed class SettingsRegistrationService
49+
{
50+
private readonly List<SettingsGroupRegistration> _groups = new();
51+
private static readonly Color DefaultExtensionsColor = new Color(100, 180, 100);
52+
53+
/// <summary>
54+
/// Gets all registered settings groups.
55+
/// </summary>
56+
public IReadOnlyList<SettingsGroupRegistration> Groups => _groups.AsReadOnly();
57+
58+
/// <summary>
59+
/// Registers a settings group with multiple pages.
60+
/// </summary>
61+
/// <param name="name">The display name of the settings group.</param>
62+
/// <param name="accentColor">The accent color used for this group in the navigation sidebar.</param>
63+
/// <param name="configure">Action that configures the group's pages via a builder.</param>
64+
public void RegisterGroup(string name, Color accentColor, Action<SettingsGroupBuilder> configure)
65+
{
66+
var builder = new SettingsGroupBuilder();
67+
configure(builder);
68+
_groups.Add(new SettingsGroupRegistration(name, accentColor, builder.Pages));
69+
}
70+
71+
/// <summary>
72+
/// Registers a single settings page under the "Extensions" group.
73+
/// Creates the Extensions group if it does not already exist.
74+
/// </summary>
75+
/// <param name="name">The display name of the settings page.</param>
76+
/// <param name="icon">Optional icon character or string shown beside the page name.</param>
77+
/// <param name="subtitle">Optional subtitle shown below the page name.</param>
78+
/// <param name="content">Factory that populates the page content panel.</param>
79+
public void RegisterPage(string name, string? icon = null,
80+
string? subtitle = null, Action<ScrollablePanelControl>? content = null)
81+
{
82+
var extGroup = _groups.Find(g => g.Name == "Extensions");
83+
if (extGroup == null)
84+
{
85+
extGroup = new SettingsGroupRegistration("Extensions", DefaultExtensionsColor, new List<SettingsPageRegistration>());
86+
_groups.Add(extGroup);
87+
}
88+
extGroup.Pages.Add(new SettingsPageRegistration(name, icon, subtitle, content ?? (_ => { })));
89+
}
90+
91+
/// <summary>
92+
/// Removes a registered settings group by name.
93+
/// </summary>
94+
/// <param name="name">The name of the group to remove.</param>
95+
public void UnregisterGroup(string name)
96+
{
97+
_groups.RemoveAll(g => g.Name == name);
98+
}
99+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using SharpConsoleUI.Builders;
2+
using SharpConsoleUI.Controls;
3+
using Ctl = SharpConsoleUI.Builders.Controls;
4+
5+
namespace SharpConsoleUI.Dialogs.Settings;
6+
7+
internal static class AnimationsPage
8+
{
9+
public static void Build(ScrollablePanelControl panel, ConsoleWindowSystem windowSystem)
10+
{
11+
var animationCount = windowSystem.Animations.ActiveCount;
12+
13+
panel.AddControl(Ctl.Markup()
14+
.AddLine("[bold rgb(252,152,103)]Animations[/]")
15+
.AddEmptyLine()
16+
.Build());
17+
18+
panel.AddControl(Ctl.RuleBuilder()
19+
.WithTitle("Settings")
20+
.WithColor(new Color(60, 100, 160))
21+
.Build());
22+
23+
panel.AddControl(Ctl.Checkbox("Enable animations")
24+
.Checked(windowSystem.Animations.IsEnabled)
25+
.OnCheckedChanged((sender, isChecked) =>
26+
{
27+
windowSystem.Animations.IsEnabled = isChecked;
28+
})
29+
.WithMargin(0, 1, 0, 0)
30+
.Build());
31+
32+
panel.AddControl(Ctl.RuleBuilder()
33+
.WithTitle("Status")
34+
.WithColor(new Color(60, 100, 160))
35+
.Build());
36+
37+
panel.AddControl(Ctl.Markup()
38+
.AddLine($"[bold]Active Animations:[/] {animationCount}")
39+
.AddEmptyLine()
40+
.AddLine("[dim]Animations include window transitions,[/]")
41+
.AddLine("[dim]navigation pane resizing, and control effects.[/]")
42+
.Build());
43+
}
44+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
using System.Runtime.InteropServices;
2+
using SharpConsoleUI.Builders;
3+
using SharpConsoleUI.Controls;
4+
using Ctl = SharpConsoleUI.Builders.Controls;
5+
6+
namespace SharpConsoleUI.Dialogs.Settings;
7+
8+
internal static class InfoPage
9+
{
10+
public static void Build(ScrollablePanelControl panel, ConsoleWindowSystem windowSystem)
11+
{
12+
var libVersion = typeof(ConsoleWindowSystem).Assembly.GetName().Version;
13+
var versionStr = libVersion != null
14+
? $"{libVersion.Major}.{libVersion.Minor}.{libVersion.Build}"
15+
: "0.0.1";
16+
17+
panel.AddControl(Ctl.Markup()
18+
.AddLine("[bold rgb(171,157,242)]System Information[/]")
19+
.AddEmptyLine()
20+
.Build());
21+
22+
panel.AddControl(Ctl.RuleBuilder()
23+
.WithTitle("Version")
24+
.WithColor(new Color(60, 100, 160))
25+
.Build());
26+
27+
panel.AddControl(Ctl.Markup()
28+
.AddLine($"[bold]SharpConsoleUI:[/] {versionStr}")
29+
.AddLine($"[bold].NET:[/] {RuntimeInformation.FrameworkDescription}")
30+
.AddLine($"[bold]OS:[/] {RuntimeInformation.OSDescription}")
31+
.AddLine($"[bold]Architecture:[/] {RuntimeInformation.OSArchitecture}")
32+
.AddEmptyLine()
33+
.Build());
34+
35+
panel.AddControl(Ctl.RuleBuilder()
36+
.WithTitle("Console")
37+
.WithColor(new Color(60, 100, 160))
38+
.Build());
39+
40+
var driver = windowSystem.ConsoleDriver;
41+
var screenSize = driver.ScreenSize;
42+
panel.AddControl(Ctl.Markup()
43+
.AddLine($"[bold]Terminal Size:[/] {screenSize.Width}x{screenSize.Height}")
44+
.AddLine($"[bold]Driver:[/] {driver.GetType().Name}")
45+
.AddEmptyLine()
46+
.Build());
47+
48+
panel.AddControl(Ctl.RuleBuilder()
49+
.WithTitle("Windows")
50+
.WithColor(new Color(60, 100, 160))
51+
.Build());
52+
53+
var windowCount = windowSystem.Windows.Count;
54+
var activeWindow = windowSystem.WindowStateService.ActiveWindow?.Title ?? "None";
55+
var modalCount = windowSystem.ModalStateService.HasModals ? "Yes" : "No";
56+
57+
panel.AddControl(Ctl.Markup()
58+
.AddLine($"[bold]Total Windows:[/] {windowCount}")
59+
.AddLine($"[bold]Active Window:[/] {activeWindow}")
60+
.AddLine($"[bold]Modals Active:[/] {modalCount}")
61+
.AddEmptyLine()
62+
.Build());
63+
64+
panel.AddControl(Ctl.RuleBuilder()
65+
.WithTitle("Plugins")
66+
.WithColor(new Color(60, 100, 160))
67+
.Build());
68+
69+
var pluginState = windowSystem.PluginStateService.CurrentState;
70+
var markupBuilder = Ctl.Markup()
71+
.AddLine($"[bold]Loaded:[/] {pluginState.LoadedPluginCount}");
72+
73+
if (pluginState.LoadedPluginCount > 0)
74+
{
75+
foreach (var name in pluginState.PluginNames)
76+
markupBuilder.AddLine($" [dim]• {name}[/]");
77+
}
78+
markupBuilder.AddEmptyLine();
79+
panel.AddControl(markupBuilder.Build());
80+
81+
panel.AddControl(Ctl.RuleBuilder()
82+
.WithTitle("Performance")
83+
.WithColor(new Color(60, 100, 160))
84+
.Build());
85+
86+
var perf = windowSystem.Performance;
87+
panel.AddControl(Ctl.Markup()
88+
.AddLine($"[bold]Current FPS:[/] {perf.CurrentFPS:F1}")
89+
.AddLine($"[bold]Frame Time:[/] {perf.CurrentFrameTimeMs:F1}ms")
90+
.AddLine($"[bold]Target FPS:[/] {perf.TargetFPS}")
91+
.AddLine($"[bold]Frame Limiting:[/] {(perf.IsFrameRateLimitingEnabled ? "On" : "Off")}")
92+
.Build());
93+
}
94+
}

0 commit comments

Comments
 (0)