1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-14 17:52:56 +08:00

Merge branch 'master' into master

This commit is contained in:
Dean Herbert 2022-10-12 14:52:07 +09:00 committed by GitHub
commit 1d6d047a69
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 507 additions and 267 deletions

View File

@ -52,7 +52,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.1008.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.1005.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.1011.0" />
</ItemGroup>
<ItemGroup Label="Transitive Dependencies">
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->

View File

@ -36,5 +36,7 @@ namespace osu.Game.Rulesets.Catch.Edit
return base.CreateHitObjectBlueprintFor(hitObject);
}
protected sealed override DragBox CreateDragBox() => new ScrollingDragBox(Composer.Playfield);
}
}

View File

@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Catch.Mods
{
this.playfield = playfield;
FlashlightSize = new Vector2(0, GetSizeFor(0));
FlashlightSize = new Vector2(0, GetSize());
FlashlightSmoothness = 1.4f;
}
@ -66,9 +66,9 @@ namespace osu.Game.Rulesets.Catch.Mods
FlashlightPosition = playfield.CatcherArea.ToSpaceOfOtherDrawable(playfield.Catcher.DrawPosition, this);
}
protected override void OnComboChange(ValueChangedEvent<int> e)
protected override void UpdateFlashlightSize(float size)
{
this.TransformTo(nameof(FlashlightSize), new Vector2(0, GetSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION);
this.TransformTo(nameof(FlashlightSize), new Vector2(0, size), FLASHLIGHT_FADE_DURATION);
}
protected override string FragmentShader => "CircularFlashlight";

View File

@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
countOk = score.Statistics.GetValueOrDefault(HitResult.Ok);
countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh);
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
scoreAccuracy = customAccuracy;
scoreAccuracy = calculateCustomAccuracy();
// Arbitrary initial value for scaling pp in order to standardize distributions across game modes.
// The specific number has no intrinsic meaning and can be adjusted as needed.
@ -73,6 +73,12 @@ namespace osu.Game.Rulesets.Mania.Difficulty
/// <summary>
/// Accuracy used to weight judgements independently from the score's actual accuracy.
/// </summary>
private double customAccuracy => (countPerfect * 320 + countGreat * 300 + countGood * 200 + countOk * 100 + countMeh * 50) / (totalHits * 320);
private double calculateCustomAccuracy()
{
if (totalHits == 0)
return 0;
return (countPerfect * 320 + countGreat * 300 + countGood * 200 + countOk * 100 + countMeh * 50) / (totalHits * 320);
}
}
}

View File

@ -33,5 +33,7 @@ namespace osu.Game.Rulesets.Mania.Edit
}
protected override SelectionHandler<HitObject> CreateSelectionHandler() => new ManiaSelectionHandler();
protected sealed override DragBox CreateDragBox() => new ScrollingDragBox(Composer.Playfield);
}
}

View File

@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public ManiaFlashlight(ManiaModFlashlight modFlashlight)
: base(modFlashlight)
{
FlashlightSize = new Vector2(DrawWidth, GetSizeFor(0));
FlashlightSize = new Vector2(DrawWidth, GetSize());
AddLayout(flashlightProperties);
}
@ -54,9 +54,9 @@ namespace osu.Game.Rulesets.Mania.Mods
}
}
protected override void OnComboChange(ValueChangedEvent<int> e)
protected override void UpdateFlashlightSize(float size)
{
this.TransformTo(nameof(FlashlightSize), new Vector2(DrawWidth, GetSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION);
this.TransformTo(nameof(FlashlightSize), new Vector2(DrawWidth, size), FLASHLIGHT_FADE_DURATION);
}
protected override string FragmentShader => "RectangularFlashlight";

View File

@ -4,12 +4,14 @@
#nullable disable
using System;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Audio;
using osu.Game.Rulesets.Mania.Skinning.Default;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
@ -38,6 +40,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
private Container<DrawableHoldNoteTail> tailContainer;
private Container<DrawableHoldNoteTick> tickContainer;
private PausableSkinnableSound slidingSample;
/// <summary>
/// Contains the size of the hold note covering the whole head/tail bounds. The size of this container changes as the hold note is being pressed.
/// </summary>
@ -108,6 +112,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
},
tickContainer = new Container<DrawableHoldNoteTick> { RelativeSizeAxes = Axes.Both },
tailContainer = new Container<DrawableHoldNoteTail> { RelativeSizeAxes = Axes.Both },
slidingSample = new PausableSkinnableSound { Looping = true }
});
maskedContents.AddRange(new[]
@ -118,6 +123,13 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
});
}
protected override void LoadComplete()
{
base.LoadComplete();
isHitting.BindValueChanged(updateSlidingSample, true);
}
protected override void OnApply()
{
base.OnApply();
@ -322,5 +334,38 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
HoldStartTime = null;
isHitting.Value = false;
}
protected override void LoadSamples()
{
// Note: base.LoadSamples() isn't called since the slider plays the tail's hitsounds for the time being.
if (HitObject.SampleControlPoint == null)
{
throw new InvalidOperationException($"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}."
+ $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}.");
}
slidingSample.Samples = HitObject.CreateSlidingSamples().Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast<ISampleInfo>().ToArray();
}
public override void StopAllSamples()
{
base.StopAllSamples();
slidingSample?.Stop();
}
private void updateSlidingSample(ValueChangedEvent<bool> tracking)
{
if (tracking.NewValue)
slidingSample?.Play();
else
slidingSample?.Stop();
}
protected override void OnFree()
{
slidingSample.Samples = null;
base.OnFree();
}
}
}

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 System;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
@ -89,13 +90,15 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
Color4 colour;
const int total_colours = 7;
if (stage.IsSpecialColumn(column))
colour = new Color4(159, 101, 255, 255);
else
{
switch (column % 8)
switch (column % total_colours)
{
default:
case 0:
colour = new Color4(240, 216, 0, 255);
break;
@ -112,20 +115,19 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
break;
case 4:
colour = new Color4(178, 0, 240, 255);
break;
case 5:
colour = new Color4(0, 96, 240, 255);
break;
case 6:
case 5:
colour = new Color4(0, 226, 240, 255);
break;
case 7:
case 6:
colour = new Color4(0, 240, 96, 255);
break;
default:
throw new ArgumentOutOfRangeException();
}
}

