Merge branch 'master' into update-check-improvements

This commit is contained in:
Bartłomiej Dach
2020-10-08 21:32:26 +02:00
committed by GitHub
78 changed files with 1155 additions and 528 deletions

View File

@ -145,7 +145,7 @@ namespace osu.Game.Rulesets.Catch
public override ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => new CatchLegacySkinTransformer(source); public override ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => new CatchLegacySkinTransformer(source);
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new CatchPerformanceCalculator(this, beatmap, score); public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => new CatchPerformanceCalculator(this, attributes, score);
public int LegacyID => 2; public int LegacyID => 2;

View File

@ -5,7 +5,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Extensions; using osu.Framework.Extensions;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
@ -25,8 +24,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty
private int tinyTicksMissed; private int tinyTicksMissed;
private int misses; private int misses;
public CatchPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, ScoreInfo score) public CatchPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score)
: base(ruleset, beatmap, score) : base(ruleset, attributes, score)
{ {
} }

View File

@ -5,7 +5,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Extensions; using osu.Framework.Extensions;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
@ -29,8 +28,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty
private int countMeh; private int countMeh;
private int countMiss; private int countMiss;
public ManiaPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, ScoreInfo score) public ManiaPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score)
: base(ruleset, beatmap, score) : base(ruleset, attributes, score)
{ {
} }

View File

@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Mania
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap, this); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap, this);
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new ManiaPerformanceCalculator(this, beatmap, score); public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => new ManiaPerformanceCalculator(this, attributes, score);
public const string SHORT_NAME = "mania"; public const string SHORT_NAME = "mania";

View File

@ -29,12 +29,12 @@ namespace osu.Game.Rulesets.Mania
new SettingsEnumDropdown<ManiaScrollingDirection> new SettingsEnumDropdown<ManiaScrollingDirection>
{ {
LabelText = "Scrolling direction", LabelText = "Scrolling direction",
Bindable = config.GetBindable<ManiaScrollingDirection>(ManiaRulesetSetting.ScrollDirection) Current = config.GetBindable<ManiaScrollingDirection>(ManiaRulesetSetting.ScrollDirection)
}, },
new SettingsSlider<double, TimeSlider> new SettingsSlider<double, TimeSlider>
{ {
LabelText = "Scroll speed", LabelText = "Scroll speed",
Bindable = config.GetBindable<double>(ManiaRulesetSetting.ScrollTime), Current = config.GetBindable<double>(ManiaRulesetSetting.ScrollTime),
KeyboardStep = 5 KeyboardStep = 5
}, },
}; };

View File

@ -11,5 +11,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
public double SpeedStrain; public double SpeedStrain;
public double ApproachRate; public double ApproachRate;
public double OverallDifficulty; public double OverallDifficulty;
public int HitCircleCount;
} }
} }

View File

@ -47,6 +47,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
// Add the ticks + tail of the slider. 1 is subtracted because the head circle would be counted twice (once for the slider itself in the line above) // Add the ticks + tail of the slider. 1 is subtracted because the head circle would be counted twice (once for the slider itself in the line above)
maxCombo += beatmap.HitObjects.OfType<Slider>().Sum(s => s.NestedHitObjects.Count - 1); maxCombo += beatmap.HitObjects.OfType<Slider>().Sum(s => s.NestedHitObjects.Count - 1);
int hitCirclesCount = beatmap.HitObjects.Count(h => h is HitCircle);
return new OsuDifficultyAttributes return new OsuDifficultyAttributes
{ {
StarRating = starRating, StarRating = starRating,
@ -56,6 +58,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5, ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5,
OverallDifficulty = (80 - hitWindowGreat) / 6, OverallDifficulty = (80 - hitWindowGreat) / 6,
MaxCombo = maxCombo, MaxCombo = maxCombo,
HitCircleCount = hitCirclesCount,
Skills = skills Skills = skills
}; };
} }

View File

@ -5,11 +5,9 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Extensions; using osu.Framework.Extensions;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring; using osu.Game.Scoring;
@ -19,9 +17,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{ {
public new OsuDifficultyAttributes Attributes => (OsuDifficultyAttributes)base.Attributes; public new OsuDifficultyAttributes Attributes => (OsuDifficultyAttributes)base.Attributes;
private readonly int countHitCircles;
private readonly int beatmapMaxCombo;
private Mod[] mods; private Mod[] mods;
private double accuracy; private double accuracy;
@ -31,14 +26,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
private int countMeh; private int countMeh;
private int countMiss; private int countMiss;
public OsuPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, ScoreInfo score) public OsuPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score)
: base(ruleset, beatmap, score) : base(ruleset, attributes, score)
{ {
countHitCircles = Beatmap.HitObjects.Count(h => h is HitCircle);
beatmapMaxCombo = Beatmap.HitObjects.Count;
// Add the ticks + tail of the slider. 1 is subtracted because the "headcircle" would be counted twice (once for the slider itself in the line above)
beatmapMaxCombo += Beatmap.HitObjects.OfType<Slider>().Sum(s => s.NestedHitObjects.Count - 1);
} }
public override double Calculate(Dictionary<string, double> categoryRatings = null) public override double Calculate(Dictionary<string, double> categoryRatings = null)
@ -81,7 +71,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
categoryRatings.Add("Accuracy", accuracyValue); categoryRatings.Add("Accuracy", accuracyValue);
categoryRatings.Add("OD", Attributes.OverallDifficulty); categoryRatings.Add("OD", Attributes.OverallDifficulty);
categoryRatings.Add("AR", Attributes.ApproachRate); categoryRatings.Add("AR", Attributes.ApproachRate);
categoryRatings.Add("Max Combo", beatmapMaxCombo); categoryRatings.Add("Max Combo", Attributes.MaxCombo);
} }
return totalValue; return totalValue;
@ -106,8 +96,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
aimValue *= Math.Pow(0.97, countMiss); aimValue *= Math.Pow(0.97, countMiss);
// Combo scaling // Combo scaling
if (beatmapMaxCombo > 0) if (Attributes.MaxCombo > 0)
aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(beatmapMaxCombo, 0.8), 1.0); aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
double approachRateFactor = 1.0; double approachRateFactor = 1.0;
@ -154,8 +144,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
speedValue *= Math.Pow(0.97, countMiss); speedValue *= Math.Pow(0.97, countMiss);
// Combo scaling // Combo scaling
if (beatmapMaxCombo > 0) if (Attributes.MaxCombo > 0)
speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(beatmapMaxCombo, 0.8), 1.0); speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
double approachRateFactor = 1.0; double approachRateFactor = 1.0;
if (Attributes.ApproachRate > 10.33) if (Attributes.ApproachRate > 10.33)
@ -178,7 +168,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{ {
// This percentage only considers HitCircles of any value - in this part of the calculation we focus on hitting the timing hit window // This percentage only considers HitCircles of any value - in this part of the calculation we focus on hitting the timing hit window
double betterAccuracyPercentage; double betterAccuracyPercentage;
int amountHitObjectsWithAccuracy = countHitCircles; int amountHitObjectsWithAccuracy = Attributes.HitCircleCount;
if (amountHitObjectsWithAccuracy > 0) if (amountHitObjectsWithAccuracy > 0)
betterAccuracyPercentage = ((countGreat - (totalHits - amountHitObjectsWithAccuracy)) * 6 + countOk * 2 + countMeh) / (double)(amountHitObjectsWithAccuracy * 6); betterAccuracyPercentage = ((countGreat - (totalHits - amountHitObjectsWithAccuracy)) * 6 + countOk * 2 + countMeh) / (double)(amountHitObjectsWithAccuracy * 6);

View File

@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Origin = Anchor.Centre; Origin = Anchor.Centre;
Masking = true; Masking = true;
BorderThickness = 10; BorderThickness = 9; // roughly matches slider borders and makes stacked circles distinctly visible from each other.
BorderColour = Color4.White; BorderColour = Color4.White;
Child = new Box Child = new Box

View File

@ -171,7 +171,7 @@ namespace osu.Game.Rulesets.Osu
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuDifficultyCalculator(this, beatmap); public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuDifficultyCalculator(this, beatmap);
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new OsuPerformanceCalculator(this, beatmap, score); public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => new OsuPerformanceCalculator(this, attributes, score);
public override HitObjectComposer CreateHitObjectComposer() => new OsuHitObjectComposer(this); public override HitObjectComposer CreateHitObjectComposer() => new OsuHitObjectComposer(this);

View File

@ -49,6 +49,23 @@ namespace osu.Game.Rulesets.Osu.Skinning
{ {
OsuHitObject osuObject = (OsuHitObject)drawableObject.HitObject; OsuHitObject osuObject = (OsuHitObject)drawableObject.HitObject;
bool allowFallback = false;
// attempt lookup using priority specification
Texture baseTexture = getTextureWithFallback(string.Empty);
// if the base texture was not found without a fallback, switch on fallback mode and re-perform the lookup.
if (baseTexture == null)
{
allowFallback = true;
baseTexture = getTextureWithFallback(string.Empty);
}
// at this point, any further texture fetches should be correctly using the priority source if the base texture was retrieved using it.
// the flow above handles the case where a sliderendcircle.png is retrieved from the skin, but sliderendcircleoverlay.png doesn't exist.
// expected behaviour in this scenario is not showing the overlay, rather than using hitcircleoverlay.png (potentially from the default/fall-through skin).
Texture overlayTexture = getTextureWithFallback("overlay");
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
circleSprites = new Container<Sprite> circleSprites = new Container<Sprite>
@ -60,13 +77,13 @@ namespace osu.Game.Rulesets.Osu.Skinning
{ {
hitCircleSprite = new Sprite hitCircleSprite = new Sprite
{ {
Texture = getTextureWithFallback(string.Empty), Texture = baseTexture,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
}, },
hitCircleOverlay = new Sprite hitCircleOverlay = new Sprite
{ {
Texture = getTextureWithFallback("overlay"), Texture = overlayTexture,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
} }
@ -101,8 +118,13 @@ namespace osu.Game.Rulesets.Osu.Skinning
Texture tex = null; Texture tex = null;
if (!string.IsNullOrEmpty(priorityLookup)) if (!string.IsNullOrEmpty(priorityLookup))
{
tex = skin.GetTexture($"{priorityLookup}{name}"); tex = skin.GetTexture($"{priorityLookup}{name}");
if (!allowFallback)
return tex;
}
return tex ?? skin.GetTexture($"hitcircle{name}"); return tex ?? skin.GetTexture($"hitcircle{name}");
} }
} }

View File

@ -27,17 +27,17 @@ namespace osu.Game.Rulesets.Osu.UI
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Snaking in sliders", LabelText = "Snaking in sliders",
Bindable = config.GetBindable<bool>(OsuRulesetSetting.SnakingInSliders) Current = config.GetBindable<bool>(OsuRulesetSetting.SnakingInSliders)
}, },
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Snaking out sliders", LabelText = "Snaking out sliders",
Bindable = config.GetBindable<bool>(OsuRulesetSetting.SnakingOutSliders) Current = config.GetBindable<bool>(OsuRulesetSetting.SnakingOutSliders)
}, },
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Cursor trail", LabelText = "Cursor trail",
Bindable = config.GetBindable<bool>(OsuRulesetSetting.ShowCursorTrail) Current = config.GetBindable<bool>(OsuRulesetSetting.ShowCursorTrail)
}, },
}; };
} }

View File

@ -5,7 +5,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Extensions; using osu.Framework.Extensions;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
@ -24,8 +23,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
private int countMeh; private int countMeh;
private int countMiss; private int countMiss;
public TaikoPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, ScoreInfo score) public TaikoPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score)
: base(ruleset, beatmap, score) : base(ruleset, attributes, score)
{ {
} }

View File

