1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-22 05:27:23 +08:00

Merge remote-tracking branch 'origin/master' into timingchange-rework

# Conflicts:
#	osu-framework
#	osu.Game.Rulesets.Mania/Timing/ControlPointContainer.cs
This commit is contained in:
smoogipooo 2017-06-09 03:37:55 +09:00
commit cd435f7ec6
87 changed files with 1449 additions and 865 deletions

View File

@ -41,7 +41,7 @@ namespace osu.Desktop.VisualTests.Tests
StarDifficulty = 5.3f, StarDifficulty = 5.3f,
Metrics = new BeatmapMetrics Metrics = new BeatmapMetrics
{ {
Ratings = Enumerable.Range(0,10), Ratings = Enumerable.Range(0, 10),
Fails = Enumerable.Range(lastRange, 100).Select(i => i % 12 - 6), Fails = Enumerable.Range(lastRange, 100).Select(i => i % 12 - 6),
Retries = Enumerable.Range(lastRange - 3, 100).Select(i => i % 12 - 6), Retries = Enumerable.Range(lastRange - 3, 100).Select(i => i % 12 - 6),
}, },

View File

@ -36,12 +36,12 @@ namespace osu.Desktop.VisualTests.Tests
}); });
first.Room.Name.Value = @"Great Room Right Here"; first.Room.Name.Value = @"Great Room Right Here";
first.Room.Host.Value = new User { Username = @"Naeferith", Id = 9492835, Country = new Country { FlagName = @"FR" }}; first.Room.Host.Value = new User { Username = @"Naeferith", Id = 9492835, Country = new Country { FlagName = @"FR" } };
first.Room.Status.Value = new RoomStatusOpen(); first.Room.Status.Value = new RoomStatusOpen();
first.Room.Beatmap.Value = new BeatmapMetadata { Title = @"Seiryu", Artist = @"Critical Crystal" }; first.Room.Beatmap.Value = new BeatmapMetadata { Title = @"Seiryu", Artist = @"Critical Crystal" };
second.Room.Name.Value = @"Relax It's The Weekend"; second.Room.Name.Value = @"Relax It's The Weekend";
second.Room.Host.Value = new User { Username = @"peppy", Id = 2, Country = new Country { FlagName = @"AU" }}; second.Room.Host.Value = new User { Username = @"peppy", Id = 2, Country = new Country { FlagName = @"AU" } };
second.Room.Status.Value = new RoomStatusPlaying(); second.Room.Status.Value = new RoomStatusPlaying();
second.Room.Beatmap.Value = new BeatmapMetadata { Title = @"ZAQ", Artist = @"Serendipity" }; second.Room.Beatmap.Value = new BeatmapMetadata { Title = @"ZAQ", Artist = @"Serendipity" };

View File

@ -30,7 +30,7 @@ namespace osu.Desktop.VisualTests.Tests
}, },
}; };
AddStep("values from 1-10", () => graph.Values = Enumerable.Range(1,10).Select(i => (float)i)); AddStep("values from 1-10", () => graph.Values = Enumerable.Range(1, 10).Select(i => (float)i));
AddStep("values from 1-100", () => graph.Values = Enumerable.Range(1, 100).Select(i => (float)i)); AddStep("values from 1-100", () => graph.Values = Enumerable.Range(1, 100).Select(i => (float)i));
AddStep("reversed values from 1-10", () => graph.Values = Enumerable.Range(1, 10).Reverse().Select(i => (float)i)); AddStep("reversed values from 1-10", () => graph.Values = Enumerable.Range(1, 10).Reverse().Select(i => (float)i));
AddStep("Bottom to top", () => graph.Direction = BarDirection.BottomToTop); AddStep("Bottom to top", () => graph.Direction = BarDirection.BottomToTop);

View File

@ -54,7 +54,7 @@ namespace osu.Desktop.VisualTests.Tests
Children = new Drawable[] Children = new Drawable[]
{ {
new SpriteText { Text = "FadeTime" }, new SpriteText { Text = "FadeTime" },
sliderBar =new TestSliderBar<int> sliderBar = new TestSliderBar<int>
{ {
Width = 150, Width = 150,
Height = 10, Height = 10,

View File

@ -35,7 +35,7 @@ namespace osu.Desktop.VisualTests.Tests
}); });
AddStep(@"Pause", delegate { AddStep(@"Pause", delegate {
if(failOverlay.State == Visibility.Visible) if (failOverlay.State == Visibility.Visible)
{ {
failOverlay.Hide(); failOverlay.Hide();
} }

View File

@ -79,7 +79,8 @@ namespace osu.Desktop.VisualTests.Tests
{ {
score.Current.Value += 300 + (ulong)(300.0 * (comboCounter.Current > 0 ? comboCounter.Current - 1 : 0) / 25.0); score.Current.Value += 300 + (ulong)(300.0 * (comboCounter.Current > 0 ? comboCounter.Current - 1 : 0) / 25.0);
comboCounter.Increment(); comboCounter.Increment();
numerator++; denominator++; numerator++;
denominator++;
accuracyCounter.SetFraction(numerator, denominator); accuracyCounter.SetFraction(numerator, denominator);
}); });

View File

@ -0,0 +1,85 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Testing;
using osu.Game.Overlays;
using osu.Game.Users;
namespace osu.Desktop.VisualTests.Tests
{
public class TestCaseSocial : TestCase
{
public override string Description => @"social browser overlay";
public override void Reset()
{
base.Reset();
SocialOverlay s = new SocialOverlay
{
Users = new[]
{
new User
{
Username = @"flyte",
Id = 3103765,
Country = new Country { FlagName = @"JP" },
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c1.jpg",
},
new User
{
Username = @"Cookiezi",
Id = 124493,
Country = new Country { FlagName = @"KR" },
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg",
},
new User
{
Username = @"Angelsim",
Id = 1777162,
Country = new Country { FlagName = @"KR" },
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
},
new User
{
Username = @"Rafis",
Id = 2558286,
Country = new Country { FlagName = @"PL" },
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c4.jpg",
},
new User
{
Username = @"hvick225",
Id = 50265,
Country = new Country { FlagName = @"TW" },
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c5.jpg",
},
new User
{
Username = @"peppy",
Id = 2,
Country = new Country { FlagName = @"AU" },
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg"
},
new User
{
Username = @"filsdelama",
Id = 2831793,
Country = new Country { FlagName = @"FR" },
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c7.jpg"
},
new User
{
Username = @"_index",
Id = 652457,
Country = new Country { FlagName = @"RU" },
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c8.jpg"
},
},
};
Add(s);
AddStep(@"toggle", s.ToggleVisibility);
}
}
}

View File

@ -224,6 +224,7 @@
<Compile Include="Tests\TestCaseDrawableRoom.cs" /> <Compile Include="Tests\TestCaseDrawableRoom.cs" />
<Compile Include="Tests\TestCaseUserPanel.cs" /> <Compile Include="Tests\TestCaseUserPanel.cs" />
<Compile Include="Tests\TestCaseDirect.cs" /> <Compile Include="Tests\TestCaseDirect.cs" />
<Compile Include="Tests\TestCaseSocial.cs" />
<Compile Include="Tests\TestCaseBreadcrumbs.cs" /> <Compile Include="Tests\TestCaseBreadcrumbs.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup /> <ItemGroup />

View File

@ -448,7 +448,6 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
return curveData.RepeatSamples[index]; return curveData.RepeatSamples[index];
} }
/// <summary> /// <summary>
/// Constructs and adds a note to a pattern. /// Constructs and adds a note to a pattern.
/// </summary> /// </summary>
@ -480,7 +479,6 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
Tail = { Samples = sampleInfoListAt(endTime) } Tail = { Samples = sampleInfoListAt(endTime) }
}; };
newObject = holdNote; newObject = holdNote;
} }

View File

@ -87,6 +87,5 @@ namespace osu.Game.Rulesets.Mania.MathUtils
bitIndex++; bitIndex++;
return ((bitBuffer >>= 1) & 1) == 1; return ((bitBuffer >>= 1) & 1) == 1;
} }
} }
} }

View File

