1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-15 10:33:01 +08:00

Merge branch 'master' into details

This commit is contained in:
Dean Herbert 2017-04-10 20:07:13 +09:00
commit c6a24bb549
No known key found for this signature in database
GPG Key ID: 46D71BF4958ABB49
46 changed files with 654 additions and 379 deletions

4
.vscode/launch.json vendored
View File

@ -11,7 +11,7 @@
"preLaunchTask": "build", "preLaunchTask": "build",
"runtimeExecutable": null, "runtimeExecutable": null,
"env": {}, "env": {},
"externalConsole": false "console": "internalConsole"
}, },
{ {
"name": "Launch Desktop", "name": "Launch Desktop",
@ -23,7 +23,7 @@
"preLaunchTask": "build", "preLaunchTask": "build",
"runtimeExecutable": null, "runtimeExecutable": null,
"env": {}, "env": {},
"externalConsole": false "console": "internalConsole"
}, },
{ {
"name": "Attach", "name": "Attach",

20
.vscode/tasks.json vendored
View File

@ -2,25 +2,23 @@
// See https://go.microsoft.com/fwlink/?LinkId=733558 // See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format // for the documentation about the tasks.json format
"version": "0.1.0", "version": "0.1.0",
"taskSelector": "/t:",
"tasks": [
{
"taskName": "build",
"isShellCommand": true,
"showOutput": "silent",
"command": "xbuild",
"windows": { "windows": {
"command": "msbuild" "command": "msbuild"
}, },
"linux": {
"command": "xbuild"
},
"args": [ "args": [
// Ask msbuild to generate full paths for file names. // Ask msbuild to generate full paths for file names.
"/property:GenerateFullPaths=true" "/property:GenerateFullPaths=true"
], ],
"taskSelector": "/t:",
"showOutput": "silent",
"tasks": [
{
"taskName": "build",
// Show the output window only if unrecognized errors occur.
"showOutput": "silent",
// Use the standard MS compiler pattern to detect errors, warnings and infos // Use the standard MS compiler pattern to detect errors, warnings and infos
"problemMatcher": "$msCompile" "problemMatcher": "$msCompile",
"isBuildCommand": true
} }
] ]
} }

@ -1 +1 @@
Subproject commit a7c99e06ff4c3f56fad24bec170eb93f42b1e149 Subproject commit 1490f003273d7aab6589e489f6e4b02d204c9f27

View File

@ -99,6 +99,7 @@ namespace osu.Desktop.VisualTests.Tests
AddToggleStep(@"auto", state => { auto = state; load(mode); }); AddToggleStep(@"auto", state => { auto = state; load(mode); });
BasicSliderBar<double> sliderBar;
Add(new Container Add(new Container
{ {
Anchor = Anchor.TopRight, Anchor = Anchor.TopRight,
@ -107,16 +108,17 @@ namespace osu.Desktop.VisualTests.Tests
Children = new Drawable[] Children = new Drawable[]
{ {
new SpriteText { Text = "Playback Speed" }, new SpriteText { Text = "Playback Speed" },
new BasicSliderBar<double> sliderBar = new BasicSliderBar<double>
{ {
Width = 150, Width = 150,
Height = 10, Height = 10,
SelectionColor = Color4.Orange, SelectionColor = Color4.Orange,
Value = playbackSpeed
} }
} }
}); });
sliderBar.Current.BindTo(playbackSpeed);
framedClock.ProcessFrame(); framedClock.ProcessFrame();
var clockAdjustContainer = new Container var clockAdjustContainer = new Container

View File

@ -44,6 +44,8 @@ namespace osu.Desktop.VisualTests.Tests
kc.Add(new KeyCounterKeyboard(key)); kc.Add(new KeyCounterKeyboard(key));
}); });
TestSliderBar<int> sliderBar;
Add(new Container Add(new Container
{ {
Anchor = Anchor.TopRight, Anchor = Anchor.TopRight,
@ -52,16 +54,17 @@ namespace osu.Desktop.VisualTests.Tests
Children = new Drawable[] Children = new Drawable[]
{ {
new SpriteText { Text = "FadeTime" }, new SpriteText { Text = "FadeTime" },
new TestSliderBar<int> sliderBar =new TestSliderBar<int>
{ {
Width = 150, Width = 150,
Height = 10, Height = 10,
SelectionColor = Color4.Orange, SelectionColor = Color4.Orange,
Value = bindable
} }
} }
}); });
sliderBar.Current.BindTo(bindable);
Add(kc); Add(kc);
} }
private class TestSliderBar<T> : SliderBar<T> where T : struct private class TestSliderBar<T> : SliderBar<T> where T : struct

View File

@ -30,7 +30,9 @@ namespace osu.Desktop.VisualTests.Tests
Anchor = Anchor.Centre Anchor = Anchor.Centre
}; };
Add(mc); Add(mc);
AddToggleStep(@"Show", state => mc.State = state ? Visibility.Visible : Visibility.Hidden);
AddToggleStep(@"toggle visibility", state => mc.State = state ? Visibility.Visible : Visibility.Hidden);
AddStep(@"show", () => mc.State = Visibility.Visible);
} }
} }
} }

View File

@ -36,7 +36,7 @@ namespace osu.Desktop.VisualTests.Tests
filter.PinItem(GroupMode.All); filter.PinItem(GroupMode.All);
filter.PinItem(GroupMode.RecentlyPlayed); filter.PinItem(GroupMode.RecentlyPlayed);
filter.SelectedItem.ValueChanged += newFilter => filter.Current.ValueChanged += newFilter =>
{ {
text.Text = "Currently Selected: " + newFilter.ToString(); text.Text = "Currently Selected: " + newFilter.ToString();
}; };

View File

@ -1,6 +1,7 @@
// 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 osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
@ -11,20 +12,21 @@ using osu.Game.Modes.Taiko.Judgements;
using osu.Game.Modes.Taiko.Objects; using osu.Game.Modes.Taiko.Objects;
using osu.Game.Modes.Taiko.Objects.Drawables; using osu.Game.Modes.Taiko.Objects.Drawables;
using osu.Game.Modes.Taiko.UI; using osu.Game.Modes.Taiko.UI;
using System;
namespace osu.Desktop.VisualTests.Tests namespace osu.Desktop.VisualTests.Tests
{ {
internal class TestCaseTaikoPlayfield : TestCase internal class TestCaseTaikoPlayfield : TestCase
{ {
public override string Description => "Taiko playfield"; private const double default_duration = 300;
private const float scroll_time = 1000;
private TaikoPlayfield playfield; public override string Description => "Taiko playfield";
protected override double TimePerAction => default_duration * 2; protected override double TimePerAction => default_duration * 2;
private const double default_duration = 300; private readonly Random rng = new Random(1337);
private TaikoPlayfield playfield;
private const float scroll_time = 1000;
public override void Reset() public override void Reset()
{ {
@ -41,7 +43,11 @@ namespace osu.Desktop.VisualTests.Tests
AddStep("Strong Rim", () => addRimHit(true)); AddStep("Strong Rim", () => addRimHit(true));
AddStep("Add bar line", () => addBarLine(false)); AddStep("Add bar line", () => addBarLine(false));
AddStep("Add major bar line", () => addBarLine(true)); AddStep("Add major bar line", () => addBarLine(true));
AddStep("Height test 1", () => changePlayfieldSize(1));
AddStep("Height test 2", () => changePlayfieldSize(2));
AddStep("Height test 3", () => changePlayfieldSize(3));
AddStep("Height test 4", () => changePlayfieldSize(4));
AddStep("Height test 5", () => changePlayfieldSize(5));
var rateAdjustClock = new StopwatchClock(true) { Rate = 1 }; var rateAdjustClock = new StopwatchClock(true) { Rate = 1 };
@ -57,21 +63,53 @@ namespace osu.Desktop.VisualTests.Tests
}); });
} }
private void changePlayfieldSize(int step)
{
switch (step)
{
case 1:
addCentreHit(false);
break;
case 2:
addCentreHit(true);
break;
case 3:
addDrumRoll(false);
break;
case 4:
addDrumRoll(true);
break;
case 5:
addSwell(1000);
playfield.Delay(scroll_time - 100);
break;
}
playfield.ResizeTo(new Vector2(1, rng.Next(25, 400)), 500);
}
private void addHitJudgement() private void addHitJudgement()
{ {
TaikoHitResult hitResult = RNG.Next(2) == 0 ? TaikoHitResult.Good : TaikoHitResult.Great; TaikoHitResult hitResult = RNG.Next(2) == 0 ? TaikoHitResult.Good : TaikoHitResult.Great;
playfield.OnJudgement(new DrawableTestHit(new Hit()) var h = new DrawableTestHit(new Hit())
{ {
X = RNG.NextSingle(hitResult == TaikoHitResult.Good ? -0.1f : -0.05f, hitResult == TaikoHitResult.Good ? 0.1f : 0.05f), X = RNG.NextSingle(hitResult == TaikoHitResult.Good ? -0.1f : -0.05f, hitResult == TaikoHitResult.Good ? 0.1f : 0.05f),
Judgement = new TaikoJudgement Judgement = new TaikoJudgement
{ {
Result = HitResult.Hit, Result = HitResult.Hit,
TaikoResult = hitResult, TaikoResult = hitResult,
TimeOffset = 0, TimeOffset = 0
SecondHit = RNG.Next(10) == 0 }
};
playfield.OnJudgement(h);
if (RNG.Next(10) == 0)
{
h.Judgement.SecondHit = true;
playfield.OnJudgement(h);
} }
});
} }
private void addMissJudgement() private void addMissJudgement()