@ -7,6 +7,7 @@ using osu.Framework.Audio.Sample;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.UI; using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Skinning; using osu.Game.Skinning;
@ -14,13 +15,29 @@ namespace osu.Game.Rulesets.Taiko.Skinning
{ {
public class TaikoLegacySkinTransformer : LegacySkinTransformer public class TaikoLegacySkinTransformer : LegacySkinTransformer
{ {
private Lazy<bool> hasExplosion;
public TaikoLegacySkinTransformer(ISkinSource source) public TaikoLegacySkinTransformer(ISkinSource source)
: base(source) : base(source)
{ {
Source.SourceChanged += sourceChanged;
sourceChanged();
}
private void sourceChanged()
{
hasExplosion = new Lazy<bool>(() => Source.GetTexture(getHitName(TaikoSkinComponents.TaikoExplosionGreat)) != null);
} }
public override Drawable GetDrawableComponent(ISkinComponent component) public override Drawable GetDrawableComponent(ISkinComponent component)
{ {
if (component is GameplaySkinComponent<HitResult>)
{
// if a taiko skin is providing explosion sprites, hide the judgements completely
if (hasExplosion.Value)
return Drawable.Empty();
}
if (!(component is TaikoSkinComponent taikoComponent)) if (!(component is TaikoSkinComponent taikoComponent))
return null; return null;
@ -87,10 +104,13 @@ namespace osu.Game.Rulesets.Taiko.Skinning
var hitName = getHitName(taikoComponent.Component); var hitName = getHitName(taikoComponent.Component);
var hitSprite = this.GetAnimation(hitName, true, false); var hitSprite = this.GetAnimation(hitName, true, false);
var strongHitSprite = this.GetAnimation($"{hitName}k", true, false);
if (hitSprite != null) if (hitSprite != null)
{
var strongHitSprite = this.GetAnimation($"{hitName}k", true, false);
return new LegacyHitExplosion(hitSprite, strongHitSprite); return new LegacyHitExplosion(hitSprite, strongHitSprite);
}
return null; return null;

View File

@ -153,7 +153,7 @@ namespace osu.Game.Rulesets.Taiko
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoDifficultyCalculator(this, beatmap); public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoDifficultyCalculator(this, beatmap);
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new TaikoPerformanceCalculator(this, beatmap, score); public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => new TaikoPerformanceCalculator(this, attributes, score);
public int LegacyID => 1; public int LegacyID => 1;

View File

@ -44,7 +44,7 @@ namespace osu.Game.Tests.Editing
{ {
HitObjects = HitObjects =
{ {
new HitCircle { StartTime = 1000 } new HitCircle { StartTime = 1000, NewCombo = true }
} }
}; };
@ -56,7 +56,7 @@ namespace osu.Game.Tests.Editing
{ {
current.AddRange(new[] current.AddRange(new[]
{ {
new HitCircle { StartTime = 1000 }, new HitCircle { StartTime = 1000, NewCombo = true },
new HitCircle { StartTime = 3000 }, new HitCircle { StartTime = 3000 },
}); });
@ -78,7 +78,7 @@ namespace osu.Game.Tests.Editing
{ {
current.AddRange(new[] current.AddRange(new[]
{ {
new HitCircle { StartTime = 1000 }, new HitCircle { StartTime = 1000, NewCombo = true },
new HitCircle { StartTime = 2000 }, new HitCircle { StartTime = 2000 },
new HitCircle { StartTime = 3000 }, new HitCircle { StartTime = 3000 },
}); });
@ -100,7 +100,7 @@ namespace osu.Game.Tests.Editing
{ {
current.AddRange(new[] current.AddRange(new[]
{ {
new HitCircle { StartTime = 1000 }, new HitCircle { StartTime = 1000, NewCombo = true },
new HitCircle { StartTime = 2000 }, new HitCircle { StartTime = 2000 },
new HitCircle { StartTime = 3000 }, new HitCircle { StartTime = 3000 },
}); });
@ -109,7 +109,7 @@ namespace osu.Game.Tests.Editing
{ {
HitObjects = HitObjects =
{ {
new HitCircle { StartTime = 500 }, new HitCircle { StartTime = 500, NewCombo = true },
(OsuHitObject)current.HitObjects[1], (OsuHitObject)current.HitObjects[1],
(OsuHitObject)current.HitObjects[2], (OsuHitObject)current.HitObjects[2],
} }
@ -123,7 +123,7 @@ namespace osu.Game.Tests.Editing
{ {
current.AddRange(new[] current.AddRange(new[]
{ {
new HitCircle { StartTime = 1000 }, new HitCircle { StartTime = 1000, NewCombo = true },
new HitCircle { StartTime = 2000 }, new HitCircle { StartTime = 2000 },
new HitCircle { StartTime = 3000 }, new HitCircle { StartTime = 3000 },
}); });
@ -146,7 +146,7 @@ namespace osu.Game.Tests.Editing
{ {
current.AddRange(new OsuHitObject[] current.AddRange(new OsuHitObject[]
{ {
new HitCircle { StartTime = 1000 }, new HitCircle { StartTime = 1000, NewCombo = true },
new Slider new Slider
{ {
StartTime = 2000, StartTime = 2000,
@ -188,7 +188,7 @@ namespace osu.Game.Tests.Editing
{ {
current.AddRange(new[] current.AddRange(new[]
{ {
new HitCircle { StartTime = 1000 }, new HitCircle { StartTime = 1000, NewCombo = true },
new HitCircle { StartTime = 2000 }, new HitCircle { StartTime = 2000 },
new HitCircle { StartTime = 3000 }, new HitCircle { StartTime = 3000 },
}); });
@ -197,7 +197,7 @@ namespace osu.Game.Tests.Editing
{ {
HitObjects = HitObjects =
{ {
new HitCircle { StartTime = 500 }, new HitCircle { StartTime = 500, NewCombo = true },
(OsuHitObject)current.HitObjects[0], (OsuHitObject)current.HitObjects[0],
new HitCircle { StartTime = 1500 }, new HitCircle { StartTime = 1500 },
(OsuHitObject)current.HitObjects[1], (OsuHitObject)current.HitObjects[1],
@ -216,7 +216,7 @@ namespace osu.Game.Tests.Editing
{ {
current.AddRange(new[] current.AddRange(new[]
{ {
new HitCircle { StartTime = 500 }, new HitCircle { StartTime = 500, NewCombo = true },
new HitCircle { StartTime = 1000 }, new HitCircle { StartTime = 1000 },
new HitCircle { StartTime = 1500 }, new HitCircle { StartTime = 1500 },
new HitCircle { StartTime = 2000 }, new HitCircle { StartTime = 2000 },
@ -226,6 +226,9 @@ namespace osu.Game.Tests.Editing
new HitCircle { StartTime = 3500 }, new HitCircle { StartTime = 3500 },
}); });
var patchedFirst = (HitCircle)current.HitObjects[1];
patchedFirst.NewCombo = true;
var patch = new OsuBeatmap var patch = new OsuBeatmap
{ {
HitObjects = HitObjects =
@ -244,7 +247,7 @@ namespace osu.Game.Tests.Editing
{ {
current.AddRange(new[] current.AddRange(new[]
{ {
new HitCircle { StartTime = 500 }, new HitCircle { StartTime = 500, NewCombo = true },
new HitCircle { StartTime = 1000 }, new HitCircle { StartTime = 1000 },
new HitCircle { StartTime = 1500 }, new HitCircle { StartTime = 1500 },
new HitCircle { StartTime = 2000 }, new HitCircle { StartTime = 2000 },
@ -277,7 +280,7 @@ namespace osu.Game.Tests.Editing
{ {
current.AddRange(new[] current.AddRange(new[]
{ {
new HitCircle { StartTime = 500 }, new HitCircle { StartTime = 500, NewCombo = true },
new HitCircle { StartTime = 1000 }, new HitCircle { StartTime = 1000 },
new HitCircle { StartTime = 1500 }, new HitCircle { StartTime = 1500 },
new HitCircle { StartTime = 2000 }, new HitCircle { StartTime = 2000 },
@ -291,7 +294,7 @@ namespace osu.Game.Tests.Editing
{ {
HitObjects = HitObjects =
{ {
new HitCircle { StartTime = 750 }, new HitCircle { StartTime = 750, NewCombo = true },
(OsuHitObject)current.HitObjects[1], (OsuHitObject)current.HitObjects[1],
(OsuHitObject)current.HitObjects[4], (OsuHitObject)current.HitObjects[4],
(OsuHitObject)current.HitObjects[5], (OsuHitObject)current.HitObjects[5],
@ -309,20 +312,20 @@ namespace osu.Game.Tests.Editing
{ {
current.AddRange(new[] current.AddRange(new[]
{ {
new HitCircle { StartTime = 500, Position = new Vector2(50) }, new HitCircle { StartTime = 500, Position = new Vector2(50), NewCombo = true },
new HitCircle { StartTime = 500, Position = new Vector2(100) }, new HitCircle { StartTime = 500, Position = new Vector2(100), NewCombo = true },
new HitCircle { StartTime = 500, Position = new Vector2(150) }, new HitCircle { StartTime = 500, Position = new Vector2(150), NewCombo = true },
new HitCircle { StartTime = 500, Position = new Vector2(200) }, new HitCircle { StartTime = 500, Position = new Vector2(200), NewCombo = true },
}); });
var patch = new OsuBeatmap var patch = new OsuBeatmap
{ {
HitObjects = HitObjects =
{ {
new HitCircle { StartTime = 500, Position = new Vector2(150) }, new HitCircle { StartTime = 500, Position = new Vector2(150), NewCombo = true },
new HitCircle { StartTime = 500, Position = new Vector2(100) }, new HitCircle { StartTime = 500, Position = new Vector2(100), NewCombo = true },
new HitCircle { StartTime = 500, Position = new Vector2(50) }, new HitCircle { StartTime = 500, Position = new Vector2(50), NewCombo = true },
new HitCircle { StartTime = 500, Position = new Vector2(200) }, new HitCircle { StartTime = 500, Position = new Vector2(200), NewCombo = true },
} }
}; };

View File

@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.Editing
using (var zip = ZipArchive.Open(temp)) using (var zip = ZipArchive.Open(temp))
zip.WriteToDirectory(extractedFolder); zip.WriteToDirectory(extractedFolder);
bool success = setup.ChangeAudioTrack(Path.Combine(extractedFolder, "03. Renatus - Soleily 192kbps.mp3")); bool success = setup.ChildrenOfType<ResourcesSection>().First().ChangeAudioTrack(Path.Combine(extractedFolder, "03. Renatus - Soleily 192kbps.mp3"));
File.Delete(temp); File.Delete(temp);
Directory.Delete(extractedFolder, true); Directory.Delete(extractedFolder, true);

View File

@ -3,6 +3,7 @@
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Rulesets; using osu.Game.Rulesets;
@ -23,33 +24,41 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test] [Test]
public void TestGameplayOverlayActivation() public void TestGameplayOverlayActivation()
{ {
AddAssert("local user playing", () => Player.LocalUserPlaying.Value);
AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled); AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
} }
[Test] [Test]
public void TestGameplayOverlayActivationPaused() public void TestGameplayOverlayActivationPaused()
{ {
AddAssert("local user playing", () => Player.LocalUserPlaying.Value);
AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled); AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
AddStep("pause gameplay", () => Player.Pause()); AddStep("pause gameplay", () => Player.Pause());
AddAssert("local user not playing", () => !Player.LocalUserPlaying.Value);
AddUntilStep("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered); AddUntilStep("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered);
} }
[Test] [Test]
public void TestGameplayOverlayActivationReplayLoaded() public void TestGameplayOverlayActivationReplayLoaded()
{ {
AddAssert("local user playing", () => Player.LocalUserPlaying.Value);
AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled); AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
AddStep("load a replay", () => Player.DrawableRuleset.HasReplayLoaded.Value = true); AddStep("load a replay", () => Player.DrawableRuleset.HasReplayLoaded.Value = true);
AddAssert("local user not playing", () => !Player.LocalUserPlaying.Value);
AddAssert("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered); AddAssert("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered);
} }
[Test] [Test]
public void TestGameplayOverlayActivationBreaks() public void TestGameplayOverlayActivationBreaks()
{ {
AddAssert("local user playing", () => Player.LocalUserPlaying.Value);
AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled); AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
AddStep("seek to break", () => Player.GameplayClockContainer.Seek(Beatmap.Value.Beatmap.Breaks.First().StartTime)); AddStep("seek to break", () => Player.GameplayClockContainer.Seek(Beatmap.Value.Beatmap.Breaks.First().StartTime));
AddUntilStep("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered); AddUntilStep("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered);
AddAssert("local user not playing", () => !Player.LocalUserPlaying.Value);
AddStep("seek to break end", () => Player.GameplayClockContainer.Seek(Beatmap.Value.Beatmap.Breaks.First().EndTime)); AddStep("seek to break end", () => Player.GameplayClockContainer.Seek(Beatmap.Value.Beatmap.Breaks.First().EndTime));
AddUntilStep("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled); AddUntilStep("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
AddAssert("local user playing", () => Player.LocalUserPlaying.Value);
} }
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new OverlayTestPlayer(); protected override TestPlayer CreatePlayer(Ruleset ruleset) => new OverlayTestPlayer();
@ -57,6 +66,7 @@ namespace osu.Game.Tests.Visual.Gameplay
protected class OverlayTestPlayer : TestPlayer protected class OverlayTestPlayer : TestPlayer
{ {
public new OverlayActivation OverlayActivationMode => base.OverlayActivationMode.Value; public new OverlayActivation OverlayActivationMode => base.OverlayActivationMode.Value;
public new Bindable<bool> LocalUserPlaying => base.LocalUserPlaying;
} }
} }
} }

View File

@ -0,0 +1,46 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.UserInterfaceV2;
namespace osu.Game.Tests.Visual.UserInterface
{
public class TestSceneLabelledSliderBar : OsuTestScene
{
[TestCase(false)]
[TestCase(true)]
public void TestSliderBar(bool hasDescription) => createSliderBar(hasDescription);
private void createSliderBar(bool hasDescription = false)
{
AddStep("create component", () =>
{
LabelledSliderBar<double> component;
Child = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Width = 500,
AutoSizeAxes = Axes.Y,
Child = component = new LabelledSliderBar<double>
{
Current = new BindableDouble(5)
{
MinValue = 0,
MaxValue = 10,
Precision = 1,
}
}
};
component.Label = "a sample component";
component.Description = hasDescription ? "this text describes the component" : string.Empty;
});
}
}
}

View File

@ -10,34 +10,34 @@ namespace osu.Game.Tournament.Components
{ {
public class DateTextBox : SettingsTextBox public class DateTextBox : SettingsTextBox
{ {
public new Bindable<DateTimeOffset> Bindable public new Bindable<DateTimeOffset> Current
{ {
get => bindable; get => current;
set set
{ {
bindable = value.GetBoundCopy(); current = value.GetBoundCopy();
bindable.BindValueChanged(dto => current.BindValueChanged(dto =>
base.Bindable.Value = dto.NewValue.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"), true); base.Current.Value = dto.NewValue.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"), true);
} }
} }
// hold a reference to the provided bindable so we don't have to in every settings section. // hold a reference to the provided bindable so we don't have to in every settings section.
private Bindable<DateTimeOffset> bindable = new Bindable<DateTimeOffset>(); private Bindable<DateTimeOffset> current = new Bindable<DateTimeOffset>();
public DateTextBox() public DateTextBox()
{ {
base.Bindable = new Bindable<string>(); base.Current = new Bindable<string>();
((OsuTextBox)Control).OnCommit += (sender, newText) => ((OsuTextBox)Control).OnCommit += (sender, newText) =>
{ {
try try
{ {
bindable.Value = DateTimeOffset.Parse(sender.Text); current.Value = DateTimeOffset.Parse(sender.Text);
} }
catch catch
{ {
// reset textbox content to its last valid state on a parse failure. // reset textbox content to its last valid state on a parse failure.
bindable.TriggerChange(); current.TriggerChange();
} }
}; };
} }

View File

@ -63,25 +63,25 @@ namespace osu.Game.Tournament.Screens.Editors
{ {
LabelText = "Name", LabelText = "Name",
Width = 0.33f, Width = 0.33f,
Bindable = Model.Name Current = Model.Name
}, },
new SettingsTextBox new SettingsTextBox
{ {
LabelText = "Description", LabelText = "Description",
Width = 0.33f, Width = 0.33f,
Bindable = Model.Description Current = Model.Description
}, },
new DateTextBox new DateTextBox
{ {
LabelText = "Start Time", LabelText = "Start Time",
Width = 0.33f, Width = 0.33f,
Bindable = Model.StartDate Current = Model.StartDate
}, },
new SettingsSlider<int> new SettingsSlider<int>
{ {
LabelText = "Best of", LabelText = "Best of",
Width = 0.33f, Width = 0.33f,
Bindable = Model.BestOf Current = Model.BestOf
}, },
new SettingsButton new SettingsButton
{ {
@ -186,14 +186,14 @@ namespace osu.Game.Tournament.Screens.Editors
LabelText = "Beatmap ID", LabelText = "Beatmap ID",
RelativeSizeAxes = Axes.None, RelativeSizeAxes = Axes.None,
Width = 200, Width = 200,
Bindable = beatmapId, Current = beatmapId,
}, },
new SettingsTextBox new SettingsTextBox
{ {
LabelText = "Mods", LabelText = "Mods",
RelativeSizeAxes = Axes.None, RelativeSizeAxes = Axes.None,
Width = 200, Width = 200,
Bindable = mods, Current = mods,
}, },
drawableContainer = new Container drawableContainer = new Container
{ {

View File

@ -74,13 +74,13 @@ namespace osu.Game.Tournament.Screens.Editors
{ {
LabelText = "Mod", LabelText = "Mod",
Width = 0.33f, Width = 0.33f,
Bindable = Model.Mod Current = Model.Mod
}, },
new SettingsSlider<int> new SettingsSlider<int>
{ {
LabelText = "Seed", LabelText = "Seed",
Width = 0.33f, Width = 0.33f,
Bindable = Model.Seed Current = Model.Seed
}, },
new SettingsButton new SettingsButton
{ {
@ -187,21 +187,21 @@ namespace osu.Game.Tournament.Screens.Editors
LabelText = "Beatmap ID", LabelText = "Beatmap ID",
RelativeSizeAxes = Axes.None, RelativeSizeAxes = Axes.None,
Width = 200, Width = 200,
Bindable = beatmapId, Current = beatmapId,
}, },
new SettingsSlider<int> new SettingsSlider<int>
{ {
LabelText = "Seed", LabelText = "Seed",
RelativeSizeAxes = Axes.None, RelativeSizeAxes = Axes.None,
Width = 200, Width = 200,
Bindable = beatmap.Seed Current = beatmap.Seed
}, },
new SettingsTextBox new SettingsTextBox
{ {
LabelText = "Score", LabelText = "Score",
RelativeSizeAxes = Axes.None, RelativeSizeAxes = Axes.None,
Width = 200, Width = 200,
Bindable = score, Current = score,
}, },
drawableContainer = new Container drawableContainer = new Container
{ {

View File

@ -102,31 +102,31 @@ namespace osu.Game.Tournament.Screens.Editors
{ {
LabelText = "Name", LabelText = "Name",
Width = 0.2f, Width = 0.2f,
Bindable = Model.FullName Current = Model.FullName
}, },
new SettingsTextBox new SettingsTextBox
{ {
LabelText = "Acronym", LabelText = "Acronym",
Width = 0.2f, Width = 0.2f,
Bindable = Model.Acronym Current = Model.Acronym
}, },
new SettingsTextBox new SettingsTextBox
{ {
LabelText = "Flag", LabelText = "Flag",
Width = 0.2f, Width = 0.2f,
Bindable = Model.FlagName Current = Model.FlagName
}, },
new SettingsTextBox new SettingsTextBox
{ {
LabelText = "Seed", LabelText = "Seed",
Width = 0.2f, Width = 0.2f,
Bindable = Model.Seed Current = Model.Seed
}, },
new SettingsSlider<int> new SettingsSlider<int>
{ {
LabelText = "Last Year Placement", LabelText = "Last Year Placement",
Width = 0.33f, Width = 0.33f,
Bindable = Model.LastYearPlacing Current = Model.LastYearPlacing
}, },
new SettingsButton new SettingsButton
{ {
@ -247,7 +247,7 @@ namespace osu.Game.Tournament.Screens.Editors
LabelText = "User ID", LabelText = "User ID",
RelativeSizeAxes = Axes.None, RelativeSizeAxes = Axes.None,
Width = 200, Width = 200,
Bindable = userId, Current = userId,
}, },
drawableContainer = new Container drawableContainer = new Container
{ {

View File

@ -113,13 +113,13 @@ namespace osu.Game.Tournament.Screens.Gameplay
new SettingsSlider<int> new SettingsSlider<int>
{ {
LabelText = "Chroma width", LabelText = "Chroma width",
Bindable = LadderInfo.ChromaKeyWidth, Current = LadderInfo.ChromaKeyWidth,
KeyboardStep = 1, KeyboardStep = 1,
}, },
new SettingsSlider<int> new SettingsSlider<int>
{ {
LabelText = "Players per team", LabelText = "Players per team",
Bindable = LadderInfo.PlayersPerTeam, Current = LadderInfo.PlayersPerTeam,
KeyboardStep = 1, KeyboardStep = 1,
} }
} }

View File

@ -51,15 +51,15 @@ namespace osu.Game.Tournament.Screens.Ladder.Components
editorInfo.Selected.ValueChanged += selection => editorInfo.Selected.ValueChanged += selection =>
{ {
roundDropdown.Bindable = selection.NewValue?.Round; roundDropdown.Current = selection.NewValue?.Round;
losersCheckbox.Current = selection.NewValue?.Losers; losersCheckbox.Current = selection.NewValue?.Losers;
dateTimeBox.Bindable = selection.NewValue?.Date; dateTimeBox.Current = selection.NewValue?.Date;
team1Dropdown.Bindable = selection.NewValue?.Team1; team1Dropdown.Current = selection.NewValue?.Team1;
team2Dropdown.Bindable = selection.NewValue?.Team2; team2Dropdown.Current = selection.NewValue?.Team2;
}; };
roundDropdown.Bindable.ValueChanged += round => roundDropdown.Current.ValueChanged += round =>
{ {
if (editorInfo.Selected.Value?.Date.Value < round.NewValue?.StartDate.Value) if (editorInfo.Selected.Value?.Date.Value < round.NewValue?.StartDate.Value)
{ {
@ -88,7 +88,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components
{ {
public SettingsRoundDropdown(BindableList<TournamentRound> rounds) public SettingsRoundDropdown(BindableList<TournamentRound> rounds)
{ {
Bindable = new Bindable<TournamentRound>(); Current = new Bindable<TournamentRound>();
foreach (var r in rounds.Prepend(new TournamentRound())) foreach (var r in rounds.Prepend(new TournamentRound()))
add(r); add(r);

View File

@ -61,7 +61,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro
new SettingsTeamDropdown(LadderInfo.Teams) new SettingsTeamDropdown(LadderInfo.Teams)
{ {
LabelText = "Show specific team", LabelText = "Show specific team",
Bindable = currentTeam, Current = currentTeam,
} }
} }
} }

View File

@ -22,8 +22,18 @@ namespace osu.Game.Beatmaps
{ {
IHasComboInformation lastObj = null; IHasComboInformation lastObj = null;
bool isFirst = true;
foreach (var obj in Beatmap.HitObjects.OfType<IHasComboInformation>()) foreach (var obj in Beatmap.HitObjects.OfType<IHasComboInformation>())
{ {
if (isFirst)
{
obj.NewCombo = true;
// first hitobject should always be marked as a new combo for sanity.
isFirst = false;
}
if (obj.NewCombo) if (obj.NewCombo)
{ {
obj.IndexInCurrentCombo = 0; obj.IndexInCurrentCombo = 0;

View File

@ -7,6 +7,7 @@ using osu.Framework.Configuration.Tracking;
using osu.Framework.Extensions; using osu.Framework.Extensions;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Input;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Select; using osu.Game.Screens.Select;
@ -69,6 +70,7 @@ namespace osu.Game.Configuration
Set(OsuSetting.MouseDisableButtons, false); Set(OsuSetting.MouseDisableButtons, false);
Set(OsuSetting.MouseDisableWheel, false); Set(OsuSetting.MouseDisableWheel, false);
Set(OsuSetting.ConfineMouseMode, OsuConfineMouseMode.DuringGameplay);
// Graphics // Graphics
Set(OsuSetting.ShowFpsDisplay, false); Set(OsuSetting.ShowFpsDisplay, false);
@ -194,6 +196,7 @@ namespace osu.Game.Configuration
FadePlayfieldWhenHealthLow, FadePlayfieldWhenHealthLow,
MouseDisableButtons, MouseDisableButtons,
MouseDisableWheel, MouseDisableWheel,
ConfineMouseMode,
AudioOffset, AudioOffset,
VolumeInactive, VolumeInactive,
MenuMusic, MenuMusic,

View File

@ -57,7 +57,7 @@ namespace osu.Game.Configuration
yield return new SettingsSlider<float> yield return new SettingsSlider<float>
{ {
LabelText = attr.Label, LabelText = attr.Label,
Bindable = bNumber, Current = bNumber,
KeyboardStep = 0.1f, KeyboardStep = 0.1f,
}; };
@ -67,7 +67,7 @@ namespace osu.Game.Configuration
yield return new SettingsSlider<double> yield return new SettingsSlider<double>
{ {
LabelText = attr.Label, LabelText = attr.Label,
Bindable = bNumber, Current = bNumber,
KeyboardStep = 0.1f, KeyboardStep = 0.1f,
}; };
@ -77,7 +77,7 @@ namespace osu.Game.Configuration
yield return new SettingsSlider<int> yield return new SettingsSlider<int>
{ {
LabelText = attr.Label, LabelText = attr.Label,
Bindable = bNumber Current = bNumber
}; };
break; break;
@ -86,7 +86,7 @@ namespace osu.Game.Configuration
yield return new SettingsCheckbox yield return new SettingsCheckbox
{ {
LabelText = attr.Label, LabelText = attr.Label,
Bindable = bBool Current = bBool
}; };
break; break;
@ -95,7 +95,7 @@ namespace osu.Game.Configuration
yield return new SettingsTextBox yield return new SettingsTextBox
{ {
LabelText = attr.Label, LabelText = attr.Label,
Bindable = bString Current = bString
}; };
break; break;
@ -105,7 +105,7 @@ namespace osu.Game.Configuration
var dropdown = (Drawable)Activator.CreateInstance(dropdownType); var dropdown = (Drawable)Activator.CreateInstance(dropdownType);
dropdownType.GetProperty(nameof(SettingsDropdown<object>.LabelText))?.SetValue(dropdown, attr.Label); dropdownType.GetProperty(nameof(SettingsDropdown<object>.LabelText))?.SetValue(dropdown, attr.Label);
dropdownType.GetProperty(nameof(SettingsDropdown<object>.Bindable))?.SetValue(dropdown, bindable); dropdownType.GetProperty(nameof(SettingsDropdown<object>.Current))?.SetValue(dropdown, bindable);
yield return dropdown; yield return dropdown;

View File

@ -0,0 +1,24 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Graphics;
using osu.Game.Overlays.Settings;
namespace osu.Game.Graphics.UserInterfaceV2
{
public class LabelledSliderBar<TNumber> : LabelledComponent<SettingsSlider<TNumber>, TNumber>
where TNumber : struct, IEquatable<TNumber>, IComparable<TNumber>, IConvertible
{
public LabelledSliderBar()
: base(true)
{
}
protected override SettingsSlider<TNumber> CreateComponent() => new SettingsSlider<TNumber>
{
TransferValueOnCommit = true,
RelativeSizeAxes = Axes.X,
};
}
}

View File

@ -0,0 +1,61 @@
// 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.Bindables;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Input;
using osu.Game.Configuration;
namespace osu.Game.Input
{
/// <summary>
/// Connects <see cref="OsuSetting.ConfineMouseMode"/> with <see cref="FrameworkSetting.ConfineMouseMode"/>.
/// If <see cref="OsuGame.LocalUserPlaying"/> is true, we should also confine the mouse cursor if it has been
/// requested with <see cref="OsuConfineMouseMode.DuringGameplay"/>.
/// </summary>
public class ConfineMouseTracker : Component
{
private Bindable<ConfineMouseMode> frameworkConfineMode;
private Bindable<OsuConfineMouseMode> osuConfineMode;
private IBindable<bool> localUserPlaying;
[BackgroundDependencyLoader]
private void load(OsuGame game, FrameworkConfigManager frameworkConfigManager, OsuConfigManager osuConfigManager)
{
frameworkConfineMode = frameworkConfigManager.GetBindable<ConfineMouseMode>(FrameworkSetting.ConfineMouseMode);
osuConfineMode = osuConfigManager.GetBindable<OsuConfineMouseMode>(OsuSetting.ConfineMouseMode);
localUserPlaying = game.LocalUserPlaying.GetBoundCopy();
osuConfineMode.ValueChanged += _ => updateConfineMode();
localUserPlaying.BindValueChanged(_ => updateConfineMode(), true);
}
private void updateConfineMode()
{
// confine mode is unavailable on some platforms
if (frameworkConfineMode.Disabled)
return;
switch (osuConfineMode.Value)
{
case OsuConfineMouseMode.Never:
frameworkConfineMode.Value = ConfineMouseMode.Never;
break;
case OsuConfineMouseMode.Fullscreen:
frameworkConfineMode.Value = ConfineMouseMode.Fullscreen;
break;
case OsuConfineMouseMode.DuringGameplay:
frameworkConfineMode.Value = localUserPlaying.Value ? ConfineMouseMode.Always : ConfineMouseMode.Never;
break;
case OsuConfineMouseMode.Always:
frameworkConfineMode.Value = ConfineMouseMode.Always;
break;
}
}
}
}

View File

@ -0,0 +1,37 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.ComponentModel;
using osu.Framework.Input;
namespace osu.Game.Input
{
/// <summary>
/// Determines the situations in which the mouse cursor should be confined to the window.
/// Expands upon <see cref="ConfineMouseMode"/> by providing the option to confine during gameplay.
/// </summary>
public enum OsuConfineMouseMode
{
/// <summary>
/// The mouse cursor will be free to move outside the game window.
/// </summary>
Never,
/// <summary>
/// The mouse cursor will be locked to the window bounds while in fullscreen mode.
/// </summary>
Fullscreen,
/// <summary>
/// The mouse cursor will be locked to the window bounds during gameplay,
/// but may otherwise move freely.
/// </summary>
[Description("During Gameplay")]
DuringGameplay,
/// <summary>
/// The mouse cursor will always be locked to the window bounds while the game has focus.
/// </summary>
Always
}
}

View File

@ -95,6 +95,15 @@ namespace osu.Game
/// </summary> /// </summary>
public readonly IBindable<OverlayActivation> OverlayActivationMode = new Bindable<OverlayActivation>(); public readonly IBindable<OverlayActivation> OverlayActivationMode = new Bindable<OverlayActivation>();
/// <summary>
/// Whether the local user is currently interacting with the game in a way that should not be interrupted.
/// </summary>
/// <remarks>
/// This is exclusively managed by <see cref="Player"/>. If other components are mutating this state, a more
/// resilient method should be used to ensure correct state.
/// </remarks>
public Bindable<bool> LocalUserPlaying = new BindableBool();
protected OsuScreenStack ScreenStack; protected OsuScreenStack ScreenStack;
protected BackButton BackButton; protected BackButton BackButton;
@ -577,7 +586,8 @@ namespace osu.Game
rightFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, rightFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both },
leftFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, leftFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both },
topMostOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, topMostOverlayContent = new Container { RelativeSizeAxes = Axes.Both },
idleTracker idleTracker,
new ConfineMouseTracker()
}); });
ScreenStack.ScreenPushed += screenPushed; ScreenStack.ScreenPushed += screenPushed;
@ -947,6 +957,9 @@ namespace osu.Game
break; break;
} }
// reset on screen change for sanity.
LocalUserPlaying.Value = false;
if (current is IOsuScreen currentOsuScreen) if (current is IOsuScreen currentOsuScreen)
OverlayActivationMode.UnbindFrom(currentOsuScreen.OverlayActivationMode); OverlayActivationMode.UnbindFrom(currentOsuScreen.OverlayActivationMode);

View File

@ -64,7 +64,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
updateItems(); updateItems();
dropdown.Bindable = audio.AudioDevice; dropdown.Current = audio.AudioDevice;
audio.OnNewDevice += onDeviceChanged; audio.OnNewDevice += onDeviceChanged;
audio.OnLostDevice += onDeviceChanged; audio.OnLostDevice += onDeviceChanged;

View File

@ -21,23 +21,23 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Interface voices", LabelText = "Interface voices",
Bindable = config.GetBindable<bool>(OsuSetting.MenuVoice) Current = config.GetBindable<bool>(OsuSetting.MenuVoice)
}, },
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "osu! music theme", LabelText = "osu! music theme",
Bindable = config.GetBindable<bool>(OsuSetting.MenuMusic) Current = config.GetBindable<bool>(OsuSetting.MenuMusic)
}, },
new SettingsDropdown<IntroSequence> new SettingsDropdown<IntroSequence>
{ {
LabelText = "Intro sequence", LabelText = "Intro sequence",
Bindable = config.GetBindable<IntroSequence>(OsuSetting.IntroSequence), Current = config.GetBindable<IntroSequence>(OsuSetting.IntroSequence),
Items = Enum.GetValues(typeof(IntroSequence)).Cast<IntroSequence>() Items = Enum.GetValues(typeof(IntroSequence)).Cast<IntroSequence>()
}, },
new SettingsDropdown<BackgroundSource> new SettingsDropdown<BackgroundSource>
{ {
LabelText = "Background source", LabelText = "Background source",
Bindable = config.GetBindable<BackgroundSource>(OsuSetting.MenuBackgroundSource), Current = config.GetBindable<BackgroundSource>(OsuSetting.MenuBackgroundSource),
Items = Enum.GetValues(typeof(BackgroundSource)).Cast<BackgroundSource>() Items = Enum.GetValues(typeof(BackgroundSource)).Cast<BackgroundSource>()
} }
}; };

View File

@ -20,7 +20,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
new SettingsSlider<double, OffsetSlider> new SettingsSlider<double, OffsetSlider>
{ {
LabelText = "Audio offset", LabelText = "Audio offset",
Bindable = config.GetBindable<double>(OsuSetting.AudioOffset), Current = config.GetBindable<double>(OsuSetting.AudioOffset),
KeyboardStep = 1f KeyboardStep = 1f
}, },
new SettingsButton new SettingsButton

View File

@ -20,28 +20,28 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
new SettingsSlider<double> new SettingsSlider<double>
{ {
LabelText = "Master", LabelText = "Master",
Bindable = audio.Volume, Current = audio.Volume,
KeyboardStep = 0.01f, KeyboardStep = 0.01f,
DisplayAsPercentage = true DisplayAsPercentage = true
}, },
new SettingsSlider<double> new SettingsSlider<double>
{ {
LabelText = "Master (window inactive)", LabelText = "Master (window inactive)",
Bindable = config.GetBindable<double>(OsuSetting.VolumeInactive), Current = config.GetBindable<double>(OsuSetting.VolumeInactive),
KeyboardStep = 0.01f, KeyboardStep = 0.01f,
DisplayAsPercentage = true DisplayAsPercentage = true
}, },
new SettingsSlider<double> new SettingsSlider<double>
{ {
LabelText = "Effect", LabelText = "Effect",
Bindable = audio.VolumeSample, Current = audio.VolumeSample,
KeyboardStep = 0.01f, KeyboardStep = 0.01f,
DisplayAsPercentage = true DisplayAsPercentage = true
}, },
new SettingsSlider<double> new SettingsSlider<double>
{ {
LabelText = "Music", LabelText = "Music",
Bindable = audio.VolumeTrack, Current = audio.VolumeTrack,
KeyboardStep = 0.01f, KeyboardStep = 0.01f,
DisplayAsPercentage = true DisplayAsPercentage = true
}, },

View File

@ -19,12 +19,12 @@ namespace osu.Game.Overlays.Settings.Sections.Debug
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Show log overlay", LabelText = "Show log overlay",
Bindable = frameworkConfig.GetBindable<bool>(FrameworkSetting.ShowLogOverlay) Current = frameworkConfig.GetBindable<bool>(FrameworkSetting.ShowLogOverlay)
}, },
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Bypass front-to-back render pass", LabelText = "Bypass front-to-back render pass",
Bindable = config.GetBindable<bool>(DebugSetting.BypassFrontToBackPass) Current = config.GetBindable<bool>(DebugSetting.BypassFrontToBackPass)
} }
}; };
} }

View File

@ -21,62 +21,62 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
new SettingsSlider<double> new SettingsSlider<double>
{ {
LabelText = "Background dim", LabelText = "Background dim",
Bindable = config.GetBindable<double>(OsuSetting.DimLevel), Current = config.GetBindable<double>(OsuSetting.DimLevel),
KeyboardStep = 0.01f, KeyboardStep = 0.01f,
DisplayAsPercentage = true DisplayAsPercentage = true
}, },
new SettingsSlider<double> new SettingsSlider<double>
{ {
LabelText = "Background blur", LabelText = "Background blur",
Bindable = config.GetBindable<double>(OsuSetting.BlurLevel), Current = config.GetBindable<double>(OsuSetting.BlurLevel),
KeyboardStep = 0.01f, KeyboardStep = 0.01f,
DisplayAsPercentage = true DisplayAsPercentage = true
}, },
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Lighten playfield during breaks", LabelText = "Lighten playfield during breaks",
Bindable = config.GetBindable<bool>(OsuSetting.LightenDuringBreaks) Current = config.GetBindable<bool>(OsuSetting.LightenDuringBreaks)
}, },
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Show score overlay", LabelText = "Show score overlay",
Bindable = config.GetBindable<bool>(OsuSetting.ShowInterface) Current = config.GetBindable<bool>(OsuSetting.ShowInterface)
}, },
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Show difficulty graph on progress bar", LabelText = "Show difficulty graph on progress bar",
Bindable = config.GetBindable<bool>(OsuSetting.ShowProgressGraph) Current = config.GetBindable<bool>(OsuSetting.ShowProgressGraph)
}, },
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Show health display even when you can't fail", LabelText = "Show health display even when you can't fail",
Bindable = config.GetBindable<bool>(OsuSetting.ShowHealthDisplayWhenCantFail), Current = config.GetBindable<bool>(OsuSetting.ShowHealthDisplayWhenCantFail),
Keywords = new[] { "hp", "bar" } Keywords = new[] { "hp", "bar" }
}, },
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Fade playfield to red when health is low", LabelText = "Fade playfield to red when health is low",
Bindable = config.GetBindable<bool>(OsuSetting.FadePlayfieldWhenHealthLow), Current = config.GetBindable<bool>(OsuSetting.FadePlayfieldWhenHealthLow),
}, },
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Always show key overlay", LabelText = "Always show key overlay",
Bindable = config.GetBindable<bool>(OsuSetting.KeyOverlay) Current = config.GetBindable<bool>(OsuSetting.KeyOverlay)
}, },
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Positional hitsounds", LabelText = "Positional hitsounds",
Bindable = config.GetBindable<bool>(OsuSetting.PositionalHitSounds) Current = config.GetBindable<bool>(OsuSetting.PositionalHitSounds)
}, },
new SettingsEnumDropdown<ScoreMeterType> new SettingsEnumDropdown<ScoreMeterType>
{ {
LabelText = "Score meter type", LabelText = "Score meter type",
Bindable = config.GetBindable<ScoreMeterType>(OsuSetting.ScoreMeter) Current = config.GetBindable<ScoreMeterType>(OsuSetting.ScoreMeter)
}, },
new SettingsEnumDropdown<ScoringMode> new SettingsEnumDropdown<ScoringMode>
{ {
LabelText = "Score display mode", LabelText = "Score display mode",
Bindable = config.GetBindable<ScoringMode>(OsuSetting.ScoreDisplayMode) Current = config.GetBindable<ScoringMode>(OsuSetting.ScoreDisplayMode)
} }
}; };
@ -85,7 +85,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
Add(new SettingsCheckbox Add(new SettingsCheckbox
{ {
LabelText = "Disable Windows key during gameplay", LabelText = "Disable Windows key during gameplay",
Bindable = config.GetBindable<bool>(OsuSetting.GameplayDisableWinKey) Current = config.GetBindable<bool>(OsuSetting.GameplayDisableWinKey)
}); });
} }
} }

View File

@ -22,7 +22,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Increase visibility of first object when visual impairment mods are enabled", LabelText = "Increase visibility of first object when visual impairment mods are enabled",
Bindable = config.GetBindable<bool>(OsuSetting.IncreaseFirstObjectVisibility), Current = config.GetBindable<bool>(OsuSetting.IncreaseFirstObjectVisibility),
}, },
}; };
} }

View File

@ -31,31 +31,31 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Right mouse drag to absolute scroll", LabelText = "Right mouse drag to absolute scroll",
Bindable = config.GetBindable<bool>(OsuSetting.SongSelectRightMouseScroll), Current = config.GetBindable<bool>(OsuSetting.SongSelectRightMouseScroll),
}, },
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Show converted beatmaps", LabelText = "Show converted beatmaps",
Bindable = config.GetBindable<bool>(OsuSetting.ShowConvertedBeatmaps), Current = config.GetBindable<bool>(OsuSetting.ShowConvertedBeatmaps),
}, },
new SettingsSlider<double, StarsSlider> new SettingsSlider<double, StarsSlider>
{ {
LabelText = "Display beatmaps from", LabelText = "Display beatmaps from",
Bindable = config.GetBindable<double>(OsuSetting.DisplayStarsMinimum), Current = config.GetBindable<double>(OsuSetting.DisplayStarsMinimum),
KeyboardStep = 0.1f, KeyboardStep = 0.1f,
Keywords = new[] { "minimum", "maximum", "star", "difficulty" } Keywords = new[] { "minimum", "maximum", "star", "difficulty" }
}, },
new SettingsSlider<double, MaximumStarsSlider> new SettingsSlider<double, MaximumStarsSlider>
{ {
LabelText = "up to", LabelText = "up to",
Bindable = config.GetBindable<double>(OsuSetting.DisplayStarsMaximum), Current = config.GetBindable<double>(OsuSetting.DisplayStarsMaximum),
KeyboardStep = 0.1f, KeyboardStep = 0.1f,
Keywords = new[] { "minimum", "maximum", "star", "difficulty" } Keywords = new[] { "minimum", "maximum", "star", "difficulty" }
}, },
new SettingsEnumDropdown<RandomSelectAlgorithm> new SettingsEnumDropdown<RandomSelectAlgorithm>
{ {
LabelText = "Random selection algorithm", LabelText = "Random selection algorithm",
Bindable = config.GetBindable<RandomSelectAlgorithm>(OsuSetting.RandomSelectAlgorithm), Current = config.GetBindable<RandomSelectAlgorithm>(OsuSetting.RandomSelectAlgorithm),
} }
}; };
} }

View File

@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Prefer metadata in original language", LabelText = "Prefer metadata in original language",
Bindable = frameworkConfig.GetBindable<bool>(FrameworkSetting.ShowUnicode) Current = frameworkConfig.GetBindable<bool>(FrameworkSetting.ShowUnicode)
}, },
}; };
} }

