mirror of
https://github.com/osukey/osukey.git
synced 2025-06-21 19:27:58 +09:00
Implement hitobject sound adjustment (#6762)
Implement hitobject sound adjustment Co-authored-by: Dean Herbert <pe@ppy.sh>
This commit is contained in:
commit
3ab332e60b
@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
|
|
||||||
public double Distance => Path.Distance;
|
public double Distance => Path.Distance;
|
||||||
|
|
||||||
public List<List<HitSampleInfo>> NodeSamples { get; set; } = new List<List<HitSampleInfo>>();
|
public List<IList<HitSampleInfo>> NodeSamples { get; set; } = new List<IList<HitSampleInfo>>();
|
||||||
|
|
||||||
public double? LegacyLastTickOffset { get; set; }
|
public double? LegacyLastTickOffset { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -255,7 +255,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="time">The time to retrieve the sample info list from.</param>
|
/// <param name="time">The time to retrieve the sample info list from.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private List<HitSampleInfo> sampleInfoListAt(double time)
|
private IList<HitSampleInfo> sampleInfoListAt(double time)
|
||||||
{
|
{
|
||||||
var curveData = HitObject as IHasCurve;
|
var curveData = HitObject as IHasCurve;
|
||||||
|
|
||||||
|
@ -472,7 +472,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="time">The time to retrieve the sample info list from.</param>
|
/// <param name="time">The time to retrieve the sample info list from.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private List<HitSampleInfo> sampleInfoListAt(double time)
|
private IList<HitSampleInfo> sampleInfoListAt(double time)
|
||||||
{
|
{
|
||||||
var curveData = HitObject as IHasCurve;
|
var curveData = HitObject as IHasCurve;
|
||||||
|
|
||||||
|
@ -126,6 +126,67 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
AddAssert("body positioned correctly", () => slider.Position == slider.HitObject.StackedPosition);
|
AddAssert("body positioned correctly", () => slider.Position == slider.HitObject.StackedPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestChangeSamplesWithNoNodeSamples()
|
||||||
|
{
|
||||||
|
DrawableSlider slider = null;
|
||||||
|
|
||||||
|
AddStep("create slider", () =>
|
||||||
|
{
|
||||||
|
slider = (DrawableSlider)createSlider(repeats: 1);
|
||||||
|
Add(slider);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("change samples", () => slider.HitObject.Samples = new[]
|
||||||
|
{
|
||||||
|
new HitSampleInfo { Name = HitSampleInfo.HIT_CLAP },
|
||||||
|
new HitSampleInfo { Name = HitSampleInfo.HIT_WHISTLE },
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("head samples updated", () => assertSamples(((Slider)slider.HitObject).HeadCircle));
|
||||||
|
AddAssert("tick samples not updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType<SliderTick>().All(assertTickSamples));
|
||||||
|
AddAssert("repeat samples updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType<RepeatPoint>().All(assertSamples));
|
||||||
|
AddAssert("tail has no samples", () => ((Slider)slider.HitObject).TailCircle.Samples.Count == 0);
|
||||||
|
|
||||||
|
bool assertTickSamples(SliderTick tick) => tick.Samples.Single().Name == "slidertick";
|
||||||
|
|
||||||
|
bool assertSamples(HitObject hitObject)
|
||||||
|
{
|
||||||
|
return hitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP)
|
||||||
|
&& hitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_WHISTLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestChangeSamplesWithNodeSamples()
|
||||||
|
{
|
||||||
|
DrawableSlider slider = null;
|
||||||
|
|
||||||
|
AddStep("create slider", () =>
|
||||||
|
{
|
||||||
|
slider = (DrawableSlider)createSlider(repeats: 1);
|
||||||
|
|
||||||
|
for (int i = 0; i < 2; i++)
|
||||||
|
((Slider)slider.HitObject).NodeSamples.Add(new List<HitSampleInfo> { new HitSampleInfo { Name = HitSampleInfo.HIT_FINISH } });
|
||||||
|
|
||||||
|
Add(slider);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("change samples", () => slider.HitObject.Samples = new[]
|
||||||
|
{
|
||||||
|
new HitSampleInfo { Name = HitSampleInfo.HIT_CLAP },
|
||||||
|
new HitSampleInfo { Name = HitSampleInfo.HIT_WHISTLE },
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("head samples not updated", () => assertSamples(((Slider)slider.HitObject).HeadCircle));
|
||||||
|
AddAssert("tick samples not updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType<SliderTick>().All(assertTickSamples));
|
||||||
|
AddAssert("repeat samples not updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType<RepeatPoint>().All(assertSamples));
|
||||||
|
AddAssert("tail has no samples", () => ((Slider)slider.HitObject).TailCircle.Samples.Count == 0);
|
||||||
|
|
||||||
|
bool assertTickSamples(SliderTick tick) => tick.Samples.Single().Name == "slidertick";
|
||||||
|
bool assertSamples(HitObject hitObject) => hitObject.Samples.All(s => s.Name != HitSampleInfo.HIT_CLAP && s.Name != HitSampleInfo.HIT_WHISTLE);
|
||||||
|
}
|
||||||
|
|
||||||
private Drawable testSimpleBig(int repeats = 0) => createSlider(2, repeats: repeats);
|
private Drawable testSimpleBig(int repeats = 0) => createSlider(2, repeats: repeats);
|
||||||
|
|
||||||
private Drawable testSimpleBigLargeStackOffset(int repeats = 0) => createSlider(2, repeats: repeats, stackHeight: 10);
|
private Drawable testSimpleBigLargeStackOffset(int repeats = 0) => createSlider(2, repeats: repeats, stackHeight: 10);
|
||||||
@ -143,7 +204,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
new Vector2(52, -34)
|
new Vector2(52, -34)
|
||||||
}, 700),
|
}, 700),
|
||||||
RepeatCount = repeats,
|
RepeatCount = repeats,
|
||||||
NodeSamples = createEmptySamples(repeats),
|
|
||||||
StackHeight = 10
|
StackHeight = 10
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -174,7 +234,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
new Vector2(distance, 0),
|
new Vector2(distance, 0),
|
||||||
}, distance),
|
}, distance),
|
||||||
RepeatCount = repeats,
|
RepeatCount = repeats,
|
||||||
NodeSamples = createEmptySamples(repeats),
|
|
||||||
StackHeight = stackHeight
|
StackHeight = stackHeight
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -194,7 +253,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
new Vector2(400, 0)
|
new Vector2(400, 0)
|
||||||
}, 600),
|
}, 600),
|
||||||
RepeatCount = repeats,
|
RepeatCount = repeats,
|
||||||
NodeSamples = createEmptySamples(repeats)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return createDrawable(slider, 2, 3);
|
return createDrawable(slider, 2, 3);
|
||||||
@ -218,7 +276,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
new Vector2(430, 0)
|
new Vector2(430, 0)
|
||||||
}),
|
}),
|
||||||
RepeatCount = repeats,
|
RepeatCount = repeats,
|
||||||
NodeSamples = createEmptySamples(repeats)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return createDrawable(slider, 2, 3);
|
return createDrawable(slider, 2, 3);
|
||||||
@ -241,7 +298,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
new Vector2(430, 0)
|
new Vector2(430, 0)
|
||||||
}),
|
}),
|
||||||
RepeatCount = repeats,
|
RepeatCount = repeats,
|
||||||
NodeSamples = createEmptySamples(repeats)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return createDrawable(slider, 2, 3);
|
return createDrawable(slider, 2, 3);
|
||||||
@ -265,7 +321,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
new Vector2(0, -200)
|
new Vector2(0, -200)
|
||||||
}),
|
}),
|
||||||
RepeatCount = repeats,
|
RepeatCount = repeats,
|
||||||
NodeSamples = createEmptySamples(repeats)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return createDrawable(slider, 2, 3);
|
return createDrawable(slider, 2, 3);
|
||||||
@ -275,7 +330,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
private Drawable createCatmull(int repeats = 0)
|
private Drawable createCatmull(int repeats = 0)
|
||||||
{
|
{
|
||||||
var repeatSamples = new List<List<HitSampleInfo>>();
|
var repeatSamples = new List<IList<HitSampleInfo>>();
|
||||||
for (int i = 0; i < repeats; i++)
|
for (int i = 0; i < repeats; i++)
|
||||||
repeatSamples.Add(new List<HitSampleInfo>());
|
repeatSamples.Add(new List<HitSampleInfo>());
|
||||||
|
|
||||||
@ -297,14 +352,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
return createDrawable(slider, 3, 1);
|
return createDrawable(slider, 3, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<List<HitSampleInfo>> createEmptySamples(int repeats)
|
|
||||||
{
|
|
||||||
var repeatSamples = new List<List<HitSampleInfo>>();
|
|
||||||
for (int i = 0; i < repeats; i++)
|
|
||||||
repeatSamples.Add(new List<HitSampleInfo>());
|
|
||||||
return repeatSamples;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Drawable createDrawable(Slider slider, float circleSize, double speedMultiplier)
|
private Drawable createDrawable(Slider slider, float circleSize, double speedMultiplier)
|
||||||
{
|
{
|
||||||
var cpi = new ControlPointInfo();
|
var cpi = new ControlPointInfo();
|
||||||
|
@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal float LazyTravelDistance;
|
internal float LazyTravelDistance;
|
||||||
|
|
||||||
public List<List<HitSampleInfo>> NodeSamples { get; set; } = new List<List<HitSampleInfo>>();
|
public List<IList<HitSampleInfo>> NodeSamples { get; set; } = new List<IList<HitSampleInfo>>();
|
||||||
|
|
||||||
private int repeatCount;
|
private int repeatCount;
|
||||||
|
|
||||||
@ -108,6 +108,12 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
public HitCircle HeadCircle;
|
public HitCircle HeadCircle;
|
||||||
public SliderTailCircle TailCircle;
|
public SliderTailCircle TailCircle;
|
||||||
|
|
||||||
|
public Slider()
|
||||||
|
{
|
||||||
|
SamplesBindable.ItemsAdded += _ => updateNestedSamples();
|
||||||
|
SamplesBindable.ItemsRemoved += _ => updateNestedSamples();
|
||||||
|
}
|
||||||
|
|
||||||
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
|
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
|
||||||
{
|
{
|
||||||
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
||||||
@ -128,20 +134,6 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
foreach (var e in
|
foreach (var e in
|
||||||
SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset))
|
SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset))
|
||||||
{
|
{
|
||||||
var firstSample = Samples.Find(s => s.Name == HitSampleInfo.HIT_NORMAL)
|
|
||||||
?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933)
|
|
||||||
var sampleList = new List<HitSampleInfo>();
|
|
||||||
|
|
||||||
if (firstSample != null)
|
|
||||||
{
|
|
||||||
sampleList.Add(new HitSampleInfo
|
|
||||||
{
|
|
||||||
Bank = firstSample.Bank,
|
|
||||||
Volume = firstSample.Volume,
|
|
||||||
Name = @"slidertick",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (e.Type)
|
switch (e.Type)
|
||||||
{
|
{
|
||||||
case SliderEventType.Tick:
|
case SliderEventType.Tick:
|
||||||
@ -153,7 +145,6 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
Position = Position + Path.PositionAt(e.PathProgress),
|
Position = Position + Path.PositionAt(e.PathProgress),
|
||||||
StackHeight = StackHeight,
|
StackHeight = StackHeight,
|
||||||
Scale = Scale,
|
Scale = Scale,
|
||||||
Samples = sampleList
|
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -163,7 +154,6 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
StartTime = e.Time,
|
StartTime = e.Time,
|
||||||
Position = Position,
|
Position = Position,
|
||||||
StackHeight = StackHeight,
|
StackHeight = StackHeight,
|
||||||
Samples = getNodeSamples(0),
|
|
||||||
SampleControlPoint = SampleControlPoint,
|
SampleControlPoint = SampleControlPoint,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
@ -189,11 +179,12 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
Position = Position + Path.PositionAt(e.PathProgress),
|
Position = Position + Path.PositionAt(e.PathProgress),
|
||||||
StackHeight = StackHeight,
|
StackHeight = StackHeight,
|
||||||
Scale = Scale,
|
Scale = Scale,
|
||||||
Samples = getNodeSamples(e.SpanIndex + 1)
|
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateNestedSamples();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateNestedPositions()
|
private void updateNestedPositions()
|
||||||
@ -205,7 +196,33 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
TailCircle.Position = EndPosition;
|
TailCircle.Position = EndPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<HitSampleInfo> getNodeSamples(int nodeIndex) =>
|
private void updateNestedSamples()
|
||||||
|
{
|
||||||
|
var firstSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL)
|
||||||
|
?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933)
|
||||||
|
var sampleList = new List<HitSampleInfo>();
|
||||||
|
|
||||||
|
if (firstSample != null)
|
||||||
|
{
|
||||||
|
sampleList.Add(new HitSampleInfo
|
||||||
|
{
|
||||||
|
Bank = firstSample.Bank,
|
||||||
|
Volume = firstSample.Volume,
|
||||||
|
Name = @"slidertick",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var tick in NestedHitObjects.OfType<SliderTick>())
|
||||||
|
tick.Samples = sampleList;
|
||||||
|
|
||||||
|
foreach (var repeat in NestedHitObjects.OfType<RepeatPoint>())
|
||||||
|
repeat.Samples = getNodeSamples(repeat.RepeatIndex + 1);
|
||||||
|
|
||||||
|
if (HeadCircle != null)
|
||||||
|
HeadCircle.Samples = getNodeSamples(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IList<HitSampleInfo> getNodeSamples(int nodeIndex) =>
|
||||||
nodeIndex < NodeSamples.Count ? NodeSamples[nodeIndex] : Samples;
|
nodeIndex < NodeSamples.Count ? NodeSamples[nodeIndex] : Samples;
|
||||||
|
|
||||||
public override Judgement CreateJudgement() => new OsuJudgement();
|
public override Judgement CreateJudgement() => new OsuJudgement();
|
||||||
|
@ -79,7 +79,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
|||||||
var curveData = obj as IHasCurve;
|
var curveData = obj as IHasCurve;
|
||||||
|
|
||||||
// Old osu! used hit sounding to determine various hit type information
|
// Old osu! used hit sounding to determine various hit type information
|
||||||
List<HitSampleInfo> samples = obj.Samples;
|
IList<HitSampleInfo> samples = obj.Samples;
|
||||||
|
|
||||||
bool strong = samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH);
|
bool strong = samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH);
|
||||||
|
|
||||||
@ -117,13 +117,13 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
|||||||
|
|
||||||
if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength)
|
if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength)
|
||||||
{
|
{
|
||||||
List<List<HitSampleInfo>> allSamples = curveData != null ? curveData.NodeSamples : new List<List<HitSampleInfo>>(new[] { samples });
|
List<IList<HitSampleInfo>> allSamples = curveData != null ? curveData.NodeSamples : new List<IList<HitSampleInfo>>(new[] { samples });
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
|
||||||
for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing)
|
for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing)
|
||||||
{
|
{
|
||||||
List<HitSampleInfo> currentSamples = allSamples[i];
|
IList<HitSampleInfo> currentSamples = allSamples[i];
|
||||||
bool isRim = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE);
|
bool isRim = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE);
|
||||||
strong = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_FINISH);
|
strong = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_FINISH);
|
||||||
|
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using osu.Framework.Configuration.Tracking;
|
using osu.Framework.Configuration.Tracking;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Configuration
|
namespace osu.Game.Rulesets.Configuration
|
||||||
{
|
{
|
||||||
public interface IRulesetConfigManager : ITrackableConfigManager
|
public interface IRulesetConfigManager : ITrackableConfigManager, IDisposable
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
// Todo: Rulesets should be overriding the resources instead, but we need to figure out where/when to apply overrides first
|
// Todo: Rulesets should be overriding the resources instead, but we need to figure out where/when to apply overrides first
|
||||||
protected virtual string SampleNamespace => null;
|
protected virtual string SampleNamespace => null;
|
||||||
|
|
||||||
protected SkinnableSound Samples;
|
protected SkinnableSound Samples { get; private set; }
|
||||||
|
|
||||||
protected virtual IEnumerable<HitSampleInfo> GetSamples() => HitObject.Samples;
|
protected virtual IEnumerable<HitSampleInfo> GetSamples() => HitObject.Samples;
|
||||||
|
|
||||||
@ -78,6 +78,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public JudgementResult Result { get; private set; }
|
public JudgementResult Result { get; private set; }
|
||||||
|
|
||||||
|
private BindableList<HitSampleInfo> samplesBindable;
|
||||||
private Bindable<double> startTimeBindable;
|
private Bindable<double> startTimeBindable;
|
||||||
private Bindable<int> comboIndexBindable;
|
private Bindable<int> comboIndexBindable;
|
||||||
|
|
||||||
@ -108,22 +109,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}.");
|
throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var samples = GetSamples().ToArray();
|
loadSamples();
|
||||||
|
|
||||||
if (samples.Length > 0)
|
|
||||||
{
|
|
||||||
if (HitObject.SampleControlPoint == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(HitObject.SampleControlPoint), $"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}."
|
|
||||||
+ $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}.");
|
|
||||||
}
|
|
||||||
|
|
||||||
samples = samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s)).ToArray();
|
|
||||||
foreach (var s in samples)
|
|
||||||
s.Namespace = SampleNamespace;
|
|
||||||
|
|
||||||
AddInternal(Samples = new SkinnableSound(samples));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
@ -141,10 +127,40 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
comboIndexBindable.BindValueChanged(_ => updateAccentColour(), true);
|
comboIndexBindable.BindValueChanged(_ => updateAccentColour(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
samplesBindable = HitObject.SamplesBindable.GetBoundCopy();
|
||||||
|
samplesBindable.ItemsAdded += _ => loadSamples();
|
||||||
|
samplesBindable.ItemsRemoved += _ => loadSamples();
|
||||||
|
|
||||||
updateState(ArmedState.Idle, true);
|
updateState(ArmedState.Idle, true);
|
||||||
onDefaultsApplied();
|
onDefaultsApplied();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void loadSamples()
|
||||||
|
{
|
||||||
|
if (Samples != null)
|
||||||
|
{
|
||||||
|
RemoveInternal(Samples);
|
||||||
|
Samples = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var samples = GetSamples().ToArray();
|
||||||
|
|
||||||
|
if (samples.Length <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (HitObject.SampleControlPoint == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(HitObject.SampleControlPoint), $"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}."
|
||||||
|
+ $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
samples = samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s)).ToArray();
|
||||||
|
foreach (var s in samples)
|
||||||
|
s.Namespace = SampleNamespace;
|
||||||
|
|
||||||
|
AddInternal(Samples = new SkinnableSound(samples));
|
||||||
|
}
|
||||||
|
|
||||||
private void onDefaultsApplied() => apply(HitObject);
|
private void onDefaultsApplied() => apply(HitObject);
|
||||||
|
|
||||||
private void apply(HitObject hitObject)
|
private void apply(HitObject hitObject)
|
||||||
|
@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
set => StartTimeBindable.Value = value;
|
set => StartTimeBindable.Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<HitSampleInfo> samples;
|
public readonly BindableList<HitSampleInfo> SamplesBindable = new BindableList<HitSampleInfo>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The samples to be played when this hit object is hit.
|
/// The samples to be played when this hit object is hit.
|
||||||
@ -54,10 +54,14 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
/// and can be treated as the default samples for the hit object.
|
/// and can be treated as the default samples for the hit object.
|
||||||
/// </para>
|
/// </para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<HitSampleInfo> Samples
|
public IList<HitSampleInfo> Samples
|
||||||
{
|
{
|
||||||
get => samples ?? (samples = new List<HitSampleInfo>());
|
get => SamplesBindable;
|
||||||
set => samples = value;
|
set
|
||||||
|
{
|
||||||
|
SamplesBindable.Clear();
|
||||||
|
SamplesBindable.AddRange(value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
|
@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount,
|
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount,
|
||||||
List<List<HitSampleInfo>> nodeSamples)
|
List<IList<HitSampleInfo>> nodeSamples)
|
||||||
{
|
{
|
||||||
newCombo |= forceNewCombo;
|
newCombo |= forceNewCombo;
|
||||||
comboOffset += extraComboOffset;
|
comboOffset += extraComboOffset;
|
||||||
|
@ -184,7 +184,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generate the final per-node samples
|
// Generate the final per-node samples
|
||||||
var nodeSamples = new List<List<HitSampleInfo>>(nodes);
|
var nodeSamples = new List<IList<HitSampleInfo>>(nodes);
|
||||||
for (int i = 0; i < nodes; i++)
|
for (int i = 0; i < nodes; i++)
|
||||||
nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i]));
|
nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i]));
|
||||||
|
|
||||||
@ -282,7 +282,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
|||||||
/// <param name="nodeSamples">The samples to be played when the slider nodes are hit. This includes the head and tail of the slider.</param>
|
/// <param name="nodeSamples">The samples to be played when the slider nodes are hit. This includes the head and tail of the slider.</param>
|
||||||
/// <returns>The hit object.</returns>
|
/// <returns>The hit object.</returns>
|
||||||
protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount,
|
protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount,
|
||||||
List<List<HitSampleInfo>> nodeSamples);
|
List<IList<HitSampleInfo>> nodeSamples);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a legacy Spinner-type hit object.
|
/// Creates a legacy Spinner-type hit object.
|
||||||
|
@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
|||||||
|
|
||||||
public double Distance => Path.Distance;
|
public double Distance => Path.Distance;
|
||||||
|
|
||||||
public List<List<HitSampleInfo>> NodeSamples { get; set; }
|
public List<IList<HitSampleInfo>> NodeSamples { get; set; }
|
||||||
public int RepeatCount { get; set; }
|
public int RepeatCount { get; set; }
|
||||||
|
|
||||||
public double EndTime => StartTime + this.SpanCount() * Distance / Velocity;
|
public double EndTime => StartTime + this.SpanCount() * Distance / Velocity;
|
||||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount,
|
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount,
|
||||||
List<List<HitSampleInfo>> nodeSamples)
|
List<IList<HitSampleInfo>> nodeSamples)
|
||||||
{
|
{
|
||||||
return new ConvertSlider
|
return new ConvertSlider
|
||||||
{
|
{
|
||||||
|
@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount,
|
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount,
|
||||||
List<List<HitSampleInfo>> nodeSamples)
|
List<IList<HitSampleInfo>> nodeSamples)
|
||||||
{
|
{
|
||||||
newCombo |= forceNewCombo;
|
newCombo |= forceNewCombo;
|
||||||
comboOffset += extraComboOffset;
|
comboOffset += extraComboOffset;
|
||||||
|
@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount,
|
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount,
|
||||||
List<List<HitSampleInfo>> nodeSamples)
|
List<IList<HitSampleInfo>> nodeSamples)
|
||||||
{
|
{
|
||||||
return new ConvertSlider
|
return new ConvertSlider
|
||||||
{
|
{
|
||||||
|
@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Objects.Types
|
|||||||
/// n-1: The last repeat.<br />
|
/// n-1: The last repeat.<br />
|
||||||
/// n: The last node.
|
/// n: The last node.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
List<List<HitSampleInfo>> NodeSamples { get; }
|
List<IList<HitSampleInfo>> NodeSamples { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class HasRepeatsExtensions
|
public static class HasRepeatsExtensions
|
||||||
|
@ -43,7 +43,7 @@ namespace osu.Game.Rulesets
|
|||||||
|
|
||||||
// ensures any potential database operations are finalised before game destruction.
|
// ensures any potential database operations are finalised before game destruction.
|
||||||
foreach (var c in configCache.Values)
|
foreach (var c in configCache.Values)
|
||||||
(c as IDisposable)?.Dispose();
|
c?.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ using osu.Game.Rulesets.Edit.Tools;
|
|||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Compose.Components
|
namespace osu.Game.Screens.Edit.Compose.Components
|
||||||
{
|
{
|
||||||
@ -96,11 +97,14 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
protected override bool OnMouseDown(MouseDownEvent e)
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
{
|
{
|
||||||
beginClickSelection(e);
|
beginClickSelection(e);
|
||||||
return true;
|
return e.Button == MouseButton.Left;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnClick(ClickEvent e)
|
protected override bool OnClick(ClickEvent e)
|
||||||
{
|
{
|
||||||
|
if (e.Button == MouseButton.Right)
|
||||||
|
return false;
|
||||||
|
|
||||||
// Deselection should only occur if no selected blueprints are hovered
|
// Deselection should only occur if no selected blueprints are hovered
|
||||||
// A special case for when a blueprint was selected via this click is added since OnClick() may occur outside the hitobject and should not trigger deselection
|
// A special case for when a blueprint was selected via this click is added since OnClick() may occur outside the hitobject and should not trigger deselection
|
||||||
if (endClickSelection() || selectionHandler.SelectedBlueprints.Any(b => b.IsHovered))
|
if (endClickSelection() || selectionHandler.SelectedBlueprints.Any(b => b.IsHovered))
|
||||||
@ -112,6 +116,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
protected override bool OnDoubleClick(DoubleClickEvent e)
|
protected override bool OnDoubleClick(DoubleClickEvent e)
|
||||||
{
|
{
|
||||||
|
if (e.Button == MouseButton.Right)
|
||||||
|
return false;
|
||||||
|
|
||||||
SelectionBlueprint clickedBlueprint = selectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered);
|
SelectionBlueprint clickedBlueprint = selectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered);
|
||||||
|
|
||||||
if (clickedBlueprint == null)
|
if (clickedBlueprint == null)
|
||||||
@ -125,7 +132,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
{
|
{
|
||||||
// Special case for when a drag happened instead of a click
|
// Special case for when a drag happened instead of a click
|
||||||
Schedule(() => endClickSelection());
|
Schedule(() => endClickSelection());
|
||||||
return true;
|
return e.Button == MouseButton.Left;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnMouseMove(MouseMoveEvent e)
|
protected override bool OnMouseMove(MouseMoveEvent e)
|
||||||
@ -141,6 +148,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
protected override bool OnDragStart(DragStartEvent e)
|
protected override bool OnDragStart(DragStartEvent e)
|
||||||
{
|
{
|
||||||
|
if (e.Button == MouseButton.Right)
|
||||||
|
return false;
|
||||||
|
|
||||||
if (!beginSelectionMovement())
|
if (!beginSelectionMovement())
|
||||||
{
|
{
|
||||||
dragBox.UpdateDrag(e);
|
dragBox.UpdateDrag(e);
|
||||||
@ -152,6 +162,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
protected override bool OnDrag(DragEvent e)
|
protected override bool OnDrag(DragEvent e)
|
||||||
{
|
{
|
||||||
|
if (e.Button == MouseButton.Right)
|
||||||
|
return false;
|
||||||
|
|
||||||
if (!moveCurrentSelection(e))
|
if (!moveCurrentSelection(e))
|
||||||
dragBox.UpdateDrag(e);
|
dragBox.UpdateDrag(e);
|
||||||
|
|
||||||
@ -160,6 +173,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
protected override bool OnDragEnd(DragEndEvent e)
|
protected override bool OnDragEnd(DragEndEvent e)
|
||||||
{
|
{
|
||||||
|
if (e.Button == MouseButton.Right)
|
||||||
|
return false;
|
||||||
|
|
||||||
if (!finishSelectionMovement())
|
if (!finishSelectionMovement())
|
||||||
{
|
{
|
||||||
dragBox.FadeOut(250, Easing.OutQuint);
|
dragBox.FadeOut(250, Easing.OutQuint);
|
||||||
|
@ -7,11 +7,15 @@ using System.Linq;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.States;
|
using osu.Framework.Input.States;
|
||||||
|
using osu.Game.Audio;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
@ -22,7 +26,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A component which outlines <see cref="DrawableHitObject"/>s and handles movement of selections.
|
/// A component which outlines <see cref="DrawableHitObject"/>s and handles movement of selections.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SelectionHandler : CompositeDrawable, IKeyBindingHandler<PlatformAction>
|
public class SelectionHandler : CompositeDrawable, IKeyBindingHandler<PlatformAction>, IHasContextMenu
|
||||||
{
|
{
|
||||||
public const float BORDER_RADIUS = 2;
|
public const float BORDER_RADIUS = 2;
|
||||||
|
|
||||||
@ -142,6 +146,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Outline Display
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates whether this <see cref="SelectionHandler"/> is visible.
|
/// Updates whether this <see cref="SelectionHandler"/> is visible.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -176,5 +182,98 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
outline.Size = bottomRight - topLeft;
|
outline.Size = bottomRight - topLeft;
|
||||||
outline.Position = topLeft;
|
outline.Position = topLeft;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Sample Changes
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a hit sample to all selected <see cref="HitObject"/>s.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sampleName">The name of the hit sample.</param>
|
||||||
|
public void AddHitSample(string sampleName)
|
||||||
|
{
|
||||||
|
foreach (var h in SelectedHitObjects)
|
||||||
|
{
|
||||||
|
// Make sure there isn't already an existing sample
|
||||||
|
if (h.Samples.Any(s => s.Name == sampleName))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
h.Samples.Add(new HitSampleInfo { Name = sampleName });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a hit sample from all selected <see cref="HitObject"/>s.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sampleName">The name of the hit sample.</param>
|
||||||
|
public void RemoveHitSample(string sampleName)
|
||||||
|
{
|
||||||
|
foreach (var h in SelectedHitObjects)
|
||||||
|
h.SamplesBindable.RemoveAll(s => s.Name == sampleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Context Menu
|
||||||
|
|
||||||
|
public virtual MenuItem[] ContextMenuItems
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!selectedBlueprints.Any(b => b.IsHovered))
|
||||||
|
return Array.Empty<MenuItem>();
|
||||||
|
|
||||||
|
return new MenuItem[]
|
||||||
|
{
|
||||||
|
new OsuMenuItem("Sound")
|
||||||
|
{
|
||||||
|
Items = new[]
|
||||||
|
{
|
||||||
|
createHitSampleMenuItem("Whistle", HitSampleInfo.HIT_WHISTLE),
|
||||||
|
createHitSampleMenuItem("Clap", HitSampleInfo.HIT_CLAP),
|
||||||
|
createHitSampleMenuItem("Finish", HitSampleInfo.HIT_FINISH)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private MenuItem createHitSampleMenuItem(string name, string sampleName)
|
||||||
|
{
|
||||||
|
return new TernaryStateMenuItem(name, MenuItemType.Standard, setHitSampleState)
|
||||||
|
{
|
||||||
|
State = { Value = getHitSampleState() }
|
||||||
|
};
|
||||||
|
|
||||||
|
void setHitSampleState(TernaryState state)
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case TernaryState.False:
|
||||||
|
RemoveHitSample(sampleName);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TernaryState.True:
|
||||||
|
AddHitSample(sampleName);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TernaryState getHitSampleState()
|
||||||
|
{
|
||||||
|
int countExisting = SelectedHitObjects.Count(h => h.Samples.Any(s => s.Name == sampleName));
|
||||||
|
|
||||||
|
if (countExisting == 0)
|
||||||
|
return TernaryState.False;
|
||||||
|
|
||||||
|
if (countExisting < SelectedHitObjects.Count())
|
||||||
|
return TernaryState.Indeterminate;
|
||||||
|
|
||||||
|
return TernaryState.True;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ using osuTK.Input;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Framework;
|
using osu.Framework;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
|
using osu.Game.Graphics.Cursor;
|
||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
using osu.Game.Screens.Edit.Compose;
|
using osu.Game.Screens.Edit.Compose;
|
||||||
using osu.Game.Screens.Edit.Setup;
|
using osu.Game.Screens.Edit.Setup;
|
||||||
@ -90,7 +91,10 @@ namespace osu.Game.Screens.Edit
|
|||||||
|
|
||||||
fileMenuItems.Add(new EditorMenuItem("Exit", MenuItemType.Standard, this.Exit));
|
fileMenuItems.Add(new EditorMenuItem("Exit", MenuItemType.Standard, this.Exit));
|
||||||
|
|
||||||
InternalChildren = new[]
|
InternalChild = new OsuContextMenuContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new[]
|
||||||
{
|
{
|
||||||
new Container
|
new Container
|
||||||
{
|
{
|
||||||
@ -171,6 +175,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
menuBar.Mode.ValueChanged += onModeChanged;
|
menuBar.Mode.ValueChanged += onModeChanged;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user