View File

@ -14,8 +14,7 @@ namespace osu.Game.Modes.Catch.UI
{ {
public CatchPlayfield() public CatchPlayfield()
{ {
RelativeSizeAxes = Axes.Y; Size = new Vector2(1, 0.9f);
Size = new Vector2(512, 0.9f);
Anchor = Anchor.BottomCentre; Anchor = Anchor.BottomCentre;
Origin = Anchor.BottomCentre; Origin = Anchor.BottomCentre;

View File

@ -15,8 +15,7 @@ namespace osu.Game.Modes.Mania.UI
{ {
public ManiaPlayfield(int columns) public ManiaPlayfield(int columns)
{ {
RelativeSizeAxes = Axes.Both; Size = new Vector2(0.8f, 1f);
Size = new Vector2(columns / 20f, 1f);
Anchor = Anchor.BottomCentre; Anchor = Anchor.BottomCentre;
Origin = Anchor.BottomCentre; Origin = Anchor.BottomCentre;

View File

@ -42,14 +42,14 @@ namespace osu.Game.Modes.Osu
{ {
if (mouseDisabled.Value) if (mouseDisabled.Value)
{ {
mouse.PressedButtons.Remove(MouseButton.Left); mouse.SetPressed(MouseButton.Left, false);
mouse.PressedButtons.Remove(MouseButton.Right); mouse.SetPressed(MouseButton.Right, false);
} }
if (leftViaKeyboard) if (leftViaKeyboard)
mouse.PressedButtons.Add(MouseButton.Left); mouse.SetPressed(MouseButton.Left, true);
if (rightViaKeyboard) if (rightViaKeyboard)
mouse.PressedButtons.Add(MouseButton.Right); mouse.SetPressed(MouseButton.Right, true);
} }
} }
} }

View File

@ -1,6 +1,7 @@
// 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 osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Modes.Objects.Drawables; using osu.Game.Modes.Objects.Drawables;
using osu.Game.Modes.Osu.Beatmaps; using osu.Game.Modes.Osu.Beatmaps;
@ -46,5 +47,7 @@ namespace osu.Game.Modes.Osu.UI
return new DrawableSpinner(spinner); return new DrawableSpinner(spinner);
return null; return null;
} }
protected override Vector2 GetPlayfieldAspectAdjust() => new Vector2(0.75f);
} }
} }

View File