View File

@ -240,12 +240,12 @@ namespace osu.Game.Overlays.Settings.Sections.General
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Remember username", LabelText = "Remember username",
Bindable = config.GetBindable<bool>(OsuSetting.SaveUsername), Current = config.GetBindable<bool>(OsuSetting.SaveUsername),
}, },
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Stay signed in", LabelText = "Stay signed in",
Bindable = config.GetBindable<bool>(OsuSetting.SavePassword), Current = config.GetBindable<bool>(OsuSetting.SavePassword),
}, },
new Container new Container
{ {

View File

@ -32,7 +32,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
Add(new SettingsEnumDropdown<ReleaseStream> Add(new SettingsEnumDropdown<ReleaseStream>
{ {
LabelText = "Release stream", LabelText = "Release stream",
Bindable = config.GetBindable<ReleaseStream>(OsuSetting.ReleaseStream), Current = config.GetBindable<ReleaseStream>(OsuSetting.ReleaseStream),
}); });
if (updateManager?.CanCheckForUpdate == true) if (updateManager?.CanCheckForUpdate == true)

View File

@ -19,22 +19,22 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Storyboard / Video", LabelText = "Storyboard / Video",
Bindable = config.GetBindable<bool>(OsuSetting.ShowStoryboard) Current = config.GetBindable<bool>(OsuSetting.ShowStoryboard)
}, },
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Hit Lighting", LabelText = "Hit Lighting",
Bindable = config.GetBindable<bool>(OsuSetting.HitLighting) Current = config.GetBindable<bool>(OsuSetting.HitLighting)
}, },
new SettingsEnumDropdown<ScreenshotFormat> new SettingsEnumDropdown<ScreenshotFormat>
{ {
LabelText = "Screenshot format", LabelText = "Screenshot format",
Bindable = config.GetBindable<ScreenshotFormat>(OsuSetting.ScreenshotFormat) Current = config.GetBindable<ScreenshotFormat>(OsuSetting.ScreenshotFormat)
}, },
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Show menu cursor in screenshots", LabelText = "Show menu cursor in screenshots",
Bindable = config.GetBindable<bool>(OsuSetting.ScreenshotCaptureMenuCursor) Current = config.GetBindable<bool>(OsuSetting.ScreenshotCaptureMenuCursor)
} }
}; };
} }

