Here’s code for a component which adds a grid underneath it for the input of a complex number:
ComponentWithMouseHandling.cs (6.9 KB)
using System.Numerics;
using Eto.Forms;
using Eto.Drawing;
using GrasshopperIO;
using Grasshopper.UI;
using Grasshopper.UI.Skinning;
using Grasshopper.Components;
using Grasshopper.Extensions;
using Grasshopper.Doc;
using Grasshopper.Doc.Attributes;
using Grasshopper.UI.Flex;
using Grasshopper.UI.Primitives;
namespace Example
{
[IoId("42f838dd-1895-4b1b-be30-c0b42f838dd2")]
public sealed class ComponentWithMouseOverrides : Component
{
public ComponentWithMouseOverrides()
: base(new Nomen("Mouse Override", "An example for handling mouse events on custom components.", "Example", "Example"))
{
Z = new Complex(0, 0);
}
public ComponentWithMouseOverrides(IReader reader) : base(reader)
{
Z = reader.Complex128(nameof(Z));
}
public override void Store(IWriter writer)
{
base.Store(writer);
writer.Complex128(nameof(Z), Z);
}
protected override IAttributes CreateAttributes()
{
return new ComponentWithMouseOverridesAttributes(this);
}
protected override void AddInputs(InputAdder inputs)
{
inputs.AddComplex("Complex Seed", "Cx", "Complex seed for fractal sequence.").Set(Complex.One);
inputs.AddInteger("Length", "Ln", "Sequence length.").Set(100);
}
protected override void AddOutputs(OutputAdder outputs)
{
outputs.AddComplex("Sequence", "Sq", "Fractal sequence.", Grasshopper.Parameters.Access.Twig);
}
public Complex Z { get; set; }
protected override void Process(IDataAccess access)
{
access.GetItem(0, out Complex seed);
access.GetItem(1, out int length);
var sequence = new Complex[length];
sequence[0] = seed;
for (int i = 1; i < length; i++)
{
seed = seed * seed + Z;
sequence[i] = seed;
}
access.SetTwig(0, sequence);
}
}
public sealed class ComponentWithMouseOverridesAttributes : ComponentAttributes
{
public ComponentWithMouseOverridesAttributes(ComponentWithMouseOverrides component) : base(component) { }
#region layout overrides
public RectangleF ComplexBounds { get; set; }
public RectangleF ComplexGrid
{
get
{
return ComplexBounds.AdjustSides(-4);
}
}
public CircleF ComplexGrip { get; private set; }
private Grasshopper.UI.Grid.OrthoGrid Grid
{
get
{
var grid = Grasshopper.UI.Grid.OrthoGrid.DefaultTight;
grid.Setup(ComplexGrid, ComplexGrid.Center, new SizeF(70, 70));
return grid;
}
}
protected override void LayoutBounds(Shape shape)
{
// This is the last stage in normal component layout.
// So we're going to let that all happen and only *then*
// glue our own box to the bottom of the layout.
base.LayoutBounds(shape);
// Our added control just adds 150 pixels to the bottom of the component.
// We can also make it wider, but that involves more work, so I'm not
// going to tarnish this example. Basically, you can choose to add
// a constant amount of pixels to the CentralBox by overriding LayoutCentralBox().
ComplexBounds = RectangleF.FromSides(Bounds.Left, Bounds.Bottom, Bounds.Right, Bounds.Bottom + 150);
// We must adjust the Bounds now to include our complex box, otherwise Grasshopper thinks
// the component is a lot smaller than it really is.
Bounds = RectangleF.Union(Bounds, ComplexBounds);
}
#endregion
#region drawing overrides
protected override void DrawForeground(Context context, Skin skin, Capsule capsule, Shade shade)
{
// Allow the foreground to draw normally.
base.DrawForeground(context, skin, capsule, shade);
// We don't want to draw anything outside of the grid,
// let's set up clipping to ensure that.
context.Graphics.SetClip(ComplexGrid);
// Now we'll overlay our Complex plane grid.
// First fill the grid area with a slightly tinted colour
// to differentiate it from the component background.
context.Graphics.FillRectangle(shade.Apex.MoveTo(shade.Edge, 0.1f), ComplexGrid);
// Then draw the axes and grid lines.
var grid = Grid;
grid.Draw(context, Grasshopper.UI.Grid.GridElements.All);
// Now draw the complex grip.
var component = (ComponentWithMouseOverrides)Owner;
grid.GridToControl(component.Z.Real, component.Z.Imaginary, out var x, out var y);
ComplexGrip = new CircleF(new PointF((float)x, (float)y), 5);
context.Graphics.FillCircle(OpenColor.Blue9, ComplexGrip);
context.Graphics.FillCircle(OpenColor.Blue4, ComplexGrip.Offset(-2));
// Finally draw a strong boundary rectangle around our grid.
context.Graphics.ResetClip();
context.Graphics.DrawRectangle(shade.Edge, ComplexGrid);
}
#endregion
#region mouse overrides
private bool _drag = false;
private PointF _dragFrom;
private Complex _dragBase;
protected override Response HandleMouseDown(MouseEventArgs e)
{
if (e.Buttons == MouseButtons.Primary &&
ComplexGrid.Contains(e.Location) &&
ComplexGrip.Offset(2).Contains(e.Location))
{
// Only override the mouse events if the users clicks within 2 pixels of the grip.
_drag = true;
_dragFrom = e.Location;
_dragBase = ((ComponentWithMouseOverrides)Owner).Z;
return Response.Capture;
}
else
{
// Otherwise allow the default mouse handling behaviour to continue.
_drag = false;
_dragFrom = PointF.Empty;
_dragBase = Complex.Zero;
return Response.Ignored;
}
}
protected override Response HandleMouseMove(MouseEventArgs e)
{
if (_drag && e.Buttons == MouseButtons.Primary)
{
// limit the dragging to within the grid. I'm not sure why.
var location = ComplexGrid.ClosestPointIn(e.Location);
var dx = location.X - _dragFrom.X;
var dy = location.Y - _dragFrom.Y;
var grid = Grid;
grid.ControlToGrid(0, 0, out var x0, out var y0);
grid.ControlToGrid(dx, dy, out var x1, out var y1);
var dr = x1 - x0;
var di = y1 - y0;
((ComponentWithMouseOverrides)Owner).Z = new Complex(_dragBase.Real + dr, _dragBase.Imaginary + di);
Owner.Document?.Solution.DelayedExpire(Owner);
return Response.Handled;
}
else
{
// Something went wrong, we're not supposed to be
// dragging if the mouse button is not down.
_drag = false;
return Response.Release;
}
}
protected override Response HandleMouseUp(MouseEventArgs e)
{
// Wipe the dragging state.
_drag = false;
_dragFrom = PointF.Empty;
_dragBase = Complex.Zero;
return Response.Release;
}
#endregion
}
}