Compare commits

...
90 Commits
Author SHA1 Message Date
FelixSe ef7f909d26 Increase version 2019-03-27 21:57:16 +01:00
FelixSe f2618fa940 Fix launch error when TERM was already defined 2019-03-27 21:13:23 +01:00
ivanjh 363f885aea Fix shell profile radio button line-ending binding (#257)
* Fix shell profile radio button line-ending binding
Only change when updated from a checked radio button

* Fix cancelling profile edit showing default line translation

* call NotifyPropertyChanged
2019-03-26 22:46:02 +01:00
Meir Blachman ed4a5adb16 added test for DialogService.ShowInputDialogAsync (#258) 2019-03-26 22:45:46 +01:00
Felix 82a3532e90 Add missing quotes (#253) 2019-03-25 22:48:26 +01:00
Felix 15d713191c Handle launch fail (#254) 2019-03-25 22:48:17 +01:00
ivanjh 575022139a Add convert line endings to CR option (#251)
* Add convert line endings to CR option

* Change order of line ending handling options (None,CRLF,CR,LF)
2019-03-25 22:48:06 +01:00
Harsh Chokshi b9686ac4ec Use idiomatic JS import style in web client (#245)
Add-ons imported as per Xterm documentation recommendations:
https://xtermjs.org/docs/guides/using-addons/
2019-03-25 20:49:13 +01:00
FelixSe 43e4f8290e Make logging configurable 2019-03-23 11:22:13 +01:00
FelixSe 93387ebc57 Increase version 2019-03-23 10:41:45 +01:00
David Refoua 54a9d7ce0d set TERM to xterm-256color (#239) 2019-03-22 23:11:54 +01:00
Felix 44b306ac20 Add option to always show original shell title in window title (#237) 2019-03-21 23:40:59 +01:00
Felix 14e1e0e3d3 [WIP] Set TERM_PROGRAM (#236)
* Implement for WinPty

* Implement for ConPty
2019-03-21 21:44:49 +01:00
Fabian 136ba3c54f Suggestion: Better CLI parsing, command (#194)
* Better CLI parsing, command

Added the option to run a command via the CLI.
Also added a better argument parsing solution.

You can now do `flute.exe new [PATH] [COMMAND]`

Before, you could only do `flute.exe new [PATH]`

You would also need to adjust your settings, say your default profile is using powershell with custom arguments, those arguments would have to end with ";", because the `[COMMAND]` would be appended to the arguments.

* parse arguments with commandlineparser nuget

* Run commands with no underlying shell process

* Create RunVerb, cleanup terminal creation

* Fix window close bug

* Use working directory of caller if none specified

* Restore profile arguments in WinPty
2019-03-20 21:12:14 +01:00
Mike MacCana 1a0c51c6d4 Fix missing Developer mode requirement for Chocolatey install (#235)
Fixes #230
2019-03-20 21:04:04 +01:00
FelixSe 363018a43f Fix terminal resizing 2019-03-17 13:32:00 +01:00
FelixSe 3dcfaa94e9 Fixed multi window close bug (hopefully) 2019-03-17 10:29:32 +01:00
FelixSe 05104e450e Improved window closed detection 2019-03-16 14:06:48 +01:00
FelixSe 37f20e70bb Fix tab close 2019-03-14 23:12:03 +01:00
FelixSe e5a39fbe0d Cleanup orphaned sessions 2019-03-14 22:33:23 +01:00
FelixSe 5c2f3e6f95 Improve initialization 2019-03-14 22:32:35 +01:00
FelixSe b3e831e3e6 Update UWP version 2019-03-14 22:32:15 +01:00
FelixSe 31ff544379 Remove debug code 2019-03-12 22:49:38 +01:00
FelixSe 7c4f62d16b Fix problems with closing secondary windows 2019-03-12 22:41:16 +01:00
FelixSe f4b4623dab Restore tab underline 2019-03-11 23:23:37 +01:00
FelixSe a32a86e15f Fix focus issues on Windows 10 1803 2019-03-11 22:53:02 +01:00
Eric Cornelissen 6b64d3985d Persist custom tab title (#225)
* Persist custom tab title

* Logically seperate Shell and Tab title, reset Tab title on empty string

* Fix applicationview title not synched with tabtitle
2019-03-10 21:55:41 +01:00
FelixSe ed9c9ad6a6 Only send read bytes 2019-03-10 21:24:11 +01:00
FelixSe bece957ea9 Fix App not closing on SystemTray exit 2019-03-10 20:07:12 +01:00
Felix 4e83e66a85 Fix close being called twice (#227) 2019-03-10 18:09:49 +01:00
Eric Cornelissen 57d86a9723 Add custom context menu for single tab layout (#224)
* Add custom context menu for single tab

* Apply color to single tab

* Make 'None' color transparent
2019-03-10 16:24:02 +01:00
FelixSe 8b6aae7bda Added logging, fixed a problem with websocket 2019-03-10 16:01:34 +01:00
FelixSe 2d204e48ab highlight monospaced fonts 2019-03-09 12:36:38 +01:00
FelixSe 38a7b71fa8 Update dependencies 2019-03-09 10:46:28 +01:00
FelixSe 95c94e3820 Removed unnecessary clearing of webview cache 2019-03-09 10:28:16 +01:00
FelixSe 1920fdf35c Removed reactiveui and implemented custom debouncing 2019-03-09 10:20:17 +01:00
FelixSe 9f3c46c366 FIx settings events on wrong thread 2019-03-09 09:38:48 +01:00
FelixSe 221a2c2c6f Update xterm.js and devDependencies 2019-03-08 22:38:17 +01:00
Wayne Arthurton bde65fa865 Update README.md (#223)
Keybindings for new terminals has been implemented
2019-03-08 18:40:42 +01:00
Felix d52860e90a Dev/renderer neutral (#220)
* Encapsulate xterm.js implementation in XtermTerminalView

* fix TerminalTheme Equals()

* Fix new output indicator

* Ut8 and some fixes

* fix test

* fixed infinite loop on shell exit
2019-03-06 20:06:19 +01:00
Eric Cornelissen b85be454ce Improve settings window (#213)
* Update settings window layout with consistent margins

Updated the layout of the different views of the settings window so that
there is always margin at the bottom. In some cases this was achieved
using <StackPanels> for clarity and extensibility, grouping e.g. a title
and some radio options together with one unified margin.

Also made some small consistency changes to the .xaml files.

* Add width to Themes text input

* Normalize margins in settings

* Add default display values in Terminal settings

... for Arguments and Working Directory

* Generalize TemplateSelector for empty strings

* Add default display value for profile keybindings

* Update margins and fontsize in theme preview

* Update 'loading...' message in About

... to "Checking for updates..." and a spinner as suggested by
@skipmeister123

* No popup when cancelling profile edit no changes

* Delete profile when new and cancelled

* Open new and cloned themes in edit mode

... and make it so canceling deletes the theme.

* No popup when cancelling theme edit no changes

* Add resources for margin definitions

* Check for null Author in TerminalTheme

* Add resource for header margin definition

* Moved createdBy text to code behind
2019-02-28 21:09:52 +01:00
FelixSe 1f3dd72214 Improved text wrapping on window title 2019-02-28 20:44:00 +01:00
Eric Cornelissen c4fcd986c0 Add keybinding for configurable new window (#216)
* Add keybind to open new configurable window

* Close new configurable window on cancel

* renamed event args
2019-02-28 19:24:18 +01:00
FelixSe ea21d37089 Make InputBuffer more threadsafe 2019-02-25 21:10:39 +01:00
Paul Galbraith 2f133720cc Chocolatey install instructions (#209) 2019-02-19 07:30:43 +01:00
Paul Galbraith 250c90fa02 install option to force context menu integration without user interaction (#204) 2019-02-15 19:22:38 +01:00
Mike 7e2c5d2f45 Line ending translation (#172)
* On paste, the TerminalViewModel uses the ShellProfile to translate line endings.

Default line ending translation style is set to not modify the pasted contents, Next up, adding the radio button to shell profile.

* Added UI elements for choosing line ending style.

Using a converter to map between an enum and radio buttons, and another converter to map from enum to text based on the description of the enum value.

* Added "Paste without newlines" command.

* Adding missing converters to project file.

* A few fixes after cherry-picks.

* Adding missing Command property to PasteWithoutNewlines keybinding.

* Cleaned up some unnecessary property settings.

* fix #114 scrolling issue on presicion touchpads (#171)

* fix #114 scrolling issue on presicion touchpads, bounce scroll & pinch zoom

* rephrased comment

* removed css that enforced autohiding

* simplified js code handling scrollbar size

* Press enter to close an Input dialog (#177)

... As if the primary button is pressed.

* Renew certificate

* Update README.md

* Add option to use bold text in terminals (#183)

* Add option to use bold text in terminals

... as per #182

* Add BoldText to setting defaults

* Remove AppVeyor files

* Update README.md

* Add keybinding to change the (current) tab title (#193)

* Add keybinding for change the (current) tab title

Default binding is Ctrl+Shift+N

* Update change tab title default keybind to Ctrl+Shift+R

* Experimental ConPTY support (#199)

* First steps of ConPty support

* Added resize functionality

* fix cwd, args, hide console window

* Handle shell process exited

* Make ConPTY optional

* Fix automerge errors

* Accepted many XAML formatting changes, and changed command handling to switch.

* Fixed spaxcng on the settings page.

* adapted radio button binding to be in line with the rest of the application
2019-02-13 22:42:23 +01:00
FelixSe 9fed3fb192 Fix automerge errors 2019-02-09 23:06:45 +01:00
Felix 0d85f933c7 Experimental ConPTY support (#199)
* First steps of ConPty support

* Added resize functionality

* fix cwd, args, hide console window

* Handle shell process exited

* Make ConPTY optional
2019-02-08 22:12:29 +01:00
Eric Cornelissen f235ddf0f1 Add keybinding to change the (current) tab title (#193)
* Add keybinding for change the (current) tab title

Default binding is Ctrl+Shift+N

* Update change tab title default keybind to Ctrl+Shift+R
2019-02-08 21:55:23 +01:00
Felix 887cd45b4c Update README.md 2019-01-30 18:32:32 +01:00
FelixSe aac2f220d6 Remove AppVeyor files 2019-01-28 22:00:33 +01:00
Eric Cornelissen 90f15cc1e3 Add option to use bold text in terminals (#183)
* Add option to use bold text in terminals

... as per #182

* Add BoldText to setting defaults
2019-01-28 21:57:36 +01:00
Felix 292b66af4d Update README.md 2019-01-28 19:26:48 +01:00
FelixSe b6d835984c Renew certificate 2019-01-26 12:29:32 +01:00
Eric Cornelissen 369b5ddacf Press enter to close an Input dialog (#177)
... As if the primary button is pressed.
2019-01-18 21:20:56 +01:00
Mike 6be29f49d7 fix #114 scrolling issue on presicion touchpads (#171)
* fix #114 scrolling issue on presicion touchpads, bounce scroll & pinch zoom

* rephrased comment

* removed css that enforced autohiding

* simplified js code handling scrollbar size
2019-01-14 21:47:35 +01:00
FelixSe 8131140cb0 Increase version 2019-01-08 19:51:54 +01:00
FelixSe 304314230c update xterm to 3.10.1 2019-01-08 19:27:47 +01:00
FelixSe 0ce605f122 Fix DialogService tests 2019-01-07 22:46:26 +01:00
FelixSe d4aadd7dd4 Added option to change the title of a tab 2019-01-07 22:35:14 +01:00
FelixSe 3cf3e0a330 Themes can now be cloned 2019-01-07 19:37:21 +01:00
FelixSe ff2cfc36b6 Fix problems with selectAll, clear and Ctrl+C 2019-01-04 18:15:24 +01:00
FelixSe b118c66959 Increase version 2019-01-02 22:15:02 +01:00
FelixSe 131e3536f1 Fix theme not updating for profiles without theme 2018-12-30 22:56:11 +01:00
FelixSe b47b544065 Added option to restore preinstalled profiles 2018-12-30 19:00:24 +01:00
FelixSe b13c40e7e8 Clone profiles 2018-12-30 16:20:56 +01:00
Felix 4e47fcef72 Added option to specify theme in profile (#158) 2018-12-30 15:12:04 +01:00
Mike 8acf618fd4 Per-shell-profile keyboard shortcuts (#85)
* Added new command wrapper, abstracts static and dynamic commands

- All uses of the `Command` are replaced with either `AppCommand`, for a command that is static to the scope of the application, or a reference to an ICommand (specifically, an `EnumCommand<AppCommand>()` instance), which provides the abstract wrapper.
- Functionality seems intact.
- Next step is to have the MainViewModel add the per-shell commands via SettingsService.CreateShellKeyboardShortcuts(), and have the SettingsService.ParseCommandString() function parse the command strings passed around properly.
- Finally, add the keyboard chord input to each shell's settings page.

* First kick at adding a new terminal keyboard command.

- It isn't presented anywhere, but references to keybindings should be using the ICommand key as their identifier for recognizing anything, and when xterm passes back a keyboard command event, the SettingsService is asked to convert the string into an ICommand.

* Renaming ICommand to Command.

It's not longer an interface, so this makes more sense.

* Renaming back to Command, and new class is AbstractCommand.

Going to simplify the PR diff considerably.

* Shortcuts for default shells work. Need to get the shell profile edit UI elements working.

* Added keyboard shortcut UI elements to shell profile pages.

* New and deleted shell profiles propagate to all terminal views.

* A bit of cleanup.

* Camel-casing command text to fix Ctrl+C and copy behaviour.

* Fixed failing test.

The new on disk store uses a .ToString(), not the Enum description, since the command isn't an Enum, it's a class now in general, and those semantics make more sense.

* Adding default value for null keybindings in shell profiles.

* Merge branch 'shell_commands' of https://github.com/Riebart/FluentTerminal into pr/85

Improved default value for keybinding property

* Renamed KeyBinding collection to KeyBindings

* Use strings as Command parameter of KeyBinding

* Added missing Commands to keyBindings of default shellProfiles

* fix build and unit tests after merge with master

* improve UI

* Split OnShellProfileCollectionChanged event
2018-12-29 13:20:24 +01:00
Eric Cornelissen 49e09f3f8b Add "Show in system tray" option (#142)
* Add "Show in system tray" option to the SettingsView

* Implement "Show in system tray" requiring restart

* Add warning to restart program when toggling tray

* Update warning text for toggling tray option
2018-12-26 16:31:28 +01:00
Fauzie 9bf8284d9f Add Padding setting (#155)
* add padding setting

* `=` instead of `:`

Co-Authored-By: fauzie811 <fauzie811@yahoo.com>

* call setPadding in createTerminal function

* Add "px" suffix to padding slider tooltip

Based on this tutorial: 
https://www.codeproject.com/Tips/715438/Slider-Thumb-Tooltip

* term.fit() after setPadding()

* move back term.fit() below term.open()

* Changed max value, added throttling, moved converter
2018-12-26 14:41:16 +01:00
FelixSe 0f8aac81de add throttle for background opacity binding 2018-12-26 14:18:54 +01:00
FelixSe abec73b868 update xterm to 3.9.1 2018-12-25 15:50:07 +01:00
Zed 86486c99f2 fixed open here dir param. (#145) 2018-12-04 07:30:32 +01:00
Abhishek Goenka d9b7052e87 Prerequisite added (#139) 2018-11-19 17:18:44 +01:00
Meir Blachman 0efe105321 added missing test for KeyboardCommandService (#124)
added missing test for KeyboardCommandService for Command.ToggleWindow
2018-11-12 21:15:57 +01:00
Eric Cornelissen ed0dad960d Fix display issue when settings window is too narrow (#115)
Closes #100
2018-11-07 07:05:16 +01:00
Eric Cornelissen 2f58ed63de Add About page to the settings window (#103)
* create "About" page in settings

* add content to About page

* Get latest version from GitHub

* Add NotificationService to servies

* Enable actions on notifications

* Get latest version asynchroniously

* Handle when latest version is not found and clean code

* Remove old UpdateService

* Remove old NotificationService

* Add icon for about tab

* Update About page styling

* Make About menu item a footer menu item

* Update About page layout

Based on suggestions by @felixse

* Instatiate temporary navigation item only once

This seems to solve display issues when opening the "About" page multiple times where the main navigation list is being rerendered for no apparent reason.

* Revert unintended changes

* Added shortcut to about page in hamburger menu

* Move About page properties to the AboutPageViewModel

* Make "loading..." and "Not found" italic to indicate non-default behaviour

* move update check back to systemtray

* Made IUpdateService async
2018-10-28 13:55:36 +01:00
FelixSe cc7e069f11 Increase version 2018-10-20 14:51:50 +02:00
FelixSe 94a0eac58f Fixed tab width, Added option to indicate new output on background tabs 2018-10-20 12:20:41 +02:00
FelixSe 9af0556d6c Update xterm.js to 3.8.0 2018-10-12 07:39:52 +02:00
A.J. Ruckman b14ca2c4ed update winpty dependencies from current winpty master code (#95) 2018-10-09 22:27:26 +02:00
FelixSe 89c1a5a614 ExtendedVirtualKey now supports all OEM keys, and uses the qwerty default for description 2018-10-09 22:20:05 +02:00
Justin Gallagher 3e32f611e8 Add automatic install script (#92) 2018-10-08 22:02:58 +02:00
Mike 07e65ce2bf Clarifying installing and dev environment setup instructions. (#83)
* Clarifying installing vs dev environment setup.

* Adding details on installing the app, as well as dev dependencies.
2018-10-05 10:55:41 +02:00
Mike f2a48487f1 Return an empty string when attempting to paste non-text clipboard buffer (#86)
* Return an empty stirng when attempting to paste non-text clipboard buffer.

Returning null Task reference was causing a crash when attempting to paste a non-text buffer.

* Simplifying returned empty string task.
2018-10-01 21:40:18 +02:00
Mike c9d0cc7368 Tab theme colours (#73) 2018-09-10 07:50:20 +02:00
Mike eecc2e8243 Development environment documentation, and npm package bump (#68)
* Updating documentation and npm packages

- The webpack 3.10 was too old, as webpack-cli and webpack-command, required dependencies for building, need 4.x.x or 4.4.0 respectively.
- `npm-check-updates` (`npm t -g npm-check-updates`) run with `ncu -u` auto-bumped xterm.js to 3.6.0, and webpack to 4.16.5. Unfortunately this also reordered the file, so those changes aren't obvious.
- I also added webpack-cli as an explicit dependency.
- Updates to the readme to detail putting together a build environment.

* Noting that the package should be for sideloading

For dev, and non-published packages, the sideloading option is best.

* Adding dev StoreAssociation XML file for dev building.

* Cleaning up the packaging instructions.

* Changed instruction from rename to copy.

- Copy will not try to clobber the working directory of the template
file.

* Bumping xterm to 3.6.0, adding notes on installing built packages
2018-08-18 20:27:53 +02:00
Mike 5c1896cd3b Added support for Ctrl+<N> to switch to tab N. (#70) 2018-08-18 10:37:51 +02:00
Jakob Hermann Preusker e0cde7fe0f Fixed Issue #63: No scrolling in some pages (#66)
* Fixed Issue #63

* Requested changes to PR #66 done

* Fixed scrollbar position in themes/profiles
2018-08-17 07:48:15 +02:00
164 changed files with 9988 additions and 3552 deletions

No files matched your search

@@ -1,3 +1,3 @@
reg add "HKCU\Software\Classes\Directory\shell\Open Fluent Terminal here\command" /d "\"%LOCALAPPDATA%\Microsoft\WindowsApps\flute.exe\" new %%1" /f
reg add "HKCU\Software\Classes\Directory\Background\shell\Open Fluent Terminal here\command" /d "\"%LOCALAPPDATA%\Microsoft\WindowsApps\flute.exe\" new %%V" /f
reg add "HKCU\Software\Classes\LibraryFolder\Background\shell\Open Fluent Terminal here\command" /d "\"%LOCALAPPDATA%\Microsoft\WindowsApps\flute.exe\" new %%V" /f
reg add "HKCU\Software\Classes\Directory\shell\Open Fluent Terminal here\command" /d "\"%LOCALAPPDATA%\Microsoft\WindowsApps\flute.exe\" new \"%%1\"" /f
reg add "HKCU\Software\Classes\Directory\Background\shell\Open Fluent Terminal here\command" /d "\"%LOCALAPPDATA%\Microsoft\WindowsApps\flute.exe\" new \"%%V\"" /f
reg add "HKCU\Software\Classes\LibraryFolder\Background\shell\Open Fluent Terminal here\command" /d "\"%LOCALAPPDATA%\Microsoft\WindowsApps\flute.exe\" new \"%%V\"" /f
@@ -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,11 +86,24 @@ 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();
shellProfileSelectionDialog.Verify(x => x.SelectProfile(), Times.Once);
}
[Fact]
public void ShowInputDialogAsync_UsesIInputDialogSetTitle()
{
var title = "title";
var inputDialog = new Mock<IInputDialog>();
var dialogService = new DialogService(Mock.Of<IShellProfileSelectionDialog>, Mock.Of<IMessageDialog>, Mock.Of<ICreateKeyBindingDialog>, () => inputDialog.Object);
dialogService.ShowInputDialogAsync(title);
inputDialog.Verify(x => x.SetTitle(title), Times.Once);
inputDialog.Verify(x => x.GetInput(), Times.Once);
}
}
}
}
@@ -11,13 +11,16 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="AutoFixture" Version="4.4.0" />
<PackageReference Include="FluentAssertions" Version="5.4.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" />
<PackageReference Include="Moq" Version="4.8.3" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
<PackageReference Include="AutoFixture" Version="4.8.0" />
<PackageReference Include="FluentAssertions" Version="5.6.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.0.1" />
<PackageReference Include="Moq" Version="4.10.1" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />
</ItemGroup>
@@ -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,12 @@ 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 id = _fixture.Create<int>();
var trayProcessCommunicationService = new TrayProcessCommunicationService(settingsService.Object);
trayProcessCommunicationService.Initialize(appServiceConnection.Object);
var result = await trayProcessCommunicationService.CreateTerminal(terminalSize, shellProfile);
var result = await trayProcessCommunicationService.CreateTerminal(id, terminalSize, shellProfile, sessionType);
result.Should().BeEquivalentTo(response);
}
@@ -57,9 +59,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);
@@ -71,23 +73,23 @@ namespace FluentTerminal.App.Services.Test
}
[Fact]
public async Task WriteText_Default_SendsWriteTextRequest()
public async Task Write_Default_SendsWriteDataRequest()
{
var terminalId = _fixture.Create<int>();
var text = _fixture.Create<string>();
var data = _fixture.Create<byte[]>();
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);
trayProcessCommunicationService.Initialize(appServiceConnection.Object);
await trayProcessCommunicationService.WriteText(terminalId, text);
await trayProcessCommunicationService.Write(terminalId, data);
appServiceConnection.Verify(x => x.SendMessageAsync(It.Is<IDictionary<string, string>>(d => d[MessageKeys.Type] == nameof(WriteTextRequest))), Times.Once);
appServiceConnection.Verify(x => x.SendMessageAsync(It.Is<IDictionary<string, string>>(d => d[MessageKeys.Type] == nameof(WriteDataRequest))), Times.Once);
}
[Fact]
@@ -96,9 +98,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 +128,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>();
@@ -149,8 +151,8 @@ namespace FluentTerminal.App.Services.Test
public void OnMessageReceived_DisplayTerminalOutputRequest_InvokesCorrectOutputHandler()
{
var terminalId = _fixture.Create<int>();
var output = _fixture.Create<string>();
var receivedOutput = string.Empty;
var output = _fixture.Create<byte[]>();
var receivedOutput = default(byte[]);
var request = new DisplayTerminalOutputRequest
{
TerminalId = terminalId,
@@ -163,9 +165,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>();
@@ -177,7 +179,7 @@ namespace FluentTerminal.App.Services.Test
appServiceConnection.Raise(x => x.MessageReceived += null, null, message);
receivedOutput.Should().Be(output);
receivedOutput.Should().BeEquivalentTo(output);
}
}
}
@@ -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,13 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="Microsoft.CSharp" Version="4.5.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="PListNet" Version="2.0.3" />
<PackageReference Include="RestSharp" Version="106.6.9" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.3.0" />
<PackageReference Include="Serilog.Sinks.File" Version="4.0.0" />
<PackageReference Include="System.Runtime.InteropServices.WindowsRuntime" Version="4.3.0" />
</ItemGroup>
<ItemGroup>
@@ -9,7 +9,9 @@ namespace FluentTerminal.App.Services
public interface IApplicationView
{
event CloseRequestedHandler CloseRequested;
event EventHandler Closed;
int Id { get; }
string Title { get; set; }
Task RunOnDispatcherThread(Action action);
@@ -17,5 +19,6 @@ namespace FluentTerminal.App.Services
Task<bool> TryClose();
bool ToggleFullScreen();
bool IsApiContractPresent(string api, ushort version);
}
}
@@ -7,18 +7,20 @@ namespace FluentTerminal.App.Services
{
public interface IDefaultValueProvider
{
IEnumerable<ShellProfile> GetPreinstalledShellProfiles();
Guid GetDefaultShellProfileId();
TerminalOptions GetDefaultTerminalOptions();
ApplicationSettings GetDefaultApplicationSettings();
ICollection<KeyBinding> GetDefaultKeyBindings(Command command);
ApplicationSettings GetDefaultApplicationSettings();
Guid GetDefaultShellProfileId();
IEnumerable<TabTheme> GetDefaultTabThemes();
TerminalOptions GetDefaultTerminalOptions();
Guid GetDefaultThemeId();
IEnumerable<ShellProfile> GetPreinstalledShellProfiles();
IEnumerable<TerminalTheme> GetPreInstalledThemes();
}
}
@@ -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);
}
}
+26 -43
View File
@@ -1,5 +1,4 @@
using FluentTerminal.Models;
using FluentTerminal.Models.Enums;
using System;
using System.Collections.Generic;
@@ -7,52 +6,36 @@ namespace FluentTerminal.App.Services
{
public interface ISettingsService
{
event EventHandler<Guid> CurrentThemeChanged;
event EventHandler<TerminalOptions> TerminalOptionsChanged;
event EventHandler<ApplicationSettings> ApplicationSettingsChanged;
event EventHandler<Guid> CurrentThemeChanged;
event EventHandler KeyBindingsChanged;
Guid GetDefaultShellProfileId();
ShellProfile GetDefaultShellProfile();
void SaveDefaultShellProfileId(Guid id);
IEnumerable<ShellProfile> GetShellProfiles();
void SaveShellProfile(ShellProfile shellProfile);
event EventHandler<ShellProfile> ShellProfileAdded;
event EventHandler<Guid> ShellProfileDeleted;
event EventHandler<TerminalOptions> TerminalOptionsChanged;
event EventHandler<Guid> ThemeDeleted;
event EventHandler<TerminalTheme> ThemeAdded;
void DeleteShellProfile(Guid id);
TerminalOptions GetTerminalOptions();
void SaveTerminalOptions(TerminalOptions terminalOptions);
ApplicationSettings GetApplicationSettings();
void SaveApplicationSettings(ApplicationSettings applicationSettings);
IDictionary<Command, ICollection<KeyBinding>> GetKeyBindings();
void SaveKeyBindings(Command command, ICollection<KeyBinding> keyBindings);
void ResetKeyBindings();
TerminalTheme GetCurrentTheme();
Guid GetCurrentThemeId();
TerminalTheme GetTheme(Guid id);
void SaveCurrentThemeId(Guid id);
IEnumerable<TerminalTheme> GetThemes();
void SaveTheme(TerminalTheme theme);
void DeleteTheme(Guid id);
ApplicationSettings GetApplicationSettings();
TerminalTheme GetCurrentTheme();
Guid GetCurrentThemeId();
ShellProfile GetDefaultShellProfile();
Guid GetDefaultShellProfileId();
IDictionary<string, ICollection<KeyBinding>> GetCommandKeyBindings();
IEnumerable<ShellProfile> GetShellProfiles();
IEnumerable<TabTheme> GetTabThemes();
TerminalOptions GetTerminalOptions();
TerminalTheme GetTheme(Guid id);
IEnumerable<TerminalTheme> GetThemes();
void ResetKeyBindings();
void SaveApplicationSettings(ApplicationSettings applicationSettings);
void SaveCurrentThemeId(Guid id);
void SaveDefaultShellProfileId(Guid id);
void SaveKeyBindings(string command, ICollection<KeyBinding> keyBindings);
void SaveShellProfile(ShellProfile shellProfile, bool newShell = false);
void SaveTerminalOptions(TerminalOptions terminalOptions);
void SaveTheme(TerminalTheme theme, bool newTheme = false);
ShellProfile GetShellProfile(Guid id);
}
}
@@ -1,9 +1,10 @@
using System.Collections.Generic;
using FluentTerminal.Models;
using System.Collections.Generic;
namespace FluentTerminal.App.Services
{
public interface ISystemFontService
{
IEnumerable<string> GetSystemFontFamilies();
IEnumerable<FontInfo> GetSystemFontFamilies();
}
}
@@ -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,16 +12,18 @@ 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);
Task UpdateToggleWindowKeyBindings();
Task WriteText(int terminalId, string text);
Task Write(int terminalId, byte[] data);
void SubscribeForTerminalOutput(int terminalId, Action<string> callback);
void SubscribeForTerminalOutput(int terminalId, Action<byte[]> 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();
}
}
@@ -14,12 +14,17 @@ namespace FluentTerminal.App.Services.Implementation
ConfirmClosingTabs = false,
ConfirmClosingWindows = false,
UnderlineSelectedTab = false,
InactiveTabColorMode = InactiveTabColorMode.Background,
NewTerminalLocation = NewTerminalLocation.Tab,
TabsPosition = TabsPosition.Top,
CopyOnSelect = false,
MouseMiddleClickAction = MouseAction.None,
MouseRightClickAction = MouseAction.ContextMenu,
AlwaysShowTabs = false
AlwaysShowTabs = false,
AlwaysUseWinPty = true,
ShowNewOutputIndicator = false,
EnableTrayIcon = true,
ShowCustomTitleInTitlebar = true
};
}
@@ -32,7 +37,7 @@ namespace FluentTerminal.App.Services.Implementation
{
new KeyBinding
{
Command = Command.ToggleWindow,
Command = nameof(Command.ToggleWindow),
Ctrl = false,
Alt = false,
Shift = false,
@@ -45,7 +50,7 @@ namespace FluentTerminal.App.Services.Implementation
{
new KeyBinding
{
Command = Command.NextTab,
Command = nameof(Command.NextTab),
Ctrl = true,
Alt = false,
Shift = false,
@@ -58,7 +63,7 @@ namespace FluentTerminal.App.Services.Implementation
{
new KeyBinding
{
Command = Command.PreviousTab,
Command = nameof(Command.PreviousTab),
Ctrl = true,
Alt = false,
Shift = true,
@@ -66,12 +71,33 @@ namespace FluentTerminal.App.Services.Implementation
}
};
case Command.SwitchToTerm1:
case Command.SwitchToTerm2:
case Command.SwitchToTerm3:
case Command.SwitchToTerm4:
case Command.SwitchToTerm5:
case Command.SwitchToTerm6:
case Command.SwitchToTerm7:
case Command.SwitchToTerm8:
case Command.SwitchToTerm9:
return new List<KeyBinding>
{
new KeyBinding
{
Command = command.ToString(),
Ctrl = true,
Alt = false,
Shift = false,
Key = (int)ExtendedVirtualKey.Number1 + (command - Command.SwitchToTerm1)
}
};
case Command.NewTab:
return new List<KeyBinding>
{
new KeyBinding
{
Command = Command.NewTab,
Command = nameof(Command.NewTab),
Ctrl = true,
Alt = false,
Shift = false,
@@ -84,7 +110,7 @@ namespace FluentTerminal.App.Services.Implementation
{
new KeyBinding
{
Command = Command.ConfigurableNewTab,
Command = nameof(Command.ConfigurableNewTab),
Ctrl = true,
Alt = false,
Shift = true,
@@ -92,12 +118,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,
@@ -110,7 +149,7 @@ namespace FluentTerminal.App.Services.Implementation
{
new KeyBinding
{
Command = Command.NewWindow,
Command = nameof(Command.NewWindow),
Ctrl = true,
Alt = false,
Shift = false,
@@ -118,12 +157,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,
@@ -136,7 +188,7 @@ namespace FluentTerminal.App.Services.Implementation
{
new KeyBinding
{
Command = Command.Copy,
Command = nameof(Command.Copy),
Ctrl = true,
Alt = false,
Shift = false,
@@ -149,7 +201,7 @@ namespace FluentTerminal.App.Services.Implementation
{
new KeyBinding
{
Command = Command.Paste,
Command = nameof(Command.Paste),
Ctrl = true,
Alt = false,
Shift = false,
@@ -157,12 +209,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,
@@ -175,7 +241,7 @@ namespace FluentTerminal.App.Services.Implementation
{
new KeyBinding
{
Command = Command.ToggleFullScreen,
Command = nameof(Command.ToggleFullScreen),
Ctrl = false,
Alt = true,
Shift = false,
@@ -188,7 +254,7 @@ namespace FluentTerminal.App.Services.Implementation
{
new KeyBinding
{
Command = Command.SelectAll,
Command = nameof(Command.SelectAll),
Ctrl = true,
Alt = false,
Shift = false,
@@ -201,7 +267,7 @@ namespace FluentTerminal.App.Services.Implementation
{
new KeyBinding
{
Command = Command.Clear,
Command = nameof(Command.Clear),
Ctrl = true,
Alt = false,
Shift = false,
@@ -218,6 +284,59 @@ namespace FluentTerminal.App.Services.Implementation
return Guid.Parse("813f2298-210a-481a-bdbf-c17bc637a3e2");
}
public IEnumerable<TabTheme> GetDefaultTabThemes()
{
return new[]
{
new TabTheme
{
Id = 0,
Name = "None",
BackgroundOpacity = double.NaN,
BackgroundPointerOverOpacity = double.NaN,
BackgroundPressedOpacity = double.NaN,
BackgroundSelectedOpacity = double.NaN,
BackgroundSelectedPointerOverOpacity = double.NaN,
BackgroundSelectedPressedOpacity = double.NaN
},
new TabTheme
{
Id = 1,
Name = "Red",
Color = "#E81123"
},
new TabTheme
{
Id = 2,
Name = "Green",
Color = "#10893E"
},
new TabTheme
{
Id = 3,
Name = "Blue",
Color = "#0078D7"
},
new TabTheme
{
Id = 4,
Name = "Purple",
Color = "#881798"
},
new TabTheme
{
Id = 5,
Name = "Orange",
Color = "#FF8C00"
},
new TabTheme
{
Id = 6,
Name = "Teal",
Color = "#00B7C3"
}
};
}
public TerminalOptions GetDefaultTerminalOptions()
{
return new TerminalOptions
@@ -228,7 +347,9 @@ namespace FluentTerminal.App.Services.Implementation
ScrollBarStyle = ScrollBarStyle.Hidden,
FontFamily = "Consolas",
FontSize = 13,
BoldText = false,
BackgroundOpacity = 0.8,
Padding = 12,
ScrollBackLimit = 1000
};
}
@@ -249,7 +370,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
{
@@ -258,7 +392,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
{
@@ -267,7 +414,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
}
}
}
};
}
@@ -437,4 +597,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,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)
{
@@ -14,19 +14,11 @@ namespace FluentTerminal.App.Services.Implementation
public const string DefaultShellProfileKey = "DefaultShellProfile";
private readonly IDefaultValueProvider _defaultValueProvider;
private readonly IApplicationDataContainer _localSettings;
private readonly IApplicationDataContainer _themes;
private readonly IApplicationDataContainer _keyBindings;
private readonly IApplicationDataContainer _shellProfiles;
private readonly IApplicationDataContainer _localSettings;
private readonly IApplicationDataContainer _roamingSettings;
public event EventHandler<Guid> CurrentThemeChanged;
public event EventHandler<TerminalOptions> TerminalOptionsChanged;
public event EventHandler<ApplicationSettings> ApplicationSettingsChanged;
public event EventHandler KeyBindingsChanged;
private readonly IApplicationDataContainer _shellProfiles;
private readonly IApplicationDataContainer _themes;
public SettingsService(IDefaultValueProvider defaultValueProvider, ApplicationDataContainers containers)
{
@@ -40,15 +32,61 @@ namespace FluentTerminal.App.Services.Implementation
foreach (var theme in _defaultValueProvider.GetPreInstalledThemes())
{
_themes.WriteValueAsJson(theme.Id.ToString(), theme);
if (GetTheme(theme.Id) == null)
{
_themes.WriteValueAsJson(theme.Id.ToString(), theme);
}
}
foreach (var shellProfile in _defaultValueProvider.GetPreinstalledShellProfiles())
{
_shellProfiles.WriteValueAsJson(shellProfile.Id.ToString(), shellProfile);
if (GetShellProfile(shellProfile.Id) == null)
{
_shellProfiles.WriteValueAsJson(shellProfile.Id.ToString(), shellProfile);
}
}
}
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()
{
return _roamingSettings.ReadValueFromJson(nameof(ApplicationSettings), _defaultValueProvider.GetDefaultApplicationSettings());
}
public TerminalTheme GetCurrentTheme()
{
var id = GetCurrentThemeId();
@@ -71,96 +109,6 @@ namespace FluentTerminal.App.Services.Implementation
return _defaultValueProvider.GetDefaultThemeId();
}
public void SaveCurrentThemeId(Guid id)
{
_roamingSettings.SetValue(CurrentThemeKey, id);
CurrentThemeChanged?.Invoke(this, id);
}
public void SaveTheme(TerminalTheme theme)
{
_themes.WriteValueAsJson(theme.Id.ToString(), theme);
if (theme.Id == GetCurrentThemeId())
{
CurrentThemeChanged?.Invoke(this, theme.Id);
}
}
public void DeleteTheme(Guid id)
{
_themes.Delete(id.ToString());
}
public IEnumerable<TerminalTheme> GetThemes()
{
return _themes.GetAll().Select(x => JsonConvert.DeserializeObject<TerminalTheme>((string)x)).ToList();
}
public TerminalTheme GetTheme(Guid id)
{
return _themes.ReadValueFromJson(id.ToString(), default(TerminalTheme));
}
public TerminalOptions GetTerminalOptions()
{
return _roamingSettings.ReadValueFromJson(nameof(TerminalOptions), _defaultValueProvider.GetDefaultTerminalOptions());
}
public void SaveTerminalOptions(TerminalOptions terminalOptions)
{
_roamingSettings.WriteValueAsJson(nameof(TerminalOptions), terminalOptions);
TerminalOptionsChanged?.Invoke(this, terminalOptions);
}
public ApplicationSettings GetApplicationSettings()
{
return _roamingSettings.ReadValueFromJson(nameof(ApplicationSettings), _defaultValueProvider.GetDefaultApplicationSettings());
}
public void SaveApplicationSettings(ApplicationSettings applicationSettings)
{
_roamingSettings.WriteValueAsJson(nameof(ApplicationSettings), applicationSettings);
ApplicationSettingsChanged?.Invoke(this, applicationSettings);
}
public IDictionary<Command, ICollection<KeyBinding>> GetKeyBindings()
{
var keyBindings = new Dictionary<Command, ICollection<KeyBinding>>();
foreach (Command command in Enum.GetValues(typeof(Command)))
{
keyBindings.Add(command, _keyBindings.ReadValueFromJson<Collection<KeyBinding>>(command.ToString(), null) ?? _defaultValueProvider.GetDefaultKeyBindings(command));
}
return keyBindings;
}
public void SaveKeyBindings(Command command, ICollection<KeyBinding> keyBindings)
{
_keyBindings.WriteValueAsJson(command.ToString(), keyBindings);
KeyBindingsChanged?.Invoke(this, System.EventArgs.Empty);
}
public void ResetKeyBindings()
{
foreach (Command command in Enum.GetValues(typeof(Command)))
{
_keyBindings.WriteValueAsJson(command.ToString(), _defaultValueProvider.GetDefaultKeyBindings(command));
}
KeyBindingsChanged?.Invoke(this, System.EventArgs.Empty);
}
public Guid GetDefaultShellProfileId()
{
if (_localSettings.TryGetValue(DefaultShellProfileKey, out object value))
{
return (Guid)value;
}
return _defaultValueProvider.GetDefaultShellProfileId();
}
public ShellProfile GetDefaultShellProfile()
{
var id = GetDefaultShellProfileId();
@@ -174,9 +122,29 @@ namespace FluentTerminal.App.Services.Implementation
return profile;
}
public void SaveDefaultShellProfileId(Guid id)
public ShellProfile GetShellProfile(Guid id)
{
_localSettings.SetValue(DefaultShellProfileKey, id);
return _shellProfiles.ReadValueFromJson(id.ToString(), default(ShellProfile));
}
public Guid GetDefaultShellProfileId()
{
if (_localSettings.TryGetValue(DefaultShellProfileKey, out object value))
{
return (Guid)value;
}
return _defaultValueProvider.GetDefaultShellProfileId();
}
public IDictionary<string, ICollection<KeyBinding>> GetCommandKeyBindings()
{
var keyBindings = new Dictionary<string, ICollection<KeyBinding>>();
foreach (Command command in Enum.GetValues(typeof(Command)))
{
keyBindings.Add(command.ToString(), _keyBindings.ReadValueFromJson<Collection<KeyBinding>>(command.ToString(), null) ?? _defaultValueProvider.GetDefaultKeyBindings(command));
}
return keyBindings;
}
public IEnumerable<ShellProfile> GetShellProfiles()
@@ -184,14 +152,97 @@ namespace FluentTerminal.App.Services.Implementation
return _shellProfiles.GetAll().Select(x => JsonConvert.DeserializeObject<ShellProfile>((string)x)).ToList();
}
public void SaveShellProfile(ShellProfile shellProfile)
public IEnumerable<TabTheme> GetTabThemes()
{
_shellProfiles.WriteValueAsJson(shellProfile.Id.ToString(), shellProfile);
return _defaultValueProvider.GetDefaultTabThemes();
}
public void DeleteShellProfile(Guid id)
public TerminalOptions GetTerminalOptions()
{
_shellProfiles.Delete(id.ToString());
return _roamingSettings.ReadValueFromJson(nameof(TerminalOptions), _defaultValueProvider.GetDefaultTerminalOptions());
}
public TerminalTheme GetTheme(Guid id)
{
return _themes.ReadValueFromJson(id.ToString(), default(TerminalTheme));
}
public IEnumerable<TerminalTheme> GetThemes()
{
return _themes.GetAll().Select(x => JsonConvert.DeserializeObject<TerminalTheme>((string)x)).ToList();
}
public void ResetKeyBindings()
{
foreach (Command command in Enum.GetValues(typeof(Command)))
{
_keyBindings.WriteValueAsJson(command.ToString(), _defaultValueProvider.GetDefaultKeyBindings(command));
}
KeyBindingsChanged?.Invoke(this, System.EventArgs.Empty);
}
public void SaveApplicationSettings(ApplicationSettings applicationSettings)
{
_roamingSettings.WriteValueAsJson(nameof(ApplicationSettings), applicationSettings);
ApplicationSettingsChanged?.Invoke(this, applicationSettings);
}
public void SaveCurrentThemeId(Guid id)
{
_roamingSettings.SetValue(CurrentThemeKey, id);
CurrentThemeChanged?.Invoke(this, id);
}
public void SaveDefaultShellProfileId(Guid id)
{
_localSettings.SetValue(DefaultShellProfileKey, id);
}
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, 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)
{
_roamingSettings.WriteValueAsJson(nameof(TerminalOptions), terminalOptions);
TerminalOptionsChanged?.Invoke(this, terminalOptions);
}
public void SaveTheme(TerminalTheme theme, bool newTheme = false)
{
_themes.WriteValueAsJson(theme.Id.ToString(), theme);
if (theme.Id == GetCurrentThemeId())
{
CurrentThemeChanged?.Invoke(this, theme.Id);
}
if (newTheme)
{
ThemeAdded?.Invoke(this, theme);
}
}
}
}
@@ -5,7 +5,6 @@ using FluentTerminal.Models.Responses;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
namespace FluentTerminal.App.Services.Implementation
@@ -14,27 +13,52 @@ namespace FluentTerminal.App.Services.Implementation
{
private readonly ISettingsService _settingsService;
private IAppServiceConnection _appServiceConnection;
private Dictionary<int, Action<string>> _terminalOutputHandlers;
private readonly Dictionary<int, Action<byte[]>> _terminalOutputHandlers;
private int _nextTerminalId = 0;
public event EventHandler<int> TerminalExited;
public TrayProcessCommunicationService(ISettingsService settingsService)
{
_settingsService = settingsService;
_terminalOutputHandlers = new Dictionary<int, Action<string>>();
_terminalOutputHandlers = new Dictionary<int, Action<byte[]>>();
}
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));
var response = JsonConvert.DeserializeObject<GetAvailablePortResponse>(responseMessage[MessageKeys.Content]);
Logger.Instance.Debug("Received GetAvailablePortResponse: {@response}", response);
return response;
}
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));
Logger.Instance.Debug("Sending CreateTerminalRequest: {@request}", request);
return JsonConvert.DeserializeObject<CreateTerminalResponse>(responseMessage[MessageKeys.Content]);
var responseMessage = await _appServiceConnection.SendMessageAsync(CreateMessage(request));
var response = JsonConvert.DeserializeObject<CreateTerminalResponse>(responseMessage[MessageKeys.Content]);
Logger.Instance.Debug("Received CreateTerminalResponse: {@response}", response);
return response;
}
public void Initialize(IAppServiceConnection appServiceConnection)
@@ -58,13 +82,15 @@ namespace FluentTerminal.App.Services.Implementation
}
else
{
Debug.WriteLine("output was not handled: " + request.Output);
Logger.Instance.Error("Received output for unknown terminal Id {id}", request.TerminalId);
}
}
else if (messageType == nameof(TerminalExitedRequest))
{
var request = JsonConvert.DeserializeObject<TerminalExitedRequest>(messageContent);
Logger.Instance.Debug("Received TerminalExitedRequest: {@request}", request);
TerminalExited?.Invoke(this, request.TerminalId);
}
}
@@ -80,14 +106,14 @@ namespace FluentTerminal.App.Services.Implementation
return _appServiceConnection.SendMessageAsync(CreateMessage(request));
}
public void SubscribeForTerminalOutput(int terminalId, Action<string> callback)
public void SubscribeForTerminalOutput(int terminalId, Action<byte[]> callback)
{
_terminalOutputHandlers[terminalId] = callback;
}
public Task UpdateToggleWindowKeyBindings()
{
var keyBindings = _settingsService.GetKeyBindings()[Command.ToggleWindow];
var keyBindings = _settingsService.GetCommandKeyBindings()[nameof(Command.ToggleWindow)];
var request = new SetToggleWindowKeyBindingsRequest
{
@@ -97,12 +123,12 @@ namespace FluentTerminal.App.Services.Implementation
return _appServiceConnection.SendMessageAsync(CreateMessage(request));
}
public Task WriteText(int id, string text)
public Task Write(int id, byte[] data)
{
var request = new WriteTextRequest
var request = new WriteDataRequest
{
TerminalId = id,
Text = text
Data = data
};
return _appServiceConnection.SendMessageAsync(CreateMessage(request));
@@ -115,6 +141,8 @@ namespace FluentTerminal.App.Services.Implementation
TerminalId = terminalId
};
Logger.Instance.Debug("Sending TerminalExitedRequest: {@request}", request);
return _appServiceConnection.SendMessageAsync(CreateMessage(request));
}
@@ -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);
}
}
}
+66
View File
@@ -0,0 +1,66 @@
using Serilog;
using Serilog.Events;
using System;
namespace FluentTerminal.App.Services
{
public sealed class Logger
{
public enum LogLevel
{
Verbose = 0,
Debug = 1,
Information = 2,
Warning = 3,
Error = 4,
Fatal = 5
}
public class Configuration
{
public LogLevel LogLevel { get; set; } = LogLevel.Error;
}
public static Logger Instance { get; } = new Logger();
private Logger()
{
}
public void Initialize(string filePath, Configuration configuration)
{
Log.Logger = new LoggerConfiguration()
.WriteTo.Async(a => a.File(filePath, rollingInterval: RollingInterval.Day, retainedFileCountLimit: 7))
.MinimumLevel.Is((LogEventLevel)configuration.LogLevel)
.CreateLogger();
Log.Information("Initialized");
}
public void Debug(string text, params object[] propertyValues)
{
Log.Debug(text, propertyValues, propertyValues);
}
public void Information(string text, params object[] propertyValues)
{
Log.Information(text, propertyValues);
}
public void Warning(string text, params object[] propertyValues)
{
Log.Warning(text, propertyValues);
}
public void Error(string text, params object[] propertyValues)
{
Log.Error(text, propertyValues);
}
public void Error(Exception exception, string text, params object[] propertyValues)
{
Log.Error(exception, text, propertyValues);
}
}
}
+144
View File
@@ -0,0 +1,144 @@
using FluentTerminal.Models;
using FluentTerminal.Models.Enums;
using FluentTerminal.Models.Responses;
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;
_trayProcessCommunicationService.TerminalExited += OnTerminalExited;
Id = _trayProcessCommunicationService.GetNextTerminalId();
}
private void OnTerminalExited(object sender, int e)
{
if (e == Id)
{
Closed?.Invoke(this, System.EventArgs.Empty);
}
}
/// <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<byte[]> 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);
}
/// <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<CreateTerminalResponse> 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);
}
return response;
}
public Task Write(byte[] data)
{
return _trayProcessCommunicationService.Write(Id, data);
}
/// <summary>
/// Tells the ViewModel that this view failed to launch and can be closed.
/// </summary>
public void ReportLauchFailed()
{
Closed?.Invoke(this, System.EventArgs.Empty);
}
}
}
@@ -6,7 +6,7 @@
<ItemGroup>
<PackageReference Include="Fleck" Version="1.0.3" />
<PackageReference Include="MvvmLightLibs" Version="5.4.1" />
<PackageReference Include="MvvmLightLibs" Version="5.4.1.1" />
</ItemGroup>
<ItemGroup>
@@ -0,0 +1,124 @@
using FluentTerminal.App.ViewModels.Utilities;
using System;
using System.Threading.Tasks;
using System.Windows.Input;
namespace FluentTerminal.App.ViewModels.Infrastructure
{
public class AsyncCommand : IAsyncCommand
{
public event EventHandler CanExecuteChanged;
private bool _isExecuting;
private readonly Func<Task> _execute;
private readonly Func<bool> _canExecute;
private readonly IErrorHandler _errorHandler;
public AsyncCommand(
Func<Task> execute,
Func<bool> canExecute = null,
IErrorHandler errorHandler = null)
{
_execute = execute;
_canExecute = canExecute;
_errorHandler = errorHandler;
}
public bool CanExecute()
{
return !_isExecuting && (_canExecute?.Invoke() ?? true);
}
public async Task ExecuteAsync()
{
if (CanExecute())
{
try
{
_isExecuting = true;
await _execute();
}
finally
{
_isExecuting = false;
}
}
RaiseCanExecuteChanged();
}
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
#region Explicit implementations
bool ICommand.CanExecute(object parameter)
{
return CanExecute();
}
void ICommand.Execute(object parameter)
{
ExecuteAsync().FireAndForgetSafeAsync(_errorHandler);
}
#endregion
}
public class AsyncCommand<T> : IAsyncCommand<T>
{
public event EventHandler CanExecuteChanged;
private bool _isExecuting;
private readonly Func<T, Task> _execute;
private readonly Func<T, bool> _canExecute;
private readonly IErrorHandler _errorHandler;
public AsyncCommand(Func<T, Task> execute, Func<T, bool> canExecute = null, IErrorHandler errorHandler = null)
{
_execute = execute;
_canExecute = canExecute;
_errorHandler = errorHandler;
}
public bool CanExecute(T parameter)
{
return !_isExecuting && (_canExecute?.Invoke(parameter) ?? true);
}
public async Task ExecuteAsync(T parameter)
{
if (CanExecute(parameter))
{
try
{
_isExecuting = true;
await _execute(parameter);
}
finally
{
_isExecuting = false;
}
}
RaiseCanExecuteChanged();
}
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
#region Explicit implementations
bool ICommand.CanExecute(object parameter)
{
return CanExecute((T)parameter);
}
void ICommand.Execute(object parameter)
{
ExecuteAsync((T)parameter).FireAndForgetSafeAsync(_errorHandler);
}
#endregion
}
}
@@ -0,0 +1,20 @@
using System.Threading.Tasks;
using System.Windows.Input;
namespace FluentTerminal.App.ViewModels.Infrastructure
{
/// <summary>
/// based on: https://github.com/johnthiriet/AsyncVoid
/// </summary>
public interface IAsyncCommand : ICommand
{
Task ExecuteAsync();
bool CanExecute();
}
public interface IAsyncCommand<T> : ICommand
{
Task ExecuteAsync(T parameter);
bool CanExecute(T parameter);
}
}
@@ -0,0 +1,9 @@
using System;
namespace FluentTerminal.App.ViewModels.Infrastructure
{
public interface IErrorHandler
{
void HandleError(Exception ex);
}
}
+181 -43
View File
@@ -14,7 +14,6 @@ namespace FluentTerminal.App.ViewModels
{
public class MainViewModel : ViewModelBase
{
private readonly IApplicationView _applicationView;
private readonly IClipboardService _clipboardService;
private readonly IDialogService _dialogService;
private readonly IDispatcherTimer _dispatcherTimer;
@@ -26,6 +25,7 @@ namespace FluentTerminal.App.ViewModels
private double _backgroundOpacity;
private TerminalViewModel _selectedTerminal;
private TabsPosition _tabsPosition;
private string _windowTitle;
public MainViewModel(ISettingsService settingsService, ITrayProcessCommunicationService trayProcessCommunicationService, IDialogService dialogService, IKeyboardCommandService keyboardCommandService,
IApplicationView applicationView, IDispatcherTimer dispatcherTimer, IClipboardService clipboardService)
@@ -34,20 +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;
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(Command.NextTab, SelectNextTab);
_keyboardCommandService.RegisterCommandHandler(Command.PreviousTab, SelectPreviousTab);
_keyboardCommandService.RegisterCommandHandler(Command.NewWindow, NewWindow);
_keyboardCommandService.RegisterCommandHandler(Command.ShowSettings, ShowSettings);
_keyboardCommandService.RegisterCommandHandler(Command.ToggleFullScreen, ToggleFullScreen);
_keyboardCommandService.RegisterCommandHandler(nameof(Command.NewTab), () => AddTerminal());
_keyboardCommandService.RegisterCommandHandler(nameof(Command.ConfigurableNewTab), () => AddConfigurableTerminal());
_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++)
{
var switchCmd = Command.SwitchToTerm1 + i;
int tabNumber = i;
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.Id));
}
var currentTheme = _settingsService.GetCurrentTheme();
var options = _settingsService.GetTerminalOptions();
Background = currentTheme.Colors.Background;
@@ -55,13 +78,30 @@ namespace FluentTerminal.App.ViewModels
_applicationSettings = _settingsService.GetApplicationSettings();
TabsPosition = _applicationSettings.TabsPosition;
AddTerminalCommand = new RelayCommand(() => AddTerminal(null, false));
AddTerminalCommand = new RelayCommand(() => AddTerminal());
ShowAboutCommand = new RelayCommand(ShowAbout);
ShowSettingsCommand = new RelayCommand(ShowSettings);
_applicationView.CloseRequested += OnCloseRequest;
ApplicationView.CloseRequested += OnCloseRequest;
ApplicationView.Closed += OnClosed;
Terminals.CollectionChanged += OnTerminalsCollectionChanged;
}
private void OnClosed(object sender, EventArgs e)
{
Closed?.Invoke(this, e);
}
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.Id));
}
private void OnTerminalsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
RaisePropertyChanged(nameof(ShowTabsOnTop));
@@ -70,12 +110,26 @@ 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 string WindowTitle
{
get => _windowTitle;
set
{
if (Set(ref _windowTitle, value))
{
ApplicationView.Title = value;
}
}
}
public bool ShowTabsOnTop
{
get => TabsPosition == TabsPosition.Top && (Terminals.Count > 1 || _applicationSettings.AlwaysShowTabs);
@@ -113,11 +167,21 @@ namespace FluentTerminal.App.ViewModels
if (SelectedTerminal != null)
{
SelectedTerminal.IsSelected = true;
if (_applicationSettings.ShowCustomTitleInTitlebar)
{
WindowTitle = SelectedTerminal.TabTitle;
}
else
{
WindowTitle = SelectedTerminal.ShellTitle;
}
}
}
}
}
public RelayCommand ShowAboutCommand { get; }
public RelayCommand ShowSettingsCommand { get; }
public TabsPosition TabsPosition
@@ -128,39 +192,89 @@ namespace FluentTerminal.App.ViewModels
public ObservableCollection<TerminalViewModel> Terminals { get; } = new ObservableCollection<TerminalViewModel>();
public Task AddTerminal(string startupDirectory, bool showProfileSelection)
public IApplicationView ApplicationView { get; }
public Task AddConfigurableTerminal()
{
return _applicationView.RunOnDispatcherThread(async () =>
return ApplicationView.RunOnDispatcherThread(async () =>
{
ShellProfile profile = null;
if (showProfileSelection)
{
profile = await _dialogService.ShowProfileSelectionDialogAsync();
var profile = await _dialogService.ShowProfileSelectionDialogAsync();
if (profile == null)
if (profile == null)
{
if (Terminals.Count == 0)
{
return;
await ApplicationView.TryClose();
}
}
else
{
profile = _settingsService.GetDefaultShellProfile();
return;
}
AddTerminal(profile);
});
}
public void AddTerminal()
{
var profile = _settingsService.GetDefaultShellProfile();
AddTerminal(profile);
}
public void AddTerminal(Guid shellProfileId)
{
var profile = _settingsService.GetShellProfile(shellProfileId);
AddTerminal(profile);
}
public void AddTerminal(ShellProfile profile)
{
ApplicationView.RunOnDispatcherThread(() =>
{
var terminal = new TerminalViewModel(_settingsService, _trayProcessCommunicationService, _dialogService, _keyboardCommandService,
_applicationSettings, startupDirectory, profile, _applicationView, _dispatcherTimer, _clipboardService);
terminal.Closed += OnTerminalCloseRequested;
_applicationSettings, profile, ApplicationView, _dispatcherTimer, _clipboardService);
terminal.Closed += OnTerminalClosed;
terminal.ShellTitleChanged += Terminal_ShellTitleChanged;
terminal.CustomTitleChanged += Terminal_CustomTitleChanged;
Terminals.Add(terminal);
SelectedTerminal = terminal;
});
}
public void CloseAllTerminals()
private void Terminal_CustomTitleChanged(object sender, string e)
{
if (sender is TerminalViewModel terminal)
{
if (terminal.IsSelected && _applicationSettings.ShowCustomTitleInTitlebar)
{
if (string.IsNullOrWhiteSpace(e))
{
WindowTitle = terminal.ShellTitle;
}
else
{
WindowTitle = e;
}
}
}
}
private void Terminal_ShellTitleChanged(object sender, string e)
{
if (sender is TerminalViewModel terminal && terminal.IsSelected && !_applicationSettings.ShowCustomTitleInTitlebar)
{
WindowTitle = e;
}
}
public async Task CloseAllTerminals()
{
foreach (var terminal in Terminals)
{
terminal.CloseView();
terminal.Closed -= OnTerminalClosed;
terminal.ShellTitleChanged -= Terminal_ShellTitleChanged;
terminal.CustomTitleChanged -= Terminal_CustomTitleChanged;
await terminal.Close();
}
}
@@ -169,19 +283,33 @@ 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)
{
await _applicationView.RunOnDispatcherThread(() =>
await ApplicationView.RunOnDispatcherThread(() =>
{
_applicationSettings = e;
TabsPosition = e.TabsPosition;
RaisePropertyChanged(nameof(ShowTabsOnTop));
RaisePropertyChanged(nameof(ShowTabsOnBottom));
if (e.ShowCustomTitleInTitlebar)
{
WindowTitle = SelectedTerminal?.TabTitle;
}
else
{
WindowTitle = SelectedTerminal?.ShellTitle;
}
});
}
@@ -193,8 +321,7 @@ namespace FluentTerminal.App.ViewModels
if (result == DialogButton.OK)
{
CloseAllTerminals();
Closed?.Invoke(this, EventArgs.Empty);
await CloseAllTerminals();
}
else
{
@@ -203,47 +330,53 @@ namespace FluentTerminal.App.ViewModels
}
else
{
CloseAllTerminals();
Closed?.Invoke(this, EventArgs.Empty);
await CloseAllTerminals();
}
}
private async void OnCurrentThemeChanged(object sender, Guid e)
{
await _applicationView.RunOnDispatcherThread(() =>
await ApplicationView.RunOnDispatcherThread(() =>
{
var currentTheme = _settingsService.GetTheme(e);
Background = currentTheme.Colors.Background;
});
}
private void OnTerminalCloseRequested(object sender, EventArgs e)
private async void OnTerminalClosed(object sender, EventArgs e)
{
if (sender is TerminalViewModel terminal)
{
terminal.CloseView();
if (SelectedTerminal == terminal)
{
SelectedTerminal = Terminals.LastOrDefault(t => t != terminal);
}
Logger.Instance.Debug("Terminal with Id: {@id} closed.", terminal.Terminal.Id);
Terminals.Remove(terminal);
if (Terminals.Count == 0)
{
Closed?.Invoke(this, EventArgs.Empty);
_applicationView.TryClose();
await ApplicationView.TryClose();
}
}
}
private async void OnTerminalOptionsChanged(object sender, TerminalOptions e)
{
await _applicationView.RunOnDispatcherThread(() =>
await ApplicationView.RunOnDispatcherThread(() =>
{
BackgroundOpacity = e.BackgroundOpacity;
});
}
private void SelectTabNumber(int tabNumber)
{
if (tabNumber < Terminals.Count)
{
SelectedTerminal = Terminals[tabNumber];
}
}
private void SelectNextTab()
{
var currentIndex = Terminals.IndexOf(SelectedTerminal);
@@ -258,6 +391,11 @@ namespace FluentTerminal.App.ViewModels
SelectedTerminal = Terminals[previousIndex];
}
private void ShowAbout()
{
ShowAboutRequested?.Invoke(this, EventArgs.Empty);
}
private void ShowSettings()
{
ShowSettingsRequested?.Invoke(this, EventArgs.Empty);
@@ -265,7 +403,7 @@ namespace FluentTerminal.App.ViewModels
private void ToggleFullScreen()
{
_applicationView.ToggleFullScreen();
ApplicationView.ToggleFullScreen();
}
}
}
@@ -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);
}
}
}
@@ -17,7 +17,9 @@ namespace FluentTerminal.App.ViewModels.Settings
private bool _canEnableStartupTask;
private bool _editingNewTerminalLocation;
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)
@@ -52,6 +54,71 @@ 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 ShowCustomTitleInTitlebar
{
get => _applicationSettings.ShowCustomTitleInTitlebar;
set
{
if (_applicationSettings.ShowCustomTitleInTitlebar != value)
{
_applicationSettings.ShowCustomTitleInTitlebar = 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;
set
{
if (_applicationSettings.ShowNewOutputIndicator != value)
{
_applicationSettings.ShowNewOutputIndicator = value;
_settingsService.SaveApplicationSettings(_applicationSettings);
RaisePropertyChanged();
}
}
}
public bool BottomIsSelected
{
get => TabsPosition == TabsPosition.Bottom;
@@ -176,6 +243,36 @@ namespace FluentTerminal.App.ViewModels.Settings
}
}
public bool BackgroundIsSelected
{
get => InactiveTabColorMode == InactiveTabColorMode.Background;
set => InactiveTabColorMode = InactiveTabColorMode.Background;
}
public bool UnderlinedIsSelected
{
get => InactiveTabColorMode == InactiveTabColorMode.Underlined;
set => InactiveTabColorMode = InactiveTabColorMode.Underlined;
}
public InactiveTabColorMode InactiveTabColorMode
{
get => _applicationSettings.InactiveTabColorMode;
set
{
if (_applicationSettings.InactiveTabColorMode != value && !_editingInactiveTabColorMode)
{
_editingInactiveTabColorMode = true;
_applicationSettings.InactiveTabColorMode = value;
_settingsService.SaveApplicationSettings(_applicationSettings);
RaisePropertyChanged();
RaisePropertyChanged(nameof(BackgroundIsSelected));
RaisePropertyChanged(nameof(UnderlinedIsSelected));
_editingInactiveTabColorMode = false;
}
}
}
public bool WindowIsSelected
{
get => NewTerminalLocation == NewTerminalLocation.Window;
@@ -192,8 +289,13 @@ namespace FluentTerminal.App.ViewModels.Settings
ConfirmClosingWindows = defaults.ConfirmClosingWindows;
ConfirmClosingTabs = defaults.ConfirmClosingTabs;
UnderlineSelectedTab = defaults.UnderlineSelectedTab;
InactiveTabColorMode = defaults.InactiveTabColorMode;
NewTerminalLocation = defaults.NewTerminalLocation;
AlwaysShowTabs = defaults.AlwaysShowTabs;
AlwaysUseWinPty = defaults.AlwaysUseWinPty;
ShowNewOutputIndicator = defaults.ShowNewOutputIndicator;
EnableTrayIcon = defaults.EnableTrayIcon;
ShowCustomTitleInTitlebar = defaults.ShowCustomTitleInTitlebar;
}
}
@@ -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;
@@ -96,6 +96,20 @@ namespace FluentTerminal.App.ViewModels.Settings
}
}
public int Padding
{
get => _terminalOptions.Padding;
set
{
if (_terminalOptions.Padding != value)
{
_terminalOptions.Padding = value;
_settingsService.SaveTerminalOptions(_terminalOptions);
RaisePropertyChanged();
}
}
}
public string ScrollBackLimit
{
get => _terminalOptions.ScrollBackLimit.ToString();
@@ -122,7 +136,21 @@ namespace FluentTerminal.App.ViewModels.Settings
}
}
public IEnumerable<string> Fonts { get; }
public bool BoldText
{
get => _terminalOptions.BoldText;
set
{
if (_terminalOptions.BoldText != value)
{
_terminalOptions.BoldText = value;
_settingsService.SaveTerminalOptions(_terminalOptions);
RaisePropertyChanged();
}
}
}
public IEnumerable<FontInfo> Fonts { get; }
public int FontSize
{
@@ -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();
}
@@ -205,7 +234,7 @@ namespace FluentTerminal.App.ViewModels.Settings
RestoreDefaultsCommand = new RelayCommand(async () => await RestoreDefaults().ConfigureAwait(false));
Fonts = systemFontService.GetSystemFontFamilies().OrderBy(s => s);
Fonts = systemFontService.GetSystemFontFamilies().OrderBy(s => s.Name);
Sizes = Enumerable.Range(1, 72);
_terminalOptions = _settingsService.GetTerminalOptions();
@@ -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,70 +1,138 @@
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;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
namespace FluentTerminal.App.ViewModels
{
public class ShellProfileViewModel : ViewModelBase
{
private readonly ShellProfile _shellProfile;
private string _name;
private string _arguments;
private string _location;
private string _workingDirectory;
private string _fallbackName;
private string _fallbackArguments;
private string _fallbackLocation;
private string _fallbackWorkingDirectory;
private bool _isDefault;
private bool _inEditMode;
private readonly ISettingsService _settingsService;
private readonly IDialogService _dialogService;
private readonly IFileSystemService _fileSystemService;
private readonly ISettingsService _settingsService;
private string _arguments;
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;
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;
Location = shellProfile.Location;
WorkingDirectory = shellProfile.WorkingDirectory;
SelectedTabTheme = TabThemes.FirstOrDefault(t => t.Id == shellProfile.TabThemeId);
PreInstalled = shellProfile.PreInstalled;
LineEndingStyle = shellProfile.LineEndingTranslation;
SetDefaultCommand = new RelayCommand(SetDefault);
DeleteCommand = new RelayCommand(async () => await Delete().ConfigureAwait(false), CanDelete);
EditCommand = new RelayCommand(Edit, CanEdit);
CancelEditCommand = new RelayCommand(async () => await CancelEdit().ConfigureAwait(false));
SaveChangesCommand = new RelayCommand(SaveChanges);
BrowseForCustomShellCommand = new RelayCommand(async () => await BrowseForCustomShell().ConfigureAwait(false));
BrowseForWorkingDirectoryCommand = new RelayCommand(async () => await BrowseForWorkingDirectory().ConfigureAwait(false));
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;
public event EventHandler SetAsDefault;
public RelayCommand DeleteCommand { get; }
public IAsyncCommand BrowseForCustomShellCommand { get; }
public IAsyncCommand BrowseForWorkingDirectoryCommand { get; }
public IAsyncCommand CancelEditCommand { get; }
public IAsyncCommand DeleteCommand { get; }
public RelayCommand EditCommand { get; }
public RelayCommand SaveChangesCommand { get; }
public RelayCommand CancelEditCommand { get; }
public RelayCommand SetDefaultCommand { get; }
public RelayCommand BrowseForCustomShellCommand { get; }
public RelayCommand BrowseForWorkingDirectoryCommand { get; }
public IAsyncCommand RestoreDefaultsCommand { get; }
public IAsyncCommand AddKeyboardShortcutCommand { get; }
public ObservableCollection<TabTheme> TabThemes { get; }
public KeyBindingsViewModel KeyBindings { get; }
public Guid Id { get; }
public string Name
{
get => _name;
set => Set(ref _name, value);
}
public ObservableCollection<TerminalTheme> TerminalThemes { get; }
public ShellProfile Model { get; private set; }
public bool PreInstalled { get; private set; }
public string Arguments
{
@@ -72,16 +140,12 @@ namespace FluentTerminal.App.ViewModels
set => Set(ref _arguments, value);
}
public string Location
{
get => _location;
set => Set(ref _location, value);
}
public Guid Id { get; private set; }
public string WorkingDirectory
public bool InEditMode
{
get => _workingDirectory;
set => Set(ref _workingDirectory, value);
get => _inEditMode;
set => Set(ref _inEditMode, value);
}
public bool IsDefault
@@ -90,71 +154,115 @@ namespace FluentTerminal.App.ViewModels
set => Set(ref _isDefault, value);
}
public bool InEditMode
public string Location
{
get => _inEditMode;
set => Set(ref _inEditMode, value);
get => _location;
set => Set(ref _location, value);
}
public string Name
{
get => _name;
set => Set(ref _name, value);
}
public TabTheme SelectedTabTheme
{
get => _selectedTabTheme;
set
{
if (value != null)
{
Set(ref _selectedTabTheme, value);
}
}
}
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 => Set(ref _lineEndingStyle, value);
}
public bool DoNotModifyIsSelected
{
get => LineEndingStyle == LineEndingStyle.DoNotModify;
set { if (value) LineEndingStyle = LineEndingStyle.DoNotModify; }
}
public bool ToCRLFIsSelected
{
get => LineEndingStyle == LineEndingStyle.ToCRLF;
set { if (value) LineEndingStyle = LineEndingStyle.ToCRLF; }
}
public bool ToLFIsSelected
{
get => LineEndingStyle == LineEndingStyle.ToLF;
set { if (value) LineEndingStyle = LineEndingStyle.ToLF; }
}
public bool ToCRIsSelected
{
get => LineEndingStyle == LineEndingStyle.ToCR;
set { if (value) LineEndingStyle = LineEndingStyle.ToCR; }
}
public void SaveChanges()
{
_shellProfile.Arguments = Arguments;
_shellProfile.Location = Location;
_shellProfile.Name = Name;
_shellProfile.WorkingDirectory = WorkingDirectory;
_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 CancelEdit()
private async Task RestoreDefaults()
{
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to discard all changes?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
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)
{
Arguments = _fallbackArguments;
Location = _fallbackLocation;
Name = _fallbackName;
WorkingDirectory = _fallbackWorkingDirectory;
InEditMode = false;
Model = _defaultValueProvider.GetPreinstalledShellProfiles().FirstOrDefault(x => x.Id == Model.Id);
InitializeViewModelProperties(Model);
_settingsService.SaveShellProfile(Model);
}
}
private void Edit()
public Task AddKeyboardShortcut()
{
_fallbackArguments = Arguments;
_fallbackLocation = Location;
_fallbackName = Name;
_fallbackWorkingDirectory = WorkingDirectory;
InEditMode = true;
}
private bool CanDelete()
{
return !_shellProfile.PreInstalled;
}
private bool CanEdit()
{
return !_shellProfile.PreInstalled;
}
private async Task Delete()
{
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to delete this theme?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
if (result == DialogButton.OK)
{
Deleted?.Invoke(this, EventArgs.Empty);
}
}
private void SetDefault()
{
SetAsDefault?.Invoke(this, EventArgs.Empty);
return KeyBindings.ShowAddKeyBindingDialog();
}
private async Task BrowseForCustomShell()
@@ -174,5 +282,95 @@ namespace FluentTerminal.App.ViewModels
WorkingDirectory = directory;
}
}
private async Task CancelEdit()
{
if (_isNew)
{
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()
};
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 !Model.PreInstalled;
}
private async Task Delete()
{
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to delete this theme?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
if (result == DialogButton.OK)
{
Deleted?.Invoke(this, EventArgs.Empty);
}
}
private void Edit()
{
// todo write copy ctor
_fallbackProfile = new ShellProfile
{
Arguments = Model.Arguments,
LineEndingTranslation = Model.LineEndingTranslation,
Location = Model.Location,
Name = Model.Name,
WorkingDirectory = Model.WorkingDirectory,
TabThemeId = Model.TabThemeId,
TerminalThemeId = Model.TerminalThemeId
};
KeyBindings.Editable = true;
InEditMode = true;
}
private void SetDefault()
{
SetAsDefault?.Invoke(this, EventArgs.Empty);
}
}
}
+230 -174
View File
@@ -1,90 +1,114 @@
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.Text;
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 _hasNewOutput;
private string _resizeOverlayContent;
private string _searchText;
private bool _showResizeOverlay;
private bool _showSearchPanel;
private int _terminalId;
private ITerminalView _terminalView;
private string _title;
private IWebSocketConnection _webSocket;
private TabTheme _tabTheme;
private TerminalTheme _terminalTheme;
private string _tabTitle;
private string _shellTitle;
private bool _hasCustomTitle;
public TerminalViewModel(ISettingsService settingsService, ITrayProcessCommunicationService trayProcessCommunicationService, IDialogService dialogService,
IKeyboardCommandService keyboardCommandService, ApplicationSettings applicationSettings, string startupDirectory, ShellProfile shellProfile,
IKeyboardCommandService keyboardCommandService, ApplicationSettings applicationSettings, 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;
ApplicationView = applicationView;
ClipboardService = clipboardService;
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);
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 event EventHandler<string> ShellTitleChanged;
public event EventHandler<string> CustomTitleChanged;
public ApplicationSettings ApplicationSettings { get; private set; }
public event EventHandler Closed;
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);
}
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;
@@ -94,14 +118,22 @@ namespace FluentTerminal.App.ViewModels
{
if (IsSelected)
{
_applicationView.Title = Title;
HasNewOutput = false;
}
RaisePropertyChanged(nameof(IsUnderlined));
RaisePropertyChanged(nameof(BackgroundTabTheme));
}
}
}
public bool IsUnderlined => IsSelected && ApplicationSettings.UnderlineSelectedTab;
public bool IsUnderlined => (IsSelected && ApplicationSettings.UnderlineSelectedTab) ||
(!IsSelected && ApplicationSettings.InactiveTabColorMode == InactiveTabColorMode.Underlined && TabTheme.Color != null);
public bool HasNewOutput
{
get => _hasNewOutput;
set => Set(ref _hasNewOutput, value);
}
public string ResizeOverlayContent
{
@@ -115,6 +147,12 @@ namespace FluentTerminal.App.ViewModels
set => Set(ref _searchText, value);
}
public RelayCommand<string> SelectTabThemeCommand { get; }
public ISettingsService SettingsService { get; }
public ShellProfile ShellProfile { get; }
public bool ShowResizeOverlay
{
get => _showResizeOverlay;
@@ -138,165 +176,134 @@ namespace FluentTerminal.App.ViewModels
set => Set(ref _showSearchPanel, value);
}
public string Title
public TabTheme TabTheme
{
get => _title;
get => _tabTheme;
set
{
value = string.IsNullOrWhiteSpace(value) ? DefaultTitle : value;
Set(ref _tabTheme, value);
RaisePropertyChanged(nameof(IsUnderlined));
RaisePropertyChanged(nameof(BackgroundTabTheme));
}
}
if (Set(ref _title, value) && IsSelected)
public ObservableCollection<TabTheme> TabThemes { get; }
public Terminal Terminal { get; private set; }
public TerminalTheme TerminalTheme
{
get => _terminalTheme;
set => Set(ref _terminalTheme, value);
}
public string TabTitle
{
get => _tabTitle;
set
{
if (Set(ref _tabTitle, value))
{
_applicationView.Title = Title;
CustomTitleChanged?.Invoke(this, value);
}
}
}
public async Task CloseView()
public string ShellTitle
{
_terminalView.Close();
await _trayProcessCommunicationService.CloseTerminal(_terminalId).ConfigureAwait(true);
get => _shellTitle;
set
{
if (Set(ref _shellTitle, value) && !_hasCustomTitle)
{
TabTitle = value;
}
}
}
public Task FocusTerminal()
public ITrayProcessCommunicationService TrayProcessCommunicationService { get; }
public Task Close()
{
return _terminalView?.FocusTerminal();
}
public async Task OnViewIsReady(ITerminalView terminalView)
{
_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);
});
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);
SettingsService.CurrentThemeChanged -= OnCurrentThemeChanged;
SettingsService.TerminalOptionsChanged -= OnTerminalOptionsChanged;
SettingsService.ApplicationSettingsChanged -= OnApplicationSettingsChanged;
SettingsService.KeyBindingsChanged -= OnKeyBindingsChanged;
return Terminal.Close();
}
public void CopyText(string text)
{
_clipboardService.SetText(text);
ClipboardService.SetText(text);
}
public async Task EditTitle()
{
var result = await DialogService.ShowInputDialogAsync("Edit Title");
if (result != null)
{
if (string.IsNullOrWhiteSpace(result))
{
_hasCustomTitle = false;
TabTitle = ShellTitle;
}
else
{
_hasCustomTitle = true;
TabTitle = result;
}
}
}
public void FocusTerminal()
{
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));
RaisePropertyChanged(nameof(BackgroundTabTheme));
});
}
private async void OnCurrentThemeChanged(object sender, Guid e)
{
await _applicationView.RunOnDispatcherThread(async () =>
await ApplicationView.RunOnDispatcherThread(() =>
{
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)
{
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);
}
await ApplicationView.RunOnDispatcherThread(() => KeyBindingsChanged?.Invoke(this, EventArgs.Empty));
}
private void OnResizeOverlayTimerFinished(object sender, object e)
@@ -305,43 +312,93 @@ namespace FluentTerminal.App.ViewModels
ShowResizeOverlay = false;
}
private void OnTerminalExited(object sender, int 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);
});
await ApplicationView.RunOnDispatcherThread(() => OptionsChanged?.Invoke(this, e));
}
private async void OnTerminalSizeChanged(object sender, TerminalSize e)
private void SelectTabTheme(string name)
{
if (!Initialized)
TabTheme = TabThemes.FirstOrDefault(t => t.Name == name);
}
private void Terminal_Closed(object sender, EventArgs e)
{
ApplicationView.RunOnDispatcherThread(() => Closed?.Invoke(this, EventArgs.Empty));
}
private async void Terminal_KeyboardCommandReceived(object sender, string e)
{
switch (e)
{
return;
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(Encoding.UTF8.GetBytes(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(Encoding.UTF8.GetBytes(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, byte[] e)
{
if (!IsSelected && ApplicationSettings.ShowNewOutputIndicator)
{
ApplicationView.RunOnDispatcherThread(() => HasNewOutput = true);
}
}
private void Terminal_SizeChanged(object sender, TerminalSize e)
{
ResizeOverlayContent = $"{e.Columns} x {e.Rows}";
ShowResizeOverlay = true;
await _trayProcessCommunicationService.ResizeTerminal(_terminalId, e).ConfigureAwait(true);
}
private void OnTerminalTitleChanged(object sender, string e)
private void Terminal_TitleChanged(object sender, string e)
{
Title = e;
ShellTitle = e;
ShellTitleChanged?.Invoke(this, 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)
{
@@ -349,8 +406,7 @@ namespace FluentTerminal.App.ViewModels
}
}
await _trayProcessCommunicationService.CloseTerminal(_terminalId).ConfigureAwait(true);
Closed?.Invoke(this, EventArgs.Empty);
await Close().ConfigureAwait(true);
}
}
}
+132 -86
View File
@@ -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,23 @@
using FluentTerminal.App.ViewModels.Infrastructure;
using System;
using System.Threading.Tasks;
namespace FluentTerminal.App.ViewModels.Utilities
{
public static class TaskUtilities
{
#pragma warning disable RECS0165 // Asynchronous methods should return a Task instead of void
public static async void FireAndForgetSafeAsync(this Task task, IErrorHandler handler = null)
#pragma warning restore RECS0165 // Asynchronous methods should return a Task instead of void
{
try
{
await task;
}
catch (Exception ex)
{
handler?.HandleError(ex);
}
}
}
}
@@ -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;
@@ -13,20 +14,37 @@ namespace FluentTerminal.App.Adapters
{
private readonly ApplicationView _applicationView;
private readonly CoreDispatcher _dispatcher;
private bool _closed;
public event CloseRequestedHandler CloseRequested;
public event EventHandler Closed;
public ApplicationViewAdapter()
{
_applicationView = ApplicationView.GetForCurrentView();
_applicationView.Consolidated += _applicationView_Consolidated;
_dispatcher = CoreApplication.GetCurrentView().Dispatcher;
SystemNavigationManagerPreview.GetForCurrentView().CloseRequested += OnCloseRequest;
Logger.Instance.Debug("Created ApplicationViewAdapter for ApplicationView with Id: {Id}", _applicationView.Id);
}
private void _applicationView_Consolidated(ApplicationView sender, ApplicationViewConsolidatedEventArgs args)
{
Closed?.Invoke(this, EventArgs.Empty);
}
public int Id => _applicationView.Id;
public string Title
{
get => _applicationView.Title;
set => _applicationView.Title = value;
set => _applicationView.Title = value ?? string.Empty;
}
public bool IsApiContractPresent(string api, ushort version)
{
return ApiInformation.IsApiContractPresent(api, version);
}
public Task RunOnDispatcherThread(Action action)
@@ -34,9 +52,16 @@ namespace FluentTerminal.App.Adapters
return _dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => action()).AsTask();
}
public Task<bool> TryClose()
public async Task<bool> TryClose()
{
return _applicationView.TryConsolidateAsync().AsTask();
if (_closed)
{
Logger.Instance.Debug("ApplicationViewAdapter.TryClose was called, but was already closed. ApplicationView.Id: {Id}", _applicationView.Id);
return true;
}
Logger.Instance.Debug("TryClose ApplicationView with Id: {Id}", _applicationView.Id);
_closed = await _applicationView.TryConsolidateAsync().AsTask();
return _closed;
}
public bool ToggleFullScreen()
@@ -57,10 +82,19 @@ 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;
if (!e.Handled)
{
SystemNavigationManagerPreview.GetForCurrentView().CloseRequested -= OnCloseRequest;
}
deferral.Complete();
}
}
+9
View File
@@ -8,6 +8,10 @@
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Styles\Custom.xaml" />
</ResourceDictionary.MergedDictionaries>
<LinearGradientBrush x:Key="MissingColorSelectionBrush" StartPoint="0,0" EndPoint="1,1">
<GradientStop Offset="0" Color="Black" />
<GradientStop Offset="1.0" Color="White" />
</LinearGradientBrush>
<converters:IntToVisibilityConverter
x:Key="OneToVisibleConverter"
Else="Collapsed"
@@ -21,6 +25,7 @@
<converters:NegateConverter x:Key="NegateConverter" />
<converters:FalseToVisibleConverter x:Key="FalseToVisibleConverter" />
<converters:TrueToVisibleConverter x:Key="TrueToVisibleConverter" />
<converters:TabColorFallbackConverter x:Key="TabColorFallbackConverter" />
<converters:IntToVisibilityConverter
x:Key="ZeroToCollapsedConverter"
Else="Visible"
@@ -30,6 +35,10 @@
<converters:BackgroundToApplicationThemeConverter x:Key="BackgroundToApplicationThemeConverter" />
<converters:StringToColorConverter x:Key="StringToColorConverter" />
<converters:EnumValueToVisibiltyConverter x:Key="EnumValueToVisibiltyConverter" />
<converters:TabThemeSelectedConverter x:Key="TabThemeSelectedConverter" />
<converters:ColorResourceKeyFallbackConverter x:Key="ColorResourceKeyFallbackConverter" />
<converters:EnumToDescriptionConverter x:Key="EnumToDescriptionConverter" />
<converters:TrueToBoldConverter x:Key="TrueToBoldConverter" />
</ResourceDictionary>
</Application.Resources>
</Application>
+197 -48
View File
@@ -1,17 +1,24 @@
using Autofac;
using CommandLine;
using FluentTerminal.App.Adapters;
using FluentTerminal.App.CommandLineArguments;
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;
using FluentTerminal.Models;
using FluentTerminal.Models.Enums;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Windows.ApplicationModel;
@@ -41,6 +48,7 @@ namespace FluentTerminal.App
private int? _settingsWindowId;
private IAppServiceConnection _appServiceConnection;
private BackgroundTaskDeferral _appServiceDeferral;
private Parser _commandLineParser;
public App()
{
@@ -48,6 +56,8 @@ namespace FluentTerminal.App
InitializeComponent();
UnhandledException += OnUnhandledException;
var applicationDataContainers = new ApplicationDataContainers
{
LocalSettings = new ApplicationDataContainerAdapter(ApplicationData.Current.LocalSettings),
@@ -63,6 +73,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 +85,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();
@@ -87,9 +100,55 @@ namespace FluentTerminal.App
_trayProcessCommunicationService = _container.Resolve<ITrayProcessCommunicationService>();
_applicationSettings = _settingsService.GetApplicationSettings();
JsonConvert.DefaultSettings = () =>
{
var settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
};
settings.Converters.Add(new StringEnumConverter(typeof(CamelCaseNamingStrategy)));
return settings;
};
_commandLineParser = new Parser(settings =>
{
settings.CaseSensitive = false;
settings.CaseInsensitiveEnumValues = true;
});
}
protected override async void OnActivated(IActivatedEventArgs args)
private void OnUnhandledException(object sender, Windows.UI.Xaml.UnhandledExceptionEventArgs e)
{
Logger.Instance.Error(e.Exception, "Unhandled Exception");
}
private static IEnumerable<string> SplitArguments(string arguments)
{
var chars = arguments.ToCharArray();
var inQuote = false;
for (var i = 0; i < chars.Length; i++)
{
if (chars[i] == '"')
{
inQuote = !inQuote;
}
if (!inQuote && chars[i] == ' ')
{
chars[i] = '\n';
}
}
foreach (var value in new string(chars).Split('\n'))
{
yield return value.Trim('"');
}
}
protected override void OnActivated(IActivatedEventArgs args)
{
if (args is CommandLineActivatedEventArgs commandLineActivated)
{
@@ -98,42 +157,67 @@ namespace FluentTerminal.App
return;
}
var arguments = commandLineActivated.Operation.Arguments.Split(' ', 2);
var command = arguments[0];
var parameter = arguments.Length > 1 ? arguments[1] : string.Empty;
if (_alreadyLaunched)
_commandLineParser.ParseArguments(SplitArguments(commandLineActivated.Operation.Arguments), typeof(NewVerb), typeof(RunVerb), typeof(SettingsVerb)).WithParsed(async verb =>
{
if (command == "settings")
if (verb is SettingsVerb)
{
await ShowSettings().ConfigureAwait(true);
}
else if (command == "new")
else if (verb is NewVerb newVerb)
{
if (_applicationSettings.NewTerminalLocation == NewTerminalLocation.Tab && _mainViewModels.Count > 0)
var profile = default(ShellProfile);
if (!string.IsNullOrWhiteSpace(newVerb.Profile))
{
await _mainViewModels.Last().AddTerminal(parameter, false).ConfigureAwait(true);
profile = _settingsService.GetShellProfiles().FirstOrDefault(x => x.Name.Equals(newVerb.Profile, StringComparison.CurrentCultureIgnoreCase));
}
else
if (profile == null)
{
await CreateNewTerminalWindow(parameter).ConfigureAwait(true);
profile = _settingsService.GetDefaultShellProfile();
}
if (!string.IsNullOrWhiteSpace(newVerb.Directory))
{
profile.WorkingDirectory = newVerb.Directory;
}
var location = newVerb.Target == Target.Default ? _applicationSettings.NewTerminalLocation
: newVerb.Target == Target.Tab ? NewTerminalLocation.Tab
: NewTerminalLocation.Window;
await CreateTerminal(profile, location).ConfigureAwait(true);
}
}
else
{
if (command == "settings")
else if (verb is RunVerb runVerb)
{
var viewModel = _container.Resolve<SettingsViewModel>();
await CreateMainView(typeof(SettingsPage), viewModel, true).ConfigureAwait(true);
var profile = new ShellProfile
{
Id = Guid.Empty,
Location = null,
Arguments = runVerb.Command,
WorkingDirectory = runVerb.Directory
};
if (!string.IsNullOrWhiteSpace(runVerb.Theme))
{
var theme = _settingsService.GetThemes().FirstOrDefault(x => x.Name.Equals(runVerb.Theme, StringComparison.CurrentCultureIgnoreCase));
if (theme != null)
{
profile.TerminalThemeId = theme.Id;
}
}
if (string.IsNullOrWhiteSpace(profile.WorkingDirectory))
{
profile.WorkingDirectory = commandLineActivated.Operation.CurrentDirectoryPath;
}
var location = runVerb.Target == Target.Default ? _applicationSettings.NewTerminalLocation
: runVerb.Target == Target.Tab ? NewTerminalLocation.Tab
: NewTerminalLocation.Window;
await CreateTerminal(profile, location).ConfigureAwait(true);
}
else if (command == "new")
{
var viewModel = _container.Resolve<MainViewModel>();
await viewModel.AddTerminal(parameter, false).ConfigureAwait(true);
await CreateMainView(typeof(MainPage), viewModel, true).ConfigureAwait(true);
}
}
});
}
}
@@ -141,14 +225,29 @@ namespace FluentTerminal.App
{
if (!_alreadyLaunched)
{
var logDirectory = await ApplicationData.Current.LocalCacheFolder.CreateFolderAsync("Logs", CreationCollisionOption.OpenIfExists);
var logFile = Path.Combine(logDirectory.Path, "fluentterminal.app.log");
var configFile = await logDirectory.CreateFileAsync("config.json", CreationCollisionOption.OpenIfExists);
var configContent = await FileIO.ReadTextAsync(configFile);
if (string.IsNullOrWhiteSpace(configContent))
{
configContent = JsonConvert.SerializeObject(new Logger.Configuration());
await FileIO.WriteTextAsync(configFile, configContent);
}
var config = JsonConvert.DeserializeObject<Logger.Configuration>(configContent) ?? new Logger.Configuration();
Logger.Instance.Initialize(logFile, config);
var viewModel = _container.Resolve<MainViewModel>();
await viewModel.AddTerminal(null, false).ConfigureAwait(true);
viewModel.AddTerminal();
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).ConfigureAwait(true);
}
}
@@ -199,6 +298,7 @@ namespace FluentTerminal.App
mainViewModel.Closed += OnMainViewModelClosed;
mainViewModel.NewWindowRequested += OnNewWindowRequested;
mainViewModel.ShowSettingsRequested += OnShowSettingsRequested;
mainViewModel.ShowAboutRequested += OnShowAboutRequested;
_mainViewModels.Add(mainViewModel);
}
@@ -208,13 +308,19 @@ namespace FluentTerminal.App
Window.Current.Activate();
}
private async Task CreateNewTerminalWindow(string startupDirectory)
private async Task<MainViewModel> CreateNewTerminalWindow()
{
var id = await CreateSecondaryView<MainViewModel>(typeof(MainPage), true, startupDirectory).ConfigureAwait(true);
await ApplicationViewSwitcher.TryShowAsStandaloneAsync(id);
var viewModel = await CreateSecondaryView<MainViewModel>(typeof(MainPage), true).ConfigureAwait(true);
viewModel.Closed += OnMainViewModelClosed;
viewModel.NewWindowRequested += OnNewWindowRequested;
viewModel.ShowSettingsRequested += OnShowSettingsRequested;
viewModel.ShowAboutRequested += OnShowAboutRequested;
_mainViewModels.Add(viewModel);
return viewModel;
}
private async Task<int> CreateSecondaryView<TViewModel>(Type pageType, bool ExtendViewIntoTitleBar, object parameter)
private async Task<TViewModel> CreateSecondaryView<TViewModel>(Type pageType, bool ExtendViewIntoTitleBar)
{
int windowId = 0;
TViewModel viewModel = default;
@@ -231,22 +337,16 @@ namespace FluentTerminal.App
windowId = ApplicationView.GetForCurrentView().Id;
});
if (viewModel is MainViewModel mainViewModel && parameter is string directory)
{
mainViewModel.Closed += OnMainViewModelClosed;
mainViewModel.NewWindowRequested += OnNewWindowRequested;
mainViewModel.ShowSettingsRequested += OnShowSettingsRequested;
_mainViewModels.Add(mainViewModel);
await mainViewModel.AddTerminal(directory, false).ConfigureAwait(true);
}
if (viewModel is SettingsViewModel settingsViewModel)
{
_settingsViewModel = settingsViewModel;
_settingsViewModel.Closed += OnSettingsClosed;
_settingsWindowId = windowId;
}
return windowId;
await ApplicationViewSwitcher.TryShowAsStandaloneAsync(windowId);
return viewModel;
}
private void OnApplicationSettingsChanged(object sender, ApplicationSettings e)
@@ -258,17 +358,29 @@ namespace FluentTerminal.App
{
if (sender is MainViewModel viewModel)
{
Logger.Instance.Debug("MainViewModel with ApplicationView Id: {@id} closed.", viewModel.ApplicationView.Id);
viewModel.Closed -= OnMainViewModelClosed;
viewModel.NewWindowRequested -= OnNewWindowRequested;
viewModel.ShowSettingsRequested -= OnShowSettingsRequested;
viewModel.ShowAboutRequested -= OnShowAboutRequested;
_mainViewModels.Remove(viewModel);
}
}
private async void OnNewWindowRequested(object sender, EventArgs e)
private async void OnNewWindowRequested(object sender, NewWindowRequestedEventArgs e)
{
await CreateNewTerminalWindow(string.Empty).ConfigureAwait(true);
var viewModel = await CreateNewTerminalWindow().ConfigureAwait(true);
if (e.ShowProfileSelection)
{
await viewModel.AddConfigurableTerminal().ConfigureAwait(true);
}
else
{
viewModel.AddTerminal();
}
}
private void OnSettingsClosed(object sender, EventArgs e)
@@ -278,25 +390,62 @@ 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 CreateTerminal(ShellProfile profile, NewTerminalLocation location)
{
if (!_alreadyLaunched)
{
var viewModel = _container.Resolve<MainViewModel>();
viewModel.AddTerminal(profile);
await CreateMainView(typeof(MainPage), viewModel, true).ConfigureAwait(true);
}
else if (location == NewTerminalLocation.Tab && _mainViewModels.Count > 0)
{
_mainViewModels.Last().AddTerminal(profile);
}
else
{
var viewModel = await CreateNewTerminalWindow().ConfigureAwait(true);
viewModel.AddTerminal(profile);
}
}
private async Task ShowSettings()
{
if (_settingsViewModel == null)
if (!_alreadyLaunched)
{
_settingsWindowId = await CreateSecondaryView<SettingsViewModel>(typeof(SettingsPage), true, null).ConfigureAwait(true);
var viewModel = _container.Resolve<SettingsViewModel>();
await CreateMainView(typeof(SettingsPage), viewModel, true).ConfigureAwait(true);
}
else if (_settingsViewModel == null)
{
await CreateSecondaryView<SettingsViewModel>(typeof(SettingsPage), true).ConfigureAwait(true);
}
else
{
await ApplicationViewSwitcher.TryShowAsStandaloneAsync(_settingsWindowId.Value);
}
await ApplicationViewSwitcher.TryShowAsStandaloneAsync(_settingsWindowId.Value);
}
private async Task StartSystemTray()
{
var launch = FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync("AppLaunchedParameterGroup").AsTask();
var clearCache = WebView.ClearTemporaryWebDataAsync().AsTask();
await Task.WhenAll(launch, clearCache, _trayReady.Task).ConfigureAwait(true);
await Task.WhenAll(launch, _trayReady.Task).ConfigureAwait(true);
_trayProcessCommunicationService.Initialize(_appServiceConnection);
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

@@ -0,0 +1,17 @@
using CommandLine;
namespace FluentTerminal.App.CommandLineArguments
{
[Verb("new")]
public class NewVerb
{
[Value(0)]
public string Directory { get; set; }
[Option("profile")]
public string Profile { get; set; }
[Option("target")]
public Target Target { get; set; }
}
}
@@ -0,0 +1,20 @@
using CommandLine;
namespace FluentTerminal.App.CommandLineArguments
{
[Verb("run")]
public class RunVerb
{
[Value(0)]
public string Command { get; set; }
[Option("directory")]
public string Directory { get; set; }
[Option("theme")]
public string Theme { get; set; }
[Option("target")]
public Target Target { get; set; }
}
}
@@ -0,0 +1,9 @@
using CommandLine;
namespace FluentTerminal.App.CommandLineArguments
{
[Verb("settings")]
public class SettingsVerb
{
}
}
@@ -0,0 +1,9 @@
namespace FluentTerminal.App.CommandLineArguments
{
public enum Target
{
Default,
Tab,
Window
}
}
@@ -0,0 +1,31 @@
using Microsoft.Toolkit.Uwp.Helpers;
using System;
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Media;
namespace FluentTerminal.App.Converters
{
public class ColorResourceKeyFallbackConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value == null)
{
return Application.Current.Resources[(string)parameter];
}
else if (value is string hex)
{
var color = hex.ToColor();
return new SolidColorBrush(color);
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}
@@ -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();
}
}
}
@@ -13,6 +13,7 @@ namespace FluentTerminal.App.Converters
{
return colorString.FromString();
}
return null;
}
@@ -0,0 +1,82 @@
using FluentTerminal.Models;
using FluentTerminal.Models.Enums;
using Microsoft.Toolkit.Uwp.Helpers;
using System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Media;
namespace FluentTerminal.App.Converters
{
public class TabColorFallbackConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is TabTheme theme && Enum.TryParse<TabThemeKey>((string)parameter, true, out TabThemeKey tabThemeKey))
{
switch (tabThemeKey)
{
case TabThemeKey.Background:
return GetBrush(tabThemeKey, theme.Color, theme.BackgroundOpacity);
case TabThemeKey.BackgroundPointerOver:
return GetBrush(tabThemeKey, theme.Color, theme.BackgroundPointerOverOpacity);
case TabThemeKey.BackgroundPressed:
return GetBrush(tabThemeKey, theme.Color, theme.BackgroundPressedOpacity);
case TabThemeKey.BackgroundSelected:
return GetBrush(tabThemeKey, theme.Color, theme.BackgroundSelectedOpacity);
case TabThemeKey.BackgroundSelectedPointerOver:
return GetBrush(tabThemeKey, theme.Color, theme.BackgroundSelectedPointerOverOpacity);
case TabThemeKey.BackgroundSelectedPressed:
return GetBrush(tabThemeKey, theme.Color, theme.BackgroundSelectedPressedOpacity);
}
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
private Brush GetBrush(TabThemeKey tabThemeKey, string color, double opacity)
{
if (double.IsNaN(opacity))
{
return (Brush)Application.Current.Resources[GetFallbackRessourceKey(tabThemeKey)];
}
else
{
return CreateBrush(color, opacity);
}
}
private Brush CreateBrush(string hex, double opacity)
{
var color = hex.ToColor();
return new SolidColorBrush(color)
{
Opacity = opacity
};
}
private string GetFallbackRessourceKey(TabThemeKey tabThemeKey)
{
switch (tabThemeKey)
{
case TabThemeKey.Background:
return "SystemControlTransparentBrush";
case TabThemeKey.BackgroundPointerOver:
return "SystemControlHighlightListLowBrush";
case TabThemeKey.BackgroundPressed:
return "SystemControlHighlightListMediumBrush";
case TabThemeKey.BackgroundSelected:
return "SystemControlHighlightListAccentLowBrush";
case TabThemeKey.BackgroundSelectedPointerOver:
return "SystemControlHighlightListAccentMediumBrush";
case TabThemeKey.BackgroundSelectedPressed:
return "SystemControlHighlightListAccentHighBrush";
}
return null;
}
}
}
@@ -0,0 +1,23 @@
using FluentTerminal.Models;
using System;
using Windows.UI.Xaml.Data;
namespace FluentTerminal.App.Converters
{
public class TabThemeSelectedConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is TabTheme theme && parameter is string name)
{
return theme.Name == name;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}
@@ -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,23 @@
using System;
using Windows.UI.Text;
using Windows.UI.Xaml.Data;
namespace FluentTerminal.App.Converters
{
public class TrueToBoldConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is bool bold)
{
return bold ? FontWeights.Bold : FontWeights.Normal;
}
return null;
}
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();
}
}
}
}
+53 -7
View File
@@ -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>
@@ -111,16 +111,29 @@
<DependentUpon>App.xaml</DependentUpon>
</Compile>
<Compile Include="Behaviors\MiddleClickBehavior.cs" />
<Compile Include="CommandLineArguments\NewVerb.cs" />
<Compile Include="CommandLineArguments\RunVerb.cs" />
<Compile Include="CommandLineArguments\SettingsVerb.cs" />
<Compile Include="CommandLineArguments\Target.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" />
<Compile Include="Converters\IntToExtendedVirtualKeyConverter.cs" />
<Compile Include="Converters\IntToVisibilityConverter.cs" />
<Compile Include="Converters\NegateConverter.cs" />
<Compile Include="Converters\StringToColorConverter.cs" />
<Compile Include="Converters\TabThemeSelectedConverter.cs" />
<Compile Include="Converters\TerminalViewModelToViewConverter.cs" />
<Compile Include="Converters\ToolTipValueToPixelConverter.cs" />
<Compile Include="Converters\TrueToBoldConverter.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>
@@ -130,6 +143,7 @@
<Compile Include="Services\SystemFontService.cs" />
<Compile Include="Utilities\ColorExtensions.cs" />
<Compile Include="Utilities\ContrastHelper.cs" />
<Compile Include="Utilities\DebouncedAction.cs" />
<Compile Include="Views\KeyBindingsView.xaml.cs">
<DependentUpon>KeyBindingsView.xaml</DependentUpon>
</Compile>
@@ -143,6 +157,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>
@@ -164,17 +181,27 @@
<Compile Include="Views\TabBar.xaml.cs">
<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\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" />
@@ -185,7 +212,11 @@
<Content Include="Win32\FluentTerminal.SystemTray.pdb" />
<Content Include="Win32\GlobalHotKey.dll" />
<Content Include="Win32\Newtonsoft.Json.dll" />
<Content Include="Win32\PListNet.dll" />
<Content Include="Win32\RestSharp.dll" />
<Content Include="Win32\Serilog.dll" />
<Content Include="Win32\Serilog.Sinks.Async.dll" />
<Content Include="Win32\Serilog.Sinks.File.dll" />
<Content Include="Win32\System.Data.Common.dll" />
<Content Include="Win32\System.Diagnostics.StackTrace.dll" />
<Content Include="Win32\System.Diagnostics.Tracing.dll" />
@@ -264,6 +295,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>
@@ -289,6 +324,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>
@@ -325,25 +364,32 @@
<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">
<Version>4.8.1</Version>
<Version>4.9.1</Version>
</PackageReference>
<PackageReference Include="CommandLineParser">
<Version>2.4.3</Version>
</PackageReference>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>6.1.5</Version>
<Version>6.2.8</Version>
</PackageReference>
<PackageReference Include="Microsoft.Toolkit.Uwp.UI">
<Version>3.0.0</Version>
<Version>5.1.0</Version>
</PackageReference>
<PackageReference Include="Microsoft.Toolkit.Uwp.UI.Controls">
<Version>3.0.0</Version>
<Version>5.1.0</Version>
</PackageReference>
<PackageReference Include="Microsoft.Xaml.Behaviors.Uwp.Managed">
<Version>2.0.0</Version>
</PackageReference>
<PackageReference Include="Newtonsoft.Json">
<Version>11.0.2</Version>
<Version>12.0.1</Version>
</PackageReference>
<PackageReference Include="Win2D.uwp">
<Version>1.21.0</Version>
@@ -0,0 +1,360 @@
<?xml version="1.0" encoding="utf-8"?>
<StoreAssociation xmlns="http://schemas.microsoft.com/appx/2010/storeassociation">
<Publisher>CN=BDC72197-B634-438A-B863-16DCB7D90C85</Publisher>
<NamespacePrefix>53621FSApps</NamespacePrefix>
<PublisherDisplayName>FS Apps</PublisherDisplayName>
<GeneratePackageHash>http://www.w3.org/2001/04/xmlenc#sha256</GeneratePackageHash>
<SupportedLocales>
<Language Code="af" InMinimumRequirementSet="false" />
<Language Code="af-za" InMinimumRequirementSet="false" />
<Language Code="am" InMinimumRequirementSet="false" />
<Language Code="am-et" InMinimumRequirementSet="false" />
<Language Code="ar" InMinimumRequirementSet="true" />
<Language Code="ar-ae" InMinimumRequirementSet="true" />
<Language Code="ar-bh" InMinimumRequirementSet="true" />
<Language Code="ar-dz" InMinimumRequirementSet="true" />
<Language Code="ar-eg" InMinimumRequirementSet="true" />
<Language Code="ar-iq" InMinimumRequirementSet="true" />
<Language Code="ar-jo" InMinimumRequirementSet="true" />
<Language Code="ar-kw" InMinimumRequirementSet="true" />
<Language Code="ar-lb" InMinimumRequirementSet="true" />
<Language Code="ar-ly" InMinimumRequirementSet="true" />
<Language Code="ar-ma" InMinimumRequirementSet="true" />
<Language Code="ar-om" InMinimumRequirementSet="true" />
<Language Code="ar-qa" InMinimumRequirementSet="true" />
<Language Code="ar-sa" InMinimumRequirementSet="true" />
<Language Code="ar-sy" InMinimumRequirementSet="true" />
<Language Code="ar-tn" InMinimumRequirementSet="true" />
<Language Code="ar-ye" InMinimumRequirementSet="true" />
<Language Code="as" InMinimumRequirementSet="false" />
<Language Code="as-in" InMinimumRequirementSet="false" />
<Language Code="az" InMinimumRequirementSet="false" />
<Language Code="az-arab" InMinimumRequirementSet="false" />
<Language Code="az-arab-az" InMinimumRequirementSet="false" />
<Language Code="az-cyrl" InMinimumRequirementSet="false" />
<Language Code="az-cyrl-az" InMinimumRequirementSet="false" />
<Language Code="az-latn" InMinimumRequirementSet="false" />
<Language Code="az-latn-az" InMinimumRequirementSet="false" />
<Language Code="be" InMinimumRequirementSet="false" />
<Language Code="be-by" InMinimumRequirementSet="false" />
<Language Code="bg" InMinimumRequirementSet="true" />
<Language Code="bg-bg" InMinimumRequirementSet="true" />
<Language Code="bn" InMinimumRequirementSet="false" />
<Language Code="bn-bd" InMinimumRequirementSet="false" />
<Language Code="bn-in" InMinimumRequirementSet="false" />
<Language Code="bs" InMinimumRequirementSet="false" />
<Language Code="bs-cyrl" InMinimumRequirementSet="false" />
<Language Code="bs-cyrl-ba" InMinimumRequirementSet="false" />
<Language Code="bs-latn" InMinimumRequirementSet="false" />
<Language Code="bs-latn-ba" InMinimumRequirementSet="false" />
<Language Code="ca" InMinimumRequirementSet="false" />
<Language Code="ca-es" InMinimumRequirementSet="false" />
<Language Code="ca-es-valencia" InMinimumRequirementSet="false" />
<Language Code="chr-cher" InMinimumRequirementSet="false" />
<Language Code="chr-cher-us" InMinimumRequirementSet="false" />
<Language Code="chr-latn" InMinimumRequirementSet="false" />
<Language Code="cs" InMinimumRequirementSet="true" />
<Language Code="cs-cz" InMinimumRequirementSet="true" />
<Language Code="cy" InMinimumRequirementSet="false" />
<Language Code="cy-gb" InMinimumRequirementSet="false" />
<Language Code="da" InMinimumRequirementSet="true" />
<Language Code="da-dk" InMinimumRequirementSet="true" />
<Language Code="de" InMinimumRequirementSet="true" />
<Language Code="de-at" InMinimumRequirementSet="true" />
<Language Code="de-ch" InMinimumRequirementSet="true" />
<Language Code="de-de" InMinimumRequirementSet="true" />
<Language Code="de-li" InMinimumRequirementSet="true" />
<Language Code="de-lu" InMinimumRequirementSet="true" />
<Language Code="el" InMinimumRequirementSet="true" />
<Language Code="el-gr" InMinimumRequirementSet="true" />
<Language Code="en" InMinimumRequirementSet="true" />
<Language Code="en-au" InMinimumRequirementSet="true" />
<Language Code="en-bz" InMinimumRequirementSet="true" />
<Language Code="en-ca" InMinimumRequirementSet="true" />
<Language Code="en-gb" InMinimumRequirementSet="true" />
<Language Code="en-hk" InMinimumRequirementSet="true" />
<Language Code="en-id" InMinimumRequirementSet="true" />
<Language Code="en-ie" InMinimumRequirementSet="true" />
<Language Code="en-in" InMinimumRequirementSet="true" />
<Language Code="en-jm" InMinimumRequirementSet="true" />
<Language Code="en-kz" InMinimumRequirementSet="true" />
<Language Code="en-mt" InMinimumRequirementSet="true" />
<Language Code="en-my" InMinimumRequirementSet="true" />
<Language Code="en-nz" InMinimumRequirementSet="true" />
<Language Code="en-ph" InMinimumRequirementSet="true" />
<Language Code="en-pk" InMinimumRequirementSet="true" />
<Language Code="en-sg" InMinimumRequirementSet="true" />
<Language Code="en-tt" InMinimumRequirementSet="true" />
<Language Code="en-us" InMinimumRequirementSet="true" />
<Language Code="en-vn" InMinimumRequirementSet="true" />
<Language Code="en-za" InMinimumRequirementSet="true" />
<Language Code="es" InMinimumRequirementSet="true" />
<Language Code="es-ar" InMinimumRequirementSet="true" />
<Language Code="es-bo" InMinimumRequirementSet="true" />
<Language Code="es-cl" InMinimumRequirementSet="true" />
<Language Code="es-co" InMinimumRequirementSet="true" />
<Language Code="es-cr" InMinimumRequirementSet="true" />
<Language Code="es-do" InMinimumRequirementSet="true" />
<Language Code="es-ec" InMinimumRequirementSet="true" />
<Language Code="es-es" InMinimumRequirementSet="true" />
<Language Code="es-gt" InMinimumRequirementSet="true" />
<Language Code="es-hn" InMinimumRequirementSet="true" />
<Language Code="es-mx" InMinimumRequirementSet="true" />
<Language Code="es-ni" InMinimumRequirementSet="true" />
<Language Code="es-pa" InMinimumRequirementSet="true" />
<Language Code="es-pe" InMinimumRequirementSet="true" />
<Language Code="es-pr" InMinimumRequirementSet="true" />
<Language Code="es-py" InMinimumRequirementSet="true" />
<Language Code="es-sv" InMinimumRequirementSet="true" />
<Language Code="es-us" InMinimumRequirementSet="true" />
<Language Code="es-uy" InMinimumRequirementSet="true" />
<Language Code="es-ve" InMinimumRequirementSet="true" />
<Language Code="et" InMinimumRequirementSet="true" />
<Language Code="et-ee" InMinimumRequirementSet="true" />
<Language Code="eu" InMinimumRequirementSet="false" />
<Language Code="eu-es" InMinimumRequirementSet="false" />
<Language Code="fa" InMinimumRequirementSet="false" />
<Language Code="fa-ir" InMinimumRequirementSet="false" />
<Language Code="fi" InMinimumRequirementSet="true" />
<Language Code="fi-fi" InMinimumRequirementSet="true" />
<Language Code="fil" InMinimumRequirementSet="false" />
<Language Code="fil-latn" InMinimumRequirementSet="false" />
<Language Code="fil-ph" InMinimumRequirementSet="false" />
<Language Code="fr" InMinimumRequirementSet="true" />
<Language Code="fr-015" InMinimumRequirementSet="true" />
<Language Code="fr-be" InMinimumRequirementSet="true" />
<Language Code="fr-ca" InMinimumRequirementSet="true" />
<Language Code="fr-cd" InMinimumRequirementSet="true" />
<Language Code="fr-ch" InMinimumRequirementSet="true" />
<Language Code="fr-ci" InMinimumRequirementSet="true" />
<Language Code="fr-cm" InMinimumRequirementSet="true" />
<Language Code="fr-fr" InMinimumRequirementSet="true" />
<Language Code="fr-ht" InMinimumRequirementSet="true" />
<Language Code="fr-lu" InMinimumRequirementSet="true" />
<Language Code="fr-ma" InMinimumRequirementSet="true" />
<Language Code="fr-mc" InMinimumRequirementSet="true" />
<Language Code="fr-ml" InMinimumRequirementSet="true" />
<Language Code="fr-re" InMinimumRequirementSet="true" />
<Language Code="frc" InMinimumRequirementSet="true" />
<Language Code="frc-latn" InMinimumRequirementSet="true" />
<Language Code="frp" InMinimumRequirementSet="true" />
<Language Code="frp-latn" InMinimumRequirementSet="true" />
<Language Code="ga" InMinimumRequirementSet="false" />
<Language Code="ga-ie" InMinimumRequirementSet="false" />
<Language Code="gd" InMinimumRequirementSet="false" />
<Language Code="gd-gb" InMinimumRequirementSet="false" />
<Language Code="gd-latn" InMinimumRequirementSet="false" />
<Language Code="gl" InMinimumRequirementSet="false" />
<Language Code="gl-es" InMinimumRequirementSet="false" />
<Language Code="gu" InMinimumRequirementSet="false" />
<Language Code="gu-in" InMinimumRequirementSet="false" />
<Language Code="ha-latn" InMinimumRequirementSet="false" />
<Language Code="ha-latn-ng" InMinimumRequirementSet="false" />
<Language Code="he" InMinimumRequirementSet="true" />
<Language Code="he-il" InMinimumRequirementSet="true" />
<Language Code="hi" InMinimumRequirementSet="true" />
<Language Code="hi-in" InMinimumRequirementSet="true" />
<Language Code="hr" InMinimumRequirementSet="true" />
<Language Code="hr-ba" InMinimumRequirementSet="true" />
<Language Code="hr-hr" InMinimumRequirementSet="true" />
<Language Code="hu" InMinimumRequirementSet="true" />
<Language Code="hu-hu" InMinimumRequirementSet="true" />
<Language Code="hy" InMinimumRequirementSet="false" />
<Language Code="hy-am" InMinimumRequirementSet="false" />
<Language Code="id" InMinimumRequirementSet="true" />
<Language Code="id-id" InMinimumRequirementSet="true" />
<Language Code="ig" InMinimumRequirementSet="false" />
<Language Code="ig-latn" InMinimumRequirementSet="false" />
<Language Code="ig-ng" InMinimumRequirementSet="false" />
<Language Code="is" InMinimumRequirementSet="false" />
<Language Code="is-is" InMinimumRequirementSet="false" />
<Language Code="it" InMinimumRequirementSet="true" />
<Language Code="it-ch" InMinimumRequirementSet="true" />
<Language Code="it-it" InMinimumRequirementSet="true" />
<Language Code="iu-cans" InMinimumRequirementSet="false" />
<Language Code="iu-latn" InMinimumRequirementSet="false" />
<Language Code="iu-latn-ca" InMinimumRequirementSet="false" />
<Language Code="ja" InMinimumRequirementSet="true" />
<Language Code="ja-jp" InMinimumRequirementSet="true" />
<Language Code="ka" InMinimumRequirementSet="false" />
<Language Code="ka-ge" InMinimumRequirementSet="false" />
<Language Code="kk" InMinimumRequirementSet="false" />
<Language Code="kk-kz" InMinimumRequirementSet="false" />
<Language Code="km" InMinimumRequirementSet="false" />
<Language Code="km-kh" InMinimumRequirementSet="false" />
<Language Code="kn" InMinimumRequirementSet="false" />
<Language Code="kn-in" InMinimumRequirementSet="false" />
<Language Code="ko" InMinimumRequirementSet="true" />
<Language Code="ko-kr" InMinimumRequirementSet="true" />
<Language Code="kok" InMinimumRequirementSet="false" />
<Language Code="kok-in" InMinimumRequirementSet="false" />
<Language Code="ku-arab" InMinimumRequirementSet="false" />
<Language Code="ku-arab-iq" InMinimumRequirementSet="false" />
<Language Code="ky" InMinimumRequirementSet="false" />
<Language Code="ky-cyrl" InMinimumRequirementSet="false" />
<Language Code="ky-kg" InMinimumRequirementSet="false" />
<Language Code="lb" InMinimumRequirementSet="false" />
<Language Code="lb-lu" InMinimumRequirementSet="false" />
<Language Code="lt" InMinimumRequirementSet="true" />
<Language Code="lt-lt" InMinimumRequirementSet="true" />
<Language Code="lv" InMinimumRequirementSet="true" />
<Language Code="lv-lv" InMinimumRequirementSet="true" />
<Language Code="mi" InMinimumRequirementSet="false" />
<Language Code="mi-latn" InMinimumRequirementSet="false" />
<Language Code="mi-nz" InMinimumRequirementSet="false" />
<Language Code="mk" InMinimumRequirementSet="false" />
<Language Code="mk-mk" InMinimumRequirementSet="false" />
<Language Code="ml" InMinimumRequirementSet="false" />
<Language Code="ml-in" InMinimumRequirementSet="false" />
<Language Code="mn" InMinimumRequirementSet="false" />
<Language Code="mn-cyrl" InMinimumRequirementSet="false" />
<Language Code="mn-mn" InMinimumRequirementSet="false" />
<Language Code="mn-mong" InMinimumRequirementSet="false" />
<Language Code="mn-phag" InMinimumRequirementSet="false" />
<Language Code="mr" InMinimumRequirementSet="false" />
<Language Code="mr-in" InMinimumRequirementSet="false" />
<Language Code="ms" InMinimumRequirementSet="false" />
<Language Code="ms-bn" InMinimumRequirementSet="false" />
<Language Code="ms-my" InMinimumRequirementSet="false" />
<Language Code="mt" InMinimumRequirementSet="false" />
<Language Code="mt-mt" InMinimumRequirementSet="false" />
<Language Code="nb" InMinimumRequirementSet="true" />
<Language Code="nb-no" InMinimumRequirementSet="true" />
<Language Code="ne" InMinimumRequirementSet="false" />
<Language Code="ne-np" InMinimumRequirementSet="false" />
<Language Code="nl" InMinimumRequirementSet="true" />
<Language Code="nl-be" InMinimumRequirementSet="true" />
<Language Code="nl-nl" InMinimumRequirementSet="true" />
<Language Code="nn" InMinimumRequirementSet="false" />
<Language Code="nn-no" InMinimumRequirementSet="false" />
<Language Code="no" InMinimumRequirementSet="true" />
<Language Code="no-no" InMinimumRequirementSet="true" />
<Language Code="nso" InMinimumRequirementSet="false" />
<Language Code="nso-za" InMinimumRequirementSet="false" />
<Language Code="or" InMinimumRequirementSet="false" />
<Language Code="or-in" InMinimumRequirementSet="false" />
<Language Code="pa" InMinimumRequirementSet="false" />
<Language Code="pa-arab" InMinimumRequirementSet="false" />
<Language Code="pa-arab-pk" InMinimumRequirementSet="false" />
<Language Code="pa-deva" InMinimumRequirementSet="false" />
<Language Code="pa-in" InMinimumRequirementSet="false" />
<Language Code="pl" InMinimumRequirementSet="true" />
<Language Code="pl-pl" InMinimumRequirementSet="true" />
<Language Code="prs" InMinimumRequirementSet="false" />
<Language Code="prs-af" InMinimumRequirementSet="false" />
<Language Code="prs-arab" InMinimumRequirementSet="false" />
<Language Code="pt" InMinimumRequirementSet="true" />
<Language Code="pt-br" InMinimumRequirementSet="true" />
<Language Code="pt-pt" InMinimumRequirementSet="true" />
<Language Code="quc" InMinimumRequirementSet="false" />
<Language Code="quc-latn" InMinimumRequirementSet="false" />
<Language Code="qut" InMinimumRequirementSet="false" />
<Language Code="qut-gt" InMinimumRequirementSet="false" />
<Language Code="qut-latn" InMinimumRequirementSet="false" />
<Language Code="quz" InMinimumRequirementSet="false" />
<Language Code="quz-bo" InMinimumRequirementSet="false" />
<Language Code="quz-ec" InMinimumRequirementSet="false" />
<Language Code="quz-pe" InMinimumRequirementSet="false" />
<Language Code="ro" InMinimumRequirementSet="true" />
<Language Code="ro-ro" InMinimumRequirementSet="true" />
<Language Code="ru" InMinimumRequirementSet="true" />
<Language Code="ru-ru" InMinimumRequirementSet="true" />
<Language Code="rw" InMinimumRequirementSet="false" />
<Language Code="rw-rw" InMinimumRequirementSet="false" />
<Language Code="sd-arab" InMinimumRequirementSet="false" />
<Language Code="sd-arab-pk" InMinimumRequirementSet="false" />
<Language Code="sd-deva" InMinimumRequirementSet="false" />
<Language Code="si" InMinimumRequirementSet="false" />
<Language Code="si-lk" InMinimumRequirementSet="false" />
<Language Code="sk" InMinimumRequirementSet="true" />
<Language Code="sk-sk" InMinimumRequirementSet="true" />
<Language Code="sl" InMinimumRequirementSet="true" />
<Language Code="sl-si" InMinimumRequirementSet="true" />
<Language Code="sq" InMinimumRequirementSet="false" />
<Language Code="sq-al" InMinimumRequirementSet="false" />
<Language Code="sr" InMinimumRequirementSet="true" />
<Language Code="sr-cyrl" InMinimumRequirementSet="false" />
<Language Code="sr-cyrl-ba" InMinimumRequirementSet="false" />
<Language Code="sr-cyrl-cs" InMinimumRequirementSet="false" />
<Language Code="sr-cyrl-me" InMinimumRequirementSet="false" />
<Language Code="sr-cyrl-rs" InMinimumRequirementSet="false" />
<Language Code="sr-latn" InMinimumRequirementSet="true" />
<Language Code="sr-latn-ba" InMinimumRequirementSet="true" />
<Language Code="sr-latn-cs" InMinimumRequirementSet="true" />
<Language Code="sr-latn-me" InMinimumRequirementSet="true" />
<Language Code="sr-latn-rs" InMinimumRequirementSet="true" />
<Language Code="sv" InMinimumRequirementSet="true" />
<Language Code="sv-fi" InMinimumRequirementSet="true" />
<Language Code="sv-se" InMinimumRequirementSet="true" />
<Language Code="sw" InMinimumRequirementSet="false" />
<Language Code="sw-ke" InMinimumRequirementSet="false" />
<Language Code="ta" InMinimumRequirementSet="false" />
<Language Code="ta-in" InMinimumRequirementSet="false" />
<Language Code="te" InMinimumRequirementSet="false" />
<Language Code="te-in" InMinimumRequirementSet="false" />
<Language Code="tg-arab" InMinimumRequirementSet="false" />
<Language Code="tg-cyrl" InMinimumRequirementSet="false" />
<Language Code="tg-cyrl-tj" InMinimumRequirementSet="false" />
<Language Code="tg-latn" InMinimumRequirementSet="false" />
<Language Code="th" InMinimumRequirementSet="true" />
<Language Code="th-th" InMinimumRequirementSet="true" />
<Language Code="ti" InMinimumRequirementSet="false" />
<Language Code="ti-et" InMinimumRequirementSet="false" />
<Language Code="tk" InMinimumRequirementSet="false" />
<Language Code="tk-cyrl" InMinimumRequirementSet="false" />
<Language Code="tk-latn" InMinimumRequirementSet="false" />
<Language Code="tk-tm" InMinimumRequirementSet="false" />
<Language Code="tk-tr" InMinimumRequirementSet="false" />
<Language Code="tn" InMinimumRequirementSet="false" />
<Language Code="tn-bw" InMinimumRequirementSet="false" />
<Language Code="tn-za" InMinimumRequirementSet="false" />
<Language Code="tr" InMinimumRequirementSet="true" />
<Language Code="tr-tr" InMinimumRequirementSet="true" />
<Language Code="tt" InMinimumRequirementSet="false" />
<Language Code="tt-arab" InMinimumRequirementSet="false" />
<Language Code="tt-cyrl" InMinimumRequirementSet="false" />
<Language Code="tt-latn" InMinimumRequirementSet="false" />
<Language Code="tt-ru" InMinimumRequirementSet="false" />
<Language Code="ug" InMinimumRequirementSet="false" />
<Language Code="ug-arab" InMinimumRequirementSet="false" />
<Language Code="ug-cn" InMinimumRequirementSet="false" />
<Language Code="ug-cyrl" InMinimumRequirementSet="false" />
<Language Code="ug-latn" InMinimumRequirementSet="false" />
<Language Code="uk" InMinimumRequirementSet="true" />
<Language Code="uk-ua" InMinimumRequirementSet="true" />
<Language Code="ur" InMinimumRequirementSet="false" />
<Language Code="ur-pk" InMinimumRequirementSet="false" />
<Language Code="uz-cyrl" InMinimumRequirementSet="false" />
<Language Code="uz-latn" InMinimumRequirementSet="false" />
<Language Code="uz-latn-uz" InMinimumRequirementSet="false" />
<Language Code="vi" InMinimumRequirementSet="false" />
<Language Code="vi-vn" InMinimumRequirementSet="false" />
<Language Code="wo" InMinimumRequirementSet="false" />
<Language Code="wo-sn" InMinimumRequirementSet="false" />
<Language Code="xh" InMinimumRequirementSet="false" />
<Language Code="xh-za" InMinimumRequirementSet="false" />
<Language Code="yo" InMinimumRequirementSet="false" />
<Language Code="yo-latn" InMinimumRequirementSet="false" />
<Language Code="yo-ng" InMinimumRequirementSet="false" />
<Language Code="zh-cn" InMinimumRequirementSet="true" />
<Language Code="zh-hans" InMinimumRequirementSet="true" />
<Language Code="zh-hans-cn" InMinimumRequirementSet="true" />
<Language Code="zh-hans-sg" InMinimumRequirementSet="true" />
<Language Code="zh-hant" InMinimumRequirementSet="true" />
<Language Code="zh-hant-hk" InMinimumRequirementSet="true" />
<Language Code="zh-hant-mo" InMinimumRequirementSet="true" />
<Language Code="zh-hant-tw" InMinimumRequirementSet="true" />
<Language Code="zh-hk" InMinimumRequirementSet="true" />
<Language Code="zh-tw" InMinimumRequirementSet="true" />
<Language Code="zu" InMinimumRequirementSet="false" />
<Language Code="zu-za" InMinimumRequirementSet="false" />
</SupportedLocales>
<ProductReservedInfo>
<MainPackageIdentityName>53621FSApps.FluentTerminal</MainPackageIdentityName>
<ReservedNames>
<ReservedName>Fluent Terminal</ReservedName>
</ReservedNames>
</ProductReservedInfo>
<AccountPackageIdentityNames />
<PackageInfoList LandingUrl="https://appdev.microsoft.com:443/StorePortals/Developer/Catalog/ReleaseAnchor/f9c07088-9f3e-4312-ad5b-0f3b4aeb9b37" />
</StoreAssociation>
Binary file not shown.
+1 -1
View File
@@ -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.0.0" />
<Identity Name="53621FSApps.FluentTerminal" Publisher="CN=BDC72197-B634-438A-B863-16DCB7D90C85" Version="0.4.1.0" />
<mp:PhoneIdentity PhoneProductId="f7225014-f405-4342-ad2e-ebf6ac60ea2c" PhonePublisherId="00000000-0000-0000-0000-000000000000" />
<Properties>
<DisplayName>Fluent Terminal</DisplayName>
@@ -13,7 +13,8 @@ namespace FluentTerminal.App.Services
{
return content.GetTextAsync().AsTask();
}
return null;
// Otherwise return a new task that just sends an empty string.
return Task.FromResult(string.Empty);
}
public void SetText(string text)
@@ -1,13 +1,28 @@
using Microsoft.Graphics.Canvas.Text;
using FluentTerminal.Models;
using Microsoft.Graphics.Canvas.Text;
using System.Collections.Generic;
using System.Linq;
namespace FluentTerminal.App.Services
{
public class SystemFontService : ISystemFontService
{
public IEnumerable<string> GetSystemFontFamilies()
public IEnumerable<FontInfo> GetSystemFontFamilies()
{
return CanvasTextFormat.GetSystemFontFamilies();
return CanvasFontSet.GetSystemFontSet().Fonts.Select(x => new FontInfo { Name = x.FamilyNames.Values.FirstOrDefault(), IsMonospaced = x.IsMonospaced }).Distinct(new FontInfoComparer());
}
private class FontInfoComparer : IEqualityComparer<FontInfo>
{
public bool Equals(FontInfo x, FontInfo y)
{
return x.Name == y.Name;
}
public int GetHashCode(FontInfo obj)
{
return obj.Name.GetHashCode();
}
}
}
}
+4
View File
@@ -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}"
@@ -40,7 +40,7 @@ namespace FluentTerminal.App.Utilities
titleBar.ButtonPressedBackgroundColor = GetColor("SystemListMediumColor", theme);
}
private static Color? GetColor(string name, ElementTheme theme)
public static Color? GetColor(string name, ElementTheme theme)
{
if (theme == ElementTheme.Light)
{
@@ -0,0 +1,49 @@
using System;
using Windows.UI.Core;
using Windows.UI.Xaml;
namespace FluentTerminal.App.Utilities
{
public class DebouncedAction<T>
{
private DispatcherTimer timer;
private readonly CoreDispatcher _dispatcher;
private readonly TimeSpan _interval;
private readonly Action<T> _action;
private T _parameter;
public DebouncedAction(CoreDispatcher dispatcher, TimeSpan interval, Action<T> action)
{
_dispatcher = dispatcher;
_interval = interval;
_action = action;
}
public void Invoke(T parameter)
{
_parameter = parameter;
timer?.Stop();
timer = null;
timer = new DispatcherTimer
{
Interval = _interval
};
timer.Tick += (s, e) =>
{
if (timer == null)
{
return;
}
timer?.Stop();
timer = null;
_dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => _action.Invoke(_parameter));
};
timer.Start();
}
}
}
+17
View File
@@ -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); }
+118 -9
View File
@@ -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,15 +62,122 @@
<SymbolIcon Symbol="Setting" />
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
<MenuFlyoutItem
Width="160"
Command="{x:Bind ViewModel.ShowAboutCommand}"
Text="About">
<MenuFlyoutItem.Icon>
<FontIcon
FontFamily="Segoe MDL2 Assets"
Glyph="&#xE946;"
ToolTipService.ToolTip="About" />
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
</MenuFlyout>
</Button.Flyout>
</Button>
<TextBlock
<RelativePanel
Grid.Column="1"
IsHitTestVisible="False"
Style="{StaticResource WindowTitleTextBlockStyle}"
Text="{x:Bind ViewModel.SelectedTerminal.Title, Mode=OneWay}"
Visibility="{x:Bind ViewModel.ShowTabsOnTop, Mode=OneWay, Converter={StaticResource FalseToVisibleConverter}}" />
Height="32"
HorizontalAlignment="Left"
VerticalAlignment="Stretch"
Background="{x:Bind ViewModel.SelectedTerminal.TabTheme, Mode=OneWay, Converter={StaticResource TabColorFallbackConverter}, ConverterParameter=Background}">
<RelativePanel.ContextFlyout>
<MenuFlyout>
<MenuFlyoutSubItem Text="Color">
<ToggleMenuFlyoutItem
Command="{x:Bind ViewModel.SelectedTerminal.SelectTabThemeCommand}"
CommandParameter="None"
IsChecked="{x:Bind ViewModel.SelectedTerminal.TabTheme, Mode=OneWay, Converter={StaticResource TabThemeSelectedConverter}, ConverterParameter=None}"
Text="None">
<ToggleMenuFlyoutItem.Icon>
<FontIcon FontFamily="Segoe MDL2 Assets" Glyph="&#xF126;" />
</ToggleMenuFlyoutItem.Icon>
</ToggleMenuFlyoutItem>
<ToggleMenuFlyoutItem
Command="{x:Bind ViewModel.SelectedTerminal.SelectTabThemeCommand}"
CommandParameter="Red"
IsChecked="{x:Bind ViewModel.SelectedTerminal.TabTheme, Mode=OneWay, Converter={StaticResource TabThemeSelectedConverter}, ConverterParameter=Red}"
Text="Red">
<ToggleMenuFlyoutItem.Icon>
<FontIcon
FontFamily="Segoe MDL2 Assets"
Foreground="#E81123"
Glyph="&#xF127;" />
</ToggleMenuFlyoutItem.Icon>
</ToggleMenuFlyoutItem>
<ToggleMenuFlyoutItem
Command="{x:Bind ViewModel.SelectedTerminal.SelectTabThemeCommand}"
CommandParameter="Green"
IsChecked="{x:Bind ViewModel.SelectedTerminal.TabTheme, Mode=OneWay, Converter={StaticResource TabThemeSelectedConverter}, ConverterParameter=Green}"
Text="Green">
<ToggleMenuFlyoutItem.Icon>
<FontIcon
FontFamily="Segoe MDL2 Assets"
Foreground="#10893E"
Glyph="&#xF127;" />
</ToggleMenuFlyoutItem.Icon>
</ToggleMenuFlyoutItem>
<ToggleMenuFlyoutItem
Command="{x:Bind ViewModel.SelectedTerminal.SelectTabThemeCommand}"
CommandParameter="Blue"
IsChecked="{x:Bind ViewModel.SelectedTerminal.TabTheme, Mode=OneWay, Converter={StaticResource TabThemeSelectedConverter}, ConverterParameter=Blue}"
Text="Blue">
<ToggleMenuFlyoutItem.Icon>
<FontIcon
FontFamily="Segoe MDL2 Assets"
Foreground="#0078D7"
Glyph="&#xF127;" />
</ToggleMenuFlyoutItem.Icon>
</ToggleMenuFlyoutItem>
<ToggleMenuFlyoutItem
Command="{x:Bind ViewModel.SelectedTerminal.SelectTabThemeCommand}"
CommandParameter="Purple"
IsChecked="{x:Bind ViewModel.SelectedTerminal.TabTheme, Mode=OneWay, Converter={StaticResource TabThemeSelectedConverter}, ConverterParameter=Purple}"
Text="Purple">
<ToggleMenuFlyoutItem.Icon>
<FontIcon
FontFamily="Segoe MDL2 Assets"
Foreground="#881798"
Glyph="&#xF127;" />
</ToggleMenuFlyoutItem.Icon>
</ToggleMenuFlyoutItem>
<ToggleMenuFlyoutItem
Command="{x:Bind ViewModel.SelectedTerminal.SelectTabThemeCommand}"
CommandParameter="Orange"
IsChecked="{x:Bind ViewModel.SelectedTerminal.TabTheme, Mode=OneWay, Converter={StaticResource TabThemeSelectedConverter}, ConverterParameter=Orange}"
Text="Orange">
<ToggleMenuFlyoutItem.Icon>
<FontIcon
FontFamily="Segoe MDL2 Assets"
Foreground="#FF8C00"
Glyph="&#xF127;" />
</ToggleMenuFlyoutItem.Icon>
</ToggleMenuFlyoutItem>
<ToggleMenuFlyoutItem
Command="{x:Bind ViewModel.SelectedTerminal.SelectTabThemeCommand}"
CommandParameter="Teal"
IsChecked="{x:Bind ViewModel.SelectedTerminal.TabTheme, Mode=OneWay, Converter={StaticResource TabThemeSelectedConverter}, ConverterParameter=Teal}"
Text="Teal">
<ToggleMenuFlyoutItem.Icon>
<FontIcon
FontFamily="Segoe MDL2 Assets"
Foreground="#00B7C3"
Glyph="&#xF127;" />
</ToggleMenuFlyoutItem.Icon>
</ToggleMenuFlyoutItem>
</MenuFlyoutSubItem>
<MenuFlyoutItem Command="{x:Bind ViewModel.SelectedTerminal.EditTitleCommand}" Text="Edit Title" />
</MenuFlyout>
</RelativePanel.ContextFlyout>
<TextBlock
Margin="12,8,12,0"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind ViewModel.WindowTitle, Mode=OneWay}"
TextTrimming="CharacterEllipsis"
TextWrapping="NoWrap"
Visibility="{x:Bind ViewModel.ShowTabsOnTop, Mode=OneWay, Converter={StaticResource FalseToVisibleConverter}}" />
</RelativePanel>
<views:TabBar
Grid.Column="1"
HorizontalAlignment="Left"
+5 -2
View File
@@ -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;
}
}
}
+11 -1
View File
@@ -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="&#xE946;"
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,48 +34,98 @@
Label="Restore Defaults" />
</CommandBar>
</Grid>
<StackPanel Grid.Row="2" Margin="24,0,0,0">
<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"
Header="Always show tabs"
IsOn="{x:Bind ViewModel.AlwaysShowTabs, Mode=TwoWay}" />
<ToggleSwitch
Margin="0,0,0,24"
Header="Underline selected tab"
IsOn="{x:Bind ViewModel.UnderlineSelectedTab, Mode=TwoWay}" />
<ToggleSwitch
Margin="0,0,0,24"
Header="Ask before closing windows"
IsOn="{x:Bind ViewModel.ConfirmClosingWindows, Mode=TwoWay}" />
<ToggleSwitch Header="Ask before closing tabs" IsOn="{x:Bind ViewModel.ConfirmClosingTabs, Mode=TwoWay}" />
</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
Margin="{StaticResource ItemMargin}"
Header="Always show tabs"
IsOn="{x:Bind ViewModel.AlwaysShowTabs, Mode=TwoWay}" />
<ToggleSwitch
Margin="{StaticResource ItemMargin}"
Header="Show custom tab title also in titlebar"
IsOn="{x:Bind ViewModel.ShowCustomTitleInTitlebar, Mode=TwoWay}" />
<ToggleSwitch
Margin="{StaticResource ItemMargin}"
Header="Underline selected tab"
IsOn="{x:Bind ViewModel.UnderlineSelectedTab, 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="{StaticResource ItemMargin}"
Header="Ask before closing windows"
IsOn="{x:Bind ViewModel.ConfirmClosingWindows, Mode=TwoWay}" />
<ToggleSwitch
Margin="{StaticResource ItemMargin}"
Header="Ask before closing tabs"
IsOn="{x:Bind ViewModel.ConfirmClosingTabs, Mode=TwoWay}" />
<ToggleSwitch
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,39 +35,45 @@
Label="Restore Defaults" />
</CommandBar>
</Grid>
<StackPanel Grid.Row="2" Margin="24,0,0,0">
<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}" />
</StackPanel>
<ScrollViewer Grid.Row="2">
<StackPanel Margin="{StaticResource ContainerMargin}">
<ToggleSwitch
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>
</Page>
@@ -5,8 +5,77 @@
xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
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">
<Page.Resources>
<views:TabThemeTemplateSelector x:Key="TabThemeTemplateSelector">
<views:TabThemeTemplateSelector.DefaultTemplate>
<DataTemplate x:DataType="models:TabTheme">
<StackPanel Orientation="Horizontal">
<FontIcon
Margin="0,0,6,0"
FontFamily="Segoe MDL2 Assets"
Glyph="&#xF126;" />
<TextBlock Text="{x:Bind Name, Mode=OneWay}" />
</StackPanel>
</DataTemplate>
</views:TabThemeTemplateSelector.DefaultTemplate>
<views:TabThemeTemplateSelector.ColoredTemplate>
<DataTemplate x:DataType="models:TabTheme">
<StackPanel Orientation="Horizontal">
<FontIcon
Margin="0,0,6,0"
FontFamily="Segoe MDL2 Assets"
Foreground="{x:Bind Color}"
Glyph="&#xF127;" />
<TextBlock Text="{x:Bind Name, Mode=OneWay}" />
</StackPanel>
</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>
<ColumnDefinition Width="160" />
@@ -21,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}"
@@ -47,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"
@@ -54,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"
@@ -73,127 +150,238 @@
Label="Cancel" />
</CommandBar>
</Grid>
<StackPanel Grid.Row="2" Grid.Column="0">
<ListView ItemsSource="{x:Bind ViewModel.ShellProfiles}" SelectedItem="{x:Bind ViewModel.SelectedShellProfile, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="viewmodels:ShellProfileViewModel">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{x:Bind Name, Mode=OneWay}" />
<ScrollViewer
Grid.Row="2"
Grid.Column="0"
VerticalScrollBarVisibility="Auto">
<StackPanel Margin="{StaticResource ItemMargin}">
<ListView ItemsSource="{x:Bind ViewModel.ShellProfiles}" SelectedItem="{x:Bind ViewModel.SelectedShellProfile, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="viewmodels:ShellProfileViewModel">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{x:Bind Name, Mode=OneWay}" />
<Viewbox
Grid.Column="1"
Width="16"
Height="16"
HorizontalAlignment="Right">
<SymbolIcon Symbol="Accept" Visibility="{x:Bind IsDefault, Mode=OneWay, Converter={StaticResource TrueToVisibleConverter}}" />
</Viewbox>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
</ListView>
<Button
Height="48"
Margin="0,12,0,0"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
Background="Transparent"
Command="{x:Bind ViewModel.CreateShellProfileCommand}"
Style="{StaticResource ButtonRevealStyle}">
<Button.Content>
<StackPanel Orientation="Horizontal">
<Viewbox
Grid.Column="1"
Width="16"
Height="16"
HorizontalAlignment="Right">
<SymbolIcon Symbol="Accept" Visibility="{x:Bind IsDefault, Mode=OneWay, Converter={StaticResource TrueToVisibleConverter}}" />
Width="12"
Height="12"
Margin="0,0,6,0">
<SymbolIcon Symbol="Add" />
</Viewbox>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
</ListView>
<Button
Height="48"
Margin="0,12,0,0"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
Background="Transparent"
Command="{x:Bind ViewModel.CreateShellProfileCommand}"
Style="{StaticResource ButtonRevealStyle}">
<Button.Content>
<StackPanel Orientation="Horizontal">
<Viewbox
Width="12"
Height="12"
Margin="0,0,6,0">
<SymbolIcon Symbol="Add" />
</Viewbox>
<TextBlock Style="{StaticResource CaptionTextBlockStyle}" Text="Create new" />
</StackPanel>
</Button.Content>
</Button>
</StackPanel>
<ContentControl
<TextBlock Style="{StaticResource CaptionTextBlockStyle}" Text="Create new" />
</StackPanel>
</Button.Content>
</Button>
</StackPanel>
</ScrollViewer>
<ScrollViewer
Grid.Row="2"
Grid.Column="1"
Margin="24,0,0,0"
Content="{x:Bind ViewModel.SelectedShellProfile, Mode=TwoWay}">
<ContentControl.ContentTemplate>
<DataTemplate x:DataType="viewmodels:ShellProfileViewModel">
<Grid>
<StackPanel Visibility="{x:Bind InEditMode, Converter={StaticResource TrueToVisibleConverter}, Mode=OneWay}">
<TextBox
Width="300"
HorizontalAlignment="Left"
Header="Name"
IsEnabled="{x:Bind InEditMode, Mode=OneWay}"
IsSpellCheckEnabled="False"
Text="{x:Bind Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<StackPanel Margin="0,24,0,0" Orientation="Horizontal">
HorizontalAlignment="Stretch"
VerticalScrollBarVisibility="Auto">
<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 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="{StaticResource ItemMargin}" Orientation="Horizontal">
<TextBox
Width="300"
Header="Shell executable location"
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"
Command="{x:Bind BrowseForCustomShellCommand}"
Content="..."
IsEnabled="{x:Bind PreInstalled, Converter={StaticResource NegateConverter}, Mode=OneWay}"
Style="{StaticResource ButtonRevealStyle}" />
</StackPanel>
<StackPanel Margin="{StaticResource ItemMargin}" Orientation="Horizontal">
<TextBox
Width="300"
Header="Working directory"
IsEnabled="{x:Bind InEditMode, Mode=OneWay}"
IsSpellCheckEnabled="False"
Text="{x:Bind WorkingDirectory, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Button
Margin="6,0,0,0"
VerticalAlignment="Bottom"
Command="{x:Bind BrowseForWorkingDirectoryCommand}"
Content="..."
Style="{StaticResource ButtonRevealStyle}" />
</StackPanel>
<TextBox
Width="300"
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="{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 CR"
GroupName="LineEndingStyle"
IsChecked="{x:Bind ToCRIsSelected, 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 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="{StaticResource ItemMargin}"
Header="Shell executable location"
IsEnabled="{x:Bind InEditMode, Mode=OneWay}"
IsSpellCheckEnabled="False"
Text="{x:Bind Location, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Button
Margin="6,0,0,0"
VerticalAlignment="Bottom"
Command="{x:Bind BrowseForCustomShellCommand}"
Content="..."
Style="{StaticResource ButtonRevealStyle}" />
</StackPanel>
Text="{x:Bind Location, Mode=OneWay}" />
<StackPanel Margin="0,24,0,0" Orientation="Horizontal">
<TextBox
Width="300"
Header="Working directory"
IsEnabled="{x:Bind InEditMode, Mode=OneWay}"
IsSpellCheckEnabled="False"
Text="{x:Bind WorkingDirectory, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Button
Margin="6,0,0,0"
VerticalAlignment="Bottom"
Command="{x:Bind BrowseForWorkingDirectoryCommand}"
Content="..."
Style="{StaticResource ButtonRevealStyle}" />
</StackPanel>
<controls:HeaderedContentControl
Margin="{StaticResource ItemMargin}"
HorizontalContentAlignment="Stretch"
Header="Working directory">
<ContentControl
Content="{x:Bind WorkingDirectory, Mode=OneWay}"
ContentTemplateSelector="{StaticResource NoValueTemplateSelector}"
FontSize="18"
FontWeight="Light" />
</controls:HeaderedContentControl>
<TextBox
Width="300"
Margin="0,24,0,0"
HorizontalAlignment="Left"
Header="Arguments"
IsEnabled="{x:Bind InEditMode, Mode=OneWay}"
IsSpellCheckEnabled="False"
Text="{x:Bind Arguments, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
<StackPanel Visibility="{x:Bind InEditMode, Mode=OneWay, Converter={StaticResource FalseToVisibleConverter}}">
<TextBlock Style="{StaticResource SubtitleTextBlockStyle}" Text="{x:Bind Name, Mode=OneWay}" />
<controls:HeaderedTextBlock
Margin="0,58,0,0"
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}" />
</StackPanel>
</Grid>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
<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}"
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>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</ScrollViewer>
</Grid>
</Page>
</Page>
@@ -2,16 +2,21 @@
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"
xmlns:models="using:FluentTerminal.Models"
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 +39,100 @@
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}" />
SelectedValue="{x:Bind ViewModel.FontFamily, Mode=TwoWay}"
SelectedValuePath="Name">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="models:FontInfo">
<TextBlock FontWeight="{x:Bind IsMonospaced, Converter={StaticResource TrueToBoldConverter}}" Text="{x:Bind Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<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="&#xEE71;" />
</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}"
@@ -93,215 +96,248 @@
Label="Cancel" />
</CommandBar>
</Grid>
<StackPanel Grid.Row="2" Grid.Column="0">
<ListView ItemsSource="{x:Bind ViewModel.Themes}" SelectedItem="{x:Bind ViewModel.SelectedTheme, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="viewmodels:ThemeViewModel">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{x:Bind Name, Mode=OneWay}" />
<ScrollViewer
Grid.Row="2"
Grid.Column="0"
VerticalScrollBarVisibility="Auto">
<StackPanel Margin="{StaticResource ItemMargin}">
<ListView ItemsSource="{x:Bind ViewModel.Themes}" SelectedItem="{x:Bind ViewModel.SelectedTheme, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="viewmodels:ThemeViewModel">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{x:Bind Name, Mode=OneWay}" />
<Viewbox
Grid.Column="1"
Width="16"
Height="16"
HorizontalAlignment="Right">
<SymbolIcon Symbol="Accept" Visibility="{x:Bind IsActive, Mode=OneWay, Converter={StaticResource TrueToVisibleConverter}}" />
</Viewbox>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
</ListView>
<Button
Height="48"
Margin="0,12,0,0"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
Background="Transparent"
Command="{x:Bind ViewModel.CreateThemeCommand}"
Style="{StaticResource ButtonRevealStyle}">
<Button.Content>
<StackPanel Orientation="Horizontal">
<Viewbox
Grid.Column="1"
Width="16"
Height="16"
HorizontalAlignment="Right">
<SymbolIcon Symbol="Accept" Visibility="{x:Bind IsActive, Mode=OneWay, Converter={StaticResource TrueToVisibleConverter}}" />
Width="12"
Height="12"
Margin="0,0,6,0">
<SymbolIcon Symbol="Add" />
</Viewbox>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
</ListView>
<Button
Height="48"
Margin="0,12,0,0"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
Background="Transparent"
Command="{x:Bind ViewModel.CreateThemeCommand}"
Style="{StaticResource ButtonRevealStyle}">
<Button.Content>
<StackPanel Orientation="Horizontal">
<Viewbox
Width="12"
Height="12"
Margin="0,0,6,0">
<SymbolIcon Symbol="Add" />
</Viewbox>
<TextBlock Style="{StaticResource CaptionTextBlockStyle}" Text="Create new" />
</StackPanel>
</Button.Content>
</Button>
<Button
Height="48"
Margin="0,0,0,0"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
Background="Transparent"
Command="{x:Bind ViewModel.ImportThemeCommand}"
Style="{StaticResource ButtonRevealStyle}">
<Button.Content>
<StackPanel Orientation="Horizontal">
<Viewbox
Width="12"
Height="12"
Margin="0,0,6,0">
<SymbolIcon Symbol="OpenFile" />
</Viewbox>
<TextBlock Style="{StaticResource CaptionTextBlockStyle}" Text="Import" />
</StackPanel>
</Button.Content>
</Button>
</StackPanel>
<TextBlock Style="{StaticResource CaptionTextBlockStyle}" Text="Create new" />
</StackPanel>
</Button.Content>
</Button>
<Button
Height="48"
Margin="0,0,0,0"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
Background="Transparent"
Command="{x:Bind ViewModel.ImportThemeCommand}"
Style="{StaticResource ButtonRevealStyle}">
<Button.Content>
<StackPanel Orientation="Horizontal">
<Viewbox
Width="12"
Height="12"
Margin="0,0,6,0">
<SymbolIcon Symbol="OpenFile" />
</Viewbox>
<TextBlock Style="{StaticResource CaptionTextBlockStyle}" Text="Import" />
</StackPanel>
</Button.Content>
</Button>
</StackPanel>
</ScrollViewer>
<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">
<StackPanel>
<TextBlock
Style="{StaticResource SubtitleTextBlockStyle}"
Text="{x:Bind Name, Mode=OneWay}"
Visibility="{x:Bind InEditMode, Mode=OneWay, Converter={StaticResource FalseToVisibleConverter}}">
<TextBlock.Foreground>
<SolidColorBrush Color="{x:Bind Foreground, Mode=OneWay}" />
</TextBlock.Foreground>
</TextBlock>
<TextBlock Style="{StaticResource CaptionTextBlockStyle}" Text="{x:Bind Author, Mode=OneWay}" />
<TextBox
Header="Name"
Text="{x:Bind Name, Mode=TwoWay}"
Visibility="{x:Bind InEditMode, Mode=OneWay, Converter={StaticResource TrueToVisibleConverter}}" />
<TextBox
Margin="0,24,0,0"
Header="Author"
Text="{x:Bind Author, Mode=TwoWay}"
Visibility="{x:Bind InEditMode, Mode=OneWay, Converter={StaticResource TrueToVisibleConverter}}" />
<Grid Margin="0,48,0,0">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.Resources>
<Style TargetType="Border">
<Setter Property="Margin" Value="0,0,0,0" />
<Setter Property="Width" Value="24" />
<Setter Property="Height" Value="24" />
</Style>
</Grid.Resources>
<StackPanel Grid.Row="0" Orientation="Horizontal">
<views:TerminalColorPicker
ColorName="Black"
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
SelectedColor="{x:Bind Black, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
<views:TerminalColorPicker
ColorName="Red"
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
SelectedColor="{x:Bind Red, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
<views:TerminalColorPicker
ColorName="Green"
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
SelectedColor="{x:Bind Green, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
<views:TerminalColorPicker
ColorName="Yellow"
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
SelectedColor="{x:Bind Yellow, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
<views:TerminalColorPicker
ColorName="Blue"
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
SelectedColor="{x:Bind Blue, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
<views:TerminalColorPicker
ColorName="Magenta"
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
SelectedColor="{x:Bind Magenta, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
<views:TerminalColorPicker
ColorName="Cyan"
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
SelectedColor="{x:Bind Cyan, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
<views:TerminalColorPicker
ColorName="White"
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
SelectedColor="{x:Bind White, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
</StackPanel>
<StackPanel Grid.Row="1" Orientation="Horizontal">
<views:TerminalColorPicker
ColorName="Bright Black"
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
SelectedColor="{x:Bind BrightBlack, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
<views:TerminalColorPicker
ColorName="Bright Red"
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
SelectedColor="{x:Bind BrightRed, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
<views:TerminalColorPicker
ColorName="Bright Green"
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
SelectedColor="{x:Bind BrightGreen, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
<views:TerminalColorPicker
ColorName="Bright Yellow"
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
SelectedColor="{x:Bind BrightYellow, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
<views:TerminalColorPicker
ColorName="Bright Blue"
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
SelectedColor="{x:Bind BrightBlue, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
<views:TerminalColorPicker
ColorName="Bright Magenta"
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
SelectedColor="{x:Bind BrightMagenta, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
<views:TerminalColorPicker
ColorName="Bright Cyan"
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
SelectedColor="{x:Bind BrightCyan, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
<views:TerminalColorPicker
ColorName="Bright White"
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
SelectedColor="{x:Bind BrightWhite, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
</StackPanel>
<StackPanel Grid.Row="2" Orientation="Horizontal">
<views:TerminalColorPicker
ColorName="Foreground"
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
SelectedColor="{x:Bind Foreground, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
<views:TerminalColorPicker
ColorName="Background"
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
SelectedColor="{x:Bind Background, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
<views:TerminalColorPicker
ColorName="Cursor"
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
SelectedColor="{x:Bind Cursor, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
<views:TerminalColorPicker
ColorName="Cursor Accent"
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
SelectedColor="{x:Bind CursorAccent, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
<views:TerminalColorPicker
ColorName="Selection"
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
IsAlphaEnabled="True"
SelectedColor="{x:Bind Selection, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=true}" />
</StackPanel>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Margin="{StaticResource ContainerMargin}">
<!-- VIEW THEME -->
<TextBlock
Grid.Row="3"
Margin="0,6,0,0"
Text="Click on a color to edit"
Margin="{StaticResource ItemMargin}"
Style="{StaticResource SubtitleTextBlockStyle}"
Text="{x:Bind Name, Mode=OneWay}"
Visibility="{x:Bind InEditMode, Mode=OneWay, Converter={StaticResource FalseToVisibleConverter}}">
<TextBlock.Foreground>
<SolidColorBrush Color="{x:Bind Foreground, Mode=OneWay}" />
</TextBlock.Foreground>
</TextBlock>
<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}}" />
</Grid>
</StackPanel>
<TextBox
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="{StaticResource ItemMargin}">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.Resources>
<Style TargetType="Border">
<Setter Property="Margin" Value="0,0,0,0" />
<Setter Property="Width" Value="24" />
<Setter Property="Height" Value="24" />
</Style>
</Grid.Resources>
<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}"
SelectedColor="{x:Bind Black, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
<views:TerminalColorPicker
ColorName="Red"
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
SelectedColor="{x:Bind Red, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
<views:TerminalColorPicker
ColorName="Green"
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
SelectedColor="{x:Bind Green, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
<views:TerminalColorPicker
ColorName="Yellow"
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
SelectedColor="{x:Bind Yellow, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
<views:TerminalColorPicker
ColorName="Blue"
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
SelectedColor="{x:Bind Blue, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
<views:TerminalColorPicker
ColorName="Magenta"
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
SelectedColor="{x:Bind Magenta, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
<views:TerminalColorPicker
ColorName="Cyan"
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
SelectedColor="{x:Bind Cyan, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
<views:TerminalColorPicker
ColorName="White"
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
SelectedColor="{x:Bind White, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
</StackPanel>
<StackPanel Grid.Row="3" Orientation="Horizontal">
<views:TerminalColorPicker
ColorName="Bright Black"
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
SelectedColor="{x:Bind BrightBlack, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
<views:TerminalColorPicker
ColorName="Bright Red"
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
SelectedColor="{x:Bind BrightRed, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
<views:TerminalColorPicker
ColorName="Bright Green"
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
SelectedColor="{x:Bind BrightGreen, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
<views:TerminalColorPicker
ColorName="Bright Yellow"
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
SelectedColor="{x:Bind BrightYellow, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
<views:TerminalColorPicker
ColorName="Bright Blue"
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
SelectedColor="{x:Bind BrightBlue, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
<views:TerminalColorPicker
ColorName="Bright Magenta"
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
SelectedColor="{x:Bind BrightMagenta, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
<views:TerminalColorPicker
ColorName="Bright Cyan"
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
SelectedColor="{x:Bind BrightCyan, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
<views:TerminalColorPicker
ColorName="Bright White"
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
SelectedColor="{x:Bind BrightWhite, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
</StackPanel>
<StackPanel Grid.Row="4" Orientation="Horizontal">
<views:TerminalColorPicker
ColorName="Foreground"
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
SelectedColor="{x:Bind Foreground, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
<views:TerminalColorPicker
ColorName="Background"
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
SelectedColor="{x:Bind Background, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
<views:TerminalColorPicker
ColorName="Cursor"
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
SelectedColor="{x:Bind Cursor, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
<views:TerminalColorPicker
ColorName="Cursor Accent"
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
SelectedColor="{x:Bind CursorAccent, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=false}" />
<views:TerminalColorPicker
ColorName="Selection"
EnableEditing="{x:Bind InEditMode, Mode=OneWay}"
IsAlphaEnabled="True"
SelectedColor="{x:Bind Selection, Mode=TwoWay, Converter={StaticResource StringToColorConverter}, ConverterParameter=true}" />
</StackPanel>
</Grid>
</StackPanel>
</ScrollViewer>
</DataTemplate>
</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);
}
+223 -12
View File
@@ -7,7 +7,9 @@
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:models="using:FluentTerminal.Models"
xmlns:viewmodels="using:FluentTerminal.App.ViewModels"
xmlns:views="using:FluentTerminal.App.Views"
d:DesignHeight="32"
d:DesignWidth="200"
mc:Ignorable="d">
@@ -44,6 +46,7 @@
<ListView
x:Name="ListView"
AllowDrop="True"
Background="Transparent"
CanDragItems="True"
CanReorderItems="True"
ItemsSource="{x:Bind ItemsSource}"
@@ -53,7 +56,9 @@
<RelativePanel
Height="32"
Margin="0"
VerticalAlignment="Stretch">
VerticalAlignment="Stretch"
Background="Transparent"
ToolTipService.ToolTip="{x:Bind TabTitle, Mode=OneWay}">
<Interactivity:Interaction.Behaviors>
<behaviors:MiddleClickBehavior>
<behaviors:MiddleClickBehavior.Actions>
@@ -61,11 +66,101 @@
</behaviors:MiddleClickBehavior.Actions>
</behaviors:MiddleClickBehavior>
</Interactivity:Interaction.Behaviors>
<RelativePanel.ContextFlyout>
<MenuFlyout>
<MenuFlyoutSubItem Text="Color">
<ToggleMenuFlyoutItem
Command="{x:Bind SelectTabThemeCommand}"
CommandParameter="None"
IsChecked="{x:Bind TabTheme, Mode=OneWay, Converter={StaticResource TabThemeSelectedConverter}, ConverterParameter=None}"
Text="None">
<ToggleMenuFlyoutItem.Icon>
<FontIcon FontFamily="Segoe MDL2 Assets" Glyph="&#xF126;" />
</ToggleMenuFlyoutItem.Icon>
</ToggleMenuFlyoutItem>
<ToggleMenuFlyoutItem
Command="{x:Bind SelectTabThemeCommand}"
CommandParameter="Red"
IsChecked="{x:Bind TabTheme, Mode=OneWay, Converter={StaticResource TabThemeSelectedConverter}, ConverterParameter=Red}"
Text="Red">
<ToggleMenuFlyoutItem.Icon>
<FontIcon
FontFamily="Segoe MDL2 Assets"
Foreground="#E81123"
Glyph="&#xF127;" />
</ToggleMenuFlyoutItem.Icon>
</ToggleMenuFlyoutItem>
<ToggleMenuFlyoutItem
Command="{x:Bind SelectTabThemeCommand}"
CommandParameter="Green"
IsChecked="{x:Bind TabTheme, Mode=OneWay, Converter={StaticResource TabThemeSelectedConverter}, ConverterParameter=Green}"
Text="Green">
<ToggleMenuFlyoutItem.Icon>
<FontIcon
FontFamily="Segoe MDL2 Assets"
Foreground="#10893E"
Glyph="&#xF127;" />
</ToggleMenuFlyoutItem.Icon>
</ToggleMenuFlyoutItem>
<ToggleMenuFlyoutItem
Command="{x:Bind SelectTabThemeCommand}"
CommandParameter="Blue"
IsChecked="{x:Bind TabTheme, Mode=OneWay, Converter={StaticResource TabThemeSelectedConverter}, ConverterParameter=Blue}"
Text="Blue">
<ToggleMenuFlyoutItem.Icon>
<FontIcon
FontFamily="Segoe MDL2 Assets"
Foreground="#0078D7"
Glyph="&#xF127;" />
</ToggleMenuFlyoutItem.Icon>
</ToggleMenuFlyoutItem>
<ToggleMenuFlyoutItem
Command="{x:Bind SelectTabThemeCommand}"
CommandParameter="Purple"
IsChecked="{x:Bind TabTheme, Mode=OneWay, Converter={StaticResource TabThemeSelectedConverter}, ConverterParameter=Purple}"
Text="Purple">
<ToggleMenuFlyoutItem.Icon>
<FontIcon
FontFamily="Segoe MDL2 Assets"
Foreground="#881798"
Glyph="&#xF127;" />
</ToggleMenuFlyoutItem.Icon>
</ToggleMenuFlyoutItem>
<ToggleMenuFlyoutItem
Command="{x:Bind SelectTabThemeCommand}"
CommandParameter="Orange"
IsChecked="{x:Bind TabTheme, Mode=OneWay, Converter={StaticResource TabThemeSelectedConverter}, ConverterParameter=Orange}"
Text="Orange">
<ToggleMenuFlyoutItem.Icon>
<FontIcon
FontFamily="Segoe MDL2 Assets"
Foreground="#FF8C00"
Glyph="&#xF127;" />
</ToggleMenuFlyoutItem.Icon>
</ToggleMenuFlyoutItem>
<ToggleMenuFlyoutItem
Command="{x:Bind SelectTabThemeCommand}"
CommandParameter="Teal"
IsChecked="{x:Bind TabTheme, Mode=OneWay, Converter={StaticResource TabThemeSelectedConverter}, ConverterParameter=Teal}"
Text="Teal">
<ToggleMenuFlyoutItem.Icon>
<FontIcon
FontFamily="Segoe MDL2 Assets"
Foreground="#00B7C3"
Glyph="&#xF127;" />
</ToggleMenuFlyoutItem.Icon>
</ToggleMenuFlyoutItem>
</MenuFlyoutSubItem>
<MenuFlyoutItem Command="{x:Bind EditTitleCommand}" Text="Edit Title" />
<MenuFlyoutSeparator />
<MenuFlyoutItem Command="{x:Bind CloseCommand}" Text="Close" />
</MenuFlyout>
</RelativePanel.ContextFlyout>
<Border
Grid.ColumnSpan="3"
Height="3"
VerticalAlignment="Bottom"
Background="{ThemeResource SystemAccentColor}"
Background="{x:Bind TabTheme.Color, Mode=OneWay, Converter={StaticResource ColorResourceKeyFallbackConverter}, ConverterParameter=SystemControlHighlightAccentBrush}"
RelativePanel.AlignBottomWithPanel="True"
RelativePanel.AlignLeftWithPanel="True"
RelativePanel.AlignRightWithPanel="True"
@@ -76,19 +171,34 @@
RelativePanel.AlignLeftWithPanel="True"
RelativePanel.LeftOf="CloseButton"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind Title, Mode=OneWay}" />
<Button
Text="{x:Bind TabTitle, Mode=OneWay}"
TextTrimming="CharacterEllipsis"
TextWrapping="NoWrap" />
<Grid
x:Name="CloseButton"
Grid.Column="2"
Width="32"
Command="{x:Bind CloseCommand}"
RelativePanel.AlignRightWithPanel="True"
Style="{StaticResource TitleBarButtonStyle}"
ToolTipService.ToolTip="Close">
<Viewbox Width="12" Height="12">
<SymbolIcon Symbol="Cancel" />
</Viewbox>
</Button>
RelativePanel.AlignRightWithPanel="True">
<Button
HorizontalAlignment="Center"
Command="{x:Bind CloseCommand}"
Style="{StaticResource TitleBarButtonStyle}"
ToolTipService.ToolTip="Close"
Visibility="{x:Bind IsSelected, Mode=OneWay, Converter={StaticResource TrueToVisibleConverter}}">
<Viewbox Width="12" Height="12">
<SymbolIcon Symbol="Cancel" />
</Viewbox>
</Button>
<Grid Height="32" Visibility="{x:Bind HasNewOutput, Mode=OneWay, Converter={StaticResource TrueToVisibleConverter}}">
<Viewbox
Width="12"
Height="12"
VerticalAlignment="Center"
ToolTipService.ToolTip="New output">
<FontIcon FontFamily="Segoe MDL2 Assets" Glyph="&#xEA38;" />
</Viewbox>
</Grid>
</Grid>
</RelativePanel>
</DataTemplate>
</ListView.ItemTemplate>
@@ -100,10 +210,111 @@
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="MinWidth" Value="100" />
<Setter Property="MaxWidth" Value="200" />
<Setter Property="MinHeight" Value="32" />
<Setter Property="Padding" Value="0" />
<Setter Property="views:TabBarBackgroundBindingHelper.BackgroundBindingPath" Value="BackgroundTabTheme" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Stretch" />
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
<Setter Property="Foreground" Value="{ThemeResource ListViewItemForeground}" />
<Setter Property="TabNavigation" Value="Local" />
<Setter Property="IsHoldingEnabled" Value="True" />
<Setter Property="AllowDrop" Value="False" />
<Setter Property="UseSystemFocusVisuals" Value="True" />
<Setter Property="FocusVisualMargin" Value="0" />
<Setter Property="FocusVisualPrimaryBrush" Value="{ThemeResource ListViewItemFocusVisualPrimaryBrush}" />
<Setter Property="FocusVisualPrimaryThickness" Value="2" />
<Setter Property="FocusVisualSecondaryBrush" Value="{ThemeResource ListViewItemFocusVisualSecondaryBrush}" />
<Setter Property="FocusVisualSecondaryThickness" Value="1" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<ListViewItemPresenter
x:Name="Root"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
CheckBoxBrush="{ThemeResource ListViewItemCheckBoxBrush}"
CheckBrush="{ThemeResource ListViewItemCheckBrush}"
CheckMode="{ThemeResource ListViewItemCheckMode}"
ContentMargin="{TemplateBinding Padding}"
ContentTransitions="{TemplateBinding ContentTransitions}"
Control.IsTemplateFocusTarget="True"
DisabledOpacity="{ThemeResource ListViewItemDisabledThemeOpacity}"
DragBackground="{ThemeResource ListViewItemDragBackground}"
DragForeground="{ThemeResource ListViewItemDragForeground}"
DragOpacity="{ThemeResource ListViewItemDragThemeOpacity}"
FocusBorderBrush="{ThemeResource ListViewItemFocusBorderBrush}"
FocusSecondaryBorderBrush="{ThemeResource ListViewItemFocusSecondaryBorderBrush}"
FocusVisualMargin="{TemplateBinding FocusVisualMargin}"
FocusVisualPrimaryBrush="{TemplateBinding FocusVisualPrimaryBrush}"
FocusVisualPrimaryThickness="{TemplateBinding FocusVisualPrimaryThickness}"
FocusVisualSecondaryBrush="{TemplateBinding FocusVisualSecondaryBrush}"
FocusVisualSecondaryThickness="{TemplateBinding FocusVisualSecondaryThickness}"
PlaceholderBackground="{ThemeResource ListViewItemPlaceholderBackground}"
PointerOverBackground="{Binding TabTheme, Mode=OneWay, Converter={StaticResource TabColorFallbackConverter}, ConverterParameter=BackgroundPointerOver}"
PointerOverForeground="{ThemeResource ListViewItemForegroundPointerOver}"
PressedBackground="{Binding TabTheme, Mode=OneWay, Converter={StaticResource TabColorFallbackConverter}, ConverterParameter=BackgroundPressed}"
ReorderHintOffset="{ThemeResource ListViewItemReorderHintThemeOffset}"
RevealBackground="{ThemeResource ListViewItemRevealBackground}"
RevealBorderBrush="{ThemeResource ListViewItemRevealBorderBrush}"
RevealBorderThickness="{ThemeResource ListViewItemRevealBorderThemeThickness}"
SelectedBackground="{Binding TabTheme, Mode=OneWay, Converter={StaticResource TabColorFallbackConverter}, ConverterParameter=BackgroundSelected}"
SelectedForeground="{ThemeResource ListViewItemForegroundSelected}"
SelectedPointerOverBackground="{Binding TabTheme, Mode=OneWay, Converter={StaticResource TabColorFallbackConverter}, ConverterParameter=BackgroundSelectedPointerOver}"
SelectedPressedBackground="{Binding TabTheme, Mode=OneWay, Converter={StaticResource TabColorFallbackConverter}, ConverterParameter=BackgroundSelectedPressed}"
SelectionCheckMarkVisualEnabled="{ThemeResource ListViewItemSelectionCheckMarkVisualEnabled}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Selected" />
<VisualState x:Name="PointerOver">
<VisualState.Setters>
<Setter Target="Root.(RevealBrush.State)" Value="PointerOver" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="PointerOverSelected">
<VisualState.Setters>
<Setter Target="Root.(RevealBrush.State)" Value="PointerOver" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="PointerOverPressed">
<VisualState.Setters>
<Setter Target="Root.(RevealBrush.State)" Value="Pressed" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Pressed">
<VisualState.Setters>
<Setter Target="Root.(RevealBrush.State)" Value="Pressed" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="PressedSelected">
<VisualState.Setters>
<Setter Target="Root.(RevealBrush.State)" Value="Pressed" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="DisabledStates">
<VisualState x:Name="Enabled" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Target="Root.RevealBorderThickness" Value="0" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</ListViewItemPresenter>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemsPanel>
@@ -0,0 +1,46 @@
using FluentTerminal.Models;
using FluentTerminal.Models.Enums;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Data;
namespace FluentTerminal.App.Views
{
/// <summary>
/// see: https://stackoverflow.com/questions/33573929/uwp-binding-in-style-setter-not-working
/// </summary>
public class TabBarBackgroundBindingHelper
{
public static readonly DependencyProperty BackgroundBindingPathProperty =
DependencyProperty.RegisterAttached("BackgroundBindingPath", typeof(string), typeof(TabBarBackgroundBindingHelper), new PropertyMetadata(null, BindingPathPropertyChanged));
public static string GetBackgroundBindingPath(DependencyObject obj)
{
return (string)obj.GetValue(BackgroundBindingPathProperty);
}
public static void SetBackgroundBindingPath(DependencyObject obj, string value)
{
obj.SetValue(BackgroundBindingPathProperty, value);
}
private static void BindingPathPropertyChanged(DependencyObject obj,
DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is string propertyPath)
{
var binding = new Binding
{
Path = new PropertyPath($"Content.{propertyPath}"),
Mode = BindingMode.OneWay,
RelativeSource = new RelativeSource
{
Mode = RelativeSourceMode.Self
},
Converter = (IValueConverter)Application.Current.Resources["TabColorFallbackConverter"],
ConverterParameter = TabThemeKey.Background.ToString()
};
BindingOperations.SetBinding(obj, ListViewItem.BackgroundProperty, binding);
}
}
}
}
@@ -0,0 +1,26 @@
using FluentTerminal.Models;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace FluentTerminal.App.Views
{
public class TabThemeTemplateSelector : DataTemplateSelector
{
public DataTemplate DefaultTemplate { get; set; }
public DataTemplate ColoredTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
return SelectTemplateCore(item);
}
protected override DataTemplate SelectTemplateCore(object item)
{
if (item is TabTheme tabTheme)
{
return tabTheme.Id == 0 ? DefaultTemplate : ColoredTemplate;
}
return null;
}
}
}
@@ -9,12 +9,13 @@
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}">
<Button.Background>
<SolidColorBrush Color="{x:Bind SelectedColor, Mode=OneWay}" />
</Button.Background>
<!--<Button.Background>
<SolidColorBrush Color="{x:Bind VisibleColor, Mode=OneWay}" />
</Button.Background>-->
<Button.Flyout>
<Flyout>
<StackPanel>
@@ -1,11 +1,14 @@
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Controls;
namespace FluentTerminal.App.Views
{
public sealed partial class TerminalColorPicker : UserControl
{
private bool _colorIsNull;
public static readonly DependencyProperty ColorNameProperty =
DependencyProperty.Register(nameof(ColorName), typeof(string), typeof(TerminalColorPicker), new PropertyMetadata(null));
@@ -16,14 +19,25 @@ namespace FluentTerminal.App.Views
DependencyProperty.Register(nameof(IsAlphaEnabled), typeof(bool), typeof(TerminalColorPicker), new PropertyMetadata(false));
public static readonly DependencyProperty SelectedColorProperty =
DependencyProperty.Register(nameof(SelectedColor), typeof(Color), typeof(TerminalColorPicker), new PropertyMetadata(Colors.Transparent, (s, e) =>
DependencyProperty.Register(nameof(SelectedColor), typeof(Color?), typeof(TerminalColorPicker), new PropertyMetadata(Colors.Transparent, (s, e) =>
{
if (s is TerminalColorPicker terminalColorPicker)
{
terminalColorPicker.colorPicker.Color = (Color)e.NewValue;
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
if (((Color?)e.NewValue) != null)
{
terminalColorPicker.colorPicker.Color = (Color)e.NewValue;
}
}
}));
public static readonly DependencyProperty ButtonBackgroundProperty =
DependencyProperty.Register(nameof(ButtonBackground), typeof(Brush), typeof(TerminalColorPicker), new PropertyMetadata(false));
public TerminalColorPicker()
{
InitializeComponent();
@@ -48,10 +62,30 @@ namespace FluentTerminal.App.Views
set { SetValue(IsAlphaEnabledProperty, value); }
}
public Color SelectedColor
public Color? SelectedColor
{
get { return (Color)GetValue(SelectedColorProperty); }
set { SetValue(SelectedColorProperty, value); }
get { return (Color?)GetValue(SelectedColorProperty); }
set
{
SetValue(SelectedColorProperty, value);
SetValue(ButtonBackgroundProperty, value);
}
}
public Brush ButtonBackground
{
get
{
if (SelectedColor == null)
{
return (Brush)Application.Current.Resources["MissingColorSelectionBrush"];
}
else
{
return new SolidColorBrush((Color)GetValue(SelectedColorProperty));
}
}
set { }
}
private void ColorPicker_ColorChanged(ColorPicker sender, ColorChangedEventArgs args)
@@ -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;
}
}
}
+2 -2
View File
@@ -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}" />
+29 -251
View File
@@ -1,241 +1,67 @@
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();
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 +73,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);
}
}
}
Loaded 100 of 164 files, more files were not shown because too many files have changed in this diff. Show more