diff --git a/src/Bonsai.Scripting.Expressions.Design/ExpressionScriptEditor.cs b/src/Bonsai.Scripting.Expressions.Design/ExpressionScriptEditor.cs index 7d151f3..a87a1d0 100644 --- a/src/Bonsai.Scripting.Expressions.Design/ExpressionScriptEditor.cs +++ b/src/Bonsai.Scripting.Expressions.Design/ExpressionScriptEditor.cs @@ -4,6 +4,7 @@ using System.Windows.Forms.Design; using System.Windows.Forms; using Bonsai.Design; +using System.Reactive; namespace Bonsai.Scripting.Expressions.Design { @@ -53,7 +54,7 @@ public override object EditValue(ITypeDescriptorContext context, IServiceProvide var editorService = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService)); if (editorService != null) { - var itType = GetDataSource(context, provider)?.ObservableType; + var itType = context.Instance is ExpressionSource ? typeof(Unit) : GetDataSource(context, provider)?.ObservableType; using var editorDialog = new ExpressionScriptEditorDialog(itType); editorDialog.Script = (string)value; return editorService.ShowDialog(editorDialog) == DialogResult.OK diff --git a/src/Bonsai.Scripting.Expressions/ExpressionSource.cs b/src/Bonsai.Scripting.Expressions/ExpressionSource.cs new file mode 100644 index 0000000..7d1a96c --- /dev/null +++ b/src/Bonsai.Scripting.Expressions/ExpressionSource.cs @@ -0,0 +1,78 @@ +using Bonsai.Expressions; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq.Expressions; +using System.Reactive; +using System.Reactive.Linq; + +namespace Bonsai.Scripting.Expressions +{ + /// + /// Represents an operator that uses an expression script to generate an + /// observable sequence with a single element. + /// + [DefaultProperty(nameof(Expression))] + [WorkflowElementCategory(ElementCategory.Source)] + [TypeDescriptionProvider(typeof(ExpressionSourceTypeDescriptionProvider))] + [Description("An expression script used to generate a sequence with a single element.")] + public class ExpressionSource : ZeroArgumentExpressionBuilder, IScriptingElement + { + /// + /// Gets or sets the name of the expression source. + /// + [Externalizable(false)] + [Category(nameof(CategoryAttribute.Design))] + [Description("The name of the expression source.")] + public string Name { get; set; } + + /// + /// Gets or sets a description for the expression source. + /// + [Externalizable(false)] + [Category(nameof(CategoryAttribute.Design))] + [Description("A description for the expression source.")] + [Editor(DesignTypes.MultilineStringEditor, DesignTypes.UITypeEditor)] + public string Description { get; set; } + + /// + /// Gets or sets the expression that generates the singleton element. + /// + /// + /// The it parameter stores the singleton object + /// representing no data. + /// + [Editor("Bonsai.Scripting.Expressions.Design.ExpressionScriptEditor, Bonsai.Scripting.Expressions.Design", DesignTypes.UITypeEditor)] + [Description("The expression that generates the singleton element.")] + public string Expression { get; set; } = "it"; + + /// + public override Expression Build(IEnumerable arguments) + { + var config = ParsingConfigHelper.CreateParsingConfig(typeof(Unit)); + var generator = DynamicExpressionHelper.ParseLambda(config, typeof(Unit), null, Expression); + return System.Linq.Expressions.Expression.Call(typeof(ExpressionSource), nameof(Process), new[] { generator.ReturnType }, generator); + } + + static IObservable Process(Func generator) + { + return Observable.Return(Unit.Default).Select(generator); + } + + class ExpressionSourceTypeDescriptionProvider : TypeDescriptionProvider + { + static readonly TypeDescriptionProvider parentProvider = TypeDescriptor.GetProvider(typeof(ExpressionSource)); + + public ExpressionSourceTypeDescriptionProvider() + : base(parentProvider) + { + } + + public override ICustomTypeDescriptor GetExtendedTypeDescriptor(object instance) + { + return new ScriptingElementTypeDescriptor(instance, + "An expression script used to generate a sequence with a single element."); + } + } + } +}