Add Terminal.Gui and ReactiveUI Demo App (#3)

* Add Terminal.Gui target
* Use latest Avalonia and adequate check boxes
* Force redraw on property change
* Dance with subjects
* Remove the accidentally added references
* Discard the unrelated changes
This commit is contained in:
Artyom V. Gorchakov 2020-09-22 18:18:22 +03:00 коммит произвёл GitHub
Родитель 399c4c11e3
Коммит b60c61ec49
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
7 изменённых файлов: 302 добавлений и 6 удалений

Просмотреть файл

@ -0,0 +1,42 @@
using Terminal.Gui;
namespace ReactiveMvvm.Terminal
{
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);
}
}
}
}

Просмотреть файл

@ -0,0 +1,22 @@
using System;
using ReactiveMvvm.Services;
using ReactiveMvvm.Terminal.Services;
using ReactiveMvvm.Terminal.Views;
using ReactiveMvvm.ViewModels;
using Terminal.Gui;
namespace ReactiveMvvm.Terminal
{
public static class Program
{
public static void Main(string[] args)
{
Application.Init();
Application.Run(
new FeedbackView(
new FeedbackViewModel(
new TerminalSender(),
new Clock())));
}
}
}

Просмотреть файл

@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Pharmacist.MsBuild" Version="1.8.1" PrivateAssets="all" />
<PackageReference Include="Pharmacist.Common" Version="1.8.1" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Terminal.Gui" Version="0.81.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ReactiveMvvm\ReactiveMvvm.csproj" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,10 @@
using System.Threading.Tasks;
using ReactiveMvvm.Interfaces;
namespace ReactiveMvvm.Terminal.Services
{
public sealed class TerminalSender : ISender
{
public Task Send(string title, string message, int section, bool bug) => Task.CompletedTask;
}
}

Просмотреть файл

@ -0,0 +1,156 @@
using System;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using ReactiveMvvm.ViewModels;
using ReactiveUI;
using Terminal.Gui;
using NStack;
namespace ReactiveMvvm.Terminal.Views
{
public sealed class FeedbackView : Window, IViewFor<FeedbackViewModel>, IDisposable
{
private readonly CompositeDisposable _subscriptions = new CompositeDisposable();
public FeedbackView(FeedbackViewModel viewModel) : base(new Rect(0, 0, 30, 20), "Feedback Form")
{
ViewModel = viewModel;
ViewModel.Activator.Activate();
this.StackPanel(TimeElapsedLabel())
.Append(new Label("Issue Title"))
.Append(TitleDescription())
.Append(TitleInput())
.Append(new Label("Issue Description"))
.Append(MessageDescription())
.Append(MessageInput())
.Append(new Label("Feedback Type"))
.Append(IssueCheckBox())
.Append(IdeaCheckBox())
.Append(new Label("Feedback Category"))
.Append(SectionRadioGroup())
.Append(new Button("Send Feedback"), 4);
}
private RadioGroup SectionRadioGroup()
{
var radioGroup = new RadioGroup(new[] {"User Interface", "Audio", "Video", "Voice"});
this.WhenAnyValue(x => x.ViewModel.Section)
.BindTo(radioGroup, x => x.Selected)
.DisposeWith(_subscriptions);
var selected = new Subject<int>().DisposeWith(_subscriptions);
radioGroup.SelectionChanged = index => selected.OnNext(index);
selected.AsObservable()
.BindTo(this, x => x.ViewModel.Section)
.DisposeWith(_subscriptions);
return radioGroup;
}
private CheckBox IssueCheckBox()
{
var item = new CheckBox("Issue", ViewModel.Issue);
this.WhenAnyValue(x => x.ViewModel.Issue)
.BindTo(item, x => x.Checked)
.DisposeWith(_subscriptions);
item.Events()
.Toggled
.Select(args => item.Checked)
.BindTo(this, x => x.ViewModel.Issue)
.DisposeWith(_subscriptions);
this.WhenAnyValue(x => x.ViewModel.Idea)
.Skip(1)
.Subscribe(args => item.Redraw(item.Frame))
.DisposeWith(_subscriptions);
return item;
}
private CheckBox IdeaCheckBox()
{
var item = new CheckBox("Suggestion", ViewModel.Idea);
this.WhenAnyValue(x => x.ViewModel.Idea)
.BindTo(item, x => x.Checked)
.DisposeWith(_subscriptions);
item.Events()
.Toggled
.Select(old => item.Checked)
.BindTo(this, x => x.ViewModel.Idea)
.DisposeWith(_subscriptions);
this.WhenAnyValue(x => x.ViewModel.Issue)
.Skip(1)
.Subscribe(args => item.Redraw(item.Frame))
.DisposeWith(_subscriptions);
return item;
}
private Label TimeElapsedLabel()
{
var label = new Label("0 seconds passed");
this.WhenAnyValue(x => x.ViewModel.Elapsed)
.Select(elapsed => (ustring) $"{elapsed} seconds passed")
.BindTo(label, x => x.Text)
.DisposeWith(_subscriptions);
return label;
}
private TextField MessageInput()
{
var text = new TextField(ViewModel.Message);
this.WhenAnyValue(x => x.ViewModel.Message)
.BindTo(text, x => x.Text)
.DisposeWith(_subscriptions);
text.Events()
.Changed
.Select(older => text.Text)
.DistinctUntilChanged()
.BindTo(ViewModel, x => x.Message)
.DisposeWith(_subscriptions);
return text;
}
private Label MessageDescription()
{
var label = new Label($"0 letters used from {ViewModel.MessageLengthMax}");
this.WhenAnyValue(x => x.ViewModel.MessageLength, x => x.ViewModel.MessageLengthMax)
.Select(values => (ustring) $"{values.Item1} letters used from {values.Item2}")
.BindTo(label, x => x.Text)
.DisposeWith(_subscriptions);
return label;
}
private TextField TitleInput()
{
var text = new TextField(ViewModel.Title);
this.WhenAnyValue(x => x.ViewModel.Title)
.BindTo(text, x => x.Text)
.DisposeWith(_subscriptions);
text.Events()
.Changed
.Select(older => text.Text)
.DistinctUntilChanged()
.BindTo(ViewModel, x => x.Title)
.DisposeWith(_subscriptions);
return text;
}
private Label TitleDescription()
{
var label = new Label($"0 letters used from {ViewModel.TitleLengthMax}");
this.WhenAnyValue(x => x.ViewModel.TitleLength, x => x.ViewModel.TitleLengthMax)
.Select(values => (ustring) $"{values.Item1} letters used from {values.Item2}")
.BindTo(label, x => x.Text)
.DisposeWith(_subscriptions);
return label;
}
public FeedbackViewModel ViewModel { get; set; }
public void Dispose() => _subscriptions.Dispose();
object IViewFor.ViewModel
{
get => ViewModel;
set => ViewModel = (FeedbackViewModel) value;
}
}
}

