1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-13 19:52:55 +08:00

Merge branch 'master' into userpage

This commit is contained in:
Huo Yaoyuan 2017-06-05 21:14:46 +08:00
commit 8fa076b5d7
105 changed files with 2782 additions and 820 deletions

@ -1 +1 @@
Subproject commit 8baad1b9484b9f35724e2f965c18cfe710907d80 Subproject commit 925bbe42bab95078b9d33189205b5b1b76bf8e01

@ -1 +1 @@
Subproject commit 9f46a456dc3a56dcbff09671a3f588b16a464106 Subproject commit a5199500cc3ba96101fd858e0f78f36e538697b1

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.Framework.Testing;
using osu.Game.Graphics.UserInterface;
using osu.Framework.Graphics;
namespace osu.Desktop.VisualTests.Tests
{
internal class TestCaseBreadcrumbs : TestCase
{
public override string Description => @"breadcrumb > control";
public override void Reset()
{
base.Reset();
BreadcrumbControl<BreadcrumbTab> c;
Add(c = new BreadcrumbControl<BreadcrumbTab>
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X,
Width = 0.5f,
});
AddStep(@"first", () => c.Current.Value = BreadcrumbTab.Click);
AddStep(@"second", () => c.Current.Value = BreadcrumbTab.The);
AddStep(@"third", () => c.Current.Value = BreadcrumbTab.Circles);
}
private enum BreadcrumbTab
{
Click,
The,
Circles,
}
}
}

View File

@ -11,6 +11,9 @@ using OpenTK;
using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Timing; using osu.Game.Rulesets.Mania.Timing;
using osu.Framework.Configuration;
using OpenTK.Input;
using osu.Framework.Timing;
namespace osu.Desktop.VisualTests.Tests namespace osu.Desktop.VisualTests.Tests
{ {
@ -59,6 +62,51 @@ namespace osu.Desktop.VisualTests.Tests
} }
}; };
Action createPlayfieldWithNotesAcceptingInput = () =>
{
Clear();
var rateAdjustClock = new StopwatchClock(true) { Rate = 0.5 };
ManiaPlayfield playField;
Add(playField = new ManiaPlayfield(4, new List<TimingChange> { new TimingChange { BeatLength = 200 } })
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Scale = new Vector2(1, -1),
Clock = new FramedClock(rateAdjustClock)
});
for (int t = 1000; t <= 2000; t += 100)
{
playField.Add(new DrawableNote(new Note
{
StartTime = t,
Column = 0
}, new Bindable<Key>(Key.D)));
playField.Add(new DrawableNote(new Note
{
StartTime = t,
Column = 3
}, new Bindable<Key>(Key.K)));
}
playField.Add(new DrawableHoldNote(new HoldNote
{
StartTime = 1000,
Duration = 1000,
Column = 1
}, new Bindable<Key>(Key.F)));
playField.Add(new DrawableHoldNote(new HoldNote
{
StartTime = 1000,
Duration = 1000,
Column = 2
}, new Bindable<Key>(Key.J)));
};
AddStep("1 column", () => createPlayfield(1, SpecialColumnPosition.Normal)); AddStep("1 column", () => createPlayfield(1, SpecialColumnPosition.Normal));
AddStep("4 columns", () => createPlayfield(4, SpecialColumnPosition.Normal)); AddStep("4 columns", () => createPlayfield(4, SpecialColumnPosition.Normal));
AddStep("Left special style", () => createPlayfield(4, SpecialColumnPosition.Left)); AddStep("Left special style", () => createPlayfield(4, SpecialColumnPosition.Left));
@ -76,11 +124,13 @@ namespace osu.Desktop.VisualTests.Tests
AddWaitStep(10); AddWaitStep(10);
AddStep("Right special style", () => createPlayfieldWithNotes(4, SpecialColumnPosition.Right)); AddStep("Right special style", () => createPlayfieldWithNotes(4, SpecialColumnPosition.Right));
AddWaitStep(10); AddWaitStep(10);
AddStep("Notes with input", () => createPlayfieldWithNotesAcceptingInput());
} }
private void triggerKeyDown(Column column) private void triggerKeyDown(Column column)
{ {
column.TriggerKeyDown(new InputState(), new KeyDownEventArgs column.TriggerOnKeyDown(new InputState(), new KeyDownEventArgs
{ {
Key = column.Key, Key = column.Key,
Repeat = false Repeat = false
@ -89,7 +139,7 @@ namespace osu.Desktop.VisualTests.Tests
private void triggerKeyUp(Column column) private void triggerKeyUp(Column column)
{ {
column.TriggerKeyUp(new InputState(), new KeyUpEventArgs column.TriggerOnKeyUp(new InputState(), new KeyUpEventArgs
{ {
Key = column.Key Key = column.Key
}); });

View File

@ -0,0 +1,55 @@
// 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.Graphics;
using osu.Framework.Testing;
using osu.Game.Graphics.UserInterface;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.ReplaySettings;
namespace osu.Desktop.VisualTests.Tests
{
internal class TestCaseReplaySettingsOverlay : TestCase
{
public override string Description => @"Settings visible in replay/auto";
private ExampleContainer container;
public override void Reset()
{
base.Reset();
Add(new ReplaySettingsOverlay()
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
});
Add(container = new ExampleContainer());
AddStep(@"Add button", () => container.Add(new OsuButton
{
RelativeSizeAxes = Axes.X,
Text = @"Button",
}));
AddStep(@"Add checkbox", () => container.Add(new ReplayCheckbox
{
LabelText = "Checkbox",
}));
AddStep(@"Add textbox", () => container.Add(new FocusedTextBox
{
RelativeSizeAxes = Axes.X,
Height = 30,
PlaceholderText = "Textbox",
HoldFocus = false,
}));
}
private class ExampleContainer : ReplayGroup
{
protected override string Title => @"example";
}
}
}

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 osu.Framework.Testing;
using osu.Game.Screens.Play;
namespace osu.Desktop.VisualTests.Tests
{
internal class TestCaseSkipButton : TestCase
{
public override string Description => @"Skip skip skippediskip";
public override void Reset()
{
base.Reset();
Add(new SkipButton(Clock.CurrentTime + 5000));
}
}
}

View File

@ -1,92 +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.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Framework.Configuration;
using OpenTK;
using osu.Game.Graphics;
namespace osu.Desktop.VisualTests.Tests
{
internal class TestCaseTooltip : TestCase
{
public override string Description => "tests tooltips on various elements";
public override void Reset()
{
base.Reset();
OsuSliderBar<int> slider;
OsuSliderBar<double> sliderDouble;
const float width = 400;
Children = new Drawable[]
{
new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 10),
Children = new Drawable[]
{
new TooltipTextContainer("text with a tooltip"),
new TooltipTextContainer("more text with another tooltip"),
new TooltipTextbox
{
Text = "a textbox with a tooltip",
Size = new Vector2(width,30),
},
slider = new OsuSliderBar<int>
{
Width = width,
},
sliderDouble = new OsuSliderBar<double>
{
Width = width,
},
},
},
};
slider.Current.BindTo(new BindableInt(5)
{
MaxValue = 10,
MinValue = 0
});
sliderDouble.Current.BindTo(new BindableDouble(0.5)
{
MaxValue = 1,
MinValue = 0
});
}
private class TooltipTextContainer : Container, IHasTooltip
{
private readonly OsuSpriteText text;
public string TooltipText => text.Text;
public TooltipTextContainer(string tooltipText)
{
AutoSizeAxes = Axes.Both;
Children = new[]
{
text = new OsuSpriteText
{
Text = tooltipText,
}
};
}
}
private class TooltipTextbox : OsuTextBox, IHasTooltip
{
public string TooltipText => Text;
}
}
}

View File

@ -3,20 +3,18 @@
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Screens.Play;
namespace osu.Desktop.VisualTests.Tests namespace osu.Desktop.VisualTests.Tests
{ {
internal class TestCaseTwoLayerButton : TestCase internal class TestCaseTwoLayerButton : TestCase
{ {
public override string Description => @"Back and skip and what not"; public override string Description => @"Mostly back button";
public override void Reset() public override void Reset()
{ {
base.Reset(); base.Reset();
Add(new BackButton()); Add(new BackButton());
Add(new SkipButton(Clock.CurrentTime + 5000));
} }
} }
} }

View File

@ -32,14 +32,14 @@ namespace osu.Desktop.VisualTests.Tests
Username = @"flyte", Username = @"flyte",
Id = 3103765, Id = 3103765,
Country = new Country { FlagName = @"JP" }, Country = new Country { FlagName = @"JP" },
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/3103765/5b012e13611d5761caa7e24fecb3d3a16e1cf48fc2a3032cfd43dd444af83d82.jpeg" CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg"
}) { Width = 300 }, }) { Width = 300 },
peppy = new UserPanel(new User peppy = new UserPanel(new User
{ {
Username = @"peppy", Username = @"peppy",
Id = 2, Id = 2,
Country = new Country { FlagName = @"AU" }, Country = new Country { FlagName = @"AU" },
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/2/08cad88747c235a64fca5f1b770e100f120827ded1ffe3b66bfcd19c940afa65.jpeg" CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg"
}) { Width = 300 }, }) { Width = 300 },
}, },
}); });

View File

@ -196,6 +196,7 @@
<Compile Include="Tests\TestCaseMusicController.cs" /> <Compile Include="Tests\TestCaseMusicController.cs" />
<Compile Include="Tests\TestCaseNotificationManager.cs" /> <Compile Include="Tests\TestCaseNotificationManager.cs" />
<Compile Include="Tests\TestCaseOnScreenDisplay.cs" /> <Compile Include="Tests\TestCaseOnScreenDisplay.cs" />
<Compile Include="Tests\TestCaseReplaySettingsOverlay.cs" />
<Compile Include="Tests\TestCasePlayer.cs" /> <Compile Include="Tests\TestCasePlayer.cs" />
<Compile Include="Tests\TestCaseHitObjects.cs" /> <Compile Include="Tests\TestCaseHitObjects.cs" />
<Compile Include="Tests\TestCaseKeyCounter.cs" /> <Compile Include="Tests\TestCaseKeyCounter.cs" />
@ -203,12 +204,12 @@
<Compile Include="Tests\TestCaseReplay.cs" /> <Compile Include="Tests\TestCaseReplay.cs" />
<Compile Include="Tests\TestCaseResults.cs" /> <Compile Include="Tests\TestCaseResults.cs" />
<Compile Include="Tests\TestCaseScoreCounter.cs" /> <Compile Include="Tests\TestCaseScoreCounter.cs" />
<Compile Include="Tests\TestCaseSkipButton.cs" />
<Compile Include="Tests\TestCaseTabControl.cs" /> <Compile Include="Tests\TestCaseTabControl.cs" />
<Compile Include="Tests\TestCaseTaikoHitObjects.cs" /> <Compile Include="Tests\TestCaseTaikoHitObjects.cs" />
<Compile Include="Tests\TestCaseTaikoPlayfield.cs" /> <Compile Include="Tests\TestCaseTaikoPlayfield.cs" />
<Compile Include="Tests\TestCaseTextAwesome.cs" /> <Compile Include="Tests\TestCaseTextAwesome.cs" />
<Compile Include="Tests\TestCasePlaySongSelect.cs" /> <Compile Include="Tests\TestCasePlaySongSelect.cs" />
<Compile Include="Tests\TestCaseTooltip.cs" />
<Compile Include="Tests\TestCaseTwoLayerButton.cs" /> <Compile Include="Tests\TestCaseTwoLayerButton.cs" />
<Compile Include="Tests\TestCaseUserProfile.cs" /> <Compile Include="Tests\TestCaseUserProfile.cs" />
<Compile Include="VisualTestGame.cs" /> <Compile Include="VisualTestGame.cs" />
@ -224,6 +225,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\TestCaseBreadcrumbs.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup /> <ItemGroup />
<ItemGroup /> <ItemGroup />

View File

@ -28,7 +28,14 @@ namespace osu.Game.Rulesets.Catch
{ {
new CatchModEasy(), new CatchModEasy(),
new CatchModNoFail(), new CatchModNoFail(),
new CatchModHalfTime(), new MultiMod
{
Mods = new Mod[]
{
new CatchModHalfTime(),
new CatchModDaycore(),
},
},
}; };
case ModType.DifficultyIncrease: case ModType.DifficultyIncrease:

View File

@ -32,6 +32,11 @@ namespace osu.Game.Rulesets.Catch.Mods
} }
public class CatchModDaycore : ModDaycore
{
public override double ScoreMultiplier => 0.5;
}
public class CatchModDoubleTime : ModDoubleTime public class CatchModDoubleTime : ModDoubleTime
{ {
public override double ScoreMultiplier => 1.06; public override double ScoreMultiplier => 1.06;

View File

@ -13,6 +13,7 @@ using osu.Game.Rulesets.Mania.MathUtils;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy; using osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy;
using OpenTK; using OpenTK;
using osu.Game.Audio;
namespace osu.Game.Rulesets.Mania.Beatmaps namespace osu.Game.Rulesets.Mania.Beatmaps
{ {
@ -161,9 +162,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
pattern.Add(new HoldNote pattern.Add(new HoldNote
{ {
StartTime = HitObject.StartTime, StartTime = HitObject.StartTime,
Samples = HitObject.Samples,
Duration = endTimeData.Duration, Duration = endTimeData.Duration,
Column = column, Column = column,
Head = { Samples = sampleInfoListAt(HitObject.StartTime) },
Tail = { Samples = sampleInfoListAt(endTimeData.EndTime) },
}); });
} }
else if (positionData != null) else if (positionData != null)
@ -178,6 +180,24 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
return pattern; return pattern;
} }
/// <summary>
/// Retrieves the sample info list at a point in time.
/// </summary>
/// <param name="time">The time to retrieve the sample info list from.</param>
/// <returns></returns>
private SampleInfoList sampleInfoListAt(double time)
{
var curveData = HitObject as IHasCurve;
if (curveData == null)
return HitObject.Samples;
double segmentTime = (curveData.EndTime - HitObject.StartTime) / curveData.RepeatCount;
int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime);
return curveData.RepeatSamples[index];
}
} }
} }
} }

View File

@ -471,14 +471,17 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
} }
else else
{ {
newObject = new HoldNote var holdNote = new HoldNote
{ {
StartTime = startTime, StartTime = startTime,
Samples = sampleInfoListAt(startTime),
EndSamples = sampleInfoListAt(endTime),
Column = column, Column = column,
Duration = endTime - startTime Duration = endTime - startTime,
Head = { Samples = sampleInfoListAt(startTime) },
Tail = { Samples = sampleInfoListAt(endTime) }
}; };
newObject = holdNote;
} }
pattern.Add(newObject); pattern.Add(newObject);

View File

