1
0
mirror of https://github.com/ppy/osu.git synced 2025-03-11 15:27:20 +08:00

Merge branch 'master' into fix-editor-osu-scale

This commit is contained in:
Dean Herbert 2018-02-22 14:14:41 +09:00 committed by GitHub
commit a3336f2577
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 252 additions and 157 deletions

@ -1 +1 @@
Subproject commit 458ebc2d4626c74bb8059cd28b44eb7adba74fbb Subproject commit f6fa5b80ed06f84c8fd25a2576eea8d51565785c

View File

@ -2,10 +2,12 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Osu.Edit namespace osu.Game.Rulesets.Osu.Edit
@ -25,5 +27,7 @@ namespace osu.Game.Rulesets.Osu.Edit
new HitObjectCompositionTool<Slider>(), new HitObjectCompositionTool<Slider>(),
new HitObjectCompositionTool<Spinner>() new HitObjectCompositionTool<Spinner>()
}; };
protected override ScalableContainer CreateLayerContainer() => new ScalableContainer(OsuPlayfield.BASE_SIZE.X) { RelativeSizeAxes = Axes.Both };
} }
} }

View File

@ -78,7 +78,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
bool isRepeatAtEnd = repeatPoint.RepeatIndex % 2 == 0; bool isRepeatAtEnd = repeatPoint.RepeatIndex % 2 == 0;
List<Vector2> curve = drawableSlider.Body.CurrentCurve; List<Vector2> curve = drawableSlider.Body.CurrentCurve;
Position = isRepeatAtEnd ? end : start; var positionOnCurve = isRepeatAtEnd ? end : start;
Position = positionOnCurve + drawableSlider.HitObject.StackOffset;
if (curve.Count < 2) if (curve.Count < 2)
return; return;
@ -89,10 +90,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
// find the next vector2 in the curve which is not equal to our current position to infer a rotation. // find the next vector2 in the curve which is not equal to our current position to infer a rotation.
for (int i = searchStart; i >= 0 && i < curve.Count; i += direction) for (int i = searchStart; i >= 0 && i < curve.Count; i += direction)
{ {
if (curve[i] == Position) if (curve[i] == positionOnCurve)
continue; continue;
Rotation = MathHelper.RadiansToDegrees((float)Math.Atan2(curve[i].Y - Position.Y, curve[i].X - Position.X)); Rotation = MathHelper.RadiansToDegrees((float)Math.Atan2(curve[i].Y - positionOnCurve.Y, curve[i].X - positionOnCurve.X));
break; break;
} }
} }

View File

@ -7,10 +7,11 @@ using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Judgements;
using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Primitives;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Configuration;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects.Drawables namespace osu.Game.Rulesets.Osu.Objects.Drawables
@ -66,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
var drawableTick = new DrawableSliderTick(tick) var drawableTick = new DrawableSliderTick(tick)
{ {
Position = tick.Position Position = tick.StackedPosition
}; };
ticks.Add(drawableTick); ticks.Add(drawableTick);
@ -78,7 +79,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
var drawableRepeatPoint = new DrawableRepeatPoint(repeatPoint, this) var drawableRepeatPoint = new DrawableRepeatPoint(repeatPoint, this)
{ {
Position = repeatPoint.Position Position = repeatPoint.StackedPosition
}; };
repeatPoints.Add(drawableRepeatPoint); repeatPoints.Add(drawableRepeatPoint);
@ -87,7 +88,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
} }
} }
private int currentSpan; [BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
config.BindWith(OsuSetting.SnakingInSliders, Body.SnakingIn);
config.BindWith(OsuSetting.SnakingOutSliders, Body.SnakingOut);
}
public bool Tracking; public bool Tracking;
protected override void Update() protected override void Update()
@ -96,19 +103,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Tracking = Ball.Tracking; Tracking = Ball.Tracking;
double progress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); double completionProgress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1);
int span = slider.SpanAt(progress);
progress = slider.ProgressAt(progress);
if (span > currentSpan)
currentSpan = span;
//todo: we probably want to reconsider this before adding scoring, but it looks and feels nice. //todo: we probably want to reconsider this before adding scoring, but it looks and feels nice.
if (!HeadCircle.IsHit) if (!HeadCircle.IsHit)
HeadCircle.Position = slider.Curve.PositionAt(progress); HeadCircle.Position = slider.StackedPositionAt(completionProgress);
foreach (var c in components.OfType<ISliderProgress>()) c.UpdateProgress(progress, span); foreach (var c in components.OfType<ISliderProgress>()) c.UpdateProgress(completionProgress);
foreach (var c in components.OfType<ITrackSnaking>()) c.UpdateSnakingPosition(slider.Curve.PositionAt(Body.SnakedStart ?? 0), slider.Curve.PositionAt(Body.SnakedEnd ?? 0)); foreach (var c in components.OfType<ITrackSnaking>()) c.UpdateSnakingPosition(slider.Curve.PositionAt(Body.SnakedStart ?? 0), slider.Curve.PositionAt(Body.SnakedEnd ?? 0));
foreach (var t in components.OfType<IRequireTracking>()) t.Tracking = Ball.Tracking; foreach (var t in components.OfType<IRequireTracking>()) t.Tracking = Ball.Tracking;
} }