@ -38,8 +38,6 @@ namespace osu.Game.Modes.Osu.UI
{ {
Anchor = Anchor.Centre; Anchor = Anchor.Centre;
Origin = Anchor.Centre; Origin = Anchor.Centre;
RelativeSizeAxes = Axes.Both;
Size = new Vector2(0.75f);
Add(new Drawable[] Add(new Drawable[]
{ {

View File

@ -6,6 +6,7 @@ using System.Linq;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Modes.Objects.Drawables; using osu.Game.Modes.Objects.Drawables;
using osu.Game.Modes.Taiko.Judgements; using osu.Game.Modes.Taiko.Judgements;
using osu.Game.Modes.Taiko.Objects.Drawables.Pieces;
using OpenTK.Input; using OpenTK.Input;
namespace osu.Game.Modes.Taiko.Objects.Drawables namespace osu.Game.Modes.Taiko.Objects.Drawables
@ -66,6 +67,10 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables
{ {
Delay(HitObject.StartTime - Time.Current + Judgement.TimeOffset, true); Delay(HitObject.StartTime - Time.Current + Judgement.TimeOffset, true);
var circlePiece = MainPiece as CirclePiece;
circlePiece?.FlashBox.Flush();
switch (State) switch (State)
{ {
case ArmedState.Idle: case ArmedState.Idle:
@ -77,6 +82,16 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables
case ArmedState.Hit: case ArmedState.Hit:
FadeOut(600); FadeOut(600);
var flash = circlePiece?.FlashBox;
if (flash != null)
{
flash.FadeTo(0.9f);
flash.FadeOut(300);
}
FadeOut(800);
const float gravity_time = 300; const float gravity_time = 300;
const float gravity_travel_height = 200; const float gravity_travel_height = 200;

View File

@ -65,7 +65,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Alpha = 0, Alpha = 0,
Size = new Vector2(TaikoHitObject.CIRCLE_RADIUS * 2), Size = new Vector2(TaikoHitObject.DEFAULT_CIRCLE_DIAMETER),
BlendingMode = BlendingMode.Additive, BlendingMode = BlendingMode.Additive,
Masking = true, Masking = true,
Children = new [] Children = new []
@ -82,7 +82,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables
Name = "Target ring (thick border)", Name = "Target ring (thick border)",
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Size = new Vector2(TaikoHitObject.CIRCLE_RADIUS * 2), Size = new Vector2(TaikoHitObject.DEFAULT_CIRCLE_DIAMETER),
Masking = true, Masking = true,
BorderThickness = target_ring_thick_border, BorderThickness = target_ring_thick_border,
BlendingMode = BlendingMode.Additive, BlendingMode = BlendingMode.Additive,

View File

@ -19,15 +19,10 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces
/// </summary> /// </summary>
public class CirclePiece : TaikoPiece public class CirclePiece : TaikoPiece
{ {
public const float SYMBOL_SIZE = TaikoHitObject.CIRCLE_RADIUS * 2f * 0.45f; public const float SYMBOL_SIZE = TaikoHitObject.DEFAULT_CIRCLE_DIAMETER * 0.45f;
public const float SYMBOL_BORDER = 8; public const float SYMBOL_BORDER = 8;
public const float SYMBOL_INNER_SIZE = SYMBOL_SIZE - 2 * SYMBOL_BORDER; public const float SYMBOL_INNER_SIZE = SYMBOL_SIZE - 2 * SYMBOL_BORDER;
/// <summary>
/// The amount to scale up the base circle to show it as a "strong" piece.
/// </summary>
private const float strong_scale = 1.5f;
/// <summary> /// <summary>
/// The colour of the inner circle and outer glows. /// The colour of the inner circle and outer glows.
/// </summary> /// </summary>
@ -64,6 +59,8 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces
private readonly Container background; private readonly Container background;
public Box FlashBox;
public CirclePiece(bool isStrong = false) public CirclePiece(bool isStrong = false)
{ {
AddInternal(new Drawable[] AddInternal(new Drawable[]
@ -104,11 +101,13 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces
Masking = true, Masking = true,
Children = new[] Children = new[]
{ {
new Box FlashBox = new Box
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = Color4.White,
BlendingMode = BlendingMode.Additive,
Alpha = 0, Alpha = 0,
AlwaysPresent = true AlwaysPresent = true
} }
@ -125,10 +124,10 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces
if (isStrong) if (isStrong)
{ {
Size *= strong_scale; Size *= TaikoHitObject.STRONG_CIRCLE_DIAMETER_SCALE;
//default for symbols etc. //default for symbols etc.
Content.Scale *= strong_scale; Content.Scale *= TaikoHitObject.STRONG_CIRCLE_DIAMETER_SCALE;
} }
} }

View File

@ -39,7 +39,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces
public TaikoPiece() public TaikoPiece()
{ {
//just a default //just a default
Size = new Vector2(TaikoHitObject.CIRCLE_RADIUS * 2); Size = new Vector2(TaikoHitObject.DEFAULT_CIRCLE_DIAMETER);
} }
} }
} }

View File

@ -15,12 +15,12 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces
/// Any tick that is not the first for a drumroll is not filled, but is instead displayed /// Any tick that is not the first for a drumroll is not filled, but is instead displayed
/// as a hollow circle. This is what controls the border width of that circle. /// as a hollow circle. This is what controls the border width of that circle.
/// </summary> /// </summary>
private const float tick_border_width = TaikoHitObject.CIRCLE_RADIUS / 2 / 4; private const float tick_border_width = TaikoHitObject.DEFAULT_CIRCLE_DIAMETER / 16;
/// <summary> /// <summary>
/// The size of a tick. /// The size of a tick.
/// </summary> /// </summary>
private const float tick_size = TaikoHitObject.CIRCLE_RADIUS / 2; private const float tick_size = TaikoHitObject.DEFAULT_CIRCLE_DIAMETER / 4;
private bool filled; private bool filled;
public bool Filled public bool Filled

View File

@ -4,15 +4,31 @@
using osu.Game.Beatmaps.Timing; using osu.Game.Beatmaps.Timing;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Modes.Objects; using osu.Game.Modes.Objects;
using osu.Game.Modes.Taiko.UI;
namespace osu.Game.Modes.Taiko.Objects namespace osu.Game.Modes.Taiko.Objects
{ {
public abstract class TaikoHitObject : HitObject public abstract class TaikoHitObject : HitObject
{ {
/// <summary> /// <summary>
/// HitCircle radius. /// Diameter of a circle relative to the size of the <see cref="TaikoPlayfield"/>.
/// </summary> /// </summary>
public const float CIRCLE_RADIUS = 42f; public const float PLAYFIELD_RELATIVE_DIAMETER = 0.5f;
/// <summary>
/// Scale multiplier for a strong circle.
/// </summary>
public const float STRONG_CIRCLE_DIAMETER_SCALE = 1.5f;
/// <summary>
/// Default circle diameter.
/// </summary>
public const float DEFAULT_CIRCLE_DIAMETER = TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT * PLAYFIELD_RELATIVE_DIAMETER;
/// <summary>
/// Default strong circle diameter.
/// </summary>
public const float DEFAULT_STRONG_CIRCLE_DIAMETER = DEFAULT_CIRCLE_DIAMETER * STRONG_CIRCLE_DIAMETER_SCALE;
/// <summary> /// <summary>
/// The time taken from the initial (off-screen) spawn position to the centre of the hit target for a <see cref="ControlPoint.BeatLength"/> of 1000ms. /// The time taken from the initial (off-screen) spawn position to the centre of the hit target for a <see cref="ControlPoint.BeatLength"/> of 1000ms.

View File

@ -18,11 +18,6 @@ namespace osu.Game.Modes.Taiko.UI
/// </summary> /// </summary>
internal class HitExplosion : CircularContainer internal class HitExplosion : CircularContainer
{ {
/// <summary>
/// The size multiplier of a hit explosion if a hit object has been hit with the second key.
/// </summary>
private const float secondhit_size_multiplier = 1.5f;
/// <summary> /// <summary>
/// The judgement this hit explosion visualises. /// The judgement this hit explosion visualises.
/// </summary> /// </summary>
@ -34,7 +29,7 @@ namespace osu.Game.Modes.Taiko.UI
{ {
Judgement = judgement; Judgement = judgement;
Size = new Vector2(TaikoHitObject.CIRCLE_RADIUS * 2); Size = new Vector2(TaikoHitObject.DEFAULT_CIRCLE_DIAMETER);
Anchor = Anchor.Centre; Anchor = Anchor.Centre;
Origin = Anchor.Centre; Origin = Anchor.Centre;
@ -85,7 +80,7 @@ namespace osu.Game.Modes.Taiko.UI
/// </summary> /// </summary>
public void VisualiseSecondHit() public void VisualiseSecondHit()
{ {
ResizeTo(Size * secondhit_size_multiplier, 50); ResizeTo(Size * TaikoHitObject.STRONG_CIRCLE_DIAMETER_SCALE, 50);
} }
} }
} }

View File

@ -15,16 +15,6 @@ namespace osu.Game.Modes.Taiko.UI
/// </summary> /// </summary>
internal class HitTarget : Container internal class HitTarget : Container
{ {
/// <summary>
/// Diameter of normal hit object circles.
/// </summary>
private const float normal_diameter = TaikoHitObject.CIRCLE_RADIUS * 2;
/// <summary>
/// Diameter of strong hit object circles.
/// </summary>
private const float strong_hit_diameter = normal_diameter * 1.5f;
/// <summary> /// <summary>
/// The 1px inner border of the taiko playfield. /// The 1px inner border of the taiko playfield.
/// </summary> /// </summary>
@ -37,7 +27,7 @@ namespace osu.Game.Modes.Taiko.UI
public HitTarget() public HitTarget()
{ {
RelativeSizeAxes = Axes.Y; Size = new Vector2(TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT);
Children = new Drawable[] Children = new Drawable[]
{ {
@ -47,7 +37,7 @@ namespace osu.Game.Modes.Taiko.UI
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
Y = border_offset, Y = border_offset,
Size = new Vector2(border_thickness, (TaikoPlayfield.PLAYFIELD_HEIGHT - strong_hit_diameter) / 2f - border_offset), Size = new Vector2(border_thickness, (TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT - TaikoHitObject.DEFAULT_STRONG_CIRCLE_DIAMETER) / 2f - border_offset),
Alpha = 0.1f Alpha = 0.1f
}, },
new CircularContainer new CircularContainer
@ -55,7 +45,7 @@ namespace osu.Game.Modes.Taiko.UI
Name = "Strong Hit Ring", Name = "Strong Hit Ring",
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Size = new Vector2(strong_hit_diameter), Size = new Vector2(TaikoHitObject.DEFAULT_STRONG_CIRCLE_DIAMETER),
Masking = true, Masking = true,
BorderColour = Color4.White, BorderColour = Color4.White,
BorderThickness = border_thickness, BorderThickness = border_thickness,
@ -75,7 +65,7 @@ namespace osu.Game.Modes.Taiko.UI
Name = "Normal Hit Ring", Name = "Normal Hit Ring",
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Size = new Vector2(normal_diameter), Size = new Vector2(TaikoHitObject.DEFAULT_CIRCLE_DIAMETER),
Masking = true, Masking = true,
BorderColour = Color4.White, BorderColour = Color4.White,
BorderThickness = border_thickness, BorderThickness = border_thickness,
@ -96,7 +86,7 @@ namespace osu.Game.Modes.Taiko.UI
Anchor = Anchor.BottomCentre, Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre, Origin = Anchor.BottomCentre,
Y = -border_offset, Y = -border_offset,
Size = new Vector2(border_thickness, (TaikoPlayfield.PLAYFIELD_HEIGHT - strong_hit_diameter) / 2f - border_offset), Size = new Vector2(border_thickness, (TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT - TaikoHitObject.DEFAULT_STRONG_CIRCLE_DIAMETER) / 2f - border_offset),
Alpha = 0.1f Alpha = 0.1f
}, },
}; };

View File

@ -21,7 +21,7 @@ namespace osu.Game.Modes.Taiko.UI
{ {
public InputDrum() public InputDrum()
{ {
Size = new Vector2(TaikoPlayfield.PLAYFIELD_HEIGHT); Size = new Vector2(TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT);
const float middle_split = 10; const float middle_split = 10;

View File

@ -17,6 +17,7 @@ using osu.Game.Modes.Taiko.Objects.Drawables;
using osu.Game.Modes.Taiko.Scoring; using osu.Game.Modes.Taiko.Scoring;
using osu.Game.Modes.UI; using osu.Game.Modes.UI;
using osu.Game.Modes.Taiko.Replays; using osu.Game.Modes.Taiko.Replays;
using OpenTK;
namespace osu.Game.Modes.Taiko.UI namespace osu.Game.Modes.Taiko.UI
{ {
@ -100,6 +101,17 @@ namespace osu.Game.Modes.Taiko.UI
} }
} }
protected override Vector2 GetPlayfieldAspectAdjust()
{
const float default_relative_height = TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT / 768;
const float default_aspect = 16f / 9f;
float aspectAdjust = MathHelper.Clamp(DrawWidth / DrawHeight, 0.4f, 4) / default_aspect;
return new Vector2(1, default_relative_height * aspectAdjust);
}
public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this); public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this);
protected override IBeatmapConverter<TaikoHitObject> CreateBeatmapConverter() => new TaikoBeatmapConverter(); protected override IBeatmapConverter<TaikoHitObject> CreateBeatmapConverter() => new TaikoBeatmapConverter();

View File

@ -16,21 +16,21 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Primitives;
using System.Linq; using System.Linq;
using osu.Game.Modes.Taiko.Objects.Drawables; using osu.Game.Modes.Taiko.Objects.Drawables;
using System;
namespace osu.Game.Modes.Taiko.UI namespace osu.Game.Modes.Taiko.UI
{ {
public class TaikoPlayfield : Playfield<TaikoHitObject, TaikoJudgement> public class TaikoPlayfield : Playfield<TaikoHitObject, TaikoJudgement>
{ {
/// <summary> /// <summary>
/// The play field height. This is relative to the size of hit objects /// The default play field height.
/// such that the playfield is just a bit larger than strong hits.
/// </summary> /// </summary>
public const float PLAYFIELD_HEIGHT = TaikoHitObject.CIRCLE_RADIUS * 2 * 2; public const float DEFAULT_PLAYFIELD_HEIGHT = 168f;
/// <summary> /// <summary>
/// The offset from <see cref="left_area_size"/> which the center of the hit target lies at. /// The offset from <see cref="left_area_size"/> which the center of the hit target lies at.
/// </summary> /// </summary>
private const float hit_target_offset = TaikoHitObject.CIRCLE_RADIUS * 1.5f + 40; private const float hit_target_offset = TaikoHitObject.DEFAULT_STRONG_CIRCLE_DIAMETER / 2f + 40;
/// <summary> /// <summary>
/// The size of the left area of the playfield. This area contains the input drum. /// The size of the left area of the playfield. This area contains the input drum.
@ -52,13 +52,11 @@ namespace osu.Game.Modes.Taiko.UI
public TaikoPlayfield() public TaikoPlayfield()
{ {
RelativeSizeAxes = Axes.X;
Height = PLAYFIELD_HEIGHT;
AddInternal(new Drawable[] AddInternal(new Drawable[]
{ {
rightBackgroundContainer = new Container rightBackgroundContainer = new Container
{ {
Name = "Transparent playfield background",
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
BorderThickness = 2, BorderThickness = 2,
Masking = true, Masking = true,
@ -77,14 +75,22 @@ namespace osu.Game.Modes.Taiko.UI
}, },
} }
}, },
new ScaleFixContainer
{
RelativeSizeAxes = Axes.X,
Height = DEFAULT_PLAYFIELD_HEIGHT,
Children = new[]
{
new Container new Container
{ {
Name = "Transparent playfield elements",
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = left_area_size }, Padding = new MarginPadding { Left = left_area_size },
Children = new Drawable[] Children = new Drawable[]
{ {
new Container new Container
{ {
Name = "Hit target container",
X = hit_target_offset, X = hit_target_offset,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Children = new Drawable[] Children = new Drawable[]
@ -93,7 +99,7 @@ namespace osu.Game.Modes.Taiko.UI
{ {
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Size = new Vector2(TaikoHitObject.CIRCLE_RADIUS * 2), RelativeSizeAxes = Axes.Y,
BlendingMode = BlendingMode.Additive BlendingMode = BlendingMode.Additive
}, },
barLineContainer = new Container<DrawableBarLine> barLineContainer = new Container<DrawableBarLine>
@ -111,7 +117,7 @@ namespace osu.Game.Modes.Taiko.UI
}, },
judgementContainer = new Container<DrawableTaikoJudgement> judgementContainer = new Container<DrawableTaikoJudgement>
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Y,
BlendingMode = BlendingMode.Additive BlendingMode = BlendingMode.Additive
}, },
}, },
@ -120,7 +126,8 @@ namespace osu.Game.Modes.Taiko.UI
}, },
leftBackgroundContainer = new Container leftBackgroundContainer = new Container
{ {
Size = new Vector2(left_area_size, PLAYFIELD_HEIGHT), Name = "Left overlay",
Size = new Vector2(left_area_size, DEFAULT_PLAYFIELD_HEIGHT),
BorderThickness = 1, BorderThickness = 1,
Children = new Drawable[] Children = new Drawable[]
{ {
@ -145,8 +152,11 @@ namespace osu.Game.Modes.Taiko.UI
}, },
} }
}, },
}
},
topLevelHitContainer = new Container topLevelHitContainer = new Container
{ {
Name = "Top level hit objects",
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
} }
}); });
@ -208,5 +218,56 @@ namespace osu.Game.Modes.Taiko.UI
else else
hitExplosionContainer.Children.FirstOrDefault(e => e.Judgement == judgedObject.Judgement)?.VisualiseSecondHit(); hitExplosionContainer.Children.FirstOrDefault(e => e.Judgement == judgedObject.Judgement)?.VisualiseSecondHit();
} }
/// <summary>
/// This is a very special type of container. It serves a similar purpose to <see cref="FillMode.Fit"/>, however unlike <see cref="FillMode.Fit"/>,
/// this will only adjust the scale relative to the height of its parent and will maintain the original width relative to its parent.
///
/// <para>
/// By adjusting the scale relative to the height of its parent, the aspect ratio of this container's children is maintained, however this is undesirable
/// in the case where the hit object container should not have its width adjusted by scale. To counteract this, another container is nested inside this
/// container which takes care of reversing the width adjustment while appearing transparent to the user.
/// </para>
/// </summary>
private class ScaleFixContainer : Container
{
protected override Container<Drawable> Content => widthAdjustmentContainer;
private readonly WidthAdjustmentContainer widthAdjustmentContainer;
/// <summary>
/// We only want to apply DrawScale in the Y-axis to preserve aspect ratio and <see cref="TaikoPlayfield"/> doesn't care about having its width adjusted.
/// </summary>
protected override Vector2 DrawScale => Scale * RelativeToAbsoluteFactor.Y / DrawHeight;
public ScaleFixContainer()
{
AddInternal(widthAdjustmentContainer = new WidthAdjustmentContainer { ParentDrawScaleReference = () => DrawScale.X });
}
/// <summary>
/// The container type that reverses the <see cref="Drawable.DrawScale"/> width adjustment.
/// </summary>
private class WidthAdjustmentContainer : Container
{
/// <summary>
/// This container needs to know its parent's <see cref="Drawable.DrawScale"/> so it can reverse the width adjustment caused by <see cref="Drawable.DrawScale"/>.
/// </summary>
public Func<float> ParentDrawScaleReference;
public WidthAdjustmentContainer()
{
// This container doesn't care about height, it should always fill its parent
RelativeSizeAxes = Axes.Y;
}
protected override void Update()
{
base.Update();
// Reverse the DrawScale adjustment
Width = Parent.DrawSize.X / ParentDrawScaleReference();
}
}
}
} }
} }

View File

@ -46,6 +46,7 @@ namespace osu.Game.Configuration
Set(OsuConfig.AutomaticDownload, true).Disabled = true; Set(OsuConfig.AutomaticDownload, true).Disabled = true;
Set(OsuConfig.AutomaticDownloadNoVideo, false).Disabled = true; Set(OsuConfig.AutomaticDownloadNoVideo, false).Disabled = true;
Set(OsuConfig.BlockNonFriendPM, false).Disabled = true; Set(OsuConfig.BlockNonFriendPM, false).Disabled = true;
Set(OsuConfig.Bloom, false).Disabled = true;
Set(OsuConfig.BloomSoftening, false).Disabled = true; Set(OsuConfig.BloomSoftening, false).Disabled = true;
Set(OsuConfig.BossKeyFirstActivation, true).Disabled = true; Set(OsuConfig.BossKeyFirstActivation, true).Disabled = true;
Set(OsuConfig.ChatAudibleHighlight, true).Disabled = true; Set(OsuConfig.ChatAudibleHighlight, true).Disabled = true;

View File

@ -3,8 +3,8 @@
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
@ -12,18 +12,18 @@ using osu.Framework.Graphics.UserInterface;
namespace osu.Game.Graphics.UserInterface namespace osu.Game.Graphics.UserInterface
{ {
public class Nub : CircularContainer, IStateful<CheckboxState> public class Nub : CircularContainer, IHasCurrentValue<bool>
{ {
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 readonly Box fill;
private const float border_width = 3; private const float border_width = 3;
private Color4 glowingColour, idleColour; private Color4 glowingColour, idleColour;
public Nub() public Nub()
{ {
Box fill;
Size = new Vector2(COLLAPSED_SIZE, 12); Size = new Vector2(COLLAPSED_SIZE, 12);
BorderColour = Color4.White; BorderColour = Color4.White;
@ -40,6 +40,14 @@ namespace osu.Game.Graphics.UserInterface
AlwaysPresent = true, AlwaysPresent = true,
}, },
}; };
Current.ValueChanged += newValue =>
{
if (newValue)
fill.FadeIn(200, EasingTypes.OutQuint);
else
fill.FadeTo(0.01f, 200, EasingTypes.OutQuint); //todo: remove once we figure why containers aren't drawing at all times
};
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -84,28 +92,6 @@ namespace osu.Game.Graphics.UserInterface
} }
} }
private CheckboxState state; public Bindable<bool> Current { get; } = new Bindable<bool>();
public CheckboxState State
{
get
{
return state;
}
set
{
state = value;
switch (state)
{
case CheckboxState.Checked:
fill.FadeIn(200, EasingTypes.OutQuint);
break;
case CheckboxState.Unchecked:
fill.FadeTo(0.01f, 200, EasingTypes.OutQuint); //todo: remove once we figure why containers aren't drawing at all times
break;
}
}
}
} }
} }