View File

@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
followDelay = modFlashlight.FollowDelay.Value;
FlashlightSize = new Vector2(0, GetSizeFor(0));
FlashlightSize = new Vector2(0, GetSize());
FlashlightSmoothness = 1.4f;
}
@ -83,9 +83,9 @@ namespace osu.Game.Rulesets.Osu.Mods
return base.OnMouseMove(e);
}
protected override void OnComboChange(ValueChangedEvent<int> e)
protected override void UpdateFlashlightSize(float size)
{
this.TransformTo(nameof(FlashlightSize), new Vector2(0, GetSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION);
this.TransformTo(nameof(FlashlightSize), new Vector2(0, size), FLASHLIGHT_FADE_DURATION);
}
protected override string FragmentShader => "CircularFlashlight";

View File

@ -186,17 +186,22 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private Vector2? lastPosition;
private bool rewinding;
public void UpdateProgress(double completionProgress)
{
Position = drawableSlider.HitObject.CurvePositionAt(completionProgress);
var diff = lastPosition.HasValue ? lastPosition.Value - Position : Position - drawableSlider.HitObject.CurvePositionAt(completionProgress + 0.01f);
if (Clock.ElapsedFrameTime != 0)
rewinding = Clock.ElapsedFrameTime < 0;
// Ensure the value is substantially high enough to allow for Atan2 to get a valid angle.
if (diff.LengthFast < 0.01f)
return;
ball.Rotation = -90 + (float)(-Math.Atan2(diff.X, diff.Y) * 180 / Math.PI);
ball.Rotation = -90 + (float)(-Math.Atan2(diff.X, diff.Y) * 180 / Math.PI) + (rewinding ? 180 : 0);
lastPosition = Position;
}
}

View File

@ -34,21 +34,6 @@ namespace osu.Game.Rulesets.Osu.Objects
public override IList<HitSampleInfo> AuxiliarySamples => CreateSlidingSamples().Concat(TailSamples).ToArray();
public IList<HitSampleInfo> CreateSlidingSamples()
{
var slidingSamples = new List<HitSampleInfo>();
var normalSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL);
if (normalSample != null)
slidingSamples.Add(normalSample.With("sliderslide"));
var whistleSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_WHISTLE);
if (whistleSample != null)
slidingSamples.Add(whistleSample.With("sliderwhistle"));
return slidingSamples;
}
private readonly Cached<Vector2> endPositionCache = new Cached<Vector2>();
public override Vector2 EndPosition => endPositionCache.IsValid ? endPositionCache.Value : endPositionCache.Value = Position + this.CurvePositionAt(1);

View File

@ -47,21 +47,21 @@ namespace osu.Game.Rulesets.Taiko.Mods
{
this.taikoPlayfield = taikoPlayfield;
FlashlightSize = getSizeFor(0);
FlashlightSize = adjustSize(GetSize());
FlashlightSmoothness = 1.4f;
AddLayout(flashlightProperties);
}
private Vector2 getSizeFor(int combo)
private Vector2 adjustSize(float size)
{
// Preserve flashlight size through the playfield's aspect adjustment.
return new Vector2(0, GetSizeFor(combo) * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT);
return new Vector2(0, size * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT);
}
protected override void OnComboChange(ValueChangedEvent<int> e)
protected override void UpdateFlashlightSize(float size)
{
this.TransformTo(nameof(FlashlightSize), getSizeFor(e.NewValue), FLASHLIGHT_FADE_DURATION);
this.TransformTo(nameof(FlashlightSize), adjustSize(size), FLASHLIGHT_FADE_DURATION);
}
protected override string FragmentShader => "CircularFlashlight";
@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
FlashlightPosition = ToLocalSpace(taikoPlayfield.HitTarget.ScreenSpaceDrawQuad.Centre);
ClearTransforms(targetMember: nameof(FlashlightSize));
FlashlightSize = getSizeFor(Combo.Value);
FlashlightSize = adjustSize(Combo.Value);
flashlightProperties.Validate();
}

View File

@ -29,16 +29,18 @@ namespace osu.Game.Tests.Visual.Editing
private TimelineBlueprintContainer blueprintContainer
=> Editor.ChildrenOfType<TimelineBlueprintContainer>().First();
private Vector2 getPosition(HitObject hitObject) =>
blueprintContainer.SelectionBlueprints.First(s => s.Item == hitObject).ScreenSpaceDrawQuad.Centre;
private Vector2 getMiddlePosition(HitObject hitObject1, HitObject hitObject2) =>
(getPosition(hitObject1) + getPosition(hitObject2)) / 2;
private void moveMouseToObject(Func<HitObject> targetFunc)
{
AddStep("move mouse to object", () =>
{
var pos = blueprintContainer.SelectionBlueprints
.First(s => s.Item == targetFunc())
.ChildrenOfType<TimelineHitObjectBlueprint>()
.First().ScreenSpaceDrawQuad.Centre;
InputManager.MoveMouseTo(pos);
var hitObject = targetFunc();
InputManager.MoveMouseTo(getPosition(hitObject));
});
}
@ -262,6 +264,56 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft));
}
[Test]
public void TestBasicDragSelection()
{
var addedObjects = new[]
{
new HitCircle { StartTime = 0 },
new HitCircle { StartTime = 500, Position = new Vector2(100) },
new HitCircle { StartTime = 1000, Position = new Vector2(200) },
new HitCircle { StartTime = 1500, Position = new Vector2(300) },
};
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects));
AddStep("move mouse", () => InputManager.MoveMouseTo(getMiddlePosition(addedObjects[0], addedObjects[1])));
AddStep("mouse down", () => InputManager.PressButton(MouseButton.Left));
AddStep("drag to select", () => InputManager.MoveMouseTo(getMiddlePosition(addedObjects[2], addedObjects[3])));
assertSelectionIs(new[] { addedObjects[1], addedObjects[2] });
AddStep("drag to deselect", () => InputManager.MoveMouseTo(getMiddlePosition(addedObjects[1], addedObjects[2])));
assertSelectionIs(new[] { addedObjects[1] });
AddStep("mouse up", () => InputManager.ReleaseButton(MouseButton.Left));
assertSelectionIs(new[] { addedObjects[1] });
}
[Test]
public void TestFastDragSelection()
{
var addedObjects = new[]
{
new HitCircle { StartTime = 0 },
new HitCircle { StartTime = 500 },
new HitCircle { StartTime = 20000, Position = new Vector2(100) },
new HitCircle { StartTime = 31000, Position = new Vector2(200) },
new HitCircle { StartTime = 60000, Position = new Vector2(300) },
};
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects));
AddStep("move mouse", () => InputManager.MoveMouseTo(getMiddlePosition(addedObjects[0], addedObjects[1])));
AddStep("mouse down", () => InputManager.PressButton(MouseButton.Left));
AddStep("start drag", () => InputManager.MoveMouseTo(getPosition(addedObjects[1])));
AddStep("jump editor clock", () => EditorClock.Seek(30000));
AddStep("jump editor clock", () => EditorClock.Seek(60000));
AddStep("end drag", () => InputManager.ReleaseButton(MouseButton.Left));
assertSelectionIs(addedObjects.Skip(1));
AddAssert("all blueprints are present", () => blueprintContainer.SelectionBlueprints.Count == EditorBeatmap.SelectedHitObjects.Count);
}
private void assertSelectionIs(IEnumerable<HitObject> hitObjects)
=> AddAssert("correct hitobjects selected", () => EditorBeatmap.SelectedHitObjects.OrderBy(h => h.StartTime).SequenceEqual(hitObjects));
}

