1
0
mirror of https://github.com/ppy/osu.git synced 2025-03-17 22:17:25 +08:00

Merge branch 'master' into cleanup-intros

This commit is contained in:
Dan Balasescu 2019-10-10 21:58:54 +09:00 committed by GitHub
commit 356c0fdca0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
67 changed files with 706 additions and 537 deletions

View File

@ -93,7 +93,7 @@ namespace osu.Game.Rulesets.Catch.Objects
Scale = 1.0f - 0.7f * (difficulty.CircleSize - 5) / 5;
}
protected override HitWindows CreateHitWindows() => null;
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
}
public enum FruitVisualRepresentation

View File

@ -6,7 +6,6 @@ using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Edit.Blueprints;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
@ -39,6 +38,8 @@ namespace osu.Game.Rulesets.Mania.Tests
AccentColour = { Value = OsuColour.Gray(0.3f) }
}
};
AddBlueprint(new HoldNoteSelectionBlueprint(drawableObject));
}
protected override void Update()
@ -51,7 +52,5 @@ namespace osu.Game.Rulesets.Mania.Tests
nested.Y = (float)(-finalPosition * content.DrawHeight);
}
}
protected override SelectionBlueprint CreateBlueprint() => new HoldNoteSelectionBlueprint(drawableObject);
}
}

View File

@ -5,7 +5,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Edit.Blueprints;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
@ -17,8 +16,6 @@ namespace osu.Game.Rulesets.Mania.Tests
{
public class TestSceneNoteSelectionBlueprint : ManiaSelectionBlueprintTestScene
{
private readonly DrawableNote drawableObject;
protected override Container<Drawable> Content => content ?? base.Content;
private readonly Container content;
@ -27,6 +24,8 @@ namespace osu.Game.Rulesets.Mania.Tests
var note = new Note { Column = 0 };
note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
DrawableNote drawableObject;
base.Content.Child = content = new ScrollingTestContainer(ScrollingDirection.Down)
{
Anchor = Anchor.Centre,
@ -34,8 +33,8 @@ namespace osu.Game.Rulesets.Mania.Tests
Size = new Vector2(50, 20),
Child = drawableObject = new DrawableNote(note)
};
}
protected override SelectionBlueprint CreateBlueprint() => new NoteSelectionBlueprint(drawableObject);
AddBlueprint(new NoteSelectionBlueprint(drawableObject));
}
}
}

View File

@ -101,6 +101,6 @@ namespace osu.Game.Rulesets.Mania.Objects
public override Judgement CreateJudgement() => new HoldNoteJudgement();
protected override HitWindows CreateHitWindows() => null;
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
}
}

View File

@ -14,6 +14,6 @@ namespace osu.Game.Rulesets.Mania.Objects
{
public override Judgement CreateJudgement() => new HoldNoteTickJudgement();
protected override HitWindows CreateHitWindows() => null;
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
}
}

View File

@ -1,10 +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 NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Tests.Visual;
@ -14,16 +15,51 @@ namespace osu.Game.Rulesets.Osu.Tests
{
public class TestSceneHitCircleSelectionBlueprint : SelectionBlueprintTestScene
{
private readonly DrawableHitCircle drawableObject;
private HitCircle hitCircle;
private DrawableHitCircle drawableObject;
private TestBlueprint blueprint;
public TestSceneHitCircleSelectionBlueprint()
[SetUp]
public void Setup() => Schedule(() =>
{
var hitCircle = new HitCircle { Position = new Vector2(256, 192) };
Clear();
hitCircle = new HitCircle { Position = new Vector2(256, 192) };
hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 });
Add(drawableObject = new DrawableHitCircle(hitCircle));
AddBlueprint(blueprint = new TestBlueprint(drawableObject));
});
[Test]
public void TestInitialState()
{
AddAssert("blueprint positioned over hitobject", () => blueprint.CirclePiece.Position == hitCircle.Position);
}
protected override SelectionBlueprint CreateBlueprint() => new HitCircleSelectionBlueprint(drawableObject);
[Test]
public void TestMoveHitObject()
{
AddStep("move hitobject", () => hitCircle.Position = new Vector2(300, 225));
AddAssert("blueprint positioned over hitobject", () => blueprint.CirclePiece.Position == hitCircle.Position);
}
[Test]
public void TestMoveAfterApplyingDefaults()
{
AddStep("apply defaults", () => hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 }));
AddStep("move hitobject", () => hitCircle.Position = new Vector2(300, 225));
AddAssert("blueprint positioned over hitobject", () => blueprint.CirclePiece.Position == hitCircle.Position);
}
private class TestBlueprint : HitCircleSelectionBlueprint
{
public new HitCirclePiece CirclePiece => base.CirclePiece;
public TestBlueprint(DrawableHitCircle hitCircle)
: base(hitCircle)
{
}
}
}
}

View File

@ -3,11 +3,13 @@
using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
using osu.Game.Rulesets.Osu.Objects;
@ -29,11 +31,16 @@ namespace osu.Game.Rulesets.Osu.Tests
typeof(PathControlPointPiece)
};
private readonly DrawableSlider drawableObject;
private Slider slider;
private DrawableSlider drawableObject;
private TestSliderBlueprint blueprint;
public TestSceneSliderSelectionBlueprint()
[SetUp]
public void Setup() => Schedule(() =>
{
var slider = new Slider
Clear();
slider = new Slider
{
Position = new Vector2(256, 192),
Path = new SliderPath(PathType.Bezier, new[]
@ -47,8 +54,71 @@ namespace osu.Game.Rulesets.Osu.Tests
slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 });
Add(drawableObject = new DrawableSlider(slider));
AddBlueprint(blueprint = new TestSliderBlueprint(drawableObject));
});
[Test]
public void TestInitialState()
{
checkPositions();
}
protected override SelectionBlueprint CreateBlueprint() => new SliderSelectionBlueprint(drawableObject);
[Test]
public void TestMoveHitObject()
{
moveHitObject();
checkPositions();
}
[Test]
public void TestMoveAfterApplyingDefaults()
{
AddStep("apply defaults", () => slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 }));
moveHitObject();
checkPositions();
}
private void moveHitObject()
{
AddStep("move hitobject", () =>
{
slider.Position = new Vector2(300, 225);
});
}
private void checkPositions()
{
AddAssert("body positioned correctly", () => blueprint.BodyPiece.Position == slider.Position);
AddAssert("head positioned correctly",
() => Precision.AlmostEquals(blueprint.HeadBlueprint.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.HeadCircle.ScreenSpaceDrawQuad.Centre));
AddAssert("tail positioned correctly",
() => Precision.AlmostEquals(blueprint.TailBlueprint.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.TailCircle.ScreenSpaceDrawQuad.Centre));
}
private class TestSliderBlueprint : SliderSelectionBlueprint
{
public new SliderBodyPiece BodyPiece => base.BodyPiece;
public new TestSliderCircleBlueprint HeadBlueprint => (TestSliderCircleBlueprint)base.HeadBlueprint;
public new TestSliderCircleBlueprint TailBlueprint => (TestSliderCircleBlueprint)base.TailBlueprint;
public TestSliderBlueprint(DrawableSlider slider)
: base(slider)
{
}
protected override SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) => new TestSliderCircleBlueprint(slider, position);
}
private class TestSliderCircleBlueprint : SliderCircleSelectionBlueprint
{
public new HitCirclePiece CirclePiece => base.CirclePiece;
public TestSliderCircleBlueprint(DrawableSlider slider, SliderPosition position)
: base(slider, position)
{
}
}
}
}

