Editor/ReadOnlyTagSetControlWidget.cs
using Editor;
using ExtendedBox.General;
using Sandbox;
using Sandbox.UI;
using System;
using System.Collections.Generic;
using System.Linq;
namespace ExtendedBox;
[CustomEditor(typeof(ReadOnlyTagSet))]
public sealed class ReadOnlyTagSetControlWidget : ControlWidget
{
private readonly Layout _tagsArea;
private GridLayout? _tagsPopupGrid;
public override bool SupportsMultiEdit => true;
public ReadOnlyTagSetControlWidget(SerializedProperty property) : base(property)
{
Layout = Layout.Row();
Layout.Spacing = 3;
Layout.Margin = new Margin(3, 0);
_tagsArea = Layout.AddRow(1);
_tagsArea.Spacing = 2;
_tagsArea.Margin = new Margin(0, 3);
Layout.AddStretchCell();
Layout.Add(new Editor.Button(null, "local_offer") { MouseLeftPress = OpenPopup, FixedWidth = Theme.RowHeight, FixedHeight = Theme.RowHeight, OnPaintOverride = PaintTagAdd, ToolTip = "Tags" });
}
protected override int ValueHash
{
get
{
var tags = SerializedProperty.GetValue<ReadOnlyTagSet>();
if(tags is null)
return 0;
HashCode code = default;
foreach(var tag in tags.TryGetAll())
code.Add(tag);
return code.ToHashCode();
}
}
protected override void OnValueChanged()
{
_tagsArea.Clear(true);
var tags = SerializedProperty.GetValue<ReadOnlyTagSet>();
if(tags is null)
return;
foreach(var tag in tags.TryGetAll().Take(32))
_tagsArea.Add(new TagButton(this) { TagText = tag, MouseLeftPress = () => RemoveTag(tag) });
}
private void ToggleTag(string tag)
{
var tags = SerializedProperty.GetValue<ReadOnlyTagSet>();
if(tags is null)
return;
tags = TagSetBuilder.Create(tags).Toggle(tag).Build();
SerializedProperty.SetValue(tags);
SerializedProperty.Parent?.NoteChanged(SerializedProperty);
}
private void AddTag(string tag)
{
var tags = SerializedProperty.GetValue<ReadOnlyTagSet>();
if(tags is null)
return;
tags = TagSetBuilder.Create(tags).Add(tag).Build();
SerializedProperty.SetValue(tags);
SerializedProperty.Parent?.NoteChanged(SerializedProperty);
}
private void RemoveTag(string tag)
{
var tags = SerializedProperty.GetValue<ReadOnlyTagSet>();
if(tags is null)
return;
tags = TagSetBuilder.Create(tags).Remove(tag).Build();
SerializedProperty.SetValue(tags);
SerializedProperty.Parent?.NoteChanged(SerializedProperty);
}
bool PaintTagAdd()
{
var alpha = Paint.HasMouseOver ? 1.0f : 0.7f;
Paint.SetPen(Theme.Blue.WithAlpha(0.5f * alpha));
Paint.DrawIcon(new Rect(0, Theme.RowHeight), "local_offer", 16);
Paint.SetPen(Theme.Blue.WithAlpha(0.8f * alpha));
Paint.DrawIcon(new Rect(0, Theme.RowHeight), "add", 13, TextFlag.LeftBottom);
return true;
}
void OpenPopup()
{
var tags = SerializedProperty.GetValue<ReadOnlyTagSet>();
if(tags is null)
{
if(SerializedProperty.PropertyType == typeof(ReadOnlyTagSet))
{
tags = new TagSet();
SerializedProperty.SetValue(tags);
}
else
{
Log.Warning($"{nameof(ReadOnlyTagSet)} is null and we don't know how to create type: {SerializedProperty.PropertyType}");
return;
}
}
var popup = new PopupWidget(this)
{
FixedWidth = 200,
Layout = Layout.Column()
};
popup.Layout.Margin = 8;
popup.Layout.Spacing = 4;
var entry = popup.Layout.Add(new LineEdit(popup));
_tagsPopupGrid = popup.Layout.Add(Layout.Grid()) as GridLayout;
entry.PlaceholderText = "New tag..";
entry.FixedHeight = Theme.RowHeight;
entry.ReturnPressed += () =>
{
AddTag(entry.Value);
entry.Clear();
RebuildTagGrid();
};
RebuildTagGrid();
popup.OpenAt(ScreenRect.BottomRight + new Vector2(-200, 0));
entry.Focus();
}
void RebuildTagGrid()
{
var scene = SceneEditorSession.Active?.Scene;
var tags = SerializedProperty.GetValue<ReadOnlyTagSet>();
if(!scene.IsValid())
return;
if(tags == null)
return;
if(!_tagsPopupGrid.IsValid())
return;
_tagsPopupGrid.Clear(true);
var defaultTags = new List<string>();
int i = 0;
foreach(var g in defaultTags.GroupBy(x => x).OrderByDescending(x => x.Count()).Take(32))
{
var t = g.First();
var c = g.Count();
var button = new Editor.Button("", this) { MouseLeftPress = () => ToggleTag(t) };
button.OnPaintOverride = () => PaintTagButton(t, c, button.LocalRect, tags.Has(t));
_tagsPopupGrid.AddCell(i % 2, i / 2, button);
++i;
}
}
private static bool PaintTagButton(string tagText, int count, Rect rect, bool has)
{
var alpha = Paint.HasMouseOver ? 1.0f : 0.7f;
var tagColor = Theme.Blue;
Color bg = Theme.TextControl.WithAlpha(0.1f);
Color color = Theme.TextControl.WithAlpha(0.7f);
if(Paint.HasMouseOver)
{
bg = Theme.TextControl.WithAlpha(0.2f);
color = Theme.TextControl;
}
if(has)
{
bg = tagColor.Darken(Paint.HasMouseOver ? 0.5f : 0.6f);
color = Paint.HasMouseOver ? Color.White : tagColor;
}
Paint.SetDefaultFont(8);
Paint.Antialiasing = true;
Paint.TextAntialiasing = true;
//if ( Paint.HasMouseOver || has )
{
Paint.SetBrush(bg);
Paint.ClearPen();
Paint.DrawRect(rect.Shrink(2), 3);
}
Paint.SetPen(color.WithAlphaMultiplied(0.9f * alpha));
Paint.ClearBrush();
Paint.DrawText(rect.Shrink(10, 0), tagText.ToLower(), TextFlag.LeftCenter);
Paint.SetDefaultFont(7);
Paint.SetPen(color.WithAlphaMultiplied(0.5f * alpha));
Paint.DrawText(rect.Shrink(10, 0), $"{count}", TextFlag.RightCenter);
return true;
}
}
file class TagButton : Widget
{
public string TagText { get; set; } = string.Empty;
public TagButton(Widget parent) : base(parent)
{
SetSizeMode(SizeMode.CanShrink, SizeMode.Default);
}
protected override Vector2 SizeHint()
{
Paint.SetDefaultFont(7);
return Paint.MeasureText(TagText.ToLower()) + new Vector2(8, 0);
}
protected override void OnPaint()
{
var alpha = Paint.HasMouseOver ? 1.0f : 0.7f;
var color = Theme.Blue;
Paint.SetDefaultFont(7);
Paint.Antialiasing = true;
Paint.TextAntialiasing = true;
Paint.SetBrush(color.Darken(0.3f).WithAlpha(0.6f * alpha));
Paint.ClearPen();
Paint.DrawRect(LocalRect, 3);
Paint.SetPen(color.WithAlpha(0.9f * alpha));
Paint.ClearBrush();
Paint.DrawText(LocalRect.Shrink(4, 0), TagText.ToLower());
}
}