@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Mania.UI
{ {
Name = "Hit target + hit objects", Name = "Hit target + hit objects",
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = ManiaPlayfield.HIT_TARGET_POSITION}, Padding = new MarginPadding { Top = ManiaPlayfield.HIT_TARGET_POSITION },
Children = new Drawable[] Children = new Drawable[]
{ {
new Container new Container

View File

@ -1,201 +0,0 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK;
using System;
using System.Diagnostics;
using System.Linq;
namespace osu.Game.Rulesets.Osu.Objects
{
internal class OsuHitObjectDifficulty
{
/// <summary>
/// Factor by how much speed / aim strain decays per second.
/// </summary>
/// <remarks>
/// These values are results of tweaking a lot and taking into account general feedback.
/// Opinionated observation: Speed is easier to maintain than accurate jumps.
/// </remarks>
internal static readonly double[] DECAY_BASE = { 0.3, 0.15 };
/// <summary>
/// Pseudo threshold values to distinguish between "singles" and "streams"
/// </summary>
/// <remarks>
/// Of course the border can not be defined clearly, therefore the algorithm has a smooth transition between those values.
/// They also are based on tweaking and general feedback.
/// </remarks>
private const double stream_spacing_threshold = 110,
single_spacing_threshold = 125;
/// <summary>
/// Scaling values for weightings to keep aim and speed difficulty in balance.
/// </summary>
/// <remarks>
/// Found from testing a very large map pool (containing all ranked maps) and keeping the average values the same.
/// </remarks>
private static readonly double[] spacing_weight_scaling = { 1400, 26.25 };
/// <summary>
/// Almost the normed diameter of a circle (104 osu pixel). That is -after- position transforming.
/// </summary>
private const double almost_diameter = 90;
internal OsuHitObject BaseHitObject;
internal double[] Strains = { 1, 1 };
internal int MaxCombo = 1;
private readonly float scalingFactor;
private float lazySliderLength;
private readonly Vector2 startPosition;
private readonly Vector2 endPosition;
internal OsuHitObjectDifficulty(OsuHitObject baseHitObject)
{
BaseHitObject = baseHitObject;
float circleRadius = baseHitObject.Scale * 64;
Slider slider = BaseHitObject as Slider;
if (slider != null)
MaxCombo += slider.Ticks.Count();
// We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps.
scalingFactor = 52.0f / circleRadius;
if (circleRadius < 30)
{
float smallCircleBonus = Math.Min(30.0f - circleRadius, 5.0f) / 50.0f;
scalingFactor *= 1.0f + smallCircleBonus;
}
lazySliderLength = 0;
startPosition = baseHitObject.StackedPosition;
// Calculate approximation of lazy movement on the slider
if (slider != null)
{
float sliderFollowCircleRadius = circleRadius * 3; // Not sure if this is correct, but here we do not need 100% exact values. This comes pretty darn close in my tests.
// For simplifying this step we use actual osu! coordinates and simply scale the length, that we obtain by the ScalingFactor later
Vector2 cursorPos = startPosition;
Action<Vector2> addSliderVertex = delegate (Vector2 pos)
{
Vector2 difference = pos - cursorPos;
float distance = difference.Length;
// Did we move away too far?
if (distance > sliderFollowCircleRadius)
{
// Yep, we need to move the cursor
difference.Normalize(); // Obtain the direction of difference. We do no longer need the actual difference
distance -= sliderFollowCircleRadius;
cursorPos += difference * distance; // We move the cursor just as far as needed to stay in the follow circle
lazySliderLength += distance;
}
};
// Actual computation of the first lazy curve
foreach (var tick in slider.Ticks)
addSliderVertex(tick.StackedPosition);
addSliderVertex(baseHitObject.StackedEndPosition);
lazySliderLength *= scalingFactor;
endPosition = cursorPos;
}
// We have a normal HitCircle or a spinner
else
endPosition = startPosition;
}
internal void CalculateStrains(OsuHitObjectDifficulty previousHitObject, double timeRate)
{
calculateSpecificStrain(previousHitObject, OsuDifficultyCalculator.DifficultyType.Speed, timeRate);
calculateSpecificStrain(previousHitObject, OsuDifficultyCalculator.DifficultyType.Aim, timeRate);
}
// Caution: The subjective values are strong with this one
private static double spacingWeight(double distance, OsuDifficultyCalculator.DifficultyType type)
{
switch (type)
{
case OsuDifficultyCalculator.DifficultyType.Speed:
if (distance > single_spacing_threshold)
return 2.5;
else if (distance > stream_spacing_threshold)
return 1.6 + 0.9 * (distance - stream_spacing_threshold) / (single_spacing_threshold - stream_spacing_threshold);
else if (distance > almost_diameter)
return 1.2 + 0.4 * (distance - almost_diameter) / (stream_spacing_threshold - almost_diameter);
else if (distance > almost_diameter / 2)
return 0.95 + 0.25 * (distance - almost_diameter / 2) / (almost_diameter / 2);
else
return 0.95;
case OsuDifficultyCalculator.DifficultyType.Aim:
return Math.Pow(distance, 0.99);
}
Debug.Assert(false, "Invalid osu difficulty hit object type.");
return 0;
}
private void calculateSpecificStrain(OsuHitObjectDifficulty previousHitObject, OsuDifficultyCalculator.DifficultyType type, double timeRate)
{
double addition = 0;
double timeElapsed = (BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime) / timeRate;
double decay = Math.Pow(DECAY_BASE[(int)type], timeElapsed / 1000);
if (BaseHitObject is Spinner)
{
// Do nothing for spinners
}
else if (BaseHitObject is Slider)
{
switch (type)
{
case OsuDifficultyCalculator.DifficultyType.Speed:
// For speed strain we treat the whole slider as a single spacing entity, since "Speed" is about how hard it is to click buttons fast.
// The spacing weight exists to differentiate between being able to easily alternate or having to single.
addition =
spacingWeight(previousHitObject.lazySliderLength +
DistanceTo(previousHitObject), type) *
spacing_weight_scaling[(int)type];
break;
case OsuDifficultyCalculator.DifficultyType.Aim:
// For Aim strain we treat each slider segment and the jump after the end of the slider as separate jumps, since movement-wise there is no difference
// to multiple jumps.
addition =
(
spacingWeight(previousHitObject.lazySliderLength, type) +
spacingWeight(DistanceTo(previousHitObject), type)
) *
spacing_weight_scaling[(int)type];
break;
}
}
else if (BaseHitObject is HitCircle)
{
addition = spacingWeight(DistanceTo(previousHitObject), type) * spacing_weight_scaling[(int)type];
}
// Scale addition by the time, that elapsed. Filter out HitObjects that are too close to be played anyway to avoid crazy values by division through close to zero.
// You will never find maps that require this amongst ranked maps.
addition /= Math.Max(timeElapsed, 50);
Strains[(int)type] = previousHitObject.Strains[(int)type] * decay + addition;
}
internal double DistanceTo(OsuHitObjectDifficulty other)
{
// Scale the distance by circle size.
return (startPosition - other.endPosition).Length * scalingFactor;
}
}
}

View File

@ -0,0 +1,73 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Beatmaps;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing;
using osu.Game.Rulesets.Osu.OsuDifficulty.Skills;
namespace osu.Game.Rulesets.Osu.OsuDifficulty
{
public class OsuDifficultyCalculator : DifficultyCalculator<OsuHitObject>
{
private const int section_length = 400;
private const double difficulty_multiplier = 0.0675;
public OsuDifficultyCalculator(Beatmap beatmap) : base(beatmap)
{
}
protected override void PreprocessHitObjects()
{
foreach (OsuHitObject h in Objects)
(h as Slider)?.Curve?.Calculate();
}
protected override double CalculateInternal(Dictionary<string, string> categoryDifficulty)
{
OsuDifficultyBeatmap beatmap = new OsuDifficultyBeatmap(Objects);
Skill[] skills =
{
new Aim(),
new Speed()
};
double sectionEnd = section_length / TimeRate;
foreach (OsuDifficultyHitObject h in beatmap)
{
while (h.BaseObject.StartTime > sectionEnd)
{
foreach (Skill s in skills)
{
s.SaveCurrentPeak();
s.StartNewSectionFrom(sectionEnd);
}
sectionEnd += section_length;
}
foreach (Skill s in skills)
s.Process(h);
}
double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier;
double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier;
double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2;
if (categoryDifficulty != null)
{
categoryDifficulty.Add("Aim", aimRating.ToString("0.00"));
categoryDifficulty.Add("Speed", speedRating.ToString("0.00"));
}
return starRating;
}
protected override BeatmapConverter<OsuHitObject> CreateBeatmapConverter() => new OsuBeatmapConverter();
}
}

View File

@ -0,0 +1,93 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections;
using System.Collections.Generic;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
{
/// <summary>
/// An enumerable container wrapping <see cref="OsuHitObject"/> input as <see cref="OsuDifficultyHitObject"/>
/// which contains extra data required for difficulty calculation.
/// </summary>
public class OsuDifficultyBeatmap : IEnumerable<OsuDifficultyHitObject>
{
private readonly IEnumerator<OsuDifficultyHitObject> difficultyObjects;
private readonly Queue<OsuDifficultyHitObject> onScreen = new Queue<OsuDifficultyHitObject>();
/// <summary>
/// Creates an enumerator, which preprocesses a list of <see cref="OsuHitObject"/>s recieved as input, wrapping them as
/// <see cref="OsuDifficultyHitObject"/> which contains extra data required for difficulty calculation.
/// </summary>
public OsuDifficultyBeatmap(List<OsuHitObject> objects)
{
// Sort OsuHitObjects by StartTime - they are not correctly ordered in some cases.
// This should probably happen before the objects reach the difficulty calculator.
objects.Sort((a, b) => a.StartTime.CompareTo(b.StartTime));
difficultyObjects = createDifficultyObjectEnumerator(objects);
}
/// <summary>
/// Returns an enumerator that enumerates all <see cref="OsuDifficultyHitObject"/>s in the <see cref="OsuDifficultyBeatmap"/>.
/// The inner loop adds objects that appear on screen into a queue until we need to hit the next object.
/// The outer loop returns objects from this queue one at a time, only after they had to be hit, and should no longer be on screen.
/// This means that we can loop through every object that is on screen at the time when a new one appears,
/// allowing us to determine a reading strain for the object that just appeared.
/// </summary>
public IEnumerator<OsuDifficultyHitObject> GetEnumerator()
{
while (true)
{
// Add upcoming objects to the queue until we have at least one object that had been hit and can be dequeued.
// This means there is always at least one object in the queue unless we reached the end of the map.
do
{
if (!difficultyObjects.MoveNext())
break; // New objects can't be added anymore, but we still need to dequeue and return the ones already on screen.
OsuDifficultyHitObject latest = difficultyObjects.Current;
// Calculate flow values here
foreach (OsuDifficultyHitObject h in onScreen)
{
h.TimeUntilHit -= latest.DeltaTime;
// Calculate reading strain here
}
onScreen.Enqueue(latest);
}
while (onScreen.Peek().TimeUntilHit > 0); // Keep adding new objects on screen while there is still time before we have to hit the next one.
if (onScreen.Count == 0) break; // We have reached the end of the map and enumerated all the objects.
yield return onScreen.Dequeue(); // Remove and return objects one by one that had to be hit before the latest one appeared.
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
private IEnumerator<OsuDifficultyHitObject> createDifficultyObjectEnumerator(List<OsuHitObject> objects)
{
// We will process OsuHitObjects in groups of three to form a triangle, so we can calculate an angle for each object.
OsuHitObject[] triangle = new OsuHitObject[3];
// OsuDifficultyHitObject construction requires three components, an extra copy of the first OsuHitObject is used at the beginning.
if (objects.Count > 1)
{
triangle[1] = objects[0]; // This copy will get shifted to the last spot in the triangle.
triangle[0] = objects[0]; // This component corresponds to the real first OsuHitOject.
}
// The final component of the first triangle will be the second OsuHitOject of the map, which forms the first jump.
// If the map has less than two OsuHitObjects, the enumerator will not return anything.
for (int i = 1; i < objects.Count; ++i)
{
triangle[2] = triangle[1];
triangle[1] = triangle[0];
triangle[0] = objects[i];
yield return new OsuDifficultyHitObject(triangle);
}
}
}
}

View File

@ -0,0 +1,70 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
{
/// <summary>
/// A wrapper around <see cref="OsuHitObject"/> extending it with additional data required for difficulty calculation.
/// </summary>
public class OsuDifficultyHitObject
{
/// <summary>
/// The <see cref="OsuHitObject"/> this <see cref="OsuDifficultyHitObject"/> refers to.
/// </summary>
public OsuHitObject BaseObject { get; }
/// <summary>
/// Normalized distance from the <see cref="OsuHitObject.StackedPosition"/> of the previous <see cref="OsuDifficultyHitObject"/>.
/// </summary>
public double Distance { get; private set; }
/// <summary>
/// Milliseconds elapsed since the StartTime of the previous <see cref="OsuDifficultyHitObject"/>.
/// </summary>
public double DeltaTime { get; private set; }
/// <summary>
/// Number of milliseconds until the <see cref="OsuDifficultyHitObject"/> has to be hit.
/// </summary>
public double TimeUntilHit { get; set; }
private const int normalized_radius = 52;
private readonly OsuHitObject[] t;
/// <summary>
/// Initializes the object calculating extra data required for difficulty calculation.
/// </summary>
public OsuDifficultyHitObject(OsuHitObject[] triangle)
{
t = triangle;
BaseObject = t[0];
setDistances();
setTimingValues();
// Calculate angle here
}
private void setDistances()
{
// We will scale distances by this factor, so we can assume a uniform CircleSize among beatmaps.
double scalingFactor = normalized_radius / BaseObject.Radius;
if (BaseObject.Radius < 30)
{
double smallCircleBonus = Math.Min(30 - BaseObject.Radius, 5) / 50;
scalingFactor *= 1 + smallCircleBonus;
}
Distance = (t[0].StackedPosition - t[1].StackedPosition).Length * scalingFactor;
}
private void setTimingValues()
{
// Every timing inverval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure.
DeltaTime = Math.Max(40, t[0].StartTime - t[1].StartTime);
TimeUntilHit = 450; // BaseObject.PreEmpt;
}
}
}

View File

@ -0,0 +1,19 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing;
namespace osu.Game.Rulesets.Osu.OsuDifficulty.Skills
{
/// <summary>
/// Represents the skill required to correctly aim at every object in the map with a uniform CircleSize and normalized distances.
/// </summary>
public class Aim : Skill
{
protected override double SkillMultiplier => 26.25;
protected override double StrainDecayBase => 0.15;
protected override double StrainValueOf(OsuDifficultyHitObject current) => Math.Pow(current.Distance, 0.99) / current.DeltaTime;
}
}

View File

@ -0,0 +1,100 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing;
using osu.Game.Rulesets.Osu.OsuDifficulty.Utils;
namespace osu.Game.Rulesets.Osu.OsuDifficulty.Skills
{
/// <summary>
/// Used to processes strain values of <see cref="OsuDifficultyHitObject"/>s, keep track of strain levels caused by the processed objects
/// and to calculate a final difficulty value representing the difficulty of hitting all the processed objects.
/// </summary>
public abstract class Skill
{
/// <summary>
/// Strain values are multiplied by this number for the given skill. Used to balance the value of different skills between each other.
/// </summary>
protected abstract double SkillMultiplier { get; }
/// <summary>
/// Determines how quickly strain decays for the given skill.
/// For example a value of 0.15 indicates that strain decays to 15% of its original value in one second.
/// </summary>
protected abstract double StrainDecayBase { get; }
/// <summary>
/// <see cref="OsuDifficultyHitObject"/>s that were processed previously. They can affect the strain values of the following objects.
/// </summary>
protected readonly History<OsuDifficultyHitObject> Previous = new History<OsuDifficultyHitObject>(2); // Contained objects not used yet
private double currentStrain = 1; // We keep track of the strain level at all times throughout the beatmap.
private double currentSectionPeak = 1; // We also keep track of the peak strain level in the current section.
private readonly List<double> strainPeaks = new List<double>();
/// <summary>
/// Process an <see cref="OsuDifficultyHitObject"/> and update current strain values accordingly.
/// </summary>
public void Process(OsuDifficultyHitObject current)
{
currentStrain *= strainDecay(current.DeltaTime);
if (!(current.BaseObject is Spinner))
currentStrain += StrainValueOf(current) * SkillMultiplier;
currentSectionPeak = Math.Max(currentStrain, currentSectionPeak);
Previous.Push(current);
}
/// <summary>
/// Saves the current peak strain level to the list of strain peaks, which will be used to calculate an overall difficulty.
/// </summary>
public void SaveCurrentPeak()
{
if (Previous.Count > 0)
strainPeaks.Add(currentSectionPeak);
}
/// <summary>
/// Sets the initial strain level for a new section.
/// </summary>
/// <param name="offset">The beginning of the new section in milliseconds</param>
public void StartNewSectionFrom(double offset)
{
// The maximum strain of the new section is not zero by default, strain decays as usual regardless of section boundaries.
// This means we need to capture the strain level at the beginning of the new section, and use that as the initial peak level.
if (Previous.Count > 0)
currentSectionPeak = currentStrain * strainDecay(offset - Previous[0].BaseObject.StartTime);
}
/// <summary>
/// Returns the calculated difficulty value representing all processed <see cref="OsuDifficultyHitObject"/>s.
/// </summary>
public double DifficultyValue()
{
strainPeaks.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain.
double difficulty = 0;
double weight = 1;
// Difficulty is the weighted sum of the highest strains from every section.
foreach (double strain in strainPeaks)
{
difficulty += strain * weight;
weight *= 0.9;
}
return difficulty;
}
/// <summary>
/// Calculates the strain value of an <see cref="OsuDifficultyHitObject"/>. This value is affected by previously processed objects.
/// </summary>
protected abstract double StrainValueOf(OsuDifficultyHitObject current);
private double strainDecay(double ms) => Math.Pow(StrainDecayBase, ms / 1000);
}
}

View File

@ -0,0 +1,39 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing;
namespace osu.Game.Rulesets.Osu.OsuDifficulty.Skills
{
/// <summary>
/// Represents the skill required to press keys with regards to keeping up with the speed at which objects need to be hit.
/// </summary>
public class Speed : Skill
{
protected override double SkillMultiplier => 1400;
protected override double StrainDecayBase => 0.3;
private const double single_spacing_threshold = 125;
private const double stream_spacing_threshold = 110;
private const double almost_diameter = 90;
protected override double StrainValueOf(OsuDifficultyHitObject current)
{
double distance = current.Distance;
double speedValue;
if (distance > single_spacing_threshold)
speedValue = 2.5;
else if (distance > stream_spacing_threshold)
speedValue = 1.6 + 0.9 * (distance - stream_spacing_threshold) / (single_spacing_threshold - stream_spacing_threshold);
else if (distance > almost_diameter)
speedValue = 1.2 + 0.4 * (distance - almost_diameter) / (stream_spacing_threshold - almost_diameter);
else if (distance > almost_diameter / 2)
speedValue = 0.95 + 0.25 * (distance - almost_diameter / 2) / (almost_diameter / 2);
else
speedValue = 0.95;
return speedValue / current.DeltaTime;
}
}
}

View File

@ -0,0 +1,86 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections;
using System.Collections.Generic;
namespace osu.Game.Rulesets.Osu.OsuDifficulty.Utils
{
/// <summary>
/// An indexed stack with Push() only, which disposes items at the bottom after the capacity is full.
/// Indexing starts at the top of the stack.
/// </summary>
public class History<T> : IEnumerable<T>
{
public int Count { get; private set; }
private readonly T[] array;
private readonly int capacity;
private int marker; // Marks the position of the most recently added item.
/// <summary>
/// Initializes a new instance of the History class that is empty and has the specified capacity.
/// </summary>
/// <param name="capacity">The number of items the History can hold.</param>
public History(int capacity)
{
if (capacity < 0)
throw new ArgumentOutOfRangeException();
this.capacity = capacity;
array = new T[capacity];
marker = capacity; // Set marker to the end of the array, outside of the indexed range by one.
}
/// <summary>
/// The most recently added item is returned at index 0.
/// </summary>
public T this[int i]
{
get
{
if (i < 0 || i > Count - 1)
throw new IndexOutOfRangeException();
i += marker;
if (i > capacity - 1)
i -= capacity;
return array[i];
}
}
/// <summary>
/// Adds the item as the most recent one in the history.
/// The oldest item is disposed if the history is full.
/// </summary>
public void Push(T item) // Overwrite the oldest item instead of shifting every item by one with every addition.
{
if (marker == 0)
marker = capacity - 1;
else
--marker;
array[marker] = item;
if (Count < capacity)
++Count;
}
/// <summary>
/// Returns an enumerator which enumerates items in the history starting from the most recently added one.
/// </summary>
public IEnumerator<T> GetEnumerator()
{
for (int i = marker; i < capacity; ++i)
yield return array[i];
if (Count == capacity)
for (int i = 0; i < marker; ++i)
yield return array[i];
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}

View File

@ -1,192 +0,0 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Beatmaps;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Objects;
using System;
using System.Collections.Generic;
namespace osu.Game.Rulesets.Osu
{
public class OsuDifficultyCalculator : DifficultyCalculator<OsuHitObject>
{
private const double star_scaling_factor = 0.0675;
private const double extreme_scaling_factor = 0.5;
/// <summary>
/// HitObjects are stored as a member variable.
/// </summary>
internal List<OsuHitObjectDifficulty> DifficultyHitObjects = new List<OsuHitObjectDifficulty>();
public OsuDifficultyCalculator(Beatmap beatmap) : base(beatmap)
{
}
protected override void PreprocessHitObjects()
{
foreach (var h in Objects)
(h as Slider)?.Curve?.Calculate();
}
protected override double CalculateInternal(Dictionary<string, string> categoryDifficulty)
{
// Fill our custom DifficultyHitObject class, that carries additional information
DifficultyHitObjects.Clear();
foreach (var hitObject in Objects)
DifficultyHitObjects.Add(new OsuHitObjectDifficulty(hitObject));
// Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure.
DifficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime));
if (!CalculateStrainValues()) return 0;
double speedDifficulty = CalculateDifficulty(DifficultyType.Speed);
double aimDifficulty = CalculateDifficulty(DifficultyType.Aim);
// OverallDifficulty is not considered in this algorithm and neither is HpDrainRate. That means, that in this form the algorithm determines how hard it physically is
// to play the map, assuming, that too much of an error will not lead to a death.
// It might be desirable to include OverallDifficulty into map difficulty, but in my personal opinion it belongs more to the weighting of the actual peformance
// and is superfluous in the beatmap difficulty rating.
// If it were to be considered, then I would look at the hit window of normal HitCircles only, since Sliders and Spinners are (almost) "free" 300s and take map length
// into account as well.
// The difficulty can be scaled by any desired metric.
// In osu!tp it gets squared to account for the rapid increase in difficulty as the limit of a human is approached. (Of course it also gets scaled afterwards.)
// It would not be suitable for a star rating, therefore:
// The following is a proposal to forge a star rating from 0 to 5. It consists of taking the square root of the difficulty, since by simply scaling the easier
// 5-star maps would end up with one star.
double speedStars = Math.Sqrt(speedDifficulty) * star_scaling_factor;
double aimStars = Math.Sqrt(aimDifficulty) * star_scaling_factor;
if (categoryDifficulty != null)
{
categoryDifficulty.Add("Aim", aimStars.ToString("0.00"));
categoryDifficulty.Add("Speed", speedStars.ToString("0.00"));
double hitWindow300 = 30/*HitObjectManager.HitWindow300*/ / TimeRate;
double preEmpt = 450/*HitObjectManager.PreEmpt*/ / TimeRate;
categoryDifficulty.Add("OD", (-(hitWindow300 - 80.0) / 6.0).ToString("0.00"));
categoryDifficulty.Add("AR", (preEmpt > 1200.0 ? -(preEmpt - 1800.0) / 120.0 : -(preEmpt - 1200.0) / 150.0 + 5.0).ToString("0.00"));
int maxCombo = 0;
foreach (OsuHitObjectDifficulty hitObject in DifficultyHitObjects)
maxCombo += hitObject.MaxCombo;
categoryDifficulty.Add("Max combo", maxCombo.ToString());
}
// Again, from own observations and from the general opinion of the community a map with high speed and low aim (or vice versa) difficulty is harder,
// than a map with mediocre difficulty in both. Therefore we can not just add both difficulties together, but will introduce a scaling that favors extremes.
double starRating = speedStars + aimStars + Math.Abs(speedStars - aimStars) * extreme_scaling_factor;
// Another approach to this would be taking Speed and Aim separately to a chosen power, which again would be equivalent. This would be more convenient if
// the hit window size is to be considered as well.
// Note: The star rating is tuned extremely tight! Airman (/b/104229) and Freedom Dive (/b/126645), two of the hardest ranked maps, both score ~4.66 stars.
// Expect the easier kind of maps that officially get 5 stars to obtain around 2 by this metric. The tutorial still scores about half a star.
// Tune by yourself as you please. ;)
return starRating;
}
protected bool CalculateStrainValues()
{
// Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment.
using (List<OsuHitObjectDifficulty>.Enumerator hitObjectsEnumerator = DifficultyHitObjects.GetEnumerator())
{
if (!hitObjectsEnumerator.MoveNext()) return false;
OsuHitObjectDifficulty current = hitObjectsEnumerator.Current;
// First hitObject starts at strain 1. 1 is the default for strain values, so we don't need to set it here. See DifficultyHitObject.
while (hitObjectsEnumerator.MoveNext())
{
var next = hitObjectsEnumerator.Current;
next?.CalculateStrains(current, TimeRate);
current = next;
}
return true;
}
}
/// <summary>
/// In milliseconds. For difficulty calculation we will only look at the highest strain value in each time interval of size STRAIN_STEP.
/// This is to eliminate higher influence of stream over aim by simply having more HitObjects with high strain.
/// The higher this value, the less strains there will be, indirectly giving long beatmaps an advantage.
/// </summary>
protected const double STRAIN_STEP = 400;
/// <summary>
/// The weighting of each strain value decays to this number * it's previous value
/// </summary>
protected const double DECAY_WEIGHT = 0.9;
protected double CalculateDifficulty(DifficultyType type)
{
double actualStrainStep = STRAIN_STEP * TimeRate;
// Find the highest strain value within each strain step
List<double> highestStrains = new List<double>();
double intervalEndTime = actualStrainStep;
double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval
OsuHitObjectDifficulty previousHitObject = null;
foreach (OsuHitObjectDifficulty hitObject in DifficultyHitObjects)
{
// While we are beyond the current interval push the currently available maximum to our strain list
while (hitObject.BaseHitObject.StartTime > intervalEndTime)
{
highestStrains.Add(maximumStrain);
// The maximum strain of the next interval is not zero by default! We need to take the last hitObject we encountered, take its strain and apply the decay
// until the beginning of the next interval.
if (previousHitObject == null)
{
maximumStrain = 0;
}
else
{
double decay = Math.Pow(OsuHitObjectDifficulty.DECAY_BASE[(int)type], (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000);
maximumStrain = previousHitObject.Strains[(int)type] * decay;
}
// Go to the next time interval
intervalEndTime += actualStrainStep;
}
// Obtain maximum strain
maximumStrain = Math.Max(hitObject.Strains[(int)type], maximumStrain);
previousHitObject = hitObject;
}
// Build the weighted sum over the highest strains for each interval
double difficulty = 0;
double weight = 1;
highestStrains.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain.
foreach (double strain in highestStrains)
{
difficulty += weight * strain;
weight *= DECAY_WEIGHT;
}
return difficulty;
}
protected override BeatmapConverter<OsuHitObject> CreateBeatmapConverter() => new OsuBeatmapConverter();
// Those values are used as array indices. Be careful when changing them!
public enum DifficultyType
{
Speed = 0,
Aim,
};
}
}

View File

@ -7,6 +7,7 @@ using osu.Game.Graphics;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.OsuDifficulty;
using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;

View File

@ -68,9 +68,14 @@
<Compile Include="Objects\Drawables\Pieces\TrianglesPiece.cs" /> <Compile Include="Objects\Drawables\Pieces\TrianglesPiece.cs" />
<Compile Include="Objects\Drawables\Pieces\SliderBall.cs" /> <Compile Include="Objects\Drawables\Pieces\SliderBall.cs" />
<Compile Include="Objects\Drawables\Pieces\SliderBody.cs" /> <Compile Include="Objects\Drawables\Pieces\SliderBody.cs" />
<Compile Include="Objects\OsuHitObjectDifficulty.cs" />
<Compile Include="Objects\SliderTick.cs" /> <Compile Include="Objects\SliderTick.cs" />
<Compile Include="OsuDifficultyCalculator.cs" /> <Compile Include="OsuDifficulty\OsuDifficultyCalculator.cs" />
<Compile Include="OsuDifficulty\Preprocessing\OsuDifficultyBeatmap.cs" />
<Compile Include="OsuDifficulty\Preprocessing\OsuDifficultyHitObject.cs" />
<Compile Include="OsuDifficulty\Skills\Aim.cs" />
<Compile Include="OsuDifficulty\Skills\Skill.cs" />
<Compile Include="OsuDifficulty\Skills\Speed.cs" />
<Compile Include="OsuDifficulty\Utils\History.cs" />
<Compile Include="OsuKeyConversionInputManager.cs" /> <Compile Include="OsuKeyConversionInputManager.cs" />
<Compile Include="Scoring\OsuScoreProcessor.cs" /> <Compile Include="Scoring\OsuScoreProcessor.cs" />
<Compile Include="UI\OsuHitRenderer.cs" /> <Compile Include="UI\OsuHitRenderer.cs" />

View File

@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
Size = new Vector2(TaikoHitObject.DEFAULT_CIRCLE_DIAMETER), Size = new Vector2(TaikoHitObject.DEFAULT_CIRCLE_DIAMETER),
BlendingMode = BlendingMode.Additive, BlendingMode = BlendingMode.Additive,
Masking = true, Masking = true,
Children = new [] Children = new[]
{ {
new Box new Box
{ {
@ -119,7 +119,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Children = new [] Children = new[]
{ {
symbol = new SwellSymbolPiece() symbol = new SwellSymbolPiece()
} }

View File

@ -229,7 +229,6 @@ namespace osu.Game.Rulesets.Taiko.UI
if (judgedObject.HitObject.Kiai) if (judgedObject.HitObject.Kiai)
kiaiExplosionContainer.Add(new KiaiHitExplosion(judgedObject.Judgement, isRim)); kiaiExplosionContainer.Add(new KiaiHitExplosion(judgedObject.Judgement, isRim));
} }
else else
hitExplosionContainer.Children.FirstOrDefault(e => e.Judgement == judgedObject.Judgement)?.VisualiseSecondHit(); hitExplosionContainer.Children.FirstOrDefault(e => e.Judgement == judgedObject.Judgement)?.VisualiseSecondHit();

View File

@ -166,4 +166,3 @@ namespace osu.Game.Tests.Beatmaps.IO
} }
} }
} }

View File

@ -86,4 +86,3 @@ namespace osu.Game.Tests.Beatmaps.IO
} }
} }
} }

