2021-04-28 14:14:48 +08:00
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
2021-04-30 11:35:58 +08:00
using System ;
2021-05-14 15:03:22 +08:00
using System.Collections.Generic ;
2023-01-26 16:46:19 +08:00
using System.Diagnostics ;
2022-03-23 14:11:35 +08:00
using System.IO ;
2021-04-30 11:35:58 +08:00
using System.Linq ;
2022-03-23 14:11:35 +08:00
using System.Threading.Tasks ;
2021-04-29 12:53:01 +08:00
using osu.Framework.Allocation ;
2021-05-11 16:00:56 +08:00
using osu.Framework.Bindables ;
2021-04-28 14:14:48 +08:00
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Containers ;
2022-03-02 19:05:37 +08:00
using osu.Framework.Graphics.UserInterface ;
2022-07-22 12:10:18 +08:00
using osu.Framework.Input ;
using osu.Framework.Input.Bindings ;
2021-04-29 16:20:22 +08:00
using osu.Framework.Input.Events ;
2022-07-20 04:18:19 +08:00
using osu.Framework.Localisation ;
2021-04-30 11:35:58 +08:00
using osu.Framework.Testing ;
2022-03-23 14:11:35 +08:00
using osu.Game.Database ;
2021-04-29 16:26:55 +08:00
using osu.Game.Graphics ;
using osu.Game.Graphics.Containers ;
2021-04-28 14:14:48 +08:00
using osu.Game.Graphics.Cursor ;
2021-05-10 21:43:48 +08:00
using osu.Game.Graphics.UserInterface ;
2022-07-20 04:18:19 +08:00
using osu.Game.Localisation ;
using osu.Game.Overlays.OSD ;
2023-02-03 17:53:22 +08:00
using osu.Game.Screens.Edit ;
2022-03-15 15:36:04 +08:00
using osu.Game.Screens.Edit.Components ;
2022-03-02 19:05:37 +08:00
using osu.Game.Screens.Edit.Components.Menus ;
2023-01-26 17:21:04 +08:00
using osu.Game.Skinning ;
2021-04-28 14:14:48 +08:00
2023-01-26 17:21:04 +08:00
namespace osu.Game.Overlays.SkinEditor
2021-04-28 14:14:48 +08:00
{
2021-05-11 16:49:00 +08:00
[Cached(typeof(SkinEditor))]
2023-02-03 17:53:22 +08:00
public partial class SkinEditor : VisibilityContainer , ICanAcceptFiles , IKeyBindingHandler < PlatformAction > , IEditorChangeHandler
2021-04-28 14:14:48 +08:00
{
2023-02-02 17:33:45 +08:00
public const double TRANSITION_DURATION = 300 ;
2021-04-29 16:20:22 +08:00
2022-06-06 17:02:42 +08:00
public const float MENU_HEIGHT = 40 ;
2023-02-15 15:01:26 +08:00
public readonly BindableList < ISerialisableDrawable > SelectedComponents = new BindableList < ISerialisableDrawable > ( ) ;
2021-05-13 12:04:17 +08:00
protected override bool StartHidden = > true ;
2023-01-26 16:46:19 +08:00
private Drawable targetScreen = null ! ;
2021-04-29 12:53:01 +08:00
2023-01-26 16:46:19 +08:00
private OsuTextFlowContainer headerText = null ! ;
2021-04-29 16:26:55 +08:00
2023-01-26 16:46:19 +08:00
private Bindable < Skin > currentSkin = null ! ;
2021-05-11 16:49:00 +08:00
2023-01-26 16:46:19 +08:00
[Resolved]
private OsuGame ? game { get ; set ; }
2022-03-23 14:11:35 +08:00
2021-05-10 21:43:48 +08:00
[Resolved]
2023-01-26 16:46:19 +08:00
private SkinManager skins { get ; set ; } = null ! ;
2021-05-10 21:43:48 +08:00
2021-05-11 17:07:58 +08:00
[Resolved]
2023-01-26 16:46:19 +08:00
private OsuColour colours { get ; set ; } = null ! ;
2021-05-11 16:49:00 +08:00
2022-04-01 14:49:05 +08:00
[Resolved]
2023-01-26 16:46:19 +08:00
private RealmAccess realm { get ; set ; } = null ! ;
2022-04-01 14:49:05 +08:00
2023-01-26 16:46:19 +08:00
[Resolved]
private SkinEditorOverlay ? skinEditorOverlay { get ; set ; }
2022-03-28 19:43:23 +08:00
2022-03-15 14:33:01 +08:00
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider ( OverlayColourScheme . Blue ) ;
2021-05-12 16:42:04 +08:00
private bool hasBegunMutating ;
2023-01-26 16:46:19 +08:00
private Container ? content ;
2022-03-11 22:08:40 +08:00
2023-01-26 16:46:19 +08:00
private EditorSidebar componentsSidebar = null ! ;
private EditorSidebar settingsSidebar = null ! ;
2022-03-11 22:30:46 +08:00
2023-02-03 17:53:22 +08:00
private SkinEditorChangeHandler ? changeHandler ;
private EditorMenuItem undoMenuItem = null ! ;
private EditorMenuItem redoMenuItem = null ! ;
2023-01-26 16:46:19 +08:00
[Resolved]
private OnScreenDisplay ? onScreenDisplay { get ; set ; }
2022-07-20 04:18:19 +08:00
2022-03-15 17:00:32 +08:00
public SkinEditor ( )
2021-04-28 14:14:48 +08:00
{
2022-03-15 17:00:32 +08:00
}
2022-03-11 22:08:40 +08:00
2022-03-15 17:00:32 +08:00
public SkinEditor ( Drawable targetScreen )
{
2022-03-11 22:08:40 +08:00
UpdateTargetScreen ( targetScreen ) ;
2021-04-28 14:14:48 +08:00
}
2021-04-29 12:53:01 +08:00
[BackgroundDependencyLoader]
2021-05-11 17:07:58 +08:00
private void load ( )
2021-04-28 14:14:48 +08:00
{
2022-03-15 17:00:32 +08:00
RelativeSizeAxes = Axes . Both ;
2021-04-28 14:14:48 +08:00
InternalChild = new OsuContextMenuContainer
{
RelativeSizeAxes = Axes . Both ,
2022-03-15 15:57:39 +08:00
Child = new GridContainer
2021-04-28 14:14:48 +08:00
{
2022-03-15 15:57:39 +08:00
RelativeSizeAxes = Axes . Both ,
RowDimensions = new [ ]
2021-04-29 16:26:55 +08:00
{
2022-03-15 15:57:39 +08:00
new Dimension ( GridSizeMode . AutoSize ) ,
new Dimension ( GridSizeMode . AutoSize ) ,
new Dimension ( ) ,
} ,
Content = new [ ]
{
new Drawable [ ]
2022-03-02 19:05:37 +08:00
{
2022-03-15 15:57:39 +08:00
new Container
2022-03-02 19:05:37 +08:00
{
2023-01-17 00:55:28 +08:00
Name = @"Menu container" ,
2022-03-15 15:57:39 +08:00
RelativeSizeAxes = Axes . X ,
Depth = float . MinValue ,
2022-06-06 17:02:42 +08:00
Height = MENU_HEIGHT ,
2022-03-15 15:57:39 +08:00
Children = new Drawable [ ]
2022-03-02 19:05:37 +08:00
{
2022-03-15 15:57:39 +08:00
new EditorMenuBar
2022-03-02 19:05:37 +08:00
{
2022-03-15 15:57:39 +08:00
Anchor = Anchor . CentreLeft ,
Origin = Anchor . CentreLeft ,
RelativeSizeAxes = Axes . Both ,
2022-03-02 19:05:37 +08:00
Items = new [ ]
{
2023-01-17 00:39:50 +08:00
new MenuItem ( CommonStrings . MenuBarFile )
2022-03-15 15:57:39 +08:00
{
Items = new [ ]
{
2023-02-02 17:42:33 +08:00
new EditorMenuItem ( Resources . Localisation . Web . CommonStrings . ButtonsSave , MenuItemType . Standard , ( ) = > Save ( ) ) ,
2023-01-17 00:39:50 +08:00
new EditorMenuItem ( CommonStrings . RevertToDefault , MenuItemType . Destructive , revert ) ,
2022-03-15 15:57:39 +08:00
new EditorMenuItemSpacer ( ) ,
2023-01-17 00:39:50 +08:00
new EditorMenuItem ( CommonStrings . Exit , MenuItemType . Standard , ( ) = > skinEditorOverlay ? . Hide ( ) ) ,
2022-03-15 15:57:39 +08:00
} ,
} ,
2023-02-03 17:53:22 +08:00
new MenuItem ( CommonStrings . MenuBarEdit )
{
Items = new [ ]
{
undoMenuItem = new EditorMenuItem ( CommonStrings . Undo , MenuItemType . Standard , Undo ) ,
redoMenuItem = new EditorMenuItem ( CommonStrings . Redo , MenuItemType . Standard , Redo ) ,
}
} ,
2022-03-15 15:57:39 +08:00
}
2022-03-02 19:05:37 +08:00
} ,
2022-03-15 15:57:39 +08:00
headerText = new OsuTextFlowContainer
{
TextAnchor = Anchor . TopRight ,
Padding = new MarginPadding ( 5 ) ,
Anchor = Anchor . TopRight ,
Origin = Anchor . TopRight ,
AutoSizeAxes = Axes . X ,
RelativeSizeAxes = Axes . Y ,
} ,
} ,
2022-03-02 19:05:37 +08:00
} ,
} ,
2022-03-15 15:57:39 +08:00
new Drawable [ ]
2021-05-10 21:43:48 +08:00
{
2022-03-15 15:57:39 +08:00
new SkinEditorSceneLibrary
{
RelativeSizeAxes = Axes . X ,
} ,
2021-05-10 21:43:48 +08:00
} ,
2022-03-15 15:57:39 +08:00
new Drawable [ ]
2021-05-11 10:57:12 +08:00
{
2022-03-15 15:57:39 +08:00
new GridContainer
2021-05-11 10:57:12 +08:00
{
2022-03-15 15:57:39 +08:00
RelativeSizeAxes = Axes . Both ,
ColumnDimensions = new [ ]
2021-05-11 17:37:41 +08:00
{
2022-03-15 15:57:39 +08:00
new Dimension ( GridSizeMode . AutoSize ) ,
new Dimension ( ) ,
new Dimension ( GridSizeMode . AutoSize ) ,
2021-05-11 17:37:41 +08:00
} ,
2022-03-15 15:57:39 +08:00
Content = new [ ]
2022-03-11 22:30:46 +08:00
{
2022-03-15 15:57:39 +08:00
new Drawable [ ]
2022-03-15 15:36:04 +08:00
{
2022-03-15 17:57:53 +08:00
componentsSidebar = new EditorSidebar ( ) ,
2022-03-15 15:57:39 +08:00
content = new Container
{
Depth = float . MaxValue ,
RelativeSizeAxes = Axes . Both ,
} ,
2022-03-15 16:41:20 +08:00
settingsSidebar = new EditorSidebar ( ) ,
2022-03-15 15:36:04 +08:00
}
2022-03-15 15:57:39 +08:00
}
2021-05-11 17:37:41 +08:00
}
2022-03-15 15:57:39 +08:00
} ,
2021-05-11 17:37:41 +08:00
}
2021-04-28 14:14:48 +08:00
}
} ;
2021-05-11 17:07:58 +08:00
}
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
Show ( ) ;
2021-04-29 16:26:55 +08:00
2022-03-23 14:11:35 +08:00
game ? . RegisterImportHandler ( this ) ;
2021-05-11 17:07:58 +08:00
// as long as the skin editor is loaded, let's make sure we can modify the current skin.
currentSkin = skins . CurrentSkin . GetBoundCopy ( ) ;
// schedule ensures this only happens when the skin editor is visible.
// also avoid some weird endless recursion / bindable feedback loop (something to do with tracking skins across three different bindable types).
// probably something which will be factored out in a future database refactor so not too concerning for now.
2022-06-24 20:25:23 +08:00
currentSkin . BindValueChanged ( _ = >
2021-05-12 16:42:04 +08:00
{
hasBegunMutating = false ;
Scheduler . AddOnce ( skinChanged ) ;
} , true ) ;
2022-03-11 22:30:46 +08:00
2022-06-24 20:25:23 +08:00
SelectedComponents . BindCollectionChanged ( ( _ , _ ) = > Scheduler . AddOnce ( populateSettings ) , true ) ;
2021-05-11 17:07:58 +08:00
}
2022-07-22 12:10:18 +08:00
public bool OnPressed ( KeyBindingPressEvent < PlatformAction > e )
{
switch ( e . Action )
{
2023-02-03 17:53:22 +08:00
case PlatformAction . Undo :
Undo ( ) ;
return true ;
case PlatformAction . Redo :
Redo ( ) ;
return true ;
2022-07-22 12:10:18 +08:00
case PlatformAction . Save :
if ( e . Repeat )
return false ;
Save ( ) ;
return true ;
}
return false ;
}
public void OnReleased ( KeyBindingReleaseEvent < PlatformAction > e )
{
}
2022-03-11 22:08:40 +08:00
public void UpdateTargetScreen ( Drawable targetScreen )
{
this . targetScreen = targetScreen ;
2023-02-03 17:53:22 +08:00
changeHandler ? . Dispose ( ) ;
2022-03-11 22:30:46 +08:00
SelectedComponents . Clear ( ) ;
2022-03-15 17:00:32 +08:00
2022-04-07 18:12:10 +08:00
// Immediately clear the previous blueprint container to ensure it doesn't try to interact with the old target.
2022-04-07 22:42:42 +08:00
content ? . Clear ( ) ;
2023-01-26 16:46:19 +08:00
2022-03-11 22:08:40 +08:00
Scheduler . AddOnce ( loadBlueprintContainer ) ;
2022-03-15 15:57:39 +08:00
Scheduler . AddOnce ( populateSettings ) ;
2022-03-11 22:08:40 +08:00
2022-03-15 17:57:53 +08:00
void loadBlueprintContainer ( )
{
2023-01-26 16:46:19 +08:00
Debug . Assert ( content ! = null ) ;
2023-02-03 17:53:22 +08:00
changeHandler = new SkinEditorChangeHandler ( targetScreen ) ;
changeHandler . CanUndo . BindValueChanged ( v = > undoMenuItem . Action . Disabled = ! v . NewValue , true ) ;
changeHandler . CanRedo . BindValueChanged ( v = > redoMenuItem . Action . Disabled = ! v . NewValue , true ) ;
2022-03-15 17:57:53 +08:00
content . Child = new SkinBlueprintContainer ( targetScreen ) ;
componentsSidebar . Child = new SkinComponentToolbox ( getFirstTarget ( ) as CompositeDrawable )
{
RequestPlacement = placeComponent
} ;
}
2022-03-11 22:08:40 +08:00
}
2021-05-11 17:07:58 +08:00
private void skinChanged ( )
{
headerText . Clear ( ) ;
2023-01-17 00:55:28 +08:00
headerText . AddParagraph ( SkinEditorStrings . SkinEditor , cp = > cp . Font = OsuFont . Default . With ( size : 16 ) ) ;
2021-05-11 17:07:58 +08:00
headerText . NewParagraph ( ) ;
2023-02-02 07:44:00 +08:00
headerText . AddText ( SkinEditorStrings . CurrentlyEditing , cp = >
2021-04-29 16:26:55 +08:00
{
cp . Font = OsuFont . Default . With ( size : 12 ) ;
cp . Colour = colours . Yellow ;
} ) ;
2021-05-11 17:07:58 +08:00
2023-02-02 07:44:00 +08:00
headerText . AddText ( $" {currentSkin.Value.SkinInfo}" , cp = >
2021-05-11 17:07:58 +08:00
{
cp . Font = OsuFont . Default . With ( size : 12 , weight : FontWeight . Bold ) ;
cp . Colour = colours . Yellow ;
} ) ;
skins . EnsureMutableSkin ( ) ;
2021-05-12 16:42:04 +08:00
hasBegunMutating = true ;
2021-04-28 14:14:48 +08:00
}
2021-04-29 12:53:01 +08:00
2021-04-30 11:35:58 +08:00
private void placeComponent ( Type type )
2022-03-23 14:11:35 +08:00
{
2023-02-15 15:01:26 +08:00
if ( ! ( Activator . CreateInstance ( type ) is ISerialisableDrawable component ) )
throw new InvalidOperationException ( $"Attempted to instantiate a component for placement which was not an {typeof(ISerialisableDrawable)}." ) ;
2022-03-23 14:11:35 +08:00
placeComponent ( component ) ;
}
2023-02-15 15:01:26 +08:00
private void placeComponent ( ISerialisableDrawable component , bool applyDefaults = true )
2021-04-30 11:35:58 +08:00
{
2022-03-15 17:57:53 +08:00
var targetContainer = getFirstTarget ( ) ;
2021-05-12 13:11:40 +08:00
if ( targetContainer = = null )
return ;
var drawableComponent = ( Drawable ) component ;
2022-04-01 15:16:49 +08:00
if ( applyDefaults )
{
// give newly added components a sane starting location.
drawableComponent . Origin = Anchor . TopCentre ;
drawableComponent . Anchor = Anchor . TopCentre ;
drawableComponent . Y = targetContainer . DrawSize . Y / 2 ;
}
2021-04-30 11:35:58 +08:00
2021-05-12 13:11:40 +08:00
targetContainer . Add ( component ) ;
2021-05-11 16:49:00 +08:00
SelectedComponents . Clear ( ) ;
2021-05-12 13:02:20 +08:00
SelectedComponents . Add ( component ) ;
2021-05-11 10:57:12 +08:00
}
2021-04-30 11:35:58 +08:00
2022-03-15 17:00:32 +08:00
private void populateSettings ( )
2022-03-11 22:30:46 +08:00
{
2022-03-15 16:41:20 +08:00
settingsSidebar . Clear ( ) ;
2022-03-11 22:30:46 +08:00
2022-03-15 16:41:20 +08:00
foreach ( var component in SelectedComponents . OfType < Drawable > ( ) )
settingsSidebar . Add ( new SkinSettingsToolbox ( component ) ) ;
2022-03-11 22:30:46 +08:00
}
2023-02-15 17:31:55 +08:00
private IEnumerable < SkinComponentsContainer > availableTargets = > targetScreen . ChildrenOfType < SkinComponentsContainer > ( ) ;
2021-05-14 15:03:22 +08:00
2023-02-15 15:28:42 +08:00
private ISerialisableDrawableContainer ? getFirstTarget ( ) = > availableTargets . FirstOrDefault ( ) ;
2022-03-15 17:57:53 +08:00
2023-02-15 17:31:55 +08:00
private ISerialisableDrawableContainer ? getTarget ( SkinComponentsContainerLookup . TargetArea target )
2021-05-11 10:57:12 +08:00
{
2023-02-15 17:31:55 +08:00
return availableTargets . FirstOrDefault ( c = > c . Lookup . Target = = target ) ;
2021-04-30 11:35:58 +08:00
}
2021-05-11 10:57:12 +08:00
private void revert ( )
{
2023-02-15 17:31:55 +08:00
SkinComponentsContainer [ ] targetContainers = availableTargets . ToArray ( ) ;
2021-05-11 10:57:12 +08:00
foreach ( var t in targetContainers )
{
2021-05-11 16:00:56 +08:00
currentSkin . Value . ResetDrawableTarget ( t ) ;
2021-05-11 10:57:12 +08:00
// add back default components
2023-02-15 17:31:55 +08:00
getTarget ( t . Lookup . Target ) ? . Reload ( ) ;
2021-05-11 10:57:12 +08:00
}
}
2023-02-03 17:53:22 +08:00
protected void Undo ( ) = > changeHandler ? . RestoreState ( - 1 ) ;
protected void Redo ( ) = > changeHandler ? . RestoreState ( 1 ) ;
2023-02-02 17:42:33 +08:00
public void Save ( bool userTriggered = true )
2021-05-10 21:43:48 +08:00
{
2021-05-12 16:42:04 +08:00
if ( ! hasBegunMutating )
return ;
2023-02-15 17:31:55 +08:00
SkinComponentsContainer [ ] targetContainers = availableTargets . ToArray ( ) ;
2021-05-10 21:43:48 +08:00
foreach ( var t in targetContainers )
2021-05-11 16:00:56 +08:00
currentSkin . Value . UpdateDrawableTarget ( t ) ;
2021-05-10 21:43:48 +08:00
2023-02-02 17:42:33 +08:00
// In the case the save was user triggered, always show the save message to make them feel confident.
if ( skins . Save ( skins . CurrentSkin . Value ) | | userTriggered )
onScreenDisplay ? . Display ( new SkinEditorToast ( ToastStrings . SkinSaved , currentSkin . Value . SkinInfo . ToString ( ) ? ? "Unknown" ) ) ;
2021-05-10 21:43:48 +08:00
}
2021-04-29 16:20:22 +08:00
protected override bool OnHover ( HoverEvent e ) = > true ;
protected override bool OnMouseDown ( MouseDownEvent e ) = > true ;
2021-04-29 12:53:01 +08:00
2022-06-20 02:34:52 +08:00
public override void Hide ( )
{
base . Hide ( ) ;
SelectedComponents . Clear ( ) ;
}
2021-04-29 12:53:01 +08:00
protected override void PopIn ( )
{
2023-02-03 01:41:35 +08:00
this . FadeIn ( TRANSITION_DURATION , Easing . OutQuint ) ;
2021-04-29 12:53:01 +08:00
}
protected override void PopOut ( )
{
2021-04-29 16:20:22 +08:00
this . FadeOut ( TRANSITION_DURATION , Easing . OutQuint ) ;
2021-04-29 12:53:01 +08:00
}
2021-05-14 15:03:22 +08:00
2023-02-15 15:01:26 +08:00
public void DeleteItems ( ISerialisableDrawable [ ] items )
2021-05-14 15:03:22 +08:00
{
2021-05-15 04:33:26 +08:00
foreach ( var item in items )
2021-05-14 15:03:22 +08:00
availableTargets . FirstOrDefault ( t = > t . Components . Contains ( item ) ) ? . Remove ( item ) ;
}
2022-03-23 14:11:35 +08:00
2022-04-04 19:30:14 +08:00
#region Drag & drop import handling
2022-03-23 14:11:35 +08:00
public Task Import ( params string [ ] paths )
{
Schedule ( ( ) = >
{
var file = new FileInfo ( paths . First ( ) ) ;
// import to skin
currentSkin . Value . SkinInfo . PerformWrite ( skinInfo = >
{
using ( var contents = file . OpenRead ( ) )
skins . AddFile ( skinInfo , contents , file . Name ) ;
} ) ;
2022-04-01 14:49:05 +08:00
// Even though we are 100% on an update thread, we need to wait for realm callbacks to fire (to correctly invalidate caches in RealmBackedResourceStore).
// See https://github.com/realm/realm-dotnet/discussions/2634#discussioncomment-2483573 for further discussion.
// This is the best we can do for now.
realm . Run ( r = > r . Refresh ( ) ) ;
2023-01-26 16:46:19 +08:00
var skinnableTarget = getFirstTarget ( ) ;
// Import still should happen for now, even if not placeable (as it allows a user to import skin resources that would apply to legacy gameplay skins).
if ( skinnableTarget = = null )
return ;
2022-03-23 14:11:35 +08:00
// place component
2022-04-01 15:16:49 +08:00
var sprite = new SkinnableSprite
2022-03-23 14:11:35 +08:00
{
2022-04-01 15:16:49 +08:00
SpriteName = { Value = file . Name } ,
Origin = Anchor . Centre ,
2023-01-26 16:46:19 +08:00
Position = skinnableTarget . ToLocalSpace ( GetContainingInputManager ( ) . CurrentState . Mouse . Position ) ,
2022-04-01 15:16:49 +08:00
} ;
placeComponent ( sprite , false ) ;
SkinSelectionHandler . ApplyClosestAnchor ( sprite ) ;
2022-03-23 14:11:35 +08:00
} ) ;
return Task . CompletedTask ;
}
2022-12-13 20:03:25 +08:00
Task ICanAcceptFiles . Import ( ImportTask [ ] tasks , ImportParameters parameters ) = > throw new NotImplementedException ( ) ;
2022-03-23 14:11:35 +08:00
public IEnumerable < string > HandledExtensions = > new [ ] { ".jpg" , ".jpeg" , ".png" } ;
2022-04-04 19:30:14 +08:00
#endregion
protected override void Dispose ( bool isDisposing )
{
base . Dispose ( isDisposing ) ;
game ? . UnregisterImportHandler ( this ) ;
}
2022-07-20 04:18:19 +08:00
private partial class SkinEditorToast : Toast
{
public SkinEditorToast ( LocalisableString value , string skinDisplayName )
2022-07-22 13:00:29 +08:00
: base ( SkinSettingsStrings . SkinLayoutEditor , value , skinDisplayName )
{
}
2022-07-20 04:18:19 +08:00
}
2023-02-03 17:53:22 +08:00
#region Delegation of IEditorChangeHandler
public event Action ? OnStateChange
{
2023-02-07 15:04:31 +08:00
add = > throw new NotImplementedException ( ) ;
remove = > throw new NotImplementedException ( ) ;
2023-02-03 17:53:22 +08:00
}
2023-02-07 15:07:33 +08:00
private IEditorChangeHandler ? beginChangeHandler ;
public void BeginChange ( )
{
// Change handler may change between begin and end, which can cause unbalanced operations.
// Let's track the one that was used when beginning the change so we can call EndChange on it specifically.
( beginChangeHandler = changeHandler ) ? . BeginChange ( ) ;
}
public void EndChange ( ) = > beginChangeHandler ? . EndChange ( ) ;
2023-02-03 17:53:22 +08:00
public void SaveState ( ) = > changeHandler ? . SaveState ( ) ;
#endregion
2021-04-28 14:14:48 +08:00
}
}