View File

@ -23,18 +23,9 @@ namespace osu.Game.Graphics.UserInterface
{ {
set set
{ {
if (bindable != null)
bindable.ValueChanged -= bindableValueChanged;
bindable = value; bindable = value;
if (bindable != null) Current.BindTo(bindable);
{ if (value?.Disabled ?? true)
bool state = State == CheckboxState.Checked;
if (state != bindable.Value)
State = bindable.Value ? CheckboxState.Checked : CheckboxState.Unchecked;
bindable.ValueChanged += bindableValueChanged;
}
if (bindable?.Disabled ?? true)
Alpha = 0.3f; Alpha = 0.3f;
} }
} }
@ -83,18 +74,16 @@ namespace osu.Game.Graphics.UserInterface
Margin = new MarginPadding { Right = 5 }, Margin = new MarginPadding { Right = 5 },
} }
}; };
}
private void bindableValueChanged(bool isChecked) nub.Current.BindTo(Current);
{
State = isChecked ? CheckboxState.Checked : CheckboxState.Unchecked;
}
protected override void Dispose(bool isDisposing) Current.ValueChanged += newValue =>
{ {
if (bindable != null) if (newValue)
bindable.ValueChanged -= bindableValueChanged; sampleChecked?.Play();
base.Dispose(isDisposing); else
sampleUnchecked?.Play();
};
} }
protected override bool OnHover(InputState state) protected override bool OnHover(InputState state)
@ -117,23 +106,5 @@ namespace osu.Game.Graphics.UserInterface
sampleChecked = audio.Sample.Get(@"Checkbox/check-on"); sampleChecked = audio.Sample.Get(@"Checkbox/check-on");
sampleUnchecked = audio.Sample.Get(@"Checkbox/check-off"); sampleUnchecked = audio.Sample.Get(@"Checkbox/check-off");
} }
protected override void OnChecked()
{
sampleChecked?.Play();
nub.State = CheckboxState.Checked;
if (bindable != null)
bindable.Value = true;
}
protected override void OnUnchecked()
{
sampleUnchecked?.Play();
nub.State = CheckboxState.Unchecked;
if (bindable != null)
bindable.Value = false;
}
} }
} }

