From 288eed53df439c594ffab544d3b83d9b2ea9d343 Mon Sep 17 00:00:00 2001 From: Sheepposu Date: Sat, 10 Feb 2024 02:02:26 -0500 Subject: [PATCH] new features + improvements Hit & aim markers are skinnable. Hidden can be toggled off. Aim line with skinnable color was added. The fadeout time is based on the approach rate. Cursor hide fixed. --- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 77 ++++++- osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 12 +- osu.Game.Rulesets.Osu/OsuSkinComponents.cs | 3 + .../Skinning/Default/DefaultHitMarker.cs | 71 +++++++ .../Skinning/Default/HitMarker.cs | 33 +++ .../Legacy/OsuLegacySkinTransformer.cs | 19 ++ .../Skinning/OsuSkinColour.cs | 1 + .../UI/HitMarkerContainer.cs | 188 ++++++++++++------ .../UI/OsuAnalysisSettings.cs | 68 +++++-- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 2 +- .../PlayerSettingsOverlayStrings.cs | 5 + .../Rulesets/Mods/IToggleableVisibility.cs | 11 + 12 files changed, 393 insertions(+), 97 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs create mode 100644 osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs create mode 100644 osu.Game/Rulesets/Mods/IToggleableVisibility.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index 6dc0d5d522..a69bed6fb2 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -2,9 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using osu.Framework.Graphics; +using osu.Framework.Graphics.Transforms; using osu.Framework.Bindables; using osu.Framework.Localisation; using osu.Game.Configuration; @@ -12,13 +14,14 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Skinning; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModHidden : ModHidden, IHidesApproachCircles + public class OsuModHidden : ModHidden, IHidesApproachCircles, IToggleableVisibility { [SettingSource("Only fade approach circles", "The main object body will not fade when enabled.")] public Bindable OnlyFadeApproachCircles { get; } = new BindableBool(); @@ -28,24 +31,41 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn), typeof(OsuModDepth) }; + private bool toggledOff = false; + private IBeatmap? appliedBeatmap; + public const double FADE_IN_DURATION_MULTIPLIER = 0.4; public const double FADE_OUT_DURATION_MULTIPLIER = 0.3; protected override bool IsFirstAdjustableObject(HitObject hitObject) => !(hitObject is Spinner || hitObject is SpinnerTick); - public override void ApplyToBeatmap(IBeatmap beatmap) + public override void ApplyToBeatmap(IBeatmap? beatmap = null) { + if (beatmap is not null) + appliedBeatmap = beatmap; + else if (appliedBeatmap is null) + return; + else + beatmap = appliedBeatmap; + base.ApplyToBeatmap(beatmap); foreach (var obj in beatmap.HitObjects.OfType()) applyFadeInAdjustment(obj); + } - static void applyFadeInAdjustment(OsuHitObject osuObject) - { - osuObject.TimeFadeIn = osuObject.TimePreempt * FADE_IN_DURATION_MULTIPLIER; - foreach (var nested in osuObject.NestedHitObjects.OfType()) - applyFadeInAdjustment(nested); - } + private static void applyFadeInAdjustment(OsuHitObject osuObject) + { + osuObject.TimeFadeIn = osuObject.TimePreempt * FADE_IN_DURATION_MULTIPLIER; + foreach (var nested in osuObject.NestedHitObjects.OfType()) + applyFadeInAdjustment(nested); + } + + private static void revertFadeInAdjustment(OsuHitObject osuObject) + { + osuObject.TimeFadeIn = OsuHitObject.CalculateTimeFadeIn(osuObject.TimePreempt); + foreach (var nested in osuObject.NestedHitObjects.OfType()) + revertFadeInAdjustment(nested); } protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) @@ -60,7 +80,7 @@ namespace osu.Game.Rulesets.Osu.Mods private void applyHiddenState(DrawableHitObject drawableObject, bool increaseVisibility) { - if (!(drawableObject is DrawableOsuHitObject drawableOsuObject)) + if (!(drawableObject is DrawableOsuHitObject drawableOsuObject) || toggledOff) return; OsuHitObject hitObject = drawableOsuObject.HitObject; @@ -183,8 +203,11 @@ namespace osu.Game.Rulesets.Osu.Mods } } - private static void hideSpinnerApproachCircle(DrawableSpinner spinner) + private void hideSpinnerApproachCircle(DrawableSpinner spinner) { + if (toggledOff) + return; + var approachCircle = (spinner.Body.Drawable as IHasApproachCircle)?.ApproachCircle; if (approachCircle == null) return; @@ -192,5 +215,39 @@ namespace osu.Game.Rulesets.Osu.Mods using (spinner.BeginAbsoluteSequence(spinner.HitObject.StartTime - spinner.HitObject.TimePreempt)) approachCircle.Hide(); } + + public void ToggleOffVisibility(Playfield playfield) + { + if (toggledOff) + return; + + toggledOff = true; + + if (appliedBeatmap is not null) + foreach (var obj in appliedBeatmap.HitObjects.OfType()) + revertFadeInAdjustment(obj); + + foreach (var dho in playfield.AllHitObjects) + { + dho.RefreshStateTransforms(); + } + } + + public void ToggleOnVisibility(Playfield playfield) + { + if (!toggledOff) + return; + + toggledOff = false; + + if (appliedBeatmap is not null) + ApplyToBeatmap(); + + foreach (var dho in playfield.AllHitObjects) + { + dho.RefreshStateTransforms(); + ApplyToDrawableHitObject(dho); + } + } } } diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 74631400ca..60305ed953 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -154,17 +154,19 @@ namespace osu.Game.Rulesets.Osu.Objects }); } + // Preempt time can go below 450ms. Normally, this is achieved via the DT mod which uniformly speeds up all animations game wide regardless of AR. + // This uniform speedup is hard to match 1:1, however we can at least make AR>10 (via mods) feel good by extending the upper linear function above. + // Note that this doesn't exactly match the AR>10 visuals as they're classically known, but it feels good. + // This adjustment is necessary for AR>10, otherwise TimePreempt can become smaller leading to hitcircles not fully fading in. + static public double CalculateTimeFadeIn(double timePreempt) => 400 * Math.Min(1, timePreempt / PREEMPT_MIN); + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); TimePreempt = (float)IBeatmapDifficultyInfo.DifficultyRange(difficulty.ApproachRate, PREEMPT_MAX, PREEMPT_MID, PREEMPT_MIN); - // Preempt time can go below 450ms. Normally, this is achieved via the DT mod which uniformly speeds up all animations game wide regardless of AR. - // This uniform speedup is hard to match 1:1, however we can at least make AR>10 (via mods) feel good by extending the upper linear function above. - // Note that this doesn't exactly match the AR>10 visuals as they're classically known, but it feels good. - // This adjustment is necessary for AR>10, otherwise TimePreempt can become smaller leading to hitcircles not fully fading in. - TimeFadeIn = 400 * Math.Min(1, TimePreempt / PREEMPT_MIN); + TimeFadeIn = CalculateTimeFadeIn(TimePreempt); Scale = LegacyRulesetExtensions.CalculateScaleFromCircleSize(difficulty.CircleSize, true); } diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs index 52fdfea95f..75db18d345 100644 --- a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs +++ b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs @@ -22,5 +22,8 @@ namespace osu.Game.Rulesets.Osu SpinnerBody, CursorSmoke, ApproachCircle, + HitMarkerLeft, + HitMarkerRight, + AimMarker } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs new file mode 100644 index 0000000000..f4c11e6c8a --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs @@ -0,0 +1,71 @@ +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Skinning.Default +{ + public partial class DefaultHitMarker : CompositeDrawable + { + public DefaultHitMarker(OsuAction? action) + { + var (colour, length, hasBorder) = getConfig(action); + + if (hasBorder) + { + InternalChildren = new Drawable[] + { + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(3, length), + Rotation = 45, + Colour = Colour4.Black.Opacity(0.5F) + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(3, length), + Rotation = 135, + Colour = Colour4.Black.Opacity(0.5F) + } + }; + } + + AddRangeInternal(new Drawable[] + { + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(1, length), + Rotation = 45, + Colour = colour + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(1, length), + Rotation = 135, + Colour = colour + } + }); + } + + private (Colour4 colour, float length, bool hasBorder) getConfig(OsuAction? action) + { + switch (action) + { + case OsuAction.LeftButton: + return (Colour4.Orange, 20, true); + case OsuAction.RightButton: + return (Colour4.LightGreen, 20, true); + default: + return (Colour4.Gray.Opacity(0.3F), 8, false); + } + } + } +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs b/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs new file mode 100644 index 0000000000..d86e3d4f79 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs @@ -0,0 +1,33 @@ +using osu.Framework.Allocation; +using osu.Framework.Graphics.Sprites; +using osu.Game.Skinning; + +namespace osu.Game.Rulesets.Osu.Skinning.Default +{ + public partial class HitMarker : Sprite + { + private readonly OsuAction? action; + + public HitMarker(OsuAction? action = null) + { + this.action = action; + } + + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + switch (action) + { + case OsuAction.LeftButton: + Texture = skin.GetTexture(@"hitmarker-left"); + break; + case OsuAction.RightButton: + Texture = skin.GetTexture(@"hitmarker-right"); + break; + default: + Texture = skin.GetTexture(@"aimmarker"); + break; + } + } + } +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index d2ebc68c52..84c055fe5e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Skinning; using osuTK; @@ -168,6 +169,24 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy return null; + case OsuSkinComponents.HitMarkerLeft: + if (GetTexture(@"hitmarker-left") != null) + return new HitMarker(OsuAction.LeftButton); + + return null; + + case OsuSkinComponents.HitMarkerRight: + if (GetTexture(@"hitmarker-right") != null) + return new HitMarker(OsuAction.RightButton); + + return null; + + case OsuSkinComponents.AimMarker: + if (GetTexture(@"aimmarker") != null) + return new HitMarker(); + + return null; + default: throw new UnsupportedSkinComponentException(lookup); } diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs index 24f9217a5f..5c864fb6c2 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs @@ -10,5 +10,6 @@ namespace osu.Game.Rulesets.Osu.Skinning SliderBall, SpinnerBackground, StarBreakAdditive, + ReplayAimLine, } } diff --git a/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs b/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs index a9fc42e596..01877a9185 100644 --- a/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs @@ -10,111 +10,128 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Skinning; +using osu.Game.Rulesets.Osu.Skinning.Default; +using osu.Game.Rulesets.UI; +using osu.Game.Skinning; using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.UI { public partial class HitMarkerContainer : Container, IRequireHighFrequencyMousePosition, IKeyBindingHandler { private Vector2 lastMousePosition; + private Vector2? lastlastMousePosition; + private double? timePreempt; + private double TimePreempt + { + get => timePreempt ?? default_time_preempt; + set => timePreempt = value; + } public Bindable HitMarkerEnabled = new BindableBool(); public Bindable AimMarkersEnabled = new BindableBool(); + public Bindable AimLinesEnabled = new BindableBool(); + + private const double default_time_preempt = 1000; + + private readonly HitObjectContainer hitObjectContainer; public override bool ReceivePositionalInputAt(Vector2 _) => true; + public HitMarkerContainer(HitObjectContainer hitObjectContainer) + { + this.hitObjectContainer = hitObjectContainer; + } + public bool OnPressed(KeyBindingPressEvent e) { if (HitMarkerEnabled.Value && (e.Action == OsuAction.LeftButton || e.Action == OsuAction.RightButton)) { + updateTimePreempt(); AddMarker(e.Action); } return false; } - public void OnReleased(KeyBindingReleaseEvent e) { } + public void OnReleased(KeyBindingReleaseEvent e) + { + } protected override bool OnMouseMove(MouseMoveEvent e) { + lastlastMousePosition = lastMousePosition; lastMousePosition = e.MousePosition; if (AimMarkersEnabled.Value) { + updateTimePreempt(); AddMarker(null); } + if (AimLinesEnabled.Value && lastlastMousePosition != null && lastlastMousePosition != lastMousePosition) + { + if (!AimMarkersEnabled.Value) + updateTimePreempt(); + Add(new AimLineDrawable((Vector2)lastlastMousePosition, lastMousePosition, TimePreempt)); + } + return base.OnMouseMove(e); } private void AddMarker(OsuAction? action) { - Add(new HitMarkerDrawable(action) { Position = lastMousePosition }); + var component = OsuSkinComponents.AimMarker; + switch(action) + { + case OsuAction.LeftButton: + component = OsuSkinComponents.HitMarkerLeft; + break; + case OsuAction.RightButton: + component = OsuSkinComponents.HitMarkerRight; + break; + } + + Add(new HitMarkerDrawable(action, component, TimePreempt) + { + Position = lastMousePosition, + Origin = Anchor.Centre, + Depth = action == null ? float.MaxValue : float.MinValue + }); } - private partial class HitMarkerDrawable : CompositeDrawable + private void updateTimePreempt() { - private const double lifetime_duration = 1000; - private const double fade_out_time = 400; + var hitObject = getHitObject(); + if (hitObject == null) + return; + + TimePreempt = hitObject.TimePreempt; + } + + private OsuHitObject? getHitObject() + { + foreach (var dho in hitObjectContainer.Objects) + return (dho as DrawableOsuHitObject)?.HitObject; + return null; + } + + private partial class HitMarkerDrawable : SkinnableDrawable + { + private readonly double lifetimeDuration; + private readonly double fadeOutTime; public override bool RemoveWhenNotAlive => true; - public HitMarkerDrawable(OsuAction? action) + public HitMarkerDrawable(OsuAction? action, OsuSkinComponents componenet, double timePreempt) + : base(new OsuSkinComponentLookup(componenet), _ => new DefaultHitMarker(action)) { - var colour = Colour4.Gray.Opacity(0.5F); - var length = 8; - var depth = float.MaxValue; - switch (action) - { - case OsuAction.LeftButton: - colour = Colour4.Orange; - length = 20; - depth = float.MinValue; - break; - case OsuAction.RightButton: - colour = Colour4.LightGreen; - length = 20; - depth = float.MinValue; - break; - } - - this.Depth = depth; - - InternalChildren = new Drawable[] - { - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(3, length), - Rotation = 45, - Colour = Colour4.Black.Opacity(0.5F) - }, - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(3, length), - Rotation = 135, - Colour = Colour4.Black.Opacity(0.5F) - }, - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(1, length), - Rotation = 45, - Colour = colour - }, - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(1, length), - Rotation = 135, - Colour = colour - } - }; + fadeOutTime = timePreempt / 2; + lifetimeDuration = timePreempt + fadeOutTime; } protected override void LoadComplete() @@ -122,12 +139,57 @@ namespace osu.Game.Rulesets.Osu.UI base.LoadComplete(); LifetimeStart = Time.Current; - LifetimeEnd = LifetimeStart + lifetime_duration; + LifetimeEnd = LifetimeStart + lifetimeDuration; Scheduler.AddDelayed(() => { - this.FadeOut(fade_out_time); - }, lifetime_duration - fade_out_time); + this.FadeOut(fadeOutTime); + }, lifetimeDuration - fadeOutTime); + } + } + + private partial class AimLineDrawable : CompositeDrawable + { + private readonly double lifetimeDuration; + private readonly double fadeOutTime; + + public override bool RemoveWhenNotAlive => true; + + public AimLineDrawable(Vector2 fromP, Vector2 toP, double timePreempt) + { + fadeOutTime = timePreempt / 2; + lifetimeDuration = timePreempt + fadeOutTime; + + float distance = Vector2.Distance(fromP, toP); + Vector2 direction = (toP - fromP); + InternalChild = new Box + { + Position = fromP + (direction / 2), + Size = new Vector2(distance, 1), + Rotation = (float)(Math.Atan(direction.Y / direction.X) * (180 / Math.PI)), + Origin = Anchor.Centre + }; + } + + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + var color = skin.GetConfig(OsuSkinColour.ReplayAimLine)?.Value ?? Color4.White; + color.A = 127; + InternalChild.Colour = color; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + LifetimeStart = Time.Current; + LifetimeEnd = LifetimeStart + lifetimeDuration; + + Scheduler.AddDelayed(() => + { + this.FadeOut(fadeOutTime); + }, lifetimeDuration - fadeOutTime); } } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs index 4694c1a560..050675d970 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs @@ -4,7 +4,9 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Logging; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; +using osu.Game.Graphics.Containers; using osu.Game.Localisation; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; @@ -15,14 +17,13 @@ namespace osu.Game.Rulesets.Osu.UI { public partial class OsuAnalysisSettings : AnalysisSettings { - private static readonly Logger logger = Logger.GetLogger("osu-analysis-settings"); - protected new DrawableOsuRuleset drawableRuleset => (DrawableOsuRuleset)base.drawableRuleset; private readonly PlayerCheckbox hitMarkerToggle; private readonly PlayerCheckbox aimMarkerToggle; private readonly PlayerCheckbox hideCursorToggle; - private readonly PlayerCheckbox? hiddenToggle; + private readonly PlayerCheckbox aimLinesToggle; + private readonly FillFlowContainer modTogglesContainer; public OsuAnalysisSettings(DrawableRuleset drawableRuleset) : base(drawableRuleset) @@ -31,18 +32,36 @@ namespace osu.Game.Rulesets.Osu.UI { hitMarkerToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HitMarkers }, aimMarkerToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.AimMarkers }, - hideCursorToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HideCursor } + aimLinesToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.AimLines }, + hideCursorToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HideCursor }, + new OsuScrollContainer(Direction.Horizontal) + { + RelativeSizeAxes = Axes.X, + Height = ModSwitchSmall.DEFAULT_SIZE, + ScrollbarOverlapsContent = false, + Child = modTogglesContainer = new FillFlowContainer + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Direction = FillDirection.Horizontal, + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X + } + } }; - - // hidden stuff is just here for testing at the moment; to create the mod disabling functionality foreach (var mod in drawableRuleset.Mods) { - if (mod is OsuModHidden) + if (mod is IToggleableVisibility toggleableMod) { - logger.Add("Hidden is enabled", LogLevel.Debug); - Add(hiddenToggle = new PlayerCheckbox { LabelText = "Disable hidden" }); - break; + var modSwitch = new SelectableModSwitchSmall(mod) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Active = { Value = true } + }; + modSwitch.Active.BindValueChanged((v) => onModToggle(toggleableMod, v)); + modTogglesContainer.Add(modSwitch); } } } @@ -51,8 +70,8 @@ namespace osu.Game.Rulesets.Osu.UI { drawableRuleset.Playfield.MarkersContainer.HitMarkerEnabled.BindTo(hitMarkerToggle.Current); drawableRuleset.Playfield.MarkersContainer.AimMarkersEnabled.BindTo(aimMarkerToggle.Current); + drawableRuleset.Playfield.MarkersContainer.AimLinesEnabled.BindTo(aimLinesToggle.Current); hideCursorToggle.Current.BindValueChanged(onCursorToggle); - hiddenToggle?.Current.BindValueChanged(onHiddenToggle); } private void onCursorToggle(ValueChangedEvent hide) @@ -60,21 +79,34 @@ namespace osu.Game.Rulesets.Osu.UI // this only hides half the cursor if (hide.NewValue) { - drawableRuleset.Playfield.Cursor.Hide(); + drawableRuleset.Playfield.Cursor.FadeOut(); } else { - drawableRuleset.Playfield.Cursor.Show(); + drawableRuleset.Playfield.Cursor.FadeIn(); } } - private void onHiddenToggle(ValueChangedEvent off) + private void onModToggle(IToggleableVisibility mod, ValueChangedEvent toggled) { - if (off.NewValue) + if (toggled.NewValue) { - logger.Add("Hidden off", LogLevel.Debug); + mod.ToggleOnVisibility(drawableRuleset.Playfield); } else { - logger.Add("Hidden on", LogLevel.Debug); + mod.ToggleOffVisibility(drawableRuleset.Playfield); + } + } + + private partial class SelectableModSwitchSmall : ModSwitchSmall + { + public SelectableModSwitchSmall(IMod mod) + : base(mod) + {} + + protected override bool OnClick(ClickEvent e) + { + Active.Value = !Active.Value; + return true; } } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index d7e1732175..3cb3c50ef7 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Osu.UI HitObjectContainer, judgementAboveHitObjectLayer = new Container { RelativeSizeAxes = Axes.Both }, approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both }, - MarkersContainer = new HitMarkerContainer { RelativeSizeAxes = Axes.Both } + MarkersContainer = new HitMarkerContainer(HitObjectContainer) { RelativeSizeAxes = Axes.Both } }; HitPolicy = new StartTimeOrderedHitPolicy(); diff --git a/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs b/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs index f829fb4ac1..017cc9bf82 100644 --- a/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs +++ b/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs @@ -34,6 +34,11 @@ namespace osu.Game.Localisation /// public static LocalisableString HideCursor => new TranslatableString(getKey(@"hide_cursor"), @"Hide cursor"); + /// + /// "Aim lines" + /// + public static LocalisableString AimLines => new TranslatableString(getKey(@"aim_lines"), @"Aim lines"); + /// /// "Seek backward {0} seconds" /// diff --git a/osu.Game/Rulesets/Mods/IToggleableVisibility.cs b/osu.Game/Rulesets/Mods/IToggleableVisibility.cs new file mode 100644 index 0000000000..5d90c24b9e --- /dev/null +++ b/osu.Game/Rulesets/Mods/IToggleableVisibility.cs @@ -0,0 +1,11 @@ +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Mods +{ + public interface IToggleableVisibility + { + public void ToggleOffVisibility(Playfield playfield); + + public void ToggleOnVisibility(Playfield playfield); + } +} \ No newline at end of file