refactor(app): compose settings and developer tools

This commit is contained in:
Erik 2026-07-22 16:11:34 +02:00
parent 60a1698ce7
commit cd7b519f78
24 changed files with 2073 additions and 333 deletions

View file

@ -0,0 +1,45 @@
using AcDream.UI.Abstractions.Input;
namespace AcDream.App.Settings;
internal interface IRuntimeKeyBindingTarget
{
void Apply(KeyBindings bindings);
}
/// <summary>
/// Applies a settings-panel keymap to the live dispatcher and then persists
/// that exact map. Persistence failure is reported without rolling back the
/// already accepted live binding, matching the former startup-root callback.
/// </summary>
internal sealed class RuntimeKeyBindingTarget : IRuntimeKeyBindingTarget
{
private readonly InputDispatcher _dispatcher;
private readonly string _path;
private readonly Action<string> _log;
public RuntimeKeyBindingTarget(
InputDispatcher dispatcher,
string? path = null,
Action<string>? log = null)
{
_dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
_path = path ?? KeyBindings.DefaultPath();
_log = log ?? Console.WriteLine;
}
public void Apply(KeyBindings bindings)
{
ArgumentNullException.ThrowIfNull(bindings);
_dispatcher.SetBindings(bindings);
try
{
bindings.SaveToFile(_path);
_log($"keybinds: saved to {_path}");
}
catch (Exception failure)
{
_log($"keybinds: save failed: {failure.Message}");
}
}
}

View file

@ -79,6 +79,34 @@ internal sealed record RuntimeSettingsSnapshot(
CharacterSettings Character,
QualitySettings Quality);
/// <summary>
/// Expected-owner lease for the optional developer settings view model.
/// Failed optional composition can withdraw only the instance it installed.
/// </summary>
internal sealed class RuntimeSettingsViewModelBinding : IDisposable
{
private readonly RuntimeSettingsController _owner;
private bool _disposed;
public RuntimeSettingsViewModelBinding(
RuntimeSettingsController owner,
SettingsVM viewModel)
{
_owner = owner ?? throw new ArgumentNullException(nameof(owner));
ViewModel = viewModel ?? throw new ArgumentNullException(nameof(viewModel));
}
public SettingsVM ViewModel { get; }
public void Dispose()
{
if (_disposed)
return;
_owner.UnbindViewModel(ViewModel);
_disposed = true;
}
}
internal interface IRuntimeSettingsStartupTarget
{
void ApplyDisplay(DisplaySettings display);
@ -248,6 +276,14 @@ internal sealed class RuntimeSettingsController :
return _viewModel;
}
public RuntimeSettingsViewModelBinding CreateViewModelBinding(
KeyBindings persistedBindings,
InputDispatcher dispatcher,
Action<KeyBindings> saveBindings) =>
new(
this,
CreateViewModel(persistedBindings, dispatcher, saveBindings));
public void UnbindViewModel(SettingsVM? expected = null)
{
if (expected is null || ReferenceEquals(_viewModel, expected))