View File

@ -7,7 +7,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components;
using osu.Game.Rulesets.Osu.Objects;
@ -25,8 +24,6 @@ namespace osu.Game.Rulesets.Osu.Tests
typeof(SpinnerPiece)
};
private readonly DrawableSpinner drawableSpinner;
public TestSceneSpinnerSelectionBlueprint()
{
var spinner = new Spinner
@ -35,16 +32,19 @@ namespace osu.Game.Rulesets.Osu.Tests
StartTime = -1000,
EndTime = 2000
};
spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 });
DrawableSpinner drawableSpinner;
Add(new Container
{
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.5f),
Child = drawableSpinner = new DrawableSpinner(spinner)
});
}
protected override SelectionBlueprint CreateBlueprint() => new SpinnerSelectionBlueprint(drawableSpinner) { Size = new Vector2(0.5f) };
AddBlueprint(new SpinnerSelectionBlueprint(drawableSpinner) { Size = new Vector2(0.5f) });
}
}
}

View File

@ -0,0 +1,25 @@
// 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.Graphics.Containers;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints
{
/// <summary>
/// A piece of a selection or placement blueprint which visualises an <see cref="OsuHitObject"/>.
/// </summary>
/// <typeparam name="T">The type of <see cref="OsuHitObject"/> which this <see cref="BlueprintPiece{T}"/> visualises.</typeparam>
public abstract class BlueprintPiece<T> : CompositeDrawable
where T : OsuHitObject
{
/// <summary>
/// Updates this <see cref="BlueprintPiece{T}"/> using the properties of a <see cref="OsuHitObject"/>.
/// </summary>
/// <param name="hitObject">The <see cref="OsuHitObject"/> to reference properties from.</param>
public virtual void UpdateFrom(T hitObject)
{
Position = hitObject.Position;
}
}
}

View File

