Namespace: Beep.Skia — Lines: 1386 — File: SkiaComponent.cs

Overview

SkiaComponent is the abstract base class for every visual element rendered on a Beep.Skia canvas. It provides the foundational infrastructure that all diagram nodes, UI controls, and custom visual components share:

  • Position & Sizing: Absolute X/Y position, Width, Height, and computed bounding boxes in both local and world coordinate spaces.
  • IDisposable: Proper resource cleanup for SkiaSharp objects (paints, surfaces, paths) through the Dispose() pattern.
  • NodeProperties: A configurable parameter dictionary that enables dynamic property editors without requiring reflection. Every component can expose its editable fields as a Dictionary<string, ParameterInfo>.
  • ConnectionPoints: Collections of input and output ports (InPoints / OutPoints) with lazy layout recomputation for performance.
  • DrawContent: A virtual method that subclasses override to render their specific visual content using a SKCanvas.
  • Invalidation: Mark the component or its region as dirty to trigger a redraw on the next render cycle.
  • Hit Testing: Point-inside-bounds and per-feature hit test methods for precise mouse interaction routing.
  • Absolute Coordinate System: All positioning uses an absolute, unscrolled coordinate space. Pan and zoom transforms are applied once globally by RenderingHelper.

The class implements IDrawableComponent and serves as the trunk of a deep inheritance tree spanning 200+ node types across 18 diagram families.

Key Properties

PropertyTypeDescription
XfloatAbsolute X coordinate of the component's top-left corner in world space.
YfloatAbsolute Y coordinate of the component's top-left corner in world space.
WidthfloatWidth of the component in logical units. Default sizes vary by component family.
HeightfloatHeight of the component in logical units. Default sizes vary by component family.
BoundsSKRectComputed bounding rectangle from (X, Y, X+Width, Y+Height). Used for hit testing and invalidation.
NodePropertiesDictionary<string, ParameterInfo>Configurable parameter map powering dynamic property editors. See NodeProperties System.
InConnectionPointsList<IConnectionPoint>Input connection ports. Positions recomputed lazily when dirty.
OutConnectionPointsList<IConnectionPoint>Output connection ports. Positions recomputed lazily when dirty.
IsSelectedboolSelection state. Affects rendering (selection highlight, resize handles) and keyboard operations.
IsVisibleboolVisibility flag. Invisible components are skipped during rendering and hit testing.
IdGuidUnique identifier for serialization, connection tracking, and lookup operations.
TagobjectArbitrary user-defined data attached to the component.

NodeProperties System

Every SkiaComponent exposes its configurable fields as a Dictionary<string, ParameterInfo>. This enables rich, dynamic property editors — such as the in-app property grid — to discover and edit component settings without runtime reflection overhead.

The ParameterInfo class captures:

  • ParameterName: Internal key and property grid label.
  • ParameterCurrentValue: The current value (as object).
  • DefaultParameterValue: The value restored when resetting the property.
  • ParameterType: Underlying CLR type (string, int, float, bool, SKColor, enum, etc.).
  • Description: Tooltip/help text shown by the property grid.
  • Choices: Optional fixed list of allowed string values, rendered as a dropdown.

Subclasses populate the dictionary in their constructor or InitializeComponent() override:

Defining NodeProperties

protected override void InitializeComponent()
{
    base.InitializeComponent();

    NodeProperties["Text"] = new ParameterInfo
    {
        ParameterName = "Text",
        ParameterCurrentValue = "New Node",
        DefaultParameterValue = "New Node",
        ParameterType = typeof(string),
        Description = "Label text"
    };

    NodeProperties["FillColor"] = new ParameterInfo
    {
        ParameterName = "FillColor",
        ParameterCurrentValue = SKColors.CornflowerBlue,
        DefaultParameterValue = SKColors.CornflowerBlue,
        ParameterType = typeof(SKColor),
        Description = "Fill color"
    };

    NodeProperties["FontSize"] = new ParameterInfo
    {
        ParameterName = "FontSize",
        ParameterCurrentValue = 14f,
        DefaultParameterValue = 14f,
        ParameterType = typeof(float),
        Description = "Font size in points"
    };
}

At runtime, the property grid reads GetProperties() to enumerate editable values and calls SetProperties(dict) to apply changes. GetProperties() returns a Dictionary<string, object> of plain values (not ParameterInfo instances), so write back through SetProperties and invalidate the component to reflect the new values on the next draw:

Getting and Setting Properties

var properties = myComponent.GetProperties();

float currentSize = (float)properties["FontSize"];

properties["FontSize"] = 18f;
myComponent.SetProperties(properties);

myComponent.InvalidateVisual();
drawingManager.RequestRedraw();

Note

Changes to NodeProperties values do not automatically trigger Invalidate(). The caller (property editor) is responsible for invalidating the component and canvas after applying changes.

Connection Points

Connection points (ports) are the anchor locations where ConnectionLine segments attach to a component. Each point is an instance of IConnectionPoint stored in the component's InConnectionPoints (input) or OutConnectionPoints (output) collections.

Lazy Port Layout