View File

@ -139,9 +139,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
} }
} }
public void UpdateProgress(double progress, int span) public void UpdateProgress(double completionProgress)
{ {
Position = slider.Curve.PositionAt(progress); Position = slider.StackedPositionAt(completionProgress);
} }
} }
} }

View File

@ -10,7 +10,6 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Lines; using osu.Framework.Graphics.Lines;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Game.Configuration;
using OpenTK; using OpenTK;
using OpenTK.Graphics.ES30; using OpenTK.Graphics.ES30;
using OpenTK.Graphics; using OpenTK.Graphics;
@ -30,6 +29,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
set { path.PathWidth = value; } set { path.PathWidth = value; }
} }
public readonly Bindable<bool> SnakingIn = new Bindable<bool>();
public readonly Bindable<bool> SnakingOut = new Bindable<bool>();
public double? SnakedStart { get; private set; } public double? SnakedStart { get; private set; }
public double? SnakedEnd { get; private set; } public double? SnakedEnd { get; private set; }
@ -46,8 +48,26 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
return; return;
accentColour = value; accentColour = value;
if (LoadState == LoadState.Ready) if (LoadState >= LoadState.Ready)
Schedule(reloadTexture); reloadTexture();
}
}
private Color4 borderColour = Color4.White;
/// <summary>
/// Used to colour the path border.
/// </summary>
public new Color4 BorderColour
{
get { return borderColour; }
set
{
if (borderColour == value)
return;
borderColour = value;
if (LoadState >= LoadState.Ready)
reloadTexture();
} }
} }
@ -97,15 +117,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
} }
} }
private Bindable<bool> snakingIn;
private Bindable<bool> snakingOut;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuConfigManager config) private void load()
{ {
snakingIn = config.GetBindable<bool>(OsuSetting.SnakingInSliders);
snakingOut = config.GetBindable<bool>(OsuSetting.SnakingOutSliders);
reloadTexture(); reloadTexture();
} }
@ -130,10 +144,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
if (progress <= border_portion) if (progress <= border_portion)
{ {
bytes[i * 4] = 255; bytes[i * 4] = (byte)(BorderColour.R * 255);
bytes[i * 4 + 1] = 255; bytes[i * 4 + 1] = (byte)(BorderColour.G * 255);
bytes[i * 4 + 2] = 255; bytes[i * 4 + 2] = (byte)(BorderColour.B * 255);
bytes[i * 4 + 3] = (byte)(Math.Min(progress / aa_portion, 1) * 255); bytes[i * 4 + 3] = (byte)(Math.Min(progress / aa_portion, 1) * (BorderColour.A * 255));
} }
else else
{ {
@ -167,21 +181,24 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
return true; return true;
} }
public void UpdateProgress(double progress, int span) public void UpdateProgress(double completionProgress)
{ {
var span = slider.SpanAt(completionProgress);
var spanProgress = slider.ProgressAt(completionProgress);
double start = 0; double start = 0;
double end = snakingIn ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / slider.TimeFadein, 0, 1) : 1; double end = SnakingIn ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / slider.TimeFadein, 0, 1) : 1;
if (span >= slider.SpanCount() - 1) if (span >= slider.SpanCount() - 1)
{ {
if (Math.Min(span, slider.SpanCount() - 1) % 2 == 1) if (Math.Min(span, slider.SpanCount() - 1) % 2 == 1)
{ {
start = 0; start = 0;
end = snakingOut ? progress : 1; end = SnakingOut ? spanProgress : 1;
} }
else else
{ {
start = snakingOut ? progress : 0; start = SnakingOut ? spanProgress : 0;
} }
} }