@ -10,18 +10,13 @@ using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components
{
public class HitCirclePiece : HitObjectPiece
public class HitCirclePiece : BlueprintPiece<HitCircle>
{
private readonly HitCircle hitCircle;
public HitCirclePiece(HitCircle hitCircle)
: base(hitCircle)
public HitCirclePiece()
{
this.hitCircle = hitCircle;
Origin = Anchor.Centre;
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
Scale = new Vector2(hitCircle.Scale);
CornerRadius = Size.X / 2;
InternalChild = new RingPiece();
@ -31,12 +26,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components
private void load(OsuColour colours)
{
Colour = colours.Yellow;
PositionBindable.BindValueChanged(_ => UpdatePosition(), true);
StackHeightBindable.BindValueChanged(_ => UpdatePosition());
ScaleBindable.BindValueChanged(scale => Scale = new Vector2(scale.NewValue), true);
}
protected virtual void UpdatePosition() => Position = hitCircle.StackedPosition;
public override void UpdateFrom(HitCircle hitObject)
{
base.UpdateFrom(hitObject);
Scale = new Vector2(hitObject.Scale);
}
}
}

View File

@ -13,10 +13,19 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
{
public new HitCircle HitObject => (HitCircle)base.HitObject;
private readonly HitCirclePiece circlePiece;
public HitCirclePlacementBlueprint()
: base(new HitCircle())
{
InternalChild = new HitCirclePiece(HitObject);
InternalChild = circlePiece = new HitCirclePiece();
}
protected override void Update()
{
base.Update();
circlePiece.UpdateFrom(HitObject);
}
protected override bool OnClick(ClickEvent e)

View File

@ -7,12 +7,21 @@ using osu.Game.Rulesets.Osu.Objects.Drawables;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
{
public class HitCircleSelectionBlueprint : OsuSelectionBlueprint
public class HitCircleSelectionBlueprint : OsuSelectionBlueprint<HitCircle>
{
protected readonly HitCirclePiece CirclePiece;
public HitCircleSelectionBlueprint(DrawableHitCircle hitCircle)
: base(hitCircle)
{
InternalChild = new HitCirclePiece((HitCircle)hitCircle.HitObject);
InternalChild = CirclePiece = new HitCirclePiece();
}
protected override void Update()
{
base.Update();
CirclePiece.UpdateFrom(HitObject);
}
}
}

View File

@ -1,36 +0,0 @@
// 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.Containers;
using osu.Game.Rulesets.Osu.Objects;
using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints
{
/// <summary>
/// A piece of a blueprint which responds to changes in the state of a <see cref="OsuHitObject"/>.
/// </summary>
public abstract class HitObjectPiece : CompositeDrawable
{
protected readonly IBindable<Vector2> PositionBindable = new Bindable<Vector2>();
protected readonly IBindable<int> StackHeightBindable = new Bindable<int>();
protected readonly IBindable<float> ScaleBindable = new Bindable<float>();
private readonly OsuHitObject hitObject;
protected HitObjectPiece(OsuHitObject hitObject)
{
this.hitObject = hitObject;
}
[BackgroundDependencyLoader]
private void load()
{
PositionBindable.BindTo(hitObject.PositionBindable);
StackHeightBindable.BindTo(hitObject.StackHeightBindable);
ScaleBindable.BindTo(hitObject.ScaleBindable);
}
}
}

View File

@ -7,11 +7,12 @@ using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints
{
public class OsuSelectionBlueprint : SelectionBlueprint
public abstract class OsuSelectionBlueprint<T> : SelectionBlueprint
where T : OsuHitObject
{
protected OsuHitObject OsuObject => (OsuHitObject)HitObject.HitObject;
protected new T HitObject => (T)base.HitObject.HitObject;
public OsuSelectionBlueprint(DrawableHitObject hitObject)
protected OsuSelectionBlueprint(DrawableHitObject hitObject)
: base(hitObject)
{
}

View File

@ -1,32 +0,0 @@
// 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.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints
{
/// <summary>
/// A piece of a blueprint which responds to changes in the state of a <see cref="Slider"/>.
/// </summary>
public abstract class SliderPiece : HitObjectPiece
{
protected readonly IBindable<SliderPath> PathBindable = new Bindable<SliderPath>();
private readonly Slider slider;
protected SliderPiece(Slider slider)
: base(slider)
{
this.slider = slider;
}
[BackgroundDependencyLoader]
private void load()
{
PathBindable.BindTo(slider.PathBindable);
}
}
}

View File

@ -14,7 +14,7 @@ using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
public class PathControlPointPiece : CompositeDrawable
public class PathControlPointPiece : BlueprintPiece<Slider>
{
private readonly Slider slider;
private readonly int index;

View File

@ -1,35 +1,29 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
public class PathControlPointVisualiser : SliderPiece
public class PathControlPointVisualiser : CompositeDrawable
{
private readonly Slider slider;
private readonly Container<PathControlPointPiece> pieces;
public PathControlPointVisualiser(Slider slider)
: base(slider)
{
this.slider = slider;
InternalChild = pieces = new Container<PathControlPointPiece> { RelativeSizeAxes = Axes.Both };
}
[BackgroundDependencyLoader]
private void load()
protected override void Update()
{
PathBindable.BindValueChanged(_ => updatePathControlPoints(), true);
}
base.Update();
private void updatePathControlPoints()
{
while (slider.Path.ControlPoints.Length > pieces.Count)
pieces.Add(new PathControlPointPiece(slider, pieces.Count));
while (slider.Path.ControlPoints.Length < pieces.Count)

View File

@ -11,19 +11,15 @@ using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
public class SliderBodyPiece : SliderPiece
public class SliderBodyPiece : BlueprintPiece<Slider>
{
private readonly Slider slider;
private readonly ManualSliderBody body;
public SliderBodyPiece(Slider slider)
: base(slider)
public SliderBodyPiece()
{
this.slider = slider;
InternalChild = body = new ManualSliderBody
{
AccentColour = Color4.Transparent,
AccentColour = Color4.Transparent
};
}
@ -31,19 +27,16 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
private void load(OsuColour colours)
{
body.BorderColour = colours.Yellow;
PositionBindable.BindValueChanged(_ => updatePosition(), true);
ScaleBindable.BindValueChanged(scale => body.PathRadius = scale.NewValue * OsuHitObject.OBJECT_RADIUS, true);
}
private void updatePosition() => Position = slider.StackedPosition;
protected override void Update()
public override void UpdateFrom(Slider hitObject)
{
base.Update();
base.UpdateFrom(hitObject);
body.PathRadius = hitObject.Scale * OsuHitObject.OBJECT_RADIUS;
var vertices = new List<Vector2>();
slider.Path.GetPathToProgress(vertices, 0, 1);
hitObject.Path.GetPathToProgress(vertices, 0, 1);
body.SetVertices(vertices);

View File

@ -1,47 +0,0 @@
// 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.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
public class SliderCirclePiece : HitCirclePiece
{
private readonly IBindable<SliderPath> pathBindable = new Bindable<SliderPath>();
private readonly Slider slider;
private readonly SliderPosition position;
public SliderCirclePiece(Slider slider, SliderPosition position)
: base(slider.HeadCircle)
{
this.slider = slider;
this.position = position;
}
[BackgroundDependencyLoader]
private void load()
{
pathBindable.BindTo(slider.PathBindable);
pathBindable.BindValueChanged(_ => UpdatePosition(), true);
}
protected override void UpdatePosition()
{
switch (position)
{
case SliderPosition.Start:
Position = slider.StackedPosition + slider.Path.PositionAt(0);
break;
case SliderPosition.End:
Position = slider.StackedPosition + slider.Path.PositionAt(1);
break;
}
}
}
}

View File

@ -1,22 +1,34 @@
// 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.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
public class SliderCircleSelectionBlueprint : OsuSelectionBlueprint
public class SliderCircleSelectionBlueprint : OsuSelectionBlueprint<Slider>
{
public SliderCircleSelectionBlueprint(DrawableOsuHitObject hitObject, Slider slider, SliderPosition position)
: base(hitObject)
protected readonly HitCirclePiece CirclePiece;
private readonly SliderPosition position;
public SliderCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position)
: base(slider)
{
InternalChild = new SliderCirclePiece(slider, position);
this.position = position;
InternalChild = CirclePiece = new HitCirclePiece();
Select();
}
protected override void Update()
{
base.Update();
CirclePiece.UpdateFrom(position == SliderPosition.Start ? HitObject.HeadCircle : HitObject.TailCircle);
}
// Todo: This is temporary, since the slider circle masks don't do anything special yet. In the future they will handle input.
public override bool HandlePositionalInput => false;
}

View File

@ -11,6 +11,7 @@ using osu.Game.Graphics;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
using osuTK;
using osuTK.Input;
@ -21,6 +22,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
public new Objects.Slider HitObject => (Objects.Slider)base.HitObject;
private SliderBodyPiece bodyPiece;
private HitCirclePiece headCirclePiece;
private HitCirclePiece tailCirclePiece;
private readonly List<Segment> segments = new List<Segment>();
private Vector2 cursor;
@ -38,9 +43,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
InternalChildren = new Drawable[]
{
new SliderBodyPiece(HitObject),
new SliderCirclePiece(HitObject, SliderPosition.Start),
new SliderCirclePiece(HitObject, SliderPosition.End),
bodyPiece = new SliderBodyPiece(),
headCirclePiece = new HitCirclePiece(),
tailCirclePiece = new HitCirclePiece(),
new PathControlPointVisualiser(HitObject),
};
@ -120,6 +125,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
var newControlPoints = segments.SelectMany(s => s.ControlPoints).Concat(cursor.Yield()).ToArray();
HitObject.Path = new SliderPath(newControlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, newControlPoints);
bodyPiece.UpdateFrom(HitObject);
headCirclePiece.UpdateFrom(HitObject.HeadCircle);
tailCirclePiece.UpdateFrom(HitObject.TailCircle);
}
private void setState(PlacementState newState)

View File

@ -9,9 +9,11 @@ using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
public class SliderSelectionBlueprint : OsuSelectionBlueprint
public class SliderSelectionBlueprint : OsuSelectionBlueprint<Slider>
{
private readonly SliderCircleSelectionBlueprint headBlueprint;
protected readonly SliderBodyPiece BodyPiece;
protected readonly SliderCircleSelectionBlueprint HeadBlueprint;
protected readonly SliderCircleSelectionBlueprint TailBlueprint;
public SliderSelectionBlueprint(DrawableSlider slider)
: base(slider)
@ -20,13 +22,22 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
InternalChildren = new Drawable[]
{
new SliderBodyPiece(sliderObject),
headBlueprint = new SliderCircleSelectionBlueprint(slider.HeadCircle, sliderObject, SliderPosition.Start),
new SliderCircleSelectionBlueprint(slider.TailCircle, sliderObject, SliderPosition.End),
BodyPiece = new SliderBodyPiece(),
HeadBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.Start),
TailBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.End),
new PathControlPointVisualiser(sliderObject),
};
}
public override Vector2 SelectionPoint => headBlueprint.SelectionPoint;
protected override void Update()
{
base.Update();
BodyPiece.UpdateFrom(HitObject);
}
public override Vector2 SelectionPoint => HeadBlueprint.SelectionPoint;
protected virtual SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) => new SliderCircleSelectionBlueprint(slider, position);
}
}

View File

@ -12,17 +12,13 @@ using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components
{
public class SpinnerPiece : HitObjectPiece
public class SpinnerPiece : BlueprintPiece<Spinner>
{
private readonly Spinner spinner;
private readonly CircularContainer circle;
private readonly RingPiece ring;
public SpinnerPiece(Spinner spinner)
: base(spinner)
public SpinnerPiece()
{
this.spinner = spinner;
Origin = Anchor.Centre;
RelativeSizeAxes = Axes.Both;
@ -44,21 +40,20 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components
Origin = Anchor.Centre
}
};
ring.Scale = new Vector2(spinner.Scale);
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Colour = colours.Yellow;
PositionBindable.BindValueChanged(_ => updatePosition(), true);
StackHeightBindable.BindValueChanged(_ => updatePosition());
ScaleBindable.BindValueChanged(scale => ring.Scale = new Vector2(scale.NewValue), true);
}
private void updatePosition() => Position = spinner.Position;
public override void UpdateFrom(Spinner hitObject)
{
base.UpdateFrom(hitObject);
ring.Scale = new Vector2(hitObject.Scale);
}
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => circle.ReceivePositionalInputAt(screenSpacePos);
}

