Editor/Widgets/SuiCodeTabWidget.cs
using Editor;
using Sandbox;
using SboxUiDesigner.Generation;
using SboxUiDesigner.Runtime;
namespace SboxUiDesigner.EditorUi.Widgets;
/// <summary>
/// Read-only code viewer — shows the generated Razor + SCSS for the current
/// document. Updates only when the user clicks Refresh or when the tab is
/// activated (M14 spec: not a live updating editor; it's an inspection tool).
///
/// Code tab editable behaviour is V2 — the .sui document is the source of
/// truth, hand-edits to the generated files would be lost on next compile.
/// </summary>
public sealed class SuiCodeTabWidget : Widget
{
private SuiDocument _document;
private TabWidget _innerTabs;
private TextEdit _razorView;
private TextEdit _scssView;
public SuiCodeTabWidget( Widget parent ) : base( parent )
{
Layout = Layout.Column();
Layout.Margin = 0;
Layout.Spacing = 0;
// Top bar with Refresh button.
var topBar = new Widget( this );
topBar.Layout = Layout.Row();
topBar.Layout.Margin = new Sandbox.UI.Margin( 6, 4, 6, 4 );
topBar.Layout.Spacing = 6;
topBar.FixedHeight = 32;
var refreshBtn = new Button( "Refresh", "refresh", topBar );
refreshBtn.ToolTip = "Re-run the generator and refresh the code view";
refreshBtn.Clicked += Reload;
topBar.Layout.Add( refreshBtn );
var hint = new Label( "Read-only — generated from the .sui document. Click Refresh to re-run.", topBar );
hint.SetStyles( "color: #9ca3af; font-size: 11px;" );
topBar.Layout.Add( hint, 1 );
Layout.Add( topBar );
// Inner tabs — Razor + SCSS side by side via tabs.
_innerTabs = new TabWidget( this );
_razorView = MakeCodeView( "// Click Refresh to generate" );
_innerTabs.AddPage( ".razor", "code", _razorView );
_scssView = MakeCodeView( "/* Click Refresh to generate */" );
_innerTabs.AddPage( ".razor.scss", "style", _scssView );
Layout.Add( _innerTabs, 1 );
}
/// <summary>
/// Build a TextEdit styled to look like a real IDE pane: monospace font,
/// 2-character tab stop (in case any \t leaks through the generator),
/// comfortable line height, dark background slightly different from the
/// dock body so the code reads as content, soft padding so the first
/// column isn't glued to the chrome.
/// </summary>
private static TextEdit MakeCodeView( string placeholder )
{
var view = new TextEdit( null );
view.PlainText = placeholder;
view.ReadOnly = true;
// Generator emits 2-space indents now (see SuiRazorGenerator/SuiScssGenerator),
// so we don't need any tab-stop tweaks here. Just an IDE-style code surface.
view.SetStyles(
"background-color: rgb(15,15,16);" +
"color: rgb(220,222,228);" +
"border: none;" +
"padding: 12px 14px;" +
"font-family: 'Consolas', 'Cascadia Code', 'Menlo', monospace;" +
"font-size: 12px;"
);
return view;
}
public void SetDocument( SuiDocument document )
{
_document = document;
// Don't auto-Reload — runs the full generation pipeline on every doc
// change. The Code tab regenerates lazily when the user opens it or
// clicks Refresh inside it.
}
public void Reload()
{
if ( _document == null )
{
_razorView.PlainText = "// (no document loaded)";
_scssView.PlainText = "/* (no document loaded) */";
return;
}
var ctx = new SuiGenerationContext
{
Document = _document,
Mode = SuiGenerationMode.Final,
OutputFolder = "",
ClassName = _document.Output?.ClassName,
Namespace = _document.Output?.Namespace,
};
var result = SuiGenerationPipeline.Run( ctx );
if ( !result.Ok )
{
var errs = string.Join( "\n", result.Errors );
_razorView.PlainText = $"// Generation failed:\n{errs}";
_scssView.PlainText = $"/* Generation failed — see .razor tab */";
return;
}
var razor = result.FindByKind( SuiGeneratedFileKind.Razor );
var scss = result.FindByKind( SuiGeneratedFileKind.Scss );
_razorView.PlainText = razor?.Content ?? "// (empty)";
_scssView.PlainText = scss?.Content ?? "/* (empty) */";
}
}