View File

@ -5,6 +5,10 @@ namespace osu.Game.Rulesets.Osu.Objects
{ {
public interface ISliderProgress public interface ISliderProgress
{ {
void UpdateProgress(double progress, int span); /// <summary>
/// Updates the progress of this <see cref="ISliderProgress"/> element along the slider.
/// </summary>
/// <param name="completionProgress">Amount of the slider completed.</param>
void UpdateProgress(double completionProgress);
} }
} }

View File

@ -66,18 +66,6 @@ namespace osu.Game.Rulesets.Osu.Objects
/// </summary> /// </summary>
public double SpanDuration => Duration / this.SpanCount(); public double SpanDuration => Duration / this.SpanCount();
private int stackHeight;
public override int StackHeight
{
get { return stackHeight; }
set
{
stackHeight = value;
Curve.Offset = StackOffset;
}
}
public double Velocity; public double Velocity;
public double TickDistance; public double TickDistance;

View File

@ -315,11 +315,11 @@ namespace osu.Game.Rulesets.Osu.Replays
for (double j = FrameDelay; j < s.Duration; j += FrameDelay) for (double j = FrameDelay; j < s.Duration; j += FrameDelay)
{ {
Vector2 pos = s.PositionAt(j / s.Duration); Vector2 pos = s.StackedPositionAt(j / s.Duration);
AddFrameToReplay(new ReplayFrame(h.StartTime + j, pos.X, pos.Y, button)); AddFrameToReplay(new ReplayFrame(h.StartTime + j, pos.X, pos.Y, button));
} }
AddFrameToReplay(new ReplayFrame(s.EndTime, s.EndPosition.X, s.EndPosition.Y, button)); AddFrameToReplay(new ReplayFrame(s.EndTime, s.StackedEndPosition.X, s.StackedEndPosition.Y, button));
} }
// We only want to let go of our button if we are at the end of the current replay. Otherwise something is still going on after us so we need to keep the button pressed! // We only want to let go of our button if we are at the end of the current replay. Otherwise something is still going on after us so we need to keep the button pressed!

View File

@ -88,10 +88,15 @@ namespace osu.Game.Rulesets.Osu.Tests
AddStep("Catmull Slider", () => testCatmull()); AddStep("Catmull Slider", () => testCatmull());
AddStep("Catmull Slider 1 Repeat", () => testCatmull(1)); AddStep("Catmull Slider 1 Repeat", () => testCatmull(1));
AddStep("Catmull Slider 2 Repeats", () => testCatmull(2)); AddStep("Catmull Slider 2 Repeats", () => testCatmull(2));
AddStep("Big Single, Large StackOffset", () => testSimpleBigLargeStackOffset());
AddStep("Big 1 Repeat, Large StackOffset", () => testSimpleBigLargeStackOffset(1));
} }
private void testSimpleBig(int repeats = 0) => createSlider(2, repeats: repeats); private void testSimpleBig(int repeats = 0) => createSlider(2, repeats: repeats);
private void testSimpleBigLargeStackOffset(int repeats = 0) => createSlider(2, repeats: repeats, stackHeight: 10);
private void testSimpleMedium(int repeats = 0) => createSlider(5, repeats: repeats); private void testSimpleMedium(int repeats = 0) => createSlider(5, repeats: repeats);
private void testSimpleSmall(int repeats = 0) => createSlider(7, repeats: repeats); private void testSimpleSmall(int repeats = 0) => createSlider(7, repeats: repeats);
@ -104,7 +109,7 @@ namespace osu.Game.Rulesets.Osu.Tests
private void testShortHighSpeed(int repeats = 0) => createSlider(distance: 100, repeats: repeats, speedMultiplier: 15); private void testShortHighSpeed(int repeats = 0) => createSlider(distance: 100, repeats: repeats, speedMultiplier: 15);
private void createSlider(float circleSize = 2, float distance = 400, int repeats = 0, double speedMultiplier = 2) private void createSlider(float circleSize = 2, float distance = 400, int repeats = 0, double speedMultiplier = 2, int stackHeight = 0)
{ {
var slider = new Slider var slider = new Slider
{ {
@ -118,7 +123,8 @@ namespace osu.Game.Rulesets.Osu.Tests
}, },
Distance = distance, Distance = distance,
RepeatCount = repeats, RepeatCount = repeats,
RepeatSamples = createEmptySamples(repeats) RepeatSamples = createEmptySamples(repeats),
StackHeight = stackHeight
}; };
addSlider(slider, circleSize, speedMultiplier); addSlider(slider, circleSize, speedMultiplier);

