1
0
mirror of https://github.com/ppy/osu synced 2025-04-01 22:48:33 +00:00

Merge branch 'master' into hover-container-disabled-hover-effect

This commit is contained in:
Dan Balasescu 2019-05-21 17:04:50 +09:00 committed by GitHub
commit 2024cdf16d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 342 additions and 232 deletions

View File

@ -170,27 +170,98 @@ namespace osu.Game.Tests.Beatmaps.Formats
var controlPoints = beatmap.ControlPointInfo; var controlPoints = beatmap.ControlPointInfo;
Assert.AreEqual(4, controlPoints.TimingPoints.Count); Assert.AreEqual(4, controlPoints.TimingPoints.Count);
var timingPoint = controlPoints.TimingPoints[0]; Assert.AreEqual(42, controlPoints.DifficultyPoints.Count);
Assert.AreEqual(42, controlPoints.SamplePoints.Count);
Assert.AreEqual(42, controlPoints.EffectPoints.Count);
var timingPoint = controlPoints.TimingPointAt(0);
Assert.AreEqual(956, timingPoint.Time);
Assert.AreEqual(329.67032967033, timingPoint.BeatLength);
Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature);
timingPoint = controlPoints.TimingPointAt(48428);
Assert.AreEqual(956, timingPoint.Time); Assert.AreEqual(956, timingPoint.Time);
Assert.AreEqual(329.67032967033d, timingPoint.BeatLength); Assert.AreEqual(329.67032967033d, timingPoint.BeatLength);
Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature); Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature);
Assert.AreEqual(5, controlPoints.DifficultyPoints.Count); timingPoint = controlPoints.TimingPointAt(119637);
var difficultyPoint = controlPoints.DifficultyPoints[0]; Assert.AreEqual(119637, timingPoint.Time);
Assert.AreEqual(116999, difficultyPoint.Time); Assert.AreEqual(659.340659340659, timingPoint.BeatLength);
Assert.AreEqual(0.75000000000000189d, difficultyPoint.SpeedMultiplier); Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature);
Assert.AreEqual(34, controlPoints.SamplePoints.Count); var difficultyPoint = controlPoints.DifficultyPointAt(0);
var soundPoint = controlPoints.SamplePoints[0]; Assert.AreEqual(0, difficultyPoint.Time);
Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier);
difficultyPoint = controlPoints.DifficultyPointAt(48428);
Assert.AreEqual(48428, difficultyPoint.Time);
Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier);
difficultyPoint = controlPoints.DifficultyPointAt(116999);
Assert.AreEqual(116999, difficultyPoint.Time);
Assert.AreEqual(0.75, difficultyPoint.SpeedMultiplier, 0.1);
var soundPoint = controlPoints.SamplePointAt(0);
Assert.AreEqual(956, soundPoint.Time); Assert.AreEqual(956, soundPoint.Time);
Assert.AreEqual("soft", soundPoint.SampleBank); Assert.AreEqual("soft", soundPoint.SampleBank);
Assert.AreEqual(60, soundPoint.SampleVolume); Assert.AreEqual(60, soundPoint.SampleVolume);
Assert.AreEqual(8, controlPoints.EffectPoints.Count); soundPoint = controlPoints.SamplePointAt(53373);
var effectPoint = controlPoints.EffectPoints[0]; Assert.AreEqual(53373, soundPoint.Time);
Assert.AreEqual("soft", soundPoint.SampleBank);
Assert.AreEqual(60, soundPoint.SampleVolume);
soundPoint = controlPoints.SamplePointAt(119637);
Assert.AreEqual(119637, soundPoint.Time);
Assert.AreEqual("soft", soundPoint.SampleBank);
Assert.AreEqual(80, soundPoint.SampleVolume);
var effectPoint = controlPoints.EffectPointAt(0);
Assert.AreEqual(0, effectPoint.Time);
Assert.IsFalse(effectPoint.KiaiMode);
Assert.IsFalse(effectPoint.OmitFirstBarLine);
effectPoint = controlPoints.EffectPointAt(53703);
Assert.AreEqual(53703, effectPoint.Time); Assert.AreEqual(53703, effectPoint.Time);
Assert.IsTrue(effectPoint.KiaiMode); Assert.IsTrue(effectPoint.KiaiMode);
Assert.IsFalse(effectPoint.OmitFirstBarLine); Assert.IsFalse(effectPoint.OmitFirstBarLine);
effectPoint = controlPoints.EffectPointAt(119637);
Assert.AreEqual(119637, effectPoint.Time);
Assert.IsFalse(effectPoint.KiaiMode);
Assert.IsFalse(effectPoint.OmitFirstBarLine);
}
}
[Test]
public void TestDecodeOverlappingTimingPoints()
{
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = TestResources.OpenResource("overlapping-control-points.osu"))
using (var stream = new StreamReader(resStream))
{
var controlPoints = decoder.Decode(stream).ControlPointInfo;
Assert.That(controlPoints.DifficultyPointAt(500).SpeedMultiplier, Is.EqualTo(1.5).Within(0.1));
Assert.That(controlPoints.DifficultyPointAt(1500).SpeedMultiplier, Is.EqualTo(1.5).Within(0.1));
Assert.That(controlPoints.DifficultyPointAt(2500).SpeedMultiplier, Is.EqualTo(0.75).Within(0.1));
Assert.That(controlPoints.DifficultyPointAt(3500).SpeedMultiplier, Is.EqualTo(1.5).Within(0.1));
Assert.That(controlPoints.EffectPointAt(500).KiaiMode, Is.True);
Assert.That(controlPoints.EffectPointAt(1500).KiaiMode, Is.True);
Assert.That(controlPoints.EffectPointAt(2500).KiaiMode, Is.False);
Assert.That(controlPoints.EffectPointAt(3500).KiaiMode, Is.True);
Assert.That(controlPoints.SamplePointAt(500).SampleBank, Is.EqualTo("drum"));
Assert.That(controlPoints.SamplePointAt(1500).SampleBank, Is.EqualTo("drum"));
Assert.That(controlPoints.SamplePointAt(2500).SampleBank, Is.EqualTo("normal"));
Assert.That(controlPoints.SamplePointAt(3500).SampleBank, Is.EqualTo("drum"));
Assert.That(controlPoints.TimingPointAt(500).BeatLength, Is.EqualTo(500).Within(0.1));
Assert.That(controlPoints.TimingPointAt(1500).BeatLength, Is.EqualTo(500).Within(0.1));
Assert.That(controlPoints.TimingPointAt(2500).BeatLength, Is.EqualTo(250).Within(0.1));
Assert.That(controlPoints.TimingPointAt(3500).BeatLength, Is.EqualTo(500).Within(0.1));
} }
} }