View File

@ -10,7 +10,6 @@ using OpenTK.Graphics;
namespace osu.Game.Beatmaps.Drawables namespace osu.Game.Beatmaps.Drawables
{ {
public class DifficultyIcon : DifficultyColouredContainer public class DifficultyIcon : DifficultyColouredContainer
{ {
private readonly BeatmapInfo beatmap; private readonly BeatmapInfo beatmap;

View File

@ -13,7 +13,6 @@ namespace osu.Game.Configuration
protected override void InitialiseDefaults() protected override void InitialiseDefaults()
{ {
// UI/selection defaults // UI/selection defaults
Set(OsuSetting.Ruleset, 0, 0, int.MaxValue); Set(OsuSetting.Ruleset, 0, 0, int.MaxValue);
Set(OsuSetting.BeatmapDetailTab, BeatmapDetailTab.Details); Set(OsuSetting.BeatmapDetailTab, BeatmapDetailTab.Details);
@ -25,7 +24,6 @@ namespace osu.Game.Configuration
Set(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2, 1); Set(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2, 1);
// Online settings // Online settings
Set(OsuSetting.Username, string.Empty); Set(OsuSetting.Username, string.Empty);
Set(OsuSetting.Token, string.Empty); Set(OsuSetting.Token, string.Empty);
@ -40,14 +38,12 @@ namespace osu.Game.Configuration
}; };
// Audio // Audio
Set(OsuSetting.MenuVoice, true); Set(OsuSetting.MenuVoice, true);
Set(OsuSetting.MenuMusic, true); Set(OsuSetting.MenuMusic, true);
Set(OsuSetting.AudioOffset, 0, -500.0, 500.0); Set(OsuSetting.AudioOffset, 0, -500.0, 500.0);
// Input // Input
Set(OsuSetting.MenuCursorSize, 1.0, 0.5f, 2); Set(OsuSetting.MenuCursorSize, 1.0, 0.5f, 2);
Set(OsuSetting.GameplayCursorSize, 1.0, 0.5f, 2); Set(OsuSetting.GameplayCursorSize, 1.0, 0.5f, 2);
Set(OsuSetting.AutoCursorSize, false); Set(OsuSetting.AutoCursorSize, false);
@ -56,7 +52,6 @@ namespace osu.Game.Configuration
Set(OsuSetting.MouseDisableWheel, false); Set(OsuSetting.MouseDisableWheel, false);
// Graphics // Graphics
Set(OsuSetting.ShowFpsDisplay, false); Set(OsuSetting.ShowFpsDisplay, false);
Set(OsuSetting.MenuParallax, true); Set(OsuSetting.MenuParallax, true);
@ -65,7 +60,6 @@ namespace osu.Game.Configuration
Set(OsuSetting.SnakingOutSliders, true); Set(OsuSetting.SnakingOutSliders, true);
// Gameplay // Gameplay
Set(OsuSetting.DimLevel, 0.3, 0, 1); Set(OsuSetting.DimLevel, 0.3, 0, 1);
Set(OsuSetting.ShowInterface, true); Set(OsuSetting.ShowInterface, true);
@ -75,7 +69,6 @@ namespace osu.Game.Configuration
Set(OsuSetting.PlaybackSpeed, 1.0, 0.5f, 2); Set(OsuSetting.PlaybackSpeed, 1.0, 0.5f, 2);
// Update // Update
Set(OsuSetting.ReleaseStream, ReleaseStream.Lazer); Set(OsuSetting.ReleaseStream, ReleaseStream.Lazer);
} }

View File

@ -39,4 +39,3 @@ namespace osu.Game.Database
} }
} }
} }