View File

@ -45,7 +45,7 @@ namespace osu.Game.Graphics.UserInterface
private class OsuDropdownMenuItem : DropdownMenuItem<T> private class OsuDropdownMenuItem : DropdownMenuItem<T>
{ {
public OsuDropdownMenuItem(string text, T value) : base(text, value) public OsuDropdownMenuItem(string text, T current) : base(text, current)
{ {
Foreground.Padding = new MarginPadding(2); Foreground.Padding = new MarginPadding(2);

View File

@ -50,7 +50,6 @@ namespace osu.Game.Graphics.UserInterface
nub = new Nub nub = new Nub
{ {
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
State = CheckboxState.Unchecked,
Expanded = true, Expanded = true,
} }
}; };
@ -94,13 +93,13 @@ namespace osu.Game.Graphics.UserInterface
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{ {
nub.State = CheckboxState.Checked; 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.State = CheckboxState.Unchecked; nub.Current.Value = false;
return base.OnMouseUp(state, args); return base.OnMouseUp(state, args);
} }

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 System;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Framework.Allocation; using osu.Framework.Allocation;
@ -24,8 +23,6 @@ namespace osu.Game.Graphics.UserInterface
private readonly SpriteText text; private readonly SpriteText text;
private readonly TextAwesome icon; private readonly TextAwesome icon;
public event EventHandler<CheckboxState> Action;
private Color4? accentColour; private Color4? accentColour;
public Color4 AccentColour public Color4 AccentColour
{ {
@ -34,7 +31,7 @@ namespace osu.Game.Graphics.UserInterface
{ {
accentColour = value; accentColour = value;
if (State != CheckboxState.Checked) if (Current)
{ {
text.Colour = AccentColour; text.Colour = AccentColour;
icon.Colour = AccentColour; icon.Colour = AccentColour;
@ -48,20 +45,6 @@ namespace osu.Game.Graphics.UserInterface
set { text.Text = value; } set { text.Text = value; }
} }
protected override void OnChecked()
{
fadeIn();
icon.Icon = FontAwesome.fa_check_circle_o;
Action?.Invoke(this, State);
}
protected override void OnUnchecked()
{
fadeOut();
icon.Icon = FontAwesome.fa_circle_o;
Action?.Invoke(this, State);
}
private const float transition_length = 500; private const float transition_length = 500;
private void fadeIn() private void fadeIn()
@ -84,7 +67,7 @@ namespace osu.Game.Graphics.UserInterface
protected override void OnHoverLost(InputState state) protected override void OnHoverLost(InputState state)
{ {
if (State == CheckboxState.Unchecked) if (!Current)
fadeOut(); fadeOut();
base.OnHoverLost(state); base.OnHoverLost(state);
@ -134,6 +117,20 @@ namespace osu.Game.Graphics.UserInterface
Anchor = Anchor.BottomLeft, Anchor = Anchor.BottomLeft,
} }
}; };
Current.ValueChanged += v =>
{
if (v)
{
fadeIn();
icon.Icon = FontAwesome.fa_check_circle_o;
}
else
{
fadeOut();
icon.Icon = FontAwesome.fa_circle_o;
}
};
} }
} }
} }

