Editor UI panel for the SuperShot tool. It builds toolbar buttons for capture/save/copy/post and a live preview canvas that renders either captured images or a live freecam preview, applies edits, and draws UI overlays like thirds grid.
using System;
using Sandbox;
namespace Editor.SuperShot;
public sealed class PreviewPanel : Widget
{
readonly SuperShotWindow _window;
PreviewCanvas _canvas;
public PreviewPanel( SuperShotWindow window ) : base( null )
{
_window = window;
Name = "Preview";
WindowTitle = "Preview";
SetWindowIcon( "image" );
Layout = Layout.Column();
Layout.Margin = 0;
Build();
}
Button _liveButton;
Button _saveButton;
Button _copyButton;
Button _postButton;
void Build()
{
var toolbar = Layout.AddRow();
toolbar.Margin = 6;
toolbar.Spacing = 4;
toolbar.Add( new Button.Primary( "Capture 1K", "photo_camera" )
{
ToolTip = "Quickly capture the current view at 1K (1024x576)",
Clicked = () => _window.CaptureAt( ShotResolution.OneK )
} );
_saveButton = new Button( "Save", "save" ) { Clicked = () => _window.SaveCurrent() };
toolbar.Add( _saveButton );
_copyButton = new Button( "Copy Path", "content_copy" ) { ToolTip = "Save the shot and copy its file path", Clicked = CopyPath };
toolbar.Add( _copyButton );
_postButton = new Button( "Post to All", "send" ) { Clicked = () => _ = _window.PostCurrentToAll() };
toolbar.Add( _postButton );
toolbar.AddStretchCell();
_liveButton = new Button( LiveLabel(), _window.Settings.LivePreview ? "videocam" : "videocam_off" )
{
ToolTip = "Toggle the live freecam preview (off saves performance)",
Clicked = ToggleLive
};
toolbar.Add( _liveButton );
var thirds = new Button( "Thirds", "grid_3x3" );
thirds.Clicked = () => { _canvas.ShowThirds = !_canvas.ShowThirds; _canvas.Update(); };
toolbar.Add( thirds );
var beforeAfter = new Button( "Before/After", "compare" );
beforeAfter.Clicked = () => { _canvas.ShowOriginal = !_canvas.ShowOriginal; _canvas.Refresh(); };
toolbar.Add( beforeAfter );
_canvas = new PreviewCanvas( this, _window );
Layout.Add( _canvas, 1 );
_window.Changed += OnChanged;
UpdateActionVisibility();
}
void UpdateActionVisibility()
{
var has = _window.HasCapture;
if ( _saveButton.IsValid() ) _saveButton.Visible = has;
if ( _copyButton.IsValid() ) _copyButton.Visible = has;
if ( _postButton.IsValid() ) _postButton.Visible = has;
}
string LiveLabel() => _window.Settings.LivePreview ? "Live: On" : "Live: Off";
void ToggleLive()
{
_window.Settings.LivePreview = !_window.Settings.LivePreview;
_window.Settings.Save();
if ( _liveButton.IsValid() )
{
_liveButton.Text = LiveLabel();
_liveButton.Icon = _window.Settings.LivePreview ? "videocam" : "videocam_off";
_liveButton.Update();
}
_canvas?.Refresh();
}
void CopyPath()
{
var path = _window.SaveCurrent();
SuperShotService.CopyPathToClipboard( path );
}
void OnChanged()
{
UpdateActionVisibility();
_canvas?.Refresh();
}
public override void OnDestroyed()
{
base.OnDestroyed();
_window.Changed -= OnChanged;
}
sealed class PreviewCanvas : Widget
{
readonly SuperShotWindow _window;
Pixmap _pixmap;
Bitmap _live;
RealTimeSince _sinceRender = 100f;
bool _dirty = true;
public bool ShowThirds { get; set; } = true;
public bool ShowOriginal { get; set; } = false;
public PreviewCanvas( Widget parent, SuperShotWindow window ) : base( parent )
{
_window = window;
MinimumSize = 64;
}
public void Refresh()
{
_dirty = true;
Update();
}
[EditorEvent.Frame]
public void Frame()
{
if ( _window.HasCapture )
{
if ( _dirty )
{
BuildFromCapture();
_dirty = false;
Update();
}
return;
}
if ( !_window.Settings.LivePreview )
{
// Drop the last live frame immediately so the empty state shows without reopening.
if ( _pixmap != null )
{
_pixmap = null;
_live?.Dispose();
_live = null;
Update();
}
return;
}
if ( _sinceRender > 0.1f )
{
_sinceRender = 0f;
if ( RenderLive() )
Update();
}
}
bool RenderLive()
{
var (w, h) = SuperShotContext.ResolveSize( _window.Settings.Capture );
float aspect = w / (float)h;
int pw = 960;
int ph = Math.Max( 1, (int)(pw / aspect) );
if ( _live is null || _live.Width != pw || _live.Height != ph )
{
_live?.Dispose();
_live = new Bitmap( pw, ph );
}
if ( !SuperShotCapture.RenderPreview( _live, _window.Settings.Capture ) )
return false;
if ( ShowOriginal )
{
EnsurePixmap( _live.Width, _live.Height );
_pixmap.UpdateFromPixels( _live );
return true;
}
using var edited = SuperShotEdit.Apply( _live, _window.Settings.Edit );
var display = edited is not null && edited.IsValid ? edited : _live;
EnsurePixmap( display.Width, display.Height );
_pixmap.UpdateFromPixels( display );
return true;
}
void BuildFromCapture()
{
Bitmap source;
bool ownsSource = false;
if ( ShowOriginal )
{
source = _window.RawCapture;
}
else
{
source = _window.BuildFinished();
ownsSource = true;
}
if ( source is null || !source.IsValid )
return;
EnsurePixmap( source.Width, source.Height );
_pixmap.UpdateFromPixels( source );
if ( ownsSource )
source.Dispose();
}
void EnsurePixmap( int w, int h )
{
if ( _pixmap is null || _pixmap.Width != w || _pixmap.Height != h )
_pixmap = new Pixmap( w, h );
}
protected override void OnPaint()
{
Paint.ClearPen();
Paint.SetBrush( Theme.ControlBackground.Darken( 0.3f ) );
Paint.DrawRect( LocalRect );
if ( _pixmap is null )
{
var message = _window.Settings.LivePreview
? "Move the editor scene view (or activate the Supershot tool), then press Capture.\nPick a scene camera in the Capture tab to shoot that view instead."
: "Live preview is off.\nPress Capture to shoot, or turn Live back on in the toolbar above.";
Paint.SetPen( Theme.TextControl.WithAlpha( 0.6f ) );
Paint.DrawText( LocalRect.Shrink( 24 ), message, TextFlag.Center | TextFlag.WordWrap );
return;
}
var rect = FitRect( _pixmap.Size, Size );
Paint.Draw( rect, _pixmap );
DrawOverlays( rect );
}
void DrawOverlays( Rect rect )
{
if ( !ShowThirds )
return;
Paint.SetPen( Color.White.WithAlpha( 0.35f ), 1f );
float x1 = rect.Left + rect.Width / 3f;
float x2 = rect.Left + rect.Width * 2f / 3f;
float y1 = rect.Top + rect.Height / 3f;
float y2 = rect.Top + rect.Height * 2f / 3f;
Paint.DrawLine( new Vector2( x1, rect.Top ), new Vector2( x1, rect.Bottom ) );
Paint.DrawLine( new Vector2( x2, rect.Top ), new Vector2( x2, rect.Bottom ) );
Paint.DrawLine( new Vector2( rect.Left, y1 ), new Vector2( rect.Right, y1 ) );
Paint.DrawLine( new Vector2( rect.Left, y2 ), new Vector2( rect.Right, y2 ) );
}
static Rect FitRect( Vector2 content, Vector2 area )
{
float scale = Math.Min( area.x / content.x, area.y / content.y );
var size = content * scale;
return new Rect( (area.x - size.x) / 2f, (area.y - size.y) / 2f, size.x, size.y );
}
public override void OnDestroyed()
{
base.OnDestroyed();
_live?.Dispose();
}
}
}