mirror of
https://github.com/ppy/osu
synced 2025-03-24 11:56:58 +00:00
Merge branch 'master' into search-filter
This commit is contained in:
commit
823078ed2d
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
@ -9,6 +10,7 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
using osu.Game.Rulesets.Osu.Utils;
|
using osu.Game.Rulesets.Osu.Utils;
|
||||||
|
|
||||||
@ -25,40 +27,100 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
private static readonly float playfield_diagonal = OsuPlayfield.BASE_SIZE.LengthFast;
|
private static readonly float playfield_diagonal = OsuPlayfield.BASE_SIZE.LengthFast;
|
||||||
|
|
||||||
private Random? rng;
|
private Random random = null!;
|
||||||
|
|
||||||
public void ApplyToBeatmap(IBeatmap beatmap)
|
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||||
{
|
{
|
||||||
if (!(beatmap is OsuBeatmap osuBeatmap))
|
if (beatmap is not OsuBeatmap osuBeatmap)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Seed.Value ??= RNG.Next();
|
Seed.Value ??= RNG.Next();
|
||||||
|
|
||||||
rng = new Random((int)Seed.Value);
|
random = new Random((int)Seed.Value);
|
||||||
|
|
||||||
var positionInfos = OsuHitObjectGenerationUtils.GeneratePositionInfos(osuBeatmap.HitObjects);
|
var positionInfos = OsuHitObjectGenerationUtils.GeneratePositionInfos(osuBeatmap.HitObjects);
|
||||||
|
|
||||||
float rateOfChangeMultiplier = 0;
|
// Offsets the angles of all hit objects in a "section" by the same amount.
|
||||||
|
float sectionOffset = 0;
|
||||||
|
|
||||||
foreach (var positionInfo in positionInfos)
|
// Whether the angles are positive or negative (clockwise or counter-clockwise flow).
|
||||||
|
bool flowDirection = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < positionInfos.Count; i++)
|
||||||
{
|
{
|
||||||
// rateOfChangeMultiplier only changes every 5 iterations in a combo
|
if (shouldStartNewSection(osuBeatmap, positionInfos, i))
|
||||||
// to prevent shaky-line-shaped streams
|
|
||||||
if (positionInfo.HitObject.IndexInCurrentCombo % 5 == 0)
|
|
||||||
rateOfChangeMultiplier = (float)rng.NextDouble() * 2 - 1;
|
|
||||||
|
|
||||||
if (positionInfo == positionInfos.First())
|
|
||||||
{
|
{
|
||||||
positionInfo.DistanceFromPrevious = (float)(rng.NextDouble() * OsuPlayfield.BASE_SIZE.Y / 2);
|
sectionOffset = OsuHitObjectGenerationUtils.RandomGaussian(random, 0, 0.0008f);
|
||||||
positionInfo.RelativeAngle = (float)(rng.NextDouble() * 2 * Math.PI - Math.PI);
|
flowDirection = !flowDirection;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == 0)
|
||||||
|
{
|
||||||
|
positionInfos[i].DistanceFromPrevious = (float)(random.NextDouble() * OsuPlayfield.BASE_SIZE.Y / 2);
|
||||||
|
positionInfos[i].RelativeAngle = (float)(random.NextDouble() * 2 * Math.PI - Math.PI);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
positionInfo.RelativeAngle = rateOfChangeMultiplier * 2 * (float)Math.PI * Math.Min(1f, positionInfo.DistanceFromPrevious / (playfield_diagonal * 0.5f));
|
// Offsets only the angle of the current hit object if a flow change occurs.
|
||||||
|
float flowChangeOffset = 0;
|
||||||
|
|
||||||
|
// Offsets only the angle of the current hit object.
|
||||||
|
float oneTimeOffset = OsuHitObjectGenerationUtils.RandomGaussian(random, 0, 0.002f);
|
||||||
|
|
||||||
|
if (shouldApplyFlowChange(positionInfos, i))
|
||||||
|
{
|
||||||
|
flowChangeOffset = OsuHitObjectGenerationUtils.RandomGaussian(random, 0, 0.002f);
|
||||||
|
flowDirection = !flowDirection;
|
||||||
|
}
|
||||||
|
|
||||||
|
float totalOffset =
|
||||||
|
// sectionOffset and oneTimeOffset should mainly affect patterns with large spacing.
|
||||||
|
(sectionOffset + oneTimeOffset) * positionInfos[i].DistanceFromPrevious +
|
||||||
|
// flowChangeOffset should mainly affect streams.
|
||||||
|
flowChangeOffset * (playfield_diagonal - positionInfos[i].DistanceFromPrevious);
|
||||||
|
|
||||||
|
positionInfos[i].RelativeAngle = getRelativeTargetAngle(positionInfos[i].DistanceFromPrevious, totalOffset, flowDirection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
osuBeatmap.HitObjects = OsuHitObjectGenerationUtils.RepositionHitObjects(positionInfos);
|
osuBeatmap.HitObjects = OsuHitObjectGenerationUtils.RepositionHitObjects(positionInfos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <param name="targetDistance">The target distance between the previous and the current <see cref="OsuHitObject"/>.</param>
|
||||||
|
/// <param name="offset">The angle (in rad) by which the target angle should be offset.</param>
|
||||||
|
/// <param name="flowDirection">Whether the relative angle should be positive or negative.</param>
|
||||||
|
private static float getRelativeTargetAngle(float targetDistance, float offset, bool flowDirection)
|
||||||
|
{
|
||||||
|
float angle = (float)(2.16 / (1 + 200 * Math.Exp(0.036 * (targetDistance - 310))) + 0.5 + offset);
|
||||||
|
float relativeAngle = (float)Math.PI - angle;
|
||||||
|
return flowDirection ? -relativeAngle : relativeAngle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <returns>Whether a new section should be started at the current <see cref="OsuHitObject"/>.</returns>
|
||||||
|
private bool shouldStartNewSection(OsuBeatmap beatmap, IReadOnlyList<OsuHitObjectGenerationUtils.ObjectPositionInfo> positionInfos, int i)
|
||||||
|
{
|
||||||
|
if (i == 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Exclude new-combo-spam and 1-2-combos.
|
||||||
|
bool previousObjectStartedCombo = positionInfos[Math.Max(0, i - 2)].HitObject.IndexInCurrentCombo > 1 &&
|
||||||
|
positionInfos[i - 1].HitObject.NewCombo;
|
||||||
|
bool previousObjectWasOnDownbeat = OsuHitObjectGenerationUtils.IsHitObjectOnBeat(beatmap, positionInfos[i - 1].HitObject, true);
|
||||||
|
bool previousObjectWasOnBeat = OsuHitObjectGenerationUtils.IsHitObjectOnBeat(beatmap, positionInfos[i - 1].HitObject);
|
||||||
|
|
||||||
|
return (previousObjectStartedCombo && random.NextDouble() < 0.6f) ||
|
||||||
|
previousObjectWasOnDownbeat ||
|
||||||
|
(previousObjectWasOnBeat && random.NextDouble() < 0.4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <returns>Whether a flow change should be applied at the current <see cref="OsuHitObject"/>.</returns>
|
||||||
|
private bool shouldApplyFlowChange(IReadOnlyList<OsuHitObjectGenerationUtils.ObjectPositionInfo> positionInfos, int i)
|
||||||
|
{
|
||||||
|
// Exclude new-combo-spam and 1-2-combos.
|
||||||
|
bool previousObjectStartedCombo = positionInfos[Math.Max(0, i - 2)].HitObject.IndexInCurrentCombo > 1 &&
|
||||||
|
positionInfos[i - 1].HitObject.NewCombo;
|
||||||
|
|
||||||
|
return previousObjectStartedCombo && random.NextDouble() < 0.6f;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ using System.Linq;
|
|||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -186,5 +187,39 @@ namespace osu.Game.Rulesets.Osu.Utils
|
|||||||
length * MathF.Sin(angle)
|
length * MathF.Sin(angle)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <param name="beatmap">The beatmap hitObject is a part of.</param>
|
||||||
|
/// <param name="hitObject">The <see cref="OsuHitObject"/> that should be checked.</param>
|
||||||
|
/// <param name="downbeatsOnly">If true, this method only returns true if hitObject is on a downbeat.
|
||||||
|
/// If false, it returns true if hitObject is on any beat.</param>
|
||||||
|
/// <returns>true if hitObject is on a (down-)beat, false otherwise.</returns>
|
||||||
|
public static bool IsHitObjectOnBeat(OsuBeatmap beatmap, OsuHitObject hitObject, bool downbeatsOnly = false)
|
||||||
|
{
|
||||||
|
var timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime);
|
||||||
|
|
||||||
|
double timeSinceTimingPoint = hitObject.StartTime - timingPoint.Time;
|
||||||
|
|
||||||
|
double beatLength = timingPoint.BeatLength;
|
||||||
|
|
||||||
|
if (downbeatsOnly)
|
||||||
|
beatLength *= timingPoint.TimeSignature.Numerator;
|
||||||
|
|
||||||
|
// Ensure within 1ms of expected location.
|
||||||
|
return Math.Abs(timeSinceTimingPoint + 1) % beatLength < 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a random number from a normal distribution using the Box-Muller transform.
|
||||||
|
/// </summary>
|
||||||
|
public static float RandomGaussian(Random rng, float mean = 0, float stdDev = 1)
|
||||||
|
{
|
||||||
|
// Generate 2 random numbers in the interval (0,1].
|
||||||
|
// x1 must not be 0 since log(0) = undefined.
|
||||||
|
double x1 = 1 - rng.NextDouble();
|
||||||
|
double x2 = 1 - rng.NextDouble();
|
||||||
|
|
||||||
|
double stdNormal = Math.Sqrt(-2 * Math.Log(x1)) * Math.Sin(2 * Math.PI * x2);
|
||||||
|
return mean + stdDev * (float)stdNormal;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,12 +15,14 @@ using osu.Framework.Testing;
|
|||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.Drawables;
|
using osu.Game.Beatmaps.Drawables;
|
||||||
using osu.Game.Beatmaps.Drawables.Cards;
|
using osu.Game.Beatmaps.Drawables.Cards;
|
||||||
|
using osu.Game.Beatmaps.Drawables.Cards.Buttons;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests;
|
using osu.Game.Online.API.Requests;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Beatmaps
|
namespace osu.Game.Tests.Visual.Beatmaps
|
||||||
{
|
{
|
||||||
@ -295,5 +297,22 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
|||||||
|
|
||||||
BeatmapCardNormal firstCard() => this.ChildrenOfType<BeatmapCardNormal>().First();
|
BeatmapCardNormal firstCard() => this.ChildrenOfType<BeatmapCardNormal>().First();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPlayButtonByTouchInput()
|
||||||
|
{
|
||||||
|
AddStep("create cards", () => Child = createContent(OverlayColourScheme.Blue, beatmapSetInfo => new BeatmapCardNormal(beatmapSetInfo)));
|
||||||
|
|
||||||
|
// mimics touch input
|
||||||
|
AddStep("touch play button area on first card", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(firstCard().ChildrenOfType<PlayButton>().Single());
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("first card is playing", () => firstCard().ChildrenOfType<PlayButton>().Single().Playing.Value);
|
||||||
|
|
||||||
|
BeatmapCardNormal firstCard() => this.ChildrenOfType<BeatmapCardNormal>().First();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
AddStep("enable dim", () => thumbnail.Dimmed.Value = true);
|
AddStep("enable dim", () => thumbnail.Dimmed.Value = true);
|
||||||
AddUntilStep("button visible", () => playButton.IsPresent);
|
AddUntilStep("button visible", () => playButton.Alpha == 1);
|
||||||
|
|
||||||
AddStep("click button", () =>
|
AddStep("click button", () =>
|
||||||
{
|
{
|
||||||
@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
|||||||
|
|
||||||
AddStep("disable dim", () => thumbnail.Dimmed.Value = false);
|
AddStep("disable dim", () => thumbnail.Dimmed.Value = false);
|
||||||
AddWaitStep("wait some", 3);
|
AddWaitStep("wait some", 3);
|
||||||
AddAssert("button still visible", () => playButton.IsPresent);
|
AddAssert("button still visible", () => playButton.Alpha == 1);
|
||||||
|
|
||||||
// The track plays in real-time, so we need to check for progress in increments to avoid timeout.
|
// The track plays in real-time, so we need to check for progress in increments to avoid timeout.
|
||||||
AddUntilStep("progress > 0.25", () => thumbnail.ChildrenOfType<PlayButton>().Single().Progress.Value > 0.25);
|
AddUntilStep("progress > 0.25", () => thumbnail.ChildrenOfType<PlayButton>().Single().Progress.Value > 0.25);
|
||||||
@ -78,7 +78,7 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
|||||||
AddUntilStep("progress > 0.75", () => thumbnail.ChildrenOfType<PlayButton>().Single().Progress.Value > 0.75);
|
AddUntilStep("progress > 0.75", () => thumbnail.ChildrenOfType<PlayButton>().Single().Progress.Value > 0.75);
|
||||||
|
|
||||||
AddUntilStep("wait for track to end", () => !playButton.Playing.Value);
|
AddUntilStep("wait for track to end", () => !playButton.Playing.Value);
|
||||||
AddUntilStep("button hidden", () => !playButton.IsPresent);
|
AddUntilStep("button hidden", () => playButton.Alpha == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void iconIs(IconUsage usage) => AddUntilStep("icon is correct", () => playButton.ChildrenOfType<SpriteIcon>().Any(icon => icon.Icon.Equals(usage)));
|
private void iconIs(IconUsage usage) => AddUntilStep("icon is correct", () => playButton.ChildrenOfType<SpriteIcon>().Any(icon => icon.Icon.Equals(usage)));
|
||||||
|
@ -0,0 +1,94 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
|
{
|
||||||
|
public class TestSceneSizePreservingSpriteText : OsuGridTestScene
|
||||||
|
{
|
||||||
|
private readonly List<Container> parentContainers = new List<Container>();
|
||||||
|
private readonly List<UprightAspectMaintainingContainer> childContainers = new List<UprightAspectMaintainingContainer>();
|
||||||
|
private readonly OsuSpriteText osuSpriteText = new OsuSpriteText();
|
||||||
|
private readonly SizePreservingSpriteText sizePreservingSpriteText = new SizePreservingSpriteText();
|
||||||
|
|
||||||
|
public TestSceneSizePreservingSpriteText()
|
||||||
|
: base(1, 2)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 2; i++)
|
||||||
|
{
|
||||||
|
UprightAspectMaintainingContainer childContainer;
|
||||||
|
Container parentContainer = new Container
|
||||||
|
{
|
||||||
|
Origin = Anchor.BottomRight,
|
||||||
|
Anchor = Anchor.BottomCentre,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Rotation = 45,
|
||||||
|
Y = -200,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Colour4.Red,
|
||||||
|
},
|
||||||
|
childContainer = new UprightAspectMaintainingContainer
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Colour4.Blue,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Container cellInfo = new Container
|
||||||
|
{
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Margin = new MarginPadding
|
||||||
|
{
|
||||||
|
Top = 100,
|
||||||
|
},
|
||||||
|
Child = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = (i == 0) ? "OsuSpriteText" : "SizePreservingSpriteText",
|
||||||
|
Font = OsuFont.GetFont(Typeface.Inter, weight: FontWeight.Bold, size: 40),
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
parentContainers.Add(parentContainer);
|
||||||
|
childContainers.Add(childContainer);
|
||||||
|
Cell(i).Add(cellInfo);
|
||||||
|
Cell(i).Add(parentContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
childContainers[0].Add(osuSpriteText);
|
||||||
|
childContainers[1].Add(sizePreservingSpriteText);
|
||||||
|
osuSpriteText.Font = sizePreservingSpriteText.Font = OsuFont.GetFont(Typeface.Venera, weight: FontWeight.Bold, size: 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
osuSpriteText.Text = sizePreservingSpriteText.Text = DateTime.Now.ToString(CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,244 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
|
{
|
||||||
|
public class TestSceneUprightAspectMaintainingContainer : OsuGridTestScene
|
||||||
|
{
|
||||||
|
private const int rows = 3;
|
||||||
|
private const int columns = 4;
|
||||||
|
|
||||||
|
private readonly ScaleMode[] scaleModeValues = { ScaleMode.NoScaling, ScaleMode.Horizontal, ScaleMode.Vertical };
|
||||||
|
private readonly float[] scalingFactorValues = { 1.0f / 3, 1.0f / 2, 1.0f, 1.5f };
|
||||||
|
|
||||||
|
private readonly List<List<Container>> parentContainers = new List<List<Container>>(rows);
|
||||||
|
private readonly List<List<UprightAspectMaintainingContainer>> childContainers = new List<List<UprightAspectMaintainingContainer>>(rows);
|
||||||
|
|
||||||
|
// Preferably should be set to (4 * 2^n)
|
||||||
|
private const int rotation_step_count = 3;
|
||||||
|
|
||||||
|
private readonly List<int> flipStates = new List<int>();
|
||||||
|
private readonly List<float> rotationSteps = new List<float>();
|
||||||
|
private readonly List<float> scaleSteps = new List<float>();
|
||||||
|
|
||||||
|
public TestSceneUprightAspectMaintainingContainer()
|
||||||
|
: base(rows, columns)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < rows; i++)
|
||||||
|
{
|
||||||
|
parentContainers.Add(new List<Container>());
|
||||||
|
childContainers.Add(new List<UprightAspectMaintainingContainer>());
|
||||||
|
|
||||||
|
for (int j = 0; j < columns; j++)
|
||||||
|
{
|
||||||
|
UprightAspectMaintainingContainer child;
|
||||||
|
Container parent = new Container
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Height = 80,
|
||||||
|
Width = 80,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = new Color4(255, 0, 0, 160),
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = "Parent",
|
||||||
|
},
|
||||||
|
child = new UprightAspectMaintainingContainer
|
||||||
|
{
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
|
||||||
|
// These are the parameters being Tested
|
||||||
|
Scaling = scaleModeValues[i],
|
||||||
|
ScalingFactor = scalingFactorValues[j],
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = new Color4(0, 0, 255, 160),
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = "Text",
|
||||||
|
Font = OsuFont.Numeric,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Padding = new MarginPadding
|
||||||
|
{
|
||||||
|
Horizontal = 4,
|
||||||
|
Vertical = 4,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Container cellInfo = new Container
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = "Scaling: " + scaleModeValues[i].ToString(),
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = "ScalingFactor: " + scalingFactorValues[j].ToString("0.00"),
|
||||||
|
Margin = new MarginPadding
|
||||||
|
{
|
||||||
|
Top = 15,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Cell(i * columns + j).Add(cellInfo);
|
||||||
|
Cell(i * columns + j).Add(parent);
|
||||||
|
parentContainers[i].Add(parent);
|
||||||
|
childContainers[i].Add(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flipStates.AddRange(new[] { 1, -1 });
|
||||||
|
rotationSteps.AddRange(Enumerable.Range(0, rotation_step_count).Select(x => 360f * ((float)x / rotation_step_count)));
|
||||||
|
scaleSteps.AddRange(new[] { 1, 0.3f, 1.5f });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ExplicitlySizedParent()
|
||||||
|
{
|
||||||
|
var parentStates = from xFlip in flipStates
|
||||||
|
from yFlip in flipStates
|
||||||
|
from xScale in scaleSteps
|
||||||
|
from yScale in scaleSteps
|
||||||
|
from rotation in rotationSteps
|
||||||
|
select new { xFlip, yFlip, xScale, yScale, rotation };
|
||||||
|
|
||||||
|
foreach (var state in parentStates)
|
||||||
|
{
|
||||||
|
Vector2 parentScale = new Vector2(state.xFlip * state.xScale, state.yFlip * state.yScale);
|
||||||
|
float parentRotation = state.rotation;
|
||||||
|
|
||||||
|
AddStep("S: (" + parentScale.X.ToString("0.00") + ", " + parentScale.Y.ToString("0.00") + "), R: " + parentRotation.ToString("0.00"), () =>
|
||||||
|
{
|
||||||
|
foreach (List<Container> list in parentContainers)
|
||||||
|
{
|
||||||
|
foreach (Container container in list)
|
||||||
|
{
|
||||||
|
container.Scale = parentScale;
|
||||||
|
container.Rotation = parentRotation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("Check if state is valid", () =>
|
||||||
|
{
|
||||||
|
foreach (int i in Enumerable.Range(0, parentContainers.Count))
|
||||||
|
{
|
||||||
|
foreach (int j in Enumerable.Range(0, parentContainers[i].Count))
|
||||||
|
{
|
||||||
|
if (!uprightAspectMaintainingContainerStateIsValid(parentContainers[i][j], childContainers[i][j]))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool uprightAspectMaintainingContainerStateIsValid(Container parent, UprightAspectMaintainingContainer child)
|
||||||
|
{
|
||||||
|
Matrix3 parentMatrix = parent.DrawInfo.Matrix;
|
||||||
|
Matrix3 childMatrix = child.DrawInfo.Matrix;
|
||||||
|
Vector3 childScale = childMatrix.ExtractScale();
|
||||||
|
Vector3 parentScale = parentMatrix.ExtractScale();
|
||||||
|
|
||||||
|
// Orientation check
|
||||||
|
if (!(isNearlyZero(MathF.Abs(childMatrix.M21)) && isNearlyZero(MathF.Abs(childMatrix.M12))))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// flip check
|
||||||
|
if (!(childMatrix.M11 * childMatrix.M22 > 0))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Aspect ratio check
|
||||||
|
if (!isNearlyZero(childScale.X - childScale.Y))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// ScalingMode check
|
||||||
|
switch (child.Scaling)
|
||||||
|
{
|
||||||
|
case ScaleMode.NoScaling:
|
||||||
|
if (!(isNearlyZero(childMatrix.M11 - 1.0f) && isNearlyZero(childMatrix.M22 - 1.0f)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ScaleMode.Vertical:
|
||||||
|
if (!(checkScaling(child.ScalingFactor, parentScale.Y, childScale.Y)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ScaleMode.Horizontal:
|
||||||
|
if (!(checkScaling(child.ScalingFactor, parentScale.X, childScale.X)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool checkScaling(float scalingFactor, float parentScale, float childScale)
|
||||||
|
{
|
||||||
|
if (scalingFactor <= 1.0f)
|
||||||
|
{
|
||||||
|
if (!isNearlyZero(1.0f + (parentScale - 1.0f) * scalingFactor - childScale))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (scalingFactor > 1.0f)
|
||||||
|
{
|
||||||
|
if (parentScale < 1.0f)
|
||||||
|
{
|
||||||
|
if (!isNearlyZero((parentScale * (1.0f / scalingFactor)) - childScale))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (!isNearlyZero(parentScale * scalingFactor - childScale))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool isNearlyZero(float f, float epsilon = Precision.FLOAT_EPSILON)
|
||||||
|
{
|
||||||
|
return f < epsilon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -41,6 +41,9 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons
|
|||||||
|
|
||||||
Anchor = Origin = Anchor.Centre;
|
Anchor = Origin = Anchor.Centre;
|
||||||
|
|
||||||
|
// needed for touch input to work when card is not hovered/expanded
|
||||||
|
AlwaysPresent = true;
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
icon = new SpriteIcon
|
icon = new SpriteIcon
|
||||||
|
@ -0,0 +1,119 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Layout;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Graphics.Containers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A container that reverts any rotation (and optionally scale) applied by its direct parent.
|
||||||
|
/// </summary>
|
||||||
|
public class UprightAspectMaintainingContainer : Container
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Controls how much this container scales compared to its parent (default is 1.0f).
|
||||||
|
/// </summary>
|
||||||
|
public float ScalingFactor { get; set; } = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Controls the scaling of this container.
|
||||||
|
/// </summary>
|
||||||
|
public ScaleMode Scaling { get; set; } = ScaleMode.Vertical;
|
||||||
|
|
||||||
|
private readonly LayoutValue layout = new LayoutValue(Invalidation.DrawInfo, InvalidationSource.Parent);
|
||||||
|
|
||||||
|
public UprightAspectMaintainingContainer()
|
||||||
|
{
|
||||||
|
AddLayout(layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
if (!layout.IsValid)
|
||||||
|
{
|
||||||
|
keepUprightAndUnstretched();
|
||||||
|
layout.Validate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Keeps the drawable upright and unstretched preventing it from being rotated, sheared, scaled or flipped with its Parent.
|
||||||
|
/// </summary>
|
||||||
|
private void keepUprightAndUnstretched()
|
||||||
|
{
|
||||||
|
// Decomposes the inverse of the parent DrawInfo.Matrix into rotation, shear and scale.
|
||||||
|
var parentMatrix = Parent.DrawInfo.Matrix;
|
||||||
|
|
||||||
|
// Remove Translation.>
|
||||||
|
parentMatrix.M31 = 0.0f;
|
||||||
|
parentMatrix.M32 = 0.0f;
|
||||||
|
|
||||||
|
Matrix3 reversedParent = parentMatrix.Inverted();
|
||||||
|
|
||||||
|
// Extract the rotation.
|
||||||
|
float angle = MathF.Atan2(reversedParent.M12, reversedParent.M11);
|
||||||
|
Rotation = MathHelper.RadiansToDegrees(angle);
|
||||||
|
|
||||||
|
// Remove rotation from the C matrix so that it only contains shear and scale.
|
||||||
|
Matrix3 m = Matrix3.CreateRotationZ(-angle);
|
||||||
|
reversedParent *= m;
|
||||||
|
|
||||||
|
// Extract shear.
|
||||||
|
float alpha = reversedParent.M21 / reversedParent.M22;
|
||||||
|
Shear = new Vector2(-alpha, 0);
|
||||||
|
|
||||||
|
// Etract scale.
|
||||||
|
float sx = reversedParent.M11;
|
||||||
|
float sy = reversedParent.M22;
|
||||||
|
|
||||||
|
Vector3 parentScale = parentMatrix.ExtractScale();
|
||||||
|
|
||||||
|
float usedScale = 1.0f;
|
||||||
|
|
||||||
|
switch (Scaling)
|
||||||
|
{
|
||||||
|
case ScaleMode.Horizontal:
|
||||||
|
usedScale = parentScale.X;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ScaleMode.Vertical:
|
||||||
|
usedScale = parentScale.Y;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Scaling != ScaleMode.NoScaling)
|
||||||
|
{
|
||||||
|
if (ScalingFactor < 1.0f)
|
||||||
|
usedScale = 1.0f + (usedScale - 1.0f) * ScalingFactor;
|
||||||
|
if (ScalingFactor > 1.0f)
|
||||||
|
usedScale = (usedScale < 1.0f) ? usedScale * (1.0f / ScalingFactor) : usedScale * ScalingFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
Scale = new Vector2(sx * usedScale, sy * usedScale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ScaleMode
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Prevent this container from scaling.
|
||||||
|
/// </summary>
|
||||||
|
NoScaling,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Scale uniformly (maintaining aspect ratio) based on the vertical scale of the parent.
|
||||||
|
/// </summary>
|
||||||
|
Vertical,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Scale uniformly (maintaining aspect ratio) based on the horizontal scale of the parent.
|
||||||
|
/// </summary>
|
||||||
|
Horizontal,
|
||||||
|
}
|
||||||
|
}
|
108
osu.Game/Graphics/Sprites/SizePreservingSpriteText.cs
Normal file
108
osu.Game/Graphics/Sprites/SizePreservingSpriteText.cs
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
// 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 osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Graphics.Sprites
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A wrapped version of <see cref="OsuSpriteText"/> which will expand in size based on text content, but never shrink back down.
|
||||||
|
/// </summary>
|
||||||
|
public class SizePreservingSpriteText : CompositeDrawable
|
||||||
|
{
|
||||||
|
private readonly OsuSpriteText text = new OsuSpriteText();
|
||||||
|
|
||||||
|
private Vector2 maximumSize;
|
||||||
|
|
||||||
|
public SizePreservingSpriteText(Vector2? minimumSize = null)
|
||||||
|
{
|
||||||
|
text.Origin = Anchor.Centre;
|
||||||
|
text.Anchor = Anchor.Centre;
|
||||||
|
|
||||||
|
AddInternal(text);
|
||||||
|
maximumSize = minimumSize ?? Vector2.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
Width = maximumSize.X = MathF.Max(maximumSize.X, text.Width);
|
||||||
|
Height = maximumSize.Y = MathF.Max(maximumSize.Y, text.Height);
|
||||||
|
}
|
||||||
|
|
||||||
|
public new Axes AutoSizeAxes
|
||||||
|
{
|
||||||
|
get => Axes.None;
|
||||||
|
set => throw new InvalidOperationException("You can't set AutoSizeAxes of this container");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the text to be displayed.
|
||||||
|
/// </summary>
|
||||||
|
public LocalisableString Text
|
||||||
|
{
|
||||||
|
get => text.Text;
|
||||||
|
set => text.Text = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains information on the font used to display the text.
|
||||||
|
/// </summary>
|
||||||
|
public FontUsage Font
|
||||||
|
{
|
||||||
|
get => text.Font;
|
||||||
|
set => text.Font = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// True if a shadow should be displayed around the text.
|
||||||
|
/// </summary>
|
||||||
|
public bool Shadow
|
||||||
|
{
|
||||||
|
get => text.Shadow;
|
||||||
|
set => text.Shadow = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The colour of the shadow displayed around the text. A shadow will only be displayed if the <see cref="Shadow"/> property is set to true.
|
||||||
|
/// </summary>
|
||||||
|
public Color4 ShadowColour
|
||||||
|
{
|
||||||
|
get => text.ShadowColour;
|
||||||
|
set => text.ShadowColour = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The offset of the shadow displayed around the text. A shadow will only be displayed if the <see cref="Shadow"/> property is set to true.
|
||||||
|
/// </summary>
|
||||||
|
public Vector2 ShadowOffset
|
||||||
|
{
|
||||||
|
get => text.ShadowOffset;
|
||||||
|
set => text.ShadowOffset = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// True if the <see cref="SpriteText"/>'s vertical size should be equal to <see cref="FontUsage.Size"/> (the full height) or precisely the size of used characters.
|
||||||
|
/// Set to false to allow better centering of individual characters/numerals/etc.
|
||||||
|
/// </summary>
|
||||||
|
public bool UseFullGlyphHeight
|
||||||
|
{
|
||||||
|
get => text.UseFullGlyphHeight;
|
||||||
|
set => text.UseFullGlyphHeight = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool IsPresent => text.IsPresent;
|
||||||
|
|
||||||
|
public override string ToString() => text.ToString();
|
||||||
|
|
||||||
|
public float LineBaseHeight => text.LineBaseHeight;
|
||||||
|
|
||||||
|
public IEnumerable<LocalisableString> FilterTerms => text.FilterTerms;
|
||||||
|
}
|
||||||
|
}
|
@ -16,7 +16,6 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
{
|
{
|
||||||
public class DefaultSongProgress : SongProgress
|
public class DefaultSongProgress : SongProgress
|
||||||
{
|
{
|
||||||
private const float info_height = 20;
|
|
||||||
private const float bottom_bar_height = 5;
|
private const float bottom_bar_height = 5;
|
||||||
private const float graph_height = SquareGraph.Column.WIDTH * 6;
|
private const float graph_height = SquareGraph.Column.WIDTH * 6;
|
||||||
private const float handle_height = 18;
|
private const float handle_height = 18;
|
||||||
@ -65,7 +64,6 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
Origin = Anchor.BottomLeft,
|
Origin = Anchor.BottomLeft,
|
||||||
Anchor = Anchor.BottomLeft,
|
Anchor = Anchor.BottomLeft,
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Height = info_height,
|
|
||||||
},
|
},
|
||||||
graph = new SongProgressGraph
|
graph = new SongProgressGraph
|
||||||
{
|
{
|
||||||
@ -178,7 +176,7 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
Height = bottom_bar_height + graph_height + handle_size.Y + info_height - graph.Y;
|
Height = bottom_bar_height + graph_height + handle_size.Y + info.Height - graph.Y;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateBarVisibility()
|
private void updateBarVisibility()
|
||||||
|
@ -7,6 +7,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
@ -14,9 +15,9 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
{
|
{
|
||||||
public class SongProgressInfo : Container
|
public class SongProgressInfo : Container
|
||||||
{
|
{
|
||||||
private OsuSpriteText timeCurrent;
|
private SizePreservingSpriteText timeCurrent;
|
||||||
private OsuSpriteText timeLeft;
|
private SizePreservingSpriteText timeLeft;
|
||||||
private OsuSpriteText progress;
|
private SizePreservingSpriteText progress;
|
||||||
|
|
||||||
private double startTime;
|
private double startTime;
|
||||||
private double endTime;
|
private double endTime;
|
||||||
@ -46,36 +47,71 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
if (clock != null)
|
if (clock != null)
|
||||||
gameplayClock = clock;
|
gameplayClock = clock;
|
||||||
|
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
timeCurrent = new OsuSpriteText
|
new Container
|
||||||
{
|
{
|
||||||
Origin = Anchor.BottomLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Anchor = Anchor.BottomLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
Colour = colours.BlueLighter,
|
AutoSizeAxes = Axes.Both,
|
||||||
Font = OsuFont.Numeric,
|
Child = new UprightAspectMaintainingContainer
|
||||||
Margin = new MarginPadding
|
|
||||||
{
|
{
|
||||||
Left = margin,
|
Origin = Anchor.Centre,
|
||||||
},
|
Anchor = Anchor.Centre,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Scaling = ScaleMode.Vertical,
|
||||||
|
ScalingFactor = 0.5f,
|
||||||
|
Child = timeCurrent = new SizePreservingSpriteText
|
||||||
|
{
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Colour = colours.BlueLighter,
|
||||||
|
Font = OsuFont.Numeric,
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
progress = new OsuSpriteText
|
new Container
|
||||||
{
|
{
|
||||||
Origin = Anchor.BottomCentre,
|
Origin = Anchor.Centre,
|
||||||
Anchor = Anchor.BottomCentre,
|
Anchor = Anchor.Centre,
|
||||||
Colour = colours.BlueLighter,
|
AutoSizeAxes = Axes.Both,
|
||||||
Font = OsuFont.Numeric,
|
Child = new UprightAspectMaintainingContainer
|
||||||
},
|
|
||||||
timeLeft = new OsuSpriteText
|
|
||||||
{
|
|
||||||
Origin = Anchor.BottomRight,
|
|
||||||
Anchor = Anchor.BottomRight,
|
|
||||||
Colour = colours.BlueLighter,
|
|
||||||
Font = OsuFont.Numeric,
|
|
||||||
Margin = new MarginPadding
|
|
||||||
{
|
{
|
||||||
Right = margin,
|
Origin = Anchor.Centre,
|
||||||
},
|
Anchor = Anchor.Centre,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Scaling = ScaleMode.Vertical,
|
||||||
|
ScalingFactor = 0.5f,
|
||||||
|
Child = progress = new SizePreservingSpriteText
|
||||||
|
{
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Colour = colours.BlueLighter,
|
||||||
|
Font = OsuFont.Numeric,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
Origin = Anchor.CentreRight,
|
||||||
|
Anchor = Anchor.CentreRight,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Child = new UprightAspectMaintainingContainer
|
||||||
|
{
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Scaling = ScaleMode.Vertical,
|
||||||
|
ScalingFactor = 0.5f,
|
||||||
|
Child = timeLeft = new SizePreservingSpriteText
|
||||||
|
{
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Colour = colours.BlueLighter,
|
||||||
|
Font = OsuFont.Numeric,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user