Sfoglia il codice sorgente

Add a TerminalScheduler, refactor things

Artyom 4 anni fa
parent
commit
6d3f8ffb96

+ 0 - 41
ReactiveExample/Extensions.cs

@@ -1,41 +0,0 @@
-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);
-			}
-		}
-	}
-}

+ 72 - 25
ReactiveExample/LoginView.cs

@@ -10,15 +10,15 @@ namespace ReactiveExample {
 		
 		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 (ClearButton ())
-				.Append (LoginProgressLabel ());
+			var title = TitleLabel ();
+			var usernameLengthLabel = UsernameLengthLabel (title);
+			var usernameInput = UsernameInput (usernameLengthLabel);
+			var passwordLengthLabel = PasswordLengthLabel (usernameInput);
+			var passwordInput = PasswordInput (passwordLengthLabel);
+			var validationLabel = ValidationLabel (passwordInput);
+			var loginButton = LoginButton (validationLabel);
+			var clearButton = ClearButton (loginButton);
+			LoginProgressLabel (clearButton);
 		}
 		
 		public LoginViewModel ViewModel { get; set; }
@@ -28,8 +28,18 @@ namespace ReactiveExample {
 			base.Dispose (disposing);
 		}
 
-		TextField UsernameInput () {
-			var usernameInput = new TextField (ViewModel.Username) { Width = 40 };
+		Label TitleLabel () {
+			var label = new Label("Login Form");
+			Add (label);
+			return label;
+		}
+
+		TextField UsernameInput (View previous) {
+			var usernameInput = new TextField (ViewModel.Username) {
+				X = Pos.Left(previous),
+				Y = Pos.Top(previous) + 1,
+				Width = 40
+			};
 			ViewModel
 				.WhenAnyValue (x => x.Username)
 				.BindTo (usernameInput, x => x.Text)
@@ -41,21 +51,31 @@ namespace ReactiveExample {
 				.DistinctUntilChanged ()
 				.BindTo (ViewModel, x => x.Username)
 				.DisposeWith (_disposable);
+			Add (usernameInput);
 			return usernameInput;
 		}
 
-		Label UsernameLengthLabel () {
-			var usernameLengthLabel = new Label { Width = 40 };
+		Label UsernameLengthLabel (View previous) {
+			var usernameLengthLabel = new Label {
+				X = Pos.Left(previous),
+				Y = Pos.Top(previous) + 1,
+				Width = 40
+			};
 			ViewModel
 				.WhenAnyValue (x => x.UsernameLength)
 				.Select (length => ustring.Make ($"Username ({length} characters)"))
 				.BindTo (usernameLengthLabel, x => x.Text)
 				.DisposeWith (_disposable);
+			Add (usernameLengthLabel);
 			return usernameLengthLabel;
 		}
 
-		TextField PasswordInput () {
-			var passwordInput = new TextField (ViewModel.Password) { Width = 40 };
+		TextField PasswordInput (View previous) {
+			var passwordInput = new TextField (ViewModel.Password) {
+				X = Pos.Left(previous),
+				Y = Pos.Top(previous) + 1,
+				Width = 40
+			};
 			ViewModel
 				.WhenAnyValue (x => x.Password)
 				.BindTo (passwordInput, x => x.Text)
@@ -67,23 +87,33 @@ namespace ReactiveExample {
 				.DistinctUntilChanged ()
 				.BindTo (ViewModel, x => x.Password)
 				.DisposeWith (_disposable);
+			Add (passwordInput);
 			return passwordInput;
 		}
 
-		Label PasswordLengthLabel () {
-			var passwordLengthLabel = new Label { Width = 40 };
+		Label PasswordLengthLabel (View previous) {
+			var passwordLengthLabel = new Label {
+				X = Pos.Left(previous),
+				Y = Pos.Top(previous) + 1,
+				Width = 40
+			};
 			ViewModel
 				.WhenAnyValue (x => x.PasswordLength)
 				.Select (length => ustring.Make ($"Password ({length} characters)"))
 				.BindTo (passwordLengthLabel, x => x.Text)
 				.DisposeWith (_disposable);
+			Add (passwordLengthLabel);
 			return passwordLengthLabel;
 		}
 
-		Label ValidationLabel () {
+		Label ValidationLabel (View previous) {
 			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 };
+			var validationLabel = new Label(error) {
+				X = Pos.Left(previous),
+				Y = Pos.Top(previous) + 1,
+				Width = 40
+			};
 			ViewModel
 				.WhenAnyValue (x => x.IsValid)	
 				.Select (valid => valid ? success : error)
@@ -94,38 +124,55 @@ namespace ReactiveExample {
 				.Select (valid => valid ? Colors.Base : Colors.Error)
 				.BindTo (validationLabel, x => x.ColorScheme)
 				.DisposeWith (_disposable);
+			Add (validationLabel);
 			return validationLabel;
 		}
 
-		Label LoginProgressLabel () {
+		Label LoginProgressLabel (View previous) {
 			var progress = ustring.Make ("Logging in...");
 			var idle = ustring.Make ("Press 'Login' to log in.");
-			var loginProgressLabel = new Label(idle) { Width = 40 };
+			var loginProgressLabel = new Label(idle) {
+				X = Pos.Left(previous),
+				Y = Pos.Top(previous) + 1,
+				Width = 40
+			};
 			ViewModel
 				.WhenAnyObservable (x => x.Login.IsExecuting)
 				.Select (executing => executing ? progress : idle)
+				.ObserveOn (RxApp.MainThreadScheduler)
 				.BindTo (loginProgressLabel, x => x.Text)
 				.DisposeWith (_disposable);
+			Add (loginProgressLabel);
 			return loginProgressLabel;
 		}
 
-		Button LoginButton () {
-			var loginButton = new Button ("Login") { Width = 40 };
+		Button LoginButton (View previous) {
+			var loginButton = new Button ("Login") {
+				X = Pos.Left(previous),
+				Y = Pos.Top(previous) + 1,
+				Width = 40
+			};
 			loginButton
 				.Events ()
 				.Clicked
 				.InvokeCommand (ViewModel, x => x.Login)
 				.DisposeWith (_disposable);
+			Add (loginButton);
 			return loginButton;
 		}
 
-		Button ClearButton () {
-			var clearButton = new Button("Clear") { Width = 40 };
+		Button ClearButton (View previous) {
+			var clearButton = new Button("Clear") {
+				X = Pos.Left(previous),
+				Y = Pos.Top(previous) + 1,
+				Width = 40
+			};
 			clearButton
 				.Events ()
 				.Clicked
 				.InvokeCommand (ViewModel, x => x.Clear)
 				.DisposeWith (_disposable);
+			Add (clearButton);
 			return clearButton;
 		}
 		

+ 0 - 1
ReactiveExample/LoginViewModel.cs

@@ -1,5 +1,4 @@
 using System;
-using System.Linq;
 using System.Reactive;
 using System.Reactive.Linq;
 using System.Runtime.Serialization;

+ 5 - 3
ReactiveExample/Program.cs

@@ -1,11 +1,13 @@
-using System;
+using System.Reactive.Concurrency;
+using ReactiveUI;
 using Terminal.Gui;
 
 namespace ReactiveExample {
 	public static class Program {
 		static void Main (string [] args) {
-			Application.Init (); // A hacky way to enable instant UI updates.
-			Application.MainLoop.AddTimeout(TimeSpan.FromSeconds(0.1), a => true);
+			Application.Init ();
+			RxApp.MainThreadScheduler = TerminalScheduler.Default;
+			RxApp.TaskpoolScheduler = TaskPoolScheduler.Default;
 			Application.Run (new LoginView (new LoginViewModel ()));
 		}
 	}

+ 41 - 0
ReactiveExample/TerminalScheduler.cs

@@ -0,0 +1,41 @@
+using System;
+using System.Reactive.Concurrency;
+using System.Reactive.Disposables;
+using Terminal.Gui;
+
+namespace ReactiveExample {
+	public class TerminalScheduler : LocalScheduler {
+		public static readonly TerminalScheduler Default = new TerminalScheduler();
+		TerminalScheduler () { }
+
+		public override IDisposable Schedule<TState> (
+			TState state, TimeSpan dueTime,
+			Func<IScheduler, TState, IDisposable> action) {
+			
+			IDisposable PostOnMainLoop() {
+				var composite = new CompositeDisposable(2);
+				var cancellation = new CancellationDisposable();
+				Application.MainLoop.Invoke (() => {
+					if (!cancellation.Token.IsCancellationRequested)
+						composite.Add(action(this, state));
+				});
+				composite.Add(cancellation);
+				return composite;
+			}
+
+			IDisposable PostAsTimeout () {
+				var composite = new CompositeDisposable(2);
+				var token = Application.MainLoop.AddTimeout (dueTime, args => {
+					composite.Add(action (this, state));
+					return true;
+				});
+				composite.Add (Disposable.Create (() => Application.MainLoop.RemoveTimeout (token)));
+				return composite;
+			}
+
+			return dueTime == TimeSpan.Zero 
+				? PostOnMainLoop ()
+				: PostAsTimeout ();
+		}
+	}
+}