View File

@ -62,7 +62,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
windowModeDropdown = new SettingsDropdown<WindowMode> windowModeDropdown = new SettingsDropdown<WindowMode>
{ {
LabelText = "Screen mode", LabelText = "Screen mode",
Bindable = config.GetBindable<WindowMode>(FrameworkSetting.WindowMode), Current = config.GetBindable<WindowMode>(FrameworkSetting.WindowMode),
ItemSource = windowModes, ItemSource = windowModes,
}, },
resolutionSettingsContainer = new Container resolutionSettingsContainer = new Container
@ -74,14 +74,14 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
{ {
LabelText = "UI Scaling", LabelText = "UI Scaling",
TransferValueOnCommit = true, TransferValueOnCommit = true,
Bindable = osuConfig.GetBindable<float>(OsuSetting.UIScale), Current = osuConfig.GetBindable<float>(OsuSetting.UIScale),
KeyboardStep = 0.01f, KeyboardStep = 0.01f,
Keywords = new[] { "scale", "letterbox" }, Keywords = new[] { "scale", "letterbox" },
}, },
new SettingsEnumDropdown<ScalingMode> new SettingsEnumDropdown<ScalingMode>
{ {
LabelText = "Screen Scaling", LabelText = "Screen Scaling",
Bindable = osuConfig.GetBindable<ScalingMode>(OsuSetting.Scaling), Current = osuConfig.GetBindable<ScalingMode>(OsuSetting.Scaling),
Keywords = new[] { "scale", "letterbox" }, Keywords = new[] { "scale", "letterbox" },
}, },
scalingSettings = new FillFlowContainer<SettingsSlider<float>> scalingSettings = new FillFlowContainer<SettingsSlider<float>>
@ -97,28 +97,28 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
new SettingsSlider<float> new SettingsSlider<float>
{ {
LabelText = "Horizontal position", LabelText = "Horizontal position",
Bindable = scalingPositionX, Current = scalingPositionX,
KeyboardStep = 0.01f, KeyboardStep = 0.01f,
DisplayAsPercentage = true DisplayAsPercentage = true
}, },
new SettingsSlider<float> new SettingsSlider<float>
{ {
LabelText = "Vertical position", LabelText = "Vertical position",
Bindable = scalingPositionY, Current = scalingPositionY,
KeyboardStep = 0.01f, KeyboardStep = 0.01f,
DisplayAsPercentage = true DisplayAsPercentage = true
}, },
new SettingsSlider<float> new SettingsSlider<float>
{ {
LabelText = "Horizontal scale", LabelText = "Horizontal scale",
Bindable = scalingSizeX, Current = scalingSizeX,
KeyboardStep = 0.01f, KeyboardStep = 0.01f,
DisplayAsPercentage = true DisplayAsPercentage = true
}, },
new SettingsSlider<float> new SettingsSlider<float>
{ {
LabelText = "Vertical scale", LabelText = "Vertical scale",
Bindable = scalingSizeY, Current = scalingSizeY,
KeyboardStep = 0.01f, KeyboardStep = 0.01f,
DisplayAsPercentage = true DisplayAsPercentage = true
}, },
@ -126,7 +126,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
}, },
}; };
scalingSettings.ForEach(s => bindPreviewEvent(s.Bindable)); scalingSettings.ForEach(s => bindPreviewEvent(s.Current));
var resolutions = getResolutions(); var resolutions = getResolutions();
@ -137,10 +137,10 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
LabelText = "Resolution", LabelText = "Resolution",
ShowsDefaultIndicator = false, ShowsDefaultIndicator = false,
Items = resolutions, Items = resolutions,
Bindable = sizeFullscreen Current = sizeFullscreen
}; };
windowModeDropdown.Bindable.BindValueChanged(mode => windowModeDropdown.Current.BindValueChanged(mode =>
{ {
if (mode.NewValue == WindowMode.Fullscreen) if (mode.NewValue == WindowMode.Fullscreen)
{ {

View File

@ -23,17 +23,17 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
new SettingsEnumDropdown<FrameSync> new SettingsEnumDropdown<FrameSync>
{ {
LabelText = "Frame limiter", LabelText = "Frame limiter",
Bindable = config.GetBindable<FrameSync>(FrameworkSetting.FrameSync) Current = config.GetBindable<FrameSync>(FrameworkSetting.FrameSync)
}, },
new SettingsEnumDropdown<ExecutionMode> new SettingsEnumDropdown<ExecutionMode>
{ {
LabelText = "Threading mode", LabelText = "Threading mode",
Bindable = config.GetBindable<ExecutionMode>(FrameworkSetting.ExecutionMode) Current = config.GetBindable<ExecutionMode>(FrameworkSetting.ExecutionMode)
}, },
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Show FPS", LabelText = "Show FPS",
Bindable = osuConfig.GetBindable<bool>(OsuSetting.ShowFpsDisplay) Current = osuConfig.GetBindable<bool>(OsuSetting.ShowFpsDisplay)
}, },
}; };
} }

