Merge pull request #25402 from peppy/skin-size-editing

Add support for adjusting size of skin elements
This commit is contained in:
Bartłomiej Dach 2023-11-12 07:31:51 +09:00 committed by GitHub
commit ccfdf1ffd6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 184 additions and 76 deletions

View File

@ -42,6 +42,7 @@ namespace osu.Game.Rulesets.Osu.Edit
SelectionBox.CanFlipX = SelectionBox.CanScaleX = quad.Width > 0;
SelectionBox.CanFlipY = SelectionBox.CanScaleY = quad.Height > 0;
SelectionBox.CanScaleDiagonally = SelectionBox.CanScaleX && SelectionBox.CanScaleY;
SelectionBox.CanReverse = EditorBeatmap.SelectedHitObjects.Count > 1 || EditorBeatmap.SelectedHitObjects.Any(s => s is Slider);
}

View File

@ -47,6 +47,7 @@ namespace osu.Game.Tests.Visual.Editing
CanScaleX = true,
CanScaleY = true,
CanScaleDiagonally = true,
CanFlipX = true,
CanFlipY = true,

View File

@ -47,12 +47,6 @@ namespace osu.Game.Tests.Visual.Gameplay
};
});
AddSliderStep("Width", 0, 1f, 1f, val =>
{
if (healthDisplay.IsNotNull())
healthDisplay.BarLength.Value = val;
});
AddSliderStep("Height", 0, 64, 0, val =>
{
if (healthDisplay.IsNotNull())

View File

@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
@ -243,7 +244,9 @@ namespace osu.Game.Tests.Visual.Gameplay
void revertAndCheckUnchanged()
{
AddStep("Revert changes", () => changeHandler.RestoreState(int.MinValue));
AddAssert("Current state is same as default", () => defaultState.SequenceEqual(changeHandler.GetCurrentState()));
AddAssert("Current state is same as default",
() => Encoding.UTF8.GetString(defaultState),
() => Is.EqualTo(Encoding.UTF8.GetString(changeHandler.GetCurrentState())));
}
}

View File

@ -7,6 +7,7 @@ using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Utils;
@ -31,8 +32,44 @@ namespace osu.Game.Overlays.SkinEditor
UpdatePosition = updateDrawablePosition
};
private bool allSelectedSupportManualSizing(Axes axis) => SelectedItems.All(b => (b as CompositeDrawable)?.AutoSizeAxes.HasFlagFast(axis) == false);
public override bool HandleScale(Vector2 scale, Anchor anchor)
{
Axes adjustAxis;
switch (anchor)
{
// for corners, adjust scale.
case Anchor.TopLeft:
case Anchor.TopRight:
case Anchor.BottomLeft:
case Anchor.BottomRight:
adjustAxis = Axes.Both;
break;
// for edges, adjust size.
// autosize elements can't be easily handled so just disable sizing for now.
case Anchor.TopCentre:
case Anchor.BottomCentre:
if (!allSelectedSupportManualSizing(Axes.Y))
return false;
adjustAxis = Axes.Y;
break;
case Anchor.CentreLeft:
case Anchor.CentreRight:
if (!allSelectedSupportManualSizing(Axes.X))
return false;
adjustAxis = Axes.X;
break;
default:
throw new ArgumentOutOfRangeException(nameof(anchor), anchor, null);
}
// convert scale to screen space
scale = ToScreenSpace(scale) - ToScreenSpace(Vector2.Zero);
@ -120,7 +157,20 @@ namespace osu.Game.Overlays.SkinEditor
if (Precision.AlmostEquals(MathF.Abs(drawableItem.Rotation) % 180, 90))
currentScaledDelta = new Vector2(scaledDelta.Y, scaledDelta.X);
drawableItem.Scale *= currentScaledDelta;
switch (adjustAxis)
{
case Axes.X:
drawableItem.Width *= currentScaledDelta.X;
break;
case Axes.Y:
drawableItem.Height *= currentScaledDelta.Y;
break;
case Axes.Both:
drawableItem.Scale *= currentScaledDelta;
break;
}
}
return true;
@ -169,8 +219,9 @@ namespace osu.Game.Overlays.SkinEditor
{
base.OnSelectionChanged();
SelectionBox.CanScaleX = true;
SelectionBox.CanScaleY = true;
SelectionBox.CanScaleX = allSelectedSupportManualSizing(Axes.X);
SelectionBox.CanScaleY = allSelectedSupportManualSizing(Axes.Y);
SelectionBox.CanScaleDiagonally = true;
SelectionBox.CanFlipX = true;
SelectionBox.CanFlipY = true;
SelectionBox.CanReverse = false;
@ -215,7 +266,15 @@ namespace osu.Game.Overlays.SkinEditor
yield return new OsuMenuItem("Reset scale", MenuItemType.Standard, () =>
{
foreach (var blueprint in SelectedBlueprints)
((Drawable)blueprint.Item).Scale = Vector2.One;
{
var blueprintItem = ((Drawable)blueprint.Item);
blueprintItem.Scale = Vector2.One;
if (blueprintItem.RelativeSizeAxes.HasFlagFast(Axes.X))
blueprintItem.Width = 1;
if (blueprintItem.RelativeSizeAxes.HasFlagFast(Axes.Y))
blueprintItem.Height = 1;
}
});
yield return new EditorMenuItemSpacer();

View File

@ -60,7 +60,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
private bool canScaleX;
/// <summary>
/// Whether horizontal scaling support should be enabled.
/// Whether horizontal scaling (from the left or right edge) support should be enabled.
/// </summary>
public bool CanScaleX
{
@ -77,7 +77,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
private bool canScaleY;
/// <summary>
/// Whether vertical scaling support should be enabled.
/// Whether vertical scaling (from the top or bottom edge) support should be enabled.
/// </summary>
public bool CanScaleY
{
@ -91,6 +91,27 @@ namespace osu.Game.Screens.Edit.Compose.Components
}
}
private bool canScaleDiagonally;
/// <summary>
/// Whether diagonal scaling (from a corner) support should be enabled.
/// </summary>
/// <remarks>
/// There are some cases where we only want to allow proportional resizing, and not allow
/// one or both explicit directions of scale.
/// </remarks>
public bool CanScaleDiagonally
{
get => canScaleDiagonally;
set
{
if (canScaleDiagonally == value) return;
canScaleDiagonally = value;
recreate();
}
}
private bool canFlipX;
/// <summary>
@ -245,7 +266,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
};
if (CanScaleX) addXScaleComponents();
if (CanScaleX && CanScaleY) addFullScaleComponents();
if (CanScaleDiagonally) addFullScaleComponents();
if (CanScaleY) addYScaleComponents();
if (CanFlipX) addXFlipComponents();
if (CanFlipY) addYFlipComponents();