View File

@ -136,7 +136,7 @@ namespace osu.Game.Modes.Replays
public ReplayMouseState(Vector2 position, IEnumerable<MouseButton> list) public ReplayMouseState(Vector2 position, IEnumerable<MouseButton> list)
{ {
Position = position; Position = position;
list.ForEach(b => PressedButtons.Add(b)); list.ForEach(b => SetPressed(b, true));
} }
} }

View File

@ -16,6 +16,7 @@ using System.Diagnostics;
using System.Linq; using System.Linq;
using osu.Game.Modes.Replays; using osu.Game.Modes.Replays;
using osu.Game.Modes.Scoring; using osu.Game.Modes.Scoring;
using OpenTK;
namespace osu.Game.Modes.UI namespace osu.Game.Modes.UI
{ {
@ -167,6 +168,11 @@ namespace osu.Game.Modes.UI
{ {
public event Action<TJudgement> OnJudgement; public event Action<TJudgement> OnJudgement;
/// <summary>
/// Whether to apply adjustments to the child <see cref="Playfield{TObject,TJudgement}"/> based on our own size.
/// </summary>
public bool AspectAdjust = true;
public sealed override bool ProvidingUserCursor => !HasReplayLoaded && Playfield.ProvidingUserCursor; public sealed override bool ProvidingUserCursor => !HasReplayLoaded && Playfield.ProvidingUserCursor;
protected override Container<Drawable> Content => content; protected override Container<Drawable> Content => content;
@ -219,6 +225,19 @@ namespace osu.Game.Modes.UI
Playfield.PostProcess(); Playfield.PostProcess();
} }
protected override void Update()
{
base.Update();
Playfield.Size = AspectAdjust ? GetPlayfieldAspectAdjust() : Vector2.One;
}
/// <summary>
/// In some cases we want to apply changes to the relative size of our contained <see cref="Playfield{TObject, TJudgement}"/> based on custom conditions.
/// </summary>
/// <returns></returns>
protected virtual Vector2 GetPlayfieldAspectAdjust() => new Vector2(0.75f); //a sane default
/// <summary> /// <summary>
/// Triggered when an object's Judgement is updated. /// Triggered when an object's Judgement is updated.
/// </summary> /// </summary>

View File

@ -1,6 +1,7 @@
// 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 osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Modes.Objects; using osu.Game.Modes.Objects;
@ -63,6 +64,12 @@ namespace osu.Game.Modes.UI
Add(HitObjects); Add(HitObjects);
} }
public override Axes RelativeSizeAxes
{
get { return Axes.Both; }
set { throw new InvalidOperationException($@"{nameof(Playfield<TObject, TJudgement>)}'s {nameof(RelativeSizeAxes)} should never be changed from {Axes.Both}"); }
}
/// <summary> /// <summary>
/// Performs post-processing tasks (if any) after all DrawableHitObjects are loaded into this Playfield. /// Performs post-processing tasks (if any) after all DrawableHitObjects are loaded into this Playfield.
/// </summary> /// </summary>

View File

@ -40,6 +40,7 @@ namespace osu.Game.Modes.UI
Anchor = Anchor.BottomRight, Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight, Origin = Anchor.BottomRight,
Margin = new MarginPadding(10), Margin = new MarginPadding(10),
Y = - TwoLayerButton.SIZE_RETRACTED.Y,
}; };
protected override ScoreCounter CreateScoreCounter() => new ScoreCounter(6) protected override ScoreCounter CreateScoreCounter() => new ScoreCounter(6)

View File

@ -5,7 +5,9 @@ using System;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Transforms;
using osu.Framework.Input; using osu.Framework.Input;
using OpenTK;
namespace osu.Game.Overlays namespace osu.Game.Overlays
{ {
@ -14,9 +16,10 @@ namespace osu.Game.Overlays
private readonly Box fill; private readonly Box fill;
public Action<float> SeekRequested; public Action<float> SeekRequested;
private bool isDragging;
private bool enabled; public bool IsSeeking { get; private set; }
private bool enabled = true;
public bool IsEnabled public bool IsEnabled
{ {
get { return enabled; } get { return enabled; }
@ -46,9 +49,9 @@ namespace osu.Game.Overlays
public void UpdatePosition(float position) public void UpdatePosition(float position)
{ {
if (isDragging || !IsEnabled) return; if (IsSeeking || !IsEnabled) return;
fill.Width = position; updatePosition(position);
} }
private void seek(InputState state) private void seek(InputState state)
@ -56,7 +59,13 @@ namespace osu.Game.Overlays
if (!IsEnabled) return; if (!IsEnabled) return;
float seekLocation = state.Mouse.Position.X / DrawWidth; float seekLocation = state.Mouse.Position.X / DrawWidth;
SeekRequested?.Invoke(seekLocation); SeekRequested?.Invoke(seekLocation);
fill.Width = seekLocation; updatePosition(seekLocation);
}
private void updatePosition(float position)
{
position = MathHelper.Clamp(position, 0, 1);
fill.TransformTo(fill.Width, position, 200, EasingTypes.OutQuint, new TransformSeek());
} }
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
@ -71,12 +80,21 @@ namespace osu.Game.Overlays
return true; return true;
} }
protected override bool OnDragStart(InputState state) => isDragging = true; protected override bool OnDragStart(InputState state) => IsSeeking = true;
protected override bool OnDragEnd(InputState state) protected override bool OnDragEnd(InputState state)
{ {
isDragging = false; IsSeeking = false;
return true; return true;
} }
private class TransformSeek : TransformFloat
{
public override void Apply(Drawable d)
{
base.Apply(d);
d.Width = CurrentValue;
}
}
} }
} }

View File