View File

@ -20,17 +20,17 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Rotate cursor when dragging", LabelText = "Rotate cursor when dragging",
Bindable = config.GetBindable<bool>(OsuSetting.CursorRotation) Current = config.GetBindable<bool>(OsuSetting.CursorRotation)
}, },
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Parallax", LabelText = "Parallax",
Bindable = config.GetBindable<bool>(OsuSetting.MenuParallax) Current = config.GetBindable<bool>(OsuSetting.MenuParallax)
}, },
new SettingsSlider<float, TimeSlider> new SettingsSlider<float, TimeSlider>
{ {
LabelText = "Hold-to-confirm activation time", LabelText = "Hold-to-confirm activation time",
Bindable = config.GetBindable<float>(OsuSetting.UIHoldActivationDelay), Current = config.GetBindable<float>(OsuSetting.UIHoldActivationDelay),
KeyboardStep = 50 KeyboardStep = 50
}, },
}; };

View File

@ -6,9 +6,9 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Configuration; using osu.Framework.Configuration;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Input;
namespace osu.Game.Overlays.Settings.Sections.Input namespace osu.Game.Overlays.Settings.Sections.Input
{ {
@ -35,32 +35,32 @@ namespace osu.Game.Overlays.Settings.Sections.Input
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Raw input", LabelText = "Raw input",
Bindable = rawInputToggle Current = rawInputToggle
}, },
new SensitivitySetting new SensitivitySetting
{ {
LabelText = "Cursor sensitivity", LabelText = "Cursor sensitivity",
Bindable = sensitivityBindable Current = sensitivityBindable
}, },
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Map absolute input to window", LabelText = "Map absolute input to window",
Bindable = config.GetBindable<bool>(FrameworkSetting.MapAbsoluteInputToWindow) Current = config.GetBindable<bool>(FrameworkSetting.MapAbsoluteInputToWindow)
}, },
new SettingsEnumDropdown<ConfineMouseMode> new SettingsEnumDropdown<OsuConfineMouseMode>
{ {
LabelText = "Confine mouse cursor to window", LabelText = "Confine mouse cursor to window",
Bindable = config.GetBindable<ConfineMouseMode>(FrameworkSetting.ConfineMouseMode), Current = osuConfig.GetBindable<OsuConfineMouseMode>(OsuSetting.ConfineMouseMode)
}, },
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Disable mouse wheel during gameplay", LabelText = "Disable mouse wheel during gameplay",
Bindable = osuConfig.GetBindable<bool>(OsuSetting.MouseDisableWheel) Current = osuConfig.GetBindable<bool>(OsuSetting.MouseDisableWheel)
}, },
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Disable mouse buttons during gameplay", LabelText = "Disable mouse buttons during gameplay",
Bindable = osuConfig.GetBindable<bool>(OsuSetting.MouseDisableButtons) Current = osuConfig.GetBindable<bool>(OsuSetting.MouseDisableButtons)
}, },
}; };

View File

@ -19,13 +19,13 @@ namespace osu.Game.Overlays.Settings.Sections.Online
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Warn about opening external links", LabelText = "Warn about opening external links",
Bindable = config.GetBindable<bool>(OsuSetting.ExternalLinkWarning) Current = config.GetBindable<bool>(OsuSetting.ExternalLinkWarning)
}, },
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Prefer downloads without video", LabelText = "Prefer downloads without video",
Keywords = new[] { "no-video" }, Keywords = new[] { "no-video" },
Bindable = config.GetBindable<bool>(OsuSetting.PreferNoVideo) Current = config.GetBindable<bool>(OsuSetting.PreferNoVideo)
}, },
}; };
} }

View File

@ -47,29 +47,29 @@ namespace osu.Game.Overlays.Settings.Sections
new SettingsSlider<float, SizeSlider> new SettingsSlider<float, SizeSlider>
{ {
LabelText = "Menu cursor size", LabelText = "Menu cursor size",
Bindable = config.GetBindable<float>(OsuSetting.MenuCursorSize), Current = config.GetBindable<float>(OsuSetting.MenuCursorSize),
KeyboardStep = 0.01f KeyboardStep = 0.01f
}, },
new SettingsSlider<float, SizeSlider> new SettingsSlider<float, SizeSlider>
{ {
LabelText = "Gameplay cursor size", LabelText = "Gameplay cursor size",
Bindable = config.GetBindable<float>(OsuSetting.GameplayCursorSize), Current = config.GetBindable<float>(OsuSetting.GameplayCursorSize),
KeyboardStep = 0.01f KeyboardStep = 0.01f
}, },
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Adjust gameplay cursor size based on current beatmap", LabelText = "Adjust gameplay cursor size based on current beatmap",
Bindable = config.GetBindable<bool>(OsuSetting.AutoCursorSize) Current = config.GetBindable<bool>(OsuSetting.AutoCursorSize)
}, },
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Beatmap skins", LabelText = "Beatmap skins",
Bindable = config.GetBindable<bool>(OsuSetting.BeatmapSkins) Current = config.GetBindable<bool>(OsuSetting.BeatmapSkins)
}, },
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Beatmap hitsounds", LabelText = "Beatmap hitsounds",
Bindable = config.GetBindable<bool>(OsuSetting.BeatmapHitsounds) Current = config.GetBindable<bool>(OsuSetting.BeatmapHitsounds)
}, },
}; };
@ -81,7 +81,7 @@ namespace osu.Game.Overlays.Settings.Sections
config.BindWith(OsuSetting.Skin, configBindable); config.BindWith(OsuSetting.Skin, configBindable);
skinDropdown.Bindable = dropdownBindable; skinDropdown.Current = dropdownBindable;
skinDropdown.Items = skins.GetAllUsableSkins().ToArray(); skinDropdown.Items = skins.GetAllUsableSkins().ToArray();
// Todo: This should not be necessary when OsuConfigManager is databased // Todo: This should not be necessary when OsuConfigManager is databased

View File

@ -21,7 +21,7 @@ using osuTK;
namespace osu.Game.Overlays.Settings namespace osu.Game.Overlays.Settings
{ {
public abstract class SettingsItem<T> : Container, IFilterable, ISettingsItem public abstract class SettingsItem<T> : Container, IFilterable, ISettingsItem, IHasCurrentValue<T>
{ {
protected abstract Drawable CreateControl(); protected abstract Drawable CreateControl();
@ -54,7 +54,14 @@ namespace osu.Game.Overlays.Settings
} }
} }
public virtual Bindable<T> Bindable [Obsolete("Use Current instead")] // Can be removed 20210406
public Bindable<T> Bindable
{
get => Current;
set => Current = value;
}
public virtual Bindable<T> Current
{ {
get => controlWithCurrent.Current; get => controlWithCurrent.Current;
set => controlWithCurrent.Current = value; set => controlWithCurrent.Current = value;

View File

@ -1,11 +1,11 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Audio.Track; using osu.Framework.Audio.Track;
using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Scoring; using osu.Game.Scoring;
@ -16,19 +16,16 @@ namespace osu.Game.Rulesets.Difficulty
protected readonly DifficultyAttributes Attributes; protected readonly DifficultyAttributes Attributes;
protected readonly Ruleset Ruleset; protected readonly Ruleset Ruleset;
protected readonly IBeatmap Beatmap;
protected readonly ScoreInfo Score; protected readonly ScoreInfo Score;
protected double TimeRate { get; private set; } = 1; protected double TimeRate { get; private set; } = 1;
protected PerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, ScoreInfo score) protected PerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score)
{ {
Ruleset = ruleset; Ruleset = ruleset;
Score = score; Score = score;
Beatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, score.Mods); Attributes = attributes ?? throw new ArgumentNullException(nameof(attributes));
Attributes = ruleset.CreateDifficultyCalculator(beatmap).Calculate(score.Mods);
ApplyMods(score.Mods); ApplyMods(score.Mods);
} }

View File

@ -273,7 +273,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
// apply any custom state overrides // apply any custom state overrides
ApplyCustomUpdateState?.Invoke(this, newState); ApplyCustomUpdateState?.Invoke(this, newState);
if (newState == ArmedState.Hit) if (!force && newState == ArmedState.Hit)
PlaySamples(); PlaySamples();
} }

View File

@ -77,6 +77,7 @@ namespace osu.Game.Rulesets.Objects
/// <summary> /// <summary>
/// The hit windows for this <see cref="HitObject"/>. /// The hit windows for this <see cref="HitObject"/>.
/// </summary> /// </summary>
[JsonIgnore]
public HitWindows HitWindows { get; set; } public HitWindows HitWindows { get; set; }
private readonly List<HitObject> nestedHitObjects = new List<HitObject>(); private readonly List<HitObject> nestedHitObjects = new List<HitObject>();

View File