View File

@ -35,14 +35,6 @@ namespace osu.Game.Screens.Play.HUD
Precision = 1
};
[SettingSource("Bar length")]
public BindableFloat BarLength { get; } = new BindableFloat(0.98f)
{
MinValue = 0.2f,
MaxValue = 1,
Precision = 0.01f,
};
private BarPath mainBar = null!;
/// <summary>
@ -140,7 +132,6 @@ namespace osu.Game.Screens.Play.HUD
Current.BindValueChanged(_ => Scheduler.AddOnce(updateCurrent), true);
BarLength.BindValueChanged(l => Width = l.NewValue, true);
BarHeight.BindValueChanged(_ => updatePath());
updatePath();
}

View File

@ -19,6 +19,7 @@ namespace osu.Game.Screens.Play.HUD
private readonly ArgonSongProgressGraph graph;
private readonly ArgonSongProgressBar bar;
private readonly Container graphContainer;
private readonly Container content;
private const float bar_height = 10;
@ -30,43 +31,50 @@ namespace osu.Game.Screens.Play.HUD
public ArgonSongProgress()
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
Anchor = Anchor.BottomCentre;
Origin = Anchor.BottomCentre;
Masking = true;
CornerRadius = 5;
Children = new Drawable[]
Child = content = new Container
{
info = new SongProgressInfo
RelativeSizeAxes = Axes.X,
Children = new Drawable[]
{
Origin = Anchor.TopLeft,
Name = "Info",
Anchor = Anchor.TopLeft,
RelativeSizeAxes = Axes.X,
ShowProgress = false
},
bar = new ArgonSongProgressBar(bar_height)
{
Name = "Seek bar",
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
OnSeek = time => player?.Seek(time),
},
graphContainer = new Container
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Masking = true,
CornerRadius = 5,
Child = graph = new ArgonSongProgressGraph
info = new SongProgressInfo
{
Name = "Difficulty graph",
RelativeSizeAxes = Axes.Both,
Blending = BlendingParameters.Additive
Origin = Anchor.TopLeft,
Name = "Info",
Anchor = Anchor.TopLeft,
RelativeSizeAxes = Axes.X,
ShowProgress = false
},
RelativeSizeAxes = Axes.X,
},
bar = new ArgonSongProgressBar(bar_height)
{
Name = "Seek bar",
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
OnSeek = time => player?.Seek(time),
},
graphContainer = new Container
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Masking = true,
CornerRadius = 5,
Child = graph = new ArgonSongProgressGraph
{
Name = "Difficulty graph",
RelativeSizeAxes = Axes.Both,
Blending = BlendingParameters.Additive
},
RelativeSizeAxes = Axes.X,
},
}
};
RelativeSizeAxes = Axes.X;
}
[BackgroundDependencyLoader]
@ -100,7 +108,7 @@ namespace osu.Game.Screens.Play.HUD
protected override void Update()
{
base.Update();
Height = bar.Height + bar_height + info.Height;
content.Height = bar.Height + bar_height + info.Height;
graphContainer.Height = bar.Height;
}

View File

@ -5,6 +5,7 @@ using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Graphics;
@ -27,6 +28,7 @@ namespace osu.Game.Screens.Play.HUD
private readonly DefaultSongProgressBar bar;
private readonly DefaultSongProgressGraph graph;
private readonly SongProgressInfo info;
private readonly Container content;
[SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowGraph), nameof(SongProgressStrings.ShowGraphDescription))]
public Bindable<bool> ShowGraph { get; } = new BindableBool(true);
@ -37,31 +39,36 @@ namespace osu.Game.Screens.Play.HUD
public DefaultSongProgress()
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
Anchor = Anchor.BottomRight;
Origin = Anchor.BottomRight;
Children = new Drawable[]
Child = content = new Container
{
info = new SongProgressInfo
RelativeSizeAxes = Axes.X,
Children = new Drawable[]
{
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
},
graph = new DefaultSongProgressGraph
{
RelativeSizeAxes = Axes.X,
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
Height = graph_height,
Margin = new MarginPadding { Bottom = bottom_bar_height },
},
bar = new DefaultSongProgressBar(bottom_bar_height, graph_height, handle_size)
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
OnSeek = time => player?.Seek(time),
},
info = new SongProgressInfo
{
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
},
graph = new DefaultSongProgressGraph
{
RelativeSizeAxes = Axes.X,
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
Height = graph_height,
Margin = new MarginPadding { Bottom = bottom_bar_height },
},
bar = new DefaultSongProgressBar(bottom_bar_height, graph_height, handle_size)
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
OnSeek = time => player?.Seek(time),
},
}
};
}
@ -107,7 +114,7 @@ namespace osu.Game.Screens.Play.HUD
float newHeight = bottom_bar_height + graph_height + handle_size.Y + info.Height - graph.Y;
if (!Precision.AlmostEquals(Height, newHeight, 5f))
Height = newHeight;
content.Height = newHeight;
}
private void updateBarVisibility()

View File

@ -19,11 +19,16 @@ namespace osu.Game.Skinning
public override bool HandleNonPositionalInput => false;
public override bool HandlePositionalInput => false;
public LegacySongProgress()
{
// User shouldn't be able to adjust width/height of this as `CircularProgress` doesn't
// handle stretched cases well.
AutoSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load()
{
Size = new Vector2(33);
InternalChildren = new Drawable[]
{
new Container
@ -39,7 +44,7 @@ namespace osu.Game.Skinning
},
new CircularContainer
{
RelativeSizeAxes = Axes.Both,
Size = new Vector2(33),
Masking = true,
BorderColour = Colour4.White,
BorderThickness = 2,

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Bindables;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Configuration;
@ -18,6 +19,10 @@ namespace osu.Game.Skinning
// todo: can probably make this better via deserialisation directly using a common interface.
component.Position = drawableInfo.Position;
component.Rotation = drawableInfo.Rotation;
if (drawableInfo.Width is float width && width != 0 && (component as CompositeDrawable)?.AutoSizeAxes.HasFlagFast(Axes.X) != true)
component.Width = width;
if (drawableInfo.Height is float height && height != 0 && (component as CompositeDrawable)?.AutoSizeAxes.HasFlagFast(Axes.Y) != true)
component.Height = height;
component.Scale = drawableInfo.Scale;
component.Anchor = drawableInfo.Anchor;
component.Origin = drawableInfo.Origin;

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using osu.Framework.Bindables;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Logging;
@ -35,6 +36,10 @@ namespace osu.Game.Skinning
public Vector2 Scale { get; set; }
public float? Width { get; set; }
public float? Height { get; set; }
public Anchor Anchor { get; set; }
public Anchor Origin { get; set; }
@ -62,6 +67,13 @@ namespace osu.Game.Skinning
Position = component.Position;
Rotation = component.Rotation;
Scale = component.Scale;
if ((component as CompositeDrawable)?.AutoSizeAxes.HasFlagFast(Axes.X) != true)
Width = component.Width;
if ((component as CompositeDrawable)?.AutoSizeAxes.HasFlagFast(Axes.Y) != true)
Height = component.Height;
Anchor = component.Anchor;
Origin = component.Origin;

View File

@ -823,6 +823,7 @@ See the LICENCE file in the repository root for full licence text.&#xD;
<s:Boolean x:Key="/Default/Environment/AutoImport2/=CSHARP/BlackLists/=System_002ENumerics_002E_002A/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/AutoImport2/=CSHARP/BlackLists/=System_002ESecurity_002ECryptography_002ERSA/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/AutoImport2/=CSHARP/BlackLists/=TagLib_002EMpeg4_002EBox/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EFeature_002EServices_002ECodeCleanup_002EFileHeader_002EFileHeaderSettingsMigrate/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EFeature_002EServices_002EDaemon_002ESettings_002EMigration_002ESwaWarningsModeSettingsMigrate/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpAttributeForSingleLineMethodUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>