Просмотреть файл

@ -21,6 +21,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReactiveMvvm.WinForms", "Re
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReactiveMvvm.Xamarin.iOS", "ReactiveMvvm.Xamarin.iOS\ReactiveMvvm.Xamarin.iOS.csproj", "{80A96B52-6758-4BDB-B0B4-CEA4CA155C60}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReactiveMvvm.Terminal", "ReactiveMvvm.Terminal\ReactiveMvvm.Terminal.csproj", "{A494A16D-1772-487B-94EF-B8381250F916}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
@ -407,6 +409,54 @@ Global
{80A96B52-6758-4BDB-B0B4-CEA4CA155C60}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
{80A96B52-6758-4BDB-B0B4-CEA4CA155C60}.Release|x64.ActiveCfg = Release|iPhone
{80A96B52-6758-4BDB-B0B4-CEA4CA155C60}.Release|x86.ActiveCfg = Release|iPhone
{A494A16D-1772-487B-94EF-B8381250F916}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.AppStore|ARM.Build.0 = Debug|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.AppStore|iPhone.Build.0 = Debug|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.AppStore|x64.ActiveCfg = Debug|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.AppStore|x64.Build.0 = Debug|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.AppStore|x86.ActiveCfg = Debug|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.AppStore|x86.Build.0 = Debug|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.Debug|ARM.ActiveCfg = Debug|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.Debug|ARM.Build.0 = Debug|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.Debug|iPhone.Build.0 = Debug|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.Debug|x64.ActiveCfg = Debug|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.Debug|x64.Build.0 = Debug|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.Debug|x86.ActiveCfg = Debug|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.Debug|x86.Build.0 = Debug|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.Release|Any CPU.Build.0 = Release|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.Release|ARM.ActiveCfg = Release|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.Release|ARM.Build.0 = Release|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.Release|iPhone.ActiveCfg = Release|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.Release|iPhone.Build.0 = Release|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.Release|x64.ActiveCfg = Release|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.Release|x64.Build.0 = Release|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.Release|x86.ActiveCfg = Release|Any CPU
{A494A16D-1772-487B-94EF-B8381250F916}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

Просмотреть файл

@ -26,17 +26,17 @@ namespace ReactiveMvvm.ViewModels
public int MessageLengthMax => 30;
public int Section { get; set; }
public bool Issue { get; set; }
public bool Issue { get; set; } = true;
public bool Idea { get; set; }
public FeedbackViewModel(ISender sender, IClock clock)
{
this.WhenAnyValue(x => x.Idea)
.Where(selected => selected)
.Subscribe(x => Issue = false);
.Select(selected => !selected)
.Subscribe(value => Issue = value);
this.WhenAnyValue(x => x.Issue)
.Where(selected => selected)
.Subscribe(x => Idea = false);
.Select(selected => !selected)
.Subscribe(value => Idea = value);
var valid = this
.WhenAnyValue(
@ -57,7 +57,7 @@ namespace ReactiveMvvm.ViewModels
valid);
Activator = new ViewModelActivator();
this.WhenActivated(disposables =>
this.WhenActivated(disposables =>
{
clock.Tick.Select(second => $"{second}s")
.ObserveOn(RxApp.MainThreadScheduler)