View File

@ -36,4 +36,3 @@ namespace osu.Game.Database
public string StoryboardFile { get; set; } public string StoryboardFile { get; set; }
} }
} }

View File

@ -81,7 +81,7 @@ namespace osu.Game.Graphics.UserInterface
background = new Box background = new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = new Color4(0,0,0,0) Colour = new Color4(0, 0, 0, 0)
}, },
bar = new Box bar = new Box
{ {

View File

@ -16,7 +16,7 @@ namespace osu.Game.Graphics.UserInterface
throw new InvalidOperationException("OsuEnumDropdown only supports enums as the generic type argument"); throw new InvalidOperationException("OsuEnumDropdown only supports enums as the generic type argument");
List<KeyValuePair<string, T>> items = new List<KeyValuePair<string, T>>(); List<KeyValuePair<string, T>> items = new List<KeyValuePair<string, T>>();
foreach(var val in (T[])Enum.GetValues(typeof(T))) foreach (var val in (T[])Enum.GetValues(typeof(T)))
{ {
var field = typeof(T).GetField(Enum.GetName(typeof(T), val)); var field = typeof(T).GetField(Enum.GetName(typeof(T), val));
items.Add( items.Add(

View File

@ -10,22 +10,20 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.Direct namespace osu.Game.Graphics.UserInterface
{ {
public class SortTabControl : OsuTabControl<SortCriteria> public class PageTabControl<T> : OsuTabControl<T>
{ {
protected override TabItem<SortCriteria> CreateTabItem(SortCriteria value) => new SortTabItem(value); protected override TabItem<T> CreateTabItem(T value) => new PageTabItem(value);
public SortTabControl() public PageTabControl()
{ {
Height = 30; Height = 30;
} }
private class SortTabItem : TabItem<SortCriteria> private class PageTabItem : TabItem<T>
{ {
private const float transition_duration = 100; private const float transition_duration = 100;
@ -46,7 +44,7 @@ namespace osu.Game.Overlays.Direct
} }
} }
public SortTabItem(SortCriteria value) : base(value) public PageTabItem(T value) : base(value)
{ {
AutoSizeAxes = Axes.X; AutoSizeAxes = Axes.X;
RelativeSizeAxes = Axes.Y; RelativeSizeAxes = Axes.Y;
@ -104,14 +102,4 @@ namespace osu.Game.Overlays.Direct
} }
} }
} }
public enum SortCriteria
{
Title,
Artist,
Creator,
Difficulty,
Ranked,
Rating,
}
} }

View File

@ -90,7 +90,7 @@ namespace osu.Game.Graphics.UserInterface
Offset = new Vector2(2, 0), Offset = new Vector2(2, 0),
Radius = 2, Radius = 2,
}, },
Children = new [] { Children = new[] {
IconLayer = new Box IconLayer = new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
@ -124,7 +124,7 @@ namespace osu.Game.Graphics.UserInterface
Offset = new Vector2(2, 0), Offset = new Vector2(2, 0),
Radius = 2, Radius = 2,
}, },
Children = new [] { Children = new[] {
TextLayer = new Box TextLayer = new Box
{ {
Origin = Anchor.TopLeft, Origin = Anchor.TopLeft,

View File

@ -59,7 +59,6 @@ namespace osu.Game.Online.API
{ {
} }
return null; return null;
} }
} }

View File

@ -0,0 +1,20 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using Newtonsoft.Json;
using osu.Game.Users;
namespace osu.Game.Online.API.Requests
{
public class GetUsersRequest : APIRequest<List<RankingEntry>>
{
protected override string Target => @"rankings/osu/performance";
}
public class RankingEntry
{
[JsonProperty]
public User User;
}
}

View File

@ -43,6 +43,8 @@ namespace osu.Game
private DirectOverlay direct; private DirectOverlay direct;
private SocialOverlay social;
private Intro intro private Intro intro
{ {
get get
@ -148,7 +150,7 @@ namespace osu.Game
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
}, },
volume = new VolumeControl(), volume = new VolumeControl(),
overlayContent = new Container{ RelativeSizeAxes = Axes.Both }, overlayContent = new Container { RelativeSizeAxes = Axes.Both },
new OnScreenDisplay(), new OnScreenDisplay(),
new GlobalHotkeys //exists because UserInputManager is at a level below us. new GlobalHotkeys //exists because UserInputManager is at a level below us.
{ {
@ -165,6 +167,7 @@ namespace osu.Game
//overlay elements //overlay elements
LoadComponentAsync(direct = new DirectOverlay { Depth = -1 }, mainContent.Add); LoadComponentAsync(direct = new DirectOverlay { Depth = -1 }, mainContent.Add);
LoadComponentAsync(social = new SocialOverlay { Depth = -1 }, mainContent.Add);
LoadComponentAsync(chat = new ChatOverlay { Depth = -1 }, mainContent.Add); LoadComponentAsync(chat = new ChatOverlay { Depth = -1 }, mainContent.Add);
LoadComponentAsync(settings = new SettingsOverlay { Depth = -1 }, overlayContent.Add); LoadComponentAsync(settings = new SettingsOverlay { Depth = -1 }, overlayContent.Add);
LoadComponentAsync(musicController = new MusicController LoadComponentAsync(musicController = new MusicController
@ -198,11 +201,16 @@ namespace osu.Game
}; };
Dependencies.Cache(settings); Dependencies.Cache(settings);
Dependencies.Cache(social);
Dependencies.Cache(chat); Dependencies.Cache(chat);
Dependencies.Cache(musicController); Dependencies.Cache(musicController);
Dependencies.Cache(notificationManager); Dependencies.Cache(notificationManager);
Dependencies.Cache(dialogOverlay); Dependencies.Cache(dialogOverlay);
// ensure both overlays aren't presented at the same time
chat.StateChanged += (container, state) => social.State = state == Visibility.Visible ? Visibility.Hidden : social.State;
social.StateChanged += (container, state) => chat.State = state == Visibility.Visible ? Visibility.Hidden : chat.State;
LoadComponentAsync(Toolbar = new Toolbar LoadComponentAsync(Toolbar = new Toolbar
{ {
Depth = -3, Depth = -3,
@ -234,6 +242,9 @@ namespace osu.Game
case Key.F8: case Key.F8:
chat.ToggleVisibility(); chat.ToggleVisibility();
return true; return true;
case Key.F9:
social.ToggleVisibility();
return true;
case Key.PageUp: case Key.PageUp:
case Key.PageDown: case Key.PageDown:
var swClock = (Clock as ThrottledFrameClock)?.Source as StopwatchClock; var swClock = (Clock as ThrottledFrameClock)?.Source as StopwatchClock;
@ -292,6 +303,7 @@ namespace osu.Game
musicController.State = Visibility.Hidden; musicController.State = Visibility.Hidden;
chat.State = Visibility.Hidden; chat.State = Visibility.Hidden;
direct.State = Visibility.Hidden; direct.State = Visibility.Hidden;
social.State = Visibility.Hidden;
} }
else else
{ {

View File

@ -86,6 +86,6 @@ namespace osu.Game.Overlays.Chat
} }
} }
private void scrollToEnd() => Scheduler.AddDelayed(() => scroll.ScrollToEnd(), 50); private void scrollToEnd() => ScheduleAfterChildren(() => scroll.ScrollToEnd());
} }
} }

View File

@ -367,7 +367,6 @@ namespace osu.Game.Overlays
} }
else else
{ {
careChannels.Add(channel); careChannels.Add(channel);
channelTabs.AddItem(channel); channelTabs.AddItem(channel);
} }

View File

