diff --git a/CommunityToolkitExample/CommunityToolkitExample.csproj b/CommunityToolkitExample/CommunityToolkitExample.csproj new file mode 100644 index 000000000..fdb41dfcd --- /dev/null +++ b/CommunityToolkitExample/CommunityToolkitExample.csproj @@ -0,0 +1,19 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + + diff --git a/CommunityToolkitExample/LoginAction.cs b/CommunityToolkitExample/LoginAction.cs new file mode 100644 index 000000000..2fd8d4a45 --- /dev/null +++ b/CommunityToolkitExample/LoginAction.cs @@ -0,0 +1,7 @@ +namespace CommunityToolkitExample; + +internal enum LoginAction +{ + Validation, + LoginProgress +} diff --git a/CommunityToolkitExample/LoginView.Designer.cs b/CommunityToolkitExample/LoginView.Designer.cs new file mode 100644 index 000000000..e1bddff45 --- /dev/null +++ b/CommunityToolkitExample/LoginView.Designer.cs @@ -0,0 +1,62 @@ +using Terminal.Gui; + +namespace CommunityToolkitExample; + +internal partial class LoginView : Window +{ + private Label titleLabel; + private Label usernameLengthLabel; + private TextField usernameInput; + private Label passwordLengthLabel; + private TextField passwordInput; + private Label validationLabel; + private Button loginButton; + private Button clearButton; + private Label loginProgressLabel; + + private void InitializeComponent () + { + titleLabel = new Label (); + titleLabel.Text = "Login Form"; + Add (titleLabel); + usernameLengthLabel = new Label (); + usernameLengthLabel.X = Pos.Left (titleLabel); + usernameLengthLabel.Y = Pos.Top (titleLabel) + 1; + Add (usernameLengthLabel); + usernameInput = new TextField (); + usernameInput.X = Pos.Right (usernameLengthLabel) + 1; + usernameInput.Y = Pos.Top (usernameLengthLabel); + usernameInput.Width = 40; + Add (usernameInput); + passwordLengthLabel = new Label (); + passwordLengthLabel.X = Pos.Left (usernameLengthLabel); + passwordLengthLabel.Y = Pos.Top (usernameLengthLabel) + 1; + Add (passwordLengthLabel); + passwordInput = new TextField (); + passwordInput.X = Pos.Right (passwordLengthLabel) + 1; + passwordInput.Y = Pos.Top (passwordLengthLabel); + passwordInput.Width = 40; + passwordInput.Secret = true; + Add (passwordInput); + validationLabel = new Label (); + validationLabel.X = Pos.Left (passwordInput); + validationLabel.Y = Pos.Top (passwordInput) + 1; + Add (validationLabel); + loginButton = new Button (); + loginButton.X = Pos.Left (validationLabel); + loginButton.Y = Pos.Top (validationLabel) + 1; + loginButton.Text = "_Login"; + Add (loginButton); + clearButton = new Button (); + clearButton.X = Pos.Left (loginButton); + clearButton.Y = Pos.Top (loginButton) + 1; + clearButton.Text = "_Clear"; + Add (clearButton); + loginProgressLabel = new Label (); + loginProgressLabel.X = Pos.Left (clearButton); + loginProgressLabel.Y = Pos.Top (clearButton) + 1; + loginProgressLabel.Width = 40; + loginProgressLabel.Height = 1; + Add (loginProgressLabel); + } +} diff --git a/CommunityToolkitExample/LoginView.cs b/CommunityToolkitExample/LoginView.cs new file mode 100644 index 000000000..262928e35 --- /dev/null +++ b/CommunityToolkitExample/LoginView.cs @@ -0,0 +1,68 @@ +using CommunityToolkit.Mvvm.Messaging; +using Terminal.Gui; + +namespace CommunityToolkitExample; + +internal partial class LoginView : IRecipient> +{ + public LoginView (LoginViewModel viewModel) + { + WeakReferenceMessenger.Default.Register (this); + Title = $"Community Toolkit MVVM Example - {Application.QuitKey} to Exit"; + ViewModel = viewModel; + InitializeComponent (); + usernameInput.TextChanged += (_, _) => + { + ViewModel.Username = usernameInput.Text; + SetText (); + }; + passwordInput.TextChanged += (_, _) => + { + ViewModel.Password = passwordInput.Text; + SetText (); + }; + loginButton.Accept += (_, _) => + { + if (!ViewModel.CanLogin) { return; } + ViewModel.LoginCommand.Execute (null); + }; + + clearButton.Accept += (_, _) => + { + ViewModel.ClearCommand.Execute (null); + SetText (); + }; + + Initialized += (_, _) => { ViewModel.Initialized (); }; + } + + public LoginViewModel ViewModel { get; set; } + + public void Receive (Message message) + { + switch (message.Value) + { + case LoginAction.LoginProgress: + { + loginProgressLabel.Text = ViewModel.LoginProgressMessage; + break; + } + case LoginAction.Validation: + { + validationLabel.Text = ViewModel.ValidationMessage; + validationLabel.ColorScheme = ViewModel.ValidationColorScheme; + break; + } + } + SetText(); + Application.Refresh (); + } + + private void SetText () + { + usernameInput.Text = ViewModel.Username; + usernameLengthLabel.Text = ViewModel.UsernameLengthMessage; + passwordInput.Text = ViewModel.Password; + passwordLengthLabel.Text = ViewModel.PasswordLengthMessage; + } +} \ No newline at end of file diff --git a/CommunityToolkitExample/LoginViewModel.cs b/CommunityToolkitExample/LoginViewModel.cs new file mode 100644 index 000000000..b5fbfa32f --- /dev/null +++ b/CommunityToolkitExample/LoginViewModel.cs @@ -0,0 +1,118 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using CommunityToolkit.Mvvm.Messaging; +using Terminal.Gui; + +namespace CommunityToolkitExample; + +internal partial class LoginViewModel : ObservableObject +{ + private const string DEFAULT_LOGIN_PROGRESS_MESSAGE = "Press 'Login' to log in."; + private const string LOGGING_IN_PROGRESS_MESSAGE = "Logging in..."; + private const string VALID_LOGIN_MESSAGE = "The input is valid!"; + private const string INVALID_LOGIN_MESSAGE = "Please enter a valid user name and password."; + + [ObservableProperty] + private bool _canLogin; + + private string _password; + + [ObservableProperty] + private string _passwordLengthMessage; + + private string _username; + + [ObservableProperty] + private string _usernameLengthMessage; + + [ObservableProperty] + private string _loginProgressMessage; + + [ObservableProperty] + private string _validationMessage; + + [ObservableProperty] + private ColorScheme? _validationColorScheme; + + public LoginViewModel () + { + Username = string.Empty; + Password = string.Empty; + + ClearCommand = new (Clear); + LoginCommand = new (Execute); + + Clear (); + + return; + + async void Execute () { await Login (); } + } + + public RelayCommand ClearCommand { get; } + + public RelayCommand LoginCommand { get; } + + public string Password + { + get => _password; + set + { + SetProperty (ref _password, value); + PasswordLengthMessage = $"_Password ({_password.Length} characters):"; + ValidateLogin (); + } + } + + private void ValidateLogin () + { + CanLogin = !string.IsNullOrEmpty (Username) && !string.IsNullOrEmpty (Password); + SendMessage (LoginAction.Validation); + } + + public string Username + { + get => _username; + set + { + SetProperty (ref _username, value); + UsernameLengthMessage = $"_Username ({_username.Length} characters):"; + ValidateLogin (); + } + } + + private void Clear () + { + Username = string.Empty; + Password = string.Empty; + SendMessage (LoginAction.Validation); + SendMessage (LoginAction.LoginProgress, DEFAULT_LOGIN_PROGRESS_MESSAGE); + } + + private async Task Login () + { + SendMessage (LoginAction.LoginProgress, LOGGING_IN_PROGRESS_MESSAGE); + await Task.Delay (TimeSpan.FromSeconds (1)); + Clear (); + } + + private void SendMessage (LoginAction loginAction, string message = "") + { + switch (loginAction) + { + case LoginAction.LoginProgress: + LoginProgressMessage = message; + break; + case LoginAction.Validation: + ValidationMessage = CanLogin ? VALID_LOGIN_MESSAGE : INVALID_LOGIN_MESSAGE; + ValidationColorScheme = CanLogin ? Colors.ColorSchemes ["Base"] : Colors.ColorSchemes ["Error"]; + break; + } + WeakReferenceMessenger.Default.Send (new Message { Value = loginAction }); + } + + public void Initialized () + { + Clear (); + } +} diff --git a/CommunityToolkitExample/Message.cs b/CommunityToolkitExample/Message.cs new file mode 100644 index 000000000..a1081512e --- /dev/null +++ b/CommunityToolkitExample/Message.cs @@ -0,0 +1,6 @@ +namespace CommunityToolkitExample; + +internal class Message +{ + public T Value { get; set; } +} diff --git a/CommunityToolkitExample/Program.cs b/CommunityToolkitExample/Program.cs new file mode 100644 index 000000000..0a557fc47 --- /dev/null +++ b/CommunityToolkitExample/Program.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.DependencyInjection; +using Terminal.Gui; + +namespace CommunityToolkitExample; + +public static class Program +{ + public static IServiceProvider Services { get; private set; } + + private static void Main (string [] args) + { + Services = ConfigureServices (); + Application.Init (); + Application.Run (Services.GetRequiredService ()); + Application.Shutdown (); + } + + private static IServiceProvider ConfigureServices () + { + var services = new ServiceCollection (); + services.AddTransient (); + services.AddTransient (); + return services.BuildServiceProvider (); + } +} \ No newline at end of file diff --git a/Terminal.sln b/Terminal.sln index 98de83b41..7dbb5432a 100644 --- a/Terminal.sln +++ b/Terminal.sln @@ -38,12 +38,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Terminal.Gui.Analyzers.Inte EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Terminal.Gui.Analyzers.Internal.Debugging", "Analyzers\Terminal.Gui.Analyzers.Internal.Debugging\Terminal.Gui.Analyzers.Internal.Debugging.csproj", "{C2AD09BD-D579-45A7-ACA3-E4EF3BC027D2}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkitExample", "CommunityToolkitExample\CommunityToolkitExample.csproj", "{58FDCA8F-08F7-4D80-9DA3-6A9AED01E163}" +EndProject Global - GlobalSection(NestedProjects) = preSolution - {5DE91722-8765-4E2B-97E4-2A18010B5CED} = {CCADA0BC-61CF-4B4B-96BA-A3B0C0A7F54D} - {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD} = {CCADA0BC-61CF-4B4B-96BA-A3B0C0A7F54D} - {C2AD09BD-D579-45A7-ACA3-E4EF3BC027D2} = {CCADA0BC-61CF-4B4B-96BA-A3B0C0A7F54D} - EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU @@ -53,14 +50,6 @@ Global {00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Release|Any CPU.Build.0 = Release|Any CPU - {5DE91722-8765-4E2B-97E4-2A18010B5CED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5DE91722-8765-4E2B-97E4-2A18010B5CED}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5DE91722-8765-4E2B-97E4-2A18010B5CED}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5DE91722-8765-4E2B-97E4-2A18010B5CED}.Release|Any CPU.Build.0 = Release|Any CPU - {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Release|Any CPU.Build.0 = Release|Any CPU {88979F89-9A42-448F-AE3E-3060145F6375}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {88979F89-9A42-448F-AE3E-3060145F6375}.Debug|Any CPU.Build.0 = Debug|Any CPU {88979F89-9A42-448F-AE3E-3060145F6375}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -77,14 +66,31 @@ Global {B0A602CD-E176-449D-8663-64238D54F857}.Debug|Any CPU.Build.0 = Debug|Any CPU {B0A602CD-E176-449D-8663-64238D54F857}.Release|Any CPU.ActiveCfg = Release|Any CPU {B0A602CD-E176-449D-8663-64238D54F857}.Release|Any CPU.Build.0 = Release|Any CPU + {5DE91722-8765-4E2B-97E4-2A18010B5CED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5DE91722-8765-4E2B-97E4-2A18010B5CED}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5DE91722-8765-4E2B-97E4-2A18010B5CED}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5DE91722-8765-4E2B-97E4-2A18010B5CED}.Release|Any CPU.Build.0 = Release|Any CPU + {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Release|Any CPU.Build.0 = Release|Any CPU {C2AD09BD-D579-45A7-ACA3-E4EF3BC027D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C2AD09BD-D579-45A7-ACA3-E4EF3BC027D2}.Debug|Any CPU.Build.0 = Debug|Any CPU {C2AD09BD-D579-45A7-ACA3-E4EF3BC027D2}.Release|Any CPU.ActiveCfg = Release|Any CPU {C2AD09BD-D579-45A7-ACA3-E4EF3BC027D2}.Release|Any CPU.Build.0 = Release|Any CPU + {58FDCA8F-08F7-4D80-9DA3-6A9AED01E163}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {58FDCA8F-08F7-4D80-9DA3-6A9AED01E163}.Debug|Any CPU.Build.0 = Debug|Any CPU + {58FDCA8F-08F7-4D80-9DA3-6A9AED01E163}.Release|Any CPU.ActiveCfg = Release|Any CPU + {58FDCA8F-08F7-4D80-9DA3-6A9AED01E163}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {5DE91722-8765-4E2B-97E4-2A18010B5CED} = {CCADA0BC-61CF-4B4B-96BA-A3B0C0A7F54D} + {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD} = {CCADA0BC-61CF-4B4B-96BA-A3B0C0A7F54D} + {C2AD09BD-D579-45A7-ACA3-E4EF3BC027D2} = {CCADA0BC-61CF-4B4B-96BA-A3B0C0A7F54D} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {9F8F8A4D-7B8D-4C2A-AC5E-CD7117F74C03} EndGlobalSection