├── scfelogo.png ├── slablogo.png ├── SCFE ├── SCFE │ ├── scfelogo.ico │ ├── FileTransferMode.cs │ ├── TaskResult.cs │ ├── FileException.cs │ ├── ScfeExtension.cs │ ├── FileChangedEventArgs.cs │ ├── FileColorScheme.cs │ ├── SCFE.csproj │ ├── FolderViewChangedEventArgs.cs │ ├── SwappableAddinHierDic.cs │ ├── SCFEActions.cs │ ├── ScfeTable.cs │ ├── ViuExampleTable.cs │ ├── FileWatchExtension.cs │ ├── ManagedTask.cs │ ├── ViuDemo.cs │ ├── FileOptionsPanel.cs │ ├── NavigationMode.cs │ ├── ComExtension.cs │ └── Program.cs ├── Viu │ ├── Program.cs │ ├── IActionable.cs │ ├── Viu.csproj │ ├── EventThreadManager.cs │ ├── ICursorFocusable.cs │ ├── Table │ │ ├── ListActionEventArgs.cs │ │ ├── ColumnType.cs │ │ ├── IndicatorColumnType.cs │ │ ├── BasicColumnType.cs │ │ └── MultistateColumnType.cs │ ├── Dimensions.cs │ ├── IFocusable.cs │ ├── Point.cs │ ├── LineStyle.cs │ ├── Components │ │ ├── Separator.cs │ │ ├── Container.cs │ │ ├── Button.cs │ │ ├── BoxContainer.cs │ │ ├── Component.cs │ │ ├── TextComponent.cs │ │ ├── Parent.cs │ │ ├── TextField.cs │ │ └── ConsoleParent.cs │ ├── Utils.cs │ ├── ActionEventArgs.cs │ ├── BaseHierarchicalDictionary.cs │ ├── AbstractHierarchicalDictionary.cs │ ├── KeyStroke.cs │ ├── Strategy │ │ ├── SwitcherStrategy.cs │ │ ├── FlowStrategy.cs │ │ ├── LineStrategy.cs │ │ └── LayoutStrategy.cs │ ├── Constants.cs │ ├── GraphicsContext.cs │ └── ConsoleGraphicsContext.cs └── SCFE.sln ├── handed ├── specs │ ├── include │ │ ├── scfelogo.png │ │ └── slablogo.png │ └── generate.sh ├── defense1 │ ├── include │ │ ├── scfelogo.png │ │ ├── slablogo.png │ │ ├── prototype.png │ │ ├── ss_layout_big.PNG │ │ ├── ss_table_long.png │ │ ├── ss_layout_small.PNG │ │ ├── ss_table_short.png │ │ ├── inputmap_outputmap.png │ │ ├── layoutstrategies.jpg │ │ └── layoutstrategies.png │ ├── outline.md │ ├── generate.sh │ └── outline.tex ├── defense2 │ ├── include │ │ ├── scfelogo.png │ │ ├── slablogo.png │ │ ├── ss_app.png │ │ ├── ss_mac.png │ │ ├── ss_sea.png │ │ ├── ss_diropt.png │ │ └── ss_hidden.png │ └── generate.sh ├── finalrep │ ├── include │ │ ├── scfelogo.png │ │ ├── slablogo.png │ │ ├── ss_app.png │ │ ├── ss_com.png │ │ ├── ss_git.png │ │ ├── ss_mac.png │ │ ├── ss_sea.png │ │ ├── prototype.png │ │ ├── inputmap_outputmap.png │ │ └── layoutstrategies.jpg │ └── generate.sh └── minimanual │ ├── include │ ├── scfelogo.png │ └── slablogo.png │ ├── generate.sh │ └── minimanual.md ├── .gitignore ├── HEADER ├── installer.nsi └── README.md /scfelogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utybo/scfe/HEAD/scfelogo.png -------------------------------------------------------------------------------- /slablogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utybo/scfe/HEAD/slablogo.png -------------------------------------------------------------------------------- /SCFE/SCFE/scfelogo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utybo/scfe/HEAD/SCFE/SCFE/scfelogo.ico -------------------------------------------------------------------------------- /handed/specs/include/scfelogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utybo/scfe/HEAD/handed/specs/include/scfelogo.png -------------------------------------------------------------------------------- /handed/specs/include/slablogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utybo/scfe/HEAD/handed/specs/include/slablogo.png -------------------------------------------------------------------------------- /handed/defense1/include/scfelogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utybo/scfe/HEAD/handed/defense1/include/scfelogo.png -------------------------------------------------------------------------------- /handed/defense1/include/slablogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utybo/scfe/HEAD/handed/defense1/include/slablogo.png -------------------------------------------------------------------------------- /handed/defense2/include/scfelogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utybo/scfe/HEAD/handed/defense2/include/scfelogo.png -------------------------------------------------------------------------------- /handed/defense2/include/slablogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utybo/scfe/HEAD/handed/defense2/include/slablogo.png -------------------------------------------------------------------------------- /handed/defense2/include/ss_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utybo/scfe/HEAD/handed/defense2/include/ss_app.png -------------------------------------------------------------------------------- /handed/defense2/include/ss_mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utybo/scfe/HEAD/handed/defense2/include/ss_mac.png -------------------------------------------------------------------------------- /handed/defense2/include/ss_sea.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utybo/scfe/HEAD/handed/defense2/include/ss_sea.png -------------------------------------------------------------------------------- /handed/finalrep/include/scfelogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utybo/scfe/HEAD/handed/finalrep/include/scfelogo.png -------------------------------------------------------------------------------- /handed/finalrep/include/slablogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utybo/scfe/HEAD/handed/finalrep/include/slablogo.png -------------------------------------------------------------------------------- /handed/finalrep/include/ss_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utybo/scfe/HEAD/handed/finalrep/include/ss_app.png -------------------------------------------------------------------------------- /handed/finalrep/include/ss_com.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utybo/scfe/HEAD/handed/finalrep/include/ss_com.png -------------------------------------------------------------------------------- /handed/finalrep/include/ss_git.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utybo/scfe/HEAD/handed/finalrep/include/ss_git.png -------------------------------------------------------------------------------- /handed/finalrep/include/ss_mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utybo/scfe/HEAD/handed/finalrep/include/ss_mac.png -------------------------------------------------------------------------------- /handed/finalrep/include/ss_sea.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utybo/scfe/HEAD/handed/finalrep/include/ss_sea.png -------------------------------------------------------------------------------- /handed/defense1/include/prototype.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utybo/scfe/HEAD/handed/defense1/include/prototype.png -------------------------------------------------------------------------------- /handed/defense2/include/ss_diropt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utybo/scfe/HEAD/handed/defense2/include/ss_diropt.png -------------------------------------------------------------------------------- /handed/defense2/include/ss_hidden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utybo/scfe/HEAD/handed/defense2/include/ss_hidden.png -------------------------------------------------------------------------------- /handed/finalrep/include/prototype.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utybo/scfe/HEAD/handed/finalrep/include/prototype.png -------------------------------------------------------------------------------- /handed/minimanual/include/scfelogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utybo/scfe/HEAD/handed/minimanual/include/scfelogo.png -------------------------------------------------------------------------------- /handed/minimanual/include/slablogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utybo/scfe/HEAD/handed/minimanual/include/slablogo.png -------------------------------------------------------------------------------- /handed/defense1/include/ss_layout_big.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utybo/scfe/HEAD/handed/defense1/include/ss_layout_big.PNG -------------------------------------------------------------------------------- /handed/defense1/include/ss_table_long.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utybo/scfe/HEAD/handed/defense1/include/ss_table_long.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | build 3 | bin 4 | obj 5 | .DS_Store 6 | .idea 7 | *.user 8 | packages 9 | .vs 10 | app.config 11 | .vscode -------------------------------------------------------------------------------- /handed/defense1/include/ss_layout_small.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utybo/scfe/HEAD/handed/defense1/include/ss_layout_small.PNG -------------------------------------------------------------------------------- /handed/defense1/include/ss_table_short.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utybo/scfe/HEAD/handed/defense1/include/ss_table_short.png -------------------------------------------------------------------------------- /handed/defense1/include/inputmap_outputmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utybo/scfe/HEAD/handed/defense1/include/inputmap_outputmap.png -------------------------------------------------------------------------------- /handed/defense1/include/layoutstrategies.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utybo/scfe/HEAD/handed/defense1/include/layoutstrategies.jpg -------------------------------------------------------------------------------- /handed/defense1/include/layoutstrategies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utybo/scfe/HEAD/handed/defense1/include/layoutstrategies.png -------------------------------------------------------------------------------- /handed/finalrep/include/inputmap_outputmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utybo/scfe/HEAD/handed/finalrep/include/inputmap_outputmap.png -------------------------------------------------------------------------------- /handed/finalrep/include/layoutstrategies.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utybo/scfe/HEAD/handed/finalrep/include/layoutstrategies.jpg -------------------------------------------------------------------------------- /HEADER: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | -------------------------------------------------------------------------------- /handed/defense1/outline.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "SCFE: First Presentation (Outline)" 3 | --- 4 | 5 | * Presentation of the project 6 | * Tasks overview 7 | * Individual participation 8 | * Matthieu Stombellini 9 | * Mathieu Rivier 10 | * François Soulier 11 | * Rakhmatullo Rashidov 12 | * Demo of the prototype 13 | * Goals for the next presentation and conclusion -------------------------------------------------------------------------------- /handed/specs/generate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo "Cleaning" 3 | # Clean build directory 4 | rm -r build 5 | echo "Building" 6 | # Make a new one 7 | mkdir build 8 | # Markdown --> LaTeX 9 | pandoc specs.md --template ../template.tex -s -o main.tex 10 | # Move the main.tex to the build area. No idea why pandoc can't put it there 11 | # directly. 12 | mv main.tex build/main.tex 13 | # Also include additional files (images...) 14 | cp include/* build/ 15 | echo "Done." 16 | -------------------------------------------------------------------------------- /SCFE/SCFE/FileTransferMode.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | namespace SCFE 10 | { 11 | public enum FileTransferMode 12 | { 13 | Copy, 14 | Cut 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /handed/defense1/generate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo "Cleaning" 3 | # Clean build directory 4 | rm -r build 5 | echo "Building" 6 | # Make a new one 7 | mkdir build 8 | # Markdown --> LaTeX 9 | pandoc report_1.md --from markdown+implicit_figures+link_attributes --template ../template.tex -s -o main.tex 10 | # Move the main.tex to the build area. No idea why pandoc can't put it there 11 | # directly. 12 | mv main.tex build/main.tex 13 | # Also include additional files (images...) 14 | cp include/* build/ 15 | echo "Done." 16 | -------------------------------------------------------------------------------- /handed/defense2/generate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo "Cleaning" 3 | # Clean build directory 4 | rm -r build 5 | echo "Building" 6 | # Make a new one 7 | mkdir build 8 | # Markdown --> LaTeX 9 | pandoc report_2.md --from markdown+implicit_figures+link_attributes --template ../template.tex -s -o main.tex 10 | # Move the main.tex to the build area. No idea why pandoc can't put it there 11 | # directly. 12 | mv main.tex build/main.tex 13 | # Also include additional files (images...) 14 | cp include/* build/ 15 | echo "Done." 16 | -------------------------------------------------------------------------------- /handed/minimanual/generate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo "Cleaning" 3 | # Clean build directory 4 | rm -r build 5 | echo "Building" 6 | # Make a new one 7 | mkdir build 8 | # Markdown --> LaTeX 9 | pandoc minimanual.md --from markdown+implicit_figures+link_attributes --template ../template.tex -s -o main.tex 10 | # Move the main.tex to the build area. No idea why pandoc can't put it there 11 | # directly. 12 | mv main.tex build/main.tex 13 | # Also include additional files (images...) 14 | cp include/* build/ 15 | echo "Done." 16 | -------------------------------------------------------------------------------- /SCFE/Viu/Program.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | namespace Viu 10 | { 11 | internal class Program 12 | { 13 | public static void Main(string[] args) 14 | { 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /SCFE/Viu/IActionable.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using System; 10 | 11 | namespace Viu 12 | { 13 | public interface IActionable : IFocusable 14 | { 15 | event EventHandler ActionOnComponent; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /SCFE/Viu/Viu.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | true 7 | win-x64;linux-x64;osx-x64 8 | 7.1 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /SCFE/Viu/EventThreadManager.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using System; 10 | 11 | namespace Viu 12 | { 13 | public interface IEventThreadManager 14 | { 15 | void DoGraphicsLater(Action action); 16 | 17 | void DoGraphicsAndWait(Action action); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /handed/finalrep/generate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo "Cleaning" 3 | # Clean build directory 4 | rm -r build 5 | echo "Building" 6 | # Make a new one 7 | mkdir build 8 | # Markdown --> LaTeX 9 | pandoc finalreport.md --from markdown+implicit_figures+link_attributes --template ../template.tex -s -o main.tex 10 | pandoc finalreport-fr.md --from markdown+implicit_figures+link_attributes --template ../template.tex -s -o mainfr.tex 11 | # Move the main.tex to the build area. No idea why pandoc can't put it there 12 | # directly. 13 | mv main.tex build/main.tex 14 | mv mainfr.tex build/mainfr.tex 15 | # Also include additional files (images...) 16 | cp include/* build/ 17 | echo "Done." 18 | -------------------------------------------------------------------------------- /SCFE/SCFE/TaskResult.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | namespace SCFE 10 | { 11 | public class TaskResult 12 | { 13 | public TaskResult(bool success, string message = "") 14 | { 15 | Message = message; 16 | Success = success; 17 | } 18 | 19 | public bool Success { get; } 20 | public string Message { get; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SCFE/SCFE/FileException.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using System; 10 | using System.IO; 11 | 12 | namespace SCFE 13 | { 14 | public class FileException : IOException 15 | { 16 | public FileException() 17 | { 18 | } 19 | 20 | public FileException(string message) : base(message) 21 | { 22 | } 23 | 24 | public FileException(string message, Exception innerException) : base(message, innerException) 25 | { 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /SCFE/SCFE/ScfeExtension.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using System; 10 | using System.Collections.Generic; 11 | using Viu; 12 | using Viu.Table; 13 | 14 | namespace SCFE 15 | { 16 | public interface IScfeExtension 17 | { 18 | IEnumerable> GetColumns(); 19 | 20 | Dictionary> GetActions(); 21 | 22 | IEnumerable GetCurrDirOptions(); 23 | 24 | IEnumerable GetFilesOptions(); 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /SCFE/SCFE/FileChangedEventArgs.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using System.Collections.Generic; 10 | using System.Collections.Immutable; 11 | using Viu; 12 | 13 | namespace SCFE 14 | { 15 | public class FileChangedEventArgs 16 | { 17 | public ImmutableList Files { get; } 18 | public GraphicsContext Graphics { get; } 19 | 20 | public FileChangedEventArgs(IEnumerable files, GraphicsContext graphics) 21 | { 22 | Files = files.ToImmutableList(); 23 | Graphics = graphics; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /SCFE/Viu/ICursorFocusable.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | namespace Viu 10 | { 11 | public interface ICursorFocusable : IFocusable 12 | { 13 | /// 14 | /// Called after all print events, including when the component is NOT focused. Sets the cursor to the position where 15 | /// it 16 | /// needs to be displayed, as well as making it visible if necessary or adjusting any other thing about the component. 17 | /// 18 | void UpdateCursorState(GraphicsContext g); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /SCFE/SCFE/FileColorScheme.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using System; 10 | 11 | namespace SCFE 12 | { 13 | public abstract class FileColorScheme 14 | { 15 | public abstract ConsoleColor? GetColorForFile(File file); 16 | } 17 | 18 | public class DiscriminateDirectoriesAndHiddenScheme : FileColorScheme 19 | { 20 | public override ConsoleColor? GetColorForFile(File file) 21 | { 22 | if (file.IsHidden()) return file.IsFolder() ? ConsoleColor.DarkYellow : ConsoleColor.DarkGray; 23 | return file.IsFolder() ? ConsoleColor.Green : (ConsoleColor?) null; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /SCFE/SCFE/SCFE.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | scfelogo.ico 6 | true 7 | win-x64;linux-x64;osx-x64 8 | 7.1 9 | netcoreapp3.1 10 | Salamanders' Lab 11 | Salamanders' Lab 12 | SCFE 13 | 1.0.1 14 | SCFE 15 | 1.0.1 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /SCFE/Viu/Table/ListActionEventArgs.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using System; 10 | 11 | namespace Viu.Table 12 | { 13 | public class ListActionEventArgs : EventArgs 14 | { 15 | public ListActionEventArgs(IActionable component, ConsoleKeyInfo? sourceKeys, T item, GraphicsContext g) 16 | { 17 | Component = component; 18 | KeySource = sourceKeys; 19 | Item = item; 20 | Graphics = g; 21 | } 22 | 23 | public IActionable Component { get; } 24 | 25 | public ConsoleKeyInfo? KeySource { get; } 26 | 27 | public T Item { get; } 28 | 29 | public GraphicsContext Graphics { get; } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /SCFE/Viu/Dimensions.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using Viu.Components; 10 | 11 | namespace Viu 12 | { 13 | public struct Dimensions 14 | { 15 | public int Width { get; set; } 16 | public int Height { get; set; } 17 | 18 | public Dimensions(int width, int height) 19 | { 20 | Width = width; 21 | Height = height; 22 | } 23 | 24 | public Dimensions Add(int addW, int addH) 25 | { 26 | return new Dimensions(addW + Width, addH + Height); 27 | } 28 | 29 | public static Dimensions DimensionsOf(Component c) 30 | { 31 | return new Dimensions(c.Width, c.Height); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /SCFE/Viu/IFocusable.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using System; 10 | 11 | namespace Viu 12 | { 13 | public interface IFocusable 14 | { 15 | /// 16 | /// Handle a key press. This is only called if the IsFocusable method returned true upon a user input. 17 | /// 18 | /// The key that was pressed 19 | /// 20 | /// Whether the key event was consumed ("accepted") or not. If not it will be passed to a focusable child. 21 | bool AcceptInput(ConsoleKeyInfo keyPressed, GraphicsContext g); 22 | 23 | bool IsFocusable(); 24 | 25 | void SetFocused(bool focused, GraphicsContext g); 26 | 27 | bool IsFocused(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /SCFE/SCFE/FolderViewChangedEventArgs.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using JetBrains.Annotations; 10 | using Viu; 11 | 12 | namespace SCFE 13 | { 14 | public class FolderViewChangedEventArgs 15 | { 16 | public bool FolderChanged { get; } 17 | 18 | [CanBeNull] 19 | public File OldFolder { get; } 20 | 21 | [NotNull] 22 | public File NewFolder { get; } 23 | 24 | [CanBeNull] 25 | public GraphicsContext Graphics { get; } 26 | 27 | public FolderViewChangedEventArgs(bool folderChanged, File oldFolder, File newFolder, GraphicsContext graphics) 28 | { 29 | FolderChanged = folderChanged; 30 | OldFolder = oldFolder; 31 | NewFolder = newFolder; 32 | Graphics = graphics; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /SCFE/Viu/Point.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using Viu.Components; 10 | 11 | namespace Viu 12 | { 13 | public struct Point 14 | { 15 | public int X { get; set; } 16 | 17 | public int Y { get; set; } 18 | 19 | public Point(int x, int y) 20 | { 21 | X = x; 22 | Y = y; 23 | } 24 | 25 | public static Point CenterOf(Point origin, Dimensions dimensions) 26 | { 27 | return new Point(origin.X + dimensions.Width / 2, origin.Y + dimensions.Height / 2); 28 | } 29 | 30 | public static Point CenterOf(Component c) 31 | { 32 | return new Point(c.X + c.Width / 2, c.Y + c.Height / 2); 33 | } 34 | 35 | public Point OriginOf(Component c) 36 | { 37 | return new Point(c.X, c.Y); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /SCFE/SCFE.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SCFE", "SCFE\SCFE.csproj", "{D2F7F4E5-C2A7-449B-81AC-86230BEC1247}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Viu", "Viu\Viu.csproj", "{49D213AA-E54E-45FA-889F-254D6416649B}" 6 | EndProject 7 | Global 8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 9 | Debug|Any CPU = Debug|Any CPU 10 | Release|Any CPU = Release|Any CPU 11 | EndGlobalSection 12 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 13 | {D2F7F4E5-C2A7-449B-81AC-86230BEC1247}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 14 | {D2F7F4E5-C2A7-449B-81AC-86230BEC1247}.Debug|Any CPU.Build.0 = Debug|Any CPU 15 | {D2F7F4E5-C2A7-449B-81AC-86230BEC1247}.Release|Any CPU.ActiveCfg = Release|Any CPU 16 | {D2F7F4E5-C2A7-449B-81AC-86230BEC1247}.Release|Any CPU.Build.0 = Release|Any CPU 17 | {49D213AA-E54E-45FA-889F-254D6416649B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 18 | {49D213AA-E54E-45FA-889F-254D6416649B}.Debug|Any CPU.Build.0 = Debug|Any CPU 19 | {49D213AA-E54E-45FA-889F-254D6416649B}.Release|Any CPU.ActiveCfg = Release|Any CPU 20 | {49D213AA-E54E-45FA-889F-254D6416649B}.Release|Any CPU.Build.0 = Release|Any CPU 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /SCFE/Viu/LineStyle.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | namespace Viu 10 | { 11 | public class LineStyle 12 | { 13 | public static readonly LineStyle Barebones = new LineStyle('-', '|', '+', '+', '+', '+'); 14 | public static readonly LineStyle Simple = new LineStyle('─', '│', '┌', '┐', '└', '┘'); 15 | public static readonly LineStyle Dotted = new LineStyle('┅', '┇', '┌', '┐', '└', '┘'); 16 | 17 | public LineStyle(char horizontal, char vertical, char topLeft, char topRight, char bottomLeft, char bottomRight) 18 | { 19 | Horizontal = horizontal; 20 | Vertical = vertical; 21 | TopLeft = topLeft; 22 | TopRight = topRight; 23 | BottomLeft = bottomLeft; 24 | BottomRight = bottomRight; 25 | } 26 | 27 | public char Horizontal { get; } 28 | public char Vertical { get; } 29 | public char TopLeft { get; } 30 | public char TopRight { get; } 31 | public char BottomLeft { get; } 32 | public char BottomRight { get; } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /SCFE/Viu/Components/Separator.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using System; 10 | 11 | namespace Viu.Components 12 | { 13 | public class Separator : Component 14 | { 15 | public Orientation Orientation { get; set; } = Orientation.Horizontal; 16 | public LineStyle Style { get; set; } = LineStyle.Simple; 17 | public ConsoleColor? Foreground { get; set; } = null; 18 | 19 | public override void Print(GraphicsContext g) 20 | { 21 | if (!Visible) 22 | return; 23 | if (Orientation == Orientation.Vertical) 24 | { 25 | var ch = Style.Vertical; 26 | for (var y = Y; y < Y + Height; y++) g.Write(X, y, ch + "", Foreground, null); 27 | } 28 | else 29 | { 30 | var ch = Style.Horizontal; 31 | g.Write(X, Y, new string(ch, Width), Foreground, null); 32 | } 33 | } 34 | 35 | public override Dimensions ComputeDimensions() 36 | { 37 | return new Dimensions(1, 1); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /SCFE/Viu/Table/ColumnType.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using System.Collections.Generic; 10 | using System.Collections.ObjectModel; 11 | using JetBrains.Annotations; 12 | using Viu.Components; 13 | 14 | namespace Viu.Table 15 | { 16 | public abstract class ColumnType 17 | { 18 | public int Priority { get; set; } 19 | 20 | public int GrowPriority { get; set; } 21 | 22 | public int ShrinkPriority { get; set; } 23 | 24 | [NotNull] 25 | public abstract Component GetRowInformation(T data, int index, int width, bool isFocused, bool isSelected, 26 | TableComponent parent); 27 | 28 | public abstract int[] GetPossibleWidths(ICollection data); 29 | 30 | public abstract int GetMaximumRowHeight(ICollection data); 31 | 32 | public abstract string GetTitle(TableComponent parent, int width); 33 | 34 | public abstract int GetTotalRowHeight(ObservableCollection data); 35 | 36 | public virtual bool IsVisible(IEnumerable enumerable) 37 | { 38 | return true; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /SCFE/Viu/Table/IndicatorColumnType.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using System.Collections.Generic; 10 | using System.Collections.ObjectModel; 11 | using Viu.Components; 12 | 13 | namespace Viu.Table 14 | { 15 | public class IndicatorColumnType : ColumnType 16 | { 17 | public override Component GetRowInformation(T data, int index, int width, bool isFocused, bool isSelected, 18 | TableComponent parent) 19 | { 20 | if (isFocused && isSelected) 21 | return new TextComponent("="); 22 | if (isFocused) 23 | return new TextComponent(">"); 24 | if (isSelected) 25 | return new TextComponent("*"); 26 | return new TextComponent(" "); 27 | } 28 | 29 | public override int[] GetPossibleWidths(ICollection data) 30 | { 31 | return new[] {1}; 32 | } 33 | 34 | public override int GetMaximumRowHeight(ICollection data) 35 | { 36 | return 1; 37 | } 38 | 39 | public override string GetTitle(TableComponent parent, int width) 40 | { 41 | return " "; 42 | } 43 | 44 | public override int GetTotalRowHeight(ObservableCollection data) 45 | { 46 | return data.Count; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /SCFE/Viu/Utils.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Linq; 12 | 13 | namespace Viu 14 | { 15 | public static class Utils 16 | { 17 | /// 18 | /// Check if a key corresponds to an action according to a given input dictionary. The input dictionary should 19 | /// be a compiled InputMap (InputMap.Compile()) 20 | /// 21 | /// A compiled InputMap (InputMap.Compiled()) 22 | /// The key that needs to check 23 | /// The action that we want to see if it is associated with the keyInfo 24 | /// Whether the dictionary contains a match between the keyInfo and the action 25 | public static bool KeyCorresponds(Dictionary dic, ConsoleKeyInfo keyInfo, string action) 26 | { 27 | return dic.Any(kv => 28 | kv.Value == action && kv.Key.Matches(keyInfo)); 29 | } 30 | 31 | /// 32 | /// Return the action name associated with the given keyInfo 33 | /// 34 | /// 35 | /// 36 | /// 37 | public static string GetActionNameForKey(Dictionary dic, ConsoleKeyInfo keyInfo) 38 | { 39 | if (dic.Any(kv => kv.Key.Matches(keyInfo))) return dic.First(kv => kv.Key.Matches(keyInfo)).Value; 40 | 41 | return null; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /installer.nsi: -------------------------------------------------------------------------------- 1 | !include "MUI2.nsh" 2 | !define UNINST_KEY \ 3 | "Software\Microsoft\Windows\CurrentVersion\Uninstall\SCFE" 4 | !define MUI_ICON "SCFE/SCFE/scfelogo.ico" 5 | ManifestSupportedOS Win10 6 | Name "SCFE" 7 | Caption "Salamanders' Console File Explorer" 8 | OutFile "scfe-installer.exe" 9 | RequestExecutionLevel admin 10 | 11 | InstallDir "$PROGRAMFILES64\SCFE" 12 | 13 | AutoCloseWindow false 14 | ShowInstDetails show 15 | 16 | !insertmacro MUI_PAGE_WELCOME 17 | !insertmacro MUI_PAGE_DIRECTORY 18 | !insertmacro MUI_PAGE_INSTFILES 19 | !insertmacro MUI_PAGE_FINISH 20 | 21 | !insertmacro MUI_UNPAGE_WELCOME 22 | !insertmacro MUI_UNPAGE_CONFIRM 23 | !insertmacro MUI_UNPAGE_INSTFILES 24 | !insertmacro MUI_UNPAGE_FINISH 25 | 26 | 27 | !insertmacro MUI_LANGUAGE "English" 28 | 29 | 30 | Section "SCFE" 31 | ; Mark this as compulsory 32 | SectionIn RO 33 | 34 | SetOutPath "$INSTDIR" 35 | ; Add files 36 | File /r "SCFE\SCFE\bin\Release\netcoreapp3.0\win-x64\publish\*" 37 | 38 | ; Write registery stuff about the uninstaller and write the uninstaller itself 39 | WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "SCFE" 40 | WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "Salamanders' Lab" 41 | WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" '"$INSTDIR\uninstall.exe"' 42 | WriteRegDWORD HKLM "${UNINST_KEY}" "NoModify" 1 43 | WriteRegDWORD HKLM "${UNINST_KEY}" "NoRepair" 1 44 | WriteUninstaller "$INSTDIR\uninstall.exe" 45 | 46 | ; Add shortcuts 47 | CreateDirectory "$SMPROGRAMS\SCFE" 48 | CreateShortCut "$SMPROGRAMS\SCFE\SCFE.lnk" "$INSTDIR\SCFE.exe" \ 49 | "%userprofile%" "$INSTDIR\SCFE.exe" 0 50 | 51 | CreateShortCut "$SMPROGRAMS\SCFE\Uninstall SCFE.lnk" "$INSTDIR\uninstall.exe" \ 52 | "" "$INSTDIR\uninstall.exe" 0 53 | SectionEnd 54 | 55 | Section "Uninstall" 56 | DeleteRegKey HKLM "${UNINST_KEY}" 57 | 58 | RMDir /r /REBOOTOK "$INSTDIR" 59 | RMDir /r /REBOOTOK "$SMPROGRAMS\SCFE" 60 | SectionEnd 61 | -------------------------------------------------------------------------------- /SCFE/Viu/ActionEventArgs.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using System; 10 | using JetBrains.Annotations; 11 | using Viu.Components; 12 | 13 | namespace Viu 14 | { 15 | /// 16 | /// Arguments passed for all events related to actions 17 | /// 18 | public class ActionEventArgs : EventArgs 19 | { 20 | /// 21 | /// Create the arguments for an action event 22 | /// 23 | /// The component on which the action happened 24 | /// The keys that triggered the action (can be null) 25 | /// The GraphicsContext subscribers should use to print components 26 | public ActionEventArgs([CanBeNull] Component component, [CanBeNull] ConsoleKeyInfo? sourceKeys, 27 | [NotNull] GraphicsContext g) 28 | { 29 | Component = component; 30 | KeySource = sourceKeys; 31 | Graphics = g; 32 | } 33 | 34 | /// 35 | /// The component on which the action was performed 36 | /// 37 | [CanBeNull] 38 | public Component Component { get; } 39 | 40 | /// 41 | /// The source key combination that triggered the action. Can be null if the key combination is not available or 42 | /// unknown 43 | /// 44 | [CanBeNull] 45 | public ConsoleKeyInfo? KeySource { get; } 46 | 47 | /// 48 | /// The GraphicsContext this action should act on. 49 | /// 50 | [NotNull] 51 | public GraphicsContext Graphics { get; } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /SCFE/Viu/Components/Container.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using System.Collections.Generic; 10 | 11 | namespace Viu.Components 12 | { 13 | /// 14 | /// 15 | /// A Container is a Component that has zero or more children Components, which have to be validated and printed. 16 | /// Validation is a process that involves setting all of the X, Y, Width and Height values of the container's 17 | /// children to their correct value. 18 | /// The list of components is not directly accessible, and this class is an abstraction layer for all kinds of 19 | /// containers. 20 | /// 21 | public abstract class Container : Component 22 | { 23 | protected readonly List Components = new List(); 24 | 25 | /// 26 | /// If true, the entire area "below" this component is cleared before printing the components. 27 | /// Set to false if you wish to use some kind of transparency. Do know that this may lead to incoherent states. 28 | /// 29 | public bool ClearAreaBeforePrint { get; set; } 30 | 31 | public abstract void Validate(); 32 | 33 | public override void Print(GraphicsContext g) 34 | { 35 | if (!Visible) 36 | return; 37 | 38 | // Clear the area first 39 | if (ClearAreaBeforePrint) 40 | for (var i = Y; i < Height + Y; i++) 41 | { 42 | var s = new string(' ', Width); 43 | g.Write(X, i, s); 44 | } 45 | 46 | foreach (var c in Components) 47 | if (c.Visible) 48 | c.Print(g); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /SCFE/SCFE/SwappableAddinHierDic.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using System.Collections.Generic; 10 | using Viu; 11 | 12 | namespace SCFE 13 | { 14 | public class SwappableAddinHierDic : AbstractHierarchicalDictionary 15 | { 16 | private readonly AbstractHierarchicalDictionary _backing; 17 | 18 | public SwappableAddinHierDic(AbstractHierarchicalDictionary backingHierarchicalDictionary) 19 | { 20 | _backing = backingHierarchicalDictionary; 21 | } 22 | 23 | public Dictionary AddInDictionary { get; set; } 24 | 25 | public override AbstractHierarchicalDictionary Parent 26 | { 27 | get => _backing.Parent; 28 | set => _backing.Parent = value; 29 | } 30 | 31 | public override TV Get(TK key) 32 | { 33 | if (AddInDictionary.ContainsKey(key)) 34 | return AddInDictionary[key]; 35 | return _backing.Get(key); 36 | } 37 | 38 | public override Dictionary Compile() 39 | { 40 | var dic = new Dictionary(); 41 | if (AddInDictionary != null) 42 | foreach (var (key, value) in AddInDictionary) 43 | if (dic.ContainsKey(key)) 44 | dic[key] = value; 45 | else 46 | dic.Add(key, value); 47 | 48 | var comp = _backing.Compile(); 49 | foreach (var (key, value) in comp) 50 | if (!dic.ContainsKey(key)) 51 | dic.Add(key, value); 52 | 53 | return dic; 54 | } 55 | 56 | public override void Put(TK key, TV value) 57 | { 58 | _backing.Put(key, value); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /SCFE/Viu/BaseHierarchicalDictionary.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using System.Collections.Generic; 10 | 11 | namespace Viu 12 | { 13 | /// 14 | /// Basic implementation of a HierarchicalDictionary that uses a Dictionary for storing its values 15 | /// 16 | /// The type of the keys 17 | /// The type of the values 18 | public class BaseHierarchicalDictionary : AbstractHierarchicalDictionary 19 | { 20 | public override AbstractHierarchicalDictionary Parent { get; set; } 21 | 22 | /// 23 | /// The backing dictionary used to store the values of this hierarchical dictionary 24 | /// 25 | private Dictionary BackingDictionary { get; } = new Dictionary(); 26 | 27 | public override Dictionary Compile() 28 | { 29 | var dic = Parent?.Compile() ?? new Dictionary(); 30 | foreach (var kv in BackingDictionary) 31 | if (dic.ContainsKey(kv.Key)) 32 | dic[kv.Key] = kv.Value; 33 | else 34 | dic.Add(kv.Key, kv.Value); 35 | 36 | return dic; 37 | } 38 | 39 | public override TV Get(TK key) 40 | { 41 | if (BackingDictionary.ContainsKey(key)) 42 | return BackingDictionary[key]; 43 | 44 | return Parent != null ? Parent.Get(key) : default; 45 | } 46 | 47 | public override void Put(TK key, TV value) 48 | { 49 | if (BackingDictionary.ContainsKey(key)) 50 | BackingDictionary[key] = value; 51 | else 52 | BackingDictionary.Add(key, value); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /SCFE/SCFE/SCFEActions.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | namespace SCFE 10 | { 11 | public static class ScfeActions 12 | { 13 | public const string CopyFile = "file_copy"; 14 | 15 | public const string CutFile = "file_cut"; 16 | 17 | public const string PasteFile = "file_paste"; 18 | 19 | public const string DeleteFile = "file_delete"; 20 | 21 | public const string GitStage = "git_addfile"; 22 | 23 | public const string GitUnstage = "git_removefile"; 24 | 25 | public const string GitInit = "git_initrepo"; 26 | 27 | public const string ChangeMode = "change_mode"; 28 | 29 | public const string GoDownFast = "move_downfast"; 30 | 31 | public const string GoUpFast = "move_upfast"; 32 | 33 | public const string ToggleShowHiddenFiles = "show_hidden_files:tggl"; 34 | 35 | public const string CreateFile = "file_create"; 36 | 37 | public const string CreateFolder = "folder_create"; 38 | 39 | public const string GoToFolder = "action_gotofolder"; 40 | 41 | public const string Refresh = "action_refresh"; 42 | 43 | public const string CurrDirOptions = "action_seecurrdiroptions"; 44 | 45 | public const string Rename = "action_rename"; 46 | 47 | public const string SelectAll = "select_all"; 48 | 49 | public const string ToggleSelection = "select_toggle"; 50 | 51 | public const string ChangeSort = "change_sortingmethod"; 52 | 53 | public const string GitCommit = "git_commit"; 54 | 55 | public const string GitPush = "git_push"; 56 | 57 | public const string GitPull = "git_pull"; 58 | 59 | public const string GitClone = "git_clone"; 60 | 61 | public const string ComMode = "change_mode_com"; 62 | 63 | public const string ToggleSortOrder = "toggle_sort_order"; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /SCFE/Viu/Table/BasicColumnType.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Collections.ObjectModel; 12 | using System.Linq; 13 | using Viu.Components; 14 | 15 | namespace Viu.Table 16 | { 17 | public class BasicColumnType : ColumnType 18 | { 19 | private readonly Func _colorGetter; 20 | private readonly Func _infoGetter; 21 | 22 | public BasicColumnType(string title, Func toRowString, Func colorGetter = null) 23 | { 24 | ColumnTitle = title; 25 | _infoGetter = toRowString; 26 | _colorGetter = colorGetter; 27 | } 28 | 29 | private string ColumnTitle { get; } 30 | 31 | public HorizontalAlignment HAlign { get; set; } = HorizontalAlignment.Left; 32 | 33 | public override Component GetRowInformation(T data, int index, int width, bool isFocused, bool isSelected, 34 | TableComponent parent) 35 | { 36 | // Cache the same component so as to avoid recreating lots of components. 37 | return new TextComponent 38 | { 39 | Text = _infoGetter(data), 40 | Background = isFocused ? _colorGetter?.Invoke(data) : null, 41 | Foreground = isFocused ? null : _colorGetter?.Invoke(data), 42 | HAlign = HAlign 43 | }; 44 | } 45 | 46 | public override int[] GetPossibleWidths(ICollection data) 47 | { 48 | var max1 = data.Max(t => _infoGetter(t).Length); 49 | return new[] {Math.Max(ColumnTitle.Length, max1)}; 50 | } 51 | 52 | public override int GetMaximumRowHeight(ICollection data) 53 | { 54 | return 1; 55 | } 56 | 57 | public override string GetTitle(TableComponent parent, int width) 58 | { 59 | return ColumnTitle; 60 | } 61 | 62 | public override int GetTotalRowHeight(ObservableCollection data) 63 | { 64 | return data.Count; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /SCFE/Viu/Components/Button.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using System; 10 | 11 | namespace Viu.Components 12 | { 13 | public class Button : Component, IActionable 14 | { 15 | public Button(string s) 16 | { 17 | Text = s; 18 | } 19 | 20 | public string Text { get; set; } 21 | 22 | public ConsoleColor? Foreground { get; set; } = null; 23 | 24 | public bool Focusable { get; set; } = true; 25 | 26 | public bool Focused { get; private set; } 27 | 28 | public bool AcceptInput(ConsoleKeyInfo keyPressed, GraphicsContext g) 29 | { 30 | var inputs = InputMap.Compile(); 31 | if (Utils.KeyCorresponds(inputs, keyPressed, StandardActionNames.BaseAction)) 32 | { 33 | ActionOnComponent?.Invoke(this, new ActionEventArgs(this, keyPressed, g)); 34 | 35 | return true; 36 | } 37 | 38 | return false; 39 | } 40 | 41 | public bool IsFocusable() 42 | { 43 | return Focusable; 44 | } 45 | 46 | public void SetFocused(bool focused, GraphicsContext g) 47 | { 48 | if (Focused != focused) 49 | { 50 | Focused = focused; 51 | Print(g); 52 | } 53 | } 54 | 55 | public bool IsFocused() 56 | { 57 | return Focused; 58 | } 59 | 60 | public event EventHandler ActionOnComponent; 61 | 62 | public override void Print(GraphicsContext g) 63 | { 64 | if (!Visible) 65 | return; 66 | var s = "[ " + Text + " ]"; 67 | if (Focused) 68 | { 69 | if (Foreground != null) 70 | g.WriteRevert(X, Y, s, Foreground.Value); 71 | else 72 | g.WriteRevert(X, Y, s); 73 | } 74 | else 75 | { 76 | g.Write(X, Y, s, Foreground, null); 77 | } 78 | } 79 | 80 | public override Dimensions ComputeDimensions() 81 | { 82 | return new Dimensions(Text.Length + 4, 1); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /handed/defense1/outline.tex: -------------------------------------------------------------------------------- 1 | \documentclass[]{article} 2 | \usepackage{lmodern} 3 | \usepackage{amssymb,amsmath} 4 | \usepackage{ifxetex,ifluatex} 5 | \usepackage{fixltx2e} % provides \textsubscript 6 | \ifnum 0\ifxetex 1\fi\ifluatex 1\fi=0 % if pdftex 7 | \usepackage[T1]{fontenc} 8 | \usepackage[utf8]{inputenc} 9 | \else % if luatex or xelatex 10 | \ifxetex 11 | \usepackage{mathspec} 12 | \else 13 | \usepackage{fontspec} 14 | \fi 15 | \defaultfontfeatures{Ligatures=TeX,Scale=MatchLowercase} 16 | \fi 17 | % use upquote if available, for straight quotes in verbatim environments 18 | \IfFileExists{upquote.sty}{\usepackage{upquote}}{} 19 | % use microtype if available 20 | \IfFileExists{microtype.sty}{% 21 | \usepackage[]{microtype} 22 | \UseMicrotypeSet[protrusion]{basicmath} % disable protrusion for tt fonts 23 | }{} 24 | \PassOptionsToPackage{hyphens}{url} % url is loaded by hyperref 25 | \usepackage[unicode=true]{hyperref} 26 | \hypersetup{ 27 | pdftitle={SCFE: First Presentation (Outline)}, 28 | pdfborder={0 0 0}, 29 | breaklinks=true} 30 | \urlstyle{same} % don't use monospace font for urls 31 | \IfFileExists{parskip.sty}{% 32 | \usepackage{parskip} 33 | }{% else 34 | \setlength{\parindent}{0pt} 35 | \setlength{\parskip}{6pt plus 2pt minus 1pt} 36 | } 37 | \setlength{\emergencystretch}{3em} % prevent overfull lines 38 | \providecommand{\tightlist}{% 39 | \setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}} 40 | \setcounter{secnumdepth}{0} 41 | % Redefines (sub)paragraphs to behave more like sections 42 | \ifx\paragraph\undefined\else 43 | \let\oldparagraph\paragraph 44 | \renewcommand{\paragraph}[1]{\oldparagraph{#1}\mbox{}} 45 | \fi 46 | \ifx\subparagraph\undefined\else 47 | \let\oldsubparagraph\subparagraph 48 | \renewcommand{\subparagraph}[1]{\oldsubparagraph{#1}\mbox{}} 49 | \fi 50 | 51 | % set default figure placement to htbp 52 | \makeatletter 53 | \def\fps@figure{htbp} 54 | \makeatother 55 | 56 | 57 | \title{SCFE: First Presentation (Outline)} 58 | \date{} 59 | 60 | \begin{document} 61 | \maketitle 62 | 63 | \begin{itemize} 64 | \tightlist 65 | \item 66 | Presentation of the project 67 | \item 68 | Tasks overview 69 | \item 70 | Individual participation 71 | 72 | \begin{itemize} 73 | \tightlist 74 | \item 75 | Matthieu Stombellini 76 | \item 77 | Mathieu Rivier 78 | \item 79 | François Soulier 80 | \item 81 | Rakhmatullo Rashidov 82 | \end{itemize} 83 | \item 84 | Demo of the prototype 85 | \item 86 | Goals for the next presentation and conclusion 87 | \end{itemize} 88 | 89 | \end{document} 90 | -------------------------------------------------------------------------------- /SCFE/SCFE/ScfeTable.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using System; 10 | using Viu; 11 | using Viu.Table; 12 | 13 | namespace SCFE 14 | { 15 | public class ScfeTable : TableComponent 16 | { 17 | private readonly ScfeApp _app; 18 | private bool _escPressedOnce; 19 | 20 | public ScfeTable(ScfeApp app) 21 | { 22 | _app = app; 23 | } 24 | 25 | public override bool AcceptInput(ConsoleKeyInfo keyPressed, GraphicsContext g) 26 | { 27 | if (_escPressedOnce) 28 | { 29 | if (keyPressed.Key == ConsoleKey.Escape) 30 | { 31 | if (_app.Tasks.GetTasks().Count != 0) 32 | { 33 | _app.ShowHelpMessage("Tasks are still running, try again later", g, ConsoleColor.Red); 34 | _escPressedOnce = false; 35 | return true; 36 | } 37 | 38 | g.Clear(); 39 | Environment.Exit(0); 40 | return true; 41 | } 42 | 43 | _app.ShowHelpMessage("Exit cancelled (press Escape twice to exit)", g); 44 | _escPressedOnce = false; 45 | } 46 | 47 | var b = base.AcceptInput(keyPressed, g); 48 | if (b) return true; 49 | 50 | if (keyPressed.Key == ConsoleKey.Escape) 51 | { 52 | _app.ShowHelpMessage("Press Escape again to exit SCFE", g, ConsoleColor.Yellow); 53 | _escPressedOnce = true; 54 | return true; 55 | } 56 | 57 | if (_app.CurrentMode.SearchEnabled && keyPressed.KeyChar != (char) 0 && 58 | !char.IsControl(keyPressed.KeyChar) && 59 | keyPressed.Key != ConsoleKey.Enter && 60 | (keyPressed.Modifiers & ConsoleModifiers.Control) == 0 && 61 | (keyPressed.Modifiers & ConsoleModifiers.Alt) == 0) 62 | { 63 | SetFocused(false, g); 64 | _app.TextBox.Text += keyPressed.KeyChar; 65 | _app.TextBox.CaretPosition += 1; 66 | _app.TextBox.Print(_app.TextBox.Width - 5, g); 67 | 68 | if (_app.TextBoxHandler == null) 69 | _app.SwitchToFolder(_app.CurrentDir, g); 70 | _app.TextBox.SetFocused(true, g); 71 | return true; 72 | } 73 | 74 | return false; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /SCFE/SCFE/ViuExampleTable.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Collections.ObjectModel; 12 | using System.Linq; 13 | using Viu.Components; 14 | using Viu.Strategy; 15 | using Viu.Table; 16 | 17 | namespace SCFE 18 | { 19 | public struct Example 20 | { 21 | public string Name; 22 | public int Number; 23 | public ConsoleColor Color; 24 | } 25 | 26 | public class ColorColumnType : ColumnType 27 | { 28 | public override Component GetRowInformation(Example data, int index, int width, bool isFocused, bool isSelected, 29 | TableComponent parent) 30 | { 31 | return new TextComponent 32 | { 33 | Text = typeof(ConsoleColor).GetEnumName(data.Color), 34 | Foreground = isFocused ? ConsoleColor.Black : data.Color, 35 | Background = isFocused ? data.Color : (ConsoleColor?) null 36 | }; 37 | } 38 | 39 | public override int[] GetPossibleWidths(ICollection data) 40 | { 41 | // ReSharper disable once PossibleNullReferenceException 42 | return new[] {Math.Max(data.Select(e => typeof(ConsoleColor).GetEnumName(e.Color).Length).Max(), 5)}; 43 | } 44 | 45 | public override int GetMaximumRowHeight(ICollection data) 46 | { 47 | return 1; 48 | } 49 | 50 | public override string GetTitle(TableComponent parent, int width) 51 | { 52 | return "Color"; 53 | } 54 | 55 | public override int GetTotalRowHeight(ObservableCollection data) 56 | { 57 | return data.Count; 58 | } 59 | } 60 | 61 | public static class ViuExampleTable 62 | { 63 | public static Parent LaunchExample() 64 | { 65 | var data = new ObservableCollection 66 | { 67 | new Example {Name = "Test", Number = 7, Color = ConsoleColor.Cyan}, 68 | new Example {Name = "Woosh", Number = 18, Color = ConsoleColor.Red}, 69 | new Example {Name = "Yeet", Number = 68, Color = ConsoleColor.Gray} 70 | }; 71 | 72 | var par = new Parent(new BorderStrategy()) {ClearAreaBeforePrint = true}; 73 | 74 | var table = new TableComponent {Data = data}; 75 | table.ActionOnListElement += (o, args) => { data.Remove(args.Item); }; 76 | table.AddColumn(new IndicatorColumnType()); 77 | table.AddColumn(new BasicColumnType("Name", e => e.Name)); 78 | table.AddColumn(new ColorColumnType()); 79 | par.AddComponent(table, BorderStrategy.Center); 80 | 81 | return par; 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /SCFE/SCFE/FileWatchExtension.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using System; 10 | using System.Collections.Generic; 11 | using System.IO; 12 | using System.Linq; 13 | using Viu; 14 | using Viu.Table; 15 | 16 | namespace SCFE 17 | { 18 | public class FileWatchExtension : IScfeExtension 19 | { 20 | private ScfeApp _app; 21 | private FileSystemWatcher _watcher; 22 | 23 | public FileWatchExtension(ScfeApp app) 24 | { 25 | _app = app; 26 | _app.OnFolderViewChanged += OnAppFolderViewChanged; 27 | } 28 | 29 | public static List TempIgnoreList { get; } = new List(); 30 | 31 | private void OnAppFolderViewChanged(object sender, FolderViewChangedEventArgs args) 32 | { 33 | if (args.FolderChanged) 34 | { 35 | if (_watcher != null) 36 | { 37 | _watcher.EnableRaisingEvents = false; 38 | _watcher.Dispose(); 39 | } 40 | 41 | _watcher = new FileSystemWatcher 42 | { 43 | Path = args.NewFolder.FullPath, 44 | IncludeSubdirectories = false 45 | }; 46 | _watcher.Changed += RefreshAppDirectory; 47 | _watcher.Deleted += RefreshAppDirectory; 48 | _watcher.Renamed += RefreshAppDirectory; 49 | _watcher.Created += RefreshAppDirectory; 50 | _watcher.EnableRaisingEvents = true; 51 | } 52 | } 53 | 54 | private void RefreshAppDirectory(object sender, FileSystemEventArgs e) 55 | { 56 | File f; 57 | if ((f = TempIgnoreList.FirstOrDefault(fi => fi.FullPath.Equals(e.FullPath))) != null) 58 | { 59 | TempIgnoreList.Remove(f); 60 | return; 61 | } 62 | var cachedCurrentDir = _app.CurrentDir; 63 | _app.DoGraphicsLater(g => 64 | { 65 | if (cachedCurrentDir == _app.CurrentDir) 66 | _app.SwitchToFolder(_app.CurrentDir, g, _app.FocusedElement); 67 | }); 68 | } 69 | 70 | public IEnumerable> GetColumns() 71 | { 72 | return new List>(); 73 | } 74 | 75 | public Dictionary> GetActions() 76 | { 77 | return new Dictionary>(); 78 | } 79 | 80 | public IEnumerable GetCurrDirOptions() 81 | { 82 | return new List(); 83 | } 84 | 85 | public IEnumerable GetFilesOptions() 86 | { 87 | return new List(); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /SCFE/SCFE/ManagedTask.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Collections.Immutable; 12 | using System.Linq; 13 | using System.Threading.Tasks; 14 | 15 | namespace SCFE 16 | { 17 | public class TaskPool 18 | { 19 | private readonly Action, TaskResult> _taskCompletedCallback; 20 | private readonly List> _tasks = new List>(); 21 | 22 | public TaskPool(Action, TaskResult> taskCompletedCallback) 23 | { 24 | _taskCompletedCallback = taskCompletedCallback; 25 | } 26 | 27 | public void AddTask(ManagedTask task) 28 | { 29 | _tasks.Add(task); 30 | if (_taskCompletedCallback != null) 31 | { 32 | task.AddCallback(_taskCompletedCallback); 33 | task.AddCallback((t, tr) => 34 | { 35 | _tasks.Remove(t); 36 | }); 37 | } 38 | 39 | task.Start(); 40 | } 41 | 42 | public void AddTask(Func, TaskResult> taskFunc) 43 | { 44 | AddTask(new ManagedTask(taskFunc)); 45 | } 46 | 47 | public ImmutableList> GetTasks() 48 | { 49 | return _tasks.ToImmutableList(); 50 | } 51 | } 52 | 53 | public class ManagedTask 54 | { 55 | private readonly List _publicationsList = new List(); 56 | private readonly Task _task; 57 | 58 | public ManagedTask(Func, TaskResult> taskFunc) 59 | { 60 | _task = new Task(() => taskFunc(this)); 61 | } 62 | 63 | public void Publish(T result) 64 | { 65 | _publicationsList.Add(result); 66 | } 67 | 68 | public T GetLastPublication() 69 | { 70 | if (_publicationsList.Count > 0) 71 | return _publicationsList.Last(); 72 | return default; 73 | } 74 | 75 | public void Start() 76 | { 77 | _task.Start(); 78 | } 79 | 80 | public bool IsDone() 81 | { 82 | return _task.IsCompleted; 83 | } 84 | 85 | public TaskResult GetResult() 86 | { 87 | if (_task.IsCompleted) 88 | return _task.Result; 89 | return null; 90 | } 91 | 92 | public TaskResult WaitForResult() 93 | { 94 | _task.Wait(); 95 | return _task.Result; 96 | } 97 | 98 | public void AddCallback(Action, TaskResult> taskCompletedCallback) 99 | { 100 | _task.ContinueWith(task1 => taskCompletedCallback(this, task1.Result)); 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /SCFE/Viu/AbstractHierarchicalDictionary.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using System.Collections.Generic; 10 | using JetBrains.Annotations; 11 | 12 | namespace Viu 13 | { 14 | /// 15 | /// A hierarchical dictionary: for any key K, it returns the value V stored in its internal dictionary if this 16 | /// value is present. If not present in the internal dictionary, it returns the value from its parent 17 | /// HierarchicalDictionary. 18 | /// Used in ActionMap and InputMaps so that components inherit key bindings from their parent hierarchy. 19 | /// 20 | /// The type of the keys 21 | /// The type of the values 22 | public abstract class AbstractHierarchicalDictionary 23 | { 24 | /// 25 | /// The parent of this hierarchical dictionary. 26 | /// 27 | [CanBeNull] 28 | public abstract AbstractHierarchicalDictionary Parent { get; set; } 29 | 30 | /// 31 | /// Retrieve a value from this hierarchical dictionary. If not found in the current hierarchical dictionary, 32 | /// it returns the result of Get for its parent hierarchical dictionary, or null if this is dictionary has no 33 | /// parent dictionary. 34 | /// 35 | /// The key for which to get the value 36 | /// 37 | /// The value matching the key in this dictionary, or in its parent if not found, or null if this 38 | /// dictionary does not have a parent 39 | /// 40 | [CanBeNull] 41 | public abstract TV Get([NotNull] TK key); 42 | 43 | /// 44 | /// Put a value in the current dictionary. Where the value is actually stored depends on the implementation 45 | /// (e.g. can be in a backing Dictionary) 46 | /// 47 | /// The key to add in the dictionary 48 | /// The value matching the key to add. 49 | public abstract void Put([NotNull] TK key, [NotNull] TV value); 50 | 51 | /// 52 | /// Compiles the dictionary's hierarchy: it returns a (non-hierarchical) dictionary with all of the keys that 53 | /// are present in the hierarchy and their matching values, corresponding the hierarchical order. This gives 54 | /// the same result as creating a Dictionary by using Get on every key present in the hierarchy. 55 | /// This only considers the parents in the hierarchy, as a HierarchicalDictionary has no information on its 56 | /// children. 57 | /// 58 | /// The compiled dictionary that corresponds to this hierarchy. 59 | [NotNull] 60 | public abstract Dictionary Compile(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /SCFE/SCFE/ViuDemo.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using System; 10 | using System.Collections.ObjectModel; 11 | using Viu; 12 | using Viu.Components; 13 | using Viu.Strategy; 14 | using Viu.Table; 15 | 16 | namespace SCFE 17 | { 18 | public class ViuDemoType 19 | { 20 | public ViuDemoType(string name, Parent par) 21 | { 22 | Name = name; 23 | Parent = par; 24 | } 25 | 26 | public string Name { get; } 27 | public Parent Parent { get; } 28 | } 29 | 30 | public class ViuDemo 31 | { 32 | private readonly ObservableCollection _demos = new ObservableCollection 33 | { 34 | new ViuDemoType("Simple Border Strategy Demo", Program.TestSimpleBorder()), 35 | new ViuDemoType("Complex Border Strategy Demo", Program.TestBorders()), 36 | new ViuDemoType("Text Field Demo", Program.TestTextArea()), 37 | new ViuDemoType("Button Test", Program.TestButtons()), 38 | new ViuDemoType("Table Test", Program.TestTable()), 39 | new ViuDemoType("Customized table test", ViuExampleTable.LaunchExample()), 40 | new ViuDemoType("Flow Test", Program.TestFlow()) 41 | }; 42 | 43 | public ViuDemo() 44 | { 45 | var mainContainer = new Parent {ClearAreaBeforePrint = true}; 46 | var cons = new ConsoleParent(mainContainer); 47 | cons.SetTitle("Viu/SCFE Demo"); 48 | var switcher = new SwitcherStrategy(mainContainer); 49 | mainContainer.Strategy = switcher; 50 | 51 | 52 | var menu = new Parent(new LineStrategy {Centered = true}); 53 | mainContainer.AddComponent(menu); 54 | menu.AddComponent(new TextComponent("~ SCFE Demo ~") 55 | {Foreground = ConsoleColor.Yellow, HAlign = HorizontalAlignment.Centered}); 56 | menu.AddComponent(new Separator()); 57 | switcher.SwitchToComponent(menu, null); 58 | 59 | var table = new TableComponent {Data = _demos}; 60 | menu.AddComponent(table); 61 | table.AddColumn(new IndicatorColumnType()); 62 | table.AddColumn( 63 | new BasicColumnType("Choose a demo to get started. Exit a demo with the Escape key.", 64 | vdt => vdt.Name)); 65 | table.ActionOnListElement += (sender, args) => 66 | { 67 | switcher.SwitchToComponent(args.Item.Parent, args.Graphics); 68 | mainContainer.Validate(); 69 | mainContainer.Print(args.Graphics); 70 | }; 71 | 72 | foreach (var vdt in _demos) 73 | mainContainer.AddComponent(vdt.Parent); 74 | 75 | mainContainer.ActionMap.Put(StandardActionNames.CancelAction, (o, args) => 76 | { 77 | mainContainer.SetFocused(false, args.Graphics); 78 | switcher.SwitchToComponent(menu, args.Graphics); 79 | mainContainer.Validate(); 80 | mainContainer.SetFocused(true, args.Graphics); 81 | mainContainer.Print(args.Graphics); 82 | }); 83 | 84 | cons.Validate(); 85 | cons.FocusFirst(); 86 | cons.Print(); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /SCFE/Viu/KeyStroke.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using System; 10 | using System.Diagnostics; 11 | 12 | namespace Viu 13 | { 14 | public struct KeyStroke 15 | { 16 | public ConsoleKey? Key { get; } 17 | public char? KeyLetter { get; } 18 | public bool? Shift { get; } 19 | public bool? Alt { get; } 20 | public bool? Control { get; } 21 | 22 | public KeyStroke(ConsoleKey key, bool? control, bool? alt, bool? shift) 23 | { 24 | Key = key; 25 | KeyLetter = null; 26 | Alt = alt; 27 | Shift = shift; 28 | Control = control; 29 | } 30 | 31 | public KeyStroke(char key, bool? control, bool? alt, bool? shift) 32 | { 33 | KeyLetter = key; 34 | Key = null; 35 | Alt = alt; 36 | Shift = shift; 37 | Control = control; 38 | } 39 | 40 | public bool Matches(ConsoleKeyInfo cki) 41 | { 42 | var altMatch = Alt == null || Alt.Value == ((cki.Modifiers & ConsoleModifiers.Alt) != 0); 43 | var ctrlMatch = Control == null || 44 | Control.Value == ((cki.Modifiers & ConsoleModifiers.Control) != 0); 45 | var shiftMatch = Shift == null || 46 | Shift.Value == ((cki.Modifiers & ConsoleModifiers.Shift) != 0); 47 | 48 | if (Key == null) 49 | { 50 | Debug.Assert(KeyLetter != null, nameof(KeyLetter) + " != null"); 51 | var keyName = Enum.GetName(typeof(ConsoleKey), cki.Key); 52 | if (keyName == null) 53 | return false; 54 | return altMatch && ctrlMatch && shiftMatch && 55 | (char.ToLower(cki.KeyChar) == char.ToLower(KeyLetter.Value) || 56 | keyName.Length == 1 && char.ToLower(keyName[0]) == char.ToLower(KeyLetter.Value)); 57 | } 58 | 59 | return altMatch && ctrlMatch && shiftMatch && cki.Key == Key; 60 | } 61 | 62 | public override bool Equals(object obj) 63 | { 64 | if (obj is KeyStroke other) 65 | return Key == other.Key && KeyLetter == other.KeyLetter && Shift == other.Shift && Alt == other.Alt && 66 | Control == other.Control; 67 | 68 | return false; 69 | } 70 | 71 | public bool Equals(KeyStroke other) 72 | { 73 | return Key == other.Key && KeyLetter == other.KeyLetter && Shift == other.Shift && Alt == other.Alt && 74 | Control == other.Control; 75 | } 76 | 77 | public override int GetHashCode() 78 | { 79 | unchecked 80 | { 81 | var hashCode = Key.GetHashCode(); 82 | hashCode = (hashCode * 397) ^ KeyLetter.GetHashCode(); 83 | hashCode = (hashCode * 397) ^ Shift.GetHashCode(); 84 | hashCode = (hashCode * 397) ^ Alt.GetHashCode(); 85 | hashCode = (hashCode * 397) ^ Control.GetHashCode(); 86 | return hashCode; 87 | } 88 | } 89 | 90 | public static bool operator ==(KeyStroke left, KeyStroke right) 91 | { 92 | return left.Equals(right); 93 | } 94 | 95 | public static bool operator !=(KeyStroke left, KeyStroke right) 96 | { 97 | return !left.Equals(right); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /SCFE/Viu/Strategy/SwitcherStrategy.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Linq; 12 | using JetBrains.Annotations; 13 | using Viu.Components; 14 | 15 | namespace Viu.Strategy 16 | { 17 | public class SwitcherStrategy : LayoutStrategy 18 | { 19 | private Component _current; 20 | private readonly Parent _parent; 21 | 22 | private readonly Dictionary _bindings = new Dictionary(); 23 | 24 | public SwitcherStrategy(Parent p) 25 | { 26 | _parent = p; 27 | } 28 | 29 | public override void ApplyLayoutStrategy(Parent p) 30 | { 31 | if (p != _parent) 32 | throw new ArgumentException( 33 | "Cannot use a switcher strategy on a parent other than the one it was created with."); 34 | 35 | var children = p.GetChildren(); 36 | 37 | foreach (var c in children) 38 | { 39 | c.X = p.X; 40 | c.Y = p.Y; 41 | c.Width = c.PrefWidth == Component.Computed ? p.Width : c.PrefWidth; 42 | c.Height = c.PrefHeight == Component.Computed ? p.Height : c.PrefHeight; 43 | c.Visible = ReferenceEquals(_current, c); 44 | } 45 | } 46 | 47 | public override Dimensions ComputeDimensions(Parent p) 48 | { 49 | return _current?.ComputeDimensions() ?? new Dimensions(0, 0); 50 | } 51 | 52 | public override bool IsHintAllowed(object hint) 53 | { 54 | return true; 55 | } 56 | 57 | public override void ComponentAdded(Parent p, Component c, object hint) 58 | { 59 | if (hint != null) 60 | _bindings.Add(hint, c); 61 | } 62 | 63 | public override void ComponentRemoved(Parent p, Component c) 64 | { 65 | if (c.LayoutInformation != null) 66 | _bindings.Remove(c.LayoutInformation); 67 | 68 | (from kv in _bindings where kv.Value == c select kv.Key).ToList().ForEach(h => _bindings.Remove(h)); 69 | } 70 | 71 | public void SwitchToComponent([NotNull] Component c, [CanBeNull] GraphicsContext g) 72 | { 73 | if (_parent.GetChildren().Contains(c)) 74 | { 75 | var switchFocus = _parent.IsFocused(); 76 | if (switchFocus && g != null && ((_current as IFocusable)?.IsFocused() ?? false)) 77 | ((IFocusable) _current).SetFocused(false, g); 78 | _current = c; 79 | if (switchFocus && g != null) 80 | (_current as IFocusable)?.SetFocused(true, g); 81 | } 82 | else 83 | { 84 | throw new ArgumentException( 85 | "The component is not part of the parent attributed to this switcher strategy"); 86 | } 87 | } 88 | 89 | public bool SwitchToComponentWithHint(object hint, GraphicsContext g) 90 | { 91 | if (!_bindings.ContainsKey(hint)) 92 | return false; 93 | SwitchToComponent(_bindings[hint], g); 94 | return true; 95 | } 96 | 97 | public override IFocusable GetPreviousFocusableElement(Parent parent, Component previousFocus) 98 | { 99 | return previousFocus == null ? _current as IFocusable : null; 100 | } 101 | 102 | public override IFocusable GetNextFocusableElement(Parent parent, Component previousFocus) 103 | { 104 | return previousFocus == null ? _current as IFocusable : null; 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /SCFE/Viu/Constants.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | namespace Viu 10 | { 11 | /// 12 | /// Alignment along the horizontal axis 13 | /// 14 | public enum HorizontalAlignment 15 | { 16 | /// 17 | /// Align to the left 18 | /// 19 | Left = -1, 20 | 21 | /// 22 | /// Align in the center 23 | /// 24 | Centered = 0, 25 | 26 | /// 27 | /// Align to the right 28 | /// 29 | Right = 1 30 | } 31 | 32 | /// 33 | /// Alignment along the vertical axis 34 | /// 35 | public enum VerticalAlignment 36 | { 37 | /// 38 | /// Align to the top 39 | /// 40 | Top = -1, 41 | 42 | /// 43 | /// Align to the center 44 | /// 45 | Centered = 0, 46 | 47 | /// 48 | /// Align to the bottom 49 | /// 50 | Bottom = 1 51 | } 52 | 53 | /// 54 | /// Orientation along either the horizontal or vertical axis 55 | /// 56 | public enum Orientation 57 | { 58 | Horizontal = 0, 59 | Vertical = 1 60 | } 61 | 62 | /// 63 | /// Standard action names used inside Viu. 64 | /// 65 | public static class StandardActionNames 66 | { 67 | /// 68 | /// Name for the action of moving up in a list, menu, etc 69 | /// 70 | public const string MoveUp = "mv_up"; 71 | 72 | /// 73 | /// Name for the action of going left in a table, menu, etc 74 | /// 75 | public const string MoveLeft = "mv_left"; 76 | 77 | /// 78 | /// Name for the action of going left by one word 79 | /// 80 | public const string MoveLeftWord = "mv_left_word"; 81 | 82 | /// 83 | /// Name for the action of moving to the beginning (e.g. of a line) 84 | /// 85 | public const string MoveLineStart = "mv_start"; 86 | 87 | /// 88 | /// Name for the action of going right in a table, menu, etc 89 | /// 90 | public const string MoveRight = "mv_right"; 91 | 92 | /// 93 | /// Name for the action of going right by one word 94 | /// 95 | public const string MoveRightWord = "mv_right_word"; 96 | 97 | public const string MoveLineEnd = "mv_end"; 98 | 99 | /// 100 | /// Name for the action of going down in a list, menu, etc 101 | /// 102 | public const string MoveDown = "mv_down"; 103 | 104 | /// 105 | /// Name for the base action: for example activating a button 106 | /// 107 | public const string BaseAction = "action_base"; 108 | 109 | public const string SecondaryAction = "acion_secondary"; 110 | 111 | /// 112 | /// The act of cancelling an action, whatever it may be 113 | /// 114 | public const string CancelAction = "action_cancel"; 115 | 116 | public const string SelectAction = "action_select"; 117 | 118 | public const string DeleteToTheLeftAction = "action_delleft"; 119 | 120 | public const string DeleteWordToTheLeftAction = "action_delwleft"; 121 | 122 | public const string DeleteToTheRightAction = "action_delright"; 123 | 124 | public const string DeleteWordToTheRightAction = "action_delwright"; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /SCFE/Viu/Table/MultistateColumnType.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Collections.Immutable; 12 | using System.Collections.ObjectModel; 13 | using System.Linq; 14 | using JetBrains.Annotations; 15 | using MoreLinq; 16 | using Viu.Components; 17 | 18 | namespace Viu.Table 19 | { 20 | public class MultistateColumnType : ColumnType 21 | { 22 | private readonly Func _colorGetter; 23 | private readonly List _titles; 24 | private readonly Func> _translators; 25 | [CanBeNull] private readonly Func, bool> _visibility; 26 | 27 | public MultistateColumnType(IEnumerable titles, Func> toRowString, 28 | Func, bool> visibilityGetter = null, Func colorGetter = null) 29 | { 30 | _titles = new List(titles); 31 | _titles.Sort((x, y) => x.Length.CompareTo(y.Length)); 32 | _translators = toRowString; 33 | _visibility = visibilityGetter; 34 | _colorGetter = colorGetter; 35 | } 36 | 37 | public HorizontalAlignment HAlign { get; set; } = HorizontalAlignment.Left; 38 | 39 | public override Component GetRowInformation(T data, int index, int width, bool isFocused, bool isSelected, 40 | TableComponent parent) 41 | { 42 | if (data == null) 43 | return new TextComponent(); 44 | var possibilities = new List(_translators(data)); 45 | possibilities.Sort((s1, s2) => s1.Length.CompareTo(s2.Length)); 46 | return possibilities.Any(s => s.Length <= width) 47 | ? new TextComponent(possibilities.Last(s => s.Length <= width)) 48 | { 49 | HAlign = HAlign, 50 | Background = isFocused ? _colorGetter?.Invoke(data) : null, 51 | Foreground = isFocused ? null : _colorGetter?.Invoke(data) 52 | } 53 | : new TextComponent(possibilities.MinBy(s => s.Length).First()) 54 | { 55 | Background = isFocused ? _colorGetter?.Invoke(data) : null, 56 | Foreground = isFocused ? null : _colorGetter?.Invoke(data) 57 | }; 58 | } 59 | 60 | public override int[] GetPossibleWidths(ICollection data) 61 | { 62 | var methodsMaxSize = new List(); 63 | 64 | foreach (var t in data) 65 | { 66 | var lengths = _translators(t).Select(s => { return s.Length; }).ToImmutableList(); 67 | for (var i = 0; i < lengths.Count; i++) 68 | if (i >= methodsMaxSize.Count) 69 | methodsMaxSize.Add(lengths[i]); 70 | else 71 | methodsMaxSize[i] = Math.Max(lengths[i], methodsMaxSize[i]); 72 | } 73 | 74 | var minTitle = _titles.Select(s => s.Length).Min(); 75 | var l = methodsMaxSize.Select(x => Math.Max(x, minTitle)).Distinct().ToList(); 76 | // If the title might be bigger, also add that as a possible width 77 | if (l.Max() < _titles.Last().Length) 78 | l.Add(_titles.Last().Length); 79 | l.Sort(); 80 | return l.ToArray(); 81 | } 82 | 83 | public override int GetMaximumRowHeight(ICollection data) 84 | { 85 | return 1; 86 | } 87 | 88 | public override string GetTitle(TableComponent parent, int width) 89 | { 90 | return _titles.Last(s => s.Length <= width); 91 | } 92 | 93 | public override int GetTotalRowHeight(ObservableCollection data) 94 | { 95 | return data.Count; 96 | } 97 | 98 | public override bool IsVisible(IEnumerable data) 99 | { 100 | return _visibility?.Invoke(data) ?? true; 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SCFE Logo SCFE 2 | 3 | Documentation website: https://utybo.github.io/scfe-website/ (some links are broken there unfortunately) 4 | 5 | ![Salamanders' Lab](slablogo.png) 6 | 7 | ## What is this? 8 | 9 | Hello! This is the open-source release of my first-year project at EPITA. I'm currently in the third year, but I thought it might be a good idea to release this for educational purposes. 10 | 11 | **This release is not maintained.** Feel free to have a look around but don't expect to have something ultra turbo nice. I also made this two years ago, and my code quality standards have vastly improved since then ;) 12 | 13 | For context: there is a mandatory programming project during the second semester in groups of four. This is, in 98.5% of cases, a video game done in Unity. However, it is also possible to have other projects if you have a solid case for yourself, although they must be in either OCaml or C#. Our project is *SCFE*, a console-based file explorer (Salmanders' Console File Explorer). There was another project that year that was not a game named [OCalc](https://github.com/et7f3/ocalc) 14 | 15 | Our group, "Salamanders' Lab", was about what you expect in this sort of project: two people doing the job, two people doing much. However, this time around, I can't really blame them: I was a very poor project leader there. In a nutshell: I made about all of the code you will find here, and Mathieu (@yxyfer on GitHub) made the website. 16 | 17 | The project was graded 19.5 out of 20 (97.5%) for the final defense. However, this was mostly thanks to a really good oral performance from the team. So, thanks guys! 18 | 19 | ## What does it do? 20 | 21 | Let's talk about the project itself: SCFE is a console-based file explorer application that aims to provide a vim-like user experience which would be a little bit more approachable. 22 | 23 | It has a lot of the features from traditional file explorers, as well as pretty nice Git integration. 24 | 25 | You can find more information on the documentation website: https://utybo.github.io/scfe-website/ 26 | 27 | I will eventually make a full fledged blog post on what this project is and how it became what it is, with more technical details, but for now, this is all that you get :) 28 | 29 | ## Repo architecture 30 | 31 | This repo contains: 32 | 33 | * `handed`: Source files for the various defenses. Compiled versions are available on the documentation website. Note that we used Pandoc to generate LaTeX files, as Markdown can be learnt in 30 seconds, while LaTeX cannot. As I was very bad at bullshitting my way through stuff, the documentation there is actually pretty interesting and not too indigestible. 34 | 35 | * `SCFE`: The main solution for the code side of things 36 | 37 | * `Viu`: A simple and probably stupid home-made framework for making nice console GUIs. This is by no means a professional framework, do not use it for actual serious work! 38 | 39 | * `SCFE`: Code for SCFE itself. 40 | 41 | * `installer.nsi`: Installation configuration for NSI. To be honest, I completely forgot how this works. 42 | 43 | * A bunch of logos and this README 44 | 45 | The only thing that was modified from the end of the project is the .NET build config files (.sln and .csproj files). 46 | 47 | ## Buildling 48 | 49 | You'll need .NET Core 3.1 for this. Just run `dotnet run` in SCFE/SCFE and everything should work -- this is a super basic .NET app architecture. 50 | 51 | ## Screenshots 52 | 53 | ![](https://utybo.github.io/scfe-website/library/ui.jpeg) 54 | 55 | ![](https://utybo.github.io/scfe-website/library/using-git-SCFE.gif) 56 | 57 | ## Additional links 58 | 59 | The following may be of interest: 60 | 61 | * [Sources for the website](https://github.com/yxyfer/salamanders-website) and my fork [here](https://github.com/utybo/scfe-website) 62 | * [Website](https://utybo.github.io/scfe-website/), this is a re-upload using GitHub Pages, the original one was hosted on https://samalanders.dev 63 | * Handed documents: 64 | * [Book of specifications](https://utybo.github.io/scfe-website/ressources/SCFE-Book-Of-Specifications.pdf) 65 | * [First defense report](https://utybo.github.io/scfe-website/ressources/SCFE-Defense-1.pdf) 66 | * [Second defense report](https://utybo.github.io/scfe-website/ressources/SCFE-Defense-2.pdf) 67 | * [Final report (English)](https://utybo.github.io/scfe-website/ressources/SCFE-Final-Presentation-en.pdf) 68 | * [Rapport de projet (français)](https://utybo.github.io/scfe-website/ressources/SCFE-Final-Presentation-fr.pdf) 69 | * [Project subject](https://github.com/Epidocs/Past-Exams/blob/master/S2/Project/projects-2023-s2-subject-en.pdf) (et en [français](https://github.com/Epidocs/Past-Exams/blob/master/S2/Project/projects-2023-s2-subject-fr.pdf)) 70 | -------------------------------------------------------------------------------- /SCFE/Viu/Strategy/FlowStrategy.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using System; 10 | using Viu.Components; 11 | 12 | namespace Viu.Strategy 13 | { 14 | public class FlowStrategy : LayoutStrategy 15 | { 16 | public FlowStrategy() 17 | { 18 | } 19 | 20 | public FlowStrategy(bool wrap) 21 | { 22 | Wrapping = wrap; 23 | } 24 | 25 | public FlowStrategy(int hGap) 26 | { 27 | HGap = hGap; 28 | } 29 | 30 | public FlowStrategy(bool wrap, int hGap, int vGap) 31 | { 32 | Wrapping = wrap; 33 | HGap = hGap; 34 | VGap = vGap; 35 | } 36 | 37 | private bool Wrapping { get; } 38 | 39 | public int HGap { get; set; } 40 | 41 | public int VGap { get; set; } 42 | 43 | public override void ApplyLayoutStrategy(Parent p) 44 | { 45 | var nextX = p.X; 46 | var nextY = p.Y; 47 | var maxY = 0; // Used for properly wrapping elements 48 | 49 | foreach (var c in p.GetChildren()) 50 | { 51 | var d = c.ComputeDimensions(); 52 | c.Width = c.PrefWidth == -1 ? d.Width : c.PrefWidth; 53 | c.Height = c.PrefHeight == -1 ? d.Height : c.PrefHeight; 54 | 55 | if (c.Width + nextX > p.X + p.Width) 56 | { 57 | if (nextX == p.X) 58 | { 59 | c.Width = p.Width; 60 | } 61 | else 62 | { 63 | if (Wrapping) 64 | { 65 | nextY += maxY + VGap; 66 | nextX = p.X; 67 | maxY = 0; 68 | } 69 | else 70 | { 71 | if (nextX < p.Width) 72 | c.Width = p.Width - nextX; 73 | else 74 | c.Width = 0; 75 | } 76 | } 77 | } 78 | 79 | c.X = nextX; 80 | c.Y = nextY; 81 | // TODO Care about min and max values 82 | 83 | if (c.Height > maxY) 84 | maxY = c.Height; 85 | 86 | nextX += c.Width + HGap; 87 | } 88 | } 89 | 90 | public override Dimensions ComputeDimensions(Parent p) 91 | { 92 | var totalH = 0; 93 | var totalW = 0; 94 | 95 | var rowH = 0; 96 | var rowW = 0; 97 | 98 | foreach (var c in p.GetChildren()) 99 | { 100 | var d = c.ComputeDimensions(); 101 | var wi = c.PrefWidth == -1 ? d.Width : c.PrefWidth; 102 | var he = c.PrefHeight == -1 ? d.Height : c.PrefHeight; 103 | 104 | if (Wrapping && rowW > 0 && rowW + wi > p.Width) 105 | { 106 | totalH += rowH + VGap; 107 | totalW = Math.Max(rowW - HGap, totalW); 108 | 109 | rowH = he; 110 | rowW = wi + HGap; 111 | } 112 | else 113 | { 114 | rowH = Math.Max(he, rowH); 115 | rowW += wi + HGap; 116 | } 117 | } 118 | 119 | return new Dimensions(Math.Max(totalW, rowW - HGap), totalH + rowH); 120 | } 121 | 122 | public override IFocusable GetNextFocusableElement(Parent parent, Component previousFocus) 123 | { 124 | var components = parent.GetChildren(); 125 | for (var i = components.IndexOf(previousFocus) + 1; i < components.Count; i++) 126 | if ((components[i] as IFocusable)?.IsFocusable() ?? false) 127 | return (IFocusable) components[i]; 128 | 129 | return null; 130 | } 131 | 132 | public override IFocusable GetPreviousFocusableElement(Parent parent, Component c) 133 | { 134 | var components = parent.GetChildren(); 135 | for (var i = components.IndexOf(c) - 1; i >= 0; i--) 136 | if ((components[i] as IFocusable)?.IsFocusable() ?? false) 137 | return (IFocusable) components[i]; 138 | 139 | return null; 140 | } 141 | 142 | public override bool IsHintAllowed(object hint) 143 | { 144 | return hint == null || "" == hint as string; 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /SCFE/Viu/Components/BoxContainer.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using System; 10 | 11 | namespace Viu.Components 12 | { 13 | /// 14 | /// 15 | /// 16 | /// A BoxContainer only contains one element which is wrapped inside a box. The style of said box can be changed by 17 | /// changing the Style property. 18 | /// 19 | public class BoxContainer : Container, ICursorFocusable 20 | { 21 | /// 22 | /// Create a new BoxContainer with the given component 23 | /// 24 | /// The component contained by this BoxContainer 25 | public BoxContainer(Component c) 26 | { 27 | SetComponent(c); 28 | } 29 | 30 | /// 31 | /// Create a new BoxContainer with the given component and with the given LineStyle 32 | /// 33 | /// The component contained by this BoxContainer 34 | /// 35 | public BoxContainer(Component c, LineStyle style) 36 | { 37 | SetComponent(c); 38 | Style = style; 39 | } 40 | 41 | /// 42 | /// The LineStyle used to print the borders of this BoxContainer 43 | /// 44 | public LineStyle Style { get; set; } = LineStyle.Simple; 45 | 46 | public bool AcceptInput(ConsoleKeyInfo keyPressed, GraphicsContext g) 47 | { 48 | var ins = Components[0].InputMap.Compile(); 49 | var str = Utils.GetActionNameForKey(ins, keyPressed); 50 | var b = (Components[0] as IFocusable)?.AcceptInput(keyPressed, g) ?? false; 51 | 52 | if (b) 53 | return true; 54 | 55 | if (str != null) 56 | { 57 | var action = Components[0].ActionMap.Get(str); 58 | if (action != null) 59 | { 60 | action(Components[0], new ActionEventArgs(Components[0], keyPressed, g)); 61 | return true; 62 | } 63 | } 64 | 65 | return false; 66 | } 67 | 68 | public bool IsFocusable() 69 | { 70 | return (Components[0] as IFocusable)?.IsFocusable() ?? false; 71 | } 72 | 73 | public void SetFocused(bool focused, GraphicsContext g) 74 | { 75 | (Components[0] as IFocusable)?.SetFocused(focused, g); 76 | } 77 | 78 | public bool IsFocused() 79 | { 80 | return (Components[0] as IFocusable)?.IsFocused() ?? false; 81 | } 82 | 83 | public void UpdateCursorState(GraphicsContext g) 84 | { 85 | (Components[0] as ICursorFocusable)?.UpdateCursorState(g); 86 | } 87 | 88 | /// 89 | /// Set this BoxContainer's component to the given one. This method does NOT redraw the BoxContainer. 90 | /// 91 | /// The component to use in this BoxContainer 92 | public void SetComponent(Component c) 93 | { 94 | // todo handle c == null 95 | Components.Clear(); 96 | Components.Add(c); 97 | c.Parent = this; 98 | } 99 | 100 | public override Dimensions ComputeDimensions() 101 | { 102 | if (Components.Count > 0) 103 | { 104 | var d = Components[0].ComputeDimensions(); 105 | return d.Add(2, 2); 106 | } 107 | 108 | return new Dimensions(2, 2); 109 | } 110 | 111 | public override void Validate() 112 | { 113 | if (Components.Count > 0) 114 | { 115 | var c = Components[0]; 116 | c.X = X + 1; 117 | c.Y = Y + 1; 118 | c.Height = Height - 2; 119 | c.Width = Width - 2; 120 | if (c is Container ct) 121 | ct.Validate(); 122 | } 123 | } 124 | 125 | public override void Print(GraphicsContext g) 126 | { 127 | if (!Visible) 128 | return; 129 | 130 | g.Write(X, Y, Style.TopLeft + new string(Style.Horizontal, Width - 2) + Style.TopRight); 131 | for (var i = 1; i < Height - 1; i++) 132 | g.Write(X, Y + i, Style.Vertical + new string(' ', Width - 2) + Style.Vertical); 133 | 134 | g.Write(X, Y + Height - 1, Style.BottomLeft + new string(Style.Horizontal, Width - 2) + Style.BottomRight); 135 | 136 | if (Components.Count > 0) 137 | { 138 | Components[0].Print(g); 139 | (Components[0] as ICursorFocusable)?.UpdateCursorState(g); 140 | } 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /handed/minimanual/minimanual.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "SCFE: Installation & Operating Manual" 3 | author: 4 | - Matthieu Stombellini 5 | - Mathieu Rivier 6 | - François Soulier 7 | - Rakhmatullo Rashidov 8 | toc: true 9 | toc-depth: 2 10 | numbersections: true 11 | fontsize: 12pt 12 | papersize: a4 13 | documentclass: article 14 | geometry: margin=3.5cm 15 | include-before: \newpage 16 | include-after-toc: \newpage 17 | logo: slablogo.png 18 | icon: scfelogo.png 19 | graphics: true 20 | --- 21 | 22 | # Introduction 23 | 24 | This manual provides a short guide on how to install and use SCFE. It is divided 25 | in two sections: one for installing SCFE and one for the basic instructions to 26 | get started. 27 | 28 | # Installation and removal 29 | 30 | ## Installing 31 | 32 | Installing SCFE is done like any other application on Windows. 33 | 34 | If you do not have a copy of the installer, you can download one from 35 | salamanders.dev under the Downloads tab. The yellow button should give you the 36 | version for your operating system, but you can also scroll down to download any 37 | version. 38 | 39 | Double click on the EXE file. Should a SmartScreen security prompt appear, click 40 | on "Additional information", then choose the button that just appeared to run 41 | the installer. Another prompt should appear asking for elevated permissions, 42 | click on Yes. 43 | 44 | Then, the welcome page of the installer should appear. Press Next. You will then 45 | be able to modify the path in which SCFE will be installed, although the default 46 | directory should work just fine. Once this is done, click on the Install button. 47 | 48 | SCFE will now be installed on your computer. This could take a few minutes 49 | depending on your computer's speed. Once the installation is done, click on the 50 | Finish button to close the installer. 51 | 52 | ## Uninstalling 53 | 54 | Launching the uninstaller software can be done through three ways: 55 | 56 | * Go in the Start Menu, go to the programs view, look for the SCFE folder, click 57 | on it, and click on "Uninstall SCFE" 58 | * Go to the Windows Settings app, choose Applications, scroll down the list 59 | until you see "SCFE", click on it and click on the Uninstall button 60 | * Go to the Control Panel, click on Uninstall a program, scroll down the list 61 | and click on SCFE, then choose Uninstall. 62 | 63 | When launching the installer, you should see a security prompt: press Yes. Then, 64 | simply press Next and then Uninstall in the removal software. Once the 65 | uninstaller is done removing SCFE, press Finish to close it. 66 | 67 | 68 | # Using SCFE 69 | 70 | Documentation on more advanced features is available in the Documentation 71 | section of the salamanders.dev website. 72 | 73 | Launch SCFE from the Start Menu. You will be greeted by a screen with a few 74 | sections. At the top, you have one line with, on the left, the name of the 75 | directory you are in, and on the right the number of files being shown. This 76 | number has a star if the view is being filtered. 77 | 78 | Note that if you are in your user directory, the `~` symbol is shown instead of 79 | the path to your user directory. 80 | 81 | In the middle, you have your files. You can go up and down using the arrow keys 82 | (you can also use J and K in NAV mode), you can go to the parent folder with the 83 | left arrow key (you can also use H in NAV mode), open a file or folder using 84 | the right arrow key or the Enter key. 85 | 86 | You can see all of the actions you can perform on a file by pressing the O key 87 | (like in "Options"). You can see all of the actions for the folder you are in 88 | with the Shift+O shortcut. 89 | 90 | At the bottom, you will find on the left the word "NAV", "SEA" or "COM", which 91 | correspond to the current mode the application is in, and next to it a text 92 | which will change when you use the app and tells you what is happening. At the 93 | very bottom, there is an input box. Should an action require input from you 94 | (e.g. renaming a file), you will be asked to enter the information at the 95 | bottom of the screen. The Search mode also uses the input box to show you the 96 | current search terms. You can cancel an action or a search with the Escape key. 97 | 98 | The mode system is the core of SCFE and allows you to switch back and forth 99 | between two ways of using the application: 100 | 101 | * NAV mode, the default. It allows you to perform actions with very little 102 | keystrokes and navigate with the arrow keys. 103 | * SEA mode, for searching. The shortcuts are the same as NAV mode but you will 104 | need to press the Ctrl key in addition to the other keys. Typing in letters 105 | will search for files containing the search terms. Pressing Enter while in 106 | the input box in Search mode will directly open the matched file or folder if 107 | only one element corresponds to the search term. If more than one element 108 | were found, pressing Enter will allow you to choose between them. 109 | 110 | For more information on more advanced features, you can have a look at the 111 | documentation on the website, or use the O and Shift+O shortcuts to see all of 112 | the actions that are available. 113 | 114 | Press the Escape key twice to exit SCFE. A third Escape key press might be 115 | necessary if you were in the input box. 116 | -------------------------------------------------------------------------------- /SCFE/SCFE/FileOptionsPanel.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Collections.Immutable; 12 | using System.Collections.ObjectModel; 13 | using System.Linq; 14 | using Viu; 15 | using Viu.Components; 16 | using Viu.Strategy; 17 | using Viu.Table; 18 | 19 | namespace SCFE 20 | { 21 | public class FileOption 22 | { 23 | public static readonly Func, object, bool> OnlyIfSingleFile = 24 | (files, o) => files.Count == 1; 25 | 26 | public static readonly Func, object, bool> Always = (files, o) => true; 27 | 28 | public static readonly List OptionsForCurrentDirectory = new List(); 29 | 30 | public static readonly List Options = new List(); 31 | 32 | 33 | public string Title { get; set; } 34 | public string ActionName { get; set; } 35 | public Func, object, bool> CanActionBeApplied { get; set; } 36 | } 37 | 38 | public class FileOptionsPanel : Parent 39 | { 40 | public FileOptionsPanel(ImmutableList f, AbstractHierarchicalDictionary inputMap) 41 | { 42 | TextComponent fileName; 43 | Strategy = new BorderStrategy(); 44 | 45 | var topParent = new Parent(new LineStrategy {Orientation = Orientation.Vertical}); 46 | AddComponent(topParent, BorderStrategy.Top); 47 | fileName = new TextComponent(f == null ? "Current directory options" : 48 | f.Count == 1 ? f[0].GetFileName() : f.Count + " files...") 49 | {HAlign = HorizontalAlignment.Centered}; 50 | topParent.AddComponent(fileName); 51 | topParent.AddComponent(new Separator()); 52 | 53 | var table = new TableComponent 54 | { 55 | Data = new ObservableCollection(f == null 56 | ? FileOption.OptionsForCurrentDirectory 57 | .Where(opt => opt.CanActionBeApplied?.Invoke(null, null) ?? true).ToList() 58 | : FileOption.Options 59 | .Where(opt => opt.CanActionBeApplied(f, null)).ToList()), 60 | ShowHeader = false 61 | }; 62 | table.AddColumn(new IndicatorColumnType()); 63 | table.AddColumn(new BasicColumnType("", option => option.Title) 64 | { 65 | GrowPriority = 1 66 | }); 67 | table.AddColumn( 68 | new BasicColumnType("", option => SearchForShortcut(option.ActionName, inputMap)) 69 | { 70 | HAlign = HorizontalAlignment.Right 71 | }); 72 | table.ActionOnListElement += (sender, args) => { RemovalCallback(args.Item.ActionName, args.Graphics); }; 73 | AddComponent(table, BorderStrategy.Center); 74 | 75 | var btn = new Button("Back"); 76 | btn.ActionOnComponent += (sender, args) => RemovalCallback?.Invoke(null, args.Graphics); 77 | AddComponent(btn, BorderStrategy.Bottom); 78 | 79 | ActionMap.Put(StandardActionNames.CancelAction, (o, args) => RemovalCallback?.Invoke(null, args.Graphics)); 80 | } 81 | 82 | public Action RemovalCallback { get; set; } 83 | 84 | private string SearchForShortcut(string optionActionName, AbstractHierarchicalDictionary dic) 85 | { 86 | var mode = dic.Compile(); 87 | foreach (var (k, v) in NavigationMode.NavMode.Bindings) 88 | if (mode.ContainsKey(k)) 89 | mode[k] = v; 90 | else 91 | mode.Add(k, v); 92 | 93 | try 94 | { 95 | return "[" + mode.Where(pair => pair.Value == optionActionName).Select(pair => pair.Key) 96 | .Select(key => 97 | { 98 | var s = ""; 99 | if (key.Control == true) 100 | s += "Ctrl+"; 101 | if (key.Alt == true) 102 | s += "Alt+"; 103 | if (key.Shift == true) 104 | s += "Shift+"; 105 | if (key.Key != null) 106 | s += Enum.GetName(typeof(ConsoleKey), key.Key); 107 | else if (key.KeyLetter != null) 108 | s += char.ToUpperInvariant(key.KeyLetter.Value); 109 | return s; 110 | }).Aggregate((cur, next) => cur + " | " + next) + "]"; 111 | } 112 | catch (Exception) 113 | { 114 | // ignored 115 | return ""; 116 | } 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /SCFE/SCFE/NavigationMode.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Diagnostics; 12 | using Viu; 13 | 14 | namespace SCFE 15 | { 16 | public class NavigationMode 17 | { 18 | public static readonly NavigationMode NavMode = new NavigationMode("NAV", null, new Dictionary 19 | { 20 | {new KeyStroke(ConsoleKey.Delete, null, null, null), ScfeActions.DeleteFile}, 21 | {new KeyStroke(ConsoleKey.Spacebar, false, false, false), StandardActionNames.SelectAction}, 22 | {new KeyStroke(ConsoleKey.Enter, true, false, false), ScfeActions.ComMode}, 23 | {new KeyStroke('m', null, false, false), ScfeActions.ChangeMode}, 24 | {new KeyStroke('m', null, false, true), ScfeActions.ComMode}, 25 | 26 | {new KeyStroke('n', null, false, false), ScfeActions.CreateFile}, 27 | {new KeyStroke('n', null, false, true), ScfeActions.CreateFolder}, 28 | {new KeyStroke('e', null, false, false), ScfeActions.ChangeMode}, 29 | {new KeyStroke('d', null, false, false), ScfeActions.DeleteFile}, 30 | {new KeyStroke('c', null, false, false), ScfeActions.CopyFile}, 31 | {new KeyStroke('c', null, false, true), ScfeActions.GitCommit}, 32 | {new KeyStroke('v', null, false, false), ScfeActions.PasteFile}, 33 | {new KeyStroke('x', null, false, false), ScfeActions.CutFile}, 34 | {new KeyStroke('g', null, false, false), ScfeActions.GoToFolder}, 35 | {new KeyStroke('g', null, false, true), ScfeActions.GitClone}, 36 | {new KeyStroke('s', null, false, false), StandardActionNames.SelectAction}, 37 | {new KeyStroke('s', null, false, true), ScfeActions.ChangeSort}, 38 | {new KeyStroke('q', null, false, true), ScfeActions.ToggleSortOrder}, 39 | {new KeyStroke('r', null, false, false), ScfeActions.Rename}, 40 | {new KeyStroke('r', null, false, true), ScfeActions.Refresh}, 41 | {new KeyStroke('o', null, false, true), ScfeActions.CurrDirOptions}, 42 | {new KeyStroke('o', null, false, false), StandardActionNames.SecondaryAction}, 43 | {new KeyStroke('t', null, false, false), ScfeActions.ToggleShowHiddenFiles}, 44 | {new KeyStroke('a', null, false, false), ScfeActions.SelectAll}, 45 | {new KeyStroke('a', null, false, true), ScfeActions.ToggleSelection}, 46 | {new KeyStroke('i', null, false, false), ScfeActions.GitStage}, 47 | {new KeyStroke('i', null, false, true), ScfeActions.GitUnstage}, 48 | {new KeyStroke('i', null, true, false), ScfeActions.GitInit}, 49 | {new KeyStroke('p', null, false, false), ScfeActions.GitPush}, 50 | {new KeyStroke('p', null, false, true), ScfeActions.GitPull}, 51 | 52 | {new KeyStroke('j', false, false, false), StandardActionNames.MoveDown}, 53 | {new KeyStroke('j', false, false, true), ScfeActions.GoDownFast}, 54 | {new KeyStroke('k', false, false, false), StandardActionNames.MoveUp}, 55 | {new KeyStroke('k', false, false, true), ScfeActions.GoUpFast}, 56 | {new KeyStroke('h', false, false, false), StandardActionNames.MoveLeft}, 57 | {new KeyStroke('l', false, false, false), StandardActionNames.MoveRight}, 58 | {new KeyStroke('l', false, false, true), StandardActionNames.SecondaryAction} 59 | }); 60 | 61 | public static readonly NavigationMode SearchMode; 62 | 63 | public static readonly NavigationMode ComMode = new NavigationMode("COM", ConsoleColor.DarkCyan, null); 64 | 65 | public static readonly List NavigationModes; 66 | 67 | static NavigationMode() 68 | { 69 | var dic = new Dictionary 70 | { 71 | {new KeyStroke(ConsoleKey.Spacebar, false, false, false), StandardActionNames.SelectAction} 72 | }; 73 | foreach (var (k, v) in NavMode.Bindings) 74 | if (k.Control == null && k.Shift != null && k.Alt != null) 75 | { 76 | if (k.Key != null) 77 | { 78 | dic.Add(new KeyStroke(k.Key.Value, true, k.Shift, k.Alt), v); 79 | } 80 | else 81 | { 82 | Debug.Assert(k.KeyLetter != null, "k.KeyLetter != null"); 83 | dic.Add(new KeyStroke(k.KeyLetter.Value, true, k.Shift, k.Alt), v); 84 | } 85 | } 86 | else if (k.Control == true) 87 | { 88 | dic.Add(k, v); 89 | } 90 | 91 | SearchMode = new NavigationMode("SEA", ConsoleColor.Magenta, dic) {SearchEnabled = true}; 92 | NavigationModes = new List {NavMode, SearchMode}; 93 | } 94 | 95 | public NavigationMode(string name, ConsoleColor? modeColor, Dictionary bindings) 96 | { 97 | Color = modeColor; 98 | Bindings = bindings; 99 | Name = name; 100 | } 101 | 102 | public ConsoleColor? Color { get; } 103 | 104 | public Dictionary Bindings { get; } 105 | public string Name { get; } 106 | 107 | public bool SearchEnabled { set; get; } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /SCFE/Viu/Strategy/LineStrategy.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using System.Linq; 10 | using Viu.Components; 11 | 12 | namespace Viu.Strategy 13 | { 14 | public class LineStrategy : LayoutStrategy 15 | { 16 | public Orientation Orientation { get; set; } = Orientation.Vertical; 17 | 18 | public bool Centered { get; set; } = false; 19 | public int Gap { get; set; } 20 | 21 | public override void ApplyLayoutStrategy(Parent p) 22 | { 23 | var children = p.GetChildren().Where(i => i.Visible).ToList(); 24 | 25 | int req; 26 | if (Orientation == Orientation.Vertical) 27 | { 28 | children.ForEach(c => c.Width = p.Width); 29 | req = children 30 | .Select(i => i.PrefHeight == Component.Computed ? i.ComputeDimensions().Height : i.PrefHeight) 31 | .Sum() 32 | + Gap * (children.Count - 1); 33 | } 34 | else 35 | { 36 | children.ForEach(c => c.Height = p.Height); 37 | req = children 38 | .Select(i => i.PrefWidth == Component.Computed ? i.ComputeDimensions().Width : i.PrefWidth) 39 | .Sum() 40 | + Gap * (children.Count - 1); 41 | } 42 | 43 | var x = Orientation == Orientation.Horizontal && Centered ? p.X + (p.Width - req) / 2 : p.X; 44 | var y = Orientation == Orientation.Vertical && Centered ? p.Y + (p.Height - req) / 2 : p.Y; 45 | 46 | foreach (var c in children) 47 | { 48 | c.X = x; 49 | c.Y = y; 50 | var dim = c.ComputeDimensions(); 51 | if (Orientation == Orientation.Vertical) 52 | { 53 | c.Height = c.PrefHeight == Component.Computed ? dim.Height : c.PrefHeight; 54 | y += c.Height + Gap; 55 | } 56 | else if (Orientation == Orientation.Horizontal) 57 | { 58 | c.Width = c.PrefWidth == Component.Computed ? dim.Height : c.PrefHeight; 59 | x += c.Width + Gap; 60 | } 61 | } 62 | } 63 | 64 | public override Dimensions ComputeDimensions(Parent p) 65 | { 66 | int wi = 0, he = 0; 67 | var children = p.GetChildren().Where(i => i.Visible).ToList(); 68 | if (Orientation == Orientation.Vertical) 69 | { 70 | wi = p.Width; 71 | 72 | he = Centered 73 | ? p.Height 74 | : children 75 | .Select(i => i.PrefHeight == Component.Computed ? i.ComputeDimensions().Height : i.PrefHeight) 76 | .Sum() 77 | + Gap * (children.Count - 1); 78 | } 79 | 80 | if (Orientation == Orientation.Horizontal) 81 | { 82 | he = p.Height; 83 | 84 | wi = Centered 85 | ? p.Width 86 | : children 87 | .Select(i => i.PrefWidth == Component.Computed ? i.ComputeDimensions().Width : i.PrefWidth) 88 | .Sum() 89 | + Gap * (children.Count - 1); 90 | } 91 | 92 | return new Dimensions(wi, he); 93 | } 94 | 95 | public override bool IsHintAllowed(object hint) 96 | { 97 | return hint == null || hint as string == ""; 98 | } 99 | 100 | public override IFocusable GetUpFocusableElement(Parent parent, Component previousFocus) 101 | { 102 | if (Orientation != Orientation.Vertical) 103 | return null; 104 | 105 | return FocusableBefore(parent, previousFocus); 106 | } 107 | 108 | public override IFocusable GetDownFocusableElement(Parent parent, Component previousFocus) 109 | { 110 | if (Orientation != Orientation.Vertical) 111 | return null; 112 | 113 | return FocusableAfter(parent, previousFocus); 114 | } 115 | 116 | public override IFocusable GetPreviousFocusableElement(Parent parent, Component previousFocus) 117 | { 118 | if (Orientation != Orientation.Horizontal) 119 | return null; 120 | 121 | return FocusableBefore(parent, previousFocus); 122 | } 123 | 124 | public override IFocusable GetNextFocusableElement(Parent parent, Component previousFocus) 125 | { 126 | if (Orientation != Orientation.Horizontal) 127 | return null; 128 | 129 | return FocusableAfter(parent, previousFocus); 130 | } 131 | 132 | private IFocusable FocusableAfter(Parent parent, Component previousFocus) 133 | { 134 | if (previousFocus == null) 135 | return (IFocusable) parent.GetChildren() 136 | .First(i => i.Visible && ((i as IFocusable)?.IsFocusable() ?? false)); 137 | 138 | for (var i = parent.GetChildren().IndexOf(previousFocus) + 1; i < parent.GetChildren().Count; i++) 139 | { 140 | var c = parent.GetChildren()[i]; 141 | if (c.Visible && ((c as IFocusable)?.IsFocusable() ?? false)) return (IFocusable) c; 142 | } 143 | 144 | return null; 145 | } 146 | 147 | private IFocusable FocusableBefore(Parent parent, Component previousFocus) 148 | { 149 | if (previousFocus == null) 150 | return (IFocusable) parent.GetChildren() 151 | .Last(i => i.Visible && ((i as IFocusable)?.IsFocusable() ?? false)); 152 | 153 | for (var i = parent.GetChildren().IndexOf(previousFocus) - 1; i >= 0; i--) 154 | { 155 | var c = parent.GetChildren()[i]; 156 | if (c.Visible && ((c as IFocusable)?.IsFocusable() ?? false)) return (IFocusable) c; 157 | } 158 | 159 | return null; 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /SCFE/Viu/Components/Component.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using System; 10 | 11 | namespace Viu.Components 12 | { 13 | /// 14 | /// A Component is an on-screen element. Classes that have children should be subclasses of Container instead of 15 | /// Component. 16 | /// 17 | public abstract class Component 18 | { 19 | /// 20 | /// Using this value for any coordinate in Pref, Max or Min values will result in the layout automatically 21 | /// attributing the values computed by the component. 22 | /// 23 | public const int Computed = -1; 24 | 25 | private Container _parent; 26 | 27 | /// 28 | /// The X coordinate of the element on the screen (relative to the console, not to the parent). 29 | /// This value is constantly changed by the layout strategy. 30 | /// 31 | public int X { get; set; } 32 | 33 | /// 34 | /// The Y coordinate of the element on the screen (relative to the console, not to the parent). 35 | /// This value is constantly changed by the layout strategy. 36 | /// 37 | public int Y { get; set; } 38 | 39 | /// 40 | /// The width of the element on the screen 41 | /// This value is constantly changed by the layout strategy. 42 | /// 43 | public int Width { get; set; } 44 | 45 | /// 46 | /// The height of the element on the screen 47 | /// This value is constantly changed by the layout strategy. 48 | /// 49 | public int Height { get; set; } 50 | 51 | /// 52 | /// The width the component wishes to have. Said width will be requested to the layout strategy but might not 53 | /// necessarily be respected. 54 | /// 55 | public int PrefWidth { get; set; } = Computed; 56 | 57 | /// 58 | /// The height the component wishes to have. Said width will be requested to the layout strategy but might not 59 | /// necessarily be respected. 60 | /// 61 | public int PrefHeight { get; set; } = Computed; 62 | 63 | // Not used yet 64 | public int MinWidth { get; set; } = Computed; 65 | 66 | // Not used yet 67 | public int MinHeight { get; set; } = Computed; 68 | 69 | // Not used yet 70 | public int MaxWidth { get; set; } = int.MaxValue; 71 | 72 | // Not used yet 73 | public int MaxHeight { get; set; } = int.MaxValue; 74 | 75 | /// 76 | /// Whether the component is visible or not. A component that is not visible does not participate in its 77 | /// parent's layout, as if it was never added. 78 | /// 79 | public bool Visible { get; set; } = true; 80 | 81 | /// 82 | /// Additional information that might be used by the layout strategy. This value is set when calling the Add 83 | /// method to a parent. 84 | /// 85 | public object LayoutInformation { get; internal set; } 86 | 87 | /// 88 | /// The parent of this component. 89 | /// 90 | public Container Parent 91 | { 92 | get => _parent; 93 | internal set 94 | { 95 | _parent = value; 96 | InputMap.Parent = value?.InputMap; 97 | ActionMap.Parent = value?.ActionMap; 98 | } 99 | } 100 | 101 | public AbstractHierarchicalDictionary InputMap { get; set; } = 102 | new BaseHierarchicalDictionary(); 103 | 104 | public AbstractHierarchicalDictionary> ActionMap { get; set; } = 105 | new BaseHierarchicalDictionary>(); 106 | 107 | /// 108 | /// Print the representation of this component on the screen. This method MUST ensure that what it prints stays 109 | /// within its bounds. 110 | /// 111 | /// 112 | public abstract void Print(GraphicsContext g); 113 | 114 | /// 115 | /// Get the default graphics context, if any. This method should only be used as a last resort if there are no 116 | /// other ways to get the graphics context 117 | /// 118 | /// 119 | [Obsolete("This method should only be used as a last resort.")] // This generates a full warning 120 | protected virtual GraphicsContext GetGraphicsContext() 121 | { 122 | if (Parent == null) 123 | throw new NullReferenceException("No graphical context provided by parent components tree"); 124 | return Parent.GetGraphicsContext(); 125 | } 126 | 127 | /// 128 | /// Computes the dimensions this component should have. These can be considered to be "bare minimum" dimensions. 129 | /// 130 | /// The dimensions the component should have 131 | public abstract Dimensions ComputeDimensions(); 132 | 133 | /// 134 | /// Shortcut to define the component's x and y coordinates as well as its width and height 135 | /// 136 | /// The X coordinate this component should have 137 | /// The Y coordinate this component should have 138 | /// The width this component should have 139 | /// The height this component should have 140 | public void SetBounds(int x, int y, int width, int height) 141 | { 142 | X = x; 143 | Y = y; 144 | Width = width; 145 | Height = height; 146 | } 147 | 148 | public virtual IEventThreadManager GetEventThread() 149 | { 150 | return Parent?.GetEventThread(); 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /SCFE/Viu/Components/TextComponent.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using System; 10 | using System.Text; 11 | 12 | namespace Viu.Components 13 | { 14 | /// 15 | /// A TextComponent displays a single line of text which can have a specific foreground color. 16 | /// A TextComponent can be focused by setting the Focusable property to true. A focused TextComponent does not "do" 17 | /// anything, but its behavior can be customized by overriding AcceptInput. 18 | /// 19 | public class TextComponent : Component, IFocusable 20 | { 21 | public TextComponent() 22 | { 23 | } 24 | 25 | public TextComponent(string s) 26 | { 27 | Text = s; 28 | } 29 | 30 | /// 31 | /// The text to display 32 | /// 33 | public string Text { get; set; } = ""; 34 | 35 | /// 36 | /// The foreground color to use when printing the text. Can be set to null to use the default one. 37 | /// 38 | public ConsoleColor? Foreground { get; set; } 39 | 40 | public ConsoleColor? Background { get; set; } 41 | 42 | /// 43 | /// If true, the Text Component will be focusable. 44 | /// 45 | public bool Focusable { get; set; } 46 | 47 | /// 48 | /// True when the text component currently has the focus 49 | /// 50 | private bool HasFocus { get; set; } 51 | 52 | public bool ReverseColors { get; set; } 53 | 54 | public HorizontalAlignment HAlign { get; set; } = HorizontalAlignment.Left; 55 | 56 | public VerticalAlignment VAlign { get; set; } = VerticalAlignment.Top; 57 | 58 | public HorizontalAlignment CutOverflowFrom { get; set; } = HorizontalAlignment.Right; 59 | 60 | public bool ClearBlankSpaceOnReprint { get; set; } 61 | 62 | public virtual bool AcceptInput(ConsoleKeyInfo keyPressed, GraphicsContext g) 63 | { 64 | // Do nothing, nothing is consumed 65 | return false; 66 | } 67 | 68 | public bool IsFocusable() 69 | { 70 | return Visible && Focusable; 71 | } 72 | 73 | public void SetFocused(bool b, GraphicsContext g) 74 | { 75 | HasFocus = b; 76 | Print(g); 77 | } 78 | 79 | public bool IsFocused() 80 | { 81 | return HasFocus; 82 | } 83 | 84 | public override void Print(GraphicsContext g) 85 | { 86 | if (!Visible) 87 | return; 88 | var disp = Text; 89 | if (disp.Length > Width) 90 | switch (CutOverflowFrom) 91 | { 92 | case HorizontalAlignment.Right: 93 | disp = disp.Substring(0, Width); 94 | if (disp.Length > 0) 95 | { 96 | var sb = new StringBuilder(disp); 97 | sb[disp.Length - 1] = '…'; 98 | disp = sb.ToString(); 99 | } 100 | 101 | break; 102 | case HorizontalAlignment.Centered: 103 | var str1 = disp.Substring(0, Width % 2 == 0 ? Width / 2 - 1 : Width / 2); 104 | var str2 = disp.Substring(disp.Length - Width / 2, Width / 2); 105 | disp = disp.Length > 0 ? str1 + '…' + str2 : ""; 106 | break; 107 | case HorizontalAlignment.Left: 108 | disp = disp.Substring(disp.Length - Width, Width); 109 | if (disp.Length > 0) 110 | { 111 | var sb = new StringBuilder(disp); 112 | sb[0] = '…'; 113 | disp = sb.ToString(); 114 | } 115 | 116 | break; 117 | default: 118 | throw new ArgumentOutOfRangeException(); 119 | } 120 | if (ClearBlankSpaceOnReprint && disp.Length < Width) 121 | disp += new string(' ', Width - disp.Length); 122 | int startX, startY; 123 | 124 | switch (VAlign) 125 | { 126 | case VerticalAlignment.Top: 127 | startY = Y; 128 | break; 129 | case VerticalAlignment.Bottom: 130 | startY = Y + Height - 1; 131 | break; 132 | case VerticalAlignment.Centered: 133 | startY = Y + Height / 2; 134 | break; 135 | default: 136 | throw new ArgumentOutOfRangeException(); 137 | } 138 | 139 | switch (HAlign) 140 | { 141 | case HorizontalAlignment.Left: 142 | startX = X; 143 | break; 144 | case HorizontalAlignment.Right: 145 | startX = X + Width - disp.Length; 146 | break; 147 | case HorizontalAlignment.Centered: 148 | startX = X + (Width - disp.Length) / 2; 149 | break; 150 | default: 151 | throw new ArgumentOutOfRangeException(); 152 | } 153 | 154 | if (!HasFocus && !ReverseColors || HasFocus && ReverseColors) 155 | { 156 | if (Foreground == null && Background == null) 157 | g.Write(startX, startY, disp); 158 | else 159 | g.Write(startX, startY, disp, Foreground, Background); 160 | } 161 | else 162 | { 163 | // TODO support background 164 | if (Foreground == null) 165 | g.WriteRevert(startX, startY, disp); 166 | else 167 | g.WriteRevert(startX, startY, disp, Foreground.Value); 168 | } 169 | } 170 | 171 | 172 | public override Dimensions ComputeDimensions() 173 | { 174 | return new Dimensions(Text.Length, 1); 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /SCFE/Viu/Strategy/LayoutStrategy.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using JetBrains.Annotations; 10 | using Viu.Components; 11 | 12 | namespace Viu.Strategy 13 | { 14 | public abstract class LayoutStrategy 15 | { 16 | /// 17 | /// Lay out the underlying components of a parent 18 | /// 19 | /// 20 | public abstract void ApplyLayoutStrategy([NotNull] Parent p); 21 | 22 | /// 23 | /// Compute the predicted width and height of the parent based on this layout 24 | /// 25 | /// 26 | /// 27 | public abstract Dimensions ComputeDimensions([NotNull] Parent p); 28 | 29 | /// 30 | /// Checks if the given hint is valid or not 31 | /// 32 | /// 33 | /// 34 | public abstract bool IsHintAllowed([CanBeNull] object hint); 35 | 36 | /// 37 | /// A method called whenever a component is added to a parent with this layout. This is called after the 38 | /// component is added to the parent's children. 39 | /// 40 | /// The parent to which a component is added 41 | /// The added component 42 | /// 43 | /// The layout hint used on the component, which can be null. This parameter is guaranteed 44 | /// to be such that IsHintAllowed(hint) 45 | /// 46 | public virtual void ComponentAdded([NotNull] Parent p, [NotNull] Component c, [CanBeNull] object hint) 47 | { 48 | } 49 | 50 | /// 51 | /// A method called whenever a component is added to a parent with this layout. This is called after the 52 | /// component is added to the parent's children. 53 | /// 54 | /// The parent to which a component is added 55 | /// The added component 56 | public virtual void ComponentRemoved([NotNull] Parent p, [NotNull] Component c) 57 | { 58 | } 59 | 60 | /// 61 | /// Get the focusable element that is before the current focusable element. The returned element has to be 62 | /// focusable (IsFocusable == true). 63 | /// Usually called when the left arrow key is pressed. 64 | /// 65 | /// The parent component in which the focused element is 66 | /// 67 | /// The element that was previously in focus. Can be null, in which case the 68 | /// strategy must give the first element that is supposed to be accessed when going "left" from outside of the 69 | /// parent. 70 | /// 71 | /// 72 | /// The element that should be focused that is before previousFocus, usually on the left of 73 | /// previousFocus, or null if the previously focused element was the first focusable element of the parent. 74 | /// 75 | [CanBeNull] 76 | public abstract IFocusable GetPreviousFocusableElement([NotNull] Parent parent, 77 | [CanBeNull] Component previousFocus); 78 | 79 | /// 80 | /// Get the focusable element that is after the current focusable element. The returned element has to be 81 | /// focusable (IsFocusable == true) 82 | /// Usually called when the right arrow key is pressed. 83 | /// 84 | /// The parent component in which the focused element is 85 | /// 86 | /// The element that was previously in focus. Can be null, in which case the 87 | /// strategy must give the first element that is supposed to be accessed when going "right" from outside of the 88 | /// parent. 89 | /// 90 | /// 91 | /// The element that should be focused next, usually on the right of previousFocus, or null if the 92 | /// previously focused element was the last focusable element of the parent. 93 | /// 94 | public abstract IFocusable 95 | GetNextFocusableElement([NotNull] Parent parent, [CanBeNull] Component previousFocus); 96 | 97 | /// 98 | /// Get the focusable element that is above the current focusable element. The returned element has to be 99 | /// focusable (IsFocusable == true) 100 | /// Usually called when the up arrow key is pressed. 101 | /// If not overriden, this always returns null. 102 | /// 103 | /// The parent component in which the focused element is 104 | /// 105 | /// The element that was previously in focus. Can be null, in which case the 106 | /// strategy must give the first element that is supposed to be accessed when going "up" from outside of the 107 | /// parent. 108 | /// 109 | /// 110 | /// The element that should be focused next, usually above previousFocus, or null if the 111 | /// previously focused element was the focusable element at the top of the parent. Can also return null if there 112 | /// is no way to go "up". 113 | /// 114 | public virtual IFocusable GetUpFocusableElement([NotNull] Parent parent, [CanBeNull] Component previousFocus) 115 | { 116 | if (previousFocus == null) 117 | GetPreviousFocusableElement(parent, null); 118 | return null; 119 | } 120 | 121 | /// 122 | /// Get the focusable element that is below the current focusable element. The returned element has to be 123 | /// focusable (IsFocusable == true) 124 | /// Usually called when the down arrow key is pressed. 125 | /// 126 | /// The parent component in which the focused element is 127 | /// 128 | /// The element that was previously in focus. Can be null, in which case the 129 | /// strategy must give the first element that is supposed to be accessed when going "down" from outside of the 130 | /// parent. 131 | /// 132 | /// 133 | /// The element that should be focused next, usually below previousFocus, or null if the 134 | /// previously focused element was the focusable element at the bottom of the parent. Can also return null if 135 | /// there is no way to go "down". 136 | /// 137 | public virtual IFocusable GetDownFocusableElement([NotNull] Parent parent, [CanBeNull] Component previousFocus) 138 | { 139 | if (previousFocus == null) 140 | GetNextFocusableElement(parent, null); 141 | return null; 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /SCFE/Viu/GraphicsContext.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using System; 10 | using JetBrains.Annotations; 11 | 12 | namespace Viu 13 | { 14 | /// 15 | /// A Graphics Context provides all of the necessary basic write operations to the display. 16 | /// 17 | public abstract class GraphicsContext 18 | { 19 | /// 20 | /// The default background used by the GraphicsContext. It is determined when the graphics context is 21 | /// initialized. 22 | /// 23 | public abstract ConsoleColor DefaultBackground { get; } 24 | 25 | /// 26 | /// The default foreground used by the GraphicsContext. It is determined when the graphics context is 27 | /// initialized. 28 | /// 29 | public abstract ConsoleColor DefaultForeground { get; } 30 | 31 | /// 32 | /// The current foreground used by the GraphicsContext. It is used in all the methods that do not provide a way 33 | /// to customize the colors, and is used when "null" is used as a parameter when a method allows you to define 34 | /// custom colors. 35 | /// 36 | public abstract ConsoleColor CurrentForeground { get; set; } 37 | 38 | /// 39 | /// The current background used by the GraphicsContext. It is used in all the methods that do not provide a way 40 | /// to customize the colors, and is used when "null" is used as a parameter when a method allows you to define 41 | /// custom colors. 42 | /// 43 | public abstract ConsoleColor CurrentBackground { get; set; } 44 | 45 | /// 46 | /// Initialize the various flags required for the GraphicsContext to work. This is called by the root 47 | /// parent and is only called once throughout the lifecycle of the application. 48 | /// This is where the default background and foreground colors should be determined. 49 | /// 50 | public abstract void Initialize(); 51 | 52 | /// 53 | /// Write a string to the console, starting at the given (x,y) coordinates. 54 | /// 55 | /// x coordinate for the first character of the string (x=0 corresponds to the left) 56 | /// y coordinate for the first character of the string (y=0 corresponds to the top) 57 | /// The string to write 58 | public abstract void Write(int x, int y, [NotNull] string s); 59 | 60 | /// 61 | /// Write a string to the console, starting at the given (x,y) coordinates and using the given colors 62 | /// 63 | /// x coordinate for the first character of the string (x=0 corresponds to the left) 64 | /// y coordinate for the first character of the string (x=0 corresponds to the top) 65 | /// The string to write 66 | /// The foreground color to use, or null to use the current one 67 | /// The background color to use, or null to use the current one 68 | public abstract void Write(int x, int y, [NotNull] string s, 69 | [CanBeNull] ConsoleColor? foreground, 70 | [CanBeNull] ConsoleColor? background); 71 | 72 | /// 73 | /// Write a string to the console, starting at the given (x,y) coordinates with reversed colors: the background 74 | /// color is the foreground color and vice versa. This does not actually change the colors in memory and only 75 | /// writes the string with swapped colors. 76 | /// 77 | /// x coordinate for the first character of the string (x=0 corresponds to the left) 78 | /// y coordinate for the first character of the string (x=0 corresponds to the top) 79 | /// The string to write 80 | public abstract void WriteRevert(int x, int y, [NotNull] string s); 81 | 82 | /// 83 | /// Write a string to the console, starting at the given (x,y) coordinates with reversed colors using a custom color 84 | /// that would normally be in the foreground (and will thus be used as the background color). The foreground color used 85 | /// will be the current background color. This does not actually change the colors in memory and only 86 | /// writes the string with swapped colors. 87 | /// 88 | /// 89 | /// 90 | /// 91 | /// 92 | public abstract void WriteRevert(int x, int y, [NotNull] string s, 93 | ConsoleColor normallyForeground); 94 | 95 | /// 96 | /// Clear an area in the console, emptying it. The current background will be used for the 97 | /// clearing. The coordinates define the top left coordinate of the rectangle to clear, its width and its height 98 | /// 99 | /// The top-left X coordinate of the rectangle to clear 100 | /// The top-left Y coordinate of the rectangle to clear 101 | /// The width of the rectangle to clear 102 | /// The height of the rectangle to clear 103 | public abstract void ClearArea(int startX, int startY, int width, 104 | int height); 105 | 106 | /// 107 | /// Swap the current foreground color with the current background color in memory. 108 | /// 109 | public abstract void SwapColors(); 110 | 111 | /// 112 | /// Set the position of the cursor to the given x and y coordinates. This should not be used as a way to 113 | /// position the cursor when writing things, but should instead be used to show the cursor for components 114 | /// that require some way to show a blinking cursor (e.g. TextField) 115 | /// 116 | /// The x position where to place the cursor (x=0 corresponds to the left) 117 | /// The y position where to pace the cursor (y=0 corresponds to the top) 118 | public abstract void SetCursorPosition(int x, int y); 119 | 120 | /// 121 | /// Set whether the cursor should be visible or not. It will be displayed at the x and y coordinates set by 122 | /// SetCursorPosition. The position of the cursor will shift if any of the Write methods are used: it is recommended to 123 | /// set the cursor's visibility to false in order to avoid it being shown jumping around. 124 | /// 125 | /// Whether the cursor should be visible (true) or not (false) 126 | public abstract void SetCursorVisible(bool visibility); 127 | 128 | /// 129 | /// Clear the entire console's display. 130 | /// 131 | public abstract void Clear(); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /SCFE/Viu/ConsoleGraphicsContext.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using System; 10 | using System.Text; 11 | 12 | namespace Viu 13 | { 14 | /// 15 | /// An implementation of GraphicsContext that uses the basic Console class 16 | /// 17 | public class ConsoleGraphicsContext : GraphicsContext 18 | { 19 | private ConsoleColor _defaultFg, _defaultBg; 20 | 21 | public override ConsoleColor DefaultBackground => _defaultBg; 22 | 23 | public override ConsoleColor DefaultForeground => _defaultFg; 24 | 25 | public override ConsoleColor CurrentForeground 26 | { 27 | get => Console.ForegroundColor; 28 | set => Console.ForegroundColor = value; 29 | } 30 | 31 | public override ConsoleColor CurrentBackground 32 | { 33 | get => Console.BackgroundColor; 34 | set => Console.BackgroundColor = value; 35 | } 36 | 37 | 38 | public override void Initialize() 39 | { 40 | Console.OutputEncoding = Encoding.UTF8; 41 | Console.CursorVisible = false; 42 | Console.TreatControlCAsInput = true; 43 | _defaultFg = Console.ForegroundColor; 44 | _defaultBg = Console.BackgroundColor; 45 | if ((int) Console.ForegroundColor == -1 || (int) Console.BackgroundColor == -1) 46 | { 47 | Console.ForegroundColor = ConsoleColor.Gray; 48 | Console.BackgroundColor = ConsoleColor.Black; 49 | } 50 | } 51 | 52 | 53 | public override void Write(int x, int y, string s) 54 | { 55 | EnsureOrFixSizing(ref x, ref y); 56 | Console.SetCursorPosition(x, y); 57 | Console.Write(s); 58 | RealignIfNecessary(); 59 | } 60 | 61 | /// 62 | /// Realign the cursor to the top of the console if it is below the window. This is to avoid the console 63 | /// scrolling down. 64 | /// 65 | private void RealignIfNecessary() 66 | { 67 | if (Console.CursorTop >= Console.WindowHeight) 68 | Console.SetCursorPosition(0, 0); 69 | } 70 | 71 | /// 72 | /// Method used to avoid crashes on rapid window size changes. Makes sure the X and Y values are within the 73 | /// bounds accepted by the Console class. 74 | /// 75 | /// Reference to the x value 76 | /// Reference to the y value 77 | private void EnsureOrFixSizing(ref int x, ref int y) 78 | { 79 | if (x >= Console.WindowWidth) 80 | x = Console.WindowWidth - 1; 81 | if (x < 0) 82 | x = 0; 83 | if (y >= Console.WindowHeight) 84 | y = Console.WindowHeight - 1; 85 | if (y < 0) 86 | y = 0; 87 | } 88 | 89 | /// 90 | /// Write a string at the given x and y coordinates, reversing the color scheme (the background is used as the 91 | /// foreground color, the foreground is used as the background). Usually used to show that an element is in 92 | /// focus. 93 | /// 94 | /// The x coordinate 95 | /// The y coordinate 96 | /// The string to display 97 | public override void WriteRevert(int x, int y, string s) 98 | { 99 | EnsureOrFixSizing(ref x, ref y); 100 | Console.SetCursorPosition(x, y); 101 | var fg = Console.ForegroundColor; 102 | var bg = Console.BackgroundColor; 103 | Console.ForegroundColor = bg; 104 | Console.BackgroundColor = fg; 105 | Console.Write(s); 106 | Console.ForegroundColor = fg; 107 | Console.BackgroundColor = bg; 108 | RealignIfNecessary(); 109 | } 110 | 111 | /// 112 | /// Write a string at the given x and y coordinates, reversing the color scheme (the background is used as the 113 | /// foreground color, the foreground is used as the background). Usually used to show that an element is in 114 | /// focus. 115 | /// This overload allows you to provide a custom foreground color (which will be used as the background color) 116 | /// 117 | /// The x coordinate 118 | /// The y coordinate 119 | /// The string to write 120 | /// 121 | /// The color that is normally used as the foreground. It will be used as the 122 | /// background color for the reverted color scheme. 123 | /// 124 | public override void WriteRevert(int x, int y, string s, ConsoleColor normallyForeground) 125 | { 126 | EnsureOrFixSizing(ref x, ref y); 127 | Console.SetCursorPosition(x, y); 128 | var fg = Console.ForegroundColor; 129 | var bg = Console.BackgroundColor; 130 | Console.ForegroundColor = bg; 131 | Console.BackgroundColor = normallyForeground; 132 | Console.Write(s); 133 | Console.ForegroundColor = fg; 134 | Console.BackgroundColor = bg; 135 | RealignIfNecessary(); 136 | } 137 | 138 | 139 | public override void ClearArea(int startX, int startY, int width, int height) 140 | { 141 | for (var y = startY; y < startY + height; y++) Write(startX, y, new string(' ', width)); 142 | RealignIfNecessary(); 143 | } 144 | 145 | public override void SwapColors() 146 | { 147 | ConsoleColor fg = Console.ForegroundColor, bg = Console.BackgroundColor; 148 | Console.ForegroundColor = bg; 149 | Console.BackgroundColor = fg; 150 | } 151 | 152 | public override void SetCursorPosition(int x, int y) 153 | { 154 | Console.SetCursorPosition(x, y); 155 | } 156 | 157 | public override void SetCursorVisible(bool visibility) 158 | { 159 | Console.CursorVisible = visibility; 160 | } 161 | 162 | public override void Clear() 163 | { 164 | Console.Clear(); 165 | Console.CursorVisible = true; 166 | } 167 | 168 | /// 169 | /// Write a string to the console at the given x and y coordinates, in the given given foreground color. The 170 | /// previous foreground color is remembered and restored. 171 | /// 172 | /// x position to start the string (x=0 is the left side) 173 | /// y position to start the string (y=0 is the top side) 174 | /// The string to write 175 | /// The foreground color to use, or null to not use any. 176 | /// The background color to use, or null to not use any. 177 | public override void Write(int x, int y, string s, ConsoleColor? foreground, ConsoleColor? background) 178 | { 179 | EnsureOrFixSizing(ref x, ref y); 180 | var fg = Console.ForegroundColor; 181 | var bg = Console.BackgroundColor; 182 | // Use the given color if it's not null, if it's null just use the regular color 183 | Console.ForegroundColor = foreground ?? Console.ForegroundColor; 184 | Console.BackgroundColor = background ?? Console.BackgroundColor; 185 | Console.SetCursorPosition(x, y); 186 | Console.Write(s); 187 | Console.ForegroundColor = fg; 188 | Console.BackgroundColor = bg; 189 | RealignIfNecessary(); 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /SCFE/SCFE/ComExtension.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using System; 10 | using System.Collections.Generic; 11 | using System.ComponentModel.Design; 12 | using System.Diagnostics; 13 | using System.Linq; 14 | using System.Text.RegularExpressions; 15 | using System.Threading; 16 | using JetBrains.Annotations; 17 | using Viu; 18 | using Viu.Table; 19 | 20 | namespace SCFE 21 | { 22 | public class ComExtension : IScfeExtension 23 | { 24 | private ScfeApp _app; 25 | private Dictionary _managedProcesses = new Dictionary(); 26 | 27 | public ComExtension(ScfeApp app) 28 | { 29 | _app = app; 30 | } 31 | 32 | public IEnumerable> GetColumns() 33 | { 34 | // No columns for the com mode 35 | return new List>(); 36 | } 37 | 38 | public Dictionary> GetActions() 39 | { 40 | return new Dictionary> 41 | { 42 | { 43 | ScfeActions.ComMode, (o, args) => 44 | { 45 | _app.PrintModeInformation(NavigationMode.ComMode, args.Graphics); 46 | _app.Request("Command...", args.Graphics, 47 | (s, context, finish) => 48 | { 49 | finish(); 50 | Regex reg = new Regex(@"^(\d+)\$(.+)$"); 51 | Match match = reg.Match(s); 52 | if (match.Success) 53 | { 54 | int id = int.Parse(match.Groups[1].Value); 55 | if (!_managedProcesses.ContainsKey(id)) 56 | { 57 | _app.ShowHelpMessage("Invalid or untracked PID: " + id, args.Graphics); 58 | return; 59 | } 60 | 61 | _managedProcesses[id].StandardInput.WriteLine(s); 62 | return; 63 | } 64 | 65 | _app.PrintModeInformation(_app.CurrentMode, args.Graphics); 66 | _app.AddTask(task => 67 | { 68 | try 69 | { 70 | string filename; 71 | string pargs; 72 | switch (Environment.OSVersion.Platform) 73 | { 74 | case PlatformID.Win32NT: 75 | filename = "powershell"; 76 | pargs = "-NonInteractive -Command \"& {" + 77 | s.Trim().Replace("\"", "\"\"\"") + "}\""; 78 | break; 79 | case PlatformID.Unix: 80 | case PlatformID.MacOSX: 81 | filename = "bash"; 82 | pargs = "-c \"" + s.Replace("\"", "\\\"") + "\""; 83 | break; 84 | default: 85 | return new TaskResult(false, 86 | "COM mode is not supported on your platform"); 87 | } 88 | 89 | var proc = new Process 90 | { 91 | StartInfo = new ProcessStartInfo 92 | { 93 | FileName = filename, 94 | Arguments = pargs, 95 | UseShellExecute = false, 96 | RedirectStandardInput = true, 97 | RedirectStandardOutput = true, 98 | RedirectStandardError = true, 99 | WorkingDirectory = _app.CurrentDir.FullPath 100 | } 101 | }; 102 | proc.Start(); 103 | Thread.Sleep(100); 104 | _managedProcesses.Add(proc.Id, proc); 105 | _app.ShowHelpMessageLater("Command started executing with PID " + proc.Id); 106 | 107 | string lastLine = null; 108 | while (!proc.StandardOutput.EndOfStream) 109 | { 110 | var line = proc.StandardOutput.ReadLine(); 111 | if (line != null) 112 | { 113 | line = line.Replace("\n", "").Replace("\t", ""); 114 | if (!string.IsNullOrWhiteSpace(line)) 115 | { 116 | lastLine = line; 117 | _app.ShowHelpMessageLater($"[{proc.Id}] {line}"); 118 | } 119 | } 120 | } 121 | 122 | if (proc.ExitCode == 0) 123 | return new TaskResult(true, 124 | lastLine != null 125 | ? $"[{proc.Id}] Finished: {lastLine}" 126 | : $"[{proc.Id}] Process finished"); 127 | 128 | string errorMsg = proc.StandardError.ReadLine(); 129 | if (errorMsg == null) 130 | return new TaskResult(false, 131 | $"[{proc.Id}] Failed (exit code {proc.ExitCode})"); 132 | return new TaskResult(false, 133 | $"[{proc.Id}] Failed (code {proc.ExitCode}): {errorMsg}"); 134 | } 135 | catch (Exception e) 136 | { 137 | return new TaskResult(false, "Error: " + e.Message); 138 | } 139 | }); 140 | }, false, g => { _app.PrintModeInformation(_app.CurrentMode, g); }); 141 | } 142 | } 143 | }; 144 | } 145 | 146 | public IEnumerable GetCurrDirOptions() 147 | { 148 | return new List 149 | { 150 | new FileOption 151 | { 152 | Title = "Switch to COM mode", 153 | ActionName = ScfeActions.ComMode 154 | } 155 | }; 156 | } 157 | 158 | public IEnumerable GetFilesOptions() 159 | { 160 | return new List(); 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /SCFE/Viu/Components/Parent.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Diagnostics; 12 | using System.Linq; 13 | using Viu.Strategy; 14 | 15 | namespace Viu.Components 16 | { 17 | /// 18 | /// 19 | /// 20 | /// A Parent is a Component which has children and organizes them according to a specific Layout Strategy. 21 | /// Children can be added to the parent using the AddComponent method, with or without the layout hint, which is 22 | /// used by the Layout Strategy to properly organize the components. 23 | /// By default, parents use a FlowStrategy which organizes elements into a single row. 24 | /// 25 | public class Parent : Container, IFocusable 26 | { 27 | /// 28 | /// Create a standard parent with a FlowStrategy 29 | /// 30 | public Parent() 31 | { 32 | Strategy = new FlowStrategy(); 33 | } 34 | 35 | /// 36 | /// Create a parent with the given layout strategy 37 | /// 38 | /// 39 | public Parent(LayoutStrategy strategy) 40 | { 41 | Strategy = strategy; 42 | } 43 | 44 | /// 45 | /// The strategy that should be used by the parent upon validation 46 | /// 47 | public LayoutStrategy Strategy { get; set; } 48 | 49 | public virtual bool AcceptInput(ConsoleKeyInfo keyPressed, GraphicsContext g) 50 | { 51 | foreach (var c in Components) 52 | if (c is IFocusable foc && foc.IsFocusable() && foc.IsFocused()) 53 | { 54 | if (foc.AcceptInput(keyPressed, g)) return true; 55 | 56 | var ins = c.InputMap.Compile(); 57 | var str = Utils.GetActionNameForKey(ins, keyPressed); 58 | if (str != null) 59 | { 60 | var actMap = c.ActionMap.Compile(); 61 | if (actMap.ContainsKey(str)) 62 | { 63 | actMap[str](c, new ActionEventArgs(c, keyPressed, g)); 64 | return true; 65 | } 66 | } 67 | } 68 | 69 | // If we are still here it means that none of the focusable components have accepted any input 70 | // Use our own shortcuts to switch to the next focusable component 71 | 72 | var prev = GetFocusedElement(false); 73 | var inputs = InputMap.Compile(); 74 | IFocusable next = null; 75 | 76 | if (Utils.KeyCorresponds(inputs, keyPressed, StandardActionNames.MoveUp)) 77 | next = Strategy.GetUpFocusableElement(this, prev); 78 | else if (Utils.KeyCorresponds(inputs, keyPressed, StandardActionNames.MoveLeft)) 79 | next = Strategy.GetPreviousFocusableElement(this, prev); 80 | else if (Utils.KeyCorresponds(inputs, keyPressed, StandardActionNames.MoveRight)) 81 | next = Strategy.GetNextFocusableElement(this, prev); 82 | else if (Utils.KeyCorresponds(inputs, keyPressed, StandardActionNames.MoveDown)) 83 | next = Strategy.GetDownFocusableElement(this, prev); 84 | 85 | if (next != null) 86 | { 87 | if (prev != null) 88 | { 89 | var f = prev as IFocusable; 90 | Debug.Assert(f != null, nameof(f) + " != null"); 91 | f.SetFocused(false, g); 92 | } 93 | 94 | next.SetFocused(true, g); 95 | return true; 96 | } 97 | 98 | 99 | return false; 100 | } 101 | 102 | public bool IsFocusable() 103 | { 104 | return Components.Any(x => (x as IFocusable)?.IsFocusable() ?? false); 105 | } 106 | 107 | public bool IsFocused() 108 | { 109 | return Components.Any(x => (x as IFocusable)?.IsFocused() ?? false); 110 | } 111 | 112 | public void SetFocused(bool focused, GraphicsContext g) 113 | { 114 | if (focused) 115 | { 116 | var f = Strategy.GetNextFocusableElement(this, null); 117 | if (f == null) 118 | foreach (var c in Components) 119 | if (c.Visible && c is IFocusable foc && foc.IsFocusable()) 120 | { 121 | f = foc; 122 | break; 123 | } 124 | 125 | f?.SetFocused(true, g); 126 | (f as ICursorFocusable)?.UpdateCursorState(g); 127 | } 128 | else 129 | { 130 | foreach (var c in Components) 131 | { 132 | var f = c as IFocusable; 133 | var b = (f?.IsFocused() ?? false) && f is ICursorFocusable; 134 | f?.SetFocused(false, g); 135 | if (b) 136 | (f as ICursorFocusable).UpdateCursorState(g); 137 | } 138 | } 139 | } 140 | 141 | /// 142 | /// Add a component to this parent. This throws an ArgumentException of the component already has a parent 143 | /// elsewhere, or if the layout hint is not allowed by the layout strategy. 144 | /// 145 | /// The component to add 146 | /// 147 | /// The layout hint to use. It is used as a tip on where to place the component for the 148 | /// strategy 149 | /// 150 | /// 151 | /// If the component already has a parent or if the strategy does not accept 152 | /// the given hint 153 | /// 154 | public void AddComponent(Component c, object layoutHint) 155 | { 156 | if (c.Parent != null) 157 | throw new ArgumentException("A component cannot have multiple parents"); 158 | if (!Strategy.IsHintAllowed(layoutHint)) 159 | throw new ArgumentException("Hint not accepted by layout strategy"); 160 | 161 | Components.Add(c); 162 | Strategy.ComponentAdded(this, c, layoutHint); 163 | c.Parent = this; 164 | c.LayoutInformation = layoutHint; 165 | } 166 | 167 | /// 168 | /// Add a component to this parent. This throws an ArgumentException of the component already has a parent 169 | /// elsewhere, or if the layout hint does not allow adding components without any layout hint. 170 | /// 171 | /// The component to add 172 | /// 173 | /// If the component already has a parent or if the strategy does not accept 174 | /// hint-less addition of components, in which case you should use the overload with an additional object 175 | /// argument. 176 | /// 177 | public void AddComponent(Component c) 178 | { 179 | if (c.Parent != null) 180 | throw new ArgumentException("A component cannot have multiple parents"); 181 | if (!Strategy.IsHintAllowed(null)) 182 | throw new ArgumentException("Component must have a layout hint according to the strategy"); 183 | 184 | Components.Add(c); 185 | Strategy.ComponentAdded(this, c, null); 186 | c.Parent = this; 187 | c.LayoutInformation = null; 188 | } 189 | 190 | public bool RemoveComponent(Component c) 191 | { 192 | var b = Components.Remove(c); 193 | if (b) 194 | c.Parent = null; 195 | Strategy.ComponentRemoved(this, c); 196 | return b; 197 | } 198 | 199 | public override void Validate() 200 | { 201 | Strategy.ApplyLayoutStrategy(this); 202 | 203 | foreach (var c in Components) (c as Container)?.Validate(); 204 | } 205 | 206 | public override Dimensions ComputeDimensions() 207 | { 208 | return Strategy.ComputeDimensions(this); 209 | } 210 | 211 | public List GetChildren() 212 | { 213 | return Components; 214 | } 215 | 216 | public override void Print(GraphicsContext g) 217 | { 218 | if (!Visible) 219 | return; 220 | 221 | base.Print(g); 222 | (GetFocusedElement(true) as ICursorFocusable)?.UpdateCursorState(g); 223 | } 224 | 225 | public Component GetFocusedElement(bool indepth) 226 | { 227 | try 228 | { 229 | var c = Components.First(x => (x as IFocusable)?.IsFocused() ?? false); 230 | if (indepth && c is Parent parent) 231 | return parent.GetFocusedElement(true); 232 | return c; 233 | } 234 | catch (InvalidOperationException) 235 | { 236 | return null; 237 | } 238 | } 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /SCFE/Viu/Components/TextField.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using System; 10 | 11 | namespace Viu.Components 12 | { 13 | public class TextField : Component, ICursorFocusable, IActionable 14 | { 15 | private bool Focused { get; set; } 16 | 17 | public bool Focusable { get; set; } = true; 18 | public string Text { get; set; } = ""; 19 | 20 | public bool HideText { get; set; } 21 | public string PlaceholderText { get; set; } = ""; 22 | 23 | public int CaretPosition { get; set; } 24 | 25 | public bool ShowLine { get; set; } = true; 26 | 27 | public string DisplayedText => HideText 28 | ? "(hidden for privacy)" 29 | : Text.Length > 0 30 | ? Text 31 | : PlaceholderText; 32 | 33 | public event EventHandler ActionOnComponent; 34 | 35 | public bool AcceptInput(ConsoleKeyInfo keyPressed, GraphicsContext g) 36 | { 37 | g.SetCursorVisible(false); 38 | var map = InputMap.Compile(); 39 | 40 | if (Utils.KeyCorresponds(map, keyPressed, StandardActionNames.MoveLeftWord)) 41 | { 42 | // DOING 43 | // Move one word to the left, i.e. move to the left until you meet a space (or the beginning of the line) 44 | 45 | var i = CaretPosition - 1; 46 | var skipSpaces = true; 47 | // this is so that we jump to the previous word if we are at the beginning of a 48 | // word when ctrl <- is invoked 49 | 50 | while (CaretPosition >= 0 && CaretPosition <= Text.Length) 51 | { 52 | if (CaretPosition == 0) 53 | { 54 | UpdateCursorState(g); 55 | return true; 56 | } 57 | 58 | switch (Text[i]) 59 | { 60 | case ' ': 61 | if (!skipSpaces) 62 | { 63 | UpdateCursorState(g); 64 | return true; 65 | } 66 | 67 | goto default; // Jump to case default 68 | default: 69 | CaretPosition--; 70 | i--; 71 | skipSpaces = false; 72 | break; 73 | } 74 | } 75 | 76 | UpdateCursorState(g); 77 | return false; 78 | } 79 | 80 | if (Utils.KeyCorresponds(map, keyPressed, StandardActionNames.MoveRightWord)) 81 | { 82 | var skipSpaces = true; 83 | 84 | while (CaretPosition <= Text.Length) 85 | { 86 | if (CaretPosition == Text.Length) 87 | { 88 | UpdateCursorState(g); 89 | return true; 90 | } 91 | 92 | switch (Text[CaretPosition]) 93 | { 94 | case ' ': 95 | if (!skipSpaces) 96 | { 97 | UpdateCursorState(g); 98 | return true; 99 | } 100 | 101 | CaretPosition++; 102 | break; 103 | default: 104 | CaretPosition++; 105 | skipSpaces = false; 106 | break; 107 | } 108 | } 109 | 110 | UpdateCursorState(g); 111 | return false; 112 | } 113 | 114 | 115 | if (Utils.KeyCorresponds(map, keyPressed, StandardActionNames.BaseAction)) 116 | { 117 | ActionOnComponent?.Invoke(this, new ActionEventArgs(this, keyPressed, g)); 118 | UpdateCursorState(g); 119 | return true; 120 | } 121 | 122 | if (Utils.KeyCorresponds(map, keyPressed, StandardActionNames.MoveLeft)) 123 | { 124 | if (CaretPosition > 0) 125 | { 126 | CaretPosition -= 1; 127 | UpdateCursorState(g); 128 | return true; 129 | } 130 | 131 | UpdateCursorState(g); 132 | return false; 133 | } 134 | 135 | if (Utils.KeyCorresponds(map, keyPressed, StandardActionNames.MoveRight)) 136 | { 137 | if (CaretPosition < Text.Length) 138 | { 139 | CaretPosition += 1; 140 | UpdateCursorState(g); 141 | return true; 142 | } 143 | 144 | UpdateCursorState(g); 145 | return false; 146 | } 147 | 148 | if (Utils.KeyCorresponds(map, keyPressed, StandardActionNames.DeleteToTheLeftAction)) 149 | { 150 | if (CaretPosition > 0) 151 | { 152 | Text = Text.Remove(CaretPosition - 1, 1); 153 | CaretPosition -= 1; 154 | Print(1, g); 155 | OnTextChanged?.Invoke(this, new ActionEventArgs(this, keyPressed, g)); 156 | 157 | UpdateCursorState(g); 158 | return true; 159 | } 160 | 161 | UpdateCursorState(g); 162 | return false; 163 | } 164 | 165 | if (Utils.KeyCorresponds(map, keyPressed, StandardActionNames.DeleteToTheRightAction)) 166 | { 167 | if (CaretPosition > 0 && CaretPosition < Text.Length) 168 | { 169 | Text = Text.Remove(CaretPosition, 1); 170 | Print(1, g); 171 | OnTextChanged?.Invoke(this, new ActionEventArgs(this, keyPressed, g)); 172 | 173 | UpdateCursorState(g); 174 | return true; 175 | } 176 | 177 | 178 | UpdateCursorState(g); 179 | return false; 180 | } 181 | 182 | if (Utils.KeyCorresponds(map, keyPressed, StandardActionNames.MoveLineStart)) 183 | { 184 | // TODO 185 | // Move the caret to the beginning of the line 186 | if (CaretPosition > 0) 187 | { 188 | CaretPosition = 0; 189 | 190 | UpdateCursorState(g); 191 | return true; 192 | } 193 | 194 | UpdateCursorState(g); 195 | return false; 196 | } 197 | 198 | if (Utils.KeyCorresponds(map, keyPressed, StandardActionNames.MoveLineEnd)) 199 | { 200 | // TODO 201 | // Move the caret to the end of the line 202 | if (CaretPosition < Text.Length) 203 | { 204 | CaretPosition = Text.Length; 205 | UpdateCursorState(g); 206 | return true; 207 | } 208 | 209 | UpdateCursorState(g); 210 | return false; 211 | } 212 | 213 | // Letters: add the letters to Text and write them with the following line. Also increase CaretPosition. 214 | // g.Write(X + CaretPosition, Y, ); 215 | // Backspace: delete the letter, careposition - 1, make sure to rewrite the string correctly (or call Print 216 | // if you're lazy) 217 | 218 | if (keyPressed.KeyChar > (char) 31 && keyPressed.Key != ConsoleKey.Enter && 219 | keyPressed.Key != ConsoleKey.Tab && keyPressed.Key != ConsoleKey.Escape && 220 | // NOT(control pressed XOR alt pressed) 221 | (keyPressed.Modifiers & ConsoleModifiers.Control) == 0 == 222 | ((keyPressed.Modifiers & ConsoleModifiers.Alt) == 0)) 223 | { 224 | Text = Text.Insert(CaretPosition, "" + keyPressed.KeyChar); 225 | CaretPosition += 1; 226 | Print(PlaceholderText.Length - 1, g); 227 | OnTextChanged?.Invoke(this, new ActionEventArgs(this, keyPressed, g)); 228 | UpdateCursorState(g); 229 | return true; 230 | } 231 | 232 | 233 | // USE .KeyChar AND NOT .Key.ToString() !!! 234 | 235 | // If it matched one of the cases above, return true 236 | // Anything else: return false 237 | UpdateCursorState(g); 238 | return false; 239 | } 240 | 241 | public bool IsFocusable() 242 | { 243 | return Focusable; 244 | } 245 | 246 | public void SetFocused(bool focused, GraphicsContext g) 247 | { 248 | Focused = focused; 249 | UpdateCursorState(g); 250 | } 251 | 252 | public bool IsFocused() 253 | { 254 | return Focused; 255 | } 256 | 257 | public void UpdateCursorState(GraphicsContext g) 258 | { 259 | if (Focused) 260 | { 261 | if (HideText) 262 | g.SetCursorPosition(X, Y); 263 | else 264 | g.SetCursorPosition(X + CaretPosition, Y); 265 | g.SetCursorVisible(true); 266 | } 267 | else 268 | { 269 | g.SetCursorVisible(false); 270 | } 271 | } 272 | 273 | public event EventHandler OnTextChanged; 274 | 275 | public override void Print(GraphicsContext g) 276 | { 277 | Print(0, g); 278 | } 279 | 280 | public void Print(int withPaddingForced, GraphicsContext g) 281 | { 282 | if (!Visible) 283 | return; 284 | 285 | var (toPrint, color) = 286 | HideText 287 | ? ("(hidden for privacy)", ConsoleColor.Cyan) 288 | : Text.Length > 0 289 | ? (Text, (ConsoleColor?) null) 290 | : (PlaceholderText, ConsoleColor.DarkGray); 291 | 292 | if (toPrint.Length > 0) 293 | g.Write(X, Y, 294 | toPrint.Length > Width 295 | ? toPrint.Substring(0, Width) 296 | : withPaddingForced > 0 297 | ? toPrint + new string(' ', 298 | Math.Min(Width - toPrint.Length, withPaddingForced)) 299 | : toPrint, 300 | color, null); 301 | 302 | if (ShowLine) 303 | g.Write(X, Y + 1, new string(LineStyle.Dotted.Horizontal, Width)); 304 | 305 | g.SetCursorPosition(X + CaretPosition, Y); 306 | } 307 | 308 | public override Dimensions ComputeDimensions() 309 | { 310 | return new Dimensions(Math.Max(20, HideText ? 20 : Text == "" ? PlaceholderText.Length : Text.Length), 311 | ShowLine ? 2 : 1); 312 | } 313 | } 314 | } 315 | -------------------------------------------------------------------------------- /SCFE/SCFE/Program.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using System; 10 | using System.Collections.ObjectModel; 11 | using System.IO; 12 | using System.Net.Http.Headers; 13 | using System.Runtime.CompilerServices; 14 | using System.Threading; 15 | using Viu; 16 | using Viu.Components; 17 | using Viu.Strategy; 18 | using Viu.Table; 19 | 20 | namespace SCFE 21 | { 22 | internal class ColorfulLabel : TextComponent 23 | { 24 | public ColorfulLabel(string s) : base(s) 25 | { 26 | InputMap.Put(new KeyStroke('c', false, false, false), "color"); 27 | ActionMap.Put("color", (o, args) => 28 | { 29 | var rng = new Random(); 30 | var values = Enum.GetValues(typeof(ConsoleColor)); 31 | Foreground = (ConsoleColor?) values.GetValue(rng.Next(values.Length)); 32 | Print(args.Graphics); 33 | }); 34 | } 35 | 36 | public override bool AcceptInput(ConsoleKeyInfo keyPressed, GraphicsContext g) 37 | { 38 | return false; 39 | } 40 | } 41 | 42 | internal static class Program 43 | { 44 | public static void Main(string[] args) 45 | { 46 | // Hello! 47 | Console.Clear(); 48 | 49 | // --- Select a test here. Uncomment it to launch it. --- 50 | // DO NOT LAUNCH TWO TESTS AT THE SAME TIME! 51 | 52 | // A test for Buttons (RAKHMATULLO) 53 | //LaunchSingleTest(TestButtons); 54 | 55 | // A test for TextAreas (MATHIEU) 56 | //LaunchSingleText(TestTextArea); 57 | 58 | // A BorderStrategy test 59 | //LaunchSingleTest(TestBorders); 60 | 61 | // An input handler test for quickly checking the properties of ConsoleKeyInfo objects 62 | //LaunchSingleTest(TestInput); 63 | 64 | // Test the Table component 65 | //LaunchSingleTest(TestTable); 66 | 67 | // Another Table component test but more complete 68 | //ViuExampleTable.LaunchExample(); 69 | 70 | // Launch the full SCFE app (or at least its current prototype) 71 | TestApp(args.Length > 0 ? args[0] : null); 72 | 73 | // --- DEFENSE 1 DEMOS --- 74 | // Figures 1 and 2 75 | //LaunchSingleTest(TestSimpleBorder); 76 | 77 | // Figure 3 78 | //LaunchSingleTest(TestTable); 79 | 80 | //var viuDemo = new ViuDemo(); 81 | 82 | while (true) 83 | Thread.Sleep(1000); 84 | // ReSharper disable once FunctionNeverReturns 85 | } 86 | 87 | // ReSharper disable once UnusedMember.Local 88 | private static void LaunchSingleTest(Func test) 89 | { 90 | var cons = new ConsoleParent(test()); 91 | cons.Validate(); 92 | cons.FocusFirst(); 93 | cons.Print(); 94 | } 95 | 96 | public static Parent TestSimpleBorder() 97 | { 98 | var par = new Parent(new BorderStrategy()); 99 | 100 | par.AddComponent(new TextComponent("Hey!") {VAlign = VerticalAlignment.Centered}, BorderStrategy.Left); 101 | par.AddComponent( 102 | new TextComponent("This is at the top of the window!") {HAlign = HorizontalAlignment.Centered}, 103 | BorderStrategy.Top); 104 | par.AddComponent( 105 | new TextComponent("This is at the bottom of the window!") {HAlign = HorizontalAlignment.Centered}, 106 | BorderStrategy.Bottom); 107 | par.AddComponent(new TextComponent("Bye!") {VAlign = VerticalAlignment.Centered}, BorderStrategy.Right); 108 | 109 | var center = new Parent(new LineStrategy {Centered = true}); 110 | par.AddComponent(center, BorderStrategy.Center); 111 | 112 | center.AddComponent(new TextComponent("This is a simple demo of the 'BorderStrategy'.") 113 | {HAlign = HorizontalAlignment.Centered, Focusable = true}); 114 | center.AddComponent(new TextComponent("The middle container uses a 'LineStrategy'") 115 | {HAlign = HorizontalAlignment.Centered, Focusable = true}); 116 | center.AddComponent(new TextComponent("with multiple Text components on top of each other.") 117 | {HAlign = HorizontalAlignment.Centered, Focusable = true}); 118 | 119 | return par; 120 | } 121 | 122 | public static Parent TestButtons() 123 | { 124 | var par = new Parent(new LineStrategy {Centered = true, Gap = 1}); 125 | 126 | var txt = new TextComponent("I am a text! I do texty stuff!") 127 | {HAlign = HorizontalAlignment.Centered}; 128 | 129 | par.AddComponent(new TextComponent( 130 | "Use arrow keys to move around options. " + 131 | "Press Enter to apply the color.")); 132 | 133 | var btns = new Parent(new FlowStrategy(true, 1, 0)); 134 | foreach (ConsoleColor col in Enum.GetValues(typeof(ConsoleColor))) 135 | { 136 | if (col == ConsoleColor.Black) 137 | continue; 138 | 139 | var b = new Button("I like " + Enum.GetName(typeof(ConsoleColor), col)) {Foreground = col}; 140 | btns.AddComponent(b); 141 | b.ActionOnComponent += (sender, args) => 142 | { 143 | txt.Foreground = col; 144 | txt.Print(args.Graphics); 145 | }; 146 | } 147 | 148 | par.AddComponent(btns); 149 | 150 | par.AddComponent(txt); 151 | 152 | return par; 153 | } 154 | 155 | public static void TestApp(string startingPath) 156 | { 157 | var colFile = File.UserHome.GetChildMaybe(".SCFE")?.GetChildMaybe("columns.txt"); 158 | string[] columns = null; 159 | if (colFile != null && colFile.Exists()) 160 | { 161 | using (StreamReader sr = new StreamReader(colFile.FullPath)) 162 | { 163 | var colStr = sr.ReadLine(); 164 | if (colStr != null) 165 | columns = colStr.Split(','); 166 | } 167 | } 168 | 169 | if (columns == null) 170 | columns = new[] {"name", "git", "size", "date"}; 171 | 172 | var f = new File(startingPath); 173 | var app = new ScfeApp(f.Exists() ? f : null, columns); 174 | app.Show(); 175 | } 176 | 177 | public static Parent TestTable() 178 | { 179 | var par = new Parent(new BorderStrategy()) {ClearAreaBeforePrint = false}; 180 | 181 | var data = new ObservableCollection 182 | { 183 | new[] {"Hello!", "This is a table", "A nice one"}, 184 | new[] {"hey", "bonjour", "buon giorno"}, 185 | new[] {"who", "are", "you?"} 186 | }; 187 | 188 | var table = new TableComponent 189 | {Data = data}; 190 | //table.AddColumn(new BasicColumnInformationType("Test.", s => s[0])); 191 | //table.AddColumn(new BasicColumnInformationType("Zboui zboui zboui.", s => s[1])); 192 | //table.AddColumn(new BasicColumnInformationType("Test...", s => s[2])); 193 | table.AddColumn(new IndicatorColumnType()); 194 | table.AddColumn(new MultistateColumnType(new[] {"This is a screenshot", "Screenshot"}, 195 | x => new[] {x[0], x[0].Split(' ')[0], x[0].ToCharArray()[0] + ""} 196 | ) {Priority = 1, GrowPriority = 10}); 197 | table.AddColumn(new MultistateColumnType( 198 | new[] {"A fairly long column name", "A shorter one", "A"}, 199 | x => new[] 200 | {x[1], x[1].Split(' ')[0], x[1].ToCharArray()[0] + ""} 201 | ) {Priority = 2, GrowPriority = 20}); 202 | table.AddColumn(new MultistateColumnType(new[] {"I am running out of ideas", "Ideas", "I"}, 203 | x => new[] {x[2], x[2].Split(' ')[0], x[2].ToCharArray()[0] + ""} 204 | ) {Priority = 2, GrowPriority = 10}); 205 | par.AddComponent(table, BorderStrategy.Center); 206 | 207 | return par; 208 | } 209 | 210 | public static void LaunchTestInput() 211 | { 212 | ConsoleKeyInfo cki; 213 | // Prevent example from ending if CTL+C is pressed. 214 | Console.TreatControlCAsInput = true; 215 | 216 | Console.WriteLine("Press any combination of CTL, ALT, and SHIFT, and a console key."); 217 | Console.WriteLine("Press the Escape (Esc) key to quit: \n"); 218 | do 219 | { 220 | cki = Console.ReadKey(); 221 | Console.Write(" --- You pressed "); 222 | if ((cki.Modifiers & ConsoleModifiers.Alt) != 0) Console.Write("ALT+"); 223 | if ((cki.Modifiers & ConsoleModifiers.Shift) != 0) Console.Write("SHIFT+"); 224 | if ((cki.Modifiers & ConsoleModifiers.Control) != 0) Console.Write("CTL+"); 225 | Console.WriteLine(cki.KeyChar + " (isletter: " + (cki.KeyChar != (char) 0) + ")"); 226 | } while (cki.Key != ConsoleKey.Escape); 227 | } 228 | 229 | public static Parent TestTextArea() 230 | { 231 | var parent = new Parent(new LineStrategy {Gap = 1}); 232 | 233 | var tf = new TextField(); 234 | parent.AddComponent(new BoxContainer(tf)); 235 | 236 | var tc = new TextComponent {HAlign = HorizontalAlignment.Centered}; 237 | var p = new Parent(new BorderStrategy()) {ClearAreaBeforePrint = true}; 238 | p.AddComponent(tc, BorderStrategy.Center); 239 | parent.AddComponent(p); 240 | 241 | tf.ActionOnComponent += (sender, args) => 242 | { 243 | tc.Text = tf.Text; 244 | p.Print(args.Graphics); 245 | }; 246 | 247 | return parent; 248 | } 249 | 250 | public static Parent TestFlow() 251 | { 252 | var parent = new Parent(new FlowStrategy(1)); 253 | 254 | var text = "This is a test of the flow layout! How amazing".Split(' '); 255 | foreach (var s in text) 256 | { 257 | var tc = new TextComponent(s) {Focusable = true}; 258 | parent.AddComponent(tc); 259 | } 260 | 261 | return parent; 262 | } 263 | 264 | public static Parent TestBorders() 265 | { 266 | var parent = new Parent(new BorderStrategy()); 267 | 268 | TextComponent txt = new ColorfulLabel("HELLO!") 269 | { 270 | VAlign = VerticalAlignment.Centered, 271 | HAlign = HorizontalAlignment.Centered, Focusable = true 272 | }; 273 | parent.AddComponent(new BoxContainer(txt), BorderStrategy.Center); 274 | 275 | var p2 = new Parent(new FlowStrategy(1)); 276 | var text = "Flow Lay".Split(' '); 277 | foreach (var s in text) 278 | { 279 | var tc = new TextComponent(s) {Focusable = true}; 280 | p2.AddComponent(tc); 281 | } 282 | 283 | parent.AddComponent(p2, BorderStrategy.Left); 284 | 285 | TextComponent txtr = new ColorfulLabel("Right!") {VAlign = VerticalAlignment.Centered, Focusable = true}; 286 | parent.AddComponent(txtr, BorderStrategy.Right); 287 | 288 | p2 = new Parent(new FlowStrategy(true, 1, 0)); 289 | text = "This is a test of the flow layout! How amazing".Split(' '); 290 | foreach (var s in text) 291 | { 292 | var tc = new TextComponent(s) {Focusable = true}; 293 | p2.AddComponent(tc); 294 | } 295 | 296 | parent.AddComponent(p2, BorderStrategy.Top); 297 | 298 | TextComponent txtb = new ColorfulLabel("Down!") {HAlign = HorizontalAlignment.Centered, Focusable = true}; 299 | parent.AddComponent(txtb, BorderStrategy.Bottom); 300 | 301 | return parent; 302 | } 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /SCFE/Viu/Components/ConsoleParent.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | * 6 | * This Source Code Form is "Incompatible With Secondary Licenses", as 7 | * defined by the Mozilla Public License, v. 2.0. 8 | */ 9 | using System; 10 | using System.Collections.Concurrent; 11 | using System.Diagnostics; 12 | using System.Linq; 13 | using System.Reflection; 14 | using System.Runtime.Versioning; 15 | using System.Threading; 16 | 17 | namespace Viu.Components 18 | { 19 | /// 20 | /// 21 | /// 22 | /// A ConsoleParent is a "root parent" which provides basic bindings of the various properties of the console. 23 | /// 24 | /// The creation of a ConsoleParent should look like this: 25 | ///
 26 |     ///             
 27 |     /// Parent p = new Parent();
 28 |     /// // Create everything...
 29 |     /// ConsoleParent cons = new ConsoleParent(p);
 30 |     /// cons.Validate();
 31 |     /// cons.Print();
 32 |     /// 
 33 |     ///         