View File

@ -0,0 +1,19 @@
osu file format v14
[TimingPoints]
// Timing then inherited
0,500,4,2,0,100,1,0
0,-66.6666666666667,4,3,0,100,0,1
// Inherited then timing (equivalent to previous)
1000,-66.6666666666667,4,3,0,100,0,1
1000,500,4,2,0,100,1,0
// Inherited then timing (different to previous)
2000,-133.333333333333,4,1,0,100,0,0
2000,250,4,2,0,100,1,0
// Timing then inherited (different to previous)
3000,500,4,2,0,100,1,0
3000,-66.6666666666667,4,3,0,100,0,1

View File

@ -53,38 +53,12 @@ namespace osu.Game.Tests.Visual.Menus
} }
[Test] [Test]
public void TestShortLoad() public void TestDelayedLoad()
{ {
bool logoVisible = false;
AddStep("begin loading", () => LoadScreen(loader = new TestLoader())); AddStep("begin loading", () => LoadScreen(loader = new TestLoader()));
AddWaitStep("wait", 3); AddUntilStep("wait for logo visible", () => loader.Logo?.Alpha > 0);
AddStep("finish loading", () => AddStep("finish loading", () => loader.AllowLoad.Set());
{
logoVisible = loader.Logo?.Alpha > 0;
loader.AllowLoad.Set();
});
AddAssert("loaded", () => loader.Logo != null && loader.ScreenLoaded); AddAssert("loaded", () => loader.Logo != null && loader.ScreenLoaded);
AddAssert("logo was visible", () => logoVisible);
AddUntilStep("logo gone", () => loader.Logo?.Alpha == 0);
}
[Test]
public void TestLongLoad()
{
bool logoVisible = false;
AddStep("begin loading", () => LoadScreen(loader = new TestLoader()));
AddWaitStep("wait", 10);
AddStep("finish loading", () =>
{
logoVisible = loader.Logo?.Alpha > 0;
loader.AllowLoad.Set();
});
AddAssert("loaded", () => loader.Logo != null && loader.ScreenLoaded);
AddAssert("logo was visible", () => logoVisible);
AddUntilStep("logo gone", () => loader.Logo?.Alpha == 0); AddUntilStep("logo gone", () => loader.Logo?.Alpha == 0);
} }