View File

@ -22,7 +22,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
public SpinnerPlacementBlueprint()
: base(new Spinner { Position = OsuPlayfield.BASE_SIZE / 2 })
{
InternalChild = piece = new SpinnerPiece(HitObject) { Alpha = 0.5f };
InternalChild = piece = new SpinnerPiece { Alpha = 0.5f };
}
protected override void Update()
{
base.Update();
piece.UpdateFrom(HitObject);
}
protected override bool OnClick(ClickEvent e)

View File

@ -8,14 +8,21 @@ using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
{
public class SpinnerSelectionBlueprint : OsuSelectionBlueprint
public class SpinnerSelectionBlueprint : OsuSelectionBlueprint<Spinner>
{
private readonly SpinnerPiece piece;
public SpinnerSelectionBlueprint(DrawableSpinner spinner)
: base(spinner)
{
InternalChild = piece = new SpinnerPiece((Spinner)spinner.HitObject);
InternalChild = piece = new SpinnerPiece();
}
protected override void Update()
{
base.Update();
piece.UpdateFrom(HitObject);
}
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => piece.ReceivePositionalInputAt(screenSpacePos);

View File

@ -40,6 +40,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
positionBindable.BindValueChanged(_ => updatePosition());
pathBindable.BindValueChanged(_ => updatePosition(), true);
// TODO: This has no drawable content. Support for skins should be added.
}
protected override void CheckForResult(bool userTriggered, double timeOffset)

View File

@ -30,6 +30,6 @@ namespace osu.Game.Rulesets.Osu.Objects
public override Judgement CreateJudgement() => new OsuJudgement();
protected override HitWindows CreateHitWindows() => null;
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
}
}

View File

@ -54,13 +54,13 @@ namespace osu.Game.Rulesets.Osu.Objects
{
base.Position = value;
endPositionCache.Invalidate();
if (HeadCircle != null)
HeadCircle.Position = value;
if (TailCircle != null)
TailCircle.Position = EndPosition;
endPositionCache.Invalidate();
}
}
@ -205,6 +205,6 @@ namespace osu.Game.Rulesets.Osu.Objects
public override Judgement CreateJudgement() => new OsuJudgement();
protected override HitWindows CreateHitWindows() => null;
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
}
}

View File

@ -25,6 +25,6 @@ namespace osu.Game.Rulesets.Osu.Objects
public override Judgement CreateJudgement() => new OsuSliderTailJudgement();
protected override HitWindows CreateHitWindows() => null;
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
}
}

View File

@ -32,6 +32,6 @@ namespace osu.Game.Rulesets.Osu.Objects
public override Judgement CreateJudgement() => new OsuJudgement();
protected override HitWindows CreateHitWindows() => null;
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
}
}

View File

@ -33,6 +33,6 @@ namespace osu.Game.Rulesets.Osu.Objects
public override Judgement CreateJudgement() => new OsuJudgement();
protected override HitWindows CreateHitWindows() => null;
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
}
}

View File

@ -88,6 +88,6 @@ namespace osu.Game.Rulesets.Taiko.Objects
public override Judgement CreateJudgement() => new TaikoDrumRollJudgement();
protected override HitWindows CreateHitWindows() => null;
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
}
}

View File

@ -27,6 +27,6 @@ namespace osu.Game.Rulesets.Taiko.Objects
public override Judgement CreateJudgement() => new TaikoDrumRollTickJudgement();
protected override HitWindows CreateHitWindows() => null;
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
}
}

View File

@ -11,6 +11,6 @@ namespace osu.Game.Rulesets.Taiko.Objects
{
public override Judgement CreateJudgement() => new TaikoStrongJudgement();
protected override HitWindows CreateHitWindows() => null;
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
}
}

View File

@ -35,6 +35,6 @@ namespace osu.Game.Rulesets.Taiko.Objects
public override Judgement CreateJudgement() => new TaikoSwellJudgement();
protected override HitWindows CreateHitWindows() => null;
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
}
}

View File

@ -11,6 +11,6 @@ namespace osu.Game.Rulesets.Taiko.Objects
{
public override Judgement CreateJudgement() => new TaikoSwellTickJudgement();
protected override HitWindows CreateHitWindows() => null;
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
}
}

View File

