Editor/CloudIconGridPanel.cs
using Editor;
using Sandbox;
using System;
using System.Collections.Generic;
namespace GeneralGame.Editor;
/// <summary>
/// Panel displaying cloud assets as large icons in a grid layout.
/// </summary>
public class CloudIconGridPanel : Widget, IBrowserPanel
{
private Widget _toolbar;
private Label _titleLabel;
private IconButton _closeBtn;
private IconButton _moveLeftBtn;
private IconButton _moveRightBtn;
private ScrollArea _scrollArea;
private CloudGridCanvas _gridCanvas;
public Action OnCloseRequested { get; set; }
public Action OnMoveLeftRequested { get; set; }
public Action OnMoveRightRequested { get; set; }
public bool ShowCloseButton
{
get => _closeBtn?.Visible ?? false;
set
{
if (_closeBtn != null)
_closeBtn.Visible = value;
}
}
public bool ShowMoveLeftButton
{
get => _moveLeftBtn?.Visible ?? false;
set
{
if (_moveLeftBtn != null)
_moveLeftBtn.Visible = value;
}
}
public bool ShowMoveRightButton
{
get => _moveRightBtn?.Visible ?? false;
set
{
if (_moveRightBtn != null)
_moveRightBtn.Visible = value;
}
}
public CloudIconGridPanel(Widget parent) : base(parent)
{
SetSizeMode(SizeMode.CanGrow, SizeMode.CanGrow);
CreateUI();
}
private void CreateUI()
{
Layout = Layout.Column();
Layout.Spacing = 4;
Layout.Margin = 4;
// Toolbar
_toolbar = Layout.Add(new Widget(this));
_toolbar.Layout = Layout.Row();
_toolbar.Layout.Spacing = 4;
_toolbar.FixedHeight = 28;
// Cloud icon
var cloudIcon = _toolbar.Layout.Add(new Label("cloud", this));
cloudIcon.SetStyles("font-family: Material Icons; font-size: 16px; color: #888;");
cloudIcon.FixedWidth = 20;
_titleLabel = _toolbar.Layout.Add(new Label("Cloud Assets Preview", this));
_titleLabel.SetStyles("color: #888; font-size: 11px;");
_toolbar.Layout.AddStretchCell();
_moveLeftBtn = _toolbar.Layout.Add(new IconButton("chevron_left"));
_moveLeftBtn.ToolTip = "Move Panel Left";
_moveLeftBtn.Background = Color.Transparent;
_moveLeftBtn.OnClick = () => OnMoveLeftRequested?.Invoke();
_moveLeftBtn.Visible = false;
_moveRightBtn = _toolbar.Layout.Add(new IconButton("chevron_right"));
_moveRightBtn.ToolTip = "Move Panel Right";
_moveRightBtn.Background = Color.Transparent;
_moveRightBtn.OnClick = () => OnMoveRightRequested?.Invoke();
_moveRightBtn.Visible = false;
_closeBtn = _toolbar.Layout.Add(new IconButton("close"));
_closeBtn.ToolTip = "Close Panel";
_closeBtn.Background = Color.Transparent;
_closeBtn.OnClick = () => OnCloseRequested?.Invoke();
_closeBtn.Visible = false;
// Scroll area with custom canvas
_scrollArea = Layout.Add(new ScrollArea(this));
_scrollArea.SetSizeMode(SizeMode.CanGrow, SizeMode.CanGrow);
_gridCanvas = new CloudGridCanvas(_scrollArea);
_scrollArea.Canvas = _gridCanvas;
}
/// <summary>
/// Show cloud packages in the grid
/// </summary>
public void ShowPackages(List<Package> packages)
{
_gridCanvas.LoadPackages(packages);
_titleLabel.Text = $"Cloud Assets ({packages.Count})";
}
/// <summary>
/// Clear the grid
/// </summary>
public void Clear()
{
_gridCanvas.Clear();
_titleLabel.Text = "Cloud Assets Preview";
}
}
/// <summary>
/// Canvas that draws cloud package icons
/// </summary>
internal class CloudGridCanvas : Widget
{
private List<CloudGridItem> _items = new();
private int _hoveredIndex = -1;
private int _selectedIndex = -1;
private const float ItemWidth = 100;
private const float ItemHeight = 120;
private const float IconSize = 64;
private const float Spacing = 8;
private const float Padding = 8;
public CloudGridCanvas(Widget parent) : base(parent)
{
MinimumHeight = 100;
}
public void Clear()
{
_items.Clear();
_hoveredIndex = -1;
_selectedIndex = -1;
MinimumHeight = 100;
Update();
}
public void LoadPackages(List<Package> packages)
{
_items.Clear();
_hoveredIndex = -1;
_selectedIndex = -1;
foreach (var pkg in packages)
{
_items.Add(new CloudGridItem
{
Package = pkg,
Title = pkg.Title ?? pkg.FullIdent,
Author = pkg.Org?.Title ?? "Unknown",
FullIdent = pkg.FullIdent,
Thumb = pkg.Thumb
});
}
UpdateSize();
Update();
}
private void UpdateSize()
{
if (_items == null || _items.Count == 0)
{
MinimumHeight = 100;
return;
}
float width = 400;
try { width = Parent?.Width ?? 400; } catch { }
if (width <= 0) width = 400;
int columns = Math.Max(1, (int)((width - Padding * 2 + Spacing) / (ItemWidth + Spacing)));
int rows = (_items.Count + columns - 1) / columns;
float height = Padding * 2 + rows * ItemHeight + Math.Max(0, rows - 1) * Spacing;
MinimumHeight = Math.Max(100, height);
}
protected override void OnPaint()
{
if (_items == null || _items.Count == 0)
{
// Draw placeholder
Paint.SetPen(Theme.Text.WithAlpha(0.3f));
Paint.SetDefaultFont(10, 400);
Paint.DrawText(LocalRect, "Select a category in the Cloud Assets tree", TextFlag.Center);
return;
}
float width = Width;
if (width <= 0) width = 400;
int columns = Math.Max(1, (int)((width - Padding * 2 + Spacing) / (ItemWidth + Spacing)));
for (int i = 0; i < _items.Count; i++)
{
int col = i % columns;
int row = i / columns;
float x = Padding + col * (ItemWidth + Spacing);
float y = Padding + row * (ItemHeight + Spacing);
var itemRect = new Rect(x, y, ItemWidth, ItemHeight);
DrawItem(i, itemRect);
}
}
private void DrawItem(int index, Rect rect)
{
var item = _items[index];
// Background
if (index == _selectedIndex)
{
Paint.SetBrush(Theme.Primary.WithAlpha(0.3f));
Paint.DrawRect(rect, 4);
}
else if (index == _hoveredIndex)
{
Paint.SetBrush(Color.White.WithAlpha(0.05f));
Paint.DrawRect(rect, 4);
}
// Icon area
var iconRect = new Rect(
rect.Left + (rect.Width - IconSize) / 2,
rect.Top + 4,
IconSize,
IconSize
);
// Draw thumbnail if available, otherwise icon based on package type
if (!string.IsNullOrEmpty(item.Thumb) && item.Thumb.StartsWith("http"))
{
Paint.Draw(iconRect, item.Thumb);
}
else
{
Paint.SetBrush(Theme.WidgetBackground);
Paint.DrawRect(iconRect, 4);
var icon = GetIconForType(item.Package.TypeName);
var iconColor = GetColorForType(item.Package.TypeName);
Paint.SetPen(iconColor);
Paint.DrawIcon(iconRect, icon, 48, TextFlag.Center);
}
// Title
var titleRect = new Rect(rect.Left + 2, iconRect.Bottom + 2, rect.Width - 4, 16);
Paint.SetPen(Theme.Text);
Paint.SetDefaultFont(8, 500);
var title = item.Title;
if (title.Length > 14) title = title.Substring(0, 12) + "..";
Paint.DrawText(titleRect, title, TextFlag.Center);
// Author
var authorRect = new Rect(rect.Left + 2, titleRect.Bottom, rect.Width - 4, 14);
Paint.SetPen(Theme.Text.WithAlpha(0.5f));
Paint.SetDefaultFont(7, 400);
var author = item.Author;
if (author.Length > 16) author = author.Substring(0, 14) + "..";
Paint.DrawText(authorRect, author, TextFlag.Center);
}
private int GetItemAtPosition(Vector2 pos)
{
if (_items == null || _items.Count == 0)
return -1;
float width = Width;
if (width <= 0) return -1;
int columns = Math.Max(1, (int)((width - Padding * 2 + Spacing) / (ItemWidth + Spacing)));
int col = (int)((pos.x - Padding) / (ItemWidth + Spacing));
int row = (int)((pos.y - Padding) / (ItemHeight + Spacing));
if (col < 0 || col >= columns) return -1;
int index = row * columns + col;
if (index < 0 || index >= _items.Count) return -1;
return index;
}
private Vector2 _dragStartPos;
private bool _isDragging;
private int _dragItemIndex = -1;
protected override void OnMouseMove(MouseEvent e)
{
base.OnMouseMove(e);
int newHover = GetItemAtPosition(e.LocalPosition);
if (newHover != _hoveredIndex)
{
_hoveredIndex = newHover;
Update();
}
// Handle drag
if (e.ButtonState.HasFlag(MouseButtons.Left) && !_isDragging && _dragItemIndex >= 0)
{
var delta = e.LocalPosition - _dragStartPos;
if (delta.Length > 5)
{
_isDragging = true;
StartDrag(_dragItemIndex);
}
}
}
protected override void OnMouseLeave()
{
base.OnMouseLeave();
_hoveredIndex = -1;
Update();
}
protected override void OnMousePress(MouseEvent e)
{
base.OnMousePress(e);
if (e.LeftMouseButton)
{
int index = GetItemAtPosition(e.LocalPosition);
_dragStartPos = e.LocalPosition;
_isDragging = false;
_dragItemIndex = index;
if (index >= 0)
{
_selectedIndex = index;
Update();
}
}
}
private void StartDrag(int index)
{
if (index < 0 || index >= _items.Count) return;
var item = _items[index];
var drag = new Drag(this);
drag.Data.Text = item.FullIdent;
// Set thumbnail URL if available
if (!string.IsNullOrEmpty(item.Thumb))
{
drag.Data.Url = new Uri(item.Thumb);
}
drag.Execute();
}
protected override void OnDoubleClick(MouseEvent e)
{
base.OnDoubleClick(e);
if (e.LeftMouseButton)
{
int index = GetItemAtPosition(e.LocalPosition);
if (index >= 0)
{
var item = _items[index];
// Open package page in browser
var url = $"https://sbox.game/{item.FullIdent}";
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
{
FileName = url,
UseShellExecute = true
});
}
}
}
protected override void OnContextMenu(ContextMenuEvent e)
{
int index = GetItemAtPosition(e.LocalPosition);
if (index < 0) return;
var item = _items[index];
var menu = new ContextMenu(this);
menu.AddOption("Open in Browser", "open_in_new", () =>
{
var url = $"https://sbox.game/{item.FullIdent}";
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
{
FileName = url,
UseShellExecute = true
});
});
menu.AddOption("Copy Identifier", "content_copy", () =>
{
EditorUtility.Clipboard.Copy(item.FullIdent);
});
menu.OpenAtCursor();
e.Accepted = true;
}
private static string GetIconForType(string typeName)
{
return typeName switch
{
"model" => "view_in_ar",
"material" => "texture",
"sound" => "audiotrack",
"map" => "landscape",
_ => "cloud_download"
};
}
private static Color GetColorForType(string typeName)
{
return typeName switch
{
"model" => new Color(0.9f, 0.6f, 0.3f),
"material" => new Color(0.9f, 0.4f, 0.6f),
"sound" => new Color(0.4f, 0.7f, 1.0f),
"map" => new Color(0.9f, 0.9f, 0.3f),
_ => Theme.Text.WithAlpha(0.7f)
};
}
private class CloudGridItem
{
public Package Package;
public string Title;
public string Author;
public string FullIdent;
public string Thumb;
}
}