View File

@ -60,7 +60,9 @@ namespace osu.Game.Tests.Visual
AddStep("Load Beatmaps", () => { carousel.BeatmapSets = beatmapSets; }); AddStep("Load Beatmaps", () => { carousel.BeatmapSets = beatmapSets; });
AddUntilStep(() => carousel.BeatmapSets.Any(), "Wait for load"); bool changed = false;
carousel.BeatmapSetsChanged = () => changed = true;
AddUntilStep(() => changed, "Wait for load");
testTraversal(); testTraversal();
testFiltering(); testFiltering();

View File

@ -5,15 +5,13 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using OpenTK; using OpenTK;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Timing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit.Layers.Selection; using osu.Game.Rulesets.Edit.Layers.Selection;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Edit;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Tests.Beatmaps;
namespace osu.Game.Tests.Visual namespace osu.Game.Tests.Visual
{ {
@ -27,44 +25,31 @@ namespace osu.Game.Tests.Visual
}; };
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load(OsuGameBase osuGame)
{ {
var playfield = new OsuEditPlayfield(); osuGame.Beatmap.Value = new TestWorkingBeatmap(new Beatmap
Children = new Drawable[]
{ {
new Container HitObjects = new List<HitObject>
{ {
RelativeSizeAxes = Axes.Both, new HitCircle { Position = new Vector2(256, 192), Scale = 0.5f },
Clock = new FramedClock(new StopwatchClock()), new HitCircle { Position = new Vector2(344, 148), Scale = 0.5f },
Child = playfield new Slider
{
ControlPoints = new List<Vector2>
{
new Vector2(128, 256),
new Vector2(344, 256),
},
Distance = 400,
Position = new Vector2(128, 256),
Velocity = 1,
TickDistance = 100,
Scale = 0.5f,
}
}, },
new SelectionLayer(playfield) });
};
var hitCircle1 = new HitCircle { Position = new Vector2(256, 192), Scale = 0.5f }; Child = new OsuHitObjectComposer(new OsuRuleset());
var hitCircle2 = new HitCircle { Position = new Vector2(344, 148), Scale = 0.5f };
var slider = new Slider
{
ControlPoints = new List<Vector2>
{
new Vector2(128, 256),
new Vector2(344, 256),
},
Distance = 400,
Position = new Vector2(128, 256),
Velocity = 1,
TickDistance = 100,
Scale = 0.5f,
};
hitCircle1.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
hitCircle2.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
playfield.Add(new DrawableHitCircle(hitCircle1));
playfield.Add(new DrawableHitCircle(hitCircle2));
playfield.Add(new DrawableSlider(slider));
} }
} }
} }

View File