@ -69,18 +69,21 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (holdNote) if (holdNote)
{ {
newObject = new HoldNote var hold = new HoldNote
{ {
StartTime = HitObject.StartTime, StartTime = HitObject.StartTime,
EndSamples = HitObject.Samples,
Column = column, Column = column,
Duration = endTime - HitObject.StartTime Duration = endTime - HitObject.StartTime
}; };
newObject.Samples.Add(new SampleInfo hold.Head.Samples.Add(new SampleInfo
{ {
Name = SampleInfo.HIT_NORMAL Name = SampleInfo.HIT_NORMAL
}); });
hold.Tail.Samples = HitObject.Samples;
newObject = hold;
} }
else else
{ {

View File

@ -0,0 +1,37 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Rulesets.Mania.Judgements
{
public class HoldNoteTailJudgement : ManiaJudgement
{
/// <summary>
/// Whether the hold note has been released too early and shouldn't give full score for the release.
/// </summary>
public bool HasBroken;
public override int NumericResultForScore(ManiaHitResult result)
{
switch (result)
{
default:
return base.NumericResultForScore(result);
case ManiaHitResult.Great:
case ManiaHitResult.Perfect:
return base.NumericResultForScore(HasBroken ? ManiaHitResult.Good : result);
}
}
public override int NumericResultForAccuracy(ManiaHitResult result)
{
switch (result)
{
default:
return base.NumericResultForAccuracy(result);
case ManiaHitResult.Great:
case ManiaHitResult.Perfect:
return base.NumericResultForAccuracy(HasBroken ? ManiaHitResult.Good : result);
}
}
}
}

View File

@ -0,0 +1,13 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Rulesets.Mania.Judgements
{
public class HoldNoteTickJudgement : ManiaJudgement
{
public override bool AffectsCombo => false;
public override int NumericResultForScore(ManiaHitResult result) => 20;
public override int NumericResultForAccuracy(ManiaHitResult result) => 0; // Don't count ticks into accuracy
}
}

View File

@ -2,11 +2,37 @@
// 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 osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Mania.Judgements namespace osu.Game.Rulesets.Mania.Judgements
{ {
public class ManiaJudgement : Judgement public class ManiaJudgement : Judgement
{ {
/// <summary>
/// The maximum possible hit result.
/// </summary>
public const ManiaHitResult MAX_HIT_RESULT = ManiaHitResult.Perfect;
/// <summary>
/// The result value for the combo portion of the score.
/// </summary>
public int ResultValueForScore => Result == HitResult.Miss ? 0 : NumericResultForScore(ManiaResult);
/// <summary>
/// The result value for the accuracy portion of the score.
/// </summary>
public int ResultValueForAccuracy => Result == HitResult.Miss ? 0 : NumericResultForAccuracy(ManiaResult);
/// <summary>
/// The maximum result value for the combo portion of the score.
/// </summary>
public int MaxResultValueForScore => NumericResultForScore(MAX_HIT_RESULT);
/// <summary>
/// The maximum result value for the accuracy portion of the score.
/// </summary>
public int MaxResultValueForAccuracy => NumericResultForAccuracy(MAX_HIT_RESULT);
public override string ResultString => string.Empty; public override string ResultString => string.Empty;
public override string MaxResultString => string.Empty; public override string MaxResultString => string.Empty;
@ -15,5 +41,42 @@ namespace osu.Game.Rulesets.Mania.Judgements
/// The hit result. /// The hit result.
/// </summary> /// </summary>
public ManiaHitResult ManiaResult; public ManiaHitResult ManiaResult;
public virtual int NumericResultForScore(ManiaHitResult result)
{
switch (result)
{
default:
return 0;
case ManiaHitResult.Bad:
return 50;
case ManiaHitResult.Ok:
return 100;
case ManiaHitResult.Good:
return 200;
case ManiaHitResult.Great:
case ManiaHitResult.Perfect:
return 300;
}
}
public virtual int NumericResultForAccuracy(ManiaHitResult result)
{
switch (result)
{
default:
return 0;
case ManiaHitResult.Bad:
return 50;
case ManiaHitResult.Ok:
return 100;
case ManiaHitResult.Good:
return 200;
case ManiaHitResult.Great:
return 300;
case ManiaHitResult.Perfect:
return 305;
}
}
} }
} }

View File

@ -27,7 +27,14 @@ namespace osu.Game.Rulesets.Mania
{ {
new ManiaModEasy(), new ManiaModEasy(),
new ManiaModNoFail(), new ManiaModNoFail(),
new ManiaModHalfTime(), new MultiMod
{
Mods = new Mod[]
{
new ManiaModHalfTime(),
new ManiaModDaycore(),
},
},
}; };
case ModType.DifficultyIncrease: case ModType.DifficultyIncrease:

View File

@ -34,6 +34,11 @@ namespace osu.Game.Rulesets.Mania.Mods
} }
public class ManiaModDaycore : ModDaycore
{
public override double ScoreMultiplier => 0.3;
}
public class ManiaModDoubleTime : ModDoubleTime public class ManiaModDoubleTime : ModDoubleTime
{ {
public override double ScoreMultiplier => 1.0; public override double ScoreMultiplier => 1.0;

View File

@ -0,0 +1,21 @@
// 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.ControlPoints;
namespace osu.Game.Rulesets.Mania.Objects
{
public class BarLine : ManiaHitObject
{
/// <summary>
/// The control point which this bar line is part of.
/// </summary>
public TimingControlPoint ControlPoint;
/// <summary>
/// The index of the beat which this bar line represents within the control point.
/// This is a "major" bar line if <see cref="BeatIndex"/> % <see cref="TimingControlPoint.TimeSignature"/> == 0.
/// </summary>
public int BeatIndex;
}
}

View File

@ -0,0 +1,74 @@
// 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.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
/// <summary>
/// Visualises a <see cref="BarLine"/>. Although this derives DrawableManiaHitObject,
/// this does not handle input/sound like a normal hit object.
/// </summary>
public class DrawableBarLine : DrawableManiaHitObject<BarLine>
{
/// <summary>
/// Height of major bar line triangles.
/// </summary>
private const float triangle_height = 12;
/// <summary>
/// Offset of the major bar line triangles from the sides of the bar line.
/// </summary>
private const float triangle_offset = 9;
public DrawableBarLine(BarLine barLine)
: base(barLine)
{
RelativeSizeAxes = Axes.X;
Height = 1;
Add(new Box
{
Name = "Bar line",
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
RelativeSizeAxes = Axes.Both,
});
bool isMajor = barLine.BeatIndex % (int)barLine.ControlPoint.TimeSignature == 0;
if (isMajor)
{
Add(new EquilateralTriangle
{
Name = "Left triangle",
Anchor = Anchor.BottomLeft,
Origin = Anchor.TopCentre,
Size = new Vector2(triangle_height),
X = -triangle_offset,
Rotation = 90
});
Add(new EquilateralTriangle
{
Name = "Right triangle",
Anchor = Anchor.BottomRight,
Origin = Anchor.TopCentre,
Size = new Vector2(triangle_height),
X = triangle_offset,
Rotation = -90
});
}
if (!isMajor && barLine.BeatIndex % 2 == 1)
Alpha = 0.2f;
}
protected override void UpdateState(ArmedState state)
{
}
}
}

View File

@ -7,14 +7,34 @@ using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Framework.Configuration; using osu.Framework.Configuration;
using OpenTK.Input; using OpenTK.Input;
using osu.Framework.Input;
using OpenTK;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Framework.Extensions.IEnumerableExtensions;
namespace osu.Game.Rulesets.Mania.Objects.Drawables namespace osu.Game.Rulesets.Mania.Objects.Drawables
{ {
/// <summary>
/// Visualises a <see cref="HoldNote"/> hit object.
/// </summary>
public class DrawableHoldNote : DrawableManiaHitObject<HoldNote> public class DrawableHoldNote : DrawableManiaHitObject<HoldNote>
{ {
private readonly NotePiece headPiece; private readonly DrawableNote head;
private readonly DrawableNote tail;
private readonly BodyPiece bodyPiece; private readonly BodyPiece bodyPiece;
private readonly NotePiece tailPiece; private readonly Container<DrawableHoldNoteTick> tickContainer;
/// <summary>
/// Time at which the user started holding this hold note. Null if the user is not holding this hold note.
/// </summary>
private double? holdStartTime;
/// <summary>
/// Whether the hold note has been released too early and shouldn't give full score for the release.
/// </summary>
private bool hasBroken;
public DrawableHoldNote(HoldNote hitObject, Bindable<Key> key = null) public DrawableHoldNote(HoldNote hitObject, Bindable<Key> key = null)
: base(hitObject, key) : base(hitObject, key)
@ -32,17 +52,39 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
}, },
headPiece = new NotePiece tickContainer = new Container<DrawableHoldNoteTick>
{
RelativeSizeAxes = Axes.Both,
RelativeCoordinateSpace = new Vector2(1, (float)HitObject.Duration)
},
head = new DrawableHeadNote(this, key)
{ {
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre Origin = Anchor.TopCentre
}, },
tailPiece = new NotePiece tail = new DrawableTailNote(this, key)
{ {
Anchor = Anchor.BottomCentre, Anchor = Anchor.BottomCentre,
Origin = Anchor.TopCentre Origin = Anchor.TopCentre
} }
}); });
foreach (var tick in HitObject.Ticks)
{
var drawableTick = new DrawableHoldNoteTick(tick)
{
HoldStartTime = () => holdStartTime
};
// To make the ticks relative to ourselves we need to offset them backwards
drawableTick.Y -= (float)HitObject.StartTime;
tickContainer.Add(drawableTick);
AddNested(drawableTick);
}
AddNested(head);
AddNested(tail);
} }
public override Color4 AccentColour public override Color4 AccentColour
@ -54,9 +96,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
return; return;
base.AccentColour = value; base.AccentColour = value;
headPiece.AccentColour = value; tickContainer.Children.ForEach(t => t.AccentColour = value);
bodyPiece.AccentColour = value; bodyPiece.AccentColour = value;
tailPiece.AccentColour = value; head.AccentColour = value;
tail.AccentColour = value;
} }
} }
@ -64,14 +108,132 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{ {
} }
protected override void Update() protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{ {
if (Time.Current > HitObject.StartTime) // Make sure the keypress happened within the body of the hold note
headPiece.Colour = Color4.Green; if (Time.Current < HitObject.StartTime || Time.Current > HitObject.EndTime)
if (Time.Current > HitObject.EndTime) return false;
if (args.Key != Key)
return false;
if (args.Repeat)
return false;
// The user has pressed during the body of the hold note, after the head note and its hit windows have passed
// and within the limited range of the above if-statement. This state will be managed by the head note if the
// user has pressed during the hit windows of the head note.
holdStartTime = Time.Current;
return true;
}
protected override bool OnKeyUp(InputState state, KeyUpEventArgs args)
{
// Make sure that the user started holding the key during the hold note
if (!holdStartTime.HasValue)
return false;
if (args.Key != Key)
return false;
holdStartTime = null;
// If the key has been released too early, the user should not receive full score for the release
if (!tail.Judged)
hasBroken = true;
return true;
}
/// <summary>
/// The head note of a hold.
/// </summary>
private class DrawableHeadNote : DrawableNote
{
private readonly DrawableHoldNote holdNote;
public DrawableHeadNote(DrawableHoldNote holdNote, Bindable<Key> key = null)
: base(holdNote.HitObject.Head, key)
{ {
bodyPiece.Colour = Color4.Green; this.holdNote = holdNote;
tailPiece.Colour = Color4.Green;
RelativePositionAxes = Axes.None;
Y = 0;
}
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{
if (!base.OnKeyDown(state, args))
return false;
// We only want to trigger a holding state from the head if the head has received a judgement
if (!Judged)
return false;
// If the key has been released too early, the user should not receive full score for the release
if (Judgement.Result == HitResult.Miss)
holdNote.hasBroken = true;
// The head note also handles early hits before the body, but we want accurate early hits to count as the body being held
// The body doesn't handle these early early hits, so we have to explicitly set the holding state here
holdNote.holdStartTime = Time.Current;
return true;
}
}
/// <summary>
/// The tail note of a hold.
/// </summary>
private class DrawableTailNote : DrawableNote
{
private readonly DrawableHoldNote holdNote;
public DrawableTailNote(DrawableHoldNote holdNote, Bindable<Key> key = null)
: base(holdNote.HitObject.Tail, key)
{
this.holdNote = holdNote;
RelativePositionAxes = Axes.None;
Y = 0;
}
protected override ManiaJudgement CreateJudgement() => new HoldNoteTailJudgement();
protected override void CheckJudgement(bool userTriggered)
{
base.CheckJudgement(userTriggered);
var tailJudgement = Judgement as HoldNoteTailJudgement;
if (tailJudgement == null)
return;
tailJudgement.HasBroken = holdNote.hasBroken;
}
protected override bool OnKeyUp(InputState state, KeyUpEventArgs args)
{
// Make sure that the user started holding the key during the hold note
if (!holdNote.holdStartTime.HasValue)
return false;
if (Judgement.Result != HitResult.None)
return false;
if (args.Key != Key)
return false;
UpdateJudgement(true);
// Handled by the hold note, which will set holding = false
return false;
}
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{
// Tail doesn't handle key down
return false;
} }
} }
} }

View File

@ -0,0 +1,121 @@
// 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.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
/// <summary>
/// Visualises a <see cref="HoldNoteTick"/> hit object.
/// </summary>
public class DrawableHoldNoteTick : DrawableManiaHitObject<HoldNoteTick>
{
/// <summary>
/// References the time at which the user started holding the hold note.
/// </summary>
public Func<double?> HoldStartTime;
/// <summary>
/// References whether the user is currently holding the hold note.
/// </summary>
public Func<bool> IsHolding;
private readonly Container glowContainer;
public DrawableHoldNoteTick(HoldNoteTick hitObject)
: base(hitObject)
{
Anchor = Anchor.TopCentre;
Origin = Anchor.TopCentre;
RelativeSizeAxes = Axes.X;
Size = new Vector2(1);
Children = new[]
{
glowContainer = new CircularContainer
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.Both,
Masking = true,
Children = new[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true
}
}
}
};
// Set the default glow
AccentColour = Color4.White;
}
public override Color4 AccentColour
{
get { return base.AccentColour; }
set
{
base.AccentColour = value;
glowContainer.EdgeEffect = new EdgeEffect
{
Type = EdgeEffectType.Glow,
Radius = 2f,
Roundness = 15f,
Colour = value.Opacity(0.3f)
};
}
}
protected override ManiaJudgement CreateJudgement() => new HoldNoteTickJudgement();
protected override void CheckJudgement(bool userTriggered)
{
if (!userTriggered)
return;
if (Time.Current < HitObject.StartTime)
return;
if (HoldStartTime?.Invoke() > HitObject.StartTime)
return;
Judgement.ManiaResult = ManiaHitResult.Perfect;
Judgement.Result = HitResult.Hit;
}
protected override void UpdateState(ArmedState state)
{
switch (State)
{
case ArmedState.Hit:
AccentColour = Color4.Green;
break;
}
}
protected override void Update()
{
if (Judgement.Result != HitResult.None)
return;
if (IsHolding?.Invoke() != true)
return;
UpdateJudgement(true);
}
}
}

View File

@ -13,6 +13,9 @@ using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Mania.Objects.Drawables namespace osu.Game.Rulesets.Mania.Objects.Drawables
{ {
/// <summary>
/// Visualises a <see cref="Note"/> hit object.
/// </summary>
public class DrawableNote : DrawableManiaHitObject<Note> public class DrawableNote : DrawableManiaHitObject<Note>
{ {
private readonly NotePiece headPiece; private readonly NotePiece headPiece;

View File

@ -1,10 +1,9 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// 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 osu.Game.Audio; using System.Collections.Generic;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Mania.Objects namespace osu.Game.Rulesets.Mania.Objects
@ -12,32 +11,99 @@ namespace osu.Game.Rulesets.Mania.Objects
/// <summary> /// <summary>
/// Represents a hit object which requires pressing, holding, and releasing a key. /// Represents a hit object which requires pressing, holding, and releasing a key.
/// </summary> /// </summary>
public class HoldNote : Note, IHasEndTime public class HoldNote : ManiaHitObject, IHasEndTime
{ {
/// <summary>
/// Lenience of release hit windows. This is to make cases where the hold note release
/// is timed alongside presses of other hit objects less awkward.
/// </summary>
private const double release_window_lenience = 1.5;
public double Duration { get; set; }
public double EndTime => StartTime + Duration; public double EndTime => StartTime + Duration;
/// <summary> private double duration;
/// The samples to be played when this hold note is released. public double Duration
/// </summary> {
public SampleInfoList EndSamples = new SampleInfoList(); get { return duration; }
set
{
duration = value;
Tail.StartTime = EndTime;
}
}
public override double StartTime
{
get { return base.StartTime; }
set
{
base.StartTime = value;
Head.StartTime = value;
Tail.StartTime = EndTime;
}
}
/// <summary> /// <summary>
/// The key-release hit windows for this hold note. /// The head note of the hold.
/// </summary> /// </summary>
public HitWindows ReleaseHitWindows { get; protected set; } = new HitWindows(); public readonly Note Head = new Note();
/// <summary>
/// The tail note of the hold.
/// </summary>
public readonly Note Tail = new TailNote();
/// <summary>
/// The time between ticks of this hold.
/// </summary>
private double tickSpacing = 50;
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{ {
base.ApplyDefaults(controlPointInfo, difficulty); base.ApplyDefaults(controlPointInfo, difficulty);
ReleaseHitWindows = HitWindows * release_window_lenience; TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
tickSpacing = timingPoint.BeatLength / difficulty.SliderTickRate;
Head.ApplyDefaults(controlPointInfo, difficulty);
Tail.ApplyDefaults(controlPointInfo, difficulty);
}
/// <summary>
/// The scoring scoring ticks of the hold note.
/// </summary>
public IEnumerable<HoldNoteTick> Ticks => ticks ?? (ticks = createTicks());
private List<HoldNoteTick> ticks;
private List<HoldNoteTick> createTicks()
{
var ret = new List<HoldNoteTick>();
if (tickSpacing == 0)
return ret;
for (double t = StartTime + tickSpacing; t <= EndTime - tickSpacing; t += tickSpacing)
{
ret.Add(new HoldNoteTick
{
StartTime = t
});
}
return ret;
}
/// <summary>
/// The tail of the hold note.
/// </summary>
private class TailNote : Note
{
/// <summary>
/// Lenience of release hit windows. This is to make cases where the hold note release
/// is timed alongside presses of other hit objects less awkward.
/// </summary>
private const double release_window_lenience = 1.5;
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaults(controlPointInfo, difficulty);
HitWindows *= release_window_lenience;
}
} }
} }
} }

View File

@ -0,0 +1,12 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Rulesets.Mania.Objects
{
/// <summary>
/// A scoring tick of a hold note.
/// </summary>
public class HoldNoteTick : ManiaHitObject
{
}
}

View File

@ -1,8 +1,13 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// 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;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Rulesets.Mania.Judgements; using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
@ -10,6 +15,143 @@ namespace osu.Game.Rulesets.Mania.Scoring
{ {
internal class ManiaScoreProcessor : ScoreProcessor<ManiaHitObject, ManiaJudgement> internal class ManiaScoreProcessor : ScoreProcessor<ManiaHitObject, ManiaJudgement>
{ {
/// <summary>
/// The maximum score achievable.
/// Does _not_ include bonus score - for bonus score see <see cref="bonusScore"/>.
/// </summary>
private const int max_score = 1000000;
/// <summary>
/// The amount of the score attributed to combo.
/// </summary>
private const double combo_portion_max = max_score * 0.2;
/// <summary>
/// The amount of the score attributed to accuracy.
/// </summary>
private const double accuracy_portion_max = max_score * 0.8;
/// <summary>
/// The factor used to determine relevance of combos.
/// </summary>
private const double combo_base = 4;
/// <summary>
/// The combo value at which hit objects result in the max score possible.
/// </summary>
private const int combo_relevance_cap = 400;
/// <summary>
/// The hit HP multiplier at OD = 0.
/// </summary>
private const double hp_multiplier_min = 0.75;
/// <summary>
/// The hit HP multiplier at OD = 0.
/// </summary>
private const double hp_multiplier_mid = 0.85;
/// <summary>
/// The hit HP multiplier at OD = 0.
/// </summary>
private const double hp_multiplier_max = 1;
/// <summary>
/// The default BAD hit HP increase.
/// </summary>
private const double hp_increase_bad = 0.005;
/// <summary>
/// The default OK hit HP increase.
/// </summary>
private const double hp_increase_ok = 0.010;
/// <summary>
/// The default GOOD hit HP increase.
/// </summary>
private const double hp_increase_good = 0.035;
/// <summary>
/// The default tick hit HP increase.
/// </summary>
private const double hp_increase_tick = 0.040;
/// <summary>
/// The default GREAT hit HP increase.
/// </summary>
private const double hp_increase_great = 0.055;
/// <summary>
/// The default PERFECT hit HP increase.
/// </summary>
private const double hp_increase_perfect = 0.065;
/// <summary>
/// The MISS HP multiplier at OD = 0.
/// </summary>
private const double hp_multiplier_miss_min = 0.5;
/// <summary>
/// The MISS HP multiplier at OD = 5.
/// </summary>
private const double hp_multiplier_miss_mid = 0.75;
/// <summary>
/// The MISS HP multiplier at OD = 10.
/// </summary>
private const double hp_multiplier_miss_max = 1;
/// <summary>
/// The default MISS HP increase.
/// </summary>
private const double hp_increase_miss = -0.125;
/// <summary>
/// The MISS HP multiplier. This is multiplied to the miss hp increase.
/// </summary>
private double hpMissMultiplier = 1;
/// <summary>
/// The HIT HP multiplier. This is multiplied to hit hp increases.
/// </summary>
private double hpMultiplier = 1;
/// <summary>
/// The cumulative combo portion of the score.
/// </summary>
private double comboScore => combo_portion_max * comboPortion / maxComboPortion;
/// <summary>
/// The cumulative accuracy portion of the score.
/// </summary>
private double accuracyScore => accuracy_portion_max * Math.Pow(Accuracy, 4) * totalHits / maxTotalHits;
/// <summary>
/// The cumulative bonus score.
/// This is added on top of <see cref="max_score"/>, thus the total score can exceed <see cref="max_score"/>.
/// </summary>
private double bonusScore;
/// <summary>
/// The <see cref="comboPortion"/> achieved by a perfect playthrough.
/// </summary>
private double maxComboPortion;
/// <summary>
/// The portion of the score dedicated to combo.
/// </summary>
private double comboPortion;
/// <summary>
/// The <see cref="totalHits"/> achieved by a perfect playthrough.
/// </summary>
private int maxTotalHits;
/// <summary>
/// The total hits.
/// </summary>
private int totalHits;
public ManiaScoreProcessor() public ManiaScoreProcessor()
{ {
} }
@ -19,8 +161,124 @@ namespace osu.Game.Rulesets.Mania.Scoring
{ {
} }
protected override void ComputeTargets(Beatmap<ManiaHitObject> beatmap)
{
BeatmapDifficulty difficulty = beatmap.BeatmapInfo.Difficulty;
hpMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_min, hp_multiplier_mid, hp_multiplier_max);
hpMissMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_miss_min, hp_multiplier_miss_mid, hp_multiplier_miss_max);
while (true)
{
foreach (var obj in beatmap.HitObjects)
{
var holdNote = obj as HoldNote;
if (obj is Note)
{
AddJudgement(new ManiaJudgement
{
Result = HitResult.Hit,
ManiaResult = ManiaHitResult.Perfect
});
}
else if (holdNote != null)
{
// Head
AddJudgement(new ManiaJudgement
{
Result = HitResult.Hit,
ManiaResult = ManiaJudgement.MAX_HIT_RESULT
});
// Ticks
int tickCount = holdNote.Ticks.Count();
for (int i = 0; i < tickCount; i++)
{
AddJudgement(new HoldNoteTickJudgement
{
Result = HitResult.Hit,
ManiaResult = ManiaJudgement.MAX_HIT_RESULT,
});
}
AddJudgement(new HoldNoteTailJudgement
{
Result = HitResult.Hit,
ManiaResult = ManiaJudgement.MAX_HIT_RESULT
});
}
}
if (!HasFailed)
break;
hpMultiplier *= 1.01;
hpMissMultiplier *= 0.98;
Reset();
}
maxTotalHits = totalHits;
maxComboPortion = comboPortion;
}
protected override void OnNewJudgement(ManiaJudgement judgement) protected override void OnNewJudgement(ManiaJudgement judgement)
{ {
bool isTick = judgement is HoldNoteTickJudgement;
if (!isTick)
totalHits++;
switch (judgement.Result)
{
case HitResult.Miss:
Health.Value += hpMissMultiplier * hp_increase_miss;
break;
case HitResult.Hit:
if (isTick)
{
Health.Value += hpMultiplier * hp_increase_tick;
bonusScore += judgement.ResultValueForScore;
}
else
{
switch (judgement.ManiaResult)
{
case ManiaHitResult.Bad:
Health.Value += hpMultiplier * hp_increase_bad;
break;
case ManiaHitResult.Ok:
Health.Value += hpMultiplier * hp_increase_ok;
break;
case ManiaHitResult.Good:
Health.Value += hpMultiplier * hp_increase_good;
break;
case ManiaHitResult.Great:
Health.Value += hpMultiplier * hp_increase_great;
break;
case ManiaHitResult.Perfect:
Health.Value += hpMultiplier * hp_increase_perfect;
break;
}
// A factor that is applied to make higher combos more relevant
double comboRelevance = Math.Min(Math.Max(0.5, Math.Log(Combo.Value, combo_base)), Math.Log(combo_relevance_cap, combo_base));
comboPortion += judgement.ResultValueForScore * comboRelevance;
}
break;
}
int scoreForAccuracy = 0;
int maxScoreForAccuracy = 0;
foreach (var j in Judgements)
{
scoreForAccuracy += j.ResultValueForAccuracy;
maxScoreForAccuracy += j.MaxResultValueForAccuracy;
}
Accuracy.Value = (double)scoreForAccuracy / maxScoreForAccuracy;
TotalScore.Value = comboScore + accuracyScore + bonusScore;
} }
protected override void Reset() protected override void Reset()
@ -28,6 +286,10 @@ namespace osu.Game.Rulesets.Mania.Scoring
base.Reset(); base.Reset();
Health.Value = 1; Health.Value = 1;
bonusScore = 0;
comboPortion = 0;
totalHits = 0;
} }
} }
} }