@ -1,8 +1,6 @@
// 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 NUnit.Framework;
using osu.Framework.Allocation;
using osu.Game.Rulesets.Osu;
@ -11,10 +9,8 @@ using osu.Game.Screens.Edit.Compose;
namespace osu.Game.Tests.Visual.Editor
{
[TestFixture]
public class TestSceneEditorCompose : EditorClockTestScene
public class TestSceneComposeScreen : EditorClockTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(ComposeScreen) };
[BackgroundDependencyLoader]
private void load()
{

View File

@ -16,6 +16,7 @@ using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Overlays;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
@ -34,6 +35,8 @@ namespace osu.Game.Tests.Visual.SongSelect
private RulesetStore rulesets;
private MusicController music;
private WorkingBeatmap defaultBeatmap;
public override IReadOnlyList<Type> RequiredTypes => new[]
@ -79,6 +82,11 @@ namespace osu.Game.Tests.Visual.SongSelect
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, defaultBeatmap = Beatmap.Default));
Dependencies.Cache(music = new MusicController());
// required to get bindables attached
Add(music);
Beatmap.SetDefault();
Dependencies.Cache(config = new OsuConfigManager(LocalStorage));
@ -93,6 +101,57 @@ namespace osu.Game.Tests.Visual.SongSelect
manager?.Delete(manager.GetAllUsableBeatmapSets());
});
[Test]
public void TestAudioResuming()
{
createSongSelect();
addRulesetImportStep(0);
addRulesetImportStep(0);
checkMusicPlaying(true);
AddStep("select first", () => songSelect.Carousel.SelectBeatmap(songSelect.Carousel.BeatmapSets.First().Beatmaps.First()));
checkMusicPlaying(true);
AddStep("manual pause", () => music.TogglePause());
checkMusicPlaying(false);
AddStep("select next difficulty", () => songSelect.Carousel.SelectNext(skipDifficulties: false));
checkMusicPlaying(false);
AddStep("select next set", () => songSelect.Carousel.SelectNext());
checkMusicPlaying(true);
}
[TestCase(false)]
[TestCase(true)]
public void TestAudioRemainsCorrectOnRulesetChange(bool rulesetsInSameBeatmap)
{
createSongSelect();
// start with non-osu! to avoid convert confusion
changeRuleset(1);
if (rulesetsInSameBeatmap)
AddStep("import multi-ruleset map", () =>
{
var usableRulesets = rulesets.AvailableRulesets.Where(r => r.ID != 2).ToArray();
manager.Import(createTestBeatmapSet(0, usableRulesets)).Wait();
});
else
{
addRulesetImportStep(1);
addRulesetImportStep(0);
}
checkMusicPlaying(true);
AddStep("manual pause", () => music.TogglePause());
checkMusicPlaying(false);
changeRuleset(0);
checkMusicPlaying(!rulesetsInSameBeatmap);
}
[Test]
public void TestDummy()
{
@ -128,12 +187,10 @@ namespace osu.Game.Tests.Visual.SongSelect
}
[Test]
[Ignore("needs fixing")]
public void TestImportUnderDifferentRuleset()
{
createSongSelect();
changeRuleset(2);
addRulesetImportStep(0);
addRulesetImportStep(2);
AddUntilStep("no selection", () => songSelect.Carousel.SelectedBeatmap == null);
}
@ -224,6 +281,9 @@ namespace osu.Game.Tests.Visual.SongSelect
private static int importId;
private int getImportId() => ++importId;
private void checkMusicPlaying(bool playing) =>
AddUntilStep($"music {(playing ? "" : "not ")}playing", () => music.IsPlaying == playing);
private void changeMods(params Mod[] mods) => AddStep($"change mods to {string.Join(", ", mods.Select(m => m.Acronym))}", () => Mods.Value = mods);
private void changeRuleset(int id) => AddStep($"change ruleset to {id}", () => Ruleset.Value = rulesets.AvailableRulesets.First(r => r.ID == id));

View File

@ -12,7 +12,7 @@ using osu.Game.Graphics.Sprites;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Editor
namespace osu.Game.Tests.Visual.UserInterface
{
[TestFixture]
public class TestSceneWaveContainer : OsuTestScene

View File

@ -84,7 +84,7 @@ namespace osu.Game.Overlays.Music
beatmaps.ForEach(addBeatmapSet);
beatmapBacking.BindTo(beatmap);
beatmapBacking.ValueChanged += _ => updateSelectedSet();
beatmapBacking.ValueChanged += _ => Scheduler.AddOnce(updateSelectedSet);
}
private void addBeatmapSet(BeatmapSetInfo obj)

View File

@ -173,7 +173,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
{
UpdateInitialTransforms();
var judgementOffset = Math.Min(HitObject.HitWindows?.WindowFor(HitResult.Miss) ?? double.MaxValue, Result?.TimeOffset ?? 0);
var judgementOffset = Result?.TimeOffset ?? 0;
using (BeginDelayedSequence(InitialLifetimeOffset + judgementOffset, true))
{
@ -379,7 +379,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
// Ensure that the judgement is given a valid time offset, because this may not get set by the caller
var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime;
Result.TimeOffset = Time.Current - endTime;
Result.TimeOffset = Math.Min(HitObject.HitWindows.WindowFor(HitResult.Miss), Time.Current - endTime);
switch (Result.Type)
{

View File

@ -66,7 +66,6 @@ namespace osu.Game.Rulesets.Objects
/// <summary>
/// The hit windows for this <see cref="HitObject"/>.
/// </summary>
[CanBeNull]
public HitWindows HitWindows { get; set; }
private readonly List<HitObject> nestedHitObjects = new List<HitObject>();
@ -113,10 +112,7 @@ namespace osu.Game.Rulesets.Objects
nestedHitObjects.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime));
foreach (var h in nestedHitObjects)
{
h.HitWindows = HitWindows;
h.ApplyDefaults(controlPointInfo, difficulty);
}
}
protected virtual void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
@ -147,7 +143,7 @@ namespace osu.Game.Rulesets.Objects
/// This will only be invoked if <see cref="HitWindows"/> hasn't been set externally (e.g. from a <see cref="BeatmapConverter{T}"/>.
/// </para>
/// </summary>
[CanBeNull]
[NotNull]
protected virtual HitWindows CreateHitWindows() => new HitWindows();
}
}

View File

@ -13,6 +13,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
{
public float X { get; set; }
protected override HitWindows CreateHitWindows() => null;
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
}
}

View File

@ -14,6 +14,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
public double Duration => EndTime - StartTime;
protected override HitWindows CreateHitWindows() => null;
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
}
}

View File

@ -13,6 +13,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
{
public float X { get; set; }
protected override HitWindows CreateHitWindows() => null;
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
}
}

View File

@ -17,6 +17,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
public float X { get; set; }
protected override HitWindows CreateHitWindows() => null;
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
}
}

View File

@ -22,6 +22,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
public int ComboOffset { get; set; }
protected override HitWindows CreateHitWindows() => null;
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
}
}

View File

@ -22,6 +22,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
public int ComboOffset { get; set; }
protected override HitWindows CreateHitWindows() => null;
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
}
}

View File

@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
public float Y => Position.Y;
protected override HitWindows CreateHitWindows() => null;
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
public bool NewCombo { get; set; }

View File

@ -10,6 +10,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
/// </summary>
internal sealed class ConvertHit : HitObject
{
protected override HitWindows CreateHitWindows() => null;
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
}
}

View File

@ -10,6 +10,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
/// </summary>
internal sealed class ConvertSlider : Legacy.ConvertSlider
{
protected override HitWindows CreateHitWindows() => null;
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
}
}

View File

