mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-05-24 06:46:26 -04:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
803 lines
18 KiB
C#
803 lines
18 KiB
C#
using Editor.NodeEditor;
|
|
using Sandbox.MovieMaker;
|
|
using Sandbox.MovieMaker.Properties;
|
|
using Sandbox.UI;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
|
|
namespace Editor.MovieMaker;
|
|
|
|
#nullable enable
|
|
|
|
/// <summary>
|
|
/// An item in the <see cref="TrackListWidget"/>, showing the name of a track with buttons to configure it.
|
|
/// </summary>
|
|
public partial class TrackWidget : Widget
|
|
{
|
|
public TrackListWidget TrackList { get; }
|
|
public new TrackWidget? Parent { get; }
|
|
|
|
public new IEnumerable<TrackWidget> Children => _children;
|
|
|
|
public TrackView View { get; }
|
|
|
|
RealTimeSince _timeSinceInteraction = 1000;
|
|
|
|
private readonly Label? _label;
|
|
private readonly Button _collapseButton;
|
|
private readonly Button _lockButton;
|
|
private readonly Layout _childLayout;
|
|
private readonly SynchronizedSet<TrackView, TrackWidget> _children;
|
|
|
|
private ControlWidget? _controlWidget;
|
|
|
|
public TrackWidget( TrackListWidget trackList, TrackWidget? parent, TrackView view )
|
|
: base( (Widget?)parent ?? trackList )
|
|
{
|
|
TrackList = trackList;
|
|
Parent = parent;
|
|
|
|
View = view;
|
|
FocusMode = FocusMode.TabOrClickOrWheel;
|
|
VerticalSizeMode = SizeMode.CanGrow;
|
|
|
|
_children = new SynchronizedSet<TrackView, TrackWidget>(
|
|
AddChildTrack, RemoveChildTrack, UpdateChildTrack );
|
|
|
|
ToolTip = View.Description;
|
|
|
|
View.Changed += View_Changed;
|
|
View.ValueChanged += View_ValueChanged;
|
|
|
|
Layout = Layout.Column();
|
|
|
|
var row = Layout.AddRow();
|
|
|
|
row.Spacing = 4f;
|
|
row.Margin = 4f;
|
|
|
|
_childLayout = Layout.Add( Layout.Column() );
|
|
_childLayout.Margin = new Margin( 8f, 0f, 0f, 0f );
|
|
|
|
_collapseButton = new CollapseButton( this );
|
|
row.Add( _collapseButton );
|
|
|
|
if ( !AddReferenceControl( row ) )
|
|
{
|
|
row.AddSpacingCell( 8f );
|
|
_label = row.Add( new Label( view.Target.Name ) );
|
|
}
|
|
|
|
row.AddStretchCell();
|
|
row.AddSpacingCell( 24f );
|
|
|
|
_lockButton = row.Add( new LockButton( this ) );
|
|
|
|
View_Changed( view );
|
|
}
|
|
|
|
private TrackWidget AddChildTrack( TrackView source ) => new( TrackList, this, source );
|
|
private void RemoveChildTrack( TrackWidget item ) => item.Destroy();
|
|
private bool UpdateChildTrack( TrackView source, TrackWidget item ) => item.UpdateLayout();
|
|
|
|
public bool UpdateLayout()
|
|
{
|
|
_children.Update( View.IsExpanded ? View.Children : [] );
|
|
_childLayout.Clear( false );
|
|
|
|
foreach ( var child in _children )
|
|
{
|
|
_childLayout.Add( child );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private bool AddReferenceControl( Layout layout )
|
|
{
|
|
if ( View.Target is not ITrackReference reference ) return false;
|
|
if ( reference is { IsBound: true, Value: GameObject go } && (go.Flags & GameObjectFlags.Bone) != 0 && View.Parent is not null ) return false;
|
|
|
|
// Add control to retarget a scene reference (Component / GameObject)
|
|
|
|
_controlWidget = null;
|
|
|
|
if ( View.Track is ProjectSequenceTrack )
|
|
{
|
|
//
|
|
}
|
|
else if ( reference is ITrackReference<GameObject> goReference )
|
|
{
|
|
_controlWidget = ControlWidget.Create( EditorTypeLibrary.CreateProperty( reference.Name,
|
|
() => goReference.Value, goReference.Bind ) );
|
|
}
|
|
else
|
|
{
|
|
var helperType = typeof( ReflectionHelper<> ).MakeGenericType( reference.TargetType );
|
|
var createControlMethod = helperType.GetMethod( nameof( ReflectionHelper<IValid>.CreateControlWidget ),
|
|
BindingFlags.Static | BindingFlags.Public )!;
|
|
|
|
_controlWidget = (ControlWidget)createControlMethod.Invoke( null, [View.Track, reference] )!;
|
|
}
|
|
|
|
if ( !_controlWidget.IsValid() ) return false;
|
|
|
|
_controlWidget.MaximumWidth = 300;
|
|
|
|
layout.Add( _controlWidget );
|
|
return true;
|
|
}
|
|
|
|
private void View_Changed( TrackView view )
|
|
{
|
|
var labelColor = new Color( 0.6f, 0.6f, 0.6f );
|
|
|
|
_collapseButton.Visible = view.Children.Count > 0;
|
|
|
|
_lockButton.Update();
|
|
_collapseButton.Update();
|
|
|
|
if ( _controlWidget is not null )
|
|
{
|
|
_controlWidget.Enabled = !View.IsLocked;
|
|
}
|
|
|
|
if ( _label is not null )
|
|
{
|
|
_label.Color = !View.IsLocked ? IsSelected ? Color.White : labelColor : labelColor.Darken( 0.25f );
|
|
}
|
|
|
|
Update();
|
|
}
|
|
|
|
private void View_ValueChanged( TrackView view )
|
|
{
|
|
_timeSinceInteraction = 0f;
|
|
Update();
|
|
}
|
|
|
|
public override void OnDestroyed()
|
|
{
|
|
View.Changed -= View_Changed;
|
|
View.ValueChanged -= View_ValueChanged;
|
|
|
|
base.OnDestroyed();
|
|
}
|
|
|
|
protected override void OnMouseClick( MouseEvent e )
|
|
{
|
|
base.OnMouseClick( e );
|
|
|
|
if ( e.LeftMouseButton )
|
|
{
|
|
View.Select();
|
|
|
|
e.Accepted = true;
|
|
}
|
|
}
|
|
|
|
protected override void OnDoubleClick( MouseEvent e )
|
|
{
|
|
View.InspectProperty();
|
|
|
|
e.Accepted = true;
|
|
}
|
|
|
|
protected override void OnMouseEnter()
|
|
{
|
|
View.IsHovered = true;
|
|
|
|
base.OnMouseEnter();
|
|
}
|
|
|
|
protected override void OnMouseLeave()
|
|
{
|
|
View.IsHovered = false;
|
|
|
|
base.OnMouseLeave();
|
|
}
|
|
|
|
protected override Vector2 SizeHint()
|
|
{
|
|
return 32;
|
|
}
|
|
|
|
public bool IsSelected => View.IsSelected;
|
|
|
|
public Color BackgroundColor
|
|
{
|
|
get
|
|
{
|
|
var canModify = !View.IsLocked;
|
|
|
|
var defaultColor = Theme.SurfaceBackground.LerpTo( Theme.ControlBackground, canModify ? 0f : 0.5f );
|
|
var hoveredColor = defaultColor.Lighten( 0.25f );
|
|
var selectedColor = Color.Lerp( defaultColor, Theme.Primary, canModify ? 0.5f : 0.2f );
|
|
|
|
var isHovered = canModify && View.IsHovered;
|
|
|
|
return IsSelected ? selectedColor
|
|
: isHovered ? hoveredColor
|
|
: defaultColor;
|
|
}
|
|
}
|
|
|
|
protected override void OnPaint()
|
|
{
|
|
Paint.Antialiasing = false;
|
|
Paint.SetBrushAndPen( BackgroundColor );
|
|
Paint.DrawRect( new Rect( LocalRect.Left + 1f, LocalRect.Top + 1f, LocalRect.Width - 2f, Timeline.TrackHeight - 2f ), 4 );
|
|
|
|
if ( _timeSinceInteraction < 2.0f )
|
|
{
|
|
var delta = _timeSinceInteraction.Relative.Remap( 2.0f, 0, 0, 1 );
|
|
Paint.SetBrush( Theme.Yellow.WithAlpha( delta ) );
|
|
Paint.DrawRect( new Rect( LocalRect.Right - 4, LocalRect.Top, 32, Timeline.TrackHeight ) );
|
|
Update();
|
|
}
|
|
}
|
|
|
|
private Menu? _menu;
|
|
|
|
protected override void OnContextMenu( ContextMenuEvent e )
|
|
{
|
|
e.Accepted = true;
|
|
|
|
ShowContextMenu();
|
|
}
|
|
|
|
public void ShowContextMenu()
|
|
{
|
|
_menu = new Menu( this );
|
|
|
|
_menu.AddHeading( $"{View.Title} Track" );
|
|
|
|
if ( View.Track is ProjectSequenceTrack sequenceTrack )
|
|
{
|
|
var rename = _menu.AddMenu( "Rename", "edit" );
|
|
|
|
rename.AddLineEdit( "Name", sequenceTrack.Name, autoFocus: true, onSubmit: OnRename );
|
|
}
|
|
|
|
_menu.AddOption( "Remove", "delete", Remove );
|
|
|
|
if ( CanMoveToRoot )
|
|
{
|
|
_menu.AddOption( "Move to Root", "subdirectory_arrow_left", MoveToRoot );
|
|
}
|
|
|
|
if ( CanMoveToParent( out var parentTrack ) )
|
|
{
|
|
_menu.AddOption( "Move to Parent", "subdirectory_arrow_right", () => MoveToParent( parentTrack ) );
|
|
}
|
|
|
|
if ( CanHaveSubTracks )
|
|
{
|
|
CreateSubTrackMenu( _menu );
|
|
}
|
|
|
|
_menu.OpenAtCursor();
|
|
}
|
|
|
|
private bool? GetAggregateLockState( IEnumerable<TrackView> trackViews )
|
|
{
|
|
var anyLocked = false;
|
|
var anyUnlocked = false;
|
|
|
|
foreach ( var view in trackViews )
|
|
{
|
|
if ( view.IsLockedSelf ) anyLocked = true;
|
|
else anyUnlocked = true;
|
|
}
|
|
|
|
return anyLocked == anyUnlocked ? null : anyLocked;
|
|
}
|
|
|
|
/// <summary>
|
|
/// True if this is a child GameObject track, and not representing a bone object.
|
|
/// </summary>
|
|
private bool CanMoveToRoot
|
|
{
|
|
get
|
|
{
|
|
if ( View is not { Target: ITrackReference<GameObject> reference, Parent: not null } )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Don't allow moving bone tracks to root
|
|
|
|
return !reference.IsBound || (reference.Value?.Flags & GameObjectFlags.Bone) == 0;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Should we show sub-track menu options?
|
|
/// </summary>
|
|
private bool CanHaveSubTracks => View.Track is not ProjectSequenceTrack && (View.Children.Count > 0 || TrackProperty.GetAll( View.Target ).Any());
|
|
|
|
/// <summary>
|
|
/// True if this is a GameObject track, and the object's parent also has a track that
|
|
/// this track isn't parented to.
|
|
/// </summary>
|
|
private bool CanMoveToParent( [NotNullWhen( true )] out IProjectReferenceTrack? parentTrack )
|
|
{
|
|
parentTrack = null;
|
|
|
|
if ( View is not { Target: ITrackReference<GameObject> reference } )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( !reference.IsBound || reference.Value is not { Parent: { } parent } )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
parentTrack = TrackList.Session.GetTrack( parent ) as IProjectReferenceTrack;
|
|
|
|
if ( parentTrack is null )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Already parented
|
|
|
|
return View.Parent?.Track != parentTrack;
|
|
}
|
|
|
|
private record AvailableTrackProperty( string Name, string Category, Type Type, Action Create );
|
|
|
|
private void CreateSubTrackMenu( Menu parent )
|
|
{
|
|
parent.AddHeading( "Sub-Tracks" );
|
|
|
|
var menu = parent.AddMenu( "Add / Remove", "playlist_add" );
|
|
|
|
var session = TrackList.Session;
|
|
var availableTracks = new List<AvailableTrackProperty>();
|
|
|
|
if ( View.Target is ITrackReference<GameObject> { IsBound: true, Value: { Components.Count: > 0 } go } )
|
|
{
|
|
foreach ( var component in go.Components.GetAll() )
|
|
{
|
|
var type = component.GetType();
|
|
|
|
availableTracks.Add( new AvailableTrackProperty( type.Name, "Components", type,
|
|
() => session.GetOrCreateTrack( component ) ) );
|
|
}
|
|
}
|
|
|
|
foreach ( var property in TrackProperty.GetAll( View.Target ) )
|
|
{
|
|
availableTracks.Add( new AvailableTrackProperty( property.Name, property.Category, property.Type,
|
|
() => session.GetOrCreateTrack( View.Track, property.Name ) ) );
|
|
}
|
|
|
|
var categories = availableTracks.GroupBy( x => x.Category ).ToArray();
|
|
|
|
Action? updateActive = null;
|
|
|
|
foreach ( var category in categories.OrderBy( x => x.Key ) )
|
|
{
|
|
var subMenu = categories.Length == 1 ? menu : menu.AddMenu( category.Key );
|
|
|
|
foreach ( var type in category.GroupBy( x => x.Type.ToSimpleString( false ) ).OrderBy( x => x.Key ) )
|
|
{
|
|
if ( category.Key != "Components" )
|
|
{
|
|
subMenu.AddHeading( type.Key ).Color = Theme.TextDisabled;
|
|
}
|
|
|
|
foreach ( var item in type.OrderBy( x => x.Name ) )
|
|
{
|
|
var option = new ToggleOption( item.Name, false, create =>
|
|
{
|
|
using var scope = session.History.Push( $"{(create ? "Create" : "Remove")} Track ({item.Name})" );
|
|
|
|
if ( create )
|
|
{
|
|
item.Create();
|
|
}
|
|
else
|
|
{
|
|
View.Children
|
|
.FirstOrDefault( x => x.Track.Name == item.Name )?
|
|
.Remove();
|
|
}
|
|
|
|
session.TrackList.Update();
|
|
session.ClipModified();
|
|
} );
|
|
|
|
updateActive += () => option.IsActive = View.Children.Any( x => x.Track.Name == item.Name );
|
|
|
|
subMenu.AddWidget( option );
|
|
}
|
|
}
|
|
}
|
|
|
|
menu.AboutToShow += () => updateActive?.Invoke();
|
|
|
|
if ( View.Children.Count > 0 )
|
|
{
|
|
parent.AddOption( "Remove Empty", "cleaning_services", RemoveEmptyChildren );
|
|
}
|
|
|
|
CreatePresetMenu( parent );
|
|
|
|
if ( View.Children.Count <= 0 ) return;
|
|
|
|
parent.AddSeparator();
|
|
|
|
var lockState = GetAggregateLockState( View.Children );
|
|
|
|
if ( lockState != true )
|
|
{
|
|
parent.AddOption( "Lock All", "lock", LockChildren );
|
|
}
|
|
|
|
if ( lockState != false )
|
|
{
|
|
parent.AddOption( "Unlock All", "lock_open", UnlockChildren );
|
|
}
|
|
}
|
|
|
|
private void CreatePresetMenu( Menu parent )
|
|
{
|
|
var session = TrackList.Session;
|
|
|
|
var matching = TrackPreset.BuiltInPresets
|
|
.Concat( session.Config.TrackPresets )
|
|
.Where( x => x.Root.Matches( View.Target ) )
|
|
.ToArray();
|
|
|
|
var canLoad = matching.Length > 0;
|
|
var canSave = View.Children.Any();
|
|
|
|
if ( !canLoad && !canSave ) return;
|
|
|
|
if ( canLoad )
|
|
{
|
|
var menu = parent.AddMenu( "Load Preset", "menu_open" );
|
|
|
|
PopulatePresetMenu( menu, matching, [View] );
|
|
}
|
|
|
|
if ( canSave )
|
|
{
|
|
var menu = parent.AddMenu( "Save Preset", "save" );
|
|
|
|
menu.AddLineEdit( "Title", autoFocus: true, onSubmit: title =>
|
|
{
|
|
if ( string.IsNullOrWhiteSpace( title ) ) return;
|
|
|
|
var preset = new TrackPreset( new TrackPresetMetadata( title ), TrackPresetNode.FromTrackView( View ) );
|
|
|
|
session.Config.TrackPresets.Add( preset );
|
|
session.SaveConfig();
|
|
} );
|
|
}
|
|
}
|
|
|
|
public static void PopulatePresetMenu( Menu menu, IReadOnlyList<TrackPreset> presets, IReadOnlyList<TrackView> targets )
|
|
{
|
|
if ( targets.Count == 0 ) return;
|
|
|
|
var session = targets[0].TrackList.Session;
|
|
|
|
Action? updateActive = null;
|
|
|
|
foreach ( var preset in presets )
|
|
{
|
|
var option = new ToggleOption( preset.Meta.Title, false, create =>
|
|
{
|
|
using var scope = session.History.Push( $"{(create ? "Create" : "Remove")} Preset Tracks ({preset.Meta.Title})" );
|
|
|
|
foreach ( var target in targets )
|
|
{
|
|
if ( create )
|
|
{
|
|
session.LoadPreset( target.Track, preset.Root );
|
|
}
|
|
else
|
|
{
|
|
session.RemovePreset( target.Track, preset.Root );
|
|
}
|
|
}
|
|
|
|
session.TrackList.Update();
|
|
session.ClipModified();
|
|
|
|
updateActive?.Invoke();
|
|
}, TrackPreset.BuiltInPresets.Contains( preset ) ? null : () =>
|
|
{
|
|
session.Config.TrackPresets.Remove( preset );
|
|
session.SaveConfig();
|
|
} );
|
|
|
|
updateActive += () =>
|
|
{
|
|
var matchingCount = targets.Min( x => preset.MatchingTrackCount( x.Track ) );
|
|
|
|
option.Title = $"{preset.Meta.Title} ({matchingCount} / {preset.TrackCount} Tracks)";
|
|
option.IsActive = targets.All( x => preset.Root.AllTracksExist( x.Track ) );
|
|
option.Update();
|
|
};
|
|
|
|
menu.AddWidget( option );
|
|
}
|
|
|
|
menu.AboutToShow += () => updateActive?.Invoke();
|
|
}
|
|
|
|
private void OnRename( string name )
|
|
{
|
|
if ( View.Track is not ProjectSequenceTrack sequenceTrack ) return;
|
|
|
|
sequenceTrack.Name = name;
|
|
|
|
if ( _label is { } label ) label.Text = name;
|
|
}
|
|
|
|
private void Remove()
|
|
{
|
|
using var scope = TrackList.Session.History.Push( "Remove Track(s)" );
|
|
View.Remove();
|
|
}
|
|
|
|
private void MoveToRoot()
|
|
{
|
|
if ( View.Track is not IProjectTrackInternal track ) return;
|
|
if ( View.Parent?.Track is not IProjectTrackInternal parentTrack ) return;
|
|
|
|
parentTrack.RemoveChild( track );
|
|
|
|
TrackList.Session.TrackList.Update();
|
|
}
|
|
|
|
private void MoveToParent( IProjectReferenceTrack parentTrack )
|
|
{
|
|
if ( View.Track is not IProjectTrackInternal track ) return;
|
|
if ( parentTrack is not IProjectTrackInternal newParentTrack ) return;
|
|
|
|
if ( View.Parent?.Track is IProjectTrackInternal currentParentTrack )
|
|
{
|
|
if ( currentParentTrack == parentTrack ) return;
|
|
|
|
currentParentTrack.RemoveChild( track );
|
|
}
|
|
|
|
newParentTrack.AddChild( track );
|
|
|
|
TrackList.Session.TrackList.Update();
|
|
}
|
|
|
|
private void RemoveEmptyChildren()
|
|
{
|
|
foreach ( var child in View.Children.ToArray() )
|
|
{
|
|
RemoveEmptyCore( child );
|
|
}
|
|
|
|
TrackList.Session.TrackList.Update();
|
|
}
|
|
|
|
private static bool RemoveEmptyCore( TrackView view )
|
|
{
|
|
var allChildrenRemoved = true;
|
|
|
|
foreach ( var child in view.Children.ToArray() )
|
|
{
|
|
allChildrenRemoved &= RemoveEmptyCore( child );
|
|
}
|
|
|
|
if ( allChildrenRemoved && view.IsEmpty )
|
|
{
|
|
view.Remove();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private void LockChildren()
|
|
{
|
|
foreach ( var child in View.Children )
|
|
{
|
|
child.IsLockedSelf = true;
|
|
}
|
|
}
|
|
|
|
private void UnlockChildren()
|
|
{
|
|
foreach ( var child in View.Children )
|
|
{
|
|
child.IsLockedSelf = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: surely there's an easier way to stop Menus from closing
|
|
file sealed class ToggleOption : Widget
|
|
{
|
|
private readonly Label _label;
|
|
private readonly Action<bool> _toggled;
|
|
|
|
public new bool Enabled
|
|
{
|
|
get => base.Enabled;
|
|
set
|
|
{
|
|
_label.Color = value ? Theme.TextControl : Theme.TextControl.Darken( 0.5f );
|
|
_label.Update();
|
|
|
|
base.Enabled = value;
|
|
}
|
|
}
|
|
|
|
private bool _isActive;
|
|
|
|
public bool IsActive
|
|
{
|
|
get => _isActive;
|
|
set
|
|
{
|
|
_isActive = value;
|
|
|
|
_label.SetStyles( value ? "font-weight: bold;" : "font-weight: regular;" );
|
|
Update();
|
|
}
|
|
}
|
|
|
|
public string Title
|
|
{
|
|
get => _label.Text;
|
|
set => _label.Text = value;
|
|
}
|
|
|
|
protected override Vector2 SizeHint()
|
|
{
|
|
// So there's enough space for the label to become bold
|
|
|
|
return base.SizeHint() * new Vector2( 1.1f, 1f );
|
|
}
|
|
|
|
public ToggleOption( string title, bool active, Action<bool> toggled, Action? deleted = null )
|
|
{
|
|
Layout = Layout.Row();
|
|
Layout.Margin = new Margin( 40f, 5f, 16f, 5f );
|
|
|
|
_label = new Label( title, this );
|
|
_toggled = toggled;
|
|
|
|
MinimumWidth = 120f;
|
|
|
|
Layout.Add( _label );
|
|
|
|
IsActive = active;
|
|
|
|
if ( deleted is not null )
|
|
{
|
|
Layout.Margin = Layout.Margin with { Right = 8f };
|
|
Layout.Add( new IconButton( "delete", () =>
|
|
{
|
|
Dialog.AskConfirm( deleted, $"Are you sure you want to delete {title}?", "Delete Confirmation" );
|
|
} )
|
|
{
|
|
Foreground = Theme.Red,
|
|
ForegroundActive = Theme.Red
|
|
} );
|
|
}
|
|
}
|
|
|
|
protected override void OnPaint()
|
|
{
|
|
if ( Paint.HasMouseOver )
|
|
{
|
|
Paint.SetBrushAndPen( Theme.SurfaceBackground );
|
|
Paint.DrawRect( LocalRect.Shrink( IsActive ? 0f : 4f, 0f, 4f, 0f ), 3f );
|
|
}
|
|
|
|
if ( IsActive )
|
|
{
|
|
Paint.SetBrushAndPen( Theme.Primary );
|
|
Paint.DrawRect( LocalRect.Contain( new Vector2( 3f, LocalRect.Height ), TextFlag.LeftCenter ) );
|
|
|
|
Paint.SetPen( Theme.Text );
|
|
Paint.DrawIcon( LocalRect with { Left = LocalRect.Left + 16f }, "done", 13f, TextFlag.LeftCenter );
|
|
}
|
|
}
|
|
|
|
protected override void OnMouseReleased( MouseEvent e )
|
|
{
|
|
base.OnMouseReleased( e );
|
|
|
|
IsActive = !IsActive;
|
|
|
|
e.Accepted = true;
|
|
|
|
_toggled.Invoke( IsActive );
|
|
Update();
|
|
}
|
|
}
|
|
|
|
file sealed class LockButton : Button
|
|
{
|
|
public TrackWidget TrackWidget { get; }
|
|
|
|
public LockButton( TrackWidget trackWidget )
|
|
{
|
|
TrackWidget = trackWidget;
|
|
|
|
FixedSize = 24f;
|
|
|
|
ToolTip = "Toggle lock";
|
|
}
|
|
|
|
protected override void OnPaint()
|
|
{
|
|
Paint.SetBrushAndPen( PaintExtensions.PaintSelectColor( Theme.ControlBackground,
|
|
Theme.ControlBackground.Darken( 0.5f ), Theme.Primary ) );
|
|
Paint.DrawRect( LocalRect, 4f );
|
|
|
|
Paint.SetPen( Theme.TextControl );
|
|
Paint.DrawIcon( LocalRect, TrackWidget.View.IsLockedSelf ? "lock" : "lock_open", 12f );
|
|
}
|
|
|
|
protected override void OnClicked()
|
|
{
|
|
using var scope = TrackWidget.TrackList.Session.History.Push( $"{(TrackWidget.View.IsLockedSelf ? "Unlocked" : "Locked")} Track" );
|
|
TrackWidget.View.IsLockedSelf = !TrackWidget.View.IsLockedSelf;
|
|
}
|
|
}
|
|
|
|
file sealed class CollapseButton : Button
|
|
{
|
|
public TrackWidget Track { get; }
|
|
|
|
public CollapseButton( TrackWidget track )
|
|
{
|
|
Track = track;
|
|
FixedSize = 24f;
|
|
|
|
ToolTip = "Toggle expanded";
|
|
}
|
|
|
|
protected override void OnPaint()
|
|
{
|
|
Paint.SetBrushAndPen( PaintExtensions.PaintSelectColor( Theme.ControlBackground,
|
|
Theme.ControlBackground.Darken( 0.5f ), Theme.Primary ) );
|
|
Paint.DrawRect( LocalRect, 4f );
|
|
|
|
Paint.SetPen( Theme.TextControl );
|
|
Paint.DrawIcon( LocalRect, Track.View.IsExpanded ? "remove" : "add", 12f );
|
|
}
|
|
|
|
protected override void OnClicked()
|
|
{
|
|
Track.View.IsExpanded = !Track.View.IsExpanded;
|
|
}
|
|
}
|
|
|
|
file sealed class ReflectionHelper<T>
|
|
where T : class, IValid
|
|
{
|
|
public static ControlWidget CreateControlWidget( IProjectReferenceTrack track, ITrackReference<T> target )
|
|
{
|
|
return ControlWidget.Create( EditorTypeLibrary.CreateProperty( target.Name,
|
|
() => target.Value, value =>
|
|
{
|
|
track.ReferenceId = value switch
|
|
{
|
|
Component cmp => cmp.Id,
|
|
GameObject go => go.Id,
|
|
_ => null
|
|
};
|
|
|
|
target.Bind( value );
|
|
} ) );
|
|
}
|
|
}
|