1
0
mirror of https://github.com/ppy/osu.git synced 2025-03-09 07:07:18 +08:00

Merge pull request #29027 from normalid-awa/feature/skin/legacy-input-overlay

Add legacy key counter support
This commit is contained in:
Dean Herbert 2024-08-07 17:48:30 +09:00 committed by GitHub
commit b1488fd5b7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 533 additions and 208 deletions

View File

@ -6,6 +6,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Skinning;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
@ -28,11 +29,15 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup)
{
if (lookup is SkinComponentsContainerLookup containerLookup)
switch (lookup)
{
switch (containerLookup.Target)
{
case SkinComponentsContainerLookup.TargetArea.MainHUDComponents:
case SkinComponentsContainerLookup containerLookup:
if (containerLookup.Target != SkinComponentsContainerLookup.TargetArea.MainHUDComponents)
return base.GetDrawableComponent(lookup);
// Modifications for global components.
if (containerLookup.Ruleset == null)
{
var components = base.GetDrawableComponent(lookup) as Container;
if (providesComboCounter && components != null)
@ -44,60 +49,84 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
}
return components;
}
}
}
if (lookup is CatchSkinComponentLookup catchSkinComponent)
{
switch (catchSkinComponent.Component)
{
case CatchSkinComponents.Fruit:
if (hasPear)
return new LegacyFruitPiece();
// Skin has configuration.
if (base.GetDrawableComponent(lookup) is Drawable d)
return d;
return null;
// Our own ruleset components default.
return new DefaultSkinComponentsContainer(container =>
{
var keyCounter = container.OfType<LegacyKeyCounterDisplay>().FirstOrDefault();
case CatchSkinComponents.Banana:
if (GetTexture("fruit-bananas") != null)
return new LegacyBananaPiece();
return null;
case CatchSkinComponents.Droplet:
if (GetTexture("fruit-drop") != null)
return new LegacyDropletPiece();
return null;
case CatchSkinComponents.Catcher:
decimal version = GetConfig<SkinConfiguration.LegacySetting, decimal>(SkinConfiguration.LegacySetting.Version)?.Value ?? 1;
if (version < 2.3m)
if (keyCounter != null)
{
if (hasOldStyleCatcherSprite())
return new LegacyCatcherOld();
// set the anchor to top right so that it won't squash to the return button to the top
keyCounter.Anchor = Anchor.CentreRight;
keyCounter.Origin = Anchor.CentreRight;
keyCounter.X = 0;
// 340px is the default height inherit from stable
keyCounter.Y = container.ToLocalSpace(new Vector2(0, container.ScreenSpaceDrawQuad.Centre.Y - 340f)).Y;
}
})
{
Children = new Drawable[]
{
new LegacyKeyCounterDisplay(),
}
};
if (hasNewStyleCatcherSprite())
return new LegacyCatcherNew();
case CatchSkinComponentLookup catchSkinComponent:
switch (catchSkinComponent.Component)
{
case CatchSkinComponents.Fruit:
if (hasPear)
return new LegacyFruitPiece();
return null;
return null;
case CatchSkinComponents.CatchComboCounter:
if (providesComboCounter)
return new LegacyCatchComboCounter();
case CatchSkinComponents.Banana:
if (GetTexture("fruit-bananas") != null)
return new LegacyBananaPiece();
return null;
return null;
case CatchSkinComponents.HitExplosion:
if (hasOldStyleCatcherSprite() || hasNewStyleCatcherSprite())
return new LegacyHitExplosion();
case CatchSkinComponents.Droplet:
if (GetTexture("fruit-drop") != null)
return new LegacyDropletPiece();
return null;
return null;
default:
throw new UnsupportedSkinComponentException(lookup);
}
case CatchSkinComponents.Catcher:
decimal version = GetConfig<SkinConfiguration.LegacySetting, decimal>(SkinConfiguration.LegacySetting.Version)?.Value ?? 1;
if (version < 2.3m)
{
if (hasOldStyleCatcherSprite())
return new LegacyCatcherOld();
}
if (hasNewStyleCatcherSprite())
return new LegacyCatcherNew();
return null;
case CatchSkinComponents.CatchComboCounter:
if (providesComboCounter)
return new LegacyCatchComboCounter();
return null;
case CatchSkinComponents.HitExplosion:
if (hasOldStyleCatcherSprite() || hasNewStyleCatcherSprite())
return new LegacyHitExplosion();
return null;
default:
throw new UnsupportedSkinComponentException(lookup);
}
}
return base.GetDrawableComponent(lookup);

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Osu.Objects;
@ -41,139 +42,178 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup)
{
if (lookup is OsuSkinComponentLookup osuComponent)
switch (lookup)
{
switch (osuComponent.Component)
{
case OsuSkinComponents.FollowPoint:
return this.GetAnimation("followpoint", true, true, true, startAtCurrentTime: false, maxSize: new Vector2(OsuHitObject.OBJECT_RADIUS * 2, OsuHitObject.OBJECT_RADIUS));
case SkinComponentsContainerLookup containerLookup:
// Only handle per ruleset defaults here.
if (containerLookup.Ruleset == null)
return base.GetDrawableComponent(lookup);
case OsuSkinComponents.SliderScorePoint:
return this.GetAnimation("sliderscorepoint", false, false, maxSize: OsuHitObject.OBJECT_DIMENSIONS);
// Skin has configuration.
if (base.GetDrawableComponent(lookup) is Drawable d)
return d;
case OsuSkinComponents.SliderFollowCircle:
var followCircleContent = this.GetAnimation("sliderfollowcircle", true, true, true, maxSize: MAX_FOLLOW_CIRCLE_AREA_SIZE);
if (followCircleContent != null)
return new LegacyFollowCircle(followCircleContent);
// Our own ruleset components default.
switch (containerLookup.Target)
{
case SkinComponentsContainerLookup.TargetArea.MainHUDComponents:
return new DefaultSkinComponentsContainer(container =>
{
var keyCounter = container.OfType<LegacyKeyCounterDisplay>().FirstOrDefault();
return null;
if (keyCounter != null)
{
// set the anchor to top right so that it won't squash to the return button to the top
keyCounter.Anchor = Anchor.CentreRight;
keyCounter.Origin = Anchor.CentreRight;
keyCounter.X = 0;
// 340px is the default height inherit from stable
keyCounter.Y = container.ToLocalSpace(new Vector2(0, container.ScreenSpaceDrawQuad.Centre.Y - 340f)).Y;
}
})
{
Children = new Drawable[]
{
new LegacyKeyCounterDisplay(),
}
};
}
case OsuSkinComponents.SliderBall:
if (GetTexture("sliderb") != null || GetTexture("sliderb0") != null)
return new LegacySliderBall(this);
return null;
return null;
case OsuSkinComponentLookup osuComponent:
switch (osuComponent.Component)
{
case OsuSkinComponents.FollowPoint:
return this.GetAnimation("followpoint", true, true, true, startAtCurrentTime: false, maxSize: new Vector2(OsuHitObject.OBJECT_RADIUS * 2, OsuHitObject.OBJECT_RADIUS));
case OsuSkinComponents.SliderBody:
if (hasHitCircle.Value)
return new LegacySliderBody();
case OsuSkinComponents.SliderScorePoint:
return this.GetAnimation("sliderscorepoint", false, false, maxSize: OsuHitObject.OBJECT_DIMENSIONS);
return null;
case OsuSkinComponents.SliderFollowCircle:
var followCircleContent = this.GetAnimation("sliderfollowcircle", true, true, true, maxSize: MAX_FOLLOW_CIRCLE_AREA_SIZE);
if (followCircleContent != null)
return new LegacyFollowCircle(followCircleContent);
case OsuSkinComponents.SliderTailHitCircle:
if (hasHitCircle.Value)
return new LegacyMainCirclePiece("sliderendcircle", false);
return null;
case OsuSkinComponents.SliderHeadHitCircle:
if (hasHitCircle.Value)
return new LegacySliderHeadHitCircle();
return null;
case OsuSkinComponents.ReverseArrow:
if (hasHitCircle.Value)
return new LegacyReverseArrow();
return null;
case OsuSkinComponents.HitCircle:
if (hasHitCircle.Value)
return new LegacyMainCirclePiece();
return null;
case OsuSkinComponents.Cursor:
if (GetTexture("cursor") != null)
return new LegacyCursor(this);
return null;
case OsuSkinComponents.CursorTrail:
if (GetTexture("cursortrail") != null)
return new LegacyCursorTrail(this);
return null;
case OsuSkinComponents.CursorRipple:
if (GetTexture("cursor-ripple") != null)
{
var ripple = this.GetAnimation("cursor-ripple", false, false);
// In stable this element was scaled down to 50% and opacity 20%, but this makes the elements WAY too big and inflexible.
// If anyone complains about these not being applied, this can be uncommented.
//
// But if no one complains I'd rather fix this in lazer. Wiki documentation doesn't mention size,
// so we might be okay.
//
// if (ripple != null)
// {
// ripple.Scale = new Vector2(0.5f);
// ripple.Alpha = 0.2f;
// }
return ripple;
}
return null;
case OsuSkinComponents.CursorParticles:
if (GetTexture("star2") != null)
return new LegacyCursorParticles();
return null;
case OsuSkinComponents.CursorSmoke:
if (GetTexture("cursor-smoke") != null)
return new LegacySmokeSegment();
return null;
case OsuSkinComponents.HitCircleText:
if (!this.HasFont(LegacyFont.HitCircle))
return null;
const float hitcircle_text_scale = 0.8f;
return new LegacySpriteText(LegacyFont.HitCircle)
{
// stable applies a blanket 0.8x scale to hitcircle fonts
Scale = new Vector2(hitcircle_text_scale),
MaxSizePerGlyph = OsuHitObject.OBJECT_DIMENSIONS * 2 / hitcircle_text_scale,
};
case OsuSkinComponents.SliderBall:
if (GetTexture("sliderb") != null || GetTexture("sliderb0") != null)
return new LegacySliderBall(this);
case OsuSkinComponents.SpinnerBody:
bool hasBackground = GetTexture("spinner-background") != null;
return null;
if (GetTexture("spinner-top") != null && !hasBackground)
return new LegacyNewStyleSpinner();
else if (hasBackground)
return new LegacyOldStyleSpinner();
case OsuSkinComponents.SliderBody:
if (hasHitCircle.Value)
return new LegacySliderBody();
return null;
return null;
case OsuSkinComponents.ApproachCircle:
if (GetTexture(@"approachcircle") != null)
return new LegacyApproachCircle();
case OsuSkinComponents.SliderTailHitCircle:
if (hasHitCircle.Value)
return new LegacyMainCirclePiece("sliderendcircle", false);
return null;
return null;
default:
throw new UnsupportedSkinComponentException(lookup);
}
case OsuSkinComponents.SliderHeadHitCircle:
if (hasHitCircle.Value)
return new LegacySliderHeadHitCircle();
return null;
case OsuSkinComponents.ReverseArrow:
if (hasHitCircle.Value)
return new LegacyReverseArrow();
return null;
case OsuSkinComponents.HitCircle:
if (hasHitCircle.Value)
return new LegacyMainCirclePiece();
return null;
case OsuSkinComponents.Cursor:
if (GetTexture("cursor") != null)
return new LegacyCursor(this);
return null;
case OsuSkinComponents.CursorTrail:
if (GetTexture("cursortrail") != null)
return new LegacyCursorTrail(this);
return null;
case OsuSkinComponents.CursorRipple:
if (GetTexture("cursor-ripple") != null)
{
var ripple = this.GetAnimation("cursor-ripple", false, false);
// In stable this element was scaled down to 50% and opacity 20%, but this makes the elements WAY too big and inflexible.
// If anyone complains about these not being applied, this can be uncommented.
//
// But if no one complains I'd rather fix this in lazer. Wiki documentation doesn't mention size,
// so we might be okay.
//
// if (ripple != null)
// {
// ripple.Scale = new Vector2(0.5f);
// ripple.Alpha = 0.2f;
// }
return ripple;
}
return null;
case OsuSkinComponents.CursorParticles:
if (GetTexture("star2") != null)
return new LegacyCursorParticles();
return null;
case OsuSkinComponents.CursorSmoke:
if (GetTexture("cursor-smoke") != null)
return new LegacySmokeSegment();
return null;
case OsuSkinComponents.HitCircleText:
if (!this.HasFont(LegacyFont.HitCircle))
return null;
const float hitcircle_text_scale = 0.8f;
return new LegacySpriteText(LegacyFont.HitCircle)
{
// stable applies a blanket 0.8x scale to hitcircle fonts
Scale = new Vector2(hitcircle_text_scale),
MaxSizePerGlyph = OsuHitObject.OBJECT_DIMENSIONS * 2 / hitcircle_text_scale,
};
case OsuSkinComponents.SpinnerBody:
bool hasBackground = GetTexture("spinner-background") != null;
if (GetTexture("spinner-top") != null && !hasBackground)
return new LegacyNewStyleSpinner();
else if (hasBackground)
return new LegacyOldStyleSpinner();
return null;
case OsuSkinComponents.ApproachCircle:
if (GetTexture(@"approachcircle") != null)
return new LegacyApproachCircle();
return null;
default:
throw new UnsupportedSkinComponentException(lookup);
}
default:
return base.GetDrawableComponent(lookup);
}
return base.GetDrawableComponent(lookup);
}
public override IBindable<TValue>? GetConfig<TLookup, TValue>(TLookup lookup)