@ -15,6 +15,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
public double Duration => EndTime - StartTime;
protected override HitWindows CreateHitWindows() => null;
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
}
}

View File

@ -3,6 +3,8 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects;
@ -30,6 +32,18 @@ namespace osu.Game.Rulesets.Scoring
private double meh;
private double miss;
/// <summary>
/// An empty <see cref="HitWindows"/> with only <see cref="HitResult.Miss"/> and <see cref="HitResult.Perfect"/>.
/// No time values are provided (meaning instantaneous hit or miss).
/// </summary>
public static HitWindows Empty => new EmptyHitWindows();
public HitWindows()
{
Debug.Assert(GetRanges().Any(r => r.Result == HitResult.Miss), $"{nameof(GetRanges)} should always contain {nameof(HitResult.Miss)}");
Debug.Assert(GetRanges().Any(r => r.Result != HitResult.Miss), $"{nameof(GetRanges)} should always contain at least one result type other than {nameof(HitResult.Miss)}.");
}
/// <summary>
/// Retrieves the <see cref="HitResult"/> with the largest hit window that produces a successful hit.
/// </summary>
@ -168,6 +182,29 @@ namespace osu.Game.Rulesets.Scoring
/// Defaults are provided but can be overridden to customise for a ruleset.
/// </summary>
protected virtual DifficultyRange[] GetRanges() => base_ranges;
public class EmptyHitWindows : HitWindows
{
private static readonly DifficultyRange[] ranges =
{
new DifficultyRange(HitResult.Perfect, 0, 0, 0),
new DifficultyRange(HitResult.Miss, 0, 0, 0),
};
public override bool IsHitResultAllowed(HitResult result)
{
switch (result)
{
case HitResult.Perfect:
case HitResult.Miss:
return true;
}
return false;
}
protected override DifficultyRange[] GetRanges() => ranges;
}
}
public struct DifficultyRange

View File

@ -426,11 +426,11 @@ namespace osu.Game.Rulesets.UI
{
foreach (var h in Objects)
{
if (h.HitWindows != null)
if (h.HitWindows.WindowFor(HitResult.Miss) > 0)
return h.HitWindows;
foreach (var n in h.NestedHitObjects)
if (n.HitWindows != null)
if (h.HitWindows.WindowFor(HitResult.Miss) > 0)
return n.HitWindows;
}

View File

@ -1,6 +1,7 @@
// 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 osuTK;
using osuTK.Graphics;
using osu.Framework.Allocation;
@ -105,6 +106,8 @@ namespace osu.Game.Screens.Edit.Components
TabContainer.Spacing = Vector2.Zero;
tempo_values.ForEach(AddItem);
Current.Value = tempo_values.Last();
}
public class PlaybackTabItem : TabItem<double>

View File

@ -1,121 +1,34 @@
// 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 JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Logging;
using osu.Game.Rulesets.Edit;
using osu.Game.Screens.Edit.Compose.Components;
using osu.Game.Screens.Edit.Compose.Components.Timeline;
using osuTK.Graphics;
using osu.Game.Skinning;
namespace osu.Game.Screens.Edit.Compose
{
public class ComposeScreen : EditorScreen
public class ComposeScreen : EditorScreenWithTimeline
{
private const float vertical_margins = 10;
private const float horizontal_margins = 20;
private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor();
private HitObjectComposer composer;
[BackgroundDependencyLoader(true)]
private void load([CanBeNull] BindableBeatDivisor beatDivisor)
protected override Drawable CreateMainContent()
{
if (beatDivisor != null)
this.beatDivisor.BindTo(beatDivisor);
Container composerContainer;
Children = new Drawable[]
{
new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
{
new Container
{
Name = "Timeline",
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black.Opacity(0.5f)
},
new Container
{
Name = "Timeline content",
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = horizontal_margins, Vertical = vertical_margins },
Child = new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Right = 5 },
Child = new TimelineArea { RelativeSizeAxes = Axes.Both }
},
new BeatDivisorControl(beatDivisor) { RelativeSizeAxes = Axes.Both }
},
},
ColumnDimensions = new[]
{
new Dimension(),
new Dimension(GridSizeMode.Absolute, 90),
}
},
}
}
}
},
new Drawable[]
{
composerContainer = new Container
{
Name = "Composer content",
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = horizontal_margins, Vertical = vertical_margins },
}
}
},
RowDimensions = new[] { new Dimension(GridSizeMode.Absolute, 110) }
},
};
var ruleset = Beatmap.Value.BeatmapInfo.Ruleset?.CreateInstance();
if (ruleset == null)
var composer = ruleset?.CreateHitObjectComposer();
if (composer != null)
{
Logger.Log("Beatmap doesn't have a ruleset assigned.");
// ExitRequested?.Invoke();
return;
var beatmapSkinProvider = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin);
// the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation
// full access to all skin sources.
var rulesetSkinProvider = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider));
// load the skinning hierarchy first.
// this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources.
return beatmapSkinProvider.WithChild(rulesetSkinProvider.WithChild(ruleset.CreateHitObjectComposer()));
}
composer = ruleset.CreateHitObjectComposer();
if (composer == null)
{
Logger.Log($"Ruleset {ruleset.Description} doesn't support hitobject composition.");
// ExitRequested?.Invoke();
return;
}
composerContainer.Child = composer;
return new ScreenWhiteBox.UnderConstructionMessage(ruleset == null ? "This beatmap" : $"{ruleset.Description}'s composer");
}
}
}

View File

@ -1,52 +1,13 @@
// 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.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics.Sprites;
using osuTK.Graphics;
namespace osu.Game.Screens.Edit.Design
{
public class DesignScreen : EditorScreen
{
public DesignScreen()
{
Add(new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
Alpha = 0.35f
},
new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
Alpha = 0.5f
},
new Container
{
AutoSizeAxes = Axes.Both,
Padding = new MarginPadding(20),
Child = new OsuSpriteText { Text = "Design screen" }
}
}
}
}
});
Child = new ScreenWhiteBox.UnderConstructionMessage("Design mode");
}
}
}

View File

@ -19,13 +19,15 @@ using osu.Framework.Timing;
using osu.Game.Graphics.UserInterface;
using osu.Game.Screens.Edit.Components;
using osu.Game.Screens.Edit.Components.Menus;
using osu.Game.Screens.Edit.Compose;
using osu.Game.Screens.Edit.Design;
using osuTK.Input;
using System.Collections.Generic;
using osu.Framework;
using osu.Framework.Input.Bindings;
using osu.Game.Input.Bindings;
using osu.Game.Screens.Edit.Compose;
using osu.Game.Screens.Edit.Setup;
using osu.Game.Screens.Edit.Timing;
using osu.Game.Users;
namespace osu.Game.Screens.Edit
@ -258,6 +260,10 @@ namespace osu.Game.Screens.Edit
switch (e.NewValue)
{
case EditorScreenMode.SongSetup:
currentScreen = new SetupScreen();
break;
case EditorScreenMode.Compose:
currentScreen = new ComposeScreen();
break;
@ -266,8 +272,8 @@ namespace osu.Game.Screens.Edit
currentScreen = new DesignScreen();
break;
default:
currentScreen = new EditorScreen();
case EditorScreenMode.Timing:
currentScreen = new TimingScreen();
break;
}