@ -158,7 +158,28 @@ namespace osu.Game.Rulesets
public abstract DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap); public abstract DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap);
public virtual PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => null; /// <summary>
/// Optionally creates a <see cref="PerformanceCalculator"/> to generate performance data from the provided score.
/// </summary>
/// <param name="attributes">Difficulty attributes for the beatmap related to the provided score.</param>
/// <param name="score">The score to be processed.</param>
/// <returns>A performance calculator instance for the provided score.</returns>
[CanBeNull]
public virtual PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => null;
/// <summary>
/// Optionally creates a <see cref="PerformanceCalculator"/> to generate performance data from the provided score.
/// </summary>
/// <param name="beatmap">The beatmap to use as a source for generating <see cref="DifficultyAttributes"/>.</param>
/// <param name="score">The score to be processed.</param>
/// <returns>A performance calculator instance for the provided score.</returns>
[CanBeNull]
public PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score)
{
var difficultyCalculator = CreateDifficultyCalculator(beatmap);
var difficultyAttributes = difficultyCalculator.Calculate(score.Mods);
return CreatePerformanceCalculator(difficultyAttributes, score);
}
public virtual HitObjectComposer CreateHitObjectComposer() => null; public virtual HitObjectComposer CreateHitObjectComposer() => null;

View File

@ -239,10 +239,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
private void deleteSelected() private void deleteSelected()
{ {
ChangeHandler?.BeginChange(); ChangeHandler?.BeginChange();
EditorBeatmap?.RemoveRange(selectedBlueprints.Select(b => b.HitObject));
foreach (var h in selectedBlueprints.ToList())
EditorBeatmap?.Remove(h.HitObject);
ChangeHandler?.EndChange(); ChangeHandler?.EndChange();
} }

View File

@ -199,7 +199,40 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
base.Update(); base.Update();
// no bindable so we perform this every update // no bindable so we perform this every update
Width = (float)(HitObject.GetEndTime() - HitObject.StartTime); float duration = (float)(HitObject.GetEndTime() - HitObject.StartTime);
if (Width != duration)
{
Width = duration;
// kind of haphazard but yeah, no bindables.
if (HitObject is IHasRepeats repeats)
updateRepeats(repeats);
}
}
private Container repeatsContainer;
private void updateRepeats(IHasRepeats repeats)
{
repeatsContainer?.Expire();
mainComponents.Add(repeatsContainer = new Container
{
RelativeSizeAxes = Axes.Both,
});
for (int i = 0; i < repeats.RepeatCount; i++)
{
repeatsContainer.Add(new Circle
{
Size = new Vector2(circle_size / 2),
Anchor = Anchor.CentreLeft,
Origin = Anchor.Centre,
RelativePositionAxes = Axes.X,
X = (float)(i + 1) / (repeats.RepeatCount + 1),
});
}
} }
protected override bool ShouldBeConsideredForInput(Drawable child) => true; protected override bool ShouldBeConsideredForInput(Drawable child) => true;

View File

@ -484,8 +484,7 @@ namespace osu.Game.Screens.Edit
protected void Cut() protected void Cut()
{ {
Copy(); Copy();
foreach (var h in editorBeatmap.SelectedHitObjects.ToArray()) editorBeatmap.RemoveRange(editorBeatmap.SelectedHitObjects.ToArray());
editorBeatmap.Remove(h);
} }
protected void Copy() protected void Copy()
@ -589,7 +588,7 @@ namespace osu.Game.Screens.Edit
private void seek(UIEvent e, int direction) private void seek(UIEvent e, int direction)
{ {
double amount = e.ShiftPressed ? 2 : 1; double amount = e.ShiftPressed ? 4 : 1;
if (direction < 1) if (direction < 1)
clock.SeekBackward(!clock.IsRunning, amount); clock.SeekBackward(!clock.IsRunning, amount);

View File

@ -91,14 +91,19 @@ namespace osu.Game.Screens.Edit
private readonly HashSet<HitObject> pendingUpdates = new HashSet<HitObject>(); private readonly HashSet<HitObject> pendingUpdates = new HashSet<HitObject>();
private bool isBatchApplying;
/// <summary> /// <summary>
/// Adds a collection of <see cref="HitObject"/>s to this <see cref="EditorBeatmap"/>. /// Adds a collection of <see cref="HitObject"/>s to this <see cref="EditorBeatmap"/>.
/// </summary> /// </summary>
/// <param name="hitObjects">The <see cref="HitObject"/>s to add.</param> /// <param name="hitObjects">The <see cref="HitObject"/>s to add.</param>
public void AddRange(IEnumerable<HitObject> hitObjects) public void AddRange(IEnumerable<HitObject> hitObjects)
{ {
foreach (var h in hitObjects) ApplyBatchChanges(_ =>
Add(h); {
foreach (var h in hitObjects)
Add(h);
});
} }
/// <summary> /// <summary>
@ -126,12 +131,17 @@ namespace osu.Game.Screens.Edit
mutableHitObjects.Insert(index, hitObject); mutableHitObjects.Insert(index, hitObject);
// must be run after any change to hitobject ordering if (isBatchApplying)
beatmapProcessor?.PreProcess(); batchPendingInserts.Add(hitObject);
processHitObject(hitObject); else
beatmapProcessor?.PostProcess(); {
// must be run after any change to hitobject ordering
beatmapProcessor?.PreProcess();
processHitObject(hitObject);
beatmapProcessor?.PostProcess();
HitObjectAdded?.Invoke(hitObject); HitObjectAdded?.Invoke(hitObject);
}
} }
/// <summary> /// <summary>
@ -159,6 +169,19 @@ namespace osu.Game.Screens.Edit
return true; return true;
} }
/// <summary>
/// Removes a collection of <see cref="HitObject"/>s to this <see cref="EditorBeatmap"/>.
/// </summary>
/// <param name="hitObjects">The <see cref="HitObject"/>s to remove.</param>
public void RemoveRange(IEnumerable<HitObject> hitObjects)
{
ApplyBatchChanges(_ =>
{
foreach (var h in hitObjects)
Remove(h);
});
}
/// <summary> /// <summary>
/// Finds the index of a <see cref="HitObject"/> in this <see cref="EditorBeatmap"/>. /// Finds the index of a <see cref="HitObject"/> in this <see cref="EditorBeatmap"/>.
/// </summary> /// </summary>
@ -180,22 +203,56 @@ namespace osu.Game.Screens.Edit
bindable.UnbindAll(); bindable.UnbindAll();
startTimeBindables.Remove(hitObject); startTimeBindables.Remove(hitObject);
// must be run after any change to hitobject ordering if (isBatchApplying)
batchPendingDeletes.Add(hitObject);
else
{
// must be run after any change to hitobject ordering
beatmapProcessor?.PreProcess();
processHitObject(hitObject);
beatmapProcessor?.PostProcess();
HitObjectRemoved?.Invoke(hitObject);
}
}
private readonly List<HitObject> batchPendingInserts = new List<HitObject>();
private readonly List<HitObject> batchPendingDeletes = new List<HitObject>();
/// <summary>
/// Apply a batch of operations in one go, without performing Pre/Postprocessing each time.
/// </summary>
/// <param name="applyFunction">The function which will apply the batch changes.</param>
public void ApplyBatchChanges(Action<EditorBeatmap> applyFunction)
{
if (isBatchApplying)
throw new InvalidOperationException("Attempting to perform a batch application from within an existing batch");
isBatchApplying = true;
applyFunction(this);
beatmapProcessor?.PreProcess(); beatmapProcessor?.PreProcess();
processHitObject(hitObject);
foreach (var h in batchPendingDeletes) processHitObject(h);
foreach (var h in batchPendingInserts) processHitObject(h);
beatmapProcessor?.PostProcess(); beatmapProcessor?.PostProcess();
HitObjectRemoved?.Invoke(hitObject); foreach (var h in batchPendingDeletes) HitObjectRemoved?.Invoke(h);
foreach (var h in batchPendingInserts) HitObjectAdded?.Invoke(h);
batchPendingDeletes.Clear();
batchPendingInserts.Clear();
isBatchApplying = false;
} }
/// <summary> /// <summary>
/// Clears all <see cref="HitObjects"/> from this <see cref="EditorBeatmap"/>. /// Clears all <see cref="HitObjects"/> from this <see cref="EditorBeatmap"/>.
/// </summary> /// </summary>
public void Clear() public void Clear() => RemoveRange(HitObjects.ToArray());
{
foreach (var h in HitObjects.ToArray())
Remove(h);
}
protected override void Update() protected override void Update()
{ {
@ -258,5 +315,14 @@ namespace osu.Game.Screens.Edit
public double GetBeatLengthAtTime(double referenceTime) => ControlPointInfo.TimingPointAt(referenceTime).BeatLength / BeatDivisor; public double GetBeatLengthAtTime(double referenceTime) => ControlPointInfo.TimingPointAt(referenceTime).BeatLength / BeatDivisor;
public int BeatDivisor => beatDivisor?.Value ?? 1; public int BeatDivisor => beatDivisor?.Value ?? 1;
/// <summary>
/// Update all hit objects with potentially changed difficulty or control point data.
/// </summary>
public void UpdateBeatmap()
{
foreach (var h in HitObjects)
pendingUpdates.Add(h);
}
} }
} }

View File

@ -86,7 +86,7 @@ namespace osu.Game.Screens.Edit
/// </summary> /// </summary>
/// <param name="snapped">Whether to snap to the closest beat after seeking.</param> /// <param name="snapped">Whether to snap to the closest beat after seeking.</param>
/// <param name="amount">The relative amount (magnitude) which should be seeked.</param> /// <param name="amount">The relative amount (magnitude) which should be seeked.</param>
public void SeekBackward(bool snapped = false, double amount = 1) => seek(-1, snapped, amount); public void SeekBackward(bool snapped = false, double amount = 1) => seek(-1, snapped, amount + (IsRunning ? 1.5 : 0));
/// <summary> /// <summary>
/// Seeks forwards by one beat length. /// Seeks forwards by one beat length.

View File

@ -68,16 +68,19 @@ namespace osu.Game.Screens.Edit
toRemove.Sort(); toRemove.Sort();
toAdd.Sort(); toAdd.Sort();
// Apply the changes. editorBeatmap.ApplyBatchChanges(eb =>
for (int i = toRemove.Count - 1; i >= 0; i--)
editorBeatmap.RemoveAt(toRemove[i]);
if (toAdd.Count > 0)
{ {
IBeatmap newBeatmap = readBeatmap(newState); // Apply the changes.
foreach (var i in toAdd) for (int i = toRemove.Count - 1; i >= 0; i--)
editorBeatmap.Insert(i, newBeatmap.HitObjects[i]); eb.RemoveAt(toRemove[i]);
}
if (toAdd.Count > 0)
{
IBeatmap newBeatmap = readBeatmap(newState);
foreach (var i in toAdd)
eb.Insert(i, newBeatmap.HitObjects[i]);
}
});
} }
private string readString(byte[] state) => Encoding.UTF8.GetString(state); private string readString(byte[] state) => Encoding.UTF8.GetString(state);

View File

@ -0,0 +1,99 @@
// 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.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterfaceV2;
namespace osu.Game.Screens.Edit.Setup
{
internal class DifficultySection : SetupSection
{
[Resolved]
private EditorBeatmap editorBeatmap { get; set; }
private LabelledSliderBar<float> circleSizeSlider;
private LabelledSliderBar<float> healthDrainSlider;
private LabelledSliderBar<float> approachRateSlider;
private LabelledSliderBar<float> overallDifficultySlider;
[BackgroundDependencyLoader]
private void load()
{
Children = new Drawable[]
{
new OsuSpriteText
{
Text = "Difficulty settings"
},
circleSizeSlider = new LabelledSliderBar<float>
{
Label = "Object Size",
Description = "The size of all hit objects",
Current = new BindableFloat(Beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize)
{
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
MinValue = 2,
MaxValue = 7,
Precision = 0.1f,
}
},
healthDrainSlider = new LabelledSliderBar<float>
{
Label = "Health Drain",
Description = "The rate of passive health drain throughout playable time",
Current = new BindableFloat(Beatmap.Value.BeatmapInfo.BaseDifficulty.DrainRate)
{
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
MinValue = 0,
MaxValue = 10,
Precision = 0.1f,
}
},
approachRateSlider = new LabelledSliderBar<float>
{
Label = "Approach Rate",
Description = "The speed at which objects are presented to the player",
Current = new BindableFloat(Beatmap.Value.BeatmapInfo.BaseDifficulty.ApproachRate)
{
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
MinValue = 0,
MaxValue = 10,
Precision = 0.1f,
}
},
overallDifficultySlider = new LabelledSliderBar<float>
{
Label = "Overall Difficulty",
Description = "The harshness of hit windows and difficulty of special objects (ie. spinners)",
Current = new BindableFloat(Beatmap.Value.BeatmapInfo.BaseDifficulty.OverallDifficulty)
{
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
MinValue = 0,
MaxValue = 10,
Precision = 0.1f,
}
},
};
foreach (var item in Children.OfType<LabelledSliderBar<float>>())
item.Current.ValueChanged += onValueChanged;
}
private void onValueChanged(ValueChangedEvent<float> args)
{
// for now, update these on commit rather than making BeatmapMetadata bindables.
// after switching database engines we can reconsider if switching to bindables is a good direction.
Beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize = circleSizeSlider.Current.Value;
Beatmap.Value.BeatmapInfo.BaseDifficulty.DrainRate = healthDrainSlider.Current.Value;
Beatmap.Value.BeatmapInfo.BaseDifficulty.ApproachRate = approachRateSlider.Current.Value;
Beatmap.Value.BeatmapInfo.BaseDifficulty.OverallDifficulty = overallDifficultySlider.Current.Value;
editorBeatmap.UpdateBeatmap();
}
}
}

View File

@ -0,0 +1,73 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.IO;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events;
using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2;
namespace osu.Game.Screens.Edit.Setup
{
/// <summary>
/// A labelled textbox which reveals an inline file chooser when clicked.
/// </summary>
internal class FileChooserLabelledTextBox : LabelledTextBox
{
public Container Target;
private readonly IBindable<FileInfo> currentFile = new Bindable<FileInfo>();
public FileChooserLabelledTextBox()
{
currentFile.BindValueChanged(onFileSelected);
}
private void onFileSelected(ValueChangedEvent<FileInfo> file)
{
if (file.NewValue == null)
return;
Target.Clear();
Current.Value = file.NewValue.FullName;
}
protected override OsuTextBox CreateTextBox() =>
new FileChooserOsuTextBox
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X,
CornerRadius = CORNER_RADIUS,
OnFocused = DisplayFileChooser
};
public void DisplayFileChooser()
{
Target.Child = new FileSelector(validFileExtensions: ResourcesSection.AudioExtensions)
{
RelativeSizeAxes = Axes.X,
Height = 400,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
CurrentFile = { BindTarget = currentFile }
};
}
internal class FileChooserOsuTextBox : OsuTextBox
{
public Action OnFocused;
protected override void OnFocus(FocusEvent e)
{
OnFocused?.Invoke();
base.OnFocus(e);
GetContainingInputManager().TriggerFocusContention(this);
}
}
}
}