@ -30,7 +30,7 @@ namespace osu.Game.Overlays
{ {
private Drawable currentBackground; private Drawable currentBackground;
private DragBar progress; private DragBar progress;
private TextAwesome playButton; private Button playButton;
private SpriteText title, artist; private SpriteText title, artist;
private List<BeatmapSetInfo> playList; private List<BeatmapSetInfo> playList;
@ -46,6 +46,10 @@ namespace osu.Game.Overlays
private Container dragContainer; private Container dragContainer;
private const float progress_height = 10;
private const float bottom_black_area_height = 55;
public MusicController() public MusicController()
{ {
Width = 400; Width = 400;
@ -115,12 +119,33 @@ namespace osu.Game.Overlays
Text = @"Nothing to play", Text = @"Nothing to play",
Font = @"Exo2.0-BoldItalic" Font = @"Exo2.0-BoldItalic"
}, },
new ClickableContainer new Container
{
Padding = new MarginPadding { Bottom = progress_height },
Height = bottom_black_area_height,
RelativeSizeAxes = Axes.X,
Origin = Anchor.BottomCentre,
Anchor = Anchor.BottomCentre,
Children = new Drawable[]
{
new FillFlowContainer<Button>
{ {
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(5),
Origin = Anchor.Centre, Origin = Anchor.Centre,
Anchor = Anchor.BottomCentre, Anchor = Anchor.Centre,
Position = new Vector2(0, -30), Children = new[]
{
new Button
{
Action = prev,
Icon = FontAwesome.fa_step_backward,
},
playButton = new Button
{
Scale = new Vector2(1.4f),
IconScale = new Vector2(1.4f),
Action = () => Action = () =>
{ {
if (current?.Track == null) return; if (current?.Track == null) return;
@ -129,75 +154,29 @@ namespace osu.Game.Overlays
else else
current.Track.Start(); current.Track.Start();
}, },
Children = new Drawable[]
{
playButton = new TextAwesome
{
TextSize = 30,
Icon = FontAwesome.fa_play_circle_o, Icon = FontAwesome.fa_play_circle_o,
Origin = Anchor.Centre,
Anchor = Anchor.Centre
}
}
}, },
new ClickableContainer new Button
{ {
AutoSizeAxes = Axes.Both,
Origin = Anchor.Centre,
Anchor = Anchor.BottomCentre,
Position = new Vector2(-30, -30),
Action = prev,
Children = new Drawable[]
{
new TextAwesome
{
TextSize = 15,
Icon = FontAwesome.fa_step_backward,
Origin = Anchor.Centre,
Anchor = Anchor.Centre
}
}
},
new ClickableContainer
{
AutoSizeAxes = Axes.Both,
Origin = Anchor.Centre,
Anchor = Anchor.BottomCentre,
Position = new Vector2(30, -30),
Action = next, Action = next,
Children = new Drawable[]
{
new TextAwesome
{
TextSize = 15,
Icon = FontAwesome.fa_step_forward, Icon = FontAwesome.fa_step_forward,
Origin = Anchor.Centre, },
Anchor = Anchor.Centre
}
} }
}, },
new ClickableContainer new Button
{ {
AutoSizeAxes = Axes.Both,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Anchor = Anchor.BottomRight, Anchor = Anchor.CentreRight,
Position = new Vector2(20, -30), Position = new Vector2(-bottom_black_area_height / 2, 0),
Children = new Drawable[]
{
new TextAwesome
{
TextSize = 15,
Icon = FontAwesome.fa_bars, Icon = FontAwesome.fa_bars,
Origin = Anchor.Centre, },
Anchor = Anchor.Centre
}
} }
}, },
progress = new DragBar progress = new DragBar
{ {
Origin = Anchor.BottomCentre, Origin = Anchor.BottomCentre,
Anchor = Anchor.BottomCentre, Anchor = Anchor.BottomCentre,
Height = 10, Height = progress_height,
Colour = colours.Yellow, Colour = colours.Yellow,
SeekRequested = seek SeekRequested = seek
} }
@ -237,7 +216,7 @@ namespace osu.Game.Overlays
if (current?.TrackLoaded ?? false) if (current?.TrackLoaded ?? false)
{ {
progress.UpdatePosition((float)(current.Track.CurrentTime / current.Track.Length)); progress.UpdatePosition(current.Track.Length == 0 ? 0 : (float)(current.Track.CurrentTime / current.Track.Length));
playButton.Icon = current.Track.IsRunning ? FontAwesome.fa_pause_circle_o : FontAwesome.fa_play_circle_o; playButton.Icon = current.Track.IsRunning ? FontAwesome.fa_pause_circle_o : FontAwesome.fa_play_circle_o;
if (current.Track.HasCompleted && !current.Track.Looping) next(); if (current.Track.HasCompleted && !current.Track.Looping) next();
@ -416,7 +395,7 @@ namespace osu.Game.Overlays
new Box new Box
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Height = 50, Height = bottom_black_area_height,
Origin = Anchor.BottomCentre, Origin = Anchor.BottomCentre,
Anchor = Anchor.BottomCentre, Anchor = Anchor.BottomCentre,
Colour = Color4.Black.Opacity(0.5f) Colour = Color4.Black.Opacity(0.5f)
@ -430,5 +409,105 @@ 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

@ -33,8 +33,8 @@ namespace osu.Game.Overlays.Options
set set
{ {
bindable = value; bindable = value;
dropdown.SelectedValue.BindTo(bindable); dropdown.Current.BindTo(bindable);
if (bindable.Disabled) if (value?.Disabled ?? true)
Alpha = 0.3f; Alpha = 0.3f;
} }
} }

View File

@ -27,12 +27,14 @@ namespace osu.Game.Overlays.Options
} }
} }
public BindableNumber<T> Bindable private Bindable<T> bindable;
public Bindable<T> Bindable
{ {
get { return slider.Value; }
set set
{ {
slider.Value = value; bindable = value;
slider.Current.BindTo(bindable);
if (value?.Disabled ?? true) if (value?.Disabled ?? true)
Alpha = 0.3f; Alpha = 0.3f;
} }

View File

@ -2,7 +2,6 @@
// 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.Framework.Configuration; using osu.Framework.Configuration;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.Options namespace osu.Game.Overlays.Options
@ -15,38 +14,11 @@ namespace osu.Game.Overlays.Options
{ {
set set
{ {
if (bindable != null)
bindable.ValueChanged -= bindableValueChanged;
bindable = value; bindable = value;
if (bindable != null) Current.BindTo(bindable);
{ if (value?.Disabled ?? true)
Text = bindable.Value;
bindable.ValueChanged += bindableValueChanged;
}
if (bindable?.Disabled ?? true)
Alpha = 0.3f; Alpha = 0.3f;
} }
} }
public OptionTextBox()
{
OnChange += onChange;
}
private void onChange(TextBox sender, bool newText)
{
if (bindable != null)
bindable.Value = Text;
}
private void bindableValueChanged(string newValue) => Text = newValue;
protected override void Dispose(bool isDisposing)
{
if (bindable != null)
bindable.ValueChanged -= bindableValueChanged;
base.Dispose(isDisposing);
}
} }
} }

View File

@ -39,7 +39,13 @@ namespace osu.Game.Overlays.Options.Sections.Audio
if (deviceItems.All(kv => kv.Value != preferredDeviceName)) if (deviceItems.All(kv => kv.Value != preferredDeviceName))
deviceItems.Add(new KeyValuePair<string, string>(preferredDeviceName, preferredDeviceName)); deviceItems.Add(new KeyValuePair<string, string>(preferredDeviceName, preferredDeviceName));
dropdown.Items = deviceItems; // The option dropdown for audio device selection lists all audio
// device names. Dropdowns, however, may not have multiple identical
// keys. Thus, we remove duplicate audio device names from
// the dropdown. BASS does not give us a simple mechanism to select
// specific audio devices in such a case anyways. Such
// functionality would require involved OS-specific code.
dropdown.Items = deviceItems.Distinct().ToList();
} }
private void onDeviceChanged(string name) => updateItems(); private void onDeviceChanged(string name) => updateItems();

