1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 23:12:56 +08:00

Merge remote-tracking branch 'upstream/master' into adjustable-double-time

This commit is contained in:
Dean Herbert 2019-12-09 16:43:17 +09:00
commit e37526888c
27 changed files with 347 additions and 130 deletions

20
osu.Desktop/app.manifest Normal file
View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<asmv1:assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<assemblyIdentity version="1.0.0.0" name="osu!" />
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
<applicationRequestMinimum>
<defaultAssemblyRequest permissionSetReference="Custom" />
<PermissionSet class="System.Security.PermissionSet" version="1" Unrestricted="true" ID="Custom" SameSite="site" />
</applicationRequestMinimum>
</security>
</trustInfo>
<asmv3:application>
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
<dpiAware>true</dpiAware>
</asmv3:windowsSettings>
</asmv3:application>
</asmv1:assembly>

View File

@ -8,6 +8,7 @@
<Title>osu!lazer</Title>
<Product>osu!lazer</Product>
<ApplicationIcon>lazer.ico</ApplicationIcon>
<ApplicationManifest>app.manifest</ApplicationManifest>
<Version>0.0.0</Version>
<FileVersion>0.0.0</FileVersion>
</PropertyGroup>

View File

@ -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, bool>(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);
}
}
}

View File

@ -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, "");

View File

@ -11,5 +11,6 @@ namespace osu.Game.Rulesets.Osu.Skinning
SliderPathRadius,
AllowSliderBallTint,
CursorExpand,
CursorRotate
}
}

View File

@ -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<Type> 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<HitObject>((Beatmap<HitObject>)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,

View File

@ -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<IApplicableToClock>())
mod.ApplyToClock(Beatmap.Value.Track);
InputManager.Child = container = new TestPlayerLoaderContainer(
loader = new TestPlayerLoader(() =>
{
@ -63,6 +68,24 @@ namespace osu.Game.Tests.Visual.Gameplay
}));
}
/// <summary>
/// When <see cref="PlayerLoader"/> 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.
/// </summary>
[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<Player> createPlayer)
: base(createPlayer)
{

View File

@ -1,9 +1,12 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using 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<Type> 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

View File

@ -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<bool> 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);

View File

@ -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,

View File

@ -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();

View File

@ -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)

View File

@ -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;
}
/// <summary>
/// Select the next available mod in a specified direction.
/// </summary>

View File

@ -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<GlobalAction>
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;
}
}
}

View File

@ -34,7 +34,9 @@ namespace osu.Game.Rulesets.Edit
where TObject : HitObject
{
protected IRulesetConfigManager Config { get; private set; }
protected EditorBeatmap<TObject> EditorBeatmap { get; private set; }
protected new EditorBeatmap<TObject> 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<TObject>(playableBeatmap);
base.EditorBeatmap = EditorBeatmap = new EditorBeatmap<TObject>(playableBeatmap);
EditorBeatmap.HitObjectAdded += addHitObject;
EditorBeatmap.HitObjectRemoved += removeHitObject;
EditorBeatmap.StartTimeChanged += UpdateHitObject;
@ -333,6 +335,11 @@ namespace osu.Game.Rulesets.Edit
/// </summary>
public abstract IEnumerable<DrawableHitObject> HitObjects { get; }
/// <summary>
/// An editor-specific beatmap, exposing mutation events.
/// </summary>
public IEditorBeatmap EditorBeatmap { get; protected set; }
/// <summary>
/// Whether the user's cursor is currently in an area of the <see cref="HitObjectComposer"/> that is valid for placement.
/// </summary>

View File

@ -14,12 +14,14 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
/// <summary>
/// Represents a part of the summary timeline..
/// </summary>
public abstract class TimelinePart : CompositeDrawable
public abstract class TimelinePart : Container
{
protected readonly IBindable<WorkingBeatmap> Beatmap = new Bindable<WorkingBeatmap>();
private readonly Container timeline;
protected override Container<Drawable> 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();

View File

@ -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());

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.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<Drawable> 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);

View File

@ -0,0 +1,108 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using 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<TimelineHitObjectRepresentation>().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,
});
}
}
}
}

View File

@ -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);
}
}

View File

@ -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 };
}
}

View File

@ -23,6 +23,11 @@ namespace osu.Game.Screens.Edit
/// Invoked when a <see cref="HitObject"/> is removed from this <see cref="IEditorBeatmap"/>.
/// </summary>
event Action<HitObject> HitObjectRemoved;
/// <summary>
/// Invoked when the start time of a <see cref="HitObject"/> in this <see cref="EditorBeatmap{T}"/> was changed.
/// </summary>
event Action<HitObject> StartTimeChanged;
}
/// <summary>

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -29,13 +29,13 @@ namespace osu.Game.Skinning
/// <param name="defaultImplementation">A function to create the default skin implementation of this element.</param>
/// <param name="allowFallback">A conditional to decide whether to allow fallback to the default implementation if a skinned element is not present.</param>
/// <param name="confineMode">How (if at all) the <see cref="Drawable"/> should be resize to fit within our own bounds.</param>
public SkinnableDrawable(ISkinComponent component, Func<ISkinComponent, Drawable> defaultImplementation, Func<ISkinSource, bool> allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit)
public SkinnableDrawable(ISkinComponent component, Func<ISkinComponent, Drawable> defaultImplementation, Func<ISkinSource, bool> allowFallback = null, ConfineMode confineMode = ConfineMode.NoScaling)
: this(component, allowFallback, confineMode)
{
createDefault = defaultImplementation;
}
protected SkinnableDrawable(ISkinComponent component, Func<ISkinSource, bool> allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit)
protected SkinnableDrawable(ISkinComponent component, Func<ISkinSource, bool> allowFallback = null, ConfineMode confineMode = ConfineMode.NoScaling)
: base(allowFallback)
{
this.component = component;

View File

@ -19,7 +19,7 @@ namespace osu.Game.Skinning
[Resolved]
private TextureStore textures { get; set; }
public SkinnableSprite(string textureName, Func<ISkinSource, bool> allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit)
public SkinnableSprite(string textureName, Func<ISkinSource, bool> allowFallback = null, ConfineMode confineMode = ConfineMode.NoScaling)
: base(new SpriteComponent(textureName), allowFallback, confineMode)
{
}

View File

@ -8,7 +8,7 @@ namespace osu.Game.Skinning
{
public class SkinnableSpriteText : SkinnableDrawable, IHasText
{
public SkinnableSpriteText(ISkinComponent component, Func<ISkinComponent, SpriteText> defaultImplementation, Func<ISkinSource, bool> allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit)
public SkinnableSpriteText(ISkinComponent component, Func<ISkinComponent, SpriteText> defaultImplementation, Func<ISkinSource, bool> allowFallback = null, ConfineMode confineMode = ConfineMode.NoScaling)
: base(component, defaultImplementation, allowFallback, confineMode)
{
}