View File

@ -10,16 +10,17 @@ using osu.Game.Beatmaps;
namespace osu.Game.Screens.Edit
{
/// <summary>
/// TODO: eventually make this inherit Screen and add a local scren stack inside the Editor.
/// TODO: eventually make this inherit Screen and add a local screen stack inside the Editor.
/// </summary>
public class EditorScreen : Container
public abstract class EditorScreen : Container
{
protected readonly IBindable<WorkingBeatmap> Beatmap = new Bindable<WorkingBeatmap>();
[Resolved]
protected IBindable<WorkingBeatmap> Beatmap { get; private set; }
protected override Container<Drawable> Content => content;
private readonly Container content;
public EditorScreen()
protected EditorScreen()
{
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
@ -28,12 +29,6 @@ namespace osu.Game.Screens.Edit
InternalChild = content = new Container { RelativeSizeAxes = Axes.Both };
}
[BackgroundDependencyLoader]
private void load(IBindable<WorkingBeatmap> beatmap)
{
Beatmap.BindTo(beatmap);
}
protected override void LoadComplete()
{
base.LoadComplete();

View File

@ -0,0 +1,107 @@
// 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 JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Screens.Edit.Compose.Components;
using osu.Game.Screens.Edit.Compose.Components.Timeline;
using osuTK.Graphics;
namespace osu.Game.Screens.Edit
{
public abstract class EditorScreenWithTimeline : EditorScreen
{
private const float vertical_margins = 10;
private const float horizontal_margins = 20;
private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor();
[BackgroundDependencyLoader(true)]
private void load([CanBeNull] BindableBeatDivisor beatDivisor)
{
if (beatDivisor != null)
this.beatDivisor.BindTo(beatDivisor);
Container mainContent;
Children = new Drawable[]
{
new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
{
new Container
{
Name = "Timeline",
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black.Opacity(0.5f)
},
new Container
{
Name = "Timeline content",
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = horizontal_margins, Vertical = vertical_margins },
Child = new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Right = 5 },
Child = CreateTimeline()
},
new BeatDivisorControl(beatDivisor) { RelativeSizeAxes = Axes.Both }
},
},
ColumnDimensions = new[]
{
new Dimension(),
new Dimension(GridSizeMode.Absolute, 90),
}
},
}
}
}
},
new Drawable[]
{
mainContent = new Container
{
Name = "Main content",
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = horizontal_margins, Vertical = vertical_margins },
}
}
},
RowDimensions = new[] { new Dimension(GridSizeMode.Absolute, 110) }
},
};
LoadComponentAsync(CreateMainContent(), content =>
{
mainContent.Add(content);
content.FadeInFromZero(300, Easing.OutQuint);
});
}
protected abstract Drawable CreateMainContent();
protected virtual Drawable CreateTimeline() => new TimelineArea { RelativeSizeAxes = Axes.Both };
}
}

View File

@ -0,0 +1,13 @@
// 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.
namespace osu.Game.Screens.Edit.Setup
{
public class SetupScreen : EditorScreen
{
public SetupScreen()
{
Child = new ScreenWhiteBox.UnderConstructionMessage("Setup mode");
}
}
}

View File

@ -0,0 +1,13 @@
// 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.
namespace osu.Game.Screens.Edit.Timing
{
public class TimingScreen : EditorScreen
{
public TimingScreen()
{
Child = new ScreenWhiteBox.UnderConstructionMessage("Timing mode");
}
}
}

View File

