mirror of
https://github.com/ppy/osu.git
synced 2025-01-28 08:02:55 +08:00
Merge branch 'master' into skin-editor-cyclic-selection
This commit is contained in:
commit
9ff9cd3b7f
@ -1,7 +1,7 @@
|
|||||||
<!-- Contains required properties for osu!framework projects. -->
|
<!-- Contains required properties for osu!framework projects. -->
|
||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup Label="C#">
|
<PropertyGroup Label="C#">
|
||||||
<LangVersion>9.0</LangVersion>
|
<LangVersion>10.0</LangVersion>
|
||||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
<AndroidManifestMerger>manifestmerger.jar</AndroidManifestMerger>
|
<AndroidManifestMerger>manifestmerger.jar</AndroidManifestMerger>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.228.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.314.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AndroidManifestOverlay Include="$(MSBuildThisFileDirectory)osu.Android\Properties\AndroidManifestOverlay.xml" />
|
<AndroidManifestOverlay Include="$(MSBuildThisFileDirectory)osu.Android\Properties\AndroidManifestOverlay.xml" />
|
||||||
|
@ -5,7 +5,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -139,7 +138,17 @@ namespace osu.Desktop
|
|||||||
|
|
||||||
desktopWindow.CursorState |= CursorState.Hidden;
|
desktopWindow.CursorState |= CursorState.Hidden;
|
||||||
desktopWindow.Title = Name;
|
desktopWindow.Title = Name;
|
||||||
desktopWindow.DragDrop += f => fileDrop(new[] { f });
|
desktopWindow.DragDrop += f =>
|
||||||
|
{
|
||||||
|
// on macOS, URL associations are handled via SDL_DROPFILE events.
|
||||||
|
if (f.StartsWith(OSU_PROTOCOL, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
HandleLink(f);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fileDrop(new[] { f });
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override BatteryInfo CreateBatteryInfo() => new SDL2BatteryInfo();
|
protected override BatteryInfo CreateBatteryInfo() => new SDL2BatteryInfo();
|
||||||
@ -151,10 +160,6 @@ namespace osu.Desktop
|
|||||||
{
|
{
|
||||||
lock (importableFiles)
|
lock (importableFiles)
|
||||||
{
|
{
|
||||||
string firstExtension = Path.GetExtension(filePaths.First());
|
|
||||||
|
|
||||||
if (filePaths.Any(f => Path.GetExtension(f) != firstExtension)) return;
|
|
||||||
|
|
||||||
importableFiles.AddRange(filePaths);
|
importableFiles.AddRange(filePaths);
|
||||||
|
|
||||||
Logger.Log($"Adding {filePaths.Length} files for import");
|
Logger.Log($"Adding {filePaths.Length} files for import");
|
||||||
|
@ -236,6 +236,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Position and resize the body to lie half-way under the head and the tail notes.
|
// Position and resize the body to lie half-way under the head and the tail notes.
|
||||||
|
// The rationale for this is account for heads/tails with corner radius.
|
||||||
bodyPiece.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * Head.Height / 2;
|
bodyPiece.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * Head.Height / 2;
|
||||||
bodyPiece.Height = DrawHeight - Head.Height / 2 + Tail.Height / 2;
|
bodyPiece.Height = DrawHeight - Head.Height / 2 + Tail.Height / 2;
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
{
|
{
|
||||||
protected override ManiaSkinComponents Component => ManiaSkinComponents.HoldNoteTail;
|
protected override ManiaSkinComponents Component => ManiaSkinComponents.HoldNoteTail;
|
||||||
|
|
||||||
protected DrawableHoldNote HoldNote => (DrawableHoldNote)ParentHitObject;
|
protected internal DrawableHoldNote HoldNote => (DrawableHoldNote)ParentHitObject;
|
||||||
|
|
||||||
public DrawableHoldNoteTail()
|
public DrawableHoldNoteTail()
|
||||||
: this(null)
|
: this(null)
|
||||||
|
@ -43,9 +43,10 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
{
|
{
|
||||||
largeFaint = new Container
|
largeFaint = new Container
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.BottomCentre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.BottomCentre,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Height = ArgonNotePiece.NOTE_ACCENT_RATIO,
|
||||||
Masking = true,
|
Masking = true,
|
||||||
CornerRadius = ArgonNotePiece.CORNER_RADIUS,
|
CornerRadius = ArgonNotePiece.CORNER_RADIUS,
|
||||||
Blending = BlendingParameters.Additive,
|
Blending = BlendingParameters.Additive,
|
||||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
private void load(IScrollingInfo scrollingInfo)
|
private void load(IScrollingInfo scrollingInfo)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
Height = ArgonNotePiece.NOTE_HEIGHT;
|
Height = ArgonNotePiece.NOTE_HEIGHT * ArgonNotePiece.NOTE_ACCENT_RATIO;
|
||||||
|
|
||||||
Masking = true;
|
Masking = true;
|
||||||
CornerRadius = ArgonNotePiece.CORNER_RADIUS;
|
CornerRadius = ArgonNotePiece.CORNER_RADIUS;
|
||||||
|
@ -20,10 +20,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
public partial class ArgonHoldBodyPiece : CompositeDrawable, IHoldNoteBody
|
public partial class ArgonHoldBodyPiece : CompositeDrawable, IHoldNoteBody
|
||||||
{
|
{
|
||||||
protected readonly Bindable<Color4> AccentColour = new Bindable<Color4>();
|
protected readonly Bindable<Color4> AccentColour = new Bindable<Color4>();
|
||||||
protected readonly IBindable<bool> IsHitting = new Bindable<bool>();
|
|
||||||
|
|
||||||
private Drawable background = null!;
|
private Drawable background = null!;
|
||||||
private Box foreground = null!;
|
private ArgonHoldNoteHittingLayer hittingLayer = null!;
|
||||||
|
|
||||||
public ArgonHoldBodyPiece()
|
public ArgonHoldBodyPiece()
|
||||||
{
|
{
|
||||||
@ -32,7 +31,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
// Without this, the width of the body will be slightly larger than the head/tail.
|
// Without this, the width of the body will be slightly larger than the head/tail.
|
||||||
Masking = true;
|
Masking = true;
|
||||||
CornerRadius = ArgonNotePiece.CORNER_RADIUS;
|
CornerRadius = ArgonNotePiece.CORNER_RADIUS;
|
||||||
Blending = BlendingParameters.Additive;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
@ -41,12 +39,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
InternalChildren = new[]
|
InternalChildren = new[]
|
||||||
{
|
{
|
||||||
background = new Box { RelativeSizeAxes = Axes.Both },
|
background = new Box { RelativeSizeAxes = Axes.Both },
|
||||||
foreground = new Box
|
hittingLayer = new ArgonHoldNoteHittingLayer()
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Blending = BlendingParameters.Additive,
|
|
||||||
Alpha = 0,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (drawableObject != null)
|
if (drawableObject != null)
|
||||||
@ -54,44 +47,19 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
var holdNote = (DrawableHoldNote)drawableObject;
|
var holdNote = (DrawableHoldNote)drawableObject;
|
||||||
|
|
||||||
AccentColour.BindTo(holdNote.AccentColour);
|
AccentColour.BindTo(holdNote.AccentColour);
|
||||||
IsHitting.BindTo(holdNote.IsHitting);
|
hittingLayer.AccentColour.BindTo(holdNote.AccentColour);
|
||||||
|
((IBindable<bool>)hittingLayer.IsHitting).BindTo(holdNote.IsHitting);
|
||||||
}
|
}
|
||||||
|
|
||||||
AccentColour.BindValueChanged(colour =>
|
AccentColour.BindValueChanged(colour =>
|
||||||
{
|
{
|
||||||
background.Colour = colour.NewValue.Darken(1.2f);
|
background.Colour = colour.NewValue.Darken(0.6f);
|
||||||
foreground.Colour = colour.NewValue.Opacity(0.2f);
|
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
IsHitting.BindValueChanged(hitting =>
|
|
||||||
{
|
|
||||||
const float animation_length = 50;
|
|
||||||
|
|
||||||
foreground.ClearTransforms();
|
|
||||||
|
|
||||||
if (hitting.NewValue)
|
|
||||||
{
|
|
||||||
// wait for the next sync point
|
|
||||||
double synchronisedOffset = animation_length * 2 - Time.Current % (animation_length * 2);
|
|
||||||
|
|
||||||
using (foreground.BeginDelayedSequence(synchronisedOffset))
|
|
||||||
{
|
|
||||||
foreground.FadeTo(1, animation_length).Then()
|
|
||||||
.FadeTo(0.5f, animation_length)
|
|
||||||
.Loop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
foreground.FadeOut(animation_length);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Recycle()
|
public void Recycle()
|
||||||
{
|
{
|
||||||
foreground.ClearTransforms();
|
hittingLayer.Recycle();
|
||||||
foreground.Alpha = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||||
|
{
|
||||||
|
internal partial class ArgonHoldNoteHeadPiece : ArgonNotePiece
|
||||||
|
{
|
||||||
|
protected override Drawable CreateIcon() => new Circle
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Y = 2,
|
||||||
|
Size = new Vector2(20, 5),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
// 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.Bindables;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
using Box = osu.Framework.Graphics.Shapes.Box;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||||
|
{
|
||||||
|
public partial class ArgonHoldNoteHittingLayer : Box
|
||||||
|
{
|
||||||
|
public readonly Bindable<Color4> AccentColour = new Bindable<Color4>();
|
||||||
|
public readonly Bindable<bool> IsHitting = new Bindable<bool>();
|
||||||
|
|
||||||
|
public ArgonHoldNoteHittingLayer()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
Blending = BlendingParameters.Additive;
|
||||||
|
Alpha = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
AccentColour.BindValueChanged(colour =>
|
||||||
|
{
|
||||||
|
Colour = colour.NewValue.Lighten(0.2f).Opacity(0.3f);
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
IsHitting.BindValueChanged(hitting =>
|
||||||
|
{
|
||||||
|
const float animation_length = 80;
|
||||||
|
|
||||||
|
ClearTransforms();
|
||||||
|
|
||||||
|
if (hitting.NewValue)
|
||||||
|
{
|
||||||
|
// wait for the next sync point
|
||||||
|
double synchronisedOffset = animation_length * 2 - Time.Current % (animation_length * 2);
|
||||||
|
|
||||||
|
using (BeginDelayedSequence(synchronisedOffset))
|
||||||
|
{
|
||||||
|
this.FadeTo(1, animation_length, Easing.OutSine).Then()
|
||||||
|
.FadeTo(0.5f, animation_length, Easing.InSine)
|
||||||
|
.Loop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.FadeOut(animation_length);
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Recycle()
|
||||||
|
{
|
||||||
|
ClearTransforms();
|
||||||
|
Alpha = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,8 +5,10 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Colour;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -16,47 +18,68 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
{
|
{
|
||||||
internal partial class ArgonHoldNoteTailPiece : CompositeDrawable
|
internal partial class ArgonHoldNoteTailPiece : CompositeDrawable
|
||||||
{
|
{
|
||||||
|
[Resolved]
|
||||||
|
private DrawableHitObject? drawableObject { get; set; }
|
||||||
|
|
||||||
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||||
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
|
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
|
||||||
|
|
||||||
private readonly Box shadeBackground;
|
private readonly Box foreground;
|
||||||
private readonly Box shadeForeground;
|
private readonly ArgonHoldNoteHittingLayer hittingLayer;
|
||||||
|
private readonly Box foregroundAdditive;
|
||||||
|
|
||||||
public ArgonHoldNoteTailPiece()
|
public ArgonHoldNoteTailPiece()
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
Height = ArgonNotePiece.NOTE_HEIGHT;
|
Height = ArgonNotePiece.NOTE_HEIGHT;
|
||||||
|
|
||||||
CornerRadius = ArgonNotePiece.CORNER_RADIUS;
|
|
||||||
Masking = true;
|
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
shadeBackground = new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
},
|
|
||||||
new Container
|
new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.X,
|
||||||
Height = ArgonNotePiece.NOTE_ACCENT_RATIO,
|
Height = ArgonNotePiece.NOTE_HEIGHT,
|
||||||
Anchor = Anchor.BottomCentre,
|
|
||||||
Origin = Anchor.BottomCentre,
|
|
||||||
CornerRadius = ArgonNotePiece.CORNER_RADIUS,
|
CornerRadius = ArgonNotePiece.CORNER_RADIUS,
|
||||||
Masking = true,
|
Masking = true,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
shadeForeground = new Box
|
new Box
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0), Colour4.Black),
|
||||||
|
// Avoid ugly single pixel overlap.
|
||||||
|
Height = 0.9f,
|
||||||
},
|
},
|
||||||
},
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.BottomCentre,
|
||||||
|
Origin = Anchor.BottomCentre,
|
||||||
|
Height = ArgonNotePiece.NOTE_ACCENT_RATIO,
|
||||||
|
CornerRadius = ArgonNotePiece.CORNER_RADIUS,
|
||||||
|
Masking = true,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
foreground = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
hittingLayer = new ArgonHoldNoteHittingLayer(),
|
||||||
|
foregroundAdditive = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
Height = 0.5f,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(IScrollingInfo scrollingInfo, DrawableHitObject? drawableObject)
|
private void load(IScrollingInfo scrollingInfo)
|
||||||
{
|
{
|
||||||
direction.BindTo(scrollingInfo.Direction);
|
direction.BindTo(scrollingInfo.Direction);
|
||||||
direction.BindValueChanged(onDirectionChanged, true);
|
direction.BindValueChanged(onDirectionChanged, true);
|
||||||
@ -65,9 +88,24 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
{
|
{
|
||||||
accentColour.BindTo(drawableObject.AccentColour);
|
accentColour.BindTo(drawableObject.AccentColour);
|
||||||
accentColour.BindValueChanged(onAccentChanged, true);
|
accentColour.BindValueChanged(onAccentChanged, true);
|
||||||
|
|
||||||
|
drawableObject.HitObjectApplied += hitObjectApplied;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void hitObjectApplied(DrawableHitObject drawableHitObject)
|
||||||
|
{
|
||||||
|
var holdNoteTail = (DrawableHoldNoteTail)drawableHitObject;
|
||||||
|
|
||||||
|
hittingLayer.Recycle();
|
||||||
|
|
||||||
|
hittingLayer.AccentColour.UnbindBindings();
|
||||||
|
hittingLayer.AccentColour.BindTo(holdNoteTail.HoldNote.AccentColour);
|
||||||
|
|
||||||
|
hittingLayer.IsHitting.UnbindBindings();
|
||||||
|
((IBindable<bool>)hittingLayer.IsHitting).BindTo(holdNoteTail.HoldNote.IsHitting);
|
||||||
|
}
|
||||||
|
|
||||||
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
||||||
{
|
{
|
||||||
Scale = new Vector2(1, direction.NewValue == ScrollingDirection.Up ? -1 : 1);
|
Scale = new Vector2(1, direction.NewValue == ScrollingDirection.Up ? -1 : 1);
|
||||||
@ -75,8 +113,20 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
|
|
||||||
private void onAccentChanged(ValueChangedEvent<Color4> accent)
|
private void onAccentChanged(ValueChangedEvent<Color4> accent)
|
||||||
{
|
{
|
||||||
shadeBackground.Colour = accent.NewValue.Darken(1.7f);
|
foreground.Colour = accent.NewValue.Darken(0.6f); // matches body
|
||||||
shadeForeground.Colour = accent.NewValue.Darken(1.1f);
|
|
||||||
|
foregroundAdditive.Colour = ColourInfo.GradientVertical(
|
||||||
|
accent.NewValue.Opacity(0.4f),
|
||||||
|
accent.NewValue.Opacity(0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
if (drawableObject != null)
|
||||||
|
drawableObject.HitObjectApplied -= hitObjectApplied;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
|
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
|
||||||
|
|
||||||
private readonly Box colouredBox;
|
private readonly Box colouredBox;
|
||||||
private readonly Box shadow;
|
|
||||||
|
|
||||||
public ArgonNotePiece()
|
public ArgonNotePiece()
|
||||||
{
|
{
|
||||||
@ -36,11 +35,12 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
CornerRadius = CORNER_RADIUS;
|
CornerRadius = CORNER_RADIUS;
|
||||||
Masking = true;
|
Masking = true;
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new[]
|
||||||
{
|
{
|
||||||
shadow = new Box
|
new Box
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0), Colour4.Black)
|
||||||
},
|
},
|
||||||
new Container
|
new Container
|
||||||
{
|
{
|
||||||
@ -65,18 +65,22 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Height = CORNER_RADIUS * 2,
|
Height = CORNER_RADIUS * 2,
|
||||||
},
|
},
|
||||||
new SpriteIcon
|
CreateIcon(),
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Y = 4,
|
|
||||||
Icon = FontAwesome.Solid.AngleDown,
|
|
||||||
Size = new Vector2(20),
|
|
||||||
Scale = new Vector2(1, 0.7f)
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual Drawable CreateIcon() => new SpriteIcon
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Y = 4,
|
||||||
|
// TODO: replace with a non-squashed version.
|
||||||
|
// The 0.7f height scale should be removed.
|
||||||
|
Icon = FontAwesome.Solid.AngleDown,
|
||||||
|
Size = new Vector2(20),
|
||||||
|
Scale = new Vector2(1, 0.7f)
|
||||||
|
};
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(IScrollingInfo scrollingInfo, DrawableHitObject? drawableObject)
|
private void load(IScrollingInfo scrollingInfo, DrawableHitObject? drawableObject)
|
||||||
{
|
{
|
||||||
@ -105,8 +109,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
accent.NewValue.Lighten(0.1f),
|
accent.NewValue.Lighten(0.1f),
|
||||||
accent.NewValue
|
accent.NewValue
|
||||||
);
|
);
|
||||||
|
|
||||||
shadow.Colour = accent.NewValue.Darken(0.5f);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
return new ArgonHoldNoteTailPiece();
|
return new ArgonHoldNoteTailPiece();
|
||||||
|
|
||||||
case ManiaSkinComponents.HoldNoteHead:
|
case ManiaSkinComponents.HoldNoteHead:
|
||||||
|
return new ArgonHoldNoteHeadPiece();
|
||||||
|
|
||||||
case ManiaSkinComponents.Note:
|
case ManiaSkinComponents.Note:
|
||||||
return new ArgonNotePiece();
|
return new ArgonNotePiece();
|
||||||
|
|
||||||
@ -69,12 +71,23 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
return base.GetDrawableComponent(lookup);
|
return base.GetDrawableComponent(lookup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static readonly Color4 colour_special_column = new Color4(169, 106, 255, 255);
|
||||||
|
|
||||||
|
private const int total_colours = 6;
|
||||||
|
|
||||||
|
private static readonly Color4 colour_yellow = new Color4(255, 197, 40, 255);
|
||||||
|
private static readonly Color4 colour_orange = new Color4(252, 109, 1, 255);
|
||||||
|
private static readonly Color4 colour_pink = new Color4(213, 35, 90, 255);
|
||||||
|
private static readonly Color4 colour_purple = new Color4(203, 60, 236, 255);
|
||||||
|
private static readonly Color4 colour_cyan = new Color4(72, 198, 255, 255);
|
||||||
|
private static readonly Color4 colour_green = new Color4(100, 192, 92, 255);
|
||||||
|
|
||||||
public override IBindable<TValue>? GetConfig<TLookup, TValue>(TLookup lookup)
|
public override IBindable<TValue>? GetConfig<TLookup, TValue>(TLookup lookup)
|
||||||
{
|
{
|
||||||
if (lookup is ManiaSkinConfigurationLookup maniaLookup)
|
if (lookup is ManiaSkinConfigurationLookup maniaLookup)
|
||||||
{
|
{
|
||||||
int column = maniaLookup.ColumnIndex ?? 0;
|
int columnIndex = maniaLookup.ColumnIndex ?? 0;
|
||||||
var stage = beatmap.GetStageForColumnIndex(column);
|
var stage = beatmap.GetStageForColumnIndex(columnIndex);
|
||||||
|
|
||||||
switch (maniaLookup.Lookup)
|
switch (maniaLookup.Lookup)
|
||||||
{
|
{
|
||||||
@ -87,53 +100,12 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
|
|
||||||
case LegacyManiaSkinConfigurationLookups.ColumnWidth:
|
case LegacyManiaSkinConfigurationLookups.ColumnWidth:
|
||||||
return SkinUtils.As<TValue>(new Bindable<float>(
|
return SkinUtils.As<TValue>(new Bindable<float>(
|
||||||
stage.IsSpecialColumn(column) ? 120 : 60
|
stage.IsSpecialColumn(columnIndex) ? 120 : 60
|
||||||
));
|
));
|
||||||
|
|
||||||
case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour:
|
case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour:
|
||||||
|
|
||||||
Color4 colour;
|
var colour = getColourForLayout(columnIndex, stage);
|
||||||
|
|
||||||
const int total_colours = 7;
|
|
||||||
|
|
||||||
if (stage.IsSpecialColumn(column))
|
|
||||||
colour = new Color4(159, 101, 255, 255);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
switch (column % total_colours)
|
|
||||||
{
|
|
||||||
case 0:
|
|
||||||
colour = new Color4(240, 216, 0, 255);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
colour = new Color4(240, 101, 0, 255);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
colour = new Color4(240, 0, 130, 255);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 3:
|
|
||||||
colour = new Color4(192, 0, 240, 255);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 4:
|
|
||||||
colour = new Color4(0, 96, 240, 255);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 5:
|
|
||||||
colour = new Color4(0, 226, 240, 255);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 6:
|
|
||||||
colour = new Color4(0, 240, 96, 255);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new ArgumentOutOfRangeException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return SkinUtils.As<TValue>(new Bindable<Color4>(colour));
|
return SkinUtils.As<TValue>(new Bindable<Color4>(colour));
|
||||||
}
|
}
|
||||||
@ -141,5 +113,203 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
|
|
||||||
return base.GetConfig<TLookup, TValue>(lookup);
|
return base.GetConfig<TLookup, TValue>(lookup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Color4 getColourForLayout(int columnIndex, StageDefinition stage)
|
||||||
|
{
|
||||||
|
// Account for cases like dual-stage (assume that all stages have the same column count for now).
|
||||||
|
columnIndex %= stage.Columns;
|
||||||
|
|
||||||
|
// For now, these are defined per column count as per https://user-images.githubusercontent.com/50823728/218038463-b450f46c-ef21-4551-b133-f866be59970c.png
|
||||||
|
// See https://github.com/ppy/osu/discussions/21996 for discussion.
|
||||||
|
switch (stage.Columns)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
return colour_yellow;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
switch (columnIndex)
|
||||||
|
{
|
||||||
|
case 0: return colour_green;
|
||||||
|
|
||||||
|
case 1: return colour_cyan;
|
||||||
|
|
||||||
|
default: throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
switch (columnIndex)
|
||||||
|
{
|
||||||
|
case 0: return colour_pink;
|
||||||
|
|
||||||
|
case 1: return colour_orange;
|
||||||
|
|
||||||
|
case 2: return colour_yellow;
|
||||||
|
|
||||||
|
default: throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
switch (columnIndex)
|
||||||
|
{
|
||||||
|
case 0: return colour_yellow;
|
||||||
|
|
||||||
|
case 1: return colour_orange;
|
||||||
|
|
||||||
|
case 2: return colour_pink;
|
||||||
|
|
||||||
|
case 3: return colour_purple;
|
||||||
|
|
||||||
|
default: throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
case 5:
|
||||||
|
switch (columnIndex)
|
||||||
|
{
|
||||||
|
case 0: return colour_pink;
|
||||||
|
|
||||||
|
case 1: return colour_orange;
|
||||||
|
|
||||||
|
case 2: return colour_yellow;
|
||||||
|
|
||||||
|
case 3: return colour_green;
|
||||||
|
|
||||||
|
case 4: return colour_cyan;
|
||||||
|
|
||||||
|
default: throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
case 6:
|
||||||
|
switch (columnIndex)
|
||||||
|
{
|
||||||
|
case 0: return colour_pink;
|
||||||
|
|
||||||
|
case 1: return colour_orange;
|
||||||
|
|
||||||
|
case 2: return colour_yellow;
|
||||||
|
|
||||||
|
case 3: return colour_cyan;
|
||||||
|
|
||||||
|
case 4: return colour_purple;
|
||||||
|
|
||||||
|
case 5: return colour_pink;
|
||||||
|
|
||||||
|
default: throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
case 7:
|
||||||
|
switch (columnIndex)
|
||||||
|
{
|
||||||
|
case 0: return colour_pink;
|
||||||
|
|
||||||
|
case 1: return colour_cyan;
|
||||||
|
|
||||||
|
case 2: return colour_pink;
|
||||||
|
|
||||||
|
case 3: return colour_special_column;
|
||||||
|
|
||||||
|
case 4: return colour_green;
|
||||||
|
|
||||||
|
case 5: return colour_cyan;
|
||||||
|
|
||||||
|
case 6: return colour_green;
|
||||||
|
|
||||||
|
default: throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
case 8:
|
||||||
|
switch (columnIndex)
|
||||||
|
{
|
||||||
|
case 0: return colour_purple;
|
||||||
|
|
||||||
|
case 1: return colour_pink;
|
||||||
|
|
||||||
|
case 2: return colour_orange;
|
||||||
|
|
||||||
|
case 3: return colour_yellow;
|
||||||
|
|
||||||
|
case 4: return colour_yellow;
|
||||||
|
|
||||||
|
case 5: return colour_orange;
|
||||||
|
|
||||||
|
case 6: return colour_pink;
|
||||||
|
|
||||||
|
case 7: return colour_purple;
|
||||||
|
|
||||||
|
default: throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
case 9:
|
||||||
|
switch (columnIndex)
|
||||||
|
{
|
||||||
|
case 0: return colour_purple;
|
||||||
|
|
||||||
|
case 1: return colour_pink;
|
||||||
|
|
||||||
|
case 2: return colour_orange;
|
||||||
|
|
||||||
|
case 3: return colour_yellow;
|
||||||
|
|
||||||
|
case 4: return colour_special_column;
|
||||||
|
|
||||||
|
case 5: return colour_yellow;
|
||||||
|
|
||||||
|
case 6: return colour_orange;
|
||||||
|
|
||||||
|
case 7: return colour_pink;
|
||||||
|
|
||||||
|
case 8: return colour_purple;
|
||||||
|
|
||||||
|
default: throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
case 10:
|
||||||
|
switch (columnIndex)
|
||||||
|
{
|
||||||
|
case 0: return colour_purple;
|
||||||
|
|
||||||
|
case 1: return colour_pink;
|
||||||
|
|
||||||
|
case 2: return colour_orange;
|
||||||
|
|
||||||
|
case 3: return colour_yellow;
|
||||||
|
|
||||||
|
case 4: return colour_cyan;
|
||||||
|
|
||||||
|
case 5: return colour_green;
|
||||||
|
|
||||||
|
case 6: return colour_yellow;
|
||||||
|
|
||||||
|
case 7: return colour_orange;
|
||||||
|
|
||||||
|
case 8: return colour_pink;
|
||||||
|
|
||||||
|
case 9: return colour_purple;
|
||||||
|
|
||||||
|
default: throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback for unhandled scenarios
|
||||||
|
|
||||||
|
if (stage.IsSpecialColumn(columnIndex))
|
||||||
|
return colour_special_column;
|
||||||
|
|
||||||
|
switch (columnIndex % total_colours)
|
||||||
|
{
|
||||||
|
case 0: return colour_yellow;
|
||||||
|
|
||||||
|
case 1: return colour_orange;
|
||||||
|
|
||||||
|
case 2: return colour_pink;
|
||||||
|
|
||||||
|
case 3: return colour_purple;
|
||||||
|
|
||||||
|
case 4: return colour_cyan;
|
||||||
|
|
||||||
|
case 5: return colour_green;
|
||||||
|
|
||||||
|
default: throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -150,6 +150,42 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
assertKeyCounter(1, 1);
|
assertKeyCounter(1, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPositionalTrackingAfterLongDistanceTravelled()
|
||||||
|
{
|
||||||
|
// When a single touch has already travelled enough distance on screen, it should remain as the positional
|
||||||
|
// tracking touch until released (unless a direct touch occurs).
|
||||||
|
|
||||||
|
beginTouch(TouchSource.Touch1);
|
||||||
|
|
||||||
|
assertKeyCounter(1, 0);
|
||||||
|
checkPressed(OsuAction.LeftButton);
|
||||||
|
checkPosition(TouchSource.Touch1);
|
||||||
|
|
||||||
|
// cover some distance
|
||||||
|
beginTouch(TouchSource.Touch1, new Vector2(0));
|
||||||
|
beginTouch(TouchSource.Touch1, new Vector2(9999));
|
||||||
|
beginTouch(TouchSource.Touch1, new Vector2(0));
|
||||||
|
beginTouch(TouchSource.Touch1, new Vector2(9999));
|
||||||
|
beginTouch(TouchSource.Touch1);
|
||||||
|
|
||||||
|
beginTouch(TouchSource.Touch2);
|
||||||
|
|
||||||
|
assertKeyCounter(1, 1);
|
||||||
|
checkNotPressed(OsuAction.LeftButton);
|
||||||
|
checkPressed(OsuAction.RightButton);
|
||||||
|
// in this case, touch 2 should not become the positional tracking touch.
|
||||||
|
checkPosition(TouchSource.Touch1);
|
||||||
|
|
||||||
|
// even if the second touch moves on the screen, the original tracking touch is retained.
|
||||||
|
beginTouch(TouchSource.Touch2, new Vector2(0));
|
||||||
|
beginTouch(TouchSource.Touch2, new Vector2(9999));
|
||||||
|
beginTouch(TouchSource.Touch2, new Vector2(0));
|
||||||
|
beginTouch(TouchSource.Touch2, new Vector2(9999));
|
||||||
|
|
||||||
|
checkPosition(TouchSource.Touch1);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestPositionalInputUpdatesOnlyFromMostRecentTouch()
|
public void TestPositionalInputUpdatesOnlyFromMostRecentTouch()
|
||||||
{
|
{
|
||||||
|
@ -13,6 +13,7 @@ using osu.Framework.Graphics.Primitives;
|
|||||||
using osu.Framework.Graphics.Rendering;
|
using osu.Framework.Graphics.Rendering;
|
||||||
using osu.Framework.Graphics.Rendering.Vertices;
|
using osu.Framework.Graphics.Rendering.Vertices;
|
||||||
using osu.Framework.Graphics.Shaders;
|
using osu.Framework.Graphics.Shaders;
|
||||||
|
using osu.Framework.Graphics.Shaders.Types;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
@ -255,15 +256,23 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
Source.parts.CopyTo(parts, 0);
|
Source.parts.CopyTo(parts, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IUniformBuffer<CursorTrailParameters> cursorTrailParameters;
|
||||||
|
|
||||||
public override void Draw(IRenderer renderer)
|
public override void Draw(IRenderer renderer)
|
||||||
{
|
{
|
||||||
base.Draw(renderer);
|
base.Draw(renderer);
|
||||||
|
|
||||||
vertexBatch ??= renderer.CreateQuadBatch<TexturedTrailVertex>(max_sprites, 1);
|
vertexBatch ??= renderer.CreateQuadBatch<TexturedTrailVertex>(max_sprites, 1);
|
||||||
|
|
||||||
|
cursorTrailParameters ??= renderer.CreateUniformBuffer<CursorTrailParameters>();
|
||||||
|
cursorTrailParameters.Data = cursorTrailParameters.Data with
|
||||||
|
{
|
||||||
|
FadeClock = time,
|
||||||
|
FadeExponent = fadeExponent
|
||||||
|
};
|
||||||
|
|
||||||
shader.Bind();
|
shader.Bind();
|
||||||
shader.GetUniform<float>("g_FadeClock").UpdateValue(ref time);
|
shader.BindUniformBlock("m_CursorTrailParameters", cursorTrailParameters);
|
||||||
shader.GetUniform<float>("g_FadeExponent").UpdateValue(ref fadeExponent);
|
|
||||||
|
|
||||||
texture.Bind();
|
texture.Bind();
|
||||||
|
|
||||||
@ -323,6 +332,15 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
vertexBatch?.Dispose();
|
vertexBatch?.Dispose();
|
||||||
|
cursorTrailParameters?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
private record struct CursorTrailParameters
|
||||||
|
{
|
||||||
|
public UniformFloat FadeClock;
|
||||||
|
public UniformFloat FadeExponent;
|
||||||
|
private readonly UniformPadding8 pad1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +22,13 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly List<TrackedTouch> trackedTouches = new List<TrackedTouch>();
|
private readonly List<TrackedTouch> trackedTouches = new List<TrackedTouch>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The distance (in local pixels) that a touch must move before being considered a permanent tracking touch.
|
||||||
|
/// After this distance is covered, any extra touches on the screen will be considered as button inputs, unless
|
||||||
|
/// a new touch directly interacts with a hit circle.
|
||||||
|
/// </summary>
|
||||||
|
private const float distance_before_position_tracking_lock_in = 100;
|
||||||
|
|
||||||
private TrackedTouch? positionTrackingTouch;
|
private TrackedTouch? positionTrackingTouch;
|
||||||
|
|
||||||
private readonly OsuInputManager osuInputManager;
|
private readonly OsuInputManager osuInputManager;
|
||||||
@ -97,26 +104,32 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ..or if the current position tracking touch was not a direct touch (this one is debatable and may be change in the future, but it's the simplest way to handle)
|
// ..or if the current position tracking touch was not a direct touch (and didn't travel across the screen too far).
|
||||||
if (!positionTrackingTouch.DirectTouch)
|
if (!positionTrackingTouch.DirectTouch && positionTrackingTouch.DistanceTravelled < distance_before_position_tracking_lock_in)
|
||||||
{
|
{
|
||||||
positionTrackingTouch = newTouch;
|
positionTrackingTouch = newTouch;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// In the case the new touch was not used for position tracking, we should also check the previous position tracking touch.
|
// In the case the new touch was not used for position tracking, we should also check the previous position tracking touch.
|
||||||
// If it was a direct touch and still has its action pressed, that action should be released.
|
// If it still has its action pressed, that action should be released.
|
||||||
//
|
//
|
||||||
// This is done to allow tracking with the initial touch while still having both Left/Right actions available for alternating with two more touches.
|
// This is done to allow tracking with the initial touch while still having both Left/Right actions available for alternating with two more touches.
|
||||||
if (positionTrackingTouch.DirectTouch && positionTrackingTouch.Action is OsuAction directTouchAction)
|
if (positionTrackingTouch.Action is OsuAction touchAction)
|
||||||
{
|
{
|
||||||
osuInputManager.KeyBindingContainer.TriggerReleased(directTouchAction);
|
osuInputManager.KeyBindingContainer.TriggerReleased(touchAction);
|
||||||
positionTrackingTouch.Action = null;
|
positionTrackingTouch.Action = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleTouchMovement(TouchEvent touchEvent)
|
private void handleTouchMovement(TouchEvent touchEvent)
|
||||||
{
|
{
|
||||||
|
if (touchEvent is TouchMoveEvent moveEvent)
|
||||||
|
{
|
||||||
|
var trackedTouch = trackedTouches.Single(t => t.Source == touchEvent.Touch.Source);
|
||||||
|
trackedTouch.DistanceTravelled += moveEvent.Delta.Length;
|
||||||
|
}
|
||||||
|
|
||||||
// Movement should only be tracked for the most recent touch.
|
// Movement should only be tracked for the most recent touch.
|
||||||
if (touchEvent.Touch.Source != positionTrackingTouch?.Source)
|
if (touchEvent.Touch.Source != positionTrackingTouch?.Source)
|
||||||
return;
|
return;
|
||||||
@ -148,8 +161,16 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
|
|
||||||
public OsuAction? Action;
|
public OsuAction? Action;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the touch was on a hit circle receptor.
|
||||||
|
/// </summary>
|
||||||
public readonly bool DirectTouch;
|
public readonly bool DirectTouch;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The total distance on screen travelled by this touch (in local pixels).
|
||||||
|
/// </summary>
|
||||||
|
public float DistanceTravelled;
|
||||||
|
|
||||||
public TrackedTouch(TouchSource source, OsuAction? action, bool directTouch)
|
public TrackedTouch(TouchSource source, OsuAction? action, bool directTouch)
|
||||||
{
|
{
|
||||||
Source = source;
|
Source = source;
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
<OutputType>Library</OutputType>
|
<OutputType>Library</OutputType>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<Description>click the circles. to the beat.</Description>
|
<Description>click the circles. to the beat.</Description>
|
||||||
|
<LangVersion>10</LangVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Label="Nuget">
|
<PropertyGroup Label="Nuget">
|
||||||
|
@ -176,6 +176,7 @@ namespace osu.Game.Tests.Resources
|
|||||||
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
||||||
},
|
},
|
||||||
BeatmapInfo = beatmap,
|
BeatmapInfo = beatmap,
|
||||||
|
BeatmapHash = beatmap.Hash,
|
||||||
Ruleset = beatmap.Ruleset,
|
Ruleset = beatmap.Ruleset,
|
||||||
Mods = new Mod[] { new TestModHardRock(), new TestModDoubleTime() },
|
Mods = new Mod[] { new TestModHardRock(), new TestModDoubleTime() },
|
||||||
TotalScore = 2845370,
|
TotalScore = 2845370,
|
||||||
|
@ -80,7 +80,7 @@ namespace osu.Game.Tests.Rulesets
|
|||||||
|
|
||||||
dependencies.CacheAs<TextureStore>(ParentTextureStore = new TestTextureStore(parent.Get<GameHost>().Renderer));
|
dependencies.CacheAs<TextureStore>(ParentTextureStore = new TestTextureStore(parent.Get<GameHost>().Renderer));
|
||||||
dependencies.CacheAs<ISampleStore>(ParentSampleStore = new TestSampleStore());
|
dependencies.CacheAs<ISampleStore>(ParentSampleStore = new TestSampleStore());
|
||||||
dependencies.CacheAs<ShaderManager>(ParentShaderManager = new TestShaderManager(parent.Get<GameHost>().Renderer));
|
dependencies.CacheAs<ShaderManager>(ParentShaderManager = new TestShaderManager(parent.Get<GameHost>().Renderer, parent.Get<ShaderManager>()));
|
||||||
|
|
||||||
return new DrawableRulesetDependencies(new OsuRuleset(), dependencies);
|
return new DrawableRulesetDependencies(new OsuRuleset(), dependencies);
|
||||||
}
|
}
|
||||||
@ -156,12 +156,15 @@ namespace osu.Game.Tests.Rulesets
|
|||||||
|
|
||||||
private class TestShaderManager : ShaderManager
|
private class TestShaderManager : ShaderManager
|
||||||
{
|
{
|
||||||
public TestShaderManager(IRenderer renderer)
|
private readonly ShaderManager parentManager;
|
||||||
|
|
||||||
|
public TestShaderManager(IRenderer renderer, ShaderManager parentManager)
|
||||||
: base(renderer, new ResourceStore<byte[]>())
|
: base(renderer, new ResourceStore<byte[]>())
|
||||||
{
|
{
|
||||||
|
this.parentManager = parentManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override byte[] LoadRaw(string name) => null;
|
public override byte[] LoadRaw(string name) => parentManager.LoadRaw(name);
|
||||||
|
|
||||||
public bool IsDisposed { get; private set; }
|
public bool IsDisposed { get; private set; }
|
||||||
|
|
||||||
|
@ -133,6 +133,25 @@ namespace osu.Game.Tests.Skins.IO
|
|||||||
assertImportedOnce(import1, import2);
|
assertImportedOnce(import1, import2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public Task TestImportExportedNonAsciiSkinFilename() => runSkinTest(async osu =>
|
||||||
|
{
|
||||||
|
MemoryStream exportStream = new MemoryStream();
|
||||||
|
|
||||||
|
var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 『1』", "author 1"), "custom.osk"));
|
||||||
|
assertCorrectMetadata(import1, "name 『1』 [custom]", "author 1", osu);
|
||||||
|
|
||||||
|
import1.PerformRead(s =>
|
||||||
|
{
|
||||||
|
new LegacySkinExporter(osu.Dependencies.Get<Storage>()).ExportModelTo(s, exportStream);
|
||||||
|
});
|
||||||
|
|
||||||
|
string exportFilename = import1.GetDisplayString().GetValidFilename();
|
||||||
|
|
||||||
|
var import2 = await loadSkinIntoOsu(osu, new ImportTask(exportStream, $"{exportFilename}.osk"));
|
||||||
|
assertCorrectMetadata(import2, "name 『1』 [custom]", "author 1", osu);
|
||||||
|
});
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public Task TestSameMetadataNameSameFolderName([Values] bool batchImport) => runSkinTest(async osu =>
|
public Task TestSameMetadataNameSameFolderName([Values] bool batchImport) => runSkinTest(async osu =>
|
||||||
{
|
{
|
||||||
|
@ -9,6 +9,7 @@ using osuTK;
|
|||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Graphics.Rendering;
|
using osu.Framework.Graphics.Rendering;
|
||||||
|
using osu.Game.Graphics.Backgrounds;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Background
|
namespace osu.Game.Tests.Visual.Background
|
||||||
{
|
{
|
||||||
@ -97,15 +98,29 @@ namespace osu.Game.Tests.Visual.Background
|
|||||||
texelSize = Source.texelSize;
|
texelSize = Source.texelSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IUniformBuffer<TriangleBorderData>? borderDataBuffer;
|
||||||
|
|
||||||
public override void Draw(IRenderer renderer)
|
public override void Draw(IRenderer renderer)
|
||||||
{
|
{
|
||||||
TextureShader.GetUniform<float>("thickness").UpdateValue(ref thickness);
|
borderDataBuffer ??= renderer.CreateUniformBuffer<TriangleBorderData>();
|
||||||
TextureShader.GetUniform<float>("texelSize").UpdateValue(ref texelSize);
|
borderDataBuffer.Data = borderDataBuffer.Data with
|
||||||
|
{
|
||||||
|
Thickness = thickness,
|
||||||
|
TexelSize = texelSize
|
||||||
|
};
|
||||||
|
|
||||||
|
TextureShader.BindUniformBlock("m_BorderData", borderDataBuffer);
|
||||||
|
|
||||||
base.Draw(renderer);
|
base.Draw(renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool CanDrawOpaqueInterior => false;
|
protected override bool CanDrawOpaqueInterior => false;
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
borderDataBuffer?.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,16 +84,80 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
});
|
});
|
||||||
|
|
||||||
clearScores();
|
clearScores();
|
||||||
checkCount(0);
|
checkDisplayedCount(0);
|
||||||
|
|
||||||
loadMoreScores(() => beatmapInfo);
|
importMoreScores(() => beatmapInfo);
|
||||||
checkCount(10);
|
checkDisplayedCount(10);
|
||||||
|
|
||||||
loadMoreScores(() => beatmapInfo);
|
importMoreScores(() => beatmapInfo);
|
||||||
checkCount(20);
|
checkDisplayedCount(20);
|
||||||
|
|
||||||
clearScores();
|
clearScores();
|
||||||
checkCount(0);
|
checkDisplayedCount(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLocalScoresDisplayOnBeatmapEdit()
|
||||||
|
{
|
||||||
|
BeatmapInfo beatmapInfo = null!;
|
||||||
|
string originalHash = string.Empty;
|
||||||
|
|
||||||
|
AddStep(@"Set scope", () => leaderboard.Scope = BeatmapLeaderboardScope.Local);
|
||||||
|
|
||||||
|
AddStep(@"Import beatmap", () =>
|
||||||
|
{
|
||||||
|
beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
|
||||||
|
beatmapInfo = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First();
|
||||||
|
|
||||||
|
leaderboard.BeatmapInfo = beatmapInfo;
|
||||||
|
});
|
||||||
|
|
||||||
|
clearScores();
|
||||||
|
checkDisplayedCount(0);
|
||||||
|
|
||||||
|
AddStep(@"Perform initial save to guarantee stable hash", () =>
|
||||||
|
{
|
||||||
|
IBeatmap beatmap = beatmapManager.GetWorkingBeatmap(beatmapInfo).Beatmap;
|
||||||
|
beatmapManager.Save(beatmapInfo, beatmap);
|
||||||
|
|
||||||
|
originalHash = beatmapInfo.Hash;
|
||||||
|
});
|
||||||
|
|
||||||
|
importMoreScores(() => beatmapInfo);
|
||||||
|
|
||||||
|
checkDisplayedCount(10);
|
||||||
|
checkStoredCount(10);
|
||||||
|
|
||||||
|
AddStep(@"Save with changes", () =>
|
||||||
|
{
|
||||||
|
IBeatmap beatmap = beatmapManager.GetWorkingBeatmap(beatmapInfo).Beatmap;
|
||||||
|
beatmap.Difficulty.ApproachRate = 12;
|
||||||
|
beatmapManager.Save(beatmapInfo, beatmap);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("Hash changed", () => beatmapInfo.Hash, () => Is.Not.EqualTo(originalHash));
|
||||||
|
checkDisplayedCount(0);
|
||||||
|
checkStoredCount(10);
|
||||||
|
|
||||||
|
importMoreScores(() => beatmapInfo);
|
||||||
|
importMoreScores(() => beatmapInfo);
|
||||||
|
checkDisplayedCount(20);
|
||||||
|
checkStoredCount(30);
|
||||||
|
|
||||||
|
AddStep(@"Revert changes", () =>
|
||||||
|
{
|
||||||
|
IBeatmap beatmap = beatmapManager.GetWorkingBeatmap(beatmapInfo).Beatmap;
|
||||||
|
beatmap.Difficulty.ApproachRate = 8;
|
||||||
|
beatmapManager.Save(beatmapInfo, beatmap);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("Hash restored", () => beatmapInfo.Hash, () => Is.EqualTo(originalHash));
|
||||||
|
checkDisplayedCount(10);
|
||||||
|
checkStoredCount(30);
|
||||||
|
|
||||||
|
clearScores();
|
||||||
|
checkDisplayedCount(0);
|
||||||
|
checkStoredCount(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -162,9 +226,9 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadMoreScores(Func<BeatmapInfo> beatmapInfo)
|
private void importMoreScores(Func<BeatmapInfo> beatmapInfo)
|
||||||
{
|
{
|
||||||
AddStep(@"Load new scores via manager", () =>
|
AddStep(@"Import new scores", () =>
|
||||||
{
|
{
|
||||||
foreach (var score in generateSampleScores(beatmapInfo()))
|
foreach (var score in generateSampleScores(beatmapInfo()))
|
||||||
scoreManager.Import(score);
|
scoreManager.Import(score);
|
||||||
@ -176,8 +240,11 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
AddStep("Clear all scores", () => scoreManager.Delete());
|
AddStep("Clear all scores", () => scoreManager.Delete());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkCount(int expected) =>
|
private void checkDisplayedCount(int expected) =>
|
||||||
AddUntilStep("Correct count displayed", () => leaderboard.ChildrenOfType<LeaderboardScore>().Count() == expected);
|
AddUntilStep($"{expected} scores displayed", () => leaderboard.ChildrenOfType<LeaderboardScore>().Count(), () => Is.EqualTo(expected));
|
||||||
|
|
||||||
|
private void checkStoredCount(int expected) =>
|
||||||
|
AddUntilStep($"Total scores stored is {expected}", () => Realm.Run(r => r.All<ScoreInfo>().Count(s => !s.DeletePending)), () => Is.EqualTo(expected));
|
||||||
|
|
||||||
private static ScoreInfo[] generateSampleScores(BeatmapInfo beatmapInfo)
|
private static ScoreInfo[] generateSampleScores(BeatmapInfo beatmapInfo)
|
||||||
{
|
{
|
||||||
@ -210,6 +277,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
},
|
},
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
{
|
{
|
||||||
Id = 6602580,
|
Id = 6602580,
|
||||||
@ -226,6 +294,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Date = DateTime.Now.AddSeconds(-30),
|
Date = DateTime.Now.AddSeconds(-30),
|
||||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
{
|
{
|
||||||
@ -243,6 +312,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Date = DateTime.Now.AddSeconds(-70),
|
Date = DateTime.Now.AddSeconds(-70),
|
||||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
|
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
@ -261,6 +331,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Date = DateTime.Now.AddMinutes(-40),
|
Date = DateTime.Now.AddMinutes(-40),
|
||||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
|
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
@ -279,6 +350,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Date = DateTime.Now.AddHours(-2),
|
Date = DateTime.Now.AddHours(-2),
|
||||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
|
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
@ -297,6 +369,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Date = DateTime.Now.AddHours(-25),
|
Date = DateTime.Now.AddHours(-25),
|
||||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
|
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
@ -315,6 +388,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Date = DateTime.Now.AddHours(-50),
|
Date = DateTime.Now.AddHours(-50),
|
||||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
|
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
@ -333,6 +407,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Date = DateTime.Now.AddHours(-72),
|
Date = DateTime.Now.AddHours(-72),
|
||||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
|
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
@ -351,6 +426,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Date = DateTime.Now.AddMonths(-3),
|
Date = DateTime.Now.AddMonths(-3),
|
||||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
|
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
@ -369,6 +445,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Date = DateTime.Now.AddYears(-2),
|
Date = DateTime.Now.AddYears(-2),
|
||||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
|
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
|
@ -94,6 +94,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
{
|
{
|
||||||
OnlineID = i,
|
OnlineID = i,
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
Accuracy = RNG.NextDouble(),
|
Accuracy = RNG.NextDouble(),
|
||||||
TotalScore = RNG.Next(1, 1000000),
|
TotalScore = RNG.Next(1, 1000000),
|
||||||
MaxCombo = RNG.Next(1, 1000),
|
MaxCombo = RNG.Next(1, 1000),
|
||||||
|
@ -70,8 +70,9 @@ namespace osu.Game.Database
|
|||||||
/// 23 2022-08-01 Added LastLocalUpdate to BeatmapInfo.
|
/// 23 2022-08-01 Added LastLocalUpdate to BeatmapInfo.
|
||||||
/// 24 2022-08-22 Added MaximumStatistics to ScoreInfo.
|
/// 24 2022-08-22 Added MaximumStatistics to ScoreInfo.
|
||||||
/// 25 2022-09-18 Remove skins to add with new naming.
|
/// 25 2022-09-18 Remove skins to add with new naming.
|
||||||
|
/// 26 2023-02-05 Added BeatmapHash to ScoreInfo.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const int schema_version = 25;
|
private const int schema_version = 26;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods.
|
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods.
|
||||||
@ -866,6 +867,15 @@ namespace osu.Game.Database
|
|||||||
// Remove the default skins so they can be added back by SkinManager with updated naming.
|
// Remove the default skins so they can be added back by SkinManager with updated naming.
|
||||||
migration.NewRealm.RemoveRange(migration.NewRealm.All<SkinInfo>().Where(s => s.Protected));
|
migration.NewRealm.RemoveRange(migration.NewRealm.All<SkinInfo>().Where(s => s.Protected));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 26:
|
||||||
|
// Add ScoreInfo.BeatmapHash property to ensure scores correspond to the correct version of beatmap.
|
||||||
|
var scores = migration.NewRealm.All<ScoreInfo>();
|
||||||
|
|
||||||
|
foreach (var score in scores)
|
||||||
|
score.BeatmapHash = score.BeatmapInfo.Hash;
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
16
osu.Game/Graphics/Backgrounds/TriangleBorderData.cs
Normal file
16
osu.Game/Graphics/Backgrounds/TriangleBorderData.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// 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.Runtime.InteropServices;
|
||||||
|
using osu.Framework.Graphics.Shaders.Types;
|
||||||
|
|
||||||
|
namespace osu.Game.Graphics.Backgrounds
|
||||||
|
{
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public record struct TriangleBorderData
|
||||||
|
{
|
||||||
|
public UniformFloat Thickness;
|
||||||
|
public UniformFloat TexelSize;
|
||||||
|
private readonly UniformPadding8 pad1;
|
||||||
|
}
|
||||||
|
}
|
@ -252,7 +252,7 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
|
|
||||||
private class TrianglesDrawNode : DrawNode
|
private class TrianglesDrawNode : DrawNode
|
||||||
{
|
{
|
||||||
private float fill = 1f;
|
private const float fill = 1f;
|
||||||
|
|
||||||
protected new Triangles Source => (Triangles)base.Source;
|
protected new Triangles Source => (Triangles)base.Source;
|
||||||
|
|
||||||
@ -284,6 +284,8 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
parts.AddRange(Source.parts);
|
parts.AddRange(Source.parts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IUniformBuffer<TriangleBorderData> borderDataBuffer;
|
||||||
|
|
||||||
public override void Draw(IRenderer renderer)
|
public override void Draw(IRenderer renderer)
|
||||||
{
|
{
|
||||||
base.Draw(renderer);
|
base.Draw(renderer);
|
||||||
@ -294,14 +296,17 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
vertexBatch = renderer.CreateQuadBatch<TexturedVertex2D>(Source.AimCount, 1);
|
vertexBatch = renderer.CreateQuadBatch<TexturedVertex2D>(Source.AimCount, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Due to triangles having various sizes we would need to set a different "texelSize" value for each of them, which is insanely expensive, thus we should use one single value.
|
borderDataBuffer ??= renderer.CreateUniformBuffer<TriangleBorderData>();
|
||||||
// texelSize computed for an average triangle (size 100) will result in big triangles becoming blurry, so we may just use 0 for all of them.
|
borderDataBuffer.Data = borderDataBuffer.Data with
|
||||||
// But we still need to specify at least something, because otherwise other shader usages will override this value.
|
{
|
||||||
float texelSize = 0f;
|
Thickness = fill,
|
||||||
|
// Due to triangles having various sizes we would need to set a different "TexelSize" value for each of them, which is insanely expensive, thus we should use one single value.
|
||||||
|
// TexelSize computed for an average triangle (size 100) will result in big triangles becoming blurry, so we may just use 0 for all of them.
|
||||||
|
TexelSize = 0
|
||||||
|
};
|
||||||
|
|
||||||
shader.Bind();
|
shader.Bind();
|
||||||
shader.GetUniform<float>("thickness").UpdateValue(ref fill);
|
shader.BindUniformBlock("m_BorderData", borderDataBuffer);
|
||||||
shader.GetUniform<float>("texelSize").UpdateValue(ref texelSize);
|
|
||||||
|
|
||||||
foreach (TriangleParticle particle in parts)
|
foreach (TriangleParticle particle in parts)
|
||||||
{
|
{
|
||||||
@ -352,6 +357,7 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
vertexBatch?.Dispose();
|
vertexBatch?.Dispose();
|
||||||
|
borderDataBuffer?.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,6 +226,8 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
parts.AddRange(Source.parts);
|
parts.AddRange(Source.parts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IUniformBuffer<TriangleBorderData>? borderDataBuffer;
|
||||||
|
|
||||||
public override void Draw(IRenderer renderer)
|
public override void Draw(IRenderer renderer)
|
||||||
{
|
{
|
||||||
base.Draw(renderer);
|
base.Draw(renderer);
|
||||||
@ -239,9 +241,15 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
vertexBatch = renderer.CreateQuadBatch<TexturedVertex2D>(Source.AimCount, 1);
|
vertexBatch = renderer.CreateQuadBatch<TexturedVertex2D>(Source.AimCount, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
borderDataBuffer ??= renderer.CreateUniformBuffer<TriangleBorderData>();
|
||||||
|
borderDataBuffer.Data = borderDataBuffer.Data with
|
||||||
|
{
|
||||||
|
Thickness = thickness,
|
||||||
|
TexelSize = texelSize
|
||||||
|
};
|
||||||
|
|
||||||
shader.Bind();
|
shader.Bind();
|
||||||
shader.GetUniform<float>("thickness").UpdateValue(ref thickness);
|
shader.BindUniformBlock("m_BorderData", borderDataBuffer);
|
||||||
shader.GetUniform<float>("texelSize").UpdateValue(ref texelSize);
|
|
||||||
|
|
||||||
Vector2 relativeSize = Vector2.Divide(triangleSize, size);
|
Vector2 relativeSize = Vector2.Divide(triangleSize, size);
|
||||||
|
|
||||||
@ -289,6 +297,7 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
vertexBatch?.Dispose();
|
vertexBatch?.Dispose();
|
||||||
|
borderDataBuffer?.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,11 +3,18 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Rendering;
|
using osu.Framework.Graphics.Rendering;
|
||||||
|
using osu.Framework.Graphics.Rendering.Vertices;
|
||||||
using osu.Framework.Graphics.Shaders;
|
using osu.Framework.Graphics.Shaders;
|
||||||
|
using osu.Framework.Graphics.Shaders.Types;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
using osuTK.Graphics.ES30;
|
||||||
|
|
||||||
namespace osu.Game.Graphics.Sprites
|
namespace osu.Game.Graphics.Sprites
|
||||||
{
|
{
|
||||||
@ -16,7 +23,7 @@ namespace osu.Game.Graphics.Sprites
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(ShaderManager shaders)
|
private void load(ShaderManager shaders)
|
||||||
{
|
{
|
||||||
TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, @"LogoAnimation");
|
TextureShader = shaders.Load(@"LogoAnimation", @"LogoAnimation");
|
||||||
}
|
}
|
||||||
|
|
||||||
private float animationProgress;
|
private float animationProgress;
|
||||||
@ -41,11 +48,22 @@ namespace osu.Game.Graphics.Sprites
|
|||||||
{
|
{
|
||||||
private LogoAnimation source => (LogoAnimation)Source;
|
private LogoAnimation source => (LogoAnimation)Source;
|
||||||
|
|
||||||
|
private readonly Action<TexturedVertex2D> addVertexAction;
|
||||||
|
|
||||||
private float progress;
|
private float progress;
|
||||||
|
|
||||||
public LogoAnimationDrawNode(LogoAnimation source)
|
public LogoAnimationDrawNode(LogoAnimation source)
|
||||||
: base(source)
|
: base(source)
|
||||||
{
|
{
|
||||||
|
addVertexAction = v =>
|
||||||
|
{
|
||||||
|
animationVertexBatch!.Add(new LogoAnimationVertex
|
||||||
|
{
|
||||||
|
Position = v.Position,
|
||||||
|
Colour = v.Colour,
|
||||||
|
TexturePosition = v.TexturePosition,
|
||||||
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void ApplyState()
|
public override void ApplyState()
|
||||||
@ -55,14 +73,62 @@ namespace osu.Game.Graphics.Sprites
|
|||||||
progress = source.animationProgress;
|
progress = source.animationProgress;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IUniformBuffer<AnimationData> animationDataBuffer;
|
||||||
|
private IVertexBatch<LogoAnimationVertex> animationVertexBatch;
|
||||||
|
|
||||||
protected override void Blit(IRenderer renderer)
|
protected override void Blit(IRenderer renderer)
|
||||||
{
|
{
|
||||||
TextureShader.GetUniform<float>("progress").UpdateValue(ref progress);
|
if (DrawRectangle.Width == 0 || DrawRectangle.Height == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
base.Blit(renderer);
|
animationDataBuffer ??= renderer.CreateUniformBuffer<AnimationData>();
|
||||||
|
animationVertexBatch ??= renderer.CreateQuadBatch<LogoAnimationVertex>(1, 2);
|
||||||
|
|
||||||
|
animationDataBuffer.Data = animationDataBuffer.Data with { Progress = progress };
|
||||||
|
|
||||||
|
TextureShader.BindUniformBlock("m_AnimationData", animationDataBuffer);
|
||||||
|
|
||||||
|
renderer.DrawQuad(
|
||||||
|
Texture,
|
||||||
|
ScreenSpaceDrawQuad,
|
||||||
|
DrawColourInfo.Colour,
|
||||||
|
inflationPercentage: new Vector2(InflationAmount.X / DrawRectangle.Width, InflationAmount.Y / DrawRectangle.Height),
|
||||||
|
textureCoords: TextureCoords,
|
||||||
|
vertexAction: addVertexAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool CanDrawOpaqueInterior => false;
|
protected override bool CanDrawOpaqueInterior => false;
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
animationDataBuffer?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
private record struct AnimationData
|
||||||
|
{
|
||||||
|
public UniformFloat Progress;
|
||||||
|
private readonly UniformPadding12 pad1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
private struct LogoAnimationVertex : IEquatable<LogoAnimationVertex>, IVertex
|
||||||
|
{
|
||||||
|
[VertexMember(2, VertexAttribPointerType.Float)]
|
||||||
|
public Vector2 Position;
|
||||||
|
|
||||||
|
[VertexMember(4, VertexAttribPointerType.Float)]
|
||||||
|
public Color4 Colour;
|
||||||
|
|
||||||
|
[VertexMember(2, VertexAttribPointerType.Float)]
|
||||||
|
public Vector2 TexturePosition;
|
||||||
|
|
||||||
|
public readonly bool Equals(LogoAnimationVertex other) =>
|
||||||
|
Position.Equals(other.Position)
|
||||||
|
&& TexturePosition.Equals(other.TexturePosition)
|
||||||
|
&& Colour.Equals(other.Colour);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// 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 osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
|
@ -8,6 +8,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
|
|
||||||
namespace osu.Game.Overlays
|
namespace osu.Game.Overlays
|
||||||
@ -39,7 +40,7 @@ namespace osu.Game.Overlays
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Padding = new MarginPadding { Right = -3 }, // Compensate for scrollbar margin
|
Padding = new MarginPadding { Right = -3 }, // Compensate for scrollbar margin
|
||||||
Child = new OsuScrollContainer
|
Child = new SidebarScrollContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Child = new Container
|
Child = new Container
|
||||||
@ -74,5 +75,30 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
[NotNull]
|
[NotNull]
|
||||||
protected virtual Drawable CreateContent() => Empty();
|
protected virtual Drawable CreateContent() => Empty();
|
||||||
|
|
||||||
|
private partial class SidebarScrollContainer : OsuScrollContainer
|
||||||
|
{
|
||||||
|
protected override bool OnScroll(ScrollEvent e)
|
||||||
|
{
|
||||||
|
if (e.ScrollDelta.Y > 0 && IsScrolledToStart())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (e.ScrollDelta.Y < 0 && IsScrolledToEnd())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return base.OnScroll(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnDragStart(DragStartEvent e)
|
||||||
|
{
|
||||||
|
if (e.Delta.Y > 0 && IsScrolledToStart())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (e.Delta.Y < 0 && IsScrolledToEnd())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return base.OnDragStart(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,15 +2,12 @@
|
|||||||
// 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 osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osuTK;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Graphics;
|
using 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.Framework.Allocation;
|
|
||||||
using osu.Framework.Extensions.LocalisationExtensions;
|
using osu.Framework.Extensions.LocalisationExtensions;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
@ -52,7 +49,6 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu
|
|||||||
{
|
{
|
||||||
private readonly OsuSpriteText valueText;
|
private readonly OsuSpriteText valueText;
|
||||||
protected readonly LinkFlowContainer DescriptionText;
|
protected readonly LinkFlowContainer DescriptionText;
|
||||||
private readonly Box lineBackground;
|
|
||||||
|
|
||||||
public new int Count
|
public new int Count
|
||||||
{
|
{
|
||||||
@ -63,25 +59,14 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
AutoSizeAxes = Axes.Y;
|
AutoSizeAxes = Axes.Y;
|
||||||
Padding = new MarginPadding { Top = 10, Bottom = 20 };
|
Padding = new MarginPadding { Bottom = 20 };
|
||||||
Child = new FillFlowContainer
|
Child = new FillFlowContainer
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Direction = FillDirection.Vertical,
|
Direction = FillDirection.Vertical,
|
||||||
Spacing = new Vector2(0, 5),
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new CircularContainer
|
|
||||||
{
|
|
||||||
Masking = true,
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Height = 2,
|
|
||||||
Child = lineBackground = new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new OsuSpriteText
|
new OsuSpriteText
|
||||||
{
|
{
|
||||||
Text = header,
|
Text = header,
|
||||||
@ -91,7 +76,6 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu
|
|||||||
{
|
{
|
||||||
Text = "0",
|
Text = "0",
|
||||||
Font = OsuFont.GetFont(size: 40, weight: FontWeight.Light),
|
Font = OsuFont.GetFont(size: 40, weight: FontWeight.Light),
|
||||||
UseFullGlyphHeight = false,
|
|
||||||
},
|
},
|
||||||
DescriptionText = new LinkFlowContainer(t => t.Font = t.Font.With(size: 14))
|
DescriptionText = new LinkFlowContainer(t => t.Font = t.Font.With(size: 14))
|
||||||
{
|
{
|
||||||
@ -101,12 +85,6 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(OverlayColourProvider colourProvider)
|
|
||||||
{
|
|
||||||
lineBackground.Colour = colourProvider.Highlight1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -9,6 +10,7 @@ using osu.Framework.Graphics.Primitives;
|
|||||||
using osu.Framework.Graphics.Rendering;
|
using osu.Framework.Graphics.Rendering;
|
||||||
using osu.Framework.Graphics.Rendering.Vertices;
|
using osu.Framework.Graphics.Rendering.Vertices;
|
||||||
using osu.Framework.Graphics.Shaders;
|
using osu.Framework.Graphics.Shaders;
|
||||||
|
using osu.Framework.Graphics.Shaders.Types;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
@ -245,6 +247,8 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
flashlightSmoothness = Source.flashlightSmoothness;
|
flashlightSmoothness = Source.flashlightSmoothness;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IUniformBuffer<FlashlightParameters>? flashlightParametersBuffer;
|
||||||
|
|
||||||
public override void Draw(IRenderer renderer)
|
public override void Draw(IRenderer renderer)
|
||||||
{
|
{
|
||||||
base.Draw(renderer);
|
base.Draw(renderer);
|
||||||
@ -259,12 +263,17 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
shader.Bind();
|
flashlightParametersBuffer ??= renderer.CreateUniformBuffer<FlashlightParameters>();
|
||||||
|
flashlightParametersBuffer.Data = flashlightParametersBuffer.Data with
|
||||||
|
{
|
||||||
|
Position = flashlightPosition,
|
||||||
|
Size = flashlightSize,
|
||||||
|
Dim = flashlightDim,
|
||||||
|
Smoothness = flashlightSmoothness
|
||||||
|
};
|
||||||
|
|
||||||
shader.GetUniform<Vector2>("flashlightPos").UpdateValue(ref flashlightPosition);
|
shader.Bind();
|
||||||
shader.GetUniform<Vector2>("flashlightSize").UpdateValue(ref flashlightSize);
|
shader.BindUniformBlock("m_FlashlightParameters", flashlightParametersBuffer);
|
||||||
shader.GetUniform<float>("flashlightDim").UpdateValue(ref flashlightDim);
|
|
||||||
shader.GetUniform<float>("flashlightSmoothness").UpdateValue(ref flashlightSmoothness);
|
|
||||||
|
|
||||||
renderer.DrawQuad(renderer.WhitePixel, screenSpaceDrawQuad, DrawColourInfo.Colour, vertexAction: addAction);
|
renderer.DrawQuad(renderer.WhitePixel, screenSpaceDrawQuad, DrawColourInfo.Colour, vertexAction: addAction);
|
||||||
|
|
||||||
@ -275,6 +284,17 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
{
|
{
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
quadBatch?.Dispose();
|
quadBatch?.Dispose();
|
||||||
|
flashlightParametersBuffer?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
private record struct FlashlightParameters
|
||||||
|
{
|
||||||
|
public UniformVector2 Position;
|
||||||
|
public UniformVector2 Size;
|
||||||
|
public UniformFloat Dim;
|
||||||
|
public UniformFloat Smoothness;
|
||||||
|
private readonly UniformPadding8 pad1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,6 +123,7 @@ namespace osu.Game.Scoring.Legacy
|
|||||||
// before returning for database import, we must restore the database-sourced BeatmapInfo.
|
// before returning for database import, we must restore the database-sourced BeatmapInfo.
|
||||||
// if not, the clone operation in GetPlayableBeatmap will cause a dereference and subsequent database exception.
|
// if not, the clone operation in GetPlayableBeatmap will cause a dereference and subsequent database exception.
|
||||||
score.ScoreInfo.BeatmapInfo = workingBeatmap.BeatmapInfo;
|
score.ScoreInfo.BeatmapInfo = workingBeatmap.BeatmapInfo;
|
||||||
|
score.ScoreInfo.BeatmapHash = workingBeatmap.BeatmapInfo.Hash;
|
||||||
|
|
||||||
return score;
|
return score;
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,9 @@ using Realms;
|
|||||||
|
|
||||||
namespace osu.Game.Scoring
|
namespace osu.Game.Scoring
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A realm model containing metadata for a single score.
|
||||||
|
/// </summary>
|
||||||
[ExcludeFromDynamicCompile]
|
[ExcludeFromDynamicCompile]
|
||||||
[MapTo("Score")]
|
[MapTo("Score")]
|
||||||
public class ScoreInfo : RealmObject, IHasGuidPrimaryKey, IHasRealmFiles, ISoftDelete, IEquatable<ScoreInfo>, IScoreInfo
|
public class ScoreInfo : RealmObject, IHasGuidPrimaryKey, IHasRealmFiles, ISoftDelete, IEquatable<ScoreInfo>, IScoreInfo
|
||||||
@ -29,8 +32,19 @@ namespace osu.Game.Scoring
|
|||||||
[PrimaryKey]
|
[PrimaryKey]
|
||||||
public Guid ID { get; set; }
|
public Guid ID { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="BeatmapInfo"/> this score was made against.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// When setting this, make sure to also set <see cref="BeatmapHash"/> to allow relational consistency when a beatmap is potentially changed.
|
||||||
|
/// </remarks>
|
||||||
public BeatmapInfo BeatmapInfo { get; set; } = null!;
|
public BeatmapInfo BeatmapInfo { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="osu.Game.Beatmaps.BeatmapInfo.Hash"/> at the point in time when the score was set.
|
||||||
|
/// </summary>
|
||||||
|
public string BeatmapHash { get; set; } = string.Empty;
|
||||||
|
|
||||||
public RulesetInfo Ruleset { get; set; } = null!;
|
public RulesetInfo Ruleset { get; set; } = null!;
|
||||||
|
|
||||||
public IList<RealmNamedFileUsage> Files { get; } = null!;
|
public IList<RealmNamedFileUsage> Files { get; } = null!;
|
||||||
|
@ -248,6 +248,7 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
// ensure the score is in a consistent state with the current player.
|
// ensure the score is in a consistent state with the current player.
|
||||||
Score.ScoreInfo.BeatmapInfo = Beatmap.Value.BeatmapInfo;
|
Score.ScoreInfo.BeatmapInfo = Beatmap.Value.BeatmapInfo;
|
||||||
|
Score.ScoreInfo.BeatmapHash = Beatmap.Value.BeatmapInfo.Hash;
|
||||||
Score.ScoreInfo.Ruleset = ruleset.RulesetInfo;
|
Score.ScoreInfo.Ruleset = ruleset.RulesetInfo;
|
||||||
Score.ScoreInfo.Mods = gameplayMods;
|
Score.ScoreInfo.Mods = gameplayMods;
|
||||||
|
|
||||||
|
@ -49,6 +49,11 @@ namespace osu.Game.Screens.Select
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Action? BeatmapSetsChanged;
|
public Action? BeatmapSetsChanged;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Triggered after filter conditions have finished being applied to the model hierarchy.
|
||||||
|
/// </summary>
|
||||||
|
public Action? FilterApplied;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The currently selected beatmap.
|
/// The currently selected beatmap.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -56,6 +61,11 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
private CarouselBeatmap? selectedBeatmap => selectedBeatmapSet?.Beatmaps.FirstOrDefault(s => s.State.Value == CarouselItemState.Selected);
|
private CarouselBeatmap? selectedBeatmap => selectedBeatmapSet?.Beatmaps.FirstOrDefault(s => s.State.Value == CarouselItemState.Selected);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The total count of non-filtered beatmaps displayed.
|
||||||
|
/// </summary>
|
||||||
|
public int CountDisplayed => beatmapSets.Where(s => !s.Filtered.Value).Sum(s => s.Beatmaps.Count(b => !b.Filtered.Value));
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The currently selected beatmap set.
|
/// The currently selected beatmap set.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -639,6 +649,8 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
if (alwaysResetScrollPosition || !Scroll.UserScrolling)
|
if (alwaysResetScrollPosition || !Scroll.UserScrolling)
|
||||||
ScrollToSelected(true);
|
ScrollToSelected(true);
|
||||||
|
|
||||||
|
FilterApplied?.Invoke();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,6 +65,7 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
r.All<ScoreInfo>()
|
r.All<ScoreInfo>()
|
||||||
.Filter($"{nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0"
|
.Filter($"{nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0"
|
||||||
+ $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $1"
|
+ $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $1"
|
||||||
|
+ $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.Hash)} == {nameof(ScoreInfo.BeatmapHash)}"
|
||||||
+ $" && {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $2"
|
+ $" && {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $2"
|
||||||
+ $" && {nameof(ScoreInfo.DeletePending)} == false", api.LocalUser.Value.Id, beatmapInfo.ID, ruleset.Value.ShortName),
|
+ $" && {nameof(ScoreInfo.DeletePending)} == false", api.LocalUser.Value.Id, beatmapInfo.ID, ruleset.Value.ShortName),
|
||||||
localScoresChanged);
|
localScoresChanged);
|
||||||
|
@ -10,6 +10,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Collections;
|
using osu.Game.Collections;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
@ -27,20 +28,27 @@ namespace osu.Game.Screens.Select
|
|||||||
{
|
{
|
||||||
public partial class FilterControl : Container
|
public partial class FilterControl : Container
|
||||||
{
|
{
|
||||||
public const float HEIGHT = 2 * side_margin + 85;
|
public const float HEIGHT = 2 * side_margin + 120;
|
||||||
private const float side_margin = 20;
|
|
||||||
|
private const float side_margin = 10;
|
||||||
|
|
||||||
public Action<FilterCriteria> FilterChanged;
|
public Action<FilterCriteria> FilterChanged;
|
||||||
|
|
||||||
public Bindable<string> CurrentTextSearch => searchTextBox.Current;
|
public Bindable<string> CurrentTextSearch => searchTextBox.Current;
|
||||||
|
|
||||||
|
public LocalisableString InformationalText
|
||||||
|
{
|
||||||
|
get => searchTextBox.FilterText.Text;
|
||||||
|
set => searchTextBox.FilterText.Text = value;
|
||||||
|
}
|
||||||
|
|
||||||
private OsuTabControl<SortMode> sortTabs;
|
private OsuTabControl<SortMode> sortTabs;
|
||||||
|
|
||||||
private Bindable<SortMode> sortMode;
|
private Bindable<SortMode> sortMode;
|
||||||
|
|
||||||
private Bindable<GroupMode> groupMode;
|
private Bindable<GroupMode> groupMode;
|
||||||
|
|
||||||
private SeekLimitedSearchTextBox searchTextBox;
|
private FilterControlTextBox searchTextBox;
|
||||||
|
|
||||||
private CollectionDropdown collectionDropdown;
|
private CollectionDropdown collectionDropdown;
|
||||||
|
|
||||||
@ -99,72 +107,63 @@ namespace osu.Game.Screens.Select
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Spacing = new Vector2(0, 5),
|
Spacing = new Vector2(0, 5),
|
||||||
Children = new[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new Container
|
searchTextBox = new FilterControlTextBox
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Height = 60,
|
},
|
||||||
Children = new Drawable[]
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = 1,
|
||||||
|
Colour = OsuColour.Gray(80),
|
||||||
|
},
|
||||||
|
new GridContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
ColumnDimensions = new[]
|
||||||
{
|
{
|
||||||
searchTextBox = new SeekLimitedSearchTextBox { RelativeSizeAxes = Axes.X },
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
new Box
|
new Dimension(GridSizeMode.Absolute, OsuTabControl<SortMode>.HORIZONTAL_SPACING),
|
||||||
|
new Dimension(),
|
||||||
|
new Dimension(GridSizeMode.Absolute, OsuTabControl<SortMode>.HORIZONTAL_SPACING),
|
||||||
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
|
},
|
||||||
|
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
new OsuSpriteText
|
||||||
Height = 1,
|
|
||||||
Colour = OsuColour.Gray(80),
|
|
||||||
Origin = Anchor.BottomLeft,
|
|
||||||
Anchor = Anchor.BottomLeft,
|
|
||||||
},
|
|
||||||
new GridContainer
|
|
||||||
{
|
|
||||||
Anchor = Anchor.BottomRight,
|
|
||||||
Origin = Anchor.BottomRight,
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
ColumnDimensions = new[]
|
|
||||||
{
|
{
|
||||||
new Dimension(GridSizeMode.AutoSize),
|
Text = SortStrings.Default,
|
||||||
new Dimension(GridSizeMode.Absolute, OsuTabControl<SortMode>.HORIZONTAL_SPACING),
|
Font = OsuFont.GetFont(size: 14),
|
||||||
new Dimension(),
|
Margin = new MarginPadding(5),
|
||||||
new Dimension(GridSizeMode.Absolute, OsuTabControl<SortMode>.HORIZONTAL_SPACING),
|
Anchor = Anchor.BottomRight,
|
||||||
new Dimension(GridSizeMode.AutoSize),
|
Origin = Anchor.BottomRight,
|
||||||
},
|
},
|
||||||
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
|
Empty(),
|
||||||
Content = new[]
|
sortTabs = new OsuTabControl<SortMode>
|
||||||
{
|
{
|
||||||
new[]
|
RelativeSizeAxes = Axes.X,
|
||||||
{
|
Height = 24,
|
||||||
new OsuSpriteText
|
AutoSort = true,
|
||||||
{
|
Anchor = Anchor.BottomRight,
|
||||||
Text = SortStrings.Default,
|
Origin = Anchor.BottomRight,
|
||||||
Font = OsuFont.GetFont(size: 14),
|
AccentColour = colours.GreenLight,
|
||||||
Margin = new MarginPadding(5),
|
Current = { BindTarget = sortMode }
|
||||||
Anchor = Anchor.BottomRight,
|
},
|
||||||
Origin = Anchor.BottomRight,
|
Empty(),
|
||||||
},
|
new OsuTabControlCheckbox
|
||||||
Empty(),
|
{
|
||||||
sortTabs = new OsuTabControl<SortMode>
|
Text = "Show converted",
|
||||||
{
|
Current = config.GetBindable<bool>(OsuSetting.ShowConvertedBeatmaps),
|
||||||
RelativeSizeAxes = Axes.X,
|
Anchor = Anchor.BottomRight,
|
||||||
Height = 24,
|
Origin = Anchor.BottomRight,
|
||||||
AutoSort = true,
|
},
|
||||||
Anchor = Anchor.BottomRight,
|
}
|
||||||
Origin = Anchor.BottomRight,
|
|
||||||
AccentColour = colours.GreenLight,
|
|
||||||
Current = { BindTarget = sortMode }
|
|
||||||
},
|
|
||||||
Empty(),
|
|
||||||
new OsuTabControlCheckbox
|
|
||||||
{
|
|
||||||
Text = "Show converted",
|
|
||||||
Current = config.GetBindable<bool>(OsuSetting.ShowConvertedBeatmaps),
|
|
||||||
Anchor = Anchor.BottomRight,
|
|
||||||
Origin = Anchor.BottomRight,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new Container
|
new Container
|
||||||
@ -248,5 +247,32 @@ namespace osu.Game.Screens.Select
|
|||||||
protected override bool OnClick(ClickEvent e) => true;
|
protected override bool OnClick(ClickEvent e) => true;
|
||||||
|
|
||||||
protected override bool OnHover(HoverEvent e) => true;
|
protected override bool OnHover(HoverEvent e) => true;
|
||||||
|
|
||||||
|
private partial class FilterControlTextBox : SeekLimitedSearchTextBox
|
||||||
|
{
|
||||||
|
private const float filter_text_size = 12;
|
||||||
|
|
||||||
|
public OsuSpriteText FilterText { get; private set; }
|
||||||
|
|
||||||
|
public FilterControlTextBox()
|
||||||
|
{
|
||||||
|
Height += filter_text_size;
|
||||||
|
TextContainer.Margin = new MarginPadding { Bottom = filter_text_size };
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
TextContainer.Add(FilterText = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.TopLeft,
|
||||||
|
Depth = float.MinValue,
|
||||||
|
Font = OsuFont.Default.With(size: filter_text_size, weight: FontWeight.SemiBold),
|
||||||
|
Margin = new MarginPadding { Top = 2, Left = 2 },
|
||||||
|
Colour = colours.Yellow
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -191,6 +191,7 @@ namespace osu.Game.Screens.Select.Leaderboards
|
|||||||
|
|
||||||
scoreSubscription = realm.RegisterForNotifications(r =>
|
scoreSubscription = realm.RegisterForNotifications(r =>
|
||||||
r.All<ScoreInfo>().Filter($"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $0"
|
r.All<ScoreInfo>().Filter($"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $0"
|
||||||
|
+ $" AND {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.Hash)} == {nameof(ScoreInfo.BeatmapHash)}"
|
||||||
+ $" AND {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $1"
|
+ $" AND {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $1"
|
||||||
+ $" AND {nameof(ScoreInfo.DeletePending)} == false"
|
+ $" AND {nameof(ScoreInfo.DeletePending)} == false"
|
||||||
, beatmapInfo.ID, ruleset.Value.ShortName), localScoresChanged);
|
, beatmapInfo.ID, ruleset.Value.ShortName), localScoresChanged);
|
||||||
|
@ -162,6 +162,7 @@ namespace osu.Game.Screens.Select
|
|||||||
BleedBottom = Footer.HEIGHT,
|
BleedBottom = Footer.HEIGHT,
|
||||||
SelectionChanged = updateSelectedBeatmap,
|
SelectionChanged = updateSelectedBeatmap,
|
||||||
BeatmapSetsChanged = carouselBeatmapsLoaded,
|
BeatmapSetsChanged = carouselBeatmapsLoaded,
|
||||||
|
FilterApplied = updateVisibleBeatmapCount,
|
||||||
GetRecommendedBeatmap = s => recommender?.GetRecommendedBeatmap(s),
|
GetRecommendedBeatmap = s => recommender?.GetRecommendedBeatmap(s),
|
||||||
}, c => carouselContainer.Child = c);
|
}, c => carouselContainer.Child = c);
|
||||||
|
|
||||||
@ -828,6 +829,7 @@ namespace osu.Game.Screens.Select
|
|||||||
private void carouselBeatmapsLoaded()
|
private void carouselBeatmapsLoaded()
|
||||||
{
|
{
|
||||||
bindBindables();
|
bindBindables();
|
||||||
|
updateVisibleBeatmapCount();
|
||||||
|
|
||||||
Carousel.AllowSelection = true;
|
Carousel.AllowSelection = true;
|
||||||
|
|
||||||
@ -857,6 +859,15 @@ namespace osu.Game.Screens.Select
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateVisibleBeatmapCount()
|
||||||
|
{
|
||||||
|
FilterControl.InformationalText = Carousel.CountDisplayed == 1
|
||||||
|
// Intentionally not localised until we have proper support for this (see https://github.com/ppy/osu-framework/pull/4918
|
||||||
|
// but also in this case we want support for formatting a number within a string).
|
||||||
|
? $"{Carousel.CountDisplayed:#,0} matching beatmap"
|
||||||
|
: $"{Carousel.CountDisplayed:#,0} matching beatmaps";
|
||||||
|
}
|
||||||
|
|
||||||
private bool boundLocalBindables;
|
private bool boundLocalBindables;
|
||||||
|
|
||||||
private void bindBindables()
|
private void bindBindables()
|
||||||
|
@ -101,7 +101,8 @@ namespace osu.Game.Skinning
|
|||||||
// In both of these cases, the expectation from the user is that the filename or folder name is displayed somewhere to identify the skin.
|
// In both of these cases, the expectation from the user is that the filename or folder name is displayed somewhere to identify the skin.
|
||||||
if (archiveName != item.Name
|
if (archiveName != item.Name
|
||||||
// lazer exports use this format
|
// lazer exports use this format
|
||||||
&& archiveName != item.GetDisplayString())
|
// GetValidFilename accounts for skins with non-ASCII characters in the name that have been exported by lazer.
|
||||||
|
&& archiveName != item.GetDisplayString().GetValidFilename())
|
||||||
item.Name = @$"{item.Name} [{archiveName}]";
|
item.Name = @$"{item.Name} [{archiveName}]";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<OutputType>Library</OutputType>
|
<OutputType>Library</OutputType>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<LangVersion>10</LangVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Label="Nuget">
|
<PropertyGroup Label="Nuget">
|
||||||
<Title>osu!</Title>
|
<Title>osu!</Title>
|
||||||
@ -35,8 +36,8 @@
|
|||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Realm" Version="10.20.0" />
|
<PackageReference Include="Realm" Version="10.20.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2023.228.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2023.314.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2023.228.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2023.320.0" />
|
||||||
<PackageReference Include="Sentry" Version="3.28.1" />
|
<PackageReference Include="Sentry" Version="3.28.1" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.32.2" />
|
<PackageReference Include="SharpCompress" Version="0.32.2" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
|
@ -16,6 +16,6 @@
|
|||||||
<RuntimeIdentifier>iossimulator-x64</RuntimeIdentifier>
|
<RuntimeIdentifier>iossimulator-x64</RuntimeIdentifier>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2023.228.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2023.314.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
Loading…
Reference in New Issue
Block a user