View File

@ -194,8 +194,6 @@ namespace osu.Game.Screens.Menu
return true; return true;
} }
protected override bool OnDragStart(InputState state) => true;
protected override bool OnClick(InputState state) protected override bool OnClick(InputState state)
{ {
if (!Interactive) return false; if (!Interactive) return false;

View File

@ -0,0 +1,82 @@
// 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.Input;
using OpenTK.Input;
using osu.Framework.Allocation;
using osu.Framework.Audio.Sample;
using osu.Framework.Audio;
using System;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using OpenTK.Graphics;
namespace osu.Game.Screens.Play
{
public class HotkeyRetryOverlay : Container
{
public Action Action;
private SampleChannel retrySample;
private Box overlay;
private const int activate_delay = 400;
private const int fadeout_delay = 200;
private bool fired;
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
retrySample = audio.Sample.Get(@"Menu/menuback");
RelativeSizeAxes = Axes.Both;
AlwaysPresent = true;
Children = new Drawable[]
{
overlay = new Box
{
Alpha = 0,
Colour = Color4.Black,
RelativeSizeAxes = Axes.Both,
}
};
}
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{
if (args.Repeat) return false;
if (args.Key == Key.Tilde)
{
overlay.FadeIn(activate_delay, EasingTypes.Out);
return true;
}
return base.OnKeyDown(state, args);
}
protected override bool OnKeyUp(InputState state, KeyUpEventArgs args)
{
if (args.Key == Key.Tilde && !fired)
{
overlay.FadeOut(fadeout_delay, EasingTypes.Out);
return true;
}
return base.OnKeyUp(state, args);
}
protected override void Update()
{
base.Update();
if (!fired && overlay.Alpha == 1)
{
fired = true;
retrySample.Play();
Action?.Invoke();
}
}
}
}

View File

@ -20,6 +20,7 @@ using osu.Game.Screens.Backgrounds;
using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking;
using System; using System;
using System.Linq; using System.Linq;
using osu.Framework.Threading;
using osu.Game.Modes.Scoring; using osu.Game.Modes.Scoring;
namespace osu.Game.Screens.Play namespace osu.Game.Screens.Play
@ -156,6 +157,10 @@ namespace osu.Game.Screens.Play
{ {
OnRetry = Restart, OnRetry = Restart,
OnQuit = Exit, OnQuit = Exit,
},
new HotkeyRetryOverlay
{
Action = Restart,
} }
}; };
} }
@ -237,14 +242,16 @@ namespace osu.Game.Screens.Play
}); });
} }
private ScheduledDelegate onCompletionEvent;
private void onCompletion() private void onCompletion()
{ {
// Only show the completion screen if the player hasn't failed // Only show the completion screen if the player hasn't failed
if (scoreProcessor.HasFailed) if (scoreProcessor.HasFailed || onCompletionEvent != null)
return; return;
Delay(1000); Delay(1000);
Schedule(delegate onCompletionEvent = Schedule(delegate
{ {
ValidForResume = false; ValidForResume = false;
Push(new Results Push(new Results
@ -289,6 +296,10 @@ namespace osu.Game.Screens.Play
sourceClock.Start(); sourceClock.Start();
initializeSkipButton(); initializeSkipButton();
}); });
//keep in mind this is using the interpolatedSourceClock so won't be run as early as we may expect.
HitRenderer.Alpha = 0;
HitRenderer.FadeIn(750, EasingTypes.OutQuint);
} }
protected override void OnSuspending(Screen next) protected override void OnSuspending(Screen next)
@ -301,26 +312,25 @@ namespace osu.Game.Screens.Play
protected override bool OnExiting(Screen next) protected override bool OnExiting(Screen next)
{ {
if (pauseOverlay == null) return false; if (pauseOverlay != null && !HitRenderer.HasReplayLoaded)
{
if (HitRenderer.HasReplayLoaded) //pause screen override logic.
return false; if (pauseOverlay?.State == Visibility.Hidden && !canPause) return true;
if (pauseOverlay.State != Visibility.Visible && !canPause) return true;
if (!IsPaused && sourceClock.IsRunning) // For if the user presses escape quickly when entering the map if (!IsPaused && sourceClock.IsRunning) // For if the user presses escape quickly when entering the map
{ {
Pause(); Pause();
return true; return true;
} }
else }
{
HitRenderer?.FadeOut(60);
FadeOut(250); FadeOut(250);
Content.ScaleTo(0.7f, 750, EasingTypes.InQuint); Content.ScaleTo(0.7f, 750, EasingTypes.InQuint);
Background?.FadeTo(1f, 200); Background?.FadeTo(1f, 200);
return base.OnExiting(next); return base.OnExiting(next);
} }
}
private Bindable<bool> mouseWheelDisabled; private Bindable<bool> mouseWheelDisabled;

View File

@ -8,7 +8,6 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
@ -24,7 +23,7 @@ namespace osu.Game.Screens.Select
private void invokeOnFilter() private void invokeOnFilter()
{ {
OnFilter?.Invoke(tabs.SelectedItem, modsCheckbox.State == CheckboxState.Checked); OnFilter?.Invoke(tabs.Current, modsCheckbox.Current);
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -61,10 +60,10 @@ namespace osu.Game.Screens.Select
}, },
}; };
tabs.SelectedItem.ValueChanged += item => invokeOnFilter(); tabs.Current.ValueChanged += item => invokeOnFilter();
modsCheckbox.Action += (sender, e) => invokeOnFilter(); modsCheckbox.Current.ValueChanged += item => invokeOnFilter();
tabs.SelectedItem.Value = BeatmapDetailTab.Global; tabs.Current.Value = BeatmapDetailTab.Global;
} }
} }

View File

@ -93,11 +93,6 @@ namespace osu.Game.Screens.Select
searchTextBox = new SearchTextBox searchTextBox = new SearchTextBox
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
OnChange = (sender, newText) =>
{
if (newText)
FilterChanged?.Invoke(CreateCriteria());
},
Exit = () => Exit?.Invoke(), Exit = () => Exit?.Invoke(),
}, },
new Box new Box
@ -149,10 +144,12 @@ namespace osu.Game.Screens.Select
} }
}; };
searchTextBox.Current.ValueChanged += t => FilterChanged?.Invoke(CreateCriteria());
groupTabs.PinItem(GroupMode.All); groupTabs.PinItem(GroupMode.All);
groupTabs.PinItem(GroupMode.RecentlyPlayed); groupTabs.PinItem(GroupMode.RecentlyPlayed);
groupTabs.SelectedItem.ValueChanged += val => Group = val; groupTabs.Current.ValueChanged += val => Group = val;
sortTabs.SelectedItem.ValueChanged += val => Sort = val; sortTabs.Current.ValueChanged += val => Sort = val;
} }
public void Deactivate() public void Deactivate()

View File

@ -181,6 +181,7 @@
<Compile Include="Screens\Backgrounds\BackgroundScreenEmpty.cs" /> <Compile Include="Screens\Backgrounds\BackgroundScreenEmpty.cs" />
<Compile Include="Screens\Charts\ChartInfo.cs" /> <Compile Include="Screens\Charts\ChartInfo.cs" />
<Compile Include="Screens\Edit\Editor.cs" /> <Compile Include="Screens\Edit\Editor.cs" />
<Compile Include="Screens\Play\HotkeyRetryOverlay.cs" />
<Compile Include="Screens\ScreenWhiteBox.cs" /> <Compile Include="Screens\ScreenWhiteBox.cs" />
<Compile Include="Screens\Loader.cs" /> <Compile Include="Screens\Loader.cs" />
<Compile Include="Screens\Menu\Button.cs" /> <Compile Include="Screens\Menu\Button.cs" />