@ -5,117 +5,35 @@ using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Configuration; using osu.Framework.Configuration;
using osu.Framework.Extensions.Color4Extensions;
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.Game.Database; using osu.Game.Database;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.SearchableList;
namespace osu.Game.Overlays.Direct namespace osu.Game.Overlays.Direct
{ {
public class FilterControl : Container public class FilterControl : SearchableListFilterControl<DirectSortCritera, RankStatus>
{ {
public static readonly float HEIGHT = 35 + 32 + 30 + padding * 2; // search + mode toggle buttons + sort tabs + padding private FillFlowContainer<RulesetToggleButton> modeButtons;
private const float padding = 10; protected override Color4 BackgroundColour => OsuColour.FromHex(@"384552");
protected override DirectSortCritera DefaultTab => DirectSortCritera.Title;
private readonly Box tabStrip; protected override Drawable CreateSupplementaryControls()
private readonly FillFlowContainer<RulesetToggleButton> modeButtons;
public readonly SearchTextBox Search;
public readonly SortTabControl SortTabs;
public readonly OsuEnumDropdown<RankStatus> RankStatusDropdown;
public readonly Bindable<DirectOverlay.PanelDisplayStyle> DisplayStyle = new Bindable<DirectOverlay.PanelDisplayStyle>();
protected override bool InternalContains(Vector2 screenSpacePos) => base.InternalContains(screenSpacePos) || RankStatusDropdown.Contains(screenSpacePos);
public FilterControl()
{ {
RelativeSizeAxes = Axes.X; modeButtons = new FillFlowContainer<RulesetToggleButton>
Height = HEIGHT;
DisplayStyle.Value = DirectOverlay.PanelDisplayStyle.Grid;
Children = new Drawable[]
{ {
new Box AutoSizeAxes = Axes.Both,
{ Spacing = new Vector2(10f, 0f),
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.FromHex(@"384552"),
Alpha = 0.9f,
},
tabStrip = new Box
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.TopLeft,
RelativeSizeAxes = Axes.X,
Height = 1,
},
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding { Left = DirectOverlay.WIDTH_PADDING, Right = DirectOverlay.WIDTH_PADDING },
Children = new Drawable[]
{
Search = new DirectSearchTextBox
{
RelativeSizeAxes = Axes.X,
Margin = new MarginPadding { Top = padding },
},
modeButtons = new FillFlowContainer<RulesetToggleButton>
{
AutoSizeAxes = Axes.Both,
Spacing = new Vector2(padding, 0f),
Margin = new MarginPadding { Top = padding },
},
SortTabs = new SortTabControl
{
RelativeSizeAxes = Axes.X,
},
},
},
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Spacing = new Vector2(10f, 0f),
Direction = FillDirection.Horizontal,
Margin = new MarginPadding { Top = HEIGHT - SlimEnumDropdown<DirectTab>.HEIGHT - padding, Right = DirectOverlay.WIDTH_PADDING },
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Spacing = new Vector2(5f, 0f),
Direction = FillDirection.Horizontal,
Children = new[]
{
new DisplayStyleToggleButton(FontAwesome.fa_th_large, DirectOverlay.PanelDisplayStyle.Grid, DisplayStyle),
new DisplayStyleToggleButton(FontAwesome.fa_list_ul, DirectOverlay.PanelDisplayStyle.List, DisplayStyle),
},
},
RankStatusDropdown = new SlimEnumDropdown<RankStatus>
{
RelativeSizeAxes = Axes.None,
Width = 160f,
},
},
},
}; };
RankStatusDropdown.Current.Value = RankStatus.RankedApproved; return modeButtons;
SortTabs.Current.Value = SortCriteria.Title;
SortTabs.Current.TriggerChange();
} }
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader(true)]
private void load(OsuGame game, RulesetDatabase rulesets, OsuColour colours) private void load(OsuGame game, RulesetDatabase rulesets, OsuColour colours)
{ {
tabStrip.Colour = colours.Yellow; DisplayStyleControl.Dropdown.AccentColour = colours.BlueDark;
RankStatusDropdown.AccentColour = colours.BlueDark;
var b = new Bindable<RulesetInfo>(); //backup bindable incase the game is null var b = new Bindable<RulesetInfo>(); //backup bindable incase the game is null
foreach (var r in rulesets.AllRulesets) foreach (var r in rulesets.AllRulesets)
@ -124,20 +42,6 @@ namespace osu.Game.Overlays.Direct
} }
} }
private class DirectSearchTextBox : SearchTextBox
{
protected override Color4 BackgroundUnfocused => backgroundColour;
protected override Color4 BackgroundFocused => backgroundColour;
private Color4 backgroundColour;
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
backgroundColour = colours.Gray2.Opacity(0.9f);
}
}
private class RulesetToggleButton : ClickableContainer private class RulesetToggleButton : ClickableContainer
{ {
private readonly TextAwesome icon; private readonly TextAwesome icon;
@ -188,46 +92,15 @@ namespace osu.Game.Overlays.Direct
base.Dispose(isDisposing); base.Dispose(isDisposing);
} }
} }
}
private class DisplayStyleToggleButton : ClickableContainer public enum DirectSortCritera
{ {
private readonly TextAwesome icon; Title,
private readonly DirectOverlay.PanelDisplayStyle style; Artist,
private readonly Bindable<DirectOverlay.PanelDisplayStyle> bindable; Creator,
Difficulty,
public DisplayStyleToggleButton(FontAwesome icon, DirectOverlay.PanelDisplayStyle style, Bindable<DirectOverlay.PanelDisplayStyle> bindable) Ranked,
{ Rating,
this.bindable = bindable;
this.style = style;
Size = new Vector2(SlimEnumDropdown<DirectTab>.HEIGHT);
Children = new Drawable[]
{
this.icon = new TextAwesome
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Icon = icon,
TextSize = 18,
UseFullGlyphHeight = false,
Alpha = 0.5f,
},
};
bindable.ValueChanged += Bindable_ValueChanged;
Bindable_ValueChanged(bindable.Value);
Action = () => bindable.Value = this.style;
}
private void Bindable_ValueChanged(DirectOverlay.PanelDisplayStyle style)
{
icon.FadeTo(style == this.style ? 1.0f : 0.5f, 100);
}
protected override void Dispose(bool isDisposing)
{
bindable.ValueChanged -= Bindable_ValueChanged;
}
}
} }
} }

View File

@ -2,113 +2,28 @@
// 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.ComponentModel; using System.ComponentModel;
using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.SearchableList;
using Container = osu.Framework.Graphics.Containers.Container;
namespace osu.Game.Overlays.Direct namespace osu.Game.Overlays.Direct
{ {
public class Header : Container public class Header : SearchableListHeader<DirectTab>
{ {
public static readonly float HEIGHT = 90; protected override Color4 BackgroundColour => OsuColour.FromHex(@"252f3a");
protected override float TabStripWidth => 298;
private readonly Box tabStrip; protected override DirectTab DefaultTab => DirectTab.Search;
protected override Drawable CreateHeaderText() => new OsuSpriteText { Text = @"osu!direct", TextSize = 25 };
public readonly OsuTabControl<DirectTab> Tabs; protected override FontAwesome Icon => FontAwesome.fa_osu_chevron_down_o;
public Header() public Header()
{ {
Height = HEIGHT;
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.FromHex(@"252f3a"),
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = DirectOverlay.WIDTH_PADDING, Right = DirectOverlay.WIDTH_PADDING },
Children = new Drawable[]
{
new FillFlowContainer
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.BottomLeft,
Position = new Vector2(-35f, 5f),
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(10f, 0f),
Children = new Drawable[]
{
new TextAwesome
{
TextSize = 25,
Icon = FontAwesome.fa_osu_chevron_down_o,
},
new OsuSpriteText
{
TextSize = 25,
Text = @"osu!direct",
},
},
},
tabStrip = new Box
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Width = 282, //todo: make this actually match the tab control's width instead of hardcoding
Height = 1,
},
Tabs = new DirectTabControl
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
},
},
},
};
Tabs.Current.Value = DirectTab.Search; Tabs.Current.Value = DirectTab.Search;
Tabs.Current.TriggerChange(); Tabs.Current.TriggerChange();
} }
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
tabStrip.Colour = colours.Green;
}
private class DirectTabControl : OsuTabControl<DirectTab>
{
protected override TabItem<DirectTab> CreateTabItem(DirectTab value) => new DirectTabItem(value);
public DirectTabControl()
{
Height = 25;
AccentColour = Color4.White;
}
private class DirectTabItem : OsuTabItem
{
public DirectTabItem(DirectTab value) : base(value)
{
Text.TextSize = 15;
}
}
}
} }
public enum DirectTab public enum DirectTab

View File

@ -7,28 +7,30 @@ using OpenTK;
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.Sprites;
using osu.Framework.Input;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Backgrounds;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Overlays.Direct; using osu.Game.Overlays.Direct;
using osu.Game.Overlays.SearchableList;
using Container = osu.Framework.Graphics.Containers.Container; using OpenTK.Graphics;
namespace osu.Game.Overlays namespace osu.Game.Overlays
{ {
public class DirectOverlay : WaveOverlayContainer public class DirectOverlay : SearchableListOverlay<DirectTab, DirectSortCritera, RankStatus>
{ {
public static readonly int WIDTH_PADDING = 80;
private const float panel_padding = 10f; private const float panel_padding = 10f;
private readonly FilterControl filter;
private readonly FillFlowContainer resultCountsContainer; private readonly FillFlowContainer resultCountsContainer;
private readonly OsuSpriteText resultCountsText; private readonly OsuSpriteText resultCountsText;
private readonly FillFlowContainer<DirectPanel> panels; private readonly FillFlowContainer<DirectPanel> panels;
protected override Color4 BackgroundColour => OsuColour.FromHex(@"485e74");
protected override Color4 TrianglesColourLight => OsuColour.FromHex(@"465b71");
protected override Color4 TrianglesColourDark => OsuColour.FromHex(@"3f5265");
protected override SearchableListHeader<DirectTab> CreateHeader() => new Header();
protected override SearchableListFilterControl<DirectSortCritera, RankStatus> CreateFilterControl() => new FilterControl();
private IEnumerable<BeatmapSetInfo> beatmapSets; private IEnumerable<BeatmapSetInfo> beatmapSets;
public IEnumerable<BeatmapSetInfo> BeatmapSets public IEnumerable<BeatmapSetInfo> BeatmapSets
{ {
@ -38,7 +40,7 @@ namespace osu.Game.Overlays
if (beatmapSets?.Equals(value) ?? false) return; if (beatmapSets?.Equals(value) ?? false) return;
beatmapSets = value; beatmapSets = value;
recreatePanels(filter.DisplayStyle.Value); recreatePanels(Filter.DisplayStyleControl.DisplayStyle.Value);
} }
} }
@ -66,96 +68,39 @@ namespace osu.Game.Overlays
ThirdWaveColour = OsuColour.FromHex(@"005774"); ThirdWaveColour = OsuColour.FromHex(@"005774");
FourthWaveColour = OsuColour.FromHex(@"003a4e"); FourthWaveColour = OsuColour.FromHex(@"003a4e");
Header header; ScrollFlow.Children = new Drawable[]
Children = new Drawable[]
{ {
new Box resultCountsContainer = new FillFlowContainer
{ {
RelativeSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Colour = OsuColour.FromHex(@"485e74"), Direction = FillDirection.Horizontal,
}, Margin = new MarginPadding { Top = 5 },
new Container Children = new Drawable[]
{
RelativeSizeAxes = Axes.Both,
Masking = true,
Children = new[]
{ {
new Triangles new OsuSpriteText
{ {
RelativeSizeAxes = Axes.Both, Text = "Found ",
TriangleScale = 5, TextSize = 15,
ColourLight = OsuColour.FromHex(@"465b71"),
ColourDark = OsuColour.FromHex(@"3f5265"),
}, },
}, resultCountsText = new OsuSpriteText
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = Header.HEIGHT + FilterControl.HEIGHT },
Children = new[]
{
new ScrollContainer
{ {
RelativeSizeAxes = Axes.Both, TextSize = 15,
ScrollbarVisible = false, Font = @"Exo2.0-Bold",
Children = new Drawable[]
{
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
resultCountsContainer = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Margin = new MarginPadding { Left = WIDTH_PADDING, Top = 6 },
Children = new Drawable[]
{
new OsuSpriteText
{
Text = "Found ",
TextSize = 15,
},
resultCountsText = new OsuSpriteText
{
TextSize = 15,
Font = @"Exo2.0-Bold",
},
}
},
panels = new FillFlowContainer<DirectPanel>
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding { Top = panel_padding, Bottom = panel_padding, Left = WIDTH_PADDING, Right = WIDTH_PADDING },
Spacing = new Vector2(panel_padding),
},
},
},
},
}, },
}, }
}, },
filter = new FilterControl panels = new FillFlowContainer<DirectPanel>
{
RelativeSizeAxes = Axes.X,
Margin = new MarginPadding { Top = Header.HEIGHT },
},
header = new Header
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(panel_padding),
Margin = new MarginPadding { Top = 10 },
}, },
}; };
header.Tabs.Current.ValueChanged += tab => { if (tab != DirectTab.Search) filter.Search.Current.Value = string.Empty; }; Header.Tabs.Current.ValueChanged += tab => { if (tab != DirectTab.Search) Filter.Search.Text = string.Empty; };
Filter.Search.Current.ValueChanged += text => { if (text != string.Empty) Header.Tabs.Current.Value = DirectTab.Search; };
filter.Search.Exit = Hide; Filter.DisplayStyleControl.DisplayStyle.ValueChanged += recreatePanels;
filter.Search.Current.ValueChanged += text => { if (text != string.Empty) header.Tabs.Current.Value = DirectTab.Search; };
filter.DisplayStyle.ValueChanged += recreatePanels;
updateResultCounts(); updateResultCounts();
} }
@ -187,30 +132,6 @@ namespace osu.Game.Overlays
panels.Children = BeatmapSets.Select(b => displayStyle == PanelDisplayStyle.Grid ? (DirectPanel)new DirectGridPanel(b) { Width = 400 } : new DirectListPanel(b)); panels.Children = BeatmapSets.Select(b => displayStyle == PanelDisplayStyle.Grid ? (DirectPanel)new DirectGridPanel(b) { Width = 400 } : new DirectListPanel(b));
} }
public override bool AcceptsFocus => true;
protected override bool OnClick(InputState state) => true;
protected override void OnFocus(InputState state)
{
InputManager.ChangeFocus(filter.Search);
base.OnFocus(state);
}
protected override void PopIn()
{
base.PopIn();
filter.Search.HoldFocus = true;
}
protected override void PopOut()
{
base.PopOut();
filter.Search.HoldFocus = false;
}
public class ResultCounts public class ResultCounts
{ {
public readonly int Artists; public readonly int Artists;
@ -224,11 +145,5 @@ namespace osu.Game.Overlays
Tags = tags; Tags = tags;
} }
} }
public enum PanelDisplayStyle
{
Grid,
List,
}
} }
} }

