🔍 s&box Package Code Search
Search C# source code, UI razor templates, shaders, and configs across s&box packages.
🔗 Raw Facepunch API Link: https://public.facepunch.com/sbox/code/search/1/?ident=sklmr.razordesigner&take=20
Showing code results for query:
*
(184 total matches found)
Editor
library
using System.Text.Json.Serialization;
namespace Grains.RazorDesigner.Document;
public sealed record CheckboxPayload : Payload
{
[JsonIgnore]
public override ControlType Kind => ControlType.Checkbox;
// Checkbox label text. Overrides Payload.Content (neutral default "").
public override string Content { get; init; } = "";
public override Length CheckboxSize { get; init; } = Length.Px( 16 );
}
Editor
library
using Editor;
using Grains.RazorDesigner.Document;
using Sandbox;
namespace Grains.RazorDesigner.Inspector;
[CustomEditor( typeof( Edges ) )]
public sealed class EdgesControlWidget : ControlWidget
{
private const string LogPrefix = "[Grains.RazorDesigner]";
public override bool SupportsMultiEdit => true;
private readonly EdgesProxy _proxy;
private readonly SerializedObject _proxySerialized;
// Synchronous change events on both sides; without this guard SetValue would loop.
private bool _syncing;
private sealed class EdgesProxy
{
public Length Top { get; set; } = Length.Px( 0 );
public Length Right { get; set; } = Length.Px( 0 );
public Length Bottom { get; set; } = Length.Px( 0 );
public Length Left { get; set; } = Length.Px( 0 );
}
public EdgesControlWidget( SerializedProperty property ) : base( property )
{
Log.Info( $"{LogPrefix} EdgesControlWidget ctor for {property.Name}" );
Layout = Layout.Column();
Layout.Spacing = 2;
_proxy = new EdgesProxy();
_proxySerialized = EditorTypeLibrary.GetSerializedObject( _proxy );
var topRow = Layout.Add( Layout.Row() );
topRow.Spacing = 2;
AddSide( topRow, nameof( EdgesProxy.Top ), "border_top" );
AddSide( topRow, nameof( EdgesProxy.Right ), "border_right" );
var bottomRow = Layout.Add( Layout.Row() );
bottomRow.Spacing = 2;
AddSide( bottomRow, nameof( EdgesProxy.Bottom ), "border_bottom" );
AddSide( bottomRow, nameof( EdgesProxy.Left ), "border_left" );
_proxySerialized.OnPropertyChanged += OnProxyChanged;
SyncFromProperty();
}
private void AddSide( Layout row, string propName, string icon )
{
var prop = _proxySerialized.GetProperty( propName );
var lengthWidget = new LengthControlWidget( prop, icon );
row.Add( lengthWidget, 1 );
}
protected override void PaintControl()
{
// nothing
}
private void SyncFromProperty()
{
if ( _syncing ) return;
_syncing = true;
try
{
var e = SerializedProperty.GetValue<Edges>( Edges.Zero );
_proxySerialized.GetProperty( nameof( EdgesProxy.Top ) ).SetValue( e.Top );
_proxySerialized.GetProperty( nameof( EdgesProxy.Right ) ).SetValue( e.Right );
_proxySerialized.GetProperty( nameof( EdgesProxy.Bottom ) ).SetValue( e.Bottom );
_proxySerialized.GetProperty( nameof( EdgesProxy.Left ) ).SetValue( e.Left );
}
finally
{
_syncing = false;
}
}
private void OnProxyChanged( SerializedProperty property )
{
if ( _syncing ) return;
if ( ReadOnly || !SerializedProperty.IsEditable )
return;
_syncing = true;
try
{
var newValue = new Edges( _proxy.Top, _proxy.Right, _proxy.Bottom, _proxy.Left );
Log.Info( $"{LogPrefix} EdgesControlWidget OnProxyChanged {newValue}" );
PropertyStartEdit();
SerializedProperty.SetValue( newValue );
SignalValuesChanged();
PropertyFinishEdit();
}
finally
{
_syncing = false;
}
}
protected override void OnValueChanged()
{
base.OnValueChanged();
SyncFromProperty();
}
}
Editor
library
using System;
using System.Collections.Generic;
using Editor;
using Grains.RazorDesigner.Common;
using Grains.RazorDesigner.Contracts;
using Grains.RazorDesigner.Document;
using Grains.RazorDesigner.Templates;
using Sandbox;
namespace Grains.RazorDesigner.Palette;
public class PalettePanel : Widget
{
private const string LogPrefix = "[Grains.RazorDesigner]";
private const string CookiePrefix = "razordesigner.palette.";
// Click-to-add target. Window decides where the new record goes (typically active selection or root).
public event Action<ControlType> TypeAddRequested;
// Click-to-add a saved template. Window decides where to insert.
public event Action<PaletteTemplate> TemplateAddRequested;
private readonly PaletteTemplateStore _templateStore = new();
private CollapsibleSection _templatesSection;
private WrapPanel _templatesWrap;
public PaletteTemplateStore TemplateStore => _templateStore;
public PalettePanel( Widget parent ) : base( parent )
{
Layout = Layout.Column();
Layout.Margin = 0;
Layout.Spacing = 0;
MinimumWidth = 180;
VerticalSizeMode = SizeMode.CanGrow;
var byCategory = new Dictionary<ControlCategory, List<ControlType>>();
foreach ( ControlType type in Enum.GetValues( typeof( ControlType ) ) )
{
var cat = ControlDefaults.For( type ).Category;
if ( !byCategory.TryGetValue( cat, out var list ) )
{
list = new List<ControlType>();
byCategory[cat] = list;
}
list.Add( type );
}
// Templates section (top of palette). Hidden when store is empty; rebuilt on Changed.
_templatesSection = new CollapsibleSection( this, "Templates", "bookmark" );
_templatesWrap = new WrapPanel( null )
{
MinItemWidth = 92,
ItemHeight = (int)( Theme.RowHeight + 4 ),
HSpacing = 4,
VSpacing = 4,
PaddingLeft = 4,
PaddingTop = 4,
PaddingRight = 14,
PaddingBottom = 4,
};
_templatesSection.BodyLayout.Add( _templatesWrap );
var templatesCookie = $"{CookiePrefix}templates.expanded";
_templatesSection.Expanded = EditorCookie.Get<bool>( templatesCookie, true );
_templatesSection.ExpandedChanged += expanded =>
{
EditorCookie.Set( templatesCookie, expanded );
Log.Info( $"{LogPrefix} Palette Templates {(expanded ? "expanded" : "collapsed")}" );
};
Layout.Add( _templatesSection );
_templateStore.Changed += RebuildTemplatesSection;
_templateStore.Scan(); // initial fill (also fires Changed and rebuilds the section)
foreach ( ControlCategory cat in Enum.GetValues( typeof( ControlCategory ) ) )
{
if ( !byCategory.TryGetValue( cat, out var list ) ) continue;
var section = new CollapsibleSection(
this,
ControlDefaults.CategoryDisplayName( cat ),
CategoryIcon( cat ) );
var wrap = new WrapPanel( null )
{
MinItemWidth = 92,
ItemHeight = (int)( Theme.RowHeight + 4 ),
HSpacing = 4,
VSpacing = 4,
PaddingLeft = 4,
PaddingTop = 4,
PaddingRight = 14, // clear the ScrollArea's vertical scrollbar
PaddingBottom = 4,
};
section.BodyLayout.Add( wrap );
foreach ( var t in list )
new PaletteTypeButton( wrap, this, t );
var cookieKey = $"{CookiePrefix}{cat}.expanded";
section.Expanded = EditorCookie.Get<bool>( cookieKey, DefaultExpanded( cat ) );
section.ExpandedChanged += expanded =>
{
EditorCookie.Set( cookieKey, expanded );
Log.Info( $"{LogPrefix} Palette category {cat} {(expanded ? "expanded" : "collapsed")}" );
};
Layout.Add( section );
}
Layout.AddStretchCell();
Log.Info( $"{LogPrefix} PalettePanel ctor (icon grid, {byCategory.Count} categories)" );
}
internal void NotifyTypeClicked( ControlType type )
{
Log.Info( $"{LogPrefix} PalettePanel.NotifyTypeClicked: {type}" );
TypeAddRequested?.Invoke( type );
}
internal void NotifyTemplateClicked( PaletteTemplate template )
{
Log.Info( $"{LogPrefix} PalettePanel.NotifyTemplateClicked: \"{template.Name}\"" );
TemplateAddRequested?.Invoke( template );
}
internal void RequestTemplateDelete( PaletteTemplate template )
{
var dialog = new Editor.Dialog( this );
dialog.Window.WindowTitle = "Delete template";
dialog.Window.SetWindowIcon( "delete" );
dialog.Window.SetModal( true, true );
dialog.Window.MinimumWidth = 320;
dialog.Layout = Layout.Column();
dialog.Layout.Margin = 16;
dialog.Layout.Spacing = 10;
dialog.Layout.Add( new Editor.Label( dialog )
{
Text = $"Delete template \"{template.Name}\"?",
} );
var hint = new Editor.Label( dialog )
{
Text = "Already-instantiated copies in open documents are unaffected.",
};
hint.SetStyles( "color: #888; font-size: 11px;" );
dialog.Layout.Add( hint );
var buttonRow = dialog.Layout.Add( Layout.Row() );
buttonRow.Spacing = 6;
buttonRow.AddStretchCell();
var cancel = new Editor.Button( dialog ) { Text = "Cancel", MinimumWidth = 72 };
cancel.MouseLeftPress += () => dialog.Close();
buttonRow.Add( cancel );
var del = new Editor.Button( dialog ) { Text = "Delete", MinimumWidth = 72 };
del.SetStyles( "color: #e07070;" );
del.MouseLeftPress += () =>
{
Log.Info( $"{LogPrefix} Palette delete confirmed: \"{template.Name}\"" );
_templateStore.Delete( template );
dialog.Close();
};
buttonRow.Add( del );
dialog.Window.AdjustSize();
dialog.Show();
}
private void RebuildTemplatesSection()
{
var templates = _templateStore.All;
// Hide the entire section (header + body) when there are no templates.
_templatesSection.Visible = templates.Count > 0;
using ( Editor.SuspendUpdates.For( _templatesWrap ) )
{
_templatesWrap.DestroyChildren();
foreach ( var t in templates )
new PaletteTemplateButton( _templatesWrap, this, t );
}
_templatesWrap.Relayout();
_templatesWrap.UpdateGeometry();
_templatesSection.UpdateGeometry();
UpdateGeometry();
Log.Info( $"{LogPrefix} PalettePanel.RebuildTemplatesSection: {templates.Count} tile(s), section.Visible={_templatesSection.Visible}" );
}
private static bool DefaultExpanded( ControlCategory cat ) =>
cat is ControlCategory.Layout or ControlCategory.Display or ControlCategory.Input;
private static string CategoryIcon( ControlCategory cat ) => cat switch
{
ControlCategory.Layout => "view_quilt",
ControlCategory.Display => "visibility",
ControlCategory.Input => "edit",
ControlCategory.Form => "list_alt",
_ => "category",
};
private sealed class PaletteTypeButton : Widget
{
private readonly PalettePanel _owner;
private readonly ControlType _type;
// InspectorIcon comes from the contract (engine-fidelity); drag defaults from ControlDefaults.
private readonly string _icon;
public PaletteTypeButton( Widget parent, PalettePanel owner, ControlType type ) : base( parent )
{
_owner = owner;
_type = type;
_icon = ContractScanner.Table.Get( type ).InspectorIcon;
ToolTip = type.ToString();
Cursor = CursorShape.Finger;
MouseTracking = true;
IsDraggable = true;
}
protected override void OnPaint()
{
var rect = LocalRect.Shrink( 1 );
Paint.Antialiasing = true;
Paint.TextAntialiasing = true;
var tint = ControlPresentation.IconTint( _type );
var fillAlpha = Paint.HasMouseOver ? 0.35f : 0.15f;
var borderAlpha = Paint.HasMouseOver ? 0.55f : 0.25f;
Paint.SetBrush( tint.WithAlpha( fillAlpha ) );
Paint.SetPen( tint.WithAlpha( borderAlpha ) );
Paint.DrawRect( rect, 3 );
var hoverOpacity = Paint.HasMouseOver ? 1f : 0.85f;
var iconRect = new Rect( rect.Left + 4, rect.Top, 20, rect.Height );
Paint.SetPen( tint.WithAlphaMultiplied( hoverOpacity ) );
Paint.DrawIcon( iconRect, _icon, 16, TextFlag.Center );
var textRect = rect;
textRect.Left = iconRect.Right + 2;
textRect.Right -= 4;
Paint.SetPen( Theme.Text.WithAlphaMultiplied( hoverOpacity ) );
Paint.SetDefaultFont();
Paint.DrawText( textRect, _type.ToString(), TextFlag.LeftCenter );
}
protected override void OnMouseClick( MouseEvent e )
{
base.OnMouseClick( e );
if ( e.LeftMouseButton )
_owner.NotifyTypeClicked( _type );
}
protected override void OnDragStart()
{
base.OnDragStart();
var drag = new Drag( this );
drag.Data.Object = _type;
drag.Data.Text = $"palette:{_type}";
drag.Execute();
Log.Info( $"{LogPrefix} PaletteTypeButton.OnDragStart: {_type}" );
}
}
private sealed class PaletteTemplateButton : Widget
{
private readonly PalettePanel _owner;
private readonly PaletteTemplate _template;
public PaletteTemplateButton( Widget parent, PalettePanel owner, PaletteTemplate template ) : base( parent )
{
_owner = owner;
_template = template;
ToolTip = template.Name;
Cursor = CursorShape.Finger;
MouseTracking = true;
IsDraggable = true;
}
protected override void OnPaint()
{
var rect = LocalRect.Shrink( 1 );
Paint.Antialiasing = true;
Paint.TextAntialiasing = true;
var tint = ControlPresentation.TemplateTint;
var fillAlpha = Paint.HasMouseOver ? 0.18f : 0.08f;
var borderAlpha = Paint.HasMouseOver ? 0.55f : 0.25f;
Paint.SetBrush( tint.WithAlpha( fillAlpha ) );
Paint.SetPen( tint.WithAlpha( borderAlpha ) );
Paint.DrawRect( rect, 3 );
var hoverOpacity = Paint.HasMouseOver ? 1f : 0.85f;
var iconRect = new Rect( rect.Left + 4, rect.Top, 20, rect.Height );
var icon = string.IsNullOrEmpty( _template.IconName ) ? "bookmark" : _template.IconName;
Paint.SetPen( tint.WithAlphaMultiplied( hoverOpacity ) );
Paint.DrawIcon( iconRect, icon, 16, TextFlag.Center );
var textRect = rect;
textRect.Left = iconRect.Right + 2;
textRect.Right -= 4;
Paint.SetPen( Theme.Text.WithAlphaMultiplied( hoverOpacity ) );
Paint.SetDefaultFont();
Paint.DrawText( textRect, _template.Name, TextFlag.LeftCenter );
}
protected override void OnMouseClick( MouseEvent e )
{
base.OnMouseClick( e );
if ( e.LeftMouseButton )
_owner.NotifyTemplateClicked( _template );
}
protected override void OnContextMenu( ContextMenuEvent e )
{
base.OnContextMenu( e );
var menu = new Menu( this );
menu.AddOption( "Delete…", "delete", () => _owner.RequestTemplateDelete( _template ) );
menu.OpenAtCursor();
e.Accepted = true;
}
protected override void OnDragStart()
{
base.OnDragStart();
var drag = new Drag( this );
drag.Data.Object = _template;
drag.Data.Text = $"template:{_template.Name}";
drag.Execute();
Log.Info( $"{LogPrefix} PaletteTemplateButton.OnDragStart: \"{_template.Name}\"" );
}
}
}
Editor
library
namespace Grains.RazorDesigner.Projection.CSharp;
public abstract record CSharpOp;
// File-level scaffold
public sealed record HeaderBanner( string ClassName, string Namespace ) : CSharpOp;
public sealed record UsingDirective( string Namespace ) : CSharpOp;
public sealed record NamespaceOpen( string Namespace ) : CSharpOp;
public sealed record ClassOpen( string ClassName, string BaseClass ) : CSharpOp;
public sealed record ClassClose() : CSharpOp;
public sealed record FieldDecl(
string Visibility, string Type, string Name, string InitialExpr,
bool IsParameter, bool IsProperty = false ) : CSharpOp;
public sealed record MethodOpen(
string Visibility, bool IsOverride, bool IsAsync,
string ReturnType, string Name, string ParameterList ) : CSharpOp;
public sealed record MethodClose() : CSharpOp;
// Body-level
public sealed record Statement( string Code ) : CSharpOp; // single `;`-terminated line
public sealed record BlockOpen( string Header ) : CSharpOp; // e.g. `if ( <cond> )` — applier writes "<header> {\n" and indents
public sealed record BlockClose() : CSharpOp;
public sealed record BlankLine() : CSharpOp;
public sealed record Comment( string Text ) : CSharpOp; // `// <text>`
Editor
library
using System.Collections.Generic;
using Grains.RazorDesigner.Projection.CSharp.Projectors;
namespace Grains.RazorDesigner.Projection.CSharp;
public static class CSharpProjector
{
public static CSharpResult Project(
IReadOnlyWiring wiring,
bool documentHasAnyBindings )
{
if ( wiring.Symbols.Count == 0 && !documentHasAnyBindings )
return new CSharpResult( System.Array.Empty<CSharpOp>(), null );
var ctx = new CSharpProjectorContext( wiring );
var ops = new List<CSharpOp>( 64 );
ops.Add( new HeaderBanner( wiring.ClassName, wiring.Namespace ) );
ops.Add( new UsingDirective( "Sandbox" ) );
ops.Add( new UsingDirective( "Sandbox.UI" ) );
foreach ( var u in wiring.Usings )
ops.Add( new UsingDirective( u ) );
ops.Add( new NamespaceOpen( wiring.Namespace ) );
ops.Add( new ClassOpen( wiring.ClassName, wiring.BaseClass ) );
// Step 3: body. SymbolProjector handles grouping + sorting + per-kind dispatch.
SymbolProjector.EmitAll( wiring, ops, ctx );
ops.Add( new ClassClose() );
var source = CSharpApplier.Apply( ops ).Replace( "\r\n", "\n" );
return new CSharpResult( ops, source );
}
}
Editor
library
using System.Collections.Generic;
using Grains.RazorDesigner.Wiring;
namespace Grains.RazorDesigner.Projection.CSharp.Projectors;
public static class ParameterSymbolProjector
{
public static void Emit( ParameterSymbol s, List<CSharpOp> ops, CSharpProjectorContext ctx )
{
var initial = s.Initial is null ? "default" : ExpressionEmitter.Emit( s.Initial, ctx );
ops.Add( new FieldDecl(
Visibility: "public", Type: s.Type, Name: s.Name,
InitialExpr: initial, IsParameter: true ) );
}
}
Editor
library
namespace Grains.RazorDesigner.Projection;
public static class Escape
{
public static string Html( string s )
{
if ( string.IsNullOrEmpty( s ) ) return "";
return s
.Replace( "&", "&" )
.Replace( "<", "<" )
.Replace( ">", ">" )
.Replace( "\"", """ );
}
}
Editor
library
using System;
using System.Collections.Generic;
using Sandbox; // Color
namespace Grains.RazorDesigner.Projection;
public interface IReadOnlyStateRule
{
Document.PseudoKind State { get; }
Document.NthChildMode NthChildMode { get; }
int NthChildArg { get; }
IAppearance Delta { get; }
public static int CompareCanonical( IReadOnlyStateRule a, IReadOnlyStateRule b )
{
int c = ((int)a.State).CompareTo( (int)b.State );
if ( c != 0 ) return c;
c = ((int)a.NthChildMode).CompareTo( (int)b.NthChildMode );
if ( c != 0 ) return c;
return a.NthChildArg.CompareTo( b.NthChildArg );
}
}
public interface IReadOnlyNode
{
Guid Id { get; }
string Kind { get; } // == ControlType.ToString()
string ClassName { get; }
IAppearance Appearance { get; }
IPayload Payload { get; }
IReadOnlyList<IReadOnlyNode> Children { get; } // non-slot children
IReadOnlyDictionary<string, IReadOnlyList<IReadOnlyNode>> Slots { get; } // slot-name -> slot children (only SplitContainer populates)
IReadOnlyList<IReadOnlyStateRule> StateRules { get; } // per-state style deltas; canonical order not guaranteed here (the Applier sorts)
}
public interface IAppearance
{
// Layout
Document.Length Width { get; }
Document.Length Height { get; }
// Flex container
Document.FlexDirection Direction { get; }
Document.JustifyContent Justify { get; }
Document.AlignItems Align { get; }
float Gap { get; }
Document.Edges Padding { get; }
Document.FlexWrap Wrap { get; }
// Positioning (grd-7t2z)
Document.PositionKind Position { get; }
Document.Length Top { get; }
Document.Length Left { get; }
Document.Length Right { get; }
Document.Length Bottom { get; }
// Flex self
float FlexGrow { get; }
float FlexShrink { get; }
Document.Length FlexBasis { get; }
Document.AlignSelfKind AlignSelf { get; }
// Typography + OverrideTypography
bool OverrideTypography { get; }
string FontFamily { get; }
Document.Length FontSize { get; }
int FontWeight { get; }
Color Color { get; }
Document.TextAlignment TextAlign { get; }
bool FontStyleItalic { get; }
Document.TextTransformKind TextTransform { get; }
Document.Length LetterSpacing { get; }
Document.Length LineHeight { get; }
// Background + OverrideBackground
bool OverrideBackground { get; }
Color BackgroundColor { get; }
string BackgroundImage { get; }
string BackgroundSize { get; }
string BackgroundPosition { get; }
string BackgroundRepeat { get; }
// Border + OverrideBorder
bool OverrideBorder { get; }
Document.Length BorderRadius { get; }
Color BorderColor { get; }
Document.Length BorderWidth { get; }
// Effects + OverrideEffects
bool OverrideEffects { get; }
Document.Length BoxShadowX { get; }
Document.Length BoxShadowY { get; }
Document.Length BoxShadowBlur { get; }
Color BoxShadowColor { get; }
bool BoxShadowInset { get; }
float Opacity { get; }
// Constraints + OverrideConstraints
bool OverrideConstraints { get; }
Document.Edges Margin { get; }
Document.Length MinWidth { get; }
Document.Length MaxWidth { get; }
Document.Length MinHeight { get; }
Document.Length MaxHeight { get; }
// Interaction + OverrideInteraction
bool OverrideInteraction { get; }
Document.CursorKind Cursor { get; }
Document.OverflowKind Overflow { get; }
int ZIndex { get; }
bool PointerEvents { get; }
}
public interface IPayload
{
string Content { get; } // Label/Button text; Checkbox label
string Placeholder { get; } // TextEntry
string Source { get; } // Image src
string IconName { get; } // IconPanel glyph
Document.Length CheckboxSize { get; } // Checkbox box size
}
Editor
library
using System.Collections.Generic;
using Grains.RazorDesigner.Projection.Appearance;
using Grains.RazorDesigner.Projection.Razor;
namespace Grains.RazorDesigner.Projection.Projectors;
[Projector( "Button" )]
public sealed class ButtonProjector : IControlProjector
{
public string Kind => "Button";
public ProjectionResult Project( IReadOnlyNode node, IAppearance a, IPayload p, ProjectionContext ctx )
{
var scss = AppearanceScss.Emit(
a,
isRoot: node.ClassName == Document.DesignerDocument.RootClassName,
isContainer: false,
childCount: 0,
isLabel: false,
isCheckbox: false,
checkboxSize: default );
var nodeId = node.Id.ToString();
var ops = new PanelOp[]
{
new SetAttribute( "data-grd-node-id", nodeId ),
new SetInnerText( p.Content ?? "" ),
};
var razorAttrs = new[] { RazorEmit.Attr( "data-grd-node-id", nodeId ) };
return new ProjectionResult(
PanelOps: ops,
ScssLines: scss,
RazorAttributes: razorAttrs,
RazorInnerText: Escape.Html( p.Content ?? "" ) );
}
}
Editor
library
using System.Collections.Generic;
using Grains.RazorDesigner.Projection.Appearance;
using Grains.RazorDesigner.Projection.Razor;
namespace Grains.RazorDesigner.Projection.Projectors;
[Projector( "Field" )]
public sealed class FieldProjector : IControlProjector
{
public string Kind => "Field";
public ProjectionResult Project( IReadOnlyNode node, IAppearance a, IPayload p, ProjectionContext ctx )
{
var scss = AppearanceScss.Emit(
a,
isRoot: node.ClassName == Document.DesignerDocument.RootClassName,
isContainer: true,
childCount: node.Children.Count,
isLabel: false,
isCheckbox: false,
checkboxSize: default );
var nodeId = node.Id.ToString();
var ops = new PanelOp[]
{
new SetAttribute( "data-grd-node-id", nodeId ),
};
var razorAttrs = new[] { RazorEmit.Attr( "data-grd-node-id", nodeId ) };
return new ProjectionResult(
PanelOps: ops,
ScssLines: scss,
RazorAttributes: razorAttrs,
RazorInnerText: null );
}
}
Editor
library
namespace Grains.RazorDesigner.Projection.Razor;
public static class RazorEmit
{
public static string Attr( string name, string value ) => $"{name}=\"{Escape.Html( value )}\"";
}
Editor
library
using System;
namespace Grains.RazorDesigner.Projection.Tests;
public static class PanelOpExhaustivenessTest
{
public static (bool pass, string message) Run()
{
var ops = new PanelOp[]
{
new SetClass( "" ),
new SetStyle( "", "" ),
new SetAttribute( "", "" ),
new SetInnerText( "" ),
};
try
{
foreach ( var op in ops )
Applier.ApplyOpToScratch( op );
return (true, $"PanelOpExhaustivenessTest: {ops.Length} variants OK");
}
catch ( Exception e )
{
return (false, $"PanelOpExhaustivenessTest FAILED: {e.GetType().Name}: {e.Message}");
}
}
}
Editor
library
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using Grains.RazorDesigner.Document;
namespace Grains.RazorDesigner.Serialization.IR;
public static class IRWriter
{
private const string LogPrefix = "[Grains.RazorDesigner]";
// Empty collections reused for nodes that have no slots/children/metadata.
private static readonly IReadOnlyDictionary<string, object> _emptyMetadata = new Dictionary<string, object>();
private static readonly IReadOnlyList<IRNodeEnvelope> _emptyChildren = System.Array.Empty<IRNodeEnvelope>();
private static readonly IReadOnlyDictionary<string, IRNodeEnvelope> _emptySlots = new Dictionary<string, IRNodeEnvelope>();
public static string WriteDocument( DesignerDocument doc )
{
if ( doc is null )
throw new ArgumentNullException( nameof( doc ) );
Log.Info( $"{LogPrefix} IRWriter.WriteDocument: serialising document (root children: {doc.RootRecord.Children.Count})" );
var envelope = new IRDocumentEnvelope
{
Root = ToNode( doc.RootRecord ),
Wiring = doc.Wiring ?? Grains.RazorDesigner.Wiring.WiringEnvelope.Empty,
};
var json = JsonSerializer.Serialize( envelope, DesignerIRJson.Options );
// Normalise CRLF → LF (canonical form; .gitattributes also pins LF as a backstop).
if ( json.Contains( '\r' ) )
json = json.Replace( "\r\n", "\n" ).Replace( "\r", "\n" );
Log.Info( $"{LogPrefix} IRWriter.WriteDocument: OK ({json.Length} chars)" );
return json;
}
public static string CanonicalHash( string json )
{
if ( json is null )
throw new ArgumentNullException( nameof( json ) );
var bytes = Encoding.UTF8.GetBytes( json );
var hash = SHA256.HashData( bytes );
return Convert.ToHexString( hash ).ToLowerInvariant();
}
// Recursively converts a ControlRecord to its IRNodeEnvelope representation.
private static IRNodeEnvelope ToNode( ControlRecord r )
{
Dictionary<string, IRNodeEnvelope> slotDict = null;
List<IRNodeEnvelope> childList = null;
foreach ( var child in r.Children )
{
if ( child.IsSlot )
{
slotDict ??= new Dictionary<string, IRNodeEnvelope>();
slotDict[child.SlotName] = ToNode( child );
}
else
{
childList ??= new List<IRNodeEnvelope>();
childList.Add( ToNode( child ) );
}
}
return new IRNodeEnvelope
{
Id = r.Id,
Kind = r.Type,
ClassName = r.ClassName,
Appearance = r.Appearance,
Payload = r.Payload,
Slots = slotDict is not null
? (IReadOnlyDictionary<string, IRNodeEnvelope>)slotDict
: _emptySlots,
Children = childList is not null
? (IReadOnlyList<IRNodeEnvelope>)childList
: _emptyChildren,
States = r.StateRules.Count == 0
? null
: r.StateRules
.OrderBy( rule => rule, Comparer<StateRule>.Create( StateRule.CompareCanonical ) )
.Select( rule => new IRStateEnvelope
{
State = rule.State,
NthChildMode = rule.NthChildMode,
NthChildArg = rule.NthChildArg,
Delta = rule.Delta,
} )
.ToList(),
Bindings = r.Bindings.Count == 0
? System.Array.Empty<Grains.RazorDesigner.Wiring.Binding>()
: r.Bindings.ToArray(),
CustomStyles = r.CustomStyles.Count == 0
? null
: new Dictionary<string, string>( r.CustomStyles ),
};
}
}
Editor
library
using System;
using System.Text.Json.Serialization;
namespace Grains.RazorDesigner.Wiring;
[JsonPolymorphic( TypeDiscriminatorPropertyName = "$type" )]
[JsonDerivedType( typeof( SetAction ), "Set" )]
[JsonDerivedType( typeof( CallAction ), "Call" )]
[JsonDerivedType( typeof( IfAction ), "If" )]
[JsonDerivedType( typeof( StateHasChangedAction ), "StateHasChanged" )]
[JsonDerivedType( typeof( LogAction ), "Log" )]
[JsonDerivedType( typeof( ReturnAction ), "Return" )]
[JsonDerivedType( typeof( InlineAction ), "Inline" )]
public abstract record Action
{
public Guid Id { get; init; } = Guid.NewGuid();
}
Editor
library
namespace Grains.RazorDesigner.Wiring;
public sealed record InlineAction : Action
{
public string Code { get; init; } = "";
}
Editor
library
namespace Grains.RazorDesigner.Wiring;
// `Target = Value;` — assignment to a Symbol field.
public sealed record SetAction : Action
{
public TargetRef Target { get; init; }
public Expression Value { get; init; }
}
Editor
library
using System.Collections.Generic;
namespace Grains.RazorDesigner.Wiring;
public sealed record EventBinding : Binding
{
public string Event { get; init; } = "";
public IReadOnlyList<Action> Body { get; init; } = System.Array.Empty<Action>();
}
Editor
library
namespace Grains.RazorDesigner.Wiring;
public sealed record VisibleBinding : Binding
{
public Expression Condition { get; init; }
}
Editor
library
namespace Grains.RazorDesigner.Wiring;
public enum SymbolVisibility
{
Private,
Public,
Internal,
Protected,
}
Editor
library
using Editor;
using Grains.RazorDesigner.Document;
using Sandbox;
namespace Grains.RazorDesigner.Canvas;
// Same shape as ShaderGraph PreviewPanel: override PreFrame to advance the scene.
public class DesignerCanvas : SceneRenderingWidget
{
private const string LogPrefix = "[Grains.RazorDesigner]";
private readonly DesignerScene _designerScene;
public DesignerCanvas( Widget parent ) : base( parent )
{
Log.Info( $"{LogPrefix} DesignerCanvas ctor" );
_designerScene = new DesignerScene();
Scene = _designerScene.Scene;
Camera = _designerScene.Camera;
HorizontalSizeMode = SizeMode.Default | SizeMode.Expand;
VerticalSizeMode = SizeMode.Default | SizeMode.Expand;
// Without AcceptDrops, OnDragHover/OnDragDrop are never invoked.
AcceptDrops = true;
MouseTracking = true;
}
public DesignerScene DesignerScene => _designerScene;
// Assigned by DesignerWindow after construction (mirrors how _viewportFrame.Canvas is wired).
public OverlayController Overlay { get; set; }
public CanvasViewportFrame ViewportFrame { get; set; }
public event System.Action<Vector2, bool, Sandbox.KeyboardModifiers> CanvasClicked;
public event System.Action<Vector2, Sandbox.KeyboardModifiers> CanvasMoved;
public event System.Action<Vector2> CanvasReleased;
public event System.Action<Vector2> CanvasPanDragged; // delta, screen px
// Fires when the cursor leaves the canvas widget. Used to clear hover-pick state.
public event System.Action CanvasHoverEnded;
public event System.Action<ControlType, Vector2> RecordDropped;
// Mirrors RecordDropped but for saved palette templates.
public event System.Action<Grains.RazorDesigner.Templates.PaletteTemplate, Vector2> TemplateDropped;
private bool _middlePanning;
private Vector2 _lastPanScreen;
private const bool ProbeFrameCost = false;
private const int ProbeWindow = 120;
private readonly System.Diagnostics.Stopwatch _probeSw = new();
private double _probeTickMs;
private double _probeUpdateMs;
private int _probeFrames;
public override void OnDragHover( DragEvent ev )
{
base.OnDragHover( ev );
if ( ev.Data.Object is ControlType || ev.Data.Object is Grains.RazorDesigner.Templates.PaletteTemplate )
{
ev.Action = DropAction.Copy;
}
}
public override void OnDragDrop( DragEvent ev )
{
base.OnDragDrop( ev );
if ( ev.Data.Object is ControlType type )
{
Log.Info( $"{LogPrefix} DesignerCanvas drop: {type} at widget ({ev.LocalPosition.x:F0}, {ev.LocalPosition.y:F0})" );
RecordDropped?.Invoke( type, ev.LocalPosition );
}
else if ( ev.Data.Object is Grains.RazorDesigner.Templates.PaletteTemplate template )
{
Log.Info( $"{LogPrefix} DesignerCanvas drop: template \"{template.Name}\" at widget ({ev.LocalPosition.x:F0}, {ev.LocalPosition.y:F0})" );
TemplateDropped?.Invoke( template, ev.LocalPosition );
}
}
protected override void OnMousePress( MouseEvent e )
{
if ( e.MiddleMouseButton )
{
// Swallow entirely — don't let base (SceneRenderingWidget) see the middle drag.
_middlePanning = true;
_lastPanScreen = e.ScreenPosition;
e.Accepted = true;
return;
}
base.OnMousePress( e );
if ( e.LeftMouseButton || e.RightMouseButton )
{
var pos = e.LocalPosition;
Log.Info( $"{LogPrefix} DesignerCanvas click at widget ({pos.x:F0}, {pos.y:F0}) right={e.RightMouseButton}" );
CanvasClicked?.Invoke( pos, e.RightMouseButton, e.KeyboardModifiers );
}
}
protected override void OnMouseMove( MouseEvent e )
{
if ( _middlePanning )
{
var s = e.ScreenPosition;
CanvasPanDragged?.Invoke( s - _lastPanScreen );
_lastPanScreen = s;
e.Accepted = true;
return;
}
base.OnMouseMove( e );
CanvasMoved?.Invoke( e.LocalPosition, e.KeyboardModifiers );
}
protected override void OnMouseReleased( MouseEvent e )
{
if ( _middlePanning )
{
_middlePanning = false;
e.Accepted = true;
return;
}
base.OnMouseReleased( e );
CanvasReleased?.Invoke( e.LocalPosition );
}
protected override void OnMouseLeave()
{
base.OnMouseLeave();
CanvasHoverEnded?.Invoke();
}
protected override void PreFrame()
{
base.PreFrame();
if ( !_designerScene.Scene.IsValid() )
return;
if ( ProbeFrameCost ) _probeSw.Restart();
using ( _designerScene.Scene.Push() )
{
// EditorTick (not GameTick): preview scene, no SceneNetworkUpdate / fixed physics.
_designerScene.Scene.EditorTick( RealTime.Now, RealTime.Delta );
}
if ( ProbeFrameCost ) { _probeTickMs += _probeSw.Elapsed.TotalMilliseconds; _probeSw.Restart(); }
// Layout in framebuffer space (Size * DpiScale); DesignerScene sets Scale = DpiScale so authoring stays in logical px.
_designerScene.Update( Size.x, Size.y, DpiScale );
Overlay?.Tick( _designerScene, DpiScale );
if ( ProbeFrameCost )
{
_probeUpdateMs += _probeSw.Elapsed.TotalMilliseconds;
if ( ++_probeFrames >= ProbeWindow )
{
Log.Info( $"{LogPrefix} probe: EditorTick avg {_probeTickMs / _probeFrames:F3}ms | Update avg {_probeUpdateMs / _probeFrames:F3}ms (over {_probeFrames} frames, canvas {Size.x:F0}x{Size.y:F0})" );
_probeTickMs = 0;
_probeUpdateMs = 0;
_probeFrames = 0;
}
}
}
protected override void OnKeyPress( KeyEvent e )
{
base.OnKeyPress( e );
if ( ViewportFrame is not { CanPanZoom: true } frame ) return;
var mods = e.KeyboardModifiers;
var ctrl = (mods & Sandbox.KeyboardModifiers.Ctrl) != 0;
var shift = (mods & Sandbox.KeyboardModifiers.Shift) != 0;
var alt = (mods & Sandbox.KeyboardModifiers.Alt) != 0;
if ( !ctrl || alt ) return; // every shortcut is Ctrl-based; Alt clears it.
switch ( e.Key )
{
case KeyCode.Num0:
if ( shift )
{
Log.Info( $"{LogPrefix} OnKeyPress Ctrl+Shift+0 → ZoomToSelection" );
ZoomToSelectionViaFrame( frame );
}
else
{
Log.Info( $"{LogPrefix} OnKeyPress Ctrl+0 → ApplyFit" );
frame.ApplyFit();
}
e.Accepted = true;
break;
case KeyCode.Num1:
if ( shift ) return;
Log.Info( $"{LogPrefix} OnKeyPress Ctrl+1 → ResetZoomOnly" );
frame.ResetZoomOnly();
e.Accepted = true;
break;
case KeyCode.Equal:
if ( shift ) return;
frame.ZoomBy( 1.25f );
e.Accepted = true;
break;
case KeyCode.Minus:
if ( shift ) return;
frame.ZoomBy( 1f / 1.25f );
e.Accepted = true;
break;
}
}
private void ZoomToSelectionViaFrame( CanvasViewportFrame frame )
{
var record = frame.Selection?.Selected;
var live = record?.LivePanel;
if ( live is null || !live.IsValid )
{
Log.Info( $"{LogPrefix} Ctrl+Shift+0: no valid selection" );
return;
}
frame.ApplyZoomToRect( live.Box.Rect, /* maxZoom */ 4f, /* padding */ 0.10f );
}
public override void OnDestroyed()
{
Log.Info( $"{LogPrefix} DesignerCanvas.OnDestroyed" );
base.OnDestroyed();
_designerScene.Dispose();
}
}
Debug: View Raw JSON Response
{
"TotalCount": 184,
"Files": [
{
"Ident": "sklmr.razordesigner",
"Path": "Editor/Document/Payloads/CheckboxPayload.cs",
"FileName": "CheckboxPayload.cs",
"PackageType": "library",
"CodeKind": "Editor",
"AssetVersionId": 306734,
"Code": "using System.Text.Json.Serialization;\r\n\r\nnamespace Grains.RazorDesigner.Document;\r\n\r\npublic sealed record CheckboxPayload : Payload\r\n{\r\n [JsonIgnore]\r\n public override ControlType Kind => ControlType.Checkbox;\r\n\r\n // Checkbox label text. Overrides Payload.Content (neutral default \"\").\r\n public override string Content { get; init; } = \"\";\r\n\r\n public override Length CheckboxSize { get; init; } = Length.Px( 16 );\r\n}\r\n"
},
{
"Ident": "sklmr.razordesigner",
"Path": "Editor/Inspector/EdgesControlWidget.cs",
"FileName": "EdgesControlWidget.cs",
"PackageType": "library",
"CodeKind": "Editor",
"AssetVersionId": 306734,
"Code": "using Editor;\r\nusing Grains.RazorDesigner.Document;\r\nusing Sandbox;\r\n\r\nnamespace Grains.RazorDesigner.Inspector;\r\n\r\n[CustomEditor( typeof( Edges ) )]\r\npublic sealed class EdgesControlWidget : ControlWidget\r\n{\r\n\tprivate const string LogPrefix = \"[Grains.RazorDesigner]\";\r\n\r\n\tpublic override bool SupportsMultiEdit => true;\r\n\r\n\tprivate readonly EdgesProxy _proxy;\r\n\tprivate readonly SerializedObject _proxySerialized;\r\n\t// Synchronous change events on both sides; without this guard SetValue would loop.\r\n\tprivate bool _syncing;\r\n\r\n\tprivate sealed class EdgesProxy\r\n\t{\r\n\t\tpublic Length Top { get; set; } = Length.Px( 0 );\r\n\t\tpublic Length Right { get; set; } = Length.Px( 0 );\r\n\t\tpublic Length Bottom { get; set; } = Length.Px( 0 );\r\n\t\tpublic Length Left { get; set; } = Length.Px( 0 );\r\n\t}\r\n\r\n\tpublic EdgesControlWidget( SerializedProperty property ) : base( property )\r\n\t{\r\n\t\tLog.Info( $\"{LogPrefix} EdgesControlWidget ctor for {property.Name}\" );\r\n\r\n\t\tLayout = Layout.Column();\r\n\t\tLayout.Spacing = 2;\r\n\r\n\t\t_proxy = new EdgesProxy();\r\n\t\t_proxySerialized = EditorTypeLibrary.GetSerializedObject( _proxy );\r\n\r\n\t\tvar topRow = Layout.Add( Layout.Row() );\r\n\t\ttopRow.Spacing = 2;\r\n\t\tAddSide( topRow, nameof( EdgesProxy.Top ), \"border_top\" );\r\n\t\tAddSide( topRow, nameof( EdgesProxy.Right ), \"border_right\" );\r\n\r\n\t\tvar bottomRow = Layout.Add( Layout.Row() );\r\n\t\tbottomRow.Spacing = 2;\r\n\t\tAddSide( bottomRow, nameof( EdgesProxy.Bottom ), \"border_bottom\" );\r\n\t\tAddSide( bottomRow, nameof( EdgesProxy.Left ), \"border_left\" );\r\n\r\n\t\t_proxySerialized.OnPropertyChanged += OnProxyChanged;\r\n\r\n\t\tSyncFromProperty();\r\n\t}\r\n\r\n\tprivate void AddSide( Layout row, string propName, string icon )\r\n\t{\r\n\t\tvar prop = _proxySerialized.GetProperty( propName );\r\n\t\tvar lengthWidget = new LengthControlWidget( prop, icon );\r\n\t\trow.Add( lengthWidget, 1 );\r\n\t}\r\n\r\n\tprotected override void PaintControl()\r\n\t{\r\n\t\t// nothing\r\n\t}\r\n\r\n\tprivate void SyncFromProperty()\r\n\t{\r\n\t\tif ( _syncing ) return;\r\n\t\t_syncing = true;\r\n\r\n\t\ttry\r\n\t\t{\r\n\t\t\tvar e = SerializedProperty.GetValue<Edges>( Edges.Zero );\r\n\r\n\t\t\t_proxySerialized.GetProperty( nameof( EdgesProxy.Top ) ).SetValue( e.Top );\r\n\t\t\t_proxySerialized.GetProperty( nameof( EdgesProxy.Right ) ).SetValue( e.Right );\r\n\t\t\t_proxySerialized.GetProperty( nameof( EdgesProxy.Bottom ) ).SetValue( e.Bottom );\r\n\t\t\t_proxySerialized.GetProperty( nameof( EdgesProxy.Left ) ).SetValue( e.Left );\r\n\t\t}\r\n\t\tfinally\r\n\t\t{\r\n\t\t\t_syncing = false;\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void OnProxyChanged( SerializedProperty property )\r\n\t{\r\n\t\tif ( _syncing ) return;\r\n\t\tif ( ReadOnly || !SerializedProperty.IsEditable )\r\n\t\t\treturn;\r\n\r\n\t\t_syncing = true;\r\n\t\ttry\r\n\t\t{\r\n\t\t\tvar newValue = new Edges( _proxy.Top, _proxy.Right, _proxy.Bottom, _proxy.Left );\r\n\r\n\t\t\tLog.Info( $\"{LogPrefix} EdgesControlWidget OnProxyChanged {newValue}\" );\r\n\r\n\t\t\tPropertyStartEdit();\r\n\t\t\tSerializedProperty.SetValue( newValue );\r\n\t\t\tSignalValuesChanged();\r\n\t\t\tPropertyFinishEdit();\r\n\t\t}\r\n\t\tfinally\r\n\t\t{\r\n\t\t\t_syncing = false;\r\n\t\t}\r\n\t}\r\n\r\n\tprotected override void OnValueChanged()\r\n\t{\r\n\t\tbase.OnValueChanged();\r\n\t\tSyncFromProperty();\r\n\t}\r\n}\r\n"
},
{
"Ident": "sklmr.razordesigner",
"Path": "Editor/Palette/PalettePanel.cs",
"FileName": "PalettePanel.cs",
"PackageType": "library",
"CodeKind": "Editor",
"AssetVersionId": 306734,
"Code": "using System;\r\nusing System.Collections.Generic;\r\nusing Editor;\r\nusing Grains.RazorDesigner.Common;\r\nusing Grains.RazorDesigner.Contracts;\r\nusing Grains.RazorDesigner.Document;\r\nusing Grains.RazorDesigner.Templates;\r\nusing Sandbox;\r\n\r\nnamespace Grains.RazorDesigner.Palette;\r\n\r\npublic class PalettePanel : Widget\r\n{\r\n\tprivate const string LogPrefix = \"[Grains.RazorDesigner]\";\r\n\tprivate const string CookiePrefix = \"razordesigner.palette.\";\r\n\r\n\t// Click-to-add target. Window decides where the new record goes (typically active selection or root).\r\n\tpublic event Action<ControlType> TypeAddRequested;\r\n\r\n\t// Click-to-add a saved template. Window decides where to insert.\r\n\tpublic event Action<PaletteTemplate> TemplateAddRequested;\r\n\r\n\tprivate readonly PaletteTemplateStore _templateStore = new();\r\n\tprivate CollapsibleSection _templatesSection;\r\n\tprivate WrapPanel _templatesWrap;\r\n\tpublic PaletteTemplateStore TemplateStore => _templateStore;\r\n\r\n\tpublic PalettePanel( Widget parent ) : base( parent )\r\n\t{\r\n\t\tLayout = Layout.Column();\r\n\t\tLayout.Margin = 0;\r\n\t\tLayout.Spacing = 0;\r\n\t\tMinimumWidth = 180;\r\n\t\tVerticalSizeMode = SizeMode.CanGrow;\r\n\r\n\t\tvar byCategory = new Dictionary<ControlCategory, List<ControlType>>();\r\n\t\tforeach ( ControlType type in Enum.GetValues( typeof( ControlType ) ) )\r\n\t\t{\r\n\t\t\tvar cat = ControlDefaults.For( type ).Category;\r\n\t\t\tif ( !byCategory.TryGetValue( cat, out var list ) )\r\n\t\t\t{\r\n\t\t\t\tlist = new List<ControlType>();\r\n\t\t\t\tbyCategory[cat] = list;\r\n\t\t\t}\r\n\t\t\tlist.Add( type );\r\n\t\t}\r\n\r\n\t\t// Templates section (top of palette). Hidden when store is empty; rebuilt on Changed.\r\n\t\t_templatesSection = new CollapsibleSection( this, \"Templates\", \"bookmark\" );\r\n\t\t_templatesWrap = new WrapPanel( null )\r\n\t\t{\r\n\t\t\tMinItemWidth = 92,\r\n\t\t\tItemHeight = (int)( Theme.RowHeight + 4 ),\r\n\t\t\tHSpacing = 4,\r\n\t\t\tVSpacing = 4,\r\n\t\t\tPaddingLeft = 4,\r\n\t\t\tPaddingTop = 4,\r\n\t\t\tPaddingRight = 14,\r\n\t\t\tPaddingBottom = 4,\r\n\t\t};\r\n\t\t_templatesSection.BodyLayout.Add( _templatesWrap );\r\n\r\n\t\tvar templatesCookie = $\"{CookiePrefix}templates.expanded\";\r\n\t\t_templatesSection.Expanded = EditorCookie.Get<bool>( templatesCookie, true );\r\n\t\t_templatesSection.ExpandedChanged += expanded =>\r\n\t\t{\r\n\t\t\tEditorCookie.Set( templatesCookie, expanded );\r\n\t\t\tLog.Info( $\"{LogPrefix} Palette Templates {(expanded ? \"expanded\" : \"collapsed\")}\" );\r\n\t\t};\r\n\r\n\t\tLayout.Add( _templatesSection );\r\n\r\n\t\t_templateStore.Changed += RebuildTemplatesSection;\r\n\t\t_templateStore.Scan(); // initial fill (also fires Changed and rebuilds the section)\r\n\r\n\t\tforeach ( ControlCategory cat in Enum.GetValues( typeof( ControlCategory ) ) )\r\n\t\t{\r\n\t\t\tif ( !byCategory.TryGetValue( cat, out var list ) ) continue;\r\n\r\n\t\t\tvar section = new CollapsibleSection(\r\n\t\t\t\tthis,\r\n\t\t\t\tControlDefaults.CategoryDisplayName( cat ),\r\n\t\t\t\tCategoryIcon( cat ) );\r\n\r\n\t\t\tvar wrap = new WrapPanel( null )\r\n\t\t\t{\r\n\t\t\t\tMinItemWidth = 92,\r\n\t\t\t\tItemHeight = (int)( Theme.RowHeight + 4 ),\r\n\t\t\t\tHSpacing = 4,\r\n\t\t\t\tVSpacing = 4,\r\n\t\t\t\tPaddingLeft = 4,\r\n\t\t\t\tPaddingTop = 4,\r\n\t\t\t\tPaddingRight = 14, // clear the ScrollArea's vertical scrollbar\r\n\t\t\t\tPaddingBottom = 4,\r\n\t\t\t};\r\n\t\t\tsection.BodyLayout.Add( wrap );\r\n\r\n\t\t\tforeach ( var t in list )\r\n\t\t\t\tnew PaletteTypeButton( wrap, this, t );\r\n\r\n\t\t\tvar cookieKey = $\"{CookiePrefix}{cat}.expanded\";\r\n\t\t\tsection.Expanded = EditorCookie.Get<bool>( cookieKey, DefaultExpanded( cat ) );\r\n\t\t\tsection.ExpandedChanged += expanded =>\r\n\t\t\t{\r\n\t\t\t\tEditorCookie.Set( cookieKey, expanded );\r\n\t\t\t\tLog.Info( $\"{LogPrefix} Palette category {cat} {(expanded ? \"expanded\" : \"collapsed\")}\" );\r\n\t\t\t};\r\n\r\n\t\t\tLayout.Add( section );\r\n\t\t}\r\n\r\n\t\tLayout.AddStretchCell();\r\n\r\n\t\tLog.Info( $\"{LogPrefix} PalettePanel ctor (icon grid, {byCategory.Count} categories)\" );\r\n\t}\r\n\r\n\tinternal void NotifyTypeClicked( ControlType type )\r\n\t{\r\n\t\tLog.Info( $\"{LogPrefix} PalettePanel.NotifyTypeClicked: {type}\" );\r\n\t\tTypeAddRequested?.Invoke( type );\r\n\t}\r\n\r\n\tinternal void NotifyTemplateClicked( PaletteTemplate template )\r\n\t{\r\n\t\tLog.Info( $\"{LogPrefix} PalettePanel.NotifyTemplateClicked: \\\"{template.Name}\\\"\" );\r\n\t\tTemplateAddRequested?.Invoke( template );\r\n\t}\r\n\r\n\tinternal void RequestTemplateDelete( PaletteTemplate template )\r\n\t{\r\n\t\tvar dialog = new Editor.Dialog( this );\r\n\t\tdialog.Window.WindowTitle = \"Delete template\";\r\n\t\tdialog.Window.SetWindowIcon( \"delete\" );\r\n\t\tdialog.Window.SetModal( true, true );\r\n\t\tdialog.Window.MinimumWidth = 320;\r\n\r\n\t\tdialog.Layout = Layout.Column();\r\n\t\tdialog.Layout.Margin = 16;\r\n\t\tdialog.Layout.Spacing = 10;\r\n\r\n\t\tdialog.Layout.Add( new Editor.Label( dialog )\r\n\t\t{\r\n\t\t\tText = $\"Delete template \\\"{template.Name}\\\"?\",\r\n\t\t} );\r\n\r\n\t\tvar hint = new Editor.Label( dialog )\r\n\t\t{\r\n\t\t\tText = \"Already-instantiated copies in open documents are unaffected.\",\r\n\t\t};\r\n\t\thint.SetStyles( \"color: #888; font-size: 11px;\" );\r\n\t\tdialog.Layout.Add( hint );\r\n\r\n\t\tvar buttonRow = dialog.Layout.Add( Layout.Row() );\r\n\t\tbuttonRow.Spacing = 6;\r\n\t\tbuttonRow.AddStretchCell();\r\n\r\n\t\tvar cancel = new Editor.Button( dialog ) { Text = \"Cancel\", MinimumWidth = 72 };\r\n\t\tcancel.MouseLeftPress += () => dialog.Close();\r\n\t\tbuttonRow.Add( cancel );\r\n\r\n\t\tvar del = new Editor.Button( dialog ) { Text = \"Delete\", MinimumWidth = 72 };\r\n\t\tdel.SetStyles( \"color: #e07070;\" );\r\n\t\tdel.MouseLeftPress += () =>\r\n\t\t{\r\n\t\t\tLog.Info( $\"{LogPrefix} Palette delete confirmed: \\\"{template.Name}\\\"\" );\r\n\t\t\t_templateStore.Delete( template );\r\n\t\t\tdialog.Close();\r\n\t\t};\r\n\t\tbuttonRow.Add( del );\r\n\r\n\t\tdialog.Window.AdjustSize();\r\n\t\tdialog.Show();\r\n\t}\r\n\r\n\tprivate void RebuildTemplatesSection()\r\n\t{\r\n\t\tvar templates = _templateStore.All;\r\n\r\n\t\t// Hide the entire section (header + body) when there are no templates.\r\n\t\t_templatesSection.Visible = templates.Count > 0;\r\n\r\n\t\tusing ( Editor.SuspendUpdates.For( _templatesWrap ) )\r\n\t\t{\r\n\t\t\t_templatesWrap.DestroyChildren();\r\n\t\t\tforeach ( var t in templates )\r\n\t\t\t\tnew PaletteTemplateButton( _templatesWrap, this, t );\r\n\t\t}\r\n\r\n\t\t_templatesWrap.Relayout();\r\n\t\t_templatesWrap.UpdateGeometry();\r\n\t\t_templatesSection.UpdateGeometry();\r\n\t\tUpdateGeometry();\r\n\r\n\t\tLog.Info( $\"{LogPrefix} PalettePanel.RebuildTemplatesSection: {templates.Count} tile(s), section.Visible={_templatesSection.Visible}\" );\r\n\t}\r\n\r\n\tprivate static bool DefaultExpanded( ControlCategory cat ) =>\r\n\t\tcat is ControlCategory.Layout or ControlCategory.Display or ControlCategory.Input;\r\n\r\n\tprivate static string CategoryIcon( ControlCategory cat ) => cat switch\r\n\t{\r\n\t\tControlCategory.Layout => \"view_quilt\",\r\n\t\tControlCategory.Display => \"visibility\",\r\n\t\tControlCategory.Input => \"edit\",\r\n\t\tControlCategory.Form => \"list_alt\",\r\n\t\t_ => \"category\",\r\n\t};\r\n\r\n\tprivate sealed class PaletteTypeButton : Widget\r\n\t{\r\n\t\tprivate readonly PalettePanel _owner;\r\n\t\tprivate readonly ControlType _type;\r\n\t\t// InspectorIcon comes from the contract (engine-fidelity); drag defaults from ControlDefaults.\r\n\t\tprivate readonly string _icon;\r\n\r\n\t\tpublic PaletteTypeButton( Widget parent, PalettePanel owner, ControlType type ) : base( parent )\r\n\t\t{\r\n\t\t\t_owner = owner;\r\n\t\t\t_type = type;\r\n\t\t\t_icon = ContractScanner.Table.Get( type ).InspectorIcon;\r\n\r\n\t\t\tToolTip = type.ToString();\r\n\t\t\tCursor = CursorShape.Finger;\r\n\t\t\tMouseTracking = true;\r\n\t\t\tIsDraggable = true;\r\n\t\t}\r\n\r\n\t\tprotected override void OnPaint()\r\n\t\t{\r\n\t\t\tvar rect = LocalRect.Shrink( 1 );\r\n\t\t\tPaint.Antialiasing = true;\r\n\t\t\tPaint.TextAntialiasing = true;\r\n\r\n\t\t\tvar tint = ControlPresentation.IconTint( _type );\r\n\t\t\tvar fillAlpha = Paint.HasMouseOver ? 0.35f : 0.15f;\r\n\t\t\tvar borderAlpha = Paint.HasMouseOver ? 0.55f : 0.25f;\r\n\t\t\tPaint.SetBrush( tint.WithAlpha( fillAlpha ) );\r\n\t\t\tPaint.SetPen( tint.WithAlpha( borderAlpha ) );\r\n\t\t\tPaint.DrawRect( rect, 3 );\r\n\r\n\t\t\tvar hoverOpacity = Paint.HasMouseOver ? 1f : 0.85f;\r\n\t\t\tvar iconRect = new Rect( rect.Left + 4, rect.Top, 20, rect.Height );\r\n\t\t\tPaint.SetPen( tint.WithAlphaMultiplied( hoverOpacity ) );\r\n\t\t\tPaint.DrawIcon( iconRect, _icon, 16, TextFlag.Center );\r\n\r\n\t\t\tvar textRect = rect;\r\n\t\t\ttextRect.Left = iconRect.Right + 2;\r\n\t\t\ttextRect.Right -= 4;\r\n\t\t\tPaint.SetPen( Theme.Text.WithAlphaMultiplied( hoverOpacity ) );\r\n\t\t\tPaint.SetDefaultFont();\r\n\t\t\tPaint.DrawText( textRect, _type.ToString(), TextFlag.LeftCenter );\r\n\t\t}\r\n\r\n\t\tprotected override void OnMouseClick( MouseEvent e )\r\n\t\t{\r\n\t\t\tbase.OnMouseClick( e );\r\n\t\t\tif ( e.LeftMouseButton )\r\n\t\t\t\t_owner.NotifyTypeClicked( _type );\r\n\t\t}\r\n\r\n\t\tprotected override void OnDragStart()\r\n\t\t{\r\n\t\t\tbase.OnDragStart();\r\n\r\n\t\t\tvar drag = new Drag( this );\r\n\t\t\tdrag.Data.Object = _type;\r\n\t\t\tdrag.Data.Text = $\"palette:{_type}\";\r\n\t\t\tdrag.Execute();\r\n\r\n\t\t\tLog.Info( $\"{LogPrefix} PaletteTypeButton.OnDragStart: {_type}\" );\r\n\t\t}\r\n\t}\r\n\r\n\tprivate sealed class PaletteTemplateButton : Widget\r\n\t{\r\n\t\tprivate readonly PalettePanel _owner;\r\n\t\tprivate readonly PaletteTemplate _template;\r\n\r\n\t\tpublic PaletteTemplateButton( Widget parent, PalettePanel owner, PaletteTemplate template ) : base( parent )\r\n\t\t{\r\n\t\t\t_owner = owner;\r\n\t\t\t_template = template;\r\n\r\n\t\t\tToolTip = template.Name;\r\n\t\t\tCursor = CursorShape.Finger;\r\n\t\t\tMouseTracking = true;\r\n\t\t\tIsDraggable = true;\r\n\t\t}\r\n\r\n\t\tprotected override void OnPaint()\r\n\t\t{\r\n\t\t\tvar rect = LocalRect.Shrink( 1 );\r\n\t\t\tPaint.Antialiasing = true;\r\n\t\t\tPaint.TextAntialiasing = true;\r\n\r\n\t\t\tvar tint = ControlPresentation.TemplateTint;\r\n\t\t\tvar fillAlpha = Paint.HasMouseOver ? 0.18f : 0.08f;\r\n\t\t\tvar borderAlpha = Paint.HasMouseOver ? 0.55f : 0.25f;\r\n\t\t\tPaint.SetBrush( tint.WithAlpha( fillAlpha ) );\r\n\t\t\tPaint.SetPen( tint.WithAlpha( borderAlpha ) );\r\n\t\t\tPaint.DrawRect( rect, 3 );\r\n\r\n\t\t\tvar hoverOpacity = Paint.HasMouseOver ? 1f : 0.85f;\r\n\t\t\tvar iconRect = new Rect( rect.Left + 4, rect.Top, 20, rect.Height );\r\n\t\t\tvar icon = string.IsNullOrEmpty( _template.IconName ) ? \"bookmark\" : _template.IconName;\r\n\t\t\tPaint.SetPen( tint.WithAlphaMultiplied( hoverOpacity ) );\r\n\t\t\tPaint.DrawIcon( iconRect, icon, 16, TextFlag.Center );\r\n\r\n\t\t\tvar textRect = rect;\r\n\t\t\ttextRect.Left = iconRect.Right + 2;\r\n\t\t\ttextRect.Right -= 4;\r\n\t\t\tPaint.SetPen( Theme.Text.WithAlphaMultiplied( hoverOpacity ) );\r\n\t\t\tPaint.SetDefaultFont();\r\n\t\t\tPaint.DrawText( textRect, _template.Name, TextFlag.LeftCenter );\r\n\t\t}\r\n\r\n\t\tprotected override void OnMouseClick( MouseEvent e )\r\n\t\t{\r\n\t\t\tbase.OnMouseClick( e );\r\n\t\t\tif ( e.LeftMouseButton )\r\n\t\t\t\t_owner.NotifyTemplateClicked( _template );\r\n\t\t}\r\n\r\n\t\tprotected override void OnContextMenu( ContextMenuEvent e )\r\n\t\t{\r\n\t\t\tbase.OnContextMenu( e );\r\n\t\t\tvar menu = new Menu( this );\r\n\t\t\tmenu.AddOption( \"Delete\u2026\", \"delete\", () => _owner.RequestTemplateDelete( _template ) );\r\n\t\t\tmenu.OpenAtCursor();\r\n\t\t\te.Accepted = true;\r\n\t\t}\r\n\r\n\t\tprotected override void OnDragStart()\r\n\t\t{\r\n\t\t\tbase.OnDragStart();\r\n\t\t\tvar drag = new Drag( this );\r\n\t\t\tdrag.Data.Object = _template;\r\n\t\t\tdrag.Data.Text = $\"template:{_template.Name}\";\r\n\t\t\tdrag.Execute();\r\n\t\t\tLog.Info( $\"{LogPrefix} PaletteTemplateButton.OnDragStart: \\\"{_template.Name}\\\"\" );\r\n\t\t}\r\n\t}\r\n}\r\n"
},
{
"Ident": "sklmr.razordesigner",
"Path": "Editor/Projection/CSharp/CSharpOp.cs",
"FileName": "CSharpOp.cs",
"PackageType": "library",
"CodeKind": "Editor",
"AssetVersionId": 306734,
"Code": "namespace Grains.RazorDesigner.Projection.CSharp;\r\n\r\npublic abstract record CSharpOp;\r\n\r\n// File-level scaffold\r\npublic sealed record HeaderBanner( string ClassName, string Namespace ) : CSharpOp;\r\npublic sealed record UsingDirective( string Namespace ) : CSharpOp;\r\npublic sealed record NamespaceOpen( string Namespace ) : CSharpOp;\r\npublic sealed record ClassOpen( string ClassName, string BaseClass ) : CSharpOp;\r\npublic sealed record ClassClose() : CSharpOp;\r\n\r\npublic sealed record FieldDecl(\r\n string Visibility, string Type, string Name, string InitialExpr,\r\n bool IsParameter, bool IsProperty = false ) : CSharpOp;\r\n\r\npublic sealed record MethodOpen(\r\n string Visibility, bool IsOverride, bool IsAsync,\r\n string ReturnType, string Name, string ParameterList ) : CSharpOp;\r\n\r\npublic sealed record MethodClose() : CSharpOp;\r\n\r\n// Body-level\r\npublic sealed record Statement( string Code ) : CSharpOp; // single `;`-terminated line\r\npublic sealed record BlockOpen( string Header ) : CSharpOp; // e.g. `if ( <cond> )` \u2014 applier writes \"<header> {\\n\" and indents\r\npublic sealed record BlockClose() : CSharpOp;\r\npublic sealed record BlankLine() : CSharpOp;\r\npublic sealed record Comment( string Text ) : CSharpOp; // `// <text>`\r\n"
},
{
"Ident": "sklmr.razordesigner",
"Path": "Editor/Projection/CSharp/CSharpProjector.cs",
"FileName": "CSharpProjector.cs",
"PackageType": "library",
"CodeKind": "Editor",
"AssetVersionId": 306734,
"Code": "using System.Collections.Generic;\r\nusing Grains.RazorDesigner.Projection.CSharp.Projectors;\r\n\r\nnamespace Grains.RazorDesigner.Projection.CSharp;\r\n\r\npublic static class CSharpProjector\r\n{\r\n public static CSharpResult Project(\r\n IReadOnlyWiring wiring,\r\n bool documentHasAnyBindings )\r\n {\r\n if ( wiring.Symbols.Count == 0 && !documentHasAnyBindings )\r\n return new CSharpResult( System.Array.Empty<CSharpOp>(), null );\r\n\r\n var ctx = new CSharpProjectorContext( wiring );\r\n var ops = new List<CSharpOp>( 64 );\r\n\r\n ops.Add( new HeaderBanner( wiring.ClassName, wiring.Namespace ) );\r\n ops.Add( new UsingDirective( \"Sandbox\" ) );\r\n ops.Add( new UsingDirective( \"Sandbox.UI\" ) );\r\n foreach ( var u in wiring.Usings )\r\n ops.Add( new UsingDirective( u ) );\r\n ops.Add( new NamespaceOpen( wiring.Namespace ) );\r\n ops.Add( new ClassOpen( wiring.ClassName, wiring.BaseClass ) );\r\n\r\n // Step 3: body. SymbolProjector handles grouping + sorting + per-kind dispatch.\r\n SymbolProjector.EmitAll( wiring, ops, ctx );\r\n\r\n ops.Add( new ClassClose() );\r\n\r\n var source = CSharpApplier.Apply( ops ).Replace( \"\\r\\n\", \"\\n\" );\r\n\r\n return new CSharpResult( ops, source );\r\n }\r\n}\r\n"
},
{
"Ident": "sklmr.razordesigner",
"Path": "Editor/Projection/CSharp/Projectors/ParameterSymbolProjector.cs",
"FileName": "ParameterSymbolProjector.cs",
"PackageType": "library",
"CodeKind": "Editor",
"AssetVersionId": 306734,
"Code": "using System.Collections.Generic;\r\nusing Grains.RazorDesigner.Wiring;\r\n\r\nnamespace Grains.RazorDesigner.Projection.CSharp.Projectors;\r\n\r\npublic static class ParameterSymbolProjector\r\n{\r\n public static void Emit( ParameterSymbol s, List<CSharpOp> ops, CSharpProjectorContext ctx )\r\n {\r\n var initial = s.Initial is null ? \"default\" : ExpressionEmitter.Emit( s.Initial, ctx );\r\n ops.Add( new FieldDecl(\r\n Visibility: \"public\", Type: s.Type, Name: s.Name,\r\n InitialExpr: initial, IsParameter: true ) );\r\n }\r\n}\r\n"
},
{
"Ident": "sklmr.razordesigner",
"Path": "Editor/Projection/Escape.cs",
"FileName": "Escape.cs",
"PackageType": "library",
"CodeKind": "Editor",
"AssetVersionId": 306734,
"Code": "namespace Grains.RazorDesigner.Projection;\r\n\r\npublic static class Escape\r\n{\r\n public static string Html( string s )\r\n {\r\n if ( string.IsNullOrEmpty( s ) ) return \"\";\r\n return s\r\n .Replace( \"&\", \"&\" )\r\n .Replace( \"<\", \"<\" )\r\n .Replace( \">\", \">\" )\r\n .Replace( \"\\\"\", \""\" );\r\n }\r\n}\r\n"
},
{
"Ident": "sklmr.razordesigner",
"Path": "Editor/Projection/IReadOnlyNode.cs",
"FileName": "IReadOnlyNode.cs",
"PackageType": "library",
"CodeKind": "Editor",
"AssetVersionId": 306734,
"Code": "using System;\r\nusing System.Collections.Generic;\r\nusing Sandbox; // Color\r\n\r\nnamespace Grains.RazorDesigner.Projection;\r\n\r\npublic interface IReadOnlyStateRule\r\n{\r\n Document.PseudoKind State { get; }\r\n Document.NthChildMode NthChildMode { get; }\r\n int NthChildArg { get; }\r\n IAppearance Delta { get; }\r\n\r\n public static int CompareCanonical( IReadOnlyStateRule a, IReadOnlyStateRule b )\r\n {\r\n int c = ((int)a.State).CompareTo( (int)b.State );\r\n if ( c != 0 ) return c;\r\n c = ((int)a.NthChildMode).CompareTo( (int)b.NthChildMode );\r\n if ( c != 0 ) return c;\r\n return a.NthChildArg.CompareTo( b.NthChildArg );\r\n }\r\n}\r\n\r\npublic interface IReadOnlyNode\r\n{\r\n Guid Id { get; }\r\n string Kind { get; } // == ControlType.ToString()\r\n string ClassName { get; }\r\n IAppearance Appearance { get; }\r\n IPayload Payload { get; }\r\n IReadOnlyList<IReadOnlyNode> Children { get; } // non-slot children\r\n IReadOnlyDictionary<string, IReadOnlyList<IReadOnlyNode>> Slots { get; } // slot-name -> slot children (only SplitContainer populates)\r\n IReadOnlyList<IReadOnlyStateRule> StateRules { get; } // per-state style deltas; canonical order not guaranteed here (the Applier sorts)\r\n}\r\n\r\npublic interface IAppearance\r\n{\r\n // Layout\r\n Document.Length Width { get; }\r\n Document.Length Height { get; }\r\n\r\n // Flex container\r\n Document.FlexDirection Direction { get; }\r\n Document.JustifyContent Justify { get; }\r\n Document.AlignItems Align { get; }\r\n float Gap { get; }\r\n Document.Edges Padding { get; }\r\n Document.FlexWrap Wrap { get; }\r\n\r\n // Positioning (grd-7t2z)\r\n Document.PositionKind Position { get; }\r\n Document.Length Top { get; }\r\n Document.Length Left { get; }\r\n Document.Length Right { get; }\r\n Document.Length Bottom { get; }\r\n\r\n // Flex self\r\n float FlexGrow { get; }\r\n float FlexShrink { get; }\r\n Document.Length FlexBasis { get; }\r\n Document.AlignSelfKind AlignSelf { get; }\r\n\r\n // Typography + OverrideTypography\r\n bool OverrideTypography { get; }\r\n string FontFamily { get; }\r\n Document.Length FontSize { get; }\r\n int FontWeight { get; }\r\n Color Color { get; }\r\n Document.TextAlignment TextAlign { get; }\r\n bool FontStyleItalic { get; }\r\n Document.TextTransformKind TextTransform { get; }\r\n Document.Length LetterSpacing { get; }\r\n Document.Length LineHeight { get; }\r\n\r\n // Background + OverrideBackground\r\n bool OverrideBackground { get; }\r\n Color BackgroundColor { get; }\r\n string BackgroundImage { get; }\r\n string BackgroundSize { get; }\r\n string BackgroundPosition { get; }\r\n string BackgroundRepeat { get; }\r\n\r\n // Border + OverrideBorder\r\n bool OverrideBorder { get; }\r\n Document.Length BorderRadius { get; }\r\n Color BorderColor { get; }\r\n Document.Length BorderWidth { get; }\r\n\r\n // Effects + OverrideEffects\r\n bool OverrideEffects { get; }\r\n Document.Length BoxShadowX { get; }\r\n Document.Length BoxShadowY { get; }\r\n Document.Length BoxShadowBlur { get; }\r\n Color BoxShadowColor { get; }\r\n bool BoxShadowInset { get; }\r\n float Opacity { get; }\r\n\r\n // Constraints + OverrideConstraints\r\n bool OverrideConstraints { get; }\r\n Document.Edges Margin { get; }\r\n Document.Length MinWidth { get; }\r\n Document.Length MaxWidth { get; }\r\n Document.Length MinHeight { get; }\r\n Document.Length MaxHeight { get; }\r\n\r\n // Interaction + OverrideInteraction\r\n bool OverrideInteraction { get; }\r\n Document.CursorKind Cursor { get; }\r\n Document.OverflowKind Overflow { get; }\r\n int ZIndex { get; }\r\n bool PointerEvents { get; }\r\n}\r\n\r\npublic interface IPayload\r\n{\r\n string Content { get; } // Label/Button text; Checkbox label\r\n string Placeholder { get; } // TextEntry\r\n string Source { get; } // Image src\r\n string IconName { get; } // IconPanel glyph\r\n Document.Length CheckboxSize { get; } // Checkbox box size\r\n}\r\n"
},
{
"Ident": "sklmr.razordesigner",
"Path": "Editor/Projection/Projectors/ButtonProjector.cs",
"FileName": "ButtonProjector.cs",
"PackageType": "library",
"CodeKind": "Editor",
"AssetVersionId": 306734,
"Code": "using System.Collections.Generic;\r\nusing Grains.RazorDesigner.Projection.Appearance;\r\nusing Grains.RazorDesigner.Projection.Razor;\r\n\r\nnamespace Grains.RazorDesigner.Projection.Projectors;\r\n\r\n[Projector( \"Button\" )]\r\npublic sealed class ButtonProjector : IControlProjector\r\n{\r\n public string Kind => \"Button\";\r\n\r\n public ProjectionResult Project( IReadOnlyNode node, IAppearance a, IPayload p, ProjectionContext ctx )\r\n {\r\n var scss = AppearanceScss.Emit(\r\n a,\r\n isRoot: node.ClassName == Document.DesignerDocument.RootClassName,\r\n isContainer: false,\r\n childCount: 0,\r\n isLabel: false,\r\n isCheckbox: false,\r\n checkboxSize: default );\r\n\r\n var nodeId = node.Id.ToString();\r\n var ops = new PanelOp[]\r\n {\r\n new SetAttribute( \"data-grd-node-id\", nodeId ),\r\n new SetInnerText( p.Content ?? \"\" ),\r\n };\r\n\r\n var razorAttrs = new[] { RazorEmit.Attr( \"data-grd-node-id\", nodeId ) };\r\n\r\n return new ProjectionResult(\r\n PanelOps: ops,\r\n ScssLines: scss,\r\n RazorAttributes: razorAttrs,\r\n RazorInnerText: Escape.Html( p.Content ?? \"\" ) );\r\n }\r\n}\r\n"
},
{
"Ident": "sklmr.razordesigner",
"Path": "Editor/Projection/Projectors/FieldProjector.cs",
"FileName": "FieldProjector.cs",
"PackageType": "library",
"CodeKind": "Editor",
"AssetVersionId": 306734,
"Code": "using System.Collections.Generic;\r\nusing Grains.RazorDesigner.Projection.Appearance;\r\nusing Grains.RazorDesigner.Projection.Razor;\r\n\r\nnamespace Grains.RazorDesigner.Projection.Projectors;\r\n\r\n[Projector( \"Field\" )]\r\npublic sealed class FieldProjector : IControlProjector\r\n{\r\n public string Kind => \"Field\";\r\n\r\n public ProjectionResult Project( IReadOnlyNode node, IAppearance a, IPayload p, ProjectionContext ctx )\r\n {\r\n var scss = AppearanceScss.Emit(\r\n a,\r\n isRoot: node.ClassName == Document.DesignerDocument.RootClassName,\r\n isContainer: true,\r\n childCount: node.Children.Count,\r\n isLabel: false,\r\n isCheckbox: false,\r\n checkboxSize: default );\r\n\r\n var nodeId = node.Id.ToString();\r\n var ops = new PanelOp[]\r\n {\r\n new SetAttribute( \"data-grd-node-id\", nodeId ),\r\n };\r\n\r\n var razorAttrs = new[] { RazorEmit.Attr( \"data-grd-node-id\", nodeId ) };\r\n\r\n return new ProjectionResult(\r\n PanelOps: ops,\r\n ScssLines: scss,\r\n RazorAttributes: razorAttrs,\r\n RazorInnerText: null );\r\n }\r\n}\r\n"
},
{
"Ident": "sklmr.razordesigner",
"Path": "Editor/Projection/Razor/RazorEmit.cs",
"FileName": "RazorEmit.cs",
"PackageType": "library",
"CodeKind": "Editor",
"AssetVersionId": 306734,
"Code": "namespace Grains.RazorDesigner.Projection.Razor;\r\n\r\npublic static class RazorEmit\r\n{\r\n public static string Attr( string name, string value ) => $\"{name}=\\\"{Escape.Html( value )}\\\"\";\r\n}\r\n"
},
{
"Ident": "sklmr.razordesigner",
"Path": "Editor/Projection/Tests/PanelOpExhaustivenessTest.cs",
"FileName": "PanelOpExhaustivenessTest.cs",
"PackageType": "library",
"CodeKind": "Editor",
"AssetVersionId": 306734,
"Code": "using System;\r\n\r\nnamespace Grains.RazorDesigner.Projection.Tests;\r\n\r\npublic static class PanelOpExhaustivenessTest\r\n{\r\n public static (bool pass, string message) Run()\r\n {\r\n var ops = new PanelOp[]\r\n {\r\n new SetClass( \"\" ),\r\n new SetStyle( \"\", \"\" ),\r\n new SetAttribute( \"\", \"\" ),\r\n new SetInnerText( \"\" ),\r\n };\r\n try\r\n {\r\n foreach ( var op in ops )\r\n Applier.ApplyOpToScratch( op );\r\n return (true, $\"PanelOpExhaustivenessTest: {ops.Length} variants OK\");\r\n }\r\n catch ( Exception e )\r\n {\r\n return (false, $\"PanelOpExhaustivenessTest FAILED: {e.GetType().Name}: {e.Message}\");\r\n }\r\n }\r\n}\r\n"
},
{
"Ident": "sklmr.razordesigner",
"Path": "Editor/Serialization/IR/IRWriter.cs",
"FileName": "IRWriter.cs",
"PackageType": "library",
"CodeKind": "Editor",
"AssetVersionId": 306734,
"Code": "using System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing System.Security.Cryptography;\r\nusing System.Text;\r\nusing System.Text.Json;\r\nusing Grains.RazorDesigner.Document;\r\n\r\nnamespace Grains.RazorDesigner.Serialization.IR;\r\n\r\npublic static class IRWriter\r\n{\r\n\tprivate const string LogPrefix = \"[Grains.RazorDesigner]\";\r\n\r\n\t// Empty collections reused for nodes that have no slots/children/metadata.\r\n\tprivate static readonly IReadOnlyDictionary<string, object> _emptyMetadata = new Dictionary<string, object>();\r\n\tprivate static readonly IReadOnlyList<IRNodeEnvelope> _emptyChildren = System.Array.Empty<IRNodeEnvelope>();\r\n\tprivate static readonly IReadOnlyDictionary<string, IRNodeEnvelope> _emptySlots = new Dictionary<string, IRNodeEnvelope>();\r\n\r\n\tpublic static string WriteDocument( DesignerDocument doc )\r\n\t{\r\n\t\tif ( doc is null )\r\n\t\t\tthrow new ArgumentNullException( nameof( doc ) );\r\n\r\n\t\tLog.Info( $\"{LogPrefix} IRWriter.WriteDocument: serialising document (root children: {doc.RootRecord.Children.Count})\" );\r\n\r\n\t\tvar envelope = new IRDocumentEnvelope\r\n\t\t{\r\n\t\t\tRoot = ToNode( doc.RootRecord ),\r\n\t\t\tWiring = doc.Wiring ?? Grains.RazorDesigner.Wiring.WiringEnvelope.Empty,\r\n\t\t};\r\n\r\n\t\tvar json = JsonSerializer.Serialize( envelope, DesignerIRJson.Options );\r\n\r\n\t\t// Normalise CRLF \u2192 LF (canonical form; .gitattributes also pins LF as a backstop).\r\n\t\tif ( json.Contains( '\\r' ) )\r\n\t\t\tjson = json.Replace( \"\\r\\n\", \"\\n\" ).Replace( \"\\r\", \"\\n\" );\r\n\r\n\t\tLog.Info( $\"{LogPrefix} IRWriter.WriteDocument: OK ({json.Length} chars)\" );\r\n\t\treturn json;\r\n\t}\r\n\r\n\tpublic static string CanonicalHash( string json )\r\n\t{\r\n\t\tif ( json is null )\r\n\t\t\tthrow new ArgumentNullException( nameof( json ) );\r\n\r\n\t\tvar bytes = Encoding.UTF8.GetBytes( json );\r\n\t\tvar hash = SHA256.HashData( bytes );\r\n\t\treturn Convert.ToHexString( hash ).ToLowerInvariant();\r\n\t}\r\n\r\n\t// Recursively converts a ControlRecord to its IRNodeEnvelope representation.\r\n\tprivate static IRNodeEnvelope ToNode( ControlRecord r )\r\n\t{\r\n\t\tDictionary<string, IRNodeEnvelope> slotDict = null;\r\n\t\tList<IRNodeEnvelope> childList = null;\r\n\r\n\t\tforeach ( var child in r.Children )\r\n\t\t{\r\n\t\t\tif ( child.IsSlot )\r\n\t\t\t{\r\n\t\t\t\tslotDict ??= new Dictionary<string, IRNodeEnvelope>();\r\n\t\t\t\tslotDict[child.SlotName] = ToNode( child );\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tchildList ??= new List<IRNodeEnvelope>();\r\n\t\t\t\tchildList.Add( ToNode( child ) );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn new IRNodeEnvelope\r\n\t\t{\r\n\t\t\tId = r.Id,\r\n\t\t\tKind = r.Type,\r\n\t\t\tClassName = r.ClassName,\r\n\t\t\tAppearance = r.Appearance,\r\n\t\t\tPayload = r.Payload,\r\n\t\t\tSlots = slotDict is not null\r\n\t\t\t\t? (IReadOnlyDictionary<string, IRNodeEnvelope>)slotDict\r\n\t\t\t\t: _emptySlots,\r\n\t\t\tChildren = childList is not null\r\n\t\t\t\t? (IReadOnlyList<IRNodeEnvelope>)childList\r\n\t\t\t\t: _emptyChildren,\r\n\t\t\tStates = r.StateRules.Count == 0\r\n\t\t\t\t? null\r\n\t\t\t\t: r.StateRules\r\n\t\t\t\t\t.OrderBy( rule => rule, Comparer<StateRule>.Create( StateRule.CompareCanonical ) )\r\n\t\t\t\t\t.Select( rule => new IRStateEnvelope\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tState = rule.State,\r\n\t\t\t\t\t\tNthChildMode = rule.NthChildMode,\r\n\t\t\t\t\t\tNthChildArg = rule.NthChildArg,\r\n\t\t\t\t\t\tDelta = rule.Delta,\r\n\t\t\t\t\t} )\r\n\t\t\t\t\t.ToList(),\r\n\t\t\tBindings = r.Bindings.Count == 0\r\n\t\t\t\t? System.Array.Empty<Grains.RazorDesigner.Wiring.Binding>()\r\n\t\t\t\t: r.Bindings.ToArray(),\r\n\r\n\t\t\t\tCustomStyles = r.CustomStyles.Count == 0 \r\n\t\t\t\t? null \r\n\t\t\t\t: new Dictionary<string, string>( r.CustomStyles ),\r\n\t\t};\r\n\t}\r\n}\r\n"
},
{
"Ident": "sklmr.razordesigner",
"Path": "Editor/Wiring/Actions/Action.cs",
"FileName": "Action.cs",
"PackageType": "library",
"CodeKind": "Editor",
"AssetVersionId": 306734,
"Code": "using System;\r\nusing System.Text.Json.Serialization;\r\n\r\nnamespace Grains.RazorDesigner.Wiring;\r\n\r\n[JsonPolymorphic( TypeDiscriminatorPropertyName = \"$type\" )]\r\n[JsonDerivedType( typeof( SetAction ), \"Set\" )]\r\n[JsonDerivedType( typeof( CallAction ), \"Call\" )]\r\n[JsonDerivedType( typeof( IfAction ), \"If\" )]\r\n[JsonDerivedType( typeof( StateHasChangedAction ), \"StateHasChanged\" )]\r\n[JsonDerivedType( typeof( LogAction ), \"Log\" )]\r\n[JsonDerivedType( typeof( ReturnAction ), \"Return\" )]\r\n[JsonDerivedType( typeof( InlineAction ), \"Inline\" )]\r\npublic abstract record Action\r\n{\r\n public Guid Id { get; init; } = Guid.NewGuid();\r\n}\r\n"
},
{
"Ident": "sklmr.razordesigner",
"Path": "Editor/Wiring/Actions/InlineAction.cs",
"FileName": "InlineAction.cs",
"PackageType": "library",
"CodeKind": "Editor",
"AssetVersionId": 306734,
"Code": "namespace Grains.RazorDesigner.Wiring;\r\n\r\npublic sealed record InlineAction : Action\r\n{\r\n public string Code { get; init; } = \"\";\r\n}\r\n"
},
{
"Ident": "sklmr.razordesigner",
"Path": "Editor/Wiring/Actions/SetAction.cs",
"FileName": "SetAction.cs",
"PackageType": "library",
"CodeKind": "Editor",
"AssetVersionId": 306734,
"Code": "namespace Grains.RazorDesigner.Wiring;\r\n\r\n// `Target = Value;` \u2014 assignment to a Symbol field.\r\npublic sealed record SetAction : Action\r\n{\r\n public TargetRef Target { get; init; }\r\n public Expression Value { get; init; }\r\n}\r\n"
},
{
"Ident": "sklmr.razordesigner",
"Path": "Editor/Wiring/Bindings/EventBinding.cs",
"FileName": "EventBinding.cs",
"PackageType": "library",
"CodeKind": "Editor",
"AssetVersionId": 306734,
"Code": "using System.Collections.Generic;\r\n\r\nnamespace Grains.RazorDesigner.Wiring;\r\n\r\npublic sealed record EventBinding : Binding\r\n{\r\n public string Event { get; init; } = \"\";\r\n public IReadOnlyList<Action> Body { get; init; } = System.Array.Empty<Action>();\r\n}\r\n"
},
{
"Ident": "sklmr.razordesigner",
"Path": "Editor/Wiring/Bindings/VisibleBinding.cs",
"FileName": "VisibleBinding.cs",
"PackageType": "library",
"CodeKind": "Editor",
"AssetVersionId": 306734,
"Code": "namespace Grains.RazorDesigner.Wiring;\r\n\r\npublic sealed record VisibleBinding : Binding\r\n{\r\n public Expression Condition { get; init; }\r\n}\r\n"
},
{
"Ident": "sklmr.razordesigner",
"Path": "Editor/Wiring/Symbols/SymbolVisibility.cs",
"FileName": "SymbolVisibility.cs",
"PackageType": "library",
"CodeKind": "Editor",
"AssetVersionId": 306734,
"Code": "namespace Grains.RazorDesigner.Wiring;\r\n\r\npublic enum SymbolVisibility\r\n{\r\n Private,\r\n Public,\r\n Internal,\r\n Protected,\r\n}\r\n"
},
{
"Ident": "sklmr.razordesigner",
"Path": "Editor/Canvas/DesignerCanvas.cs",
"FileName": "DesignerCanvas.cs",
"PackageType": "library",
"CodeKind": "Editor",
"AssetVersionId": 306734,
"Code": "using Editor;\r\nusing Grains.RazorDesigner.Document;\r\nusing Sandbox;\r\n\r\nnamespace Grains.RazorDesigner.Canvas;\r\n\r\n// Same shape as ShaderGraph PreviewPanel: override PreFrame to advance the scene.\r\npublic class DesignerCanvas : SceneRenderingWidget\r\n{\r\n\tprivate const string LogPrefix = \"[Grains.RazorDesigner]\";\r\n\r\n\tprivate readonly DesignerScene _designerScene;\r\n\r\n\tpublic DesignerCanvas( Widget parent ) : base( parent )\r\n\t{\r\n\t\tLog.Info( $\"{LogPrefix} DesignerCanvas ctor\" );\r\n\r\n\t\t_designerScene = new DesignerScene();\r\n\r\n\t\tScene = _designerScene.Scene;\r\n\t\tCamera = _designerScene.Camera;\r\n\r\n\t\tHorizontalSizeMode = SizeMode.Default | SizeMode.Expand;\r\n\t\tVerticalSizeMode = SizeMode.Default | SizeMode.Expand;\r\n\r\n\t\t// Without AcceptDrops, OnDragHover/OnDragDrop are never invoked.\r\n\t\tAcceptDrops = true;\r\n\r\n\t\tMouseTracking = true;\r\n\t}\r\n\r\n\tpublic DesignerScene DesignerScene => _designerScene;\r\n\r\n\t// Assigned by DesignerWindow after construction (mirrors how _viewportFrame.Canvas is wired).\r\n\tpublic OverlayController Overlay { get; set; }\r\n\r\n\tpublic CanvasViewportFrame ViewportFrame { get; set; }\r\n\r\n\tpublic event System.Action<Vector2, bool, Sandbox.KeyboardModifiers> CanvasClicked;\r\n\tpublic event System.Action<Vector2, Sandbox.KeyboardModifiers> CanvasMoved;\r\n\tpublic event System.Action<Vector2> CanvasReleased;\r\n\r\n\tpublic event System.Action<Vector2> CanvasPanDragged; // delta, screen px\r\n\r\n\t// Fires when the cursor leaves the canvas widget. Used to clear hover-pick state.\r\n\tpublic event System.Action CanvasHoverEnded;\r\n\r\n\tpublic event System.Action<ControlType, Vector2> RecordDropped;\r\n\t// Mirrors RecordDropped but for saved palette templates.\r\n\tpublic event System.Action<Grains.RazorDesigner.Templates.PaletteTemplate, Vector2> TemplateDropped;\r\n\r\n\tprivate bool _middlePanning;\r\n\tprivate Vector2 _lastPanScreen;\r\n\r\n\tprivate const bool ProbeFrameCost = false;\r\n\tprivate const int ProbeWindow = 120;\r\n\tprivate readonly System.Diagnostics.Stopwatch _probeSw = new();\r\n\tprivate double _probeTickMs;\r\n\tprivate double _probeUpdateMs;\r\n\tprivate int _probeFrames;\r\n\r\n\tpublic override void OnDragHover( DragEvent ev )\r\n\t{\r\n\t\tbase.OnDragHover( ev );\r\n\t\tif ( ev.Data.Object is ControlType || ev.Data.Object is Grains.RazorDesigner.Templates.PaletteTemplate )\r\n\t\t{\r\n\t\t\tev.Action = DropAction.Copy;\r\n\t\t}\r\n\t}\r\n\r\n\tpublic override void OnDragDrop( DragEvent ev )\r\n\t{\r\n\t\tbase.OnDragDrop( ev );\r\n\t\tif ( ev.Data.Object is ControlType type )\r\n\t\t{\r\n\t\t\tLog.Info( $\"{LogPrefix} DesignerCanvas drop: {type} at widget ({ev.LocalPosition.x:F0}, {ev.LocalPosition.y:F0})\" );\r\n\t\t\tRecordDropped?.Invoke( type, ev.LocalPosition );\r\n\t\t}\r\n\t\telse if ( ev.Data.Object is Grains.RazorDesigner.Templates.PaletteTemplate template )\r\n\t\t{\r\n\t\t\tLog.Info( $\"{LogPrefix} DesignerCanvas drop: template \\\"{template.Name}\\\" at widget ({ev.LocalPosition.x:F0}, {ev.LocalPosition.y:F0})\" );\r\n\t\t\tTemplateDropped?.Invoke( template, ev.LocalPosition );\r\n\t\t}\r\n\t}\r\n\r\n\tprotected override void OnMousePress( MouseEvent e )\r\n\t{\r\n\t\tif ( e.MiddleMouseButton )\r\n\t\t{\r\n\t\t\t// Swallow entirely \u2014 don't let base (SceneRenderingWidget) see the middle drag.\r\n\t\t\t_middlePanning = true;\r\n\t\t\t_lastPanScreen = e.ScreenPosition;\r\n\t\t\te.Accepted = true;\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tbase.OnMousePress( e );\r\n\t\tif ( e.LeftMouseButton || e.RightMouseButton )\r\n\t\t{\r\n\t\t\tvar pos = e.LocalPosition;\r\n\t\t\tLog.Info( $\"{LogPrefix} DesignerCanvas click at widget ({pos.x:F0}, {pos.y:F0}) right={e.RightMouseButton}\" );\r\n\t\t\tCanvasClicked?.Invoke( pos, e.RightMouseButton, e.KeyboardModifiers );\r\n\t\t}\r\n\t}\r\n\r\n\tprotected override void OnMouseMove( MouseEvent e )\r\n\t{\r\n\t\tif ( _middlePanning )\r\n\t\t{\r\n\t\t\tvar s = e.ScreenPosition;\r\n\t\t\tCanvasPanDragged?.Invoke( s - _lastPanScreen );\r\n\t\t\t_lastPanScreen = s;\r\n\t\t\te.Accepted = true;\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tbase.OnMouseMove( e );\r\n\t\tCanvasMoved?.Invoke( e.LocalPosition, e.KeyboardModifiers );\r\n\t}\r\n\r\n\tprotected override void OnMouseReleased( MouseEvent e )\r\n\t{\r\n\t\tif ( _middlePanning )\r\n\t\t{\r\n\t\t\t_middlePanning = false;\r\n\t\t\te.Accepted = true;\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tbase.OnMouseReleased( e );\r\n\t\tCanvasReleased?.Invoke( e.LocalPosition );\r\n\t}\r\n\r\n\tprotected override void OnMouseLeave()\r\n\t{\r\n\t\tbase.OnMouseLeave();\r\n\t\tCanvasHoverEnded?.Invoke();\r\n\t}\r\n\r\n\tprotected override void PreFrame()\r\n\t{\r\n\t\tbase.PreFrame();\r\n\r\n\t\tif ( !_designerScene.Scene.IsValid() )\r\n\t\t\treturn;\r\n\r\n\t\tif ( ProbeFrameCost ) _probeSw.Restart();\r\n\r\n\t\tusing ( _designerScene.Scene.Push() )\r\n\t\t{\r\n\t\t\t// EditorTick (not GameTick): preview scene, no SceneNetworkUpdate / fixed physics.\r\n\t\t\t_designerScene.Scene.EditorTick( RealTime.Now, RealTime.Delta );\r\n\t\t}\r\n\r\n\t\tif ( ProbeFrameCost ) { _probeTickMs += _probeSw.Elapsed.TotalMilliseconds; _probeSw.Restart(); }\r\n\r\n\t\t// Layout in framebuffer space (Size * DpiScale); DesignerScene sets Scale = DpiScale so authoring stays in logical px.\r\n\t\t_designerScene.Update( Size.x, Size.y, DpiScale );\r\n\r\n\t\tOverlay?.Tick( _designerScene, DpiScale );\r\n\r\n\t\tif ( ProbeFrameCost )\r\n\t\t{\r\n\t\t\t_probeUpdateMs += _probeSw.Elapsed.TotalMilliseconds;\r\n\t\t\tif ( ++_probeFrames >= ProbeWindow )\r\n\t\t\t{\r\n\t\t\t\tLog.Info( $\"{LogPrefix} probe: EditorTick avg {_probeTickMs / _probeFrames:F3}ms | Update avg {_probeUpdateMs / _probeFrames:F3}ms (over {_probeFrames} frames, canvas {Size.x:F0}x{Size.y:F0})\" );\r\n\t\t\t\t_probeTickMs = 0;\r\n\t\t\t\t_probeUpdateMs = 0;\r\n\t\t\t\t_probeFrames = 0;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tprotected override void OnKeyPress( KeyEvent e )\r\n\t{\r\n\t\tbase.OnKeyPress( e );\r\n\r\n\t\tif ( ViewportFrame is not { CanPanZoom: true } frame ) return;\r\n\r\n\t\tvar mods = e.KeyboardModifiers;\r\n\t\tvar ctrl = (mods & Sandbox.KeyboardModifiers.Ctrl) != 0;\r\n\t\tvar shift = (mods & Sandbox.KeyboardModifiers.Shift) != 0;\r\n\t\tvar alt = (mods & Sandbox.KeyboardModifiers.Alt) != 0;\r\n\r\n\t\tif ( !ctrl || alt ) return; // every shortcut is Ctrl-based; Alt clears it.\r\n\r\n\t\tswitch ( e.Key )\r\n\t\t{\r\n\t\t\tcase KeyCode.Num0:\r\n\t\t\t\tif ( shift )\r\n\t\t\t\t{\r\n\t\t\t\t\tLog.Info( $\"{LogPrefix} OnKeyPress Ctrl+Shift+0 \u2192 ZoomToSelection\" );\r\n\t\t\t\t\tZoomToSelectionViaFrame( frame );\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tLog.Info( $\"{LogPrefix} OnKeyPress Ctrl+0 \u2192 ApplyFit\" );\r\n\t\t\t\t\tframe.ApplyFit();\r\n\t\t\t\t}\r\n\t\t\t\te.Accepted = true;\r\n\t\t\t\tbreak;\r\n\r\n\t\t\tcase KeyCode.Num1:\r\n\t\t\t\tif ( shift ) return;\r\n\t\t\t\tLog.Info( $\"{LogPrefix} OnKeyPress Ctrl+1 \u2192 ResetZoomOnly\" );\r\n\t\t\t\tframe.ResetZoomOnly();\r\n\t\t\t\te.Accepted = true;\r\n\t\t\t\tbreak;\r\n\r\n\t\t\tcase KeyCode.Equal:\r\n\t\t\t\tif ( shift ) return;\r\n\t\t\t\tframe.ZoomBy( 1.25f );\r\n\t\t\t\te.Accepted = true;\r\n\t\t\t\tbreak;\r\n\r\n\t\t\tcase KeyCode.Minus:\r\n\t\t\t\tif ( shift ) return;\r\n\t\t\t\tframe.ZoomBy( 1f / 1.25f );\r\n\t\t\t\te.Accepted = true;\r\n\t\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void ZoomToSelectionViaFrame( CanvasViewportFrame frame )\r\n\t{\r\n\t\tvar record = frame.Selection?.Selected;\r\n\t\tvar live = record?.LivePanel;\r\n\t\tif ( live is null || !live.IsValid )\r\n\t\t{\r\n\t\t\tLog.Info( $\"{LogPrefix} Ctrl+Shift+0: no valid selection\" );\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tframe.ApplyZoomToRect( live.Box.Rect, /* maxZoom */ 4f, /* padding */ 0.10f );\r\n\t}\r\n\r\n\tpublic override void OnDestroyed()\r\n\t{\r\n\t\tLog.Info( $\"{LogPrefix} DesignerCanvas.OnDestroyed\" );\r\n\t\tbase.OnDestroyed();\r\n\t\t_designerScene.Dispose();\r\n\t}\r\n}\r\n"
}
]
}