A model type used by the Editor to represent a free-floating sticky note on a canvas. Stores id, position, size, color and text, computes a clamped Rect, provides summary text, hit testing, cloning, and string representation.
using Editor.Prism.Core;
namespace Editor.Prism.Model;
/// <summary>
/// A free-floating annotation on the canvas. Unlike a <see cref="GraphGroup"/> it owns nothing and
/// encloses nothing — it is documentation the author leaves for whoever opens the graph next.
/// </summary>
public sealed class StickyNote
{
/// <summary>Build an empty note with a fresh id.</summary>
public StickyNote()
{
Id = Ids.NewShortId();
}
/// <summary>Build a note at a position with a fresh id.</summary>
public StickyNote( string text, Vector2 position ) : this()
{
Text = text;
Position = position;
}
/// <summary>Stable id, minted once and never rewritten.</summary>
public string Id { get; set; }
/// <summary>Top-left corner in scene space.</summary>
public Vector2 Position { get; set; }
/// <summary>Width and height in scene space.</summary>
public Vector2 Size { get; set; } = DefaultSize;
/// <summary>Named colour from <see cref="GraphGroup.Palette"/>.</summary>
public string Color { get; set; } = "Yellow";
/// <summary>The note body. Plain text; newlines are preserved.</summary>
public string Text { get; set; } = string.Empty;
/// <summary>The note's scene rectangle, with the minimum size enforced.</summary>
public Rect Rect
{
get => new( Position, new Vector2( Math.Max( Size.x, MinSize.x ), Math.Max( Size.y, MinSize.y ) ) );
set
{
Position = value.Position;
Size = value.Size;
}
}
/// <summary>The first line of the note, for compact listings and tooltips.</summary>
public string Summary
{
get
{
if ( string.IsNullOrEmpty( Text ) ) return string.Empty;
var end = Text.IndexOfAny( s_lineBreaks );
var line = end < 0 ? Text : Text[..end];
return line.Length <= 64 ? line : line[..61] + "...";
}
}
/// <summary>True when a scene-space point lies inside the note.</summary>
public bool Contains( Vector2 point )
{
var rect = Rect;
return point.x >= rect.Left && point.x <= rect.Left + rect.Width &&
point.y >= rect.Top && point.y <= rect.Top + rect.Height;
}
/// <summary>Deep copy, keeping the same id.</summary>
public StickyNote Clone() => new()
{
Id = Id,
Position = Position,
Size = Size,
Color = Color,
Text = Text
};
/// <summary>Deep copy with a freshly minted id.</summary>
public StickyNote CloneWithNewId()
{
var copy = Clone();
copy.Id = Ids.NewShortId();
return copy;
}
/// <inheritdoc/>
public override string ToString() => $"Note '{Summary}'";
/// <summary>The size a freshly created note gets.</summary>
public static readonly Vector2 DefaultSize = new( 260f, 96f );
/// <summary>The smallest a note may be, so it stays grabbable.</summary>
public static readonly Vector2 MinSize = new( 96f, 48f );
static readonly char[] s_lineBreaks = { '\r', '\n' };
}