View File

@ -20,7 +20,6 @@ using osu.Framework.Graphics.Cursor;
namespace osu.Game.Overlays.Mods namespace osu.Game.Overlays.Mods
{ {
/// <summary> /// <summary>
/// Represents a clickable button which can cycle through one of more mods. /// Represents a clickable button which can cycle through one of more mods.
/// </summary> /// </summary>
@ -80,7 +79,7 @@ namespace osu.Game.Overlays.Mods
backgroundIcon.RotateTo(-rotate_angle * direction, mod_switch_duration, mod_switch_easing); backgroundIcon.RotateTo(-rotate_angle * direction, mod_switch_duration, mod_switch_easing);
backgroundIcon.Icon = modAfter.Icon; backgroundIcon.Icon = modAfter.Icon;
using (iconsContainer.BeginDelayedSequence(mod_switch_duration, true)) using (BeginDelayedSequence(mod_switch_duration, true))
{ {
foregroundIcon.RotateTo(-rotate_angle * direction); foregroundIcon.RotateTo(-rotate_angle * direction);
foregroundIcon.RotateTo(0f, mod_switch_duration, mod_switch_easing); foregroundIcon.RotateTo(0f, mod_switch_duration, mod_switch_easing);
@ -88,7 +87,7 @@ namespace osu.Game.Overlays.Mods
backgroundIcon.RotateTo(rotate_angle * direction); backgroundIcon.RotateTo(rotate_angle * direction);
backgroundIcon.RotateTo(0f, mod_switch_duration, mod_switch_easing); backgroundIcon.RotateTo(0f, mod_switch_duration, mod_switch_easing);
iconsContainer.Schedule(() => displayMod(modAfter)); Schedule(() => displayMod(modAfter));
} }
} }

View File

@ -137,6 +137,7 @@ namespace osu.Game.Overlays.Music
public bool MatchingFilter public bool MatchingFilter
{ {
get { return matching; }
set set
{ {
if (matching == value) return; if (matching == value) return;
@ -145,10 +146,6 @@ namespace osu.Game.Overlays.Music
FadeTo(matching ? 1 : 0, 200); FadeTo(matching ? 1 : 0, 200);
} }
get
{
return matching;
}
} }
} }
} }

View File

@ -46,19 +46,19 @@ namespace osu.Game.Overlays
Direction = FillDirection.Vertical, Direction = FillDirection.Vertical,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Children = new [] Children = new[]
{ {
new NotificationSection new NotificationSection
{ {
Title = @"Notifications", Title = @"Notifications",
ClearText = @"Clear All", ClearText = @"Clear All",
AcceptTypes = new [] { typeof(SimpleNotification) }, AcceptTypes = new[] { typeof(SimpleNotification) },
}, },
new NotificationSection new NotificationSection
{ {
Title = @"Running Tasks", Title = @"Running Tasks",
ClearText = @"Cancel All", ClearText = @"Cancel All",
AcceptTypes = new [] { typeof(ProgressNotification) }, AcceptTypes = new[] { typeof(ProgressNotification) },
}, },
} }
} }

View File

@ -0,0 +1,102 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
namespace osu.Game.Overlays.SearchableList
{
public class DisplayStyleControl<T> : Container
{
public readonly SlimEnumDropdown<T> Dropdown;
public readonly Bindable<PanelDisplayStyle> DisplayStyle = new Bindable<PanelDisplayStyle>();
public DisplayStyleControl()
{
AutoSizeAxes = Axes.Both;
Children = new[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Spacing = new Vector2(10f, 0f),
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Spacing = new Vector2(5f, 0f),
Direction = FillDirection.Horizontal,
Children = new[]
{
new DisplayStyleToggleButton(FontAwesome.fa_th_large, PanelDisplayStyle.Grid, DisplayStyle),
new DisplayStyleToggleButton(FontAwesome.fa_list_ul, PanelDisplayStyle.List, DisplayStyle),
},
},
Dropdown = new SlimEnumDropdown<T>
{
RelativeSizeAxes = Axes.None,
Width = 160f,
},
},
},
};
DisplayStyle.Value = PanelDisplayStyle.Grid;
}
private class DisplayStyleToggleButton : ClickableContainer
{
private readonly TextAwesome icon;
private readonly PanelDisplayStyle style;
private readonly Bindable<PanelDisplayStyle> bindable;
public DisplayStyleToggleButton(FontAwesome icon, PanelDisplayStyle style, Bindable<PanelDisplayStyle> bindable)
{
this.bindable = bindable;
this.style = style;
Size = new Vector2(25f);
Children = new Drawable[]
{
this.icon = new TextAwesome
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Icon = icon,
TextSize = 18,
UseFullGlyphHeight = false,
Alpha = 0.5f,
},
};
bindable.ValueChanged += Bindable_ValueChanged;
Bindable_ValueChanged(bindable.Value);
Action = () => bindable.Value = this.style;
}
private void Bindable_ValueChanged(PanelDisplayStyle style)
{
icon.FadeTo(style == this.style ? 1.0f : 0.5f, 100);
}
protected override void Dispose(bool isDisposing)
{
bindable.ValueChanged -= Bindable_ValueChanged;
}
}
}
public enum PanelDisplayStyle
{
Grid,
List,
}
}

View File

@ -0,0 +1,28 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK.Graphics;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.SearchableList
{
public class HeaderTabControl<T> : OsuTabControl<T>
{
protected override TabItem<T> CreateTabItem(T value) => new HeaderTabItem(value);
public HeaderTabControl()
{
Height = 26;
AccentColour = Color4.White;
}
private class HeaderTabItem : OsuTabItem
{
public HeaderTabItem(T value) : base(value)
{
Text.TextSize = 16;
}
}
}
}

View File

@ -0,0 +1,136 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.SearchableList
{
public abstract class SearchableListFilterControl<T, U> : Container
{
private const float padding = 10;
private readonly Container filterContainer;
private readonly Box tabStrip;
public readonly SearchTextBox Search;
public readonly PageTabControl<T> Tabs;
public readonly DisplayStyleControl<U> DisplayStyleControl;
protected abstract Color4 BackgroundColour { get; }
protected abstract T DefaultTab { get; }
protected virtual Drawable CreateSupplementaryControls() => null;
protected override bool InternalContains(Vector2 screenSpacePos) => base.InternalContains(screenSpacePos) || DisplayStyleControl.Dropdown.Contains(screenSpacePos);
protected SearchableListFilterControl()
{
if (!typeof(T).IsEnum)
throw new InvalidOperationException("SearchableListFilterControl's sort tabs only support enums as the generic type argument");
RelativeSizeAxes = Axes.X;
var controls = CreateSupplementaryControls();
Container controlsContainer;
Children = new Drawable[]
{
filterContainer = new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = BackgroundColour,
Alpha = 0.9f,
},
tabStrip = new Box
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
Height = 1,
},
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding { Top = padding, Horizontal = SearchableListOverlay.WIDTH_PADDING },
Children = new Drawable[]
{
Search = new FilterSearchTextBox
{
RelativeSizeAxes = Axes.X,
},
controlsContainer = new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Margin = new MarginPadding { Top = controls != null ? padding : 0 },
},
Tabs = new PageTabControl<T>
{
RelativeSizeAxes = Axes.X,
},
new Box //keep the tab strip part of autosize, but don't put it in the flow container
{
RelativeSizeAxes = Axes.X,
Height = 1,
Colour = Color4.White.Opacity(0),
},
},
},
},
},
DisplayStyleControl = new DisplayStyleControl<U>
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
},
};
if (controls != null) controlsContainer.Children = new[] { controls };
Tabs.Current.Value = DefaultTab;
Tabs.Current.TriggerChange();
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
tabStrip.Colour = colours.Yellow;
}
protected override void Update()
{
base.Update();
Height = filterContainer.Height;
DisplayStyleControl.Margin = new MarginPadding { Top = filterContainer.Height - 35, Right = SearchableListOverlay.WIDTH_PADDING };
}
private class FilterSearchTextBox : SearchTextBox
{
protected override Color4 BackgroundUnfocused => backgroundColour;
protected override Color4 BackgroundFocused => backgroundColour;
private Color4 backgroundColour;
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
backgroundColour = colours.Gray2.Opacity(0.9f);
}
}
}
}

View File

@ -0,0 +1,93 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
namespace osu.Game.Overlays.SearchableList
{
public abstract class SearchableListHeader<T> : Container
{
private readonly Box tabStrip;
public readonly HeaderTabControl<T> Tabs;
protected abstract Color4 BackgroundColour { get; }
protected abstract float TabStripWidth { get; } //can be removed once (if?) TabControl support auto sizing
protected abstract T DefaultTab { get; }
protected abstract Drawable CreateHeaderText();
protected abstract FontAwesome Icon { get; }
protected SearchableListHeader()
{
if (!typeof(T).IsEnum)
throw new InvalidOperationException("BrowseHeader only supports enums as the generic type argument");
RelativeSizeAxes = Axes.X;
Height = 90;
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = BackgroundColour,
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = SearchableListOverlay.WIDTH_PADDING, Right = SearchableListOverlay.WIDTH_PADDING },
Children = new Drawable[]
{
new FillFlowContainer
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.BottomLeft,
Position = new Vector2(-35f, 5f),
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(10f, 0f),
Children = new[]
{
new TextAwesome
{
TextSize = 25,
Icon = Icon,
},
CreateHeaderText(),
},
},
tabStrip = new Box
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Width = TabStripWidth,
Height = 1,
},
Tabs = new HeaderTabControl<T>
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
},
},
},
};
Tabs.Current.Value = DefaultTab;
Tabs.Current.TriggerChange();
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
tabStrip.Colour = colours.Green;
}
}
}

View File

@ -0,0 +1,123 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK.Graphics;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input;
using osu.Game.Graphics.Backgrounds;
namespace osu.Game.Overlays.SearchableList
{
public abstract class SearchableListOverlay : WaveOverlayContainer
{
public static readonly float WIDTH_PADDING = 80;
}
public abstract class SearchableListOverlay<T, U, S> : SearchableListOverlay
{
private readonly Container scrollContainer;
protected readonly SearchableListHeader<T> Header;
protected readonly SearchableListFilterControl<U, S> Filter;
protected readonly FillFlowContainer ScrollFlow;
protected abstract Color4 BackgroundColour { get; }
protected abstract Color4 TrianglesColourLight { get; }
protected abstract Color4 TrianglesColourDark { get; }
protected abstract SearchableListHeader<T> CreateHeader();
protected abstract SearchableListFilterControl<U, S> CreateFilterControl();
protected SearchableListOverlay()
{
RelativeSizeAxes = Axes.Both;
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = BackgroundColour,
},
new Container
{
RelativeSizeAxes = Axes.Both,
Masking = true,
Children = new[]
{
new Triangles
{
RelativeSizeAxes = Axes.Both,
TriangleScale = 5,
ColourLight = TrianglesColourLight,
ColourDark = TrianglesColourDark,
},
},
},
scrollContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Children = new[]
{
new ScrollContainer
{
RelativeSizeAxes = Axes.Both,
ScrollbarVisible = false,
Children = new[]
{
ScrollFlow = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding { Horizontal = WIDTH_PADDING, Bottom = 50 },
Direction = FillDirection.Vertical,
},
},
},
},
},
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
AlwaysReceiveInput = true,
Children = new Drawable[]
{
Header = CreateHeader(),
Filter = CreateFilterControl(),
},
},
};
Filter.Search.Exit = Hide;
}
protected override void Update()
{
base.Update();
scrollContainer.Padding = new MarginPadding { Top = Header.Height + Filter.Height };
}
protected override void OnFocus(InputState state)
{
InputManager.ChangeFocus(Filter.Search);
}
protected override void PopIn()
{
base.PopIn();
Filter.Search.HoldFocus = true;
}
protected override void PopOut()
{
base.PopOut();
Filter.Search.HoldFocus = false;
}
}
}

