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:
Родитель
399c4c11e3
Коммит
b60c61ec49
|
@ -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)
|
||||
|
|
Загрузка…
Ссылка в новой задаче