mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 18:23:04 +08:00
Merge branch 'master' into scroll-speed-std
This commit is contained in:
commit
4e8fb0dcab
@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
if (withModifiedSkin)
|
if (withModifiedSkin)
|
||||||
{
|
{
|
||||||
AddStep("change component scale", () => Player.ChildrenOfType<LegacyScoreCounter>().First().Scale = new Vector2(2f));
|
AddStep("change component scale", () => Player.ChildrenOfType<LegacyScoreCounter>().First().Scale = new Vector2(2f));
|
||||||
AddStep("update target", () => Player.ChildrenOfType<SkinComponentsContainer>().ForEach(LegacySkin.UpdateDrawableTarget));
|
AddStep("update target", () => Player.ChildrenOfType<SkinnableContainer>().ForEach(LegacySkin.UpdateDrawableTarget));
|
||||||
AddStep("exit player", () => Player.Exit());
|
AddStep("exit player", () => Player.Exit());
|
||||||
CreateTest();
|
CreateTest();
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ using osu.Game.Skinning;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch
|
namespace osu.Game.Rulesets.Catch
|
||||||
{
|
{
|
||||||
public class CatchSkinComponentLookup : GameplaySkinComponentLookup<CatchSkinComponents>
|
public class CatchSkinComponentLookup : SkinComponentLookup<CatchSkinComponents>
|
||||||
{
|
{
|
||||||
public CatchSkinComponentLookup(CatchSkinComponents component)
|
public CatchSkinComponentLookup(CatchSkinComponents component)
|
||||||
: base(component)
|
: base(component)
|
||||||
|
@ -30,23 +30,19 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
|||||||
{
|
{
|
||||||
switch (lookup)
|
switch (lookup)
|
||||||
{
|
{
|
||||||
case SkinComponentsContainerLookup containerLookup:
|
case GlobalSkinnableContainerLookup containerLookup:
|
||||||
// Only handle per ruleset defaults here.
|
// Only handle per ruleset defaults here.
|
||||||
if (containerLookup.Ruleset == null)
|
if (containerLookup.Ruleset == null)
|
||||||
return base.GetDrawableComponent(lookup);
|
return base.GetDrawableComponent(lookup);
|
||||||
|
|
||||||
// Skin has configuration.
|
|
||||||
if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d)
|
|
||||||
return d;
|
|
||||||
|
|
||||||
// we don't have enough assets to display these components (this is especially the case on a "beatmap" skin).
|
// we don't have enough assets to display these components (this is especially the case on a "beatmap" skin).
|
||||||
if (!IsProvidingLegacyResources)
|
if (!IsProvidingLegacyResources)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
// Our own ruleset components default.
|
// Our own ruleset components default.
|
||||||
switch (containerLookup.Target)
|
switch (containerLookup.Lookup)
|
||||||
{
|
{
|
||||||
case SkinComponentsContainerLookup.TargetArea.MainHUDComponents:
|
case GlobalSkinnableContainers.MainHUDComponents:
|
||||||
// todo: remove CatchSkinComponents.CatchComboCounter and refactor LegacyCatchComboCounter to be added here instead.
|
// todo: remove CatchSkinComponents.CatchComboCounter and refactor LegacyCatchComboCounter to be added here instead.
|
||||||
return new DefaultSkinComponentsContainer(container =>
|
return new DefaultSkinComponentsContainer(container =>
|
||||||
{
|
{
|
||||||
|
@ -6,7 +6,6 @@ using System;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using osu.Game.Audio;
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
@ -271,7 +270,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
|||||||
Duration = endTimeData.Duration,
|
Duration = endTimeData.Duration,
|
||||||
Column = column,
|
Column = column,
|
||||||
Samples = HitObject.Samples,
|
Samples = HitObject.Samples,
|
||||||
NodeSamples = (HitObject as IHasRepeats)?.NodeSamples ?? defaultNodeSamples
|
NodeSamples = (HitObject as IHasRepeats)?.NodeSamples ?? HoldNote.CreateDefaultNodeSamples(HitObject)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if (HitObject is IHasXPosition)
|
else if (HitObject is IHasXPosition)
|
||||||
@ -286,16 +285,6 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
|||||||
|
|
||||||
return pattern;
|
return pattern;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <remarks>
|
|
||||||
/// osu!mania-specific beatmaps in stable only play samples at the start of the hold note.
|
|
||||||
/// </remarks>
|
|
||||||
private List<IList<HitSampleInfo>> defaultNodeSamples
|
|
||||||
=> new List<IList<HitSampleInfo>>
|
|
||||||
{
|
|
||||||
HitObject.Samples,
|
|
||||||
new List<HitSampleInfo>()
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ using osu.Game.Skinning;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania
|
namespace osu.Game.Rulesets.Mania
|
||||||
{
|
{
|
||||||
public class ManiaSkinComponentLookup : GameplaySkinComponentLookup<ManiaSkinComponents>
|
public class ManiaSkinComponentLookup : SkinComponentLookup<ManiaSkinComponents>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="ManiaSkinComponentLookup"/>.
|
/// Creates a new <see cref="ManiaSkinComponentLookup"/>.
|
||||||
|
@ -7,6 +7,7 @@ using System.Collections.Generic;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
@ -91,6 +92,10 @@ namespace osu.Game.Rulesets.Mania.Objects
|
|||||||
{
|
{
|
||||||
base.CreateNestedHitObjects(cancellationToken);
|
base.CreateNestedHitObjects(cancellationToken);
|
||||||
|
|
||||||
|
// Generally node samples will be populated by ManiaBeatmapConverter, but in a case like the editor they may not be.
|
||||||
|
// Ensure they are set to a sane default here.
|
||||||
|
NodeSamples ??= CreateDefaultNodeSamples(this);
|
||||||
|
|
||||||
AddNested(Head = new HeadNote
|
AddNested(Head = new HeadNote
|
||||||
{
|
{
|
||||||
StartTime = StartTime,
|
StartTime = StartTime,
|
||||||
@ -102,7 +107,7 @@ namespace osu.Game.Rulesets.Mania.Objects
|
|||||||
{
|
{
|
||||||
StartTime = EndTime,
|
StartTime = EndTime,
|
||||||
Column = Column,
|
Column = Column,
|
||||||
Samples = GetNodeSamples((NodeSamples?.Count - 1) ?? 1),
|
Samples = GetNodeSamples(NodeSamples.Count - 1),
|
||||||
});
|
});
|
||||||
|
|
||||||
AddNested(Body = new HoldNoteBody
|
AddNested(Body = new HoldNoteBody
|
||||||
@ -116,7 +121,20 @@ namespace osu.Game.Rulesets.Mania.Objects
|
|||||||
|
|
||||||
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
|
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
|
||||||
|
|
||||||
public IList<HitSampleInfo> GetNodeSamples(int nodeIndex) =>
|
public IList<HitSampleInfo> GetNodeSamples(int nodeIndex) => nodeIndex < NodeSamples?.Count ? NodeSamples[nodeIndex] : Samples;
|
||||||
nodeIndex < NodeSamples?.Count ? NodeSamples[nodeIndex] : Samples;
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create the default note samples for a hold note, based off their main sample.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// By default, osu!mania beatmaps in only play samples at the start of the hold note.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="obj">The object to use as a basis for the head sample.</param>
|
||||||
|
/// <returns>Defaults for assigning to <see cref="HoldNote.NodeSamples"/>.</returns>
|
||||||
|
public static List<IList<HitSampleInfo>> CreateDefaultNodeSamples(HitObject obj) => new List<IList<HitSampleInfo>>
|
||||||
|
{
|
||||||
|
obj.Samples,
|
||||||
|
new List<HitSampleInfo>(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,18 +28,14 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
{
|
{
|
||||||
switch (lookup)
|
switch (lookup)
|
||||||
{
|
{
|
||||||
case SkinComponentsContainerLookup containerLookup:
|
case GlobalSkinnableContainerLookup containerLookup:
|
||||||
// Only handle per ruleset defaults here.
|
// Only handle per ruleset defaults here.
|
||||||
if (containerLookup.Ruleset == null)
|
if (containerLookup.Ruleset == null)
|
||||||
return base.GetDrawableComponent(lookup);
|
return base.GetDrawableComponent(lookup);
|
||||||
|
|
||||||
// Skin has configuration.
|
switch (containerLookup.Lookup)
|
||||||
if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d)
|
|
||||||
return d;
|
|
||||||
|
|
||||||
switch (containerLookup.Target)
|
|
||||||
{
|
{
|
||||||
case SkinComponentsContainerLookup.TargetArea.MainHUDComponents:
|
case GlobalSkinnableContainers.MainHUDComponents:
|
||||||
return new DefaultSkinComponentsContainer(container =>
|
return new DefaultSkinComponentsContainer(container =>
|
||||||
{
|
{
|
||||||
var combo = container.ChildrenOfType<ArgonManiaComboCounter>().FirstOrDefault();
|
var combo = container.ChildrenOfType<ArgonManiaComboCounter>().FirstOrDefault();
|
||||||
@ -59,7 +55,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
case GameplaySkinComponentLookup<HitResult> resultComponent:
|
case SkinComponentLookup<HitResult> resultComponent:
|
||||||
// This should eventually be moved to a skin setting, when supported.
|
// This should eventually be moved to a skin setting, when supported.
|
||||||
if (Skin is ArgonProSkin && resultComponent.Component >= HitResult.Great)
|
if (Skin is ArgonProSkin && resultComponent.Component >= HitResult.Great)
|
||||||
return Drawable.Empty();
|
return Drawable.Empty();
|
||||||
|
@ -80,22 +80,18 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
|||||||
{
|
{
|
||||||
switch (lookup)
|
switch (lookup)
|
||||||
{
|
{
|
||||||
case SkinComponentsContainerLookup containerLookup:
|
case GlobalSkinnableContainerLookup containerLookup:
|
||||||
// Modifications for global components.
|
// Modifications for global components.
|
||||||
if (containerLookup.Ruleset == null)
|
if (containerLookup.Ruleset == null)
|
||||||
return base.GetDrawableComponent(lookup);
|
return base.GetDrawableComponent(lookup);
|
||||||
|
|
||||||
// Skin has configuration.
|
|
||||||
if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d)
|
|
||||||
return d;
|
|
||||||
|
|
||||||
// we don't have enough assets to display these components (this is especially the case on a "beatmap" skin).
|
// we don't have enough assets to display these components (this is especially the case on a "beatmap" skin).
|
||||||
if (!IsProvidingLegacyResources)
|
if (!IsProvidingLegacyResources)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
switch (containerLookup.Target)
|
switch (containerLookup.Lookup)
|
||||||
{
|
{
|
||||||
case SkinComponentsContainerLookup.TargetArea.MainHUDComponents:
|
case GlobalSkinnableContainers.MainHUDComponents:
|
||||||
return new DefaultSkinComponentsContainer(container =>
|
return new DefaultSkinComponentsContainer(container =>
|
||||||
{
|
{
|
||||||
var combo = container.ChildrenOfType<LegacyManiaComboCounter>().FirstOrDefault();
|
var combo = container.ChildrenOfType<LegacyManiaComboCounter>().FirstOrDefault();
|
||||||
@ -114,7 +110,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
case GameplaySkinComponentLookup<HitResult> resultComponent:
|
case SkinComponentLookup<HitResult> resultComponent:
|
||||||
return getResult(resultComponent.Component);
|
return getResult(resultComponent.Component);
|
||||||
|
|
||||||
case ManiaSkinComponentLookup maniaComponent:
|
case ManiaSkinComponentLookup maniaComponent:
|
||||||
|
@ -163,6 +163,44 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
checkControlPointSelected(1, false);
|
checkControlPointSelected(1, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAdjustLength()
|
||||||
|
{
|
||||||
|
AddStep("move mouse to drag marker", () =>
|
||||||
|
{
|
||||||
|
Vector2 position = slider.Position + slider.Path.PositionAt(1) + new Vector2(60, 0);
|
||||||
|
InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position));
|
||||||
|
});
|
||||||
|
AddStep("start drag", () => InputManager.PressButton(MouseButton.Left));
|
||||||
|
AddStep("move mouse to control point 1", () =>
|
||||||
|
{
|
||||||
|
Vector2 position = slider.Position + slider.Path.ControlPoints[1].Position + new Vector2(60, 0);
|
||||||
|
InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position));
|
||||||
|
});
|
||||||
|
AddStep("end adjust length", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||||
|
AddAssert("expected distance halved",
|
||||||
|
() => Precision.AlmostEquals(slider.Path.Distance, 172.2, 0.1));
|
||||||
|
|
||||||
|
AddStep("move mouse to drag marker", () =>
|
||||||
|
{
|
||||||
|
Vector2 position = slider.Position + slider.Path.PositionAt(1) + new Vector2(60, 0);
|
||||||
|
InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position));
|
||||||
|
});
|
||||||
|
AddStep("start drag", () => InputManager.PressButton(MouseButton.Left));
|
||||||
|
AddStep("move mouse beyond last control point", () =>
|
||||||
|
{
|
||||||
|
Vector2 position = slider.Position + slider.Path.ControlPoints[2].Position + new Vector2(100, 0);
|
||||||
|
InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position));
|
||||||
|
});
|
||||||
|
AddStep("end adjust length", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||||
|
AddAssert("expected distance is calculated distance",
|
||||||
|
() => Precision.AlmostEquals(slider.Path.Distance, slider.Path.CalculatedDistance, 0.1));
|
||||||
|
|
||||||
|
moveMouseToControlPoint(1);
|
||||||
|
AddAssert("expected distance is unchanged",
|
||||||
|
() => Precision.AlmostEquals(slider.Path.Distance, slider.Path.CalculatedDistance, 0.1));
|
||||||
|
}
|
||||||
|
|
||||||
private void moveHitObject()
|
private void moveHitObject()
|
||||||
{
|
{
|
||||||
AddStep("move hitobject", () =>
|
AddStep("move hitobject", () =>
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
// 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 osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
|
||||||
@ -9,11 +12,28 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
{
|
{
|
||||||
public partial class SliderCircleOverlay : CompositeDrawable
|
public partial class SliderCircleOverlay : CompositeDrawable
|
||||||
{
|
{
|
||||||
|
public SliderEndDragMarker? EndDragMarker { get; }
|
||||||
|
|
||||||
|
public RectangleF VisibleQuad
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var result = CirclePiece.ScreenSpaceDrawQuad.AABBFloat;
|
||||||
|
|
||||||
|
if (endDragMarkerContainer == null) return result;
|
||||||
|
|
||||||
|
var size = result.Size * 1.4f;
|
||||||
|
var location = result.TopLeft - result.Size * 0.2f;
|
||||||
|
return new RectangleF(location, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected readonly HitCirclePiece CirclePiece;
|
protected readonly HitCirclePiece CirclePiece;
|
||||||
|
|
||||||
private readonly Slider slider;
|
private readonly Slider slider;
|
||||||
private readonly SliderPosition position;
|
private readonly SliderPosition position;
|
||||||
private readonly HitCircleOverlapMarker? marker;
|
private readonly HitCircleOverlapMarker? marker;
|
||||||
|
private readonly Container? endDragMarkerContainer;
|
||||||
|
|
||||||
public SliderCircleOverlay(Slider slider, SliderPosition position)
|
public SliderCircleOverlay(Slider slider, SliderPosition position)
|
||||||
{
|
{
|
||||||
@ -24,26 +44,49 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
AddInternal(marker = new HitCircleOverlapMarker());
|
AddInternal(marker = new HitCircleOverlapMarker());
|
||||||
|
|
||||||
AddInternal(CirclePiece = new HitCirclePiece());
|
AddInternal(CirclePiece = new HitCirclePiece());
|
||||||
|
|
||||||
|
if (position == SliderPosition.End)
|
||||||
|
{
|
||||||
|
AddInternal(endDragMarkerContainer = new Container
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Padding = new MarginPadding(-2.5f),
|
||||||
|
Child = EndDragMarker = new SliderEndDragMarker()
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
var circle = position == SliderPosition.Start ? (HitCircle)slider.HeadCircle : slider.TailCircle;
|
var circle = position == SliderPosition.Start ? (HitCircle)slider.HeadCircle :
|
||||||
|
slider.RepeatCount % 2 == 0 ? slider.TailCircle : slider.LastRepeat!;
|
||||||
|
|
||||||
CirclePiece.UpdateFrom(circle);
|
CirclePiece.UpdateFrom(circle);
|
||||||
marker?.UpdateFrom(circle);
|
marker?.UpdateFrom(circle);
|
||||||
|
|
||||||
|
if (endDragMarkerContainer != null)
|
||||||
|
{
|
||||||
|
endDragMarkerContainer.Position = circle.Position;
|
||||||
|
endDragMarkerContainer.Scale = CirclePiece.Scale * 1.2f;
|
||||||
|
var diff = slider.Path.PositionAt(1) - slider.Path.PositionAt(0.99f);
|
||||||
|
endDragMarkerContainer.Rotation = float.RadiansToDegrees(MathF.Atan2(diff.Y, diff.X));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Hide()
|
public override void Hide()
|
||||||
{
|
{
|
||||||
CirclePiece.Hide();
|
CirclePiece.Hide();
|
||||||
|
endDragMarkerContainer?.Hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Show()
|
public override void Show()
|
||||||
{
|
{
|
||||||
CirclePiece.Show();
|
CirclePiece.Show();
|
||||||
|
endDragMarkerContainer?.Show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,84 @@
|
|||||||
|
// 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.Lines;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||||
|
{
|
||||||
|
public partial class SliderEndDragMarker : SmoothPath
|
||||||
|
{
|
||||||
|
public Action<DragStartEvent>? StartDrag { get; set; }
|
||||||
|
public Action<DragEvent>? Drag { get; set; }
|
||||||
|
public Action? EndDrag { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuColour colours { get; set; } = null!;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
var path = PathApproximator.CircularArcToPiecewiseLinear([
|
||||||
|
new Vector2(0, OsuHitObject.OBJECT_RADIUS),
|
||||||
|
new Vector2(OsuHitObject.OBJECT_RADIUS, 0),
|
||||||
|
new Vector2(0, -OsuHitObject.OBJECT_RADIUS)
|
||||||
|
]);
|
||||||
|
|
||||||
|
Anchor = Anchor.CentreLeft;
|
||||||
|
Origin = Anchor.CentreLeft;
|
||||||
|
PathRadius = 5;
|
||||||
|
Vertices = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
updateState();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
{
|
||||||
|
updateState();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
|
{
|
||||||
|
updateState();
|
||||||
|
base.OnHoverLost(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnDragStart(DragStartEvent e)
|
||||||
|
{
|
||||||
|
updateState();
|
||||||
|
StartDrag?.Invoke(e);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnDrag(DragEvent e)
|
||||||
|
{
|
||||||
|
updateState();
|
||||||
|
base.OnDrag(e);
|
||||||
|
Drag?.Invoke(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnDragEnd(DragEndEvent e)
|
||||||
|
{
|
||||||
|
updateState();
|
||||||
|
EndDrag?.Invoke();
|
||||||
|
base.OnDragEnd(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateState()
|
||||||
|
{
|
||||||
|
Colour = IsHovered || IsDragged ? colours.Red : colours.Yellow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,9 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using JetBrains.Annotations;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
@ -29,30 +25,29 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
{
|
{
|
||||||
public new Slider HitObject => (Slider)base.HitObject;
|
public new Slider HitObject => (Slider)base.HitObject;
|
||||||
|
|
||||||
private SliderBodyPiece bodyPiece;
|
private SliderBodyPiece bodyPiece = null!;
|
||||||
private HitCirclePiece headCirclePiece;
|
private HitCirclePiece headCirclePiece = null!;
|
||||||
private HitCirclePiece tailCirclePiece;
|
private HitCirclePiece tailCirclePiece = null!;
|
||||||
private PathControlPointVisualiser<Slider> controlPointVisualiser;
|
private PathControlPointVisualiser<Slider> controlPointVisualiser = null!;
|
||||||
|
|
||||||
private InputManager inputManager;
|
private InputManager inputManager = null!;
|
||||||
|
|
||||||
|
private PathControlPoint? cursor;
|
||||||
|
|
||||||
private SliderPlacementState state;
|
private SliderPlacementState state;
|
||||||
private PathControlPoint segmentStart;
|
private PathControlPoint segmentStart;
|
||||||
private PathControlPoint cursor;
|
|
||||||
private int currentSegmentLength;
|
private int currentSegmentLength;
|
||||||
private bool usingCustomSegmentType;
|
private bool usingCustomSegmentType;
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved]
|
||||||
[CanBeNull]
|
private IPositionSnapProvider? positionSnapProvider { get; set; }
|
||||||
private IPositionSnapProvider positionSnapProvider { get; set; }
|
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved]
|
||||||
[CanBeNull]
|
private IDistanceSnapProvider? distanceSnapProvider { get; set; }
|
||||||
private IDistanceSnapProvider distanceSnapProvider { get; set; }
|
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved]
|
||||||
[CanBeNull]
|
private FreehandSliderToolboxGroup? freehandToolboxGroup { get; set; }
|
||||||
private FreehandSliderToolboxGroup freehandToolboxGroup { get; set; }
|
|
||||||
|
|
||||||
private readonly IncrementalBSplineBuilder bSplineBuilder = new IncrementalBSplineBuilder { Degree = 4 };
|
private readonly IncrementalBSplineBuilder bSplineBuilder = new IncrementalBSplineBuilder { Degree = 4 };
|
||||||
|
|
||||||
@ -84,7 +79,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
inputManager = GetContainingInputManager();
|
|
||||||
|
inputManager = GetContainingInputManager()!;
|
||||||
|
|
||||||
if (freehandToolboxGroup != null)
|
if (freehandToolboxGroup != null)
|
||||||
{
|
{
|
||||||
@ -108,7 +104,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private EditorBeatmap editorBeatmap { get; set; }
|
private EditorBeatmap editorBeatmap { get; set; } = null!;
|
||||||
|
|
||||||
public override void UpdateTimeAndPosition(SnapResult result)
|
public override void UpdateTimeAndPosition(SnapResult result)
|
||||||
{
|
{
|
||||||
@ -151,7 +147,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
case SliderPlacementState.ControlPoints:
|
case SliderPlacementState.ControlPoints:
|
||||||
if (canPlaceNewControlPoint(out var lastPoint))
|
if (canPlaceNewControlPoint(out var lastPoint))
|
||||||
placeNewControlPoint();
|
placeNewControlPoint();
|
||||||
else
|
else if (lastPoint != null)
|
||||||
beginNewSegment(lastPoint);
|
beginNewSegment(lastPoint);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@ -162,9 +158,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
|
|
||||||
private void beginNewSegment(PathControlPoint lastPoint)
|
private void beginNewSegment(PathControlPoint lastPoint)
|
||||||
{
|
{
|
||||||
// Transform the last point into a new segment.
|
|
||||||
Debug.Assert(lastPoint != null);
|
|
||||||
|
|
||||||
segmentStart = lastPoint;
|
segmentStart = lastPoint;
|
||||||
segmentStart.Type = PathType.LINEAR;
|
segmentStart.Type = PathType.LINEAR;
|
||||||
|
|
||||||
@ -384,7 +377,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="lastPoint">The last-placed control point. May be null, but is not null if <c>false</c> is returned.</param>
|
/// <param name="lastPoint">The last-placed control point. May be null, but is not null if <c>false</c> is returned.</param>
|
||||||
/// <returns>Whether a new control point can be placed at the current position.</returns>
|
/// <returns>Whether a new control point can be placed at the current position.</returns>
|
||||||
private bool canPlaceNewControlPoint([CanBeNull] out PathControlPoint lastPoint)
|
private bool canPlaceNewControlPoint(out PathControlPoint? lastPoint)
|
||||||
{
|
{
|
||||||
// We cannot rely on the ordering of drawable pieces, so find the respective drawable piece by searching for the last non-cursor control point.
|
// We cannot rely on the ordering of drawable pieces, so find the respective drawable piece by searching for the last non-cursor control point.
|
||||||
var last = HitObject.Path.ControlPoints.LastOrDefault(p => p != cursor);
|
var last = HitObject.Path.ControlPoints.LastOrDefault(p => p != cursor);
|
||||||
@ -436,7 +429,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
// Replace this segment with a circular arc if it is a reasonable substitute.
|
// Replace this segment with a circular arc if it is a reasonable substitute.
|
||||||
var circleArcSegment = tryCircleArc(segment);
|
var circleArcSegment = tryCircleArc(segment);
|
||||||
|
|
||||||
if (circleArcSegment is not null)
|
if (circleArcSegment != null)
|
||||||
{
|
{
|
||||||
HitObject.Path.ControlPoints.Add(new PathControlPoint(circleArcSegment[0], PathType.PERFECT_CURVE));
|
HitObject.Path.ControlPoints.Add(new PathControlPoint(circleArcSegment[0], PathType.PERFECT_CURVE));
|
||||||
HitObject.Path.ControlPoints.Add(new PathControlPoint(circleArcSegment[1]));
|
HitObject.Path.ControlPoints.Add(new PathControlPoint(circleArcSegment[1]));
|
||||||
@ -453,7 +446,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Vector2[] tryCircleArc(List<Vector2> segment)
|
private Vector2[]? tryCircleArc(List<Vector2> segment)
|
||||||
{
|
{
|
||||||
if (segment.Count < 3 || freehandToolboxGroup?.CircleThreshold.Value == 0) return null;
|
if (segment.Count < 3 || freehandToolboxGroup?.CircleThreshold.Value == 0) return null;
|
||||||
|
|
||||||
|
@ -1,13 +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.
|
||||||
|
|
||||||
#nullable disable
|
using System;
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using JetBrains.Annotations;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Caching;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Primitives;
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
@ -33,27 +33,28 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
{
|
{
|
||||||
protected new DrawableSlider DrawableObject => (DrawableSlider)base.DrawableObject;
|
protected new DrawableSlider DrawableObject => (DrawableSlider)base.DrawableObject;
|
||||||
|
|
||||||
protected SliderBodyPiece BodyPiece { get; private set; }
|
protected SliderBodyPiece BodyPiece { get; private set; } = null!;
|
||||||
protected SliderCircleOverlay HeadOverlay { get; private set; }
|
protected SliderCircleOverlay HeadOverlay { get; private set; } = null!;
|
||||||
protected SliderCircleOverlay TailOverlay { get; private set; }
|
protected SliderCircleOverlay TailOverlay { get; private set; } = null!;
|
||||||
|
|
||||||
[CanBeNull]
|
protected PathControlPointVisualiser<Slider>? ControlPointVisualiser { get; private set; }
|
||||||
protected PathControlPointVisualiser<Slider> ControlPointVisualiser { get; private set; }
|
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved]
|
||||||
private IDistanceSnapProvider distanceSnapProvider { get; set; }
|
private IDistanceSnapProvider? distanceSnapProvider { get; set; }
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved]
|
||||||
private IPlacementHandler placementHandler { get; set; }
|
private IPlacementHandler? placementHandler { get; set; }
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved]
|
||||||
private EditorBeatmap editorBeatmap { get; set; }
|
private EditorBeatmap? editorBeatmap { get; set; }
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved]
|
||||||
private IEditorChangeHandler changeHandler { get; set; }
|
private IEditorChangeHandler? changeHandler { get; set; }
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved]
|
||||||
private BindableBeatDivisor beatDivisor { get; set; }
|
private BindableBeatDivisor? beatDivisor { get; set; }
|
||||||
|
|
||||||
|
private PathControlPoint? placementControlPoint;
|
||||||
|
|
||||||
public override Quad SelectionQuad
|
public override Quad SelectionQuad
|
||||||
{
|
{
|
||||||
@ -61,6 +62,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
{
|
{
|
||||||
var result = BodyPiece.ScreenSpaceDrawQuad.AABBFloat;
|
var result = BodyPiece.ScreenSpaceDrawQuad.AABBFloat;
|
||||||
|
|
||||||
|
result = RectangleF.Union(result, HeadOverlay.VisibleQuad);
|
||||||
|
result = RectangleF.Union(result, TailOverlay.VisibleQuad);
|
||||||
|
|
||||||
if (ControlPointVisualiser != null)
|
if (ControlPointVisualiser != null)
|
||||||
{
|
{
|
||||||
foreach (var piece in ControlPointVisualiser.Pieces)
|
foreach (var piece in ControlPointVisualiser.Pieces)
|
||||||
@ -76,6 +80,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
private readonly BindableList<HitObject> selectedObjects = new BindableList<HitObject>();
|
private readonly BindableList<HitObject> selectedObjects = new BindableList<HitObject>();
|
||||||
private readonly Bindable<bool> showHitMarkers = new Bindable<bool>();
|
private readonly Bindable<bool> showHitMarkers = new Bindable<bool>();
|
||||||
|
|
||||||
|
// Cached slider path which ignored the expected distance value.
|
||||||
|
private readonly Cached<SliderPath> fullPathCache = new Cached<SliderPath>();
|
||||||
|
|
||||||
|
private Vector2 lastRightClickPosition;
|
||||||
|
|
||||||
public SliderSelectionBlueprint(Slider slider)
|
public SliderSelectionBlueprint(Slider slider)
|
||||||
: base(slider)
|
: base(slider)
|
||||||
{
|
{
|
||||||
@ -91,6 +100,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
TailOverlay = CreateCircleOverlay(HitObject, SliderPosition.End),
|
TailOverlay = CreateCircleOverlay(HitObject, SliderPosition.End),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// tail will always have a non-null end drag marker.
|
||||||
|
Debug.Assert(TailOverlay.EndDragMarker != null);
|
||||||
|
|
||||||
|
TailOverlay.EndDragMarker.StartDrag += startAdjustingLength;
|
||||||
|
TailOverlay.EndDragMarker.Drag += adjustLength;
|
||||||
|
TailOverlay.EndDragMarker.EndDrag += endAdjustLength;
|
||||||
|
|
||||||
config.BindWith(OsuSetting.EditorShowHitMarkers, showHitMarkers);
|
config.BindWith(OsuSetting.EditorShowHitMarkers, showHitMarkers);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,6 +115,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
controlPoints.BindTo(HitObject.Path.ControlPoints);
|
controlPoints.BindTo(HitObject.Path.ControlPoints);
|
||||||
|
controlPoints.CollectionChanged += (_, _) => fullPathCache.Invalidate();
|
||||||
|
|
||||||
pathVersion.BindTo(HitObject.Path.Version);
|
pathVersion.BindTo(HitObject.Path.Version);
|
||||||
pathVersion.BindValueChanged(_ => editorBeatmap?.Update(HitObject));
|
pathVersion.BindValueChanged(_ => editorBeatmap?.Update(HitObject));
|
||||||
@ -123,7 +140,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
hoveredControlPoint.IsSelected.Value = true;
|
hoveredControlPoint.IsSelected.Value = true;
|
||||||
ControlPointVisualiser.DeleteSelected();
|
ControlPointVisualiser?.DeleteSelected();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,7 +158,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
{
|
{
|
||||||
updateVisualDefinition();
|
updateVisualDefinition();
|
||||||
|
|
||||||
return base.OnHover(e);
|
return base.OnHover(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,17 +202,16 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Vector2 rightClickPosition;
|
|
||||||
|
|
||||||
protected override bool OnMouseDown(MouseDownEvent e)
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
{
|
{
|
||||||
switch (e.Button)
|
switch (e.Button)
|
||||||
{
|
{
|
||||||
case MouseButton.Right:
|
case MouseButton.Right:
|
||||||
rightClickPosition = e.MouseDownPosition;
|
lastRightClickPosition = e.MouseDownPosition;
|
||||||
return false; // Allow right click to be handled by context menu
|
return false; // Allow right click to be handled by context menu
|
||||||
|
|
||||||
case MouseButton.Left:
|
case MouseButton.Left:
|
||||||
|
|
||||||
// If there's more than two objects selected, ctrl+click should deselect
|
// If there's more than two objects selected, ctrl+click should deselect
|
||||||
if (e.ControlPressed && IsSelected && selectedObjects.Count < 2)
|
if (e.ControlPressed && IsSelected && selectedObjects.Count < 2)
|
||||||
{
|
{
|
||||||
@ -212,8 +227,134 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
[CanBeNull]
|
#region Length Adjustment (independent of path nodes)
|
||||||
private PathControlPoint placementControlPoint;
|
|
||||||
|
private Vector2 lengthAdjustMouseOffset;
|
||||||
|
private double oldDuration;
|
||||||
|
private double oldVelocityMultiplier;
|
||||||
|
private double desiredDistance;
|
||||||
|
private bool isAdjustingLength;
|
||||||
|
private bool adjustVelocityMomentary;
|
||||||
|
|
||||||
|
private void startAdjustingLength(DragStartEvent e)
|
||||||
|
{
|
||||||
|
isAdjustingLength = true;
|
||||||
|
adjustVelocityMomentary = e.ShiftPressed;
|
||||||
|
lengthAdjustMouseOffset = ToLocalSpace(e.ScreenSpaceMouseDownPosition) - HitObject.Position - HitObject.Path.PositionAt(1);
|
||||||
|
oldDuration = HitObject.Path.Distance / HitObject.SliderVelocityMultiplier;
|
||||||
|
oldVelocityMultiplier = HitObject.SliderVelocityMultiplier;
|
||||||
|
changeHandler?.BeginChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void endAdjustLength()
|
||||||
|
{
|
||||||
|
trimExcessControlPoints(HitObject.Path);
|
||||||
|
changeHandler?.EndChange();
|
||||||
|
isAdjustingLength = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void adjustLength(MouseEvent e) => adjustLength(findClosestPathDistance(e), e.ShiftPressed);
|
||||||
|
|
||||||
|
private void adjustLength(double proposedDistance, bool adjustVelocity)
|
||||||
|
{
|
||||||
|
desiredDistance = proposedDistance;
|
||||||
|
double proposedVelocity = oldVelocityMultiplier;
|
||||||
|
|
||||||
|
if (adjustVelocity)
|
||||||
|
{
|
||||||
|
proposedVelocity = proposedDistance / oldDuration;
|
||||||
|
proposedDistance = MathHelper.Clamp(proposedDistance, 0.1 * oldDuration, 10 * oldDuration);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
double minDistance = distanceSnapProvider?.GetBeatSnapDistanceAt(HitObject, false) * oldVelocityMultiplier ?? 1;
|
||||||
|
// Add a small amount to the proposed distance to make it easier to snap to the full length of the slider.
|
||||||
|
proposedDistance = distanceSnapProvider?.FindSnappedDistance(HitObject, (float)proposedDistance + 1) ?? proposedDistance;
|
||||||
|
proposedDistance = MathHelper.Clamp(proposedDistance, minDistance, HitObject.Path.CalculatedDistance);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Precision.AlmostEquals(proposedDistance, HitObject.Path.Distance) && Precision.AlmostEquals(proposedVelocity, HitObject.SliderVelocityMultiplier))
|
||||||
|
return;
|
||||||
|
|
||||||
|
HitObject.SliderVelocityMultiplier = proposedVelocity;
|
||||||
|
HitObject.Path.ExpectedDistance.Value = proposedDistance;
|
||||||
|
editorBeatmap?.Update(HitObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Trims control points from the end of the slider path which are not required to reach the expected end of the slider.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sliderPath">The slider path to trim control points of.</param>
|
||||||
|
private void trimExcessControlPoints(SliderPath sliderPath)
|
||||||
|
{
|
||||||
|
if (!sliderPath.ExpectedDistance.Value.HasValue)
|
||||||
|
return;
|
||||||
|
|
||||||
|
double[] segmentEnds = sliderPath.GetSegmentEnds().ToArray();
|
||||||
|
int segmentIndex = 0;
|
||||||
|
|
||||||
|
for (int i = 1; i < sliderPath.ControlPoints.Count - 1; i++)
|
||||||
|
{
|
||||||
|
if (!sliderPath.ControlPoints[i].Type.HasValue) continue;
|
||||||
|
|
||||||
|
if (Precision.AlmostBigger(segmentEnds[segmentIndex], 1, 1E-3))
|
||||||
|
{
|
||||||
|
sliderPath.ControlPoints.RemoveRange(i + 1, sliderPath.ControlPoints.Count - i - 1);
|
||||||
|
sliderPath.ControlPoints[^1].Type = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
segmentIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds the expected distance value for which the slider end is closest to the mouse position.
|
||||||
|
/// </summary>
|
||||||
|
private double findClosestPathDistance(MouseEvent e)
|
||||||
|
{
|
||||||
|
const double step1 = 10;
|
||||||
|
const double step2 = 0.1;
|
||||||
|
const double longer_distance_bias = 0.01;
|
||||||
|
|
||||||
|
var desiredPosition = ToLocalSpace(e.ScreenSpaceMousePosition) - HitObject.Position - lengthAdjustMouseOffset;
|
||||||
|
|
||||||
|
if (!fullPathCache.IsValid)
|
||||||
|
fullPathCache.Value = new SliderPath(HitObject.Path.ControlPoints.ToArray());
|
||||||
|
|
||||||
|
// Do a linear search to find the closest point on the path to the mouse position.
|
||||||
|
double bestValue = 0;
|
||||||
|
double minDistance = double.MaxValue;
|
||||||
|
|
||||||
|
for (double d = 0; d <= fullPathCache.Value.CalculatedDistance; d += step1)
|
||||||
|
{
|
||||||
|
double t = d / fullPathCache.Value.CalculatedDistance;
|
||||||
|
double dist = Vector2.Distance(fullPathCache.Value.PositionAt(t), desiredPosition) - d * longer_distance_bias;
|
||||||
|
|
||||||
|
if (dist >= minDistance) continue;
|
||||||
|
|
||||||
|
minDistance = dist;
|
||||||
|
bestValue = d;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do another linear search to fine-tune the result.
|
||||||
|
double maxValue = Math.Min(bestValue + step1, fullPathCache.Value.CalculatedDistance);
|
||||||
|
|
||||||
|
for (double d = bestValue - step1; d <= maxValue; d += step2)
|
||||||
|
{
|
||||||
|
double t = d / fullPathCache.Value.CalculatedDistance;
|
||||||
|
double dist = Vector2.Distance(fullPathCache.Value.PositionAt(t), desiredPosition) - d * longer_distance_bias;
|
||||||
|
|
||||||
|
if (dist >= minDistance) continue;
|
||||||
|
|
||||||
|
minDistance = dist;
|
||||||
|
bestValue = d;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bestValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
protected override bool OnDragStart(DragStartEvent e)
|
protected override bool OnDragStart(DragStartEvent e)
|
||||||
{
|
{
|
||||||
@ -255,9 +396,24 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isAdjustingLength && e.ShiftPressed != adjustVelocityMomentary)
|
||||||
|
{
|
||||||
|
adjustVelocityMomentary = e.ShiftPressed;
|
||||||
|
adjustLength(desiredDistance, adjustVelocityMomentary);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnKeyUp(KeyUpEvent e)
|
||||||
|
{
|
||||||
|
if (!IsSelected || !isAdjustingLength || e.ShiftPressed == adjustVelocityMomentary) return;
|
||||||
|
|
||||||
|
adjustVelocityMomentary = e.ShiftPressed;
|
||||||
|
adjustLength(desiredDistance, adjustVelocityMomentary);
|
||||||
|
}
|
||||||
|
|
||||||
private PathControlPoint addControlPoint(Vector2 position)
|
private PathControlPoint addControlPoint(Vector2 position)
|
||||||
{
|
{
|
||||||
position -= HitObject.Position;
|
position -= HitObject.Position;
|
||||||
@ -326,6 +482,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
|
|
||||||
private void splitControlPoints(List<PathControlPoint> controlPointsToSplitAt)
|
private void splitControlPoints(List<PathControlPoint> controlPointsToSplitAt)
|
||||||
{
|
{
|
||||||
|
if (editorBeatmap == null)
|
||||||
|
return;
|
||||||
|
|
||||||
// Arbitrary gap in milliseconds to put between split slider pieces
|
// Arbitrary gap in milliseconds to put between split slider pieces
|
||||||
const double split_gap = 100;
|
const double split_gap = 100;
|
||||||
|
|
||||||
@ -432,7 +591,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
new OsuMenuItem("Add control point", MenuItemType.Standard, () =>
|
new OsuMenuItem("Add control point", MenuItemType.Standard, () =>
|
||||||
{
|
{
|
||||||
changeHandler?.BeginChange();
|
changeHandler?.BeginChange();
|
||||||
addControlPoint(rightClickPosition);
|
addControlPoint(lastRightClickPosition);
|
||||||
changeHandler?.EndChange();
|
changeHandler?.EndChange();
|
||||||
}),
|
}),
|
||||||
new OsuMenuItem("Convert to stream", MenuItemType.Destructive, convertToStream),
|
new OsuMenuItem("Convert to stream", MenuItemType.Destructive, convertToStream),
|
||||||
|
@ -9,6 +9,7 @@ using System.Collections.Generic;
|
|||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Caching;
|
using osu.Framework.Caching;
|
||||||
@ -162,6 +163,10 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public SliderTailCircle TailCircle { get; protected set; }
|
public SliderTailCircle TailCircle { get; protected set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
[CanBeNull]
|
||||||
|
public SliderRepeat LastRepeat { get; protected set; }
|
||||||
|
|
||||||
public Slider()
|
public Slider()
|
||||||
{
|
{
|
||||||
SamplesBindable.CollectionChanged += (_, _) => UpdateNestedSamples();
|
SamplesBindable.CollectionChanged += (_, _) => UpdateNestedSamples();
|
||||||
@ -225,7 +230,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case SliderEventType.Repeat:
|
case SliderEventType.Repeat:
|
||||||
AddNested(new SliderRepeat(this)
|
AddNested(LastRepeat = new SliderRepeat(this)
|
||||||
{
|
{
|
||||||
RepeatIndex = e.SpanIndex,
|
RepeatIndex = e.SpanIndex,
|
||||||
StartTime = StartTime + (e.SpanIndex + 1) * SpanDuration,
|
StartTime = StartTime + (e.SpanIndex + 1) * SpanDuration,
|
||||||
@ -248,6 +253,9 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
|
|
||||||
if (TailCircle != null)
|
if (TailCircle != null)
|
||||||
TailCircle.Position = EndPosition;
|
TailCircle.Position = EndPosition;
|
||||||
|
|
||||||
|
if (LastRepeat != null)
|
||||||
|
LastRepeat.Position = RepeatCount % 2 == 0 ? Position : Position + Path.PositionAt(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void UpdateNestedSamples()
|
protected void UpdateNestedSamples()
|
||||||
|
@ -5,7 +5,7 @@ using osu.Game.Skinning;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu
|
namespace osu.Game.Rulesets.Osu
|
||||||
{
|
{
|
||||||
public class OsuSkinComponentLookup : GameplaySkinComponentLookup<OsuSkinComponents>
|
public class OsuSkinComponentLookup : SkinComponentLookup<OsuSkinComponents>
|
||||||
{
|
{
|
||||||
public OsuSkinComponentLookup(OsuSkinComponents component)
|
public OsuSkinComponentLookup(OsuSkinComponents component)
|
||||||
: base(component)
|
: base(component)
|
||||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
|||||||
{
|
{
|
||||||
switch (lookup)
|
switch (lookup)
|
||||||
{
|
{
|
||||||
case GameplaySkinComponentLookup<HitResult> resultComponent:
|
case SkinComponentLookup<HitResult> resultComponent:
|
||||||
HitResult result = resultComponent.Component;
|
HitResult result = resultComponent.Component;
|
||||||
|
|
||||||
// This should eventually be moved to a skin setting, when supported.
|
// This should eventually be moved to a skin setting, when supported.
|
||||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
{
|
{
|
||||||
switch (lookup)
|
switch (lookup)
|
||||||
{
|
{
|
||||||
case GameplaySkinComponentLookup<HitResult> resultComponent:
|
case SkinComponentLookup<HitResult> resultComponent:
|
||||||
HitResult result = resultComponent.Component;
|
HitResult result = resultComponent.Component;
|
||||||
|
|
||||||
switch (result)
|
switch (result)
|
||||||
|
@ -44,23 +44,19 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
{
|
{
|
||||||
switch (lookup)
|
switch (lookup)
|
||||||
{
|
{
|
||||||
case SkinComponentsContainerLookup containerLookup:
|
case GlobalSkinnableContainerLookup containerLookup:
|
||||||
// Only handle per ruleset defaults here.
|
// Only handle per ruleset defaults here.
|
||||||
if (containerLookup.Ruleset == null)
|
if (containerLookup.Ruleset == null)
|
||||||
return base.GetDrawableComponent(lookup);
|
return base.GetDrawableComponent(lookup);
|
||||||
|
|
||||||
// Skin has configuration.
|
|
||||||
if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d)
|
|
||||||
return d;
|
|
||||||
|
|
||||||
// we don't have enough assets to display these components (this is especially the case on a "beatmap" skin).
|
// we don't have enough assets to display these components (this is especially the case on a "beatmap" skin).
|
||||||
if (!IsProvidingLegacyResources)
|
if (!IsProvidingLegacyResources)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
// Our own ruleset components default.
|
// Our own ruleset components default.
|
||||||
switch (containerLookup.Target)
|
switch (containerLookup.Lookup)
|
||||||
{
|
{
|
||||||
case SkinComponentsContainerLookup.TargetArea.MainHUDComponents:
|
case GlobalSkinnableContainers.MainHUDComponents:
|
||||||
return new DefaultSkinComponentsContainer(container =>
|
return new DefaultSkinComponentsContainer(container =>
|
||||||
{
|
{
|
||||||
var keyCounter = container.OfType<LegacyKeyCounterDisplay>().FirstOrDefault();
|
var keyCounter = container.OfType<LegacyKeyCounterDisplay>().FirstOrDefault();
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Cursor;
|
using osu.Framework.Graphics.Cursor;
|
||||||
@ -35,9 +36,11 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
{
|
{
|
||||||
OsuResumeOverlayInputBlocker? inputBlocker = null;
|
OsuResumeOverlayInputBlocker? inputBlocker = null;
|
||||||
|
|
||||||
if (drawableRuleset != null)
|
var drawableOsuRuleset = (DrawableOsuRuleset?)drawableRuleset;
|
||||||
|
|
||||||
|
if (drawableOsuRuleset != null)
|
||||||
{
|
{
|
||||||
var osuPlayfield = (OsuPlayfield)drawableRuleset.Playfield;
|
var osuPlayfield = drawableOsuRuleset.Playfield;
|
||||||
osuPlayfield.AttachResumeOverlayInputBlocker(inputBlocker = new OsuResumeOverlayInputBlocker());
|
osuPlayfield.AttachResumeOverlayInputBlocker(inputBlocker = new OsuResumeOverlayInputBlocker());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,13 +48,14 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
{
|
{
|
||||||
Child = clickToResumeCursor = new OsuClickToResumeCursor
|
Child = clickToResumeCursor = new OsuClickToResumeCursor
|
||||||
{
|
{
|
||||||
ResumeRequested = () =>
|
ResumeRequested = action =>
|
||||||
{
|
{
|
||||||
// since the user had to press a button to tap the resume cursor,
|
// since the user had to press a button to tap the resume cursor,
|
||||||
// block that press event from potentially reaching a hit circle that's behind the cursor.
|
// block that press event from potentially reaching a hit circle that's behind the cursor.
|
||||||
// we cannot do this from OsuClickToResumeCursor directly since we're in a different input manager tree than the gameplay one,
|
// we cannot do this from OsuClickToResumeCursor directly since we're in a different input manager tree than the gameplay one,
|
||||||
// so we rely on a dedicated input blocking component that's implanted in there to do that for us.
|
// so we rely on a dedicated input blocking component that's implanted in there to do that for us.
|
||||||
if (inputBlocker != null)
|
// note this only matters when the user didn't pause while they were holding the same key that they are resuming with.
|
||||||
|
if (inputBlocker != null && !drawableOsuRuleset.AsNonNull().KeyBindingInputManager.PressedActions.Contains(action))
|
||||||
inputBlocker.BlockNextPress = true;
|
inputBlocker.BlockNextPress = true;
|
||||||
|
|
||||||
Resume();
|
Resume();
|
||||||
@ -94,7 +98,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
{
|
{
|
||||||
public override bool HandlePositionalInput => true;
|
public override bool HandlePositionalInput => true;
|
||||||
|
|
||||||
public Action? ResumeRequested;
|
public Action<OsuAction>? ResumeRequested;
|
||||||
private Container scaleTransitionContainer = null!;
|
private Container scaleTransitionContainer = null!;
|
||||||
|
|
||||||
public OsuClickToResumeCursor()
|
public OsuClickToResumeCursor()
|
||||||
@ -136,7 +140,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
scaleTransitionContainer.ScaleTo(2, TRANSITION_TIME, Easing.OutQuint);
|
scaleTransitionContainer.ScaleTo(2, TRANSITION_TIME, Easing.OutQuint);
|
||||||
ResumeRequested?.Invoke();
|
ResumeRequested?.Invoke(e.Action);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
|
|||||||
{
|
{
|
||||||
switch (lookup)
|
switch (lookup)
|
||||||
{
|
{
|
||||||
case GameplaySkinComponentLookup<HitResult> resultComponent:
|
case SkinComponentLookup<HitResult> resultComponent:
|
||||||
// This should eventually be moved to a skin setting, when supported.
|
// This should eventually be moved to a skin setting, when supported.
|
||||||
if (Skin is ArgonProSkin && resultComponent.Component >= HitResult.Great)
|
if (Skin is ArgonProSkin && resultComponent.Component >= HitResult.Great)
|
||||||
return Drawable.Empty();
|
return Drawable.Empty();
|
||||||
|
@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
|||||||
|
|
||||||
public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup)
|
public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup)
|
||||||
{
|
{
|
||||||
if (lookup is GameplaySkinComponentLookup<HitResult>)
|
if (lookup is SkinComponentLookup<HitResult>)
|
||||||
{
|
{
|
||||||
// if a taiko skin is providing explosion sprites, hide the judgements completely
|
// if a taiko skin is providing explosion sprites, hide the judgements completely
|
||||||
if (hasExplosion.Value)
|
if (hasExplosion.Value)
|
||||||
|
@ -5,7 +5,7 @@ using osu.Game.Skinning;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko
|
namespace osu.Game.Rulesets.Taiko
|
||||||
{
|
{
|
||||||
public class TaikoSkinComponentLookup : GameplaySkinComponentLookup<TaikoSkinComponents>
|
public class TaikoSkinComponentLookup : SkinComponentLookup<TaikoSkinComponents>
|
||||||
{
|
{
|
||||||
public TaikoSkinComponentLookup(TaikoSkinComponents component)
|
public TaikoSkinComponentLookup(TaikoSkinComponents component)
|
||||||
: base(component)
|
: base(component)
|
||||||
|
@ -537,7 +537,7 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
|||||||
[TestCaseSource(nameof(correct_date_query_examples))]
|
[TestCaseSource(nameof(correct_date_query_examples))]
|
||||||
public void TestValidDateQueries(string dateQuery)
|
public void TestValidDateQueries(string dateQuery)
|
||||||
{
|
{
|
||||||
string query = $"played<{dateQuery} time";
|
string query = $"lastplayed<{dateQuery} time";
|
||||||
var filterCriteria = new FilterCriteria();
|
var filterCriteria = new FilterCriteria();
|
||||||
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||||
Assert.AreEqual(true, filterCriteria.LastPlayed.HasFilter);
|
Assert.AreEqual(true, filterCriteria.LastPlayed.HasFilter);
|
||||||
@ -571,7 +571,7 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestGreaterDateQuery()
|
public void TestGreaterDateQuery()
|
||||||
{
|
{
|
||||||
const string query = "played>50";
|
const string query = "lastplayed>50";
|
||||||
var filterCriteria = new FilterCriteria();
|
var filterCriteria = new FilterCriteria();
|
||||||
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||||
Assert.That(filterCriteria.LastPlayed.Max, Is.Not.Null);
|
Assert.That(filterCriteria.LastPlayed.Max, Is.Not.Null);
|
||||||
@ -584,7 +584,7 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestLowerDateQuery()
|
public void TestLowerDateQuery()
|
||||||
{
|
{
|
||||||
const string query = "played<50";
|
const string query = "lastplayed<50";
|
||||||
var filterCriteria = new FilterCriteria();
|
var filterCriteria = new FilterCriteria();
|
||||||
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||||
Assert.That(filterCriteria.LastPlayed.Max, Is.Null);
|
Assert.That(filterCriteria.LastPlayed.Max, Is.Null);
|
||||||
@ -597,7 +597,7 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestBothSidesDateQuery()
|
public void TestBothSidesDateQuery()
|
||||||
{
|
{
|
||||||
const string query = "played>3M played<1y6M";
|
const string query = "lastplayed>3M lastplayed<1y6M";
|
||||||
var filterCriteria = new FilterCriteria();
|
var filterCriteria = new FilterCriteria();
|
||||||
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||||
Assert.That(filterCriteria.LastPlayed.Min, Is.Not.Null);
|
Assert.That(filterCriteria.LastPlayed.Min, Is.Not.Null);
|
||||||
@ -611,7 +611,7 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestEqualDateQuery()
|
public void TestEqualDateQuery()
|
||||||
{
|
{
|
||||||
const string query = "played=50";
|
const string query = "lastplayed=50";
|
||||||
var filterCriteria = new FilterCriteria();
|
var filterCriteria = new FilterCriteria();
|
||||||
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||||
Assert.AreEqual(false, filterCriteria.LastPlayed.HasFilter);
|
Assert.AreEqual(false, filterCriteria.LastPlayed.HasFilter);
|
||||||
@ -620,11 +620,34 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestOutOfRangeDateQuery()
|
public void TestOutOfRangeDateQuery()
|
||||||
{
|
{
|
||||||
const string query = "played<10000y";
|
const string query = "lastplayed<10000y";
|
||||||
var filterCriteria = new FilterCriteria();
|
var filterCriteria = new FilterCriteria();
|
||||||
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||||
Assert.AreEqual(true, filterCriteria.LastPlayed.HasFilter);
|
Assert.AreEqual(true, filterCriteria.LastPlayed.HasFilter);
|
||||||
Assert.AreEqual(DateTimeOffset.MinValue.AddMilliseconds(1), filterCriteria.LastPlayed.Min);
|
Assert.AreEqual(DateTimeOffset.MinValue.AddMilliseconds(1), filterCriteria.LastPlayed.Min);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static readonly object[] played_query_tests =
|
||||||
|
{
|
||||||
|
new object[] { "0", DateTimeOffset.MinValue, true },
|
||||||
|
new object[] { "0", DateTimeOffset.Now, false },
|
||||||
|
new object[] { "false", DateTimeOffset.MinValue, true },
|
||||||
|
new object[] { "false", DateTimeOffset.Now, false },
|
||||||
|
|
||||||
|
new object[] { "1", DateTimeOffset.MinValue, false },
|
||||||
|
new object[] { "1", DateTimeOffset.Now, true },
|
||||||
|
new object[] { "true", DateTimeOffset.MinValue, false },
|
||||||
|
new object[] { "true", DateTimeOffset.Now, true },
|
||||||
|
};
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[TestCaseSource(nameof(played_query_tests))]
|
||||||
|
public void TestPlayedQuery(string query, DateTimeOffset reference, bool matched)
|
||||||
|
{
|
||||||
|
var filterCriteria = new FilterCriteria();
|
||||||
|
FilterQueryParser.ApplyQueries(filterCriteria, $"played={query}");
|
||||||
|
Assert.AreEqual(true, filterCriteria.LastPlayed.HasFilter);
|
||||||
|
Assert.AreEqual(matched, filterCriteria.LastPlayed.IsInRange(reference));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BIN
osu.Game.Tests/Resources/Archives/argon-invalid-drawable.osk
Normal file
BIN
osu.Game.Tests/Resources/Archives/argon-invalid-drawable.osk
Normal file
Binary file not shown.
@ -12,6 +12,7 @@ using osu.Framework.IO.Stores;
|
|||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.IO;
|
using osu.Game.IO;
|
||||||
using osu.Game.IO.Archives;
|
using osu.Game.IO.Archives;
|
||||||
|
using osu.Game.Screens.Menu;
|
||||||
using osu.Game.Screens.Play.HUD;
|
using osu.Game.Screens.Play.HUD;
|
||||||
using osu.Game.Screens.Play.HUD.HitErrorMeters;
|
using osu.Game.Screens.Play.HUD.HitErrorMeters;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
@ -107,7 +108,7 @@ namespace osu.Game.Tests.Skins
|
|||||||
var skin = new TestSkin(new SkinInfo(), null, storage);
|
var skin = new TestSkin(new SkinInfo(), null, storage);
|
||||||
|
|
||||||
Assert.That(skin.LayoutInfos, Has.Count.EqualTo(2));
|
Assert.That(skin.LayoutInfos, Has.Count.EqualTo(2));
|
||||||
Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].AllDrawables.ToArray(), Has.Length.EqualTo(9));
|
Assert.That(skin.LayoutInfos[GlobalSkinnableContainers.MainHUDComponents].AllDrawables.ToArray(), Has.Length.EqualTo(9));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,8 +121,20 @@ namespace osu.Game.Tests.Skins
|
|||||||
var skin = new TestSkin(new SkinInfo(), null, storage);
|
var skin = new TestSkin(new SkinInfo(), null, storage);
|
||||||
|
|
||||||
Assert.That(skin.LayoutInfos, Has.Count.EqualTo(2));
|
Assert.That(skin.LayoutInfos, Has.Count.EqualTo(2));
|
||||||
Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].AllDrawables.ToArray(), Has.Length.EqualTo(10));
|
Assert.That(skin.LayoutInfos[GlobalSkinnableContainers.MainHUDComponents].AllDrawables.ToArray(), Has.Length.EqualTo(10));
|
||||||
Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].AllDrawables.Select(i => i.Type), Contains.Item(typeof(PlayerName)));
|
Assert.That(skin.LayoutInfos[GlobalSkinnableContainers.MainHUDComponents].AllDrawables.Select(i => i.Type), Contains.Item(typeof(PlayerName)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDeserialiseInvalidDrawables()
|
||||||
|
{
|
||||||
|
using (var stream = TestResources.OpenResource("Archives/argon-invalid-drawable.osk"))
|
||||||
|
using (var storage = new ZipArchiveReader(stream))
|
||||||
|
{
|
||||||
|
var skin = new TestSkin(new SkinInfo(), null, storage);
|
||||||
|
|
||||||
|
Assert.That(skin.LayoutInfos.Any(kvp => kvp.Value.AllDrawables.Any(d => d.Type == typeof(StarFountain))), Is.False);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,10 +147,10 @@ namespace osu.Game.Tests.Skins
|
|||||||
var skin = new TestSkin(new SkinInfo(), null, storage);
|
var skin = new TestSkin(new SkinInfo(), null, storage);
|
||||||
|
|
||||||
Assert.That(skin.LayoutInfos, Has.Count.EqualTo(2));
|
Assert.That(skin.LayoutInfos, Has.Count.EqualTo(2));
|
||||||
Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].AllDrawables.ToArray(), Has.Length.EqualTo(6));
|
Assert.That(skin.LayoutInfos[GlobalSkinnableContainers.MainHUDComponents].AllDrawables.ToArray(), Has.Length.EqualTo(6));
|
||||||
Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.SongSelect].AllDrawables.ToArray(), Has.Length.EqualTo(1));
|
Assert.That(skin.LayoutInfos[GlobalSkinnableContainers.SongSelect].AllDrawables.ToArray(), Has.Length.EqualTo(1));
|
||||||
|
|
||||||
var skinnableInfo = skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.SongSelect].AllDrawables.First();
|
var skinnableInfo = skin.LayoutInfos[GlobalSkinnableContainers.SongSelect].AllDrawables.First();
|
||||||
|
|
||||||
Assert.That(skinnableInfo.Type, Is.EqualTo(typeof(SkinnableSprite)));
|
Assert.That(skinnableInfo.Type, Is.EqualTo(typeof(SkinnableSprite)));
|
||||||
Assert.That(skinnableInfo.Settings.First().Key, Is.EqualTo("sprite_name"));
|
Assert.That(skinnableInfo.Settings.First().Key, Is.EqualTo("sprite_name"));
|
||||||
@ -148,10 +161,10 @@ namespace osu.Game.Tests.Skins
|
|||||||
using (var storage = new ZipArchiveReader(stream))
|
using (var storage = new ZipArchiveReader(stream))
|
||||||
{
|
{
|
||||||
var skin = new TestSkin(new SkinInfo(), null, storage);
|
var skin = new TestSkin(new SkinInfo(), null, storage);
|
||||||
Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].AllDrawables.ToArray(), Has.Length.EqualTo(8));
|
Assert.That(skin.LayoutInfos[GlobalSkinnableContainers.MainHUDComponents].AllDrawables.ToArray(), Has.Length.EqualTo(8));
|
||||||
Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].AllDrawables.Select(i => i.Type), Contains.Item(typeof(UnstableRateCounter)));
|
Assert.That(skin.LayoutInfos[GlobalSkinnableContainers.MainHUDComponents].AllDrawables.Select(i => i.Type), Contains.Item(typeof(UnstableRateCounter)));
|
||||||
Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].AllDrawables.Select(i => i.Type), Contains.Item(typeof(ColourHitErrorMeter)));
|
Assert.That(skin.LayoutInfos[GlobalSkinnableContainers.MainHUDComponents].AllDrawables.Select(i => i.Type), Contains.Item(typeof(ColourHitErrorMeter)));
|
||||||
Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].AllDrawables.Select(i => i.Type), Contains.Item(typeof(LegacySongProgress)));
|
Assert.That(skin.LayoutInfos[GlobalSkinnableContainers.MainHUDComponents].AllDrawables.Select(i => i.Type), Contains.Item(typeof(LegacySongProgress)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -548,6 +548,63 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
hitObjectNodeHasSamples(2, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
|
hitObjectNodeHasSamples(2, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHotkeysUnifySliderSamplesAndNodeSamples()
|
||||||
|
{
|
||||||
|
AddStep("add slider", () =>
|
||||||
|
{
|
||||||
|
EditorBeatmap.Clear();
|
||||||
|
EditorBeatmap.Add(new Slider
|
||||||
|
{
|
||||||
|
Position = new Vector2(256, 256),
|
||||||
|
StartTime = 1000,
|
||||||
|
Path = new SliderPath(new[] { new PathControlPoint(Vector2.Zero), new PathControlPoint(new Vector2(250, 0)) }),
|
||||||
|
Samples =
|
||||||
|
{
|
||||||
|
new HitSampleInfo(HitSampleInfo.HIT_NORMAL, HitSampleInfo.BANK_SOFT),
|
||||||
|
new HitSampleInfo(HitSampleInfo.HIT_WHISTLE, bank: HitSampleInfo.BANK_DRUM),
|
||||||
|
},
|
||||||
|
NodeSamples = new List<IList<HitSampleInfo>>
|
||||||
|
{
|
||||||
|
new List<HitSampleInfo>
|
||||||
|
{
|
||||||
|
new HitSampleInfo(HitSampleInfo.HIT_NORMAL, bank: HitSampleInfo.BANK_DRUM),
|
||||||
|
new HitSampleInfo(HitSampleInfo.HIT_CLAP, bank: HitSampleInfo.BANK_DRUM),
|
||||||
|
},
|
||||||
|
new List<HitSampleInfo>
|
||||||
|
{
|
||||||
|
new HitSampleInfo(HitSampleInfo.HIT_NORMAL, bank: HitSampleInfo.BANK_SOFT),
|
||||||
|
new HitSampleInfo(HitSampleInfo.HIT_WHISTLE, bank: HitSampleInfo.BANK_SOFT),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
AddStep("select everything", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects));
|
||||||
|
|
||||||
|
AddStep("set soft bank", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.LShift);
|
||||||
|
InputManager.Key(Key.E);
|
||||||
|
InputManager.ReleaseKey(Key.LShift);
|
||||||
|
});
|
||||||
|
|
||||||
|
hitObjectHasSampleBank(0, HitSampleInfo.BANK_SOFT);
|
||||||
|
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
|
||||||
|
hitObjectNodeHasSampleBank(0, 0, HitSampleInfo.BANK_SOFT);
|
||||||
|
hitObjectNodeHasSamples(0, 0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP);
|
||||||
|
hitObjectNodeHasSampleBank(0, 1, HitSampleInfo.BANK_SOFT);
|
||||||
|
hitObjectNodeHasSamples(0, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
|
||||||
|
|
||||||
|
AddStep("unify whistle addition", () => InputManager.Key(Key.W));
|
||||||
|
|
||||||
|
hitObjectHasSampleBank(0, HitSampleInfo.BANK_SOFT);
|
||||||
|
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
|
||||||
|
hitObjectNodeHasSampleBank(0, 0, HitSampleInfo.BANK_SOFT);
|
||||||
|
hitObjectNodeHasSamples(0, 0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP, HitSampleInfo.HIT_WHISTLE);
|
||||||
|
hitObjectNodeHasSampleBank(0, 1, HitSampleInfo.BANK_SOFT);
|
||||||
|
hitObjectNodeHasSamples(0, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestSelectingObjectDoesNotMutateSamples()
|
public void TestSelectingObjectDoesNotMutateSamples()
|
||||||
{
|
{
|
||||||
|
@ -37,8 +37,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
public void TestEmptyLegacyBeatmapSkinFallsBack()
|
public void TestEmptyLegacyBeatmapSkinFallsBack()
|
||||||
{
|
{
|
||||||
CreateSkinTest(TrianglesSkin.CreateInfo(), () => new LegacyBeatmapSkin(new BeatmapInfo(), null));
|
CreateSkinTest(TrianglesSkin.CreateInfo(), () => new LegacyBeatmapSkin(new BeatmapInfo(), null));
|
||||||
AddUntilStep("wait for hud load", () => Player.ChildrenOfType<SkinComponentsContainer>().All(c => c.ComponentsLoaded));
|
AddUntilStep("wait for hud load", () => Player.ChildrenOfType<SkinnableContainer>().All(c => c.ComponentsLoaded));
|
||||||
AddAssert("hud from default skin", () => AssertComponentsFromExpectedSource(SkinComponentsContainerLookup.TargetArea.MainHUDComponents, skinManager.CurrentSkin.Value));
|
AddAssert("hud from default skin", () => AssertComponentsFromExpectedSource(GlobalSkinnableContainers.MainHUDComponents, skinManager.CurrentSkin.Value));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void CreateSkinTest(SkinInfo gameCurrentSkin, Func<ISkin> getBeatmapSkin)
|
protected void CreateSkinTest(SkinInfo gameCurrentSkin, Func<ISkin> getBeatmapSkin)
|
||||||
@ -53,9 +53,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected bool AssertComponentsFromExpectedSource(SkinComponentsContainerLookup.TargetArea target, ISkin expectedSource)
|
protected bool AssertComponentsFromExpectedSource(GlobalSkinnableContainers target, ISkin expectedSource)
|
||||||
{
|
{
|
||||||
var targetContainer = Player.ChildrenOfType<SkinComponentsContainer>().First(s => s.Lookup.Target == target);
|
var targetContainer = Player.ChildrenOfType<SkinnableContainer>().First(s => s.Lookup.Lookup == target);
|
||||||
var actualComponentsContainer = targetContainer.ChildrenOfType<Container>().SingleOrDefault(c => c.Parent == targetContainer);
|
var actualComponentsContainer = targetContainer.ChildrenOfType<Container>().SingleOrDefault(c => c.Parent == targetContainer);
|
||||||
|
|
||||||
if (actualComponentsContainer == null)
|
if (actualComponentsContainer == null)
|
||||||
@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
var actualInfo = actualComponentsContainer.CreateSerialisedInfo();
|
var actualInfo = actualComponentsContainer.CreateSerialisedInfo();
|
||||||
|
|
||||||
var expectedComponentsContainer = expectedSource.GetDrawableComponent(new SkinComponentsContainerLookup(target)) as Container;
|
var expectedComponentsContainer = expectedSource.GetDrawableComponent(new GlobalSkinnableContainerLookup(target)) as Container;
|
||||||
if (expectedComponentsContainer == null)
|
if (expectedComponentsContainer == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
@ -7,9 +7,13 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
using osu.Game.Beatmaps.Timing;
|
using osu.Game.Beatmaps.Timing;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Gameplay
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
{
|
{
|
||||||
@ -28,14 +32,19 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
public TestSceneBreakTracker()
|
public TestSceneBreakTracker()
|
||||||
{
|
{
|
||||||
AddRange(new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = Color4.White,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
breakTracker = new TestBreakTracker(),
|
breakTracker = new TestBreakTracker(),
|
||||||
breakOverlay = new BreakOverlay(true, null)
|
breakOverlay = new BreakOverlay(true, new ScoreProcessor(new OsuRuleset()))
|
||||||
{
|
{
|
||||||
ProcessCustomClock = false,
|
ProcessCustomClock = false,
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
|
@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
private readonly IGameplayClock gameplayClock = new GameplayClockContainer(new TrackVirtual(60000), false, false);
|
private readonly IGameplayClock gameplayClock = new GameplayClockContainer(new TrackVirtual(60000), false, false);
|
||||||
|
|
||||||
// best way to check without exposing.
|
// best way to check without exposing.
|
||||||
private Drawable hideTarget => hudOverlay.ChildrenOfType<SkinComponentsContainer>().First();
|
private Drawable hideTarget => hudOverlay.ChildrenOfType<SkinnableContainer>().First();
|
||||||
private Drawable keyCounterContent => hudOverlay.ChildrenOfType<KeyCounterDisplay>().First().ChildrenOfType<Drawable>().Skip(1).First();
|
private Drawable keyCounterContent => hudOverlay.ChildrenOfType<KeyCounterDisplay>().First().ChildrenOfType<Drawable>().Skip(1).First();
|
||||||
|
|
||||||
public TestSceneHUDOverlay()
|
public TestSceneHUDOverlay()
|
||||||
@ -242,8 +242,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
createNew();
|
createNew();
|
||||||
|
|
||||||
AddUntilStep("wait for components to be hidden", () => hudOverlay.ChildrenOfType<SkinComponentsContainer>().Single().Alpha == 0);
|
AddUntilStep("wait for components to be hidden", () => hudOverlay.ChildrenOfType<SkinnableContainer>().Single().Alpha == 0);
|
||||||
AddUntilStep("wait for hud load", () => hudOverlay.ChildrenOfType<SkinComponentsContainer>().All(c => c.ComponentsLoaded));
|
AddUntilStep("wait for hud load", () => hudOverlay.ChildrenOfType<SkinnableContainer>().All(c => c.ComponentsLoaded));
|
||||||
|
|
||||||
AddStep("bind on update", () =>
|
AddStep("bind on update", () =>
|
||||||
{
|
{
|
||||||
@ -260,10 +260,10 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
createNew();
|
createNew();
|
||||||
|
|
||||||
AddUntilStep("wait for components to be hidden", () => hudOverlay.ChildrenOfType<SkinComponentsContainer>().Single().Alpha == 0);
|
AddUntilStep("wait for components to be hidden", () => hudOverlay.ChildrenOfType<SkinnableContainer>().Single().Alpha == 0);
|
||||||
|
|
||||||
AddStep("reload components", () => hudOverlay.ChildrenOfType<SkinComponentsContainer>().Single().Reload());
|
AddStep("reload components", () => hudOverlay.ChildrenOfType<SkinnableContainer>().Single().Reload());
|
||||||
AddUntilStep("skinnable components loaded", () => hudOverlay.ChildrenOfType<SkinComponentsContainer>().Single().ComponentsLoaded);
|
AddUntilStep("skinnable components loaded", () => hudOverlay.ChildrenOfType<SkinnableContainer>().Single().ComponentsLoaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createNew(Action<HUDOverlay>? action = null)
|
private void createNew(Action<HUDOverlay>? action = null)
|
||||||
|
@ -47,6 +47,16 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
Position = OsuPlayfield.BASE_SIZE / 2,
|
Position = OsuPlayfield.BASE_SIZE / 2,
|
||||||
StartTime = 5000,
|
StartTime = 5000,
|
||||||
|
},
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
Position = OsuPlayfield.BASE_SIZE / 2,
|
||||||
|
StartTime = 10000,
|
||||||
|
},
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
Position = OsuPlayfield.BASE_SIZE / 2,
|
||||||
|
StartTime = 15000,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -256,7 +266,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestOsuRegisterInputFromPressingOrangeCursorButPressIsBlocked()
|
public void TestOsuHitCircleNotReceivingInputOnResume()
|
||||||
{
|
{
|
||||||
KeyCounter counter = null!;
|
KeyCounter counter = null!;
|
||||||
|
|
||||||
@ -281,19 +291,82 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddAssert("button is released in kbc", () => !Player.DrawableRuleset.Playfield.FindClosestParent<OsuInputManager>()!.PressedActions.Any());
|
AddAssert("button is released in kbc", () => !Player.DrawableRuleset.Playfield.FindClosestParent<OsuInputManager>()!.PressedActions.Any());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOsuHitCircleNotReceivingInputOnResume_PauseWhileHoldingSameKey()
|
||||||
|
{
|
||||||
|
KeyCounter counter = null!;
|
||||||
|
|
||||||
|
loadPlayer(() => new OsuRuleset());
|
||||||
|
AddStep("get key counter", () => counter = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<OsuAction> actionTrigger && actionTrigger.Action == OsuAction.LeftButton));
|
||||||
|
|
||||||
|
AddStep("press Z", () => InputManager.PressKey(Key.Z));
|
||||||
|
AddAssert("circle hit", () => Player.ScoreProcessor.HighestCombo.Value, () => Is.EqualTo(1));
|
||||||
|
|
||||||
|
AddStep("pause", () => Player.Pause());
|
||||||
|
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
||||||
|
|
||||||
|
AddStep("resume", () => Player.Resume());
|
||||||
|
AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType<OsuResumeOverlay.OsuClickToResumeCursor>().Single()));
|
||||||
|
AddStep("press Z to resume", () => InputManager.PressKey(Key.Z));
|
||||||
|
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
||||||
|
|
||||||
|
checkKey(() => counter, 1, false);
|
||||||
|
|
||||||
|
seekTo(5000);
|
||||||
|
|
||||||
|
AddStep("press Z", () => InputManager.PressKey(Key.Z));
|
||||||
|
|
||||||
|
checkKey(() => counter, 2, true);
|
||||||
|
AddAssert("circle hit", () => Player.ScoreProcessor.HighestCombo.Value, () => Is.EqualTo(2));
|
||||||
|
|
||||||
|
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
||||||
|
checkKey(() => counter, 2, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOsuHitCircleNotReceivingInputOnResume_PauseWhileHoldingOtherKey()
|
||||||
|
{
|
||||||
|
loadPlayer(() => new OsuRuleset());
|
||||||
|
|
||||||
|
AddStep("press X", () => InputManager.PressKey(Key.X));
|
||||||
|
AddAssert("circle hit", () => Player.ScoreProcessor.HighestCombo.Value, () => Is.EqualTo(1));
|
||||||
|
|
||||||
|
seekTo(5000);
|
||||||
|
|
||||||
|
AddStep("pause", () => Player.Pause());
|
||||||
|
AddStep("release X", () => InputManager.ReleaseKey(Key.X));
|
||||||
|
|
||||||
|
AddStep("resume", () => Player.Resume());
|
||||||
|
AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType<OsuResumeOverlay.OsuClickToResumeCursor>().Single()));
|
||||||
|
AddStep("press Z to resume", () => InputManager.PressKey(Key.Z));
|
||||||
|
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
||||||
|
|
||||||
|
AddAssert("circle not hit", () => Player.ScoreProcessor.HighestCombo.Value, () => Is.EqualTo(1));
|
||||||
|
|
||||||
|
AddStep("press X", () => InputManager.PressKey(Key.X));
|
||||||
|
AddStep("release X", () => InputManager.ReleaseKey(Key.X));
|
||||||
|
|
||||||
|
AddAssert("circle hit", () => Player.ScoreProcessor.HighestCombo.Value, () => Is.EqualTo(2));
|
||||||
|
}
|
||||||
|
|
||||||
private void loadPlayer(Func<Ruleset> createRuleset)
|
private void loadPlayer(Func<Ruleset> createRuleset)
|
||||||
{
|
{
|
||||||
AddStep("set ruleset", () => currentRuleset = createRuleset());
|
AddStep("set ruleset", () => currentRuleset = createRuleset());
|
||||||
AddStep("load player", LoadPlayer);
|
AddStep("load player", LoadPlayer);
|
||||||
AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1);
|
AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1);
|
||||||
AddUntilStep("wait for hud", () => Player.HUDOverlay.ChildrenOfType<SkinComponentsContainer>().All(s => s.ComponentsLoaded));
|
AddUntilStep("wait for hud", () => Player.HUDOverlay.ChildrenOfType<SkinnableContainer>().All(s => s.ComponentsLoaded));
|
||||||
|
|
||||||
AddStep("seek to gameplay", () => Player.GameplayClockContainer.Seek(0));
|
seekTo(0);
|
||||||
AddUntilStep("wait for seek to finish", () => Player.DrawableRuleset.FrameStableClock.CurrentTime, () => Is.EqualTo(0).Within(500));
|
|
||||||
AddAssert("not in break", () => !Player.IsBreakTime.Value);
|
AddAssert("not in break", () => !Player.IsBreakTime.Value);
|
||||||
AddStep("move cursor to center", () => InputManager.MoveMouseTo(Player.DrawableRuleset.Playfield));
|
AddStep("move cursor to center", () => InputManager.MoveMouseTo(Player.DrawableRuleset.Playfield));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void seekTo(double time)
|
||||||
|
{
|
||||||
|
AddStep($"seek to {time}ms", () => Player.GameplayClockContainer.Seek(time));
|
||||||
|
AddUntilStep("wait for seek to finish", () => Player.DrawableRuleset.FrameStableClock.CurrentTime, () => Is.EqualTo(time).Within(500));
|
||||||
|
}
|
||||||
|
|
||||||
private void checkKey(Func<KeyCounter> counter, int count, bool active)
|
private void checkKey(Func<KeyCounter> counter, int count, bool active)
|
||||||
{
|
{
|
||||||
AddAssert($"key count = {count}", () => counter().CountPresses.Value, () => Is.EqualTo(count));
|
AddAssert($"key count = {count}", () => counter().CountPresses.Value, () => Is.EqualTo(count));
|
||||||
|
@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private SkinManager skins { get; set; } = null!;
|
private SkinManager skins { get; set; } = null!;
|
||||||
|
|
||||||
private SkinComponentsContainer targetContainer => Player.ChildrenOfType<SkinComponentsContainer>().First();
|
private SkinnableContainer targetContainer => Player.ChildrenOfType<SkinnableContainer>().First();
|
||||||
|
|
||||||
[SetUpSteps]
|
[SetUpSteps]
|
||||||
public override void SetUpSteps()
|
public override void SetUpSteps()
|
||||||
@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
AddStep("Add big black boxes", () =>
|
AddStep("Add big black boxes", () =>
|
||||||
{
|
{
|
||||||
var target = Player.ChildrenOfType<SkinComponentsContainer>().First();
|
var target = Player.ChildrenOfType<SkinnableContainer>().First();
|
||||||
target.Add(box1 = new BigBlackBox
|
target.Add(box1 = new BigBlackBox
|
||||||
{
|
{
|
||||||
Position = new Vector2(-90),
|
Position = new Vector2(-90),
|
||||||
@ -200,14 +200,14 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestUndoEditHistory()
|
public void TestUndoEditHistory()
|
||||||
{
|
{
|
||||||
SkinComponentsContainer firstTarget = null!;
|
SkinnableContainer firstTarget = null!;
|
||||||
TestSkinEditorChangeHandler changeHandler = null!;
|
TestSkinEditorChangeHandler changeHandler = null!;
|
||||||
byte[] defaultState = null!;
|
byte[] defaultState = null!;
|
||||||
IEnumerable<ISerialisableDrawable> testComponents = null!;
|
IEnumerable<ISerialisableDrawable> testComponents = null!;
|
||||||
|
|
||||||
AddStep("Load necessary things", () =>
|
AddStep("Load necessary things", () =>
|
||||||
{
|
{
|
||||||
firstTarget = Player.ChildrenOfType<SkinComponentsContainer>().First();
|
firstTarget = Player.ChildrenOfType<SkinnableContainer>().First();
|
||||||
changeHandler = new TestSkinEditorChangeHandler(firstTarget);
|
changeHandler = new TestSkinEditorChangeHandler(firstTarget);
|
||||||
|
|
||||||
changeHandler.SaveState();
|
changeHandler.SaveState();
|
||||||
@ -377,11 +377,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
() => Is.EqualTo(3));
|
() => Is.EqualTo(3));
|
||||||
}
|
}
|
||||||
|
|
||||||
private SkinComponentsContainer globalHUDTarget => Player.ChildrenOfType<SkinComponentsContainer>()
|
private SkinnableContainer globalHUDTarget => Player.ChildrenOfType<SkinnableContainer>()
|
||||||
.Single(c => c.Lookup.Target == SkinComponentsContainerLookup.TargetArea.MainHUDComponents && c.Lookup.Ruleset == null);
|
.Single(c => c.Lookup.Lookup == GlobalSkinnableContainers.MainHUDComponents && c.Lookup.Ruleset == null);
|
||||||
|
|
||||||
private SkinComponentsContainer rulesetHUDTarget => Player.ChildrenOfType<SkinComponentsContainer>()
|
private SkinnableContainer rulesetHUDTarget => Player.ChildrenOfType<SkinnableContainer>()
|
||||||
.Single(c => c.Lookup.Target == SkinComponentsContainerLookup.TargetArea.MainHUDComponents && c.Lookup.Ruleset != null);
|
.Single(c => c.Lookup.Lookup == GlobalSkinnableContainers.MainHUDComponents && c.Lookup.Ruleset != null);
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestMigrationArgon()
|
public void TestMigrationArgon()
|
||||||
|
@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestToggleEditor()
|
public void TestToggleEditor()
|
||||||
{
|
{
|
||||||
var skinComponentsContainer = new SkinComponentsContainer(new SkinComponentsContainerLookup(SkinComponentsContainerLookup.TargetArea.SongSelect));
|
var skinComponentsContainer = new SkinnableContainer(new GlobalSkinnableContainerLookup(GlobalSkinnableContainers.SongSelect));
|
||||||
|
|
||||||
AddStep("show available components", () => SetContents(_ => new SkinComponentToolbox(skinComponentsContainer, null)
|
AddStep("show available components", () => SetContents(_ => new SkinComponentToolbox(skinComponentsContainer, null)
|
||||||
{
|
{
|
||||||
|
@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
private IEnumerable<HUDOverlay> hudOverlays => CreatedDrawables.OfType<HUDOverlay>();
|
private IEnumerable<HUDOverlay> hudOverlays => CreatedDrawables.OfType<HUDOverlay>();
|
||||||
|
|
||||||
// best way to check without exposing.
|
// best way to check without exposing.
|
||||||
private Drawable hideTarget => hudOverlay.ChildrenOfType<SkinComponentsContainer>().First();
|
private Drawable hideTarget => hudOverlay.ChildrenOfType<SkinnableContainer>().First();
|
||||||
private Drawable keyCounterFlow => hudOverlay.ChildrenOfType<KeyCounterDisplay>().First().ChildrenOfType<FillFlowContainer<KeyCounter>>().Single();
|
private Drawable keyCounterFlow => hudOverlay.ChildrenOfType<KeyCounterDisplay>().First().ChildrenOfType<FillFlowContainer<KeyCounter>>().Single();
|
||||||
|
|
||||||
public TestSceneSkinnableHUDOverlay()
|
public TestSceneSkinnableHUDOverlay()
|
||||||
@ -101,7 +101,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
});
|
});
|
||||||
AddUntilStep("HUD overlay loaded", () => hudOverlay.IsAlive);
|
AddUntilStep("HUD overlay loaded", () => hudOverlay.IsAlive);
|
||||||
AddUntilStep("components container loaded",
|
AddUntilStep("components container loaded",
|
||||||
() => hudOverlay.ChildrenOfType<SkinComponentsContainer>().Any(scc => scc.ComponentsLoaded));
|
() => hudOverlay.ChildrenOfType<SkinnableContainer>().Any(scc => scc.ComponentsLoaded));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
|
protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
|
||||||
|
@ -336,13 +336,13 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
});
|
});
|
||||||
|
|
||||||
AddStep("change to triangles skin", () => Game.Dependencies.Get<SkinManager>().SetSkinFromConfiguration(SkinInfo.TRIANGLES_SKIN.ToString()));
|
AddStep("change to triangles skin", () => Game.Dependencies.Get<SkinManager>().SetSkinFromConfiguration(SkinInfo.TRIANGLES_SKIN.ToString()));
|
||||||
AddUntilStep("components loaded", () => Game.ChildrenOfType<SkinComponentsContainer>().All(c => c.ComponentsLoaded));
|
AddUntilStep("components loaded", () => Game.ChildrenOfType<SkinnableContainer>().All(c => c.ComponentsLoaded));
|
||||||
// sort of implicitly relies on song select not being skinnable.
|
// sort of implicitly relies on song select not being skinnable.
|
||||||
// TODO: revisit if the above ever changes
|
// TODO: revisit if the above ever changes
|
||||||
AddUntilStep("skin changed", () => !skinEditor.ChildrenOfType<SkinBlueprint>().Any());
|
AddUntilStep("skin changed", () => !skinEditor.ChildrenOfType<SkinBlueprint>().Any());
|
||||||
|
|
||||||
AddStep("change back to modified skin", () => Game.Dependencies.Get<SkinManager>().SetSkinFromConfiguration(editedSkinId.ToString()));
|
AddStep("change back to modified skin", () => Game.Dependencies.Get<SkinManager>().SetSkinFromConfiguration(editedSkinId.ToString()));
|
||||||
AddUntilStep("components loaded", () => Game.ChildrenOfType<SkinComponentsContainer>().All(c => c.ComponentsLoaded));
|
AddUntilStep("components loaded", () => Game.ChildrenOfType<SkinnableContainer>().All(c => c.ComponentsLoaded));
|
||||||
AddUntilStep("changes saved", () => skinEditor.ChildrenOfType<SkinBlueprint>().Any());
|
AddUntilStep("changes saved", () => skinEditor.ChildrenOfType<SkinBlueprint>().Any());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ using osu.Game.Online.API;
|
|||||||
using osu.Game.Online.API.Requests;
|
using osu.Game.Online.API.Requests;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Overlays.Comments;
|
using osu.Game.Overlays.Comments;
|
||||||
|
using osu.Game.Overlays.Comments.Buttons;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Online
|
namespace osu.Game.Tests.Visual.Online
|
||||||
{
|
{
|
||||||
@ -58,6 +59,11 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
AddStep("show comments", () => commentsContainer.ShowComments(CommentableType.Beatmapset, 123));
|
AddStep("show comments", () => commentsContainer.ShowComments(CommentableType.Beatmapset, 123));
|
||||||
AddUntilStep("show more button hidden",
|
AddUntilStep("show more button hidden",
|
||||||
() => commentsContainer.ChildrenOfType<CommentsShowMoreButton>().Single().Alpha == 0);
|
() => commentsContainer.ChildrenOfType<CommentsShowMoreButton>().Single().Alpha == 0);
|
||||||
|
|
||||||
|
if (withPinned)
|
||||||
|
AddAssert("pinned comment replies collapsed", () => commentsContainer.ChildrenOfType<ShowRepliesButton>().First().Expanded.Value, () => Is.False);
|
||||||
|
else
|
||||||
|
AddAssert("first comment replies expanded", () => commentsContainer.ChildrenOfType<ShowRepliesButton>().First().Expanded.Value, () => Is.True);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(false)]
|
[TestCase(false)]
|
||||||
@ -302,7 +308,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
bundle.Comments.Add(new Comment
|
bundle.Comments.Add(new Comment
|
||||||
{
|
{
|
||||||
Id = 20,
|
Id = 20,
|
||||||
Message = "Reply to pinned comment",
|
Message = "Reply to pinned comment initially hidden",
|
||||||
LegacyName = "AbandonedUser",
|
LegacyName = "AbandonedUser",
|
||||||
CreatedAt = DateTimeOffset.Now,
|
CreatedAt = DateTimeOffset.Now,
|
||||||
VotesCount = 0,
|
VotesCount = 0,
|
||||||
|
@ -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 NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -11,6 +12,7 @@ using osu.Game.Overlays;
|
|||||||
using osu.Game.Overlays.Profile;
|
using osu.Game.Overlays.Profile;
|
||||||
using osu.Game.Overlays.Profile.Header.Components;
|
using osu.Game.Overlays.Profile.Header.Components;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Scoring;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Online
|
namespace osu.Game.Tests.Visual.Online
|
||||||
@ -60,5 +62,12 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
change.Invoke(User.Value!.User.DailyChallengeStatistics);
|
change.Invoke(User.Value!.User.DailyChallengeStatistics);
|
||||||
User.Value = new UserProfileData(User.Value.User, User.Value.Ruleset);
|
User.Value = new UserProfileData(User.Value.User, User.Value.Ruleset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPlayCountRankingTier()
|
||||||
|
{
|
||||||
|
AddAssert("1 before silver", () => DailyChallengeStatsDisplay.TierForPlayCount(30) == RankingTier.Bronze);
|
||||||
|
AddAssert("first silver", () => DailyChallengeStatsDisplay.TierForPlayCount(31) == RankingTier.Silver);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@ namespace osu.Game.Overlays.Comments
|
|||||||
|
|
||||||
public readonly BindableList<DrawableComment> Replies = new BindableList<DrawableComment>();
|
public readonly BindableList<DrawableComment> Replies = new BindableList<DrawableComment>();
|
||||||
|
|
||||||
private readonly BindableBool childrenExpanded = new BindableBool(true);
|
private readonly BindableBool childrenExpanded;
|
||||||
|
|
||||||
private int currentPage;
|
private int currentPage;
|
||||||
|
|
||||||
@ -92,6 +92,8 @@ namespace osu.Game.Overlays.Comments
|
|||||||
{
|
{
|
||||||
Comment = comment;
|
Comment = comment;
|
||||||
Meta = meta;
|
Meta = meta;
|
||||||
|
|
||||||
|
childrenExpanded = new BindableBool(!comment.Pinned);
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
|
@ -30,7 +30,8 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
private const float border_width = 5;
|
private const float border_width = 5;
|
||||||
|
|
||||||
private readonly Medal medal;
|
public readonly Medal Medal;
|
||||||
|
|
||||||
private readonly Box background;
|
private readonly Box background;
|
||||||
private readonly Container backgroundStrip, particleContainer;
|
private readonly Container backgroundStrip, particleContainer;
|
||||||
private readonly BackgroundStrip leftStrip, rightStrip;
|
private readonly BackgroundStrip leftStrip, rightStrip;
|
||||||
@ -44,7 +45,7 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
public MedalAnimation(Medal medal)
|
public MedalAnimation(Medal medal)
|
||||||
{
|
{
|
||||||
this.medal = medal;
|
Medal = medal;
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
Child = content = new Container
|
Child = content = new Container
|
||||||
@ -168,7 +169,7 @@ namespace osu.Game.Overlays
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
LoadComponentAsync(drawableMedal = new DrawableMedal(medal)
|
LoadComponentAsync(drawableMedal = new DrawableMedal(Medal)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
Origin = Anchor.TopCentre,
|
Origin = Anchor.TopCentre,
|
||||||
|
@ -8,6 +8,7 @@ using osu.Framework.Extensions.ObjectExtensions;
|
|||||||
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.Logging;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
@ -81,7 +82,10 @@ namespace osu.Game.Overlays
|
|||||||
};
|
};
|
||||||
|
|
||||||
var medalAnimation = new MedalAnimation(medal);
|
var medalAnimation = new MedalAnimation(medal);
|
||||||
|
|
||||||
queuedMedals.Enqueue(medalAnimation);
|
queuedMedals.Enqueue(medalAnimation);
|
||||||
|
Logger.Log($"Queueing medal unlock for \"{medal.Name}\" ({queuedMedals.Count} to display)");
|
||||||
|
|
||||||
if (OverlayActivationMode.Value == OverlayActivation.All)
|
if (OverlayActivationMode.Value == OverlayActivation.All)
|
||||||
Scheduler.AddOnce(Show);
|
Scheduler.AddOnce(Show);
|
||||||
}
|
}
|
||||||
@ -95,10 +99,12 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
if (!queuedMedals.TryDequeue(out lastAnimation))
|
if (!queuedMedals.TryDequeue(out lastAnimation))
|
||||||
{
|
{
|
||||||
|
Logger.Log("All queued medals have been displayed!");
|
||||||
Hide();
|
Hide();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.Log($"Preparing to display \"{lastAnimation.Medal.Name}\"");
|
||||||
LoadComponentAsync(lastAnimation, medalContainer.Add);
|
LoadComponentAsync(lastAnimation, medalContainer.Add);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,7 +115,7 @@ namespace osu.Game.Overlays
|
|||||||
seekDelegate?.Cancel();
|
seekDelegate?.Cancel();
|
||||||
seekDelegate = Schedule(() =>
|
seekDelegate = Schedule(() =>
|
||||||
{
|
{
|
||||||
if (beatmap.Disabled || !AllowTrackControl.Value)
|
if (!AllowTrackControl.Value)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
CurrentTrack.Seek(position);
|
CurrentTrack.Seek(position);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// 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 osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.LocalisationExtensions;
|
using osu.Framework.Extensions.LocalisationExtensions;
|
||||||
@ -11,9 +12,9 @@ using osu.Framework.Graphics.Shapes;
|
|||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Localisation;
|
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Profile.Header.Components
|
namespace osu.Game.Overlays.Profile.Header.Components
|
||||||
{
|
{
|
||||||
@ -107,15 +108,18 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
|||||||
APIUserDailyChallengeStatistics stats = User.Value.User.DailyChallengeStatistics;
|
APIUserDailyChallengeStatistics stats = User.Value.User.DailyChallengeStatistics;
|
||||||
|
|
||||||
dailyPlayCount.Text = DailyChallengeStatsDisplayStrings.UnitDay(stats.PlayCount.ToLocalisableString("N0"));
|
dailyPlayCount.Text = DailyChallengeStatsDisplayStrings.UnitDay(stats.PlayCount.ToLocalisableString("N0"));
|
||||||
dailyPlayCount.Colour = colours.ForRankingTier(tierForPlayCount(stats.PlayCount));
|
dailyPlayCount.Colour = colours.ForRankingTier(TierForPlayCount(stats.PlayCount));
|
||||||
|
|
||||||
TooltipContent = new DailyChallengeTooltipData(colourProvider, stats);
|
TooltipContent = new DailyChallengeTooltipData(colourProvider, stats);
|
||||||
|
|
||||||
Show();
|
Show();
|
||||||
|
|
||||||
static RankingTier tierForPlayCount(int playCount) => DailyChallengeStatsTooltip.TierForDaily(playCount / 3);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rounding up is needed here to ensure the overlay shows the same colour as osu-web for the play count.
|
||||||
|
// This is because, for example, 31 / 3 > 10 in JavaScript because floats are used, while here it would
|
||||||
|
// get truncated to 10 with an integer division and show a lower tier.
|
||||||
|
public static RankingTier TierForPlayCount(int playCount) => DailyChallengeStatsTooltip.TierForDaily((int)Math.Ceiling(playCount / 3.0d));
|
||||||
|
|
||||||
public ITooltip<DailyChallengeTooltipData> GetCustomTooltip() => new DailyChallengeStatsTooltip();
|
public ITooltip<DailyChallengeTooltipData> GetCustomTooltip() => new DailyChallengeStatsTooltip();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
{
|
{
|
||||||
public Action<Type>? RequestPlacement;
|
public Action<Type>? RequestPlacement;
|
||||||
|
|
||||||
private readonly SkinComponentsContainer target;
|
private readonly SkinnableContainer target;
|
||||||
|
|
||||||
private readonly RulesetInfo? ruleset;
|
private readonly RulesetInfo? ruleset;
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="target">The target. This is mainly used as a dependency source to find candidate components.</param>
|
/// <param name="target">The target. This is mainly used as a dependency source to find candidate components.</param>
|
||||||
/// <param name="ruleset">A ruleset to filter components by. If null, only components which are not ruleset-specific will be included.</param>
|
/// <param name="ruleset">A ruleset to filter components by. If null, only components which are not ruleset-specific will be included.</param>
|
||||||
public SkinComponentToolbox(SkinComponentsContainer target, RulesetInfo? ruleset)
|
public SkinComponentToolbox(SkinnableContainer target, RulesetInfo? ruleset)
|
||||||
: base(ruleset == null ? SkinEditorStrings.Components : LocalisableString.Interpolate($"{SkinEditorStrings.Components} ({ruleset.Name})"))
|
: base(ruleset == null ? SkinEditorStrings.Components : LocalisableString.Interpolate($"{SkinEditorStrings.Components} ({ruleset.Name})"))
|
||||||
{
|
{
|
||||||
this.target = target;
|
this.target = target;
|
||||||
|
@ -72,7 +72,7 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
[Cached]
|
[Cached]
|
||||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
|
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
|
||||||
|
|
||||||
private readonly Bindable<SkinComponentsContainerLookup?> selectedTarget = new Bindable<SkinComponentsContainerLookup?>();
|
private readonly Bindable<GlobalSkinnableContainerLookup?> selectedTarget = new Bindable<GlobalSkinnableContainerLookup?>();
|
||||||
|
|
||||||
private bool hasBegunMutating;
|
private bool hasBegunMutating;
|
||||||
|
|
||||||
@ -330,7 +330,7 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void targetChanged(ValueChangedEvent<SkinComponentsContainerLookup?> target)
|
private void targetChanged(ValueChangedEvent<GlobalSkinnableContainerLookup?> target)
|
||||||
{
|
{
|
||||||
foreach (var toolbox in componentsSidebar.OfType<SkinComponentToolbox>())
|
foreach (var toolbox in componentsSidebar.OfType<SkinComponentToolbox>())
|
||||||
toolbox.Expire();
|
toolbox.Expire();
|
||||||
@ -360,7 +360,7 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
{
|
{
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new SettingsDropdown<SkinComponentsContainerLookup?>
|
new SettingsDropdown<GlobalSkinnableContainerLookup?>
|
||||||
{
|
{
|
||||||
Items = availableTargets.Select(t => t.Lookup).Distinct(),
|
Items = availableTargets.Select(t => t.Lookup).Distinct(),
|
||||||
Current = selectedTarget,
|
Current = selectedTarget,
|
||||||
@ -472,18 +472,18 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
settingsSidebar.Add(new SkinSettingsToolbox(component));
|
settingsSidebar.Add(new SkinSettingsToolbox(component));
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<SkinComponentsContainer> availableTargets => targetScreen.ChildrenOfType<SkinComponentsContainer>();
|
private IEnumerable<SkinnableContainer> availableTargets => targetScreen.ChildrenOfType<SkinnableContainer>();
|
||||||
|
|
||||||
private SkinComponentsContainer? getFirstTarget() => availableTargets.FirstOrDefault();
|
private SkinnableContainer? getFirstTarget() => availableTargets.FirstOrDefault();
|
||||||
|
|
||||||
private SkinComponentsContainer? getTarget(SkinComponentsContainerLookup? target)
|
private SkinnableContainer? getTarget(GlobalSkinnableContainerLookup? target)
|
||||||
{
|
{
|
||||||
return availableTargets.FirstOrDefault(c => c.Lookup.Equals(target));
|
return availableTargets.FirstOrDefault(c => c.Lookup.Equals(target));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void revert()
|
private void revert()
|
||||||
{
|
{
|
||||||
SkinComponentsContainer[] targetContainers = availableTargets.ToArray();
|
SkinnableContainer[] targetContainers = availableTargets.ToArray();
|
||||||
|
|
||||||
foreach (var t in targetContainers)
|
foreach (var t in targetContainers)
|
||||||
{
|
{
|
||||||
@ -555,7 +555,7 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
if (targetScreen?.IsLoaded != true)
|
if (targetScreen?.IsLoaded != true)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
SkinComponentsContainer[] targetContainers = availableTargets.ToArray();
|
SkinnableContainer[] targetContainers = availableTargets.ToArray();
|
||||||
|
|
||||||
if (!targetContainers.All(c => c.ComponentsLoaded))
|
if (!targetContainers.All(c => c.ComponentsLoaded))
|
||||||
return;
|
return;
|
||||||
@ -600,7 +600,7 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
|
|
||||||
public void BringSelectionToFront()
|
public void BringSelectionToFront()
|
||||||
{
|
{
|
||||||
if (getTarget(selectedTarget.Value) is not SkinComponentsContainer target)
|
if (getTarget(selectedTarget.Value) is not SkinnableContainer target)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
changeHandler?.BeginChange();
|
changeHandler?.BeginChange();
|
||||||
@ -624,7 +624,7 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
|
|
||||||
public void SendSelectionToBack()
|
public void SendSelectionToBack()
|
||||||
{
|
{
|
||||||
if (getTarget(selectedTarget.Value) is not SkinComponentsContainer target)
|
if (getTarget(selectedTarget.Value) is not SkinnableContainer target)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
changeHandler?.BeginChange();
|
changeHandler?.BeginChange();
|
||||||
|
@ -163,7 +163,7 @@ namespace osu.Game.Rulesets.Judgements
|
|||||||
if (JudgementBody != null)
|
if (JudgementBody != null)
|
||||||
RemoveInternal(JudgementBody, true);
|
RemoveInternal(JudgementBody, true);
|
||||||
|
|
||||||
AddInternal(JudgementBody = new SkinnableDrawable(new GameplaySkinComponentLookup<HitResult>(type), _ =>
|
AddInternal(JudgementBody = new SkinnableDrawable(new SkinComponentLookup<HitResult>(type), _ =>
|
||||||
CreateDefaultJudgement(type), confineMode: ConfineMode.NoScaling));
|
CreateDefaultJudgement(type), confineMode: ConfineMode.NoScaling));
|
||||||
|
|
||||||
JudgementBody.OnSkinChanged += () =>
|
JudgementBody.OnSkinChanged += () =>
|
||||||
|
@ -11,6 +11,7 @@ using osu.Framework.Graphics.Cursor;
|
|||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
@ -139,7 +140,7 @@ namespace osu.Game.Rulesets.UI
|
|||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Alpha = 0,
|
Alpha = 0,
|
||||||
Font = OsuFont.Numeric.With(null, 22f),
|
Font = OsuFont.Numeric.With(size: 22f, weight: FontWeight.Black),
|
||||||
UseFullGlyphHeight = false,
|
UseFullGlyphHeight = false,
|
||||||
Text = mod.Acronym
|
Text = mod.Acronym
|
||||||
},
|
},
|
||||||
@ -204,7 +205,7 @@ namespace osu.Game.Rulesets.UI
|
|||||||
|
|
||||||
private void updateColour()
|
private void updateColour()
|
||||||
{
|
{
|
||||||
modAcronym.Colour = modIcon.Colour = OsuColour.Gray(84);
|
modAcronym.Colour = modIcon.Colour = Interpolation.ValueAt<Colour4>(0.1f, Colour4.Black, backgroundColour, 0, 1);
|
||||||
|
|
||||||
extendedText.Colour = background.Colour = Selected.Value ? backgroundColour.Lighten(0.2f) : backgroundColour;
|
extendedText.Colour = background.Colour = Selected.Value ? backgroundColour.Lighten(0.2f) : backgroundColour;
|
||||||
extendedBackground.Colour = Selected.Value ? backgroundColour.Darken(2.4f) : backgroundColour.Darken(2.8f);
|
extendedBackground.Colour = Selected.Value ? backgroundColour.Darken(2.4f) : backgroundColour.Darken(2.8f);
|
||||||
|
@ -229,7 +229,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
EditorBeatmap.PerformOnSelection(h =>
|
EditorBeatmap.PerformOnSelection(h =>
|
||||||
{
|
{
|
||||||
if (h.Samples.All(s => s.Bank == bankName))
|
if (hasRelevantBank(h))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
h.Samples = h.Samples.Select(s => s.With(newBank: bankName)).ToList();
|
h.Samples = h.Samples.Select(s => s.With(newBank: bankName)).ToList();
|
||||||
@ -269,10 +269,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
EditorBeatmap.PerformOnSelection(h =>
|
EditorBeatmap.PerformOnSelection(h =>
|
||||||
{
|
{
|
||||||
// Make sure there isn't already an existing sample
|
// Make sure there isn't already an existing sample
|
||||||
if (h.Samples.Any(s => s.Name == sampleName))
|
if (h.Samples.All(s => s.Name != sampleName))
|
||||||
return;
|
h.Samples.Add(h.CreateHitSampleInfo(sampleName));
|
||||||
|
|
||||||
h.Samples.Add(h.CreateHitSampleInfo(sampleName));
|
|
||||||
|
|
||||||
if (h is IHasRepeats hasRepeats)
|
if (h is IHasRepeats hasRepeats)
|
||||||
{
|
{
|
||||||
|
@ -93,14 +93,15 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
|||||||
protected override BackgroundScreen CreateBackground() => new DailyChallengeIntroBackgroundScreen(colourProvider);
|
protected override BackgroundScreen CreateBackground() => new DailyChallengeIntroBackgroundScreen(colourProvider);
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(BeatmapDifficultyCache difficultyCache, BeatmapModelDownloader beatmapDownloader, OsuConfigManager config, AudioManager audio)
|
private void load(RulesetStore rulesets, BeatmapDifficultyCache difficultyCache, BeatmapModelDownloader beatmapDownloader, OsuConfigManager config, AudioManager audio)
|
||||||
{
|
{
|
||||||
const float horizontal_info_size = 500f;
|
const float horizontal_info_size = 500f;
|
||||||
|
|
||||||
Ruleset ruleset = Ruleset.Value.CreateInstance();
|
|
||||||
|
|
||||||
StarRatingDisplay starRatingDisplay;
|
StarRatingDisplay starRatingDisplay;
|
||||||
|
|
||||||
|
IBeatmapInfo beatmap = item.Beatmap;
|
||||||
|
Ruleset ruleset = rulesets.GetRuleset(item.Beatmap.Ruleset.ShortName)!.CreateInstance();
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
beatmapAvailabilityTracker,
|
beatmapAvailabilityTracker,
|
||||||
@ -242,13 +243,13 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
|||||||
Origin = Anchor.TopCentre,
|
Origin = Anchor.TopCentre,
|
||||||
Shear = new Vector2(-OsuGame.SHEAR, 0f),
|
Shear = new Vector2(-OsuGame.SHEAR, 0f),
|
||||||
MaxWidth = horizontal_info_size,
|
MaxWidth = horizontal_info_size,
|
||||||
Text = item.Beatmap.BeatmapSet!.Metadata.GetDisplayTitleRomanisable(false),
|
Text = beatmap.BeatmapSet!.Metadata.GetDisplayTitleRomanisable(false),
|
||||||
Padding = new MarginPadding { Horizontal = 5f },
|
Padding = new MarginPadding { Horizontal = 5f },
|
||||||
Font = OsuFont.GetFont(size: 26),
|
Font = OsuFont.GetFont(size: 26),
|
||||||
},
|
},
|
||||||
new TruncatingSpriteText
|
new TruncatingSpriteText
|
||||||
{
|
{
|
||||||
Text = $"Difficulty: {item.Beatmap.DifficultyName}",
|
Text = $"Difficulty: {beatmap.DifficultyName}",
|
||||||
Font = OsuFont.GetFont(size: 20, italics: true),
|
Font = OsuFont.GetFont(size: 20, italics: true),
|
||||||
MaxWidth = horizontal_info_size,
|
MaxWidth = horizontal_info_size,
|
||||||
Shear = new Vector2(-OsuGame.SHEAR, 0f),
|
Shear = new Vector2(-OsuGame.SHEAR, 0f),
|
||||||
@ -257,7 +258,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
|||||||
},
|
},
|
||||||
new TruncatingSpriteText
|
new TruncatingSpriteText
|
||||||
{
|
{
|
||||||
Text = $"by {item.Beatmap.Metadata.Author.Username}",
|
Text = $"by {beatmap.Metadata.Author.Username}",
|
||||||
Font = OsuFont.GetFont(size: 16, italics: true),
|
Font = OsuFont.GetFont(size: 16, italics: true),
|
||||||
MaxWidth = horizontal_info_size,
|
MaxWidth = horizontal_info_size,
|
||||||
Shear = new Vector2(-OsuGame.SHEAR, 0f),
|
Shear = new Vector2(-OsuGame.SHEAR, 0f),
|
||||||
@ -309,14 +310,14 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
starDifficulty = difficultyCache.GetBindableDifficulty(item.Beatmap);
|
starDifficulty = difficultyCache.GetBindableDifficulty(beatmap);
|
||||||
starDifficulty.BindValueChanged(star =>
|
starDifficulty.BindValueChanged(star =>
|
||||||
{
|
{
|
||||||
if (star.NewValue != null)
|
if (star.NewValue != null)
|
||||||
starRatingDisplay.Current.Value = star.NewValue.Value;
|
starRatingDisplay.Current.Value = star.NewValue.Value;
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
LoadComponentAsync(new OnlineBeatmapSetCover(item.Beatmap.BeatmapSet as IBeatmapSetOnlineInfo)
|
LoadComponentAsync(new OnlineBeatmapSetCover(beatmap.BeatmapSet as IBeatmapSetOnlineInfo)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
@ -334,8 +335,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
|||||||
|
|
||||||
if (config.Get<bool>(OsuSetting.AutomaticallyDownloadMissingBeatmaps))
|
if (config.Get<bool>(OsuSetting.AutomaticallyDownloadMissingBeatmaps))
|
||||||
{
|
{
|
||||||
if (!beatmapManager.IsAvailableLocally(new BeatmapSetInfo { OnlineID = item.Beatmap.BeatmapSet!.OnlineID }))
|
if (!beatmapManager.IsAvailableLocally(new BeatmapSetInfo { OnlineID = beatmap.BeatmapSet!.OnlineID }))
|
||||||
beatmapDownloader.Download(item.Beatmap.BeatmapSet!, config.Get<bool>(OsuSetting.PreferNoVideo));
|
beatmapDownloader.Download(beatmap.BeatmapSet!, config.Get<bool>(OsuSetting.PreferNoVideo));
|
||||||
}
|
}
|
||||||
|
|
||||||
dateWindupSample = audio.Samples.Get(@"DailyChallenge/date-windup");
|
dateWindupSample = audio.Samples.Get(@"DailyChallenge/date-windup");
|
||||||
|
@ -11,12 +11,12 @@ namespace osu.Game.Screens.Play.Break
|
|||||||
{
|
{
|
||||||
public partial class LetterboxOverlay : CompositeDrawable
|
public partial class LetterboxOverlay : CompositeDrawable
|
||||||
{
|
{
|
||||||
private const int height = 350;
|
|
||||||
|
|
||||||
private static readonly Color4 transparent_black = new Color4(0, 0, 0, 0);
|
private static readonly Color4 transparent_black = new Color4(0, 0, 0, 0);
|
||||||
|
|
||||||
public LetterboxOverlay()
|
public LetterboxOverlay()
|
||||||
{
|
{
|
||||||
|
const int height = 150;
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Effects;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Game.Beatmaps.Timing;
|
using osu.Game.Beatmaps.Timing;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens.Play.Break;
|
using osu.Game.Screens.Play.Break;
|
||||||
@ -29,7 +30,7 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
private readonly Container fadeContainer;
|
private readonly Container fadeContainer;
|
||||||
|
|
||||||
private IReadOnlyList<BreakPeriod> breaks;
|
private IReadOnlyList<BreakPeriod> breaks = Array.Empty<BreakPeriod>();
|
||||||
|
|
||||||
public IReadOnlyList<BreakPeriod> Breaks
|
public IReadOnlyList<BreakPeriod> Breaks
|
||||||
{
|
{
|
||||||
@ -69,6 +70,30 @@ namespace osu.Game.Screens.Play
|
|||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
},
|
},
|
||||||
|
new CircularContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Width = 80,
|
||||||
|
Height = 4,
|
||||||
|
Masking = true,
|
||||||
|
EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Shadow,
|
||||||
|
Radius = 260,
|
||||||
|
Colour = OsuColour.Gray(0.2f).Opacity(0.8f),
|
||||||
|
Roundness = 12
|
||||||
|
},
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Alpha = 0,
|
||||||
|
AlwaysPresent = true,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
remainingTimeAdjustmentBox = new Container
|
remainingTimeAdjustmentBox = new Container
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
@ -111,11 +136,8 @@ namespace osu.Game.Screens.Play
|
|||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
initializeBreaks();
|
initializeBreaks();
|
||||||
|
|
||||||
if (scoreProcessor != null)
|
info.AccuracyDisplay.Current.BindTo(scoreProcessor.Accuracy);
|
||||||
{
|
((IBindable<ScoreRank>)info.GradeDisplay.Current).BindTo(scoreProcessor.Rank);
|
||||||
info.AccuracyDisplay.Current.BindTo(scoreProcessor.Accuracy);
|
|
||||||
((IBindable<ScoreRank>)info.GradeDisplay.Current).BindTo(scoreProcessor.Rank);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
@ -130,8 +152,6 @@ namespace osu.Game.Screens.Play
|
|||||||
FinishTransforms(true);
|
FinishTransforms(true);
|
||||||
Scheduler.CancelDelayedTasks();
|
Scheduler.CancelDelayedTasks();
|
||||||
|
|
||||||
if (breaks == null) return; // we need breaks.
|
|
||||||
|
|
||||||
foreach (var b in breaks)
|
foreach (var b in breaks)
|
||||||
{
|
{
|
||||||
if (!b.HasEffect)
|
if (!b.HasEffect)
|
||||||
|
@ -95,10 +95,10 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
private readonly BindableBool holdingForHUD = new BindableBool();
|
private readonly BindableBool holdingForHUD = new BindableBool();
|
||||||
|
|
||||||
private readonly SkinComponentsContainer mainComponents;
|
private readonly SkinnableContainer mainComponents;
|
||||||
|
|
||||||
[CanBeNull]
|
[CanBeNull]
|
||||||
private readonly SkinComponentsContainer rulesetComponents;
|
private readonly SkinnableContainer rulesetComponents;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A flow which sits at the left side of the screen to house leaderboard (and related) components.
|
/// A flow which sits at the left side of the screen to house leaderboard (and related) components.
|
||||||
@ -109,7 +109,7 @@ namespace osu.Game.Screens.Play
|
|||||||
private readonly List<Drawable> hideTargets;
|
private readonly List<Drawable> hideTargets;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The container for skin components attached to <see cref="SkinComponentsContainerLookup.TargetArea.Playfield"/>
|
/// The container for skin components attached to <see cref="GlobalSkinnableContainers.Playfield"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal readonly Drawable PlayfieldSkinLayer;
|
internal readonly Drawable PlayfieldSkinLayer;
|
||||||
|
|
||||||
@ -132,7 +132,7 @@ namespace osu.Game.Screens.Play
|
|||||||
? (rulesetComponents = new HUDComponentsContainer(drawableRuleset.Ruleset.RulesetInfo) { AlwaysPresent = true, })
|
? (rulesetComponents = new HUDComponentsContainer(drawableRuleset.Ruleset.RulesetInfo) { AlwaysPresent = true, })
|
||||||
: Empty(),
|
: Empty(),
|
||||||
PlayfieldSkinLayer = drawableRuleset != null
|
PlayfieldSkinLayer = drawableRuleset != null
|
||||||
? new SkinComponentsContainer(new SkinComponentsContainerLookup(SkinComponentsContainerLookup.TargetArea.Playfield, drawableRuleset.Ruleset.RulesetInfo)) { AlwaysPresent = true, }
|
? new SkinnableContainer(new GlobalSkinnableContainerLookup(GlobalSkinnableContainers.Playfield, drawableRuleset.Ruleset.RulesetInfo)) { AlwaysPresent = true, }
|
||||||
: Empty(),
|
: Empty(),
|
||||||
topRightElements = new FillFlowContainer
|
topRightElements = new FillFlowContainer
|
||||||
{
|
{
|
||||||
@ -280,7 +280,7 @@ namespace osu.Game.Screens.Play
|
|||||||
else
|
else
|
||||||
bottomRightElements.Y = 0;
|
bottomRightElements.Y = 0;
|
||||||
|
|
||||||
void processDrawables(SkinComponentsContainer components)
|
void processDrawables(SkinnableContainer components)
|
||||||
{
|
{
|
||||||
// Avoid using foreach due to missing GetEnumerator implementation.
|
// Avoid using foreach due to missing GetEnumerator implementation.
|
||||||
// See https://github.com/ppy/osu-framework/blob/e10051e6643731e393b09de40a3a3d209a545031/osu.Framework/Bindables/IBindableList.cs#L41-L44.
|
// See https://github.com/ppy/osu-framework/blob/e10051e6643731e393b09de40a3a3d209a545031/osu.Framework/Bindables/IBindableList.cs#L41-L44.
|
||||||
@ -440,7 +440,7 @@ namespace osu.Game.Screens.Play
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private partial class HUDComponentsContainer : SkinComponentsContainer
|
private partial class HUDComponentsContainer : SkinnableContainer
|
||||||
{
|
{
|
||||||
private Bindable<ScoringMode> scoringMode;
|
private Bindable<ScoringMode> scoringMode;
|
||||||
|
|
||||||
@ -448,7 +448,7 @@ namespace osu.Game.Screens.Play
|
|||||||
private OsuConfigManager config { get; set; }
|
private OsuConfigManager config { get; set; }
|
||||||
|
|
||||||
public HUDComponentsContainer([CanBeNull] RulesetInfo ruleset = null)
|
public HUDComponentsContainer([CanBeNull] RulesetInfo ruleset = null)
|
||||||
: base(new SkinComponentsContainerLookup(SkinComponentsContainerLookup.TargetArea.MainHUDComponents, ruleset))
|
: base(new GlobalSkinnableContainerLookup(GlobalSkinnableContainers.MainHUDComponents, ruleset))
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
}
|
}
|
||||||
|
@ -446,14 +446,6 @@ namespace osu.Game.Screens.Play
|
|||||||
Children = new[]
|
Children = new[]
|
||||||
{
|
{
|
||||||
DimmableStoryboard.OverlayLayerContainer.CreateProxy(),
|
DimmableStoryboard.OverlayLayerContainer.CreateProxy(),
|
||||||
BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor)
|
|
||||||
{
|
|
||||||
Clock = DrawableRuleset.FrameStableClock,
|
|
||||||
ProcessCustomClock = false,
|
|
||||||
Breaks = working.Beatmap.Breaks
|
|
||||||
},
|
|
||||||
// display the cursor above some HUD elements.
|
|
||||||
DrawableRuleset.Cursor?.CreateProxy() ?? new Container(),
|
|
||||||
HUDOverlay = new HUDOverlay(DrawableRuleset, GameplayState.Mods, Configuration.AlwaysShowLeaderboard)
|
HUDOverlay = new HUDOverlay(DrawableRuleset, GameplayState.Mods, Configuration.AlwaysShowLeaderboard)
|
||||||
{
|
{
|
||||||
HoldToQuit =
|
HoldToQuit =
|
||||||
@ -472,6 +464,14 @@ namespace osu.Game.Screens.Play
|
|||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre
|
Origin = Anchor.Centre
|
||||||
},
|
},
|
||||||
|
BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor)
|
||||||
|
{
|
||||||
|
Clock = DrawableRuleset.FrameStableClock,
|
||||||
|
ProcessCustomClock = false,
|
||||||
|
Breaks = working.Beatmap.Breaks
|
||||||
|
},
|
||||||
|
// display the cursor above some HUD elements.
|
||||||
|
DrawableRuleset.Cursor?.CreateProxy() ?? new Container(),
|
||||||
skipIntroOverlay = new SkipOverlay(DrawableRuleset.GameplayStartTime)
|
skipIntroOverlay = new SkipOverlay(DrawableRuleset.GameplayStartTime)
|
||||||
{
|
{
|
||||||
RequestSkip = performUserRequestedSkip
|
RequestSkip = performUserRequestedSkip
|
||||||
|
@ -62,10 +62,31 @@ namespace osu.Game.Screens.Select
|
|||||||
case "length":
|
case "length":
|
||||||
return tryUpdateLengthRange(criteria, op, value);
|
return tryUpdateLengthRange(criteria, op, value);
|
||||||
|
|
||||||
case "played":
|
|
||||||
case "lastplayed":
|
case "lastplayed":
|
||||||
return tryUpdateDateAgoRange(ref criteria.LastPlayed, op, value);
|
return tryUpdateDateAgoRange(ref criteria.LastPlayed, op, value);
|
||||||
|
|
||||||
|
case "played":
|
||||||
|
if (!tryParseBool(value, out bool played))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Unplayed beatmaps are filtered on DateTimeOffset.MinValue.
|
||||||
|
|
||||||
|
if (played)
|
||||||
|
{
|
||||||
|
criteria.LastPlayed.Min = DateTimeOffset.MinValue;
|
||||||
|
criteria.LastPlayed.Max = DateTimeOffset.MaxValue;
|
||||||
|
criteria.LastPlayed.IsLowerInclusive = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
criteria.LastPlayed.Min = DateTimeOffset.MinValue;
|
||||||
|
criteria.LastPlayed.Max = DateTimeOffset.MinValue;
|
||||||
|
criteria.LastPlayed.IsLowerInclusive = true;
|
||||||
|
criteria.LastPlayed.IsUpperInclusive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
case "divisor":
|
case "divisor":
|
||||||
return TryUpdateCriteriaRange(ref criteria.BeatDivisor, op, value, tryParseInt);
|
return TryUpdateCriteriaRange(ref criteria.BeatDivisor, op, value, tryParseInt);
|
||||||
|
|
||||||
@ -133,6 +154,23 @@ namespace osu.Game.Screens.Select
|
|||||||
private static bool tryParseInt(string value, out int result) =>
|
private static bool tryParseInt(string value, out int result) =>
|
||||||
int.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out result);
|
int.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out result);
|
||||||
|
|
||||||
|
private static bool tryParseBool(string value, out bool result)
|
||||||
|
{
|
||||||
|
switch (value)
|
||||||
|
{
|
||||||
|
case "1":
|
||||||
|
result = true;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case "0":
|
||||||
|
result = false;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return bool.TryParse(value, out result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static bool tryParseEnum<TEnum>(string value, out TEnum result) where TEnum : struct
|
private static bool tryParseEnum<TEnum>(string value, out TEnum result) where TEnum : struct
|
||||||
{
|
{
|
||||||
// First try an exact match.
|
// First try an exact match.
|
||||||
|
@ -321,7 +321,7 @@ namespace osu.Game.Screens.Select
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new SkinComponentsContainer(new SkinComponentsContainerLookup(SkinComponentsContainerLookup.TargetArea.SongSelect))
|
new SkinnableContainer(new GlobalSkinnableContainerLookup(GlobalSkinnableContainers.SongSelect))
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
},
|
},
|
||||||
|
@ -96,14 +96,10 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
switch (lookup)
|
switch (lookup)
|
||||||
{
|
{
|
||||||
case SkinComponentsContainerLookup containerLookup:
|
case GlobalSkinnableContainerLookup containerLookup:
|
||||||
|
switch (containerLookup.Lookup)
|
||||||
if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer c)
|
|
||||||
return c;
|
|
||||||
|
|
||||||
switch (containerLookup.Target)
|
|
||||||
{
|
{
|
||||||
case SkinComponentsContainerLookup.TargetArea.SongSelect:
|
case GlobalSkinnableContainers.SongSelect:
|
||||||
var songSelectComponents = new DefaultSkinComponentsContainer(_ =>
|
var songSelectComponents = new DefaultSkinComponentsContainer(_ =>
|
||||||
{
|
{
|
||||||
// do stuff when we need to.
|
// do stuff when we need to.
|
||||||
@ -111,7 +107,7 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
return songSelectComponents;
|
return songSelectComponents;
|
||||||
|
|
||||||
case SkinComponentsContainerLookup.TargetArea.MainHUDComponents:
|
case GlobalSkinnableContainers.MainHUDComponents:
|
||||||
if (containerLookup.Ruleset != null)
|
if (containerLookup.Ruleset != null)
|
||||||
{
|
{
|
||||||
return new Container
|
return new Container
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
// 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.Game.Rulesets.Judgements;
|
|
||||||
using osu.Game.Rulesets.Scoring;
|
|
||||||
|
|
||||||
namespace osu.Game.Skinning
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A lookup type intended for use for skinnable gameplay components (not HUD level components).
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// The most common usage of this class is for ruleset-specific skinning implementations, but it can also be used directly
|
|
||||||
/// (see <see cref="DrawableJudgement"/>'s usage for <see cref="HitResult"/>) where ruleset-agnostic elements are required.
|
|
||||||
/// </remarks>
|
|
||||||
/// <typeparam name="T">An enum lookup type.</typeparam>
|
|
||||||
public class GameplaySkinComponentLookup<T> : ISkinComponentLookup
|
|
||||||
where T : Enum
|
|
||||||
{
|
|
||||||
public readonly T Component;
|
|
||||||
|
|
||||||
public GameplaySkinComponentLookup(T component)
|
|
||||||
{
|
|
||||||
Component = component;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,21 +2,20 @@
|
|||||||
// 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.ComponentModel;
|
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
|
|
||||||
namespace osu.Game.Skinning
|
namespace osu.Game.Skinning
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a lookup of a collection of elements that make up a particular skinnable <see cref="TargetArea"/> of the game.
|
/// Represents a lookup of a collection of elements that make up a particular skinnable <see cref="GlobalSkinnableContainers"/> of the game.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SkinComponentsContainerLookup : ISkinComponentLookup, IEquatable<SkinComponentsContainerLookup>
|
public class GlobalSkinnableContainerLookup : ISkinComponentLookup, IEquatable<GlobalSkinnableContainerLookup>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The target area / layer of the game for which skin components will be returned.
|
/// The target area / layer of the game for which skin components will be returned.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly TargetArea Target;
|
public readonly GlobalSkinnableContainers Lookup;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The ruleset for which skin components should be returned.
|
/// The ruleset for which skin components should be returned.
|
||||||
@ -24,25 +23,25 @@ namespace osu.Game.Skinning
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly RulesetInfo? Ruleset;
|
public readonly RulesetInfo? Ruleset;
|
||||||
|
|
||||||
public SkinComponentsContainerLookup(TargetArea target, RulesetInfo? ruleset = null)
|
public GlobalSkinnableContainerLookup(GlobalSkinnableContainers lookup, RulesetInfo? ruleset = null)
|
||||||
{
|
{
|
||||||
Target = target;
|
Lookup = lookup;
|
||||||
Ruleset = ruleset;
|
Ruleset = ruleset;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
if (Ruleset == null) return Target.GetDescription();
|
if (Ruleset == null) return Lookup.GetDescription();
|
||||||
|
|
||||||
return $"{Target.GetDescription()} (\"{Ruleset.Name}\" only)";
|
return $"{Lookup.GetDescription()} (\"{Ruleset.Name}\" only)";
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Equals(SkinComponentsContainerLookup? other)
|
public bool Equals(GlobalSkinnableContainerLookup? other)
|
||||||
{
|
{
|
||||||
if (ReferenceEquals(null, other)) return false;
|
if (ReferenceEquals(null, other)) return false;
|
||||||
if (ReferenceEquals(this, other)) return true;
|
if (ReferenceEquals(this, other)) return true;
|
||||||
|
|
||||||
return Target == other.Target && (ReferenceEquals(Ruleset, other.Ruleset) || Ruleset?.Equals(other.Ruleset) == true);
|
return Lookup == other.Lookup && (ReferenceEquals(Ruleset, other.Ruleset) || Ruleset?.Equals(other.Ruleset) == true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool Equals(object? obj)
|
public override bool Equals(object? obj)
|
||||||
@ -51,27 +50,12 @@ namespace osu.Game.Skinning
|
|||||||
if (ReferenceEquals(this, obj)) return true;
|
if (ReferenceEquals(this, obj)) return true;
|
||||||
if (obj.GetType() != GetType()) return false;
|
if (obj.GetType() != GetType()) return false;
|
||||||
|
|
||||||
return Equals((SkinComponentsContainerLookup)obj);
|
return Equals((GlobalSkinnableContainerLookup)obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override int GetHashCode()
|
public override int GetHashCode()
|
||||||
{
|
{
|
||||||
return HashCode.Combine((int)Target, Ruleset);
|
return HashCode.Combine((int)Lookup, Ruleset);
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a particular area or part of a game screen whose layout can be customised using the skin editor.
|
|
||||||
/// </summary>
|
|
||||||
public enum TargetArea
|
|
||||||
{
|
|
||||||
[Description("HUD")]
|
|
||||||
MainHUDComponents,
|
|
||||||
|
|
||||||
[Description("Song select")]
|
|
||||||
SongSelect,
|
|
||||||
|
|
||||||
[Description("Playfield")]
|
|
||||||
Playfield
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
22
osu.Game/Skinning/GlobalSkinnableContainers.cs
Normal file
22
osu.Game/Skinning/GlobalSkinnableContainers.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// 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.ComponentModel;
|
||||||
|
|
||||||
|
namespace osu.Game.Skinning
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a particular area or part of a game screen whose layout can be customised using the skin editor.
|
||||||
|
/// </summary>
|
||||||
|
public enum GlobalSkinnableContainers
|
||||||
|
{
|
||||||
|
[Description("HUD")]
|
||||||
|
MainHUDComponents,
|
||||||
|
|
||||||
|
[Description("Song select")]
|
||||||
|
SongSelect,
|
||||||
|
|
||||||
|
[Description("Playfield")]
|
||||||
|
Playfield
|
||||||
|
}
|
||||||
|
}
|
@ -12,7 +12,7 @@ namespace osu.Game.Skinning
|
|||||||
/// to scope particular lookup variations. Using this, a ruleset or skin implementation could make its own lookup
|
/// to scope particular lookup variations. Using this, a ruleset or skin implementation could make its own lookup
|
||||||
/// type to scope away from more global contexts.
|
/// type to scope away from more global contexts.
|
||||||
///
|
///
|
||||||
/// More commonly, a ruleset could make use of <see cref="GameplaySkinComponentLookup{T}"/> to do a simple lookup based on
|
/// More commonly, a ruleset could make use of <see cref="SkinComponentLookup{T}"/> to do a simple lookup based on
|
||||||
/// a provided enum.
|
/// a provided enum.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public interface ISkinComponentLookup
|
public interface ISkinComponentLookup
|
||||||
|
@ -50,11 +50,11 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup)
|
public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup)
|
||||||
{
|
{
|
||||||
if (lookup is SkinComponentsContainerLookup containerLookup)
|
if (lookup is GlobalSkinnableContainerLookup containerLookup)
|
||||||
{
|
{
|
||||||
switch (containerLookup.Target)
|
switch (containerLookup.Lookup)
|
||||||
{
|
{
|
||||||
case SkinComponentsContainerLookup.TargetArea.MainHUDComponents:
|
case GlobalSkinnableContainers.MainHUDComponents:
|
||||||
// this should exist in LegacySkin instead, but there isn't a fallback skin for LegacySkins yet.
|
// this should exist in LegacySkin instead, but there isn't a fallback skin for LegacySkins yet.
|
||||||
// therefore keep the check here until fallback default legacy skin is supported.
|
// therefore keep the check here until fallback default legacy skin is supported.
|
||||||
if (!this.HasFont(LegacyFont.Score))
|
if (!this.HasFont(LegacyFont.Score))
|
||||||
|
@ -358,13 +358,10 @@ namespace osu.Game.Skinning
|
|||||||
{
|
{
|
||||||
switch (lookup)
|
switch (lookup)
|
||||||
{
|
{
|
||||||
case SkinComponentsContainerLookup containerLookup:
|
case GlobalSkinnableContainerLookup containerLookup:
|
||||||
if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer c)
|
switch (containerLookup.Lookup)
|
||||||
return c;
|
|
||||||
|
|
||||||
switch (containerLookup.Target)
|
|
||||||
{
|
{
|
||||||
case SkinComponentsContainerLookup.TargetArea.MainHUDComponents:
|
case GlobalSkinnableContainers.MainHUDComponents:
|
||||||
if (containerLookup.Ruleset != null)
|
if (containerLookup.Ruleset != null)
|
||||||
{
|
{
|
||||||
return new DefaultSkinComponentsContainer(container =>
|
return new DefaultSkinComponentsContainer(container =>
|
||||||
@ -426,7 +423,7 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
case GameplaySkinComponentLookup<HitResult> resultComponent:
|
case SkinComponentLookup<HitResult> resultComponent:
|
||||||
|
|
||||||
// kind of wasteful that we throw this away, but should do for now.
|
// kind of wasteful that we throw this away, but should do for now.
|
||||||
if (getJudgementAnimation(resultComponent.Component) != null)
|
if (getJudgementAnimation(resultComponent.Component) != null)
|
||||||
|
@ -14,6 +14,7 @@ using osu.Framework.Audio.Sample;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.TypeExtensions;
|
using osu.Framework.Extensions.TypeExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Framework.IO.Stores;
|
using osu.Framework.IO.Stores;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
@ -43,10 +44,10 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
public SkinConfiguration Configuration { get; set; }
|
public SkinConfiguration Configuration { get; set; }
|
||||||
|
|
||||||
public IDictionary<SkinComponentsContainerLookup.TargetArea, SkinLayoutInfo> LayoutInfos => layoutInfos;
|
public IDictionary<GlobalSkinnableContainers, SkinLayoutInfo> LayoutInfos => layoutInfos;
|
||||||
|
|
||||||
private readonly Dictionary<SkinComponentsContainerLookup.TargetArea, SkinLayoutInfo> layoutInfos =
|
private readonly Dictionary<GlobalSkinnableContainers, SkinLayoutInfo> layoutInfos =
|
||||||
new Dictionary<SkinComponentsContainerLookup.TargetArea, SkinLayoutInfo>();
|
new Dictionary<GlobalSkinnableContainers, SkinLayoutInfo>();
|
||||||
|
|
||||||
public abstract ISample? GetSample(ISampleInfo sampleInfo);
|
public abstract ISample? GetSample(ISampleInfo sampleInfo);
|
||||||
|
|
||||||
@ -123,7 +124,7 @@ namespace osu.Game.Skinning
|
|||||||
}
|
}
|
||||||
|
|
||||||
// skininfo files may be null for default skin.
|
// skininfo files may be null for default skin.
|
||||||
foreach (SkinComponentsContainerLookup.TargetArea skinnableTarget in Enum.GetValues<SkinComponentsContainerLookup.TargetArea>())
|
foreach (GlobalSkinnableContainers skinnableTarget in Enum.GetValues<GlobalSkinnableContainers>())
|
||||||
{
|
{
|
||||||
string filename = $"{skinnableTarget}.json";
|
string filename = $"{skinnableTarget}.json";
|
||||||
|
|
||||||
@ -162,19 +163,19 @@ namespace osu.Game.Skinning
|
|||||||
/// Remove all stored customisations for the provided target.
|
/// Remove all stored customisations for the provided target.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="targetContainer">The target container to reset.</param>
|
/// <param name="targetContainer">The target container to reset.</param>
|
||||||
public void ResetDrawableTarget(SkinComponentsContainer targetContainer)
|
public void ResetDrawableTarget(SkinnableContainer targetContainer)
|
||||||
{
|
{
|
||||||
LayoutInfos.Remove(targetContainer.Lookup.Target);
|
LayoutInfos.Remove(targetContainer.Lookup.Lookup);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Update serialised information for the provided target.
|
/// Update serialised information for the provided target.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="targetContainer">The target container to serialise to this skin.</param>
|
/// <param name="targetContainer">The target container to serialise to this skin.</param>
|
||||||
public void UpdateDrawableTarget(SkinComponentsContainer targetContainer)
|
public void UpdateDrawableTarget(SkinnableContainer targetContainer)
|
||||||
{
|
{
|
||||||
if (!LayoutInfos.TryGetValue(targetContainer.Lookup.Target, out var layoutInfo))
|
if (!LayoutInfos.TryGetValue(targetContainer.Lookup.Lookup, out var layoutInfo))
|
||||||
layoutInfos[targetContainer.Lookup.Target] = layoutInfo = new SkinLayoutInfo();
|
layoutInfos[targetContainer.Lookup.Lookup] = layoutInfo = new SkinLayoutInfo();
|
||||||
|
|
||||||
layoutInfo.Update(targetContainer.Lookup.Ruleset, ((ISerialisableDrawableContainer)targetContainer).CreateSerialisedInfo().ToArray());
|
layoutInfo.Update(targetContainer.Lookup.Ruleset, ((ISerialisableDrawableContainer)targetContainer).CreateSerialisedInfo().ToArray());
|
||||||
}
|
}
|
||||||
@ -187,18 +188,23 @@ namespace osu.Game.Skinning
|
|||||||
case SkinnableSprite.SpriteComponentLookup sprite:
|
case SkinnableSprite.SpriteComponentLookup sprite:
|
||||||
return this.GetAnimation(sprite.LookupName, false, false, maxSize: sprite.MaxSize);
|
return this.GetAnimation(sprite.LookupName, false, false, maxSize: sprite.MaxSize);
|
||||||
|
|
||||||
case SkinComponentsContainerLookup containerLookup:
|
case UserSkinComponentLookup userLookup:
|
||||||
|
switch (userLookup.Component)
|
||||||
// It is important to return null if the user has not configured this yet.
|
|
||||||
// This allows skin transformers the opportunity to provide default components.
|
|
||||||
if (!LayoutInfos.TryGetValue(containerLookup.Target, out var layoutInfo)) return null;
|
|
||||||
if (!layoutInfo.TryGetDrawableInfo(containerLookup.Ruleset, out var drawableInfos)) return null;
|
|
||||||
|
|
||||||
return new UserConfiguredLayoutContainer
|
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
case GlobalSkinnableContainerLookup containerLookup:
|
||||||
ChildrenEnumerable = drawableInfos.Select(i => i.CreateInstance())
|
// It is important to return null if the user has not configured this yet.
|
||||||
};
|
// This allows skin transformers the opportunity to provide default components.
|
||||||
|
if (!LayoutInfos.TryGetValue(containerLookup.Lookup, out var layoutInfo)) return null;
|
||||||
|
if (!layoutInfo.TryGetDrawableInfo(containerLookup.Ruleset, out var drawableInfos)) return null;
|
||||||
|
|
||||||
|
return new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
ChildrenEnumerable = drawableInfos.Select(i => i.CreateInstance())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@ -206,7 +212,7 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
#region Deserialisation & Migration
|
#region Deserialisation & Migration
|
||||||
|
|
||||||
private SkinLayoutInfo? parseLayoutInfo(string jsonContent, SkinComponentsContainerLookup.TargetArea target)
|
private SkinLayoutInfo? parseLayoutInfo(string jsonContent, GlobalSkinnableContainers target)
|
||||||
{
|
{
|
||||||
SkinLayoutInfo? layout = null;
|
SkinLayoutInfo? layout = null;
|
||||||
|
|
||||||
@ -242,10 +248,34 @@ namespace osu.Game.Skinning
|
|||||||
applyMigration(layout, target, i);
|
applyMigration(layout, target, i);
|
||||||
|
|
||||||
layout.Version = SkinLayoutInfo.LATEST_VERSION;
|
layout.Version = SkinLayoutInfo.LATEST_VERSION;
|
||||||
|
|
||||||
|
foreach (var kvp in layout.DrawableInfo.ToArray())
|
||||||
|
{
|
||||||
|
foreach (var di in kvp.Value)
|
||||||
|
{
|
||||||
|
if (!isValidDrawable(di))
|
||||||
|
layout.DrawableInfo[kvp.Key] = kvp.Value.Where(i => i.Type != di.Type).ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return layout;
|
return layout;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyMigration(SkinLayoutInfo layout, SkinComponentsContainerLookup.TargetArea target, int version)
|
private bool isValidDrawable(SerialisedDrawableInfo di)
|
||||||
|
{
|
||||||
|
if (!typeof(ISerialisableDrawable).IsAssignableFrom(di.Type))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
foreach (var child in di.Children)
|
||||||
|
{
|
||||||
|
if (!isValidDrawable(child))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyMigration(SkinLayoutInfo layout, GlobalSkinnableContainers target, int version)
|
||||||
{
|
{
|
||||||
switch (version)
|
switch (version)
|
||||||
{
|
{
|
||||||
@ -253,7 +283,7 @@ namespace osu.Game.Skinning
|
|||||||
{
|
{
|
||||||
// Combo counters were moved out of the global HUD components into per-ruleset.
|
// Combo counters were moved out of the global HUD components into per-ruleset.
|
||||||
// This is to allow some rulesets to customise further (ie. mania and catch moving the combo to within their play area).
|
// This is to allow some rulesets to customise further (ie. mania and catch moving the combo to within their play area).
|
||||||
if (target != SkinComponentsContainerLookup.TargetArea.MainHUDComponents ||
|
if (target != GlobalSkinnableContainers.MainHUDComponents ||
|
||||||
!layout.TryGetDrawableInfo(null, out var globalHUDComponents) ||
|
!layout.TryGetDrawableInfo(null, out var globalHUDComponents) ||
|
||||||
resources == null)
|
resources == null)
|
||||||
break;
|
break;
|
||||||
|
22
osu.Game/Skinning/SkinComponentLookup.cs
Normal file
22
osu.Game/Skinning/SkinComponentLookup.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace osu.Game.Skinning
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A lookup type intended for use for skinnable components.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">An enum lookup type.</typeparam>
|
||||||
|
public class SkinComponentLookup<T> : ISkinComponentLookup
|
||||||
|
where T : Enum
|
||||||
|
{
|
||||||
|
public readonly T Component;
|
||||||
|
|
||||||
|
public SkinComponentLookup(T component)
|
||||||
|
{
|
||||||
|
Component = component;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -11,8 +11,8 @@ using osu.Game.Rulesets;
|
|||||||
namespace osu.Game.Skinning
|
namespace osu.Game.Skinning
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A serialisable model describing layout of a <see cref="SkinComponentsContainer"/>.
|
/// A serialisable model describing layout of a <see cref="SkinnableContainer"/>.
|
||||||
/// May contain multiple configurations for different rulesets, each of which should manifest their own <see cref="SkinComponentsContainer"/> as required.
|
/// May contain multiple configurations for different rulesets, each of which should manifest their own <see cref="SkinnableContainer"/> as required.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public class SkinLayoutInfo
|
public class SkinLayoutInfo
|
||||||
|
@ -16,17 +16,17 @@ namespace osu.Game.Skinning
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This is currently used as a means of serialising skin layouts to files.
|
/// This is currently used as a means of serialising skin layouts to files.
|
||||||
/// Currently, one json file in a skin will represent one <see cref="SkinComponentsContainer"/>, containing
|
/// Currently, one json file in a skin will represent one <see cref="SkinnableContainer"/>, containing
|
||||||
/// the output of <see cref="ISerialisableDrawableContainer.CreateSerialisedInfo"/>.
|
/// the output of <see cref="ISerialisableDrawableContainer.CreateSerialisedInfo"/>.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public partial class SkinComponentsContainer : SkinReloadableDrawable, ISerialisableDrawableContainer
|
public partial class SkinnableContainer : SkinReloadableDrawable, ISerialisableDrawableContainer
|
||||||
{
|
{
|
||||||
private Container? content;
|
private Container? content;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The lookup criteria which will be used to retrieve components from the active skin.
|
/// The lookup criteria which will be used to retrieve components from the active skin.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public SkinComponentsContainerLookup Lookup { get; }
|
public GlobalSkinnableContainerLookup Lookup { get; }
|
||||||
|
|
||||||
public IBindableList<ISerialisableDrawable> Components => components;
|
public IBindableList<ISerialisableDrawable> Components => components;
|
||||||
|
|
||||||
@ -38,12 +38,15 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
private CancellationTokenSource? cancellationSource;
|
private CancellationTokenSource? cancellationSource;
|
||||||
|
|
||||||
public SkinComponentsContainer(SkinComponentsContainerLookup lookup)
|
public SkinnableContainer(GlobalSkinnableContainerLookup lookup)
|
||||||
{
|
{
|
||||||
Lookup = lookup;
|
Lookup = lookup;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Reload() => Reload(CurrentSkin.GetDrawableComponent(Lookup) as Container);
|
public void Reload() => Reload((
|
||||||
|
CurrentSkin.GetDrawableComponent(new UserSkinComponentLookup(Lookup))
|
||||||
|
?? CurrentSkin.GetDrawableComponent(Lookup))
|
||||||
|
as Container);
|
||||||
|
|
||||||
public void Reload(Container? componentsContainer)
|
public void Reload(Container? componentsContainer)
|
||||||
{
|
{
|
@ -66,17 +66,14 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
switch (lookup)
|
switch (lookup)
|
||||||
{
|
{
|
||||||
case SkinComponentsContainerLookup containerLookup:
|
case GlobalSkinnableContainerLookup containerLookup:
|
||||||
if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer c)
|
|
||||||
return c;
|
|
||||||
|
|
||||||
// Only handle global level defaults for now.
|
// Only handle global level defaults for now.
|
||||||
if (containerLookup.Ruleset != null)
|
if (containerLookup.Ruleset != null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
switch (containerLookup.Target)
|
switch (containerLookup.Lookup)
|
||||||
{
|
{
|
||||||
case SkinComponentsContainerLookup.TargetArea.SongSelect:
|
case GlobalSkinnableContainers.SongSelect:
|
||||||
var songSelectComponents = new DefaultSkinComponentsContainer(_ =>
|
var songSelectComponents = new DefaultSkinComponentsContainer(_ =>
|
||||||
{
|
{
|
||||||
// do stuff when we need to.
|
// do stuff when we need to.
|
||||||
@ -84,7 +81,7 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
return songSelectComponents;
|
return songSelectComponents;
|
||||||
|
|
||||||
case SkinComponentsContainerLookup.TargetArea.MainHUDComponents:
|
case GlobalSkinnableContainers.MainHUDComponents:
|
||||||
var skinnableTargetWrapper = new DefaultSkinComponentsContainer(container =>
|
var skinnableTargetWrapper = new DefaultSkinComponentsContainer(container =>
|
||||||
{
|
{
|
||||||
var score = container.OfType<DefaultScoreCounter>().FirstOrDefault();
|
var score = container.OfType<DefaultScoreCounter>().FirstOrDefault();
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
// 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.Containers;
|
|
||||||
|
|
||||||
namespace osu.Game.Skinning
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This signifies that a <see cref="Skin.GetDrawableComponent"/> call resolved a configuration created
|
|
||||||
/// by a user in their skin. Generally this should be given priority over any local defaults or overrides.
|
|
||||||
/// </summary>
|
|
||||||
public partial class UserConfiguredLayoutContainer : Container
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
18
osu.Game/Skinning/UserSkinComponentLookup.cs
Normal file
18
osu.Game/Skinning/UserSkinComponentLookup.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// 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>
|
||||||
|
/// A lookup class which is only for internal use, and explicitly to get a user-level configuration.
|
||||||
|
/// </summary>
|
||||||
|
internal class UserSkinComponentLookup : ISkinComponentLookup
|
||||||
|
{
|
||||||
|
public readonly ISkinComponentLookup Component;
|
||||||
|
|
||||||
|
public UserSkinComponentLookup(ISkinComponentLookup component)
|
||||||
|
{
|
||||||
|
Component = component;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -45,13 +45,13 @@ namespace osu.Game.Tests.Visual
|
|||||||
|
|
||||||
private void addResetTargetsStep()
|
private void addResetTargetsStep()
|
||||||
{
|
{
|
||||||
AddStep("reset targets", () => this.ChildrenOfType<SkinComponentsContainer>().ForEach(t =>
|
AddStep("reset targets", () => this.ChildrenOfType<SkinnableContainer>().ForEach(t =>
|
||||||
{
|
{
|
||||||
LegacySkin.ResetDrawableTarget(t);
|
LegacySkin.ResetDrawableTarget(t);
|
||||||
t.Reload();
|
t.Reload();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
AddUntilStep("wait for components to load", () => this.ChildrenOfType<SkinComponentsContainer>().All(t => t.ComponentsLoaded));
|
AddUntilStep("wait for components to load", () => this.ChildrenOfType<SkinnableContainer>().All(t => t.ComponentsLoaded));
|
||||||
}
|
}
|
||||||
|
|
||||||
public partial class SkinProvidingPlayer : TestPlayer
|
public partial class SkinProvidingPlayer : TestPlayer
|
||||||
|
@ -845,6 +845,7 @@ See the LICENCE file in the repository root for full licence text.
|
|||||||
<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_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/=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/AutoImport2/=CSHARP/BlackLists/=TagLib_002EMpeg4_002EBox/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/Environment/AutoImport2/=CSHARP/BlackLists/=Vortice_002E_002A/@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_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_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_002ECSharpAttributeForSingleLineMethodUpgrade/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
Loading…
Reference in New Issue
Block a user