@ -25,20 +25,24 @@ namespace osu.Game.Rulesets.Edit
protected ICompositionTool CurrentTool { get; private set; } protected ICompositionTool CurrentTool { get; private set; }
private RulesetContainer rulesetContainer;
private readonly List<Container> layerContainers = new List<Container>();
protected HitObjectComposer(Ruleset ruleset) protected HitObjectComposer(Ruleset ruleset)
{ {
this.ruleset = ruleset; this.ruleset = ruleset;
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuGameBase osuGame) private void load(OsuGameBase osuGame)
{ {
RulesetContainer rulesetContainer;
try try
{ {
rulesetContainer = CreateRulesetContainer(ruleset, osuGame.Beatmap.Value); rulesetContainer = CreateRulesetContainer(ruleset, osuGame.Beatmap.Value);
// TODO: should probably be done at a RulesetContainer level to share logic with Player.
rulesetContainer.Clock = new InterpolatingFramedClock((IAdjustableClock)osuGame.Beatmap.Value.Track ?? new StopwatchClock());
} }
catch (Exception e) catch (Exception e)
{ {
@ -46,6 +50,14 @@ namespace osu.Game.Rulesets.Edit
return; return;
} }
ScalableContainer createLayerContainerWithContent(Drawable content)
{
var container = CreateLayerContainer();
container.Child = content;
layerContainers.Add(container);
return container;
}
RadioButtonCollection toolboxCollection; RadioButtonCollection toolboxCollection;
InternalChild = new GridContainer InternalChild = new GridContainer
{ {
@ -66,20 +78,21 @@ namespace osu.Game.Rulesets.Edit
}, },
new Container new Container
{ {
Name = "Content",
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Masking = true,
BorderColour = Color4.White,
BorderThickness = 2,
Children = new Drawable[] Children = new Drawable[]
{ {
new Box createLayerContainerWithContent(new Container
{ {
Name = "Border",
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Alpha = 0, Masking = true,
AlwaysPresent = true, BorderColour = Color4.White,
}, BorderThickness = 2,
Child = new Box { RelativeSizeAxes = Axes.Both, Alpha = 0, AlwaysPresent = true }
}),
rulesetContainer, rulesetContainer,
new SelectionLayer(rulesetContainer.Playfield) createLayerContainerWithContent(new SelectionLayer(rulesetContainer.Playfield))
} }
} }
}, },
@ -90,8 +103,6 @@ namespace osu.Game.Rulesets.Edit
} }
}; };
rulesetContainer.Clock = new InterpolatingFramedClock((IAdjustableClock)osuGame.Beatmap.Value.Track ?? new StopwatchClock());
toolboxCollection.Items = toolboxCollection.Items =
new[] { new RadioButton("Select", () => setCompositionTool(null)) } new[] { new RadioButton("Select", () => setCompositionTool(null)) }
.Concat( .Concat(
@ -102,10 +113,28 @@ namespace osu.Game.Rulesets.Edit
toolboxCollection.Items[0].Select(); toolboxCollection.Items[0].Select();
} }
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
layerContainers.ForEach(l =>
{
l.Anchor = rulesetContainer.Playfield.Anchor;
l.Origin = rulesetContainer.Playfield.Origin;
l.Position = rulesetContainer.Playfield.Position;
l.Size = rulesetContainer.Playfield.Size;
});
}
private void setCompositionTool(ICompositionTool tool) => CurrentTool = tool; private void setCompositionTool(ICompositionTool tool) => CurrentTool = tool;
protected virtual RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) => ruleset.CreateRulesetContainerWith(beatmap, true); protected virtual RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) => ruleset.CreateRulesetContainerWith(beatmap, true);
protected abstract IReadOnlyList<ICompositionTool> CompositionTools { get; } protected abstract IReadOnlyList<ICompositionTool> CompositionTools { get; }
/// <summary>
/// Creates a <see cref="ScalableContainer"/> which provides a layer above or below the <see cref="Playfield"/>.
/// </summary>
protected virtual ScalableContainer CreateLayerContainer() => new ScalableContainer { RelativeSizeAxes = Axes.Both };
} }
} }

View File