View File

@ -16,6 +16,7 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Play.HUD.HitErrorMeters;
using osu.Game.Skinning;
using osu.Game.Tests.Gameplay;
@ -148,6 +149,42 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("key counters still hidden", () => !keyCounterFlow.IsPresent);
}
[Test]
public void TestInputDoesntWorkWhenHUDHidden()
{
SongProgressBar getSongProgress() => hudOverlay.ChildrenOfType<SongProgressBar>().Single();
bool seeked = false;
createNew();
AddStep("bind seek", () =>
{
seeked = false;
var progress = getSongProgress();
progress.ShowHandle = true;
progress.OnSeek += _ => seeked = true;
});
AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent);
AddStep("attempt seek", () =>
{
InputManager.MoveMouseTo(getSongProgress());
InputManager.Click(MouseButton.Left);
});
AddAssert("seek not performed", () => !seeked);
AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true);
AddStep("attempt seek", () => InputManager.Click(MouseButton.Left));
AddAssert("seek performed", () => seeked);
}
[Test]
public void TestHiddenHUDDoesntBlockComponentUpdates()
{

View File

@ -189,6 +189,16 @@ Line after image";
});
}
[Test]
public void TestFlag()
{
AddStep("Add flag", () =>
{
markdownContainer.CurrentPath = @"https://dev.ppy.sh";
markdownContainer.Text = "::{flag=\"AU\"}:: ::{flag=\"ZZ\"}::";
});
}
private class TestMarkdownContainer : WikiMarkdownContainer
{
public LinkInline Link;

File diff suppressed because one or more lines are too long

View File

@ -45,7 +45,7 @@ namespace osu.Game.Database
public bool Download(T model, bool minimiseDownloadSize = false) => Download(model, minimiseDownloadSize, null);
public void DownloadAsUpdate(TModel originalModel) => Download(originalModel, false, originalModel);
public void DownloadAsUpdate(TModel originalModel, bool minimiseDownloadSize) => Download(originalModel, minimiseDownloadSize, originalModel);
protected bool Download(T model, bool minimiseDownloadSize, TModel? originalModel)
{

View File

@ -5,6 +5,7 @@
using Markdig;
using Markdig.Extensions.AutoLinks;
using Markdig.Extensions.CustomContainers;
using Markdig.Extensions.EmphasisExtras;
using Markdig.Extensions.Footnotes;
using Markdig.Extensions.Tables;
@ -32,6 +33,12 @@ namespace osu.Game.Graphics.Containers.Markdown
/// <seealso cref="AutoLinkExtension"/>
protected virtual bool Autolinks => false;
/// <summary>
/// Allows this markdown container to parse custom containers (used for flags and infoboxes).
/// </summary>
/// <seealso cref="CustomContainerExtension"/>
protected virtual bool CustomContainers => false;
public OsuMarkdownContainer()
{
LineSpacing = 21;
@ -107,6 +114,9 @@ namespace osu.Game.Graphics.Containers.Markdown
if (Autolinks)
pipeline = pipeline.UseAutoLinks();
if (CustomContainers)
pipeline.UseCustomContainers();
return pipeline.Build();
}
}

View File

@ -3,6 +3,9 @@
#nullable disable
using System;
using System.Linq;
using Markdig.Extensions.CustomContainers;
using Markdig.Syntax.Inlines;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
@ -11,6 +14,9 @@ using osu.Framework.Graphics.Containers.Markdown;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Game.Overlays;
using osu.Game.Users;
using osu.Game.Users.Drawables;
using osuTK;
namespace osu.Game.Graphics.Containers.Markdown
{
@ -33,6 +39,31 @@ namespace osu.Game.Graphics.Containers.Markdown
protected override SpriteText CreateEmphasisedSpriteText(bool bold, bool italic)
=> CreateSpriteText().With(t => t.Font = t.Font.With(weight: bold ? FontWeight.Bold : FontWeight.Regular, italics: italic));
protected override void AddCustomComponent(CustomContainerInline inline)
{
if (!(inline.FirstChild is LiteralInline literal))
{
base.AddCustomComponent(inline);
return;
}
string[] attributes = literal.Content.ToString().Trim(' ', '{', '}').Split();
string flagAttribute = attributes.SingleOrDefault(a => a.StartsWith(@"flag", StringComparison.Ordinal));
if (flagAttribute == null)
{
base.AddCustomComponent(inline);
return;
}
string flag = flagAttribute.Split('=').Last().Trim('"');
if (!Enum.TryParse<CountryCode>(flag, out var countryCode))
countryCode = CountryCode.Unknown;
AddDrawable(new DrawableFlag(countryCode) { Size = new Vector2(20, 15) });
}
private class OsuMarkdownInlineCode : Container
{
[Resolved]

View File

@ -563,6 +563,15 @@ namespace osu.Game
// This should be able to be performed from song select, but that is disabled for now
// due to the weird decoupled ruleset logic (which can cause a crash in certain filter scenarios).
//
// As a special case, if the beatmap and ruleset already match, allow immediately displaying the score from song select.
// This is guaranteed to not crash, and feels better from a user's perspective (ie. if they are clicking a score in the
// song select leaderboard).
IEnumerable<Type> validScreens =
Beatmap.Value.BeatmapInfo.Equals(databasedBeatmap) && Ruleset.Value.Equals(databasedScore.ScoreInfo.Ruleset)
? new[] { typeof(SongSelect) }
: Array.Empty<Type>();
PerformFromScreen(screen =>
{
Logger.Log($"{nameof(PresentScore)} updating beatmap ({databasedBeatmap}) and ruleset ({databasedScore.ScoreInfo.Ruleset}) to match score");
@ -580,7 +589,7 @@ namespace osu.Game
screen.Push(new SoloResultsScreen(databasedScore.ScoreInfo, false));
break;
}
});
}, validScreens: validScreens);
}
public override Task Import(params ImportTask[] imports)