View File

@ -7,6 +7,7 @@ using osu.Framework.Allocation;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
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.Overlays;
using osu.Game.Overlays.Profile; using osu.Game.Overlays.Profile;
using osu.Game.Overlays.Profile.Header; using osu.Game.Overlays.Profile.Header;
using osu.Game.Overlays.Profile.Header.Components; using osu.Game.Overlays.Profile.Header.Components;
@ -21,7 +22,7 @@ namespace osu.Game.Tests.Visual.Online
typeof(ProfileHeader), typeof(ProfileHeader),
typeof(RankGraph), typeof(RankGraph),
typeof(LineGraph), typeof(LineGraph),
typeof(ProfileHeaderTabControl), typeof(OverlayHeaderTabControl),
typeof(CentreHeaderContainer), typeof(CentreHeaderContainer),
typeof(BottomHeaderContainer), typeof(BottomHeaderContainer),
typeof(DetailHeaderContainer), typeof(DetailHeaderContainer),

View File

@ -12,17 +12,14 @@ namespace osu.Game.Beatmaps.ControlPoints
/// </summary> /// </summary>
public double Time; public double Time;
/// <summary>
/// Whether this timing point was generated internally, as opposed to parsed from the underlying beatmap.
/// </summary>
internal bool AutoGenerated;
public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time); public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time);
/// <summary>
/// Whether this <see cref="ControlPoint"/> provides the same parametric changes as another <see cref="ControlPoint"/>.
/// Basically an equality check without considering the <see cref="Time"/>.
/// </summary>
/// <param name="other">The <see cref="ControlPoint"/> to compare to.</param>
/// <returns>Whether this <see cref="ControlPoint"/> is equivalent to <paramref name="other"/>.</returns>
public virtual bool EquivalentTo(ControlPoint other) => true;
public bool Equals(ControlPoint other) public bool Equals(ControlPoint other)
=> EquivalentTo(other) && Time.Equals(other?.Time); => Time.Equals(other?.Time);
} }
} }

View File

@ -1,11 +1,12 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using osuTK; using osuTK;
namespace osu.Game.Beatmaps.ControlPoints namespace osu.Game.Beatmaps.ControlPoints
{ {
public class DifficultyControlPoint : ControlPoint public class DifficultyControlPoint : ControlPoint, IEquatable<DifficultyControlPoint>
{ {
/// <summary> /// <summary>
/// The speed multiplier at this control point. /// The speed multiplier at this control point.
@ -18,9 +19,8 @@ namespace osu.Game.Beatmaps.ControlPoints
private double speedMultiplier = 1; private double speedMultiplier = 1;
public override bool EquivalentTo(ControlPoint other) public bool Equals(DifficultyControlPoint other)
=> base.EquivalentTo(other) => base.Equals(other)
&& other is DifficultyControlPoint difficulty && SpeedMultiplier.Equals(other?.SpeedMultiplier);
&& SpeedMultiplier.Equals(difficulty.SpeedMultiplier);
} }
} }

View File

@ -1,9 +1,11 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
namespace osu.Game.Beatmaps.ControlPoints namespace osu.Game.Beatmaps.ControlPoints
{ {
public class EffectControlPoint : ControlPoint public class EffectControlPoint : ControlPoint, IEquatable<EffectControlPoint>
{ {
/// <summary> /// <summary>
/// Whether this control point enables Kiai mode. /// Whether this control point enables Kiai mode.
@ -15,10 +17,8 @@ namespace osu.Game.Beatmaps.ControlPoints
/// </summary> /// </summary>
public bool OmitFirstBarLine; public bool OmitFirstBarLine;
public override bool EquivalentTo(ControlPoint other) public bool Equals(EffectControlPoint other)
=> base.EquivalentTo(other) => base.Equals(other)
&& other is EffectControlPoint effect && KiaiMode == other?.KiaiMode && OmitFirstBarLine == other.OmitFirstBarLine;
&& KiaiMode.Equals(effect.KiaiMode)
&& OmitFirstBarLine.Equals(effect.OmitFirstBarLine);
} }
} }

View File

