diff --git a/osu.Desktop/app.manifest b/osu.Desktop/app.manifest
new file mode 100644
index 0000000000..2e9127bf44
--- /dev/null
+++ b/osu.Desktop/app.manifest
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+
\ No newline at end of file
diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj
index 453cf6f94d..01e4ada2f1 100644
--- a/osu.Desktop/osu.Desktop.csproj
+++ b/osu.Desktop/osu.Desktop.csproj
@@ -8,6 +8,7 @@
osu!lazer
osu!lazer
lazer.ico
+ app.manifest
0.0.0
0.0.0
diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs
index 470ba3acae..05b38ae195 100644
--- a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs
@@ -11,6 +11,9 @@ namespace osu.Game.Rulesets.Osu.Skinning
{
public class LegacyCursor : CompositeDrawable
{
+ private NonPlayfieldSprite cursor;
+ private bool spin;
+
public LegacyCursor()
{
Size = new Vector2(50);
@@ -22,6 +25,8 @@ namespace osu.Game.Rulesets.Osu.Skinning
[BackgroundDependencyLoader]
private void load(ISkinSource skin)
{
+ spin = skin.GetConfig(OsuSkinConfiguration.CursorRotate)?.Value ?? true;
+
InternalChildren = new Drawable[]
{
new NonPlayfieldSprite
@@ -30,7 +35,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
- new NonPlayfieldSprite
+ cursor = new NonPlayfieldSprite
{
Texture = skin.GetTexture("cursor"),
Anchor = Anchor.Centre,
@@ -38,5 +43,11 @@ namespace osu.Game.Rulesets.Osu.Skinning
}
};
}
+
+ protected override void LoadComplete()
+ {
+ if (spin)
+ cursor.Spin(10000, RotationDirection.Clockwise);
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs
index 479c250eab..f5b7d9166f 100644
--- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs
@@ -49,7 +49,11 @@ namespace osu.Game.Rulesets.Osu.Skinning
return this.GetAnimation(component.LookupName, true, false);
case OsuSkinComponents.SliderFollowCircle:
- return this.GetAnimation("sliderfollowcircle", true, true);
+ var followCircle = this.GetAnimation("sliderfollowcircle", true, true);
+ if (followCircle != null)
+ // follow circles are 2x the hitcircle resolution in legacy skins (since they are scaled down from >1x
+ followCircle.Scale *= 0.5f;
+ return followCircle;
case OsuSkinComponents.SliderBall:
var sliderBallContent = this.GetAnimation("sliderb", true, true, "");
diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs
index 98219cafe8..5d99960f10 100644
--- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs
@@ -11,5 +11,6 @@ namespace osu.Game.Rulesets.Osu.Skinning
SliderPathRadius,
AllowSliderBallTint,
CursorExpand,
+ CursorRotate
}
}
diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs
index 6e5b3b93e9..e618256c03 100644
--- a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs
+++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs
@@ -13,6 +13,8 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components.Timeline;
using osuTK;
using osuTK.Graphics;
@@ -25,6 +27,7 @@ namespace osu.Game.Tests.Visual.Editor
public override IReadOnlyList RequiredTypes => new[]
{
typeof(TimelineArea),
+ typeof(TimelineHitObjectDisplay),
typeof(Timeline),
typeof(TimelineButton),
typeof(CentreMarker)
@@ -35,6 +38,8 @@ namespace osu.Game.Tests.Visual.Editor
{
Beatmap.Value = new WaveformTestBeatmap(audio);
+ var editorBeatmap = new EditorBeatmap((Beatmap)Beatmap.Value.Beatmap);
+
Children = new Drawable[]
{
new FillFlowContainer
@@ -50,6 +55,7 @@ namespace osu.Game.Tests.Visual.Editor
},
new TimelineArea
{
+ Child = new TimelineHitObjectDisplay(editorBeatmap),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X,
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
index 74ae641bfe..dbea8d28a6 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
+using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
@@ -19,6 +20,7 @@ using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens;
@@ -55,6 +57,9 @@ namespace osu.Game.Tests.Visual.Gameplay
beforeLoadAction?.Invoke();
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
+ foreach (var mod in Mods.Value.OfType())
+ mod.ApplyToClock(Beatmap.Value.Track);
+
InputManager.Child = container = new TestPlayerLoaderContainer(
loader = new TestPlayerLoader(() =>
{
@@ -63,6 +68,24 @@ namespace osu.Game.Tests.Visual.Gameplay
}));
}
+ ///
+ /// When exits early, it has to wait for the player load task
+ /// to complete before running disposal on player. This previously caused an issue where mod
+ /// speed adjustments were undone too late, causing cross-screen pollution.
+ ///
+ [Test]
+ public void TestEarlyExit()
+ {
+ AddStep("load dummy beatmap", () => ResetPlayer(false, () => Mods.Value = new[] { new OsuModNightcore() }));
+ AddUntilStep("wait for current", () => loader.IsCurrentScreen());
+ AddAssert("mod rate applied", () => Beatmap.Value.Track.Rate != 1);
+ AddStep("exit loader", () => loader.Exit());
+ AddUntilStep("wait for not current", () => !loader.IsCurrentScreen());
+ AddAssert("player did not load", () => !player.IsLoaded);
+ AddUntilStep("player disposed", () => loader.DisposalTask?.IsCompleted == true);
+ AddAssert("mod rate still applied", () => Beatmap.Value.Track.Rate != 1);
+ }
+
[Test]
public void TestBlockLoadViaMouseMovement()
{
@@ -196,6 +219,8 @@ namespace osu.Game.Tests.Visual.Gameplay
{
public new VisualSettings VisualSettings => base.VisualSettings;
+ public new Task DisposalTask => base.DisposalTask;
+
public TestPlayerLoader(Func createPlayer)
: base(createPlayer)
{
diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs
index 3d39bb7003..7207506ccd 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs
@@ -1,9 +1,12 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
+using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
+using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Dialog;
namespace osu.Game.Tests.Visual.UserInterface
@@ -11,13 +14,22 @@ namespace osu.Game.Tests.Visual.UserInterface
[TestFixture]
public class TestScenePopupDialog : OsuTestScene
{
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(PopupDialogOkButton),
+ typeof(PopupDialogCancelButton),
+ typeof(PopupDialogButton),
+ typeof(DialogButton),
+ };
+
public TestScenePopupDialog()
{
- Add(new TestPopupDialog
- {
- RelativeSizeAxes = Axes.Both,
- State = { Value = Framework.Graphics.Containers.Visibility.Visible },
- });
+ AddStep("new popup", () =>
+ Add(new TestPopupDialog
+ {
+ RelativeSizeAxes = Axes.Both,
+ State = { Value = Framework.Graphics.Containers.Visibility.Visible },
+ }));
}
private class TestPopupDialog : PopupDialog
diff --git a/osu.Game/Graphics/UserInterface/DialogButton.cs b/osu.Game/Graphics/UserInterface/DialogButton.cs
index 927ad13829..aed07e56ee 100644
--- a/osu.Game/Graphics/UserInterface/DialogButton.cs
+++ b/osu.Game/Graphics/UserInterface/DialogButton.cs
@@ -20,9 +20,10 @@ namespace osu.Game.Graphics.UserInterface
{
public class DialogButton : OsuClickableContainer
{
+ private const float idle_width = 0.8f;
private const float hover_width = 0.9f;
+
private const float hover_duration = 500;
- private const float glow_fade_duration = 250;
private const float click_duration = 200;
public readonly BindableBool Selected = new BindableBool();
@@ -99,7 +100,7 @@ namespace osu.Game.Graphics.UserInterface
RelativeSizeAxes = Axes.Both,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
- Width = 0.8f,
+ Width = idle_width,
Masking = true,
MaskingSmoothness = 2,
EdgeEffect = new EdgeEffectParameters
@@ -199,26 +200,50 @@ namespace osu.Game.Graphics.UserInterface
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => backgroundContainer.ReceivePositionalInputAt(screenSpacePos);
+ private bool clickAnimating;
+
protected override bool OnClick(ClickEvent e)
{
- colourContainer.ResizeTo(new Vector2(1.5f, 1f), click_duration, Easing.In);
- flash();
-
- this.Delay(click_duration).Schedule(delegate
+ var flash = new Box
{
- colourContainer.ResizeTo(new Vector2(0.8f, 1f));
- spriteText.Spacing = Vector2.Zero;
- glowContainer.FadeOut();
- });
+ RelativeSizeAxes = Axes.Both,
+ Colour = ButtonColour,
+ Blending = BlendingParameters.Additive,
+ Alpha = 0.05f
+ };
+
+ colourContainer.Add(flash);
+ flash.FadeOutFromOne(100).Expire();
+
+ clickAnimating = true;
+ colourContainer.ResizeWidthTo(colourContainer.Width * 1.05f, 100, Easing.OutQuint)
+ .OnComplete(_ =>
+ {
+ clickAnimating = false;
+ Selected.TriggerChange();
+ });
return base.OnClick(e);
}
+ protected override bool OnMouseDown(MouseDownEvent e)
+ {
+ colourContainer.ResizeWidthTo(hover_width * 0.98f, click_duration * 4, Easing.OutQuad);
+ return base.OnMouseDown(e);
+ }
+
+ protected override bool OnMouseUp(MouseUpEvent e)
+ {
+ if (Selected.Value)
+ colourContainer.ResizeWidthTo(hover_width, click_duration, Easing.In);
+ return base.OnMouseUp(e);
+ }
+
protected override bool OnHover(HoverEvent e)
{
base.OnHover(e);
-
Selected.Value = true;
+
return true;
}
@@ -230,36 +255,23 @@ namespace osu.Game.Graphics.UserInterface
private void selectionChanged(ValueChangedEvent args)
{
+ if (clickAnimating)
+ return;
+
if (args.NewValue)
{
spriteText.TransformSpacingTo(hoverSpacing, hover_duration, Easing.OutElastic);
- colourContainer.ResizeTo(new Vector2(hover_width, 1f), hover_duration, Easing.OutElastic);
- glowContainer.FadeIn(glow_fade_duration, Easing.Out);
+ colourContainer.ResizeWidthTo(hover_width, hover_duration, Easing.OutElastic);
+ glowContainer.FadeIn(hover_duration, Easing.OutQuint);
}
else
{
- colourContainer.ResizeTo(new Vector2(0.8f, 1f), hover_duration, Easing.OutElastic);
+ colourContainer.ResizeWidthTo(idle_width, hover_duration, Easing.OutElastic);
spriteText.TransformSpacingTo(Vector2.Zero, hover_duration, Easing.OutElastic);
- glowContainer.FadeOut(glow_fade_duration, Easing.Out);
+ glowContainer.FadeOut(hover_duration, Easing.OutQuint);
}
}
- private void flash()
- {
- var flash = new Box
- {
- RelativeSizeAxes = Axes.Both
- };
-
- colourContainer.Add(flash);
-
- flash.Colour = ButtonColour;
- flash.Blending = BlendingParameters.Additive;
- flash.Alpha = 0.3f;
- flash.FadeOutFromOne(click_duration);
- flash.Expire();
- }
-
private void updateGlow()
{
leftGlow.Colour = ColourInfo.GradientHorizontal(new Color4(ButtonColour.R, ButtonColour.G, ButtonColour.B, 0f), ButtonColour);
diff --git a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs
index 79ce04ed66..b941cd8973 100644
--- a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs
+++ b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs
@@ -34,7 +34,7 @@ namespace osu.Game.Online.API.Requests.Responses
PP = PP,
Beatmap = Beatmap,
RulesetID = OnlineRulesetID,
- Hash = "online", // todo: temporary?
+ Hash = Replay ? "online" : string.Empty, // todo: temporary?
Rank = Rank,
Ruleset = ruleset,
Mods = mods,
diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs
index 58f5f02956..f6723839b2 100644
--- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs
+++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs
@@ -63,7 +63,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
return;
for (int i = 0; i < value.Count; i++)
- backgroundFlow.Add(new ScoreTableRowBackground(i));
+ backgroundFlow.Add(new ScoreTableRowBackground(i, value[i]));
Columns = createHeaders(value[0]);
Content = value.Select((s, i) => createContent(i, s)).ToArray().ToRectangular();
diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRowBackground.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRowBackground.cs
index d820f4d89d..724a7f8b55 100644
--- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRowBackground.cs
+++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRowBackground.cs
@@ -7,6 +7,8 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
+using osu.Game.Online.API;
+using osu.Game.Scoring;
namespace osu.Game.Overlays.BeatmapSet.Scores
{
@@ -17,8 +19,14 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
private readonly Box hoveredBackground;
private readonly Box background;
- public ScoreTableRowBackground(int index)
+ private readonly int index;
+ private readonly ScoreInfo score;
+
+ public ScoreTableRowBackground(int index, ScoreInfo score)
{
+ this.index = index;
+ this.score = score;
+
RelativeSizeAxes = Axes.X;
Height = 25;
@@ -37,16 +45,21 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
Alpha = 0,
},
};
-
- if (index % 2 != 0)
- background.Alpha = 0;
}
[BackgroundDependencyLoader]
- private void load(OsuColour colours)
+ private void load(OsuColour colours, IAPIProvider api)
{
- hoveredBackground.Colour = colours.Gray4;
- background.Colour = colours.Gray3;
+ var isOwnScore = api.LocalUser.Value.Id == score.UserID;
+
+ if (isOwnScore)
+ background.Colour = colours.GreenDarker;
+ else if (index % 2 == 0)
+ background.Colour = colours.Gray3;
+ else
+ background.Alpha = 0;
+
+ hoveredBackground.Colour = isOwnScore ? colours.GreenDark : colours.Gray4;
}
protected override bool OnHover(HoverEvent e)
diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs
index c6b4787ff1..69a4a4181a 100644
--- a/osu.Game/Overlays/Mods/ModButton.cs
+++ b/osu.Game/Overlays/Mods/ModButton.cs
@@ -167,10 +167,6 @@ namespace osu.Game.Overlays.Mods
{
switch (e.Button)
{
- case MouseButton.Left:
- SelectNext(1);
- break;
-
case MouseButton.Right:
SelectNext(-1);
break;
@@ -180,6 +176,13 @@ namespace osu.Game.Overlays.Mods
return true;
}
+ protected override bool OnClick(ClickEvent e)
+ {
+ SelectNext(1);
+
+ return true;
+ }
+
///
/// Select the next available mod in a specified direction.
///
diff --git a/osu.Game/Overlays/SettingsSubPanel.cs b/osu.Game/Overlays/SettingsSubPanel.cs
index 5000156e97..1fa233d9d4 100644
--- a/osu.Game/Overlays/SettingsSubPanel.cs
+++ b/osu.Game/Overlays/SettingsSubPanel.cs
@@ -3,16 +3,14 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
-using osu.Framework.Input.Bindings;
-using osu.Framework.Input.Events;
using osu.Game.Graphics;
-using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
-using osu.Game.Input.Bindings;
+using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Settings;
-using osu.Game.Screens.Ranking;
using osuTK;
+using osuTK.Graphics;
namespace osu.Game.Overlays
{
@@ -36,21 +34,21 @@ namespace osu.Game.Overlays
protected override bool DimMainContent => false; // dimming is handled by main overlay
- private class BackButton : OsuClickableContainer, IKeyBindingHandler
+ private class BackButton : OsuButton
{
- private AspectContainer aspect;
-
[BackgroundDependencyLoader]
private void load()
{
Size = new Vector2(Sidebar.DEFAULT_WIDTH);
- Children = new Drawable[]
+
+ BackgroundColour = Color4.Black;
+
+ AddRange(new Drawable[]
{
- aspect = new AspectContainer
+ new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Y,
Children = new Drawable[]
{
new SpriteIcon
@@ -71,34 +69,8 @@ namespace osu.Game.Overlays
},
}
}
- };
+ });
}
-
- protected override bool OnMouseDown(MouseDownEvent e)
- {
- aspect.ScaleTo(0.75f, 2000, Easing.OutQuint);
- return base.OnMouseDown(e);
- }
-
- protected override bool OnMouseUp(MouseUpEvent e)
- {
- aspect.ScaleTo(1, 1000, Easing.OutElastic);
- return base.OnMouseUp(e);
- }
-
- public bool OnPressed(GlobalAction action)
- {
- switch (action)
- {
- case GlobalAction.Back:
- Click();
- return true;
- }
-
- return false;
- }
-
- public bool OnReleased(GlobalAction action) => false;
}
}
}
diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs
index 805fc2b46f..9ac967ef74 100644
--- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs
+++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs
@@ -34,7 +34,9 @@ namespace osu.Game.Rulesets.Edit
where TObject : HitObject
{
protected IRulesetConfigManager Config { get; private set; }
- protected EditorBeatmap EditorBeatmap { get; private set; }
+
+ protected new EditorBeatmap EditorBeatmap { get; private set; }
+
protected readonly Ruleset Ruleset;
[Resolved]
@@ -148,7 +150,7 @@ namespace osu.Game.Rulesets.Edit
beatmapProcessor = Ruleset.CreateBeatmapProcessor(playableBeatmap);
- EditorBeatmap = new EditorBeatmap(playableBeatmap);
+ base.EditorBeatmap = EditorBeatmap = new EditorBeatmap(playableBeatmap);
EditorBeatmap.HitObjectAdded += addHitObject;
EditorBeatmap.HitObjectRemoved += removeHitObject;
EditorBeatmap.StartTimeChanged += UpdateHitObject;
@@ -333,6 +335,11 @@ namespace osu.Game.Rulesets.Edit
///
public abstract IEnumerable HitObjects { get; }
+ ///
+ /// An editor-specific beatmap, exposing mutation events.
+ ///
+ public IEditorBeatmap EditorBeatmap { get; protected set; }
+
///
/// Whether the user's cursor is currently in an area of the that is valid for placement.
///
diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs
index 26d9614631..7706e33179 100644
--- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs
+++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs
@@ -14,12 +14,14 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
///
/// Represents a part of the summary timeline..
///
- public abstract class TimelinePart : CompositeDrawable
+ public abstract class TimelinePart : Container
{
protected readonly IBindable Beatmap = new Bindable();
private readonly Container timeline;
+ protected override Container Content => timeline;
+
protected TimelinePart()
{
AddInternal(timeline = new Container { RelativeSizeAxes = Axes.Both });
@@ -50,8 +52,6 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
timeline.RelativeChildSize = new Vector2((float)Math.Max(1, Beatmap.Value.Track.Length), 1);
}
- protected void Add(Drawable visualisation) => timeline.Add(visualisation);
-
protected virtual void LoadBeatmap(WorkingBeatmap beatmap)
{
timeline.Clear();
diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs
index 748c9e2ba3..b4f3b1f610 100644
--- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs
@@ -36,7 +36,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{
this.adjustableClock = adjustableClock;
- Child = waveform = new WaveformGraph
+ Add(waveform = new WaveformGraph
{
RelativeSizeAxes = Axes.Both,
Colour = colours.Blue.Opacity(0.2f),
@@ -44,7 +44,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
MidColour = colours.BlueDark,
HighColour = colours.BlueDarker,
Depth = float.MaxValue
- };
+ });
// We don't want the centre marker to scroll
AddInternal(new CentreMarker());
diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs
index 863a120fc3..02e5db306d 100644
--- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@@ -11,17 +12,18 @@ using osuTK;
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{
- public class TimelineArea : CompositeDrawable
+ public class TimelineArea : Container
{
- private readonly Timeline timeline;
+ private readonly Timeline timeline = new Timeline { RelativeSizeAxes = Axes.Both };
- public TimelineArea()
+ protected override Container Content => timeline;
+
+ [BackgroundDependencyLoader]
+ private void load()
{
Masking = true;
CornerRadius = 5;
- OsuCheckbox hitObjectsCheckbox;
- OsuCheckbox hitSoundsCheckbox;
OsuCheckbox waveformCheckbox;
InternalChildren = new Drawable[]
@@ -60,8 +62,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
Spacing = new Vector2(0, 4),
Children = new[]
{
- hitObjectsCheckbox = new OsuCheckbox { LabelText = "Hit objects" },
- hitSoundsCheckbox = new OsuCheckbox { LabelText = "Hit sounds" },
waveformCheckbox = new OsuCheckbox { LabelText = "Waveform" }
}
}
@@ -107,7 +107,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
}
}
},
- timeline = new Timeline { RelativeSizeAxes = Axes.Both }
+ timeline
},
},
ColumnDimensions = new[]
@@ -119,8 +119,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
}
};
- hitObjectsCheckbox.Current.Value = true;
- hitSoundsCheckbox.Current.Value = true;
waveformCheckbox.Current.Value = true;
timeline.WaveformVisible.BindTo(waveformCheckbox.Current);
diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs
new file mode 100644
index 0000000000..db4aca75e5
--- /dev/null
+++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs
@@ -0,0 +1,108 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Screens.Edit.Compose.Components.Timeline
+{
+ internal class TimelineHitObjectDisplay : TimelinePart
+ {
+ private IEditorBeatmap beatmap { get; }
+
+ public TimelineHitObjectDisplay(IEditorBeatmap beatmap)
+ {
+ RelativeSizeAxes = Axes.Both;
+
+ this.beatmap = beatmap;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ foreach (var h in beatmap.HitObjects)
+ add(h);
+
+ beatmap.HitObjectAdded += add;
+ beatmap.HitObjectRemoved += remove;
+ beatmap.StartTimeChanged += h =>
+ {
+ remove(h);
+ add(h);
+ };
+ }
+
+ private void remove(HitObject h)
+ {
+ foreach (var d in Children.OfType().Where(c => c.HitObject == h))
+ d.Expire();
+ }
+
+ private void add(HitObject h)
+ {
+ var yOffset = Children.Count(d => d.X == h.StartTime);
+
+ Add(new TimelineHitObjectRepresentation(h) { Y = -yOffset * TimelineHitObjectRepresentation.THICKNESS });
+ }
+
+ private class TimelineHitObjectRepresentation : CompositeDrawable
+ {
+ public const float THICKNESS = 3;
+
+ public readonly HitObject HitObject;
+
+ public TimelineHitObjectRepresentation(HitObject hitObject)
+ {
+ HitObject = hitObject;
+ Anchor = Anchor.CentreLeft;
+ Origin = Anchor.CentreLeft;
+
+ Width = (float)(hitObject.GetEndTime() - hitObject.StartTime);
+
+ X = (float)hitObject.StartTime;
+
+ RelativePositionAxes = Axes.X;
+ RelativeSizeAxes = Axes.X;
+
+ if (hitObject is IHasEndTime)
+ {
+ AddInternal(new Container
+ {
+ CornerRadius = 2,
+ Masking = true,
+ Size = new Vector2(1, THICKNESS),
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ RelativePositionAxes = Axes.X,
+ RelativeSizeAxes = Axes.X,
+ Colour = Color4.Black,
+ Child = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ }
+ });
+ }
+
+ AddInternal(new Circle
+ {
+ Size = new Vector2(16),
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.Centre,
+ RelativePositionAxes = Axes.X,
+ AlwaysPresent = true,
+ Colour = Color4.White,
+ BorderColour = Color4.Black,
+ BorderThickness = THICKNESS,
+ });
+ }
+ }
+ }
+}
diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs
index 2e9094ebe6..6984716a2c 100644
--- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs
+++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs
@@ -3,32 +3,35 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Screens.Edit.Compose.Components.Timeline;
using osu.Game.Skinning;
namespace osu.Game.Screens.Edit.Compose
{
public class ComposeScreen : EditorScreenWithTimeline
{
+ private HitObjectComposer composer;
+
protected override Drawable CreateMainContent()
{
var ruleset = Beatmap.Value.BeatmapInfo.Ruleset?.CreateInstance();
+ composer = ruleset?.CreateHitObjectComposer();
- var composer = ruleset?.CreateHitObjectComposer();
+ if (ruleset == null || composer == null)
+ return new ScreenWhiteBox.UnderConstructionMessage(ruleset == null ? "This beatmap" : $"{ruleset.Description}'s composer");
- if (composer != null)
- {
- var beatmapSkinProvider = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin);
+ var beatmapSkinProvider = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin);
- // the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation
- // full access to all skin sources.
- var rulesetSkinProvider = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider));
+ // the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation
+ // full access to all skin sources.
+ var rulesetSkinProvider = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider));
- // load the skinning hierarchy first.
- // this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources.
- return beatmapSkinProvider.WithChild(rulesetSkinProvider.WithChild(ruleset.CreateHitObjectComposer()));
- }
-
- return new ScreenWhiteBox.UnderConstructionMessage(ruleset == null ? "This beatmap" : $"{ruleset.Description}'s composer");
+ // load the skinning hierarchy first.
+ // this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources.
+ return beatmapSkinProvider.WithChild(rulesetSkinProvider.WithChild(composer));
}
+
+ protected override Drawable CreateTimelineContent() => new TimelineHitObjectDisplay(composer.EditorBeatmap);
}
}
diff --git a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs
index 752356e8c4..aa8d99b517 100644
--- a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs
+++ b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs
@@ -20,6 +20,8 @@ namespace osu.Game.Screens.Edit
private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor();
+ private TimelineArea timelineArea;
+
[BackgroundDependencyLoader(true)]
private void load([CanBeNull] BindableBeatDivisor beatDivisor)
{
@@ -64,7 +66,7 @@ namespace osu.Game.Screens.Edit
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Right = 5 },
- Child = CreateTimeline()
+ Child = timelineArea = CreateTimelineArea()
},
new BeatDivisorControl(beatDivisor) { RelativeSizeAxes = Axes.Both }
},
@@ -97,11 +99,15 @@ namespace osu.Game.Screens.Edit
{
mainContent.Add(content);
content.FadeInFromZero(300, Easing.OutQuint);
+
+ LoadComponentAsync(CreateTimelineContent(), timelineArea.Add);
});
}
protected abstract Drawable CreateMainContent();
- protected virtual Drawable CreateTimeline() => new TimelineArea { RelativeSizeAxes = Axes.Both };
+ protected virtual Drawable CreateTimelineContent() => new Container();
+
+ protected TimelineArea CreateTimelineArea() => new TimelineArea { RelativeSizeAxes = Axes.Both };
}
}
diff --git a/osu.Game/Screens/Edit/IEditorBeatmap.cs b/osu.Game/Screens/Edit/IEditorBeatmap.cs
index 2f250ba446..3e3418ef79 100644
--- a/osu.Game/Screens/Edit/IEditorBeatmap.cs
+++ b/osu.Game/Screens/Edit/IEditorBeatmap.cs
@@ -23,6 +23,11 @@ namespace osu.Game.Screens.Edit
/// Invoked when a is removed from this .
///
event Action HitObjectRemoved;
+
+ ///
+ /// Invoked when the start time of a in this was changed.
+ ///
+ event Action StartTimeChanged;
}
///
diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs
index ff78d85bf0..58c9a6a784 100644
--- a/osu.Game/Screens/Play/GameplayClockContainer.cs
+++ b/osu.Game/Screens/Play/GameplayClockContainer.cs
@@ -214,10 +214,13 @@ namespace osu.Game.Screens.Play
base.Update();
}
+ private bool speedAdjustmentsApplied;
+
private void updateRate()
{
if (sourceClock == null) return;
+ speedAdjustmentsApplied = true;
sourceClock.ResetSpeedAdjustments();
if (sourceClock is IHasTempoAdjust tempo)
@@ -239,7 +242,12 @@ namespace osu.Game.Screens.Play
private void removeSourceClockAdjustments()
{
- sourceClock.ResetSpeedAdjustments();
+ if (speedAdjustmentsApplied)
+ {
+ sourceClock.ResetSpeedAdjustments();
+ speedAdjustmentsApplied = false;
+ }
+
(sourceClock as IAdjustableAudioComponent)?.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust);
}
}
diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs
index 87d902b547..57021dfc68 100644
--- a/osu.Game/Screens/Play/PlayerLoader.cs
+++ b/osu.Game/Screens/Play/PlayerLoader.cs
@@ -55,7 +55,9 @@ namespace osu.Game.Screens.Play
protected override bool PlayResumeSound => false;
- private Task loadTask;
+ protected Task LoadTask { get; private set; }
+
+ protected Task DisposalTask { get; private set; }
private InputManager inputManager;
private IdleTracker idleTracker;
@@ -159,7 +161,7 @@ namespace osu.Game.Screens.Play
player.RestartCount = restartCount;
player.RestartRequested = restartRequested;
- loadTask = LoadComponentAsync(player, _ => info.Loading = false);
+ LoadTask = LoadComponentAsync(player, _ => info.Loading = false);
}
private void contentIn()
@@ -250,7 +252,7 @@ namespace osu.Game.Screens.Play
{
if (!this.IsCurrentScreen()) return;
- loadTask = null;
+ LoadTask = null;
//By default, we want to load the player and never be returned to.
//Note that this may change if the player we load requested a re-run.
@@ -301,7 +303,7 @@ namespace osu.Game.Screens.Play
if (isDisposing)
{
// if the player never got pushed, we should explicitly dispose it.
- loadTask?.ContinueWith(_ => player.Dispose());
+ DisposalTask = LoadTask?.ContinueWith(_ => player.Dispose());
}
}
diff --git a/osu.Game/Skinning/SkinnableDrawable.cs b/osu.Game/Skinning/SkinnableDrawable.cs
index 9ca5d60cb0..fda031e6cb 100644
--- a/osu.Game/Skinning/SkinnableDrawable.cs
+++ b/osu.Game/Skinning/SkinnableDrawable.cs
@@ -29,13 +29,13 @@ namespace osu.Game.Skinning
/// A function to create the default skin implementation of this element.
/// A conditional to decide whether to allow fallback to the default implementation if a skinned element is not present.
/// How (if at all) the should be resize to fit within our own bounds.
- public SkinnableDrawable(ISkinComponent component, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit)
+ public SkinnableDrawable(ISkinComponent component, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.NoScaling)
: this(component, allowFallback, confineMode)
{
createDefault = defaultImplementation;
}
- protected SkinnableDrawable(ISkinComponent component, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit)
+ protected SkinnableDrawable(ISkinComponent component, Func allowFallback = null, ConfineMode confineMode = ConfineMode.NoScaling)
: base(allowFallback)
{
this.component = component;
diff --git a/osu.Game/Skinning/SkinnableSprite.cs b/osu.Game/Skinning/SkinnableSprite.cs
index e225bfc490..5352928ec6 100644
--- a/osu.Game/Skinning/SkinnableSprite.cs
+++ b/osu.Game/Skinning/SkinnableSprite.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Skinning
[Resolved]
private TextureStore textures { get; set; }
- public SkinnableSprite(string textureName, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit)
+ public SkinnableSprite(string textureName, Func allowFallback = null, ConfineMode confineMode = ConfineMode.NoScaling)
: base(new SpriteComponent(textureName), allowFallback, confineMode)
{
}
diff --git a/osu.Game/Skinning/SkinnableSpriteText.cs b/osu.Game/Skinning/SkinnableSpriteText.cs
index e72f9c9811..567dd348e1 100644
--- a/osu.Game/Skinning/SkinnableSpriteText.cs
+++ b/osu.Game/Skinning/SkinnableSpriteText.cs
@@ -8,7 +8,7 @@ namespace osu.Game.Skinning
{
public class SkinnableSpriteText : SkinnableDrawable, IHasText
{
- public SkinnableSpriteText(ISkinComponent component, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit)
+ public SkinnableSpriteText(ISkinComponent component, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.NoScaling)
: base(component, defaultImplementation, allowFallback, confineMode)
{
}