View File

@ -387,14 +387,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input
if (bindTarget != null) bindTarget.IsBinding = true;
}
private void updateStoreFromButton(KeyButton button)
{
realm.Run(r =>
{
var binding = r.Find<RealmKeyBinding>(((IHasGuidPrimaryKey)button.KeyBinding).ID);
r.Write(() => binding.KeyCombinationString = button.KeyBinding.KeyCombinationString);
});
}
private void updateStoreFromButton(KeyButton button) =>
realm.WriteAsync(r => r.Find<RealmKeyBinding>(button.KeyBinding.ID).KeyCombinationString = button.KeyBinding.KeyCombinationString);
private void updateIsDefaultValue()
{

View File

@ -4,6 +4,7 @@
#nullable disable
using System.Linq;
using Markdig.Extensions.CustomContainers;
using Markdig.Extensions.Yaml;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
@ -16,6 +17,7 @@ namespace osu.Game.Overlays.Wiki.Markdown
public class WikiMarkdownContainer : OsuMarkdownContainer
{
protected override bool Footnotes => true;
protected override bool CustomContainers => true;
public string CurrentPath
{
@ -26,6 +28,11 @@ namespace osu.Game.Overlays.Wiki.Markdown
{
switch (markdownObject)
{
case CustomContainer:
// infoboxes are parsed into CustomContainer objects, but we don't have support for infoboxes yet.
// todo: add support for infobox.
break;
case YamlFrontMatterBlock yamlFrontMatterBlock:
container.Add(new WikiNoticeContainer(yamlFrontMatterBlock));
break;

View File

@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@ -12,7 +11,6 @@ using osu.Framework.Graphics.Rendering.Vertices;
using osu.Framework.Graphics.Shaders;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Beatmaps.Timing;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.OpenGL.Vertices;
@ -20,6 +18,7 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
using osu.Game.Screens.Play;
using osuTK;
using osuTK.Graphics;
@ -84,8 +83,6 @@ namespace osu.Game.Rulesets.Mods
flashlight.Combo.BindTo(Combo);
drawableRuleset.KeyBindingInputManager.Add(flashlight);
flashlight.Breaks = drawableRuleset.Beatmap.Breaks;
}
protected abstract Flashlight CreateFlashlight();
@ -100,8 +97,6 @@ namespace osu.Game.Rulesets.Mods
public override bool RemoveCompletedTransforms => false;
public List<BreakPeriod> Breaks = new List<BreakPeriod>();
private readonly float defaultFlashlightSize;
private readonly float sizeMultiplier;
private readonly bool comboBasedSize;
@ -119,37 +114,36 @@ namespace osu.Game.Rulesets.Mods
shader = shaderManager.Load("PositionAndColour", FragmentShader);
}
[Resolved]
private Player? player { get; set; }
private readonly IBindable<bool> isBreakTime = new BindableBool();
protected override void LoadComplete()
{
base.LoadComplete();
Combo.ValueChanged += OnComboChange;
Combo.ValueChanged += _ => UpdateFlashlightSize(GetSize());
using (BeginAbsoluteSequence(0))
if (player != null)
{
foreach (var breakPeriod in Breaks)
{
if (!breakPeriod.HasEffect)
continue;
if (breakPeriod.Duration < FLASHLIGHT_FADE_DURATION * 2) continue;
this.Delay(breakPeriod.StartTime + FLASHLIGHT_FADE_DURATION).FadeOutFromOne(FLASHLIGHT_FADE_DURATION);
this.Delay(breakPeriod.EndTime - FLASHLIGHT_FADE_DURATION).FadeInFromZero(FLASHLIGHT_FADE_DURATION);
}
isBreakTime.BindTo(player.IsBreakTime);
isBreakTime.BindValueChanged(_ => UpdateFlashlightSize(GetSize()), true);
}
}
protected abstract void OnComboChange(ValueChangedEvent<int> e);
protected abstract void UpdateFlashlightSize(float size);
protected abstract string FragmentShader { get; }
protected float GetSizeFor(int combo)
protected float GetSize()
{
float size = defaultFlashlightSize * sizeMultiplier;
if (comboBasedSize)
size *= GetComboScaleFor(combo);
if (isBreakTime.Value)
size *= 2.5f;
else if (comboBasedSize)
size *= GetComboScaleFor(Combo.Value);
return size;
}

View File

@ -6,6 +6,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using JetBrains.Annotations;
using Newtonsoft.Json;
@ -198,6 +199,21 @@ namespace osu.Game.Rulesets.Objects
/// </summary>
[NotNull]
protected virtual HitWindows CreateHitWindows() => new HitWindows();
public IList<HitSampleInfo> CreateSlidingSamples()
{
var slidingSamples = new List<HitSampleInfo>();
var normalSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL);
if (normalSample != null)
slidingSamples.Add(normalSample.With("sliderslide"));
var whistleSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_WHISTLE);
if (whistleSample != null)
slidingSamples.Add(whistleSample.With("sliderwhistle"));
return slidingSamples;
}
}
public static class HitObjectExtensions