View File

@ -8,6 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using OpenTK; using OpenTK;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Mania.Timing namespace osu.Game.Rulesets.Mania.Timing
{ {
@ -128,6 +129,8 @@ namespace osu.Game.Rulesets.Mania.Timing
/// </summary> /// </summary>
private class AutoTimeRelativeContainer : Container private class AutoTimeRelativeContainer : Container
{ {
protected override IComparer<Drawable> DepthComparer => new HitObjectReverseStartTimeComparer();
public override void InvalidateFromChild(Invalidation invalidation) public override void InvalidateFromChild(Invalidation invalidation)
{ {
// We only want to re-compute our size when a child's size or position has changed // We only want to re-compute our size when a child's size or position has changed

View File

@ -1,7 +1,6 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// 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 OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using OpenTK.Input; using OpenTK.Input;
@ -188,7 +187,11 @@ namespace osu.Game.Rulesets.Mania.UI
} }
} }
public void Add(DrawableHitObject<ManiaHitObject, ManiaJudgement> hitObject) => ControlPointContainer.Add(hitObject); public void Add(DrawableHitObject<ManiaHitObject, ManiaJudgement> hitObject)
{
hitObject.AccentColour = AccentColour;
ControlPointContainer.Add(hitObject);
}
private bool onKeyDown(InputState state, KeyDownEventArgs args) private bool onKeyDown(InputState state, KeyDownEventArgs args)
{ {

View File

@ -6,9 +6,11 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using OpenTK; using OpenTK;
using OpenTK.Input; using OpenTK.Input;
using osu.Framework.Allocation;
using osu.Framework.Configuration; using osu.Framework.Configuration;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Lists; using osu.Framework.Lists;
using osu.Framework.MathUtils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Beatmaps; using osu.Game.Rulesets.Beatmaps;
@ -85,6 +87,34 @@ namespace osu.Game.Rulesets.Mania.UI
}; };
} }
[BackgroundDependencyLoader]
private void load()
{
var maniaPlayfield = (ManiaPlayfield)Playfield;
double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue;
SortedList<TimingControlPoint> timingPoints = Beatmap.ControlPointInfo.TimingPoints;
for (int i = 0; i < timingPoints.Count; i++)
{
TimingControlPoint point = timingPoints[i];
// Stop on the beat before the next timing point, or if there is no next timing point stop slightly past the last object
double endTime = i < timingPoints.Count - 1 ? timingPoints[i + 1].Time - point.BeatLength : lastObjectTime + point.BeatLength * (int)point.TimeSignature;
int index = 0;
for (double t = timingPoints[i].Time; Precision.DefinitelyBigger(endTime, t); t += point.BeatLength, index++)
{
maniaPlayfield.Add(new DrawableBarLine(new BarLine
{
StartTime = t,
ControlPoint = point,
BeatIndex = index
}));
}
}
}
public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this); public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this);
protected override BeatmapConverter<ManiaHitObject> CreateBeatmapConverter() => new ManiaBeatmapConverter(); protected override BeatmapConverter<ManiaHitObject> CreateBeatmapConverter() => new ManiaBeatmapConverter();

View File

@ -21,6 +21,7 @@ using osu.Game.Rulesets.Mania.Timing;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Framework.Graphics.Transforms; using osu.Framework.Graphics.Transforms;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Game.Rulesets.Mania.Objects.Drawables;
namespace osu.Game.Rulesets.Mania.UI namespace osu.Game.Rulesets.Mania.UI
{ {
@ -57,7 +58,7 @@ namespace osu.Game.Rulesets.Mania.UI
private readonly FlowContainer<Column> columns; private readonly FlowContainer<Column> columns;
public IEnumerable<Column> Columns => columns.Children; public IEnumerable<Column> Columns => columns.Children;
private readonly ControlPointContainer barlineContainer; private readonly ControlPointContainer barLineContainer;
private List<Color4> normalColumnColours = new List<Color4>(); private List<Color4> normalColumnColours = new List<Color4>();
private Color4 specialColumnColour; private Color4 specialColumnColour;
@ -77,35 +78,51 @@ namespace osu.Game.Rulesets.Mania.UI
{ {
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Both,
AutoSizeAxes = Axes.X,
Masking = true, Masking = true,
Children = new Drawable[] Children = new Drawable[]
{ {
new Box new Container
{ {
RelativeSizeAxes = Axes.Both, Name = "Masked elements",
Colour = Color4.Black Anchor = Anchor.TopCentre,
}, Origin = Anchor.TopCentre,
columns = new FillFlowContainer<Column>
{
Name = "Columns",
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X, AutoSizeAxes = Axes.X,
Direction = FillDirection.Horizontal, Masking = true,
Padding = new MarginPadding { Left = 1, Right = 1 }, Children = new Drawable[]
Spacing = new Vector2(1, 0) {
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black
},
columns = new FillFlowContainer<Column>
{
Name = "Columns",
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
Direction = FillDirection.Horizontal,
Padding = new MarginPadding { Left = 1, Right = 1 },
Spacing = new Vector2(1, 0)
}
}
}, },
new Container new Container
{ {
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = HIT_TARGET_POSITION }, Padding = new MarginPadding { Top = HIT_TARGET_POSITION },
Children = new[] Children = new[]
{ {
barlineContainer = new ControlPointContainer(timingChanges) barLineContainer = new ControlPointContainer(timingChanges)
{ {
Name = "Bar lines", Name = "Bar lines",
RelativeSizeAxes = Axes.Both, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.Y
// Width is set in the Update method
} }
} }
} }
@ -190,6 +207,7 @@ namespace osu.Game.Rulesets.Mania.UI
} }
public override void Add(DrawableHitObject<ManiaHitObject, ManiaJudgement> h) => Columns.ElementAt(h.HitObject.Column).Add(h); public override void Add(DrawableHitObject<ManiaHitObject, ManiaJudgement> h) => Columns.ElementAt(h.HitObject.Column).Add(h);
public void Add(DrawableBarLine barline) => barLineContainer.Add(barline);
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{ {
@ -224,7 +242,7 @@ namespace osu.Game.Rulesets.Mania.UI
timeSpan = MathHelper.Clamp(timeSpan, time_span_min, time_span_max); timeSpan = MathHelper.Clamp(timeSpan, time_span_min, time_span_max);
barlineContainer.TimeSpan = value; barLineContainer.TimeSpan = value;
Columns.ForEach(c => c.ControlPointContainer.TimeSpan = value); Columns.ForEach(c => c.ControlPointContainer.TimeSpan = value);
} }
} }
@ -234,6 +252,13 @@ namespace osu.Game.Rulesets.Mania.UI
TransformTo(() => TimeSpan, newTimeSpan, duration, easing, new TransformTimeSpan()); TransformTo(() => TimeSpan, newTimeSpan, duration, easing, new TransformTimeSpan());
} }
protected override void Update()
{
// Due to masking differences, it is not possible to get the width of the columns container automatically
// While masking on effectively only the Y-axis, so we need to set the width of the bar line container manually
barLineContainer.Width = columns.Width;
}
private class TransformTimeSpan : Transform<double> private class TransformTimeSpan : Transform<double>
{ {
public override double CurrentValue public override double CurrentValue

View File

@ -57,17 +57,23 @@
<Compile Include="Beatmaps\Patterns\Pattern.cs" /> <Compile Include="Beatmaps\Patterns\Pattern.cs" />
<Compile Include="MathUtils\FastRandom.cs" /> <Compile Include="MathUtils\FastRandom.cs" />
<Compile Include="Judgements\HitWindows.cs" /> <Compile Include="Judgements\HitWindows.cs" />
<Compile Include="Judgements\HoldNoteTailJudgement.cs" />
<Compile Include="Judgements\HoldNoteTickJudgement.cs" />
<Compile Include="Judgements\ManiaHitResult.cs" /> <Compile Include="Judgements\ManiaHitResult.cs" />
<Compile Include="Judgements\ManiaJudgement.cs" /> <Compile Include="Judgements\ManiaJudgement.cs" />
<Compile Include="ManiaDifficultyCalculator.cs" /> <Compile Include="ManiaDifficultyCalculator.cs" />
<Compile Include="Objects\Drawables\DrawableBarLine.cs" />
<Compile Include="Objects\Drawables\DrawableHoldNote.cs" /> <Compile Include="Objects\Drawables\DrawableHoldNote.cs" />
<Compile Include="Objects\Drawables\DrawableHoldNoteTick.cs" />
<Compile Include="Objects\Drawables\DrawableManiaHitObject.cs" /> <Compile Include="Objects\Drawables\DrawableManiaHitObject.cs" />
<Compile Include="Objects\Drawables\DrawableNote.cs" /> <Compile Include="Objects\Drawables\DrawableNote.cs" />
<Compile Include="Objects\Drawables\Pieces\BodyPiece.cs" /> <Compile Include="Objects\Drawables\Pieces\BodyPiece.cs" />
<Compile Include="Objects\Drawables\Pieces\NotePiece.cs" /> <Compile Include="Objects\Drawables\Pieces\NotePiece.cs" />
<Compile Include="Objects\Types\IHasColumn.cs" /> <Compile Include="Objects\Types\IHasColumn.cs" />
<Compile Include="Scoring\ManiaScoreProcessor.cs" /> <Compile Include="Scoring\ManiaScoreProcessor.cs" />
<Compile Include="Objects\BarLine.cs" />
<Compile Include="Objects\HoldNote.cs" /> <Compile Include="Objects\HoldNote.cs" />
<Compile Include="Objects\HoldNoteTick.cs" />
<Compile Include="Objects\ManiaHitObject.cs" /> <Compile Include="Objects\ManiaHitObject.cs" />
<Compile Include="Objects\Note.cs" /> <Compile Include="Objects\Note.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />

View File

@ -39,6 +39,11 @@ namespace osu.Game.Rulesets.Osu.Mods
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot) }).ToArray(); public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot) }).ToArray();
} }
public class OsuModDaycore : ModDaycore
{
public override double ScoreMultiplier => 0.5;
}
public class OsuModDoubleTime : ModDoubleTime public class OsuModDoubleTime : ModDoubleTime
{ {
public override double ScoreMultiplier => 1.12; public override double ScoreMultiplier => 1.12;

View File

@ -46,7 +46,14 @@ namespace osu.Game.Rulesets.Osu
{ {
new OsuModEasy(), new OsuModEasy(),
new OsuModNoFail(), new OsuModNoFail(),
new OsuModHalfTime(), new MultiMod
{
Mods = new Mod[]
{
new OsuModHalfTime(),
new OsuModDaycore(),
},
},
}; };
case ModType.DifficultyIncrease: case ModType.DifficultyIncrease:

View File

@ -37,6 +37,11 @@ namespace osu.Game.Rulesets.Taiko.Mods
} }
public class TaikoModDaycore : ModDaycore
{
public override double ScoreMultiplier => 0.5;
}
public class TaikoModDoubleTime : ModDoubleTime public class TaikoModDoubleTime : ModDoubleTime
{ {
public override double ScoreMultiplier => 1.12; public override double ScoreMultiplier => 1.12;

View File

@ -28,7 +28,14 @@ namespace osu.Game.Rulesets.Taiko
{ {
new TaikoModEasy(), new TaikoModEasy(),
new TaikoModNoFail(), new TaikoModNoFail(),
new TaikoModHalfTime(), new MultiMod
{
Mods = new Mod[]
{
new TaikoModHalfTime(),
new TaikoModDaycore(),
},
},
}; };
case ModType.DifficultyIncrease: case ModType.DifficultyIncrease:

View File

@ -56,7 +56,7 @@ namespace osu.Game.Tests.Beatmaps.IO
Assert.IsTrue(File.Exists(temp)); Assert.IsTrue(File.Exists(temp));
var importer = new BeatmapIPCChannel(client); var importer = new BeatmapIPCChannel(client);
if (!importer.ImportAsync(temp).Wait(5000)) if (!importer.ImportAsync(temp).Wait(10000))
Assert.Fail(@"IPC took too long to send"); Assert.Fail(@"IPC took too long to send");
ensureLoaded(host); ensureLoaded(host);

View File

@ -2,6 +2,7 @@
// 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; using System;
using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using OpenTK.Graphics; using OpenTK.Graphics;
@ -31,6 +32,8 @@ namespace osu.Game.Beatmaps.Formats
private ConvertHitObjectParser parser; private ConvertHitObjectParser parser;
private readonly Dictionary<string, string> variables = new Dictionary<string, string>();
private LegacySampleBank defaultSampleBank; private LegacySampleBank defaultSampleBank;
private int defaultSampleVolume = 100; private int defaultSampleVolume = 100;
@ -56,36 +59,39 @@ namespace osu.Game.Beatmaps.Formats
TimingPoints, TimingPoints,
Colours, Colours,
HitObjects, HitObjects,
Variables,
} }
private void handleGeneral(Beatmap beatmap, string key, string val) private void handleGeneral(Beatmap beatmap, string line)
{ {
var pair = splitKeyVal(line, ':');
var metadata = beatmap.BeatmapInfo.Metadata; var metadata = beatmap.BeatmapInfo.Metadata;
switch (key) switch (pair.Key)
{ {
case @"AudioFilename": case @"AudioFilename":
metadata.AudioFile = val; metadata.AudioFile = pair.Value;
break; break;
case @"AudioLeadIn": case @"AudioLeadIn":
beatmap.BeatmapInfo.AudioLeadIn = int.Parse(val); beatmap.BeatmapInfo.AudioLeadIn = int.Parse(pair.Value);
break; break;
case @"PreviewTime": case @"PreviewTime":
metadata.PreviewTime = int.Parse(val); metadata.PreviewTime = int.Parse(pair.Value);
break; break;
case @"Countdown": case @"Countdown":
beatmap.BeatmapInfo.Countdown = int.Parse(val) == 1; beatmap.BeatmapInfo.Countdown = int.Parse(pair.Value) == 1;
break; break;
case @"SampleSet": case @"SampleSet":
defaultSampleBank = (LegacySampleBank)Enum.Parse(typeof(LegacySampleBank), val); defaultSampleBank = (LegacySampleBank)Enum.Parse(typeof(LegacySampleBank), pair.Value);
break; break;
case @"SampleVolume": case @"SampleVolume":
defaultSampleVolume = int.Parse(val); defaultSampleVolume = int.Parse(pair.Value);
break; break;
case @"StackLeniency": case @"StackLeniency":
beatmap.BeatmapInfo.StackLeniency = float.Parse(val, NumberFormatInfo.InvariantInfo); beatmap.BeatmapInfo.StackLeniency = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
break; break;
case @"Mode": case @"Mode":
beatmap.BeatmapInfo.RulesetID = int.Parse(val); beatmap.BeatmapInfo.RulesetID = int.Parse(pair.Value);
switch (beatmap.BeatmapInfo.RulesetID) switch (beatmap.BeatmapInfo.RulesetID)
{ {
@ -104,107 +110,135 @@ namespace osu.Game.Beatmaps.Formats
} }
break; break;
case @"LetterboxInBreaks": case @"LetterboxInBreaks":
beatmap.BeatmapInfo.LetterboxInBreaks = int.Parse(val) == 1; beatmap.BeatmapInfo.LetterboxInBreaks = int.Parse(pair.Value) == 1;
break; break;
case @"SpecialStyle": case @"SpecialStyle":
beatmap.BeatmapInfo.SpecialStyle = int.Parse(val) == 1; beatmap.BeatmapInfo.SpecialStyle = int.Parse(pair.Value) == 1;
break; break;
case @"WidescreenStoryboard": case @"WidescreenStoryboard":
beatmap.BeatmapInfo.WidescreenStoryboard = int.Parse(val) == 1; beatmap.BeatmapInfo.WidescreenStoryboard = int.Parse(pair.Value) == 1;
break; break;
} }
} }
private void handleEditor(Beatmap beatmap, string key, string val) private void handleEditor(Beatmap beatmap, string line)
{ {
switch (key) var pair = splitKeyVal(line, ':');
switch (pair.Key)
{ {
case @"Bookmarks": case @"Bookmarks":
beatmap.BeatmapInfo.StoredBookmarks = val; beatmap.BeatmapInfo.StoredBookmarks = pair.Value;
break; break;
case @"DistanceSpacing": case @"DistanceSpacing":
beatmap.BeatmapInfo.DistanceSpacing = double.Parse(val, NumberFormatInfo.InvariantInfo); beatmap.BeatmapInfo.DistanceSpacing = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
break; break;
case @"BeatDivisor": case @"BeatDivisor":
beatmap.BeatmapInfo.BeatDivisor = int.Parse(val); beatmap.BeatmapInfo.BeatDivisor = int.Parse(pair.Value);
break; break;
case @"GridSize": case @"GridSize":
beatmap.BeatmapInfo.GridSize = int.Parse(val); beatmap.BeatmapInfo.GridSize = int.Parse(pair.Value);
break; break;
case @"TimelineZoom": case @"TimelineZoom":
beatmap.BeatmapInfo.TimelineZoom = double.Parse(val, NumberFormatInfo.InvariantInfo); beatmap.BeatmapInfo.TimelineZoom = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
break; break;
} }
} }
private void handleMetadata(Beatmap beatmap, string key, string val) private void handleMetadata(Beatmap beatmap, string line)
{ {
var pair = splitKeyVal(line, ':');
var metadata = beatmap.BeatmapInfo.Metadata; var metadata = beatmap.BeatmapInfo.Metadata;
switch (key) switch (pair.Key)
{ {
case @"Title": case @"Title":
metadata.Title = val; metadata.Title = pair.Value;
break; break;
case @"TitleUnicode": case @"TitleUnicode":
metadata.TitleUnicode = val; metadata.TitleUnicode = pair.Value;
break; break;
case @"Artist": case @"Artist":
metadata.Artist = val; metadata.Artist = pair.Value;
break; break;
case @"ArtistUnicode": case @"ArtistUnicode":
metadata.ArtistUnicode = val; metadata.ArtistUnicode = pair.Value;
break; break;
case @"Creator": case @"Creator":
metadata.Author = val; metadata.Author = pair.Value;
break; break;
case @"Version": case @"Version":
beatmap.BeatmapInfo.Version = val; beatmap.BeatmapInfo.Version = pair.Value;
break; break;
case @"Source": case @"Source":
beatmap.BeatmapInfo.Metadata.Source = val; beatmap.BeatmapInfo.Metadata.Source = pair.Value;
break; break;
case @"Tags": case @"Tags":
beatmap.BeatmapInfo.Metadata.Tags = val; beatmap.BeatmapInfo.Metadata.Tags = pair.Value;
break; break;
case @"BeatmapID": case @"BeatmapID":
beatmap.BeatmapInfo.OnlineBeatmapID = int.Parse(val); beatmap.BeatmapInfo.OnlineBeatmapID = int.Parse(pair.Value);
break; break;
case @"BeatmapSetID": case @"BeatmapSetID":
beatmap.BeatmapInfo.OnlineBeatmapSetID = int.Parse(val); beatmap.BeatmapInfo.OnlineBeatmapSetID = int.Parse(pair.Value);
metadata.OnlineBeatmapSetID = int.Parse(val); metadata.OnlineBeatmapSetID = int.Parse(pair.Value);
break; break;
} }
} }
private void handleDifficulty(Beatmap beatmap, string key, string val) private void handleDifficulty(Beatmap beatmap, string line)
{ {
var pair = splitKeyVal(line, ':');
var difficulty = beatmap.BeatmapInfo.Difficulty; var difficulty = beatmap.BeatmapInfo.Difficulty;
switch (key) switch (pair.Key)
{ {
case @"HPDrainRate": case @"HPDrainRate":
difficulty.DrainRate = float.Parse(val, NumberFormatInfo.InvariantInfo); difficulty.DrainRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
break; break;
case @"CircleSize": case @"CircleSize":
difficulty.CircleSize = float.Parse(val, NumberFormatInfo.InvariantInfo); difficulty.CircleSize = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
break; break;
case @"OverallDifficulty": case @"OverallDifficulty":
difficulty.OverallDifficulty = float.Parse(val, NumberFormatInfo.InvariantInfo); difficulty.OverallDifficulty = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
break; break;
case @"ApproachRate": case @"ApproachRate":
difficulty.ApproachRate = float.Parse(val, NumberFormatInfo.InvariantInfo); difficulty.ApproachRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
break; break;
case @"SliderMultiplier": case @"SliderMultiplier":
difficulty.SliderMultiplier = float.Parse(val, NumberFormatInfo.InvariantInfo); difficulty.SliderMultiplier = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
break; break;
case @"SliderTickRate": case @"SliderTickRate":
difficulty.SliderTickRate = float.Parse(val, NumberFormatInfo.InvariantInfo); difficulty.SliderTickRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
break; break;
} }
} }
private void handleEvents(Beatmap beatmap, string val) /// <summary>
/// Decodes any beatmap variables present in a line into their real values.
/// </summary>
/// <param name="line">The line which may contains variables.</param>
private void decodeVariables(ref string line)
{ {
string[] split = val.Split(','); while (line.IndexOf('$') >= 0)
{
string[] split = line.Split(',');
for (int i = 0; i < split.Length; i++)
{
var item = split[i];
if (item.StartsWith("$") && variables.ContainsKey(item))
split[i] = variables[item];
}
line = string.Join(",", split);
}
}
private void handleEvents(Beatmap beatmap, string line)
{
decodeVariables(ref line);
string[] split = line.Split(',');
EventType type; EventType type;
if (!Enum.TryParse(split[0], out type)) if (!Enum.TryParse(split[0], out type))
@ -236,9 +270,9 @@ namespace osu.Game.Beatmaps.Formats
} }
} }
private void handleTimingPoints(Beatmap beatmap, string val) private void handleTimingPoints(Beatmap beatmap, string line)
{ {
string[] split = val.Split(','); string[] split = line.Split(',');
double time = double.Parse(split[0].Trim(), NumberFormatInfo.InvariantInfo); double time = double.Parse(split[0].Trim(), NumberFormatInfo.InvariantInfo);
double beatLength = double.Parse(split[1].Trim(), NumberFormatInfo.InvariantInfo); double beatLength = double.Parse(split[1].Trim(), NumberFormatInfo.InvariantInfo);
@ -321,12 +355,14 @@ namespace osu.Game.Beatmaps.Formats
} }
} }
private void handleColours(Beatmap beatmap, string key, string val, ref bool hasCustomColours) private void handleColours(Beatmap beatmap, string line, ref bool hasCustomColours)
{ {
string[] split = val.Split(','); var pair = splitKeyVal(line, ':');
string[] split = pair.Value.Split(',');
if (split.Length != 3) if (split.Length != 3)
throw new InvalidOperationException($@"Color specified in incorrect format (should be R,G,B): {val}"); throw new InvalidOperationException($@"Color specified in incorrect format (should be R,G,B): {pair.Value}");
byte r, g, b; byte r, g, b;
if (!byte.TryParse(split[0], out r) || !byte.TryParse(split[1], out g) || !byte.TryParse(split[2], out b)) if (!byte.TryParse(split[0], out r) || !byte.TryParse(split[1], out g) || !byte.TryParse(split[2], out b))
@ -339,7 +375,7 @@ namespace osu.Game.Beatmaps.Formats
} }
// Note: the combo index specified in the beatmap is discarded // Note: the combo index specified in the beatmap is discarded
if (key.StartsWith(@"Combo")) if (pair.Key.StartsWith(@"Combo"))
{ {
beatmap.ComboColors.Add(new Color4 beatmap.ComboColors.Add(new Color4
{ {
@ -351,6 +387,12 @@ namespace osu.Game.Beatmaps.Formats
} }
} }
private void handleVariables(string line)
{
var pair = splitKeyVal(line, '=');
variables[pair.Key] = pair.Value;
}
protected override Beatmap ParseFile(StreamReader stream) protected override Beatmap ParseFile(StreamReader stream)
{ {
return new LegacyBeatmap(base.ParseFile(stream)); return new LegacyBeatmap(base.ParseFile(stream));
@ -390,44 +432,53 @@ namespace osu.Game.Beatmaps.Formats
continue; continue;
} }
string val = line, key = null;
if (section != Section.Events && section != Section.TimingPoints && section != Section.HitObjects)
{
key = val.Remove(val.IndexOf(':')).Trim();
val = val.Substring(val.IndexOf(':') + 1).Trim();
}
switch (section) switch (section)
{ {
case Section.General: case Section.General:
handleGeneral(beatmap, key, val); handleGeneral(beatmap, line);
break; break;
case Section.Editor: case Section.Editor:
handleEditor(beatmap, key, val); handleEditor(beatmap, line);
break; break;
case Section.Metadata: case Section.Metadata:
handleMetadata(beatmap, key, val); handleMetadata(beatmap, line);
break; break;
case Section.Difficulty: case Section.Difficulty:
handleDifficulty(beatmap, key, val); handleDifficulty(beatmap, line);
break; break;
case Section.Events: case Section.Events:
handleEvents(beatmap, val); handleEvents(beatmap, line);
break; break;
case Section.TimingPoints: case Section.TimingPoints:
handleTimingPoints(beatmap, val); handleTimingPoints(beatmap, line);
break; break;
case Section.Colours: case Section.Colours:
handleColours(beatmap, key, val, ref hasCustomColours); handleColours(beatmap, line, ref hasCustomColours);
break; break;
case Section.HitObjects: case Section.HitObjects:
var obj = parser.Parse(val); var obj = parser.Parse(line);
if (obj != null) if (obj != null)
beatmap.HitObjects.Add(obj); beatmap.HitObjects.Add(obj);
break; break;
case Section.Variables:
handleVariables(line);
break;
} }
} }
foreach (var hitObject in beatmap.HitObjects)
hitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.Difficulty);
}
private KeyValuePair<string, string> splitKeyVal(string line, char separator)
{
return new KeyValuePair<string, string>
(
line.Remove(line.IndexOf(separator)).Trim(),
line.Substring(line.IndexOf(separator) + 1).Trim()
);
} }
internal enum LegacySampleBank internal enum LegacySampleBank