34 | /// At any given time a ConsoleParent has two threads running: 35 | /// * A thread which listens for key inputs and propagates them to the IFocusable architecture of this component 36 | /// * A thread which watches the changes in the size of the console and applies these changes to the ConsoleParent 37 | ///
38 | ///
39 | /// This is a root parent, meaning that it provides a graphical context for all other components. 40 | public class ConsoleParent : Container, IFocusable, IEventThreadManager 41 | { 42 | private readonly ConcurrentQueue> _eventQueue = 43 | new ConcurrentQueue>(); 44 | 45 | private Thread _watcher, _input, _eventThread; 46 | 47 | /// 48 | /// Create and initialize a new ConsoleParent, using the given parent as its first component. The given parent 49 | /// will have its X and Y coordinates set to 0, and its height and width set and progressively resized to the 50 | /// dimensions of the Console. 51 | /// 52 | /// 53 | public ConsoleParent(Parent root) 54 | { 55 | SetRootContainer(root); 56 | Initialize(); 57 | } 58 | 59 | public ConsoleGraphicsContext GraphicsContext { get; } = new ConsoleGraphicsContext(); 60 | 61 | public Action ExceptionHandler { get; set; } 62 | 63 | public void DoGraphicsLater(Action action) 64 | { 65 | _eventQueue.Enqueue(action); 66 | } 67 | 68 | public void DoGraphicsAndWait(Action action) 69 | { 70 | CountdownEvent cde = new CountdownEvent(1); 71 | _eventQueue.Enqueue(g => 72 | { 73 | try 74 | { 75 | action(g); 76 | } 77 | finally 78 | { 79 | cde.Signal(); 80 | } 81 | }); 82 | cde.Wait(); 83 | } 84 | 85 | public bool AcceptInput(ConsoleKeyInfo keyPressed, GraphicsContext g) 86 | { 87 | return (Components[0] as IFocusable)?.AcceptInput(keyPressed, g) ?? false; 88 | } 89 | 90 | public bool IsFocusable() 91 | { 92 | return (Components[0] as IFocusable)?.IsFocusable() ?? false; 93 | } 94 | 95 | public void SetFocused(bool focused, GraphicsContext g) 96 | { 97 | (Components[0] as IFocusable)?.SetFocused(focused, g); 98 | } 99 | 100 | public bool IsFocused() 101 | { 102 | return (Components[0] as IFocusable)?.IsFocused() ?? false; 103 | } 104 | 105 | /// 106 | /// Initialize the ConsoleParent, launching the threads and creating the default values in the InputMap 107 | /// 108 | private void Initialize() 109 | { 110 | GraphicsContext.Initialize(); 111 | _watcher = new Thread(WatchConsoleSizeChanges) {IsBackground = true}; 112 | _watcher.Start(); 113 | _input = new Thread(WatchInputs) {IsBackground = true}; 114 | _input.Start(); 115 | _eventThread = new Thread(WatchEvents) {IsBackground = true}; 116 | _eventThread.Start(); 117 | 118 | // Initialize basic keys 119 | InputMap.Put(new KeyStroke(ConsoleKey.UpArrow, false, false, false), StandardActionNames.MoveUp); 120 | InputMap.Put(new KeyStroke(ConsoleKey.LeftArrow, false, false, false), StandardActionNames.MoveLeft); 121 | InputMap.Put(new KeyStroke(ConsoleKey.RightArrow, false, false, false), StandardActionNames.MoveRight); 122 | InputMap.Put(new KeyStroke(ConsoleKey.DownArrow, false, false, false), StandardActionNames.MoveDown); 123 | InputMap.Put(new KeyStroke(ConsoleKey.Enter, false, false, false), StandardActionNames.BaseAction); 124 | InputMap.Put(new KeyStroke(ConsoleKey.Enter, false, false, true), StandardActionNames.SecondaryAction); 125 | 126 | InputMap.Put(new KeyStroke(ConsoleKey.Backspace, false, false, false), 127 | StandardActionNames.DeleteToTheLeftAction); 128 | InputMap.Put(new KeyStroke(ConsoleKey.Backspace, true, false, false), 129 | StandardActionNames.DeleteWordToTheLeftAction); 130 | InputMap.Put(new KeyStroke(ConsoleKey.Delete, false, false, false), 131 | StandardActionNames.DeleteToTheRightAction); 132 | InputMap.Put(new KeyStroke(ConsoleKey.Delete, true, false, false), 133 | StandardActionNames.DeleteWordToTheRightAction); 134 | InputMap.Put(new KeyStroke(ConsoleKey.Home, false, false, false), StandardActionNames.MoveLineStart); 135 | InputMap.Put(new KeyStroke(ConsoleKey.End, false, false, false), StandardActionNames.MoveLineEnd); 136 | InputMap.Put(new KeyStroke(ConsoleKey.Escape, false, false, false), StandardActionNames.CancelAction); 137 | 138 | 139 | InputMap.Put(new KeyStroke(ConsoleKey.LeftArrow, true, false, false), StandardActionNames.MoveLeftWord); 140 | InputMap.Put(new KeyStroke(ConsoleKey.RightArrow, true, false, false), StandardActionNames.MoveRightWord); 141 | } 142 | 143 | private void WatchEvents() 144 | { 145 | while (Thread.CurrentThread.IsAlive) 146 | { 147 | while (_eventQueue.Count > 0) 148 | { 149 | _eventQueue.TryDequeue(out var act); 150 | 151 | try 152 | { 153 | act?.Invoke(GraphicsContext); 154 | } 155 | catch (Exception e) 156 | { 157 | try 158 | { 159 | ExceptionHandler?.Invoke(e, GraphicsContext); 160 | } 161 | catch 162 | { 163 | // ignored 164 | } 165 | } 166 | } 167 | 168 | Thread.Sleep(5); 169 | } 170 | } 171 | 172 | private void WatchInputs() 173 | { 174 | var usesV2 = Assembly.GetEntryAssembly()?.GetCustomAttribute()?.FrameworkName 175 | ?.Contains("v2") ?? false; 176 | if (Environment.OSVersion.Platform == PlatformID.Unix) 177 | while (Thread.CurrentThread.IsAlive) 178 | if (Console.KeyAvailable) 179 | { 180 | var key = Console.ReadKey(true); 181 | 182 | var f = Components[0] as IFocusable; 183 | DoGraphicsLater(g => 184 | { 185 | f?.AcceptInput(key, g); 186 | if (usesV2) 187 | { 188 | g.SetCursorVisible(false); 189 | Print(g); 190 | ((Components[0] as Parent)?.GetFocusedElement(true) as ICursorFocusable) 191 | ?.UpdateCursorState(g); 192 | } 193 | }); 194 | } 195 | else 196 | { 197 | Thread.Sleep(10); 198 | } 199 | else 200 | while (Thread.CurrentThread.IsAlive) 201 | { 202 | var key = Console.ReadKey(true); 203 | 204 | var f = Components[0] as IFocusable; 205 | DoGraphicsLater(g => { f?.AcceptInput(key, g); }); 206 | } 207 | } 208 | 209 | private void WatchConsoleSizeChanges() 210 | { 211 | int w = Console.WindowWidth, h = Console.WindowHeight; 212 | long lastResizeTime = 0; 213 | var needsReprint = false; 214 | while (Thread.CurrentThread.IsAlive) 215 | try 216 | { 217 | if (needsReprint && lastResizeTime + 100 <= DateTimeOffset.Now.ToUnixTimeMilliseconds()) 218 | { 219 | Console.Clear(); 220 | Validate(); 221 | Print(GraphicsContext); 222 | needsReprint = false; 223 | } 224 | 225 | Thread.Sleep(20); 226 | if (Console.WindowWidth == w && Console.WindowHeight == h) 227 | continue; 228 | Console.Clear(); 229 | Console.CursorVisible = false; 230 | 231 | w = Console.WindowWidth; 232 | h = Console.WindowHeight; 233 | var s = "Width: " + w + " | Height: " + h; 234 | var x = w / 2 - s.Length / 2; 235 | var y = h / 2; 236 | Console.SetCursorPosition(x < 0 ? 0 : x, y); 237 | Console.Write(s); 238 | needsReprint = true; 239 | lastResizeTime = DateTimeOffset.Now.ToUnixTimeMilliseconds(); 240 | } 241 | catch (Exception) 242 | { 243 | //ignored 244 | } 245 | } 246 | 247 | /// 248 | /// Replace the main container of this ConsoleParent by the given one 249 | /// 250 | /// The new parent to use 251 | public void SetRootContainer(Parent p) 252 | { 253 | Components.Clear(); 254 | Components.Add(p); 255 | p.Parent = this; 256 | } 257 | 258 | public override Dimensions ComputeDimensions() 259 | { 260 | return new Dimensions(Console.WindowWidth, Console.WindowHeight); 261 | } 262 | 263 | public override void Validate() 264 | { 265 | var d = ComputeDimensions(); 266 | Width = d.Width; 267 | Height = d.Height; 268 | 269 | if (Components.Count <= 0) 270 | return; 271 | var cont = Components[0] as Container; 272 | Debug.Assert(cont != null, nameof(cont) + " != null"); 273 | cont.Height = Height; 274 | cont.Width = Width; 275 | cont.X = 0; 276 | cont.Y = 0; 277 | cont.Validate(); 278 | } 279 | 280 | public void Print() 281 | { 282 | Print(GraphicsContext); 283 | } 284 | 285 | [Obsolete("This method should only be used as a last resort.")] 286 | protected override GraphicsContext GetGraphicsContext() 287 | { 288 | return GraphicsContext; 289 | } 290 | 291 | /// 292 | /// Focus the first component of the tree by focusing the root parent of this ConsoleParent 293 | /// 294 | public void FocusFirst() 295 | { 296 | var p = Components[0] as Parent; 297 | p?.SetFocused(true, GraphicsContext); 298 | } 299 | 300 | /// 301 | /// Set the title of the console to the given string 302 | /// 303 | /// The new title of the console 304 | public void SetTitle(string title) 305 | { 306 | Console.Title = title; 307 | } 308 | 309 | public override IEventThreadManager GetEventThread() 310 | { 311 | return this; 312 | } 313 | } 314 | } 315 | --------------------------------------------------------------------------------