Просмотр исходного кода

Add files for the reactive example

Artyom 4 лет назад
Родитель
Сommit
e5b0795aff

+ 41 - 0
ReactiveExample/Extensions.cs

@@ -0,0 +1,41 @@
+using Terminal.Gui;
+
+namespace ReactiveExample {
+	public static class Extensions
+	{
+		public static MemoizedElement<TOwner, TNew> StackPanel<TOwner, TNew>(
+			this TOwner owner,
+			TNew control)
+			where TOwner : View
+			where TNew : View =>
+			new MemoizedElement<TOwner, TNew>(owner, control);
+
+		public static MemoizedElement<TOwner, TNew> Append<TOwner, TOld, TNew>(
+			this MemoizedElement<TOwner, TOld> owner, 
+			TNew control,
+			int height = 1)
+			where TOwner : View 
+			where TOld : View
+			where TNew : View
+		{
+			control.X = Pos.Left(owner.Control);
+			control.Y = Pos.Top(owner.Control) + height;
+			return new MemoizedElement<TOwner, TNew>(owner.View, control);
+		}
+
+		public class MemoizedElement<TOwner, TControl> 
+			where TOwner : View 
+			where TControl : View
+		{
+			public TOwner View { get; }
+			public TControl Control { get; }
+
+			public MemoizedElement(TOwner owner, TControl control)
+			{
+				View = owner;
+				Control = control;
+				View.Add(control);
+			}
+		}
+	}
+}

+ 3 - 0
ReactiveExample/FodyWeavers.xml

@@ -0,0 +1,3 @@
+<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
+  <ReactiveUI />
+</Weavers>

+ 126 - 0
ReactiveExample/LoginView.cs

@@ -0,0 +1,126 @@
+using System.Reactive.Disposables;
+using System.Reactive.Linq;
+using NStack;
+using ReactiveUI;
+using Terminal.Gui;
+
+namespace ReactiveExample {
+	public class LoginView : Window, IViewFor<LoginViewModel> {
+		readonly CompositeDisposable _disposable = new CompositeDisposable();
+		
+		public LoginView (LoginViewModel viewModel) : base("Reactive Extensions Example") {
+			ViewModel = viewModel;
+			this.StackPanel (new Label ("Login Form"))
+				.Append (UsernameLengthLabel ())
+				.Append (UsernameInput ())
+				.Append (PasswordLengthLabel ())
+				.Append (PasswordInput ())
+				.Append (ValidationLabel ())
+				.Append (LoginButton ())
+				.Append (LoginProgressLabel ());
+		}
+		
+		public LoginViewModel ViewModel { get; set; }
+
+		protected override void Dispose (bool disposing) {
+			_disposable.Dispose ();
+			base.Dispose (disposing);
+		}
+
+		TextField UsernameInput () {
+			var usernameInput = new TextField (ViewModel.Username) { Width = 40 };
+			ViewModel
+				.WhenAnyValue (x => x.Username)
+				.BindTo (usernameInput, x => x.Text)
+				.DisposeWith (_disposable);
+			usernameInput
+				.Events ()
+				.TextChanged
+				.Select (old => usernameInput.Text.ToString ())
+				.DistinctUntilChanged ()
+				.BindTo (ViewModel, x => x.Username)
+				.DisposeWith (_disposable);
+			return usernameInput;
+		}
+
+		Label UsernameLengthLabel () {
+			var usernameLengthLabel = new Label { Width = 40 };
+			ViewModel
+				.WhenAnyValue (x => x.UsernameLength)
+				.Select (length => ustring.Make ($"Username ({length} characters)"))
+				.BindTo (usernameLengthLabel, x => x.Text)
+				.DisposeWith (_disposable);
+			return usernameLengthLabel;
+		}
+
+		TextField PasswordInput () {
+			var passwordInput = new TextField (ViewModel.Password) { Width = 40 };
+			ViewModel
+				.WhenAnyValue (x => x.Password)
+				.BindTo (passwordInput, x => x.Text)
+				.DisposeWith (_disposable);
+			passwordInput
+				.Events ()
+				.TextChanged
+				.Select (old => passwordInput.Text.ToString ())
+				.DistinctUntilChanged ()
+				.BindTo (ViewModel, x => x.Password)
+				.DisposeWith (_disposable);
+			return passwordInput;
+		}
+
+		Label PasswordLengthLabel () {
+			var passwordLengthLabel = new Label { Width = 40 };
+			ViewModel
+				.WhenAnyValue (x => x.PasswordLength)
+				.Select (length => ustring.Make ($"Password ({length} characters)"))
+				.BindTo (passwordLengthLabel, x => x.Text)
+				.DisposeWith (_disposable);
+			return passwordLengthLabel;
+		}
+
+		Label ValidationLabel () {
+			var error = ustring.Make("Please, enter user name and password.");
+			var success = ustring.Make("The input is valid!");
+			var validationLabel = new Label(error) { Width = 40 };
+			ViewModel
+				.WhenAnyValue (x => x.IsValid)	
+				.Select (valid => valid ? success : error)
+				.BindTo (validationLabel, x => x.Text)
+				.DisposeWith (_disposable);
+			ViewModel
+				.WhenAnyValue (x => x.IsValid)	
+				.Select (valid => valid ? Colors.Base : Colors.Error)
+				.BindTo (validationLabel, x => x.ColorScheme)
+				.DisposeWith (_disposable);
+			return validationLabel;
+		}
+
+		Label LoginProgressLabel () {
+			var progress = ustring.Make ("Logging in...");
+			var idle = ustring.Make ("Press 'Login' to log in.");
+			var loginProgressLabel = new Label(idle) { Width = 40 };
+			ViewModel
+				.WhenAnyObservable (x => x.Login.IsExecuting)
+				.Select (executing => executing ? progress : idle)
+				.BindTo (loginProgressLabel, x => x.Text)
+				.DisposeWith (_disposable);
+			return loginProgressLabel;
+		}
+
+		Button LoginButton () {
+			var loginButton = new Button ("Login") { Width = 40 };
+			loginButton
+				.Events ()
+				.Clicked
+				.InvokeCommand (ViewModel, x => x.Login)
+				.DisposeWith (_disposable);
+			return loginButton;
+		}
+		
+		object IViewFor.ViewModel {
+			get => ViewModel;
+			set => ViewModel = (LoginViewModel) value;
+		}
+	}
+}

