2021-04-28 06:14:48 +00: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 03:35:58 +00:00
using System ;
2021-05-14 07:03:22 +00:00
using System.Collections.Generic ;
2023-01-26 08:46:19 +00:00
using System.Diagnostics ;
2022-03-23 06:11:35 +00:00
using System.IO ;
2021-04-30 03:35:58 +00:00
using System.Linq ;
2022-03-23 06:11:35 +00:00
using System.Threading.Tasks ;
2023-02-15 10:26:44 +00:00
using Newtonsoft.Json ;
2021-04-29 04:53:01 +00:00
using osu.Framework.Allocation ;
2021-05-11 08:00:56 +00:00
using osu.Framework.Bindables ;
2021-04-28 06:14:48 +00:00
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Containers ;
2022-03-02 11:05:37 +00:00
using osu.Framework.Graphics.UserInterface ;
2022-07-22 04:10:18 +00:00
using osu.Framework.Input ;
using osu.Framework.Input.Bindings ;
2021-04-29 08:20:22 +00:00
using osu.Framework.Input.Events ;
2022-07-19 20:18:19 +00:00
using osu.Framework.Localisation ;
2021-04-30 03:35:58 +00:00
using osu.Framework.Testing ;
2022-03-23 06:11:35 +00:00
using osu.Game.Database ;
2021-04-29 08:26:55 +00:00
using osu.Game.Graphics ;
using osu.Game.Graphics.Containers ;
2021-04-28 06:14:48 +00:00
using osu.Game.Graphics.Cursor ;
2021-05-10 13:43:48 +00:00
using osu.Game.Graphics.UserInterface ;
2022-07-19 20:18:19 +00:00
using osu.Game.Localisation ;
using osu.Game.Overlays.OSD ;
2023-02-03 09:53:22 +00:00
using osu.Game.Screens.Edit ;
2022-03-15 07:36:04 +00:00
using osu.Game.Screens.Edit.Components ;
2022-03-02 11:05:37 +00:00
using osu.Game.Screens.Edit.Components.Menus ;
2023-01-26 09:21:04 +00:00
using osu.Game.Skinning ;
2021-04-28 06:14:48 +00:00
2023-01-26 09:21:04 +00:00
namespace osu.Game.Overlays.SkinEditor
2021-04-28 06:14:48 +00:00
{
2021-05-11 08:49:00 +00:00
[Cached(typeof(SkinEditor))]
2023-02-03 09:53:22 +00:00
public partial class SkinEditor : VisibilityContainer , ICanAcceptFiles , IKeyBindingHandler < PlatformAction > , IEditorChangeHandler
2021-04-28 06:14:48 +00:00
{
2023-02-02 09:33:45 +00:00
public const double TRANSITION_DURATION = 300 ;
2021-04-29 08:20:22 +00:00
2022-06-06 09:02:42 +00:00
public const float MENU_HEIGHT = 40 ;
2023-02-15 07:01:26 +00:00
public readonly BindableList < ISerialisableDrawable > SelectedComponents = new BindableList < ISerialisableDrawable > ( ) ;
2021-05-13 04:04:17 +00:00
protected override bool StartHidden = > true ;
2023-01-26 08:46:19 +00:00
private Drawable targetScreen = null ! ;
2021-04-29 04:53:01 +00:00
2023-01-26 08:46:19 +00:00
private OsuTextFlowContainer headerText = null ! ;
2021-04-29 08:26:55 +00:00
2023-01-26 08:46:19 +00:00
private Bindable < Skin > currentSkin = null ! ;
2021-05-11 08:49:00 +00:00
2023-02-15 10:26:44 +00:00
[Cached]
public readonly EditorClipboard Clipboard = new EditorClipboard ( ) ;
2023-01-26 08:46:19 +00:00
[Resolved]
private OsuGame ? game { get ; set ; }
2022-03-23 06:11:35 +00:00
2021-05-10 13:43:48 +00:00
[Resolved]
2023-01-26 08:46:19 +00:00
private SkinManager skins { get ; set ; } = null ! ;
2021-05-10 13:43:48 +00:00
2021-05-11 09:07:58 +00:00
[Resolved]
2023-01-26 08:46:19 +00:00
private OsuColour colours { get ; set ; } = null ! ;
2021-05-11 08:49:00 +00:00
2022-04-01 06:49:05 +00:00
[Resolved]
2023-01-26 08:46:19 +00:00
private RealmAccess realm { get ; set ; } = null ! ;
2022-04-01 06:49:05 +00:00
2023-01-26 08:46:19 +00:00
[Resolved]
private SkinEditorOverlay ? skinEditorOverlay { get ; set ; }
2022-03-28 11:43:23 +00:00
2022-03-15 06:33:01 +00:00
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider ( OverlayColourScheme . Blue ) ;
2021-05-12 08:42:04 +00:00
private bool hasBegunMutating ;
2023-01-26 08:46:19 +00:00
private Container ? content ;
2022-03-11 14:08:40 +00:00
2023-01-26 08:46:19 +00:00
private EditorSidebar componentsSidebar = null ! ;
private EditorSidebar settingsSidebar = null ! ;
2022-03-11 14:30:46 +00:00
2023-02-03 09:53:22 +00:00
private SkinEditorChangeHandler ? changeHandler ;
private EditorMenuItem undoMenuItem = null ! ;
private EditorMenuItem redoMenuItem = null ! ;
2023-02-15 10:26:44 +00:00
private EditorMenuItem cutMenuItem = null ! ;
private EditorMenuItem copyMenuItem = null ! ;
private EditorMenuItem cloneMenuItem = null ! ;
private EditorMenuItem pasteMenuItem = null ! ;
private readonly BindableWithCurrent < bool > canCut = new BindableWithCurrent < bool > ( ) ;
private readonly BindableWithCurrent < bool > canCopy = new BindableWithCurrent < bool > ( ) ;
private readonly BindableWithCurrent < bool > canPaste = new BindableWithCurrent < bool > ( ) ;
2023-01-26 08:46:19 +00:00
[Resolved]
private OnScreenDisplay ? onScreenDisplay { get ; set ; }
2022-07-19 20:18:19 +00:00
2022-03-15 09:00:32 +00:00
public SkinEditor ( )
2021-04-28 06:14:48 +00:00
{
2022-03-15 09:00:32 +00:00
}
2022-03-11 14:08:40 +00:00
2022-03-15 09:00:32 +00:00
public SkinEditor ( Drawable targetScreen )
{
2022-03-11 14:08:40 +00:00
UpdateTargetScreen ( targetScreen ) ;
2021-04-28 06:14:48 +00:00
}
2021-04-29 04:53:01 +00:00
[BackgroundDependencyLoader]
2021-05-11 09:07:58 +00:00
private void load ( )
2021-04-28 06:14:48 +00:00
{
2022-03-15 09:00:32 +00:00
RelativeSizeAxes = Axes . Both ;
2021-04-28 06:14:48 +00:00
InternalChild = new OsuContextMenuContainer
{
RelativeSizeAxes = Axes . Both ,
2022-03-15 07:57:39 +00:00
Child = new GridContainer
2021-04-28 06:14:48 +00:00
{
2022-03-15 07:57:39 +00:00
RelativeSizeAxes = Axes . Both ,
RowDimensions = new [ ]
2021-04-29 08:26:55 +00:00
{
2022-03-15 07:57:39 +00:00
new Dimension ( GridSizeMode . AutoSize ) ,
new Dimension ( GridSizeMode . AutoSize ) ,
new Dimension ( ) ,
} ,
Content = new [ ]
{
new Drawable [ ]
2022-03-02 11:05:37 +00:00
{
2022-03-15 07:57:39 +00:00
new Container
2022-03-02 11:05:37 +00:00
{
2023-01-16 16:55:28 +00:00
Name = @"Menu container" ,
2022-03-15 07:57:39 +00:00
RelativeSizeAxes = Axes . X ,
Depth = float . MinValue ,
2022-06-06 09:02:42 +00:00
Height = MENU_HEIGHT ,
2022-03-15 07:57:39 +00:00
Children = new Drawable [ ]
2022-03-02 11:05:37 +00:00
{
2022-03-15 07:57:39 +00:00
new EditorMenuBar
2022-03-02 11:05:37 +00:00
{
2022-03-15 07:57:39 +00:00
Anchor = Anchor . CentreLeft ,
Origin = Anchor . CentreLeft ,
RelativeSizeAxes = Axes . Both ,
2022-03-02 11:05:37 +00:00
Items = new [ ]
{
2023-01-16 16:39:50 +00:00
new MenuItem ( CommonStrings . MenuBarFile )
2022-03-15 07:57:39 +00:00
{
Items = new [ ]
{
2023-02-02 09:42:33 +00:00
new EditorMenuItem ( Resources . Localisation . Web . CommonStrings . ButtonsSave , MenuItemType . Standard , ( ) = > Save ( ) ) ,
2023-01-16 16:39:50 +00:00
new EditorMenuItem ( CommonStrings . RevertToDefault , MenuItemType . Destructive , revert ) ,
2022-03-15 07:57:39 +00:00
new EditorMenuItemSpacer ( ) ,
2023-01-16 16:39:50 +00:00
new EditorMenuItem ( CommonStrings . Exit , MenuItemType . Standard , ( ) = > skinEditorOverlay ? . Hide ( ) ) ,
2022-03-15 07:57:39 +00:00
} ,
} ,
2023-02-03 09:53:22 +00:00
new MenuItem ( CommonStrings . MenuBarEdit )
{
Items = new [ ]
{
undoMenuItem = new EditorMenuItem ( CommonStrings . Undo , MenuItemType . Standard , Undo ) ,
redoMenuItem = new EditorMenuItem ( CommonStrings . Redo , MenuItemType . Standard , Redo ) ,
2023-02-15 10:26:44 +00:00
new EditorMenuItemSpacer ( ) ,
cutMenuItem = new EditorMenuItem ( CommonStrings . Cut , MenuItemType . Standard , Cut ) ,
copyMenuItem = new EditorMenuItem ( CommonStrings . Copy , MenuItemType . Standard , Copy ) ,
pasteMenuItem = new EditorMenuItem ( CommonStrings . Paste , MenuItemType . Standard , Paste ) ,
cloneMenuItem = new EditorMenuItem ( CommonStrings . Clone , MenuItemType . Standard , Clone ) ,
2023-02-03 09:53:22 +00:00
}
} ,
2022-03-15 07:57:39 +00:00
}
2022-03-02 11:05:37 +00:00
} ,
2022-03-15 07:57:39 +00: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 11:05:37 +00:00
} ,
} ,
2022-03-15 07:57:39 +00:00
new Drawable [ ]
2021-05-10 13:43:48 +00:00
{
2022-03-15 07:57:39 +00:00
new SkinEditorSceneLibrary
{
RelativeSizeAxes = Axes . X ,
} ,
2021-05-10 13:43:48 +00:00
} ,
2022-03-15 07:57:39 +00:00
new Drawable [ ]
2021-05-11 02:57:12 +00:00
{
2022-03-15 07:57:39 +00:00
new GridContainer
2021-05-11 02:57:12 +00:00
{
2022-03-15 07:57:39 +00:00
RelativeSizeAxes = Axes . Both ,
ColumnDimensions = new [ ]
2021-05-11 09:37:41 +00:00
{
2022-03-15 07:57:39 +00:00
new Dimension ( GridSizeMode . AutoSize ) ,
new Dimension ( ) ,
new Dimension ( GridSizeMode . AutoSize ) ,
2021-05-11 09:37:41 +00:00
} ,
2022-03-15 07:57:39 +00:00
Content = new [ ]
2022-03-11 14:30:46 +00:00
{
2022-03-15 07:57:39 +00:00
new Drawable [ ]
2022-03-15 07:36:04 +00:00
{
2022-03-15 09:57:53 +00:00
componentsSidebar = new EditorSidebar ( ) ,
2022-03-15 07:57:39 +00:00
content = new Container
{
Depth = float . MaxValue ,
RelativeSizeAxes = Axes . Both ,
} ,
2022-03-15 08:41:20 +00:00
settingsSidebar = new EditorSidebar ( ) ,
2022-03-15 07:36:04 +00:00
}
2022-03-15 07:57:39 +00:00
}
2021-05-11 09:37:41 +00:00
}
2022-03-15 07:57:39 +00:00
} ,
2021-05-11 09:37:41 +00:00
}
2021-04-28 06:14:48 +00:00
}
} ;
2021-05-11 09:07:58 +00:00
}
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
2023-02-15 10:26:44 +00:00
canCut . Current . BindValueChanged ( cut = > cutMenuItem . Action . Disabled = ! cut . NewValue , true ) ;
canCopy . Current . BindValueChanged ( copy = >
{
copyMenuItem . Action . Disabled = ! copy . NewValue ;
cloneMenuItem . Action . Disabled = ! copy . NewValue ;
} , true ) ;
canPaste . Current . BindValueChanged ( paste = > pasteMenuItem . Action . Disabled = ! paste . NewValue , true ) ;
SelectedComponents . BindCollectionChanged ( ( _ , _ ) = >
{
canCopy . Value = canCut . Value = SelectedComponents . Any ( ) ;
} , true ) ;
Clipboard . Content . BindValueChanged ( content = > canPaste . Value = ! string . IsNullOrEmpty ( content . NewValue ) , true ) ;
2021-05-11 09:07:58 +00:00
Show ( ) ;
2021-04-29 08:26:55 +00:00
2022-03-23 06:11:35 +00:00
game ? . RegisterImportHandler ( this ) ;
2021-05-11 09:07:58 +00: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 12:25:23 +00:00
currentSkin . BindValueChanged ( _ = >
2021-05-12 08:42:04 +00:00
{
hasBegunMutating = false ;
Scheduler . AddOnce ( skinChanged ) ;
} , true ) ;
2022-03-11 14:30:46 +00:00
2022-06-24 12:25:23 +00:00
SelectedComponents . BindCollectionChanged ( ( _ , _ ) = > Scheduler . AddOnce ( populateSettings ) , true ) ;
2021-05-11 09:07:58 +00:00
}
2022-07-22 04:10:18 +00:00
public bool OnPressed ( KeyBindingPressEvent < PlatformAction > e )
{
switch ( e . Action )
{
2023-02-15 10:26:44 +00:00
case PlatformAction . Cut :
Cut ( ) ;
return true ;
case PlatformAction . Copy :
Copy ( ) ;
return true ;
case PlatformAction . Paste :
Paste ( ) ;
return true ;
2023-02-03 09:53:22 +00:00
case PlatformAction . Undo :
Undo ( ) ;
return true ;
case PlatformAction . Redo :
Redo ( ) ;
return true ;
2022-07-22 04:10:18 +00:00
case PlatformAction . Save :
if ( e . Repeat )
return false ;
Save ( ) ;
return true ;
}
return false ;
}
public void OnReleased ( KeyBindingReleaseEvent < PlatformAction > e )
{
}
2022-03-11 14:08:40 +00:00
public void UpdateTargetScreen ( Drawable targetScreen )
{
this . targetScreen = targetScreen ;
2023-02-03 09:53:22 +00:00
changeHandler ? . Dispose ( ) ;
2022-03-11 14:30:46 +00:00
SelectedComponents . Clear ( ) ;
2022-03-15 09:00:32 +00:00
2022-04-07 10:12:10 +00:00
// Immediately clear the previous blueprint container to ensure it doesn't try to interact with the old target.
2022-04-07 14:42:42 +00:00
content ? . Clear ( ) ;
2023-01-26 08:46:19 +00:00
2022-03-11 14:08:40 +00:00
Scheduler . AddOnce ( loadBlueprintContainer ) ;
2022-03-15 07:57:39 +00:00
Scheduler . AddOnce ( populateSettings ) ;
2022-03-11 14:08:40 +00:00
2022-03-15 09:57:53 +00:00
void loadBlueprintContainer ( )
{
2023-01-26 08:46:19 +00:00
Debug . Assert ( content ! = null ) ;
2023-02-03 09:53:22 +00: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 09:57:53 +00:00
content . Child = new SkinBlueprintContainer ( targetScreen ) ;
componentsSidebar . Child = new SkinComponentToolbox ( getFirstTarget ( ) as CompositeDrawable )
{
RequestPlacement = placeComponent
} ;
}
2022-03-11 14:08:40 +00:00
}
2021-05-11 09:07:58 +00:00
private void skinChanged ( )
{
headerText . Clear ( ) ;
2023-01-16 16:55:28 +00:00
headerText . AddParagraph ( SkinEditorStrings . SkinEditor , cp = > cp . Font = OsuFont . Default . With ( size : 16 ) ) ;
2021-05-11 09:07:58 +00:00
headerText . NewParagraph ( ) ;
2023-02-01 23:44:00 +00:00
headerText . AddText ( SkinEditorStrings . CurrentlyEditing , cp = >
2021-04-29 08:26:55 +00:00
{
cp . Font = OsuFont . Default . With ( size : 12 ) ;
cp . Colour = colours . Yellow ;
} ) ;
2021-05-11 09:07:58 +00:00
2023-02-01 23:44:00 +00:00
headerText . AddText ( $" {currentSkin.Value.SkinInfo}" , cp = >
2021-05-11 09:07:58 +00:00
{
cp . Font = OsuFont . Default . With ( size : 12 , weight : FontWeight . Bold ) ;
cp . Colour = colours . Yellow ;
} ) ;
skins . EnsureMutableSkin ( ) ;
2021-05-12 08:42:04 +00:00
hasBegunMutating = true ;
2021-04-28 06:14:48 +00:00
}
2021-04-29 04:53:01 +00:00
2021-04-30 03:35:58 +00:00
private void placeComponent ( Type type )
2022-03-23 06:11:35 +00:00
{
2023-02-15 07:01:26 +00: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 06:11:35 +00:00
placeComponent ( component ) ;
}
2023-02-15 07:01:26 +00:00
private void placeComponent ( ISerialisableDrawable component , bool applyDefaults = true )
2021-04-30 03:35:58 +00:00
{
2022-03-15 09:57:53 +00:00
var targetContainer = getFirstTarget ( ) ;
2021-05-12 05:11:40 +00:00
if ( targetContainer = = null )
return ;
var drawableComponent = ( Drawable ) component ;
2022-04-01 07:16:49 +00: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 03:35:58 +00:00
2021-05-12 05:11:40 +00:00
targetContainer . Add ( component ) ;
2021-05-11 08:49:00 +00:00
SelectedComponents . Clear ( ) ;
2021-05-12 05:02:20 +00:00
SelectedComponents . Add ( component ) ;
2021-05-11 02:57:12 +00:00
}
2021-04-30 03:35:58 +00:00
2022-03-15 09:00:32 +00:00
private void populateSettings ( )
2022-03-11 14:30:46 +00:00
{
2022-03-15 08:41:20 +00:00
settingsSidebar . Clear ( ) ;
2022-03-11 14:30:46 +00:00
2022-03-15 08:41:20 +00:00
foreach ( var component in SelectedComponents . OfType < Drawable > ( ) )
settingsSidebar . Add ( new SkinSettingsToolbox ( component ) ) ;
2022-03-11 14:30:46 +00:00
}
2023-02-15 09:31:55 +00:00
private IEnumerable < SkinComponentsContainer > availableTargets = > targetScreen . ChildrenOfType < SkinComponentsContainer > ( ) ;
2021-05-14 07:03:22 +00:00
2023-02-15 07:28:42 +00:00
private ISerialisableDrawableContainer ? getFirstTarget ( ) = > availableTargets . FirstOrDefault ( ) ;
2022-03-15 09:57:53 +00:00
2023-02-15 09:31:55 +00:00
private ISerialisableDrawableContainer ? getTarget ( SkinComponentsContainerLookup . TargetArea target )
2021-05-11 02:57:12 +00:00
{
2023-02-15 09:31:55 +00:00
return availableTargets . FirstOrDefault ( c = > c . Lookup . Target = = target ) ;
2021-04-30 03:35:58 +00:00
}
2021-05-11 02:57:12 +00:00
private void revert ( )
{
2023-02-15 09:31:55 +00:00
SkinComponentsContainer [ ] targetContainers = availableTargets . ToArray ( ) ;
2021-05-11 02:57:12 +00:00
foreach ( var t in targetContainers )
{
2021-05-11 08:00:56 +00:00
currentSkin . Value . ResetDrawableTarget ( t ) ;
2021-05-11 02:57:12 +00:00
// add back default components
2023-02-15 09:31:55 +00:00
getTarget ( t . Lookup . Target ) ? . Reload ( ) ;
2021-05-11 02:57:12 +00:00
}
}
2023-02-15 10:26:44 +00:00
protected void Cut ( )
{
Copy ( ) ;
DeleteItems ( SelectedComponents . ToArray ( ) ) ;
}
protected void Copy ( )
{
Clipboard . Content . Value = JsonConvert . SerializeObject ( SelectedComponents . Cast < Drawable > ( ) . Select ( s = > s . CreateSerialisedInfo ( ) ) . ToArray ( ) ) ;
}
protected void Clone ( )
{
// Avoid attempting to clone if copying is not available (as it may result in pasting something unexpected).
if ( ! canCopy . Value )
return ;
// This is an initial implementation just to get an idea of how people used this function.
// There are a couple of differences from osu!stable's implementation which will require more work to match:
// - The "clipboard" is not populated during the duplication process.
// - The duplicated hitobjects are inserted after the original pattern (add one beat_length and then quantize using beat snap).
// - The duplicated hitobjects are selected (but this is also applied for all paste operations so should be changed there).
Copy ( ) ;
Paste ( ) ;
}
protected void Paste ( )
{
var drawableInfo = JsonConvert . DeserializeObject < SerialisedDrawableInfo [ ] > ( Clipboard . Content . Value ) ;
if ( drawableInfo = = null )
return ;
var instances = drawableInfo . Select ( d = > d . CreateInstance ( ) )
. OfType < ISerialisableDrawable > ( )
. ToArray ( ) ;
foreach ( var i in instances )
2023-02-15 10:34:42 +00:00
placeComponent ( i , false ) ;
2023-02-15 10:26:44 +00:00
SelectedComponents . Clear ( ) ;
SelectedComponents . AddRange ( instances ) ;
}
2023-02-03 09:53:22 +00:00
protected void Undo ( ) = > changeHandler ? . RestoreState ( - 1 ) ;
protected void Redo ( ) = > changeHandler ? . RestoreState ( 1 ) ;
2023-02-02 09:42:33 +00:00
public void Save ( bool userTriggered = true )
2021-05-10 13:43:48 +00:00
{
2021-05-12 08:42:04 +00:00
if ( ! hasBegunMutating )
return ;
2023-02-15 09:31:55 +00:00
SkinComponentsContainer [ ] targetContainers = availableTargets . ToArray ( ) ;
2021-05-10 13:43:48 +00:00
foreach ( var t in targetContainers )
2021-05-11 08:00:56 +00:00
currentSkin . Value . UpdateDrawableTarget ( t ) ;
2021-05-10 13:43:48 +00:00
2023-02-02 09:42:33 +00: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 13:43:48 +00:00
}
2021-04-29 08:20:22 +00:00
protected override bool OnHover ( HoverEvent e ) = > true ;
protected override bool OnMouseDown ( MouseDownEvent e ) = > true ;
2021-04-29 04:53:01 +00:00
2022-06-19 18:34:52 +00:00
public override void Hide ( )
{
base . Hide ( ) ;
SelectedComponents . Clear ( ) ;
}
2021-04-29 04:53:01 +00:00
protected override void PopIn ( )
{
2023-02-02 17:41:35 +00:00
this . FadeIn ( TRANSITION_DURATION , Easing . OutQuint ) ;
2021-04-29 04:53:01 +00:00
}
protected override void PopOut ( )
{
2021-04-29 08:20:22 +00:00
this . FadeOut ( TRANSITION_DURATION , Easing . OutQuint ) ;
2021-04-29 04:53:01 +00:00
}
2021-05-14 07:03:22 +00:00
2023-02-15 07:01:26 +00:00
public void DeleteItems ( ISerialisableDrawable [ ] items )
2021-05-14 07:03:22 +00:00
{
2021-05-14 20:33:26 +00:00
foreach ( var item in items )
2021-05-14 07:03:22 +00:00
availableTargets . FirstOrDefault ( t = > t . Components . Contains ( item ) ) ? . Remove ( item ) ;
}
2022-03-23 06:11:35 +00:00
2022-04-04 11:30:14 +00:00
#region Drag & drop import handling
2022-03-23 06:11:35 +00: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 06:49:05 +00: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 08:46:19 +00: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 06:11:35 +00:00
// place component
2022-04-01 07:16:49 +00:00
var sprite = new SkinnableSprite
2022-03-23 06:11:35 +00:00
{
2022-04-01 07:16:49 +00:00
SpriteName = { Value = file . Name } ,
Origin = Anchor . Centre ,
2023-01-26 08:46:19 +00:00
Position = skinnableTarget . ToLocalSpace ( GetContainingInputManager ( ) . CurrentState . Mouse . Position ) ,
2022-04-01 07:16:49 +00:00
} ;
placeComponent ( sprite , false ) ;
SkinSelectionHandler . ApplyClosestAnchor ( sprite ) ;
2022-03-23 06:11:35 +00:00
} ) ;
return Task . CompletedTask ;
}
2022-12-13 12:03:25 +00:00
Task ICanAcceptFiles . Import ( ImportTask [ ] tasks , ImportParameters parameters ) = > throw new NotImplementedException ( ) ;
2022-03-23 06:11:35 +00:00
public IEnumerable < string > HandledExtensions = > new [ ] { ".jpg" , ".jpeg" , ".png" } ;
2022-04-04 11:30:14 +00:00
#endregion
protected override void Dispose ( bool isDisposing )
{
base . Dispose ( isDisposing ) ;
game ? . UnregisterImportHandler ( this ) ;
}
2022-07-19 20:18:19 +00:00
private partial class SkinEditorToast : Toast
{
public SkinEditorToast ( LocalisableString value , string skinDisplayName )
2022-07-22 05:00:29 +00:00
: base ( SkinSettingsStrings . SkinLayoutEditor , value , skinDisplayName )
{
}
2022-07-19 20:18:19 +00:00
}
2023-02-03 09:53:22 +00:00
#region Delegation of IEditorChangeHandler
public event Action ? OnStateChange
{
2023-02-07 07:04:31 +00:00
add = > throw new NotImplementedException ( ) ;
remove = > throw new NotImplementedException ( ) ;
2023-02-03 09:53:22 +00:00
}
2023-02-07 07:07:33 +00: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 09:53:22 +00:00
public void SaveState ( ) = > changeHandler ? . SaveState ( ) ;
#endregion
2021-04-28 06:14:48 +00:00
}
}