An editor UI panel that displays a gallery of captured screenshots. It lays out thumbnail widgets in a responsive grid, handles resizing, rebuilds when the gallery changes, and provides context menu actions to open, save, post, reveal, or delete shots.
using System;
using Sandbox;
namespace Editor.SuperShot;
public sealed class GalleryPanel : Widget
{
readonly SuperShotWindow _window;
Widget _grid;
int _columns;
const int ThumbWidthPx = 156;
const int ThumbHeightPx = 100;
const int GridSpacing = 6;
public GalleryPanel( SuperShotWindow window ) : base( null )
{
_window = window;
Name = "Gallery";
WindowTitle = "Gallery";
SetWindowIcon( "collections" );
Layout = Layout.Column();
Layout.Margin = 6;
Layout.Spacing = 4;
var toolbar = Layout.AddRow();
toolbar.Spacing = 4;
toolbar.Add( new Button( "Refresh", "refresh" )
{
ToolTip = "Re-scan the output folder for saved shots",
Clicked = () => _window.RefreshGalleryFromDisk()
} );
toolbar.AddStretchCell();
var scroll = new ScrollArea( this );
scroll.Canvas = new Widget( scroll );
scroll.Canvas.Layout = Layout.Column();
scroll.Canvas.Layout.Spacing = GridSpacing;
scroll.Canvas.Layout.Margin = 4;
_grid = scroll.Canvas;
Layout.Add( scroll, 1 );
_window.Changed += Rebuild;
Rebuild();
}
int ComputeColumns()
{
float avail = _grid.Width;
if ( avail <= 1 )
return Math.Max( 1, _columns );
return Math.Max( 1, (int)((avail - 8 + GridSpacing) / (ThumbWidthPx + GridSpacing)) );
}
[EditorEvent.Frame]
void OnFrame()
{
if ( _window.Gallery.Count == 0 )
return;
if ( ComputeColumns() != _columns )
Rebuild();
}
void Rebuild()
{
_grid.Layout.Clear( true );
if ( _window.Gallery.Count == 0 )
{
_columns = 0;
_grid.Layout.Add( new Label( "No shots yet - press Capture to start. Saved shots land here; click a thumbnail to open it in the Edit tab, right-click for save/post/delete." ) );
return;
}
_columns = ComputeColumns();
Layout row = null;
int inRow = 0;
for ( int i = _window.Gallery.Count - 1; i >= 0; i-- )
{
if ( inRow == 0 )
{
row = _grid.Layout.AddRow();
row.Spacing = GridSpacing;
}
row.Add( new ThumbWidget( _window, _window.Gallery[i] ) );
inRow++;
if ( inRow >= _columns )
{
row.AddStretchCell();
inRow = 0;
}
}
if ( inRow > 0 )
row.AddStretchCell();
_grid.Layout.AddStretchCell();
}
public override void OnDestroyed()
{
base.OnDestroyed();
_window.Changed -= Rebuild;
}
sealed class ThumbWidget : Widget
{
readonly SuperShotWindow _window;
readonly CapturedShot _shot;
public ThumbWidget( SuperShotWindow window, CapturedShot shot ) : base( null )
{
_window = window;
_shot = shot;
FixedSize = new Vector2( ThumbWidthPx, ThumbHeightPx );
Cursor = CursorShape.Finger;
ToolTip = $"{shot.Size.x}x{shot.Size.y} {shot.Time:HH:mm:ss}";
}
protected override void OnPaint()
{
Paint.ClearPen();
Paint.SetBrush( Color.Black );
Paint.DrawRect( LocalRect );
if ( _shot.Thumbnail is not null )
{
var rect = FitRect( _shot.Thumbnail.Size, new Vector2( Width, Height - 16 ) );
Paint.Draw( rect, _shot.Thumbnail );
}
Paint.SetPen( Theme.TextControl.WithAlpha( 0.8f ) );
var label = _shot.SavedPath is not null ? $"{_shot.Size.x}x{_shot.Size.y} (saved)" : $"{_shot.Size.x}x{_shot.Size.y}";
Paint.DrawText( new Rect( 0, Height - 16, Width, 16 ), label, TextFlag.Center );
}
protected override void OnMouseClick( MouseEvent e )
{
base.OnMouseClick( e );
if ( e.LeftMouseButton )
{
_window.OpenInEditor( _shot );
}
else if ( e.RightMouseButton )
{
var menu = new Menu();
menu.AddOption( "Open in Edit Tab", "edit", () => _window.OpenInEditor( _shot ) );
menu.AddOption( "Save", "save", () =>
{
_window.LoadFromGallery( _shot );
_window.SaveCurrent();
} );
menu.AddOption( "Post to All Channels", "send", () =>
{
_window.LoadFromGallery( _shot );
_ = _window.PostCurrentToAll();
} );
if ( _shot.SavedPath is not null )
menu.AddOption( "Open File Location", "folder", () => SuperShotService.RevealInExplorer( _shot.SavedPath ) );
menu.AddSeparator();
menu.AddOption( "Delete", "delete", () => _window.RemoveFromGallery( _shot ) );
menu.OpenAtCursor();
}
}
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 );
}
}
}