+ 57 - 0
ReactiveExample/LoginViewModel.cs

@@ -0,0 +1,57 @@
+using System;
+using System.Reactive;
+using System.Reactive.Linq;
+using System.Runtime.Serialization;
+using System.Threading.Tasks;
+using ReactiveUI;
+using ReactiveUI.Fody.Helpers;
+
+namespace ReactiveExample {
+	[DataContract]
+	public class LoginViewModel : ReactiveObject {
+		readonly ObservableAsPropertyHelper<int> _usernameLength;
+		readonly ObservableAsPropertyHelper<int> _passwordLength;
+		readonly ObservableAsPropertyHelper<bool> _isValid;
+		
+		public LoginViewModel () {
+			var canLogin = this.WhenAnyValue (
+				x => x.Username, 
+				x => x.Password,
+				(username, password) =>
+					!string.IsNullOrWhiteSpace (username) &&
+					!string.IsNullOrWhiteSpace (password));
+			
+			_isValid = canLogin.ToProperty (this, x => x.IsValid);
+			Login = ReactiveCommand.CreateFromTask (
+				() => Task.Delay (TimeSpan.FromSeconds (1)),
+				canLogin);
+			
+			_usernameLength = this
+				.WhenAnyValue (x => x.Username)
+				.Select (name => name.Length)
+				.ToProperty (this, x => x.UsernameLength);
+			_passwordLength = this
+				.WhenAnyValue (x => x.Password)
+				.Select (password => password.Length)
+				.ToProperty (this, x => x.PasswordLength);
+		}
+		
+		[Reactive, DataMember]
+		public string Username { get; set; } = string.Empty;
+		
+		[Reactive, DataMember]
+		public string Password { get; set; } = string.Empty;
+		
+		[IgnoreDataMember]
+		public int UsernameLength => _usernameLength.Value;
+		
+		[IgnoreDataMember]
+		public int PasswordLength => _passwordLength.Value;
+
+		[IgnoreDataMember]
+		public ReactiveCommand<Unit, Unit> Login { get; }
+		
+		[IgnoreDataMember]
+		public bool IsValid => _isValid.Value;
+	}
+}

+ 6 - 4
ReactiveExample/Program.cs

@@ -1,10 +1,12 @@
 using System;
 using System;
+using Terminal.Gui;
 
 
 namespace ReactiveExample {
 namespace ReactiveExample {
-	class Program {
-		static void Main (string [] args)
-		{
-			Console.WriteLine ("Hello World!");
+	public static class Program {
+		static void Main (string [] args) {
+			Application.Init (); // A hacky way to enable instant UI updates.
+			Application.MainLoop.AddTimeout(TimeSpan.FromMilliseconds(600), a => true);
+			Application.Run (new LoginView (new LoginViewModel ()));
 		}
 		}
 	}
 	}
 }
 }

+ 7 - 2
ReactiveExample/ReactiveExample.csproj

@@ -1,8 +1,13 @@
 <Project Sdk="Microsoft.NET.Sdk">
 <Project Sdk="Microsoft.NET.Sdk">
-
     <PropertyGroup>
     <PropertyGroup>
         <OutputType>Exe</OutputType>
         <OutputType>Exe</OutputType>
         <TargetFramework>netcoreapp3.1</TargetFramework>
         <TargetFramework>netcoreapp3.1</TargetFramework>
     </PropertyGroup>
     </PropertyGroup>
-
+    <ItemGroup>
+        <PackageReference Include="Pharmacist.MsBuild" Version="1.8.1" PrivateAssets="all" />
+        <PackageReference Include="Pharmacist.Common" Version="1.8.1" />
+        <PackageReference Include="Terminal.Gui" Version="0.90.3" />
+        <PackageReference Include="ReactiveUI.Fody" Version="11.5.35" />
+        <PackageReference Include="ReactiveUI" Version="11.5.35" />
+    </ItemGroup>
 </Project>
 </Project>