View File

@ -20,6 +20,8 @@ namespace osu.Game.Configuration
Set(OsuSetting.DisplayStarsMinimum, 0.0, 0, 10); Set(OsuSetting.DisplayStarsMinimum, 0.0, 0, 10);
Set(OsuSetting.DisplayStarsMaximum, 10.0, 0, 10); Set(OsuSetting.DisplayStarsMaximum, 10.0, 0, 10);
Set(OsuSetting.SelectionRandomType, SelectionRandomType.RandomPermutation);
Set(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2, 1); Set(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2, 1);
// Online settings // Online settings
@ -69,6 +71,9 @@ namespace osu.Game.Configuration
Set(OsuSetting.ShowInterface, true); Set(OsuSetting.ShowInterface, true);
Set(OsuSetting.KeyOverlay, false); Set(OsuSetting.KeyOverlay, false);
Set(OsuSetting.FloatingComments, false);
Set(OsuSetting.PlaybackSpeed, 1.0, 0.5f, 2);
// Update // Update
Set(OsuSetting.ReleaseStream, ReleaseStream.Lazer); Set(OsuSetting.ReleaseStream, ReleaseStream.Lazer);
@ -88,6 +93,8 @@ namespace osu.Game.Configuration
AutoCursorSize, AutoCursorSize,
DimLevel, DimLevel,
KeyOverlay, KeyOverlay,
FloatingComments,
PlaybackSpeed,
ShowInterface, ShowInterface,
MouseDisableButtons, MouseDisableButtons,
MouseDisableWheel, MouseDisableWheel,
@ -102,6 +109,7 @@ namespace osu.Game.Configuration
SaveUsername, SaveUsername,
DisplayStarsMinimum, DisplayStarsMinimum,
DisplayStarsMaximum, DisplayStarsMaximum,
SelectionRandomType,
SnakingInSliders, SnakingInSliders,
SnakingOutSliders, SnakingOutSliders,
ShowFpsDisplay, ShowFpsDisplay,

View File

@ -0,0 +1,15 @@
// 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.ComponentModel;
namespace osu.Game.Configuration
{
public enum SelectionRandomType
{
[Description("Never repeat")]
RandomPermutation,
[Description("Random")]
Random
}
}

View File

@ -23,6 +23,13 @@ namespace osu.Game.Graphics.Backgrounds
public class Triangles : Drawable public class Triangles : Drawable
{ {
private const float triangle_size = 100; private const float triangle_size = 100;
private const float base_velocity = 50;
/// <summary>
/// How many screen-space pixels are smoothed over.
/// Same behavior as Sprite's EdgeSmoothness.
/// </summary>
private const float edge_smoothness = 1;
public override bool HandleInput => false; public override bool HandleInput => false;
@ -103,31 +110,34 @@ namespace osu.Game.Graphics.Backgrounds
Invalidate(Invalidation.DrawNode, shallPropagate: false); Invalidate(Invalidation.DrawNode, shallPropagate: false);
if (CreateNewTriangles)
addTriangles(false);
float adjustedAlpha = HideAlphaDiscrepancies ?
// Cubically scale alpha to make it drop off more sharply.
(float)Math.Pow(DrawInfo.Colour.AverageColour.Linear.A, 3) :
1;
float elapsedSeconds = (float)Time.Elapsed / 1000;
// Since position is relative, the velocity needs to scale inversely with DrawHeight.
// Since we will later multiply by the scale of individual triangles we normalize by
// dividing by triangleScale.
float movedDistance = -elapsedSeconds * Velocity * base_velocity / (DrawHeight * triangleScale);
for (int i = 0; i < parts.Count; i++) for (int i = 0; i < parts.Count; i++)
{ {
TriangleParticle newParticle = parts[i]; TriangleParticle newParticle = parts[i];
float adjustedAlpha = HideAlphaDiscrepancies ? // Scale moved distance by the size of the triangle. Smaller triangles should move more slowly.
// Cubically scale alpha to make it drop off more sharply. newParticle.Position.Y += parts[i].Scale * movedDistance;
(float)Math.Pow(DrawInfo.Colour.AverageColour.Linear.A, 3) :
1;
newParticle.Position += new Vector2(0, -(parts[i].Scale * (50 / DrawHeight)) / triangleScale * Velocity) * ((float)Time.Elapsed / 950);
newParticle.Colour.A = adjustedAlpha; newParticle.Colour.A = adjustedAlpha;
parts[i] = newParticle; parts[i] = newParticle;
if (!CreateNewTriangles)
continue;
float bottomPos = parts[i].Position.Y + triangle_size * parts[i].Scale * 0.866f / DrawHeight; float bottomPos = parts[i].Position.Y + triangle_size * parts[i].Scale * 0.866f / DrawHeight;
if (bottomPos < 0) if (bottomPos < 0)
parts.RemoveAt(i); parts.RemoveAt(i);
} }
addTriangles(false);
} }
private void addTriangles(bool randomY) private void addTriangles(bool randomY)
@ -211,20 +221,28 @@ namespace osu.Game.Graphics.Backgrounds
Shader.Bind(); Shader.Bind();
Texture.TextureGL.Bind(); Texture.TextureGL.Bind();
Vector2 localInflationAmount = edge_smoothness * DrawInfo.MatrixInverse.ExtractScale().Xy;
foreach (TriangleParticle particle in Parts) foreach (TriangleParticle particle in Parts)
{ {
var offset = new Vector2(particle.Scale * 0.5f, particle.Scale * 0.866f); var offset = triangle_size * new Vector2(particle.Scale * 0.5f, particle.Scale * 0.866f);
var size = new Vector2(2 * offset.X, offset.Y);
var triangle = new Triangle( var triangle = new Triangle(
particle.Position * Size * DrawInfo.Matrix, particle.Position * Size * DrawInfo.Matrix,
(particle.Position * Size + offset * triangle_size) * DrawInfo.Matrix, (particle.Position * Size + offset) * DrawInfo.Matrix,
(particle.Position * Size + new Vector2(-offset.X, offset.Y) * triangle_size) * DrawInfo.Matrix (particle.Position * Size + new Vector2(-offset.X, offset.Y)) * DrawInfo.Matrix
); );
ColourInfo colourInfo = DrawInfo.Colour; ColourInfo colourInfo = DrawInfo.Colour;
colourInfo.ApplyChild(particle.Colour); colourInfo.ApplyChild(particle.Colour);
Texture.DrawTriangle(triangle, colourInfo, null, Shared.VertexBatch.Add); Texture.DrawTriangle(
triangle,
colourInfo,
null,
Shared.VertexBatch.Add,
Vector2.Divide(localInflationAmount, size));
} }
Shader.Unbind(); Shader.Unbind();

View File

@ -11,6 +11,6 @@ namespace osu.Game.Graphics.Containers
public class ReverseDepthFillFlowContainer<T> : FillFlowContainer<T> where T : Drawable public class ReverseDepthFillFlowContainer<T> : FillFlowContainer<T> where T : Drawable
{ {
protected override IComparer<Drawable> DepthComparer => new ReverseCreationOrderDepthComparer(); protected override IComparer<Drawable> DepthComparer => new ReverseCreationOrderDepthComparer();
protected override IEnumerable<T> FlowingChildren => base.FlowingChildren.Reverse(); protected override IEnumerable<Drawable> FlowingChildren => base.FlowingChildren.Reverse();
} }
} }

View File

