mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 16:02:55 +08:00
Merge branch 'master' into update-check-improvements
This commit is contained in:
commit
c867fb4985
@ -145,7 +145,7 @@ namespace osu.Game.Rulesets.Catch
|
||||
|
||||
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;
|
||||
|
||||
|
@ -5,7 +5,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
@ -25,8 +24,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
private int tinyTicksMissed;
|
||||
private int misses;
|
||||
|
||||
public CatchPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, ScoreInfo score)
|
||||
: base(ruleset, beatmap, score)
|
||||
public CatchPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score)
|
||||
: base(ruleset, attributes, score)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
@ -29,8 +28,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
private int countMeh;
|
||||
private int countMiss;
|
||||
|
||||
public ManiaPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, ScoreInfo score)
|
||||
: base(ruleset, beatmap, score)
|
||||
public ManiaPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score)
|
||||
: base(ruleset, attributes, score)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Mania
|
||||
|
||||
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";
|
||||
|
||||
|
@ -29,12 +29,12 @@ namespace osu.Game.Rulesets.Mania
|
||||
new SettingsEnumDropdown<ManiaScrollingDirection>
|
||||
{
|
||||
LabelText = "Scrolling direction",
|
||||
Bindable = config.GetBindable<ManiaScrollingDirection>(ManiaRulesetSetting.ScrollDirection)
|
||||
Current = config.GetBindable<ManiaScrollingDirection>(ManiaRulesetSetting.ScrollDirection)
|
||||
},
|
||||
new SettingsSlider<double, TimeSlider>
|
||||
{
|
||||
LabelText = "Scroll speed",
|
||||
Bindable = config.GetBindable<double>(ManiaRulesetSetting.ScrollTime),
|
||||
Current = config.GetBindable<double>(ManiaRulesetSetting.ScrollTime),
|
||||
KeyboardStep = 5
|
||||
},
|
||||
};
|
||||
|
@ -11,5 +11,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
public double SpeedStrain;
|
||||
public double ApproachRate;
|
||||
public double OverallDifficulty;
|
||||
public int HitCircleCount;
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
maxCombo += beatmap.HitObjects.OfType<Slider>().Sum(s => s.NestedHitObjects.Count - 1);
|
||||
|
||||
int hitCirclesCount = beatmap.HitObjects.Count(h => h is HitCircle);
|
||||
|
||||
return new OsuDifficultyAttributes
|
||||
{
|
||||
StarRating = starRating,
|
||||
@ -56,6 +58,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5,
|
||||
OverallDifficulty = (80 - hitWindowGreat) / 6,
|
||||
MaxCombo = maxCombo,
|
||||
HitCircleCount = hitCirclesCount,
|
||||
Skills = skills
|
||||
};
|
||||
}
|
||||
|
@ -5,11 +5,9 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
|
||||
@ -19,9 +17,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
{
|
||||
public new OsuDifficultyAttributes Attributes => (OsuDifficultyAttributes)base.Attributes;
|
||||
|
||||
private readonly int countHitCircles;
|
||||
private readonly int beatmapMaxCombo;
|
||||
|
||||
private Mod[] mods;
|
||||
|
||||
private double accuracy;
|
||||
@ -31,14 +26,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
private int countMeh;
|
||||
private int countMiss;
|
||||
|
||||
public OsuPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, ScoreInfo score)
|
||||
: base(ruleset, beatmap, score)
|
||||
public OsuPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo 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)
|
||||
@ -81,7 +71,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
categoryRatings.Add("Accuracy", accuracyValue);
|
||||
categoryRatings.Add("OD", Attributes.OverallDifficulty);
|
||||
categoryRatings.Add("AR", Attributes.ApproachRate);
|
||||
categoryRatings.Add("Max Combo", beatmapMaxCombo);
|
||||
categoryRatings.Add("Max Combo", Attributes.MaxCombo);
|
||||
}
|
||||
|
||||
return totalValue;
|
||||
@ -106,8 +96,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
aimValue *= Math.Pow(0.97, countMiss);
|
||||
|
||||
// Combo scaling
|
||||
if (beatmapMaxCombo > 0)
|
||||
aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(beatmapMaxCombo, 0.8), 1.0);
|
||||
if (Attributes.MaxCombo > 0)
|
||||
aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
|
||||
|
||||
double approachRateFactor = 1.0;
|
||||
|
||||
@ -154,8 +144,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
speedValue *= Math.Pow(0.97, countMiss);
|
||||
|
||||
// Combo scaling
|
||||
if (beatmapMaxCombo > 0)
|
||||
speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(beatmapMaxCombo, 0.8), 1.0);
|
||||
if (Attributes.MaxCombo > 0)
|
||||
speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
|
||||
|
||||
double approachRateFactor = 1.0;
|
||||
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
|
||||
double betterAccuracyPercentage;
|
||||
int amountHitObjectsWithAccuracy = countHitCircles;
|
||||
int amountHitObjectsWithAccuracy = Attributes.HitCircleCount;
|
||||
|
||||
if (amountHitObjectsWithAccuracy > 0)
|
||||
betterAccuracyPercentage = ((countGreat - (totalHits - amountHitObjectsWithAccuracy)) * 6 + countOk * 2 + countMeh) / (double)(amountHitObjectsWithAccuracy * 6);
|
||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
Masking = true;
|
||||
BorderThickness = 10;
|
||||
BorderThickness = 9; // roughly matches slider borders and makes stacked circles distinctly visible from each other.
|
||||
BorderColour = Color4.White;
|
||||
|
||||
Child = new Box
|
||||
|
@ -171,7 +171,7 @@ namespace osu.Game.Rulesets.Osu
|
||||
|
||||
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);
|
||||
|
||||
|
@ -49,6 +49,23 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
{
|
||||
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[]
|
||||
{
|
||||
circleSprites = new Container<Sprite>
|
||||
@ -60,13 +77,13 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
{
|
||||
hitCircleSprite = new Sprite
|
||||
{
|
||||
Texture = getTextureWithFallback(string.Empty),
|
||||
Texture = baseTexture,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
hitCircleOverlay = new Sprite
|
||||
{
|
||||
Texture = getTextureWithFallback("overlay"),
|
||||
Texture = overlayTexture,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
}
|
||||
@ -101,8 +118,13 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
Texture tex = null;
|
||||
|
||||
if (!string.IsNullOrEmpty(priorityLookup))
|
||||
{
|
||||
tex = skin.GetTexture($"{priorityLookup}{name}");
|
||||
|
||||
if (!allowFallback)
|
||||
return tex;
|
||||
}
|
||||
|
||||
return tex ?? skin.GetTexture($"hitcircle{name}");
|
||||
}
|
||||
}
|
||||
|
@ -27,17 +27,17 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Snaking in sliders",
|
||||
Bindable = config.GetBindable<bool>(OsuRulesetSetting.SnakingInSliders)
|
||||
Current = config.GetBindable<bool>(OsuRulesetSetting.SnakingInSliders)
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Snaking out sliders",
|
||||
Bindable = config.GetBindable<bool>(OsuRulesetSetting.SnakingOutSliders)
|
||||
Current = config.GetBindable<bool>(OsuRulesetSetting.SnakingOutSliders)
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Cursor trail",
|
||||
Bindable = config.GetBindable<bool>(OsuRulesetSetting.ShowCursorTrail)
|
||||
Current = config.GetBindable<bool>(OsuRulesetSetting.ShowCursorTrail)
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
@ -24,8 +23,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
private int countMeh;
|
||||
private int countMiss;
|
||||
|
||||
public TaikoPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, ScoreInfo score)
|
||||
: base(ruleset, beatmap, score)
|
||||
public TaikoPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score)
|
||||
: base(ruleset, attributes, score)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Taiko.UI;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
@ -14,13 +15,29 @@ namespace osu.Game.Rulesets.Taiko.Skinning
|
||||
{
|
||||
public class TaikoLegacySkinTransformer : LegacySkinTransformer
|
||||
{
|
||||
private Lazy<bool> hasExplosion;
|
||||
|
||||
public TaikoLegacySkinTransformer(ISkinSource 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)
|
||||
{
|
||||
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))
|
||||
return null;
|
||||
|
||||
@ -87,10 +104,13 @@ namespace osu.Game.Rulesets.Taiko.Skinning
|
||||
|
||||
var hitName = getHitName(taikoComponent.Component);
|
||||
var hitSprite = this.GetAnimation(hitName, true, false);
|
||||
var strongHitSprite = this.GetAnimation($"{hitName}k", true, false);
|
||||
|
||||
if (hitSprite != null)
|
||||
{
|
||||
var strongHitSprite = this.GetAnimation($"{hitName}k", true, false);
|
||||
|
||||
return new LegacyHitExplosion(hitSprite, strongHitSprite);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
|
@ -153,7 +153,7 @@ namespace osu.Game.Rulesets.Taiko
|
||||
|
||||
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;
|
||||
|
||||
|
@ -44,7 +44,7 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 1000 }
|
||||
new HitCircle { StartTime = 1000, NewCombo = true }
|
||||
}
|
||||
};
|
||||
|
||||
@ -56,7 +56,7 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
current.AddRange(new[]
|
||||
{
|
||||
new HitCircle { StartTime = 1000 },
|
||||
new HitCircle { StartTime = 1000, NewCombo = true },
|
||||
new HitCircle { StartTime = 3000 },
|
||||
});
|
||||
|
||||
@ -78,7 +78,7 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
current.AddRange(new[]
|
||||
{
|
||||
new HitCircle { StartTime = 1000 },
|
||||
new HitCircle { StartTime = 1000, NewCombo = true },
|
||||
new HitCircle { StartTime = 2000 },
|
||||
new HitCircle { StartTime = 3000 },
|
||||
});
|
||||
@ -100,7 +100,7 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
current.AddRange(new[]
|
||||
{
|
||||
new HitCircle { StartTime = 1000 },
|
||||
new HitCircle { StartTime = 1000, NewCombo = true },
|
||||
new HitCircle { StartTime = 2000 },
|
||||
new HitCircle { StartTime = 3000 },
|
||||
});
|
||||
@ -109,7 +109,7 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 500 },
|
||||
new HitCircle { StartTime = 500, NewCombo = true },
|
||||
(OsuHitObject)current.HitObjects[1],
|
||||
(OsuHitObject)current.HitObjects[2],
|
||||
}
|
||||
@ -123,7 +123,7 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
current.AddRange(new[]
|
||||
{
|
||||
new HitCircle { StartTime = 1000 },
|
||||
new HitCircle { StartTime = 1000, NewCombo = true },
|
||||
new HitCircle { StartTime = 2000 },
|
||||
new HitCircle { StartTime = 3000 },
|
||||
});
|
||||
@ -146,7 +146,7 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
current.AddRange(new OsuHitObject[]
|
||||
{
|
||||
new HitCircle { StartTime = 1000 },
|
||||
new HitCircle { StartTime = 1000, NewCombo = true },
|
||||
new Slider
|
||||
{
|
||||
StartTime = 2000,
|
||||
@ -188,7 +188,7 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
current.AddRange(new[]
|
||||
{
|
||||
new HitCircle { StartTime = 1000 },
|
||||
new HitCircle { StartTime = 1000, NewCombo = true },
|
||||
new HitCircle { StartTime = 2000 },
|
||||
new HitCircle { StartTime = 3000 },
|
||||
});
|
||||
@ -197,7 +197,7 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 500 },
|
||||
new HitCircle { StartTime = 500, NewCombo = true },
|
||||
(OsuHitObject)current.HitObjects[0],
|
||||
new HitCircle { StartTime = 1500 },
|
||||
(OsuHitObject)current.HitObjects[1],
|
||||
@ -216,7 +216,7 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
current.AddRange(new[]
|
||||
{
|
||||
new HitCircle { StartTime = 500 },
|
||||
new HitCircle { StartTime = 500, NewCombo = true },
|
||||
new HitCircle { StartTime = 1000 },
|
||||
new HitCircle { StartTime = 1500 },
|
||||
new HitCircle { StartTime = 2000 },
|
||||
@ -226,6 +226,9 @@ namespace osu.Game.Tests.Editing
|
||||
new HitCircle { StartTime = 3500 },
|
||||
});
|
||||
|
||||
var patchedFirst = (HitCircle)current.HitObjects[1];
|
||||
patchedFirst.NewCombo = true;
|
||||
|
||||
var patch = new OsuBeatmap
|
||||
{
|
||||
HitObjects =
|
||||
@ -244,7 +247,7 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
current.AddRange(new[]
|
||||
{
|
||||
new HitCircle { StartTime = 500 },
|
||||
new HitCircle { StartTime = 500, NewCombo = true },
|
||||
new HitCircle { StartTime = 1000 },
|
||||
new HitCircle { StartTime = 1500 },
|
||||
new HitCircle { StartTime = 2000 },
|
||||
@ -277,7 +280,7 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
current.AddRange(new[]
|
||||
{
|
||||
new HitCircle { StartTime = 500 },
|
||||
new HitCircle { StartTime = 500, NewCombo = true },
|
||||
new HitCircle { StartTime = 1000 },
|
||||
new HitCircle { StartTime = 1500 },
|
||||
new HitCircle { StartTime = 2000 },
|
||||
@ -291,7 +294,7 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 750 },
|
||||
new HitCircle { StartTime = 750, NewCombo = true },
|
||||
(OsuHitObject)current.HitObjects[1],
|
||||
(OsuHitObject)current.HitObjects[4],
|
||||
(OsuHitObject)current.HitObjects[5],
|
||||
@ -309,20 +312,20 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
current.AddRange(new[]
|
||||
{
|
||||
new HitCircle { StartTime = 500, Position = new Vector2(50) },
|
||||
new HitCircle { StartTime = 500, Position = new Vector2(100) },
|
||||
new HitCircle { StartTime = 500, Position = new Vector2(150) },
|
||||
new HitCircle { StartTime = 500, Position = new Vector2(200) },
|
||||
new HitCircle { StartTime = 500, Position = new Vector2(50), NewCombo = true },
|
||||
new HitCircle { StartTime = 500, Position = new Vector2(100), NewCombo = true },
|
||||
new HitCircle { StartTime = 500, Position = new Vector2(150), NewCombo = true },
|
||||
new HitCircle { StartTime = 500, Position = new Vector2(200), NewCombo = true },
|
||||
});
|
||||
|
||||
var patch = new OsuBeatmap
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 500, Position = new Vector2(150) },
|
||||
new HitCircle { StartTime = 500, Position = new Vector2(100) },
|
||||
new HitCircle { StartTime = 500, Position = new Vector2(50) },
|
||||
new HitCircle { StartTime = 500, Position = new Vector2(200) },
|
||||
new HitCircle { StartTime = 500, Position = new Vector2(150), NewCombo = true },
|
||||
new HitCircle { StartTime = 500, Position = new Vector2(100), NewCombo = true },
|
||||
new HitCircle { StartTime = 500, Position = new Vector2(50), NewCombo = true },
|
||||
new HitCircle { StartTime = 500, Position = new Vector2(200), NewCombo = true },
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
using (var zip = ZipArchive.Open(temp))
|
||||
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);
|
||||
Directory.Delete(extractedFolder, true);
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets;
|
||||
|
||||
@ -23,33 +24,41 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Test]
|
||||
public void TestGameplayOverlayActivation()
|
||||
{
|
||||
AddAssert("local user playing", () => Player.LocalUserPlaying.Value);
|
||||
AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestGameplayOverlayActivationPaused()
|
||||
{
|
||||
AddAssert("local user playing", () => Player.LocalUserPlaying.Value);
|
||||
AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
|
||||
AddStep("pause gameplay", () => Player.Pause());
|
||||
AddAssert("local user not playing", () => !Player.LocalUserPlaying.Value);
|
||||
AddUntilStep("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestGameplayOverlayActivationReplayLoaded()
|
||||
{
|
||||
AddAssert("local user playing", () => Player.LocalUserPlaying.Value);
|
||||
AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
|
||||
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);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestGameplayOverlayActivationBreaks()
|
||||
{
|
||||
AddAssert("local user playing", () => Player.LocalUserPlaying.Value);
|
||||
AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
|
||||
AddStep("seek to break", () => Player.GameplayClockContainer.Seek(Beatmap.Value.Beatmap.Breaks.First().StartTime));
|
||||
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));
|
||||
AddUntilStep("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
|
||||
AddAssert("local user playing", () => Player.LocalUserPlaying.Value);
|
||||
}
|
||||
|
||||
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new OverlayTestPlayer();
|
||||
@ -57,6 +66,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
protected class OverlayTestPlayer : TestPlayer
|
||||
{
|
||||
public new OverlayActivation OverlayActivationMode => base.OverlayActivationMode.Value;
|
||||
public new Bindable<bool> LocalUserPlaying => base.LocalUserPlaying;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -10,34 +10,34 @@ namespace osu.Game.Tournament.Components
|
||||
{
|
||||
public class DateTextBox : SettingsTextBox
|
||||
{
|
||||
public new Bindable<DateTimeOffset> Bindable
|
||||
public new Bindable<DateTimeOffset> Current
|
||||
{
|
||||
get => bindable;
|
||||
get => current;
|
||||
set
|
||||
{
|
||||
bindable = value.GetBoundCopy();
|
||||
bindable.BindValueChanged(dto =>
|
||||
base.Bindable.Value = dto.NewValue.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"), true);
|
||||
current = value.GetBoundCopy();
|
||||
current.BindValueChanged(dto =>
|
||||
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.
|
||||
private Bindable<DateTimeOffset> bindable = new Bindable<DateTimeOffset>();
|
||||
private Bindable<DateTimeOffset> current = new Bindable<DateTimeOffset>();
|
||||
|
||||
public DateTextBox()
|
||||
{
|
||||
base.Bindable = new Bindable<string>();
|
||||
base.Current = new Bindable<string>();
|
||||
|
||||
((OsuTextBox)Control).OnCommit += (sender, newText) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
bindable.Value = DateTimeOffset.Parse(sender.Text);
|
||||
current.Value = DateTimeOffset.Parse(sender.Text);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// reset textbox content to its last valid state on a parse failure.
|
||||
bindable.TriggerChange();
|
||||
current.TriggerChange();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -63,25 +63,25 @@ namespace osu.Game.Tournament.Screens.Editors
|
||||
{
|
||||
LabelText = "Name",
|
||||
Width = 0.33f,
|
||||
Bindable = Model.Name
|
||||
Current = Model.Name
|
||||
},
|
||||
new SettingsTextBox
|
||||
{
|
||||
LabelText = "Description",
|
||||
Width = 0.33f,
|
||||
Bindable = Model.Description
|
||||
Current = Model.Description
|
||||
},
|
||||
new DateTextBox
|
||||
{
|
||||
LabelText = "Start Time",
|
||||
Width = 0.33f,
|
||||
Bindable = Model.StartDate
|
||||
Current = Model.StartDate
|
||||
},
|
||||
new SettingsSlider<int>
|
||||
{
|
||||
LabelText = "Best of",
|
||||
Width = 0.33f,
|
||||
Bindable = Model.BestOf
|
||||
Current = Model.BestOf
|
||||
},
|
||||
new SettingsButton
|
||||
{
|
||||
@ -186,14 +186,14 @@ namespace osu.Game.Tournament.Screens.Editors
|
||||
LabelText = "Beatmap ID",
|
||||
RelativeSizeAxes = Axes.None,
|
||||
Width = 200,
|
||||
Bindable = beatmapId,
|
||||
Current = beatmapId,
|
||||
},
|
||||
new SettingsTextBox
|
||||
{
|
||||
LabelText = "Mods",
|
||||
RelativeSizeAxes = Axes.None,
|
||||
Width = 200,
|
||||
Bindable = mods,
|
||||
Current = mods,
|
||||
},
|
||||
drawableContainer = new Container
|
||||
{
|
||||
|
@ -74,13 +74,13 @@ namespace osu.Game.Tournament.Screens.Editors
|
||||
{
|
||||
LabelText = "Mod",
|
||||
Width = 0.33f,
|
||||
Bindable = Model.Mod
|
||||
Current = Model.Mod
|
||||
},
|
||||
new SettingsSlider<int>
|
||||
{
|
||||
LabelText = "Seed",
|
||||
Width = 0.33f,
|
||||
Bindable = Model.Seed
|
||||
Current = Model.Seed
|
||||
},
|
||||
new SettingsButton
|
||||
{
|
||||
@ -187,21 +187,21 @@ namespace osu.Game.Tournament.Screens.Editors
|
||||
LabelText = "Beatmap ID",
|
||||
RelativeSizeAxes = Axes.None,
|
||||
Width = 200,
|
||||
Bindable = beatmapId,
|
||||
Current = beatmapId,
|
||||
},
|
||||
new SettingsSlider<int>
|
||||
{
|
||||
LabelText = "Seed",
|
||||
RelativeSizeAxes = Axes.None,
|
||||
Width = 200,
|
||||
Bindable = beatmap.Seed
|
||||
Current = beatmap.Seed
|
||||
},
|
||||
new SettingsTextBox
|
||||
{
|
||||
LabelText = "Score",
|
||||
RelativeSizeAxes = Axes.None,
|
||||
Width = 200,
|
||||
Bindable = score,
|
||||
Current = score,
|
||||
},
|
||||
drawableContainer = new Container
|
||||
{
|
||||
|
@ -102,31 +102,31 @@ namespace osu.Game.Tournament.Screens.Editors
|
||||
{
|
||||
LabelText = "Name",
|
||||
Width = 0.2f,
|
||||
Bindable = Model.FullName
|
||||
Current = Model.FullName
|
||||
},
|
||||
new SettingsTextBox
|
||||
{
|
||||
LabelText = "Acronym",
|
||||
Width = 0.2f,
|
||||
Bindable = Model.Acronym
|
||||
Current = Model.Acronym
|
||||
},
|
||||
new SettingsTextBox
|
||||
{
|
||||
LabelText = "Flag",
|
||||
Width = 0.2f,
|
||||
Bindable = Model.FlagName
|
||||
Current = Model.FlagName
|
||||
},
|
||||
new SettingsTextBox
|
||||
{
|
||||
LabelText = "Seed",
|
||||
Width = 0.2f,
|
||||
Bindable = Model.Seed
|
||||
Current = Model.Seed
|
||||
},
|
||||
new SettingsSlider<int>
|
||||
{
|
||||
LabelText = "Last Year Placement",
|
||||
Width = 0.33f,
|
||||
Bindable = Model.LastYearPlacing
|
||||
Current = Model.LastYearPlacing
|
||||
},
|
||||
new SettingsButton
|
||||
{
|
||||
@ -247,7 +247,7 @@ namespace osu.Game.Tournament.Screens.Editors
|
||||
LabelText = "User ID",
|
||||
RelativeSizeAxes = Axes.None,
|
||||
Width = 200,
|
||||
Bindable = userId,
|
||||
Current = userId,
|
||||
},
|
||||
drawableContainer = new Container
|
||||
{
|
||||
|
@ -113,13 +113,13 @@ namespace osu.Game.Tournament.Screens.Gameplay
|
||||
new SettingsSlider<int>
|
||||
{
|
||||
LabelText = "Chroma width",
|
||||
Bindable = LadderInfo.ChromaKeyWidth,
|
||||
Current = LadderInfo.ChromaKeyWidth,
|
||||
KeyboardStep = 1,
|
||||
},
|
||||
new SettingsSlider<int>
|
||||
{
|
||||
LabelText = "Players per team",
|
||||
Bindable = LadderInfo.PlayersPerTeam,
|
||||
Current = LadderInfo.PlayersPerTeam,
|
||||
KeyboardStep = 1,
|
||||
}
|
||||
}
|
||||
|
@ -51,15 +51,15 @@ namespace osu.Game.Tournament.Screens.Ladder.Components
|
||||
|
||||
editorInfo.Selected.ValueChanged += selection =>
|
||||
{
|
||||
roundDropdown.Bindable = selection.NewValue?.Round;
|
||||
roundDropdown.Current = selection.NewValue?.Round;
|
||||
losersCheckbox.Current = selection.NewValue?.Losers;
|
||||
dateTimeBox.Bindable = selection.NewValue?.Date;
|
||||
dateTimeBox.Current = selection.NewValue?.Date;
|
||||
|
||||
team1Dropdown.Bindable = selection.NewValue?.Team1;
|
||||
team2Dropdown.Bindable = selection.NewValue?.Team2;
|
||||
team1Dropdown.Current = selection.NewValue?.Team1;
|
||||
team2Dropdown.Current = selection.NewValue?.Team2;
|
||||
};
|
||||
|
||||
roundDropdown.Bindable.ValueChanged += round =>
|
||||
roundDropdown.Current.ValueChanged += round =>
|
||||
{
|
||||
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)
|
||||
{
|
||||
Bindable = new Bindable<TournamentRound>();
|
||||
Current = new Bindable<TournamentRound>();
|
||||
|
||||
foreach (var r in rounds.Prepend(new TournamentRound()))
|
||||
add(r);
|
||||
|
@ -61,7 +61,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro
|
||||
new SettingsTeamDropdown(LadderInfo.Teams)
|
||||
{
|
||||
LabelText = "Show specific team",
|
||||
Bindable = currentTeam,
|
||||
Current = currentTeam,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,8 +22,18 @@ namespace osu.Game.Beatmaps
|
||||
{
|
||||
IHasComboInformation lastObj = null;
|
||||
|
||||
bool isFirst = true;
|
||||
|
||||
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)
|
||||
{
|
||||
obj.IndexInCurrentCombo = 0;
|
||||
|
@ -7,6 +7,7 @@ using osu.Framework.Configuration.Tracking;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Input;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Select;
|
||||
@ -69,6 +70,7 @@ namespace osu.Game.Configuration
|
||||
|
||||
Set(OsuSetting.MouseDisableButtons, false);
|
||||
Set(OsuSetting.MouseDisableWheel, false);
|
||||
Set(OsuSetting.ConfineMouseMode, OsuConfineMouseMode.DuringGameplay);
|
||||
|
||||
// Graphics
|
||||
Set(OsuSetting.ShowFpsDisplay, false);
|
||||
@ -194,6 +196,7 @@ namespace osu.Game.Configuration
|
||||
FadePlayfieldWhenHealthLow,
|
||||
MouseDisableButtons,
|
||||
MouseDisableWheel,
|
||||
ConfineMouseMode,
|
||||
AudioOffset,
|
||||
VolumeInactive,
|
||||
MenuMusic,
|
||||
|
@ -57,7 +57,7 @@ namespace osu.Game.Configuration
|
||||
yield return new SettingsSlider<float>
|
||||
{
|
||||
LabelText = attr.Label,
|
||||
Bindable = bNumber,
|
||||
Current = bNumber,
|
||||
KeyboardStep = 0.1f,
|
||||
};
|
||||
|
||||
@ -67,7 +67,7 @@ namespace osu.Game.Configuration
|
||||
yield return new SettingsSlider<double>
|
||||
{
|
||||
LabelText = attr.Label,
|
||||
Bindable = bNumber,
|
||||
Current = bNumber,
|
||||
KeyboardStep = 0.1f,
|
||||
};
|
||||
|
||||
@ -77,7 +77,7 @@ namespace osu.Game.Configuration
|
||||
yield return new SettingsSlider<int>
|
||||
{
|
||||
LabelText = attr.Label,
|
||||
Bindable = bNumber
|
||||
Current = bNumber
|
||||
};
|
||||
|
||||
break;
|
||||
@ -86,7 +86,7 @@ namespace osu.Game.Configuration
|
||||
yield return new SettingsCheckbox
|
||||
{
|
||||
LabelText = attr.Label,
|
||||
Bindable = bBool
|
||||
Current = bBool
|
||||
};
|
||||
|
||||
break;
|
||||
@ -95,7 +95,7 @@ namespace osu.Game.Configuration
|
||||
yield return new SettingsTextBox
|
||||
{
|
||||
LabelText = attr.Label,
|
||||
Bindable = bString
|
||||
Current = bString
|
||||
};
|
||||
|
||||
break;
|
||||
@ -105,7 +105,7 @@ namespace osu.Game.Configuration
|
||||
var dropdown = (Drawable)Activator.CreateInstance(dropdownType);
|
||||
|
||||
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;
|
||||
|
||||
|
24
osu.Game/Graphics/UserInterfaceV2/LabelledSliderBar.cs
Normal file
24
osu.Game/Graphics/UserInterfaceV2/LabelledSliderBar.cs
Normal 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,
|
||||
};
|
||||
}
|
||||
}
|
61
osu.Game/Input/ConfineMouseTracker.cs
Normal file
61
osu.Game/Input/ConfineMouseTracker.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
37
osu.Game/Input/OsuConfineMouseMode.cs
Normal file
37
osu.Game/Input/OsuConfineMouseMode.cs
Normal 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
|
||||
}
|
||||
}
|
@ -95,6 +95,15 @@ namespace osu.Game
|
||||
/// </summary>
|
||||
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 BackButton BackButton;
|
||||
@ -577,7 +586,8 @@ namespace osu.Game
|
||||
rightFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both },
|
||||
leftFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both },
|
||||
topMostOverlayContent = new Container { RelativeSizeAxes = Axes.Both },
|
||||
idleTracker
|
||||
idleTracker,
|
||||
new ConfineMouseTracker()
|
||||
});
|
||||
|
||||
ScreenStack.ScreenPushed += screenPushed;
|
||||
@ -947,6 +957,9 @@ namespace osu.Game
|
||||
break;
|
||||
}
|
||||
|
||||
// reset on screen change for sanity.
|
||||
LocalUserPlaying.Value = false;
|
||||
|
||||
if (current is IOsuScreen currentOsuScreen)
|
||||
OverlayActivationMode.UnbindFrom(currentOsuScreen.OverlayActivationMode);
|
||||
|
||||
|
@ -64,7 +64,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
|
||||
|
||||
updateItems();
|
||||
|
||||
dropdown.Bindable = audio.AudioDevice;
|
||||
dropdown.Current = audio.AudioDevice;
|
||||
|
||||
audio.OnNewDevice += onDeviceChanged;
|
||||
audio.OnLostDevice += onDeviceChanged;
|
||||
|
@ -21,23 +21,23 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Interface voices",
|
||||
Bindable = config.GetBindable<bool>(OsuSetting.MenuVoice)
|
||||
Current = config.GetBindable<bool>(OsuSetting.MenuVoice)
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "osu! music theme",
|
||||
Bindable = config.GetBindable<bool>(OsuSetting.MenuMusic)
|
||||
Current = config.GetBindable<bool>(OsuSetting.MenuMusic)
|
||||
},
|
||||
new SettingsDropdown<IntroSequence>
|
||||
{
|
||||
LabelText = "Intro sequence",
|
||||
Bindable = config.GetBindable<IntroSequence>(OsuSetting.IntroSequence),
|
||||
Current = config.GetBindable<IntroSequence>(OsuSetting.IntroSequence),
|
||||
Items = Enum.GetValues(typeof(IntroSequence)).Cast<IntroSequence>()
|
||||
},
|
||||
new SettingsDropdown<BackgroundSource>
|
||||
{
|
||||
LabelText = "Background source",
|
||||
Bindable = config.GetBindable<BackgroundSource>(OsuSetting.MenuBackgroundSource),
|
||||
Current = config.GetBindable<BackgroundSource>(OsuSetting.MenuBackgroundSource),
|
||||
Items = Enum.GetValues(typeof(BackgroundSource)).Cast<BackgroundSource>()
|
||||
}
|
||||
};
|
||||
|
@ -20,7 +20,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
|
||||
new SettingsSlider<double, OffsetSlider>
|
||||
{
|
||||
LabelText = "Audio offset",
|
||||
Bindable = config.GetBindable<double>(OsuSetting.AudioOffset),
|
||||
Current = config.GetBindable<double>(OsuSetting.AudioOffset),
|
||||
KeyboardStep = 1f
|
||||
},
|
||||
new SettingsButton
|
||||
|
@ -20,28 +20,28 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
|
||||
new SettingsSlider<double>
|
||||
{
|
||||
LabelText = "Master",
|
||||
Bindable = audio.Volume,
|
||||
Current = audio.Volume,
|
||||
KeyboardStep = 0.01f,
|
||||
DisplayAsPercentage = true
|
||||
},
|
||||
new SettingsSlider<double>
|
||||
{
|
||||
LabelText = "Master (window inactive)",
|
||||
Bindable = config.GetBindable<double>(OsuSetting.VolumeInactive),
|
||||
Current = config.GetBindable<double>(OsuSetting.VolumeInactive),
|
||||
KeyboardStep = 0.01f,
|
||||
DisplayAsPercentage = true
|
||||
},
|
||||
new SettingsSlider<double>
|
||||
{
|
||||
LabelText = "Effect",
|
||||
Bindable = audio.VolumeSample,
|
||||
Current = audio.VolumeSample,
|
||||
KeyboardStep = 0.01f,
|
||||
DisplayAsPercentage = true
|
||||
},
|
||||
new SettingsSlider<double>
|
||||
{
|
||||
LabelText = "Music",
|
||||
Bindable = audio.VolumeTrack,
|
||||
Current = audio.VolumeTrack,
|
||||
KeyboardStep = 0.01f,
|
||||
DisplayAsPercentage = true
|
||||
},
|
||||
|
@ -19,12 +19,12 @@ namespace osu.Game.Overlays.Settings.Sections.Debug
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Show log overlay",
|
||||
Bindable = frameworkConfig.GetBindable<bool>(FrameworkSetting.ShowLogOverlay)
|
||||
Current = frameworkConfig.GetBindable<bool>(FrameworkSetting.ShowLogOverlay)
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Bypass front-to-back render pass",
|
||||
Bindable = config.GetBindable<bool>(DebugSetting.BypassFrontToBackPass)
|
||||
Current = config.GetBindable<bool>(DebugSetting.BypassFrontToBackPass)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -21,62 +21,62 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
|
||||
new SettingsSlider<double>
|
||||
{
|
||||
LabelText = "Background dim",
|
||||
Bindable = config.GetBindable<double>(OsuSetting.DimLevel),
|
||||
Current = config.GetBindable<double>(OsuSetting.DimLevel),
|
||||
KeyboardStep = 0.01f,
|
||||
DisplayAsPercentage = true
|
||||
},
|
||||
new SettingsSlider<double>
|
||||
{
|
||||
LabelText = "Background blur",
|
||||
Bindable = config.GetBindable<double>(OsuSetting.BlurLevel),
|
||||
Current = config.GetBindable<double>(OsuSetting.BlurLevel),
|
||||
KeyboardStep = 0.01f,
|
||||
DisplayAsPercentage = true
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Lighten playfield during breaks",
|
||||
Bindable = config.GetBindable<bool>(OsuSetting.LightenDuringBreaks)
|
||||
Current = config.GetBindable<bool>(OsuSetting.LightenDuringBreaks)
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Show score overlay",
|
||||
Bindable = config.GetBindable<bool>(OsuSetting.ShowInterface)
|
||||
Current = config.GetBindable<bool>(OsuSetting.ShowInterface)
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Show difficulty graph on progress bar",
|
||||
Bindable = config.GetBindable<bool>(OsuSetting.ShowProgressGraph)
|
||||
Current = config.GetBindable<bool>(OsuSetting.ShowProgressGraph)
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
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" }
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Fade playfield to red when health is low",
|
||||
Bindable = config.GetBindable<bool>(OsuSetting.FadePlayfieldWhenHealthLow),
|
||||
Current = config.GetBindable<bool>(OsuSetting.FadePlayfieldWhenHealthLow),
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Always show key overlay",
|
||||
Bindable = config.GetBindable<bool>(OsuSetting.KeyOverlay)
|
||||
Current = config.GetBindable<bool>(OsuSetting.KeyOverlay)
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Positional hitsounds",
|
||||
Bindable = config.GetBindable<bool>(OsuSetting.PositionalHitSounds)
|
||||
Current = config.GetBindable<bool>(OsuSetting.PositionalHitSounds)
|
||||
},
|
||||
new SettingsEnumDropdown<ScoreMeterType>
|
||||
{
|
||||
LabelText = "Score meter type",
|
||||
Bindable = config.GetBindable<ScoreMeterType>(OsuSetting.ScoreMeter)
|
||||
Current = config.GetBindable<ScoreMeterType>(OsuSetting.ScoreMeter)
|
||||
},
|
||||
new SettingsEnumDropdown<ScoringMode>
|
||||
{
|
||||
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
|
||||
{
|
||||
LabelText = "Disable Windows key during gameplay",
|
||||
Bindable = config.GetBindable<bool>(OsuSetting.GameplayDisableWinKey)
|
||||
Current = config.GetBindable<bool>(OsuSetting.GameplayDisableWinKey)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Increase visibility of first object when visual impairment mods are enabled",
|
||||
Bindable = config.GetBindable<bool>(OsuSetting.IncreaseFirstObjectVisibility),
|
||||
Current = config.GetBindable<bool>(OsuSetting.IncreaseFirstObjectVisibility),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -31,31 +31,31 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Right mouse drag to absolute scroll",
|
||||
Bindable = config.GetBindable<bool>(OsuSetting.SongSelectRightMouseScroll),
|
||||
Current = config.GetBindable<bool>(OsuSetting.SongSelectRightMouseScroll),
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Show converted beatmaps",
|
||||
Bindable = config.GetBindable<bool>(OsuSetting.ShowConvertedBeatmaps),
|
||||
Current = config.GetBindable<bool>(OsuSetting.ShowConvertedBeatmaps),
|
||||
},
|
||||
new SettingsSlider<double, StarsSlider>
|
||||
{
|
||||
LabelText = "Display beatmaps from",
|
||||
Bindable = config.GetBindable<double>(OsuSetting.DisplayStarsMinimum),
|
||||
Current = config.GetBindable<double>(OsuSetting.DisplayStarsMinimum),
|
||||
KeyboardStep = 0.1f,
|
||||
Keywords = new[] { "minimum", "maximum", "star", "difficulty" }
|
||||
},
|
||||
new SettingsSlider<double, MaximumStarsSlider>
|
||||
{
|
||||
LabelText = "up to",
|
||||
Bindable = config.GetBindable<double>(OsuSetting.DisplayStarsMaximum),
|
||||
Current = config.GetBindable<double>(OsuSetting.DisplayStarsMaximum),
|
||||
KeyboardStep = 0.1f,
|
||||
Keywords = new[] { "minimum", "maximum", "star", "difficulty" }
|
||||
},
|
||||
new SettingsEnumDropdown<RandomSelectAlgorithm>
|
||||
{
|
||||
LabelText = "Random selection algorithm",
|
||||
Bindable = config.GetBindable<RandomSelectAlgorithm>(OsuSetting.RandomSelectAlgorithm),
|
||||
Current = config.GetBindable<RandomSelectAlgorithm>(OsuSetting.RandomSelectAlgorithm),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Prefer metadata in original language",
|
||||
Bindable = frameworkConfig.GetBindable<bool>(FrameworkSetting.ShowUnicode)
|
||||
Current = frameworkConfig.GetBindable<bool>(FrameworkSetting.ShowUnicode)
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -240,12 +240,12 @@ namespace osu.Game.Overlays.Settings.Sections.General
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Remember username",
|
||||
Bindable = config.GetBindable<bool>(OsuSetting.SaveUsername),
|
||||
Current = config.GetBindable<bool>(OsuSetting.SaveUsername),
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Stay signed in",
|
||||
Bindable = config.GetBindable<bool>(OsuSetting.SavePassword),
|
||||
Current = config.GetBindable<bool>(OsuSetting.SavePassword),
|
||||
},
|
||||
new Container
|
||||
{
|
||||
|
@ -32,7 +32,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
|
||||
Add(new SettingsEnumDropdown<ReleaseStream>
|
||||
{
|
||||
LabelText = "Release stream",
|
||||
Bindable = config.GetBindable<ReleaseStream>(OsuSetting.ReleaseStream),
|
||||
Current = config.GetBindable<ReleaseStream>(OsuSetting.ReleaseStream),
|
||||
});
|
||||
|
||||
if (updateManager?.CanCheckForUpdate == true)
|
||||
|
@ -19,22 +19,22 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Storyboard / Video",
|
||||
Bindable = config.GetBindable<bool>(OsuSetting.ShowStoryboard)
|
||||
Current = config.GetBindable<bool>(OsuSetting.ShowStoryboard)
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Hit Lighting",
|
||||
Bindable = config.GetBindable<bool>(OsuSetting.HitLighting)
|
||||
Current = config.GetBindable<bool>(OsuSetting.HitLighting)
|
||||
},
|
||||
new SettingsEnumDropdown<ScreenshotFormat>
|
||||
{
|
||||
LabelText = "Screenshot format",
|
||||
Bindable = config.GetBindable<ScreenshotFormat>(OsuSetting.ScreenshotFormat)
|
||||
Current = config.GetBindable<ScreenshotFormat>(OsuSetting.ScreenshotFormat)
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Show menu cursor in screenshots",
|
||||
Bindable = config.GetBindable<bool>(OsuSetting.ScreenshotCaptureMenuCursor)
|
||||
Current = config.GetBindable<bool>(OsuSetting.ScreenshotCaptureMenuCursor)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
||||
windowModeDropdown = new SettingsDropdown<WindowMode>
|
||||
{
|
||||
LabelText = "Screen mode",
|
||||
Bindable = config.GetBindable<WindowMode>(FrameworkSetting.WindowMode),
|
||||
Current = config.GetBindable<WindowMode>(FrameworkSetting.WindowMode),
|
||||
ItemSource = windowModes,
|
||||
},
|
||||
resolutionSettingsContainer = new Container
|
||||
@ -74,14 +74,14 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
||||
{
|
||||
LabelText = "UI Scaling",
|
||||
TransferValueOnCommit = true,
|
||||
Bindable = osuConfig.GetBindable<float>(OsuSetting.UIScale),
|
||||
Current = osuConfig.GetBindable<float>(OsuSetting.UIScale),
|
||||
KeyboardStep = 0.01f,
|
||||
Keywords = new[] { "scale", "letterbox" },
|
||||
},
|
||||
new SettingsEnumDropdown<ScalingMode>
|
||||
{
|
||||
LabelText = "Screen Scaling",
|
||||
Bindable = osuConfig.GetBindable<ScalingMode>(OsuSetting.Scaling),
|
||||
Current = osuConfig.GetBindable<ScalingMode>(OsuSetting.Scaling),
|
||||
Keywords = new[] { "scale", "letterbox" },
|
||||
},
|
||||
scalingSettings = new FillFlowContainer<SettingsSlider<float>>
|
||||
@ -97,28 +97,28 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
||||
new SettingsSlider<float>
|
||||
{
|
||||
LabelText = "Horizontal position",
|
||||
Bindable = scalingPositionX,
|
||||
Current = scalingPositionX,
|
||||
KeyboardStep = 0.01f,
|
||||
DisplayAsPercentage = true
|
||||
},
|
||||
new SettingsSlider<float>
|
||||
{
|
||||
LabelText = "Vertical position",
|
||||
Bindable = scalingPositionY,
|
||||
Current = scalingPositionY,
|
||||
KeyboardStep = 0.01f,
|
||||
DisplayAsPercentage = true
|
||||
},
|
||||
new SettingsSlider<float>
|
||||
{
|
||||
LabelText = "Horizontal scale",
|
||||
Bindable = scalingSizeX,
|
||||
Current = scalingSizeX,
|
||||
KeyboardStep = 0.01f,
|
||||
DisplayAsPercentage = true
|
||||
},
|
||||
new SettingsSlider<float>
|
||||
{
|
||||
LabelText = "Vertical scale",
|
||||
Bindable = scalingSizeY,
|
||||
Current = scalingSizeY,
|
||||
KeyboardStep = 0.01f,
|
||||
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();
|
||||
|
||||
@ -137,10 +137,10 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
||||
LabelText = "Resolution",
|
||||
ShowsDefaultIndicator = false,
|
||||
Items = resolutions,
|
||||
Bindable = sizeFullscreen
|
||||
Current = sizeFullscreen
|
||||
};
|
||||
|
||||
windowModeDropdown.Bindable.BindValueChanged(mode =>
|
||||
windowModeDropdown.Current.BindValueChanged(mode =>
|
||||
{
|
||||
if (mode.NewValue == WindowMode.Fullscreen)
|
||||
{
|
||||
|
@ -23,17 +23,17 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
||||
new SettingsEnumDropdown<FrameSync>
|
||||
{
|
||||
LabelText = "Frame limiter",
|
||||
Bindable = config.GetBindable<FrameSync>(FrameworkSetting.FrameSync)
|
||||
Current = config.GetBindable<FrameSync>(FrameworkSetting.FrameSync)
|
||||
},
|
||||
new SettingsEnumDropdown<ExecutionMode>
|
||||
{
|
||||
LabelText = "Threading mode",
|
||||
Bindable = config.GetBindable<ExecutionMode>(FrameworkSetting.ExecutionMode)
|
||||
Current = config.GetBindable<ExecutionMode>(FrameworkSetting.ExecutionMode)
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Show FPS",
|
||||
Bindable = osuConfig.GetBindable<bool>(OsuSetting.ShowFpsDisplay)
|
||||
Current = osuConfig.GetBindable<bool>(OsuSetting.ShowFpsDisplay)
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -20,17 +20,17 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Rotate cursor when dragging",
|
||||
Bindable = config.GetBindable<bool>(OsuSetting.CursorRotation)
|
||||
Current = config.GetBindable<bool>(OsuSetting.CursorRotation)
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Parallax",
|
||||
Bindable = config.GetBindable<bool>(OsuSetting.MenuParallax)
|
||||
Current = config.GetBindable<bool>(OsuSetting.MenuParallax)
|
||||
},
|
||||
new SettingsSlider<float, TimeSlider>
|
||||
{
|
||||
LabelText = "Hold-to-confirm activation time",
|
||||
Bindable = config.GetBindable<float>(OsuSetting.UIHoldActivationDelay),
|
||||
Current = config.GetBindable<float>(OsuSetting.UIHoldActivationDelay),
|
||||
KeyboardStep = 50
|
||||
},
|
||||
};
|
||||
|
@ -6,9 +6,9 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Input;
|
||||
|
||||
namespace osu.Game.Overlays.Settings.Sections.Input
|
||||
{
|
||||
@ -35,32 +35,32 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Raw input",
|
||||
Bindable = rawInputToggle
|
||||
Current = rawInputToggle
|
||||
},
|
||||
new SensitivitySetting
|
||||
{
|
||||
LabelText = "Cursor sensitivity",
|
||||
Bindable = sensitivityBindable
|
||||
Current = sensitivityBindable
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
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",
|
||||
Bindable = config.GetBindable<ConfineMouseMode>(FrameworkSetting.ConfineMouseMode),
|
||||
Current = osuConfig.GetBindable<OsuConfineMouseMode>(OsuSetting.ConfineMouseMode)
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Disable mouse wheel during gameplay",
|
||||
Bindable = osuConfig.GetBindable<bool>(OsuSetting.MouseDisableWheel)
|
||||
Current = osuConfig.GetBindable<bool>(OsuSetting.MouseDisableWheel)
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Disable mouse buttons during gameplay",
|
||||
Bindable = osuConfig.GetBindable<bool>(OsuSetting.MouseDisableButtons)
|
||||
Current = osuConfig.GetBindable<bool>(OsuSetting.MouseDisableButtons)
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -19,13 +19,13 @@ namespace osu.Game.Overlays.Settings.Sections.Online
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Warn about opening external links",
|
||||
Bindable = config.GetBindable<bool>(OsuSetting.ExternalLinkWarning)
|
||||
Current = config.GetBindable<bool>(OsuSetting.ExternalLinkWarning)
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Prefer downloads without video",
|
||||
Keywords = new[] { "no-video" },
|
||||
Bindable = config.GetBindable<bool>(OsuSetting.PreferNoVideo)
|
||||
Current = config.GetBindable<bool>(OsuSetting.PreferNoVideo)
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -47,29 +47,29 @@ namespace osu.Game.Overlays.Settings.Sections
|
||||
new SettingsSlider<float, SizeSlider>
|
||||
{
|
||||
LabelText = "Menu cursor size",
|
||||
Bindable = config.GetBindable<float>(OsuSetting.MenuCursorSize),
|
||||
Current = config.GetBindable<float>(OsuSetting.MenuCursorSize),
|
||||
KeyboardStep = 0.01f
|
||||
},
|
||||
new SettingsSlider<float, SizeSlider>
|
||||
{
|
||||
LabelText = "Gameplay cursor size",
|
||||
Bindable = config.GetBindable<float>(OsuSetting.GameplayCursorSize),
|
||||
Current = config.GetBindable<float>(OsuSetting.GameplayCursorSize),
|
||||
KeyboardStep = 0.01f
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Adjust gameplay cursor size based on current beatmap",
|
||||
Bindable = config.GetBindable<bool>(OsuSetting.AutoCursorSize)
|
||||
Current = config.GetBindable<bool>(OsuSetting.AutoCursorSize)
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Beatmap skins",
|
||||
Bindable = config.GetBindable<bool>(OsuSetting.BeatmapSkins)
|
||||
Current = config.GetBindable<bool>(OsuSetting.BeatmapSkins)
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
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);
|
||||
|
||||
skinDropdown.Bindable = dropdownBindable;
|
||||
skinDropdown.Current = dropdownBindable;
|
||||
skinDropdown.Items = skins.GetAllUsableSkins().ToArray();
|
||||
|
||||
// Todo: This should not be necessary when OsuConfigManager is databased
|
||||
|
@ -21,7 +21,7 @@ using osuTK;
|
||||
|
||||
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();
|
||||
|
||||
@ -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;
|
||||
set => controlWithCurrent.Current = value;
|
||||
|
@ -1,11 +1,11 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Scoring;
|
||||
|
||||
@ -16,19 +16,16 @@ namespace osu.Game.Rulesets.Difficulty
|
||||
protected readonly DifficultyAttributes Attributes;
|
||||
|
||||
protected readonly Ruleset Ruleset;
|
||||
protected readonly IBeatmap Beatmap;
|
||||
protected readonly ScoreInfo Score;
|
||||
|
||||
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;
|
||||
Score = score;
|
||||
|
||||
Beatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, score.Mods);
|
||||
|
||||
Attributes = ruleset.CreateDifficultyCalculator(beatmap).Calculate(score.Mods);
|
||||
Attributes = attributes ?? throw new ArgumentNullException(nameof(attributes));
|
||||
|
||||
ApplyMods(score.Mods);
|
||||
}
|
||||
|
@ -273,7 +273,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
// apply any custom state overrides
|
||||
ApplyCustomUpdateState?.Invoke(this, newState);
|
||||
|
||||
if (newState == ArmedState.Hit)
|
||||
if (!force && newState == ArmedState.Hit)
|
||||
PlaySamples();
|
||||
}
|
||||
|
||||
|
@ -77,6 +77,7 @@ namespace osu.Game.Rulesets.Objects
|
||||
/// <summary>
|
||||
/// The hit windows for this <see cref="HitObject"/>.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public HitWindows HitWindows { get; set; }
|
||||
|
||||
private readonly List<HitObject> nestedHitObjects = new List<HitObject>();
|
||||
|
@ -158,7 +158,28 @@ namespace osu.Game.Rulesets
|
||||
|
||||
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;
|
||||
|
||||
|
@ -239,10 +239,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
private void deleteSelected()
|
||||
{
|
||||
ChangeHandler?.BeginChange();
|
||||
|
||||
foreach (var h in selectedBlueprints.ToList())
|
||||
EditorBeatmap?.Remove(h.HitObject);
|
||||
|
||||
EditorBeatmap?.RemoveRange(selectedBlueprints.Select(b => b.HitObject));
|
||||
ChangeHandler?.EndChange();
|
||||
}
|
||||
|
||||
|
@ -199,7 +199,40 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
base.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;
|
||||
|
@ -484,8 +484,7 @@ namespace osu.Game.Screens.Edit
|
||||
protected void Cut()
|
||||
{
|
||||
Copy();
|
||||
foreach (var h in editorBeatmap.SelectedHitObjects.ToArray())
|
||||
editorBeatmap.Remove(h);
|
||||
editorBeatmap.RemoveRange(editorBeatmap.SelectedHitObjects.ToArray());
|
||||
}
|
||||
|
||||
protected void Copy()
|
||||
@ -589,7 +588,7 @@ namespace osu.Game.Screens.Edit
|
||||
|
||||
private void seek(UIEvent e, int direction)
|
||||
{
|
||||
double amount = e.ShiftPressed ? 2 : 1;
|
||||
double amount = e.ShiftPressed ? 4 : 1;
|
||||
|
||||
if (direction < 1)
|
||||
clock.SeekBackward(!clock.IsRunning, amount);
|
||||
|
@ -91,14 +91,19 @@ namespace osu.Game.Screens.Edit
|
||||
|
||||
private readonly HashSet<HitObject> pendingUpdates = new HashSet<HitObject>();
|
||||
|
||||
private bool isBatchApplying;
|
||||
|
||||
/// <summary>
|
||||
/// Adds a collection of <see cref="HitObject"/>s to this <see cref="EditorBeatmap"/>.
|
||||
/// </summary>
|
||||
/// <param name="hitObjects">The <see cref="HitObject"/>s to add.</param>
|
||||
public void AddRange(IEnumerable<HitObject> hitObjects)
|
||||
{
|
||||
foreach (var h in hitObjects)
|
||||
Add(h);
|
||||
ApplyBatchChanges(_ =>
|
||||
{
|
||||
foreach (var h in hitObjects)
|
||||
Add(h);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -126,12 +131,17 @@ namespace osu.Game.Screens.Edit
|
||||
|
||||
mutableHitObjects.Insert(index, hitObject);
|
||||
|
||||
// must be run after any change to hitobject ordering
|
||||
beatmapProcessor?.PreProcess();
|
||||
processHitObject(hitObject);
|
||||
beatmapProcessor?.PostProcess();
|
||||
if (isBatchApplying)
|
||||
batchPendingInserts.Add(hitObject);
|
||||
else
|
||||
{
|
||||
// must be run after any change to hitobject ordering
|
||||
beatmapProcessor?.PreProcess();
|
||||
processHitObject(hitObject);
|
||||
beatmapProcessor?.PostProcess();
|
||||
|
||||
HitObjectAdded?.Invoke(hitObject);
|
||||
HitObjectAdded?.Invoke(hitObject);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -159,6 +169,19 @@ namespace osu.Game.Screens.Edit
|
||||
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>
|
||||
/// Finds the index of a <see cref="HitObject"/> in this <see cref="EditorBeatmap"/>.
|
||||
/// </summary>
|
||||
@ -180,22 +203,56 @@ namespace osu.Game.Screens.Edit
|
||||
bindable.UnbindAll();
|
||||
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();
|
||||
processHitObject(hitObject);
|
||||
|
||||
foreach (var h in batchPendingDeletes) processHitObject(h);
|
||||
foreach (var h in batchPendingInserts) processHitObject(h);
|
||||
|
||||
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>
|
||||
/// Clears all <see cref="HitObjects"/> from this <see cref="EditorBeatmap"/>.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
foreach (var h in HitObjects.ToArray())
|
||||
Remove(h);
|
||||
}
|
||||
public void Clear() => RemoveRange(HitObjects.ToArray());
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
@ -258,5 +315,14 @@ namespace osu.Game.Screens.Edit
|
||||
public double GetBeatLengthAtTime(double referenceTime) => ControlPointInfo.TimingPointAt(referenceTime).BeatLength / BeatDivisor;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ namespace osu.Game.Screens.Edit
|
||||
/// </summary>
|
||||
/// <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>
|
||||
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>
|
||||
/// Seeks forwards by one beat length.
|
||||
|
@ -68,16 +68,19 @@ namespace osu.Game.Screens.Edit
|
||||
toRemove.Sort();
|
||||
toAdd.Sort();
|
||||
|
||||
// Apply the changes.
|
||||
for (int i = toRemove.Count - 1; i >= 0; i--)
|
||||
editorBeatmap.RemoveAt(toRemove[i]);
|
||||
|
||||
if (toAdd.Count > 0)
|
||||
editorBeatmap.ApplyBatchChanges(eb =>
|
||||
{
|
||||
IBeatmap newBeatmap = readBeatmap(newState);
|
||||
foreach (var i in toAdd)
|
||||
editorBeatmap.Insert(i, newBeatmap.HitObjects[i]);
|
||||
}
|
||||
// Apply the changes.
|
||||
for (int i = toRemove.Count - 1; i >= 0; 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);
|
||||
|
99
osu.Game/Screens/Edit/Setup/DifficultySection.cs
Normal file
99
osu.Game/Screens/Edit/Setup/DifficultySection.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
73
osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs
Normal file
73
osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
71
osu.Game/Screens/Edit/Setup/MetadataSection.cs
Normal file
71
osu.Game/Screens/Edit/Setup/MetadataSection.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
211
osu.Game/Screens/Edit/Setup/ResourcesSection.cs
Normal file
211
osu.Game/Screens/Edit/Setup/ResourcesSection.cs
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,73 +1,33 @@
|
||||
// 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.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.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.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
|
||||
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]
|
||||
private OsuGameBase game { get; set; }
|
||||
private OsuColour colours { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private MusicController music { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private BeatmapManager beatmaps { get; set; }
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private Editor editor { get; set; }
|
||||
[Cached]
|
||||
protected readonly OverlayColourProvider ColourProvider;
|
||||
|
||||
public SetupScreen()
|
||||
: base(EditorScreenMode.SongSetup)
|
||||
{
|
||||
ColourProvider = new OverlayColourProvider(OverlayColourScheme.Green);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
private void load()
|
||||
{
|
||||
Container audioTrackFileChooserContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
};
|
||||
|
||||
Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
@ -84,253 +44,34 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
Colour = colours.GreySeafoamDark,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new OsuScrollContainer
|
||||
new SectionsContainer<SetupSection>
|
||||
{
|
||||
FixedHeader = new SetupScreenHeader(),
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding(10),
|
||||
Child = flow = new FillFlowContainer
|
||||
Children = new SetupSection[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Spacing = new Vector2(20),
|
||||
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
|
||||
},
|
||||
}
|
||||
},
|
||||
new ResourcesSection(),
|
||||
new MetadataSection(),
|
||||
new DifficultySection(),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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>();
|
||||
|
||||
public FileChooserLabelledTextBox()
|
||||
private class SetupScreenTitle : OverlayTitle
|
||||
{
|
||||
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
|
||||
public SetupScreenTitle()
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
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);
|
||||
Title = "beatmap setup";
|
||||
Description = "change general settings of your beatmap";
|
||||
IconTexture = "Icons/Hexacons/social";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
42
osu.Game/Screens/Edit/Setup/SetupSection.cs
Normal file
42
osu.Game/Screens/Edit/Setup/SetupSection.cs
Normal 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -113,7 +113,6 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
};
|
||||
|
||||
controlPoints = group.ControlPoints.GetBoundCopy();
|
||||
controlPoints.CollectionChanged += (_, __) => createChildren();
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
@ -125,6 +124,12 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
createChildren();
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
controlPoints.CollectionChanged += (_, __) => createChildren();
|
||||
}
|
||||
|
||||
private void createChildren()
|
||||
{
|
||||
fill.ChildrenEnumerable = controlPoints.Select(createAttribute).Where(c => c != null);
|
||||
|
@ -111,7 +111,8 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
foreach (var cp in currentGroupItems)
|
||||
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();
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ using osu.Game.Overlays.Settings;
|
||||
|
||||
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
|
||||
{
|
||||
private readonly SettingsSlider<T> slider;
|
||||
@ -51,7 +51,7 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
|
||||
try
|
||||
{
|
||||
slider.Bindable.Parse(t.Text);
|
||||
slider.Current.Parse(t.Text);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@ -71,8 +71,8 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
|
||||
public Bindable<T> Current
|
||||
{
|
||||
get => slider.Bindable;
|
||||
set => slider.Bindable = value;
|
||||
get => slider.Current;
|
||||
set => slider.Current = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,14 +36,14 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
{
|
||||
if (point.NewValue != null)
|
||||
{
|
||||
bpmSlider.Bindable = point.NewValue.BeatLengthBindable;
|
||||
bpmSlider.Bindable.BindValueChanged(_ => ChangeHandler?.SaveState());
|
||||
bpmSlider.Current = point.NewValue.BeatLengthBindable;
|
||||
bpmSlider.Current.BindValueChanged(_ => ChangeHandler?.SaveState());
|
||||
|
||||
bpmTextEntry.Bindable = point.NewValue.BeatLengthBindable;
|
||||
// no need to hook change handler here as it's the same bindable as above
|
||||
|
||||
timeSignature.Bindable = point.NewValue.TimeSignatureBindable;
|
||||
timeSignature.Bindable.BindValueChanged(_ => ChangeHandler?.SaveState());
|
||||
timeSignature.Current = point.NewValue.TimeSignatureBindable;
|
||||
timeSignature.Current.BindValueChanged(_ => ChangeHandler?.SaveState());
|
||||
}
|
||||
}
|
||||
|
||||
@ -121,14 +121,14 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
beatLengthBindable.BindValueChanged(beatLength => updateCurrent(beatLengthToBpm(beatLength.NewValue)), true);
|
||||
bpmBindable.BindValueChanged(bpm => beatLengthBindable.Value = beatLengthToBpm(bpm.NewValue));
|
||||
|
||||
base.Bindable = bpmBindable;
|
||||
base.Current = bpmBindable;
|
||||
|
||||
TransferValueOnCommit = true;
|
||||
}
|
||||
|
||||
public override Bindable<double> Bindable
|
||||
public override Bindable<double> Current
|
||||
{
|
||||
get => base.Bindable;
|
||||
get => base.Current;
|
||||
set
|
||||
{
|
||||
// incoming will be beat length, not bpm
|
||||
|
@ -42,8 +42,11 @@ namespace osu.Game.Screens.Menu
|
||||
ValidForResume = false;
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours, IAPIProvider api)
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
@ -104,7 +107,9 @@ namespace osu.Game.Screens.Menu
|
||||
|
||||
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 =>
|
||||
{
|
||||
supportFlow.Children.ForEach(d => d.FadeOut().Expire());
|
||||
@ -141,6 +146,8 @@ namespace osu.Game.Screens.Menu
|
||||
base.LoadComplete();
|
||||
if (nextScreen != null)
|
||||
LoadComponentAsync(nextScreen);
|
||||
|
||||
currentUser.BindTo(api.LocalUser);
|
||||
}
|
||||
|
||||
public override void OnEntering(IScreen last)
|
||||
|
@ -68,6 +68,8 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
private readonly Bindable<bool> storyboardReplacesBackground = new Bindable<bool>();
|
||||
|
||||
protected readonly Bindable<bool> LocalUserPlaying = new Bindable<bool>();
|
||||
|
||||
public int RestartCount;
|
||||
|
||||
[Resolved]
|
||||
@ -155,8 +157,8 @@ namespace osu.Game.Screens.Play
|
||||
DrawableRuleset.SetRecordTarget(recordingReplay = new Replay());
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio, OsuConfigManager config)
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(AudioManager audio, OsuConfigManager config, OsuGame game)
|
||||
{
|
||||
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);
|
||||
|
||||
if (game != null)
|
||||
LocalUserPlaying.BindTo(game.LocalUserPlaying);
|
||||
|
||||
DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value);
|
||||
|
||||
ScoreProcessor = ruleset.CreateScoreProcessor();
|
||||
@ -219,9 +224,9 @@ namespace osu.Game.Screens.Play
|
||||
skipOverlay.Hide();
|
||||
}
|
||||
|
||||
DrawableRuleset.IsPaused.BindValueChanged(_ => updateOverlayActivationMode());
|
||||
DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updateOverlayActivationMode());
|
||||
breakTracker.IsBreakTime.BindValueChanged(_ => updateOverlayActivationMode());
|
||||
DrawableRuleset.IsPaused.BindValueChanged(_ => updateGameplayState());
|
||||
DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updateGameplayState());
|
||||
breakTracker.IsBreakTime.BindValueChanged(_ => updateGameplayState());
|
||||
|
||||
DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true);
|
||||
|
||||
@ -353,14 +358,11 @@ namespace osu.Game.Screens.Play
|
||||
HUDOverlay.KeyCounter.IsCounting = !isBreakTime.NewValue;
|
||||
}
|
||||
|
||||
private void updateOverlayActivationMode()
|
||||
private void updateGameplayState()
|
||||
{
|
||||
bool canTriggerOverlays = DrawableRuleset.IsPaused.Value || breakTracker.IsBreakTime.Value;
|
||||
|
||||
if (DrawableRuleset.HasReplayLoaded.Value || canTriggerOverlays)
|
||||
OverlayActivationMode.Value = OverlayActivation.UserTriggered;
|
||||
else
|
||||
OverlayActivationMode.Value = OverlayActivation.Disabled;
|
||||
bool inGameplay = !DrawableRuleset.HasReplayLoaded.Value && !DrawableRuleset.IsPaused.Value && !breakTracker.IsBreakTime.Value;
|
||||
OverlayActivationMode.Value = inGameplay ? OverlayActivation.Disabled : OverlayActivation.UserTriggered;
|
||||
LocalUserPlaying.Value = inGameplay;
|
||||
}
|
||||
|
||||
private void updatePauseOnFocusLostState() =>
|
||||
@ -441,6 +443,10 @@ namespace osu.Game.Screens.Play
|
||||
/// </summary>
|
||||
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();
|
||||
RestartRequested?.Invoke();
|
||||
|
||||
@ -657,7 +663,7 @@ namespace osu.Game.Screens.Play
|
||||
foreach (var mod in Mods.Value.OfType<IApplicableToTrack>())
|
||||
mod.ApplyToTrack(musicController.CurrentTrack);
|
||||
|
||||
updateOverlayActivationMode();
|
||||
updateGameplayState();
|
||||
}
|
||||
|
||||
public override void OnSuspending(IScreen next)
|
||||
|
@ -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()
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -50,8 +50,8 @@ namespace osu.Game.Screens.Play.PlayerSettings
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager config)
|
||||
{
|
||||
dimSliderBar.Bindable = config.GetBindable<double>(OsuSetting.DimLevel);
|
||||
blurSliderBar.Bindable = config.GetBindable<double>(OsuSetting.BlurLevel);
|
||||
dimSliderBar.Current = config.GetBindable<double>(OsuSetting.DimLevel);
|
||||
blurSliderBar.Current = config.GetBindable<double>(OsuSetting.BlurLevel);
|
||||
showStoryboardToggle.Current = config.GetBindable<bool>(OsuSetting.ShowStoryboard);
|
||||
beatmapSkinsToggle.Current = config.GetBindable<bool>(OsuSetting.BeatmapSkins);
|
||||
beatmapHitsoundsToggle.Current = config.GetBindable<bool>(OsuSetting.BeatmapHitsounds);
|
||||
|
@ -84,6 +84,11 @@ namespace osu.Game.Updater
|
||||
bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".AppImage"));
|
||||
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:
|
||||
// on our testing device this causes the download to magically disappear.
|
||||
//bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".apk"));
|
||||
|
@ -11,5 +11,7 @@ namespace osu.iOS
|
||||
public class OsuGameIOS : OsuGame
|
||||
{
|
||||
public override Version AssemblyVersion => new Version(NSBundle.MainBundle.InfoDictionary["CFBundleVersion"].ToString());
|
||||
|
||||
protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager();
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user