@ -1,11 +1,12 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using osu.Game.Audio; using osu.Game.Audio;
namespace osu.Game.Beatmaps.ControlPoints namespace osu.Game.Beatmaps.ControlPoints
{ {
public class SampleControlPoint : ControlPoint public class SampleControlPoint : ControlPoint, IEquatable<SampleControlPoint>
{ {
public const string DEFAULT_BANK = "normal"; public const string DEFAULT_BANK = "normal";
@ -44,10 +45,8 @@ namespace osu.Game.Beatmaps.ControlPoints
return newSampleInfo; return newSampleInfo;
} }
public override bool EquivalentTo(ControlPoint other) public bool Equals(SampleControlPoint other)
=> base.EquivalentTo(other) => base.Equals(other)
&& other is SampleControlPoint sample && string.Equals(SampleBank, other?.SampleBank) && SampleVolume == other?.SampleVolume;
&& SampleBank.Equals(sample.SampleBank)
&& SampleVolume.Equals(sample.SampleVolume);
} }
} }

View File

@ -1,12 +1,13 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using osuTK; using osuTK;
using osu.Game.Beatmaps.Timing; using osu.Game.Beatmaps.Timing;
namespace osu.Game.Beatmaps.ControlPoints namespace osu.Game.Beatmaps.ControlPoints
{ {
public class TimingControlPoint : ControlPoint public class TimingControlPoint : ControlPoint, IEquatable<TimingControlPoint>
{ {
/// <summary> /// <summary>
/// The time signature at this control point. /// The time signature at this control point.
@ -24,10 +25,8 @@ namespace osu.Game.Beatmaps.ControlPoints
private double beatLength = 1000; private double beatLength = 1000;
public override bool EquivalentTo(ControlPoint other) public bool Equals(TimingControlPoint other)
=> base.EquivalentTo(other) => base.Equals(other)
&& other is TimingControlPoint timing && TimeSignature == other?.TimeSignature && beatLength.Equals(other.beatLength);
&& TimeSignature.Equals(timing.TimeSignature)
&& BeatLength.Equals(timing.BeatLength);
} }
} }

View File

@ -374,14 +374,16 @@ namespace osu.Game.Beatmaps.Formats
handleDifficultyControlPoint(new DifficultyControlPoint handleDifficultyControlPoint(new DifficultyControlPoint
{ {
Time = time, Time = time,
SpeedMultiplier = speedMultiplier SpeedMultiplier = speedMultiplier,
AutoGenerated = timingChange
}); });
handleEffectControlPoint(new EffectControlPoint handleEffectControlPoint(new EffectControlPoint
{ {
Time = time, Time = time,
KiaiMode = kiaiMode, KiaiMode = kiaiMode,
OmitFirstBarLine = omitFirstBarSignature OmitFirstBarLine = omitFirstBarSignature,
AutoGenerated = timingChange
}); });
handleSampleControlPoint(new LegacySampleControlPoint handleSampleControlPoint(new LegacySampleControlPoint
@ -389,7 +391,8 @@ namespace osu.Game.Beatmaps.Formats
Time = time, Time = time,
SampleBank = stringSampleSet, SampleBank = stringSampleSet,
SampleVolume = sampleVolume, SampleVolume = sampleVolume,
CustomSampleBank = customSampleBank CustomSampleBank = customSampleBank,
AutoGenerated = timingChange
}); });
} }
catch (FormatException) catch (FormatException)
@ -407,7 +410,14 @@ namespace osu.Game.Beatmaps.Formats
var existing = beatmap.ControlPointInfo.TimingPointAt(newPoint.Time); var existing = beatmap.ControlPointInfo.TimingPointAt(newPoint.Time);
if (existing.Time == newPoint.Time) if (existing.Time == newPoint.Time)
{
// autogenerated points should not replace non-autogenerated.
// this allows for incorrectly ordered timing points to still be correctly handled.
if (newPoint.AutoGenerated && !existing.AutoGenerated)
return;
beatmap.ControlPointInfo.TimingPoints.Remove(existing); beatmap.ControlPointInfo.TimingPoints.Remove(existing);
}
beatmap.ControlPointInfo.TimingPoints.Add(newPoint); beatmap.ControlPointInfo.TimingPoints.Add(newPoint);
} }
@ -416,11 +426,15 @@ namespace osu.Game.Beatmaps.Formats
{ {
var existing = beatmap.ControlPointInfo.DifficultyPointAt(newPoint.Time); var existing = beatmap.ControlPointInfo.DifficultyPointAt(newPoint.Time);
if (newPoint.EquivalentTo(existing))
return;
if (existing.Time == newPoint.Time) if (existing.Time == newPoint.Time)
{
// autogenerated points should not replace non-autogenerated.
// this allows for incorrectly ordered timing points to still be correctly handled.
if (newPoint.AutoGenerated && !existing.AutoGenerated)
return;
beatmap.ControlPointInfo.DifficultyPoints.Remove(existing); beatmap.ControlPointInfo.DifficultyPoints.Remove(existing);
}
beatmap.ControlPointInfo.DifficultyPoints.Add(newPoint); beatmap.ControlPointInfo.DifficultyPoints.Add(newPoint);
} }
@ -429,11 +443,15 @@ namespace osu.Game.Beatmaps.Formats
{ {
var existing = beatmap.ControlPointInfo.EffectPointAt(newPoint.Time); var existing = beatmap.ControlPointInfo.EffectPointAt(newPoint.Time);
if (newPoint.EquivalentTo(existing))
return;
if (existing.Time == newPoint.Time) if (existing.Time == newPoint.Time)
{
// autogenerated points should not replace non-autogenerated.
// this allows for incorrectly ordered timing points to still be correctly handled.
if (newPoint.AutoGenerated && !existing.AutoGenerated)
return;
beatmap.ControlPointInfo.EffectPoints.Remove(existing); beatmap.ControlPointInfo.EffectPoints.Remove(existing);
}
beatmap.ControlPointInfo.EffectPoints.Add(newPoint); beatmap.ControlPointInfo.EffectPoints.Add(newPoint);
} }
@ -442,11 +460,15 @@ namespace osu.Game.Beatmaps.Formats
{ {
var existing = beatmap.ControlPointInfo.SamplePointAt(newPoint.Time); var existing = beatmap.ControlPointInfo.SamplePointAt(newPoint.Time);
if (newPoint.EquivalentTo(existing))
return;
if (existing.Time == newPoint.Time) if (existing.Time == newPoint.Time)
{
// autogenerated points should not replace non-autogenerated.
// this allows for incorrectly ordered timing points to still be correctly handled.
if (newPoint.AutoGenerated && !existing.AutoGenerated)
return;
beatmap.ControlPointInfo.SamplePoints.Remove(existing); beatmap.ControlPointInfo.SamplePoints.Remove(existing);
}
beatmap.ControlPointInfo.SamplePoints.Add(newPoint); beatmap.ControlPointInfo.SamplePoints.Add(newPoint);
} }