@ -93,6 +93,7 @@ namespace osu.Game.Graphics.Cursor
{ {
private Container cursorContainer; private Container cursorContainer;
private Bindable<double> cursorScale; private Bindable<double> cursorScale;
private const float base_scale = 0.15f;
public Sprite AdditiveLayer; public Sprite AdditiveLayer;
@ -108,17 +109,15 @@ namespace osu.Game.Graphics.Cursor
{ {
cursorContainer = new Container cursorContainer = new Container
{ {
Size = new Vector2(32), AutoSizeAxes = Axes.Both,
Children = new Drawable[] Children = new Drawable[]
{ {
new Sprite new Sprite
{ {
FillMode = FillMode.Fit,
Texture = textures.Get(@"Cursor/menu-cursor"), Texture = textures.Get(@"Cursor/menu-cursor"),
}, },
AdditiveLayer = new Sprite AdditiveLayer = new Sprite
{ {
FillMode = FillMode.Fit,
BlendingMode = BlendingMode.Additive, BlendingMode = BlendingMode.Additive,
Colour = colour.Pink, Colour = colour.Pink,
Alpha = 0, Alpha = 0,
@ -129,7 +128,7 @@ namespace osu.Game.Graphics.Cursor
}; };
cursorScale = config.GetBindable<double>(OsuSetting.MenuCursorSize); cursorScale = config.GetBindable<double>(OsuSetting.MenuCursorSize);
cursorScale.ValueChanged += newScale => cursorContainer.Scale = new Vector2((float)newScale); cursorScale.ValueChanged += newScale => cursorContainer.Scale = new Vector2((float)newScale * base_scale);
cursorScale.TriggerChange(); cursorScale.TriggerChange();
} }
} }

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 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.Cursor;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics.Sprites;
namespace osu.Game.Graphics.Cursor
{
public class OsuTooltipContainer : TooltipContainer
{
protected override Tooltip CreateTooltip() => new OsuTooltip();
public OsuTooltipContainer(CursorContainer cursor) : base(cursor)
{
}
public class OsuTooltip : Tooltip
{
private readonly Box background;
private readonly OsuSpriteText text;
private bool instantMovement = true;
public override string TooltipText
{
set
{
if (value == text.Text) return;
text.Text = value;
if (IsPresent)
{
AutoSizeDuration = 250;
background.FlashColour(OsuColour.Gray(0.4f), 1000, EasingTypes.OutQuint);
}
else
AutoSizeDuration = 0;
}
}
private const float text_size = 16;
public OsuTooltip()
{
AutoSizeEasing = EasingTypes.OutQuint;
CornerRadius = 5;
Masking = true;
EdgeEffect = new EdgeEffect
{
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(40),
Radius = 5,
};
Children = new Drawable[]
{
background = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0.9f,
},
text = new OsuSpriteText
{
TextSize = text_size,
Padding = new MarginPadding(5),
Font = @"Exo2.0-Regular",
}
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colour)
{
background.Colour = colour.Gray3;
}
protected override void PopIn()
{
instantMovement |= !IsPresent;
FadeIn(500, EasingTypes.OutQuint);
}
protected override void PopOut()
{
using (BeginDelayedSequence(150))
FadeOut(500, EasingTypes.OutQuint);
}
public override void Move(Vector2 pos)
{
if (instantMovement)
{
Position = pos;
instantMovement = false;
}
else
{
MoveTo(pos, 200, EasingTypes.OutQuint);
}
}
}
}
}

View File

@ -1,157 +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 OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input;
using osu.Framework.Threading;
using osu.Game.Graphics.Sprites;
using System.Linq;
namespace osu.Game.Graphics.Cursor
{
public class TooltipContainer : Container
{
private readonly CursorContainer cursor;
private readonly Tooltip tooltip;
private ScheduledDelegate findTooltipTask;
private UserInputManager inputManager;
private const int default_appear_delay = 220;
private IHasTooltip currentlyDisplayed;
public TooltipContainer(CursorContainer cursor)
{
this.cursor = cursor;
AlwaysPresent = true;
RelativeSizeAxes = Axes.Both;
Add(tooltip = new Tooltip { Alpha = 0 });
}
[BackgroundDependencyLoader]
private void load(UserInputManager input)
{
inputManager = input;
}
protected override void Update()
{
if (tooltip.IsPresent)
{
if (currentlyDisplayed != null)
tooltip.TooltipText = currentlyDisplayed.TooltipText;
//update the position of the displayed tooltip.
tooltip.Position = ToLocalSpace(cursor.ActiveCursor.ScreenSpaceDrawQuad.Centre) + new Vector2(10);
}
}
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
{
updateTooltipState(state);
return base.OnMouseUp(state, args);
}
protected override bool OnMouseMove(InputState state)
{
updateTooltipState(state);
return base.OnMouseMove(state);
}
private void updateTooltipState(InputState state)
{
if (currentlyDisplayed?.Hovering != true)
{
if (currentlyDisplayed != null && !state.Mouse.HasMainButtonPressed)
{
tooltip.Delay(150);
tooltip.FadeOut(500, EasingTypes.OutQuint);
currentlyDisplayed = null;
}
findTooltipTask?.Cancel();
findTooltipTask = Scheduler.AddDelayed(delegate
{
var tooltipTarget = inputManager.HoveredDrawables.OfType<IHasTooltip>().FirstOrDefault();
if (tooltipTarget == null) return;
tooltip.TooltipText = tooltipTarget.TooltipText;
tooltip.FadeIn(500, EasingTypes.OutQuint);
currentlyDisplayed = tooltipTarget;
}, (1 - tooltip.Alpha) * default_appear_delay);
}
}
public class Tooltip : Container
{
private readonly Box background;
private readonly OsuSpriteText text;
public string TooltipText
{
set
{
if (value == text.Text) return;
text.Text = value;
if (Alpha > 0)
{
AutoSizeDuration = 250;
background.FlashColour(OsuColour.Gray(0.4f), 1000, EasingTypes.OutQuint);
}
else
AutoSizeDuration = 0;
}
}
public override bool HandleInput => false;
private const float text_size = 16;
public Tooltip()
{
AutoSizeEasing = EasingTypes.OutQuint;
AutoSizeAxes = Axes.Both;
CornerRadius = 5;
Masking = true;
EdgeEffect = new EdgeEffect
{
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(40),
Radius = 5,
};
Children = new Drawable[]
{
background = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0.9f,
},
text = new OsuSpriteText
{
TextSize = text_size,
Padding = new MarginPadding(5),
Font = @"Exo2.0-Regular",
}
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colour)
{
background.Colour = colour.Gray3;
}
}
}
}

View File

@ -1,15 +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.Framework.Graphics;
namespace osu.Game.Graphics
{
public interface IHasTooltip : IDrawable
{
/// <summary>
/// Tooltip that shows when hovering the drawable
/// </summary>
string TooltipText { get; }
}
}

View File

@ -0,0 +1,84 @@
// 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;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using System.Linq;
namespace osu.Game.Graphics.UserInterface
{
public class BreadcrumbControl<T> : OsuTabControl<T>
{
private const float padding = 10;
protected override TabItem<T> CreateTabItem(T value) => new BreadcrumbTabItem(value);
public BreadcrumbControl()
{
Height = 26;
TabContainer.Spacing = new Vector2(padding, 0f);
Current.ValueChanged += tab =>
{
foreach (var t in TabContainer.Children.OfType<BreadcrumbTabItem>())
{
var tIndex = TabContainer.IndexOf(t);
var tabIndex = TabContainer.IndexOf(TabMap[tab]);
t.State = tIndex < tabIndex ? Visibility.Hidden : Visibility.Visible;
t.Chevron.FadeTo(tIndex <= tabIndex ? 0f : 1f, 500, EasingTypes.OutQuint);
}
};
}
private class BreadcrumbTabItem : OsuTabItem, IStateful<Visibility>
{
public readonly TextAwesome Chevron;
//don't allow clicking between transitions and don't make the chevron clickable
protected override bool InternalContains(Vector2 screenSpacePos) => Alpha == 1f && Text.Contains(screenSpacePos);
public override bool HandleInput => State == Visibility.Visible;
private Visibility state;
public Visibility State
{
get { return state; }
set
{
if (value == state) return;
state = value;
const float transition_duration = 500;
if (State == Visibility.Visible)
{
FadeIn(transition_duration, EasingTypes.OutQuint);
ScaleTo(new Vector2(1f), transition_duration, EasingTypes.OutQuint);
}
else
{
FadeOut(transition_duration, EasingTypes.OutQuint);
ScaleTo(new Vector2(0.8f, 1f), transition_duration, EasingTypes.OutQuint);
}
}
}
public BreadcrumbTabItem(T value) : base(value)
{
Text.TextSize = 16;
Padding = new MarginPadding { Right = padding + 8 }; //padding + chevron width
Add(Chevron = new TextAwesome
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreLeft,
TextSize = 12,
Icon = FontAwesome.fa_chevron_right,
Margin = new MarginPadding { Left = padding },
Alpha = 0f,
});
}
}
}
}

View File

@ -3,6 +3,7 @@
using OpenTK.Graphics; using OpenTK.Graphics;
using OpenTK.Input; using OpenTK.Input;
using osu.Framework.Allocation;
using osu.Framework.Input; using osu.Framework.Input;
using System; using System;
using System.Linq; using System.Linq;
@ -23,16 +24,23 @@ namespace osu.Game.Graphics.UserInterface
set set
{ {
focus = value; focus = value;
if (!focus) if (!focus && HasFocus)
TriggerFocusLost(); inputManager.ChangeFocus(null);
} }
} }
protected override bool OnFocus(InputState state) private InputManager inputManager;
[BackgroundDependencyLoader]
private void load(UserInputManager inputManager)
{ {
var result = base.OnFocus(state); this.inputManager = inputManager;
}
protected override void OnFocus(InputState state)
{
base.OnFocus(state);
BorderThickness = 0; BorderThickness = 0;
return result;
} }
protected override void OnFocusLost(InputState state) protected override void OnFocusLost(InputState state)
@ -47,6 +55,6 @@ namespace osu.Game.Graphics.UserInterface
base.OnFocusLost(state); base.OnFocusLost(state);
} }
public override bool RequestingFocus => HoldFocus; public override bool RequestsFocus => HoldFocus;
} }
} }

View File

@ -0,0 +1,114 @@
// 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 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.Framework.Input;
namespace osu.Game.Graphics.UserInterface
{
public class IconButton : ClickableContainer
{
private readonly TextAwesome icon;
private readonly Box hover;
private readonly Container content;
public FontAwesome Icon
{
get { return icon.Icon; }
set { icon.Icon = value; }
}
private const float button_size = 30;
private Color4 flashColour;
public Vector2 IconScale
{
get { return icon.Scale; }
set { icon.Scale = value; }
}
public IconButton()
{
AutoSizeAxes = Axes.Both;
Origin = Anchor.Centre;
Anchor = Anchor.Centre;
Children = new Drawable[]
{
content = new Container
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Size = new Vector2 (button_size),
CornerRadius = 5,
Masking = true,
EdgeEffect = new EdgeEffect
{
Colour = Color4.Black.Opacity(0.04f),
Type = EdgeEffectType.Shadow,
Radius = 5,
},
Children = new Drawable[]
{
hover = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
},
icon = new TextAwesome
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
TextSize = 18,
}
}
}
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
hover.Colour = colours.Yellow.Opacity(0.6f);
flashColour = colours.Yellow;
}
protected override bool OnHover(InputState state)
{
hover.FadeIn(500, EasingTypes.OutQuint);
return base.OnHover(state);
}
protected override void OnHoverLost(InputState state)
{
hover.FadeOut(500, EasingTypes.OutQuint);
base.OnHoverLost(state);
}
protected override bool OnClick(InputState state)
{
hover.FlashColour(flashColour, 800, EasingTypes.OutQuint);
return base.OnClick(state);
}
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{
content.ScaleTo(0.75f, 2000, EasingTypes.OutQuint);
return base.OnMouseDown(state, args);
}
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
{
content.ScaleTo(1, 1000, EasingTypes.OutElastic);
return base.OnMouseUp(state, args);
}
}
}

View File

@ -12,13 +12,12 @@ using osu.Framework.Graphics.UserInterface;
namespace osu.Game.Graphics.UserInterface namespace osu.Game.Graphics.UserInterface
{ {
public class Nub : CircularContainer, IHasCurrentValue<bool> public class Nub : CircularContainer, IHasCurrentValue<bool>, IHasAccentColour
{ {
public const float COLLAPSED_SIZE = 20; public const float COLLAPSED_SIZE = 20;
public const float EXPANDED_SIZE = 40; public const float EXPANDED_SIZE = 40;
private const float border_width = 3; private const float border_width = 3;
private Color4 glowingColour, idleColour;
public Nub() public Nub()
{ {
@ -53,33 +52,41 @@ namespace osu.Game.Graphics.UserInterface
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
Colour = idleColour = colours.Pink; AccentColour = colours.Pink;
glowingColour = colours.PinkLighter; GlowingAccentColour = colours.PinkLighter;
GlowColour = colours.PinkDarker;
EdgeEffect = new EdgeEffect EdgeEffect = new EdgeEffect
{ {
Colour = colours.PinkDarker, Colour = GlowColour,
Type = EdgeEffectType.Glow, Type = EdgeEffectType.Glow,
Radius = 10, Radius = 10,
Roundness = 8, Roundness = 8,
}; };
}
protected override void LoadComplete()
{
FadeEdgeEffectTo(0); FadeEdgeEffectTo(0);
} }
private bool glowing;
public bool Glowing public bool Glowing
{ {
get { return glowing; }
set set
{ {
glowing = value;
if (value) if (value)
{ {
FadeColour(glowingColour, 500, EasingTypes.OutQuint); FadeColour(GlowingAccentColour, 500, EasingTypes.OutQuint);
FadeEdgeEffectTo(1, 500, EasingTypes.OutQuint); FadeEdgeEffectTo(1, 500, EasingTypes.OutQuint);
} }
else else
{ {
FadeEdgeEffectTo(0, 500); FadeEdgeEffectTo(0, 500);
FadeColour(idleColour, 500); FadeColour(AccentColour, 500);
} }
} }
} }
@ -93,5 +100,43 @@ namespace osu.Game.Graphics.UserInterface
} }
public Bindable<bool> Current { get; } = new Bindable<bool>(); public Bindable<bool> Current { get; } = new Bindable<bool>();
private Color4 accentColour;
public Color4 AccentColour
{
get { return accentColour; }
set
{
accentColour = value;
if (!Glowing)
Colour = value;
}
}
private Color4 glowingAccentColour;
public Color4 GlowingAccentColour
{
get { return glowingAccentColour; }
set
{
glowingAccentColour = value;
if (Glowing)
Colour = value;
}
}
private Color4 glowColour;
public Color4 GlowColour
{
get { return glowColour; }
set
{
glowColour = value;
var effect = EdgeEffect;
effect.Colour = value;
EdgeEffect = effect;
}
}
} }
} }

View File

@ -51,7 +51,8 @@ namespace osu.Game.Graphics.UserInterface
} }
} }
private readonly Nub nub; protected readonly Nub Nub;
private readonly SpriteText labelSpriteText; private readonly SpriteText labelSpriteText;
private SampleChannel sampleChecked; private SampleChannel sampleChecked;
private SampleChannel sampleUnchecked; private SampleChannel sampleUnchecked;
@ -64,7 +65,7 @@ namespace osu.Game.Graphics.UserInterface
Children = new Drawable[] Children = new Drawable[]
{ {
labelSpriteText = new OsuSpriteText(), labelSpriteText = new OsuSpriteText(),
nub = new Nub Nub = new Nub
{ {
Anchor = Anchor.CentreRight, Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight, Origin = Anchor.CentreRight,
@ -72,7 +73,7 @@ namespace osu.Game.Graphics.UserInterface
} }
}; };
nub.Current.BindTo(Current); Nub.Current.BindTo(Current);
Current.ValueChanged += newValue => Current.ValueChanged += newValue =>
{ {
@ -90,15 +91,15 @@ namespace osu.Game.Graphics.UserInterface
protected override bool OnHover(InputState state) protected override bool OnHover(InputState state)
{ {
nub.Glowing = true; Nub.Glowing = true;
nub.Expanded = true; Nub.Expanded = true;
return base.OnHover(state); return base.OnHover(state);
} }
protected override void OnHoverLost(InputState state) protected override void OnHoverLost(InputState state)
{ {
nub.Glowing = false; Nub.Glowing = false;
nub.Expanded = false; Nub.Expanded = false;
base.OnHoverLost(state); base.OnHoverLost(state);
} }

View File

@ -72,7 +72,7 @@ namespace osu.Game.Graphics.UserInterface
Origin = Anchor.CentreLeft, Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
}, },
new OsuSpriteText { Label = new OsuSpriteText {
Text = text, Text = text,
Origin = Anchor.CentreLeft, Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
@ -85,6 +85,7 @@ namespace osu.Game.Graphics.UserInterface
private Color4? accentColour; private Color4? accentColour;
protected readonly TextAwesome Chevron; protected readonly TextAwesome Chevron;
protected readonly OsuSpriteText Label;
protected override void FormatForeground(bool hover = false) protected override void FormatForeground(bool hover = false)
{ {
@ -170,4 +171,4 @@ namespace osu.Game.Graphics.UserInterface
} }
} }
} }
} }

View File

@ -3,6 +3,7 @@
using System; using System;
using OpenTK; using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Audio.Sample; using osu.Framework.Audio.Sample;
@ -11,17 +12,18 @@ 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.Framework.Graphics.Cursor;
namespace osu.Game.Graphics.UserInterface namespace osu.Game.Graphics.UserInterface
{ {
public class OsuSliderBar<T> : SliderBar<T>, IHasTooltip public class OsuSliderBar<T> : SliderBar<T>, IHasTooltip, IHasAccentColour
where T : struct, IEquatable<T> where T : struct, IEquatable<T>
{ {
private SampleChannel sample; private SampleChannel sample;
private double lastSampleTime; private double lastSampleTime;
private T lastSampleValue; private T lastSampleValue;
private readonly Nub nub; protected readonly Nub Nub;
private readonly Box leftBox; private readonly Box leftBox;
private readonly Box rightBox; private readonly Box rightBox;
@ -45,6 +47,18 @@ namespace osu.Game.Graphics.UserInterface
} }
} }
private Color4 accentColour;
public Color4 AccentColour
{
get { return accentColour; }
set
{
accentColour = value;
leftBox.Colour = value;
rightBox.Colour = value;
}
}
public OsuSliderBar() public OsuSliderBar()
{ {
Height = 12; Height = 12;
@ -70,7 +84,7 @@ namespace osu.Game.Graphics.UserInterface
Origin = Anchor.CentreRight, Origin = Anchor.CentreRight,
Alpha = 0.5f, Alpha = 0.5f,
}, },
nub = new Nub Nub = new Nub
{ {
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
Expanded = true, Expanded = true,
@ -87,19 +101,18 @@ namespace osu.Game.Graphics.UserInterface
private void load(AudioManager audio, OsuColour colours) private void load(AudioManager audio, OsuColour colours)
{ {
sample = audio.Sample.Get(@"Sliderbar/sliderbar"); sample = audio.Sample.Get(@"Sliderbar/sliderbar");
leftBox.Colour = colours.Pink; AccentColour = colours.Pink;
rightBox.Colour = colours.Pink;
} }
protected override bool OnHover(InputState state) protected override bool OnHover(InputState state)
{ {
nub.Glowing = true; Nub.Glowing = true;
return base.OnHover(state); return base.OnHover(state);
} }
protected override void OnHoverLost(InputState state) protected override void OnHoverLost(InputState state)
{ {
nub.Glowing = false; Nub.Glowing = false;
base.OnHoverLost(state); base.OnHoverLost(state);
} }
@ -132,13 +145,13 @@ namespace osu.Game.Graphics.UserInterface
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{ {
nub.Current.Value = true; Nub.Current.Value = true;
return base.OnMouseDown(state, args); return base.OnMouseDown(state, args);
} }
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
{ {
nub.Current.Value = false; Nub.Current.Value = false;
return base.OnMouseUp(state, args); return base.OnMouseUp(state, args);
} }
@ -146,14 +159,14 @@ namespace osu.Game.Graphics.UserInterface
{ {
base.UpdateAfterChildren(); base.UpdateAfterChildren();
leftBox.Scale = new Vector2(MathHelper.Clamp( leftBox.Scale = new Vector2(MathHelper.Clamp(
nub.DrawPosition.X - nub.DrawWidth / 2, 0, DrawWidth), 1); Nub.DrawPosition.X - Nub.DrawWidth / 2, 0, DrawWidth), 1);
rightBox.Scale = new Vector2(MathHelper.Clamp( rightBox.Scale = new Vector2(MathHelper.Clamp(
DrawWidth - nub.DrawPosition.X - nub.DrawWidth / 2, 0, DrawWidth), 1); DrawWidth - Nub.DrawPosition.X - Nub.DrawWidth / 2, 0, DrawWidth), 1);
} }
protected override void UpdateValue(float value) protected override void UpdateValue(float value)
{ {
nub.MoveToX(RangePadding + UsableWidth * value, 250, EasingTypes.OutQuint); Nub.MoveToX(RangePadding + UsableWidth * value, 250, EasingTypes.OutQuint);
} }
} }
} }

View File

@ -45,11 +45,10 @@ namespace osu.Game.Graphics.UserInterface
BorderColour = colour.Yellow; BorderColour = colour.Yellow;
} }
protected override bool OnFocus(InputState state) protected override void OnFocus(InputState state)
{ {
BorderThickness = 3; BorderThickness = 3;
base.OnFocus(state);
return base.OnFocus(state);
} }
protected override void OnFocusLost(InputState state) protected override void OnFocusLost(InputState state)

View File

@ -5,18 +5,21 @@ using osu.Framework.Audio.Sample;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Transforms;
using osu.Framework.Input; using osu.Framework.Input;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Game.Graphics.Containers;
using osu.Game.Beatmaps.ControlPoints;
using osu.Framework.Audio.Track;
using System;
namespace osu.Game.Graphics.UserInterface namespace osu.Game.Graphics.UserInterface
{ {
public class TwoLayerButton : ClickableContainer public class TwoLayerButton : ClickableContainer
{ {
private readonly TextAwesome icon; private readonly BouncingIcon bouncingIcon;
public Box IconLayer; public Box IconLayer;
public Box TextLayer; public Box TextLayer;
@ -95,11 +98,10 @@ namespace osu.Game.Graphics.UserInterface
}, },
} }
}, },
icon = new TextAwesome bouncingIcon = new BouncingIcon
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
TextSize = 25,
}, },
} }
}, },
@ -146,7 +148,7 @@ namespace osu.Game.Graphics.UserInterface
{ {
set set
{ {
icon.Icon = value; bouncingIcon.Icon = value;
} }
} }
@ -162,58 +164,20 @@ namespace osu.Game.Graphics.UserInterface
protected override bool OnHover(InputState state) protected override bool OnHover(InputState state)
{ {
icon.ClearTransforms();
ResizeTo(SIZE_EXTENDED, transform_time, EasingTypes.OutElastic); ResizeTo(SIZE_EXTENDED, transform_time, EasingTypes.OutElastic);
int duration = 0; //(int)(Game.Audio.BeatLength / 2);
if (duration == 0) duration = pulse_length;
IconLayer.FadeColour(HoverColour, transform_time, EasingTypes.OutElastic); IconLayer.FadeColour(HoverColour, transform_time, EasingTypes.OutElastic);
const double offset = 0; //(1 - Game.Audio.SyncBeatProgress) * duration; bouncingIcon.ScaleTo(1.1f, transform_time, EasingTypes.OutElastic);
double startTime = Time.Current + offset;
// basic pulse
icon.Transforms.Add(new TransformScale
{
StartValue = new Vector2(1.1f),
EndValue = Vector2.One,
StartTime = startTime,
EndTime = startTime + duration,
Easing = EasingTypes.Out,
LoopCount = -1,
LoopDelay = duration
});
return true; return true;
} }
protected override void OnHoverLost(InputState state) protected override void OnHoverLost(InputState state)
{ {
icon.ClearTransforms();
ResizeTo(SIZE_RETRACTED, transform_time, EasingTypes.OutElastic); ResizeTo(SIZE_RETRACTED, transform_time, EasingTypes.OutElastic);
IconLayer.FadeColour(TextLayer.Colour, transform_time, EasingTypes.OutElastic); IconLayer.FadeColour(TextLayer.Colour, transform_time, EasingTypes.OutElastic);
int duration = 0; //(int)(Game.Audio.BeatLength); bouncingIcon.ScaleTo(1, transform_time, EasingTypes.OutElastic);
if (duration == 0) duration = pulse_length * 2;
const double offset = 0; //(1 - Game.Audio.SyncBeatProgress) * duration;
double startTime = Time.Current + offset;
// slow pulse
icon.Transforms.Add(new TransformScale
{
StartValue = new Vector2(1.1f),
EndValue = Vector2.One,
StartTime = startTime,
EndTime = startTime + duration,
Easing = EasingTypes.Out,
LoopCount = -1,
LoopDelay = duration
});
} }
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
@ -239,5 +203,45 @@ namespace osu.Game.Graphics.UserInterface
return base.OnClick(state); return base.OnClick(state);
} }
private class BouncingIcon : BeatSyncedContainer
{
private const double beat_in_time = 60;
private readonly TextAwesome icon;
public FontAwesome Icon { set { icon.Icon = value; } }
public BouncingIcon()
{
EarlyActivationMilliseconds = beat_in_time;
AutoSizeAxes = Axes.Both;
Children = new Drawable[]
{
icon = new TextAwesome
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
TextSize = 25
}
};
}
protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes)
{
base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes);
var beatLength = timingPoint.BeatLength;
float amplitudeAdjust = Math.Min(1, 0.4f + amplitudes.Maximum);
if (beatIndex < 0) return;
icon.ScaleTo(1 - 0.1f * amplitudeAdjust, beat_in_time, EasingTypes.Out);
using (icon.BeginDelayedSequence(beat_in_time))
icon.ScaleTo(1, beatLength * 2, EasingTypes.OutQuint);
}
}
} }
} }