View File

@ -7,12 +7,10 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.Direct namespace osu.Game.Overlays.SearchableList
{ {
public class SlimEnumDropdown<T> : OsuEnumDropdown<T> public class SlimEnumDropdown<T> : OsuEnumDropdown<T>
{ {
public const float HEIGHT = 25;
protected override DropdownHeader CreateHeader() => new SlimDropdownHeader { AccentColour = AccentColour }; protected override DropdownHeader CreateHeader() => new SlimDropdownHeader { AccentColour = AccentColour };
protected override Menu CreateMenu() => new SlimMenu(); protected override Menu CreateMenu() => new SlimMenu();
@ -20,7 +18,7 @@ namespace osu.Game.Overlays.Direct
{ {
public SlimDropdownHeader() public SlimDropdownHeader()
{ {
Height = HEIGHT; Height = 25;
Icon.TextSize = 16; Icon.TextSize = 16;
Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 8, Right = 4 }; Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 8, Right = 4 };
} }

View File

@ -41,4 +41,3 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
} }
} }
} }

View File

@ -39,7 +39,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
set set
{ {
bounding = value; bounding = value;
Invalidate(Invalidation.Geometry); Invalidate(Invalidation.MiscGeometry);
} }
} }

View File

@ -33,4 +33,3 @@ namespace osu.Game.Overlays.Settings.Sections.General
} }
} }
} }

View File

@ -22,4 +22,3 @@ namespace osu.Game.Overlays.Settings.Sections
} }
} }
} }

View File

@ -65,7 +65,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
letterboxSettings.ClearTransforms(); letterboxSettings.ClearTransforms();
letterboxSettings.AutoSizeAxes = isVisible ? Axes.Y : Axes.None; letterboxSettings.AutoSizeAxes = isVisible ? Axes.Y : Axes.None;
if(!isVisible) if (!isVisible)
letterboxSettings.ResizeHeightTo(0, transition_duration, EasingTypes.OutQuint); letterboxSettings.ResizeHeightTo(0, transition_duration, EasingTypes.OutQuint);
}; };
letterboxing.TriggerChange(); letterboxing.TriggerChange();

View File

@ -24,4 +24,3 @@ namespace osu.Game.Overlays.Settings.Sections
} }
} }
} }

View File

@ -22,4 +22,3 @@ namespace osu.Game.Overlays.Settings.Sections
} }
} }
} }

View File

@ -52,4 +52,3 @@ namespace osu.Game.Overlays.Settings
} }
} }
} }

View File