View File

@ -0,0 +1,71 @@
// 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.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterfaceV2;
namespace osu.Game.Screens.Edit.Setup
{
internal class MetadataSection : SetupSection
{
private LabelledTextBox artistTextBox;
private LabelledTextBox titleTextBox;
private LabelledTextBox creatorTextBox;
private LabelledTextBox difficultyTextBox;
[BackgroundDependencyLoader]
private void load()
{
Children = new Drawable[]
{
new OsuSpriteText
{
Text = "Beatmap metadata"
},
artistTextBox = new LabelledTextBox
{
Label = "Artist",
Current = { Value = Beatmap.Value.Metadata.Artist },
TabbableContentContainer = this
},
titleTextBox = new LabelledTextBox
{
Label = "Title",
Current = { Value = Beatmap.Value.Metadata.Title },
TabbableContentContainer = this
},
creatorTextBox = new LabelledTextBox
{
Label = "Creator",
Current = { Value = Beatmap.Value.Metadata.AuthorString },
TabbableContentContainer = this
},
difficultyTextBox = new LabelledTextBox
{
Label = "Difficulty Name",
Current = { Value = Beatmap.Value.BeatmapInfo.Version },
TabbableContentContainer = this
},
};
foreach (var item in Children.OfType<LabelledTextBox>())
item.OnCommit += onCommit;
}
private void onCommit(TextBox sender, bool newText)
{
if (!newText) return;
// for now, update these on commit rather than making BeatmapMetadata bindables.
// after switching database engines we can reconsider if switching to bindables is a good direction.
Beatmap.Value.Metadata.Artist = artistTextBox.Current.Value;
Beatmap.Value.Metadata.Title = titleTextBox.Current.Value;
Beatmap.Value.Metadata.AuthorString = creatorTextBox.Current.Value;
Beatmap.Value.BeatmapInfo.Version = difficultyTextBox.Current.Value;
}
}
}

View File

@ -0,0 +1,211 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Database;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Overlays;
namespace osu.Game.Screens.Edit.Setup
{
internal class ResourcesSection : SetupSection, ICanAcceptFiles
{
private LabelledTextBox audioTrackTextBox;
private Container backgroundSpriteContainer;
public IEnumerable<string> HandledExtensions => ImageExtensions.Concat(AudioExtensions);
public static string[] ImageExtensions { get; } = { ".jpg", ".jpeg", ".png" };
public static string[] AudioExtensions { get; } = { ".mp3", ".ogg" };
[Resolved]
private OsuGameBase game { get; set; }
[Resolved]
private MusicController music { get; set; }
[Resolved]
private BeatmapManager beatmaps { get; set; }
[Resolved(canBeNull: true)]
private Editor editor { get; set; }
[BackgroundDependencyLoader]
private void load()
{
Container audioTrackFileChooserContainer = new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
};
Children = new Drawable[]
{
backgroundSpriteContainer = new Container
{
RelativeSizeAxes = Axes.X,
Height = 250,
Masking = true,
CornerRadius = 10,
},
new OsuSpriteText
{
Text = "Resources"
},
audioTrackTextBox = new FileChooserLabelledTextBox
{
Label = "Audio Track",
Current = { Value = Beatmap.Value.Metadata.AudioFile ?? "Click to select a track" },
Target = audioTrackFileChooserContainer,
TabbableContentContainer = this
},
audioTrackFileChooserContainer,
};
updateBackgroundSprite();
audioTrackTextBox.Current.BindValueChanged(audioTrackChanged);
}
Task ICanAcceptFiles.Import(params string[] paths)
{
Schedule(() =>
{
var firstFile = new FileInfo(paths.First());
if (ImageExtensions.Contains(firstFile.Extension))
{
ChangeBackgroundImage(firstFile.FullName);
}
else if (AudioExtensions.Contains(firstFile.Extension))
{
audioTrackTextBox.Text = firstFile.FullName;
}
});
return Task.CompletedTask;
}
protected override void LoadComplete()
{
base.LoadComplete();
game.RegisterImportHandler(this);
}
public bool ChangeBackgroundImage(string path)
{
var info = new FileInfo(path);
if (!info.Exists)
return false;
var set = Beatmap.Value.BeatmapSetInfo;
// remove the previous background for now.
// in the future we probably want to check if this is being used elsewhere (other difficulties?)
var oldFile = set.Files.FirstOrDefault(f => f.Filename == Beatmap.Value.Metadata.BackgroundFile);
using (var stream = info.OpenRead())
{
if (oldFile != null)
beatmaps.ReplaceFile(set, oldFile, stream, info.Name);
else
beatmaps.AddFile(set, stream, info.Name);
}
Beatmap.Value.Metadata.BackgroundFile = info.Name;
updateBackgroundSprite();
return true;
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
game?.UnregisterImportHandler(this);
}
public bool ChangeAudioTrack(string path)
{
var info = new FileInfo(path);
if (!info.Exists)
return false;
var set = Beatmap.Value.BeatmapSetInfo;
// remove the previous audio track for now.
// in the future we probably want to check if this is being used elsewhere (other difficulties?)
var oldFile = set.Files.FirstOrDefault(f => f.Filename == Beatmap.Value.Metadata.AudioFile);
using (var stream = info.OpenRead())
{
if (oldFile != null)
beatmaps.ReplaceFile(set, oldFile, stream, info.Name);
else
beatmaps.AddFile(set, stream, info.Name);
}
Beatmap.Value.Metadata.AudioFile = info.Name;
music.ReloadCurrentTrack();
editor?.UpdateClockSource();
return true;
}
private void audioTrackChanged(ValueChangedEvent<string> filePath)
{
if (!ChangeAudioTrack(filePath.NewValue))
audioTrackTextBox.Current.Value = filePath.OldValue;
}
private void updateBackgroundSprite()
{
LoadComponentAsync(new BeatmapBackgroundSprite(Beatmap.Value)
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
FillMode = FillMode.Fill,
}, background =>
{
if (background.Texture != null)
backgroundSpriteContainer.Child = background;
else
{
backgroundSpriteContainer.Children = new Drawable[]
{
new Box
{
Colour = Colours.GreySeafoamDarker,
RelativeSizeAxes = Axes.Both,
},
new OsuTextFlowContainer(t => t.Font = OsuFont.Default.With(size: 24))
{
Text = "Drag image here to set beatmap background!",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.X,
}
};
}
background.FadeInFromZero(500);
});
}
}
}

View File

@ -1,73 +1,33 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables;
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.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Database;
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.UserInterface;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Overlays; using osu.Game.Overlays;
using osuTK;
namespace osu.Game.Screens.Edit.Setup namespace osu.Game.Screens.Edit.Setup
{ {
public class SetupScreen : EditorScreen, ICanAcceptFiles public class SetupScreen : EditorScreen
{ {
public IEnumerable<string> HandledExtensions => ImageExtensions.Concat(AudioExtensions);
public static string[] ImageExtensions { get; } = { ".jpg", ".jpeg", ".png" };
public static string[] AudioExtensions { get; } = { ".mp3", ".ogg" };
private FillFlowContainer flow;
private LabelledTextBox artistTextBox;
private LabelledTextBox titleTextBox;
private LabelledTextBox creatorTextBox;
private LabelledTextBox difficultyTextBox;
private LabelledTextBox audioTrackTextBox;
private Container backgroundSpriteContainer;
[Resolved] [Resolved]
private OsuGameBase game { get; set; } private OsuColour colours { get; set; }
[Resolved] [Cached]
private MusicController music { get; set; } protected readonly OverlayColourProvider ColourProvider;
[Resolved]
private BeatmapManager beatmaps { get; set; }
[Resolved(canBeNull: true)]
private Editor editor { get; set; }
public SetupScreen() public SetupScreen()
: base(EditorScreenMode.SongSetup) : base(EditorScreenMode.SongSetup)
{ {
ColourProvider = new OverlayColourProvider(OverlayColourScheme.Green);
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load()
{ {
Container audioTrackFileChooserContainer = new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
};
Child = new Container Child = new Container
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
@ -84,253 +44,34 @@ namespace osu.Game.Screens.Edit.Setup
Colour = colours.GreySeafoamDark, Colour = colours.GreySeafoamDark,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
}, },
new OsuScrollContainer new SectionsContainer<SetupSection>
{ {
FixedHeader = new SetupScreenHeader(),
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(10), Children = new SetupSection[]
Child = flow = new FillFlowContainer
{ {
RelativeSizeAxes = Axes.X, new ResourcesSection(),
AutoSizeAxes = Axes.Y, new MetadataSection(),
Spacing = new Vector2(20), new DifficultySection(),
Direction = FillDirection.Vertical, }
Children = new Drawable[]
{
backgroundSpriteContainer = new Container
{
RelativeSizeAxes = Axes.X,
Height = 250,
Masking = true,
CornerRadius = 10,
},
new OsuSpriteText
{
Text = "Resources"
},
audioTrackTextBox = new FileChooserLabelledTextBox
{
Label = "Audio Track",
Current = { Value = Beatmap.Value.Metadata.AudioFile ?? "Click to select a track" },
Target = audioTrackFileChooserContainer,
TabbableContentContainer = this
},
audioTrackFileChooserContainer,
new OsuSpriteText
{
Text = "Beatmap metadata"
},
artistTextBox = new LabelledTextBox
{
Label = "Artist",
Current = { Value = Beatmap.Value.Metadata.Artist },
TabbableContentContainer = this
},
titleTextBox = new LabelledTextBox
{
Label = "Title",
Current = { Value = Beatmap.Value.Metadata.Title },
TabbableContentContainer = this
},
creatorTextBox = new LabelledTextBox
{
Label = "Creator",
Current = { Value = Beatmap.Value.Metadata.AuthorString },
TabbableContentContainer = this
},
difficultyTextBox = new LabelledTextBox
{
Label = "Difficulty Name",
Current = { Value = Beatmap.Value.BeatmapInfo.Version },
TabbableContentContainer = this
},
}
},
}, },
} }
} }
}; };
updateBackgroundSprite();
audioTrackTextBox.Current.BindValueChanged(audioTrackChanged);
foreach (var item in flow.OfType<LabelledTextBox>())
item.OnCommit += onCommit;
}
Task ICanAcceptFiles.Import(params string[] paths)
{
Schedule(() =>
{
var firstFile = new FileInfo(paths.First());
if (ImageExtensions.Contains(firstFile.Extension))
{
ChangeBackgroundImage(firstFile.FullName);
}
else if (AudioExtensions.Contains(firstFile.Extension))
{
audioTrackTextBox.Text = firstFile.FullName;
}
});
return Task.CompletedTask;
}
private void updateBackgroundSprite()
{
LoadComponentAsync(new BeatmapBackgroundSprite(Beatmap.Value)
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
FillMode = FillMode.Fill,
}, background =>
{
backgroundSpriteContainer.Child = background;
background.FadeInFromZero(500);
});
}
protected override void LoadComplete()
{
base.LoadComplete();
game.RegisterImportHandler(this);
}
public bool ChangeBackgroundImage(string path)
{
var info = new FileInfo(path);
if (!info.Exists)
return false;
var set = Beatmap.Value.BeatmapSetInfo;
// remove the previous background for now.
// in the future we probably want to check if this is being used elsewhere (other difficulties?)
var oldFile = set.Files.FirstOrDefault(f => f.Filename == Beatmap.Value.Metadata.BackgroundFile);
using (var stream = info.OpenRead())
{
if (oldFile != null)
beatmaps.ReplaceFile(set, oldFile, stream, info.Name);
else
beatmaps.AddFile(set, stream, info.Name);
}
Beatmap.Value.Metadata.BackgroundFile = info.Name;
updateBackgroundSprite();
return true;
}
public bool ChangeAudioTrack(string path)
{
var info = new FileInfo(path);
if (!info.Exists)
return false;
var set = Beatmap.Value.BeatmapSetInfo;
// remove the previous audio track for now.
// in the future we probably want to check if this is being used elsewhere (other difficulties?)
var oldFile = set.Files.FirstOrDefault(f => f.Filename == Beatmap.Value.Metadata.AudioFile);
using (var stream = info.OpenRead())
{
if (oldFile != null)
beatmaps.ReplaceFile(set, oldFile, stream, info.Name);
else
beatmaps.AddFile(set, stream, info.Name);
}
Beatmap.Value.Metadata.AudioFile = info.Name;
music.ReloadCurrentTrack();
editor?.UpdateClockSource();
return true;
}
private void audioTrackChanged(ValueChangedEvent<string> filePath)
{
if (!ChangeAudioTrack(filePath.NewValue))
audioTrackTextBox.Current.Value = filePath.OldValue;
}
private void onCommit(TextBox sender, bool newText)
{
if (!newText) return;
// for now, update these on commit rather than making BeatmapMetadata bindables.
// after switching database engines we can reconsider if switching to bindables is a good direction.
Beatmap.Value.Metadata.Artist = artistTextBox.Current.Value;
Beatmap.Value.Metadata.Title = titleTextBox.Current.Value;
Beatmap.Value.Metadata.AuthorString = creatorTextBox.Current.Value;
Beatmap.Value.BeatmapInfo.Version = difficultyTextBox.Current.Value;
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
game?.UnregisterImportHandler(this);
} }
} }
internal class FileChooserLabelledTextBox : LabelledTextBox internal class SetupScreenHeader : OverlayHeader
{ {
public Container Target; protected override OverlayTitle CreateTitle() => new SetupScreenTitle();
private readonly IBindable<FileInfo> currentFile = new Bindable<FileInfo>(); private class SetupScreenTitle : OverlayTitle
public FileChooserLabelledTextBox()
{ {
currentFile.BindValueChanged(onFileSelected); public SetupScreenTitle()
}
private void onFileSelected(ValueChangedEvent<FileInfo> file)
{
if (file.NewValue == null)
return;
Target.Clear();
Current.Value = file.NewValue.FullName;
}
protected override OsuTextBox CreateTextBox() =>
new FileChooserOsuTextBox
{ {
Anchor = Anchor.Centre, Title = "beatmap setup";
Origin = Anchor.Centre, Description = "change general settings of your beatmap";
RelativeSizeAxes = Axes.X, IconTexture = "Icons/Hexacons/social";
CornerRadius = CORNER_RADIUS,
OnFocused = DisplayFileChooser
};
public void DisplayFileChooser()
{
Target.Child = new FileSelector(validFileExtensions: SetupScreen.AudioExtensions)
{
RelativeSizeAxes = Axes.X,
Height = 400,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
CurrentFile = { BindTarget = currentFile }
};
}
internal class FileChooserOsuTextBox : OsuTextBox
{
public Action OnFocused;
protected override void OnFocus(FocusEvent e)
{
OnFocused?.Invoke();
base.OnFocus(e);
GetContainingInputManager().TriggerFocusContention(this);
} }
} }
} }

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.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osuTK;
namespace osu.Game.Screens.Edit.Setup
{
internal class SetupSection : Container
{
private readonly FillFlowContainer flow;
[Resolved]
protected OsuColour Colours { get; private set; }
[Resolved]
protected IBindable<WorkingBeatmap> Beatmap { get; private set; }
protected override Container<Drawable> Content => flow;
public SetupSection()
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
Padding = new MarginPadding(10);
InternalChild = flow = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(20),
Direction = FillDirection.Vertical,
};
}
}
}