View File

@ -74,7 +74,7 @@ namespace osu.Game.Graphics.UserInterface.Volume
return; return;
} }
volumeMeterMaster.TriggerWheel(state); volumeMeterMaster.TriggerOnWheel(state);
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]

View File

@ -255,6 +255,9 @@ namespace osu.Game
settings.ToggleVisibility(); settings.ToggleVisibility();
return true; return true;
case Key.D: case Key.D:
if (state.Keyboard.ShiftPressed || state.Keyboard.AltPressed)
return false;
direct.ToggleVisibility(); direct.ToggleVisibility();
return true; return true;
} }

View File

@ -158,7 +158,7 @@ namespace osu.Game
Children = new Drawable[] Children = new Drawable[]
{ {
Cursor = new MenuCursor(), Cursor = new MenuCursor(),
new TooltipContainer(Cursor) { Depth = -1 }, new OsuTooltipContainer(Cursor) { Depth = -1 },
} }
}, },
} }

View File

@ -14,6 +14,7 @@ using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Chat; using osu.Game.Online.Chat;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Framework.Configuration;
namespace osu.Game.Overlays.Chat namespace osu.Game.Overlays.Chat
{ {
@ -23,6 +24,8 @@ namespace osu.Game.Overlays.Chat
private const float shear_width = 10; private const float shear_width = 10;
public readonly Bindable<bool> ChannelSelectorActive = new Bindable<bool>();
public ChatTabControl() public ChatTabControl()
{ {
TabContainer.Margin = new MarginPadding { Left = 50 }; TabContainer.Margin = new MarginPadding { Left = 50 };
@ -37,6 +40,8 @@ namespace osu.Game.Overlays.Chat
TextSize = 20, TextSize = 20,
Padding = new MarginPadding(10), Padding = new MarginPadding(10),
}); });
AddTabItem(new ChannelTabItem.ChannelSelectorTabItem(new Channel { Name = "+" }, ChannelSelectorActive));
} }
private class ChannelTabItem : TabItem<Channel> private class ChannelTabItem : TabItem<Channel>
@ -49,6 +54,7 @@ namespace osu.Game.Overlays.Chat
private readonly SpriteText textBold; private readonly SpriteText textBold;
private readonly Box box; private readonly Box box;
private readonly Box highlightBox; private readonly Box highlightBox;
private readonly TextAwesome icon;
public override bool Active public override bool Active
{ {
@ -114,6 +120,11 @@ namespace osu.Game.Overlays.Chat
backgroundHover = colours.Gray7; backgroundHover = colours.Gray7;
highlightBox.Colour = colours.Yellow; highlightBox.Colour = colours.Yellow;
}
protected override void LoadComplete()
{
base.LoadComplete();
updateState(); updateState();
} }
@ -159,7 +170,7 @@ namespace osu.Game.Overlays.Chat
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Children = new Drawable[] Children = new Drawable[]
{ {
new TextAwesome icon = new TextAwesome
{ {
Icon = FontAwesome.fa_hashtag, Icon = FontAwesome.fa_hashtag,
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
@ -191,6 +202,40 @@ namespace osu.Game.Overlays.Chat
} }
}; };
} }
public class ChannelSelectorTabItem : ChannelTabItem
{
public override bool Active
{
get { return base.Active; }
set
{
activeBindable.Value = value;
base.Active = value;
}
}
private readonly Bindable<bool> activeBindable;
public ChannelSelectorTabItem(Channel value, Bindable<bool> active) : base(value)
{
activeBindable = active;
Depth = float.MaxValue;
Width = 45;
icon.Alpha = 0;
text.TextSize = 45;
textBold.TextSize = 45;
}
[BackgroundDependencyLoader]
private new void load(OsuColour colour)
{
backgroundInactive = colour.Gray2;
backgroundActive = colour.Gray3;
}
}
} }
} }
} }

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Online.Chat; using osu.Game.Online.Chat;
@ -43,11 +44,15 @@ namespace osu.Game.Overlays.Chat
channel.NewMessagesArrived += newMessagesArrived; channel.NewMessagesArrived += newMessagesArrived;
} }
[BackgroundDependencyLoader]
private void load()
{
newMessagesArrived(Channel.Messages);
}
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
newMessagesArrived(Channel.Messages);
scrollToEnd(); scrollToEnd();
} }
@ -59,13 +64,13 @@ namespace osu.Game.Overlays.Chat
private void newMessagesArrived(IEnumerable<Message> newMessages) private void newMessagesArrived(IEnumerable<Message> newMessages)
{ {
if (!IsLoaded) return;
var displayMessages = newMessages.Skip(Math.Max(0, newMessages.Count() - Channel.MAX_HISTORY)); var displayMessages = newMessages.Skip(Math.Max(0, newMessages.Count() - Channel.MAX_HISTORY));
//up to last Channel.MAX_HISTORY messages //up to last Channel.MAX_HISTORY messages
flow.Add(displayMessages.Select(m => new ChatLine(m))); flow.Add(displayMessages.Select(m => new ChatLine(m)));
if (!IsLoaded) return;
if (scroll.IsScrolledToEnd(10) || !flow.Children.Any()) if (scroll.IsScrolledToEnd(10) || !flow.Children.Any())
scrollToEnd(); scrollToEnd();

View File

@ -135,17 +135,22 @@ namespace osu.Game.Overlays
channelTabs.Current.ValueChanged += newChannel => CurrentChannel = newChannel; channelTabs.Current.ValueChanged += newChannel => CurrentChannel = newChannel;
} }
private double startDragChatHeight;
protected override bool OnDragStart(InputState state) protected override bool OnDragStart(InputState state)
{ {
if (channelTabs.Hovering) if (!channelTabs.Hovering)
return true; return base.OnDragStart(state);
return base.OnDragStart(state); startDragChatHeight = chatHeight.Value;
return true;
} }
protected override bool OnDrag(InputState state) protected override bool OnDrag(InputState state)
{ {
chatHeight.Value = Height - state.Mouse.Delta.Y / Parent.DrawSize.Y; Trace.Assert(state.Mouse.PositionMouseDown != null);
chatHeight.Value = startDragChatHeight - (state.Mouse.Position.Y - state.Mouse.PositionMouseDown.Value.Y) / Parent.DrawSize.Y;
return base.OnDrag(state); return base.OnDrag(state);
} }
@ -162,11 +167,15 @@ namespace osu.Game.Overlays
} }
} }
protected override bool OnFocus(InputState state) public override bool AcceptsFocus => true;
protected override bool OnClick(InputState state) => true;
protected override void OnFocus(InputState state)
{ {
//this is necessary as inputTextBox is masked away and therefore can't get focus :( //this is necessary as inputTextBox is masked away and therefore can't get focus :(
inputTextBox.TriggerFocus(); InputManager.ChangeFocus(inputTextBox);
return false; base.OnFocus(state);
} }
protected override void PopIn() protected override void PopIn()
@ -255,20 +264,32 @@ namespace osu.Game.Overlays
{ {
if (currentChannel == value) return; if (currentChannel == value) return;
if (currentChannel != null) if (channelTabs.ChannelSelectorActive) return;
currentChannelContainer.Clear(false);
currentChannel = value; currentChannel = value;
inputTextBox.Current.Disabled = currentChannel.ReadOnly;
channelTabs.Current.Value = value;
var loaded = loadedChannels.Find(d => d.Channel == value); var loaded = loadedChannels.Find(d => d.Channel == value);
if (loaded == null) if (loaded == null)
loadedChannels.Add(loaded = new DrawableChannel(currentChannel)); {
currentChannelContainer.FadeOut(500, EasingTypes.OutQuint);
inputTextBox.Current.Disabled = currentChannel.ReadOnly; loaded = new DrawableChannel(currentChannel);
loadedChannels.Add(loaded);
currentChannelContainer.Add(loaded); LoadComponentAsync(loaded, l =>
{
channelTabs.Current.Value = value; currentChannelContainer.Clear(false);
currentChannelContainer.Add(l);
currentChannelContainer.FadeIn(500, EasingTypes.OutQuint);
});
}
else
{
currentChannelContainer.Clear(false);
currentChannelContainer.Add(loaded);
}
} }
} }

View File

@ -71,7 +71,7 @@ namespace osu.Game.Overlays.Dialog
private void pressButtonAtIndex(int index) private void pressButtonAtIndex(int index)
{ {
if (index < Buttons.Count()) if (index < Buttons.Count())
Buttons.Skip(index).First().TriggerClick(); Buttons.Skip(index).First().TriggerOnClick();
} }
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
@ -80,7 +80,7 @@ namespace osu.Game.Overlays.Dialog
if (args.Key == Key.Enter) if (args.Key == Key.Enter)
{ {
Buttons.OfType<PopupDialogOkButton>().FirstOrDefault()?.TriggerClick(); Buttons.OfType<PopupDialogOkButton>().FirstOrDefault()?.TriggerOnClick();
return true; return true;
} }

View File

@ -98,7 +98,7 @@ namespace osu.Game.Overlays
new ScrollContainer new ScrollContainer
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
ScrollDraggerVisible = false, ScrollbarVisible = false,
Children = new Drawable[] Children = new Drawable[]
{ {
new FillFlowContainer new FillFlowContainer
@ -187,10 +187,14 @@ 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));
} }
protected override bool OnFocus(InputState state) public override bool AcceptsFocus => true;
protected override bool OnClick(InputState state) => true;
protected override void OnFocus(InputState state)
{ {
filter.Search.TriggerFocus(); InputManager.ChangeFocus(filter.Search);
return false; base.OnFocus(state);
} }
protected override void PopIn() protected override void PopIn()

View File

@ -66,7 +66,7 @@ namespace osu.Game.Overlays
settingsSection.Bounding = true; settingsSection.Bounding = true;
FadeIn(transition_time, EasingTypes.OutQuint); FadeIn(transition_time, EasingTypes.OutQuint);
settingsSection.TriggerFocus(); InputManager.ChangeFocus(settingsSection);
} }
protected override void PopOut() protected override void PopOut()

View File

@ -16,7 +16,7 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using System; using System;
using System.Linq; using System.Linq;
using osu.Game.Graphics; using osu.Framework.Graphics.Cursor;
namespace osu.Game.Overlays.Mods namespace osu.Game.Overlays.Mods
{ {
@ -27,6 +27,7 @@ namespace osu.Game.Overlays.Mods
public class ModButton : ModButtonEmpty, IHasTooltip public class ModButton : ModButtonEmpty, IHasTooltip
{ {
private ModIcon foregroundIcon; private ModIcon foregroundIcon;
private ModIcon backgroundIcon;
private readonly SpriteText text; private readonly SpriteText text;
private readonly Container<ModIcon> iconsContainer; private readonly Container<ModIcon> iconsContainer;
private SampleChannel sampleOn, sampleOff; private SampleChannel sampleOn, sampleOff;
@ -35,38 +36,67 @@ namespace osu.Game.Overlays.Mods
public string TooltipText => (SelectedMod?.Description ?? Mods.FirstOrDefault()?.Description) ?? string.Empty; public string TooltipText => (SelectedMod?.Description ?? Mods.FirstOrDefault()?.Description) ?? string.Empty;
private int _selectedIndex = -1; private const EasingTypes mod_switch_easing = EasingTypes.InOutSine;
private int selectedIndex private const double mod_switch_duration = 120;
// A selected index of -1 means not selected.
private int selectedIndex = -1;
protected int SelectedIndex
{ {
get get
{ {
return _selectedIndex; return selectedIndex;
} }
set set
{ {
if (value == _selectedIndex) return; if (value == selectedIndex) return;
_selectedIndex = value;
int direction = value < selectedIndex ? -1 : 1;
bool beforeSelected = Selected;
Mod modBefore = SelectedMod ?? Mods[0];
if (value >= Mods.Length) if (value >= Mods.Length)
selectedIndex = -1;
else if (value < -1)
selectedIndex = Mods.Length - 1;
else
selectedIndex = value;
Mod modAfter = SelectedMod ?? Mods[0];
if (beforeSelected != Selected)
{ {
_selectedIndex = -1; iconsContainer.RotateTo(Selected ? 5f : 0f, 300, EasingTypes.OutElastic);
} iconsContainer.ScaleTo(Selected ? 1.1f : 1f, 300, EasingTypes.OutElastic);
else if (value <= -2) }
{
_selectedIndex = Mods.Length - 1; if (modBefore != modAfter)
{
const float rotate_angle = 16;
foregroundIcon.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;
using (iconsContainer.BeginDelayedSequence(mod_switch_duration, true))
{
foregroundIcon.RotateTo(-rotate_angle * direction);
foregroundIcon.RotateTo(0f, mod_switch_duration, mod_switch_easing);
backgroundIcon.RotateTo(rotate_angle * direction);
backgroundIcon.RotateTo(0f, mod_switch_duration, mod_switch_easing);
iconsContainer.Schedule(() => displayMod(modAfter));
}
} }
iconsContainer.RotateTo(Selected ? 5f : 0f, 300, EasingTypes.OutElastic);
iconsContainer.ScaleTo(Selected ? 1.1f : 1f, 300, EasingTypes.OutElastic);
foregroundIcon.Highlighted = Selected; foregroundIcon.Highlighted = Selected;
if (mod != null)
displayMod(SelectedMod ?? Mods[0]);
} }
} }
public bool Selected => selectedIndex != -1; public bool Selected => SelectedIndex != -1;
private Color4 selectedColour; private Color4 selectedColour;
public Color4 SelectedColour public Color4 SelectedColour
@ -117,7 +147,7 @@ namespace osu.Game.Overlays.Mods
// the mods from Mod, only multiple if Mod is a MultiMod // the mods from Mod, only multiple if Mod is a MultiMod
public override Mod SelectedMod => Mods.ElementAtOrDefault(selectedIndex); public override Mod SelectedMod => Mods.ElementAtOrDefault(SelectedIndex);
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(AudioManager audio) private void load(AudioManager audio)
@ -142,23 +172,25 @@ namespace osu.Game.Overlays.Mods
public void SelectNext() public void SelectNext()
{ {
(++selectedIndex == -1 ? sampleOff : sampleOn).Play(); (++SelectedIndex == -1 ? sampleOff : sampleOn).Play();
Action?.Invoke(SelectedMod); Action?.Invoke(SelectedMod);
} }
public void SelectPrevious() public void SelectPrevious()
{ {
(--selectedIndex == -1 ? sampleOff : sampleOn).Play(); (--SelectedIndex == -1 ? sampleOff : sampleOn).Play();
Action?.Invoke(SelectedMod); Action?.Invoke(SelectedMod);
} }
public void Deselect() public void Deselect()
{ {
selectedIndex = -1; SelectedIndex = -1;
} }
private void displayMod(Mod mod) private void displayMod(Mod mod)
{ {
if (backgroundIcon != null)
backgroundIcon.Icon = foregroundIcon.Icon;
foregroundIcon.Icon = mod.Icon; foregroundIcon.Icon = mod.Icon;
text.Text = mod.Name; text.Text = mod.Name;
} }
@ -170,17 +202,17 @@ namespace osu.Game.Overlays.Mods
{ {
iconsContainer.Add(new[] iconsContainer.Add(new[]
{ {
new ModIcon(Mods[0]) backgroundIcon = new ModIcon(Mods[1])
{ {
Origin = Anchor.Centre, Origin = Anchor.BottomRight,
Anchor = Anchor.Centre, Anchor = Anchor.BottomRight,
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Position = new Vector2(1.5f), Position = new Vector2(1.5f),
}, },
foregroundIcon = new ModIcon(Mods[0]) foregroundIcon = new ModIcon(Mods[0])
{ {
Origin = Anchor.Centre, Origin = Anchor.BottomRight,
Anchor = Anchor.Centre, Anchor = Anchor.BottomRight,
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Position = new Vector2(-1.5f), Position = new Vector2(-1.5f),
}, },

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 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.UserInterface;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.Music
{
public class CollectionsDropdown<T> : OsuDropdown<T>
{
protected override DropdownHeader CreateHeader() => new CollectionsHeader { AccentColour = AccentColour };
protected override Menu CreateMenu() => new CollectionsMenu();
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
AccentColour = colours.Gray6;
}
private class CollectionsHeader : OsuDropdownHeader
{
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
BackgroundColour = colours.Gray4;
}
public CollectionsHeader()
{
CornerRadius = 5;
Height = 30;
Icon.TextSize = 14;
Icon.Margin = new MarginPadding(0);
Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 10, Right = 10 };
EdgeEffect = new EdgeEffect
{
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(0.3f),
Radius = 3,
Offset = new Vector2(0f, 1f),
};
}
}
private class CollectionsMenu : OsuMenu
{
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Background.Colour = colours.Gray4;
}
public CollectionsMenu()
{
CornerRadius = 5;
EdgeEffect = new EdgeEffect
{
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(0.3f),
Radius = 3,
Offset = new Vector2(0f, 1f),
};
}
}
}
}

View File

@ -3,10 +3,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Allocation; using osu.Framework.Allocation;
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.UserInterface;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using OpenTK; using OpenTK;
@ -74,63 +72,5 @@ namespace osu.Game.Overlays.Music
backgroundColour = colours.Gray2; backgroundColour = colours.Gray2;
} }
} }
private class CollectionsDropdown<T> : OsuDropdown<T>
{
protected override DropdownHeader CreateHeader() => new CollectionsHeader { AccentColour = AccentColour };
protected override Menu CreateMenu() => new CollectionsMenu();
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
AccentColour = colours.Gray6;
}
private class CollectionsHeader : OsuDropdownHeader
{
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
BackgroundColour = colours.Gray4;
}
public CollectionsHeader()
{
CornerRadius = 5;
Height = 30;
Icon.TextSize = 14;
Icon.Margin = new MarginPadding(0);
Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 10, Right = 10 };
EdgeEffect = new EdgeEffect
{
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(0.3f),
Radius = 3,
Offset = new Vector2(0f, 1f),
};
}
}
private class CollectionsMenu : OsuMenu
{
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Background.Colour = colours.Gray4;
}
public CollectionsMenu()
{
CornerRadius = 5;
EdgeEffect = new EdgeEffect
{
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(0.3f),
Radius = 3,
Offset = new Vector2(0f, 1f),
};
}
}
}
} }
} }