View File

@ -189,7 +189,7 @@ namespace osu.Game.Beatmaps.Formats
Foreground = 3 Foreground = 3
} }
internal class LegacySampleControlPoint : SampleControlPoint internal class LegacySampleControlPoint : SampleControlPoint, IEquatable<LegacySampleControlPoint>
{ {
public int CustomSampleBank; public int CustomSampleBank;
@ -203,10 +203,9 @@ namespace osu.Game.Beatmaps.Formats
return baseInfo; return baseInfo;
} }
public override bool EquivalentTo(ControlPoint other) public bool Equals(LegacySampleControlPoint other)
=> base.EquivalentTo(other) => base.Equals(other)
&& other is LegacySampleControlPoint legacy && CustomSampleBank == other?.CustomSampleBank;
&& CustomSampleBank == legacy.CustomSampleBank;
} }
} }
} }

View File

@ -4,11 +4,12 @@
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
namespace osu.Game.Graphics.Containers namespace osu.Game.Graphics.Containers
{ {
public class OsuClickableContainer : ClickableContainer public class OsuClickableContainer : ClickableContainer, IHasTooltip
{ {
private readonly HoverSampleSet sampleSet; private readonly HoverSampleSet sampleSet;
@ -23,6 +24,8 @@ namespace osu.Game.Graphics.Containers
this.sampleSet = sampleSet; this.sampleSet = sampleSet;
} }
public virtual string TooltipText { get; set; }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
@ -12,26 +13,33 @@ namespace osu.Game.Graphics.UserInterface
{ {
public abstract class ScreenTitle : CompositeDrawable, IHasAccentColour public abstract class ScreenTitle : CompositeDrawable, IHasAccentColour
{ {
private readonly SpriteIcon iconSprite; public const float ICON_WIDTH = ICON_SIZE + icon_spacing;
protected const float ICON_SIZE = 25;
private SpriteIcon iconSprite;
private readonly OsuSpriteText titleText, pageText; private readonly OsuSpriteText titleText, pageText;
public const float ICON_WIDTH = icon_size + icon_spacing;
private const float icon_size = 25, icon_spacing = 10; private const float icon_spacing = 10;
protected IconUsage Icon protected IconUsage Icon
{ {
get => iconSprite.Icon; set
set => iconSprite.Icon = value; {
if (iconSprite == null)
throw new InvalidOperationException($"Cannot use {nameof(Icon)} with a custom {nameof(CreateIcon)} function.");
iconSprite.Icon = value;
}
} }
protected string Title protected string Title
{ {
get => titleText.Text;
set => titleText.Text = value; set => titleText.Text = value;
} }
protected string Section protected string Section
{ {
get => pageText.Text;
set => pageText.Text = value; set => pageText.Text = value;
} }
@ -41,6 +49,11 @@ namespace osu.Game.Graphics.UserInterface
set => pageText.Colour = value; set => pageText.Colour = value;
} }
protected virtual Drawable CreateIcon() => iconSprite = new SpriteIcon
{
Size = new Vector2(ICON_SIZE),
};
protected ScreenTitle() protected ScreenTitle()
{ {
AutoSizeAxes = Axes.Both; AutoSizeAxes = Axes.Both;
@ -51,12 +64,9 @@ namespace osu.Game.Graphics.UserInterface
{ {
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Spacing = new Vector2(icon_spacing, 0), Spacing = new Vector2(icon_spacing, 0),
Children = new Drawable[] Children = new[]
{ {
iconSprite = new SpriteIcon CreateIcon(),
{
Size = new Vector2(icon_size),
},
new FillFlowContainer new FillFlowContainer
{ {
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,

View File

@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Cursor;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
@ -16,7 +15,7 @@ namespace osu.Game.Online.Chat
/// <summary> /// <summary>
/// An invisible drawable that brings multiple <see cref="Drawable"/> pieces together to form a consumable clickable link. /// An invisible drawable that brings multiple <see cref="Drawable"/> pieces together to form a consumable clickable link.
/// </summary> /// </summary>
public class DrawableLinkCompiler : OsuHoverContainer, IHasTooltip public class DrawableLinkCompiler : OsuHoverContainer
{ {
/// <summary> /// <summary>
/// Each word part of a chat link (split for word-wrap support). /// Each word part of a chat link (split for word-wrap support).
@ -40,8 +39,6 @@ namespace osu.Game.Online.Chat
protected override IEnumerable<Drawable> EffectTargets => Parts; protected override IEnumerable<Drawable> EffectTargets => Parts;
public string TooltipText { get; set; }
private class LinkHoverSounds : HoverClickSounds private class LinkHoverSounds : HoverClickSounds
{ {
private readonly List<Drawable> parts; private readonly List<Drawable> parts;

View File

@ -9,8 +9,6 @@ using osu.Game.Graphics.Sprites;
using osu.Game.Users; using osu.Game.Users;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
using osu.Game.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics; using osu.Game.Graphics;
@ -129,10 +127,5 @@ namespace osu.Game.Overlays.BeatmapSet
}; };
} }
} }
private class ClickableArea : OsuClickableContainer, IHasTooltip
{
public string TooltipText => @"View Profile";
}
} }
} }

View File

@ -0,0 +1,70 @@
// 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.Game.Graphics.UserInterface;
namespace osu.Game.Overlays
{
public abstract class OverlayHeader : Container
{
protected readonly OverlayHeaderTabControl TabControl;
private const float cover_height = 150;
private const float cover_info_height = 75;
protected OverlayHeader()
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
Children = new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.X,
Height = cover_height,
Masking = true,
Child = CreateBackground()
},
new Container
{
Margin = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN },
Y = cover_height,
Height = cover_info_height,
RelativeSizeAxes = Axes.X,
Anchor = Anchor.TopLeft,
Origin = Anchor.BottomLeft,
Depth = -float.MaxValue,
Children = new Drawable[]
{
CreateTitle().With(t => t.X = -ScreenTitle.ICON_WIDTH),
TabControl = new OverlayHeaderTabControl
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
Height = cover_info_height - 30,
Margin = new MarginPadding { Left = -UserProfileOverlay.CONTENT_X_MARGIN },
Padding = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN }
}
}
},
new Container
{
Margin = new MarginPadding { Top = cover_height },
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Child = CreateContent()
}
};
}
protected abstract Drawable CreateBackground();
protected abstract Drawable CreateContent();
protected abstract ScreenTitle CreateTitle();
}
}

