Compare commits
41
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0733b1ab02 | ||
|
|
4e54668092 | ||
|
|
74abd4f448 | ||
|
|
b85be454ce | ||
|
|
1f3dd72214 | ||
|
|
c4fcd986c0 | ||
|
|
ea21d37089 | ||
|
|
2f133720cc | ||
|
|
250c90fa02 | ||
|
|
7e2c5d2f45 | ||
|
|
9fed3fb192 | ||
|
|
0d85f933c7 | ||
|
|
f235ddf0f1 | ||
|
|
887cd45b4c | ||
|
|
aac2f220d6 | ||
|
|
90f15cc1e3 | ||
|
|
292b66af4d | ||
|
|
b6d835984c | ||
|
|
369b5ddacf | ||
|
|
6be29f49d7 | ||
|
|
8131140cb0 | ||
|
|
304314230c | ||
|
|
0ce605f122 | ||
|
|
d4aadd7dd4 | ||
|
|
3cf3e0a330 | ||
|
|
ff2cfc36b6 | ||
|
|
b118c66959 | ||
|
|
131e3536f1 | ||
|
|
b47b544065 | ||
|
|
b13c40e7e8 | ||
|
|
4e47fcef72 | ||
|
|
8acf618fd4 | ||
|
|
49e09f3f8b | ||
|
|
9bf8284d9f | ||
|
|
0f8aac81de | ||
|
|
abec73b868 | ||
|
|
86486c99f2 | ||
|
|
d9b7052e87 | ||
|
|
0efe105321 | ||
|
|
ed0dad960d | ||
|
|
2f58ed63de |
No files matched your search
@@ -23,7 +23,7 @@ namespace FluentTerminal.App.Services.Test
|
||||
public void ShowCreateKeyBindingDialog_Default_UsesCreateKeyBindingDialog()
|
||||
{
|
||||
var createKeyBindingDialog = new Mock<ICreateKeyBindingDialog>();
|
||||
var dialogService = new DialogService(Mock.Of<IShellProfileSelectionDialog>, Mock.Of<IMessageDialog>, () => createKeyBindingDialog.Object);
|
||||
var dialogService = new DialogService(Mock.Of<IShellProfileSelectionDialog>, Mock.Of<IMessageDialog>, () => createKeyBindingDialog.Object, Mock.Of<IInputDialog>);
|
||||
|
||||
dialogService.ShowCreateKeyBindingDialog();
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace FluentTerminal.App.Services.Test
|
||||
var title = string.Empty;
|
||||
var content = _fixture.Create<string>();
|
||||
var buttons = _fixture.CreateMany<DialogButton>(2);
|
||||
var dialogService = new DialogService(Mock.Of<IShellProfileSelectionDialog>, Mock.Of<IMessageDialog>, Mock.Of<ICreateKeyBindingDialog>);
|
||||
var dialogService = new DialogService(Mock.Of<IShellProfileSelectionDialog>, Mock.Of<IMessageDialog>, Mock.Of<ICreateKeyBindingDialog>, Mock.Of<IInputDialog>);
|
||||
|
||||
Func<Task<DialogButton>> showMessageDialogAsnyc = () => dialogService.ShowMessageDialogAsnyc(title, content, buttons.ElementAt(0), buttons.ElementAt(1));
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace FluentTerminal.App.Services.Test
|
||||
var title = _fixture.Create<string>();
|
||||
var content = string.Empty;
|
||||
var buttons = _fixture.CreateMany<DialogButton>(2);
|
||||
var dialogService = new DialogService(Mock.Of<IShellProfileSelectionDialog>, Mock.Of<IMessageDialog>, Mock.Of<ICreateKeyBindingDialog>);
|
||||
var dialogService = new DialogService(Mock.Of<IShellProfileSelectionDialog>, Mock.Of<IMessageDialog>, Mock.Of<ICreateKeyBindingDialog>, Mock.Of<IInputDialog>);
|
||||
|
||||
Func<Task<DialogButton>> showMessageDialogAsnyc = () => dialogService.ShowMessageDialogAsnyc(title, content, buttons.ElementAt(0), buttons.ElementAt(1));
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace FluentTerminal.App.Services.Test
|
||||
{
|
||||
var title = _fixture.Create<string>();
|
||||
var content = _fixture.Create<string>();
|
||||
var dialogService = new DialogService(Mock.Of<IShellProfileSelectionDialog>, Mock.Of<IMessageDialog>, Mock.Of<ICreateKeyBindingDialog>);
|
||||
var dialogService = new DialogService(Mock.Of<IShellProfileSelectionDialog>, Mock.Of<IMessageDialog>, Mock.Of<ICreateKeyBindingDialog>, Mock.Of<IInputDialog>);
|
||||
|
||||
Func<Task<DialogButton>> showMessageDialogAsnyc = () => dialogService.ShowMessageDialogAsnyc(title, content);
|
||||
|
||||
@@ -75,7 +75,7 @@ namespace FluentTerminal.App.Services.Test
|
||||
var content = _fixture.Create<string>();
|
||||
var buttons = _fixture.CreateMany<DialogButton>(2);
|
||||
var messageDialog = new Mock<IMessageDialog>();
|
||||
var dialogService = new DialogService(Mock.Of<IShellProfileSelectionDialog>, () => messageDialog.Object, Mock.Of<ICreateKeyBindingDialog>);
|
||||
var dialogService = new DialogService(Mock.Of<IShellProfileSelectionDialog>, () => messageDialog.Object, Mock.Of<ICreateKeyBindingDialog>, Mock.Of<IInputDialog>);
|
||||
|
||||
dialogService.ShowMessageDialogAsnyc(title, content, buttons.ElementAt(0), buttons.ElementAt(1));
|
||||
|
||||
@@ -86,7 +86,7 @@ namespace FluentTerminal.App.Services.Test
|
||||
public void ShowProfileSelectionDialogAsync_Default_UsesShellProfileSelectionDialog()
|
||||
{
|
||||
var shellProfileSelectionDialog = new Mock<IShellProfileSelectionDialog>();
|
||||
var dialogService = new DialogService(() => shellProfileSelectionDialog.Object, Mock.Of<IMessageDialog>, Mock.Of<ICreateKeyBindingDialog>);
|
||||
var dialogService = new DialogService(() => shellProfileSelectionDialog.Object, Mock.Of<IMessageDialog>, Mock.Of<ICreateKeyBindingDialog>, Mock.Of<IInputDialog>);
|
||||
|
||||
dialogService.ShowProfileSelectionDialogAsync();
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace FluentTerminal.App.Services.Test
|
||||
[Fact]
|
||||
public void RegisterCommandHandler_HandlerIsNull_ThrowsArgumentNullException()
|
||||
{
|
||||
var command = (Command)new Random().Next(1, Enum.GetValues(typeof(Command)).Length);
|
||||
var command = ((Command)new Random().Next(1, Enum.GetValues(typeof(Command)).Length)).ToString();
|
||||
Action handler = null;
|
||||
var keyboardCommandService = new KeyboardCommandService();
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace FluentTerminal.App.Services.Test
|
||||
[Fact]
|
||||
public void RegisterCommandHandler_CommandAlreadyRegisted_ThrowsInvalidOperationException()
|
||||
{
|
||||
var command = (Command)new Random().Next(1, Enum.GetValues(typeof(Command)).Length);
|
||||
var command = ((Command)new Random().Next(1, Enum.GetValues(typeof(Command)).Length)).ToString();
|
||||
var handler = _fixture.Create<Action>();
|
||||
var keyboardCommandService = new KeyboardCommandService();
|
||||
keyboardCommandService.RegisterCommandHandler(command, handler);
|
||||
@@ -45,7 +45,7 @@ namespace FluentTerminal.App.Services.Test
|
||||
[Fact]
|
||||
public void SendCommand_CommandIsRegistered_HandlerGetsInvoked()
|
||||
{
|
||||
var command = (Command)new Random().Next(1, Enum.GetValues(typeof(Command)).Length);
|
||||
var command = ((Command)new Random().Next(1, Enum.GetValues(typeof(Command)).Length)).ToString();
|
||||
var handlerCalled = false;
|
||||
var keyboardCommandService = new KeyboardCommandService();
|
||||
keyboardCommandService.RegisterCommandHandler(command, () => handlerCalled = true);
|
||||
@@ -58,12 +58,23 @@ namespace FluentTerminal.App.Services.Test
|
||||
[Fact]
|
||||
public void SendCommand_CommandIsNotRegisted_KeyNotFoundExceptionIsThrown()
|
||||
{
|
||||
var command = (Command)new Random().Next(1, Enum.GetValues(typeof(Command)).Length);
|
||||
var command = ((Command)new Random().Next(1, Enum.GetValues(typeof(Command)).Length)).ToString();
|
||||
var keyboardCommandService = new KeyboardCommandService();
|
||||
|
||||
Action invoke = () => keyboardCommandService.SendCommand(command);
|
||||
|
||||
invoke.Should().Throw<KeyNotFoundException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SendCommand_CommandIsToggleWindow_ShouldNotThrow()
|
||||
{
|
||||
var command = nameof(Command.ToggleWindow);
|
||||
var keyboardCommandService = new KeyboardCommandService();
|
||||
|
||||
Action invoke = () => keyboardCommandService.SendCommand(command);
|
||||
|
||||
invoke.Should().NotThrow();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using AutoFixture;
|
||||
using FluentAssertions;
|
||||
using FluentTerminal.App.Services.Implementation;
|
||||
using FluentTerminal.App.Services.Utilities;
|
||||
using FluentTerminal.Models;
|
||||
using FluentTerminal.Models.Enums;
|
||||
using Moq;
|
||||
@@ -439,12 +440,12 @@ namespace FluentTerminal.App.Services.Test
|
||||
[Fact]
|
||||
public void GetKeyBindings_KeyBindingsInKeyBindingsContainer_ReturnsKeyBindingsFromKeyBindingsContainer()
|
||||
{
|
||||
var keyBindings = _fixture.CreateMany<KeyBinding>(5);
|
||||
var commands = _fixture.CreateMany<Command>(5);
|
||||
var defaultValueProvider = Mock.Of<IDefaultValueProvider>();
|
||||
var keyBindingsContainer = new Mock<IApplicationDataContainer>();
|
||||
foreach (var keyBinding in keyBindings)
|
||||
foreach (var command in commands)
|
||||
{
|
||||
keyBindingsContainer.Setup(x => x.ReadValueFromJson<Collection<KeyBinding>>(keyBinding.Command.ToString(), null)).Returns(new Collection<KeyBinding> { keyBinding });
|
||||
keyBindingsContainer.Setup(x => x.ReadValueFromJson<Collection<KeyBinding>>(command.ToString(), null)).Returns(new Collection<KeyBinding> { });
|
||||
}
|
||||
var applicationDataContainers = new ApplicationDataContainers
|
||||
{
|
||||
@@ -456,23 +457,23 @@ namespace FluentTerminal.App.Services.Test
|
||||
};
|
||||
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
|
||||
|
||||
var result = settingsService.GetKeyBindings();
|
||||
var result = settingsService.GetCommandKeyBindings();
|
||||
|
||||
foreach (var keyBinding in keyBindings)
|
||||
foreach (var command in commands)
|
||||
{
|
||||
keyBindingsContainer.Verify(x => x.ReadValueFromJson<Collection<KeyBinding>>(keyBinding.Command.ToString(), null), Times.Once);
|
||||
keyBindingsContainer.Verify(x => x.ReadValueFromJson<Collection<KeyBinding>>(command.ToString(), null), Times.Once);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetKeyBindings_KeyBindingsNotInKeyBindingsContainer_ReturnsKeyBindingsFromDefaultValueProvider()
|
||||
{
|
||||
var keyBindings = _fixture.CreateMany<KeyBinding>(5);
|
||||
var commands = _fixture.CreateMany<Command>(5);
|
||||
var defaultValueProvider = new Mock<IDefaultValueProvider>();
|
||||
var keyBindingsContainer = new Mock<IApplicationDataContainer>();
|
||||
foreach (var keyBinding in keyBindings)
|
||||
foreach (var command in commands)
|
||||
{
|
||||
keyBindingsContainer.Setup(x => x.ReadValueFromJson<Collection<KeyBinding>>(keyBinding.Command.ToString(), null)).Returns(new Collection<KeyBinding> { keyBinding });
|
||||
keyBindingsContainer.Setup(x => x.ReadValueFromJson<Collection<KeyBinding>>(EnumHelper.GetEnumDescription(command), null)).Returns(new Collection<KeyBinding> { });
|
||||
}
|
||||
var applicationDataContainers = new ApplicationDataContainers
|
||||
{
|
||||
@@ -484,14 +485,10 @@ namespace FluentTerminal.App.Services.Test
|
||||
};
|
||||
var settingsService = new SettingsService(defaultValueProvider.Object, applicationDataContainers);
|
||||
|
||||
var result = settingsService.GetKeyBindings();
|
||||
var result = settingsService.GetCommandKeyBindings();
|
||||
|
||||
foreach (Command command in Enum.GetValues(typeof(Command)))
|
||||
{
|
||||
if (keyBindings.Select(x => x.Command).Contains(command))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
defaultValueProvider.Verify(x => x.GetDefaultKeyBindings(command), Times.Once);
|
||||
}
|
||||
}
|
||||
@@ -513,7 +510,7 @@ namespace FluentTerminal.App.Services.Test
|
||||
};
|
||||
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
|
||||
|
||||
settingsService.SaveKeyBindings(command, keyBindings);
|
||||
settingsService.SaveKeyBindings(command.ToString(), keyBindings);
|
||||
|
||||
keyBindingsContainer.Verify(x => x.WriteValueAsJson(command.ToString(), keyBindings), Times.Once);
|
||||
}
|
||||
@@ -537,7 +534,7 @@ namespace FluentTerminal.App.Services.Test
|
||||
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
|
||||
settingsService.KeyBindingsChanged += (s, e) => keyBindingsChangedEventInvoked = true;
|
||||
|
||||
settingsService.SaveKeyBindings(command, keyBindings);
|
||||
settingsService.SaveKeyBindings(command.ToString(), keyBindings);
|
||||
|
||||
keyBindingsChangedEventInvoked.Should().BeTrue();
|
||||
}
|
||||
|
||||
@@ -28,9 +28,9 @@ namespace FluentTerminal.App.Services.Test
|
||||
{
|
||||
var settingsService = new Mock<ISettingsService>();
|
||||
var keyBindings = _fixture.CreateMany<KeyBinding>(3);
|
||||
settingsService.Setup(x => x.GetKeyBindings()).Returns(new Dictionary<Command, ICollection<KeyBinding>>
|
||||
settingsService.Setup(x => x.GetCommandKeyBindings()).Returns(new Dictionary<string, ICollection<KeyBinding>>
|
||||
{
|
||||
[Command.ToggleWindow] = keyBindings.ToList()
|
||||
[nameof(Command.ToggleWindow)] = keyBindings.ToList()
|
||||
});
|
||||
var appServiceConnection = new Mock<IAppServiceConnection>();
|
||||
var response = _fixture.Create<CreateTerminalResponse>();
|
||||
@@ -42,10 +42,11 @@ namespace FluentTerminal.App.Services.Test
|
||||
appServiceConnection.Setup(x => x.SendMessageAsync(It.IsAny<IDictionary<string, string>>())).Returns(Task.FromResult((IDictionary<string, string>)responseMessage));
|
||||
var terminalSize = _fixture.Create<TerminalSize>();
|
||||
var shellProfile = _fixture.Create<ShellProfile>();
|
||||
var sessionType = _fixture.Create<SessionType>();
|
||||
var trayProcessCommunicationService = new TrayProcessCommunicationService(settingsService.Object);
|
||||
trayProcessCommunicationService.Initialize(appServiceConnection.Object);
|
||||
|
||||
var result = await trayProcessCommunicationService.CreateTerminal(terminalSize, shellProfile);
|
||||
var result = await trayProcessCommunicationService.CreateTerminal(terminalSize, shellProfile, sessionType);
|
||||
|
||||
result.Should().BeEquivalentTo(response);
|
||||
}
|
||||
@@ -57,9 +58,9 @@ namespace FluentTerminal.App.Services.Test
|
||||
var terminalSize = _fixture.Create<TerminalSize>();
|
||||
var settingsService = new Mock<ISettingsService>();
|
||||
var keyBindings = _fixture.CreateMany<KeyBinding>(3);
|
||||
settingsService.Setup(x => x.GetKeyBindings()).Returns(new Dictionary<Command, ICollection<KeyBinding>>
|
||||
settingsService.Setup(x => x.GetCommandKeyBindings()).Returns(new Dictionary<string, ICollection<KeyBinding>>
|
||||
{
|
||||
[Command.ToggleWindow] = keyBindings.ToList()
|
||||
[nameof(Command.ToggleWindow)] = keyBindings.ToList()
|
||||
});
|
||||
var appServiceConnection = new Mock<IAppServiceConnection>();
|
||||
var trayProcessCommunicationService = new TrayProcessCommunicationService(settingsService.Object);
|
||||
@@ -77,9 +78,9 @@ namespace FluentTerminal.App.Services.Test
|
||||
var text = _fixture.Create<string>();
|
||||
var settingsService = new Mock<ISettingsService>();
|
||||
var keyBindings = _fixture.CreateMany<KeyBinding>(3);
|
||||
settingsService.Setup(x => x.GetKeyBindings()).Returns(new Dictionary<Command, ICollection<KeyBinding>>
|
||||
settingsService.Setup(x => x.GetCommandKeyBindings()).Returns(new Dictionary<string, ICollection<KeyBinding>>
|
||||
{
|
||||
[Command.ToggleWindow] = keyBindings.ToList()
|
||||
[nameof(Command.ToggleWindow)] = keyBindings.ToList()
|
||||
});
|
||||
var appServiceConnection = new Mock<IAppServiceConnection>();
|
||||
var trayProcessCommunicationService = new TrayProcessCommunicationService(settingsService.Object);
|
||||
@@ -96,9 +97,9 @@ namespace FluentTerminal.App.Services.Test
|
||||
var terminalId = _fixture.Create<int>();
|
||||
var settingsService = new Mock<ISettingsService>();
|
||||
var keyBindings = _fixture.CreateMany<KeyBinding>(3);
|
||||
settingsService.Setup(x => x.GetKeyBindings()).Returns(new Dictionary<Command, ICollection<KeyBinding>>
|
||||
settingsService.Setup(x => x.GetCommandKeyBindings()).Returns(new Dictionary<string, ICollection<KeyBinding>>
|
||||
{
|
||||
[Command.ToggleWindow] = keyBindings.ToList()
|
||||
[nameof(Command.ToggleWindow)] = keyBindings.ToList()
|
||||
});
|
||||
var appServiceConnection = new Mock<IAppServiceConnection>();
|
||||
var trayProcessCommunicationService = new TrayProcessCommunicationService(settingsService.Object);
|
||||
@@ -126,9 +127,9 @@ namespace FluentTerminal.App.Services.Test
|
||||
};
|
||||
var settingsService = new Mock<ISettingsService>();
|
||||
var keyBindings = _fixture.CreateMany<KeyBinding>(3);
|
||||
settingsService.Setup(x => x.GetKeyBindings()).Returns(new Dictionary<Command, ICollection<KeyBinding>>
|
||||
settingsService.Setup(x => x.GetCommandKeyBindings()).Returns(new Dictionary<string, ICollection<KeyBinding>>
|
||||
{
|
||||
[Command.ToggleWindow] = keyBindings.ToList()
|
||||
[nameof(Command.ToggleWindow)] = keyBindings.ToList()
|
||||
});
|
||||
var trayProcessCommunicationService = new TrayProcessCommunicationService(settingsService.Object);
|
||||
var appServiceConnection = new Mock<IAppServiceConnection>();
|
||||
@@ -163,9 +164,9 @@ namespace FluentTerminal.App.Services.Test
|
||||
};
|
||||
var settingsService = new Mock<ISettingsService>();
|
||||
var keyBindings = _fixture.CreateMany<KeyBinding>(3);
|
||||
settingsService.Setup(x => x.GetKeyBindings()).Returns(new Dictionary<Command, ICollection<KeyBinding>>
|
||||
settingsService.Setup(x => x.GetCommandKeyBindings()).Returns(new Dictionary<string, ICollection<KeyBinding>>
|
||||
{
|
||||
[Command.ToggleWindow] = keyBindings.ToList()
|
||||
[nameof(Command.ToggleWindow)] = keyBindings.ToList()
|
||||
});
|
||||
var trayProcessCommunicationService = new TrayProcessCommunicationService(settingsService.Object);
|
||||
var appServiceConnection = new Mock<IAppServiceConnection>();
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FluentTerminal.App.Services.Dialogs
|
||||
{
|
||||
public interface IInputDialog
|
||||
{
|
||||
void SetTitle(string title);
|
||||
Task<string> GetInput();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace FluentTerminal.App.Services.EventArgs
|
||||
{
|
||||
public class NewWindowRequestedEventArgs : System.EventArgs
|
||||
{
|
||||
public bool ShowProfileSelection { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,11 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CSharp" Version="4.5.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
|
||||
<PackageReference Include="PListNet" Version="2.0.3" />
|
||||
<PackageReference Include="RestSharp" Version="106.5.4" />
|
||||
<PackageReference Include="System.Runtime.InteropServices.WindowsRuntime" Version="4.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -17,5 +17,6 @@ namespace FluentTerminal.App.Services
|
||||
Task<bool> TryClose();
|
||||
|
||||
bool ToggleFullScreen();
|
||||
bool IsApiContractPresent(string api, ushort version);
|
||||
}
|
||||
}
|
||||
@@ -16,5 +16,6 @@ namespace FluentTerminal.App.Services
|
||||
Task<ShellProfile> ShowProfileSelectionDialogAsync();
|
||||
|
||||
Task<KeyBinding> ShowCreateKeyBindingDialog();
|
||||
Task<string> ShowInputDialogAsync(string title);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
using FluentTerminal.Models.Enums;
|
||||
using System;
|
||||
using System;
|
||||
|
||||
namespace FluentTerminal.App.Services
|
||||
{
|
||||
public interface IKeyboardCommandService
|
||||
{
|
||||
void RegisterCommandHandler(Command command, Action handler);
|
||||
void RegisterCommandHandler(string command, Action handler);
|
||||
|
||||
void SendCommand(Command command);
|
||||
void SendCommand(string command);
|
||||
void DeregisterCommandHandler(string command);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace FluentTerminal.App.Services
|
||||
{
|
||||
public interface INotificationService
|
||||
{
|
||||
void ShowNotification(string title, string content, string url = null);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using FluentTerminal.Models;
|
||||
using FluentTerminal.Models.Enums;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
@@ -10,7 +9,12 @@ namespace FluentTerminal.App.Services
|
||||
event EventHandler<ApplicationSettings> ApplicationSettingsChanged;
|
||||
event EventHandler<Guid> CurrentThemeChanged;
|
||||
event EventHandler KeyBindingsChanged;
|
||||
event EventHandler<ShellProfile> ShellProfileAdded;
|
||||
event EventHandler<Guid> ShellProfileDeleted;
|
||||
event EventHandler<TerminalOptions> TerminalOptionsChanged;
|
||||
event EventHandler<Guid> ThemeDeleted;
|
||||
event EventHandler<TerminalTheme> ThemeAdded;
|
||||
|
||||
void DeleteShellProfile(Guid id);
|
||||
void DeleteTheme(Guid id);
|
||||
ApplicationSettings GetApplicationSettings();
|
||||
@@ -18,7 +22,7 @@ namespace FluentTerminal.App.Services
|
||||
Guid GetCurrentThemeId();
|
||||
ShellProfile GetDefaultShellProfile();
|
||||
Guid GetDefaultShellProfileId();
|
||||
IDictionary<Command, ICollection<KeyBinding>> GetKeyBindings();
|
||||
IDictionary<string, ICollection<KeyBinding>> GetCommandKeyBindings();
|
||||
IEnumerable<ShellProfile> GetShellProfiles();
|
||||
IEnumerable<TabTheme> GetTabThemes();
|
||||
TerminalOptions GetTerminalOptions();
|
||||
@@ -28,9 +32,10 @@ namespace FluentTerminal.App.Services
|
||||
void SaveApplicationSettings(ApplicationSettings applicationSettings);
|
||||
void SaveCurrentThemeId(Guid id);
|
||||
void SaveDefaultShellProfileId(Guid id);
|
||||
void SaveKeyBindings(Command command, ICollection<KeyBinding> keyBindings);
|
||||
void SaveShellProfile(ShellProfile shellProfile);
|
||||
void SaveKeyBindings(string command, ICollection<KeyBinding> keyBindings);
|
||||
void SaveShellProfile(ShellProfile shellProfile, bool newShell = false);
|
||||
void SaveTerminalOptions(TerminalOptions terminalOptions);
|
||||
void SaveTheme(TerminalTheme theme);
|
||||
void SaveTheme(TerminalTheme theme, bool newTheme = false);
|
||||
ShellProfile GetShellProfile(Guid id);
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
using FluentTerminal.Models;
|
||||
using FluentTerminal.Models.Enums;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FluentTerminal.App.Services
|
||||
{
|
||||
public interface ITerminalView
|
||||
{
|
||||
event EventHandler<TerminalSize> TerminalSizeChanged;
|
||||
|
||||
event EventHandler<string> TerminalTitleChanged;
|
||||
|
||||
event EventHandler<Command> KeyboardCommandReceived;
|
||||
|
||||
Task ChangeTheme(TerminalColors theme);
|
||||
|
||||
Task ChangeOptions(TerminalOptions options);
|
||||
|
||||
Task ChangeKeyBindings(IEnumerable<KeyBinding> keyBindings);
|
||||
|
||||
Task<TerminalSize> CreateTerminal(TerminalOptions options, TerminalColors theme, IEnumerable<KeyBinding> keyBindings);
|
||||
|
||||
Task ConnectToSocket(string url);
|
||||
|
||||
void Close();
|
||||
|
||||
Task FocusTerminal();
|
||||
|
||||
Task<string> GetSelection();
|
||||
|
||||
Task FindNext(string searchText);
|
||||
|
||||
Task FindPrevious(string searchText);
|
||||
|
||||
void FocusSearchTextBox();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using FluentTerminal.Models;
|
||||
using FluentTerminal.Models.Enums;
|
||||
using FluentTerminal.Models.Responses;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
@@ -11,7 +12,7 @@ namespace FluentTerminal.App.Services
|
||||
|
||||
void Initialize(IAppServiceConnection appServiceConnection);
|
||||
|
||||
Task<CreateTerminalResponse> CreateTerminal(TerminalSize size, ShellProfile shellProfile);
|
||||
Task<CreateTerminalResponse> CreateTerminal(int id, TerminalSize size, ShellProfile shellProfile, SessionType sessionType);
|
||||
|
||||
Task ResizeTerminal(int id, TerminalSize size);
|
||||
|
||||
@@ -22,5 +23,7 @@ namespace FluentTerminal.App.Services
|
||||
void SubscribeForTerminalOutput(int terminalId, Action<string> callback);
|
||||
|
||||
Task CloseTerminal(int terminalId);
|
||||
Task<GetAvailablePortResponse> GetAvailablePort();
|
||||
int GetNextTerminalId();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FluentTerminal.App.Services
|
||||
{
|
||||
public interface IUpdateService
|
||||
{
|
||||
Task CheckForUpdate(bool notifyNoUpdate = false);
|
||||
Version GetCurrentVersion();
|
||||
Task<Version> GetLatestVersionAsync();
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,9 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
MouseMiddleClickAction = MouseAction.None,
|
||||
MouseRightClickAction = MouseAction.ContextMenu,
|
||||
AlwaysShowTabs = false,
|
||||
ShowNewOutputIndicator = false
|
||||
AlwaysUseWinPty = true,
|
||||
ShowNewOutputIndicator = false,
|
||||
EnableTrayIcon = true
|
||||
};
|
||||
}
|
||||
|
||||
@@ -34,7 +36,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
new KeyBinding
|
||||
{
|
||||
Command = Command.ToggleWindow,
|
||||
Command = nameof(Command.ToggleWindow),
|
||||
Ctrl = false,
|
||||
Alt = false,
|
||||
Shift = false,
|
||||
@@ -47,7 +49,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
new KeyBinding
|
||||
{
|
||||
Command = Command.NextTab,
|
||||
Command = nameof(Command.NextTab),
|
||||
Ctrl = true,
|
||||
Alt = false,
|
||||
Shift = false,
|
||||
@@ -60,7 +62,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
new KeyBinding
|
||||
{
|
||||
Command = Command.PreviousTab,
|
||||
Command = nameof(Command.PreviousTab),
|
||||
Ctrl = true,
|
||||
Alt = false,
|
||||
Shift = true,
|
||||
@@ -81,7 +83,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
new KeyBinding
|
||||
{
|
||||
Command = command,
|
||||
Command = command.ToString(),
|
||||
Ctrl = true,
|
||||
Alt = false,
|
||||
Shift = false,
|
||||
@@ -94,7 +96,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
new KeyBinding
|
||||
{
|
||||
Command = Command.NewTab,
|
||||
Command = nameof(Command.NewTab),
|
||||
Ctrl = true,
|
||||
Alt = false,
|
||||
Shift = false,
|
||||
@@ -107,7 +109,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
new KeyBinding
|
||||
{
|
||||
Command = Command.ConfigurableNewTab,
|
||||
Command = nameof(Command.ConfigurableNewTab),
|
||||
Ctrl = true,
|
||||
Alt = false,
|
||||
Shift = true,
|
||||
@@ -115,12 +117,25 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
}
|
||||
};
|
||||
|
||||
case Command.ChangeTabTitle:
|
||||
return new List<KeyBinding>
|
||||
{
|
||||
new KeyBinding
|
||||
{
|
||||
Command = nameof(Command.ChangeTabTitle),
|
||||
Ctrl = true,
|
||||
Alt = false,
|
||||
Shift = true,
|
||||
Key = (int)ExtendedVirtualKey.R
|
||||
}
|
||||
};
|
||||
|
||||
case Command.CloseTab:
|
||||
return new List<KeyBinding>
|
||||
{
|
||||
new KeyBinding
|
||||
{
|
||||
Command = Command.CloseTab,
|
||||
Command = nameof(Command.CloseTab),
|
||||
Ctrl = true,
|
||||
Alt = false,
|
||||
Shift = false,
|
||||
@@ -133,7 +148,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
new KeyBinding
|
||||
{
|
||||
Command = Command.NewWindow,
|
||||
Command = nameof(Command.NewWindow),
|
||||
Ctrl = true,
|
||||
Alt = false,
|
||||
Shift = false,
|
||||
@@ -141,12 +156,25 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
}
|
||||
};
|
||||
|
||||
case Command.ConfigurableNewWindow:
|
||||
return new List<KeyBinding>
|
||||
{
|
||||
new KeyBinding
|
||||
{
|
||||
Command = nameof(Command.ConfigurableNewWindow),
|
||||
Ctrl = true,
|
||||
Alt = false,
|
||||
Shift = true,
|
||||
Key = (int)ExtendedVirtualKey.N
|
||||
}
|
||||
};
|
||||
|
||||
case Command.ShowSettings:
|
||||
return new List<KeyBinding>
|
||||
{
|
||||
new KeyBinding
|
||||
{
|
||||
Command = Command.ShowSettings,
|
||||
Command = nameof(Command.ShowSettings),
|
||||
Ctrl = true,
|
||||
Alt = false,
|
||||
Shift = false,
|
||||
@@ -159,7 +187,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
new KeyBinding
|
||||
{
|
||||
Command = Command.Copy,
|
||||
Command = nameof(Command.Copy),
|
||||
Ctrl = true,
|
||||
Alt = false,
|
||||
Shift = false,
|
||||
@@ -172,7 +200,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
new KeyBinding
|
||||
{
|
||||
Command = Command.Paste,
|
||||
Command = nameof(Command.Paste),
|
||||
Ctrl = true,
|
||||
Alt = false,
|
||||
Shift = false,
|
||||
@@ -180,12 +208,26 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
case Command.PasteWithoutNewlines:
|
||||
return new List<KeyBinding>
|
||||
{
|
||||
new KeyBinding
|
||||
{
|
||||
Command = nameof(Command.PasteWithoutNewlines),
|
||||
Ctrl = true,
|
||||
Alt = false,
|
||||
Shift = true,
|
||||
Key = (int)ExtendedVirtualKey.V
|
||||
}
|
||||
};
|
||||
|
||||
case Command.Search:
|
||||
return new List<KeyBinding>
|
||||
{
|
||||
new KeyBinding
|
||||
{
|
||||
Command = Command.Search,
|
||||
Command = nameof(Command.Search),
|
||||
Ctrl = true,
|
||||
Alt = false,
|
||||
Shift = false,
|
||||
@@ -198,7 +240,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
new KeyBinding
|
||||
{
|
||||
Command = Command.ToggleFullScreen,
|
||||
Command = nameof(Command.ToggleFullScreen),
|
||||
Ctrl = false,
|
||||
Alt = true,
|
||||
Shift = false,
|
||||
@@ -211,7 +253,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
new KeyBinding
|
||||
{
|
||||
Command = Command.SelectAll,
|
||||
Command = nameof(Command.SelectAll),
|
||||
Ctrl = true,
|
||||
Alt = false,
|
||||
Shift = false,
|
||||
@@ -224,7 +266,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
new KeyBinding
|
||||
{
|
||||
Command = Command.Clear,
|
||||
Command = nameof(Command.Clear),
|
||||
Ctrl = true,
|
||||
Alt = false,
|
||||
Shift = false,
|
||||
@@ -304,7 +346,9 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
ScrollBarStyle = ScrollBarStyle.Hidden,
|
||||
FontFamily = "Consolas",
|
||||
FontSize = 13,
|
||||
BoldText = false,
|
||||
BackgroundOpacity = 0.8,
|
||||
Padding = 12,
|
||||
ScrollBackLimit = 1000
|
||||
};
|
||||
}
|
||||
@@ -325,7 +369,20 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
Arguments = string.Empty,
|
||||
Location = @"C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe",
|
||||
PreInstalled = true,
|
||||
WorkingDirectory = string.Empty
|
||||
WorkingDirectory = string.Empty,
|
||||
LineEndingTranslation = LineEndingStyle.DoNotModify,
|
||||
KeyBindings = new []
|
||||
{
|
||||
new KeyBinding
|
||||
{
|
||||
Command = GetDefaultShellProfileId().ToString(),
|
||||
Ctrl=true,
|
||||
Alt=true,
|
||||
Shift=false,
|
||||
Meta=false,
|
||||
Key=(int)ExtendedVirtualKey.Number1
|
||||
}
|
||||
}
|
||||
},
|
||||
new ShellProfile
|
||||
{
|
||||
@@ -334,7 +391,20 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
Arguments = string.Empty,
|
||||
Location = @"C:\Windows\System32\cmd.exe",
|
||||
PreInstalled = true,
|
||||
WorkingDirectory = string.Empty
|
||||
WorkingDirectory = string.Empty,
|
||||
LineEndingTranslation = LineEndingStyle.DoNotModify,
|
||||
KeyBindings = new []
|
||||
{
|
||||
new KeyBinding
|
||||
{
|
||||
Command = "ab942a61-7673-4755-9bd8-765aff91d9a3",
|
||||
Ctrl=true,
|
||||
Alt=true,
|
||||
Shift=false,
|
||||
Meta=false,
|
||||
Key=(int)ExtendedVirtualKey.Number2
|
||||
}
|
||||
}
|
||||
},
|
||||
new ShellProfile
|
||||
{
|
||||
@@ -343,7 +413,20 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
Arguments = string.Empty,
|
||||
Location = @"C:\windows\system32\wsl.exe",
|
||||
PreInstalled = true,
|
||||
WorkingDirectory = string.Empty
|
||||
WorkingDirectory = string.Empty,
|
||||
LineEndingTranslation = LineEndingStyle.DoNotModify,
|
||||
KeyBindings = new []
|
||||
{
|
||||
new KeyBinding
|
||||
{
|
||||
Command = "e5785ad6-584f-40cb-bdcd-d5b3b3953e7f",
|
||||
Ctrl=true,
|
||||
Alt=true,
|
||||
Shift=false,
|
||||
Meta=false,
|
||||
Key=(int)ExtendedVirtualKey.Number3
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -513,4 +596,4 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
return new[] { defaultXterm, powerShell, homebrew, tomorrow, ubuntu };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,12 +10,14 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
private readonly Func<IShellProfileSelectionDialog> _shellProfileSelectionDialogFactory;
|
||||
private readonly Func<IMessageDialog> _messageDialogFactory;
|
||||
private readonly Func<ICreateKeyBindingDialog> _createKeyBindingDialogFactory;
|
||||
private readonly Func<IInputDialog> _inputDialogFactory;
|
||||
|
||||
public DialogService(Func<IShellProfileSelectionDialog> shellProfileSelectionDialogFactory, Func<IMessageDialog> messageDialogFactory, Func<ICreateKeyBindingDialog> createKeyBindingDialogFactory)
|
||||
public DialogService(Func<IShellProfileSelectionDialog> shellProfileSelectionDialogFactory, Func<IMessageDialog> messageDialogFactory, Func<ICreateKeyBindingDialog> createKeyBindingDialogFactory, Func<IInputDialog> inputDialogFactory)
|
||||
{
|
||||
_shellProfileSelectionDialogFactory = shellProfileSelectionDialogFactory;
|
||||
_messageDialogFactory = messageDialogFactory;
|
||||
_createKeyBindingDialogFactory = createKeyBindingDialogFactory;
|
||||
_inputDialogFactory = inputDialogFactory;
|
||||
}
|
||||
|
||||
public Task<KeyBinding> ShowCreateKeyBindingDialog()
|
||||
@@ -54,6 +56,14 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
return dialog.ShowAsync();
|
||||
}
|
||||
|
||||
public Task<string> ShowInputDialogAsync(string title)
|
||||
{
|
||||
var dialog = _inputDialogFactory.Invoke();
|
||||
dialog.SetTitle(title);
|
||||
|
||||
return dialog.GetInput();
|
||||
}
|
||||
|
||||
public Task<ShellProfile> ShowProfileSelectionDialogAsync()
|
||||
{
|
||||
var dialog = _shellProfileSelectionDialogFactory();
|
||||
|
||||
@@ -1,38 +1,46 @@
|
||||
using FluentTerminal.Models.Enums;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
public class KeyboardCommandService : IKeyboardCommandService
|
||||
{
|
||||
private readonly Dictionary<Command, Action> _commandHandlers = new Dictionary<Command, Action>();
|
||||
|
||||
public void RegisterCommandHandler(Command command, Action handler)
|
||||
{
|
||||
if (_commandHandlers.ContainsKey(command))
|
||||
{
|
||||
throw new InvalidOperationException("command already registered");
|
||||
}
|
||||
|
||||
_commandHandlers[command] = handler ?? throw new ArgumentNullException(nameof(handler));
|
||||
}
|
||||
|
||||
public void SendCommand(Command command)
|
||||
{
|
||||
if (_commandHandlers.TryGetValue(command, out Action handler))
|
||||
{
|
||||
handler.Invoke();
|
||||
return;
|
||||
}
|
||||
|
||||
using FluentTerminal.Models.Enums;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
public class KeyboardCommandService : IKeyboardCommandService
|
||||
{
|
||||
private readonly Dictionary<string, Action> _commandHandlers = new Dictionary<string, Action>();
|
||||
|
||||
public void RegisterCommandHandler(string command, Action handler)
|
||||
{
|
||||
if (_commandHandlers.ContainsKey(command))
|
||||
{
|
||||
throw new InvalidOperationException("command already registered");
|
||||
}
|
||||
|
||||
_commandHandlers[command] = handler ?? throw new ArgumentNullException(nameof(handler));
|
||||
}
|
||||
|
||||
public void DeregisterCommandHandler(string command)
|
||||
{
|
||||
if (_commandHandlers.ContainsKey(command))
|
||||
{
|
||||
_commandHandlers.Remove(command);
|
||||
}
|
||||
}
|
||||
|
||||
public void SendCommand(string command)
|
||||
{
|
||||
if (_commandHandlers.TryGetValue(command, out Action handler))
|
||||
{
|
||||
handler.Invoke();
|
||||
return;
|
||||
}
|
||||
|
||||
// already registered by SystemTray's ToggleWindowService
|
||||
if (command == Command.ToggleWindow)
|
||||
if (command == nameof(Command.ToggleWindow))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
throw new KeyNotFoundException($"No handler registered for command: {command}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new KeyNotFoundException($"No handler registered for command: {command}");
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -2,9 +2,9 @@
|
||||
using Windows.Data.Xml.Dom;
|
||||
using Windows.UI.Notifications;
|
||||
|
||||
namespace FluentTerminal.SystemTray.Services
|
||||
namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
public class NotificationService
|
||||
public class NotificationService : INotificationService
|
||||
{
|
||||
public void ShowNotification(string title, string content, string url = null)
|
||||
{
|
||||
@@ -19,6 +19,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
private readonly IApplicationDataContainer _roamingSettings;
|
||||
private readonly IApplicationDataContainer _shellProfiles;
|
||||
private readonly IApplicationDataContainer _themes;
|
||||
|
||||
public SettingsService(IDefaultValueProvider defaultValueProvider, ApplicationDataContainers containers)
|
||||
{
|
||||
_defaultValueProvider = defaultValueProvider;
|
||||
@@ -48,19 +49,37 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
|
||||
public event EventHandler<ApplicationSettings> ApplicationSettingsChanged;
|
||||
|
||||
public event EventHandler<TerminalTheme> ThemeAdded;
|
||||
public event EventHandler<Guid> CurrentThemeChanged;
|
||||
public event EventHandler<Guid> ThemeDeleted;
|
||||
|
||||
public event EventHandler KeyBindingsChanged;
|
||||
|
||||
public event EventHandler<ShellProfile> ShellProfileAdded;
|
||||
public event EventHandler<Guid> ShellProfileDeleted;
|
||||
|
||||
public event EventHandler<TerminalOptions> TerminalOptionsChanged;
|
||||
|
||||
public void DeleteShellProfile(Guid id)
|
||||
{
|
||||
_shellProfiles.Delete(id.ToString());
|
||||
ShellProfileDeleted?.Invoke(this, id);
|
||||
}
|
||||
|
||||
public void DeleteTheme(Guid id)
|
||||
{
|
||||
_themes.Delete(id.ToString());
|
||||
|
||||
foreach (var profile in GetShellProfiles())
|
||||
{
|
||||
if (profile.TerminalThemeId == id)
|
||||
{
|
||||
profile.TerminalThemeId = Guid.Empty;
|
||||
SaveShellProfile(profile);
|
||||
}
|
||||
}
|
||||
|
||||
ThemeDeleted?.Invoke(this, id);
|
||||
}
|
||||
|
||||
public ApplicationSettings GetApplicationSettings()
|
||||
@@ -117,14 +136,14 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
return _defaultValueProvider.GetDefaultShellProfileId();
|
||||
}
|
||||
|
||||
public IDictionary<Command, ICollection<KeyBinding>> GetKeyBindings()
|
||||
public IDictionary<string, ICollection<KeyBinding>> GetCommandKeyBindings()
|
||||
{
|
||||
var keyBindings = new Dictionary<Command, ICollection<KeyBinding>>();
|
||||
var keyBindings = new Dictionary<string, ICollection<KeyBinding>>();
|
||||
|
||||
foreach (Command command in Enum.GetValues(typeof(Command)))
|
||||
{
|
||||
keyBindings.Add(command, _keyBindings.ReadValueFromJson<Collection<KeyBinding>>(command.ToString(), null) ?? _defaultValueProvider.GetDefaultKeyBindings(command));
|
||||
keyBindings.Add(command.ToString(), _keyBindings.ReadValueFromJson<Collection<KeyBinding>>(command.ToString(), null) ?? _defaultValueProvider.GetDefaultKeyBindings(command));
|
||||
}
|
||||
|
||||
return keyBindings;
|
||||
}
|
||||
|
||||
@@ -181,15 +200,28 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
_localSettings.SetValue(DefaultShellProfileKey, id);
|
||||
}
|
||||
|
||||
public void SaveKeyBindings(Command command, ICollection<KeyBinding> keyBindings)
|
||||
public void SaveKeyBindings(string command, ICollection<KeyBinding> keyBindings)
|
||||
{
|
||||
if (!Enum.IsDefined(typeof(Command), command))
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
_keyBindings.WriteValueAsJson(command.ToString(), keyBindings);
|
||||
KeyBindingsChanged?.Invoke(this, System.EventArgs.Empty);
|
||||
}
|
||||
|
||||
public void SaveShellProfile(ShellProfile shellProfile)
|
||||
public void SaveShellProfile(ShellProfile shellProfile, bool newShell = false)
|
||||
{
|
||||
_shellProfiles.WriteValueAsJson(shellProfile.Id.ToString(), shellProfile);
|
||||
|
||||
// When saving the shell profile, we also need to update keybindings for everywhere.
|
||||
KeyBindingsChanged?.Invoke(this, System.EventArgs.Empty);
|
||||
|
||||
if (newShell)
|
||||
{
|
||||
ShellProfileAdded?.Invoke(this, shellProfile);
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveTerminalOptions(TerminalOptions terminalOptions)
|
||||
@@ -198,7 +230,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
TerminalOptionsChanged?.Invoke(this, terminalOptions);
|
||||
}
|
||||
|
||||
public void SaveTheme(TerminalTheme theme)
|
||||
public void SaveTheme(TerminalTheme theme, bool newTheme = false)
|
||||
{
|
||||
_themes.WriteValueAsJson(theme.Id.ToString(), theme);
|
||||
|
||||
@@ -206,6 +238,11 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
CurrentThemeChanged?.Invoke(this, theme.Id);
|
||||
}
|
||||
|
||||
if (newTheme)
|
||||
{
|
||||
ThemeAdded?.Invoke(this, theme);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,8 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
private readonly ISettingsService _settingsService;
|
||||
private IAppServiceConnection _appServiceConnection;
|
||||
private Dictionary<int, Action<string>> _terminalOutputHandlers;
|
||||
private readonly Dictionary<int, Action<string>> _terminalOutputHandlers;
|
||||
private int _nextTerminalId = 0;
|
||||
|
||||
public event EventHandler<int> TerminalExited;
|
||||
|
||||
@@ -24,12 +25,28 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
_terminalOutputHandlers = new Dictionary<int, Action<string>>();
|
||||
}
|
||||
|
||||
public async Task<CreateTerminalResponse> CreateTerminal(TerminalSize size, ShellProfile shellProfile)
|
||||
public int GetNextTerminalId()
|
||||
{
|
||||
return _nextTerminalId++;
|
||||
}
|
||||
|
||||
public async Task<GetAvailablePortResponse> GetAvailablePort()
|
||||
{
|
||||
var request = new GetAvailablePortRequest();
|
||||
|
||||
var responseMessage = await _appServiceConnection.SendMessageAsync(CreateMessage(request));
|
||||
|
||||
return JsonConvert.DeserializeObject<GetAvailablePortResponse>(responseMessage[MessageKeys.Content]);
|
||||
}
|
||||
|
||||
public async Task<CreateTerminalResponse> CreateTerminal(int id, TerminalSize size, ShellProfile shellProfile, SessionType sessionType)
|
||||
{
|
||||
var request = new CreateTerminalRequest
|
||||
{
|
||||
Id = id,
|
||||
Size = size,
|
||||
Profile = shellProfile
|
||||
Profile = shellProfile,
|
||||
SessionType = sessionType
|
||||
};
|
||||
|
||||
var responseMessage = await _appServiceConnection.SendMessageAsync(CreateMessage(request));
|
||||
@@ -87,7 +104,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
|
||||
public Task UpdateToggleWindowKeyBindings()
|
||||
{
|
||||
var keyBindings = _settingsService.GetKeyBindings()[Command.ToggleWindow];
|
||||
var keyBindings = _settingsService.GetCommandKeyBindings()[nameof(Command.ToggleWindow)];
|
||||
|
||||
var request = new SetToggleWindowKeyBindingsRequest
|
||||
{
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using Windows.ApplicationModel;
|
||||
using Newtonsoft.Json;
|
||||
using RestSharp;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
public class UpdateService : IUpdateService
|
||||
{
|
||||
private const string apiEndpoint = "https://api.github.com";
|
||||
|
||||
private readonly INotificationService _notificationService;
|
||||
|
||||
public UpdateService(INotificationService notificationService)
|
||||
{
|
||||
_notificationService = notificationService;
|
||||
}
|
||||
|
||||
public async Task CheckForUpdate(bool notifyNoUpdate = false)
|
||||
{
|
||||
var latest = await GetLatestVersionAsync();
|
||||
if (latest > GetCurrentVersion())
|
||||
{
|
||||
_notificationService.ShowNotification("Update available",
|
||||
"Click to open the releases page.", "https://github.com/felixse/FluentTerminal/releases");
|
||||
}
|
||||
else if (notifyNoUpdate)
|
||||
{
|
||||
_notificationService.ShowNotification("No update available", "You're up to date!");
|
||||
}
|
||||
}
|
||||
|
||||
public Version GetCurrentVersion()
|
||||
{
|
||||
var currentVersion = Package.Current.Id.Version;
|
||||
return new Version(currentVersion.Major, currentVersion.Minor, currentVersion.Build, currentVersion.Revision);
|
||||
}
|
||||
|
||||
public async Task<Version> GetLatestVersionAsync()
|
||||
{
|
||||
var restClient = new RestClient(apiEndpoint);
|
||||
var restRequest = new RestRequest("/repos/felixse/fluentterminal/releases", Method.GET);
|
||||
|
||||
var restResponse = await restClient.ExecuteTaskAsync(restRequest);
|
||||
if (restResponse.IsSuccessful)
|
||||
{
|
||||
dynamic restResponseData = JsonConvert.DeserializeObject(restResponse.Content);
|
||||
string tag = restResponseData[0].tag_name;
|
||||
var latestVersion = new Version(tag);
|
||||
return new Version(latestVersion.Major, latestVersion.Minor, latestVersion.Build, latestVersion.Revision);
|
||||
}
|
||||
return new Version(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
using FluentTerminal.Models;
|
||||
using FluentTerminal.Models.Enums;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FluentTerminal.App.Services
|
||||
{
|
||||
public class Terminal
|
||||
{
|
||||
private readonly ITrayProcessCommunicationService _trayProcessCommunicationService;
|
||||
private Func<Task<string>> _selectedTextCallback;
|
||||
|
||||
public Terminal(ITrayProcessCommunicationService trayProcessCommunicationService)
|
||||
{
|
||||
_trayProcessCommunicationService = trayProcessCommunicationService;
|
||||
Id = _trayProcessCommunicationService.GetNextTerminalId();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To be observed by both view and viewmodel
|
||||
/// </summary>
|
||||
public event EventHandler Closed;
|
||||
|
||||
/// <summary>
|
||||
/// to be observed by viewmodel
|
||||
/// </summary>
|
||||
public event EventHandler<string> KeyboardCommandReceived;
|
||||
|
||||
/// <summary>
|
||||
/// to be observed by view
|
||||
/// </summary>
|
||||
public event EventHandler<string> OutputReceived;
|
||||
|
||||
/// <summary>
|
||||
/// to be observed by viewmodel
|
||||
/// </summary>
|
||||
public event EventHandler<TerminalSize> SizeChanged;
|
||||
|
||||
/// <summary>
|
||||
/// to be observed by viewmodel
|
||||
/// </summary>
|
||||
public event EventHandler<string> TitleChanged;
|
||||
|
||||
public string FallbackTitle { get; private set; }
|
||||
|
||||
public int Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// To be called by either view or viewmodel
|
||||
/// </summary>
|
||||
public async Task Close()
|
||||
{
|
||||
await _trayProcessCommunicationService.CloseTerminal(Id).ConfigureAwait(true);
|
||||
Closed?.Invoke(this, System.EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// to be called by viewmodel
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Task<string> GetSelectedText()
|
||||
{
|
||||
return _selectedTextCallback();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// to be called by view
|
||||
/// </summary>
|
||||
public void ProcessKeyboardCommand(string command)
|
||||
{
|
||||
KeyboardCommandReceived?.Invoke(this, command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To be set by view
|
||||
/// </summary>
|
||||
public void RegisterSelectedTextCallback(Func<Task<string>> selectedTextCallback)
|
||||
{
|
||||
_selectedTextCallback = selectedTextCallback;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To be called by view
|
||||
/// </summary>
|
||||
public async Task SetSize(TerminalSize size)
|
||||
{
|
||||
await _trayProcessCommunicationService.ResizeTerminal(Id, size);
|
||||
SizeChanged?.Invoke(this, size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// to be called by view
|
||||
/// </summary>
|
||||
public void SetTitle(string title)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(title))
|
||||
{
|
||||
title = FallbackTitle;
|
||||
}
|
||||
TitleChanged?.Invoke(this, title);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// to be called by view when ready
|
||||
/// </summary>
|
||||
/// <param name="shellProfile"></param>
|
||||
/// <param name="size"></param>
|
||||
/// <param name="sessionType"></param>
|
||||
public async Task StartShellProcess(ShellProfile shellProfile, TerminalSize size, SessionType sessionType)
|
||||
{
|
||||
_trayProcessCommunicationService.SubscribeForTerminalOutput(Id, t => OutputReceived?.Invoke(this, t));
|
||||
var response = await _trayProcessCommunicationService.CreateTerminal(Id, size, shellProfile, sessionType).ConfigureAwait(true);
|
||||
|
||||
if (response.Success)
|
||||
{
|
||||
FallbackTitle = response.ShellExecutableName;
|
||||
SetTitle(FallbackTitle);
|
||||
}
|
||||
}
|
||||
|
||||
public Task Write(string text)
|
||||
{
|
||||
return _trayProcessCommunicationService.WriteText(Id, text);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Fleck" Version="1.0.3" />
|
||||
<PackageReference Include="MvvmLightLibs" Version="5.4.1" />
|
||||
<PackageReference Include="ReactiveUI" Version="9.4.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -34,30 +34,43 @@ namespace FluentTerminal.App.ViewModels
|
||||
_settingsService.CurrentThemeChanged += OnCurrentThemeChanged;
|
||||
_settingsService.ApplicationSettingsChanged += OnApplicationSettingsChanged;
|
||||
_settingsService.TerminalOptionsChanged += OnTerminalOptionsChanged;
|
||||
_settingsService.ShellProfileAdded += OnShellProfileAdded;
|
||||
_settingsService.ShellProfileDeleted += OnShellProfileDeleted;
|
||||
|
||||
_trayProcessCommunicationService = trayProcessCommunicationService;
|
||||
_dialogService = dialogService;
|
||||
_applicationView = applicationView;
|
||||
_dispatcherTimer = dispatcherTimer;
|
||||
_clipboardService = clipboardService;
|
||||
_keyboardCommandService = keyboardCommandService;
|
||||
_keyboardCommandService.RegisterCommandHandler(Command.NewTab, () => AddTerminal(null, false));
|
||||
_keyboardCommandService.RegisterCommandHandler(Command.ConfigurableNewTab, () => AddTerminal(null, true));
|
||||
_keyboardCommandService.RegisterCommandHandler(Command.CloseTab, CloseCurrentTab);
|
||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.NewTab), () => AddTerminal(null, false, Guid.Empty));
|
||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.ConfigurableNewTab), () => AddTerminal(null, true, Guid.Empty));
|
||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.ChangeTabTitle), () => SelectedTerminal.EditTitle());
|
||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.CloseTab), CloseCurrentTab);
|
||||
|
||||
// Add all of the commands for switching to a tab of a given ID, if there's one open there
|
||||
for (int i = 0; i < 9; i++)
|
||||
{
|
||||
Command switchCmd = Command.SwitchToTerm1 + i;
|
||||
var switchCmd = Command.SwitchToTerm1 + i;
|
||||
int tabNumber = i;
|
||||
Action handler = () => SelectTabNumber(tabNumber);
|
||||
_keyboardCommandService.RegisterCommandHandler(switchCmd, handler);
|
||||
void handler() => SelectTabNumber(tabNumber);
|
||||
_keyboardCommandService.RegisterCommandHandler(switchCmd.ToString(), handler);
|
||||
}
|
||||
|
||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.NextTab), SelectNextTab);
|
||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.PreviousTab), SelectPreviousTab);
|
||||
|
||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.NewWindow), () => NewWindow(false));
|
||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.ConfigurableNewWindow), () => NewWindow(true));
|
||||
|
||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.ShowSettings), ShowSettings);
|
||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.ToggleFullScreen), ToggleFullScreen);
|
||||
|
||||
foreach (ShellProfile profile in _settingsService.GetShellProfiles())
|
||||
{
|
||||
_keyboardCommandService.RegisterCommandHandler(profile.Id.ToString(), () => AddTerminal(profile.WorkingDirectory, false, profile.Id));
|
||||
}
|
||||
_keyboardCommandService.RegisterCommandHandler(Command.NextTab, SelectNextTab);
|
||||
_keyboardCommandService.RegisterCommandHandler(Command.PreviousTab, SelectPreviousTab);
|
||||
|
||||
_keyboardCommandService.RegisterCommandHandler(Command.NewWindow, NewWindow);
|
||||
_keyboardCommandService.RegisterCommandHandler(Command.ShowSettings, ShowSettings);
|
||||
_keyboardCommandService.RegisterCommandHandler(Command.ToggleFullScreen, ToggleFullScreen);
|
||||
var currentTheme = _settingsService.GetCurrentTheme();
|
||||
var options = _settingsService.GetTerminalOptions();
|
||||
Background = currentTheme.Colors.Background;
|
||||
@@ -65,13 +78,24 @@ namespace FluentTerminal.App.ViewModels
|
||||
_applicationSettings = _settingsService.GetApplicationSettings();
|
||||
TabsPosition = _applicationSettings.TabsPosition;
|
||||
|
||||
AddTerminalCommand = new RelayCommand(() => AddTerminal(null, false));
|
||||
AddTerminalCommand = new RelayCommand(() => AddTerminal(null, false, Guid.Empty));
|
||||
ShowAboutCommand = new RelayCommand(ShowAbout);
|
||||
ShowSettingsCommand = new RelayCommand(ShowSettings);
|
||||
|
||||
_applicationView.CloseRequested += OnCloseRequest;
|
||||
Terminals.CollectionChanged += OnTerminalsCollectionChanged;
|
||||
}
|
||||
|
||||
private void OnShellProfileDeleted(object sender, Guid e)
|
||||
{
|
||||
_keyboardCommandService.DeregisterCommandHandler(e.ToString());
|
||||
}
|
||||
|
||||
private void OnShellProfileAdded(object sender, ShellProfile e)
|
||||
{
|
||||
_keyboardCommandService.RegisterCommandHandler(e.Id.ToString(), () => AddTerminal(e.WorkingDirectory, false, e.Id));
|
||||
}
|
||||
|
||||
private void OnTerminalsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
RaisePropertyChanged(nameof(ShowTabsOnTop));
|
||||
@@ -80,10 +104,12 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
public event EventHandler Closed;
|
||||
|
||||
public event EventHandler NewWindowRequested;
|
||||
public event EventHandler<NewWindowRequestedEventArgs> NewWindowRequested;
|
||||
|
||||
public event EventHandler ShowSettingsRequested;
|
||||
|
||||
public event EventHandler ShowAboutRequested;
|
||||
|
||||
public RelayCommand AddTerminalCommand { get; }
|
||||
|
||||
public bool ShowTabsOnTop
|
||||
@@ -128,6 +154,8 @@ namespace FluentTerminal.App.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public RelayCommand ShowAboutCommand { get; }
|
||||
|
||||
public RelayCommand ShowSettingsCommand { get; }
|
||||
|
||||
public TabsPosition TabsPosition
|
||||
@@ -138,24 +166,39 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
public ObservableCollection<TerminalViewModel> Terminals { get; } = new ObservableCollection<TerminalViewModel>();
|
||||
|
||||
public Task AddTerminal(string startupDirectory, bool showProfileSelection)
|
||||
/// <summary>
|
||||
/// Add a new terminal window, either with a specified terminal profile, with the default, or by showing the profile selection dialog.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Task AddTerminal(string startupDirectory, bool showProfileSelection, Guid profileId)
|
||||
{
|
||||
return _applicationView.RunOnDispatcherThread(async () =>
|
||||
{
|
||||
ShellProfile profile = null;
|
||||
|
||||
if (showProfileSelection)
|
||||
{
|
||||
profile = await _dialogService.ShowProfileSelectionDialogAsync();
|
||||
|
||||
if (profile == null)
|
||||
{
|
||||
if (Terminals.Count == 0)
|
||||
{
|
||||
Closed?.Invoke(this, EventArgs.Empty);
|
||||
_applicationView.TryClose();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (profileId == Guid.Empty)
|
||||
{
|
||||
profile = _settingsService.GetDefaultShellProfile();
|
||||
}
|
||||
else
|
||||
{
|
||||
profile = _settingsService.GetShellProfile(profileId);
|
||||
}
|
||||
|
||||
var terminal = new TerminalViewModel(_settingsService, _trayProcessCommunicationService, _dialogService, _keyboardCommandService,
|
||||
_applicationSettings, startupDirectory, profile, _applicationView, _dispatcherTimer, _clipboardService);
|
||||
@@ -170,7 +213,7 @@ namespace FluentTerminal.App.ViewModels
|
||||
{
|
||||
foreach (var terminal in Terminals)
|
||||
{
|
||||
terminal.CloseView();
|
||||
terminal.Close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,9 +222,14 @@ namespace FluentTerminal.App.ViewModels
|
||||
SelectedTerminal?.CloseCommand.Execute(null);
|
||||
}
|
||||
|
||||
private void NewWindow()
|
||||
private void NewWindow(bool showProfileSelection)
|
||||
{
|
||||
NewWindowRequested?.Invoke(this, EventArgs.Empty);
|
||||
var args = new NewWindowRequestedEventArgs
|
||||
{
|
||||
ShowProfileSelection = showProfileSelection
|
||||
};
|
||||
|
||||
NewWindowRequested?.Invoke(this, args);
|
||||
}
|
||||
|
||||
private async void OnApplicationSettingsChanged(object sender, ApplicationSettings e)
|
||||
@@ -231,7 +279,7 @@ namespace FluentTerminal.App.ViewModels
|
||||
{
|
||||
if (sender is TerminalViewModel terminal)
|
||||
{
|
||||
terminal.CloseView();
|
||||
terminal.Close();
|
||||
if (SelectedTerminal == terminal)
|
||||
{
|
||||
SelectedTerminal = Terminals.LastOrDefault(t => t != terminal);
|
||||
@@ -276,6 +324,11 @@ namespace FluentTerminal.App.ViewModels
|
||||
SelectedTerminal = Terminals[previousIndex];
|
||||
}
|
||||
|
||||
private void ShowAbout()
|
||||
{
|
||||
ShowAboutRequested?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void ShowSettings()
|
||||
{
|
||||
ShowSettingsRequested?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
using FluentTerminal.App.Services;
|
||||
using FluentTerminal.App.ViewModels.Infrastructure;
|
||||
using GalaSoft.MvvmLight;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FluentTerminal.App.ViewModels.Settings
|
||||
{
|
||||
public class AboutPageViewModel : ViewModelBase
|
||||
{
|
||||
private const string BaseUrl = "https://github.com/felixse/FluentTerminal/releases/tag/";
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly IUpdateService _updateService;
|
||||
private string _latestVersion;
|
||||
private readonly IApplicationView _applicationView;
|
||||
|
||||
public AboutPageViewModel(ISettingsService settingsService, IUpdateService updateService, IApplicationView applicationView)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
_updateService = updateService;
|
||||
_applicationView = applicationView;
|
||||
|
||||
CheckForUpdatesCommand = new AsyncCommand(() => CheckForUpdate(true));
|
||||
}
|
||||
|
||||
public IAsyncCommand CheckForUpdatesCommand { get; }
|
||||
|
||||
public string CurrentVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
var version = _updateService.GetCurrentVersion();
|
||||
return ConvertVersionToString(version);
|
||||
}
|
||||
}
|
||||
|
||||
public string CurrentVersionReleaseNotesURL => BaseUrl + CurrentVersion;
|
||||
|
||||
public string LatestVersion
|
||||
{
|
||||
get => _latestVersion;
|
||||
set
|
||||
{
|
||||
if (Set(ref _latestVersion, value))
|
||||
{
|
||||
RaisePropertyChanged(nameof(LatestVersionFound));
|
||||
RaisePropertyChanged(nameof(LatestVersionLoading));
|
||||
RaisePropertyChanged(nameof(LatestVersionNotFound));
|
||||
RaisePropertyChanged(nameof(LatestVersionReleaseNotesURL));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool LatestVersionFound => !LatestVersionNotFound && !LatestVersionLoading;
|
||||
|
||||
public bool LatestVersionLoading => LatestVersion == null;
|
||||
|
||||
public bool LatestVersionNotFound => LatestVersion == "0.0.0.0";
|
||||
|
||||
public string LatestVersionReleaseNotesURL => BaseUrl + LatestVersion;
|
||||
|
||||
public Task OnNavigatedTo()
|
||||
{
|
||||
return CheckForUpdate(false);
|
||||
}
|
||||
|
||||
public async Task CheckForUpdate(bool notifyNoUpdate)
|
||||
{
|
||||
var version = ConvertVersionToString(await _updateService.GetLatestVersionAsync());
|
||||
await _applicationView.RunOnDispatcherThread(() => LatestVersion = version);
|
||||
await _updateService.CheckForUpdate(notifyNoUpdate);
|
||||
}
|
||||
|
||||
private string ConvertVersionToString(Version version)
|
||||
{
|
||||
return string.Format("{0}.{1}.{2}.{3}", version.Major, version.Minor, version.Build, version.Revision);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
private bool _editingTabsPosition;
|
||||
private bool _editingInactiveTabColorMode;
|
||||
private bool _startupTaskEnabled;
|
||||
private bool _shouldRestartForTrayMessage;
|
||||
private string _startupTaskErrorMessage;
|
||||
|
||||
public GeneralPageViewModel(ISettingsService settingsService, IDialogService dialogService, IDefaultValueProvider defaultValueProvider, IStartupTaskService startupTaskService)
|
||||
@@ -53,6 +54,43 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
}
|
||||
}
|
||||
|
||||
public bool AlwaysUseWinPty
|
||||
{
|
||||
get => _applicationSettings.AlwaysUseWinPty;
|
||||
set
|
||||
{
|
||||
if (_applicationSettings.AlwaysUseWinPty != value)
|
||||
{
|
||||
_applicationSettings.AlwaysUseWinPty = value;
|
||||
_settingsService.SaveApplicationSettings(_applicationSettings);
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool EnableTrayIcon
|
||||
{
|
||||
get => _applicationSettings.EnableTrayIcon;
|
||||
set
|
||||
{
|
||||
if (_applicationSettings.EnableTrayIcon != value)
|
||||
{
|
||||
_applicationSettings.EnableTrayIcon = value;
|
||||
_settingsService.SaveApplicationSettings(_applicationSettings);
|
||||
RaisePropertyChanged();
|
||||
|
||||
// Toggle message telling user to restart the program
|
||||
ShouldRestartForTrayMessage = !ShouldRestartForTrayMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShouldRestartForTrayMessage
|
||||
{
|
||||
get => _shouldRestartForTrayMessage;
|
||||
set => Set(ref _shouldRestartForTrayMessage, value);
|
||||
}
|
||||
|
||||
public bool ShowNewOutputIndicator
|
||||
{
|
||||
get => _applicationSettings.ShowNewOutputIndicator;
|
||||
@@ -240,7 +278,9 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
InactiveTabColorMode = defaults.InactiveTabColorMode;
|
||||
NewTerminalLocation = defaults.NewTerminalLocation;
|
||||
AlwaysShowTabs = defaults.AlwaysShowTabs;
|
||||
AlwaysUseWinPty = defaults.AlwaysUseWinPty;
|
||||
ShowNewOutputIndicator = defaults.ShowNewOutputIndicator;
|
||||
EnableTrayIcon = defaults.EnableTrayIcon;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using FluentTerminal.App.Services;
|
||||
using FluentTerminal.App.ViewModels.Infrastructure;
|
||||
using FluentTerminal.Models;
|
||||
using GalaSoft.MvvmLight;
|
||||
using GalaSoft.MvvmLight.Command;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -11,18 +11,21 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
{
|
||||
private readonly IDialogService _dialogService;
|
||||
|
||||
public KeyBindingViewModel(KeyBinding keyBinding, IDialogService dialogService)
|
||||
public KeyBindingViewModel(KeyBinding keyBinding, IDialogService dialogService, KeyBindingsViewModel parent)
|
||||
{
|
||||
Model = keyBinding;
|
||||
Parent = parent;
|
||||
_dialogService = dialogService;
|
||||
EditCommand = new RelayCommand(async () => await Edit().ConfigureAwait(false));
|
||||
DeleteCommand = new RelayCommand(async () => await Delete().ConfigureAwait(false));
|
||||
EditCommand = new AsyncCommand(Edit);
|
||||
DeleteCommand = new AsyncCommand(Delete);
|
||||
}
|
||||
|
||||
public event EventHandler Deleted;
|
||||
|
||||
public event EventHandler Edited;
|
||||
|
||||
public KeyBindingsViewModel Parent { get; }
|
||||
|
||||
public bool Meta
|
||||
{
|
||||
get => Model.Meta;
|
||||
@@ -62,8 +65,8 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
}
|
||||
}
|
||||
|
||||
public RelayCommand DeleteCommand { get; }
|
||||
public RelayCommand EditCommand { get; }
|
||||
public IAsyncCommand DeleteCommand { get; }
|
||||
public IAsyncCommand EditCommand { get; }
|
||||
|
||||
public int Key
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using FluentTerminal.App.Services;
|
||||
using FluentTerminal.App.Services.Utilities;
|
||||
using FluentTerminal.Models;
|
||||
using FluentTerminal.Models.Enums;
|
||||
using GalaSoft.MvvmLight;
|
||||
@@ -17,7 +18,7 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
private readonly IDialogService _dialogService;
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly ITrayProcessCommunicationService _trayProcessCommunicationService;
|
||||
private IDictionary<Command, ICollection<KeyBinding>> _keyBindings;
|
||||
private IDictionary<string, ICollection<KeyBinding>> _keyBindings;
|
||||
|
||||
public KeyBindingsPageViewModel(ISettingsService settingsService, IDialogService dialogService, IDefaultValueProvider defaultValueProvider, ITrayProcessCommunicationService trayProcessCommunicationService)
|
||||
{
|
||||
@@ -26,19 +27,19 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
_defaultValueProvider = defaultValueProvider;
|
||||
_trayProcessCommunicationService = trayProcessCommunicationService;
|
||||
RestoreDefaultsCommand = new RelayCommand(async () => await RestoreDefaults().ConfigureAwait(false));
|
||||
AddCommand = new RelayCommand<Command>(async command => await Add(command).ConfigureAwait(false));
|
||||
AddCommand = new RelayCommand<string>(async command => await Add(command).ConfigureAwait(false));
|
||||
|
||||
Initialize(_settingsService.GetKeyBindings());
|
||||
Initialize(_settingsService.GetCommandKeyBindings());
|
||||
}
|
||||
|
||||
public RelayCommand<Command> AddCommand { get; }
|
||||
public RelayCommand<string> AddCommand { get; }
|
||||
public ObservableCollection<KeyBindingsViewModel> KeyBindings { get; } = new ObservableCollection<KeyBindingsViewModel>();
|
||||
public RelayCommand RestoreDefaultsCommand { get; }
|
||||
|
||||
private async Task Add(Command command)
|
||||
private async Task Add(string command)
|
||||
{
|
||||
var keyBinding = KeyBindings.FirstOrDefault(k => k.Command == command);
|
||||
await keyBinding?.Add();
|
||||
await keyBinding?.ShowAddKeyBindingDialog();
|
||||
}
|
||||
|
||||
private void ClearKeyBindings()
|
||||
@@ -51,7 +52,7 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
KeyBindings.Clear();
|
||||
}
|
||||
|
||||
private void Initialize(IDictionary<Command, ICollection<KeyBinding>> keyBindings)
|
||||
private void Initialize(IDictionary<string, ICollection<KeyBinding>> keyBindings)
|
||||
{
|
||||
_keyBindings = keyBindings;
|
||||
|
||||
@@ -60,13 +61,17 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
foreach (var value in Enum.GetValues(typeof(Command)))
|
||||
{
|
||||
var command = (Command)value;
|
||||
var viewModel = new KeyBindingsViewModel(command, _keyBindings[command], _dialogService);
|
||||
var viewModel = new KeyBindingsViewModel(command.ToString(), _dialogService, EnumHelper.GetEnumDescription(command), true);
|
||||
foreach (var keyBinding in _keyBindings[command.ToString()])
|
||||
{
|
||||
viewModel.Add(keyBinding);
|
||||
}
|
||||
viewModel.Edited += OnEdited;
|
||||
KeyBindings.Add(viewModel);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEdited(Command command, ICollection<KeyBinding> keyBindings)
|
||||
private void OnEdited(string command, ICollection<KeyBinding> keyBindings)
|
||||
{
|
||||
_settingsService.SaveKeyBindings(command, keyBindings);
|
||||
_trayProcessCommunicationService.UpdateToggleWindowKeyBindings();
|
||||
@@ -79,7 +84,7 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
if (result == DialogButton.OK)
|
||||
{
|
||||
_settingsService.ResetKeyBindings();
|
||||
Initialize(_settingsService.GetKeyBindings());
|
||||
Initialize(_settingsService.GetCommandKeyBindings());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
using FluentTerminal.App.Services;
|
||||
using FluentTerminal.App.Services.Utilities;
|
||||
using FluentTerminal.Models;
|
||||
using FluentTerminal.Models.Enums;
|
||||
using GalaSoft.MvvmLight;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FluentTerminal.App.ViewModels.Settings
|
||||
@@ -13,62 +12,73 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
public class KeyBindingsViewModel : ViewModelBase
|
||||
{
|
||||
private readonly IDialogService _dialogService;
|
||||
private readonly ICollection<KeyBinding> _keyBindings;
|
||||
private bool _editable;
|
||||
|
||||
public KeyBindingsViewModel(Command command, ICollection<KeyBinding> keyBindings, IDialogService dialogService)
|
||||
public KeyBindingsViewModel(string command, IDialogService dialogService, string commandName, bool editable)
|
||||
{
|
||||
Command = command;
|
||||
_keyBindings = keyBindings;
|
||||
_dialogService = dialogService;
|
||||
|
||||
CommandName = EnumHelper.GetEnumDescription(command);
|
||||
|
||||
foreach (var binding in keyBindings)
|
||||
{
|
||||
var viewModel = new KeyBindingViewModel(binding, _dialogService);
|
||||
viewModel.Deleted += ViewModel_Deleted;
|
||||
viewModel.Edited += ViewModel_Edited;
|
||||
KeyBindings.Add(viewModel);
|
||||
}
|
||||
Command = command;
|
||||
CommandName = commandName;
|
||||
Editable = editable;
|
||||
}
|
||||
|
||||
public delegate void EditedEvent(Command command, ICollection<KeyBinding> keyBindings);
|
||||
public delegate void EditedEvent(string command, ICollection<KeyBinding> keyBindings);
|
||||
|
||||
public event EditedEvent Edited;
|
||||
|
||||
public Command Command { get; }
|
||||
public string Command { get; }
|
||||
|
||||
public string CommandName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the "Edit" and "Delete" links appear next to the key binding list.
|
||||
/// </summary>
|
||||
public bool Editable
|
||||
{
|
||||
get => _editable;
|
||||
set => Set(ref _editable, value);
|
||||
}
|
||||
|
||||
public ObservableCollection<KeyBindingViewModel> KeyBindings { get; } = new ObservableCollection<KeyBindingViewModel>();
|
||||
|
||||
public async Task Add()
|
||||
public async Task ShowAddKeyBindingDialog()
|
||||
{
|
||||
var newKeyBinding = new KeyBindingViewModel(new KeyBinding { Command = Command }, _dialogService);
|
||||
var newKeyBinding = new KeyBindingViewModel(new KeyBinding { Command = Command }, _dialogService, this);
|
||||
|
||||
if (await newKeyBinding.Edit().ConfigureAwait(true))
|
||||
{
|
||||
newKeyBinding.Deleted += ViewModel_Deleted;
|
||||
newKeyBinding.Edited += ViewModel_Edited;
|
||||
KeyBindings.Add(newKeyBinding);
|
||||
_keyBindings.Add(newKeyBinding.Model);
|
||||
Edited?.Invoke(Command, _keyBindings);
|
||||
Add(newKeyBinding.Model);
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
KeyBindings.Clear();
|
||||
}
|
||||
|
||||
public void Add(KeyBinding keyBinding)
|
||||
{
|
||||
var viewModel = new KeyBindingViewModel(keyBinding, _dialogService, this);
|
||||
|
||||
viewModel.Deleted += ViewModel_Deleted;
|
||||
viewModel.Edited += ViewModel_Edited;
|
||||
KeyBindings.Add(viewModel);
|
||||
Edited?.Invoke(Command, KeyBindings.Select(x => x.Model).ToList());
|
||||
}
|
||||
|
||||
private void ViewModel_Deleted(object sender, EventArgs e)
|
||||
{
|
||||
if (sender is KeyBindingViewModel keyBinding)
|
||||
{
|
||||
_keyBindings.Remove(keyBinding.Model);
|
||||
KeyBindings.Remove(keyBinding);
|
||||
Edited?.Invoke(Command, _keyBindings);
|
||||
Edited?.Invoke(Command, KeyBindings.Select(x => x.Model).ToList());
|
||||
}
|
||||
}
|
||||
|
||||
private void ViewModel_Edited(object sender, EventArgs e)
|
||||
{
|
||||
Edited?.Invoke(Command, _keyBindings);
|
||||
Edited?.Invoke(Command, KeyBindings.Select(x => x.Model).ToList());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using GalaSoft.MvvmLight;
|
||||
using GalaSoft.MvvmLight.Command;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace FluentTerminal.App.ViewModels.Settings
|
||||
@@ -15,20 +16,23 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly IFileSystemService _fileSystemService;
|
||||
private ShellProfileViewModel _selectedShellProfile;
|
||||
private readonly IApplicationView _applicationView;
|
||||
|
||||
public ProfilesPageViewModel(ISettingsService settingsService, IDialogService dialogService, IDefaultValueProvider defaultValueProvider, IFileSystemService fileSystemService)
|
||||
public ProfilesPageViewModel(ISettingsService settingsService, IDialogService dialogService, IDefaultValueProvider defaultValueProvider, IFileSystemService fileSystemService, IApplicationView applicationView)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
_dialogService = dialogService;
|
||||
_defaultValueProvider = defaultValueProvider;
|
||||
_fileSystemService = fileSystemService;
|
||||
_applicationView = applicationView;
|
||||
|
||||
CreateShellProfileCommand = new RelayCommand(CreateShellProfile);
|
||||
CloneCommand = new RelayCommand<ShellProfileViewModel>(Clone);
|
||||
|
||||
var defaultShellProfileId = _settingsService.GetDefaultShellProfileId();
|
||||
foreach (var shellProfile in _settingsService.GetShellProfiles())
|
||||
{
|
||||
var viewModel = new ShellProfileViewModel(shellProfile, settingsService, dialogService, fileSystemService);
|
||||
var viewModel = new ShellProfileViewModel(shellProfile, settingsService, dialogService, fileSystemService, applicationView, defaultValueProvider, false);
|
||||
viewModel.Deleted += OnShellProfileDeleted;
|
||||
viewModel.SetAsDefault += OnShellProfileSetAsDefault;
|
||||
|
||||
@@ -40,6 +44,7 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
}
|
||||
|
||||
SelectedShellProfile = ShellProfiles.First(p => p.IsDefault);
|
||||
|
||||
}
|
||||
|
||||
private void OnShellProfileSetAsDefault(object sender, EventArgs e)
|
||||
@@ -76,18 +81,38 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
|
||||
public RelayCommand CreateShellProfileCommand { get; }
|
||||
|
||||
public RelayCommand<ShellProfileViewModel> CloneCommand { get; }
|
||||
|
||||
private void CreateShellProfile()
|
||||
{
|
||||
var shellProfile = new ShellProfile
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
PreInstalled = false,
|
||||
Name = "New profile"
|
||||
Name = "New profile",
|
||||
KeyBindings = new List<KeyBinding>()
|
||||
};
|
||||
|
||||
_settingsService.SaveShellProfile(shellProfile);
|
||||
AddShellProfile(shellProfile);
|
||||
}
|
||||
|
||||
var viewModel = new ShellProfileViewModel(shellProfile, _settingsService, _dialogService, _fileSystemService);
|
||||
private void Clone(ShellProfileViewModel shellProfile)
|
||||
{
|
||||
var cloned = new ShellProfile(shellProfile.Model)
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
PreInstalled = false,
|
||||
Name = $"Copy of {shellProfile.Name}"
|
||||
};
|
||||
|
||||
AddShellProfile(cloned);
|
||||
}
|
||||
|
||||
private void AddShellProfile(ShellProfile shellProfile)
|
||||
{
|
||||
_settingsService.SaveShellProfile(shellProfile, true);
|
||||
|
||||
var viewModel = new ShellProfileViewModel(shellProfile, _settingsService, _dialogService, _fileSystemService, _applicationView, _defaultValueProvider, true);
|
||||
viewModel.EditCommand.Execute(null);
|
||||
viewModel.SetAsDefault += OnShellProfileSetAsDefault;
|
||||
viewModel.Deleted += OnShellProfileDeleted;
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
using FluentTerminal.App.Services;
|
||||
using FluentTerminal.Models;
|
||||
using FluentTerminal.Models.Enums;
|
||||
using GalaSoft.MvvmLight;
|
||||
using GalaSoft.MvvmLight.Command;
|
||||
using ReactiveUI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FluentTerminal.App.ViewModels.Settings
|
||||
{
|
||||
public class TerminalPageViewModel : ViewModelBase
|
||||
public class TerminalPageViewModel : ReactiveObject
|
||||
{
|
||||
private readonly TerminalOptions _terminalOptions;
|
||||
private readonly ISettingsService _settingsService;
|
||||
@@ -63,7 +65,7 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
{
|
||||
_terminalOptions.CursorBlink = value;
|
||||
_settingsService.SaveTerminalOptions(_terminalOptions);
|
||||
RaisePropertyChanged();
|
||||
this.RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,7 +79,7 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
{
|
||||
_terminalOptions.FontFamily = value;
|
||||
_settingsService.SaveTerminalOptions(_terminalOptions);
|
||||
RaisePropertyChanged();
|
||||
this.RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,8 +92,20 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
if (_terminalOptions.BackgroundOpacity != value)
|
||||
{
|
||||
_terminalOptions.BackgroundOpacity = value;
|
||||
_settingsService.SaveTerminalOptions(_terminalOptions);
|
||||
RaisePropertyChanged();
|
||||
this.RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int Padding
|
||||
{
|
||||
get => _terminalOptions.Padding;
|
||||
set
|
||||
{
|
||||
if (_terminalOptions.Padding != value)
|
||||
{
|
||||
_terminalOptions.Padding = value;
|
||||
this.RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,12 +126,26 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
{
|
||||
_terminalOptions.ScrollBackLimit = intValue;
|
||||
_settingsService.SaveTerminalOptions(_terminalOptions);
|
||||
RaisePropertyChanged();
|
||||
this.RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RaisePropertyChanged();
|
||||
this.RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool BoldText
|
||||
{
|
||||
get => _terminalOptions.BoldText;
|
||||
set
|
||||
{
|
||||
if (_terminalOptions.BoldText != value)
|
||||
{
|
||||
_terminalOptions.BoldText = value;
|
||||
_settingsService.SaveTerminalOptions(_terminalOptions);
|
||||
this.RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -133,7 +161,7 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
{
|
||||
_terminalOptions.FontSize = value;
|
||||
_settingsService.SaveTerminalOptions(_terminalOptions);
|
||||
RaisePropertyChanged();
|
||||
this.RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -152,10 +180,10 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
_isEditingCursorStyle = true;
|
||||
_terminalOptions.CursorStyle = value;
|
||||
_settingsService.SaveTerminalOptions(_terminalOptions);
|
||||
RaisePropertyChanged();
|
||||
RaisePropertyChanged(nameof(BlockIsSelected));
|
||||
RaisePropertyChanged(nameof(BarIsSelected));
|
||||
RaisePropertyChanged(nameof(UnderlineIsSelected));
|
||||
this.RaisePropertyChanged();
|
||||
this.RaisePropertyChanged(nameof(BlockIsSelected));
|
||||
this.RaisePropertyChanged(nameof(BarIsSelected));
|
||||
this.RaisePropertyChanged(nameof(UnderlineIsSelected));
|
||||
_isEditingCursorStyle = false;
|
||||
}
|
||||
}
|
||||
@@ -171,10 +199,10 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
_isEditingScrollBarStyle = true;
|
||||
_terminalOptions.ScrollBarStyle = value;
|
||||
_settingsService.SaveTerminalOptions(_terminalOptions);
|
||||
RaisePropertyChanged();
|
||||
RaisePropertyChanged(nameof(HiddenIsSelected));
|
||||
RaisePropertyChanged(nameof(AutoHidingIsSelected));
|
||||
RaisePropertyChanged(nameof(VisibleIsSelected));
|
||||
this.RaisePropertyChanged();
|
||||
this.RaisePropertyChanged(nameof(HiddenIsSelected));
|
||||
this.RaisePropertyChanged(nameof(AutoHidingIsSelected));
|
||||
this.RaisePropertyChanged(nameof(VisibleIsSelected));
|
||||
_isEditingScrollBarStyle = false;
|
||||
}
|
||||
}
|
||||
@@ -192,6 +220,7 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
ScrollBarStyle = defaults.ScrollBarStyle;
|
||||
FontFamily = defaults.FontFamily;
|
||||
FontSize = defaults.FontSize;
|
||||
BoldText = defaults.BoldText;
|
||||
BackgroundOpacity = defaults.BackgroundOpacity;
|
||||
ScrollBackLimit = defaults.ScrollBackLimit.ToString();
|
||||
}
|
||||
@@ -209,6 +238,15 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
Sizes = Enumerable.Range(1, 72);
|
||||
|
||||
_terminalOptions = _settingsService.GetTerminalOptions();
|
||||
|
||||
this.ObservableForProperty(x => x.BackgroundOpacity).Throttle(TimeSpan.FromMilliseconds(800)).ObserveOn(RxApp.MainThreadScheduler).Subscribe(x =>
|
||||
{
|
||||
_settingsService.SaveTerminalOptions(_terminalOptions);
|
||||
});
|
||||
this.ObservableForProperty(x => x.Padding).Throttle(TimeSpan.FromMilliseconds(800)).ObserveOn(RxApp.MainThreadScheduler).Subscribe(x =>
|
||||
{
|
||||
_settingsService.SaveTerminalOptions(_terminalOptions);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
|
||||
CreateThemeCommand = new RelayCommand(CreateTheme);
|
||||
ImportThemeCommand = new RelayCommand(ImportTheme);
|
||||
CloneCommand = new RelayCommand<ThemeViewModel>(CloneTheme);
|
||||
|
||||
_settingsService.TerminalOptionsChanged += OnTerminalOptionsChanged;
|
||||
|
||||
@@ -39,7 +40,7 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
var activeThemeId = _settingsService.GetCurrentThemeId();
|
||||
foreach (var theme in _settingsService.GetThemes())
|
||||
{
|
||||
var viewModel = new ThemeViewModel(theme, _settingsService, _dialogService, fileSystemService);
|
||||
var viewModel = new ThemeViewModel(theme, _settingsService, _dialogService, fileSystemService, false);
|
||||
viewModel.Activated += OnThemeActivated;
|
||||
viewModel.Deleted += OnThemeDeleted;
|
||||
|
||||
@@ -55,6 +56,7 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
|
||||
public RelayCommand CreateThemeCommand { get; }
|
||||
public RelayCommand ImportThemeCommand { get; }
|
||||
public RelayCommand<ThemeViewModel> CloneCommand { get; set; }
|
||||
|
||||
public double BackgroundOpacity
|
||||
{
|
||||
@@ -81,6 +83,18 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
|
||||
public ObservableCollection<ThemeViewModel> Themes { get; } = new ObservableCollection<ThemeViewModel>();
|
||||
|
||||
private void CloneTheme(ThemeViewModel theme)
|
||||
{
|
||||
var cloned = new TerminalTheme(theme.Model)
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
PreInstalled = false,
|
||||
Name = $"Copy of {theme.Name}"
|
||||
};
|
||||
|
||||
AddTheme(cloned);
|
||||
}
|
||||
|
||||
private void CreateTheme()
|
||||
{
|
||||
var defaultTheme = _settingsService.GetTheme(_defaultValueProvider.GetDefaultThemeId());
|
||||
@@ -92,13 +106,7 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
Colors = new TerminalColors(defaultTheme.Colors)
|
||||
};
|
||||
|
||||
_settingsService.SaveTheme(theme);
|
||||
|
||||
var viewModel = new ThemeViewModel(theme, _settingsService, _dialogService, _fileSystemService);
|
||||
viewModel.Activated += OnThemeActivated;
|
||||
viewModel.Deleted += OnThemeDeleted;
|
||||
Themes.Add(viewModel);
|
||||
SelectedTheme = viewModel;
|
||||
AddTheme(theme);
|
||||
}
|
||||
|
||||
private async void ImportTheme()
|
||||
@@ -118,13 +126,7 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
{
|
||||
var theme = await parser.Parse(file.Name, file.Content).ConfigureAwait(true);
|
||||
|
||||
_settingsService.SaveTheme(theme);
|
||||
|
||||
var viewModel = new ThemeViewModel(theme, _settingsService, _dialogService, _fileSystemService);
|
||||
viewModel.Activated += OnThemeActivated;
|
||||
viewModel.Deleted += OnThemeDeleted;
|
||||
Themes.Add(viewModel);
|
||||
SelectedTheme = viewModel;
|
||||
AddTheme(theme);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
@@ -133,6 +135,18 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
}
|
||||
}
|
||||
|
||||
private void AddTheme(TerminalTheme theme)
|
||||
{
|
||||
_settingsService.SaveTheme(theme, true);
|
||||
|
||||
var viewModel = new ThemeViewModel(theme, _settingsService, _dialogService, _fileSystemService, true);
|
||||
viewModel.EditCommand.Execute(null);
|
||||
viewModel.Activated += OnThemeActivated;
|
||||
viewModel.Deleted += OnThemeDeleted;
|
||||
Themes.Add(viewModel);
|
||||
SelectedTheme = viewModel;
|
||||
}
|
||||
|
||||
private void OnThemeActivated(object sender, EventArgs e)
|
||||
{
|
||||
if (sender is ThemeViewModel activatedTheme)
|
||||
|
||||
@@ -8,17 +8,20 @@ namespace FluentTerminal.App.ViewModels
|
||||
public class SettingsViewModel : ViewModelBase
|
||||
{
|
||||
public SettingsViewModel(ISettingsService settingsService, IDefaultValueProvider defaultValueProvider, IDialogService dialogService,
|
||||
ITrayProcessCommunicationService trayProcessCommunicationService, IThemeParserFactory themeParserFactory, ISystemFontService systemFontService, IFileSystemService fileSystemService, IStartupTaskService startupTaskService)
|
||||
ITrayProcessCommunicationService trayProcessCommunicationService, IThemeParserFactory themeParserFactory, ISystemFontService systemFontService,
|
||||
IFileSystemService fileSystemService, IStartupTaskService startupTaskService, IUpdateService updateService, IApplicationView applicationView)
|
||||
{
|
||||
About = new AboutPageViewModel(settingsService, updateService, applicationView);
|
||||
KeyBindings = new KeyBindingsPageViewModel(settingsService, dialogService, defaultValueProvider, trayProcessCommunicationService);
|
||||
General = new GeneralPageViewModel(settingsService, dialogService, defaultValueProvider, startupTaskService);
|
||||
Shell = new ProfilesPageViewModel(settingsService, dialogService, defaultValueProvider, fileSystemService);
|
||||
Shell = new ProfilesPageViewModel(settingsService, dialogService, defaultValueProvider, fileSystemService, applicationView);
|
||||
Terminal = new TerminalPageViewModel(settingsService, dialogService, defaultValueProvider, systemFontService);
|
||||
Themes = new ThemesPageViewModel(settingsService, dialogService, defaultValueProvider, themeParserFactory, fileSystemService);
|
||||
Mouse = new MousePageViewModel(settingsService, dialogService, defaultValueProvider);
|
||||
}
|
||||
|
||||
public event EventHandler Closed;
|
||||
public event EventHandler AboutPageRequested;
|
||||
|
||||
public GeneralPageViewModel General { get; }
|
||||
public KeyBindingsPageViewModel KeyBindings { get; }
|
||||
@@ -26,6 +29,12 @@ namespace FluentTerminal.App.ViewModels
|
||||
public TerminalPageViewModel Terminal { get; }
|
||||
public ThemesPageViewModel Themes { get; }
|
||||
public MousePageViewModel Mouse { get; }
|
||||
public AboutPageViewModel About { get; }
|
||||
|
||||
public void NavigateToAboutPage()
|
||||
{
|
||||
AboutPageRequested?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using FluentTerminal.App.Services;
|
||||
using FluentTerminal.App.ViewModels.Infrastructure;
|
||||
using FluentTerminal.App.ViewModels.Settings;
|
||||
using FluentTerminal.Models;
|
||||
using FluentTerminal.Models.Enums;
|
||||
using GalaSoft.MvvmLight;
|
||||
using GalaSoft.MvvmLight.Command;
|
||||
using System;
|
||||
@@ -15,29 +17,65 @@ namespace FluentTerminal.App.ViewModels
|
||||
private readonly IDialogService _dialogService;
|
||||
private readonly IFileSystemService _fileSystemService;
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly ShellProfile _shellProfile;
|
||||
private string _arguments;
|
||||
private string _fallbackArguments;
|
||||
private string _fallbackLocation;
|
||||
private string _fallbackName;
|
||||
private int _fallbackTabThemeId;
|
||||
private string _fallbackWorkingDirectory;
|
||||
private ShellProfile _fallbackProfile;
|
||||
private bool _inEditMode;
|
||||
private bool _isDefault;
|
||||
private string _location;
|
||||
private LineEndingStyle _lineEndingStyle;
|
||||
private string _name;
|
||||
private bool _isNew;
|
||||
private TabTheme _selectedTabTheme;
|
||||
private TerminalTheme _selectedTerminalTheme;
|
||||
private string _workingDirectory;
|
||||
private readonly IApplicationView _applicationView;
|
||||
private readonly IDefaultValueProvider _defaultValueProvider;
|
||||
private bool _editingLineEndingStyle;
|
||||
|
||||
public ShellProfileViewModel(ShellProfile shellProfile, ISettingsService settingsService, IDialogService dialogService, IFileSystemService fileSystemService)
|
||||
public ShellProfileViewModel(ShellProfile shellProfile, ISettingsService settingsService, IDialogService dialogService, IFileSystemService fileSystemService, IApplicationView applicationView, IDefaultValueProvider defaultValueProvider, Boolean isNew)
|
||||
{
|
||||
_shellProfile = shellProfile;
|
||||
Model = shellProfile;
|
||||
_settingsService = settingsService;
|
||||
_dialogService = dialogService;
|
||||
_fileSystemService = fileSystemService;
|
||||
_applicationView = applicationView;
|
||||
_defaultValueProvider = defaultValueProvider;
|
||||
_isNew = isNew;
|
||||
|
||||
_settingsService.ThemeAdded += OnThemeAdded;
|
||||
_settingsService.ThemeDeleted += OnThemeDeleted;
|
||||
|
||||
TabThemes = new ObservableCollection<TabTheme>(settingsService.GetTabThemes());
|
||||
|
||||
TerminalThemes = new ObservableCollection<TerminalTheme>();
|
||||
TerminalThemes.Add(new TerminalTheme
|
||||
{
|
||||
Id = Guid.Empty,
|
||||
Name = "Default"
|
||||
});
|
||||
foreach (var theme in _settingsService.GetThemes())
|
||||
{
|
||||
TerminalThemes.Add(theme);
|
||||
}
|
||||
|
||||
KeyBindings = new KeyBindingsViewModel(shellProfile.Id.ToString(), _dialogService, string.Empty, false);
|
||||
|
||||
InitializeViewModelProperties(shellProfile);
|
||||
|
||||
SetDefaultCommand = new RelayCommand(SetDefault);
|
||||
DeleteCommand = new AsyncCommand(Delete, CanDelete);
|
||||
EditCommand = new RelayCommand(Edit);
|
||||
CancelEditCommand = new AsyncCommand(CancelEdit);
|
||||
SaveChangesCommand = new RelayCommand(SaveChanges);
|
||||
AddKeyboardShortcutCommand = new AsyncCommand(AddKeyboardShortcut);
|
||||
BrowseForCustomShellCommand = new AsyncCommand(BrowseForCustomShell);
|
||||
BrowseForWorkingDirectoryCommand = new AsyncCommand(BrowseForWorkingDirectory);
|
||||
RestoreDefaultsCommand = new AsyncCommand(RestoreDefaults);
|
||||
}
|
||||
|
||||
private void InitializeViewModelProperties(ShellProfile shellProfile)
|
||||
{
|
||||
SelectedTerminalTheme = TerminalThemes.FirstOrDefault(t => t.Id == shellProfile.TerminalThemeId);
|
||||
Id = shellProfile.Id;
|
||||
Name = shellProfile.Name;
|
||||
Arguments = shellProfile.Arguments;
|
||||
@@ -45,14 +83,37 @@ namespace FluentTerminal.App.ViewModels
|
||||
WorkingDirectory = shellProfile.WorkingDirectory;
|
||||
SelectedTabTheme = TabThemes.FirstOrDefault(t => t.Id == shellProfile.TabThemeId);
|
||||
PreInstalled = shellProfile.PreInstalled;
|
||||
LineEndingStyle = shellProfile.LineEndingTranslation;
|
||||
|
||||
SetDefaultCommand = new RelayCommand(SetDefault);
|
||||
DeleteCommand = new AsyncCommand(Delete, CanDelete);
|
||||
EditCommand = new RelayCommand(Edit);
|
||||
CancelEditCommand = new AsyncCommand(CancelEdit);
|
||||
SaveChangesCommand = new RelayCommand(SaveChanges);
|
||||
BrowseForCustomShellCommand = new AsyncCommand(BrowseForCustomShell);
|
||||
BrowseForWorkingDirectoryCommand = new AsyncCommand(BrowseForWorkingDirectory);
|
||||
KeyBindings.Clear();
|
||||
foreach (var keyBinding in shellProfile.KeyBindings.Select(x => new KeyBinding(x)).ToList())
|
||||
{
|
||||
KeyBindings.Add(keyBinding);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnThemeAdded(object sender, TerminalTheme e)
|
||||
{
|
||||
_applicationView.RunOnDispatcherThread(() =>
|
||||
{
|
||||
TerminalThemes.Add(e);
|
||||
});
|
||||
}
|
||||
|
||||
private void OnThemeDeleted(object sender, Guid e)
|
||||
{
|
||||
if (SelectedTerminalTheme.Id == e)
|
||||
{
|
||||
_applicationView.RunOnDispatcherThread(() =>
|
||||
{
|
||||
SelectedTerminalTheme = TerminalThemes.FirstOrDefault(x => x.Id == Guid.Empty);
|
||||
Model.TerminalThemeId = Guid.Empty;
|
||||
if (_fallbackProfile != null)
|
||||
{
|
||||
_fallbackProfile.TerminalThemeId = Guid.Empty;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public event EventHandler Deleted;
|
||||
@@ -65,10 +126,14 @@ namespace FluentTerminal.App.ViewModels
|
||||
public RelayCommand EditCommand { get; }
|
||||
public RelayCommand SaveChangesCommand { get; }
|
||||
public RelayCommand SetDefaultCommand { get; }
|
||||
|
||||
public IAsyncCommand RestoreDefaultsCommand { get; }
|
||||
public IAsyncCommand AddKeyboardShortcutCommand { get; }
|
||||
public ObservableCollection<TabTheme> TabThemes { get; }
|
||||
public KeyBindingsViewModel KeyBindings { get; }
|
||||
|
||||
public bool PreInstalled { get; }
|
||||
public ObservableCollection<TerminalTheme> TerminalThemes { get; }
|
||||
public ShellProfile Model { get; private set; }
|
||||
public bool PreInstalled { get; private set; }
|
||||
|
||||
public string Arguments
|
||||
{
|
||||
@@ -76,7 +141,7 @@ namespace FluentTerminal.App.ViewModels
|
||||
set => Set(ref _arguments, value);
|
||||
}
|
||||
|
||||
public Guid Id { get; }
|
||||
public Guid Id { get; private set; }
|
||||
|
||||
public bool InEditMode
|
||||
{
|
||||
@@ -114,23 +179,97 @@ namespace FluentTerminal.App.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public TerminalTheme SelectedTerminalTheme
|
||||
{
|
||||
get => _selectedTerminalTheme;
|
||||
set
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
Set(ref _selectedTerminalTheme, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string WorkingDirectory
|
||||
{
|
||||
get => _workingDirectory;
|
||||
set => Set(ref _workingDirectory, value);
|
||||
}
|
||||
|
||||
public LineEndingStyle LineEndingStyle
|
||||
{
|
||||
get => _lineEndingStyle;
|
||||
set
|
||||
{
|
||||
if (_lineEndingStyle != value && !_editingLineEndingStyle)
|
||||
{
|
||||
_editingLineEndingStyle = true;
|
||||
_lineEndingStyle = value;
|
||||
RaisePropertyChanged();
|
||||
RaisePropertyChanged(nameof(DoNotModifyIsSelected));
|
||||
RaisePropertyChanged(nameof(ToCRLFIsSelected));
|
||||
RaisePropertyChanged(nameof(ToLFIsSelected));
|
||||
_editingLineEndingStyle = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool DoNotModifyIsSelected
|
||||
{
|
||||
get => LineEndingStyle == LineEndingStyle.DoNotModify;
|
||||
set => LineEndingStyle = LineEndingStyle.DoNotModify;
|
||||
}
|
||||
|
||||
public bool ToCRLFIsSelected
|
||||
{
|
||||
get => LineEndingStyle == LineEndingStyle.ToCRLF;
|
||||
set => LineEndingStyle = LineEndingStyle.ToCRLF;
|
||||
}
|
||||
|
||||
public bool ToLFIsSelected
|
||||
{
|
||||
get => LineEndingStyle == LineEndingStyle.ToLF;
|
||||
set => LineEndingStyle = LineEndingStyle.ToLF;
|
||||
}
|
||||
|
||||
public void SaveChanges()
|
||||
{
|
||||
_shellProfile.Arguments = Arguments;
|
||||
_shellProfile.Location = Location;
|
||||
_shellProfile.Name = Name;
|
||||
_shellProfile.WorkingDirectory = WorkingDirectory;
|
||||
_shellProfile.TabThemeId = SelectedTabTheme.Id;
|
||||
|
||||
_settingsService.SaveShellProfile(_shellProfile);
|
||||
Model.Arguments = Arguments;
|
||||
Model.Location = Location;
|
||||
Model.Name = Name;
|
||||
Model.WorkingDirectory = WorkingDirectory;
|
||||
Model.TabThemeId = SelectedTabTheme.Id;
|
||||
Model.TerminalThemeId = SelectedTerminalTheme.Id;
|
||||
Model.LineEndingTranslation = _lineEndingStyle;
|
||||
Model.KeyBindings = KeyBindings.KeyBindings.Select(x => x.Model).ToList();
|
||||
_settingsService.SaveShellProfile(Model);
|
||||
|
||||
KeyBindings.Editable = false;
|
||||
InEditMode = false;
|
||||
_isNew = false;
|
||||
}
|
||||
|
||||
private async Task RestoreDefaults()
|
||||
{
|
||||
if (InEditMode || !PreInstalled)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to restore this profile?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
|
||||
if (result == DialogButton.OK)
|
||||
{
|
||||
Model = _defaultValueProvider.GetPreinstalledShellProfiles().FirstOrDefault(x => x.Id == Model.Id);
|
||||
InitializeViewModelProperties(Model);
|
||||
_settingsService.SaveShellProfile(Model);
|
||||
}
|
||||
}
|
||||
|
||||
public Task AddKeyboardShortcut()
|
||||
{
|
||||
return KeyBindings.ShowAddKeyBindingDialog();
|
||||
}
|
||||
|
||||
private async Task BrowseForCustomShell()
|
||||
@@ -153,23 +292,59 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
private async Task CancelEdit()
|
||||
{
|
||||
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to discard all changes?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
|
||||
if (result == DialogButton.OK)
|
||||
if (_isNew)
|
||||
{
|
||||
Arguments = _fallbackArguments;
|
||||
Location = _fallbackLocation;
|
||||
Name = _fallbackName;
|
||||
WorkingDirectory = _fallbackWorkingDirectory;
|
||||
SelectedTabTheme = TabThemes.FirstOrDefault(t => t.Id == _fallbackTabThemeId);
|
||||
await Delete();
|
||||
}
|
||||
else
|
||||
{
|
||||
ShellProfile changedProfile = new ShellProfile
|
||||
{
|
||||
Arguments = Arguments,
|
||||
Location = Location,
|
||||
Name = Name,
|
||||
WorkingDirectory = WorkingDirectory,
|
||||
TabThemeId = SelectedTabTheme.Id,
|
||||
TerminalThemeId = SelectedTerminalTheme.Id,
|
||||
LineEndingTranslation = _lineEndingStyle,
|
||||
KeyBindings = KeyBindings.KeyBindings.Select(x => x.Model).ToList()
|
||||
};
|
||||
|
||||
InEditMode = false;
|
||||
if (!_fallbackProfile.Equals(changedProfile))
|
||||
{
|
||||
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to discard all changes?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
|
||||
if (result == DialogButton.OK)
|
||||
{
|
||||
Arguments = _fallbackProfile.Arguments;
|
||||
Location = _fallbackProfile.Location;
|
||||
Name = _fallbackProfile.Name;
|
||||
WorkingDirectory = _fallbackProfile.WorkingDirectory;
|
||||
LineEndingStyle = _fallbackProfile.LineEndingTranslation;
|
||||
SelectedTerminalTheme = TerminalThemes.FirstOrDefault(t => t.Id == _fallbackProfile.TerminalThemeId);
|
||||
SelectedTabTheme = TabThemes.FirstOrDefault(t => t.Id == _fallbackProfile.TabThemeId);
|
||||
|
||||
KeyBindings.KeyBindings.Clear();
|
||||
foreach (var keyBinding in Model.KeyBindings.Select(x => new KeyBinding(x)).ToList())
|
||||
{
|
||||
KeyBindings.Add(keyBinding);
|
||||
}
|
||||
|
||||
KeyBindings.Editable = false;
|
||||
InEditMode = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
KeyBindings.Editable = false;
|
||||
InEditMode = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanDelete()
|
||||
{
|
||||
return !_shellProfile.PreInstalled;
|
||||
return !Model.PreInstalled;
|
||||
}
|
||||
|
||||
private async Task Delete()
|
||||
@@ -184,11 +359,18 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
private void Edit()
|
||||
{
|
||||
_fallbackArguments = _shellProfile.Arguments;
|
||||
_fallbackLocation = _shellProfile.Location;
|
||||
_fallbackName = _shellProfile.Name;
|
||||
_fallbackWorkingDirectory = _shellProfile.WorkingDirectory;
|
||||
_fallbackTabThemeId = _shellProfile.TabThemeId;
|
||||
// todo write copy ctor
|
||||
_fallbackProfile = new ShellProfile
|
||||
{
|
||||
Arguments = Model.Arguments,
|
||||
Location = Model.Location,
|
||||
Name = Model.Name,
|
||||
WorkingDirectory = Model.WorkingDirectory,
|
||||
TabThemeId = Model.TabThemeId,
|
||||
TerminalThemeId = Model.TerminalThemeId
|
||||
};
|
||||
|
||||
KeyBindings.Editable = true;
|
||||
InEditMode = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,30 +1,20 @@
|
||||
using Fleck;
|
||||
using FluentTerminal.App.Services;
|
||||
using FluentTerminal.App.Services;
|
||||
using FluentTerminal.App.ViewModels.Infrastructure;
|
||||
using FluentTerminal.Models;
|
||||
using FluentTerminal.Models.Enums;
|
||||
using GalaSoft.MvvmLight;
|
||||
using GalaSoft.MvvmLight.Command;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FluentTerminal.App.ViewModels
|
||||
{
|
||||
public class TerminalViewModel : ViewModelBase
|
||||
{
|
||||
private readonly IApplicationView _applicationView;
|
||||
private readonly IClipboardService _clipboardService;
|
||||
private readonly ManualResetEventSlim _connectedEvent;
|
||||
private readonly IDialogService _dialogService;
|
||||
private readonly IKeyboardCommandService _keyboardCommandService;
|
||||
private readonly IDispatcherTimer _resizeOverlayTimer;
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly ShellProfile _shellProfile;
|
||||
private readonly string _startupDirectory;
|
||||
private readonly ITrayProcessCommunicationService _trayProcessCommunicationService;
|
||||
private bool _isSelected;
|
||||
private bool _newOutput;
|
||||
private string _resizeOverlayContent;
|
||||
@@ -32,75 +22,94 @@ namespace FluentTerminal.App.ViewModels
|
||||
private bool _showResizeOverlay;
|
||||
private bool _showSearchPanel;
|
||||
private TabTheme _tabTheme;
|
||||
private int _terminalId;
|
||||
private ITerminalView _terminalView;
|
||||
private TerminalTheme _terminalTheme;
|
||||
private string _title;
|
||||
private IWebSocketConnection _webSocket;
|
||||
|
||||
public TerminalViewModel(ISettingsService settingsService, ITrayProcessCommunicationService trayProcessCommunicationService, IDialogService dialogService,
|
||||
IKeyboardCommandService keyboardCommandService, ApplicationSettings applicationSettings, string startupDirectory, ShellProfile shellProfile,
|
||||
IApplicationView applicationView, IDispatcherTimer dispatcherTimer, IClipboardService clipboardService)
|
||||
{
|
||||
Title = DefaultTitle;
|
||||
SettingsService = settingsService;
|
||||
SettingsService.CurrentThemeChanged += OnCurrentThemeChanged;
|
||||
SettingsService.TerminalOptionsChanged += OnTerminalOptionsChanged;
|
||||
SettingsService.ApplicationSettingsChanged += OnApplicationSettingsChanged;
|
||||
SettingsService.KeyBindingsChanged += OnKeyBindingsChanged;
|
||||
|
||||
_connectedEvent = new ManualResetEventSlim(false);
|
||||
TrayProcessCommunicationService = trayProcessCommunicationService;
|
||||
|
||||
_settingsService = settingsService;
|
||||
_settingsService.CurrentThemeChanged += OnCurrentThemeChanged;
|
||||
_settingsService.TerminalOptionsChanged += OnTerminalOptionsChanged;
|
||||
_settingsService.ApplicationSettingsChanged += OnApplicationSettingsChanged;
|
||||
_settingsService.KeyBindingsChanged += OnKeyBindingsChanged;
|
||||
|
||||
_trayProcessCommunicationService = trayProcessCommunicationService;
|
||||
_trayProcessCommunicationService.TerminalExited += OnTerminalExited;
|
||||
|
||||
_dialogService = dialogService;
|
||||
DialogService = dialogService;
|
||||
_keyboardCommandService = keyboardCommandService;
|
||||
ApplicationSettings = applicationSettings;
|
||||
_startupDirectory = startupDirectory;
|
||||
_shellProfile = shellProfile;
|
||||
_applicationView = applicationView;
|
||||
_clipboardService = clipboardService;
|
||||
StartupDirectory = startupDirectory;
|
||||
ApplicationView = applicationView;
|
||||
ClipboardService = clipboardService;
|
||||
|
||||
TabThemes = new ObservableCollection<TabTheme>(_settingsService.GetTabThemes());
|
||||
TabTheme = TabThemes.FirstOrDefault(t => t.Id == _shellProfile.TabThemeId);
|
||||
ShellProfile = shellProfile;
|
||||
TerminalTheme = shellProfile.TerminalThemeId == Guid.Empty ? SettingsService.GetCurrentTheme() : SettingsService.GetTheme(shellProfile.TerminalThemeId);
|
||||
|
||||
TabThemes = new ObservableCollection<TabTheme>(SettingsService.GetTabThemes());
|
||||
TabTheme = TabThemes.FirstOrDefault(t => t.Id == ShellProfile.TabThemeId);
|
||||
|
||||
_resizeOverlayTimer = dispatcherTimer;
|
||||
_resizeOverlayTimer.Interval = new TimeSpan(0, 0, 2);
|
||||
_resizeOverlayTimer.Tick += OnResizeOverlayTimerFinished;
|
||||
|
||||
CloseCommand = new RelayCommand(async () => await TryClose().ConfigureAwait(false));
|
||||
FindNextCommand = new RelayCommand(async () => await FindNext().ConfigureAwait(false));
|
||||
FindPreviousCommand = new RelayCommand(async () => await FindPrevious().ConfigureAwait(false));
|
||||
FindNextCommand = new RelayCommand(FindNext);
|
||||
FindPreviousCommand = new RelayCommand(FindPrevious);
|
||||
CloseSearchPanelCommand = new RelayCommand(CloseSearchPanel);
|
||||
SelectTabThemeCommand = new RelayCommand<string>(SelectTabTheme);
|
||||
EditTitleCommand = new AsyncCommand(EditTitle);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(StartupDirectory))
|
||||
{
|
||||
ShellProfile.WorkingDirectory = StartupDirectory;
|
||||
}
|
||||
|
||||
Terminal = new Terminal(TrayProcessCommunicationService);
|
||||
Terminal.KeyboardCommandReceived += Terminal_KeyboardCommandReceived;
|
||||
Terminal.OutputReceived += Terminal_OutputReceived;
|
||||
Terminal.SizeChanged += Terminal_SizeChanged;
|
||||
Terminal.TitleChanged += Terminal_TitleChanged;
|
||||
Terminal.Closed += Terminal_Closed;
|
||||
}
|
||||
|
||||
public event EventHandler Activated;
|
||||
public event EventHandler Closed;
|
||||
public event EventHandler<string> FindNextRequested;
|
||||
public event EventHandler<string> FindPreviousRequested;
|
||||
public event EventHandler KeyBindingsChanged;
|
||||
public event EventHandler<TerminalOptions> OptionsChanged;
|
||||
public event EventHandler SearchStarted;
|
||||
public event EventHandler<TerminalTheme> ThemeChanged;
|
||||
|
||||
public ApplicationSettings ApplicationSettings { get; private set; }
|
||||
|
||||
public IApplicationView ApplicationView { get; }
|
||||
|
||||
public TabTheme BackgroundTabTheme
|
||||
{
|
||||
// The effective background theme depends on whether it is selected (use the theme), or if it is inactive
|
||||
// (if we're set to underline inactive tabs, use the null theme).
|
||||
get => IsSelected || (!IsSelected && ApplicationSettings.InactiveTabColorMode == InactiveTabColorMode.Background) ?
|
||||
_tabTheme :
|
||||
_settingsService.GetTabThemes().FirstOrDefault(t => t.Color == null);
|
||||
SettingsService.GetTabThemes().FirstOrDefault(t => t.Color == null);
|
||||
}
|
||||
|
||||
public IClipboardService ClipboardService { get; }
|
||||
|
||||
public RelayCommand CloseCommand { get; }
|
||||
|
||||
public RelayCommand CloseSearchPanelCommand { get; }
|
||||
|
||||
public string DefaultTitle { get; private set; } = string.Empty;
|
||||
public IDialogService DialogService { get; }
|
||||
|
||||
public IAsyncCommand EditTitleCommand { get; }
|
||||
|
||||
public RelayCommand FindNextCommand { get; }
|
||||
|
||||
public RelayCommand FindPreviousCommand { get; }
|
||||
|
||||
public bool Initialized { get; private set; }
|
||||
|
||||
public bool IsSelected
|
||||
{
|
||||
get => _isSelected;
|
||||
@@ -110,8 +119,8 @@ namespace FluentTerminal.App.ViewModels
|
||||
{
|
||||
if (IsSelected)
|
||||
{
|
||||
_applicationView.Title = Title;
|
||||
_applicationView.RunOnDispatcherThread(() => NewOutput = false);
|
||||
ApplicationView.Title = Title ?? string.Empty;
|
||||
NewOutput = false;
|
||||
}
|
||||
RaisePropertyChanged(nameof(IsUnderlined));
|
||||
RaisePropertyChanged(nameof(BackgroundTabTheme));
|
||||
@@ -142,6 +151,10 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
public RelayCommand<string> SelectTabThemeCommand { get; }
|
||||
|
||||
public ISettingsService SettingsService { get; }
|
||||
|
||||
public ShellProfile ShellProfile { get; }
|
||||
|
||||
public bool ShowResizeOverlay
|
||||
{
|
||||
get => _showResizeOverlay;
|
||||
@@ -165,6 +178,8 @@ namespace FluentTerminal.App.ViewModels
|
||||
set => Set(ref _showSearchPanel, value);
|
||||
}
|
||||
|
||||
public string StartupDirectory { get; }
|
||||
|
||||
public TabTheme TabTheme
|
||||
{
|
||||
get => _tabTheme;
|
||||
@@ -178,118 +193,72 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
public ObservableCollection<TabTheme> TabThemes { get; }
|
||||
|
||||
public Terminal Terminal { get; private set; }
|
||||
|
||||
public TerminalTheme TerminalTheme
|
||||
{
|
||||
get => _terminalTheme;
|
||||
set => Set(ref _terminalTheme, value);
|
||||
}
|
||||
|
||||
public string Title
|
||||
{
|
||||
get => _title;
|
||||
set
|
||||
{
|
||||
value = string.IsNullOrWhiteSpace(value) ? DefaultTitle : value;
|
||||
|
||||
if (Set(ref _title, value) && IsSelected)
|
||||
{
|
||||
_applicationView.Title = Title;
|
||||
ApplicationView.Title = Title;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CloseView()
|
||||
public ITrayProcessCommunicationService TrayProcessCommunicationService { get; }
|
||||
|
||||
public Task Close()
|
||||
{
|
||||
_terminalView.Close();
|
||||
await _trayProcessCommunicationService.CloseTerminal(_terminalId).ConfigureAwait(true);
|
||||
return Terminal.Close();
|
||||
}
|
||||
|
||||
public void CopyText(string text)
|
||||
{
|
||||
_clipboardService.SetText(text);
|
||||
ClipboardService.SetText(text);
|
||||
}
|
||||
|
||||
public Task FocusTerminal()
|
||||
public async Task EditTitle()
|
||||
{
|
||||
return _terminalView?.FocusTerminal();
|
||||
var result = await DialogService.ShowInputDialogAsync("Edit Title");
|
||||
if (result != null)
|
||||
{
|
||||
Title = result;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task OnViewIsReady(ITerminalView terminalView)
|
||||
public void FocusTerminal()
|
||||
{
|
||||
_terminalView = terminalView;
|
||||
|
||||
var options = _settingsService.GetTerminalOptions();
|
||||
var theme = _settingsService.GetCurrentTheme();
|
||||
var keyBindings = _settingsService.GetKeyBindings();
|
||||
|
||||
var size = await _terminalView.CreateTerminal(options, theme.Colors, FlattenKeyBindings(keyBindings)).ConfigureAwait(true);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_startupDirectory))
|
||||
{
|
||||
_shellProfile.WorkingDirectory = _startupDirectory;
|
||||
}
|
||||
|
||||
var response = await _trayProcessCommunicationService.CreateTerminal(size, _shellProfile).ConfigureAwait(true);
|
||||
|
||||
if (response.Success)
|
||||
{
|
||||
_terminalId = response.Id;
|
||||
_terminalView.TerminalSizeChanged += OnTerminalSizeChanged;
|
||||
_terminalView.TerminalTitleChanged += OnTerminalTitleChanged;
|
||||
_terminalView.KeyboardCommandReceived += OnKeyboardCommandReceived;
|
||||
|
||||
_trayProcessCommunicationService.SubscribeForTerminalOutput(_terminalId, t =>
|
||||
{
|
||||
_connectedEvent.Wait();
|
||||
_webSocket.Send(t);
|
||||
if (!IsSelected && ApplicationSettings.ShowNewOutputIndicator)
|
||||
{
|
||||
_applicationView.RunOnDispatcherThread(() => NewOutput = true);
|
||||
}
|
||||
});
|
||||
|
||||
DefaultTitle = response.ShellExecutableName;
|
||||
Title = DefaultTitle;
|
||||
|
||||
var webSocketUrl = "ws://127.0.0.1:" + response.Id;
|
||||
var webSocketServer = new WebSocketServer(webSocketUrl);
|
||||
webSocketServer.Start(socket =>
|
||||
{
|
||||
_webSocket = socket;
|
||||
socket.OnOpen = () => _connectedEvent.Set();
|
||||
socket.OnMessage = message => _trayProcessCommunicationService.WriteText(_terminalId, message);
|
||||
});
|
||||
|
||||
await _terminalView.ConnectToSocket(webSocketUrl).ConfigureAwait(true);
|
||||
Initialized = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
await _dialogService.ShowMessageDialogAsnyc("Error", response.Error, DialogButton.OK).ConfigureAwait(true);
|
||||
}
|
||||
|
||||
await FocusTerminal().ConfigureAwait(true);
|
||||
Activated?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void CloseSearchPanel()
|
||||
{
|
||||
SearchText = string.Empty;
|
||||
ShowSearchPanel = false;
|
||||
_terminalView.FocusTerminal();
|
||||
FocusTerminal();
|
||||
}
|
||||
|
||||
private Task FindNext()
|
||||
private void FindNext()
|
||||
{
|
||||
return _terminalView.FindNext(SearchText);
|
||||
FindNextRequested?.Invoke(this, SearchText);
|
||||
}
|
||||
|
||||
private Task FindPrevious()
|
||||
private void FindPrevious()
|
||||
{
|
||||
return _terminalView.FindPrevious(SearchText);
|
||||
}
|
||||
|
||||
private IEnumerable<KeyBinding> FlattenKeyBindings(IDictionary<Command, ICollection<KeyBinding>> keyBindings)
|
||||
{
|
||||
return keyBindings.Values.SelectMany(k => k);
|
||||
FindPreviousRequested?.Invoke(this, SearchText);
|
||||
}
|
||||
|
||||
private async void OnApplicationSettingsChanged(object sender, ApplicationSettings e)
|
||||
{
|
||||
await _applicationView.RunOnDispatcherThread(() =>
|
||||
await ApplicationView.RunOnDispatcherThread(() =>
|
||||
{
|
||||
ApplicationSettings = e;
|
||||
RaisePropertyChanged(nameof(IsUnderlined));
|
||||
@@ -299,52 +268,21 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
private async void OnCurrentThemeChanged(object sender, Guid e)
|
||||
{
|
||||
await _applicationView.RunOnDispatcherThread(async () =>
|
||||
await ApplicationView.RunOnDispatcherThread(() =>
|
||||
{
|
||||
RaisePropertyChanged(nameof(IsUnderlined));
|
||||
RaisePropertyChanged(nameof(BackgroundTabTheme));
|
||||
|
||||
var currentTheme = _settingsService.GetTheme(e);
|
||||
await _terminalView.ChangeTheme(currentTheme.Colors).ConfigureAwait(true);
|
||||
// only change theme if not overwritten by profile
|
||||
if (ShellProfile.TerminalThemeId == Guid.Empty)
|
||||
{
|
||||
var currentTheme = SettingsService.GetTheme(e);
|
||||
TerminalTheme = currentTheme;
|
||||
ThemeChanged?.Invoke(this, currentTheme);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async void OnKeyBindingsChanged(object sender, EventArgs e)
|
||||
private void OnKeyBindingsChanged(object sender, EventArgs e)
|
||||
{
|
||||
var keyBindings = _settingsService.GetKeyBindings();
|
||||
await _applicationView.RunOnDispatcherThread(async () =>
|
||||
{
|
||||
await _terminalView.ChangeKeyBindings(FlattenKeyBindings(keyBindings)).ConfigureAwait(false);
|
||||
});
|
||||
}
|
||||
|
||||
private async void OnKeyboardCommandReceived(object sender, Command e)
|
||||
{
|
||||
if (e == Command.Copy)
|
||||
{
|
||||
var selection = await _terminalView.GetSelection().ConfigureAwait(true);
|
||||
_clipboardService.SetText(selection);
|
||||
}
|
||||
else if (e == Command.Paste)
|
||||
{
|
||||
var content = await _clipboardService.GetText().ConfigureAwait(true);
|
||||
if (content != null)
|
||||
{
|
||||
await _trayProcessCommunicationService.WriteText(_terminalId, content).ConfigureAwait(true);
|
||||
}
|
||||
}
|
||||
else if (e == Command.Search)
|
||||
{
|
||||
ShowSearchPanel = !ShowSearchPanel;
|
||||
if (ShowSearchPanel)
|
||||
{
|
||||
_terminalView.FocusSearchTextBox();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_keyboardCommandService.SendCommand(e);
|
||||
}
|
||||
KeyBindingsChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void OnResizeOverlayTimerFinished(object sender, object e)
|
||||
@@ -353,36 +291,9 @@ namespace FluentTerminal.App.ViewModels
|
||||
ShowResizeOverlay = false;
|
||||
}
|
||||
|
||||
private void OnTerminalExited(object sender, int e)
|
||||
private void OnTerminalOptionsChanged(object sender, TerminalOptions e)
|
||||
{
|
||||
if (e == _terminalId)
|
||||
{
|
||||
_applicationView.RunOnDispatcherThread(() => Closed?.Invoke(this, EventArgs.Empty));
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnTerminalOptionsChanged(object sender, TerminalOptions e)
|
||||
{
|
||||
await _applicationView.RunOnDispatcherThread(async () =>
|
||||
{
|
||||
await _terminalView.ChangeOptions(e).ConfigureAwait(true);
|
||||
});
|
||||
}
|
||||
|
||||
private async void OnTerminalSizeChanged(object sender, TerminalSize e)
|
||||
{
|
||||
if (!Initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ResizeOverlayContent = $"{e.Columns} x {e.Rows}";
|
||||
ShowResizeOverlay = true;
|
||||
await _trayProcessCommunicationService.ResizeTerminal(_terminalId, e).ConfigureAwait(true);
|
||||
}
|
||||
|
||||
private void OnTerminalTitleChanged(object sender, string e)
|
||||
{
|
||||
Title = e;
|
||||
OptionsChanged?.Invoke(this, e);
|
||||
}
|
||||
|
||||
private void SelectTabTheme(string name)
|
||||
@@ -390,11 +301,82 @@ namespace FluentTerminal.App.ViewModels
|
||||
TabTheme = TabThemes.FirstOrDefault(t => t.Name == name);
|
||||
}
|
||||
|
||||
private void Terminal_Closed(object sender, EventArgs e)
|
||||
{
|
||||
Closed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private async void Terminal_KeyboardCommandReceived(object sender, string e)
|
||||
{
|
||||
switch (e)
|
||||
{
|
||||
case nameof(Command.Copy):
|
||||
{
|
||||
var selection = await Terminal.GetSelectedText().ConfigureAwait(true);
|
||||
ClipboardService.SetText(selection);
|
||||
break;
|
||||
}
|
||||
case nameof(Command.Paste):
|
||||
{
|
||||
string content = await ClipboardService.GetText().ConfigureAwait(true);
|
||||
if (content != null)
|
||||
{
|
||||
content = ShellProfile.TranslateLineEndings(content);
|
||||
await Terminal.Write(content).ConfigureAwait(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case nameof(Command.PasteWithoutNewlines):
|
||||
{
|
||||
string content = await ClipboardService.GetText().ConfigureAwait(true);
|
||||
if (content != null)
|
||||
{
|
||||
content = ShellProfile.NewlinePattern.Replace(content, string.Empty);
|
||||
await Terminal.Write(content).ConfigureAwait(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case nameof(Command.Search):
|
||||
{
|
||||
ShowSearchPanel = !ShowSearchPanel;
|
||||
if (ShowSearchPanel)
|
||||
{
|
||||
SearchStarted?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
_keyboardCommandService.SendCommand(e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Terminal_OutputReceived(object sender, string e)
|
||||
{
|
||||
if (!IsSelected && ApplicationSettings.ShowNewOutputIndicator)
|
||||
{
|
||||
NewOutput = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void Terminal_SizeChanged(object sender, TerminalSize e)
|
||||
{
|
||||
ResizeOverlayContent = $"{e.Columns} x {e.Rows}";
|
||||
ShowResizeOverlay = true;
|
||||
}
|
||||
|
||||
private void Terminal_TitleChanged(object sender, string e)
|
||||
{
|
||||
Title = e;
|
||||
}
|
||||
|
||||
private async Task TryClose()
|
||||
{
|
||||
if (ApplicationSettings.ConfirmClosingTabs)
|
||||
{
|
||||
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to close this tab?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
var result = await DialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to close this tab?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
|
||||
if (result == DialogButton.Cancel)
|
||||
{
|
||||
@@ -402,8 +384,7 @@ namespace FluentTerminal.App.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
await _trayProcessCommunicationService.CloseTerminal(_terminalId).ConfigureAwait(true);
|
||||
Closed?.Invoke(this, EventArgs.Empty);
|
||||
await Close().ConfigureAwait(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,58 +29,57 @@ namespace FluentTerminal.App.ViewModels
|
||||
private string _cursor;
|
||||
private string _cursorAccent;
|
||||
private string _cyan;
|
||||
private string _fallBackAuthor;
|
||||
private TerminalColors _fallBackColors;
|
||||
private string _fallBackName;
|
||||
private TerminalTheme _fallbackTheme;
|
||||
private string _foreground;
|
||||
private string _green;
|
||||
private bool _inEditMode;
|
||||
private bool _isActive;
|
||||
private string _magenta;
|
||||
private string _name;
|
||||
private bool _isNew;
|
||||
private string _red;
|
||||
private string _selection;
|
||||
private readonly TerminalTheme _theme;
|
||||
private string _white;
|
||||
private string _yellow;
|
||||
private readonly IFileSystemService _fileSystemService;
|
||||
|
||||
public event EventHandler<string> BackgroundChanged;
|
||||
|
||||
public ThemeViewModel(TerminalTheme theme, ISettingsService settingsService, IDialogService dialogService, IFileSystemService fileSystemService)
|
||||
public ThemeViewModel(TerminalTheme theme, ISettingsService settingsService, IDialogService dialogService, IFileSystemService fileSystemService, bool isNew)
|
||||
{
|
||||
_theme = theme;
|
||||
Model = theme;
|
||||
_settingsService = settingsService;
|
||||
_dialogService = dialogService;
|
||||
_fileSystemService = fileSystemService;
|
||||
_isNew = isNew;
|
||||
|
||||
Name = _theme.Name;
|
||||
Author = _theme.Author;
|
||||
Id = _theme.Id;
|
||||
Name = Model.Name;
|
||||
Author = Model.Author;
|
||||
Id = Model.Id;
|
||||
|
||||
Black = _theme.Colors.Black;
|
||||
Red = _theme.Colors.Red;
|
||||
Green = _theme.Colors.Green;
|
||||
Yellow = _theme.Colors.Yellow;
|
||||
Blue = _theme.Colors.Blue;
|
||||
Magenta = _theme.Colors.Magenta;
|
||||
Cyan = _theme.Colors.Cyan;
|
||||
White = _theme.Colors.White;
|
||||
Black = Model.Colors.Black;
|
||||
Red = Model.Colors.Red;
|
||||
Green = Model.Colors.Green;
|
||||
Yellow = Model.Colors.Yellow;
|
||||
Blue = Model.Colors.Blue;
|
||||
Magenta = Model.Colors.Magenta;
|
||||
Cyan = Model.Colors.Cyan;
|
||||
White = Model.Colors.White;
|
||||
|
||||
BrightBlack = _theme.Colors.BrightBlack;
|
||||
BrightRed = _theme.Colors.BrightRed;
|
||||
BrightGreen = _theme.Colors.BrightGreen;
|
||||
BrightYellow = _theme.Colors.BrightYellow;
|
||||
BrightBlue = _theme.Colors.BrightBlue;
|
||||
BrightMagenta = _theme.Colors.BrightMagenta;
|
||||
BrightCyan = _theme.Colors.BrightCyan;
|
||||
BrightWhite = _theme.Colors.BrightWhite;
|
||||
BrightBlack = Model.Colors.BrightBlack;
|
||||
BrightRed = Model.Colors.BrightRed;
|
||||
BrightGreen = Model.Colors.BrightGreen;
|
||||
BrightYellow = Model.Colors.BrightYellow;
|
||||
BrightBlue = Model.Colors.BrightBlue;
|
||||
BrightMagenta = Model.Colors.BrightMagenta;
|
||||
BrightCyan = Model.Colors.BrightCyan;
|
||||
BrightWhite = Model.Colors.BrightWhite;
|
||||
|
||||
Background = _theme.Colors.Background;
|
||||
Foreground = _theme.Colors.Foreground;
|
||||
Cursor = _theme.Colors.Cursor;
|
||||
CursorAccent = _theme.Colors.CursorAccent;
|
||||
Selection = _theme.Colors.Selection;
|
||||
Background = Model.Colors.Background;
|
||||
Foreground = Model.Colors.Foreground;
|
||||
Cursor = Model.Colors.Cursor;
|
||||
CursorAccent = Model.Colors.CursorAccent;
|
||||
Selection = Model.Colors.Selection;
|
||||
|
||||
SetActiveCommand = new RelayCommand(SetActive);
|
||||
DeleteCommand = new RelayCommand(async () => await Delete().ConfigureAwait(false), NotPreInstalled);
|
||||
@@ -94,6 +93,8 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
public event EventHandler Deleted;
|
||||
|
||||
public TerminalTheme Model { get; }
|
||||
|
||||
public string Author
|
||||
{
|
||||
get => _author;
|
||||
@@ -268,78 +269,125 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
public void SaveChanges()
|
||||
{
|
||||
_theme.Name = Name;
|
||||
_theme.Author = Author;
|
||||
Model.Name = Name;
|
||||
Model.Author = Author;
|
||||
|
||||
_theme.Colors.Black = Black;
|
||||
_theme.Colors.Red = Red;
|
||||
_theme.Colors.Green = Green;
|
||||
_theme.Colors.Yellow = Yellow;
|
||||
_theme.Colors.Blue = Blue;
|
||||
_theme.Colors.Magenta = Magenta;
|
||||
_theme.Colors.Cyan = Cyan;
|
||||
_theme.Colors.White = White;
|
||||
Model.Colors.Black = Black;
|
||||
Model.Colors.Red = Red;
|
||||
Model.Colors.Green = Green;
|
||||
Model.Colors.Yellow = Yellow;
|
||||
Model.Colors.Blue = Blue;
|
||||
Model.Colors.Magenta = Magenta;
|
||||
Model.Colors.Cyan = Cyan;
|
||||
Model.Colors.White = White;
|
||||
|
||||
_theme.Colors.BrightBlack = BrightBlack;
|
||||
_theme.Colors.BrightRed = BrightRed;
|
||||
_theme.Colors.BrightGreen = BrightGreen;
|
||||
_theme.Colors.BrightYellow = BrightYellow;
|
||||
_theme.Colors.BrightBlue = BrightBlue;
|
||||
_theme.Colors.BrightMagenta = BrightMagenta;
|
||||
_theme.Colors.BrightCyan = BrightCyan;
|
||||
_theme.Colors.BrightWhite = BrightWhite;
|
||||
Model.Colors.BrightBlack = BrightBlack;
|
||||
Model.Colors.BrightRed = BrightRed;
|
||||
Model.Colors.BrightGreen = BrightGreen;
|
||||
Model.Colors.BrightYellow = BrightYellow;
|
||||
Model.Colors.BrightBlue = BrightBlue;
|
||||
Model.Colors.BrightMagenta = BrightMagenta;
|
||||
Model.Colors.BrightCyan = BrightCyan;
|
||||
Model.Colors.BrightWhite = BrightWhite;
|
||||
|
||||
_theme.Colors.Background = Background;
|
||||
_theme.Colors.Foreground = Foreground;
|
||||
_theme.Colors.Cursor = Cursor;
|
||||
_theme.Colors.CursorAccent = CursorAccent;
|
||||
_theme.Colors.Selection = Selection;
|
||||
Model.Colors.Background = Background;
|
||||
Model.Colors.Foreground = Foreground;
|
||||
Model.Colors.Cursor = Cursor;
|
||||
Model.Colors.CursorAccent = CursorAccent;
|
||||
Model.Colors.Selection = Selection;
|
||||
|
||||
_settingsService.SaveTheme(_theme);
|
||||
_settingsService.SaveTheme(Model);
|
||||
|
||||
InEditMode = false;
|
||||
_isNew = false;
|
||||
}
|
||||
|
||||
private async Task CancelEdit()
|
||||
{
|
||||
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to discard all changes?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
|
||||
if (result == DialogButton.OK)
|
||||
if (_isNew)
|
||||
{
|
||||
Black = _fallBackColors.Black;
|
||||
Red = _fallBackColors.Red;
|
||||
Green = _fallBackColors.Green;
|
||||
Yellow = _fallBackColors.Yellow;
|
||||
Blue = _fallBackColors.Blue;
|
||||
Magenta = _fallBackColors.Magenta;
|
||||
Cyan = _fallBackColors.Cyan;
|
||||
White = _fallBackColors.White;
|
||||
await Delete();
|
||||
}
|
||||
else
|
||||
{
|
||||
TerminalTheme changedTheme = new TerminalTheme()
|
||||
{
|
||||
Name = Name,
|
||||
Author = Author,
|
||||
Colors = new TerminalColors()
|
||||
{
|
||||
Black = Black,
|
||||
Red = Red,
|
||||
Green = Green,
|
||||
Yellow = Yellow,
|
||||
Blue = Blue,
|
||||
Magenta = Magenta,
|
||||
Cyan = Cyan,
|
||||
White = White,
|
||||
|
||||
BrightBlack = _fallBackColors.BrightBlack;
|
||||
BrightRed = _fallBackColors.BrightRed;
|
||||
BrightGreen = _fallBackColors.BrightGreen;
|
||||
BrightYellow = _fallBackColors.BrightYellow;
|
||||
BrightBlue = _fallBackColors.BrightBlue;
|
||||
BrightMagenta = _fallBackColors.BrightMagenta;
|
||||
BrightCyan = _fallBackColors.BrightCyan;
|
||||
BrightWhite = _fallBackColors.BrightWhite;
|
||||
BrightBlack = BrightBlack,
|
||||
BrightRed = BrightRed,
|
||||
BrightGreen = BrightGreen,
|
||||
BrightYellow = BrightYellow,
|
||||
BrightBlue = BrightBlue,
|
||||
BrightMagenta = BrightMagenta,
|
||||
BrightCyan = BrightCyan,
|
||||
BrightWhite = BrightWhite,
|
||||
|
||||
Background = _fallBackColors.Background;
|
||||
Foreground = _fallBackColors.Foreground;
|
||||
Cursor = _fallBackColors.Cursor;
|
||||
CursorAccent = _fallBackColors.CursorAccent;
|
||||
Selection = _fallBackColors.Selection;
|
||||
Background = Background,
|
||||
Foreground = Foreground,
|
||||
Cursor = Cursor,
|
||||
CursorAccent = CursorAccent,
|
||||
Selection = Selection
|
||||
}
|
||||
};
|
||||
|
||||
Name = _fallBackName;
|
||||
Author = _fallBackAuthor;
|
||||
if (!_fallbackTheme.Equals(changedTheme))
|
||||
{
|
||||
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to discard all changes?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
|
||||
InEditMode = false;
|
||||
if (result == DialogButton.OK)
|
||||
{
|
||||
Black = _fallbackTheme.Colors.Black;
|
||||
Red = _fallbackTheme.Colors.Red;
|
||||
Green = _fallbackTheme.Colors.Green;
|
||||
Yellow = _fallbackTheme.Colors.Yellow;
|
||||
Blue = _fallbackTheme.Colors.Blue;
|
||||
Magenta = _fallbackTheme.Colors.Magenta;
|
||||
Cyan = _fallbackTheme.Colors.Cyan;
|
||||
White = _fallbackTheme.Colors.White;
|
||||
|
||||
BrightBlack = _fallbackTheme.Colors.BrightBlack;
|
||||
BrightRed = _fallbackTheme.Colors.BrightRed;
|
||||
BrightGreen = _fallbackTheme.Colors.BrightGreen;
|
||||
BrightYellow = _fallbackTheme.Colors.BrightYellow;
|
||||
BrightBlue = _fallbackTheme.Colors.BrightBlue;
|
||||
BrightMagenta = _fallbackTheme.Colors.BrightMagenta;
|
||||
BrightCyan = _fallbackTheme.Colors.BrightCyan;
|
||||
BrightWhite = _fallbackTheme.Colors.BrightWhite;
|
||||
|
||||
Background = _fallbackTheme.Colors.Background;
|
||||
Foreground = _fallbackTheme.Colors.Foreground;
|
||||
Cursor = _fallbackTheme.Colors.Cursor;
|
||||
CursorAccent = _fallbackTheme.Colors.CursorAccent;
|
||||
Selection = _fallbackTheme.Colors.Selection;
|
||||
|
||||
Name = _fallbackTheme.Name;
|
||||
Author = _fallbackTheme.Author;
|
||||
|
||||
InEditMode = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
InEditMode = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool NotPreInstalled()
|
||||
{
|
||||
return !_theme.PreInstalled;
|
||||
return !Model.PreInstalled;
|
||||
}
|
||||
|
||||
private async Task Delete()
|
||||
@@ -354,9 +402,7 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
private void Edit()
|
||||
{
|
||||
_fallBackColors = new TerminalColors(_theme.Colors);
|
||||
_fallBackName = Name;
|
||||
_fallBackAuthor = Author;
|
||||
_fallbackTheme = new TerminalTheme(Model);
|
||||
InEditMode = true;
|
||||
}
|
||||
|
||||
@@ -367,7 +413,7 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
private Task Export()
|
||||
{
|
||||
var content = JsonConvert.SerializeObject(_theme, Formatting.Indented, new JsonSerializerSettings { ContractResolver = new TerminalThemeContractResolver() });
|
||||
var content = JsonConvert.SerializeObject(Model, Formatting.Indented, new JsonSerializerSettings { ContractResolver = new TerminalThemeContractResolver() });
|
||||
return _fileSystemService.SaveTextFile(Name, "Fluent Terminal Theme", ".flutecolors", content);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Timer = System.Timers.Timer;
|
||||
|
||||
namespace FluentTerminal.App.ViewModels.Utilities
|
||||
{
|
||||
public class InputBuffer
|
||||
{
|
||||
private readonly Timer _timer = new Timer();
|
||||
private readonly Func<string, Task> _writeCallback;
|
||||
private readonly List<string> _buffer = new List<string>();
|
||||
private readonly object _lock = new object();
|
||||
|
||||
public InputBuffer(Func<string, Task> writeCallback)
|
||||
{
|
||||
_timer.AutoReset = true;
|
||||
_timer.Elapsed += _timer_Elapsed;
|
||||
_writeCallback = writeCallback;
|
||||
_timer.Interval = 5;
|
||||
_timer.Start();
|
||||
}
|
||||
|
||||
private void _timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var data = string.Concat(_buffer);
|
||||
_buffer.Clear();
|
||||
_writeCallback.Invoke(data);
|
||||
}
|
||||
}
|
||||
|
||||
public void Write(string data)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_buffer.Add(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using FluentTerminal.App.Services.EventArgs;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.ApplicationModel.Core;
|
||||
using Windows.Foundation.Metadata;
|
||||
using Windows.UI.Core;
|
||||
using Windows.UI.Core.Preview;
|
||||
using Windows.UI.ViewManagement;
|
||||
@@ -29,6 +30,11 @@ namespace FluentTerminal.App.Adapters
|
||||
set => _applicationView.Title = value;
|
||||
}
|
||||
|
||||
public bool IsApiContractPresent(string api, ushort version)
|
||||
{
|
||||
return ApiInformation.IsApiContractPresent(api, version);
|
||||
}
|
||||
|
||||
public Task RunOnDispatcherThread(Action action)
|
||||
{
|
||||
return _dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => action()).AsTask();
|
||||
@@ -57,7 +63,10 @@ namespace FluentTerminal.App.Adapters
|
||||
var deferral = e.GetDeferral();
|
||||
|
||||
var args = new CancelableEventArgs();
|
||||
await CloseRequested?.Invoke(this, args);
|
||||
if (CloseRequested != null)
|
||||
{
|
||||
await CloseRequested.Invoke(this, args);
|
||||
}
|
||||
|
||||
e.Handled = args.Cancelled;
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
<converters:EnumValueToVisibiltyConverter x:Key="EnumValueToVisibiltyConverter" />
|
||||
<converters:TabThemeSelectedConverter x:Key="TabThemeSelectedConverter" />
|
||||
<converters:ColorResourceKeyFallbackConverter x:Key="ColorResourceKeyFallbackConverter" />
|
||||
<converters:EnumToDescriptionConverter x:Key="EnumToDescriptionConverter" />
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
@@ -4,6 +4,7 @@ using FluentTerminal.App.Dialogs;
|
||||
using FluentTerminal.App.Services;
|
||||
using FluentTerminal.App.Services.Adapters;
|
||||
using FluentTerminal.App.Services.Dialogs;
|
||||
using FluentTerminal.App.Services.EventArgs;
|
||||
using FluentTerminal.App.Services.Implementation;
|
||||
using FluentTerminal.App.ViewModels;
|
||||
using FluentTerminal.App.Views;
|
||||
@@ -48,6 +49,8 @@ namespace FluentTerminal.App
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
UnhandledException += OnUnhandledException;
|
||||
|
||||
var applicationDataContainers = new ApplicationDataContainers
|
||||
{
|
||||
LocalSettings = new ApplicationDataContainerAdapter(ApplicationData.Current.LocalSettings),
|
||||
@@ -63,6 +66,8 @@ namespace FluentTerminal.App
|
||||
builder.RegisterType<TrayProcessCommunicationService>().As<ITrayProcessCommunicationService>().SingleInstance();
|
||||
builder.RegisterType<DialogService>().As<IDialogService>().SingleInstance();
|
||||
builder.RegisterType<KeyboardCommandService>().As<IKeyboardCommandService>().InstancePerDependency();
|
||||
builder.RegisterType<NotificationService>().As<INotificationService>().InstancePerDependency();
|
||||
builder.RegisterType<UpdateService>().As<IUpdateService>().InstancePerDependency();
|
||||
builder.RegisterType<MainViewModel>().InstancePerDependency();
|
||||
builder.RegisterType<SettingsViewModel>().InstancePerDependency();
|
||||
builder.RegisterType<ThemeParserFactory>().As<IThemeParserFactory>().SingleInstance();
|
||||
@@ -73,6 +78,7 @@ namespace FluentTerminal.App
|
||||
builder.RegisterType<SystemFontService>().As<ISystemFontService>().SingleInstance();
|
||||
builder.RegisterType<ShellProfileSelectionDialog>().As<IShellProfileSelectionDialog>().InstancePerDependency();
|
||||
builder.RegisterType<CreateKeyBindingDialog>().As<ICreateKeyBindingDialog>().InstancePerDependency();
|
||||
builder.RegisterType<InputDialog>().As<IInputDialog>().InstancePerDependency();
|
||||
builder.RegisterType<MessageDialogAdapter>().As<IMessageDialog>().InstancePerDependency();
|
||||
builder.RegisterType<ApplicationViewAdapter>().As<IApplicationView>().InstancePerDependency();
|
||||
builder.RegisterType<DispatcherTimerAdapter>().As<IDispatcherTimer>().InstancePerDependency();
|
||||
@@ -89,6 +95,11 @@ namespace FluentTerminal.App
|
||||
_applicationSettings = _settingsService.GetApplicationSettings();
|
||||
}
|
||||
|
||||
private void OnUnhandledException(object sender, Windows.UI.Xaml.UnhandledExceptionEventArgs e)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(e.Exception.ToString());
|
||||
}
|
||||
|
||||
protected override async void OnActivated(IActivatedEventArgs args)
|
||||
{
|
||||
if (args is CommandLineActivatedEventArgs commandLineActivated)
|
||||
@@ -112,11 +123,11 @@ namespace FluentTerminal.App
|
||||
{
|
||||
if (_applicationSettings.NewTerminalLocation == NewTerminalLocation.Tab && _mainViewModels.Count > 0)
|
||||
{
|
||||
await _mainViewModels.Last().AddTerminal(parameter, false).ConfigureAwait(true);
|
||||
await _mainViewModels.Last().AddTerminal(parameter, false, Guid.Empty).ConfigureAwait(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
await CreateNewTerminalWindow(parameter).ConfigureAwait(true);
|
||||
await CreateNewTerminalWindow(parameter, false).ConfigureAwait(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,7 +141,7 @@ namespace FluentTerminal.App
|
||||
else if (command == "new")
|
||||
{
|
||||
var viewModel = _container.Resolve<MainViewModel>();
|
||||
await viewModel.AddTerminal(parameter, false).ConfigureAwait(true);
|
||||
await viewModel.AddTerminal(parameter, false, Guid.Empty).ConfigureAwait(true);
|
||||
await CreateMainView(typeof(MainPage), viewModel, true).ConfigureAwait(true);
|
||||
}
|
||||
}
|
||||
@@ -142,13 +153,13 @@ namespace FluentTerminal.App
|
||||
if (!_alreadyLaunched)
|
||||
{
|
||||
var viewModel = _container.Resolve<MainViewModel>();
|
||||
await viewModel.AddTerminal(null, false).ConfigureAwait(true);
|
||||
await viewModel.AddTerminal(null, false, Guid.Empty).ConfigureAwait(true);
|
||||
await CreateMainView(typeof(MainPage), viewModel, true).ConfigureAwait(true);
|
||||
Window.Current.Activate();
|
||||
}
|
||||
else if (_mainViewModels.Count == 0)
|
||||
{
|
||||
await CreateSecondaryView<MainViewModel>(typeof(MainPage), true, string.Empty).ConfigureAwait(true);
|
||||
await CreateSecondaryView<MainViewModel>(typeof(MainPage), true, false, string.Empty).ConfigureAwait(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,6 +210,7 @@ namespace FluentTerminal.App
|
||||
mainViewModel.Closed += OnMainViewModelClosed;
|
||||
mainViewModel.NewWindowRequested += OnNewWindowRequested;
|
||||
mainViewModel.ShowSettingsRequested += OnShowSettingsRequested;
|
||||
mainViewModel.ShowAboutRequested += OnShowAboutRequested;
|
||||
_mainViewModels.Add(mainViewModel);
|
||||
}
|
||||
|
||||
@@ -208,13 +220,13 @@ namespace FluentTerminal.App
|
||||
Window.Current.Activate();
|
||||
}
|
||||
|
||||
private async Task CreateNewTerminalWindow(string startupDirectory)
|
||||
private async Task CreateNewTerminalWindow(string startupDirectory, bool showProfileSelection)
|
||||
{
|
||||
var id = await CreateSecondaryView<MainViewModel>(typeof(MainPage), true, startupDirectory).ConfigureAwait(true);
|
||||
var id = await CreateSecondaryView<MainViewModel>(typeof(MainPage), true, showProfileSelection, startupDirectory).ConfigureAwait(true);
|
||||
await ApplicationViewSwitcher.TryShowAsStandaloneAsync(id);
|
||||
}
|
||||
|
||||
private async Task<int> CreateSecondaryView<TViewModel>(Type pageType, bool ExtendViewIntoTitleBar, object parameter)
|
||||
private async Task<int> CreateSecondaryView<TViewModel>(Type pageType, bool ExtendViewIntoTitleBar, bool showProfileSelection, object parameter)
|
||||
{
|
||||
int windowId = 0;
|
||||
TViewModel viewModel = default;
|
||||
@@ -236,8 +248,9 @@ namespace FluentTerminal.App
|
||||
mainViewModel.Closed += OnMainViewModelClosed;
|
||||
mainViewModel.NewWindowRequested += OnNewWindowRequested;
|
||||
mainViewModel.ShowSettingsRequested += OnShowSettingsRequested;
|
||||
mainViewModel.ShowAboutRequested += OnShowAboutRequested;
|
||||
_mainViewModels.Add(mainViewModel);
|
||||
await mainViewModel.AddTerminal(directory, false).ConfigureAwait(true);
|
||||
await mainViewModel.AddTerminal(directory, showProfileSelection, Guid.Empty).ConfigureAwait(true);
|
||||
}
|
||||
|
||||
if (viewModel is SettingsViewModel settingsViewModel)
|
||||
@@ -266,9 +279,9 @@ namespace FluentTerminal.App
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnNewWindowRequested(object sender, EventArgs e)
|
||||
private async void OnNewWindowRequested(object sender, NewWindowRequestedEventArgs e)
|
||||
{
|
||||
await CreateNewTerminalWindow(string.Empty).ConfigureAwait(true);
|
||||
await CreateNewTerminalWindow(string.Empty, e.ShowProfileSelection).ConfigureAwait(true);
|
||||
}
|
||||
|
||||
private void OnSettingsClosed(object sender, EventArgs e)
|
||||
@@ -278,16 +291,27 @@ namespace FluentTerminal.App
|
||||
_settingsWindowId = null;
|
||||
}
|
||||
|
||||
private void OnShowAboutRequested(object sender, EventArgs e)
|
||||
{
|
||||
ShowAbout().ConfigureAwait(true);
|
||||
}
|
||||
|
||||
private async void OnShowSettingsRequested(object sender, EventArgs e)
|
||||
{
|
||||
await ShowSettings().ConfigureAwait(true);
|
||||
}
|
||||
|
||||
private async Task ShowAbout()
|
||||
{
|
||||
await ShowSettings().ConfigureAwait(true);
|
||||
_settingsViewModel.NavigateToAboutPage();
|
||||
}
|
||||
|
||||
private async Task ShowSettings()
|
||||
{
|
||||
if (_settingsViewModel == null)
|
||||
{
|
||||
_settingsWindowId = await CreateSecondaryView<SettingsViewModel>(typeof(SettingsPage), true, null).ConfigureAwait(true);
|
||||
_settingsWindowId = await CreateSecondaryView<SettingsViewModel>(typeof(SettingsPage), true, false, null).ConfigureAwait(true);
|
||||
}
|
||||
await ApplicationViewSwitcher.TryShowAsStandaloneAsync(_settingsWindowId.Value);
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.0 KiB |
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using FluentTerminal.App.Services.Utilities;
|
||||
using Windows.UI.Xaml.Data;
|
||||
|
||||
namespace FluentTerminal.App.Converters
|
||||
{
|
||||
public class EnumToDescriptionConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
// value is the Enum, parameter is the string.
|
||||
if (value is Enum enumValue)
|
||||
{
|
||||
return EnumHelper.GetEnumDescription(enumValue);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using Windows.UI.Xaml.Data;
|
||||
|
||||
namespace FluentTerminal.App.Converters
|
||||
{
|
||||
public class ToolTipValueToPixelConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
return string.Format("{0} px", value);
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<ContentDialog
|
||||
x:Class="FluentTerminal.App.Dialogs.InputDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
|
||||
CloseButtonText="Cancel"
|
||||
PrimaryButtonText="OK"
|
||||
KeyDown="Dialog_KeyUp"
|
||||
mc:Ignorable="d">
|
||||
<TextBox VerticalAlignment="Center" Text="{x:Bind Input, Mode=TwoWay}" />
|
||||
</ContentDialog>
|
||||
@@ -0,0 +1,45 @@
|
||||
using FluentTerminal.App.Services.Dialogs;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using System;
|
||||
using Windows.UI.Xaml.Input;
|
||||
using Windows.System;
|
||||
|
||||
namespace FluentTerminal.App.Dialogs
|
||||
{
|
||||
public sealed partial class InputDialog : ContentDialog, IInputDialog
|
||||
{
|
||||
|
||||
private bool EnterPressed = false;
|
||||
public string Input { get; private set; }
|
||||
|
||||
public InputDialog()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
|
||||
public async Task<string> GetInput()
|
||||
{
|
||||
var result = await ShowAsync();
|
||||
if (result == ContentDialogResult.Primary || EnterPressed)
|
||||
{
|
||||
return Input;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void SetTitle(string title)
|
||||
{
|
||||
Title = title;
|
||||
}
|
||||
|
||||
void Dialog_KeyUp(object sender, KeyRoutedEventArgs e)
|
||||
{
|
||||
if (e.Key == VirtualKey.Enter)
|
||||
{
|
||||
EnterPressed = true;
|
||||
Hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,8 @@
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<ProjectTypeGuids>{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<WindowsXamlEnableOverview>true</WindowsXamlEnableOverview>
|
||||
<PackageCertificateKeyFile>FluentTerminal.App_StoreKey.pfx</PackageCertificateKeyFile>
|
||||
<PackageCertificateThumbprint>8FA8EF727E767FAE5409DDD1694C1CEE054A2962</PackageCertificateThumbprint>
|
||||
<PackageCertificateKeyFile>FluentTerminal.App_TemporaryKey.pfx</PackageCertificateKeyFile>
|
||||
<PackageCertificateThumbprint>F96F3E0E485F07E8E98DA6E5C06AA4B6F112B498</PackageCertificateThumbprint>
|
||||
<AppxAutoIncrementPackageRevision>False</AppxAutoIncrementPackageRevision>
|
||||
<AppxBundle>Always</AppxBundle>
|
||||
<AppxBundlePlatforms>x86|x64</AppxBundlePlatforms>
|
||||
@@ -113,6 +113,7 @@
|
||||
<Compile Include="Behaviors\MiddleClickBehavior.cs" />
|
||||
<Compile Include="Converters\BackgroundToApplicationThemeConverter.cs" />
|
||||
<Compile Include="Converters\ColorResourceKeyFallbackConverter.cs" />
|
||||
<Compile Include="Converters\EnumToDescriptionConverter.cs" />
|
||||
<Compile Include="Converters\TabColorFallbackConverter.cs" />
|
||||
<Compile Include="Converters\EnumValueToVisibiltyConverter.cs" />
|
||||
<Compile Include="Converters\FalseToVisibleConverter.cs" />
|
||||
@@ -122,8 +123,12 @@
|
||||
<Compile Include="Converters\StringToColorConverter.cs" />
|
||||
<Compile Include="Converters\TabThemeSelectedConverter.cs" />
|
||||
<Compile Include="Converters\TerminalViewModelToViewConverter.cs" />
|
||||
<Compile Include="Converters\ToolTipValueToPixelConverter.cs" />
|
||||
<Compile Include="Converters\TrueToVisibleConverter.cs" />
|
||||
<Compile Include="Adapters\MessageDialogAdapter.cs" />
|
||||
<Compile Include="Dialogs\InputDialog.xaml.cs">
|
||||
<DependentUpon>InputDialog.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Dialogs\ShellProfileSelectionDialog.xaml.cs">
|
||||
<DependentUpon>ShellProfileSelectionDialog.xaml</DependentUpon>
|
||||
</Compile>
|
||||
@@ -146,6 +151,9 @@
|
||||
<Compile Include="Views\SettingsPage.xaml.cs">
|
||||
<DependentUpon>SettingsPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\SettingsPages\About.xaml.cs">
|
||||
<DependentUpon>About.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\SettingsPages\MouseSettings.xaml.cs">
|
||||
<DependentUpon>MouseSettings.xaml</DependentUpon>
|
||||
</Compile>
|
||||
@@ -168,18 +176,29 @@
|
||||
<DependentUpon>TabBar.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\TabBarBackgroundBindingHelper.cs" />
|
||||
<Compile Include="Views\TerminalKeybindTemplateSelector.cs" />
|
||||
<Compile Include="Views\TabThemeTemplateSelector.cs" />
|
||||
<Compile Include="Views\TerminalColorPicker.xaml.cs">
|
||||
<DependentUpon>TerminalColorPicker.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\NoValueTemplateSelector.cs" />
|
||||
<Compile Include="Views\TerminalThemeTemplateSelector.cs" />
|
||||
<Compile Include="Views\ITerminalView.cs" />
|
||||
<Compile Include="Views\TerminalView.xaml.cs">
|
||||
<DependentUpon>TerminalView.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\VtNetCoreTerminalView.xaml.cs">
|
||||
<DependentUpon>VtNetCoreTerminalView.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\XtermTerminalView.xaml.cs">
|
||||
<DependentUpon>XtermTerminalView.xaml</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AppxManifest Include="Package.appxmanifest">
|
||||
<SubType>Designer</SubType>
|
||||
</AppxManifest>
|
||||
<Content Include="Assets\AppIcons\Square106x106Logo.black.png" />
|
||||
<Content Include="Assets\settingsicons.ttf" />
|
||||
<Content Include="Win32\Autofac.dll" />
|
||||
<Content Include="Win32\FluentTerminal.App.Services.dll" />
|
||||
@@ -269,6 +288,10 @@
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</ApplicationDefinition>
|
||||
<Page Include="Dialogs\InputDialog.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Dialogs\ShellProfileSelectionDialog.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
@@ -294,6 +317,10 @@
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Views\SettingsPages\About.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Views\SettingsPages\MouseSettings.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
@@ -330,6 +357,14 @@
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Views\VtNetCoreTerminalView.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Views\XtermTerminalView.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Autofac">
|
||||
@@ -350,6 +385,9 @@
|
||||
<PackageReference Include="Newtonsoft.Json">
|
||||
<Version>11.0.2</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="VtNetCore">
|
||||
<Version>1.0.15</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Win2D.uwp">
|
||||
<Version>1.21.0</Version>
|
||||
</PackageReference>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10" IgnorableNamespaces="uap mp desktop rescap">
|
||||
<Identity Name="53621FSApps.FluentTerminal" Publisher="CN=BDC72197-B634-438A-B863-16DCB7D90C85" Version="0.2.1.0" />
|
||||
<Identity Name="53621FSApps.FluentTerminal" Publisher="CN=BDC72197-B634-438A-B863-16DCB7D90C85" Version="0.3.1.0" />
|
||||
<mp:PhoneIdentity PhoneProductId="f7225014-f405-4342-ad2e-ebf6ac60ea2c" PhonePublisherId="00000000-0000-0000-0000-000000000000" />
|
||||
<Properties>
|
||||
<DisplayName>Fluent Terminal</DisplayName>
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
<Thickness x:Key="HeaderMargin">24,0,0,0</Thickness>
|
||||
<Thickness x:Key="ContainerMargin">24,0,24,24</Thickness>
|
||||
<Thickness x:Key="ItemMargin">0,24,0,0</Thickness>
|
||||
|
||||
<Style
|
||||
x:Key="WindowTitleTextBlockStyle"
|
||||
BasedOn="{StaticResource CaptionTextBlockStyle}"
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
using FluentTerminal.App.ViewModels;
|
||||
using FluentTerminal.Models;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FluentTerminal.App.Views
|
||||
{
|
||||
public interface ITerminalView
|
||||
{
|
||||
Task ChangeTheme(TerminalTheme theme);
|
||||
Task ChangeKeyBindings();
|
||||
Task ChangeOptions(TerminalOptions options);
|
||||
Task Initialize(TerminalViewModel viewModel);
|
||||
Task FindNext(string searchText);
|
||||
Task FindPrevious(string searchText);
|
||||
Task FocusTerminal();
|
||||
}
|
||||
}
|
||||
@@ -10,13 +10,15 @@
|
||||
mc:Ignorable="d">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="180" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
Width="180"
|
||||
Margin="0,6,24,0"
|
||||
Text="{x:Bind ViewModel.CommandName, Mode=OneWay}" />
|
||||
Text="{x:Bind ViewModel.CommandName, Mode=OneWay}"
|
||||
Visibility="{x:Bind ShowCommandName, Mode=OneWay}" />
|
||||
<ItemsControl Grid.Column="1" ItemsSource="{x:Bind ViewModel.KeyBindings, Mode=OneWay}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="settings:KeyBindingViewModel">
|
||||
@@ -82,11 +84,13 @@
|
||||
<HyperlinkButton
|
||||
Margin="12,0,0,0"
|
||||
Command="{x:Bind EditCommand}"
|
||||
Content="Edit" />
|
||||
Content="Edit"
|
||||
Visibility="{x:Bind Parent.Editable, Converter={StaticResource TrueToVisibleConverter}, Mode=OneWay}" />
|
||||
<HyperlinkButton
|
||||
Margin="12,0,0,0"
|
||||
Command="{x:Bind DeleteCommand}"
|
||||
Content="Delete" />
|
||||
Content="Delete"
|
||||
Visibility="{x:Bind Parent.Editable, Converter={StaticResource TrueToVisibleConverter}, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
|
||||
@@ -6,14 +6,23 @@ namespace FluentTerminal.App.Views
|
||||
{
|
||||
public sealed partial class KeyBindingsView : UserControl
|
||||
{
|
||||
public static readonly DependencyProperty ShowCommandNameProperty =
|
||||
DependencyProperty.Register(nameof(ShowCommandName), typeof(bool), typeof(KeyBindingsView), new PropertyMetadata(true));
|
||||
|
||||
public static readonly DependencyProperty ViewModelProperty =
|
||||
DependencyProperty.Register(nameof(ViewModel), typeof(KeyBindingsViewModel), typeof(KeyBindingsView), new PropertyMetadata(null));
|
||||
DependencyProperty.Register(nameof(ViewModel), typeof(KeyBindingsViewModel), typeof(KeyBindingsView), new PropertyMetadata(null));
|
||||
|
||||
public KeyBindingsView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public bool ShowCommandName
|
||||
{
|
||||
get { return (bool)GetValue(ShowCommandNameProperty); }
|
||||
set { SetValue(ShowCommandNameProperty, value); }
|
||||
}
|
||||
|
||||
public KeyBindingsViewModel ViewModel
|
||||
{
|
||||
get { return (KeyBindingsViewModel)GetValue(ViewModelProperty); }
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:views="using:FluentTerminal.App.Views"
|
||||
RequestedTheme="{x:Bind ViewModel.Background, Mode=OneWay, Converter={StaticResource BackgroundToApplicationThemeConverter}}"
|
||||
RequestedTheme="{x:Bind ViewModel.SelectedTerminal.TerminalTheme.Colors.Background, Mode=OneWay, Converter={StaticResource BackgroundToApplicationThemeConverter}}"
|
||||
mc:Ignorable="d">
|
||||
<Page.Resources>
|
||||
<converters:TerminalViewModelToViewConverter x:Key="TerminalViewModelToViewConverter" />
|
||||
@@ -15,9 +15,11 @@
|
||||
<Grid.Background>
|
||||
<AcrylicBrush
|
||||
BackgroundSource="HostBackdrop"
|
||||
FallbackColor="{x:Bind ViewModel.Background, Mode=OneWay}"
|
||||
TintColor="{x:Bind ViewModel.Background, Mode=OneWay}"
|
||||
TintOpacity="{x:Bind ViewModel.BackgroundOpacity, Mode=OneWay}" />
|
||||
FallbackColor="{x:Bind ViewModel.SelectedTerminal.TerminalTheme.Colors.Background, Mode=OneWay}"
|
||||
TintColor="{x:Bind ViewModel.SelectedTerminal.TerminalTheme.Colors.Background, Mode=OneWay}"
|
||||
TintOpacity="{x:Bind ViewModel.BackgroundOpacity, Mode=OneWay}"
|
||||
TintTransitionDuration="{x:Bind NoDuration}" />
|
||||
|
||||
</Grid.Background>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
@@ -60,6 +62,17 @@
|
||||
<SymbolIcon Symbol="Setting" />
|
||||
</MenuFlyoutItem.Icon>
|
||||
</MenuFlyoutItem>
|
||||
<MenuFlyoutItem
|
||||
Width="160"
|
||||
Command="{x:Bind ViewModel.ShowAboutCommand}"
|
||||
Text="About">
|
||||
<MenuFlyoutItem.Icon>
|
||||
<FontIcon
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
Glyph=""
|
||||
ToolTipService.ToolTip="About" />
|
||||
</MenuFlyoutItem.Icon>
|
||||
</MenuFlyoutItem>
|
||||
</MenuFlyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
@@ -68,6 +81,8 @@
|
||||
IsHitTestVisible="False"
|
||||
Style="{StaticResource WindowTitleTextBlockStyle}"
|
||||
Text="{x:Bind ViewModel.SelectedTerminal.Title, Mode=OneWay}"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="NoWrap"
|
||||
Visibility="{x:Bind ViewModel.ShowTabsOnTop, Mode=OneWay, Converter={StaticResource FalseToVisibleConverter}}" />
|
||||
<views:TabBar
|
||||
Grid.Column="1"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using FluentTerminal.App.Utilities;
|
||||
using FluentTerminal.App.ViewModels;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using Windows.ApplicationModel.Core;
|
||||
using Windows.UI.Core;
|
||||
@@ -18,6 +19,8 @@ namespace FluentTerminal.App.Views
|
||||
|
||||
public double CoreTitleBarHeight => coreTitleBar.Height;
|
||||
|
||||
public TimeSpan NoDuration => TimeSpan.Zero;
|
||||
|
||||
public Thickness CoreTitleBarPadding
|
||||
{
|
||||
get
|
||||
@@ -59,11 +62,11 @@ namespace FluentTerminal.App.Views
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnWindowActivated(object sender, WindowActivatedEventArgs e)
|
||||
private void OnWindowActivated(object sender, WindowActivatedEventArgs e)
|
||||
{
|
||||
if (e.WindowActivationState != CoreWindowActivationState.Deactivated && TerminalContainer.Content is TerminalView terminal)
|
||||
{
|
||||
await terminal.FocusWebView().ConfigureAwait(true);
|
||||
terminal.ViewModel.FocusTerminal();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
|
||||
namespace FluentTerminal.App.Views
|
||||
{
|
||||
public class NoValueTemplateSelector : DataTemplateSelector
|
||||
{
|
||||
public DataTemplate DefaultTemplate { get; set; }
|
||||
public DataTemplate CustomTemplate { get; set; }
|
||||
|
||||
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
|
||||
{
|
||||
return SelectTemplateCore(item);
|
||||
}
|
||||
|
||||
protected override DataTemplate SelectTemplateCore(object item)
|
||||
{
|
||||
if (item is String arguments)
|
||||
{
|
||||
return String.Empty.Equals(arguments) ? DefaultTemplate : CustomTemplate;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@
|
||||
x:Name="NavigationView"
|
||||
AlwaysShowHeader="False"
|
||||
Canvas.ZIndex="0"
|
||||
CompactModeThresholdWidth="560"
|
||||
CompactModeThresholdWidth="0"
|
||||
ExpandedModeThresholdWidth="560"
|
||||
IsSettingsVisible="False"
|
||||
Loaded="NavigationView_Loaded"
|
||||
@@ -74,6 +74,16 @@
|
||||
</NavigationViewItem.Icon>
|
||||
</NavigationViewItem>
|
||||
</NavigationView.MenuItems>
|
||||
<NavigationView.PaneFooter>
|
||||
<NavigationViewItem Content="About" Tapped="AboutTapped">
|
||||
<NavigationViewItem.Icon>
|
||||
<FontIcon
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
Glyph=""
|
||||
ToolTipService.ToolTip="About" />
|
||||
</NavigationViewItem.Icon>
|
||||
</NavigationViewItem>
|
||||
</NavigationView.PaneFooter>
|
||||
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
<Frame x:Name="ContentFrame" />
|
||||
<TextBlock
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using FluentTerminal.App.Utilities;
|
||||
using FluentTerminal.App.ViewModels;
|
||||
using FluentTerminal.App.Views.SettingsPages;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Windows.ApplicationModel.Core;
|
||||
using Windows.Foundation;
|
||||
@@ -9,6 +10,7 @@ using Windows.UI.Core.Preview;
|
||||
using Windows.UI.ViewManagement;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Input;
|
||||
using Windows.UI.Xaml.Navigation;
|
||||
|
||||
namespace FluentTerminal.App.Views
|
||||
@@ -18,6 +20,7 @@ namespace FluentTerminal.App.Views
|
||||
private readonly UISettings _uiSettings;
|
||||
private readonly ApplicationViewTitleBar _titleBar;
|
||||
private readonly CoreDispatcher _dispatcher;
|
||||
private readonly NavigationViewItem hiddenNavigationItem;
|
||||
private bool _onThemesPage;
|
||||
|
||||
public SettingsPage()
|
||||
@@ -39,6 +42,8 @@ namespace FluentTerminal.App.Views
|
||||
_uiSettings = new UISettings();
|
||||
_uiSettings.ColorValuesChanged += OnColorValuesChanged;
|
||||
SystemNavigationManagerPreview.GetForCurrentView().CloseRequested += SettingsPage_CloseRequested;
|
||||
|
||||
hiddenNavigationItem = new NavigationViewItem();
|
||||
}
|
||||
|
||||
private void OnColorValuesChanged(UISettings sender, object args)
|
||||
@@ -67,9 +72,24 @@ namespace FluentTerminal.App.Views
|
||||
if (e.Parameter is SettingsViewModel viewModel)
|
||||
{
|
||||
ViewModel = viewModel;
|
||||
ViewModel.AboutPageRequested += OnAboutPageRequested;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAboutPageRequested(object sender, System.EventArgs e)
|
||||
{
|
||||
_dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
|
||||
{
|
||||
ContentFrame.Navigate(typeof(About), ViewModel.About);
|
||||
|
||||
// Deselect item in NavigationView (https://stackoverflow.com/a/49082640/4132379)
|
||||
NavigationView.MenuItems.Add(hiddenNavigationItem);
|
||||
NavigationView.SelectedItem = hiddenNavigationItem;
|
||||
NavigationView.SelectedItem = null;
|
||||
NavigationView.MenuItems.Remove(hiddenNavigationItem);
|
||||
});
|
||||
}
|
||||
|
||||
private void NavigationView_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
NavigationView.SelectedItem = NavigationView.MenuItems.Cast<NavigationViewItemBase>().FirstOrDefault(m => m.Tag.ToString() == "general");
|
||||
@@ -116,6 +136,11 @@ namespace FluentTerminal.App.Views
|
||||
}
|
||||
}
|
||||
|
||||
private void AboutTapped(object sender, TappedRoutedEventArgs e)
|
||||
{
|
||||
OnAboutPageRequested(sender, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void SettingsPage_CloseRequested(object sender, SystemNavigationCloseRequestedPreviewEventArgs e)
|
||||
{
|
||||
ViewModel.Close();
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
<Page
|
||||
x:Class="FluentTerminal.App.Views.SettingsPages.About"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
|
||||
mc:Ignorable="d">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="32" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Grid.Row="1" Margin="{StaticResource HeaderMargin}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
Margin="0,10,0,0"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="28"
|
||||
Style="{StaticResource TitleTextBlockStyle}"
|
||||
Text="About" />
|
||||
<CommandBar
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Background="{ThemeResource SystemControlBackgroundAltHighBrush}"
|
||||
DefaultLabelPosition="Right">
|
||||
<AppBarButton
|
||||
Command="{x:Bind ViewModel.CheckForUpdatesCommand}"
|
||||
Icon="Sync"
|
||||
Label="Check for updates" />
|
||||
</CommandBar>
|
||||
</Grid>
|
||||
<ScrollViewer Grid.Row="2">
|
||||
<Grid Margin="{StaticResource ContainerMargin}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel Grid.Row="0">
|
||||
<Image
|
||||
MaxWidth="106"
|
||||
MaxHeight="106"
|
||||
Margin="{StaticResource ItemMargin}"
|
||||
HorizontalAlignment="Left"
|
||||
Source="/Assets/AppIcons/Square106x106Logo.black.png"
|
||||
Stretch="None" />
|
||||
|
||||
<StackPanel Margin="{StaticResource ItemMargin}">
|
||||
<TextBlock Margin="0,0,0,8" Text="Current version" />
|
||||
<TextBlock>
|
||||
<Run Text="{x:Bind ViewModel.CurrentVersion}" />
|
||||
-<Hyperlink NavigateUri="{x:Bind ViewModel.CurrentVersionReleaseNotesURL}">Release notes</Hyperlink>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Margin="{StaticResource ItemMargin}">
|
||||
<TextBlock Margin="0,0,0,8" Text="Latest version" />
|
||||
<StackPanel Orientation="Horizontal" Visibility="{x:Bind ViewModel.LatestVersionLoading, Mode=OneWay, Converter={StaticResource TrueToVisibleConverter}}">
|
||||
<ProgressRing Margin="0,0,8,0" IsActive="True" />
|
||||
<TextBlock><Italic>Checking for updates...</Italic></TextBlock>
|
||||
</StackPanel>
|
||||
<TextBlock Visibility="{x:Bind ViewModel.LatestVersionNotFound, Mode=OneWay, Converter={StaticResource TrueToVisibleConverter}}">
|
||||
<Italic>Not found</Italic>
|
||||
</TextBlock>
|
||||
<TextBlock Visibility="{x:Bind ViewModel.LatestVersionFound, Mode=OneWay, Converter={StaticResource TrueToVisibleConverter}}">
|
||||
<Run Text="{x:Bind ViewModel.LatestVersion, Mode=OneWay}" />
|
||||
-<Hyperlink NavigateUri="{x:Bind ViewModel.LatestVersionReleaseNotesURL, Mode=OneWay}">Release notes</Hyperlink>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Margin="{StaticResource ItemMargin}">
|
||||
<TextBlock Margin="0,0,0,8" Text="License" />
|
||||
<TextBlock><Hyperlink NavigateUri="https://github.com/felixse/FluentTerminal/blob/master/LICENSE">GNU General Public License v3.0</Hyperlink></TextBlock>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Grid.Row="1"
|
||||
Margin="0,72,0,0"
|
||||
VerticalAlignment="Bottom">
|
||||
<TextBlock x:Name="CreatedBy" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Page>
|
||||
@@ -0,0 +1,31 @@
|
||||
using FluentTerminal.App.ViewModels.Settings;
|
||||
using System;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Documents;
|
||||
using Windows.UI.Xaml.Navigation;
|
||||
|
||||
namespace FluentTerminal.App.Views.SettingsPages
|
||||
{
|
||||
public sealed partial class About : Page
|
||||
{
|
||||
public AboutPageViewModel ViewModel { get; private set; }
|
||||
|
||||
public About()
|
||||
{
|
||||
InitializeComponent();
|
||||
CreatedBy.Inlines.Add(new Run { Text = "Created by " });
|
||||
CreatedBy.Inlines.Add(new Italic { Inlines = { new Run { Text = "felixse " } } });
|
||||
CreatedBy.Inlines.Add(new Run { Text = "and " });
|
||||
CreatedBy.Inlines.Add(new Hyperlink { Inlines = { new Run { Text = "contributors" } }, NavigateUri = new Uri("https://github.com/felixse/FluentTerminal/graphs/contributors") });
|
||||
}
|
||||
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
if (e.Parameter is AboutPageViewModel viewModel)
|
||||
{
|
||||
ViewModel = viewModel;
|
||||
}
|
||||
ViewModel.OnNavigatedTo();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Grid.Row="1" Margin="24,00,0,24">
|
||||
<Grid Grid.Row="1" Margin="{StaticResource HeaderMargin}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
@@ -34,67 +34,93 @@
|
||||
Label="Restore Defaults" />
|
||||
</CommandBar>
|
||||
</Grid>
|
||||
<ScrollViewer Grid.Row="2" Margin="24,0,0,0">
|
||||
<StackPanel>
|
||||
<ScrollViewer Grid.Row="2">
|
||||
<StackPanel Margin="{StaticResource ContainerMargin}">
|
||||
<StackPanel Margin="{StaticResource ItemMargin}">
|
||||
<ToggleSwitch
|
||||
Header="Run on startup"
|
||||
IsEnabled="{x:Bind ViewModel.CanEnableStartupTask, Mode=OneWay}"
|
||||
IsOn="{x:Bind ViewModel.StartupTaskEnabled, Mode=TwoWay}" />
|
||||
<TextBlock
|
||||
Foreground="Red"
|
||||
Text="{x:Bind ViewModel.StartupTaskErrorMessage, Mode=OneWay}"
|
||||
Visibility="{x:Bind ViewModel.CanEnableStartupTask, Mode=OneWay, Converter={StaticResource FalseToVisibleConverter}}" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Margin="{StaticResource ItemMargin}">
|
||||
<ToggleSwitch Header="Show in system tray" IsOn="{x:Bind ViewModel.EnableTrayIcon, Mode=TwoWay}" />
|
||||
<TextBlock
|
||||
Foreground="Red"
|
||||
Text="Relaunch required to apply setting"
|
||||
Visibility="{x:Bind ViewModel.ShouldRestartForTrayMessage, Mode=OneWay, Converter={StaticResource TrueToVisibleConverter}}" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Margin="{StaticResource ItemMargin}">
|
||||
<TextBlock Margin="0,0,0,8" Text="Open new terminals as" />
|
||||
<RadioButton
|
||||
Content="Tabs"
|
||||
GroupName="NewTerminalLocation"
|
||||
IsChecked="{x:Bind ViewModel.TabIsSelected, Mode=TwoWay}" />
|
||||
<RadioButton
|
||||
Content="Windows"
|
||||
GroupName="NewTerminalLocation"
|
||||
IsChecked="{x:Bind ViewModel.WindowIsSelected, Mode=TwoWay}" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Margin="{StaticResource ItemMargin}">
|
||||
<TextBlock Margin="0,0,0,8" Text="Show tabs on" />
|
||||
<RadioButton
|
||||
Content="Top"
|
||||
GroupName="TabsPosition"
|
||||
IsChecked="{x:Bind ViewModel.TopIsSelected, Mode=TwoWay}" />
|
||||
<RadioButton
|
||||
Content="Bottom"
|
||||
GroupName="TabsPosition"
|
||||
IsChecked="{x:Bind ViewModel.BottomIsSelected, Mode=TwoWay}" />
|
||||
</StackPanel>
|
||||
|
||||
<ToggleSwitch
|
||||
Header="Run on startup"
|
||||
IsEnabled="{x:Bind ViewModel.CanEnableStartupTask, Mode=OneWay}"
|
||||
IsOn="{x:Bind ViewModel.StartupTaskEnabled, Mode=TwoWay}" />
|
||||
<TextBlock
|
||||
Foreground="Red"
|
||||
Text="{x:Bind ViewModel.StartupTaskErrorMessage, Mode=OneWay}"
|
||||
Visibility="{x:Bind ViewModel.CanEnableStartupTask, Mode=OneWay, Converter={StaticResource FalseToVisibleConverter}}" />
|
||||
<TextBlock Margin="0,24,0,8" Text="Open new terminals as" />
|
||||
<RadioButton
|
||||
Content="Tabs"
|
||||
GroupName="NewTerminalLocation"
|
||||
IsChecked="{x:Bind ViewModel.TabIsSelected, Mode=TwoWay}" />
|
||||
<RadioButton
|
||||
Margin="0,0,0,24"
|
||||
Content="Windows"
|
||||
GroupName="NewTerminalLocation"
|
||||
IsChecked="{x:Bind ViewModel.WindowIsSelected, Mode=TwoWay}" />
|
||||
<TextBlock Margin="0,0,0,8" Text="Show tabs on" />
|
||||
<RadioButton
|
||||
Content="Top"
|
||||
GroupName="TabsPosition"
|
||||
IsChecked="{x:Bind ViewModel.TopIsSelected, Mode=TwoWay}" />
|
||||
<RadioButton
|
||||
Margin="0,0,0,24"
|
||||
Content="Bottom"
|
||||
GroupName="TabsPosition"
|
||||
IsChecked="{x:Bind ViewModel.BottomIsSelected, Mode=TwoWay}" />
|
||||
<ToggleSwitch
|
||||
Margin="0,0,0,24"
|
||||
Margin="{StaticResource ItemMargin}"
|
||||
Header="Always show tabs"
|
||||
IsOn="{x:Bind ViewModel.AlwaysShowTabs, Mode=TwoWay}" />
|
||||
|
||||
<ToggleSwitch
|
||||
Margin="0,0,0,24"
|
||||
Margin="{StaticResource ItemMargin}"
|
||||
Header="Underline selected tab"
|
||||
IsOn="{x:Bind ViewModel.UnderlineSelectedTab, Mode=TwoWay}" />
|
||||
<TextBlock Margin="0,0,0,8" Text="Color on inactive tabs" />
|
||||
<RadioButton
|
||||
Content="Background"
|
||||
GroupName="InactiveTabColorMode"
|
||||
IsChecked="{x:Bind ViewModel.BackgroundIsSelected, Mode=TwoWay}" />
|
||||
<RadioButton
|
||||
Margin="0,0,0,24"
|
||||
Content="Underline"
|
||||
GroupName="InactiveTabColorMode"
|
||||
IsChecked="{x:Bind ViewModel.UnderlinedIsSelected, Mode=TwoWay}" />
|
||||
|
||||
<StackPanel Margin="{StaticResource ItemMargin}">
|
||||
<TextBlock Margin="0,0,0,8" Text="Color on inactive tabs" />
|
||||
<RadioButton
|
||||
Content="Background"
|
||||
GroupName="InactiveTabColorMode"
|
||||
IsChecked="{x:Bind ViewModel.BackgroundIsSelected, Mode=TwoWay}" />
|
||||
<RadioButton
|
||||
Content="Underline"
|
||||
GroupName="InactiveTabColorMode"
|
||||
IsChecked="{x:Bind ViewModel.UnderlinedIsSelected, Mode=TwoWay}" />
|
||||
</StackPanel>
|
||||
|
||||
<ToggleSwitch
|
||||
Margin="0,0,0,24"
|
||||
Margin="{StaticResource ItemMargin}"
|
||||
Header="Ask before closing windows"
|
||||
IsOn="{x:Bind ViewModel.ConfirmClosingWindows, Mode=TwoWay}" />
|
||||
|
||||
<ToggleSwitch
|
||||
Margin="0,0,0,24"
|
||||
Margin="{StaticResource ItemMargin}"
|
||||
Header="Ask before closing tabs"
|
||||
IsOn="{x:Bind ViewModel.ConfirmClosingTabs, Mode=TwoWay}" />
|
||||
|
||||
<ToggleSwitch
|
||||
Margin="0,0,0,24"
|
||||
Margin="{StaticResource ItemMargin}"
|
||||
Header="Show new output indicator on background tabs"
|
||||
IsOn="{x:Bind ViewModel.ShowNewOutputIndicator, Mode=TwoWay}" />
|
||||
|
||||
<ToggleSwitch
|
||||
Margin="{StaticResource ItemMargin}"
|
||||
Header="Windows 10 1809+: Always use WinPty"
|
||||
IsOn="{x:Bind ViewModel.AlwaysUseWinPty, Mode=TwoWay}" />
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Page>
|
||||
</Page>
|
||||
@@ -13,7 +13,7 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Grid.Row="1" Margin="24,0,0,0">
|
||||
<Grid Grid.Row="1" Margin="{StaticResource HeaderMargin}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
@@ -42,11 +42,11 @@
|
||||
Label="Restore Defaults" />
|
||||
</CommandBar>
|
||||
</Grid>
|
||||
<ScrollViewer Grid.Row="2" Margin="24,24,0,0">
|
||||
<ItemsControl ItemsSource="{x:Bind ViewModel.KeyBindings, Mode=OneWay}">
|
||||
<ScrollViewer Grid.Row="2">
|
||||
<ItemsControl Margin="{StaticResource ContainerMargin}" ItemsSource="{x:Bind ViewModel.KeyBindings, Mode=OneWay}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="settings:KeyBindingsViewModel">
|
||||
<views:KeyBindingsView Margin="0,0,0,24" ViewModel="{x:Bind}" />
|
||||
<views:KeyBindingsView Margin="{StaticResource ItemMargin}" ViewModel="{x:Bind}" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using FluentTerminal.App.Services.Utilities;
|
||||
using FluentTerminal.App.ViewModels.Settings;
|
||||
using FluentTerminal.Models.Enums;
|
||||
using FluentTerminal.Models;
|
||||
using System;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Navigation;
|
||||
@@ -22,17 +23,18 @@ namespace FluentTerminal.App.Views.SettingsPages
|
||||
{
|
||||
ViewModel = viewModel;
|
||||
|
||||
// Add the commands corresponding to all of the application's static commands
|
||||
foreach (var value in Enum.GetValues(typeof(Command)))
|
||||
{
|
||||
var command = (Command)value;
|
||||
Command command = (Command)value;
|
||||
AddCommandMenu.Items.Add(new MenuFlyoutItem
|
||||
{
|
||||
Text = EnumHelper.GetEnumDescription(command),
|
||||
Command = ViewModel.AddCommand,
|
||||
CommandParameter = command
|
||||
CommandParameter = command.ToString()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,14 +6,13 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="32" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Grid.Row="1" Margin="24,00,0,24">
|
||||
<Grid Grid.Row="1" Margin="{StaticResource HeaderMargin}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
@@ -36,40 +35,44 @@
|
||||
Label="Restore Defaults" />
|
||||
</CommandBar>
|
||||
</Grid>
|
||||
<ScrollViewer Grid.Row="2" Margin="24,0,0,0">
|
||||
<StackPanel>
|
||||
<ScrollViewer Grid.Row="2">
|
||||
<StackPanel Margin="{StaticResource ContainerMargin}">
|
||||
<ToggleSwitch
|
||||
Margin="0,0,0,24"
|
||||
Header="Copy on select"
|
||||
IsOn="{x:Bind ViewModel.CopyOnSelect, Mode=TwoWay}" />
|
||||
<TextBlock Margin="0,0,0,8" Text="Middle button action" />
|
||||
<RadioButton
|
||||
Content="Nothing"
|
||||
GroupName="MiddleButton"
|
||||
IsChecked="{x:Bind ViewModel.MouseMiddleClickNoneIsSelected, Mode=TwoWay}" />
|
||||
<RadioButton
|
||||
Content="Context menu"
|
||||
GroupName="MiddleButton"
|
||||
IsChecked="{x:Bind ViewModel.MouseMiddleClickContextMenuIsSelected, Mode=TwoWay}" />
|
||||
<RadioButton
|
||||
Margin="0,0,0,24"
|
||||
Content="Paste"
|
||||
GroupName="MiddleButton"
|
||||
IsChecked="{x:Bind ViewModel.MouseMiddleClickPasteIsSelected, Mode=TwoWay}" />
|
||||
<TextBlock Margin="0,0,0,8" Text="Right button action" />
|
||||
<RadioButton
|
||||
Content="Nothing"
|
||||
GroupName="RightButton"
|
||||
IsChecked="{x:Bind ViewModel.MouseRightClickNoneIsSelected, Mode=TwoWay}" />
|
||||
<RadioButton
|
||||
Content="Context menu"
|
||||
GroupName="RightButton"
|
||||
IsChecked="{x:Bind ViewModel.MouseRightClickContextMenuIsSelected, Mode=TwoWay}" />
|
||||
<RadioButton
|
||||
Margin="0,0,0,24"
|
||||
Content="Paste"
|
||||
GroupName="RightButton"
|
||||
IsChecked="{x:Bind ViewModel.MouseRightClickPasteIsSelected, Mode=TwoWay}" />
|
||||
Margin="{StaticResource ItemMargin}"
|
||||
Header="Copy on select"
|
||||
IsOn="{x:Bind ViewModel.CopyOnSelect, Mode=TwoWay}" />
|
||||
|
||||
<StackPanel Margin="{StaticResource ItemMargin}">
|
||||
<TextBlock Margin="0,0,0,8" Text="Middle button action" />
|
||||
<RadioButton
|
||||
Content="Nothing"
|
||||
GroupName="MiddleButton"
|
||||
IsChecked="{x:Bind ViewModel.MouseMiddleClickNoneIsSelected, Mode=TwoWay}" />
|
||||
<RadioButton
|
||||
Content="Context menu"
|
||||
GroupName="MiddleButton"
|
||||
IsChecked="{x:Bind ViewModel.MouseMiddleClickContextMenuIsSelected, Mode=TwoWay}" />
|
||||
<RadioButton
|
||||
Content="Paste"
|
||||
GroupName="MiddleButton"
|
||||
IsChecked="{x:Bind ViewModel.MouseMiddleClickPasteIsSelected, Mode=TwoWay}" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Margin="{StaticResource ItemMargin}">
|
||||
<TextBlock Margin="0,0,0,8" Text="Right button action" />
|
||||
<RadioButton
|
||||
Content="Nothing"
|
||||
GroupName="RightButton"
|
||||
IsChecked="{x:Bind ViewModel.MouseRightClickNoneIsSelected, Mode=TwoWay}" />
|
||||
<RadioButton
|
||||
Content="Context menu"
|
||||
GroupName="RightButton"
|
||||
IsChecked="{x:Bind ViewModel.MouseRightClickContextMenuIsSelected, Mode=TwoWay}" />
|
||||
<RadioButton
|
||||
Content="Paste"
|
||||
GroupName="RightButton"
|
||||
IsChecked="{x:Bind ViewModel.MouseRightClickPasteIsSelected, Mode=TwoWay}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:models="using:FluentTerminal.Models"
|
||||
xmlns:settings="using:FluentTerminal.App.ViewModels.Settings"
|
||||
xmlns:viewmodels="using:FluentTerminal.App.ViewModels"
|
||||
xmlns:views="using:FluentTerminal.App.Views"
|
||||
mc:Ignorable="d">
|
||||
@@ -35,6 +36,45 @@
|
||||
</DataTemplate>
|
||||
</views:TabThemeTemplateSelector.ColoredTemplate>
|
||||
</views:TabThemeTemplateSelector>
|
||||
<views:TerminalThemeTemplateSelector x:Key="TerminalThemeTemplateSelector">
|
||||
<views:TerminalThemeTemplateSelector.DefaultTemplate>
|
||||
<DataTemplate x:DataType="models:TerminalTheme">
|
||||
<TextBlock FontStyle="Italic" Text="{x:Bind Name}" />
|
||||
</DataTemplate>
|
||||
</views:TerminalThemeTemplateSelector.DefaultTemplate>
|
||||
<views:TerminalThemeTemplateSelector.ThemeTemplate>
|
||||
<DataTemplate x:DataType="models:TerminalTheme">
|
||||
<TextBlock Text="{x:Bind Name}" />
|
||||
</DataTemplate>
|
||||
</views:TerminalThemeTemplateSelector.ThemeTemplate>
|
||||
</views:TerminalThemeTemplateSelector>
|
||||
<views:TerminalKeybindTemplateSelector x:Key="TerminalKeybindTemplateSelector">
|
||||
<views:TerminalKeybindTemplateSelector.DefaultTemplate>
|
||||
<DataTemplate x:DataType="settings:KeyBindingsViewModel">
|
||||
<TextBlock FontStyle="Italic" Text="None" />
|
||||
</DataTemplate>
|
||||
</views:TerminalKeybindTemplateSelector.DefaultTemplate>
|
||||
<views:TerminalKeybindTemplateSelector.KeyBindingsTemplate>
|
||||
<DataTemplate x:DataType="settings:KeyBindingsViewModel">
|
||||
<views:KeyBindingsView
|
||||
Margin="0,6,0,0"
|
||||
ShowCommandName="False"
|
||||
ViewModel="{x:Bind}" />
|
||||
</DataTemplate>
|
||||
</views:TerminalKeybindTemplateSelector.KeyBindingsTemplate>
|
||||
</views:TerminalKeybindTemplateSelector>
|
||||
<views:NoValueTemplateSelector x:Key="NoValueTemplateSelector">
|
||||
<views:NoValueTemplateSelector.DefaultTemplate>
|
||||
<DataTemplate x:DataType="x:String">
|
||||
<TextBlock FontStyle="Italic" Text="None" />
|
||||
</DataTemplate>
|
||||
</views:NoValueTemplateSelector.DefaultTemplate>
|
||||
<views:NoValueTemplateSelector.CustomTemplate>
|
||||
<DataTemplate x:DataType="x:String">
|
||||
<TextBlock Text="{x:Bind}" />
|
||||
</DataTemplate>
|
||||
</views:NoValueTemplateSelector.CustomTemplate>
|
||||
</views:NoValueTemplateSelector>
|
||||
</Page.Resources>
|
||||
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
<Grid.ColumnDefinitions>
|
||||
@@ -50,16 +90,13 @@
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="0"
|
||||
Background="{ThemeResource SystemControlAcrylicWindowBrush}" />
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="2"
|
||||
Margin="0,0,0,24">
|
||||
<Grid Grid.Row="1" Grid.ColumnSpan="2" Margin="{StaticResource HeaderMargin}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
Margin="24,10,0,0"
|
||||
Margin="0,10,0,0"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="28"
|
||||
Style="{StaticResource TitleTextBlockStyle}"
|
||||
@@ -76,6 +113,11 @@
|
||||
Icon="Accept"
|
||||
IsEnabled="{x:Bind ViewModel.SelectedShellProfile.IsDefault, Mode=OneWay, Converter={StaticResource NegateConverter}}"
|
||||
Label="Set Default" />
|
||||
<AppBarButton
|
||||
Command="{x:Bind ViewModel.CloneCommand, Mode=OneWay}"
|
||||
CommandParameter="{x:Bind ViewModel.SelectedShellProfile, Mode=OneWay}"
|
||||
Icon="Copy"
|
||||
Label="Clone" />
|
||||
<AppBarButton
|
||||
Command="{x:Bind ViewModel.SelectedShellProfile.EditCommand, Mode=OneWay}"
|
||||
Icon="Edit"
|
||||
@@ -83,7 +125,13 @@
|
||||
<AppBarButton
|
||||
Command="{x:Bind ViewModel.SelectedShellProfile.DeleteCommand, Mode=OneWay}"
|
||||
Icon="Delete"
|
||||
Label="Delete" />
|
||||
Label="Delete"
|
||||
Visibility="{x:Bind ViewModel.SelectedShellProfile.PreInstalled, Mode=OneWay, Converter={StaticResource FalseToVisibleConverter}}" />
|
||||
<AppBarButton
|
||||
Command="{x:Bind ViewModel.SelectedShellProfile.RestoreDefaultsCommand, Mode=OneWay}"
|
||||
Icon="Undo"
|
||||
Label="Restore Defaults"
|
||||
Visibility="{x:Bind ViewModel.SelectedShellProfile.PreInstalled, Mode=OneWay, Converter={StaticResource TrueToVisibleConverter}}" />
|
||||
</CommandBar>
|
||||
<CommandBar
|
||||
Grid.Column="1"
|
||||
@@ -106,7 +154,7 @@
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel>
|
||||
<StackPanel Margin="{StaticResource ItemMargin}">
|
||||
<ListView ItemsSource="{x:Bind ViewModel.ShellProfiles}" SelectedItem="{x:Bind ViewModel.SelectedShellProfile, Mode=TwoWay}">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="viewmodels:ShellProfileViewModel">
|
||||
@@ -157,39 +205,42 @@
|
||||
<ScrollViewer
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Margin="24,0,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<ContentControl HorizontalContentAlignment="Stretch" Content="{x:Bind ViewModel.SelectedShellProfile, Mode=OneWay}">
|
||||
<ContentControl
|
||||
Margin="{StaticResource ContainerMargin}"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
Content="{x:Bind ViewModel.SelectedShellProfile, Mode=OneWay}">
|
||||
<ContentControl.ContentTemplate>
|
||||
<DataTemplate x:DataType="viewmodels:ShellProfileViewModel">
|
||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<StackPanel Margin="0,0,24,0" Visibility="{x:Bind InEditMode, Converter={StaticResource TrueToVisibleConverter}, Mode=OneWay}">
|
||||
<StackPanel Visibility="{x:Bind InEditMode, Converter={StaticResource TrueToVisibleConverter}, Mode=OneWay}">
|
||||
<TextBox
|
||||
Width="300"
|
||||
Margin="{StaticResource ItemMargin}"
|
||||
HorizontalAlignment="Left"
|
||||
Header="Name"
|
||||
IsEnabled="{x:Bind PreInstalled, Converter={StaticResource NegateConverter}, Mode=OneWay}"
|
||||
IsSpellCheckEnabled="False"
|
||||
Text="{x:Bind Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
|
||||
<StackPanel Margin="0,24,0,0" Orientation="Horizontal">
|
||||
<StackPanel Margin="{StaticResource ItemMargin}" Orientation="Horizontal">
|
||||
<TextBox
|
||||
Width="300"
|
||||
Header="Shell executable location"
|
||||
IsSpellCheckEnabled="False"
|
||||
IsEnabled="{x:Bind PreInstalled, Converter={StaticResource NegateConverter}, Mode=OneWay}"
|
||||
IsSpellCheckEnabled="False"
|
||||
Text="{x:Bind Location, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<Button
|
||||
Margin="6,0,0,0"
|
||||
VerticalAlignment="Bottom"
|
||||
IsEnabled="{x:Bind PreInstalled, Converter={StaticResource NegateConverter}, Mode=OneWay}"
|
||||
Command="{x:Bind BrowseForCustomShellCommand}"
|
||||
Content="..."
|
||||
IsEnabled="{x:Bind PreInstalled, Converter={StaticResource NegateConverter}, Mode=OneWay}"
|
||||
Style="{StaticResource ButtonRevealStyle}" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Margin="0,24,0,0" Orientation="Horizontal">
|
||||
<StackPanel Margin="{StaticResource ItemMargin}" Orientation="Horizontal">
|
||||
<TextBox
|
||||
Width="300"
|
||||
Header="Working directory"
|
||||
@@ -206,43 +257,121 @@
|
||||
|
||||
<TextBox
|
||||
Width="300"
|
||||
Margin="0,24,0,0"
|
||||
Margin="{StaticResource ItemMargin}"
|
||||
HorizontalAlignment="Left"
|
||||
Header="Arguments"
|
||||
IsEnabled="{x:Bind InEditMode, Mode=OneWay}"
|
||||
IsSpellCheckEnabled="False"
|
||||
Text="{x:Bind Arguments, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
|
||||
<ComboBox
|
||||
Width="150"
|
||||
Margin="0,24,0,0"
|
||||
Margin="{StaticResource ItemMargin}"
|
||||
Header="Tab color"
|
||||
IsEnabled="{x:Bind InEditMode, Mode=OneWay}"
|
||||
ItemTemplateSelector="{StaticResource TabThemeTemplateSelector}"
|
||||
ItemsSource="{x:Bind TabThemes, Mode=OneWay}"
|
||||
SelectedItem="{Binding SelectedTabTheme, Mode=TwoWay}" />
|
||||
|
||||
<ComboBox
|
||||
Width="150"
|
||||
Margin="{StaticResource ItemMargin}"
|
||||
Header="Theme"
|
||||
IsEnabled="{x:Bind InEditMode, Mode=OneWay}"
|
||||
ItemTemplateSelector="{StaticResource TerminalThemeTemplateSelector}"
|
||||
ItemsSource="{x:Bind TerminalThemes, Mode=OneWay}"
|
||||
SelectedItem="{Binding SelectedTerminalTheme, Mode=TwoWay}" />
|
||||
|
||||
<StackPanel Margin="{StaticResource ItemMargin}">
|
||||
<TextBlock Margin="0,0,0,8" Text="Line ending translation mode on paste" />
|
||||
<RadioButton
|
||||
Content="Do not modify line endings"
|
||||
GroupName="LineEndingStyle"
|
||||
IsChecked="{x:Bind DoNotModifyIsSelected, Mode=TwoWay}" />
|
||||
<RadioButton
|
||||
Content="Convert line endings to CR+LF"
|
||||
GroupName="LineEndingStyle"
|
||||
IsChecked="{x:Bind ToCRLFIsSelected, Mode=TwoWay}" />
|
||||
<RadioButton
|
||||
Content="Convert line endings to LF"
|
||||
GroupName="LineEndingStyle"
|
||||
IsChecked="{x:Bind ToLFIsSelected, Mode=TwoWay}" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Margin="{StaticResource ItemMargin}">
|
||||
<TextBlock
|
||||
Margin="0,0,6,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="Keyboard shortcut" />
|
||||
<HyperlinkButton Command="{x:Bind AddKeyboardShortcutCommand}" Content="Add" />
|
||||
<views:KeyBindingsView ShowCommandName="False" ViewModel="{x:Bind KeyBindings}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<StackPanel Margin="0,0,24,0" Visibility="{x:Bind InEditMode, Mode=OneWay, Converter={StaticResource FalseToVisibleConverter}}">
|
||||
<TextBlock Style="{StaticResource SubtitleTextBlockStyle}" Text="{x:Bind Name, Mode=OneWay}" />
|
||||
<StackPanel Visibility="{x:Bind InEditMode, Mode=OneWay, Converter={StaticResource FalseToVisibleConverter}}">
|
||||
<TextBlock
|
||||
Margin="{StaticResource ItemMargin}"
|
||||
Style="{StaticResource SubtitleTextBlockStyle}"
|
||||
Text="{x:Bind Name, Mode=OneWay}" />
|
||||
|
||||
<controls:HeaderedTextBlock
|
||||
Margin="0,58,0,0"
|
||||
Margin="{StaticResource ItemMargin}"
|
||||
Header="Shell executable location"
|
||||
Text="{x:Bind Location, Mode=OneWay}" />
|
||||
<controls:HeaderedTextBlock
|
||||
Margin="0,36,0,0"
|
||||
Header="Working directory"
|
||||
Text="{x:Bind WorkingDirectory, Mode=OneWay}" />
|
||||
<controls:HeaderedTextBlock
|
||||
Margin="0,36,0,0"
|
||||
Header="Arguments"
|
||||
Text="{x:Bind Arguments, Mode=OneWay}" />
|
||||
|
||||
<controls:HeaderedContentControl
|
||||
Margin="0,36,0,0"
|
||||
Margin="{StaticResource ItemMargin}"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
Header="Working directory">
|
||||
<ContentControl
|
||||
Content="{x:Bind WorkingDirectory, Mode=OneWay}"
|
||||
ContentTemplateSelector="{StaticResource NoValueTemplateSelector}"
|
||||
FontSize="18"
|
||||
FontWeight="Light" />
|
||||
</controls:HeaderedContentControl>
|
||||
|
||||
<controls:HeaderedContentControl
|
||||
Margin="{StaticResource ItemMargin}"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
Header="Arguments">
|
||||
<ContentControl
|
||||
Content="{x:Bind Arguments, Mode=OneWay}"
|
||||
ContentTemplateSelector="{StaticResource NoValueTemplateSelector}"
|
||||
FontSize="18"
|
||||
FontWeight="Light" />
|
||||
</controls:HeaderedContentControl>
|
||||
|
||||
<controls:HeaderedContentControl
|
||||
Margin="{StaticResource ItemMargin}"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
Header="Tab color">
|
||||
<ContentControl
|
||||
Margin="0,6,0,0"
|
||||
Content="{x:Bind SelectedTabTheme, Mode=OneWay}"
|
||||
ContentTemplateSelector="{StaticResource TabThemeTemplateSelector}" />
|
||||
ContentTemplateSelector="{StaticResource TabThemeTemplateSelector}"
|
||||
FontSize="18"
|
||||
FontWeight="Light" />
|
||||
</controls:HeaderedContentControl>
|
||||
|
||||
<controls:HeaderedContentControl
|
||||
Margin="{StaticResource ItemMargin}"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
Header="Line ending translation mode">
|
||||
<ContentControl
|
||||
Content="{x:Bind LineEndingStyle, Mode=OneWay, Converter={StaticResource EnumToDescriptionConverter}}"
|
||||
ContentTemplateSelector="{StaticResource TerminalThemeTemplateSelector}"
|
||||
FontSize="18"
|
||||
FontWeight="Light" />
|
||||
</controls:HeaderedContentControl>
|
||||
|
||||
<controls:HeaderedContentControl
|
||||
Margin="{StaticResource ItemMargin}"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
Header="Keyboard shortcut">
|
||||
<ContentControl
|
||||
Content="{x:Bind KeyBindings, Mode=OneWay}"
|
||||
ContentTemplateSelector="{StaticResource TerminalKeybindTemplateSelector}"
|
||||
FontSize="18"
|
||||
FontWeight="Light" />
|
||||
</controls:HeaderedContentControl>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
@@ -251,4 +380,4 @@
|
||||
</ContentControl>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Page>
|
||||
</Page>
|
||||
@@ -2,16 +2,20 @@
|
||||
x:Class="FluentTerminal.App.Views.SettingsPages.TerminalSettings"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:converters="using:FluentTerminal.App.Converters"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
<Page.Resources>
|
||||
<converters:ToolTipValueToPixelConverter x:Key="ToolTipValueToPixelConverter" />
|
||||
</Page.Resources>
|
||||
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="32" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Grid.Row="1" Margin="24,00,0,24">
|
||||
<Grid Grid.Row="1" Margin="{StaticResource HeaderMargin}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
@@ -34,68 +38,93 @@
|
||||
Label="Restore Defaults" />
|
||||
</CommandBar>
|
||||
</Grid>
|
||||
<ScrollViewer Grid.Row="2" Margin="24,0,0,0">
|
||||
<StackPanel>
|
||||
<ScrollViewer Grid.Row="2">
|
||||
<StackPanel Margin="{StaticResource ContainerMargin}">
|
||||
<ComboBox
|
||||
Width="200"
|
||||
Margin="0,0,0,24"
|
||||
Margin="{StaticResource ItemMargin}"
|
||||
Header="Font family"
|
||||
ItemsSource="{x:Bind ViewModel.Fonts}"
|
||||
SelectedValue="{x:Bind ViewModel.FontFamily, Mode=TwoWay}" />
|
||||
|
||||
<ComboBox
|
||||
Width="200"
|
||||
Margin="0,0,0,24"
|
||||
Margin="{StaticResource ItemMargin}"
|
||||
Header="Font size"
|
||||
ItemsSource="{x:Bind ViewModel.Sizes}"
|
||||
SelectedValue="{x:Bind ViewModel.FontSize, Mode=TwoWay}" />
|
||||
|
||||
<ToggleSwitch
|
||||
Margin="{StaticResource ItemMargin}"
|
||||
Header="Bold text"
|
||||
IsOn="{x:Bind ViewModel.BoldText, Mode=TwoWay}" />
|
||||
|
||||
<Slider
|
||||
Width="200"
|
||||
Margin="0,0,0,24"
|
||||
Margin="{StaticResource ItemMargin}"
|
||||
HorizontalAlignment="Left"
|
||||
Header="Background opacity"
|
||||
Maximum="1"
|
||||
Minimum="0"
|
||||
StepFrequency="0.01"
|
||||
Value="{x:Bind ViewModel.BackgroundOpacity, Mode=TwoWay}" />
|
||||
|
||||
<Slider
|
||||
Width="200"
|
||||
Margin="{StaticResource ItemMargin}"
|
||||
HorizontalAlignment="Left"
|
||||
Header="Padding"
|
||||
Maximum="64"
|
||||
Minimum="0"
|
||||
StepFrequency="1"
|
||||
ThumbToolTipValueConverter="{StaticResource ToolTipValueToPixelConverter}"
|
||||
Value="{x:Bind ViewModel.Padding, Mode=TwoWay}" />
|
||||
|
||||
<TextBox
|
||||
Width="200"
|
||||
Margin="0,0,0,24"
|
||||
Margin="{StaticResource ItemMargin}"
|
||||
HorizontalAlignment="Left"
|
||||
Header="Scrollback limit"
|
||||
KeyDown="ScrollBackTextBoxOnKeyDown"
|
||||
Text="{x:Bind ViewModel.ScrollBackLimit, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
|
||||
<ToggleSwitch
|
||||
Margin="0,0,0,24"
|
||||
Margin="{StaticResource ItemMargin}"
|
||||
Header="Blinking cursor"
|
||||
IsOn="{x:Bind ViewModel.CursorBlink, Mode=TwoWay}" />
|
||||
<TextBlock Margin="0,0,0,8" Text="Cursor style" />
|
||||
<RadioButton
|
||||
Content="Block"
|
||||
GroupName="Cursor"
|
||||
IsChecked="{x:Bind ViewModel.BlockIsSelected, Mode=TwoWay}" />
|
||||
<RadioButton
|
||||
Content="Underline"
|
||||
GroupName="Cursor"
|
||||
IsChecked="{x:Bind ViewModel.UnderlineIsSelected, Mode=TwoWay}" />
|
||||
<RadioButton
|
||||
Content="Bar"
|
||||
GroupName="Cursor"
|
||||
IsChecked="{x:Bind ViewModel.BarIsSelected, Mode=TwoWay}" />
|
||||
|
||||
<TextBlock Margin="0,24,0,8" Text="Scrollbar style" />
|
||||
<RadioButton
|
||||
Content="Hidden"
|
||||
GroupName="ScrollBar"
|
||||
IsChecked="{x:Bind ViewModel.HiddenIsSelected, Mode=TwoWay}" />
|
||||
<RadioButton
|
||||
Content="Auto hiding"
|
||||
GroupName="ScrollBar"
|
||||
IsChecked="{x:Bind ViewModel.AutoHidingIsSelected, Mode=TwoWay}" />
|
||||
<RadioButton
|
||||
Content="Visible"
|
||||
GroupName="ScrollBar"
|
||||
IsChecked="{x:Bind ViewModel.VisibleIsSelected, Mode=TwoWay}" />
|
||||
<StackPanel Margin="{StaticResource ItemMargin}">
|
||||
<TextBlock Margin="0,0,0,8" Text="Cursor style" />
|
||||
<RadioButton
|
||||
Content="Block"
|
||||
GroupName="Cursor"
|
||||
IsChecked="{x:Bind ViewModel.BlockIsSelected, Mode=TwoWay}" />
|
||||
<RadioButton
|
||||
Content="Underline"
|
||||
GroupName="Cursor"
|
||||
IsChecked="{x:Bind ViewModel.UnderlineIsSelected, Mode=TwoWay}" />
|
||||
<RadioButton
|
||||
Content="Bar"
|
||||
GroupName="Cursor"
|
||||
IsChecked="{x:Bind ViewModel.BarIsSelected, Mode=TwoWay}" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Margin="{StaticResource ItemMargin}">
|
||||
<TextBlock Margin="0,0,0,8" Text="Scrollbar style" />
|
||||
<RadioButton
|
||||
Content="Hidden"
|
||||
GroupName="ScrollBar"
|
||||
IsChecked="{x:Bind ViewModel.HiddenIsSelected, Mode=TwoWay}" />
|
||||
<RadioButton
|
||||
Content="Auto hiding"
|
||||
GroupName="ScrollBar"
|
||||
IsChecked="{x:Bind ViewModel.AutoHidingIsSelected, Mode=TwoWay}" />
|
||||
<RadioButton
|
||||
Content="Visible"
|
||||
GroupName="ScrollBar"
|
||||
IsChecked="{x:Bind ViewModel.VisibleIsSelected, Mode=TwoWay}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Page>
|
||||
</Page>
|
||||
@@ -28,16 +28,13 @@
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="0"
|
||||
Background="{ThemeResource SystemControlAcrylicWindowBrush}" />
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="2"
|
||||
Margin="0,0,0,24">
|
||||
<Grid Grid.Row="1" Grid.ColumnSpan="2" Margin="{StaticResource HeaderMargin}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
Margin="24,10,0,0"
|
||||
Margin="0,10,0,0"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="28"
|
||||
Style="{StaticResource TitleTextBlockStyle}"
|
||||
@@ -63,6 +60,12 @@
|
||||
<FontIcon FontFamily="Segoe MDL2 Assets" Glyph="" />
|
||||
</AppBarButton.Icon>
|
||||
</AppBarButton>
|
||||
<AppBarButton
|
||||
Name="CloneButton"
|
||||
Command="{x:Bind ViewModel.CloneCommand, Mode=OneWay}"
|
||||
CommandParameter="{x:Bind ViewModel.SelectedTheme, Mode=OneWay}"
|
||||
Icon="Copy"
|
||||
Label="Clone" />
|
||||
<AppBarButton
|
||||
Name="EditButton"
|
||||
Command="{x:Bind ViewModel.SelectedTheme.EditCommand, Mode=OneWay}"
|
||||
@@ -97,7 +100,7 @@
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel>
|
||||
<StackPanel Margin="{StaticResource ItemMargin}">
|
||||
<ListView ItemsSource="{x:Bind ViewModel.Themes}" SelectedItem="{x:Bind ViewModel.SelectedTheme, Mode=TwoWay}">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="viewmodels:ThemeViewModel">
|
||||
@@ -169,15 +172,16 @@
|
||||
<ContentControl
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Margin="24,0,0,0"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
Content="{x:Bind ViewModel.SelectedTheme, Mode=TwoWay}"
|
||||
RequestedTheme="{x:Bind ViewModel.SelectedTheme.Background, Mode=OneWay, Converter={StaticResource BackgroundToApplicationThemeConverter}}">
|
||||
<ContentControl.ContentTemplate>
|
||||
<DataTemplate x:DataType="viewmodels:ThemeViewModel">
|
||||
<ScrollViewer Padding="0,0,24,0" VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel>
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Margin="{StaticResource ContainerMargin}">
|
||||
<!-- VIEW THEME -->
|
||||
<TextBlock
|
||||
Margin="{StaticResource ItemMargin}"
|
||||
Style="{StaticResource SubtitleTextBlockStyle}"
|
||||
Text="{x:Bind Name, Mode=OneWay}"
|
||||
Visibility="{x:Bind InEditMode, Mode=OneWay, Converter={StaticResource FalseToVisibleConverter}}">
|
||||
@@ -185,22 +189,42 @@
|
||||
<SolidColorBrush Color="{x:Bind Foreground, Mode=OneWay}" />
|
||||
</TextBlock.Foreground>
|
||||
</TextBlock>
|
||||
<TextBlock Style="{StaticResource CaptionTextBlockStyle}" Text="{x:Bind Author, Mode=OneWay}" />
|
||||
|
||||
<TextBlock
|
||||
Margin="{StaticResource ItemMargin}"
|
||||
FontSize="14"
|
||||
Style="{StaticResource SubtitleTextBlockStyle}"
|
||||
Text="{x:Bind Author, Mode=OneWay}"
|
||||
Visibility="{x:Bind InEditMode, Mode=OneWay, Converter={StaticResource FalseToVisibleConverter}}">
|
||||
<TextBlock.Foreground>
|
||||
<SolidColorBrush Color="{x:Bind Foreground, Mode=OneWay}" />
|
||||
</TextBlock.Foreground>
|
||||
</TextBlock>
|
||||
|
||||
<!-- EDIT THEME -->
|
||||
<TextBox
|
||||
Width="200"
|
||||
Margin="{StaticResource ItemMargin}"
|
||||
HorizontalAlignment="Left"
|
||||
Header="Name"
|
||||
Text="{x:Bind Name, Mode=TwoWay}"
|
||||
Visibility="{x:Bind InEditMode, Mode=OneWay, Converter={StaticResource TrueToVisibleConverter}}" />
|
||||
|
||||
<TextBox
|
||||
Margin="0,24,0,0"
|
||||
Width="200"
|
||||
Margin="{StaticResource ItemMargin}"
|
||||
HorizontalAlignment="Left"
|
||||
Header="Author"
|
||||
Text="{x:Bind Author, Mode=TwoWay}"
|
||||
Visibility="{x:Bind InEditMode, Mode=OneWay, Converter={StaticResource TrueToVisibleConverter}}" />
|
||||
<Grid Margin="0,48,0,0">
|
||||
|
||||
<Grid Margin="{StaticResource ItemMargin}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.Resources>
|
||||
<Style TargetType="Border">
|
||||
@@ -209,7 +233,16 @@
|
||||
<Setter Property="Height" Value="24" />
|
||||
</Style>
|
||||
</Grid.Resources>
|
||||
<StackPanel Grid.Row="0" Orientation="Horizontal">
|
||||
<TextBlock Grid.Row="0" Text="Color palette" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Margin="0,0,0,8"
|
||||
FontSize="14"
|
||||
FontStyle="Italic"
|
||||
Foreground="LightGray"
|
||||
Text="Click on a color to edit"
|
||||
Visibility="{x:Bind InEditMode, Mode=OneWay, Converter={StaticResource TrueToVisibleConverter}}" />
|
||||
<StackPanel Grid.Row="2" Orientation="Horizontal">
|
||||
<views:TerminalColorPicker
|
||||
ColorName="Black"
|
||||
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
|
||||
@@ -243,7 +276,7 @@
|
||||
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
|
||||
SelectedColor="{x:Bind White, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="1" Orientation="Horizontal">
|
||||
<StackPanel Grid.Row="3" Orientation="Horizontal">
|
||||
<views:TerminalColorPicker
|
||||
ColorName="Bright Black"
|
||||
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
|
||||
@@ -277,7 +310,7 @@
|
||||
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
|
||||
SelectedColor="{x:Bind BrightWhite, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="2" Orientation="Horizontal">
|
||||
<StackPanel Grid.Row="4" Orientation="Horizontal">
|
||||
<views:TerminalColorPicker
|
||||
ColorName="Foreground"
|
||||
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
|
||||
@@ -300,11 +333,6 @@
|
||||
IsAlphaEnabled="True"
|
||||
SelectedColor="{x:Bind Selection, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=true}" />
|
||||
</StackPanel>
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Margin="0,6,0,0"
|
||||
Text="Click on a color to edit"
|
||||
Visibility="{x:Bind InEditMode, Mode=OneWay, Converter={StaticResource TrueToVisibleConverter}}" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
@@ -312,4 +340,4 @@
|
||||
</ContentControl.ContentTemplate>
|
||||
</ContentControl>
|
||||
</Grid>
|
||||
</Page>
|
||||
</Page>
|
||||
@@ -41,6 +41,7 @@ namespace FluentTerminal.App.Views.SettingsPages
|
||||
SaveButton.RequestedTheme = theme;
|
||||
CancelButton.RequestedTheme = theme;
|
||||
ExportButton.RequestedTheme = theme;
|
||||
CloneButton.RequestedTheme = theme;
|
||||
|
||||
ContrastHelper.SetTitleBarButtonsForTheme(theme);
|
||||
}
|
||||
|
||||
@@ -151,6 +151,7 @@
|
||||
</ToggleMenuFlyoutItem.Icon>
|
||||
</ToggleMenuFlyoutItem>
|
||||
</MenuFlyoutSubItem>
|
||||
<MenuFlyoutItem Command="{x:Bind EditTitleCommand}" Text="Edit Title" />
|
||||
<MenuFlyoutSeparator />
|
||||
<MenuFlyoutItem Command="{x:Bind CloseCommand}" Text="Close" />
|
||||
</MenuFlyout>
|
||||
@@ -176,7 +177,7 @@
|
||||
<Grid
|
||||
x:Name="CloseButton"
|
||||
Grid.Column="2"
|
||||
Width="32"
|
||||
Width="32"
|
||||
RelativePanel.AlignRightWithPanel="True">
|
||||
<Button
|
||||
HorizontalAlignment="Center"
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
mc:Ignorable="d">
|
||||
<Button
|
||||
x:Name="Root"
|
||||
Background="{x:Bind ButtonBackground, Mode=OneWay}"
|
||||
IsEnabled="{x:Bind EnableEditing, Mode=OneWay}"
|
||||
Style="{StaticResource ColorPreviewButtonStyle}"
|
||||
ToolTipService.ToolTip="{x:Bind ColorName}"
|
||||
Background="{x:Bind ButtonBackground, Mode=OneWay}">
|
||||
ToolTipService.ToolTip="{x:Bind ColorName}">
|
||||
<!--<Button.Background>
|
||||
<SolidColorBrush Color="{x:Bind VisibleColor, Mode=OneWay}" />
|
||||
</Button.Background>-->
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace FluentTerminal.App.Views
|
||||
{
|
||||
public sealed partial class TerminalColorPicker : UserControl
|
||||
{
|
||||
bool colorIsNull = false;
|
||||
private bool _colorIsNull;
|
||||
|
||||
public static readonly DependencyProperty ColorNameProperty =
|
||||
DependencyProperty.Register(nameof(ColorName), typeof(string), typeof(TerminalColorPicker), new PropertyMetadata(null));
|
||||
@@ -23,7 +23,7 @@ namespace FluentTerminal.App.Views
|
||||
{
|
||||
if (s is TerminalColorPicker terminalColorPicker)
|
||||
{
|
||||
terminalColorPicker.colorIsNull = ((Color?)e.NewValue) == null;
|
||||
terminalColorPicker._colorIsNull = ((Color?)e.NewValue) == null;
|
||||
|
||||
// When leading the color, or changing the selected color (happens on page load)
|
||||
// only set the picker (triggering the event action later) if the color is non-null
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
using FluentTerminal.App.ViewModels.Settings;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
|
||||
namespace FluentTerminal.App.Views
|
||||
{
|
||||
public class TerminalKeybindTemplateSelector : DataTemplateSelector
|
||||
{
|
||||
public DataTemplate DefaultTemplate { get; set; }
|
||||
public DataTemplate KeyBindingsTemplate { get; set; }
|
||||
|
||||
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
|
||||
{
|
||||
return SelectTemplateCore(item);
|
||||
}
|
||||
|
||||
protected override DataTemplate SelectTemplateCore(object item)
|
||||
{
|
||||
if (item is KeyBindingsViewModel keyBindings)
|
||||
{
|
||||
return keyBindings.KeyBindings.Count == 0 ? DefaultTemplate : KeyBindingsTemplate;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using FluentTerminal.Models;
|
||||
using System;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
|
||||
namespace FluentTerminal.App.Views
|
||||
{
|
||||
public class TerminalThemeTemplateSelector : DataTemplateSelector
|
||||
{
|
||||
public DataTemplate DefaultTemplate { get; set; }
|
||||
public DataTemplate ThemeTemplate { get; set; }
|
||||
|
||||
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
|
||||
{
|
||||
return SelectTemplateCore(item);
|
||||
}
|
||||
|
||||
protected override DataTemplate SelectTemplateCore(object item)
|
||||
{
|
||||
if (item is TerminalTheme theme)
|
||||
{
|
||||
return theme.Id == Guid.Empty ? DefaultTemplate : ThemeTemplate;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid x:Name="WebViewContainer" Grid.Row="0" />
|
||||
<Grid x:Name="TerminalContainer" Grid.Row="0" />
|
||||
<Grid
|
||||
Grid.Row="0"
|
||||
Margin="12"
|
||||
@@ -38,7 +38,7 @@
|
||||
Name="SearchTextBox"
|
||||
Width="300"
|
||||
Margin="6,0,6,0"
|
||||
KeyUp="SearchTextBox_KeyUp"
|
||||
KeyUp="OnSearchTextBoxKeyUp"
|
||||
PlaceholderText="Enter search term"
|
||||
RelativePanel.RightOf="SearchLabel"
|
||||
Text="{x:Bind ViewModel.SearchText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
|
||||
@@ -1,241 +1,68 @@
|
||||
using FluentTerminal.App.Services;
|
||||
using FluentTerminal.App.ViewModels;
|
||||
using FluentTerminal.Models;
|
||||
using FluentTerminal.Models.Enums;
|
||||
using FluentTerminal.RuntimeComponent.Enums;
|
||||
using FluentTerminal.RuntimeComponent.Interfaces;
|
||||
using FluentTerminal.RuntimeComponent.WebAllowedObjects;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using FluentTerminal.App.ViewModels;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.ApplicationModel.Core;
|
||||
using Windows.Foundation;
|
||||
using Windows.System;
|
||||
using Windows.UI;
|
||||
using Windows.UI.Core;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
|
||||
namespace FluentTerminal.App.Views
|
||||
{
|
||||
public sealed partial class TerminalView : UserControl, ITerminalView, ITerminalEventListener
|
||||
public sealed partial class TerminalView : UserControl
|
||||
{
|
||||
private MenuFlyoutItem _copyMenuItem;
|
||||
private BlockingCollection<Action> _dispatcherJobs;
|
||||
private readonly SemaphoreSlim _loaded;
|
||||
private MenuFlyoutItem _pasteMenuItem;
|
||||
private WebView _webView;
|
||||
private readonly ITerminalView _terminalView;
|
||||
|
||||
public TerminalView(TerminalViewModel viewModel)
|
||||
{
|
||||
ViewModel = viewModel;
|
||||
ViewModel.SearchStarted += OnSearchStarted;
|
||||
ViewModel.Activated += OnActivated;
|
||||
ViewModel.ThemeChanged += OnThemeChanged;
|
||||
ViewModel.OptionsChanged += OnOptionsChanged;
|
||||
ViewModel.KeyBindingsChanged += OnKeyBindingsChanged;
|
||||
ViewModel.FindNextRequested += OnFindNextRequested;
|
||||
ViewModel.FindPreviousRequested += OnFindPreviousRequested;
|
||||
InitializeComponent();
|
||||
Loaded += OnLoaded;
|
||||
_loaded = new SemaphoreSlim(0, 1);
|
||||
|
||||
JsonConvert.DefaultSettings = () =>
|
||||
{
|
||||
var settings = new JsonSerializerSettings
|
||||
{
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver(),
|
||||
};
|
||||
settings.Converters.Add(new StringEnumConverter(true));
|
||||
|
||||
return settings;
|
||||
};
|
||||
|
||||
StartMediatorTask();
|
||||
//_terminalView = new XtermTerminalView();
|
||||
_terminalView = new VtNetCoreTerminalView();
|
||||
TerminalContainer.Children.Add((UIElement)_terminalView);
|
||||
_terminalView.Initialize(ViewModel);
|
||||
}
|
||||
|
||||
public event EventHandler<Command> KeyboardCommandReceived;
|
||||
|
||||
public event EventHandler<TerminalSize> TerminalSizeChanged;
|
||||
|
||||
public event EventHandler<string> TerminalTitleChanged;
|
||||
|
||||
public TerminalViewModel ViewModel { get; }
|
||||
|
||||
public Task ChangeOptions(TerminalOptions options)
|
||||
private async void OnActivated(object sender, EventArgs e)
|
||||
{
|
||||
var serialized = JsonConvert.SerializeObject(options);
|
||||
return ExecuteScriptAsync($"changeOptions('{serialized}')");
|
||||
}
|
||||
|
||||
public Task ChangeTheme(TerminalColors theme)
|
||||
{
|
||||
var serialized = JsonConvert.SerializeObject(theme);
|
||||
return ExecuteScriptAsync($"changeTheme('{serialized}')");
|
||||
}
|
||||
|
||||
public Task ChangeKeyBindings(IEnumerable<KeyBinding> keyBindings)
|
||||
{
|
||||
var serialized = JsonConvert.SerializeObject(keyBindings);
|
||||
return ExecuteScriptAsync($"changeKeyBindings('{serialized}')");
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
_webView?.Navigate(new Uri("about:blank"));
|
||||
_webView = null;
|
||||
}
|
||||
|
||||
public Task ConnectToSocket(string url)
|
||||
{
|
||||
return ExecuteScriptAsync($"connectToWebSocket('{url}');");
|
||||
}
|
||||
|
||||
public async Task<TerminalSize> CreateTerminal(TerminalOptions options, TerminalColors theme, IEnumerable<KeyBinding> keyBindings)
|
||||
{
|
||||
var serializedOptions = JsonConvert.SerializeObject(options);
|
||||
var serializedTheme = JsonConvert.SerializeObject(theme);
|
||||
var serializedKeyBindings = JsonConvert.SerializeObject(keyBindings);
|
||||
var size = await ExecuteScriptAsync($"createTerminal('{serializedOptions}', '{serializedTheme}', '{serializedKeyBindings}')").ConfigureAwait(true);
|
||||
return JsonConvert.DeserializeObject<TerminalSize>(size);
|
||||
}
|
||||
|
||||
public Task<string> ExecuteScriptAsync(string script)
|
||||
{
|
||||
try
|
||||
if (_terminalView != null)
|
||||
{
|
||||
return _webView.InvokeScriptAsync("eval", new[] { script }).AsTask();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.WriteLine($"Exception while running:\n \"{script}\"\n\n {e}");
|
||||
}
|
||||
return Task.FromResult(string.Empty);
|
||||
}
|
||||
|
||||
public async Task FocusTerminal()
|
||||
{
|
||||
if (_webView != null)
|
||||
{
|
||||
_webView.Focus(FocusState.Programmatic);
|
||||
await ExecuteScriptAsync("document.focus();").ConfigureAwait(true);
|
||||
await _terminalView.FocusTerminal().ConfigureAwait(true);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task FocusWebView()
|
||||
private async void OnFindNextRequested(object sender, string e)
|
||||
{
|
||||
if (_webView != null)
|
||||
{
|
||||
_webView.Focus(FocusState.Programmatic);
|
||||
await ExecuteScriptAsync("document.focus();").ConfigureAwait(true);
|
||||
}
|
||||
await _terminalView.FindNext(e).ConfigureAwait(true);
|
||||
}
|
||||
|
||||
public Task<string> GetSelection() => ExecuteScriptAsync("term.getSelection()");
|
||||
|
||||
public void OnKeyboardCommand(string command)
|
||||
private async void OnFindPreviousRequested(object sender, string e)
|
||||
{
|
||||
_dispatcherJobs.Add(() => KeyboardCommandReceived?.Invoke(this, Enum.Parse<Command>(command, true)));
|
||||
await _terminalView.FindPrevious(e).ConfigureAwait(true);
|
||||
}
|
||||
|
||||
public void OnTerminalResized(int columns, int rows)
|
||||
private async void OnKeyBindingsChanged(object sender, EventArgs e)
|
||||
{
|
||||
_dispatcherJobs.Add(() => TerminalSizeChanged?.Invoke(this, new TerminalSize { Columns = columns, Rows = rows }));
|
||||
await _terminalView.ChangeKeyBindings().ConfigureAwait(true);
|
||||
}
|
||||
|
||||
public void OnTitleChanged(string title)
|
||||
private async void OnOptionsChanged(object sender, Models.TerminalOptions e)
|
||||
{
|
||||
_dispatcherJobs.Add(() => TerminalTitleChanged?.Invoke(this, title));
|
||||
await _terminalView.ChangeOptions(e).ConfigureAwait(true);
|
||||
}
|
||||
|
||||
public Task FindNext(string searchText)
|
||||
private void OnSearchStarted(object sender, EventArgs e)
|
||||
{
|
||||
return ExecuteScriptAsync($"findNext('{searchText}')");
|
||||
SearchTextBox.Focus(FocusState.Programmatic);
|
||||
}
|
||||
|
||||
public Task FindPrevious(string searchText)
|
||||
{
|
||||
return ExecuteScriptAsync($"findPrevious('{searchText}')");
|
||||
}
|
||||
|
||||
private void _webView_NavigationCompleted(WebView sender, WebViewNavigationCompletedEventArgs args)
|
||||
{
|
||||
_loaded.Release();
|
||||
}
|
||||
|
||||
private void _webView_NavigationStarting(WebView sender, WebViewNavigationStartingEventArgs args)
|
||||
{
|
||||
var bridge = new TerminalBridge(this);
|
||||
_webView.AddWebAllowedObject("terminalBridge", bridge);
|
||||
}
|
||||
|
||||
private void Copy_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
OnKeyboardCommand(nameof(Command.Copy));
|
||||
}
|
||||
|
||||
private async void OnLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (WebViewContainer.Children.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_webView = new WebView(WebViewExecutionMode.SeparateThread)
|
||||
{
|
||||
DefaultBackgroundColor = Colors.Transparent
|
||||
};
|
||||
WebViewContainer.Children.Add(_webView);
|
||||
|
||||
_webView.NavigationCompleted += _webView_NavigationCompleted;
|
||||
_webView.NavigationStarting += _webView_NavigationStarting;
|
||||
|
||||
_copyMenuItem = new MenuFlyoutItem { Text = "Copy" };
|
||||
_copyMenuItem.Click += Copy_Click;
|
||||
|
||||
_pasteMenuItem = new MenuFlyoutItem { Text = "Paste" };
|
||||
_pasteMenuItem.Click += Paste_Click;
|
||||
|
||||
_webView.ContextFlyout = new MenuFlyout
|
||||
{
|
||||
Items = { _copyMenuItem, _pasteMenuItem }
|
||||
};
|
||||
|
||||
_webView.Navigate(new Uri("ms-appx-web:///Client/index.html"));
|
||||
|
||||
await _loaded.WaitAsync().ConfigureAwait(true);
|
||||
|
||||
_webView.Focus(FocusState.Programmatic);
|
||||
|
||||
await ViewModel.OnViewIsReady(this).ConfigureAwait(true);
|
||||
}
|
||||
|
||||
private void Paste_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
OnKeyboardCommand(nameof(Command.Paste));
|
||||
}
|
||||
|
||||
private void StartMediatorTask()
|
||||
{
|
||||
_dispatcherJobs = new BlockingCollection<Action>();
|
||||
var dispatcher = CoreApplication.GetCurrentView().CoreWindow.Dispatcher;
|
||||
Task.Run(async () =>
|
||||
{
|
||||
foreach (var job in _dispatcherJobs.GetConsumingEnumerable())
|
||||
{
|
||||
try
|
||||
{
|
||||
await dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => job.Invoke());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.WriteLine(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void SearchTextBox_KeyUp(object sender, Windows.UI.Xaml.Input.KeyRoutedEventArgs e)
|
||||
private void OnSearchTextBoxKeyUp(object sender, Windows.UI.Xaml.Input.KeyRoutedEventArgs e)
|
||||
{
|
||||
if (e.Key == VirtualKey.Escape)
|
||||
{
|
||||
@@ -247,57 +74,9 @@ namespace FluentTerminal.App.Views
|
||||
}
|
||||
}
|
||||
|
||||
public void FocusSearchTextBox()
|
||||
private async void OnThemeChanged(object sender, Models.TerminalTheme e)
|
||||
{
|
||||
SearchTextBox.Focus(FocusState.Programmatic);
|
||||
}
|
||||
|
||||
public void OnMouseClick(MouseButton mouseButton, int x, int y, bool hasSelection)
|
||||
{
|
||||
_dispatcherJobs.Add(() =>
|
||||
{
|
||||
if (mouseButton == MouseButton.Middle)
|
||||
{
|
||||
if (ViewModel.ApplicationSettings.MouseMiddleClickAction == MouseAction.ContextMenu)
|
||||
{
|
||||
ShowContextMenu(x, y, hasSelection);
|
||||
}
|
||||
else if (ViewModel.ApplicationSettings.MouseMiddleClickAction == MouseAction.Paste)
|
||||
{
|
||||
OnKeyboardCommand(nameof(Command.Paste));
|
||||
}
|
||||
}
|
||||
else if (mouseButton == MouseButton.Right)
|
||||
{
|
||||
if (ViewModel.ApplicationSettings.MouseRightClickAction == MouseAction.ContextMenu)
|
||||
{
|
||||
ShowContextMenu(x, y, hasSelection);
|
||||
}
|
||||
else if (ViewModel.ApplicationSettings.MouseRightClickAction == MouseAction.Paste)
|
||||
{
|
||||
OnKeyboardCommand(nameof(Command.Paste));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async void OnSelectionChanged(string selection)
|
||||
{
|
||||
if (ViewModel.ApplicationSettings.CopyOnSelect && !ViewModel.ShowSearchPanel)
|
||||
{
|
||||
_dispatcherJobs.Add(async () =>
|
||||
{
|
||||
ViewModel.CopyText(selection);
|
||||
await ExecuteScriptAsync("term.clearSelection()");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowContextMenu(int x, int y, bool terminalHasSelection)
|
||||
{
|
||||
var flyout = (MenuFlyout)_webView.ContextFlyout;
|
||||
_copyMenuItem.IsEnabled = terminalHasSelection;
|
||||
flyout.ShowAt(_webView, new Point(x, y));
|
||||
await _terminalView.ChangeTheme(e).ConfigureAwait(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<UserControl
|
||||
x:Class="FluentTerminal.App.Views.VtNetCoreTerminalView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:canvas="using:Microsoft.Graphics.Canvas.UI.Xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
d:DesignHeight="300"
|
||||
d:DesignWidth="400"
|
||||
IsTabStop="True"
|
||||
mc:Ignorable="d">
|
||||
<Grid>
|
||||
<canvas:CanvasControl x:Name="Canvas" ClearColor="Transparent" />
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,660 @@
|
||||
using FluentTerminal.App.ViewModels;
|
||||
using FluentTerminal.Models;
|
||||
using FluentTerminal.Models.Enums;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using Microsoft.Graphics.Canvas.Text;
|
||||
using Microsoft.Graphics.Canvas.UI.Xaml;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using VtNetCore.VirtualTerminal;
|
||||
using VtNetCore.VirtualTerminal.Layout;
|
||||
using VtNetCore.XTermParser;
|
||||
using Windows.ApplicationModel.DataTransfer;
|
||||
using Windows.Foundation;
|
||||
using Windows.UI;
|
||||
using Windows.UI.Core;
|
||||
using Windows.UI.Text;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Input;
|
||||
using Windows.UI.Xaml.Media;
|
||||
|
||||
namespace FluentTerminal.App.Views
|
||||
{
|
||||
public sealed partial class VtNetCoreTerminalView : UserControl, ITerminalView
|
||||
{
|
||||
private readonly int _blinkHideMs = 300;
|
||||
private readonly int _blinkShowMs = 600;
|
||||
private readonly DataConsumer _consumer;
|
||||
private readonly VirtualTerminalController _terminal;
|
||||
private readonly DispatcherTimer blinkDispatcher;
|
||||
private double _characterHeight = -1;
|
||||
private double _characterWidth = -1;
|
||||
private int _columns = -1;
|
||||
private TextPosition _mouseOver = new TextPosition();
|
||||
private int _rows = -1;
|
||||
private bool _scrolling;
|
||||
private bool _selecting;
|
||||
|
||||
private TextRange _textSelection;
|
||||
|
||||
public VtNetCoreTerminalView()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
|
||||
_terminal = new VirtualTerminalController();
|
||||
_consumer = new DataConsumer(_terminal);
|
||||
ViewTop = _terminal.ViewPort.TopRow;
|
||||
_terminal.SendData += OnSendData;
|
||||
|
||||
blinkDispatcher = new DispatcherTimer();
|
||||
blinkDispatcher.Tick += BlinkTimerHandler;
|
||||
blinkDispatcher.Interval = TimeSpan.FromMilliseconds(Math.Min(150, GCD(_blinkShowMs, _blinkHideMs)));
|
||||
blinkDispatcher.Start();
|
||||
|
||||
Canvas.Draw += OnDraw;
|
||||
|
||||
KeyDown += TerminalKeyDown;
|
||||
Tapped += OnTapped;
|
||||
GotFocus += TerminalGotFocus;
|
||||
LostFocus += TerminalLostFocus;
|
||||
PointerWheelChanged += OnWheelChanged;
|
||||
PointerMoved += TerminalPointerMoved;
|
||||
PointerExited += TerminalPointerExited;
|
||||
PointerPressed += TerminalPointerPressed;
|
||||
PointerReleased += TerminalPointerReleased;
|
||||
}
|
||||
|
||||
public int MaxScrollValue => _terminal.ViewPort.TopRow;
|
||||
|
||||
public TextPosition MousePressedAt { get; set; }
|
||||
|
||||
public int ScrollValue
|
||||
{
|
||||
get { return ViewTop; }
|
||||
set
|
||||
{
|
||||
_scrolling = true;
|
||||
ViewTop = value;
|
||||
Canvas.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public TerminalViewModel ViewModel { get; private set; }
|
||||
public int ViewTop { get; set; }
|
||||
|
||||
public Task ChangeKeyBindings()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task ChangeOptions(TerminalOptions options)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task ChangeTheme(TerminalTheme theme)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task FindNext(string searchText)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task FindPrevious(string searchText)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task FocusTerminal()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Color GetSolidColorBrush(string hex)
|
||||
{
|
||||
byte a = 255; // (byte)(Convert.ToUInt32(hex.Substring(0, 2), 16));
|
||||
byte r = (byte)(Convert.ToUInt32(hex.Substring(1, 2), 16));
|
||||
byte g = (byte)(Convert.ToUInt32(hex.Substring(3, 2), 16));
|
||||
byte b = (byte)(Convert.ToUInt32(hex.Substring(5, 2), 16));
|
||||
return Color.FromArgb(a, r, g, b);
|
||||
}
|
||||
|
||||
public async Task Initialize(TerminalViewModel viewModel)
|
||||
{
|
||||
ViewModel = viewModel;
|
||||
|
||||
ViewModel.Terminal.OutputReceived += Terminal_OutputReceived;
|
||||
ViewModel.Terminal.Closed += Terminal_Closed;
|
||||
ViewModel.Terminal.RegisterSelectedTextCallback(() => Task.FromResult(_terminal.GetText(_textSelection.Start.Column, _textSelection.Start.Row, _textSelection.End.Column, _textSelection.End.Row)));
|
||||
|
||||
_terminal.WindowTitleChanged += OnWindowTitleChanged;
|
||||
_terminal.SizeChanged += OnSizeChanged;
|
||||
|
||||
var settings = ViewModel.SettingsService.GetApplicationSettings();
|
||||
var options = ViewModel.SettingsService.GetTerminalOptions();
|
||||
|
||||
Canvas.FontFamily = new FontFamily(options.FontFamily);
|
||||
Canvas.FontSize = options.FontSize;
|
||||
Canvas.Margin = new Thickness(options.Padding, options.Padding, options.Padding, options.Padding);
|
||||
|
||||
var sessionType = SessionType.Unknown;
|
||||
if (settings.AlwaysUseWinPty || !ViewModel.ApplicationView.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 7))
|
||||
{
|
||||
sessionType = SessionType.WinPty;
|
||||
}
|
||||
else
|
||||
{
|
||||
sessionType = SessionType.ConPty;
|
||||
}
|
||||
|
||||
await ViewModel.Terminal.StartShellProcess(ViewModel.ShellProfile, new TerminalSize { Columns = 80, Rows = 30 }, sessionType).ConfigureAwait(true);
|
||||
}
|
||||
|
||||
private void BlinkTimerHandler(object sender, object e)
|
||||
{
|
||||
Canvas.Invalidate();
|
||||
}
|
||||
|
||||
private bool BlinkVisible()
|
||||
{
|
||||
var blinkCycle = _blinkShowMs + _blinkHideMs;
|
||||
|
||||
return (DateTime.Now.Subtract(DateTime.MinValue).Milliseconds % blinkCycle) < _blinkHideMs;
|
||||
}
|
||||
|
||||
private bool ControlPressed()
|
||||
{
|
||||
return (Window.Current.CoreWindow.GetKeyState(Windows.System.VirtualKey.Control) & CoreVirtualKeyStates.Down) != 0;
|
||||
}
|
||||
|
||||
private void CoreWindow_CharacterReceived(CoreWindow sender, CharacterReceivedEventArgs args)
|
||||
{
|
||||
var ch = char.ConvertFromUtf32((int)args.KeyCode);
|
||||
|
||||
// Since I get the same key twice in TerminalKeyDown and in CoreWindow_CharacterReceived
|
||||
// I lookup whether KeyPressed should handle the key here or there.
|
||||
var code = _terminal.GetKeySequence(ch, false, false);
|
||||
if (code == null)
|
||||
{
|
||||
args.Handled = _terminal.KeyPressed(ch, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
private long GCD(long a, long b)
|
||||
{
|
||||
a = Math.Abs(a);
|
||||
b = Math.Abs(b);
|
||||
|
||||
// Pull out remainders.
|
||||
for (; ; )
|
||||
{
|
||||
long remainder = a % b;
|
||||
if (remainder == 0) return b;
|
||||
a = b;
|
||||
b = remainder;
|
||||
};
|
||||
}
|
||||
|
||||
private void OnDraw(CanvasControl sender, CanvasDrawEventArgs args)
|
||||
{
|
||||
var drawingSession = args.DrawingSession;
|
||||
|
||||
var textFormat =
|
||||
new CanvasTextFormat
|
||||
{
|
||||
FontSize = Convert.ToSingle(Canvas.FontSize),
|
||||
FontFamily = Canvas.FontFamily.Source,
|
||||
FontWeight = Canvas.FontWeight,
|
||||
WordWrapping = CanvasWordWrapping.NoWrap
|
||||
};
|
||||
|
||||
ProcessTextFormat(drawingSession, textFormat);
|
||||
|
||||
var showBlink = BlinkVisible();
|
||||
var spans = default(List<LayoutRow>);
|
||||
var cursorPosition = default(TextPosition);
|
||||
var showCursor = false;
|
||||
var cursorColor = Colors.Green;
|
||||
var TerminalTop = -1;
|
||||
|
||||
lock (_terminal)
|
||||
{
|
||||
TerminalTop = _terminal.ViewPort.TopRow;
|
||||
|
||||
if (_terminal.ViewPort.CursorPosition.Row > _rows)
|
||||
{
|
||||
throw new Exception("We should never be here");
|
||||
}
|
||||
|
||||
spans = _terminal.ViewPort.GetPageSpans(ViewTop, _rows, _columns, _textSelection);
|
||||
showCursor = _terminal.CursorState.ShowCursor;
|
||||
cursorPosition = _terminal.ViewPort.CursorPosition.Clone();
|
||||
cursorColor = GetSolidColorBrush(_terminal.CursorState.Attributes.WebColor);
|
||||
}
|
||||
|
||||
if (!_scrolling && ViewTop != TerminalTop)
|
||||
{
|
||||
ViewTop = TerminalTop;
|
||||
}
|
||||
|
||||
var defaultTransform = drawingSession.Transform;
|
||||
|
||||
PaintBackgroundLayer(drawingSession, spans);
|
||||
|
||||
PaintTextLayer(drawingSession, spans, textFormat, showBlink);
|
||||
|
||||
if (showCursor)
|
||||
{
|
||||
PaintCursor(drawingSession, spans, textFormat, cursorPosition.OffsetBy(0, TerminalTop - ViewTop), cursorColor);
|
||||
}
|
||||
|
||||
drawingSession.Transform = defaultTransform;
|
||||
}
|
||||
|
||||
private void OnSendData(object sender, SendDataEventArgs e)
|
||||
{
|
||||
ViewModel.Terminal.Write(Encoding.UTF8.GetString(e.Data));
|
||||
}
|
||||
|
||||
private async void OnSizeChanged(object sender, SizeEventArgs args)
|
||||
{
|
||||
await ViewModel.Terminal.SetSize(new TerminalSize { Rows = args.Height, Columns = args.Width }).ConfigureAwait(true);
|
||||
}
|
||||
|
||||
private void OnWindowTitleChanged(object sender, TextEventArgs args)
|
||||
{
|
||||
ViewModel.Terminal.SetTitle(args.Text);
|
||||
}
|
||||
|
||||
private void PaintBackgroundLayer(CanvasDrawingSession drawingSession, List<LayoutRow> spans)
|
||||
{
|
||||
double lineY = 0;
|
||||
foreach (var textRow in spans)
|
||||
{
|
||||
drawingSession.Transform =
|
||||
Matrix3x2.CreateScale(
|
||||
(float)(textRow.DoubleWidth ? 2.0 : 1.0), // Scale double width
|
||||
(float)(textRow.DoubleHeightBottom | textRow.DoubleHeightTop ? 2.0 : 1.0) // Scale double high
|
||||
);
|
||||
|
||||
var drawY =
|
||||
(lineY - (textRow.DoubleHeightBottom ? _characterHeight : 0)) * // Offset position upwards for bottom of double high char
|
||||
((textRow.DoubleHeightBottom | textRow.DoubleHeightTop) ? 0.5 : 1.0); // Scale position for double height
|
||||
|
||||
double drawX = 0;
|
||||
foreach (var textSpan in textRow.Spans)
|
||||
{
|
||||
var bounds =
|
||||
new Rect(
|
||||
drawX,
|
||||
drawY,
|
||||
_characterWidth * (textSpan.Text.Length) + 0.9,
|
||||
_characterHeight + 0.9
|
||||
);
|
||||
|
||||
if (textSpan.BackgroundColor != "#000000") // todo set theme
|
||||
{
|
||||
drawingSession.FillRectangle(bounds, GetSolidColorBrush(textSpan.BackgroundColor));
|
||||
}
|
||||
|
||||
|
||||
drawX += _characterWidth * (textSpan.Text.Length);
|
||||
}
|
||||
|
||||
lineY += _characterHeight;
|
||||
}
|
||||
}
|
||||
|
||||
private void PaintCursor(CanvasDrawingSession drawingSession, List<LayoutRow> spans, CanvasTextFormat textFormat, TextPosition cursorPosition, Color cursorColor)
|
||||
{
|
||||
var cursorY = cursorPosition.Row;
|
||||
if (cursorY < 0 || cursorY >= _rows)
|
||||
return;
|
||||
|
||||
var drawX = cursorPosition.Column * _characterWidth;
|
||||
var drawY = (cursorY * _characterHeight);
|
||||
|
||||
if (cursorY < spans.Count)
|
||||
{
|
||||
var textRow = spans[cursorY];
|
||||
|
||||
drawingSession.Transform =
|
||||
Matrix3x2.CreateTranslation(
|
||||
1.0f,
|
||||
(float)(textRow.DoubleHeightBottom ? -_characterHeight : 0)
|
||||
) *
|
||||
Matrix3x2.CreateScale(
|
||||
(float)(textRow.DoubleWidth ? 2.0 : 1.0),
|
||||
(float)(textRow.DoubleHeightBottom | textRow.DoubleHeightTop ? 2.0 : 1.0)
|
||||
);
|
||||
|
||||
drawY *= (textRow.DoubleHeightBottom | textRow.DoubleHeightTop) ? 0.5 : 1.0;
|
||||
}
|
||||
|
||||
var cursorRect = new Rect(
|
||||
drawX,
|
||||
drawY,
|
||||
_characterWidth,
|
||||
_characterHeight + 0.9
|
||||
);
|
||||
|
||||
drawingSession.DrawRectangle(cursorRect, cursorColor);
|
||||
}
|
||||
|
||||
private void PaintTextLayer(CanvasDrawingSession drawingSession, List<LayoutRow> spans, CanvasTextFormat textFormat, bool showBlink)
|
||||
{
|
||||
var dipToDpiRatio = Windows.Graphics.Display.DisplayInformation.GetForCurrentView().LogicalDpi / 96;
|
||||
|
||||
double lineY = 0;
|
||||
foreach (var textRow in spans)
|
||||
{
|
||||
drawingSession.Transform =
|
||||
Matrix3x2.CreateScale(
|
||||
(float)(textRow.DoubleWidth ? 2.0 : 1.0), // Scale double width
|
||||
(float)(textRow.DoubleHeightBottom | textRow.DoubleHeightTop ? 2.0 : 1.0) // Scale double high
|
||||
);
|
||||
|
||||
var drawY =
|
||||
(lineY - (textRow.DoubleHeightBottom ? _characterHeight : 0)) * // Offset position upwards for bottom of double high char
|
||||
((textRow.DoubleHeightBottom | textRow.DoubleHeightTop) ? 0.5 : 1.0); // Scale position for double height
|
||||
|
||||
double drawX = 0;
|
||||
foreach (var textSpan in textRow.Spans)
|
||||
{
|
||||
var runWidth = _characterWidth * (textSpan.Text.Length);
|
||||
|
||||
if (textSpan.Hidden || (textSpan.Blink && !showBlink))
|
||||
{
|
||||
drawX += runWidth;
|
||||
continue;
|
||||
}
|
||||
|
||||
var color = GetSolidColorBrush(textSpan.ForgroundColor);
|
||||
textFormat.FontWeight = textSpan.Bold ? FontWeights.Bold : FontWeights.Light;
|
||||
|
||||
var textLayout = new CanvasTextLayout(drawingSession, textSpan.Text, textFormat, 0.0f, 0.0f);
|
||||
drawingSession.DrawTextLayout(textLayout, (float)drawX, (float)drawY, color);
|
||||
|
||||
if (textSpan.Underline)
|
||||
{
|
||||
// TODO : Come up with a better means of identifying line weight and offset
|
||||
var underlineOffset = textLayout.LineMetrics[0].Baseline * dipToDpiRatio * 1.07;
|
||||
|
||||
drawingSession.DrawLine(new Vector2((float)drawX, (float)(drawY + underlineOffset)), new Vector2((float)(drawX + runWidth), (float)(drawY + underlineOffset)), color);
|
||||
}
|
||||
|
||||
drawX += _characterWidth * (textSpan.Text.Length);
|
||||
}
|
||||
|
||||
lineY += _characterHeight;
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessTextFormat(CanvasDrawingSession drawingSession, CanvasTextFormat format)
|
||||
{
|
||||
CanvasTextLayout textLayout = new CanvasTextLayout(drawingSession, "\u2560", format, 0.0f, 0.0f);
|
||||
if (_characterWidth != textLayout.DrawBounds.Width || _characterHeight != textLayout.DrawBounds.Height)
|
||||
{
|
||||
_characterWidth = textLayout.DrawBounds.Width;
|
||||
_characterHeight = textLayout.DrawBounds.Height;
|
||||
}
|
||||
|
||||
int columns = Convert.ToInt32(Math.Floor(Canvas.RenderSize.Width / _characterWidth));
|
||||
int rows = Convert.ToInt32(Math.Floor(Canvas.RenderSize.Height / _characterHeight));
|
||||
if (_columns != columns || _rows != rows)
|
||||
{
|
||||
_columns = columns;
|
||||
_rows = rows;
|
||||
ViewModel.Terminal.SetSize(new TerminalSize { Columns = columns, Rows = rows });
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShiftPressed()
|
||||
{
|
||||
return (Window.Current.CoreWindow.GetKeyState(Windows.System.VirtualKey.Shift) & CoreVirtualKeyStates.Down) != 0;
|
||||
}
|
||||
|
||||
private void Terminal_Closed(object sender, EventArgs e)
|
||||
{
|
||||
}
|
||||
|
||||
private void Terminal_OutputReceived(object sender, string e)
|
||||
{
|
||||
var oldTopRow = _terminal.ViewPort.TopRow;
|
||||
|
||||
_consumer.Push(Encoding.UTF8.GetBytes(e));
|
||||
|
||||
if (_terminal.Changed)
|
||||
{
|
||||
_terminal.ClearChanges();
|
||||
|
||||
if (oldTopRow != _terminal.ViewPort.TopRow && oldTopRow >= ViewTop)
|
||||
{
|
||||
ViewTop = _terminal.ViewPort.TopRow;
|
||||
}
|
||||
|
||||
Canvas.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
private void TerminalGotFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Window.Current.CoreWindow.CharacterReceived += CoreWindow_CharacterReceived;
|
||||
|
||||
_terminal?.FocusIn();
|
||||
}
|
||||
|
||||
private void TerminalKeyDown(object sender, KeyRoutedEventArgs e)
|
||||
{
|
||||
_scrolling = false;
|
||||
|
||||
// Since I get the same key twice in TerminalKeyDown and in CoreWindow_CharacterReceived
|
||||
// I lookup whether KeyPressed should handle the key here or there.
|
||||
var code = _terminal.GetKeySequence(e.Key.ToString(), ControlPressed(), ShiftPressed());
|
||||
if (code != null)
|
||||
e.Handled = _terminal.KeyPressed(e.Key.ToString(), ControlPressed(), ShiftPressed());
|
||||
|
||||
if (ViewTop != _terminal.ViewPort.TopRow)
|
||||
{
|
||||
ViewTop = _terminal.ViewPort.TopRow;
|
||||
Canvas.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
private void TerminalLostFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Window.Current.CoreWindow.CharacterReceived -= CoreWindow_CharacterReceived;
|
||||
_terminal?.FocusOut();
|
||||
}
|
||||
|
||||
private void TerminalPointerExited(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
_mouseOver = null;
|
||||
Canvas.Invalidate();
|
||||
}
|
||||
|
||||
private void TerminalPointerMoved(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
var pointer = e.GetCurrentPoint(Canvas);
|
||||
var position = PointToTextPosition(pointer.Position);
|
||||
|
||||
var textPosition = position.OffsetBy(0, ViewTop);
|
||||
|
||||
if ((_terminal.UseAllMouseTracking || _terminal.CellMotionMouseTracking) && position.Column >= 0 && position.Row >= 0 && position.Column < _columns && position.Row < _rows)
|
||||
{
|
||||
var button =
|
||||
pointer.Properties.IsLeftButtonPressed ? 0 :
|
||||
pointer.Properties.IsRightButtonPressed ? 1 :
|
||||
pointer.Properties.IsMiddleButtonPressed ? 2 :
|
||||
3; // No button
|
||||
|
||||
_terminal.MouseMove(position.Column, position.Row, button, ControlPressed(), ShiftPressed());
|
||||
|
||||
if (button == 3 && !_terminal.UseAllMouseTracking)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (_mouseOver != null && _mouseOver == position)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_mouseOver = position;
|
||||
|
||||
if (pointer.Properties.IsLeftButtonPressed && MousePressedAt != null)
|
||||
{
|
||||
TextRange newSelection;
|
||||
|
||||
if (MousePressedAt != textPosition)
|
||||
{
|
||||
if (MousePressedAt <= textPosition)
|
||||
{
|
||||
newSelection = new TextRange
|
||||
{
|
||||
Start = MousePressedAt,
|
||||
End = textPosition.OffsetBy(-1, 0)
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
newSelection = new TextRange
|
||||
{
|
||||
Start = textPosition,
|
||||
End = MousePressedAt
|
||||
};
|
||||
}
|
||||
|
||||
_selecting = true;
|
||||
|
||||
if (_textSelection != newSelection)
|
||||
{
|
||||
_textSelection = newSelection;
|
||||
Canvas.Invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TerminalPointerPressed(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
var pointer = e.GetCurrentPoint(Canvas);
|
||||
var position = PointToTextPosition(pointer.Position);
|
||||
|
||||
var textPosition = position.OffsetBy(0, ViewTop);
|
||||
|
||||
if (!_terminal.MouseTrackingEnabled)
|
||||
{
|
||||
if (pointer.Properties.IsLeftButtonPressed)
|
||||
MousePressedAt = textPosition;
|
||||
//else if (pointer.Properties.IsRightButtonPressed)
|
||||
//PasteClipboard();
|
||||
}
|
||||
|
||||
if (position.Column >= 0 && position.Row >= 0 && position.Column < _columns && position.Row < _rows)
|
||||
{
|
||||
var button =
|
||||
pointer.Properties.IsLeftButtonPressed ? 0 :
|
||||
pointer.Properties.IsRightButtonPressed ? 1 :
|
||||
pointer.Properties.IsMiddleButtonPressed ? 2 :
|
||||
3; // No button
|
||||
|
||||
_terminal.MousePress(position.Column, position.Row, button, ControlPressed(), ShiftPressed());
|
||||
}
|
||||
}
|
||||
|
||||
private void TerminalPointerReleased(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
var pointer = e.GetCurrentPoint(Canvas);
|
||||
var position = PointToTextPosition(pointer.Position);
|
||||
var textPosition = position.OffsetBy(0, ViewTop);
|
||||
|
||||
if (!pointer.Properties.IsLeftButtonPressed)
|
||||
{
|
||||
if (_selecting)
|
||||
{
|
||||
MousePressedAt = null;
|
||||
_selecting = false;
|
||||
|
||||
var captured = _terminal.GetText(_textSelection.Start.Column, _textSelection.Start.Row, _textSelection.End.Column, _textSelection.End.Row);
|
||||
|
||||
var dataPackage = new DataPackage();
|
||||
dataPackage.SetText(captured);
|
||||
dataPackage.Properties.EnterpriseId = "FluentTerminal";
|
||||
try
|
||||
{
|
||||
Clipboard.SetContent(dataPackage);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.WriteLine("Failed to post to clipboard: " + exception.Message);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_textSelection = null;
|
||||
Canvas.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
if (position.Column >= 0 && position.Row >= 0 && position.Column < _columns && position.Row < _rows)
|
||||
{
|
||||
_terminal.MouseRelease(position.Column, position.Row, ControlPressed(), ShiftPressed());
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTapped(object sender, TappedRoutedEventArgs e)
|
||||
{
|
||||
Focus(FocusState.Pointer);
|
||||
}
|
||||
|
||||
private void OnWheelChanged(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
var pointer = e.GetCurrentPoint(Canvas);
|
||||
|
||||
int oldViewTop = ViewTop;
|
||||
|
||||
ViewTop -= pointer.Properties.MouseWheelDelta / 40;
|
||||
_scrolling = true;
|
||||
|
||||
if (ViewTop < 0)
|
||||
{
|
||||
ViewTop = 0;
|
||||
}
|
||||
else if (ViewTop > _terminal.ViewPort.TopRow)
|
||||
{
|
||||
ViewTop = _terminal.ViewPort.TopRow;
|
||||
}
|
||||
|
||||
if (oldViewTop != ViewTop)
|
||||
{
|
||||
Canvas.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
private TextPosition PointToTextPosition(Point point)
|
||||
{
|
||||
var overColumn = (int)Math.Floor(point.X / _characterWidth);
|
||||
if (overColumn >= _columns)
|
||||
{
|
||||
overColumn = _columns - 1;
|
||||
}
|
||||
|
||||
var overRow = (int)Math.Floor(point.Y / _characterHeight);
|
||||
if (overRow >= _rows)
|
||||
{
|
||||
overRow = _rows - 1;
|
||||
}
|
||||
|
||||
return new TextPosition { Column = overColumn, Row = overRow };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<UserControl
|
||||
x:Class="FluentTerminal.App.Views.XtermTerminalView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:FluentTerminal.App.Views"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
d:DesignHeight="300"
|
||||
d:DesignWidth="400"
|
||||
mc:Ignorable="d">
|
||||
<Grid x:Name="Root" />
|
||||
</UserControl>
|
||||
@@ -0,0 +1,350 @@
|
||||
using Fleck;
|
||||
using FluentTerminal.App.ViewModels;
|
||||
using FluentTerminal.App.ViewModels.Utilities;
|
||||
using FluentTerminal.Models;
|
||||
using FluentTerminal.Models.Enums;
|
||||
using FluentTerminal.RuntimeComponent.Enums;
|
||||
using FluentTerminal.RuntimeComponent.Interfaces;
|
||||
using FluentTerminal.RuntimeComponent.WebAllowedObjects;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.ApplicationModel.Core;
|
||||
using Windows.Foundation;
|
||||
using Windows.UI;
|
||||
using Windows.UI.Core;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
|
||||
namespace FluentTerminal.App.Views
|
||||
{
|
||||
public sealed partial class XtermTerminalView : UserControl, IxtermEventListener, ITerminalView
|
||||
{
|
||||
private readonly ManualResetEventSlim _connectedEvent;
|
||||
private readonly SemaphoreSlim _loaded;
|
||||
private bool _alreadyLoaded;
|
||||
private InputBuffer _buffer;
|
||||
private MenuFlyoutItem _copyMenuItem;
|
||||
private BlockingCollection<Action> _dispatcherJobs;
|
||||
private MenuFlyoutItem _pasteMenuItem;
|
||||
private IWebSocketConnection _webSocket;
|
||||
private WebView _webView;
|
||||
|
||||
public XtermTerminalView()
|
||||
{
|
||||
_loaded = new SemaphoreSlim(0, 1);
|
||||
_connectedEvent = new ManualResetEventSlim(false);
|
||||
|
||||
JsonConvert.DefaultSettings = () =>
|
||||
{
|
||||
var settings = new JsonSerializerSettings
|
||||
{
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver(),
|
||||
};
|
||||
settings.Converters.Add(new StringEnumConverter(true));
|
||||
|
||||
return settings;
|
||||
};
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
StartMediatorTask();
|
||||
|
||||
Loaded += XtermTerminalView_Loaded;
|
||||
GotFocus += XtermTerminalView_GotFocus;
|
||||
}
|
||||
|
||||
public TerminalViewModel ViewModel { get; private set; }
|
||||
|
||||
public Task ChangeKeyBindings()
|
||||
{
|
||||
var keyBindings = ViewModel.SettingsService.GetCommandKeyBindings();
|
||||
var profiles = ViewModel.SettingsService.GetShellProfiles();
|
||||
var serialized = JsonConvert.SerializeObject(FlattenKeyBindings(keyBindings, profiles));
|
||||
return ExecuteScriptAsync($"changeKeyBindings('{serialized}')");
|
||||
}
|
||||
|
||||
public Task ChangeOptions(TerminalOptions options)
|
||||
{
|
||||
var serialized = JsonConvert.SerializeObject(options);
|
||||
return ExecuteScriptAsync($"changeOptions('{serialized}')");
|
||||
}
|
||||
|
||||
public Task ChangeTheme(TerminalTheme theme)
|
||||
{
|
||||
var serialized = JsonConvert.SerializeObject(theme.Colors);
|
||||
return ExecuteScriptAsync($"changeTheme('{serialized}')");
|
||||
}
|
||||
|
||||
public Task FindNext(string searchText)
|
||||
{
|
||||
return ExecuteScriptAsync($"findNext('{searchText}')");
|
||||
}
|
||||
|
||||
public Task FindPrevious(string searchText)
|
||||
{
|
||||
return ExecuteScriptAsync($"findPrevious('{searchText}')");
|
||||
}
|
||||
|
||||
public async Task FocusTerminal()
|
||||
{
|
||||
if (_webView != null)
|
||||
{
|
||||
_webView.Focus(FocusState.Programmatic);
|
||||
await ExecuteScriptAsync("document.focus();").ConfigureAwait(true);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Initialize(TerminalViewModel viewModel)
|
||||
{
|
||||
ViewModel = viewModel;
|
||||
ViewModel.Terminal.OutputReceived += Terminal_OutputReceived;
|
||||
ViewModel.Terminal.RegisterSelectedTextCallback(() => ExecuteScriptAsync("term.getSelection()"));
|
||||
ViewModel.Terminal.Closed += Terminal_Closed;
|
||||
|
||||
var options = ViewModel.SettingsService.GetTerminalOptions();
|
||||
var keyBindings = ViewModel.SettingsService.GetCommandKeyBindings();
|
||||
var profiles = ViewModel.SettingsService.GetShellProfiles();
|
||||
var settings = ViewModel.SettingsService.GetApplicationSettings();
|
||||
var theme = ViewModel.TerminalTheme;
|
||||
var sessionType = SessionType.Unknown;
|
||||
if (settings.AlwaysUseWinPty || !ViewModel.ApplicationView.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 7))
|
||||
{
|
||||
sessionType = SessionType.WinPty;
|
||||
}
|
||||
else
|
||||
{
|
||||
sessionType = SessionType.ConPty;
|
||||
}
|
||||
await _loaded.WaitAsync().ConfigureAwait(true);
|
||||
var size = await CreateXtermView(options, theme.Colors, FlattenKeyBindings(keyBindings, profiles), sessionType).ConfigureAwait(true);
|
||||
var port = await ViewModel.TrayProcessCommunicationService.GetAvailablePort().ConfigureAwait(true);
|
||||
await CreateWebSocketServer(port.Port).ConfigureAwait(true);
|
||||
_connectedEvent.Wait();
|
||||
await ViewModel.Terminal.StartShellProcess(ViewModel.ShellProfile, size, sessionType).ConfigureAwait(true);
|
||||
_webView.Focus(FocusState.Programmatic);
|
||||
}
|
||||
|
||||
void IxtermEventListener.OnKeyboardCommand(string command)
|
||||
{
|
||||
if (Enum.TryParse(command, true, out Command commandValue))
|
||||
{
|
||||
_dispatcherJobs.Add(() => ViewModel.Terminal.ProcessKeyboardCommand(commandValue.ToString()));
|
||||
}
|
||||
else if (Guid.TryParse(command, out Guid shellProfileId))
|
||||
{
|
||||
_dispatcherJobs.Add(() => ViewModel.Terminal.ProcessKeyboardCommand(shellProfileId.ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
void IxtermEventListener.OnMouseClick(MouseButton mouseButton, int x, int y, bool hasSelection)
|
||||
{
|
||||
_dispatcherJobs.Add(() =>
|
||||
{
|
||||
if (mouseButton == MouseButton.Middle)
|
||||
{
|
||||
if (ViewModel.ApplicationSettings.MouseMiddleClickAction == MouseAction.ContextMenu)
|
||||
{
|
||||
ShowContextMenu(x, y, hasSelection);
|
||||
}
|
||||
else if (ViewModel.ApplicationSettings.MouseMiddleClickAction == MouseAction.Paste)
|
||||
{
|
||||
((IxtermEventListener)this).OnKeyboardCommand(nameof(Command.Paste));
|
||||
}
|
||||
}
|
||||
else if (mouseButton == MouseButton.Right)
|
||||
{
|
||||
if (ViewModel.ApplicationSettings.MouseRightClickAction == MouseAction.ContextMenu)
|
||||
{
|
||||
ShowContextMenu(x, y, hasSelection);
|
||||
}
|
||||
else if (ViewModel.ApplicationSettings.MouseRightClickAction == MouseAction.Paste)
|
||||
{
|
||||
((IxtermEventListener)this).OnKeyboardCommand(nameof(Command.Paste));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void IxtermEventListener.OnSelectionChanged(string selection)
|
||||
{
|
||||
if (ViewModel.ApplicationSettings.CopyOnSelect && !ViewModel.ShowSearchPanel)
|
||||
{
|
||||
_dispatcherJobs.Add(async () =>
|
||||
{
|
||||
ViewModel.CopyText(selection);
|
||||
await ExecuteScriptAsync("term.clearSelection()");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void IxtermEventListener.OnTerminalResized(int columns, int rows)
|
||||
{
|
||||
if (_connectedEvent.IsSet) // only propagate after xterm.js is finished with fitting
|
||||
{
|
||||
_dispatcherJobs.Add(async () => await ViewModel.Terminal.SetSize(new TerminalSize { Columns = columns, Rows = rows }).ConfigureAwait(true));
|
||||
}
|
||||
}
|
||||
|
||||
void IxtermEventListener.OnTitleChanged(string title)
|
||||
{
|
||||
_dispatcherJobs.Add(() => ViewModel.Terminal.SetTitle(title));
|
||||
}
|
||||
|
||||
private void _webView_NavigationCompleted(WebView sender, WebViewNavigationCompletedEventArgs args)
|
||||
{
|
||||
_loaded.Release();
|
||||
}
|
||||
|
||||
private void _webView_NavigationStarting(WebView sender, WebViewNavigationStartingEventArgs args)
|
||||
{
|
||||
var bridge = new TerminalBridge(this);
|
||||
_webView.AddWebAllowedObject("terminalBridge", bridge);
|
||||
}
|
||||
|
||||
private void Copy_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
((IxtermEventListener)this).OnKeyboardCommand(nameof(Command.Copy));
|
||||
}
|
||||
|
||||
private async Task CreateWebSocketServer(int port)
|
||||
{
|
||||
var webSocketUrl = "ws://127.0.0.1:" + port;
|
||||
var webSocketServer = new WebSocketServer(webSocketUrl);
|
||||
webSocketServer.Start(socket =>
|
||||
{
|
||||
_webSocket = socket;
|
||||
_buffer = new InputBuffer(_webSocket.Send);
|
||||
socket.OnOpen = () => _connectedEvent.Set();
|
||||
socket.OnMessage = message => ViewModel.Terminal.Write(message);
|
||||
});
|
||||
|
||||
await ExecuteScriptAsync($"connectToWebSocket('{webSocketUrl}');").ConfigureAwait(true);
|
||||
}
|
||||
|
||||
private async Task<TerminalSize> CreateXtermView(TerminalOptions options, TerminalColors theme, IEnumerable<KeyBinding> keyBindings, SessionType sessionType)
|
||||
{
|
||||
var serializedOptions = JsonConvert.SerializeObject(options);
|
||||
var serializedTheme = JsonConvert.SerializeObject(theme);
|
||||
var serializedKeyBindings = JsonConvert.SerializeObject(keyBindings);
|
||||
var size = await ExecuteScriptAsync($"createTerminal('{serializedOptions}', '{serializedTheme}', '{serializedKeyBindings}', '{sessionType}')").ConfigureAwait(true);
|
||||
return JsonConvert.DeserializeObject<TerminalSize>(size);
|
||||
}
|
||||
|
||||
private Task<string> ExecuteScriptAsync(string script)
|
||||
{
|
||||
try
|
||||
{
|
||||
return _webView.InvokeScriptAsync("eval", new[] { script }).AsTask();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.WriteLine($"Exception while running:\n \"{script}\"\n\n {e}");
|
||||
}
|
||||
return Task.FromResult(string.Empty);
|
||||
}
|
||||
|
||||
private IEnumerable<KeyBinding> FlattenKeyBindings(IDictionary<string, ICollection<KeyBinding>> commandKeyBindings, IEnumerable<ShellProfile> profiles)
|
||||
{
|
||||
return commandKeyBindings.Values.SelectMany(k => k).Concat(profiles.SelectMany(x => x.KeyBindings));
|
||||
}
|
||||
|
||||
private void Paste_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
((IxtermEventListener)this).OnKeyboardCommand(nameof(Command.Paste));
|
||||
}
|
||||
|
||||
private void ShowContextMenu(int x, int y, bool terminalHasSelection)
|
||||
{
|
||||
var flyout = (MenuFlyout)_webView.ContextFlyout;
|
||||
_copyMenuItem.IsEnabled = terminalHasSelection;
|
||||
flyout.ShowAt(_webView, new Point(x, y));
|
||||
}
|
||||
|
||||
private void StartMediatorTask()
|
||||
{
|
||||
_dispatcherJobs = new BlockingCollection<Action>();
|
||||
var dispatcher = CoreApplication.GetCurrentView().CoreWindow.Dispatcher;
|
||||
Task.Run(async () =>
|
||||
{
|
||||
foreach (var job in _dispatcherJobs.GetConsumingEnumerable())
|
||||
{
|
||||
try
|
||||
{
|
||||
await dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => job.Invoke());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.WriteLine(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async void Terminal_Closed(object sender, EventArgs e)
|
||||
{
|
||||
ViewModel.Terminal.OutputReceived -= Terminal_OutputReceived;
|
||||
ViewModel.Terminal.Closed -= Terminal_Closed;
|
||||
_webView?.Navigate(new Uri("about:blank"));
|
||||
_webView = null;
|
||||
await ViewModel.Terminal.Close().ConfigureAwait(true);
|
||||
}
|
||||
|
||||
private void Terminal_OutputReceived(object sender, string e)
|
||||
{
|
||||
_webSocket.Send(e);
|
||||
}
|
||||
|
||||
private async void XtermTerminalView_GotFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_webView != null)
|
||||
{
|
||||
_webView.Focus(FocusState.Programmatic);
|
||||
await ExecuteScriptAsync("document.focus();").ConfigureAwait(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void XtermTerminalView_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_alreadyLoaded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Root.Children.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_webView = new WebView(WebViewExecutionMode.SeparateThread)
|
||||
{
|
||||
DefaultBackgroundColor = Colors.Transparent
|
||||
};
|
||||
Root.Children.Add(_webView);
|
||||
|
||||
_webView.NavigationCompleted += _webView_NavigationCompleted;
|
||||
_webView.NavigationStarting += _webView_NavigationStarting;
|
||||
|
||||
_copyMenuItem = new MenuFlyoutItem { Text = "Copy" };
|
||||
_copyMenuItem.Click += Copy_Click;
|
||||
|
||||
_pasteMenuItem = new MenuFlyoutItem { Text = "Paste" };
|
||||
_pasteMenuItem.Click += Paste_Click;
|
||||
|
||||
_webView.ContextFlyout = new MenuFlyout
|
||||
{
|
||||
Items = { _copyMenuItem, _pasteMenuItem }
|
||||
};
|
||||
|
||||
_webView.Navigate(new Uri("ms-appx-web:///Client/index.html"));
|
||||
_alreadyLoaded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+3
-3
@@ -4305,9 +4305,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"xterm": {
|
||||
"version": "3.8.0",
|
||||
"resolved": "https://registry.npmjs.org/xterm/-/xterm-3.8.0.tgz",
|
||||
"integrity": "sha512-rS3HLryuMWbLsv98+jVVSUXCxmoyXPwqwJNC0ad0VSMdXgl65LefPztQVwfurkaF7kM7ZSgM8eJjnJ9kkdoR1w=="
|
||||
"version": "3.11.0",
|
||||
"resolved": "https://registry.npmjs.org/xterm/-/xterm-3.11.0.tgz",
|
||||
"integrity": "sha512-VB9+s2Fhig31pNBkbxPnz+/wdCvxdQ6JQ0HZmWDwpULV8iAggzxoyUonB4FR+WW3lj0LNVb/ZaD64rxbw+HB4A=="
|
||||
},
|
||||
"y18n": {
|
||||
"version": "4.0.0",
|
||||
|
||||
@@ -14,6 +14,6 @@
|
||||
"webpack-cli": "^3.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"xterm": "^3.8.0"
|
||||
"xterm": "^3.11.0"
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" href="xterm.css" />
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
@@ -13,7 +13,7 @@ Terminal.applyAddon(search);
|
||||
var term, socket;
|
||||
var terminalContainer = document.getElementById('terminal-container');
|
||||
|
||||
function createTerminal(options, theme, keyBindings) {
|
||||
function createTerminal(options, theme, keyBindings, sessionType) {
|
||||
while (terminalContainer.children.length) {
|
||||
terminalContainer.removeChild(terminalContainer.children[0]);
|
||||
}
|
||||
@@ -29,6 +29,8 @@ function createTerminal(options, theme, keyBindings) {
|
||||
var terminalOptions = {
|
||||
fontFamily: options.fontFamily,
|
||||
fontSize: options.fontSize,
|
||||
fontWeight: options.boldText ? 'bold' : 'normal',
|
||||
fontWeightBold: options.boldText ? 'bolder' : 'bold',
|
||||
cursorStyle: options.cursorStyle,
|
||||
cursorBlink: options.cursorBlink,
|
||||
bellStyle: options.bellStyle,
|
||||
@@ -55,9 +57,15 @@ function createTerminal(options, theme, keyBindings) {
|
||||
});
|
||||
|
||||
term.open(terminalContainer);
|
||||
term.winptyCompatInit();
|
||||
term.fit();
|
||||
|
||||
if (sessionType === 'WinPty') {
|
||||
term.winptyCompatInit();
|
||||
}
|
||||
|
||||
term.focus();
|
||||
|
||||
setPadding(options.padding);
|
||||
|
||||
var resizeTimeout;
|
||||
window.onresize = function () {
|
||||
@@ -82,15 +90,15 @@ function createTerminal(options, theme, keyBindings) {
|
||||
&& keyBinding.shift == e.shiftKey
|
||||
&& keyBinding.key == e.keyCode) {
|
||||
if (document.visibilityState == 'visible') {
|
||||
if (keyBinding.command == 'copy' && term.getSelection() == '') {
|
||||
if (keyBinding.command == 'Copy' && term.getSelection() == '') {
|
||||
return true;
|
||||
}
|
||||
if (keyBinding.command == 'clear') {
|
||||
if (keyBinding.command == 'Clear') {
|
||||
term.clearSelection();
|
||||
term.clear();
|
||||
return false;
|
||||
}
|
||||
if (keyBinding.command == 'selectAll') {
|
||||
if (keyBinding.command == 'SelectAll') {
|
||||
term.selectAll();
|
||||
return false;
|
||||
}
|
||||
@@ -137,19 +145,24 @@ function changeOptions(options) {
|
||||
term.setOption('cursorStyle', options.cursorStyle);
|
||||
term.setOption('fontFamily', options.fontFamily);
|
||||
term.setOption('fontSize', options.fontSize);
|
||||
term.setOption('fontWeight', options.boldText ? 'bold' : 'normal');
|
||||
term.setOption('fontWeightBold', options.boldText ? 'bolder' : 'bold');
|
||||
term.setOption('scrollback', options.scrollBackLimit);
|
||||
setScrollBarStyle(options.scrollBarStyle);
|
||||
setPadding(options.padding);
|
||||
}
|
||||
|
||||
function setScrollBarStyle(scrollBarStyle) {
|
||||
if (scrollBarStyle == 'hidden') {
|
||||
document.getElementById('terminal-container').style['-ms-overflow-style'] = 'none';
|
||||
} else if (scrollBarStyle == 'autoHiding') {
|
||||
document.getElementById('terminal-container').style['-ms-overflow-style'] = '-ms-autohiding-scrollbar';
|
||||
} else if (scrollBarStyle == 'visible') {
|
||||
document.getElementById('terminal-container').style['-ms-overflow-style'] = 'scrollbar';
|
||||
switch (scrollBarStyle) {
|
||||
case 'hidden': return terminalContainer.style['-ms-overflow-style'] = 'none';
|
||||
case 'autoHiding': return terminalContainer.style['-ms-overflow-style'] = '-ms-autohiding-scrollbar';
|
||||
case 'visible': return terminalContainer.style['-ms-overflow-style'] = 'scrollbar';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function setPadding(padding) {
|
||||
document.querySelector('.terminal').style.padding = padding + 'px';
|
||||
term.fit();
|
||||
}
|
||||
|
||||
function changeKeyBindings(keyBindings) {
|
||||
@@ -181,4 +194,4 @@ window.changeTheme = changeTheme;
|
||||
window.changeOptions = changeOptions;
|
||||
window.changeKeyBindings = changeKeyBindings;
|
||||
window.findNext = findNext;
|
||||
window.findPrevious = findPrevious;
|
||||
window.findPrevious = findPrevious;
|
||||
@@ -5,11 +5,19 @@
|
||||
html, body {
|
||||
background: transparent;
|
||||
height: 100%;
|
||||
}
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
-ms-content-zooming: none;
|
||||
}
|
||||
|
||||
#terminal-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/* re-enable scrolling only in the xterm container */
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
touch-action: pan-y;
|
||||
-ms-touch-action: pan-y;
|
||||
}
|
||||
|
||||
.xterm .xterm-viewport {
|
||||
@@ -18,5 +26,4 @@ html, body {
|
||||
|
||||
.terminal {
|
||||
background: transparent !important;
|
||||
padding: 12px;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
mode: 'production',
|
||||
entry: './src/index.js',
|
||||
output: {
|
||||
filename: 'bundle.js',
|
||||
|
||||
@@ -15,5 +15,7 @@ namespace FluentTerminal.Models
|
||||
public MouseAction MouseRightClickAction { get; set; }
|
||||
public bool AlwaysShowTabs { get; set; }
|
||||
public bool ShowNewOutputIndicator { get; set; }
|
||||
public bool EnableTrayIcon { get; set; }
|
||||
public bool AlwaysUseWinPty { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -19,12 +19,18 @@ namespace FluentTerminal.Models.Enums
|
||||
[Description("Configurable new tab")]
|
||||
ConfigurableNewTab,
|
||||
|
||||
[Description("Change tab title")]
|
||||
ChangeTabTitle,
|
||||
|
||||
[Description("Close tab")]
|
||||
CloseTab,
|
||||
|
||||
[Description("New window")]
|
||||
NewWindow,
|
||||
|
||||
[Description("Configurable new window")]
|
||||
ConfigurableNewWindow,
|
||||
|
||||
[Description("Show settings")]
|
||||
ShowSettings,
|
||||
|
||||
@@ -34,6 +40,9 @@ namespace FluentTerminal.Models.Enums
|
||||
[Description("Paste")]
|
||||
Paste,
|
||||
|
||||
[Description("Paste without newlines")]
|
||||
PasteWithoutNewlines,
|
||||
|
||||
[Description("Search")]
|
||||
Search,
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace FluentTerminal.Models.Enums
|
||||
{
|
||||
public enum LineEndingStyle
|
||||
{
|
||||
[Description("Do not modify")]
|
||||
DoNotModify,
|
||||
|
||||
[Description("Convert to CRLF")]
|
||||
ToCRLF,
|
||||
|
||||
[Description("Convert to LF")]
|
||||
ToLF
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace FluentTerminal.Models.Enums
|
||||
{
|
||||
public enum SessionType
|
||||
{
|
||||
Unknown,
|
||||
WinPty,
|
||||
ConPty
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,41 @@
|
||||
using FluentTerminal.Models.Enums;
|
||||
|
||||
namespace FluentTerminal.Models
|
||||
namespace FluentTerminal.Models
|
||||
{
|
||||
public class KeyBinding
|
||||
{
|
||||
public Command Command { get; set; }
|
||||
public KeyBinding()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public KeyBinding(KeyBinding other)
|
||||
{
|
||||
Command = other.Command;
|
||||
Key = other.Key;
|
||||
Ctrl = other.Ctrl;
|
||||
Alt = other.Alt;
|
||||
Shift = other.Shift;
|
||||
Meta = other.Meta;
|
||||
}
|
||||
|
||||
public string Command { get; set; }
|
||||
public int Key { get; set; }
|
||||
public bool Ctrl { get; set; }
|
||||
public bool Alt { get; set; }
|
||||
public bool Shift { get; set; }
|
||||
public bool Meta { get; set; }
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is KeyBinding other)
|
||||
{
|
||||
return other.Command.Equals(Command)
|
||||
&& other.Key == Key
|
||||
&& other.Ctrl == Ctrl
|
||||
&& other.Alt == Alt
|
||||
&& other.Shift == Shift
|
||||
&& other.Meta == Meta;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,10 @@ namespace FluentTerminal.Models
|
||||
public ICollection<KeyBinding> PreviousTab { get; set; }
|
||||
public ICollection<KeyBinding> NewTab { get; set; }
|
||||
public ICollection<KeyBinding> ConfigurableNewTab { get; set; }
|
||||
public ICollection<KeyBinding> ChangeTabTitle { get; set; }
|
||||
public ICollection<KeyBinding> CloseTab { get; set; }
|
||||
public ICollection<KeyBinding> NewWindow { get; set; }
|
||||
public ICollection<KeyBinding> ConfigurableNewWindow { get; set; }
|
||||
public ICollection<KeyBinding> ShowSettings { get; set; }
|
||||
public ICollection<KeyBinding> Copy { get; set; }
|
||||
public ICollection<KeyBinding> Paste { get; set; }
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
namespace FluentTerminal.Models.Requests
|
||||
using FluentTerminal.Models.Enums;
|
||||
|
||||
namespace FluentTerminal.Models.Requests
|
||||
{
|
||||
public class CreateTerminalRequest
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public TerminalSize Size { get; set; }
|
||||
public ShellProfile Profile { get; set; }
|
||||
public SessionType SessionType { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace FluentTerminal.Models.Requests
|
||||
{
|
||||
public class GetAvailablePortRequest
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
{
|
||||
public class CreateTerminalResponse
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public bool Success { get; set; }
|
||||
public string Error { get; set; }
|
||||
public string ShellExecutableName { get; set; }
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace FluentTerminal.Models.Responses
|
||||
{
|
||||
public class GetAvailablePortResponse
|
||||
{
|
||||
public int Port { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using FluentTerminal.Models.Enums;
|
||||
using System.Linq;
|
||||
|
||||
namespace FluentTerminal.Models
|
||||
{
|
||||
public class ShellProfile
|
||||
{
|
||||
/// <summary>
|
||||
/// Replace all instances of anything resembling a newline, treating pairs of \r\n in either order as a single linebreak.
|
||||
/// </summary>
|
||||
public static Regex NewlinePattern = new Regex(@"\n\r|\r\n|\r|\n", RegexOptions.Compiled);
|
||||
|
||||
public ShellProfile()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public ShellProfile(ShellProfile other)
|
||||
{
|
||||
Id = other.Id;
|
||||
PreInstalled = other.PreInstalled;
|
||||
Name = other.Name;
|
||||
Arguments = other.Arguments;
|
||||
Location = other.Location;
|
||||
WorkingDirectory = other.WorkingDirectory;
|
||||
TabThemeId = other.TabThemeId;
|
||||
TerminalThemeId = other.TerminalThemeId;
|
||||
LineEndingTranslation = other.LineEndingTranslation;
|
||||
KeyBindings = other.KeyBindings.Select(x => new KeyBinding(x)).ToList();
|
||||
}
|
||||
|
||||
public Guid Id { get; set; }
|
||||
public bool PreInstalled { get; set; }
|
||||
public string Name { get; set; }
|
||||
@@ -11,5 +39,41 @@ namespace FluentTerminal.Models
|
||||
public string Location { get; set; }
|
||||
public string WorkingDirectory { get; set; }
|
||||
public int TabThemeId { get; set; }
|
||||
public LineEndingStyle LineEndingTranslation { get; set; }
|
||||
|
||||
public string TranslateLineEndings(string content)
|
||||
{
|
||||
switch (LineEndingTranslation)
|
||||
{
|
||||
case LineEndingStyle.ToCRLF:
|
||||
return NewlinePattern.Replace(content, "\r\n");
|
||||
case LineEndingStyle.ToLF:
|
||||
return NewlinePattern.Replace(content, "\n");
|
||||
case LineEndingStyle.DoNotModify:
|
||||
default:
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
public Guid TerminalThemeId { get; set; }
|
||||
public ICollection<KeyBinding> KeyBindings { get; set; } = new List<KeyBinding>();
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is ShellProfile other)
|
||||
{
|
||||
return other.Id.Equals(Id)
|
||||
&& other.PreInstalled.Equals(PreInstalled)
|
||||
&& other.Name.Equals(Name)
|
||||
&& other.Arguments.Equals(Arguments)
|
||||
&& other.Location.Equals(Location)
|
||||
&& other.WorkingDirectory.Equals(WorkingDirectory)
|
||||
&& other.TabThemeId.Equals(TabThemeId)
|
||||
&& other.TerminalThemeId.Equals(TerminalThemeId)
|
||||
&& other.LineEndingTranslation == LineEndingTranslation
|
||||
&& other.KeyBindings.SequenceEqual(KeyBindings);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,5 +56,34 @@
|
||||
BrightCyan = other.BrightCyan;
|
||||
BrightWhite = other.BrightWhite;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is TerminalColors other)
|
||||
{
|
||||
return other.Foreground.Equals(Foreground)
|
||||
&& other.Background.Equals(Background)
|
||||
&& other.Cursor.Equals(Cursor)
|
||||
&& other.CursorAccent.Equals(CursorAccent)
|
||||
&& other.Selection.Equals(Selection)
|
||||
&& other.Black.Equals(Black)
|
||||
&& other.Red.Equals(Red)
|
||||
&& other.Green.Equals(Green)
|
||||
&& other.Yellow.Equals(Yellow)
|
||||
&& other.Blue.Equals(Blue)
|
||||
&& other.Magenta.Equals(Magenta)
|
||||
&& other.Cyan.Equals(Cyan)
|
||||
&& other.White.Equals(White)
|
||||
&& other.BrightBlack.Equals(BrightBlack)
|
||||
&& other.BrightRed.Equals(BrightRed)
|
||||
&& other.BrightGreen.Equals(BrightGreen)
|
||||
&& other.BrightYellow.Equals(BrightYellow)
|
||||
&& other.BrightBlue.Equals(BrightBlue)
|
||||
&& other.BrightMagenta.Equals(BrightMagenta)
|
||||
&& other.BrightCyan.Equals(BrightCyan)
|
||||
&& other.BrightWhite.Equals(BrightWhite);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@ namespace FluentTerminal.Models
|
||||
|
||||
public int FontSize { get; set; }
|
||||
|
||||
public bool BoldText { get; set; }
|
||||
|
||||
public CursorStyle CursorStyle { get; set; }
|
||||
|
||||
public bool CursorBlink { get; set; }
|
||||
@@ -18,6 +20,8 @@ namespace FluentTerminal.Models
|
||||
|
||||
public double BackgroundOpacity { get; set; }
|
||||
|
||||
public int Padding { get; set; }
|
||||
|
||||
public uint ScrollBackLimit { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,34 @@ namespace FluentTerminal.Models
|
||||
{
|
||||
public class TerminalTheme
|
||||
{
|
||||
public TerminalTheme()
|
||||
{
|
||||
}
|
||||
|
||||
public TerminalTheme(TerminalTheme other)
|
||||
{
|
||||
Id = other.Id;
|
||||
Name = other.Name;
|
||||
Author = other.Author;
|
||||
PreInstalled = other.PreInstalled;
|
||||
Colors = new TerminalColors(other.Colors);
|
||||
}
|
||||
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Author { get; set; }
|
||||
public bool PreInstalled { get; set; }
|
||||
public TerminalColors Colors { get; set; }
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is TerminalTheme other)
|
||||
{
|
||||
return Equals(other.Name, Name)
|
||||
&& Equals(other.Author, Author)
|
||||
&& Equals(other.Colors, Colors);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loaded 100 of 125 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user