Editor: profile rename/new, PC-output toggle, save & lamp feedback
- Profile name field in the editor (rename, with empty/duplicate checks);
"Edit profile" becomes a submenu listing each profile plus "New profile…".
- "Send button output to the PC" toggle: editor sessions route keyboard/mouse
and joystick through gates (GatedInputSink/GatedJoystickSink) that stay closed
until the user opts in, so editing never injects input by default.
- Save profile now confirms ("Saved ✓"/"Save failed") — the write was silent.
- A lit button renders dim at idle and bright only when physically pressed, so
the press stays visible (three states: off / dim-lit / bright-held).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
using RioJoy.Core.Calibration;
|
||||
|
||||
namespace RioJoy.Core.Mapping;
|
||||
|
||||
/// <summary>
|
||||
/// An <see cref="IInputSink"/> that forwards to an inner sink only while
|
||||
/// <see cref="Enabled"/> is set, and swallows output otherwise. Lets the editor put
|
||||
/// real keyboard/mouse output behind a toggle (off by default, so editing the RIO
|
||||
/// never injects input until the user opts in).
|
||||
/// </summary>
|
||||
public sealed class GatedInputSink : IInputSink
|
||||
{
|
||||
private readonly IInputSink _inner;
|
||||
|
||||
public GatedInputSink(IInputSink inner) =>
|
||||
_inner = inner ?? throw new ArgumentNullException(nameof(inner));
|
||||
|
||||
/// <summary>When false, all output is dropped.</summary>
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public void KeyDown(byte virtualKey, bool extended) { if (Enabled) _inner.KeyDown(virtualKey, extended); }
|
||||
|
||||
public void KeyUp(byte virtualKey, bool extended) { if (Enabled) _inner.KeyUp(virtualKey, extended); }
|
||||
|
||||
public void MouseMove(int dx, int dy) { if (Enabled) _inner.MouseMove(dx, dy); }
|
||||
|
||||
public void MouseButton(MouseButton button, bool down) { if (Enabled) _inner.MouseButton(button, down); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An <see cref="IJoystickSink"/> that forwards to an inner sink only while
|
||||
/// <see cref="Enabled"/> is set. The editor's output toggle gates the virtual
|
||||
/// joystick the same way as the keyboard/mouse.
|
||||
/// </summary>
|
||||
public sealed class GatedJoystickSink : IJoystickSink
|
||||
{
|
||||
private readonly IJoystickSink _inner;
|
||||
|
||||
public GatedJoystickSink(IJoystickSink inner) =>
|
||||
_inner = inner ?? throw new ArgumentNullException(nameof(inner));
|
||||
|
||||
/// <summary>When false, all output is dropped.</summary>
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public void SetButton(int button, bool pressed) { if (Enabled) _inner.SetButton(button, pressed); }
|
||||
|
||||
public void SetHat(RioHat position) { if (Enabled) _inner.SetHat(position); }
|
||||
|
||||
public void SetAxis(JoyAxis axis, int value) { if (Enabled) _inner.SetAxis(axis, value); }
|
||||
}
|
||||
@@ -110,19 +110,22 @@ public static class PanelView
|
||||
string? fn = function(b.Address);
|
||||
bool assigned = !string.IsNullOrEmpty(fn);
|
||||
|
||||
// A live (physically held) button lights bright in its own colour, as does
|
||||
// a lamp configured ON; otherwise the cell shows its idle/dim shade.
|
||||
// Three lamp states in the cell's own colour: a physically held button is
|
||||
// BRIGHT; a lamp configured on (IsLit) is DIM at idle (so the press still
|
||||
// reads brighter); everything else is OFF.
|
||||
bool yellow = b.Group.Title is "Secondary" or "Screen";
|
||||
bool bright = lit(b.Address) || live(b.Address);
|
||||
bool held = live(b.Address);
|
||||
bool litCfg = lit(b.Address);
|
||||
Color fill = !b.LampCapable
|
||||
? (live(b.Address) ? Color.FromArgb(205, 228, 255) // keypad — bright blue when pressed
|
||||
: Color.FromArgb(150, 182, 226)) // keypad — neutral blue
|
||||
? (held ? Color.FromArgb(205, 228, 255) // keypad — bright blue when pressed
|
||||
: Color.FromArgb(150, 182, 226)) // keypad — neutral blue
|
||||
: yellow
|
||||
? (bright ? Color.FromArgb(240, 205, 50) // Secondary/Screen — yellow lamp
|
||||
: Color.FromArgb(120, 102, 30)) // Secondary/Screen — dim yellow
|
||||
: bright
|
||||
? Color.FromArgb(215, 60, 60) // lamp ON (lit or held)
|
||||
: Color.FromArgb(86, 48, 48); // lamp OFF
|
||||
? (held ? Color.FromArgb(245, 210, 60) // Secondary/Screen — bright
|
||||
: litCfg ? Color.FromArgb(140, 118, 38) // dim (lamp on, idle)
|
||||
: Color.FromArgb(70, 60, 24)) // off
|
||||
: (held ? Color.FromArgb(230, 70, 70) // lamp — bright (held)
|
||||
: litCfg ? Color.FromArgb(120, 50, 50) // dim (lamp on, idle)
|
||||
: Color.FromArgb(64, 40, 40)); // off
|
||||
using (var fb = new SolidBrush(fill)) g.FillRectangle(fb, r);
|
||||
|
||||
Color fg = (b.LampCapable && !yellow) ? Color.White : Color.Black;
|
||||
|
||||
@@ -29,25 +29,27 @@ public sealed class ProfileEditorForm : Form
|
||||
private readonly RioProfile _profile;
|
||||
private readonly PanelCanvas _canvas = new();
|
||||
|
||||
private readonly Label _info = new() { AutoSize = true, Location = new Point(12, 12), MaximumSize = new Size(310, 0) };
|
||||
private readonly TextBox _labelBox = new() { Location = new Point(70, 44), Width = 230 };
|
||||
private readonly ComboBox _kindBox = new() { Location = new Point(70, 78), Width = 150, DropDownStyle = ComboBoxStyle.DropDownList };
|
||||
private readonly Label _valueLabel = new() { Text = "Key:", Location = new Point(12, 115), AutoSize = true };
|
||||
private readonly ComboBox _valueCombo = new() { Location = new Point(70, 112), Width = 230, DropDownStyle = ComboBoxStyle.DropDownList };
|
||||
private readonly CheckBox _shift = new() { Text = "Shift", Location = new Point(70, 144), AutoSize = true };
|
||||
private readonly CheckBox _ctrl = new() { Text = "Ctrl", Location = new Point(140, 144), AutoSize = true };
|
||||
private readonly CheckBox _alt = new() { Text = "Alt", Location = new Point(200, 144), AutoSize = true };
|
||||
private readonly CheckBox _ext = new() { Text = "Ext", Location = new Point(250, 144), AutoSize = true };
|
||||
private readonly CheckBox _lit = new() { Text = "Lit", Location = new Point(70, 172), AutoSize = true };
|
||||
private readonly Button _apply = new() { Text = "Apply to cell", Location = new Point(70, 206), Width = 110 };
|
||||
private readonly Button _unassign = new() { Text = "Unassign", Location = new Point(190, 206), Width = 110 };
|
||||
private readonly Button _save = new() { Text = "Save profile", Location = new Point(70, 240), Width = 110 };
|
||||
private readonly Button _close = new() { Text = "Close", Location = new Point(190, 240), Width = 80 };
|
||||
private readonly TextBox _nameBox = new() { Location = new Point(66, 12), Width = 234 };
|
||||
private readonly Label _info = new() { AutoSize = true, Location = new Point(12, 40), MaximumSize = new Size(310, 0) };
|
||||
private readonly TextBox _labelBox = new() { Location = new Point(70, 72), Width = 230 };
|
||||
private readonly ComboBox _kindBox = new() { Location = new Point(70, 106), Width = 150, DropDownStyle = ComboBoxStyle.DropDownList };
|
||||
private readonly Label _valueLabel = new() { Text = "Key:", Location = new Point(12, 143), AutoSize = true };
|
||||
private readonly ComboBox _valueCombo = new() { Location = new Point(70, 140), Width = 230, DropDownStyle = ComboBoxStyle.DropDownList };
|
||||
private readonly CheckBox _shift = new() { Text = "Shift", Location = new Point(70, 172), AutoSize = true };
|
||||
private readonly CheckBox _ctrl = new() { Text = "Ctrl", Location = new Point(140, 172), AutoSize = true };
|
||||
private readonly CheckBox _alt = new() { Text = "Alt", Location = new Point(200, 172), AutoSize = true };
|
||||
private readonly CheckBox _ext = new() { Text = "Ext", Location = new Point(250, 172), AutoSize = true };
|
||||
private readonly CheckBox _lit = new() { Text = "Lit", Location = new Point(70, 200), AutoSize = true };
|
||||
private readonly Button _apply = new() { Text = "Apply to cell", Location = new Point(70, 234), Width = 110 };
|
||||
private readonly Button _unassign = new() { Text = "Unassign", Location = new Point(190, 234), Width = 110 };
|
||||
private readonly Button _save = new() { Text = "Save profile", Location = new Point(70, 268), Width = 110 };
|
||||
private readonly Button _close = new() { Text = "Close", Location = new Point(190, 268), Width = 80 };
|
||||
private readonly CheckBox _outputToggle = new() { Text = "Send button output to the PC", Location = new Point(12, 300), AutoSize = true };
|
||||
|
||||
private readonly TextBox _statusBox = new()
|
||||
{
|
||||
Location = new Point(12, 566),
|
||||
Size = new Size(306, 190),
|
||||
Location = new Point(12, 618),
|
||||
Size = new Size(306, 172),
|
||||
Multiline = true,
|
||||
ReadOnly = true,
|
||||
ScrollBars = ScrollBars.Vertical,
|
||||
@@ -57,6 +59,9 @@ public sealed class ProfileEditorForm : Form
|
||||
Text = "Press \"Request version & status\" to query the RIO.",
|
||||
};
|
||||
|
||||
// Briefly flips the Save button to a confirmation, then reverts.
|
||||
private readonly System.Windows.Forms.Timer _saveFlash = new() { Interval = 1600 };
|
||||
|
||||
private int? _address;
|
||||
private bool _loading;
|
||||
|
||||
@@ -87,14 +92,24 @@ public sealed class ProfileEditorForm : Form
|
||||
/// <summary>Raised when the user saves; the argument is the edited profile.</summary>
|
||||
public event Action<RioProfile>? Saved;
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether a candidate profile name is free to use (i.e. not taken by a
|
||||
/// <em>different</em> profile). Set by the host; null = no uniqueness check.
|
||||
/// </summary>
|
||||
public Func<string, bool>? IsNameAvailable { get; set; }
|
||||
|
||||
/// <summary>Raised when the user clicks one of the live RIO-command buttons.</summary>
|
||||
public event Action<RioCommandCode>? CommandRequested;
|
||||
|
||||
/// <summary>Raised when the "send output to the PC" toggle changes (true = send).</summary>
|
||||
public event Action<bool>? OutputsEnabledChanged;
|
||||
|
||||
public ProfileEditorForm(RioProfile profile)
|
||||
{
|
||||
_profile = profile ?? throw new ArgumentNullException(nameof(profile));
|
||||
|
||||
Text = $"RIOJoy — Edit profile: {profile.Name}";
|
||||
_nameBox.Text = profile.Name;
|
||||
ClientSize = new Size(1320, 820);
|
||||
StartPosition = FormStartPosition.CenterScreen;
|
||||
MinimumSize = new Size(900, 500);
|
||||
@@ -119,8 +134,17 @@ public sealed class ProfileEditorForm : Form
|
||||
|
||||
_apply.Click += (_, _) => ApplyToCell();
|
||||
_unassign.Click += (_, _) => Unassign();
|
||||
_save.Click += (_, _) => { ApplyToCell(); Saved?.Invoke(_profile); };
|
||||
_save.Click += (_, _) => SaveProfile();
|
||||
_close.Click += (_, _) => Close();
|
||||
_outputToggle.CheckedChanged += (_, _) => OutputsEnabledChanged?.Invoke(_outputToggle.Checked);
|
||||
|
||||
_saveFlash.Tick += (_, _) =>
|
||||
{
|
||||
_saveFlash.Stop();
|
||||
_save.Text = "Save profile";
|
||||
_save.ForeColor = SystemColors.ControlText;
|
||||
};
|
||||
FormClosed += (_, _) => _saveFlash.Dispose();
|
||||
|
||||
SetEditingEnabled(false);
|
||||
_info.Text = "Click a button to edit it.";
|
||||
@@ -133,16 +157,18 @@ public sealed class ProfileEditorForm : Form
|
||||
{
|
||||
var panel = new Panel { Dock = DockStyle.Right, Width = 330, Padding = new Padding(8), BorderStyle = BorderStyle.FixedSingle, AutoScroll = true };
|
||||
|
||||
panel.Controls.Add(new Label { Text = "Profile:", Location = new Point(12, 15), AutoSize = true });
|
||||
panel.Controls.Add(_nameBox);
|
||||
panel.Controls.Add(_info);
|
||||
panel.Controls.Add(new Label { Text = "Label:", Location = new Point(12, 47), AutoSize = true });
|
||||
panel.Controls.Add(new Label { Text = "Label:", Location = new Point(12, 75), AutoSize = true });
|
||||
panel.Controls.Add(_labelBox);
|
||||
panel.Controls.Add(new Label { Text = "Action:", Location = new Point(12, 81), AutoSize = true });
|
||||
panel.Controls.Add(new Label { Text = "Action:", Location = new Point(12, 109), AutoSize = true });
|
||||
panel.Controls.Add(_kindBox);
|
||||
panel.Controls.Add(_valueLabel);
|
||||
panel.Controls.Add(_valueCombo);
|
||||
panel.Controls.AddRange(new Control[] { _shift, _ctrl, _alt, _ext, _lit, _apply, _unassign, _save, _close });
|
||||
panel.Controls.AddRange(new Control[] { _shift, _ctrl, _alt, _ext, _lit, _apply, _unassign, _save, _close, _outputToggle });
|
||||
panel.Controls.Add(BuildCommandGroup());
|
||||
panel.Controls.Add(new Label { Text = "RIO reply:", Location = new Point(12, 548), AutoSize = true });
|
||||
panel.Controls.Add(new Label { Text = "RIO reply:", Location = new Point(12, 600), AutoSize = true });
|
||||
panel.Controls.Add(_statusBox);
|
||||
|
||||
return panel;
|
||||
@@ -151,7 +177,7 @@ public sealed class ProfileEditorForm : Form
|
||||
// A button per RIO device command, fired against the live RIO via CommandRequested.
|
||||
private GroupBox BuildCommandGroup()
|
||||
{
|
||||
var group = new GroupBox { Text = "RIO commands (live)", Location = new Point(12, 278), Size = new Size(306, 262) };
|
||||
var group = new GroupBox { Text = "RIO commands (live)", Location = new Point(12, 330), Size = new Size(306, 262) };
|
||||
|
||||
int y = 24;
|
||||
foreach ((string label, RioCommandCode code) in RioCommands)
|
||||
@@ -309,6 +335,56 @@ public sealed class ProfileEditorForm : Form
|
||||
_canvas.Refresh(addr); // update lamp shade + label on the panel
|
||||
}
|
||||
|
||||
// Apply the pending cell edit, persist via the Saved handler, and confirm on the
|
||||
// button (the actual write happens in the host's Saved handler — config.json).
|
||||
private void SaveProfile()
|
||||
{
|
||||
if (!ApplyName())
|
||||
return;
|
||||
|
||||
ApplyToCell();
|
||||
try
|
||||
{
|
||||
Saved?.Invoke(_profile);
|
||||
_save.Text = "Saved ✓";
|
||||
_save.ForeColor = Color.ForestGreen;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_save.Text = "Save failed";
|
||||
_save.ForeColor = Color.Firebrick;
|
||||
MessageBox.Show(this, $"Could not save the profile:\n{ex.Message}",
|
||||
"RIOJoy", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
}
|
||||
_saveFlash.Stop();
|
||||
_saveFlash.Start();
|
||||
}
|
||||
|
||||
// Validate and apply the profile name from the name box. Returns false (and warns)
|
||||
// if the name is blank or already taken by another profile, so the save is aborted.
|
||||
private bool ApplyName()
|
||||
{
|
||||
string name = _nameBox.Text.Trim();
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
MessageBox.Show(this, "Profile name cannot be empty.", "RIOJoy",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool changed = !string.Equals(name, _profile.Name, StringComparison.OrdinalIgnoreCase);
|
||||
if (changed && IsNameAvailable?.Invoke(name) == false)
|
||||
{
|
||||
MessageBox.Show(this, $"A profile named \"{name}\" already exists.", "RIOJoy",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
return false;
|
||||
}
|
||||
|
||||
_profile.Name = name;
|
||||
Text = $"RIOJoy — Edit profile: {name}";
|
||||
return true;
|
||||
}
|
||||
|
||||
// Clear the selected button's action mapping (the label is left untouched).
|
||||
private void Unassign()
|
||||
{
|
||||
|
||||
@@ -31,6 +31,11 @@ public sealed class RioCoordinator : IDisposable
|
||||
private bool _auto = true;
|
||||
private bool _editorSession;
|
||||
|
||||
// While editing, real keyboard/mouse + joystick run through these gates (off until
|
||||
// the editor's "send output" toggle opens them).
|
||||
private GatedInputSink? _editorInput;
|
||||
private GatedJoystickSink? _editorJoystick;
|
||||
|
||||
// The active connection (null when dormant).
|
||||
private IRioTransport? _transport;
|
||||
private RioSerialLink? _link;
|
||||
@@ -116,6 +121,23 @@ public sealed class RioCoordinator : IDisposable
|
||||
Activate(profile, routeInput: false); // hold the port but don't inject keystrokes/joystick
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Open or close the editor's output gates: when enabled, RIO button presses
|
||||
/// drive real keyboard/mouse and the virtual joystick; when disabled they don't.
|
||||
/// No-op outside an editor session.
|
||||
/// </summary>
|
||||
public void SetEditorOutputs(bool enabled)
|
||||
{
|
||||
if (_editorInput is null || _editorJoystick is null)
|
||||
return;
|
||||
|
||||
_editorInput.Enabled = enabled;
|
||||
_editorJoystick.Enabled = enabled;
|
||||
|
||||
if (_activeProfileName is not null)
|
||||
SetStatus($"Editing: {_activeProfileName} — outputs {(enabled ? "ON (sending to PC)" : "off")}");
|
||||
}
|
||||
|
||||
/// <summary>Release the editor's port hold and hand control back to the watcher.</summary>
|
||||
public void EndEditorSession()
|
||||
{
|
||||
@@ -138,17 +160,31 @@ public sealed class RioCoordinator : IDisposable
|
||||
|
||||
try
|
||||
{
|
||||
// Keyboard/mouse + joystick are suppressed while editing so the RIO can be
|
||||
// exercised without injecting input; lamp feedback over serial still applies.
|
||||
// Keyboard/mouse + joystick are gated off while editing so the RIO can be
|
||||
// exercised without injecting input (the editor toggle opens the gates);
|
||||
// lamp feedback over serial still applies.
|
||||
IInputSink input;
|
||||
IJoystickSink joystick;
|
||||
string note;
|
||||
if (!routeInput)
|
||||
{
|
||||
input = new NullInputSink();
|
||||
joystick = new NullJoystickSink();
|
||||
_joystick = null;
|
||||
note = " [editing — input suppressed]";
|
||||
IJoystickSink realJoystick;
|
||||
if (HidFeederJoystickSink.TryCreate(out HidFeederJoystickSink? editorFeeder))
|
||||
{
|
||||
realJoystick = editorFeeder!;
|
||||
_joystick = editorFeeder;
|
||||
}
|
||||
else
|
||||
{
|
||||
realJoystick = new NullJoystickSink();
|
||||
_joystick = null;
|
||||
}
|
||||
|
||||
_editorInput = new GatedInputSink(new SendInputSink());
|
||||
_editorJoystick = new GatedJoystickSink(realJoystick);
|
||||
input = _editorInput;
|
||||
joystick = _editorJoystick;
|
||||
note = " [editing — outputs off]";
|
||||
}
|
||||
else if (HidFeederJoystickSink.TryCreate(out HidFeederJoystickSink? feeder))
|
||||
{
|
||||
@@ -252,6 +288,8 @@ public sealed class RioCoordinator : IDisposable
|
||||
|
||||
_joystick?.Dispose(); // closes the HID feeder handle
|
||||
_joystick = null;
|
||||
_editorInput = null;
|
||||
_editorJoystick = null;
|
||||
|
||||
_transport?.Dispose(); // releases the COM port
|
||||
_transport = null;
|
||||
|
||||
@@ -69,7 +69,11 @@ internal sealed class TrayApplicationContext : ApplicationContext
|
||||
RebuildProfileMenu(profileMenu);
|
||||
menu.Items.Add(profileMenu);
|
||||
|
||||
menu.Items.Add("Edit profile…", null, (_, _) => OpenEditor());
|
||||
var editMenu = new ToolStripMenuItem("Edit profile");
|
||||
editMenu.DropDownOpening += (_, _) => RebuildEditMenu(editMenu);
|
||||
RebuildEditMenu(editMenu);
|
||||
menu.Items.Add(editMenu);
|
||||
|
||||
menu.Items.Add("Import .ini…", null, (_, _) => ImportIniProfiles());
|
||||
|
||||
// RIO commands mirroring the legacy console menu.
|
||||
@@ -134,20 +138,50 @@ internal sealed class TrayApplicationContext : ApplicationContext
|
||||
profileMenu.DropDownItems.Add(dormant);
|
||||
}
|
||||
|
||||
private void OpenEditor()
|
||||
// Rebuild the "Edit profile" submenu: one entry per profile (opens it in the
|
||||
// editor) plus an entry to create a new one.
|
||||
private void RebuildEditMenu(ToolStripMenuItem editMenu)
|
||||
{
|
||||
// The cockpit panel layout is built in (CockpitPanel); the editor just needs
|
||||
// a profile to edit.
|
||||
RioProfile profile = _config.Profiles.FirstOrDefault() ?? AddBlankProfile();
|
||||
editMenu.DropDownItems.Clear();
|
||||
|
||||
foreach (RioProfile profile in _config.Profiles)
|
||||
{
|
||||
RioProfile captured = profile;
|
||||
editMenu.DropDownItems.Add(new ToolStripMenuItem(profile.Name, null, (_, _) => OpenEditor(captured)));
|
||||
}
|
||||
|
||||
if (_config.Profiles.Count > 0)
|
||||
editMenu.DropDownItems.Add(new ToolStripSeparator());
|
||||
|
||||
editMenu.DropDownItems.Add(new ToolStripMenuItem("New profile…", null, (_, _) => OpenEditor(NewProfile())));
|
||||
}
|
||||
|
||||
// Create, register and persist a fresh empty profile with a unique name.
|
||||
private RioProfile NewProfile()
|
||||
{
|
||||
string name = "New profile";
|
||||
for (int i = 2; _config.FindProfile(name) is not null; i++)
|
||||
name = $"New profile {i}";
|
||||
|
||||
var profile = new RioProfile { Name = name };
|
||||
_config.Profiles.Add(profile);
|
||||
ConfigStore.Save(_config, ConfigPath);
|
||||
return profile;
|
||||
}
|
||||
|
||||
private void OpenEditor(RioProfile profile)
|
||||
{
|
||||
// Acquiring the RIO is one of the two reasons we take the serial port (the
|
||||
// other being a profiled game) — so the editor can check button function. Input
|
||||
// is suppressed (no keystrokes); the editor only shows which button is pressed.
|
||||
_coordinator.BeginEditorSession(profile);
|
||||
|
||||
var editor = new ProfileEditorForm(profile);
|
||||
editor.IsNameAvailable = name => !_config.Profiles.Any(
|
||||
p => !ReferenceEquals(p, profile) && string.Equals(p.Name, name, StringComparison.OrdinalIgnoreCase));
|
||||
editor.Saved += _ => ConfigStore.Save(_config, ConfigPath);
|
||||
editor.CommandRequested += cmd => _coordinator.Runtime?.Trigger(cmd);
|
||||
editor.OutputsEnabledChanged += enabled => _coordinator.SetEditorOutputs(enabled);
|
||||
|
||||
RioRuntime? runtime = _coordinator.Runtime;
|
||||
Action<int, bool>? activity = null;
|
||||
@@ -174,13 +208,6 @@ internal sealed class TrayApplicationContext : ApplicationContext
|
||||
editor.Show();
|
||||
}
|
||||
|
||||
private RioProfile AddBlankProfile()
|
||||
{
|
||||
var profile = new RioProfile { Name = "New profile" };
|
||||
_config.Profiles.Add(profile);
|
||||
return profile;
|
||||
}
|
||||
|
||||
// Import one or more legacy RIO.ini files (the gsheet "INI_V2" format) as
|
||||
// profiles, naming each after its filename and de-duping collisions.
|
||||
private void ImportIniProfiles()
|
||||
|
||||
Reference in New Issue
Block a user