View File

@ -11,13 +11,13 @@ using osu.Game.Graphics.UserInterface;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
namespace osu.Game.Overlays.Profile.Header namespace osu.Game.Overlays
{ {
public class ProfileHeaderTabControl : TabControl<string> public class OverlayHeaderTabControl : TabControl<string>
{ {
private readonly Box bar; private readonly Box bar;
private Color4 accentColour; private Color4 accentColour = Color4.White;
public Color4 AccentColour public Color4 AccentColour
{ {
@ -32,7 +32,7 @@ namespace osu.Game.Overlays.Profile.Header
foreach (TabItem<string> tabItem in TabContainer) foreach (TabItem<string> tabItem in TabContainer)
{ {
((ProfileHeaderTabItem)tabItem).AccentColour = value; ((HeaderTabItem)tabItem).AccentColour = value;
} }
} }
} }
@ -43,7 +43,7 @@ namespace osu.Game.Overlays.Profile.Header
set => TabContainer.Padding = value; set => TabContainer.Padding = value;
} }
public ProfileHeaderTabControl() public OverlayHeaderTabControl()
{ {
TabContainer.Masking = false; TabContainer.Masking = false;
TabContainer.Spacing = new Vector2(15, 0); TabContainer.Spacing = new Vector2(15, 0);
@ -59,12 +59,12 @@ namespace osu.Game.Overlays.Profile.Header
protected override Dropdown<string> CreateDropdown() => null; protected override Dropdown<string> CreateDropdown() => null;
protected override TabItem<string> CreateTabItem(string value) => new ProfileHeaderTabItem(value) protected override TabItem<string> CreateTabItem(string value) => new HeaderTabItem(value)
{ {
AccentColour = AccentColour AccentColour = AccentColour
}; };
private class ProfileHeaderTabItem : TabItem<string> private class HeaderTabItem : TabItem<string>
{ {
private readonly OsuSpriteText text; private readonly OsuSpriteText text;
private readonly Drawable bar; private readonly Drawable bar;
@ -86,7 +86,7 @@ namespace osu.Game.Overlays.Profile.Header
} }
} }
public ProfileHeaderTabItem(string value) public HeaderTabItem(string value)
: base(value) : base(value)
{ {
AutoSizeAxes = Axes.X; AutoSizeAxes = Axes.X;

View File

@ -4,7 +4,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
@ -12,10 +11,8 @@ using osuTK.Graphics;
namespace osu.Game.Overlays.Profile.Header.Components namespace osu.Game.Overlays.Profile.Header.Components
{ {
public abstract class ProfileHeaderButton : OsuHoverContainer, IHasTooltip public abstract class ProfileHeaderButton : OsuHoverContainer
{ {
public abstract string TooltipText { get; }
private readonly Box background; private readonly Box background;
private readonly Container content; private readonly Container content;

View File

@ -15,124 +15,87 @@ using osu.Game.Users;
namespace osu.Game.Overlays.Profile namespace osu.Game.Overlays.Profile
{ {
public class ProfileHeader : Container public class ProfileHeader : OverlayHeader
{ {
private readonly UserCoverBackground coverContainer; private UserCoverBackground coverContainer;
private readonly ProfileHeaderTabControl infoTabControl;
private const float cover_height = 150; public Bindable<User> User = new Bindable<User>();
private const float cover_info_height = 75;
private CentreHeaderContainer centreHeaderContainer;
private DetailHeaderContainer detailHeaderContainer;
public ProfileHeader() public ProfileHeader()
{ {
CentreHeaderContainer centreHeaderContainer; User.ValueChanged += e => updateDisplay(e.NewValue);
DetailHeaderContainer detailHeaderContainer;
RelativeSizeAxes = Axes.X; TabControl.AddItem("Info");
AutoSizeAxes = Axes.Y; TabControl.AddItem("Modding");
Children = new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.X,
Height = cover_height,
Masking = true,
Children = new Drawable[]
{
coverContainer = new UserCoverBackground
{
RelativeSizeAxes = Axes.Both,
},
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = ColourInfo.GradientVertical(OsuColour.FromHex("222").Opacity(0.8f), OsuColour.FromHex("222").Opacity(0.2f))
},
}
},
new Container
{
Margin = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN },
Y = cover_height,
Height = cover_info_height,
RelativeSizeAxes = Axes.X,
Anchor = Anchor.TopLeft,
Origin = Anchor.BottomLeft,
Depth = -float.MaxValue,
Children = new Drawable[]
{
new ProfileHeaderTitle
{
X = -ScreenTitle.ICON_WIDTH,
},
infoTabControl = new ProfileHeaderTabControl
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
Height = cover_info_height - 30,
Margin = new MarginPadding { Left = -UserProfileOverlay.CONTENT_X_MARGIN },
Padding = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN }
}
}
},
new FillFlowContainer
{
Margin = new MarginPadding { Top = cover_height },
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new TopHeaderContainer
{
RelativeSizeAxes = Axes.X,
User = { BindTarget = User },
},
centreHeaderContainer = new CentreHeaderContainer
{
RelativeSizeAxes = Axes.X,
User = { BindTarget = User },
},
detailHeaderContainer = new DetailHeaderContainer
{
RelativeSizeAxes = Axes.X,
User = { BindTarget = User },
},
new MedalHeaderContainer
{
RelativeSizeAxes = Axes.X,
User = { BindTarget = User },
},
new BottomHeaderContainer
{
RelativeSizeAxes = Axes.X,
User = { BindTarget = User },
},
}
}
};
infoTabControl.AddItem("Info");
infoTabControl.AddItem("Modding");
centreHeaderContainer.DetailsVisible.BindValueChanged(visible => detailHeaderContainer.Expanded = visible.NewValue, true); centreHeaderContainer.DetailsVisible.BindValueChanged(visible => detailHeaderContainer.Expanded = visible.NewValue, true);
User.ValueChanged += e => updateDisplay(e.NewValue);
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
infoTabControl.AccentColour = colours.Seafoam; TabControl.AccentColour = colours.Seafoam;
} }
public Bindable<User> User = new Bindable<User>(); protected override Drawable CreateBackground() =>
new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
coverContainer = new UserCoverBackground
{
RelativeSizeAxes = Axes.Both,
},
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = ColourInfo.GradientVertical(OsuColour.FromHex("222").Opacity(0.8f), OsuColour.FromHex("222").Opacity(0.2f))
},
}
};
private void updateDisplay(User user) protected override Drawable CreateContent() => new FillFlowContainer
{ {
coverContainer.User = user; RelativeSizeAxes = Axes.X,
} AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new TopHeaderContainer
{
RelativeSizeAxes = Axes.X,
User = { BindTarget = User },
},
centreHeaderContainer = new CentreHeaderContainer
{
RelativeSizeAxes = Axes.X,
User = { BindTarget = User },
},
detailHeaderContainer = new DetailHeaderContainer
{
RelativeSizeAxes = Axes.X,
User = { BindTarget = User },
},
new MedalHeaderContainer
{
RelativeSizeAxes = Axes.X,
User = { BindTarget = User },
},
new BottomHeaderContainer
{
RelativeSizeAxes = Axes.X,
User = { BindTarget = User },
},
}
};
protected override ScreenTitle CreateTitle() => new ProfileHeaderTitle();
private void updateDisplay(User user) => coverContainer.User = user;
private class ProfileHeaderTitle : ScreenTitle private class ProfileHeaderTitle : ScreenTitle
{ {

View File

@ -4,7 +4,6 @@
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics; using osu.Game.Graphics;
@ -16,7 +15,7 @@ namespace osu.Game.Overlays.Profile.Sections
/// <summary> /// <summary>
/// Display artist/title/mapper information, commonly used as the left portion of a profile or score display row (see <see cref="DrawableProfileRow"/>). /// Display artist/title/mapper information, commonly used as the left portion of a profile or score display row (see <see cref="DrawableProfileRow"/>).
/// </summary> /// </summary>
public class BeatmapMetadataContainer : OsuHoverContainer, IHasTooltip public class BeatmapMetadataContainer : OsuHoverContainer
{ {
private readonly BeatmapInfo beatmap; private readonly BeatmapInfo beatmap;
@ -27,8 +26,6 @@ namespace osu.Game.Overlays.Profile.Sections
TooltipText = $"{beatmap.Metadata.Artist} - {beatmap.Metadata.Title}"; TooltipText = $"{beatmap.Metadata.Artist} - {beatmap.Metadata.Title}";
} }
public string TooltipText { get; }
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader(true)]
private void load(BeatmapSetOverlay beatmapSetOverlay) private void load(BeatmapSetOverlay beatmapSetOverlay)
{ {

View File

@ -6,7 +6,6 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
@ -72,9 +71,9 @@ namespace osu.Game.Users
game?.ShowUser(user.Id); game?.ShowUser(user.Id);
} }
private class ClickableArea : OsuClickableContainer, IHasTooltip private class ClickableArea : OsuClickableContainer
{ {
public string TooltipText => Enabled.Value ? @"View Profile" : null; public override string TooltipText => Enabled.Value ? @"View Profile" : null;
protected override bool OnClick(ClickEvent e) protected override bool OnClick(ClickEvent e)
{ {