View File

@ -135,7 +135,7 @@ namespace osu.Game.Overlays.Music
private bool matching = true; private bool matching = true;
public bool MatchingCurrentFilter public bool MatchingFilter
{ {
set set
{ {

View File

@ -22,7 +22,7 @@ namespace osu.Game.Overlays.Music
} }
} }
public BeatmapSetInfo FirstVisibleSet => items.Children.FirstOrDefault(i => i.MatchingCurrentFilter)?.BeatmapSetInfo; public BeatmapSetInfo FirstVisibleSet => items.Children.FirstOrDefault(i => i.MatchingFilter)?.BeatmapSetInfo;
private void itemSelected(BeatmapSetInfo b) private void itemSelected(BeatmapSetInfo b)
{ {
@ -75,7 +75,7 @@ namespace osu.Game.Overlays.Music
private class ItemSearchContainer : FillFlowContainer<PlaylistItem>, IHasFilterableChildren private class ItemSearchContainer : FillFlowContainer<PlaylistItem>, IHasFilterableChildren
{ {
public string[] FilterTerms => new string[] { }; public string[] FilterTerms => new string[] { };
public bool MatchingCurrentFilter public bool MatchingFilter
{ {
set set
{ {

View File

@ -17,6 +17,7 @@ using osu.Game.Graphics;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Framework.Extensions; using osu.Framework.Extensions;
using osu.Framework.Input;
namespace osu.Game.Overlays.Music namespace osu.Game.Overlays.Music
{ {
@ -35,10 +36,12 @@ namespace osu.Game.Overlays.Music
private readonly Bindable<WorkingBeatmap> beatmapBacking = new Bindable<WorkingBeatmap>(); private readonly Bindable<WorkingBeatmap> beatmapBacking = new Bindable<WorkingBeatmap>();
public IEnumerable<BeatmapSetInfo> BeatmapSets; public IEnumerable<BeatmapSetInfo> BeatmapSets;
private InputManager inputManager;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuGameBase game, BeatmapDatabase beatmaps, OsuColour colours) private void load(OsuGameBase game, BeatmapDatabase beatmaps, OsuColour colours, UserInputManager inputManager)
{ {
this.inputManager = inputManager;
this.beatmaps = beatmaps; this.beatmaps = beatmaps;
trackManager = game.Audio.Track; trackManager = game.Audio.Track;
@ -100,7 +103,7 @@ namespace osu.Game.Overlays.Music
protected override void PopIn() protected override void PopIn()
{ {
filter.Search.HoldFocus = true; filter.Search.HoldFocus = true;
Schedule(() => filter.Search.TriggerFocus()); Schedule(() => inputManager.ChangeFocus(filter.Search));
ResizeTo(new Vector2(1, playlist_height), transition_duration, EasingTypes.OutQuint); ResizeTo(new Vector2(1, playlist_height), transition_duration, EasingTypes.OutQuint);
FadeIn(transition_duration, EasingTypes.OutQuint); FadeIn(transition_duration, EasingTypes.OutQuint);

View File

@ -22,6 +22,7 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Framework.Threading; using osu.Framework.Threading;
using osu.Game.Overlays.Music; using osu.Game.Overlays.Music;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays namespace osu.Game.Overlays
{ {
@ -38,8 +39,8 @@ namespace osu.Game.Overlays
private Drawable currentBackground; private Drawable currentBackground;
private DragBar progressBar; private DragBar progressBar;
private Button playButton; private IconButton playButton;
private Button playlistButton; private IconButton playlistButton;
private SpriteText title, artist; private SpriteText title, artist;
@ -143,7 +144,7 @@ namespace osu.Game.Overlays
Anchor = Anchor.BottomCentre, Anchor = Anchor.BottomCentre,
Children = new Drawable[] Children = new Drawable[]
{ {
new FillFlowContainer<Button> new FillFlowContainer<IconButton>
{ {
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal, Direction = FillDirection.Horizontal,
@ -152,26 +153,26 @@ namespace osu.Game.Overlays
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Children = new[] Children = new[]
{ {
new Button new IconButton
{ {
Action = prev, Action = prev,
Icon = FontAwesome.fa_step_backward, Icon = FontAwesome.fa_step_backward,
}, },
playButton = new Button playButton = new IconButton
{ {
Scale = new Vector2(1.4f), Scale = new Vector2(1.4f),
IconScale = new Vector2(1.4f), IconScale = new Vector2(1.4f),
Action = play, Action = play,
Icon = FontAwesome.fa_play_circle_o, Icon = FontAwesome.fa_play_circle_o,
}, },
new Button new IconButton
{ {
Action = next, Action = next,
Icon = FontAwesome.fa_step_forward, Icon = FontAwesome.fa_step_forward,
}, },
} }
}, },
playlistButton = new Button playlistButton = new IconButton
{ {
Origin = Anchor.Centre, Origin = Anchor.Centre,
Anchor = Anchor.CentreRight, Anchor = Anchor.CentreRight,
@ -266,24 +267,27 @@ namespace osu.Game.Overlays
{ {
progressBar.IsEnabled = beatmap != null; progressBar.IsEnabled = beatmap != null;
bool audioEquals = beatmapBacking.Value?.BeatmapInfo?.AudioEquals(current?.BeatmapInfo) ?? false; TransformDirection direction = TransformDirection.None;
TransformDirection direction; if (current != null)
if (audioEquals)
direction = TransformDirection.None;
else if (queuedDirection.HasValue)
{ {
direction = queuedDirection.Value; bool audioEquals = beatmapBacking.Value?.BeatmapInfo?.AudioEquals(current.BeatmapInfo) ?? false;
queuedDirection = null;
}
else
{
//figure out the best direction based on order in playlist.
var last = current == null ? -1 : playlist.BeatmapSets.TakeWhile(b => b.ID != current.BeatmapSetInfo.ID).Count();
var next = beatmapBacking.Value == null ? -1 : playlist.BeatmapSets.TakeWhile(b => b.ID != beatmapBacking.Value.BeatmapSetInfo.ID).Count();
direction = last > next ? TransformDirection.Prev : TransformDirection.Next; if (audioEquals)
direction = TransformDirection.None;
else if (queuedDirection.HasValue)
{
direction = queuedDirection.Value;
queuedDirection = null;
}
else
{
//figure out the best direction based on order in playlist.
var last = playlist.BeatmapSets.TakeWhile(b => b.ID != current.BeatmapSetInfo.ID).Count();
var next = beatmapBacking.Value == null ? -1 : playlist.BeatmapSets.TakeWhile(b => b.ID != beatmapBacking.Value.BeatmapSetInfo.ID).Count();
direction = last > next ? TransformDirection.Prev : TransformDirection.Next;
}
} }
current = beatmapBacking.Value; current = beatmapBacking.Value;
@ -412,105 +416,5 @@ namespace osu.Game.Overlays
sprite.Texture = beatmap?.Background ?? textures.Get(@"Backgrounds/bg4"); sprite.Texture = beatmap?.Background ?? textures.Get(@"Backgrounds/bg4");
} }
} }
private class Button : ClickableContainer
{
private readonly TextAwesome icon;
private readonly Box hover;
private readonly Container content;
public FontAwesome Icon
{
get { return icon.Icon; }
set { icon.Icon = value; }
}
private const float button_size = 30;
private Color4 flashColour;
public Vector2 IconScale
{
get { return icon.Scale; }
set { icon.Scale = value; }
}
public Button()
{
AutoSizeAxes = Axes.Both;
Origin = Anchor.Centre;
Anchor = Anchor.Centre;
Children = new Drawable[]
{
content = new Container
{
Size = new Vector2(button_size),
CornerRadius = 5,
Masking = true,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
EdgeEffect = new EdgeEffect
{
Colour = Color4.Black.Opacity(0.04f),
Type = EdgeEffectType.Shadow,
Radius = 5,
},
Children = new Drawable[]
{
hover = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
},
icon = new TextAwesome
{
TextSize = 18,
Origin = Anchor.Centre,
Anchor = Anchor.Centre
}
}
}
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
hover.Colour = colours.Yellow.Opacity(0.6f);
flashColour = colours.Yellow;
}
protected override bool OnHover(InputState state)
{
hover.FadeIn(500, EasingTypes.OutQuint);
return base.OnHover(state);
}
protected override void OnHoverLost(InputState state)
{
hover.FadeOut(500, EasingTypes.OutQuint);
base.OnHoverLost(state);
}
protected override bool OnClick(InputState state)
{
hover.FlashColour(flashColour, 800, EasingTypes.OutQuint);
return base.OnClick(state);
}
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{
content.ScaleTo(0.75f, 2000, EasingTypes.OutQuint);
return base.OnMouseDown(state, args);
}
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
{
content.ScaleTo(1, 1000, EasingTypes.OutElastic);
return base.OnMouseUp(state, args);
}
}
} }
} }

View File

@ -27,6 +27,11 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
LabelText = "up to", LabelText = "up to",
Bindable = config.GetBindable<double>(OsuSetting.DisplayStarsMaximum) Bindable = config.GetBindable<double>(OsuSetting.DisplayStarsMaximum)
}, },
new SettingsEnumDropdown<SelectionRandomType>
{
LabelText = "Random beatmap selection",
Bindable = config.GetBindable<SelectionRandomType>(OsuSetting.SelectionRandomType),
},
}; };
} }

View File

@ -51,9 +51,12 @@ namespace osu.Game.Overlays.Settings.Sections.General
Spacing = new Vector2(0f, 5f); Spacing = new Vector2(0f, 5f);
} }
private InputManager inputManager;
[BackgroundDependencyLoader(permitNulls: true)] [BackgroundDependencyLoader(permitNulls: true)]
private void load(OsuColour colours, APIAccess api) private void load(OsuColour colours, APIAccess api, UserInputManager inputManager)
{ {
this.inputManager = inputManager;
this.colours = colours; this.colours = colours;
api?.Register(this); api?.Register(this);
} }
@ -160,13 +163,17 @@ namespace osu.Game.Overlays.Settings.Sections.General
break; break;
} }
form?.TriggerFocus(); if (form != null) inputManager.ChangeFocus(form);
} }
protected override bool OnFocus(InputState state) public override bool AcceptsFocus => true;
protected override bool OnClick(InputState state) => true;
protected override void OnFocus(InputState state)
{ {
form?.TriggerFocus(); if (form != null) inputManager.ChangeFocus(form);
return base.OnFocus(state); base.OnFocus(state);
} }
private class LoginForm : FillFlowContainer private class LoginForm : FillFlowContainer
@ -174,6 +181,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
private TextBox username; private TextBox username;
private TextBox password; private TextBox password;
private APIAccess api; private APIAccess api;
private InputManager inputManager;
private void performLogin() private void performLogin()
{ {
@ -182,8 +190,9 @@ namespace osu.Game.Overlays.Settings.Sections.General
} }
[BackgroundDependencyLoader(permitNulls: true)] [BackgroundDependencyLoader(permitNulls: true)]
private void load(APIAccess api, OsuConfigManager config) private void load(APIAccess api, OsuConfigManager config, UserInputManager inputManager)
{ {
this.inputManager = inputManager;
this.api = api; this.api = api;
Direction = FillDirection.Vertical; Direction = FillDirection.Vertical;
Spacing = new Vector2(0, 5); Spacing = new Vector2(0, 5);
@ -230,17 +239,13 @@ namespace osu.Game.Overlays.Settings.Sections.General
}; };
} }
protected override bool OnFocus(InputState state) public override bool AcceptsFocus => true;
{
Schedule(() =>
{
if (string.IsNullOrEmpty(username.Text))
username.TriggerFocus();
else
password.TriggerFocus();
});
return base.OnFocus(state); protected override bool OnClick(InputState state) => true;
protected override void OnFocus(InputState state)
{
Schedule(() => { inputManager.ChangeFocus(string.IsNullOrEmpty(username.Text) ? username : password); });
} }
} }
@ -340,8 +345,8 @@ namespace osu.Game.Overlays.Settings.Sections.General
{ {
public UserDropdownMenuItem(string text, UserAction current) : base(text, current) public UserDropdownMenuItem(string text, UserAction current) : base(text, current)
{ {
Foreground.Padding = new MarginPadding { Top = 5, Bottom = 5, Left = UserDropdownHeader.LABEL_LEFT_MARGIN, Right = 5 }; Foreground.Padding = new MarginPadding { Top = 5, Bottom = 5, Left = 10, Right = 5 };
Chevron.Margin = new MarginPadding { Left = 2, Right = 3 }; Label.Margin = new MarginPadding { Left = UserDropdownHeader.LABEL_LEFT_MARGIN - 11 };
CornerRadius = 5; CornerRadius = 5;
} }
} }

View File

@ -14,14 +14,31 @@ namespace osu.Game.Overlays.Settings.Sections.Input
{ {
protected override string Header => "Mouse"; protected override string Header => "Mouse";
private readonly BindableBool rawInputToggle = new BindableBool();
private Bindable<string> activeInputHandlers;
private SensitivitySetting sensitivity;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuConfigManager osuConfig, FrameworkConfigManager config) private void load(OsuConfigManager osuConfig, FrameworkConfigManager config)
{ {
activeInputHandlers = config.GetBindable<string>(FrameworkSetting.ActiveInputHandlers);
rawInputToggle.Value = activeInputHandlers.Value.Contains("Raw");
Children = new Drawable[] Children = new Drawable[]
{ {
new SettingsCheckbox
{
LabelText = "Raw Input",
Bindable = rawInputToggle
},
sensitivity = new SensitivitySetting
{
LabelText = "Cursor Sensitivity",
Bindable = config.GetBindable<double>(FrameworkSetting.CursorSensitivity)
},
new SettingsEnumDropdown<ConfineMouseMode> new SettingsEnumDropdown<ConfineMouseMode>
{ {
LabelText = "Confine mouse cursor", LabelText = "Confine mouse cursor to window",
Bindable = config.GetBindable<ConfineMouseMode>(FrameworkSetting.ConfineMouseMode), Bindable = config.GetBindable<ConfineMouseMode>(FrameworkSetting.ConfineMouseMode),
}, },
new SettingsCheckbox new SettingsCheckbox
@ -35,11 +52,82 @@ namespace osu.Game.Overlays.Settings.Sections.Input
Bindable = osuConfig.GetBindable<bool>(OsuSetting.MouseDisableButtons) Bindable = osuConfig.GetBindable<bool>(OsuSetting.MouseDisableButtons)
}, },
}; };
rawInputToggle.ValueChanged += enabled =>
{
// this is temporary until we support per-handler settings.
const string raw_mouse_handler = @"OpenTKRawMouseHandler";
const string standard_mouse_handler = @"OpenTKMouseHandler";
activeInputHandlers.Value = enabled ?
activeInputHandlers.Value.Replace(standard_mouse_handler, raw_mouse_handler) :
activeInputHandlers.Value.Replace(raw_mouse_handler, standard_mouse_handler);
sensitivity.Bindable.Disabled = !enabled;
};
rawInputToggle.TriggerChange();
}
private class SensitivitySetting : SettingsSlider<double, SensitivitySlider>
{
public override Bindable<double> Bindable
{
get { return ((SensitivitySlider)Control).Sensitivity; }
set
{
BindableDouble doubleValue = (BindableDouble)value;
// create a second layer of bindable so we can only handle state changes when not being dragged.
((SensitivitySlider)Control).Sensitivity = doubleValue;
// this bindable will still act as the "interactive" bindable displayed during a drag.
base.Bindable = new BindableDouble(doubleValue.Value)
{
MinValue = doubleValue.MinValue,
MaxValue = doubleValue.MaxValue
};
// one-way binding to update the sliderbar with changes from external actions.
doubleValue.DisabledChanged += disabled => base.Bindable.Disabled = disabled;
doubleValue.ValueChanged += newValue => base.Bindable.Value = newValue;
}
}
} }
private class SensitivitySlider : OsuSliderBar<double> private class SensitivitySlider : OsuSliderBar<double>
{ {
public override string TooltipText => Current.Value.ToString(@"0.##x"); public Bindable<double> Sensitivity;
public SensitivitySlider()
{
KeyboardStep = 0.01f;
Current.ValueChanged += newValue =>
{
if (!isDragging && Sensitivity != null)
Sensitivity.Value = newValue;
};
}
private bool isDragging;
protected override bool OnDragStart(InputState state)
{
isDragging = true;
return base.OnDragStart(state);
}
protected override bool OnDragEnd(InputState state)
{
isDragging = false;
Current.TriggerChange();
return base.OnDragEnd(state);
}
public override string TooltipText => Current.Disabled ? "Enable raw input to adjust sensitivity" : Current.Value.ToString(@"0.##x");
} }
} }
} }

View File