@ -31,7 +31,7 @@ namespace osu.Game.Overlays.Settings
}, },
new SidebarScrollContainer new SidebarScrollContainer
{ {
Children = new [] Children = new[]
{ {
content = new FillFlowContainer content = new FillFlowContainer
{ {

View File

@ -0,0 +1,31 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK.Graphics;
using osu.Framework.Graphics;
using osu.Game.Graphics;
using osu.Game.Overlays.SearchableList;
namespace osu.Game.Overlays.Social
{
public class FilterControl : SearchableListFilterControl<SocialSortCriteria, SortDirection>
{
protected override Color4 BackgroundColour => OsuColour.FromHex(@"47253a");
protected override SocialSortCriteria DefaultTab => SocialSortCriteria.Rank;
public FilterControl()
{
Tabs.Margin = new MarginPadding { Top = 10 };
}
}
public enum SocialSortCriteria
{
Rank,
//Location,
//[Description("Time Zone")]
//TimeZone,
//[Description("World Map")]
//WorldMap,
}
}

View File

@ -0,0 +1,65 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Overlays.SearchableList;
using OpenTK.Graphics;
using osu.Framework.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Framework.Allocation;
using System.ComponentModel;
namespace osu.Game.Overlays.Social
{
public class Header : SearchableListHeader<SocialTab>
{
private OsuSpriteText browser;
protected override Color4 BackgroundColour => OsuColour.FromHex(@"38202e");
protected override float TabStripWidth => 438;
protected override SocialTab DefaultTab => SocialTab.OnlinePlayers;
protected override FontAwesome Icon => FontAwesome.fa_users;
protected override Drawable CreateHeaderText()
{
return new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new[]
{
new OsuSpriteText
{
Text = "social ",
TextSize = 25,
},
browser = new OsuSpriteText
{
Text = "browser",
TextSize = 25,
Font = @"Exo2.0-Light",
},
},
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
browser.Colour = colours.Pink;
}
}
public enum SocialTab
{
[Description("Online Players")]
OnlinePlayers,
//[Description("Online Friends")]
//OnlineFriends,
//[Description("Online Team Members")]
//OnlineTeamMembers,
//[Description("Chat Channels")]
//ChatChannels,
}
}

View File

@ -0,0 +1,109 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Overlays.SearchableList;
using osu.Game.Overlays.Social;
using osu.Game.Users;
namespace osu.Game.Overlays
{
public class SocialOverlay : SearchableListOverlay<SocialTab, SocialSortCriteria, SortDirection>, IOnlineComponent
{
private readonly FillFlowContainer<UserPanel> panelFlow;
protected override Color4 BackgroundColour => OsuColour.FromHex(@"60284b");
protected override Color4 TrianglesColourLight => OsuColour.FromHex(@"672b51");
protected override Color4 TrianglesColourDark => OsuColour.FromHex(@"5c2648");
protected override SearchableListHeader<SocialTab> CreateHeader() => new Header();
protected override SearchableListFilterControl<SocialSortCriteria, SortDirection> CreateFilterControl() => new FilterControl();
private IEnumerable<User> users;
public IEnumerable<User> Users
{
get { return users; }
set
{
if (users?.Equals(value) ?? false) return;
users = value;
if (users == null)
panelFlow.Clear();
else
{
panelFlow.Children = users.Select(u =>
{
var p = new UserPanel(u) { Width = 300 };
p.Status.BindTo(u.Status);
return p;
});
}
}
}
public SocialOverlay()
{
FirstWaveColour = OsuColour.FromHex(@"cb5fa0");
SecondWaveColour = OsuColour.FromHex(@"b04384");
ThirdWaveColour = OsuColour.FromHex(@"9b2b6e");
FourthWaveColour = OsuColour.FromHex(@"6d214d");
ScrollFlow.Children = new[]
{
panelFlow = new FillFlowContainer<UserPanel>
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Margin = new MarginPadding { Top = 20 },
Spacing = new Vector2(10f),
},
};
}
[BackgroundDependencyLoader]
private void load(APIAccess api)
{
if (Users == null)
reloadUsers(api);
}
private void reloadUsers(APIAccess api)
{
Users = null;
// no this is not the correct data source, but it's something.
var request = new GetUsersRequest();
request.Success += res => Users = res.Select(e => e.User);
api.Queue(request);
}
public void APIStateChanged(APIAccess api, APIState state)
{
switch (state)
{
case APIState.Online:
reloadUsers(api);
break;
default:
Users = null;
break;
}
}
}
public enum SortDirection
{
Descending,
Ascending,
}
}

View File

@ -63,6 +63,7 @@ namespace osu.Game.Overlays.Toolbar
AutoSizeAxes = Axes.X, AutoSizeAxes = Axes.X,
Children = new Drawable[] Children = new Drawable[]
{ {
new ToolbarSocialButton(),
new ToolbarChatButton(), new ToolbarChatButton(),
new ToolbarMusicButton(), new ToolbarMusicButton(),
new ToolbarButton new ToolbarButton

View File

@ -53,7 +53,7 @@ namespace osu.Game.Overlays.Toolbar
Radius = 15, Radius = 15,
Roundness = 15, Roundness = 15,
}, },
Children = new [] Children = new[]
{ {
new Box new Box
{ {

View File

@ -0,0 +1,22 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Game.Graphics;
namespace osu.Game.Overlays.Toolbar
{
internal class ToolbarSocialButton : ToolbarOverlayToggleButton
{
public ToolbarSocialButton()
{
Icon = FontAwesome.fa_users;
}
[BackgroundDependencyLoader]
private void load(SocialOverlay chat)
{
StateContainer = chat;
}
}
}

View File

@ -25,13 +25,13 @@ namespace osu.Game.Screens.Edit
protected override void OnEntering(Screen last) protected override void OnEntering(Screen last)
{ {
base.OnEntering(last); base.OnEntering(last);
Background.Schedule(() => Background.FadeColour(Color4.DarkGray, 500)); Background.FadeColour(Color4.DarkGray, 500);
Beatmap?.Track?.Stop(); Beatmap?.Track?.Stop();
} }
protected override bool OnExiting(Screen next) protected override bool OnExiting(Screen next)
{ {
Background.Schedule(() => Background.FadeColour(Color4.White, 500)); Background.FadeColour(Color4.White, 500);
Beatmap?.Track?.Start(); Beatmap?.Track?.Start();
return base.OnExiting(next); return base.OnExiting(next);
} }

View File

@ -66,7 +66,7 @@ namespace osu.Game.Screens.Menu
Scale = new Vector2(0, 1), Scale = new Vector2(0, 1),
Size = boxSize, Size = boxSize,
Shear = new Vector2(ButtonSystem.WEDGE_WIDTH / boxSize.Y, 0), Shear = new Vector2(ButtonSystem.WEDGE_WIDTH / boxSize.Y, 0),
Children = new [] Children = new[]
{ {
new Box new Box
{ {
@ -283,9 +283,9 @@ namespace osu.Game.Screens.Menu
public ButtonState State public ButtonState State
{ {
get { return state; } get { return state; }
set set
{ {
if (state == value) if (state == value)
return; return;

View File

@ -192,10 +192,8 @@ namespace osu.Game.Screens.Menu
public MenuState State public MenuState State
{ {
get get { return state; }
{
return state;
}
set set
{ {
if (state == value) return; if (state == value) return;

View File

@ -51,10 +51,7 @@ namespace osu.Game.Screens.Menu
public bool Triangles public bool Triangles
{ {
set set { colourAndTriangles.Alpha = value ? 1 : 0; }
{
colourAndTriangles.Alpha = value ? 1 : 0;
}
} }
protected override bool InternalContains(Vector2 screenSpacePos) => logoContainer.Contains(screenSpacePos); protected override bool InternalContains(Vector2 screenSpacePos) => logoContainer.Contains(screenSpacePos);
@ -62,10 +59,7 @@ namespace osu.Game.Screens.Menu
public bool Ripple public bool Ripple
{ {
get { return rippleContainer.Alpha > 0; } get { return rippleContainer.Alpha > 0; }
set set { rippleContainer.Alpha = value ? 1 : 0; }
{
rippleContainer.Alpha = value ? 1 : 0;
}
} }
public bool Interactive = true; public bool Interactive = true;

View File

@ -68,7 +68,7 @@ namespace osu.Game.Screens.Multiplayer
}, },
avatar = new UpdateableAvatar avatar = new UpdateableAvatar
{ {
Size = new Vector2(Height - content_padding* 2), Size = new Vector2(Height - content_padding * 2),
Masking = true, Masking = true,
CornerRadius = 5f, CornerRadius = 5f,
Margin = new MarginPadding { Left = content_padding * 2, Top = content_padding }, Margin = new MarginPadding { Left = content_padding * 2, Top = content_padding },

View File

@ -24,12 +24,12 @@ namespace osu.Game.Screens.Multiplayer
{ {
base.OnEntering(last); base.OnEntering(last);
Background.Schedule(() => Background.FadeColour(Color4.DarkGray, 500)); Background.FadeColour(Color4.DarkGray, 500);
} }
protected override bool OnExiting(Screen next) protected override bool OnExiting(Screen next)
{ {
Background.Schedule(() => Background.FadeColour(Color4.White, 500)); Background.FadeColour(Color4.White, 500);
return base.OnExiting(next); return base.OnExiting(next);
} }
} }

View File

@ -54,7 +54,6 @@ namespace osu.Game.Screens.Play.HUD
{ {
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Scale = new Vector2(0.6f), Scale = new Vector2(0.6f),
}); });
} }

View File

@ -278,7 +278,6 @@ namespace osu.Game.Screens.Play
{ {
if (!pauseContainer.IsPaused) if (!pauseContainer.IsPaused)
decoupledClock.Start(); decoupledClock.Start();
}); });
pauseContainer.Alpha = 0; pauseContainer.Alpha = 0;

View File

@ -79,7 +79,7 @@ namespace osu.Game.Screens.Play.ReplaySettings
{ {
Origin = Anchor.Centre, Origin = Anchor.Centre,
Anchor = Anchor.CentreRight, Anchor = Anchor.CentreRight,
Position = new Vector2(-15,0), Position = new Vector2(-15, 0),
Icon = FontAwesome.fa_bars, Icon = FontAwesome.fa_bars,
Scale = new Vector2(0.75f), Scale = new Vector2(0.75f),
Action = toggleContentVisibility, Action = toggleContentVisibility,

View File

@ -232,7 +232,7 @@ namespace osu.Game.Screens.Play
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Direction = FillDirection.Horizontal, Direction = FillDirection.Horizontal,
Children = new [] Children = new[]
{ {
new TextAwesome { Icon = FontAwesome.fa_chevron_right }, new TextAwesome { Icon = FontAwesome.fa_chevron_right },
new TextAwesome { Icon = FontAwesome.fa_chevron_right }, new TextAwesome { Icon = FontAwesome.fa_chevron_right },

View File

@ -146,7 +146,7 @@ namespace osu.Game.Screens.Play
double progress = ((audioClock?.CurrentTime ?? Time.Current) - firstHitTime) / (lastHitTime - firstHitTime); double progress = ((audioClock?.CurrentTime ?? Time.Current) - firstHitTime) / (lastHitTime - firstHitTime);
if(progress < 1) if (progress < 1)
{ {
bar.UpdatePosition((float)progress); bar.UpdatePosition((float)progress);
graph.Progress = (int)(graph.ColumnCount * progress); graph.Progress = (int)(graph.ColumnCount * progress);

View File

@ -34,7 +34,7 @@ namespace osu.Game.Screens.Play
{ {
IHasEndTime end = h as IHasEndTime; IHasEndTime end = h as IHasEndTime;
int startRange = (int)((h.StartTime - firstHit)/ interval); int startRange = (int)((h.StartTime - firstHit) / interval);
int endRange = (int)(((end?.EndTime > 0 ? end.EndTime : h.StartTime) - firstHit) / interval); int endRange = (int)(((end?.EndTime > 0 ? end.EndTime : h.StartTime) - firstHit) / interval);
for (int i = startRange; i <= endRange; i++) for (int i = startRange; i <= endRange; i++)
values[i]++; values[i]++;

View File

@ -69,7 +69,7 @@ namespace osu.Game.Screens.Play
public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true)
{ {
if ((invalidation & Invalidation.SizeInParentSpace) > 0) if ((invalidation & Invalidation.DrawSize) > 0)
layout.Invalidate(); layout.Invalidate();
return base.Invalidate(invalidation, source, shallPropagate); return base.Invalidate(invalidation, source, shallPropagate);
} }

View File

@ -71,7 +71,6 @@ namespace osu.Game.Screens.Ranking
using (BeginDelayedSequence(transition_time * 0.25f, true)) using (BeginDelayedSequence(transition_time * 0.25f, true))
{ {
circleOuter.ScaleTo(1, transition_time, EasingTypes.OutQuint); circleOuter.ScaleTo(1, transition_time, EasingTypes.OutQuint);
circleOuter.FadeTo(1, transition_time, EasingTypes.OutQuint); circleOuter.FadeTo(1, transition_time, EasingTypes.OutQuint);

View File

@ -64,7 +64,8 @@ namespace osu.Game.Screens.Ranking
Origin = Anchor.Centre, Origin = Anchor.Centre,
Children = new Drawable[] Children = new Drawable[]
{ {
new Box{ new Box
{
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Alpha = 0, Alpha = 0,
AlwaysPresent = true AlwaysPresent = true
@ -87,6 +88,5 @@ namespace osu.Game.Screens.Ranking
} }
}); });
} }
} }
} }

View File

@ -244,7 +244,7 @@ namespace osu.Game.Screens.Select
private BeatmapGroup createGroup(BeatmapSetInfo beatmapSet) private BeatmapGroup createGroup(BeatmapSetInfo beatmapSet)
{ {
foreach(var b in beatmapSet.Beatmaps) foreach (var b in beatmapSet.Beatmaps)
{ {
if (b.Metadata == null) if (b.Metadata == null)
b.Metadata = beatmapSet.Metadata; b.Metadata = beatmapSet.Metadata;

View File

@ -47,6 +47,7 @@ namespace osu.Game.Screens.Select
public BeatmapInfo Beatmap public BeatmapInfo Beatmap
{ {
get { return beatmap; } get { return beatmap; }
set set
{ {
if (beatmap == value) return; if (beatmap == value) return;
@ -165,7 +166,7 @@ namespace osu.Game.Screens.Select
Direction = FillDirection.Vertical, Direction = FillDirection.Vertical,
LayoutDuration = 200, LayoutDuration = 200,
LayoutEasing = EasingTypes.OutQuint, LayoutEasing = EasingTypes.OutQuint,
Children = new [] Children = new[]
{ {
description = new MetadataSegment("Description"), description = new MetadataSegment("Description"),
source = new MetadataSegment("Source"), source = new MetadataSegment("Source"),
@ -199,9 +200,9 @@ namespace osu.Game.Screens.Select
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical, Direction = FillDirection.Vertical,
Spacing = new Vector2(0,5), Spacing = new Vector2(0, 5),
Padding = new MarginPadding(10), Padding = new MarginPadding(10),
Children = new [] Children = new[]
{ {
circleSize = new DifficultyRow("Circle Size", 7), circleSize = new DifficultyRow("Circle Size", 7),
drainRate = new DifficultyRow("HP Drain"), drainRate = new DifficultyRow("HP Drain"),
@ -479,7 +480,7 @@ namespace osu.Game.Screens.Select
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
Direction = FillDirection.Full, Direction = FillDirection.Full,
Spacing = new Vector2(5,0), Spacing = new Vector2(5, 0),
Margin = new MarginPadding { Top = header.TextSize } Margin = new MarginPadding { Top = header.TextSize }
} }
}; };

View File

@ -335,7 +335,6 @@ namespace osu.Game.Screens.Tournament
{ {
Logger.Error(ex, "Failed to read last drawings results."); Logger.Error(ex, "Failed to read last drawings results.");
} }
} }
else else
{ {

View File

@ -83,6 +83,7 @@ namespace osu.Game.Screens.Tournament
private ScrollState scrollState private ScrollState scrollState
{ {
get { return _scrollState; } get { return _scrollState; }
set set
{ {
if (_scrollState == value) if (_scrollState == value)
@ -329,6 +330,7 @@ namespace osu.Game.Screens.Tournament
public bool Selected public bool Selected
{ {
get { return selected; } get { return selected; }
set set
{ {
selected = value; selected = value;

View File

@ -48,7 +48,7 @@ namespace osu.Game.Users
public override string Message => @"Solo Game"; public override string Message => @"Solo Game";
} }
public class UserStatusMultiplayerGame: UserStatusBusy public class UserStatusMultiplayerGame : UserStatusBusy
{ {
public override string Message => @"Multiplaying"; public override string Message => @"Multiplaying";
} }

View File

@ -43,7 +43,7 @@
<HintPath>$(SolutionDir)\packages\ppy.OpenTK.2.0.50727.1341\lib\net45\OpenTK.dll</HintPath> <HintPath>$(SolutionDir)\packages\ppy.OpenTK.2.0.50727.1341\lib\net45\OpenTK.dll</HintPath>
</Reference> </Reference>
<Reference Include="SharpCompress, Version=0.15.2.0, Culture=neutral, PublicKeyToken=afb0a02973931d96, processorArchitecture=MSIL"> <Reference Include="SharpCompress, Version=0.15.2.0, Culture=neutral, PublicKeyToken=afb0a02973931d96, processorArchitecture=MSIL">
<HintPath>$(SolutionDir)\packages\SharpCompress.0.15.2\lib\net45\SharpCompress.dll</HintPath> <HintPath>$(SolutionDir)\packages\sharpcompress.0.15.2\lib\net45\SharpCompress.dll</HintPath>
</Reference> </Reference>
<Reference Include="SQLite.Net, Version=3.1.0.0, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="SQLite.Net, Version=3.1.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>$(SolutionDir)\packages\SQLite.Net.Core-PCL.3.1.1\lib\portable-win8+net45+wp8+wpa81+MonoAndroid1+MonoTouch1\SQLite.Net.dll</HintPath> <HintPath>$(SolutionDir)\packages\SQLite.Net.Core-PCL.3.1.1\lib\portable-win8+net45+wp8+wpa81+MonoAndroid1+MonoTouch1\SQLite.Net.dll</HintPath>
@ -76,6 +76,7 @@
<Compile Include="Beatmaps\DifficultyCalculator.cs" /> <Compile Include="Beatmaps\DifficultyCalculator.cs" />
<Compile Include="Graphics\UserInterface\IconButton.cs" /> <Compile Include="Graphics\UserInterface\IconButton.cs" />
<Compile Include="Configuration\SelectionRandomType.cs" /> <Compile Include="Configuration\SelectionRandomType.cs" />
<Compile Include="Online\API\Requests\GetUsersRequest.cs" />
<Compile Include="Online\API\Requests\PostMessageRequest.cs" /> <Compile Include="Online\API\Requests\PostMessageRequest.cs" />
<Compile Include="Online\Chat\ErrorMessage.cs" /> <Compile Include="Online\Chat\ErrorMessage.cs" />
<Compile Include="Overlays\Chat\ChatTabControl.cs" /> <Compile Include="Overlays\Chat\ChatTabControl.cs" />
@ -88,6 +89,7 @@
<Compile Include="Overlays\Settings\SettingsHeader.cs" /> <Compile Include="Overlays\Settings\SettingsHeader.cs" />
<Compile Include="Overlays\Settings\Sections\Audio\MainMenuSettings.cs" /> <Compile Include="Overlays\Settings\Sections\Audio\MainMenuSettings.cs" />
<Compile Include="Overlays\Toolbar\ToolbarChatButton.cs" /> <Compile Include="Overlays\Toolbar\ToolbarChatButton.cs" />
<Compile Include="Overlays\Toolbar\ToolbarSocialButton.cs" />
<Compile Include="Rulesets\Beatmaps\BeatmapConverter.cs" /> <Compile Include="Rulesets\Beatmaps\BeatmapConverter.cs" />
<Compile Include="Rulesets\Beatmaps\BeatmapProcessor.cs" /> <Compile Include="Rulesets\Beatmaps\BeatmapProcessor.cs" />
<Compile Include="Beatmaps\ControlPoints\ControlPoint.cs" /> <Compile Include="Beatmaps\ControlPoints\ControlPoint.cs" />
@ -457,16 +459,24 @@
<Compile Include="Overlays\DirectOverlay.cs" /> <Compile Include="Overlays\DirectOverlay.cs" />
<Compile Include="Overlays\Direct\FilterControl.cs" /> <Compile Include="Overlays\Direct\FilterControl.cs" />
<Compile Include="Overlays\Direct\Header.cs" /> <Compile Include="Overlays\Direct\Header.cs" />
<Compile Include="Overlays\Direct\SortTabControl.cs" />
<Compile Include="Graphics\UserInterface\OsuEnumDropdown.cs" /> <Compile Include="Graphics\UserInterface\OsuEnumDropdown.cs" />
<Compile Include="Overlays\Direct\DirectPanel.cs" /> <Compile Include="Overlays\Direct\DirectPanel.cs" />
<Compile Include="Overlays\Direct\DirectGridPanel.cs" /> <Compile Include="Overlays\Direct\DirectGridPanel.cs" />
<Compile Include="Overlays\Direct\DirectListPanel.cs" /> <Compile Include="Overlays\Direct\DirectListPanel.cs" />
<Compile Include="Database\OnlineWorkingBeatmap.cs" /> <Compile Include="Database\OnlineWorkingBeatmap.cs" />
<Compile Include="Database\BeatmapOnlineInfo.cs" /> <Compile Include="Database\BeatmapOnlineInfo.cs" />
<Compile Include="Overlays\Direct\SlimEnumDropdown.cs" />
<Compile Include="Graphics\Containers\ReverseDepthFillFlowContainer.cs" /> <Compile Include="Graphics\Containers\ReverseDepthFillFlowContainer.cs" />
<Compile Include="Database\RankStatus.cs" /> <Compile Include="Database\RankStatus.cs" />
<Compile Include="Overlays\SearchableList\SearchableListHeader.cs" />
<Compile Include="Overlays\SearchableList\HeaderTabControl.cs" />
<Compile Include="Overlays\Social\FilterControl.cs" />
<Compile Include="Overlays\Social\Header.cs" />
<Compile Include="Overlays\SearchableList\SearchableListFilterControl.cs" />
<Compile Include="Overlays\SearchableList\SearchableListOverlay.cs" />
<Compile Include="Graphics\UserInterface\PageTabControl.cs" />
<Compile Include="Overlays\SocialOverlay.cs" />
<Compile Include="Overlays\SearchableList\SlimEnumDropdown.cs" />
<Compile Include="Overlays\SearchableList\DisplayStyleControl.cs" />
<Compile Include="Graphics\UserInterface\BreadcrumbControl.cs" /> <Compile Include="Graphics\UserInterface\BreadcrumbControl.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>