@ -30,21 +30,19 @@ namespace osu.Game.Rulesets.Objects.Types
public static class HasCurveExtensions public static class HasCurveExtensions
{ {
/// <summary> /// <summary>
/// Computes the position on the curve at a given progress, accounting for repeat logic. /// Computes the position on the curve relative to how much of the <see cref="HitObject"/> has been completed.
/// <para>
/// Ranges from [0, 1] where 0 is the beginning of the curve and 1 is the end of the curve.
/// </para>
/// </summary> /// </summary>
/// <param name="obj">The curve.</param> /// <param name="obj">The curve.</param>
/// <param name="progress">[0, 1] where 0 is the beginning of the curve and 1 is the end of the curve.</param> /// <param name="progress">[0, 1] where 0 is the start time of the <see cref="HitObject"/> and 1 is the end time of the <see cref="HitObject"/>.</param>
/// <returns>The position on the curve.</returns>
public static Vector2 PositionAt(this IHasCurve obj, double progress) public static Vector2 PositionAt(this IHasCurve obj, double progress)
=> obj.Curve.PositionAt(obj.ProgressAt(progress)); => obj.Curve.PositionAt(obj.ProgressAt(progress));
/// <summary> /// <summary>
/// Finds the progress along the curve, accounting for repeat logic. /// Computes the progress along the curve relative to how much of the <see cref="HitObject"/> has been completed.
/// </summary> /// </summary>
/// <param name="obj">The curve.</param> /// <param name="obj">The curve.</param>
/// <param name="progress">[0, 1] where 0 is the beginning of the curve and 1 is the end of the curve.</param> /// <param name="progress">[0, 1] where 0 is the start time of the <see cref="HitObject"/> and 1 is the end time of the <see cref="HitObject"/>.</param>
/// <returns>[0, 1] where 0 is the beginning of the curve and 1 is the end of the curve.</returns> /// <returns>[0, 1] where 0 is the beginning of the curve and 1 is the end of the curve.</returns>
public static double ProgressAt(this IHasCurve obj, double progress) public static double ProgressAt(this IHasCurve obj, double progress)
{ {

View File

@ -3,52 +3,37 @@
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using OpenTK;
using osu.Framework.Allocation; using osu.Framework.Allocation;
namespace osu.Game.Rulesets.UI namespace osu.Game.Rulesets.UI
{ {
public abstract class Playfield : Container public abstract class Playfield : ScalableContainer
{ {
/// <summary> /// <summary>
/// The HitObjects contained in this Playfield. /// The HitObjects contained in this Playfield.
/// </summary> /// </summary>
public HitObjectContainer HitObjects { get; private set; } public HitObjectContainer HitObjects { get; private set; }
public Container<Drawable> ScaledContent;
protected override Container<Drawable> Content => content;
private readonly Container<Drawable> content;
private List<Playfield> nestedPlayfields;
/// <summary> /// <summary>
/// All the <see cref="Playfield"/>s nested inside this playfield. /// All the <see cref="Playfield"/>s nested inside this playfield.
/// </summary> /// </summary>
public IReadOnlyList<Playfield> NestedPlayfields => nestedPlayfields; public IReadOnlyList<Playfield> NestedPlayfields => nestedPlayfields;
private List<Playfield> nestedPlayfields;
/// <summary> /// <summary>
/// A container for keeping track of DrawableHitObjects. /// A container for keeping track of DrawableHitObjects.
/// </summary> /// </summary>
/// <param name="customWidth">Whether we want our internal coordinate system to be scaled to a specified width.</param> /// <param name="customWidth">The width to scale the internal coordinate space to.
protected Playfield(float? customWidth = null) /// May be null if scaling based on <paramref name="customHeight"/> is desired. If <paramref name="customHeight"/> is also null, no scaling will occur.
/// </param>
/// <param name="customHeight">The height to scale the internal coordinate space to.
/// May be null if scaling based on <paramref name="customWidth"/> is desired. If <paramref name="customWidth"/> is also null, no scaling will occur.
/// </param>
protected Playfield(float? customWidth = null, float? customHeight = null)
: base(customWidth, customHeight)
{ {
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
AddInternal(ScaledContent = new ScaledContainer
{
CustomWidth = customWidth,
RelativeSizeAxes = Axes.Both,
Children = new[]
{
content = new Container
{
RelativeSizeAxes = Axes.Both,
}
}
});
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -94,22 +79,5 @@ namespace osu.Game.Rulesets.UI
/// Creates the container that will be used to contain the <see cref="DrawableHitObject"/>s. /// Creates the container that will be used to contain the <see cref="DrawableHitObject"/>s.
/// </summary> /// </summary>
protected virtual HitObjectContainer CreateHitObjectContainer() => new HitObjectContainer(); protected virtual HitObjectContainer CreateHitObjectContainer() => new HitObjectContainer();
private class ScaledContainer : Container
{
/// <summary>
/// A value (in game pixels that we should scale our content to match).
/// </summary>
public float? CustomWidth;
//dividing by the customwidth will effectively scale our content to the required container size.
protected override Vector2 DrawScale => CustomWidth.HasValue ? new Vector2(DrawSize.X / CustomWidth.Value) : base.DrawScale;
protected override void Update()
{
base.Update();
RelativeChildSize = new Vector2(DrawScale.X, RelativeChildSize.Y);
}
}
} }
} }

View File

@ -0,0 +1,86 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using OpenTK;
namespace osu.Game.Rulesets.UI
{
/// <summary>
/// A <see cref="Container"/> which can have its internal coordinate system scaled to a specific size.
/// </summary>
public class ScalableContainer : Container
{
/// <summary>
/// The scaled content.
/// </summary>
public readonly Container ScaledContent;
protected override Container<Drawable> Content => content;
private readonly Container content;
/// <summary>
/// A <see cref="Container"/> which can have its internal coordinate system scaled to a specific size.
/// </summary>
/// <param name="customWidth">The width to scale the internal coordinate space to.
/// May be null if scaling based on <paramref name="customHeight"/> is desired. If <paramref name="customHeight"/> is also null, no scaling will occur.
/// </param>
/// <param name="customHeight">The height to scale the internal coordinate space to.
/// May be null if scaling based on <paramref name="customWidth"/> is desired. If <paramref name="customWidth"/> is also null, no scaling will occur.
/// </param>
public ScalableContainer(float? customWidth = null, float? customHeight = null)
{
AddInternal(ScaledContent = new ScaledContainer
{
CustomWidth = customWidth,
CustomHeight = customHeight,
RelativeSizeAxes = Axes.Both,
Child = content = new Container { RelativeSizeAxes = Axes.Both }
});
}
public class ScaledContainer : Container
{
/// <summary>
/// The value to scale the width of the content to match.
/// If null, <see cref="CustomHeight"/> is used.
/// </summary>
public float? CustomWidth;
/// <summary>
/// The value to scale the height of the content to match.
/// if null, <see cref="CustomWidth"/> is used.
/// </summary>
public float? CustomHeight;
/// <summary>
/// The scale that is required for the size of the content to match <see cref="CustomWidth"/> and <see cref="CustomHeight"/>.
/// </summary>
private Vector2 sizeScale
{
get
{
if (CustomWidth.HasValue && CustomHeight.HasValue)
return Vector2.Divide(DrawSize, new Vector2(CustomWidth.Value, CustomHeight.Value));
if (CustomWidth.HasValue)
return new Vector2(DrawSize.X / CustomWidth.Value);
if (CustomHeight.HasValue)
return new Vector2(DrawSize.Y / CustomHeight.Value);
return Vector2.One;
}
}
/// <summary>
/// Scale the content to the required container size by multiplying by <see cref="sizeScale"/>.
/// </summary>
protected override Vector2 DrawScale => sizeScale * base.DrawScale;
protected override void Update()
{
base.Update();
RelativeChildSize = new Vector2(CustomWidth.HasValue ? sizeScale.X : RelativeChildSize.X, CustomHeight.HasValue ? sizeScale.Y : RelativeChildSize.Y);
}
}
}
}

View File

@ -62,9 +62,14 @@ namespace osu.Game.Rulesets.UI.Scrolling
/// Creates a new <see cref="ScrollingPlayfield"/>. /// Creates a new <see cref="ScrollingPlayfield"/>.
/// </summary> /// </summary>
/// <param name="direction">The direction in which <see cref="DrawableHitObject"/>s in this container should scroll.</param> /// <param name="direction">The direction in which <see cref="DrawableHitObject"/>s in this container should scroll.</param>
/// <param name="customWidth">Whether we want our internal coordinate system to be scaled to a specified width</param> /// <param name="customWidth">The width to scale the internal coordinate space to.
protected ScrollingPlayfield(ScrollingDirection direction, float? customWidth = null) /// May be null if scaling based on <paramref name="customHeight"/> is desired. If <paramref name="customHeight"/> is also null, no scaling will occur.
: base(customWidth) /// </param>
/// <param name="customHeight">The height to scale the internal coordinate space to.
/// May be null if scaling based on <paramref name="customWidth"/> is desired. If <paramref name="customWidth"/> is also null, no scaling will occur.
/// </param>
protected ScrollingPlayfield(ScrollingDirection direction, float? customWidth = null, float? customHeight = null)
: base(customWidth, customHeight)
{ {
this.direction = direction; this.direction = direction;
} }

View File

@ -357,6 +357,7 @@
<Compile Include="Overlays\Social\SocialPanel.cs" /> <Compile Include="Overlays\Social\SocialPanel.cs" />
<Compile Include="Rulesets\Mods\IApplicableToDrawableHitObject.cs" /> <Compile Include="Rulesets\Mods\IApplicableToDrawableHitObject.cs" />
<Compile Include="Rulesets\Objects\HitWindows.cs" /> <Compile Include="Rulesets\Objects\HitWindows.cs" />
<Compile Include="Rulesets\UI\ScalableContainer.cs" />
<Compile Include="Screens\Play\PlayerSettings\VisualSettings.cs" /> <Compile Include="Screens\Play\PlayerSettings\VisualSettings.cs" />
<Compile Include="Rulesets\Objects\CatmullApproximator.cs" /> <Compile Include="Rulesets\Objects\CatmullApproximator.cs" />
<Compile Include="Rulesets\UI\HitObjectContainer.cs" /> <Compile Include="Rulesets\UI\HitObjectContainer.cs" />