Port positions are recomputed only when needed. The flow is:

  1. MarkPortsDirty() (protected, on SkiaComponent) sets the dirty flag. OnBoundsChanged() calls it automatically, so moves and resizes always mark the layout stale.
  2. Inside DrawContent, call EnsurePortLayout(() => LayoutPorts()) (protected, on MaterialControl). It invokes your layout action only when ArePortsDirty is true, then clears the flag.
  3. LayoutPorts() recomputes port centers, positions and bounds from the current Bounds.

This keeps rapid drags cheap: geometry changes only set a flag, and the expensive layout runs once, on the next draw.

ConnectionPoint Properties

PropertyTypeDescription
TypeConnectionPointTypeIn or Out. Determines which side of a component the port appears on.
DataTypestringDescriptive data type of the values flowing through this port (for example "string", "number"). Used for data-flow visualization and connection validation.
Position / CenterSKPointAbsolute world-space position of the port anchor. Updated by LayoutPorts().
Bounds / RectSKRectHit-test rectangle around the port, recomputed with the position.
RowIdGuid?Optional identifier for ERD row-level connections. Enables per-field linking in entity diagrams.
IdGuidUnique identifier used for serialization and connection restoration.
IsAvailableboolWhether the port can accept a new connection.
ConnectionIConnectionPointThe port this one is connected to, or null when free.

Creating and Layouting Ports

protected override void DrawContent(SKCanvas canvas, DrawingContext context)
{
    // Re-layout ports only when the dirty flag is set
    EnsurePortLayout(() => LayoutPorts());

    // ... draw the component content
}

protected override void LayoutPorts()
{
    var b = Bounds;
    float midY = b.MidY;

    InConnectionPoints.Clear();
    OutConnectionPoints.Clear();

    InConnectionPoints.Add(new ConnectionPoint
    {
        Type = ConnectionPointType.In,
        Center = new SKPoint(b.Left, midY),
        Position = new SKPoint(b.Left, midY),
        DataType = "string",
        Component = this,
        IsAvailable = true
    });

    OutConnectionPoints.Add(new ConnectionPoint
    {
        Type = ConnectionPointType.Out,
        Center = new SKPoint(b.Right, midY),
        Position = new SKPoint(b.Right, midY),
        DataType = "string",
        Component = this,
        IsAvailable = true
    });
}

Lifecycle

Every SkiaComponent follows a defined lifecycle from creation through disposal:

PhaseMethodDescription
ConstructionSkiaComponent()Sets default values for X, Y, Width, Height, Opacity and visibility. Initializes the connection-point collections and the NodeProperties dictionary, and assigns a new Guid.
InitializationInitializeComponent()Protected virtual, called by the constructor. Subclasses populate NodeProperties, create initial ports and set default sizes. Always call base.InitializeComponent().
RenderDrawContent(SKCanvas, DrawingContext)Abstract on SkiaComponent; every component renders here. The canvas already has pan/zoom transforms applied, and the DrawingContext carries zoom and viewport state.
UpdateUpdate(DrawingContext)Virtual, called once per frame by the render loop before drawing. Use it for time-based state (animations, hover easing).
InvalidationInvalidateVisual()Public virtual on MaterialControl. Marks the component as needing a redraw; the host schedules the next frame.
Hit TestingHitTest(SKPoint)Public virtual. Returns true if the point falls within the component. Override for precise per-shape hit testing.
DisposalDispose() / Dispose(bool)Breaks incoming and outgoing connections, then releases SKPaint, SKPath, SKBitmap and other SkiaSharp resources. Sets State = ComponentState.Disposing to prevent use-after-free.

Typical Component Lifecycle Usage

var node = new ProcessNode { Name = "Process Step" };
node.X = 100;
node.Y = 200;

drawingManager.AddComponent(node);

drawingManager.RequestRedraw();

drawingManager.RemoveComponent(node);
node.Dispose();

Inheritance Hierarchy

The SkiaComponent class sits at the center of a deep inheritance chain that spans from the abstract interface down to concrete diagram nodes:

IDrawableComponent
↓
SkiaComponent
1386 lines — abstract base
↓
MaterialControl
Material Design 3.0 theming, elevation, state layers, ripple
↓
Family Base Controls
FlowChartNodeBase, ERDEntityBase, UMLClassifierBase, etc.
↓
Concrete Nodes
ProcessNode, DecisionNode, ERDEntity, UMLClass, NetworkDevice, etc. (293 types)
LevelTypePurpose
0IDrawableComponent (interface)Contract: Draw, hit test, bounds, dispose, connection points.
1SkiaComponent (abstract)Position, size, NodeProperties, ConnectionPoints, base rendering. All diagram nodes derive from here.
2MaterialControl (abstract)Material Design 3.0 theming: elevation shadows, state layers (hover, focus, pressed), ripple animation, theme-aware color resolution.
3Family-level base classesShared behavior per diagram family: flowchart routing rules, ERD entity rendering, UML notation, network device icons. Override DrawContent or DrawFlowchartContent for family-specific appearance.
4Concrete leaf nodesFully rendered diagram nodes: ProcessNode, DecisionNode, ERDEntity, UMLClass, NetworkRouter, etc.

Tip

When creating a custom component, inherit from the family-level base class rather than SkiaComponent directly. This gives you free Material Design theming, family-specific behavior, and proper palette integration through SkiaComponentRegistry.