View File

@ -3,7 +3,6 @@
#nullable disable
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
@ -13,7 +12,6 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
@ -61,25 +59,31 @@ namespace osu.Game.Screens.Edit.Compose.Components
{
case NotifyCollectionChangedAction.Add:
foreach (object o in args.NewItems)
SelectionBlueprints.FirstOrDefault(b => b.Item == o)?.Select();
{
if (blueprintMap.TryGetValue((T)o, out var blueprint))
blueprint.Select();
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (object o in args.OldItems)
SelectionBlueprints.FirstOrDefault(b => b.Item == o)?.Deselect();
{
if (blueprintMap.TryGetValue((T)o, out var blueprint))
blueprint.Deselect();
}
break;
}
};
SelectionHandler = CreateSelectionHandler();
SelectionHandler.DeselectAll = deselectAll;
SelectionHandler.DeselectAll = DeselectAll;
SelectionHandler.SelectedItems.BindTo(SelectedItems);
AddRangeInternal(new[]
{
DragBox = CreateDragBox(selectBlueprintsFromDragRectangle),
DragBox = CreateDragBox(),
SelectionHandler,
SelectionBlueprints = CreateSelectionBlueprintContainer(),
SelectionHandler.CreateProxy(),
@ -101,12 +105,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
[CanBeNull]
protected virtual SelectionBlueprint<T> CreateBlueprintFor(T item) => null;
protected virtual DragBox CreateDragBox(Action<RectangleF> performSelect) => new DragBox(performSelect);
/// <summary>
/// Whether this component is in a state where items outside a drag selection should be deselected. If false, selection will only be added to.
/// </summary>
protected virtual bool AllowDeselectionDuringDrag => true;
protected virtual DragBox CreateDragBox() => new DragBox();
protected override bool OnMouseDown(MouseDownEvent e)
{
@ -142,7 +141,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
if (endClickSelection(e) || ClickedBlueprint != null)
return true;
deselectAll();
DeselectAll();
return true;
}
@ -171,11 +170,15 @@ namespace osu.Game.Screens.Edit.Compose.Components
finishSelectionMovement();
}
private MouseButtonEvent lastDragEvent;
protected override bool OnDragStart(DragStartEvent e)
{
if (e.Button == MouseButton.Right)
return false;
lastDragEvent = e;
if (movementBlueprints != null)
{
isDraggingBlueprint = true;
@ -183,30 +186,21 @@ namespace osu.Game.Screens.Edit.Compose.Components
return true;
}
if (DragBox.HandleDrag(e))
{
DragBox.Show();
return true;
}
return false;
DragBox.HandleDrag(e);
DragBox.Show();
return true;
}
protected override void OnDrag(DragEvent e)
{
if (e.Button == MouseButton.Right)
return;
if (DragBox.State == Visibility.Visible)
DragBox.HandleDrag(e);
lastDragEvent = e;
moveCurrentSelection(e);
}
protected override void OnDragEnd(DragEndEvent e)
{
if (e.Button == MouseButton.Right)
return;
lastDragEvent = null;
if (isDraggingBlueprint)
{
@ -214,8 +208,19 @@ namespace osu.Game.Screens.Edit.Compose.Components
changeHandler?.EndChange();
}
if (DragBox.State == Visibility.Visible)
DragBox.Hide();
DragBox.Hide();
}
protected override void Update()
{
base.Update();
if (lastDragEvent != null && DragBox.State == Visibility.Visible)
{
lastDragEvent.Target = this;
DragBox.HandleDrag(lastDragEvent);
UpdateSelectionFromDragBox();
}
}
/// <summary>
@ -233,7 +238,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
if (!SelectionHandler.SelectedBlueprints.Any())
return false;
deselectAll();
DeselectAll();
return true;
}
@ -380,44 +385,39 @@ namespace osu.Game.Screens.Edit.Compose.Components
}
/// <summary>
/// Select all masks in a given rectangle selection area.
/// Select all blueprints in a selection area specified by <see cref="DragBox"/>.
/// </summary>
/// <param name="rect">The rectangle to perform a selection on in screen-space coordinates.</param>
private void selectBlueprintsFromDragRectangle(RectangleF rect)
protected virtual void UpdateSelectionFromDragBox()
{
var quad = DragBox.Box.ScreenSpaceDrawQuad;
foreach (var blueprint in SelectionBlueprints)
{
// only run when utmost necessary to avoid unnecessary rect computations.
bool isValidForSelection() => blueprint.IsAlive && blueprint.IsPresent && rect.Contains(blueprint.ScreenSpaceSelectionPoint);
switch (blueprint.State)
{
case SelectionState.NotSelected:
if (isValidForSelection())
blueprint.Select();
case SelectionState.Selected:
// Selection is preserved even after blueprint becomes dead.
if (!quad.Contains(blueprint.ScreenSpaceSelectionPoint))
blueprint.Deselect();
break;
case SelectionState.Selected:
if (AllowDeselectionDuringDrag && !isValidForSelection())
blueprint.Deselect();
case SelectionState.NotSelected:
if (blueprint.IsAlive && blueprint.IsPresent && quad.Contains(blueprint.ScreenSpaceSelectionPoint))
blueprint.Select();
break;
}
}
}
/// <summary>
/// Selects all <see cref="SelectionBlueprint{T}"/>s.
/// Select all currently-present items.
/// </summary>
protected virtual void SelectAll()
{
// Scheduled to allow the change in lifetime to take place.
Schedule(() => SelectionBlueprints.ToList().ForEach(m => m.Select()));
}
protected abstract void SelectAll();
/// <summary>
/// Deselects all selected <see cref="SelectionBlueprint{T}"/>s.
/// Deselect all selected items.
/// </summary>
private void deselectAll() => SelectionHandler.SelectedBlueprints.ToList().ForEach(m => m.Deselect());
protected void DeselectAll() => SelectedItems.Clear();
protected virtual void OnBlueprintSelected(SelectionBlueprint<T> blueprint)
{

View File

@ -12,7 +12,6 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input;
using osu.Framework.Input.Events;
using osu.Game.Audio;
using osu.Game.Graphics.UserInterface;
@ -37,7 +36,6 @@ namespace osu.Game.Screens.Edit.Compose.Components
protected new EditorSelectionHandler SelectionHandler => (EditorSelectionHandler)base.SelectionHandler;
private PlacementBlueprint currentPlacement;
private InputManager inputManager;
/// <remarks>
/// Positional input must be received outside the container's bounds,
@ -66,8 +64,6 @@ namespace osu.Game.Screens.Edit.Compose.Components
{
base.LoadComplete();
inputManager = GetContainingInputManager();
Beatmap.HitObjectAdded += hitObjectAdded;
// updates to selected are handled for us by SelectionHandler.
@ -220,7 +216,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
private void updatePlacementPosition()
{
var snapResult = Composer.FindSnappedPositionAndTime(inputManager.CurrentState.Mouse.Position);
var snapResult = Composer.FindSnappedPositionAndTime(InputManager.CurrentState.Mouse.Position);
// if no time was found from positional snapping, we should still quantize to the beat.
snapResult.Time ??= Beatmap.SnapTime(EditorClock.CurrentTime, null);

View File

@ -8,7 +8,6 @@ using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Framework.Layout;
@ -21,18 +20,13 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// </summary>
public class DragBox : CompositeDrawable, IStateful<Visibility>
{
protected readonly Action<RectangleF> PerformSelection;
protected Drawable Box;
public Drawable Box { get; private set; }
/// <summary>
/// Creates a new <see cref="DragBox"/>.
/// </summary>
/// <param name="performSelection">A delegate that performs drag selection.</param>
public DragBox(Action<RectangleF> performSelection)
public DragBox()
{
PerformSelection = performSelection;
RelativeSizeAxes = Axes.Both;
AlwaysPresent = true;
Alpha = 0;
@ -46,30 +40,14 @@ namespace osu.Game.Screens.Edit.Compose.Components
protected virtual Drawable CreateBox() => new BoxWithBorders();
private RectangleF? dragRectangle;
/// <summary>
/// Handle a forwarded mouse event.
/// </summary>
/// <param name="e">The mouse event.</param>
/// <returns>Whether the event should be handled and blocking.</returns>
public virtual bool HandleDrag(MouseButtonEvent e)
public virtual void HandleDrag(MouseButtonEvent e)
{
var dragPosition = e.ScreenSpaceMousePosition;
var dragStartPosition = e.ScreenSpaceMouseDownPosition;
var dragQuad = new Quad(dragStartPosition.X, dragStartPosition.Y, dragPosition.X - dragStartPosition.X, dragPosition.Y - dragStartPosition.Y);
// We use AABBFloat instead of RectangleF since it handles negative sizes for us
var rec = dragQuad.AABBFloat;
dragRectangle = rec;
var topLeft = ToLocalSpace(rec.TopLeft);
var bottomRight = ToLocalSpace(rec.BottomRight);
Box.Position = topLeft;
Box.Size = bottomRight - topLeft;
return true;
Box.Position = Vector2.ComponentMin(e.MouseDownPosition, e.MousePosition);
Box.Size = Vector2.ComponentMax(e.MouseDownPosition, e.MousePosition) - Box.Position;
}
private Visibility state;
@ -87,19 +65,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
}
}
protected override void Update()
{
base.Update();
if (dragRectangle != null)
PerformSelection?.Invoke(dragRectangle.Value);
}
public override void Hide()
{
State = Visibility.Hidden;
dragRectangle = null;
}
public override void Hide() => State = Visibility.Hidden;
public override void Show() => State = Visibility.Visible;

View File

@ -8,6 +8,7 @@ using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
@ -27,6 +28,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
private HitObjectUsageEventBuffer usageEventBuffer;
protected InputManager InputManager { get; private set; }
protected EditorBlueprintContainer(HitObjectComposer composer)
{
Composer = composer;
@ -42,6 +45,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
{
base.LoadComplete();
InputManager = GetContainingInputManager();
Beatmap.HitObjectAdded += AddBlueprintFor;
Beatmap.HitObjectRemoved += RemoveBlueprintFor;
@ -66,8 +71,6 @@ namespace osu.Game.Screens.Edit.Compose.Components
protected override IEnumerable<SelectionBlueprint<HitObject>> SortForMovement(IReadOnlyList<SelectionBlueprint<HitObject>> blueprints)
=> blueprints.OrderBy(b => b.Item.StartTime);
protected override bool AllowDeselectionDuringDrag => !EditorClock.IsRunning;
protected override bool ApplySnapResult(SelectionBlueprint<HitObject>[] blueprints, SnapResult result)
{
if (!base.ApplySnapResult(blueprints, result))
@ -133,8 +136,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
protected override void SelectAll()
{
Composer.Playfield.KeepAllAlive();
base.SelectAll();
SelectedItems.AddRange(Beatmap.HitObjects.Except(SelectedItems).ToArray());
}
protected override void OnBlueprintSelected(SelectionBlueprint<HitObject> blueprint)

View File

@ -0,0 +1,64 @@
// 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 osu.Framework.Input.Events;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Screens.Edit.Compose.Components
{
/// <summary>
/// A <see cref="DragBox"/> that scrolls along with the scrolling playfield.
/// </summary>
public class ScrollingDragBox : DragBox
{
public double MinTime { get; private set; }
public double MaxTime { get; private set; }
private double? startTime;
private readonly ScrollingPlayfield playfield;
public ScrollingDragBox(Playfield playfield)
{
this.playfield = playfield as ScrollingPlayfield ?? throw new ArgumentException("Playfield must be of type {nameof(ScrollingPlayfield)} to use this class.", nameof(playfield));
}
public override void HandleDrag(MouseButtonEvent e)
{
base.HandleDrag(e);
startTime ??= playfield.TimeAtScreenSpacePosition(e.ScreenSpaceMouseDownPosition);
double endTime = playfield.TimeAtScreenSpacePosition(e.ScreenSpaceMousePosition);
MinTime = Math.Min(startTime.Value, endTime);
MaxTime = Math.Max(startTime.Value, endTime);
var startPos = ToLocalSpace(playfield.ScreenSpacePositionAtTime(startTime.Value));
var endPos = ToLocalSpace(playfield.ScreenSpacePositionAtTime(endTime));
switch (playfield.ScrollingInfo.Direction.Value)
{
case ScrollingDirection.Up:
case ScrollingDirection.Down:
Box.Y = Math.Min(startPos.Y, endPos.Y);
Box.Height = Math.Max(startPos.Y, endPos.Y) - Box.Y;
break;
case ScrollingDirection.Left:
case ScrollingDirection.Right:
Box.X = Math.Min(startPos.X, endPos.X);
Box.Width = Math.Max(startPos.X, endPos.X) - Box.X;
break;
}
}
public override void Hide()
{
base.Hide();
startTime = null;
}
}
}

View File

@ -305,7 +305,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
protected void DeleteSelected()
{
DeleteItems(selectedBlueprints.Select(b => b.Item));
DeleteItems(SelectedItems.ToArray());
}
#endregion

View File

@ -304,10 +304,20 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
/// </summary>
public double VisibleRange => editorClock.TrackLength / Zoom;
public SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All) =>
new SnapResult(screenSpacePosition, beatSnapProvider.SnapTime(getTimeFromPosition(Content.ToLocalSpace(screenSpacePosition))));
public double TimeAtPosition(float x)
{
return x / Content.DrawWidth * editorClock.TrackLength;
}
private double getTimeFromPosition(Vector2 localPosition) =>
(localPosition.X / Content.DrawWidth) * editorClock.TrackLength;
public float PositionAtTime(double time)
{
return (float)(time / editorClock.TrackLength * Content.DrawWidth);
}
public SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All)
{
double time = TimeAtPosition(Content.ToLocalSpace(screenSpacePosition).X);
return new SnapResult(screenSpacePosition, beatSnapProvider.SnapTime(time));
}
}
}

View File

@ -3,7 +3,6 @@
#nullable disable
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
@ -13,7 +12,6 @@ using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Framework.Utils;
@ -31,10 +29,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
[Resolved(CanBeNull = true)]
private Timeline timeline { get; set; }
private DragEvent lastDragEvent;
private Bindable<HitObject> placement;
private SelectionBlueprint<HitObject> placementBlueprint;
private bool hitObjectDragged;
/// <remarks>
/// Positional input must be received outside the container's bounds,
/// in order to handle timeline blueprints which are stacked offscreen.
@ -65,7 +64,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
protected override void LoadComplete()
{
base.LoadComplete();
DragBox.Alpha = 0;
placement = Beatmap.PlacementObject.GetBoundCopy();
placement.ValueChanged += placementChanged;
@ -93,24 +91,18 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
protected override Container<SelectionBlueprint<HitObject>> CreateSelectionBlueprintContainer() => new TimelineSelectionBlueprintContainer { RelativeSizeAxes = Axes.Both };
protected override void OnDrag(DragEvent e)
protected override bool OnDragStart(DragStartEvent e)
{
handleScrollViaDrag(e);
if (!base.ReceivePositionalInputAt(e.ScreenSpaceMouseDownPosition))
return false;
base.OnDrag(e);
}
protected override void OnDragEnd(DragEndEvent e)
{
base.OnDragEnd(e);
lastDragEvent = null;
return base.OnDragStart(e);
}
protected override void Update()
{
// trigger every frame so drags continue to update selection while playback is scrolling the timeline.
if (lastDragEvent != null)
OnDrag(lastDragEvent);
if (IsDragged || hitObjectDragged)
handleScrollViaDrag();
if (Composer != null && timeline != null)
{
@ -165,30 +157,45 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{
return new TimelineHitObjectBlueprint(item)
{
OnDragHandled = handleScrollViaDrag,
OnDragHandled = e => hitObjectDragged = e != null,
};
}
protected override DragBox CreateDragBox(Action<RectangleF> performSelect) => new TimelineDragBox(performSelect);
protected sealed override DragBox CreateDragBox() => new TimelineDragBox();
private void handleScrollViaDrag(DragEvent e)
protected override void UpdateSelectionFromDragBox()
{
lastDragEvent = e;
var dragBox = (TimelineDragBox)DragBox;
double minTime = dragBox.MinTime;
double maxTime = dragBox.MaxTime;
if (lastDragEvent == null)
return;
SelectedItems.RemoveAll(hitObject => !shouldBeSelected(hitObject));
if (timeline != null)
foreach (var hitObject in Beatmap.HitObjects.Except(SelectedItems).Where(shouldBeSelected))
{
var timelineQuad = timeline.ScreenSpaceDrawQuad;
float mouseX = e.ScreenSpaceMousePosition.X;
// scroll if in a drag and dragging outside visible extents
if (mouseX > timelineQuad.TopRight.X)
timeline.ScrollBy((float)((mouseX - timelineQuad.TopRight.X) / 10 * Clock.ElapsedFrameTime));
else if (mouseX < timelineQuad.TopLeft.X)
timeline.ScrollBy((float)((mouseX - timelineQuad.TopLeft.X) / 10 * Clock.ElapsedFrameTime));
Composer.Playfield.SetKeepAlive(hitObject, true);
SelectedItems.Add(hitObject);
}
bool shouldBeSelected(HitObject hitObject)
{
double midTime = (hitObject.StartTime + hitObject.GetEndTime()) / 2;
return minTime <= midTime && midTime <= maxTime;
}
}
private void handleScrollViaDrag()
{
if (timeline == null) return;
var timelineQuad = timeline.ScreenSpaceDrawQuad;
float mouseX = InputManager.CurrentState.Mouse.Position.X;
// scroll if in a drag and dragging outside visible extents
if (mouseX > timelineQuad.TopRight.X)
timeline.ScrollBy((float)((mouseX - timelineQuad.TopRight.X) / 10 * Clock.ElapsedFrameTime));
else if (mouseX < timelineQuad.TopLeft.X)
timeline.ScrollBy((float)((mouseX - timelineQuad.TopLeft.X) / 10 * Clock.ElapsedFrameTime));
}
private class SelectableAreaBackground : CompositeDrawable

View File

@ -6,76 +6,44 @@
using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Framework.Utils;
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{
public class TimelineDragBox : DragBox
{
// the following values hold the start and end X positions of the drag box in the timeline's local space,
// but with zoom unapplied in order to be able to compensate for positional changes
// while the timeline is being zoomed in/out.
private float? selectionStart;
private float selectionEnd;
public double MinTime { get; private set; }
public double MaxTime { get; private set; }
private double? startTime;
[Resolved]
private Timeline timeline { get; set; }
public TimelineDragBox(Action<RectangleF> performSelect)
: base(performSelect)
{
}
protected override Drawable CreateBox() => new Box
{
RelativeSizeAxes = Axes.Y,
Alpha = 0.3f
};
public override bool HandleDrag(MouseButtonEvent e)
public override void HandleDrag(MouseButtonEvent e)
{
// The dragbox should only be active if the mouseDownPosition.Y is within this drawable's bounds.
float localY = ToLocalSpace(e.ScreenSpaceMouseDownPosition).Y;
if (DrawRectangle.Top > localY || DrawRectangle.Bottom < localY)
return false;
startTime ??= timeline.TimeAtPosition(e.MouseDownPosition.X);
double endTime = timeline.TimeAtPosition(e.MousePosition.X);
selectionStart ??= e.MouseDownPosition.X / timeline.CurrentZoom;
MinTime = Math.Min(startTime.Value, endTime);
MaxTime = Math.Max(startTime.Value, endTime);
// only calculate end when a transition is not in progress to avoid bouncing.
if (Precision.AlmostEquals(timeline.CurrentZoom, timeline.Zoom))
selectionEnd = e.MousePosition.X / timeline.CurrentZoom;
updateDragBoxPosition();
return true;
}
private void updateDragBoxPosition()
{
if (selectionStart == null)
return;
float rescaledStart = selectionStart.Value * timeline.CurrentZoom;
float rescaledEnd = selectionEnd * timeline.CurrentZoom;
Box.X = Math.Min(rescaledStart, rescaledEnd);
Box.Width = Math.Abs(rescaledStart - rescaledEnd);
var boxScreenRect = Box.ScreenSpaceDrawQuad.AABBFloat;
// we don't care about where the hitobjects are vertically. in cases like stacking display, they may be outside the box without this adjustment.
boxScreenRect.Y -= boxScreenRect.Height;
boxScreenRect.Height *= 2;
PerformSelection?.Invoke(boxScreenRect);
Box.X = timeline.PositionAtTime(MinTime);
Box.Width = timeline.PositionAtTime(MaxTime) - Box.X;
}
public override void Hide()
{
base.Hide();
selectionStart = null;
startTime = null;
}
}
}

View File

@ -352,6 +352,8 @@ namespace osu.Game.Screens.Edit
var updates = batchPendingUpdates.ToArray();
batchPendingUpdates.Clear();
foreach (var h in deletes) SelectedHitObjects.Remove(h);
foreach (var h in deletes) HitObjectRemoved?.Invoke(h);
foreach (var h in inserts) HitObjectAdded?.Invoke(h);
foreach (var h in updates) HitObjectUpdated?.Invoke(h);

View File

@ -39,6 +39,10 @@ namespace osu.Game.Screens.Play
/// </summary>
public float BottomScoringElementsHeight { get; private set; }
// HUD uses AlwaysVisible on child components so they can be in an updated state for next display.
// Without blocking input, this would also allow them to be interacted with in such a state.
public override bool PropagatePositionalInputSubTree => ShowHud.Value;
public readonly KeyCounterDisplay KeyCounter;
public readonly ModDisplay ModDisplay;
public readonly HoldForMenuButton HoldToQuit;

View File

@ -2,12 +2,14 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
@ -43,11 +45,15 @@ namespace osu.Game.Screens.Select.Carousel
Origin = Anchor.CentreLeft;
}
private Bindable<bool> preferNoVideo = null!;
[BackgroundDependencyLoader]
private void load()
private void load(OsuConfigManager config)
{
const float icon_size = 14;
preferNoVideo = config.GetBindable<bool>(OsuSetting.PreferNoVideo);
Content.Anchor = Anchor.CentreLeft;
Content.Origin = Anchor.CentreLeft;
@ -104,7 +110,7 @@ namespace osu.Game.Screens.Select.Carousel
return;
}
beatmapDownloader.DownloadAsUpdate(beatmapSetInfo);
beatmapDownloader.DownloadAsUpdate(beatmapSetInfo, preferNoVideo.Value);
attachExistingDownload();
};
}

View File

@ -117,6 +117,11 @@ namespace osu.Game.Skinning.Editor
return false;
}
protected override void SelectAll()
{
SelectedItems.AddRange(targetComponents.SelectMany(list => list).Except(SelectedItems).ToArray());
}
/// <summary>
/// Move the current selection spatially by the specified delta, in screen coordinates (ie. the same coordinates as the blueprints).
/// </summary>

View File

@ -27,7 +27,10 @@ namespace osu.Game.Storyboards
public void Add(Easing easing, double startTime, double endTime, T startValue, T endValue)
{
if (endTime < startTime)
return;
{
(startTime, endTime) = (endTime, startTime);
(startValue, endValue) = (endValue, startValue);
}
commands.Add(new TypedCommand { Easing = easing, StartTime = startTime, EndTime = endTime, StartValue = startValue, EndValue = endValue });

View File

@ -35,7 +35,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Realm" Version="10.17.0" />
<PackageReference Include="ppy.osu.Framework" Version="2022.1005.0" />
<PackageReference Include="ppy.osu.Framework" Version="2022.1011.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.1008.0" />
<PackageReference Include="Sentry" Version="3.22.0" />
<PackageReference Include="SharpCompress" Version="0.32.2" />

View File

@ -62,7 +62,7 @@
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.1008.0" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.1005.0" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.1011.0" />
</ItemGroup>
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net6.0) -->
<PropertyGroup>
@ -82,7 +82,7 @@
<PackageReference Include="DiffPlex" Version="1.7.1" />
<PackageReference Include="Humanizer" Version="2.14.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="ppy.osu.Framework" Version="2022.1005.0" />
<PackageReference Include="ppy.osu.Framework" Version="2022.1011.0" />
<PackageReference Include="SharpCompress" Version="0.32.2" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />