Merge remote-tracking branch 'refs/remotes/ppy/master' into news-sidebar-new

This commit is contained in:
Andrei Zavatski
2021-05-11 13:52:51 +03:00
37 changed files with 833 additions and 214 deletions

View File

@ -52,6 +52,6 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.422.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.422.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.507.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2021.510.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -10,7 +10,7 @@ using osu.Game.Rulesets.Osu.Mods;
namespace osu.Game.Tests.Beatmaps namespace osu.Game.Tests.Beatmaps
{ {
[TestFixture] [TestFixture]
public class BeatmapDifficultyManagerTest public class BeatmapDifficultyCacheTest
{ {
[Test] [Test]
public void TestKeyEqualsWithDifferentModInstances() public void TestKeyEqualsWithDifferentModInstances()

View File

@ -1,45 +1,50 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Framework.Threading;
using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Screens.Edit.Compose.Components;
using osuTK; using osuTK;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Editing namespace osu.Game.Tests.Visual.Editing
{ {
public class TestSceneComposeSelectBox : OsuTestScene public class TestSceneComposeSelectBox : OsuManualInputManagerTestScene
{ {
private Container selectionArea; private Container selectionArea;
private SelectionBox selectionBox;
public TestSceneComposeSelectBox() [SetUp]
public void SetUp() => Schedule(() =>
{ {
SelectionBox selectionBox = null; Child = selectionArea = new Container
{
AddStep("create box", () => Size = new Vector2(400),
Child = selectionArea = new Container Position = -new Vector2(150),
Anchor = Anchor.Centre,
Children = new Drawable[]
{ {
Size = new Vector2(400), selectionBox = new SelectionBox
Position = -new Vector2(150),
Anchor = Anchor.Centre,
Children = new Drawable[]
{ {
selectionBox = new SelectionBox RelativeSizeAxes = Axes.Both,
{
CanRotate = true,
CanScaleX = true,
CanScaleY = true,
OnRotation = handleRotation, CanRotate = true,
OnScale = handleScale CanScaleX = true,
} CanScaleY = true,
OnRotation = handleRotation,
OnScale = handleScale
} }
}); }
};
AddToggleStep("toggle rotation", state => selectionBox.CanRotate = state); InputManager.MoveMouseTo(selectionBox);
AddToggleStep("toggle x", state => selectionBox.CanScaleX = state); InputManager.ReleaseButton(MouseButton.Left);
AddToggleStep("toggle y", state => selectionBox.CanScaleY = state); });
}
private bool handleScale(Vector2 amount, Anchor reference) private bool handleScale(Vector2 amount, Anchor reference)
{ {
@ -68,5 +73,99 @@ namespace osu.Game.Tests.Visual.Editing
selectionArea.Rotation += angle; selectionArea.Rotation += angle;
return true; return true;
} }
[Test]
public void TestRotationHandleShownOnHover()
{
SelectionBoxRotationHandle rotationHandle = null;
AddStep("retrieve rotation handle", () => rotationHandle = this.ChildrenOfType<SelectionBoxRotationHandle>().First());
AddAssert("handle hidden", () => rotationHandle.Alpha == 0);
AddStep("hover over handle", () => InputManager.MoveMouseTo(rotationHandle));
AddUntilStep("rotation handle shown", () => rotationHandle.Alpha == 1);
AddStep("move mouse away", () => InputManager.MoveMouseTo(selectionBox));
AddUntilStep("handle hidden", () => rotationHandle.Alpha == 0);
}
[Test]
public void TestRotationHandleShownOnHoveringClosestScaleHandler()
{
SelectionBoxRotationHandle rotationHandle = null;
AddStep("retrieve rotation handle", () => rotationHandle = this.ChildrenOfType<SelectionBoxRotationHandle>().First());
AddAssert("rotation handle hidden", () => rotationHandle.Alpha == 0);
AddStep("hover over closest scale handle", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<SelectionBoxScaleHandle>().Single(s => s.Anchor == rotationHandle.Anchor));
});
AddUntilStep("rotation handle shown", () => rotationHandle.Alpha == 1);
AddStep("move mouse away", () => InputManager.MoveMouseTo(selectionBox));
AddUntilStep("handle hidden", () => rotationHandle.Alpha == 0);
}
[Test]
public void TestHoverRotationHandleFromScaleHandle()
{
SelectionBoxRotationHandle rotationHandle = null;
AddStep("retrieve rotation handle", () => rotationHandle = this.ChildrenOfType<SelectionBoxRotationHandle>().First());
AddAssert("rotation handle hidden", () => rotationHandle.Alpha == 0);
AddStep("hover over closest scale handle", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<SelectionBoxScaleHandle>().Single(s => s.Anchor == rotationHandle.Anchor));
});
AddUntilStep("rotation handle shown", () => rotationHandle.Alpha == 1);
AddAssert("rotation handle not hovered", () => !rotationHandle.IsHovered);
AddStep("hover over rotation handle", () => InputManager.MoveMouseTo(rotationHandle));
AddAssert("rotation handle still shown", () => rotationHandle.Alpha == 1);
AddAssert("rotation handle hovered", () => rotationHandle.IsHovered);
AddStep("move mouse away", () => InputManager.MoveMouseTo(selectionBox));
AddUntilStep("handle hidden", () => rotationHandle.Alpha == 0);
}
[Test]
public void TestHoldingScaleHandleHidesCorrespondingRotationHandle()
{
SelectionBoxRotationHandle rotationHandle = null;
AddStep("retrieve rotation handle", () => rotationHandle = this.ChildrenOfType<SelectionBoxRotationHandle>().First());
AddAssert("rotation handle hidden", () => rotationHandle.Alpha == 0);
AddStep("hover over closest scale handle", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<SelectionBoxScaleHandle>().Single(s => s.Anchor == rotationHandle.Anchor));
});
AddUntilStep("rotation handle shown", () => rotationHandle.Alpha == 1);
AddStep("hold scale handle", () => InputManager.PressButton(MouseButton.Left));
AddUntilStep("rotation handle hidden", () => rotationHandle.Alpha == 0);
int i;
ScheduledDelegate mouseMove = null;
AddStep("start dragging", () =>
{
i = 0;
mouseMove = Scheduler.AddDelayed(() =>
{
InputManager.MoveMouseTo(selectionBox.ScreenSpaceDrawQuad.TopLeft + Vector2.One * (5 * ++i));
}, 100, true);
});
AddAssert("rotation handle still hidden", () => rotationHandle.Alpha == 0);
AddStep("end dragging", () => mouseMove.Cancel());
AddAssert("rotation handle still hidden", () => rotationHandle.Alpha == 0);
AddStep("unhold left", () => InputManager.ReleaseButton(MouseButton.Left));
AddUntilStep("rotation handle shown", () => rotationHandle.Alpha == 1);
AddStep("move mouse away", () => InputManager.MoveMouseTo(selectionBox, new Vector2(20)));
AddUntilStep("rotation handle hidden", () => rotationHandle.Alpha == 0);
}
} }
} }

View File

@ -50,9 +50,9 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[] AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[]
{ {
new HitCircle { StartTime = 100 }, new HitCircle { StartTime = 100 },
new HitCircle { StartTime = 200, Position = new Vector2(50) }, new HitCircle { StartTime = 200, Position = new Vector2(100) },
new HitCircle { StartTime = 300, Position = new Vector2(100) }, new HitCircle { StartTime = 300, Position = new Vector2(200) },
new HitCircle { StartTime = 400, Position = new Vector2(150) }, new HitCircle { StartTime = 400, Position = new Vector2(300) },
})); }));
AddStep("select objects", () => EditorBeatmap.SelectedHitObjects.AddRange(addedObjects)); AddStep("select objects", () => EditorBeatmap.SelectedHitObjects.AddRange(addedObjects));
@ -95,9 +95,9 @@ namespace osu.Game.Tests.Visual.Editing
var addedObjects = new[] var addedObjects = new[]
{ {
new HitCircle { StartTime = 100 }, new HitCircle { StartTime = 100 },
new HitCircle { StartTime = 200, Position = new Vector2(50) }, new HitCircle { StartTime = 200, Position = new Vector2(100) },
new HitCircle { StartTime = 300, Position = new Vector2(100) }, new HitCircle { StartTime = 300, Position = new Vector2(200) },
new HitCircle { StartTime = 400, Position = new Vector2(150) }, new HitCircle { StartTime = 400, Position = new Vector2(300) },
}; };
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects)); AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects));
@ -131,9 +131,9 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[] AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[]
{ {
new HitCircle { StartTime = 100 }, new HitCircle { StartTime = 100 },
new HitCircle { StartTime = 200, Position = new Vector2(50) }, new HitCircle { StartTime = 200, Position = new Vector2(100) },
new HitCircle { StartTime = 300, Position = new Vector2(100) }, new HitCircle { StartTime = 300, Position = new Vector2(200) },
new HitCircle { StartTime = 400, Position = new Vector2(150) }, new HitCircle { StartTime = 400, Position = new Vector2(300) },
})); }));
moveMouseToObject(() => addedObjects[0]); moveMouseToObject(() => addedObjects[0]);

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
@ -21,6 +22,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("add editor overlay", () => AddStep("add editor overlay", () =>
{ {
skinEditor?.Expire(); skinEditor?.Expire();
Player.ScaleTo(SkinEditorOverlay.VISIBLE_TARGET_SCALE);
LoadComponentAsync(skinEditor = new SkinEditor(Player), Add); LoadComponentAsync(skinEditor = new SkinEditor(Player), Add);
}); });
} }

View File

@ -0,0 +1,26 @@
// 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.
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Skinning.Editor;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneSkinEditorComponentsList : SkinnableTestScene
{
[Test]
public void TestToggleEditor()
{
AddStep("show available components", () => SetContents(() => new SkinComponentToolbox(300)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
}));
}
protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
}
}

View File

@ -6,7 +6,6 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers.Markdown; using osu.Framework.Graphics.Containers.Markdown;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics.Sprites;
using osu.Game.Overlays; using osu.Game.Overlays;
namespace osu.Game.Graphics.Containers.Markdown namespace osu.Game.Graphics.Containers.Markdown
@ -16,11 +15,6 @@ namespace osu.Game.Graphics.Containers.Markdown
[Resolved] [Resolved]
private OverlayColourProvider colourProvider { get; set; } private OverlayColourProvider colourProvider { get; set; }
protected override SpriteText CreateSpriteText() => new OsuSpriteText
{
Font = OsuFont.GetFont(size: 14),
};
protected override void AddLinkText(string text, LinkInline linkInline) protected override void AddLinkText(string text, LinkInline linkInline)
=> AddDrawable(new OsuMarkdownLinkText(text, linkInline)); => AddDrawable(new OsuMarkdownLinkText(text, linkInline));

View File

@ -6,13 +6,14 @@ using osu.Framework.Graphics;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Input.Bindings;
namespace osu.Game.Input namespace osu.Game.Input
{ {
/// <summary> /// <summary>
/// Track whether the end-user is in an idle state, based on their last interaction with the game. /// Track whether the end-user is in an idle state, based on their last interaction with the game.
/// </summary> /// </summary>
public class IdleTracker : Component, IKeyBindingHandler<PlatformAction>, IHandleGlobalKeyboardInput public class IdleTracker : Component, IKeyBindingHandler<PlatformAction>, IKeyBindingHandler<GlobalAction>, IHandleGlobalKeyboardInput
{ {
private readonly double timeToIdle; private readonly double timeToIdle;
@ -58,6 +59,10 @@ namespace osu.Game.Input
public void OnReleased(PlatformAction action) => updateLastInteractionTime(); public void OnReleased(PlatformAction action) => updateLastInteractionTime();
public bool OnPressed(GlobalAction action) => updateLastInteractionTime();
public void OnReleased(GlobalAction action) => updateLastInteractionTime();
protected override bool Handle(UIEvent e) protected override bool Handle(UIEvent e)
{ {
switch (e) switch (e)

View File

@ -82,6 +82,21 @@ namespace osu.Game
private SkinEditorOverlay skinEditor; private SkinEditorOverlay skinEditor;
private Container overlayContent;
private Container rightFloatingOverlayContent;
private Container leftFloatingOverlayContent;
private Container topMostOverlayContent;
private ScalingContainer screenContainer;
private Container screenOffsetContainer;
[Resolved]
private FrameworkConfigManager frameworkConfig { get; set; }
[Cached] [Cached]
private readonly DifficultyRecommender difficultyRecommender = new DifficultyRecommender(); private readonly DifficultyRecommender difficultyRecommender = new DifficultyRecommender();
@ -597,28 +612,35 @@ namespace osu.Game
ActionRequested = action => volume.Adjust(action), ActionRequested = action => volume.Adjust(action),
ScrollActionRequested = (action, amount, isPrecise) => volume.Adjust(action, amount, isPrecise), ScrollActionRequested = (action, amount, isPrecise) => volume.Adjust(action, amount, isPrecise),
}, },
screenContainer = new ScalingContainer(ScalingMode.ExcludeOverlays) screenOffsetContainer = new Container
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[] Children = new Drawable[]
{ {
receptor = new BackButton.Receptor(), screenContainer = new ScalingContainer(ScalingMode.ExcludeOverlays)
ScreenStack = new OsuScreenStack { RelativeSizeAxes = Axes.Both },
BackButton = new BackButton(receptor)
{ {
Anchor = Anchor.BottomLeft, RelativeSizeAxes = Axes.Both,
Origin = Anchor.BottomLeft, Anchor = Anchor.Centre,
Action = () => Origin = Anchor.Centre,
Children = new Drawable[]
{ {
var currentScreen = ScreenStack.CurrentScreen as IOsuScreen; receptor = new BackButton.Receptor(),
ScreenStack = new OsuScreenStack { RelativeSizeAxes = Axes.Both },
BackButton = new BackButton(receptor)
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Action = () =>
{
var currentScreen = ScreenStack.CurrentScreen as IOsuScreen;
if (currentScreen?.AllowBackButton == true && !currentScreen.OnBackButton()) if (currentScreen?.AllowBackButton == true && !currentScreen.OnBackButton())
ScreenStack.Exit(); ScreenStack.Exit();
}
},
logoContainer = new Container { RelativeSizeAxes = Axes.Both },
} }
}, },
logoContainer = new Container { RelativeSizeAxes = Axes.Both },
} }
}, },
overlayContent = new Container { RelativeSizeAxes = Axes.Both }, overlayContent = new Container { RelativeSizeAxes = Axes.Both },
@ -768,7 +790,7 @@ namespace osu.Game
if (notifications.State.Value == Visibility.Visible) if (notifications.State.Value == Visibility.Visible)
offset -= Toolbar.HEIGHT / 2; offset -= Toolbar.HEIGHT / 2;
screenContainer.MoveToX(offset, SettingsPanel.TRANSITION_LENGTH, Easing.OutQuint); screenOffsetContainer.MoveToX(offset, SettingsPanel.TRANSITION_LENGTH, Easing.OutQuint);
} }
Settings.State.ValueChanged += _ => updateScreenOffset(); Settings.State.ValueChanged += _ => updateScreenOffset();
@ -935,19 +957,6 @@ namespace osu.Game
{ {
} }
private Container overlayContent;
private Container rightFloatingOverlayContent;
private Container leftFloatingOverlayContent;
private Container topMostOverlayContent;
[Resolved]
private FrameworkConfigManager frameworkConfig { get; set; }
private ScalingContainer screenContainer;
protected override bool OnExiting() protected override bool OnExiting()
{ {
if (ScreenStack.CurrentScreen is Loader) if (ScreenStack.CurrentScreen is Loader)
@ -966,7 +975,7 @@ namespace osu.Game
{ {
base.UpdateAfterChildren(); base.UpdateAfterChildren();
screenContainer.Padding = new MarginPadding { Top = ToolbarOffset }; screenOffsetContainer.Padding = new MarginPadding { Top = ToolbarOffset };
overlayContent.Padding = new MarginPadding { Top = ToolbarOffset }; overlayContent.Padding = new MarginPadding { Top = ToolbarOffset };
MenuCursorContainer.CanShowCursor = (ScreenStack.CurrentScreen as IOsuScreen)?.CursorVisible ?? false; MenuCursorContainer.CanShowCursor = (ScreenStack.CurrentScreen as IOsuScreen)?.CursorVisible ?? false;

View File

@ -69,13 +69,15 @@ namespace osu.Game.Overlays.Toolbar
base.LoadComplete(); base.LoadComplete();
Current.BindDisabledChanged(disabled => this.FadeColour(disabled ? Color4.Gray : Color4.White, 300), true); Current.BindDisabledChanged(disabled => this.FadeColour(disabled ? Color4.Gray : Color4.White, 300), true);
Current.BindValueChanged(_ => moveLineToCurrent(), true); Current.BindValueChanged(_ => moveLineToCurrent());
// Scheduled to allow the button flow layout to be computed before the line position is updated
ScheduleAfterChildren(moveLineToCurrent);
} }
private bool hasInitialPosition; private bool hasInitialPosition;
// Scheduled to allow the flow layout to be computed before the line position is updated private void moveLineToCurrent()
private void moveLineToCurrent() => ScheduleAfterChildren(() =>
{ {
if (SelectedTab != null) if (SelectedTab != null)
{ {
@ -86,7 +88,7 @@ namespace osu.Game.Overlays.Toolbar
hasInitialPosition = true; hasInitialPosition = true;
} }
}); }
public override bool HandleNonPositionalInput => !Current.Disabled && base.HandleNonPositionalInput; public override bool HandleNonPositionalInput => !Current.Disabled && base.HandleNonPositionalInput;

View File

@ -0,0 +1,32 @@
// 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.
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.Containers;
namespace osu.Game.Rulesets.Edit
{
public class ScrollingToolboxGroup : ToolboxGroup
{
protected readonly OsuScrollContainer Scroll;
protected override Container<Drawable> Content { get; }
public ScrollingToolboxGroup(string title, float scrollAreaHeight)
: base(title)
{
base.Content.Add(Scroll = new OsuScrollContainer
{
RelativeSizeAxes = Axes.X,
Height = scrollAreaHeight,
Child = Content = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
},
});
}
}
}

View File

@ -111,7 +111,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
} }
} }
private Container dragHandles; private SelectionBoxDragHandleContainer dragHandles;
private FillFlowContainer buttons; private FillFlowContainer buttons;
private OsuSpriteText selectionDetailsText; private OsuSpriteText selectionDetailsText;
@ -195,7 +195,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
}, },
} }
}, },
dragHandles = new Container dragHandles = new SelectionBoxDragHandleContainer
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
// ensures that the centres of all drag handles line up with the middle of the selection box border. // ensures that the centres of all drag handles line up with the middle of the selection box border.
@ -220,75 +220,76 @@ namespace osu.Game.Screens.Edit.Compose.Components
private void addRotationComponents() private void addRotationComponents()
{ {
const float separation = 40;
addButton(FontAwesome.Solid.Undo, "Rotate 90 degrees counter-clockwise", () => OnRotation?.Invoke(-90)); addButton(FontAwesome.Solid.Undo, "Rotate 90 degrees counter-clockwise", () => OnRotation?.Invoke(-90));
addButton(FontAwesome.Solid.Redo, "Rotate 90 degrees clockwise", () => OnRotation?.Invoke(90)); addButton(FontAwesome.Solid.Redo, "Rotate 90 degrees clockwise", () => OnRotation?.Invoke(90));
AddRangeInternal(new Drawable[] addRotateHandle(Anchor.TopLeft);
{ addRotateHandle(Anchor.TopRight);
new Box addRotateHandle(Anchor.BottomLeft);
{ addRotateHandle(Anchor.BottomRight);
Depth = float.MaxValue,
Colour = colours.YellowLight,
Blending = BlendingParameters.Additive,
Alpha = 0.3f,
Size = new Vector2(BORDER_RADIUS, separation),
Anchor = Anchor.TopCentre,
Origin = Anchor.BottomCentre,
},
new SelectionBoxDragHandleButton(FontAwesome.Solid.Redo, "Free rotate")
{
Anchor = Anchor.TopCentre,
Y = -separation,
HandleDrag = e => OnRotation?.Invoke(convertDragEventToAngleOfRotation(e)),
OperationStarted = operationStarted,
OperationEnded = operationEnded
}
});
} }
private void addYScaleComponents() private void addYScaleComponents()
{ {
addButton(FontAwesome.Solid.ArrowsAltV, "Flip vertically (Ctrl-J)", () => OnFlip?.Invoke(Direction.Vertical)); addButton(FontAwesome.Solid.ArrowsAltV, "Flip vertically (Ctrl-J)", () => OnFlip?.Invoke(Direction.Vertical));
addDragHandle(Anchor.TopCentre); addScaleHandle(Anchor.TopCentre);
addDragHandle(Anchor.BottomCentre); addScaleHandle(Anchor.BottomCentre);
} }
private void addFullScaleComponents() private void addFullScaleComponents()
{ {
addDragHandle(Anchor.TopLeft); addScaleHandle(Anchor.TopLeft);
addDragHandle(Anchor.TopRight); addScaleHandle(Anchor.TopRight);
addDragHandle(Anchor.BottomLeft); addScaleHandle(Anchor.BottomLeft);
addDragHandle(Anchor.BottomRight); addScaleHandle(Anchor.BottomRight);
} }
private void addXScaleComponents() private void addXScaleComponents()
{ {
addButton(FontAwesome.Solid.ArrowsAltH, "Flip horizontally (Ctrl-H)", () => OnFlip?.Invoke(Direction.Horizontal)); addButton(FontAwesome.Solid.ArrowsAltH, "Flip horizontally (Ctrl-H)", () => OnFlip?.Invoke(Direction.Horizontal));
addDragHandle(Anchor.CentreLeft); addScaleHandle(Anchor.CentreLeft);
addDragHandle(Anchor.CentreRight); addScaleHandle(Anchor.CentreRight);
} }
private void addButton(IconUsage icon, string tooltip, Action action) private void addButton(IconUsage icon, string tooltip, Action action)
{ {
buttons.Add(new SelectionBoxDragHandleButton(icon, tooltip) var button = new SelectionBoxButton(icon, tooltip)
{ {
OperationStarted = operationStarted,
OperationEnded = operationEnded,
Action = action Action = action
}); };
button.OperationStarted += operationStarted;
button.OperationEnded += operationEnded;
buttons.Add(button);
} }
private void addDragHandle(Anchor anchor) => dragHandles.Add(new SelectionBoxDragHandle private void addScaleHandle(Anchor anchor)
{ {
Anchor = anchor, var handle = new SelectionBoxScaleHandle
HandleDrag = e => OnScale?.Invoke(e.Delta, anchor), {
OperationStarted = operationStarted, Anchor = anchor,
OperationEnded = operationEnded HandleDrag = e => OnScale?.Invoke(e.Delta, anchor)
}); };
handle.OperationStarted += operationStarted;
handle.OperationEnded += operationEnded;
dragHandles.AddScaleHandle(handle);
}
private void addRotateHandle(Anchor anchor)
{
var handle = new SelectionBoxRotationHandle
{
Anchor = anchor,
HandleDrag = e => OnRotation?.Invoke(convertDragEventToAngleOfRotation(e))
};
handle.OperationStarted += operationStarted;
handle.OperationEnded += operationEnded;
dragHandles.AddRotationHandle(handle);
}
private int activeOperations; private int activeOperations;