View File

@ -65,7 +65,9 @@ namespace osu.Game.Tests.Skins
// Covers default rank display
"Archives/modified-default-20230809.osk",
// Covers legacy rank display
"Archives/modified-classic-20230809.osk"
"Archives/modified-classic-20230809.osk",
// Covers legacy key counter
"Archives/modified-classic-20240724.osk"
};
/// <summary>

View File

@ -9,7 +9,6 @@ using osu.Framework.Allocation;
using osu.Framework.Audio.Track;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Configuration;
using osu.Game.Graphics.Containers;
@ -45,7 +44,7 @@ namespace osu.Game.Tests.Visual.Gameplay
// best way to check without exposing.
private Drawable hideTarget => hudOverlay.ChildrenOfType<SkinComponentsContainer>().First();
private Drawable keyCounterFlow => hudOverlay.ChildrenOfType<KeyCounterDisplay>().First().ChildrenOfType<FillFlowContainer<KeyCounter>>().Single();
private Drawable keyCounterContent => hudOverlay.ChildrenOfType<KeyCounterDisplay>().First().ChildrenOfType<Drawable>().Skip(1).First();
public TestSceneHUDOverlay()
{
@ -79,7 +78,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("showhud is set", () => hudOverlay.ShowHud.Value);
AddAssert("hidetarget is visible", () => hideTarget.Alpha, () => Is.GreaterThan(0));
AddAssert("key counter flow is visible", () => keyCounterFlow.IsPresent);
AddAssert("key counter flow is visible", () => keyCounterContent.IsPresent);
AddAssert("pause button is visible", () => hudOverlay.HoldToQuit.IsPresent);
}
@ -104,7 +103,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("pause button is still visible", () => hudOverlay.HoldToQuit.IsPresent);
// Key counter flow container should not be affected by this, only the key counter display will be hidden as checked above.
AddAssert("key counter flow not affected", () => keyCounterFlow.IsPresent);
AddAssert("key counter flow not affected", () => keyCounterContent.IsPresent);
}
[Test]
@ -150,11 +149,11 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
AddUntilStep("hidetarget is hidden", () => hideTarget.Alpha, () => Is.LessThanOrEqualTo(0));
AddUntilStep("key counters hidden", () => !keyCounterFlow.IsPresent);
AddUntilStep("key counters hidden", () => !keyCounterContent.IsPresent);
AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true);
AddUntilStep("hidetarget is visible", () => hideTarget.Alpha, () => Is.GreaterThan(0));
AddUntilStep("key counters still hidden", () => !keyCounterFlow.IsPresent);
AddUntilStep("key counters still hidden", () => !keyCounterContent.IsPresent);
}
[Test]

