-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #4 from bonsai-rx/feature-dev
Add core infrastructure for control visualizers
- Loading branch information
Showing
7 changed files
with
550 additions
and
171 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
using System; | ||
using System.Drawing; | ||
using System.Windows.Forms; | ||
using Bonsai.Design; | ||
using Bonsai.Expressions; | ||
|
||
namespace Bonsai.Gui | ||
{ | ||
/// <summary> | ||
/// Provides an abstract base class for visualizers representing UI elements which can | ||
/// contain other nested UI elements. | ||
/// </summary> | ||
/// <typeparam name="TControl"> | ||
/// The type of the container control exposed by this type visualizer. | ||
/// </typeparam> | ||
/// <typeparam name="TControlBuilder"> | ||
/// The type of the workflow element used to create the container control. | ||
/// </typeparam> | ||
public abstract class ContainerControlVisualizerBase<TControl, TControlBuilder> | ||
: MashupControlVisualizerBase<TControl, TControlBuilder> | ||
where TControl : Control | ||
where TControlBuilder : ExpressionBuilder | ||
{ | ||
/// <summary> | ||
/// Adds a control to the container at the specified index. | ||
/// </summary> | ||
/// <param name="index">The index of the control, following the order of visualizer sources.</param> | ||
/// <param name="control">The control to add to the container.</param> | ||
protected abstract void AddControl(int index, Control control); | ||
|
||
/// <inheritdoc/> | ||
public override void LoadMashups(IServiceProvider provider) | ||
{ | ||
for (int i = 0; i < MashupSources.Count; i++) | ||
{ | ||
var mashupSource = MashupSources[i]; | ||
var containerProvider = new ContainerControlServiceProvider(i, this, mashupSource.Source, provider); | ||
mashupSource.Visualizer.Load(containerProvider); | ||
} | ||
} | ||
|
||
/// <inheritdoc/> | ||
public override void UnloadMashups() | ||
{ | ||
base.UnloadMashups(); | ||
Control.Controls.Clear(); | ||
} | ||
|
||
/// <inheritdoc/> | ||
public override MashupSource GetMashupSource(int x, int y) | ||
{ | ||
if (Control == null) return null; | ||
var panelPoint = Control.PointToClient(new Point(x, y)); | ||
var childControl = Control.GetChildAtPoint(panelPoint); | ||
if (childControl != null) | ||
{ | ||
var index = Control.Controls.GetChildIndex(childControl); | ||
return MashupSources[index]; | ||
} | ||
|
||
return null; | ||
} | ||
|
||
class ContainerControlServiceProvider : MashupControlServiceProvider, IDialogTypeVisualizerService | ||
{ | ||
public ContainerControlServiceProvider( | ||
int index, | ||
ContainerControlVisualizerBase<TControl, TControlBuilder> visualizer, | ||
InspectBuilder source, | ||
IServiceProvider provider) | ||
: base(index, visualizer, source, provider) | ||
{ | ||
} | ||
|
||
public void AddControl(Control control) | ||
{ | ||
var container = (ContainerControlVisualizerBase<TControl, TControlBuilder>)Visualizer; | ||
container.AddControl(Index, control); | ||
} | ||
|
||
public override object GetService(Type serviceType) | ||
{ | ||
if (serviceType == typeof(IDialogTypeVisualizerService)) | ||
{ | ||
return this; | ||
} | ||
|
||
return base.GetService(serviceType); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.ComponentModel; | ||
using System.Linq.Expressions; | ||
using Bonsai.Expressions; | ||
using System.Reactive.Subjects; | ||
using System.Reactive.Linq; | ||
using System.Reactive; | ||
using System.Linq; | ||
|
||
namespace Bonsai.Gui | ||
{ | ||
/// <summary> | ||
/// Provides an abstract base class with common UI control functionality. | ||
/// </summary> | ||
public abstract class ControlBuilderBase : ZeroArgumentExpressionBuilder, INamedElement | ||
{ | ||
internal readonly BehaviorSubject<bool> _Enabled = new(true); | ||
internal readonly BehaviorSubject<bool> _Visible = new(true); | ||
|
||
/// <summary> | ||
/// Gets or sets the name of the control. | ||
/// </summary> | ||
[Category(nameof(CategoryAttribute.Design))] | ||
[Description("The name of the control.")] | ||
public string Name { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets a value specifying whether the control can respond to user interaction. | ||
/// </summary> | ||
[Category(nameof(CategoryAttribute.Behavior))] | ||
[Description("Specifies whether the control can respond to user interaction.")] | ||
public bool Enabled | ||
{ | ||
get => _Enabled.Value; | ||
set => _Enabled.OnNext(value); | ||
} | ||
|
||
/// <summary> | ||
/// Gets or sets a value specifying whether the control and all its child controls | ||
/// are displayed. | ||
/// </summary> | ||
[Category(nameof(CategoryAttribute.Behavior))] | ||
[Description("Specifies whether the control and all its child controls are displayed.")] | ||
public bool Visible | ||
{ | ||
get => _Visible.Value; | ||
set => _Visible.OnNext(value); | ||
} | ||
|
||
/// <summary> | ||
/// Builds the expression tree for configuring and calling the UI control. | ||
/// </summary> | ||
/// <inheritdoc/> | ||
public override Expression Build(IEnumerable<Expression> arguments) | ||
{ | ||
return Expression.Call(typeof(Observable), nameof(Observable.Never), new[] { typeof(Unit) }); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Provides an abstract base class with common UI event source control functionality. | ||
/// </summary> | ||
/// <typeparam name="TEvent">The type of event notifications emitted by the UI control.</typeparam> | ||
public abstract class ControlBuilderBase<TEvent> : ControlBuilderBase, INamedElement | ||
{ | ||
/// <summary> | ||
/// Builds the expression tree for configuring and calling the UI control. | ||
/// </summary> | ||
/// <inheritdoc/> | ||
public override Expression Build(IEnumerable<Expression> arguments) | ||
{ | ||
return Expression.Call(Expression.Constant(this), nameof(Generate), null); | ||
} | ||
|
||
/// <summary> | ||
/// Generates an observable sequence of event notifications from the UI control. | ||
/// </summary> | ||
/// <returns> | ||
/// An observable sequence of events of type <typeparamref name="TEvent"/>. | ||
/// </returns> | ||
protected abstract IObservable<TEvent> Generate(); | ||
} | ||
|
||
/// <summary> | ||
/// Provides an abstract base class with common functionality for UI controls that | ||
/// accept an optional sequence of command notifications. | ||
/// </summary> | ||
/// <typeparam name="TCommand">The type of command notifications accepted by the UI control.</typeparam> | ||
/// <typeparam name="TEvent">The type of event notifications emitted by the UI control.</typeparam> | ||
public abstract class ControlBuilderBase<TCommand, TEvent> : ControlBuilderBase<TEvent>, INamedElement | ||
{ | ||
static readonly Range<int> argumentRange = Range.Create(lowerBound: 0, upperBound: 1); | ||
|
||
/// <summary> | ||
/// Gets the range of input arguments that this expression builder accepts. | ||
/// </summary> | ||
public override Range<int> ArgumentRange => argumentRange; | ||
|
||
/// <summary> | ||
/// Builds the expression tree for configuring and calling the UI control. | ||
/// </summary> | ||
/// <inheritdoc/> | ||
public override Expression Build(IEnumerable<Expression> arguments) | ||
{ | ||
var source = arguments.SingleOrDefault(); | ||
var commands = source == null ? Array.Empty<Expression>() : new[] { source }; | ||
return Expression.Call(Expression.Constant(this), nameof(Generate), null, commands); | ||
} | ||
|
||
/// <summary> | ||
/// Generates an observable sequence of event notifications from the UI control. | ||
/// </summary> | ||
/// <param name="source"> | ||
/// An observable sequence of commands of type <typeparamref name="TCommand"/>. | ||
/// </param> | ||
/// <returns> | ||
/// An observable sequence of events of type <typeparamref name="TEvent"/>. | ||
/// </returns> | ||
protected abstract IObservable<TEvent> Generate(IObservable<TCommand> source); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
using System; | ||
using System.Collections.ObjectModel; | ||
using System.Collections.Specialized; | ||
using System.Linq; | ||
using System.Reactive.Linq; | ||
using System.Windows.Forms; | ||
using Bonsai.Design; | ||
using Bonsai.Expressions; | ||
|
||
namespace Bonsai.Gui | ||
{ | ||
/// <summary> | ||
/// Provides a set of static methods for subscribing delegates to | ||
/// observable sequences of event notifications. | ||
/// </summary> | ||
public static class ControlExtensions | ||
{ | ||
/// <summary> | ||
/// Subscribes the control to change notifications in the specified | ||
/// observable collection. | ||
/// </summary> | ||
/// <typeparam name="TSource"> | ||
/// The type of the elements in the <paramref name="source"/> collection. | ||
/// </typeparam> | ||
/// <param name="control">The control on which to observe notifications.</param> | ||
/// <param name="source"> | ||
/// A data collection that provides notifications when items are added, | ||
/// removed, or when the whole list is refreshed. | ||
/// </param> | ||
/// <param name="action"> | ||
/// The action to invoke on each new version of the observable collection. | ||
/// </param> | ||
/// <returns> | ||
/// A disposable object used to unsubscribe from the observable sequence. | ||
/// </returns> | ||
public static IDisposable SubscribeTo<TSource>( | ||
this Control control, | ||
ObservableCollection<TSource> source, | ||
Action<TSource[]> action) | ||
{ | ||
var collectionChanged = Observable.FromEventPattern<NotifyCollectionChangedEventHandler, NotifyCollectionChangedEventArgs>( | ||
handler => source.CollectionChanged += handler, | ||
handler => source.CollectionChanged -= handler) | ||
.Select(evt => source.ToArray()); | ||
return SubscribeTo(control, collectionChanged, action); | ||
} | ||
|
||
/// <summary> | ||
/// Subscribes the control to event notifications from the specified | ||
/// observable sequence. | ||
/// </summary> | ||
/// <typeparam name="TSource"> | ||
/// The type of the elements in the <paramref name="source"/> sequence. | ||
/// </typeparam> | ||
/// <param name="control">The control on which to observe notifications.</param> | ||
/// <param name="source">The observable sequence of event notifications.</param> | ||
/// <param name="action">The action to invoke on each event notification.</param> | ||
/// <returns> | ||
/// A disposable object used to unsubscribe from the observable sequence. | ||
/// </returns> | ||
public static IDisposable SubscribeTo<TSource>(this Control control, IObservable<TSource> source, Action<TSource> action) | ||
{ | ||
var handleCreated = Observable.FromEventPattern<EventHandler, EventArgs>( | ||
handler => control.HandleCreated += handler, | ||
handler => control.HandleCreated -= handler); | ||
var handleDestroyed = Observable.FromEventPattern<EventHandler, EventArgs>( | ||
handler => control.HandleDestroyed += handler, | ||
handler => control.HandleDestroyed -= handler); | ||
var notifications = handleCreated | ||
.SelectMany(_ => source.ObserveOn(control)) | ||
.TakeUntil(handleDestroyed); | ||
return notifications.Subscribe(action); | ||
} | ||
|
||
internal static void SubscribeTo(this Control control, ExpressionBuilder builder) | ||
{ | ||
if (string.IsNullOrEmpty(control.Name)) | ||
{ | ||
control.Name = ExpressionBuilder.GetElementDisplayName(builder); | ||
} | ||
|
||
if (builder is ControlBuilderBase controlBuilder) | ||
{ | ||
control.SubscribeTo(controlBuilder._Enabled, value => control.Enabled = value); | ||
control.SubscribeTo(controlBuilder._Visible, value => control.Visible = value); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
using System; | ||
using System.Windows.Forms; | ||
using System.Xml.Serialization; | ||
using Bonsai.Design; | ||
using Bonsai.Expressions; | ||
|
||
namespace Bonsai.Gui | ||
{ | ||
/// <summary> | ||
/// Provides an abstract base class for visualizers representing individual UI elements. | ||
/// </summary> | ||
/// <typeparam name="TControl">The type of the control exposed by this type visualizer.</typeparam> | ||
/// <typeparam name="TControlBuilder">The type of the workflow element used to create the control.</typeparam> | ||
public abstract class ControlVisualizerBase<TControl, TControlBuilder> | ||
: DialogTypeVisualizer | ||
where TControl : Control | ||
where TControlBuilder : ExpressionBuilder | ||
{ | ||
/// <summary> | ||
/// Gets the active control exposed by this type visualizer. | ||
/// </summary> | ||
[XmlIgnore] | ||
public TControl Control { get; private set; } | ||
|
||
/// <summary> | ||
/// Creates and configures the visual control associated with | ||
/// the specified workflow operator. | ||
/// </summary> | ||
/// <param name="provider"> | ||
/// A service provider object which can be used to obtain visualization, | ||
/// runtime inspection, or other editing services. | ||
/// </param> | ||
/// <param name="builder"> | ||
/// The <see cref="ExpressionBuilder"/> object used to provide configuration | ||
/// properties to the control. | ||
/// </param> | ||
/// <returns> | ||
/// A new instance of the control class associated with the specified | ||
/// workflow operator. | ||
/// </returns> | ||
protected abstract TControl CreateControl(IServiceProvider provider, TControlBuilder builder); | ||
|
||
/// <inheritdoc/> | ||
public override void Load(IServiceProvider provider) | ||
{ | ||
var context = (ITypeVisualizerContext)provider.GetService(typeof(ITypeVisualizerContext)); | ||
var controlBuilder = (TControlBuilder)ExpressionBuilder.GetVisualizerElement(context.Source).Builder; | ||
Control = CreateControl(provider, controlBuilder); | ||
Control.SubscribeTo(controlBuilder); | ||
|
||
var visualizerService = (IDialogTypeVisualizerService)provider.GetService(typeof(IDialogTypeVisualizerService)); | ||
visualizerService?.AddControl(Control); | ||
} | ||
|
||
/// <inheritdoc/> | ||
public override void Show(object value) | ||
{ | ||
} | ||
|
||
/// <inheritdoc/> | ||
public override void Unload() | ||
{ | ||
Control.Dispose(); | ||
Control = null; | ||
} | ||
} | ||
} |
Oops, something went wrong.