218 lines
6.5 KiB
C#
218 lines
6.5 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Linq;
|
|
using System.Windows.Input;
|
|
using ytLive.Helpers;
|
|
using ytLive.Services;
|
|
|
|
namespace ytLive.ViewModels;
|
|
|
|
/// <summary>One row in the hotkey config dialog — an action and its current chord.</summary>
|
|
public sealed class HotkeyBindingRow : ViewModelBase
|
|
{
|
|
public HotkeyId Id { get; }
|
|
public string ActionName { get; }
|
|
|
|
private HotkeyChord? _chord;
|
|
public HotkeyChord? Chord
|
|
{
|
|
get => _chord;
|
|
set
|
|
{
|
|
if (SetProperty(ref _chord, value))
|
|
OnPropertyChanged(nameof(DisplayText));
|
|
}
|
|
}
|
|
|
|
private bool _isCapturing;
|
|
public bool IsCapturing
|
|
{
|
|
get => _isCapturing;
|
|
set
|
|
{
|
|
if (SetProperty(ref _isCapturing, value))
|
|
OnPropertyChanged(nameof(DisplayText));
|
|
}
|
|
}
|
|
|
|
public string DisplayText => IsCapturing
|
|
? "▶ Press a key combination..."
|
|
: HotkeyChord.DisplayString(Chord);
|
|
|
|
public HotkeyBindingRow(HotkeyId id, HotkeyChord? chord)
|
|
{
|
|
Id = id;
|
|
ActionName = GetActionName(id);
|
|
Chord = chord;
|
|
}
|
|
|
|
private static string GetActionName(HotkeyId id) => id switch
|
|
{
|
|
HotkeyId.SceneStarting => "Scene: Starting",
|
|
HotkeyId.SceneLive => "Scene: Live",
|
|
HotkeyId.SceneBrb => "Scene: BRB",
|
|
HotkeyId.SceneChat => "Scene: Chat",
|
|
HotkeyId.SceneEnding => "Scene: Ending",
|
|
HotkeyId.StartStopStream => "Start / Stop Stream",
|
|
HotkeyId.ToggleMicMute => "Toggle Mic Mute",
|
|
HotkeyId.ToggleGameMute => "Toggle Desktop Audio Mute",
|
|
HotkeyId.TraxPlayPause => "TRAX Play / Pause",
|
|
_ => id.ToString(),
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// ViewModel for the hotkey config dialog. Manages the working copy of
|
|
/// bindings, capture mode, conflict detection, and save/cancel/reset.
|
|
/// </summary>
|
|
public sealed class HotkeyConfigViewModel : ViewModelBase
|
|
{
|
|
private readonly LayoutStore _store;
|
|
private readonly Dictionary<HotkeyId, HotkeyChord?> _original;
|
|
|
|
public ObservableCollection<HotkeyBindingRow> Rows { get; }
|
|
|
|
public event Action? SaveRequested;
|
|
public event Action? CancelRequested;
|
|
|
|
private string? _conflictMessage;
|
|
public string? ConflictMessage
|
|
{
|
|
get => _conflictMessage;
|
|
set => SetProperty(ref _conflictMessage, value);
|
|
}
|
|
|
|
private HotkeyBindingRow? _capturingRow;
|
|
public HotkeyBindingRow? CapturingRow
|
|
{
|
|
get => _capturingRow;
|
|
private set => SetProperty(ref _capturingRow, value);
|
|
}
|
|
|
|
public ICommand StartCaptureCommand { get; }
|
|
public ICommand UnbindCommand { get; }
|
|
public ICommand ResetToDefaultsCommand { get; }
|
|
public ICommand SaveCommand { get; }
|
|
public ICommand CancelCommand { get; }
|
|
|
|
public HotkeyConfigViewModel(LayoutStore store)
|
|
{
|
|
_store = store;
|
|
ConflictMessage = null;
|
|
|
|
// Load saved bindings — keys present in DB are either custom or "none" (unbound).
|
|
var saved = store.LoadHotkeyBindings();
|
|
|
|
// Build working copy: start from defaults, overlay saved, fill gaps.
|
|
_original = new Dictionary<HotkeyId, HotkeyChord?>();
|
|
foreach (HotkeyId id in Enum.GetValues(typeof(HotkeyId)))
|
|
{
|
|
if (saved.TryGetValue(id, out var chord))
|
|
_original[id] = chord; // custom or null (explicitly unbound)
|
|
else
|
|
_original[id] = GlobalHotkeyManager.Defaults.TryGetValue(id, out var def)
|
|
? def
|
|
: null;
|
|
}
|
|
|
|
Rows = new ObservableCollection<HotkeyBindingRow>(
|
|
_original.Select(kv => new HotkeyBindingRow(kv.Key, kv.Value)));
|
|
|
|
StartCaptureCommand = new RelayCommand(row =>
|
|
{
|
|
if (row is HotkeyBindingRow r) StartCapture(r);
|
|
});
|
|
UnbindCommand = new RelayCommand(row =>
|
|
{
|
|
if (row is HotkeyBindingRow r) Unbind(r);
|
|
});
|
|
ResetToDefaultsCommand = new RelayCommand(_ => ResetToDefaults());
|
|
SaveCommand = new RelayCommand(_ => Save());
|
|
CancelCommand = new RelayCommand(_ => CancelRequested?.Invoke());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called by the dialog after it captures a key combo from the user.
|
|
/// Assigns the chord if free; shows a conflict warning otherwise.
|
|
/// </summary>
|
|
public void AssignCapturedChord(HotkeyChord chord)
|
|
{
|
|
if (CapturingRow == null) return;
|
|
|
|
// Same chord as current — no-op
|
|
if (CapturingRow.Chord != null && CapturingRow.Chord.Equals(chord))
|
|
{
|
|
CapturingRow.IsCapturing = false;
|
|
CapturingRow = null;
|
|
return;
|
|
}
|
|
|
|
// Conflict: another action already uses this chord
|
|
var conflict = Rows.FirstOrDefault(r =>
|
|
r.Id != CapturingRow.Id && r.Chord != null && r.Chord.Equals(chord));
|
|
if (conflict != null)
|
|
{
|
|
ConflictMessage = $"{chord.ToDisplayString()} is already bound to {conflict.ActionName}. Unbind it first.";
|
|
CapturingRow.IsCapturing = false;
|
|
CapturingRow = null;
|
|
return;
|
|
}
|
|
|
|
// Free — assign it
|
|
ConflictMessage = null;
|
|
CapturingRow.Chord = chord;
|
|
CapturingRow.IsCapturing = false;
|
|
CapturingRow = null;
|
|
}
|
|
|
|
/// <summary>Cancel capture mode without changing the binding.</summary>
|
|
public void CancelCapture()
|
|
{
|
|
if (CapturingRow == null) return;
|
|
CapturingRow.IsCapturing = false;
|
|
CapturingRow = null;
|
|
ConflictMessage = null;
|
|
}
|
|
|
|
private void StartCapture(HotkeyBindingRow row)
|
|
{
|
|
CancelCapture(); // cancel any in-progress capture first
|
|
ConflictMessage = null;
|
|
row.IsCapturing = true;
|
|
CapturingRow = row;
|
|
}
|
|
|
|
private void Unbind(HotkeyBindingRow row)
|
|
{
|
|
CancelCapture();
|
|
ConflictMessage = null;
|
|
row.Chord = null;
|
|
}
|
|
|
|
private void ResetToDefaults()
|
|
{
|
|
CancelCapture();
|
|
ConflictMessage = null;
|
|
foreach (var row in Rows)
|
|
{
|
|
row.Chord = GlobalHotkeyManager.Defaults.TryGetValue(row.Id, out var def)
|
|
? def
|
|
: null;
|
|
}
|
|
}
|
|
|
|
private void Save()
|
|
{
|
|
CancelCapture();
|
|
ConflictMessage = null;
|
|
|
|
var bindings = new Dictionary<HotkeyId, HotkeyChord?>();
|
|
foreach (var row in Rows)
|
|
bindings[row.Id] = row.Chord;
|
|
|
|
_store.SaveHotkeyBindings(bindings);
|
|
SaveRequested?.Invoke();
|
|
}
|
|
}
|