@ -48,7 +48,7 @@ namespace osu.Game.Screens.Play.HUD
private void onNewJudgement(JudgementResult result)
{
if (result.HitObject.HitWindows == null)
if (result.HitObject.HitWindows.WindowFor(HitResult.Miss) == 0)
return;
foreach (var c in Children)

View File

@ -20,38 +20,17 @@ namespace osu.Game.Screens
{
public class ScreenWhiteBox : OsuScreen
{
private readonly UnderConstructionMessage message;
private const double transition_time = 1000;
protected virtual IEnumerable<Type> PossibleChildren => null;
private readonly FillFlowContainer textContainer;
private readonly Container boxContainer;
protected override BackgroundScreen CreateBackground() => new BackgroundScreenCustom(@"Backgrounds/bg2");
public override void OnEntering(IScreen last)
{
base.OnEntering(last);
Alpha = 0;
textContainer.Position = new Vector2(DrawSize.X / 16, 0);
boxContainer.ScaleTo(0.2f);
boxContainer.RotateTo(-20);
using (BeginDelayedSequence(300, true))
{
boxContainer.ScaleTo(1, transition_time, Easing.OutElastic);
boxContainer.RotateTo(0, transition_time / 2, Easing.OutQuint);
textContainer.MoveTo(Vector2.Zero, transition_time, Easing.OutExpo);
this.FadeIn(transition_time, Easing.OutExpo);
}
}
public override bool OnExiting(IScreen next)
{
textContainer.MoveTo(new Vector2(DrawSize.X / 16, 0), transition_time, Easing.OutExpo);
message.TextContainer.MoveTo(new Vector2(DrawSize.X / 16, 0), transition_time, Easing.OutExpo);
this.FadeOut(transition_time, Easing.OutExpo);
return base.OnExiting(next);
@ -61,7 +40,7 @@ namespace osu.Game.Screens
{
base.OnSuspending(next);
textContainer.MoveTo(new Vector2(-(DrawSize.X / 16), 0), transition_time, Easing.OutExpo);
message.TextContainer.MoveTo(new Vector2(-(DrawSize.X / 16), 0), transition_time, Easing.OutExpo);
this.FadeOut(transition_time, Easing.OutExpo);
}
@ -69,7 +48,7 @@ namespace osu.Game.Screens
{
base.OnResuming(last);
textContainer.MoveTo(Vector2.Zero, transition_time, Easing.OutExpo);
message.TextContainer.MoveTo(Vector2.Zero, transition_time, Easing.OutExpo);
this.FadeIn(transition_time, Easing.OutExpo);
}
@ -79,65 +58,7 @@ namespace osu.Game.Screens
InternalChildren = new Drawable[]
{
boxContainer = new Container
{
Size = new Vector2(0.3f),
RelativeSizeAxes = Axes.Both,
CornerRadius = 20,
Masking = true,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = getColourFor(GetType()),
Alpha = 0.2f,
Blending = BlendingParameters.Additive,
},
textContainer = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new SpriteIcon
{
Icon = FontAwesome.Solid.UniversalAccess,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Size = new Vector2(50),
},
new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = GetType().Name,
Colour = getColourFor(GetType()).Lighten(0.8f),
Font = OsuFont.GetFont(size: 50),
},
new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = "is not yet ready for use!",
Font = OsuFont.GetFont(size: 20),
},
new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = "please check back a bit later.",
Font = OsuFont.GetFont(size: 14),
},
}
},
}
},
message = new UnderConstructionMessage(GetType().Name),
childModeButtons = new FillFlowContainer
{
Direction = FillDirection.Vertical,
@ -155,24 +76,24 @@ namespace osu.Game.Screens
childModeButtons.Add(new ChildModeButton
{
Text = $@"{t.Name}",
BackgroundColour = getColourFor(t),
HoverColour = getColourFor(t).Lighten(0.2f),
BackgroundColour = getColourFor(t.Name),
HoverColour = getColourFor(t.Name).Lighten(0.2f),
Action = delegate { this.Push(Activator.CreateInstance(t) as Screen); }
});
}
}
}
private Color4 getColourFor(Type type)
private static Color4 getColourFor(object type)
{
int hash = type.Name.GetHashCode();
int hash = type.GetHashCode();
byte r = (byte)MathHelper.Clamp(((hash & 0xFF0000) >> 16) * 0.8f, 20, 255);
byte g = (byte)MathHelper.Clamp(((hash & 0x00FF00) >> 8) * 0.8f, 20, 255);
byte b = (byte)MathHelper.Clamp((hash & 0x0000FF) * 0.8f, 20, 255);
return new Color4(r, g, b, 255);
}
public class ChildModeButton : TwoLayerButton
private class ChildModeButton : TwoLayerButton
{
public ChildModeButton()
{
@ -181,5 +102,104 @@ namespace osu.Game.Screens
Origin = Anchor.BottomRight;
}
}
public class UnderConstructionMessage : CompositeDrawable
{
public FillFlowContainer TextContainer { get; }
private readonly Container boxContainer;
public UnderConstructionMessage(string name)
{
RelativeSizeAxes = Axes.Both;
Size = new Vector2(0.3f);
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
var colour = getColourFor(name);
InternalChildren = new Drawable[]
{
boxContainer = new Container
{
CornerRadius = 20,
Masking = true,
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colour,
Alpha = 0.2f,
Blending = BlendingParameters.Additive,
},
TextContainer = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new SpriteIcon
{
Icon = FontAwesome.Solid.UniversalAccess,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Size = new Vector2(50),
},
new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = name,
Colour = colour.Lighten(0.8f),
Font = OsuFont.GetFont(size: 36),
},
new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = "is not yet ready for use!",
Font = OsuFont.GetFont(size: 20),
},
new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = "please check back a bit later.",
Font = OsuFont.GetFont(size: 14),
},
}
},
}
},
};
}
protected override void LoadComplete()
{
base.LoadComplete();
TextContainer.Position = new Vector2(DrawSize.X / 16, 0);
boxContainer.Hide();
boxContainer.ScaleTo(0.2f);
boxContainer.RotateTo(-20);
using (BeginDelayedSequence(300, true))
{
boxContainer.ScaleTo(1, transition_time, Easing.OutElastic);
boxContainer.RotateTo(0, transition_time / 2, Easing.OutQuint);
TextContainer.MoveTo(Vector2.Zero, transition_time, Easing.OutExpo);
boxContainer.FadeIn(transition_time, Easing.OutExpo);
}
}
}
}
}

View File

@ -412,9 +412,6 @@ namespace osu.Game.Screens.Select
WorkingBeatmap previous = Beatmap.Value;
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap, previous);
if (this.IsCurrentScreen() && Beatmap.Value?.Track != previous?.Track)
ensurePlayingSelected(true);
if (beatmap != null)
{
if (beatmap.BeatmapSetInfoID == beatmapNoDebounce?.BeatmapSetInfoID)
@ -424,6 +421,9 @@ namespace osu.Game.Screens.Select
}
}
if (this.IsCurrentScreen())
ensurePlayingSelected();
UpdateBeatmap(Beatmap.Value);
}
}
@ -581,19 +581,24 @@ namespace osu.Game.Screens.Select
beatmap.Track.Looping = true;
}
private void ensurePlayingSelected(bool restart = false)
private readonly WeakReference<Track> lastTrack = new WeakReference<Track>(null);
/// <summary>
/// Ensures some music is playing for the current track.
/// Will resume playback from a manual user pause if the track has changed.
/// </summary>
private void ensurePlayingSelected()
{
Track track = Beatmap.Value.Track;
if (!track.IsRunning)
{
track.RestartPoint = Beatmap.Value.Metadata.PreviewTime;
bool isNewTrack = !lastTrack.TryGetTarget(out var last) || last != track;
if (restart)
track.Restart();
else
track.Start();
}
track.RestartPoint = Beatmap.Value.Metadata.PreviewTime;
if (!track.IsRunning && (music?.IsUserPaused != true || isNewTrack))
track.Restart();
lastTrack.SetTarget(track);
}
private void onBeatmapSetAdded(BeatmapSetInfo s) => Carousel.UpdateBeatmapSet(s);

View File

@ -53,7 +53,7 @@ namespace osu.Game.Skinning
if (AllowTextureLookup(componentName) && (sourceTexture = skin?.GetTexture(componentName)) != null)
return sourceTexture;
return fallbackSource.GetTexture(componentName);
return fallbackSource?.GetTexture(componentName);
}
public SampleChannel GetSample(ISampleInfo sampleInfo)

View File

@ -1,10 +1,8 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events;
using osu.Framework.Timing;
using osu.Game.Rulesets.Edit;
@ -12,8 +10,6 @@ namespace osu.Game.Tests.Visual
{
public abstract class SelectionBlueprintTestScene : OsuTestScene
{
private SelectionBlueprint blueprint;
protected override Container<Drawable> Content => content ?? base.Content;
private readonly Container content;
@ -26,25 +22,13 @@ namespace osu.Game.Tests.Visual
});
}
[BackgroundDependencyLoader]
private void load()
protected void AddBlueprint(SelectionBlueprint blueprint)
{
blueprint = CreateBlueprint();
blueprint.Depth = float.MinValue;
blueprint.SelectionRequested += (_, __) => blueprint.Select();
Add(blueprint);
AddStep("Select", () => blueprint.Select());
AddStep("Deselect", () => blueprint.Deselect());
Add(blueprint.With(d =>
{
d.Depth = float.MinValue;
d.Select();
}));
}
protected override bool OnClick(ClickEvent e)
{
blueprint.Deselect();
return true;
}
protected abstract SelectionBlueprint CreateBlueprint();
}
}