@ -39,7 +39,7 @@ namespace osu.Game.Overlays.Settings
// hold a reference to the provided bindable so we don't have to in every settings section. // hold a reference to the provided bindable so we don't have to in every settings section.
private Bindable<T> bindable; private Bindable<T> bindable;
public Bindable<T> Bindable public virtual Bindable<T> Bindable
{ {
get get
{ {
@ -55,7 +55,7 @@ namespace osu.Game.Overlays.Settings
public string[] FilterTerms => new[] { LabelText }; public string[] FilterTerms => new[] { LabelText };
public bool MatchingCurrentFilter public bool MatchingFilter
{ {
set set
{ {

View File

@ -24,7 +24,7 @@ namespace osu.Game.Overlays.Settings
public IEnumerable<IFilterable> FilterableChildren => Children.OfType<IFilterable>(); public IEnumerable<IFilterable> FilterableChildren => Children.OfType<IFilterable>();
public string[] FilterTerms => new[] { Header }; public string[] FilterTerms => new[] { Header };
public bool MatchingCurrentFilter public bool MatchingFilter
{ {
set set
{ {

View File

@ -20,7 +20,7 @@ namespace osu.Game.Overlays.Settings
public IEnumerable<IFilterable> FilterableChildren => Children.OfType<IFilterable>(); public IEnumerable<IFilterable> FilterableChildren => Children.OfType<IFilterable>();
public string[] FilterTerms => new[] { Header }; public string[] FilterTerms => new[] { Header };
public bool MatchingCurrentFilter public bool MatchingFilter
{ {
set set
{ {

View File

@ -134,13 +134,18 @@ namespace osu.Game.Overlays
FadeTo(0, TRANSITION_LENGTH / 2); FadeTo(0, TRANSITION_LENGTH / 2);
searchTextBox.HoldFocus = false; searchTextBox.HoldFocus = false;
searchTextBox.TriggerFocusLost(); if (searchTextBox.HasFocus)
InputManager.ChangeFocus(null);
} }
protected override bool OnFocus(InputState state) public override bool AcceptsFocus => true;
protected override bool OnClick(InputState state) => true;
protected override void OnFocus(InputState state)
{ {
searchTextBox.TriggerFocus(state); InputManager.ChangeFocus(searchTextBox);
return false; base.OnFocus(state);
} }
private class SettingsSectionsContainer : SectionsContainer private class SettingsSectionsContainer : SectionsContainer
@ -158,7 +163,7 @@ namespace osu.Game.Overlays
public SettingsSectionsContainer() public SettingsSectionsContainer()
{ {
ScrollContainer.ScrollDraggerVisible = false; ScrollContainer.ScrollbarVisible = false;
Add(headerBackground = new Box Add(headerBackground = new Box
{ {
Colour = Color4.Black, Colour = Color4.Black,

View File

@ -0,0 +1,25 @@
// 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.Audio;
using osu.Framework.Timing;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mods
{
public abstract class ModDaycore : ModHalfTime
{
public override string Name => "Daycore";
public override FontAwesome Icon => FontAwesome.fa_question;
public override string Description => "whoaaaaa";
public override void ApplyToClock(IAdjustableClock clock)
{
var pitchAdjust = clock as IHasPitchAdjust;
if (pitchAdjust != null)
pitchAdjust.PitchAdjust = 0.75;
else
base.ApplyToClock(clock);
}
}
}

View File

@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mods
public override double ScoreMultiplier => 1.12; public override double ScoreMultiplier => 1.12;
public void ApplyToClock(IAdjustableClock clock) public virtual void ApplyToClock(IAdjustableClock clock)
{ {
clock.Rate = 0.75; clock.Rate = 0.75;
} }

View File

@ -15,28 +15,51 @@ using System.Linq;
namespace osu.Game.Rulesets.Objects.Drawables namespace osu.Game.Rulesets.Objects.Drawables
{ {
public abstract class DrawableHitObject<TObject, TJudgement> : Container public abstract class DrawableHitObject : Container
where TObject : HitObject
where TJudgement : Judgement
{ {
public event Action<DrawableHitObject<TObject, TJudgement>> OnJudgement; public readonly HitObject HitObject;
public TObject HitObject;
/// <summary> /// <summary>
/// The colour used for various elements of this DrawableHitObject. /// The colour used for various elements of this DrawableHitObject.
/// </summary> /// </summary>
public virtual Color4 AccentColour { get; set; } public virtual Color4 AccentColour { get; set; }
protected DrawableHitObject(HitObject hitObject)
{
HitObject = hitObject;
}
}
public abstract class DrawableHitObject<TObject> : DrawableHitObject
where TObject : HitObject
{
public new readonly TObject HitObject;
protected DrawableHitObject(TObject hitObject)
: base(hitObject)
{
HitObject = hitObject;
}
}
public abstract class DrawableHitObject<TObject, TJudgement> : DrawableHitObject<TObject>
where TObject : HitObject
where TJudgement : Judgement
{
public event Action<DrawableHitObject<TObject, TJudgement>> OnJudgement;
public override bool HandleInput => Interactive; public override bool HandleInput => Interactive;
public bool Interactive = true; public bool Interactive = true;
public TJudgement Judgement; public TJudgement Judgement;
protected abstract TJudgement CreateJudgement(); protected List<SampleChannel> Samples = new List<SampleChannel>();
protected abstract void UpdateState(ArmedState state); protected DrawableHitObject(TObject hitObject)
: base(hitObject)
{
}
private ArmedState state; private ArmedState state;
public ArmedState State public ArmedState State
@ -59,8 +82,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
} }
} }
protected List<SampleChannel> Samples = new List<SampleChannel>();
protected void PlaySamples() protected void PlaySamples()
{ {
Samples.ForEach(s => s?.Play()); Samples.ForEach(s => s?.Play());
@ -79,11 +100,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
/// </summary> /// </summary>
public bool Judged => (Judgement?.Result ?? HitResult.None) != HitResult.None && (NestedHitObjects?.All(h => h.Judged) ?? true); public bool Judged => (Judgement?.Result ?? HitResult.None) != HitResult.None && (NestedHitObjects?.All(h => h.Judged) ?? true);
protected DrawableHitObject(TObject hitObject)
{
HitObject = hitObject;
}
/// <summary> /// <summary>
/// Process a hit of this hitobject. Carries out judgement. /// Process a hit of this hitobject. Carries out judgement.
/// </summary> /// </summary>
@ -176,5 +192,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
h.OnJudgement += d => OnJudgement?.Invoke(d); h.OnJudgement += d => OnJudgement?.Invoke(d);
nestedHitObjects.Add(h); nestedHitObjects.Add(h);
} }
protected abstract TJudgement CreateJudgement();
protected abstract void UpdateState(ArmedState state);
} }
} }

View File

@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Objects
/// <summary> /// <summary>
/// The time at which the HitObject starts. /// The time at which the HitObject starts.
/// </summary> /// </summary>
public double StartTime; public virtual double StartTime { get; set; }
/// <summary> /// <summary>
/// The samples to be played when this hit object is hit. /// The samples to be played when this hit object is hit.

View File

@ -0,0 +1,55 @@
// 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.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Objects
{
/// <summary>
/// Compares two hit objects by their start time, falling back to creation order if their start time is equal.
/// </summary>
public class HitObjectStartTimeComparer : Drawable.CreationOrderDepthComparer
{
public override int Compare(Drawable x, Drawable y)
{
var hitObjectX = x as DrawableHitObject;
var hitObjectY = y as DrawableHitObject;
// If either of the two drawables are not hit objects, fall back to the base comparer
if (hitObjectX?.HitObject == null || hitObjectY?.HitObject == null)
return base.Compare(x, y);
// Compare by start time
int i = hitObjectX.HitObject.StartTime.CompareTo(hitObjectY.HitObject.StartTime);
if (i != 0)
return i;
return base.Compare(x, y);
}
}
/// <summary>
/// Compares two hit objects by their start time, falling back to creation order if their start time is equal.
/// This will compare the two hit objects in reverse order.
/// </summary>
public class HitObjectReverseStartTimeComparer : Drawable.ReverseCreationOrderDepthComparer
{
public override int Compare(Drawable x, Drawable y)
{
var hitObjectX = x as DrawableHitObject;
var hitObjectY = y as DrawableHitObject;
// If either of the two drawables are not hit objects, fall back to the base comparer
if (hitObjectX?.HitObject == null || hitObjectY?.HitObject == null)
return base.Compare(x, y);
// Compare by start time
int i = hitObjectY.HitObject.StartTime.CompareTo(hitObjectX.HitObject.StartTime);
if (i != 0)
return i;
return base.Compare(x, y);
}
}
}

View File

@ -6,20 +6,30 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using OpenTK; using OpenTK;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Database;
namespace osu.Game.Rulesets.Objects.Legacy namespace osu.Game.Rulesets.Objects.Legacy
{ {
internal abstract class ConvertSlider : HitObject, IHasCurve internal abstract class ConvertSlider : HitObject, IHasCurve
{ {
/// <summary>
/// Scoring distance with a speed-adjusted beat length of 1 second.
/// </summary>
private const float base_scoring_distance = 100;
public List<Vector2> ControlPoints { get; set; } public List<Vector2> ControlPoints { get; set; }
public CurveType CurveType { get; set; } public CurveType CurveType { get; set; }
public double Distance { get; set; } public double Distance { get; set; }
public List<SampleInfoList> RepeatSamples { get; set; } public List<SampleInfoList> RepeatSamples { get; set; }
public int RepeatCount { get; set; } = 1; public int RepeatCount { get; set; } = 1;
public double EndTime { get; set; } public double EndTime => StartTime + RepeatCount * Distance / Velocity;
public double Duration { get; set; } public double Duration => EndTime - StartTime;
public double Velocity = 1;
public Vector2 PositionAt(double progress) public Vector2 PositionAt(double progress)
{ {
@ -35,5 +45,17 @@ namespace osu.Game.Rulesets.Objects.Legacy
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaults(controlPointInfo, difficulty);
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime);
double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier / difficultyPoint.SpeedMultiplier;
Velocity = scoringDistance / timingPoint.BeatLength;
}
} }
} }

View File

@ -135,7 +135,7 @@ namespace osu.Game.Screens.Menu
switch (args.Key) switch (args.Key)
{ {
case Key.Space: case Key.Space:
osuLogo.TriggerClick(state); osuLogo.TriggerOnClick(state);
return true; return true;
case Key.Escape: case Key.Escape:
switch (State) switch (State)
@ -144,7 +144,7 @@ namespace osu.Game.Screens.Menu
State = MenuState.Initial; State = MenuState.Initial;
return true; return true;
case MenuState.Play: case MenuState.Play:
backButton.TriggerClick(); backButton.TriggerOnClick();
return true; return true;
} }
@ -178,10 +178,10 @@ namespace osu.Game.Screens.Menu
State = MenuState.TopLevel; State = MenuState.TopLevel;
return; return;
case MenuState.TopLevel: case MenuState.TopLevel:
buttonsTopLevel.First().TriggerClick(); buttonsTopLevel.First().TriggerOnClick();
return; return;
case MenuState.Play: case MenuState.Play:
buttonsPlay.First().TriggerClick(); buttonsPlay.First().TriggerOnClick();
return; return;
} }
} }

View File

@ -131,7 +131,15 @@ namespace osu.Game.Screens
Background.Exit(); Background.Exit();
} }
return base.OnExiting(next); if (base.OnExiting(next))
return true;
// while this is not necessary as we are constructing our own bindable, there are cases where
// the GC doesn't run as fast as expected and this is triggered post-exit.
// added to resolve https://github.com/ppy/osu/issues/829
beatmap.ValueChanged -= OnBeatmapChanged;
return false;
} }
} }
} }

View File

@ -26,7 +26,7 @@ namespace osu.Game.Screens.Play
{ {
if (!args.Repeat && args.Key == Key.Escape) if (!args.Repeat && args.Key == Key.Escape)
{ {
Buttons.Children.Last().TriggerClick(); Buttons.Children.Last().TriggerOnClick();
return true; return true;
} }

View File

@ -31,6 +31,7 @@ namespace osu.Game.Screens.Play
public readonly ModDisplay ModDisplay; public readonly ModDisplay ModDisplay;
private Bindable<bool> showHud; private Bindable<bool> showHud;
private bool replayLoaded;
private static bool hasShownNotificationOnce; private static bool hasShownNotificationOnce;
@ -41,6 +42,7 @@ namespace osu.Game.Screens.Play
protected abstract HealthDisplay CreateHealthDisplay(); protected abstract HealthDisplay CreateHealthDisplay();
protected abstract SongProgress CreateProgress(); protected abstract SongProgress CreateProgress();
protected abstract ModDisplay CreateModsContainer(); protected abstract ModDisplay CreateModsContainer();
//protected abstract ReplaySettingsOverlay CreateReplaySettingsOverlay();
protected HUDOverlay() protected HUDOverlay()
{ {
@ -59,6 +61,7 @@ namespace osu.Game.Screens.Play
HealthDisplay = CreateHealthDisplay(), HealthDisplay = CreateHealthDisplay(),
Progress = CreateProgress(), Progress = CreateProgress(),
ModDisplay = CreateModsContainer(), ModDisplay = CreateModsContainer(),
//ReplaySettingsOverlay = CreateReplaySettingsOverlay(),
} }
}); });
} }
@ -93,10 +96,14 @@ namespace osu.Game.Screens.Play
{ {
hitRenderer.InputManager.Add(KeyCounter.GetReceptor()); hitRenderer.InputManager.Add(KeyCounter.GetReceptor());
replayLoaded = hitRenderer.HasReplayLoaded;
// in the case a replay isn't loaded, we want some elements to only appear briefly. // in the case a replay isn't loaded, we want some elements to only appear briefly.
if (!hitRenderer.HasReplayLoaded) if (!replayLoaded)
{
using (ModDisplay.BeginDelayedSequence(2000)) using (ModDisplay.BeginDelayedSequence(2000))
ModDisplay.FadeOut(200); ModDisplay.FadeOut(200);
}
} }
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)

View File

@ -131,13 +131,13 @@ namespace osu.Game.Screens.Play
public override bool HandleInput => true; public override bool HandleInput => true;
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) => target.Children.Any(c => c.TriggerKeyDown(state, args)); protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) => target.Children.Any(c => c.TriggerOnKeyDown(state, args));
protected override bool OnKeyUp(InputState state, KeyUpEventArgs args) => target.Children.Any(c => c.TriggerKeyUp(state, args)); protected override bool OnKeyUp(InputState state, KeyUpEventArgs args) => target.Children.Any(c => c.TriggerOnKeyUp(state, args));
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => target.Children.Any(c => c.TriggerMouseDown(state, args)); protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => target.Children.Any(c => c.TriggerOnMouseDown(state, args));
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) => target.Children.Any(c => c.TriggerMouseUp(state, args)); protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) => target.Children.Any(c => c.TriggerOnMouseUp(state, args));
} }
} }
} }

View File

@ -136,7 +136,7 @@ namespace osu.Game.Screens.Play
{ {
if (!args.Repeat && args.Key == Key.Escape) if (!args.Repeat && args.Key == Key.Escape)
{ {
Buttons.Children.First().TriggerClick(); Buttons.Children.First().TriggerOnClick();
return true; return true;
} }

View File

@ -293,7 +293,7 @@ namespace osu.Game.Screens.Play
protected override bool OnExiting(Screen next) protected override bool OnExiting(Screen next)
{ {
if (HasFailed || !ValidForResume || pauseContainer.AllowExit || HitRenderer.HasReplayLoaded) if (HasFailed || !ValidForResume || pauseContainer?.AllowExit != false || HitRenderer?.HasReplayLoaded != false)
{ {
fadeOut(); fadeOut();
return base.OnExiting(next); return base.OnExiting(next);
@ -310,7 +310,7 @@ namespace osu.Game.Screens.Play
HitRenderer?.FadeOut(fade_out_duration); HitRenderer?.FadeOut(fade_out_duration);
Content.FadeOut(fade_out_duration); Content.FadeOut(fade_out_duration);
hudOverlay.ScaleTo(0.7f, fade_out_duration * 3, EasingTypes.In); hudOverlay?.ScaleTo(0.7f, fade_out_duration * 3, EasingTypes.In);
Background?.FadeTo(1f, fade_out_duration); Background?.FadeTo(1f, fade_out_duration);
} }

View File

@ -0,0 +1,33 @@
// 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.Framework.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Overlays.Music;
using System.Collections.Generic;
namespace osu.Game.Screens.Play.ReplaySettings
{
public class CollectionSettings : ReplayGroup
{
protected override string Title => @"collections";
[BackgroundDependencyLoader]
private void load()
{
Children = new Drawable[]
{
new OsuSpriteText
{
Text = @"Add current song to",
},
new CollectionsDropdown<PlaylistCollection>
{
RelativeSizeAxes = Axes.X,
Items = new[] { new KeyValuePair<string, PlaylistCollection>(@"All", PlaylistCollection.All) },
},
};
}
}
}

View File

@ -0,0 +1,35 @@
// 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.Framework.Graphics;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Screens.Play.ReplaySettings
{
public class DiscussionSettings : ReplayGroup
{
protected override string Title => @"discussions";
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
Children = new Drawable[]
{
new ReplayCheckbox
{
LabelText = "Show floating comments",
Bindable = config.GetBindable<bool>(OsuSetting.FloatingComments)
},
new FocusedTextBox
{
RelativeSizeAxes = Axes.X,
Height = 30,
PlaceholderText = "Add Comment",
HoldFocus = false,
},
};
}
}
}

View File

@ -0,0 +1,27 @@
// 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.Configuration;
using osu.Framework.Graphics;
namespace osu.Game.Screens.Play.ReplaySettings
{
public class PlaybackSettings : ReplayGroup
{
protected override string Title => @"playback";
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
Children = new Drawable[]
{
new ReplaySliderBar<double>()
{
LabelText = "Playback speed",
Bindable = config.GetBindable<double>(OsuSetting.PlaybackSpeed)
}
};
}
}
}

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 osu.Framework.Allocation;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Screens.Play.ReplaySettings
{
public class ReplayCheckbox : OsuCheckbox
{
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Nub.AccentColour = colours.Yellow;
Nub.GlowingAccentColour = colours.YellowLighter;
Nub.GlowColour = colours.YellowDarker;
}
}
}

View File

@ -0,0 +1,132 @@
// 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 OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Screens.Play.ReplaySettings
{
public abstract class ReplayGroup : Container
{
/// <summary>
/// The title to be displayed in the header of this group.
/// </summary>
protected abstract string Title { get; }
private const float transition_duration = 250;
private const int container_width = 270;
private const int border_thickness = 2;
private const int header_height = 30;
private const int corner_radius = 5;
private readonly FillFlowContainer content;
private readonly IconButton button;
private bool expanded = true;
private Color4 buttonActiveColour;
protected ReplayGroup()
{
AutoSizeAxes = Axes.Y;
Width = container_width;
Masking = true;
CornerRadius = corner_radius;
BorderColour = Color4.Black;
BorderThickness = border_thickness;
InternalChildren = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
Alpha = 0.5f,
},
new FillFlowContainer
{
Direction = FillDirection.Vertical,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
new Container
{
Name = @"Header",
Origin = Anchor.TopCentre,
Anchor = Anchor.TopCentre,
RelativeSizeAxes = Axes.X,
Height = header_height,
Children = new Drawable[]
{
new OsuSpriteText
{
Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft,
Text = Title.ToUpper(),
TextSize = 17,
Font = @"Exo2.0-Bold",
Margin = new MarginPadding { Left = 10 },
},
button = new IconButton
{
Origin = Anchor.Centre,
Anchor = Anchor.CentreRight,
Position = new Vector2(-15,0),
Icon = FontAwesome.fa_bars,
Scale = new Vector2(0.75f),
Action = toggleContentVisibility,
},
}
},
content = new FillFlowContainer
{
Name = @"Content",
Origin = Anchor.TopCentre,
Anchor = Anchor.TopCentre,
Direction = FillDirection.Vertical,
RelativeSizeAxes = Axes.X,
AutoSizeDuration = transition_duration,
AutoSizeEasing = EasingTypes.OutQuint,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding(15),
Spacing = new Vector2(0, 15),
}
}
},
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
button.Colour = buttonActiveColour = colours.Yellow;
}
protected override Container<Drawable> Content => content;
private void toggleContentVisibility()
{
content.ClearTransforms();
expanded = !expanded;
if (expanded)
content.AutoSizeAxes = Axes.Y;
else
{
content.AutoSizeAxes = Axes.None;
content.ResizeHeightTo(0, transition_duration, EasingTypes.OutQuint);
}
button.FadeColour(expanded ? buttonActiveColour : Color4.White, 200, EasingTypes.OutQuint);
}
}
}

View File

@ -0,0 +1,34 @@
// 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.UserInterface;
using System;
using osu.Game.Graphics;
using osu.Framework.Graphics;
using osu.Game.Overlays.Settings;
namespace osu.Game.Screens.Play.ReplaySettings
{
public class ReplaySliderBar<T> : SettingsSlider<T>
where T : struct, IEquatable<T>
{
protected override Drawable CreateControl() => new Sliderbar()
{
Margin = new MarginPadding { Top = 5, Bottom = 5 },
RelativeSizeAxes = Axes.X
};
private class Sliderbar : OsuSliderBar<T>
{
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
AccentColour = colours.Yellow;
Nub.AccentColour = colours.Yellow;
Nub.GlowingAccentColour = colours.YellowLighter;
Nub.GlowColour = colours.YellowDarker;
}
}
}
}

View File

@ -0,0 +1,24 @@
// 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.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Screens.Play.ReplaySettings;
using OpenTK;
namespace osu.Game.Screens.Play
{
public class ReplaySettingsOverlay : FillFlowContainer
{
public ReplaySettingsOverlay()
{
Direction = FillDirection.Vertical;
AutoSizeAxes = Axes.Both;
Spacing = new Vector2(0, 20);
Add(new CollectionSettings());
Add(new DiscussionSettings());
Add(new PlaybackSettings());
}
}
}

View File

@ -127,7 +127,7 @@ namespace osu.Game.Screens.Play
switch (args.Key) switch (args.Key)
{ {
case Key.Space: case Key.Space:
button.TriggerClick(); button.TriggerOnClick();
return true; return true;
} }

Some files were not shown because too many files have changed in this diff Show More