View File

@ -113,7 +113,6 @@ namespace osu.Game.Screens.Edit.Timing
}; };
controlPoints = group.ControlPoints.GetBoundCopy(); controlPoints = group.ControlPoints.GetBoundCopy();
controlPoints.CollectionChanged += (_, __) => createChildren();
} }
[Resolved] [Resolved]
@ -125,6 +124,12 @@ namespace osu.Game.Screens.Edit.Timing
createChildren(); createChildren();
} }
protected override void LoadComplete()
{
base.LoadComplete();
controlPoints.CollectionChanged += (_, __) => createChildren();
}
private void createChildren() private void createChildren()
{ {
fill.ChildrenEnumerable = controlPoints.Select(createAttribute).Where(c => c != null); fill.ChildrenEnumerable = controlPoints.Select(createAttribute).Where(c => c != null);

View File

@ -111,7 +111,8 @@ namespace osu.Game.Screens.Edit.Timing
foreach (var cp in currentGroupItems) foreach (var cp in currentGroupItems)
Beatmap.Value.Beatmap.ControlPointInfo.Add(time, cp); Beatmap.Value.Beatmap.ControlPointInfo.Add(time, cp);
SelectedGroup.Value = Beatmap.Value.Beatmap.ControlPointInfo.GroupAt(time); // the control point might not necessarily exist yet, if currentGroupItems was empty.
SelectedGroup.Value = Beatmap.Value.Beatmap.ControlPointInfo.GroupAt(time, true);
changeHandler?.EndChange(); changeHandler?.EndChange();
} }

View File

@ -11,7 +11,7 @@ using osu.Game.Overlays.Settings;
namespace osu.Game.Screens.Edit.Timing namespace osu.Game.Screens.Edit.Timing
{ {
internal class SliderWithTextBoxInput<T> : CompositeDrawable, IHasCurrentValue<T> public class SliderWithTextBoxInput<T> : CompositeDrawable, IHasCurrentValue<T>
where T : struct, IEquatable<T>, IComparable<T>, IConvertible where T : struct, IEquatable<T>, IComparable<T>, IConvertible
{ {
private readonly SettingsSlider<T> slider; private readonly SettingsSlider<T> slider;
@ -51,7 +51,7 @@ namespace osu.Game.Screens.Edit.Timing
try try
{ {
slider.Bindable.Parse(t.Text); slider.Current.Parse(t.Text);
} }
catch catch
{ {
@ -71,8 +71,8 @@ namespace osu.Game.Screens.Edit.Timing
public Bindable<T> Current public Bindable<T> Current
{ {
get => slider.Bindable; get => slider.Current;
set => slider.Bindable = value; set => slider.Current = value;
} }
} }
} }

View File

@ -36,14 +36,14 @@ namespace osu.Game.Screens.Edit.Timing
{ {
if (point.NewValue != null) if (point.NewValue != null)
{ {
bpmSlider.Bindable = point.NewValue.BeatLengthBindable; bpmSlider.Current = point.NewValue.BeatLengthBindable;
bpmSlider.Bindable.BindValueChanged(_ => ChangeHandler?.SaveState()); bpmSlider.Current.BindValueChanged(_ => ChangeHandler?.SaveState());
bpmTextEntry.Bindable = point.NewValue.BeatLengthBindable; bpmTextEntry.Bindable = point.NewValue.BeatLengthBindable;
// no need to hook change handler here as it's the same bindable as above // no need to hook change handler here as it's the same bindable as above
timeSignature.Bindable = point.NewValue.TimeSignatureBindable; timeSignature.Current = point.NewValue.TimeSignatureBindable;
timeSignature.Bindable.BindValueChanged(_ => ChangeHandler?.SaveState()); timeSignature.Current.BindValueChanged(_ => ChangeHandler?.SaveState());
} }
} }
@ -121,14 +121,14 @@ namespace osu.Game.Screens.Edit.Timing
beatLengthBindable.BindValueChanged(beatLength => updateCurrent(beatLengthToBpm(beatLength.NewValue)), true); beatLengthBindable.BindValueChanged(beatLength => updateCurrent(beatLengthToBpm(beatLength.NewValue)), true);
bpmBindable.BindValueChanged(bpm => beatLengthBindable.Value = beatLengthToBpm(bpm.NewValue)); bpmBindable.BindValueChanged(bpm => beatLengthBindable.Value = beatLengthToBpm(bpm.NewValue));
base.Bindable = bpmBindable; base.Current = bpmBindable;
TransferValueOnCommit = true; TransferValueOnCommit = true;
} }
public override Bindable<double> Bindable public override Bindable<double> Current
{ {
get => base.Bindable; get => base.Current;
set set
{ {
// incoming will be beat length, not bpm // incoming will be beat length, not bpm

View File

@ -42,8 +42,11 @@ namespace osu.Game.Screens.Menu
ValidForResume = false; ValidForResume = false;
} }
[Resolved]
private IAPIProvider api { get; set; }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours, IAPIProvider api) private void load(OsuColour colours)
{ {
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
@ -104,7 +107,9 @@ namespace osu.Game.Screens.Menu
iconColour = colours.Yellow; iconColour = colours.Yellow;
currentUser.BindTo(api.LocalUser); // manually transfer the user once, but only do the final bind in LoadComplete to avoid thread woes (API scheduler could run while this screen is still loading).
// the manual transfer is here to ensure all text content is loaded ahead of time as this is very early in the game load process and we want to avoid stutters.
currentUser.Value = api.LocalUser.Value;
currentUser.BindValueChanged(e => currentUser.BindValueChanged(e =>
{ {
supportFlow.Children.ForEach(d => d.FadeOut().Expire()); supportFlow.Children.ForEach(d => d.FadeOut().Expire());
@ -141,6 +146,8 @@ namespace osu.Game.Screens.Menu
base.LoadComplete(); base.LoadComplete();
if (nextScreen != null) if (nextScreen != null)
LoadComponentAsync(nextScreen); LoadComponentAsync(nextScreen);
currentUser.BindTo(api.LocalUser);
} }
public override void OnEntering(IScreen last) public override void OnEntering(IScreen last)

View File

@ -68,6 +68,8 @@ namespace osu.Game.Screens.Play
private readonly Bindable<bool> storyboardReplacesBackground = new Bindable<bool>(); private readonly Bindable<bool> storyboardReplacesBackground = new Bindable<bool>();
protected readonly Bindable<bool> LocalUserPlaying = new Bindable<bool>();
public int RestartCount; public int RestartCount;
[Resolved] [Resolved]
@ -155,8 +157,8 @@ namespace osu.Game.Screens.Play
DrawableRuleset.SetRecordTarget(recordingReplay = new Replay()); DrawableRuleset.SetRecordTarget(recordingReplay = new Replay());
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader(true)]
private void load(AudioManager audio, OsuConfigManager config) private void load(AudioManager audio, OsuConfigManager config, OsuGame game)
{ {
Mods.Value = base.Mods.Value.Select(m => m.CreateCopy()).ToArray(); Mods.Value = base.Mods.Value.Select(m => m.CreateCopy()).ToArray();
@ -172,6 +174,9 @@ namespace osu.Game.Screens.Play
mouseWheelDisabled = config.GetBindable<bool>(OsuSetting.MouseDisableWheel); mouseWheelDisabled = config.GetBindable<bool>(OsuSetting.MouseDisableWheel);
if (game != null)
LocalUserPlaying.BindTo(game.LocalUserPlaying);
DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value); DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value);
ScoreProcessor = ruleset.CreateScoreProcessor(); ScoreProcessor = ruleset.CreateScoreProcessor();
@ -219,9 +224,9 @@ namespace osu.Game.Screens.Play
skipOverlay.Hide(); skipOverlay.Hide();
} }
DrawableRuleset.IsPaused.BindValueChanged(_ => updateOverlayActivationMode()); DrawableRuleset.IsPaused.BindValueChanged(_ => updateGameplayState());
DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updateOverlayActivationMode()); DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updateGameplayState());
breakTracker.IsBreakTime.BindValueChanged(_ => updateOverlayActivationMode()); breakTracker.IsBreakTime.BindValueChanged(_ => updateGameplayState());
DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true); DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true);
@ -353,14 +358,11 @@ namespace osu.Game.Screens.Play
HUDOverlay.KeyCounter.IsCounting = !isBreakTime.NewValue; HUDOverlay.KeyCounter.IsCounting = !isBreakTime.NewValue;
} }
private void updateOverlayActivationMode() private void updateGameplayState()
{ {
bool canTriggerOverlays = DrawableRuleset.IsPaused.Value || breakTracker.IsBreakTime.Value; bool inGameplay = !DrawableRuleset.HasReplayLoaded.Value && !DrawableRuleset.IsPaused.Value && !breakTracker.IsBreakTime.Value;
OverlayActivationMode.Value = inGameplay ? OverlayActivation.Disabled : OverlayActivation.UserTriggered;
if (DrawableRuleset.HasReplayLoaded.Value || canTriggerOverlays) LocalUserPlaying.Value = inGameplay;
OverlayActivationMode.Value = OverlayActivation.UserTriggered;
else
OverlayActivationMode.Value = OverlayActivation.Disabled;
} }
private void updatePauseOnFocusLostState() => private void updatePauseOnFocusLostState() =>
@ -441,6 +443,10 @@ namespace osu.Game.Screens.Play
/// </summary> /// </summary>
public void Restart() public void Restart()
{ {
// at the point of restarting the track should either already be paused or the volume should be zero.
// stopping here is to ensure music doesn't become audible after exiting back to PlayerLoader.
musicController.Stop();
sampleRestart?.Play(); sampleRestart?.Play();
RestartRequested?.Invoke(); RestartRequested?.Invoke();
@ -657,7 +663,7 @@ namespace osu.Game.Screens.Play
foreach (var mod in Mods.Value.OfType<IApplicableToTrack>()) foreach (var mod in Mods.Value.OfType<IApplicableToTrack>())
mod.ApplyToTrack(musicController.CurrentTrack); mod.ApplyToTrack(musicController.CurrentTrack);
updateOverlayActivationMode(); updateGameplayState();
} }
public override void OnSuspending(IScreen next) public override void OnSuspending(IScreen next)

View File

@ -51,14 +51,14 @@ namespace osu.Game.Screens.Play.PlayerSettings
} }
}, },
}, },
rateSlider = new PlayerSliderBar<double> { Bindable = UserPlaybackRate } rateSlider = new PlayerSliderBar<double> { Current = UserPlaybackRate }
}; };
} }
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
rateSlider.Bindable.BindValueChanged(multiplier => multiplierText.Text = $"{multiplier.NewValue:0.0}x", true); rateSlider.Current.BindValueChanged(multiplier => multiplierText.Text = $"{multiplier.NewValue:0.0}x", true);
} }
} }
} }

View File

@ -50,8 +50,8 @@ namespace osu.Game.Screens.Play.PlayerSettings
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuConfigManager config) private void load(OsuConfigManager config)
{ {
dimSliderBar.Bindable = config.GetBindable<double>(OsuSetting.DimLevel); dimSliderBar.Current = config.GetBindable<double>(OsuSetting.DimLevel);
blurSliderBar.Bindable = config.GetBindable<double>(OsuSetting.BlurLevel); blurSliderBar.Current = config.GetBindable<double>(OsuSetting.BlurLevel);
showStoryboardToggle.Current = config.GetBindable<bool>(OsuSetting.ShowStoryboard); showStoryboardToggle.Current = config.GetBindable<bool>(OsuSetting.ShowStoryboard);
beatmapSkinsToggle.Current = config.GetBindable<bool>(OsuSetting.BeatmapSkins); beatmapSkinsToggle.Current = config.GetBindable<bool>(OsuSetting.BeatmapSkins);
beatmapHitsoundsToggle.Current = config.GetBindable<bool>(OsuSetting.BeatmapHitsounds); beatmapHitsoundsToggle.Current = config.GetBindable<bool>(OsuSetting.BeatmapHitsounds);

View File

@ -84,6 +84,11 @@ namespace osu.Game.Updater
bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".AppImage")); bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".AppImage"));
break; break;
case RuntimeInfo.Platform.iOS:
// iOS releases are available via testflight. this link seems to work well enough for now.
// see https://stackoverflow.com/a/32960501
return "itms-beta://beta.itunes.apple.com/v1/app/1447765923";
case RuntimeInfo.Platform.Android: case RuntimeInfo.Platform.Android:
// on our testing device this causes the download to magically disappear. // on our testing device this causes the download to magically disappear.
//bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".apk")); //bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".apk"));

View File

@ -11,5 +11,7 @@ namespace osu.iOS
public class OsuGameIOS : OsuGame public class OsuGameIOS : OsuGame
{ {
public override Version AssemblyVersion => new Version(NSBundle.MainBundle.InfoDictionary["CFBundleVersion"].ToString()); public override Version AssemblyVersion => new Version(NSBundle.MainBundle.InfoDictionary["CFBundleVersion"].ToString());
protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager();
} }
} }