View File

@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Utils;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD;
using osu.Game.Skinning;
using osuTK;
using osuTK.Input;
@ -56,6 +57,11 @@ namespace osu.Game.Tests.Visual.Gameplay
Anchor = Anchor.Centre,
Scale = new Vector2(1, -1)
},
new LegacyKeyCounterDisplay
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
},
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
@ -89,6 +95,12 @@ namespace osu.Game.Tests.Visual.Gameplay
Anchor = Anchor.Centre,
Rotation = 90,
},
new LegacyKeyCounterDisplay
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Rotation = 90,
},
}
},
}

View File

@ -16,12 +16,13 @@ using osu.Framework.Testing;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD;
using osu.Game.Skinning;
using osu.Game.Tests.Gameplay;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
{
@ -91,10 +92,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
SetContents(_ =>
{
hudOverlay = new HUDOverlay(null, Array.Empty<Mod>());
// Add any key just to display the key counter visually.
hudOverlay.InputCountController.Add(new KeyCounterKeyboardTrigger(Key.Space));
hudOverlay = new HUDOverlay(new DrawableOsuRuleset(new OsuRuleset(), new OsuBeatmap()), Array.Empty<Mod>());
action?.Invoke(hudOverlay);

View File

@ -0,0 +1,42 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD;
using osu.Game.Skinning;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
{
public partial class TestSceneSkinnableKeyCounter : SkinnableHUDComponentTestScene
{
[Cached]
private readonly InputCountController controller = new InputCountController();
public override void SetUpSteps()
{
AddStep("create dependencies", () =>
{
Add(controller);
controller.Add(new KeyCounterKeyboardTrigger(Key.Z));
controller.Add(new KeyCounterKeyboardTrigger(Key.X));
controller.Add(new KeyCounterKeyboardTrigger(Key.C));
controller.Add(new KeyCounterKeyboardTrigger(Key.V));
foreach (var trigger in controller.Triggers)
Add(trigger);
});
base.SetUpSteps();
}
protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
protected override Drawable CreateDefaultImplementation() => new ArgonKeyCounterDisplay();
protected override Drawable CreateLegacyImplementation() => new LegacyKeyCounterDisplay();
}
}

View File

@ -93,14 +93,8 @@ namespace osu.Game.Beatmaps.Formats
return line;
}
protected void HandleColours<TModel>(TModel output, string line, bool allowAlpha)
private Color4 convertSettingStringToColor4(string[] split, bool allowAlpha, KeyValuePair<string, string> pair)
{
var pair = SplitKeyVal(line);
bool isCombo = pair.Key.StartsWith(@"Combo", StringComparison.Ordinal);
string[] split = pair.Value.Split(',');
if (split.Length != 3 && split.Length != 4)
throw new InvalidOperationException($@"Color specified in incorrect format (should be R,G,B or R,G,B,A): {pair.Value}");
@ -116,6 +110,18 @@ namespace osu.Game.Beatmaps.Formats
throw new InvalidOperationException(@"Color must be specified with 8-bit integer components");
}
return colour;
}
protected void HandleColours<TModel>(TModel output, string line, bool allowAlpha)
{
var pair = SplitKeyVal(line);
string[] split = pair.Value.Split(',');
Color4 colour = convertSettingStringToColor4(split, allowAlpha, pair);
bool isCombo = pair.Key.StartsWith(@"Combo", StringComparison.Ordinal);
if (isCombo)
{
if (!(output is IHasComboColours tHasComboColours)) return;

View File

@ -14,11 +14,10 @@ namespace osu.Game.Screens.Play
public ArgonKeyCounterDisplay()
{
InternalChild = KeyFlow = new FillFlowContainer<KeyCounter>
Child = KeyFlow = new FillFlowContainer<KeyCounter>
{
Direction = FillDirection.Horizontal,
AutoSizeAxes = Axes.Both,
Alpha = 0,
Spacing = new Vector2(2),
};
}

View File

@ -16,11 +16,10 @@ namespace osu.Game.Screens.Play.HUD
public DefaultKeyCounterDisplay()
{
InternalChild = KeyFlow = new FillFlowContainer<KeyCounter>
Child = KeyFlow = new FillFlowContainer<KeyCounter>
{
Direction = FillDirection.Horizontal,
AutoSizeAxes = Axes.Both,
Alpha = 0,
};
}

View File

@ -15,7 +15,7 @@ namespace osu.Game.Screens.Play.HUD
/// <summary>
/// A flowing display of all gameplay keys. Individual keys can be added using <see cref="InputTrigger"/> implementations.
/// </summary>
public abstract partial class KeyCounterDisplay : CompositeDrawable, ISerialisableDrawable
public abstract partial class KeyCounterDisplay : Container, ISerialisableDrawable
{
/// <summary>
/// Whether the key counter should be visible regardless of the configuration value.
@ -29,25 +29,22 @@ namespace osu.Game.Screens.Play.HUD
private readonly IBindableList<InputTrigger> triggers = new BindableList<InputTrigger>();
protected override Container<Drawable> Content { get; } = new Container
{
Alpha = 0,
AutoSizeAxes = Axes.Both,
};
[Resolved]
private InputCountController controller { get; set; } = null!;
private const int duration = 100;
protected void UpdateVisibility()
protected KeyCounterDisplay()
{
bool visible = AlwaysVisible.Value || ConfigVisibility.Value;
// Isolate changing visibility of the key counters from fading this component.
KeyFlow.FadeTo(visible ? 1 : 0, duration);
// Ensure a valid size is immediately obtained even if partially off-screen
// See https://github.com/ppy/osu/issues/14793.
KeyFlow.AlwaysPresent = visible;
AddInternal(Content);
}
protected abstract KeyCounter CreateCounter(InputTrigger trigger);
[BackgroundDependencyLoader]
private void load(OsuConfigManager config, DrawableRuleset? drawableRuleset)
{
@ -70,6 +67,20 @@ namespace osu.Game.Screens.Play.HUD
ConfigVisibility.BindValueChanged(_ => UpdateVisibility(), true);
}
protected void UpdateVisibility()
{
bool visible = AlwaysVisible.Value || ConfigVisibility.Value;
// Isolate changing visibility of the key counters from fading this component.
Content.FadeTo(visible ? 1 : 0, duration);
// Ensure a valid size is immediately obtained even if partially off-screen
// See https://github.com/ppy/osu/issues/14793.
Content.AlwaysPresent = visible;
}
protected abstract KeyCounter CreateCounter(InputTrigger trigger);
private void triggersChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
KeyFlow.Clear();

View File

@ -47,6 +47,9 @@ namespace osu.Game.Skinning
/// <summary>
/// Retrieve a configuration value.
/// </summary>
/// <remarks>
/// Note that while this returns a bindable value, it is not actually updated.
/// Until the API is fixed, just use the received bindable's <see cref="IBindable{TValue}.Value"/> immediately.</remarks>
/// <param name="lookup">The requested configuration value.</param>
/// <returns>A matching value boxed in an <see cref="IBindable{TValue}"/>, or null if unavailable.</returns>
IBindable<TValue>? GetConfig<TLookup, TValue>(TLookup lookup)

View File

@ -0,0 +1,101 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Screens.Play.HUD;
namespace osu.Game.Skinning
{
public partial class LegacyKeyCounter : KeyCounter
{
private const float transition_duration = 160;
public Colour4 ActiveColour { get; set; }
private Colour4 textColour;
public Colour4 TextColour
{
get => textColour;
set
{
textColour = value;
overlayKeyText.Colour = value;
}
}
private readonly Container keyContainer;
private readonly OsuSpriteText overlayKeyText;
private readonly Sprite keySprite;
public LegacyKeyCounter(InputTrigger trigger)
: base(trigger)
{
Origin = Anchor.Centre;
Anchor = Anchor.Centre;
Child = keyContainer = new Container
{
AutoSizeAxes = Axes.Both,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Children = new Drawable[]
{
keySprite = new Sprite
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
new UprightAspectMaintainingContainer
{
AutoSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Child = overlayKeyText = new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Text = trigger.Name,
Colour = textColour,
Font = OsuFont.GetFont(weight: FontWeight.SemiBold),
},
},
}
};
// matches longest dimension of default skin asset
Height = Width = 46;
}
[BackgroundDependencyLoader]
private void load(ISkinSource source)
{
Texture? keyTexture = source.GetTexture(@"inputoverlay-key");
if (keyTexture != null)
keySprite.Texture = keyTexture;
}
protected override void Activate(bool forwardPlayback = true)
{
base.Activate(forwardPlayback);
keyContainer.ScaleTo(0.75f, transition_duration, Easing.Out);
keySprite.Colour = ActiveColour;
overlayKeyText.Text = CountPresses.Value.ToString();
overlayKeyText.Font = overlayKeyText.Font.With(weight: FontWeight.SemiBold);
}
protected override void Deactivate(bool forwardPlayback = true)
{
base.Deactivate(forwardPlayback);
keyContainer.ScaleTo(1f, transition_duration, Easing.Out);
keySprite.Colour = Colour4.White;
}
}
}

View File

@ -0,0 +1,92 @@
// 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.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Screens.Play.HUD;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Skinning
{
public partial class LegacyKeyCounterDisplay : KeyCounterDisplay
{
private static readonly Colour4 active_colour_top = Colour4.FromHex(@"#ffde00");
private static readonly Colour4 active_colour_bottom = Colour4.FromHex(@"#f8009e");
protected override FillFlowContainer<KeyCounter> KeyFlow { get; }
private readonly Sprite backgroundSprite;
public LegacyKeyCounterDisplay()
{
AutoSizeAxes = Axes.Both;
AddRange(new Drawable[]
{
backgroundSprite = new Sprite
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopLeft,
Scale = new Vector2(1.05f, 1),
Rotation = 90,
},
KeyFlow = new FillFlowContainer<KeyCounter>
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
X = -1.5f,
Y = 7,
Spacing = new Vector2(1.8f),
Direction = FillDirection.Vertical,
AutoSizeAxes = Axes.Both,
},
});
}
[Resolved]
private ISkinSource source { get; set; } = null!;
protected override void LoadComplete()
{
base.LoadComplete();
KeyTextColor = source.GetConfig<SkinCustomColourLookup, Color4>(new SkinCustomColourLookup(SkinConfiguration.LegacySetting.InputOverlayText))?.Value ?? Color4.Black;
Texture? backgroundTexture = source.GetTexture(@"inputoverlay-background");
if (backgroundTexture != null)
backgroundSprite.Texture = backgroundTexture;
for (int i = 0; i < KeyFlow.Count; ++i)
{
((LegacyKeyCounter)KeyFlow[i]).ActiveColour = i < 2 ? active_colour_top : active_colour_bottom;
}
}
protected override KeyCounter CreateCounter(InputTrigger trigger) => new LegacyKeyCounter(trigger)
{
TextColour = keyTextColor,
};
private Colour4 keyTextColor = Colour4.White;
public Colour4 KeyTextColor
{
get => keyTextColor;
set
{
if (value != keyTextColor)
{
keyTextColor = value;
foreach (var child in KeyFlow.Cast<LegacyKeyCounter>())
child.TextColour = value;
}
}
}
}
}

View File

@ -23,7 +23,6 @@ using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Play.HUD.HitErrorMeters;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Skinning
@ -310,6 +309,9 @@ namespace osu.Game.Skinning
case SkinConfiguration.LegacySetting.Version:
return SkinUtils.As<TValue>(new Bindable<decimal>(Configuration.LegacyVersion ?? SkinConfiguration.LATEST_VERSION));
case SkinConfiguration.LegacySetting.InputOverlayText:
return SkinUtils.As<TValue>(new Bindable<Colour4>(Configuration.CustomColours.TryGetValue(@"InputOverlayText", out var colour) ? colour : Colour4.Black));
default:
return genericLookup<SkinConfiguration.LegacySetting, TValue>(legacySetting);
}
@ -381,22 +383,12 @@ namespace osu.Game.Skinning
}
var hitError = container.OfType<HitErrorMeter>().FirstOrDefault();
var keyCounter = container.OfType<DefaultKeyCounterDisplay>().FirstOrDefault();
if (hitError != null)
{
hitError.Anchor = Anchor.BottomCentre;
hitError.Origin = Anchor.CentreLeft;
hitError.Rotation = -90;
if (keyCounter != null)
{
const float padding = 10;
keyCounter.Anchor = Anchor.BottomRight;
keyCounter.Origin = Anchor.BottomRight;
keyCounter.Position = new Vector2(-padding, -(padding + hitError.Width));
}
}
})
{
@ -408,7 +400,6 @@ namespace osu.Game.Skinning
new LegacySongProgress(),
new LegacyHealthDisplay(),
new BarHitErrorMeter(),
new DefaultKeyCounterDisplay()
}
};
}

View File

@ -38,6 +38,7 @@ namespace osu.Game.Skinning
AnimationFramerate,
LayeredHitSounds,
AllowSliderBallTint,
InputOverlayText,
}
public static List<Color4> DefaultComboColours { get; } = new List<Color4>