View File

@ -12,10 +12,7 @@ using osuTK.Graphics;
namespace osu.Game.Screens.Edit.Compose.Components namespace osu.Game.Screens.Edit.Compose.Components
{ {
/// <summary> public sealed class SelectionBoxButton : SelectionBoxControl, IHasTooltip
/// A drag "handle" which shares the visual appearance but behaves more like a clickable button.
/// </summary>
public sealed class SelectionBoxDragHandleButton : SelectionBoxDragHandle, IHasTooltip
{ {
private SpriteIcon icon; private SpriteIcon icon;
@ -23,7 +20,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
public Action Action; public Action Action;
public SelectionBoxDragHandleButton(IconUsage iconUsage, string tooltip) public SelectionBoxButton(IconUsage iconUsage, string tooltip)
{ {
this.iconUsage = iconUsage; this.iconUsage = iconUsage;
@ -36,7 +33,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
Size *= 2; Size = new Vector2(20);
AddInternal(icon = new SpriteIcon AddInternal(icon = new SpriteIcon
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
@ -49,16 +46,16 @@ namespace osu.Game.Screens.Edit.Compose.Components
protected override bool OnClick(ClickEvent e) protected override bool OnClick(ClickEvent e)
{ {
OperationStarted?.Invoke(); TriggerOperationStarted();
Action?.Invoke(); Action?.Invoke();
OperationEnded?.Invoke(); TriggerOperatoinEnded();
return true; return true;
} }
protected override void UpdateHoverState() protected override void UpdateHoverState()
{ {
base.UpdateHoverState(); base.UpdateHoverState();
icon.Colour = !HandlingMouse && IsHovered ? Color4.White : Color4.Black; icon.FadeColour(!IsHeld && IsHovered ? Color4.White : Color4.Black, TRANSFORM_DURATION, Easing.OutQuint);
} }
public string TooltipText { get; } public string TooltipText { get; }

View File

@ -0,0 +1,97 @@
// 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.
using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
namespace osu.Game.Screens.Edit.Compose.Components
{
/// <summary>
/// Represents the base appearance for UI controls of the <see cref="SelectionBox"/>,
/// such as scale handles, rotation handles, buttons, etc...
/// </summary>
public abstract class SelectionBoxControl : CompositeDrawable
{
public const double TRANSFORM_DURATION = 100;
public event Action OperationStarted;
public event Action OperationEnded;
private Circle circle;
/// <summary>
/// Whether the user is currently holding the control with mouse.
/// </summary>
public bool IsHeld { get; private set; }
[Resolved]
protected OsuColour Colours { get; private set; }
[BackgroundDependencyLoader]
private void load()
{
Origin = Anchor.Centre;
InternalChildren = new Drawable[]
{
circle = new Circle
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
};
}
protected override void LoadComplete()
{
base.LoadComplete();
UpdateHoverState();
FinishTransforms(true);
}
protected override bool OnHover(HoverEvent e)
{
UpdateHoverState();
return true;
}
protected override void OnHoverLost(HoverLostEvent e)
{
UpdateHoverState();
}
protected override bool OnMouseDown(MouseDownEvent e)
{
IsHeld = true;
UpdateHoverState();
return true;
}
protected override void OnMouseUp(MouseUpEvent e)
{
IsHeld = false;
UpdateHoverState();
}
protected virtual void UpdateHoverState()
{
if (IsHeld)
circle.FadeColour(Colours.GrayF, TRANSFORM_DURATION, Easing.OutQuint);
else
circle.FadeColour(IsHovered ? Colours.Red : Colours.YellowDark, TRANSFORM_DURATION, Easing.OutQuint);
this.ScaleTo(IsHeld || IsHovered ? 1.5f : 1, TRANSFORM_DURATION, Easing.OutQuint);
}
protected void TriggerOperationStarted() => OperationStarted?.Invoke();
protected void TriggerOperatoinEnded() => OperationEnded?.Invoke();
}
}

View File

@ -2,75 +2,17 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osuTK;
namespace osu.Game.Screens.Edit.Compose.Components namespace osu.Game.Screens.Edit.Compose.Components
{ {
public class SelectionBoxDragHandle : Container public abstract class SelectionBoxDragHandle : SelectionBoxControl
{ {
public Action OperationStarted;
public Action OperationEnded;
public Action<DragEvent> HandleDrag { get; set; } public Action<DragEvent> HandleDrag { get; set; }
private Circle circle;
[Resolved]
private OsuColour colours { get; set; }
[BackgroundDependencyLoader]
private void load()
{
Size = new Vector2(10);
Origin = Anchor.Centre;
InternalChildren = new Drawable[]
{
circle = new Circle
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
};
}
protected override void LoadComplete()
{
base.LoadComplete();
UpdateHoverState();
}
protected override bool OnHover(HoverEvent e)
{
UpdateHoverState();
return base.OnHover(e);
}
protected override void OnHoverLost(HoverLostEvent e)
{
base.OnHoverLost(e);
UpdateHoverState();
}
protected bool HandlingMouse;
protected override bool OnMouseDown(MouseDownEvent e)
{
HandlingMouse = true;
UpdateHoverState();
return true;
}
protected override bool OnDragStart(DragStartEvent e) protected override bool OnDragStart(DragStartEvent e)
{ {
OperationStarted?.Invoke(); TriggerOperationStarted();
return true; return true;
} }
@ -82,24 +24,45 @@ namespace osu.Game.Screens.Edit.Compose.Components
protected override void OnDragEnd(DragEndEvent e) protected override void OnDragEnd(DragEndEvent e)
{ {
HandlingMouse = false; TriggerOperatoinEnded();
OperationEnded?.Invoke();
UpdateHoverState(); UpdateHoverState();
base.OnDragEnd(e); base.OnDragEnd(e);
} }
protected override void OnMouseUp(MouseUpEvent e) #region Internal events for SelectionBoxDragHandleContainer
internal event Action HoverGained;
internal event Action HoverLost;
internal event Action MouseDown;
internal event Action MouseUp;
protected override bool OnHover(HoverEvent e)
{ {
HandlingMouse = false; bool result = base.OnHover(e);
UpdateHoverState(); HoverGained?.Invoke();
base.OnMouseUp(e); return result;
} }
protected virtual void UpdateHoverState() protected override void OnHoverLost(HoverLostEvent e)
{ {
circle.Colour = HandlingMouse ? colours.GrayF : (IsHovered ? colours.Red : colours.YellowDark); base.OnHoverLost(e);
this.ScaleTo(HandlingMouse || IsHovered ? 1.5f : 1, 100, Easing.OutQuint); HoverLost?.Invoke();
} }
protected override bool OnMouseDown(MouseDownEvent e)
{
bool result = base.OnMouseDown(e);
MouseDown?.Invoke();
return result;
}
protected override void OnMouseUp(MouseUpEvent e)
{
base.OnMouseUp(e);
MouseUp?.Invoke();
}
#endregion
} }
} }

View File

@ -0,0 +1,109 @@
// 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.
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
namespace osu.Game.Screens.Edit.Compose.Components
{
/// <summary>
/// Represents a display composite containing and managing the visibility state of the selection box's drag handles.
/// </summary>
public class SelectionBoxDragHandleContainer : CompositeDrawable
{
private Container<SelectionBoxScaleHandle> scaleHandles;
private Container<SelectionBoxRotationHandle> rotationHandles;
private readonly List<SelectionBoxDragHandle> allDragHandles = new List<SelectionBoxDragHandle>();
public new MarginPadding Padding
{
get => base.Padding;
set => base.Padding = value;
}
[BackgroundDependencyLoader]
private void load()
{
InternalChildren = new Drawable[]
{
scaleHandles = new Container<SelectionBoxScaleHandle>
{
RelativeSizeAxes = Axes.Both,
},
rotationHandles = new Container<SelectionBoxRotationHandle>
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(-12.5f),
},
};
}
public void AddScaleHandle(SelectionBoxScaleHandle handle)
{
bindDragHandle(handle);
scaleHandles.Add(handle);
}
public void AddRotationHandle(SelectionBoxRotationHandle handle)
{
handle.Alpha = 0;
handle.AlwaysPresent = true;
bindDragHandle(handle);
rotationHandles.Add(handle);
}
private void bindDragHandle(SelectionBoxDragHandle handle)
{
handle.HoverGained += updateRotationHandlesVisibility;
handle.HoverLost += updateRotationHandlesVisibility;
handle.MouseDown += updateRotationHandlesVisibility;
handle.MouseUp += updateRotationHandlesVisibility;
allDragHandles.Add(handle);
}
private SelectionBoxRotationHandle displayedRotationHandle;
private SelectionBoxDragHandle activeHandle;
private void updateRotationHandlesVisibility()
{
// if the active handle is a rotation handle and is held or hovered,
// then no need to perform any updates to the rotation handles visibility.
if (activeHandle is SelectionBoxRotationHandle && (activeHandle?.IsHeld == true || activeHandle?.IsHovered == true))
return;
displayedRotationHandle?.FadeOut(SelectionBoxControl.TRANSFORM_DURATION, Easing.OutQuint);
displayedRotationHandle = null;
// if the active handle is not a rotation handle but is held, then keep the rotation handle hidden.
if (activeHandle?.IsHeld == true)
return;
activeHandle = rotationHandles.SingleOrDefault(h => h.IsHeld || h.IsHovered);
activeHandle ??= allDragHandles.SingleOrDefault(h => h.IsHovered);
if (activeHandle != null)
{
displayedRotationHandle = getCorrespondingRotationHandle(activeHandle, rotationHandles);
displayedRotationHandle?.FadeIn(SelectionBoxControl.TRANSFORM_DURATION, Easing.OutQuint);
}
}
/// <summary>
/// Gets the rotation handle corresponding to the given handle.
/// </summary>
[CanBeNull]
private static SelectionBoxRotationHandle getCorrespondingRotationHandle(SelectionBoxDragHandle handle, IEnumerable<SelectionBoxRotationHandle> rotationHandles)
{
if (handle is SelectionBoxRotationHandle rotationHandle)
return rotationHandle;
return rotationHandles.SingleOrDefault(r => r.Anchor == handle.Anchor);
}
}
}

View File

@ -0,0 +1,42 @@
// 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.
using osu.Framework.Allocation;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.Edit.Compose.Components
{
public class SelectionBoxRotationHandle : SelectionBoxDragHandle
{
private SpriteIcon icon;
[BackgroundDependencyLoader]
private void load()
{
Size = new Vector2(15f);
AddInternal(icon = new SpriteIcon
{
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.5f),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Icon = FontAwesome.Solid.Redo,
Scale = new Vector2
{
X = Anchor.HasFlagFast(Anchor.x0) ? 1f : -1f,
Y = Anchor.HasFlagFast(Anchor.y0) ? 1f : -1f
}
});
}
protected override void UpdateHoverState()
{
base.UpdateHoverState();
icon.FadeColour(!IsHeld && IsHovered ? Color4.White : Color4.Black, TRANSFORM_DURATION, Easing.OutQuint);
}
}
}

View File

@ -0,0 +1,17 @@
// 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.
using osu.Framework.Allocation;
using osuTK;
namespace osu.Game.Screens.Edit.Compose.Components
{
public class SelectionBoxScaleHandle : SelectionBoxDragHandle
{
[BackgroundDependencyLoader]
private void load()
{
Size = new Vector2(10);
}
}
}

View File

@ -4,6 +4,7 @@
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Skinning;
using osuTK; using osuTK;
namespace osu.Game.Screens.Play.HUD namespace osu.Game.Screens.Play.HUD

View File

@ -8,6 +8,7 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning;
using osuTK; using osuTK;
namespace osu.Game.Screens.Play.HUD namespace osu.Game.Screens.Play.HUD

View File

@ -13,6 +13,7 @@ using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Skinning;
namespace osu.Game.Screens.Play.HUD namespace osu.Game.Screens.Play.HUD
{ {

View File

@ -4,6 +4,7 @@
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Skinning;
namespace osu.Game.Screens.Play.HUD namespace osu.Game.Screens.Play.HUD
{ {

View File

@ -13,6 +13,7 @@ using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;

View File

@ -16,12 +16,13 @@ using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD;
using osu.Game.Skinning;
using osuTK; using osuTK;
namespace osu.Game.Screens.Play namespace osu.Game.Screens.Play
{ {
[Cached] [Cached]
public class HUDOverlay : Container, IKeyBindingHandler<GlobalAction> public class HUDOverlay : Container, IKeyBindingHandler<GlobalAction>, IDefaultSkinnableTarget
{ {
public const float FADE_DURATION = 300; public const float FADE_DURATION = 300;

View File

@ -14,7 +14,7 @@ using osu.Framework.Timing;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play.HUD; using osu.Game.Skinning;
namespace osu.Game.Screens.Play namespace osu.Game.Screens.Play
{ {
@ -76,10 +76,6 @@ namespace osu.Game.Screens.Play
{ {
new SongProgressDisplay new SongProgressDisplay
{ {
Masking = true,
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
Children = new Drawable[] Children = new Drawable[]
{ {
info = new SongProgressInfo info = new SongProgressInfo
@ -187,8 +183,16 @@ namespace osu.Game.Screens.Play
public class SongProgressDisplay : Container, ISkinnableComponent public class SongProgressDisplay : Container, ISkinnableComponent
{ {
// TODO: move actual implementation into this. public SongProgressDisplay()
// exists for skin customisation purposes. {
// TODO: move actual implementation into this.
// exists for skin customisation purposes.
Masking = true;
RelativeSizeAxes = Axes.Both;
Anchor = Anchor.BottomCentre;
Origin = Anchor.BottomCentre;
}
} }
} }
} }

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using JetBrains.Annotations; using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
@ -9,6 +10,7 @@ using osu.Framework.Logging;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Mods;
using osu.Game.Scoring; using osu.Game.Scoring;
namespace osu.Game.Screens.Play namespace osu.Game.Screens.Play
@ -33,9 +35,8 @@ namespace osu.Game.Screens.Play
protected override void LoadAsyncComplete() protected override void LoadAsyncComplete()
{ {
if (!handleTokenRetrieval()) return;
base.LoadAsyncComplete(); base.LoadAsyncComplete();
handleTokenRetrieval();
} }
private bool handleTokenRetrieval() private bool handleTokenRetrieval()
@ -43,6 +44,12 @@ namespace osu.Game.Screens.Play
// Token request construction should happen post-load to allow derived classes to potentially prepare DI backings that are used to create the request. // Token request construction should happen post-load to allow derived classes to potentially prepare DI backings that are used to create the request.
var tcs = new TaskCompletionSource<bool>(); var tcs = new TaskCompletionSource<bool>();
if (Mods.Value.Any(m => m is ModAutoplay))
{
handleTokenFailure(new InvalidOperationException("Autoplay loaded."));
return false;
}
if (!api.IsLoggedIn) if (!api.IsLoggedIn)
{ {
handleTokenFailure(new InvalidOperationException("API is not online.")); handleTokenFailure(new InvalidOperationException("API is not online."));

View File

@ -9,7 +9,6 @@ using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Screens.Play.HUD;
using osuTK; using osuTK;
namespace osu.Game.Skinning.Editor namespace osu.Game.Skinning.Editor

View File

@ -6,7 +6,6 @@ using osu.Framework.Graphics;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Screens.Edit.Compose.Components;
using osu.Game.Screens.Play.HUD;
namespace osu.Game.Skinning.Editor namespace osu.Game.Skinning.Editor
{ {

View File

@ -0,0 +1,162 @@
// 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.
using System;
using System.Diagnostics;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Scoring;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Skinning.Editor
{
public class SkinComponentToolbox : ScrollingToolboxGroup
{
public Action<Type> RequestPlacement;
private const float component_display_scale = 0.8f;
[Cached]
private ScoreProcessor scoreProcessor = new ScoreProcessor
{
Combo = { Value = 727 },
TotalScore = { Value = 1337377 }
};
[Cached(typeof(HealthProcessor))]
private HealthProcessor healthProcessor = new DrainingHealthProcessor(0);
public SkinComponentToolbox(float height)
: base("Components", height)
{
RelativeSizeAxes = Axes.None;
Width = 200;
}
[BackgroundDependencyLoader]
private void load()
{
FillFlowContainer fill;
Child = fill = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Spacing = new Vector2(20)
};
var skinnableTypes = typeof(OsuGame).Assembly.GetTypes().Where(t => typeof(ISkinnableComponent).IsAssignableFrom(t)).ToArray();
foreach (var type in skinnableTypes)
{
var component = attemptAddComponent(type);
if (component != null)
{
component.RequestPlacement = t => RequestPlacement?.Invoke(t);
fill.Add(component);
}
}
}
private static ToolboxComponentButton attemptAddComponent(Type type)
{
try
{
var instance = (Drawable)Activator.CreateInstance(type);
Debug.Assert(instance != null);
return new ToolboxComponentButton(instance);
}
catch
{
return null;
}
}
private class ToolboxComponentButton : OsuButton
{
private readonly Drawable component;
public Action<Type> RequestPlacement;
private Container innerContainer;
public ToolboxComponentButton(Drawable component)
{
this.component = component;
Enabled.Value = true;
RelativeSizeAxes = Axes.X;
Height = 70;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
BackgroundColour = colours.Gray3;
Content.EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Radius = 2,
Offset = new Vector2(0, 1),
Colour = Color4.Black.Opacity(0.5f)
};
AddRange(new Drawable[]
{
new OsuSpriteText
{
Text = component.GetType().Name,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
},
innerContainer = new Container
{
Y = 10,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Scale = new Vector2(component_display_scale),
Masking = true,
Child = component
}
});
// adjust provided component to fit / display in a known state.
component.Anchor = Anchor.Centre;
component.Origin = Anchor.Centre;
}
protected override void LoadComplete()
{
base.LoadComplete();
if (component.RelativeSizeAxes != Axes.None)
{
innerContainer.AutoSizeAxes = Axes.None;
innerContainer.Height = 100;
}
}
protected override bool OnClick(ClickEvent e)
{
RequestPlacement?.Invoke(component.GetType());
return true;
}
}
}
}

View File

@ -1,10 +1,13 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Testing;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Cursor; using osu.Game.Graphics.Cursor;
@ -45,6 +48,12 @@ namespace osu.Game.Skinning.Editor
RelativeSizeAxes = Axes.X RelativeSizeAxes = Axes.X
}, },
new SkinBlueprintContainer(target), new SkinBlueprintContainer(target),
new SkinComponentToolbox(600)
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
RequestPlacement = placeComponent
}
} }
}; };
@ -56,6 +65,15 @@ namespace osu.Game.Skinning.Editor
}); });
} }
private void placeComponent(Type type)
{
var instance = (Drawable)Activator.CreateInstance(type);
var targetContainer = target.ChildrenOfType<IDefaultSkinnableTarget>().FirstOrDefault();
targetContainer?.Add(instance);
}
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();

View File

@ -21,7 +21,7 @@ namespace osu.Game.Skinning.Editor
private readonly ScalingContainer target; private readonly ScalingContainer target;
private SkinEditor skinEditor; private SkinEditor skinEditor;
private const float visible_target_scale = 0.8f; public const float VISIBLE_TARGET_SCALE = 0.8f;
[Resolved] [Resolved]
private OsuColour colours { get; set; } private OsuColour colours { get; set; }
@ -64,12 +64,14 @@ namespace osu.Game.Skinning.Editor
{ {
if (visibility.NewValue == Visibility.Visible) if (visibility.NewValue == Visibility.Visible)
{ {
target.ScaleTo(visible_target_scale, SkinEditor.TRANSITION_DURATION, Easing.OutQuint);
target.Masking = true; target.Masking = true;
target.BorderThickness = 5; target.BorderThickness = 5;
target.BorderColour = colours.Yellow; target.BorderColour = colours.Yellow;
target.AllowScaling = false; target.AllowScaling = false;
target.RelativePositionAxes = Axes.Both;
target.ScaleTo(VISIBLE_TARGET_SCALE, SkinEditor.TRANSITION_DURATION, Easing.OutQuint);
target.MoveToX(0.1f, SkinEditor.TRANSITION_DURATION, Easing.OutQuint);
} }
else else
{ {
@ -77,6 +79,7 @@ namespace osu.Game.Skinning.Editor
target.AllowScaling = true; target.AllowScaling = true;
target.ScaleTo(1, SkinEditor.TRANSITION_DURATION, Easing.OutQuint).OnComplete(_ => target.Masking = false); target.ScaleTo(1, SkinEditor.TRANSITION_DURATION, Easing.OutQuint).OnComplete(_ => target.Masking = false);
target.MoveToX(0f, SkinEditor.TRANSITION_DURATION, Easing.OutQuint);
} }
} }

View File

@ -10,7 +10,6 @@ using osu.Game.Extensions;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Screens.Edit.Compose.Components;
using osu.Game.Screens.Play.HUD;
using osuTK; using osuTK;
namespace osu.Game.Skinning.Editor namespace osu.Game.Skinning.Editor

View File

@ -0,0 +1,12 @@
// 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.
namespace osu.Game.Skinning
{
/// <summary>
/// The default placement location for new <see cref="ISkinnableComponent"/>s.
/// </summary>
public interface IDefaultSkinnableTarget : ISkinnableTarget
{
}
}

View File

@ -3,7 +3,7 @@
using osu.Framework.Graphics; using osu.Framework.Graphics;
namespace osu.Game.Screens.Play.HUD namespace osu.Game.Skinning
{ {
/// <summary> /// <summary>
/// Denotes a drawable which, as a drawable, can be adjusted via skinning specifications. /// Denotes a drawable which, as a drawable, can be adjusted via skinning specifications.

View File

@ -0,0 +1,15 @@
// 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.
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
namespace osu.Game.Skinning
{
/// <summary>
/// Denotes a container which can house <see cref="ISkinnableComponent"/>s.
/// </summary>
public interface ISkinnableTarget : IContainerCollection<Drawable>
{
}
}

View File

@ -29,7 +29,7 @@
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.NETCore.Targets" Version="3.1.0" /> <PackageReference Include="Microsoft.NETCore.Targets" Version="3.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="ppy.osu.Framework" Version="2021.507.0" /> <PackageReference Include="ppy.osu.Framework" Version="2021.510.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.422.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.422.0" />
<PackageReference Include="Sentry" Version="3.3.4" /> <PackageReference Include="Sentry" Version="3.3.4" />
<PackageReference Include="SharpCompress" Version="0.28.2" /> <PackageReference Include="SharpCompress" Version="0.28.2" />

View File

@ -70,7 +70,7 @@
<Reference Include="System.Net.Http" /> <Reference Include="System.Net.Http" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.507.0" /> <PackageReference Include="ppy.osu.Framework.iOS" Version="2021.510.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.422.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.422.0" />
</ItemGroup> </ItemGroup>
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) --> <!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
@ -93,7 +93,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="ppy.osu.Framework" Version="2021.507.0" /> <PackageReference Include="ppy.osu.Framework" Version="2021.510.0" />
<PackageReference Include="SharpCompress" Version="0.28.2" /> <PackageReference Include="SharpCompress" Version="0.28.2" />
<PackageReference Include="NUnit" Version="3.13.2" /> <PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="SharpRaven" Version="2.4.0" /> <PackageReference Include="SharpRaven" Version="2.4.0" />