mirror of
https://github.com/ppy/osu.git
synced 2025-01-28 18:12:56 +08:00
Merge branch 'master' into hit-error-skinnable
This commit is contained in:
commit
363aec8179
@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
AddBlueprint(new HoldNoteSelectionBlueprint(drawableObject));
|
AddBlueprint(new HoldNoteSelectionBlueprint(holdNote), drawableObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
|
@ -184,8 +184,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
|||||||
AddAssert("head note positioned correctly", () => Precision.AlmostEquals(holdNote.ScreenSpaceDrawQuad.BottomLeft, holdNote.Head.ScreenSpaceDrawQuad.BottomLeft));
|
AddAssert("head note positioned correctly", () => Precision.AlmostEquals(holdNote.ScreenSpaceDrawQuad.BottomLeft, holdNote.Head.ScreenSpaceDrawQuad.BottomLeft));
|
||||||
AddAssert("tail note positioned correctly", () => Precision.AlmostEquals(holdNote.ScreenSpaceDrawQuad.TopLeft, holdNote.Tail.ScreenSpaceDrawQuad.BottomLeft));
|
AddAssert("tail note positioned correctly", () => Precision.AlmostEquals(holdNote.ScreenSpaceDrawQuad.TopLeft, holdNote.Tail.ScreenSpaceDrawQuad.BottomLeft));
|
||||||
|
|
||||||
AddAssert("head blueprint positioned correctly", () => this.ChildrenOfType<HoldNoteNoteSelectionBlueprint>().ElementAt(0).DrawPosition == holdNote.Head.DrawPosition);
|
AddAssert("head blueprint positioned correctly", () => this.ChildrenOfType<HoldNoteNoteOverlay>().ElementAt(0).DrawPosition == holdNote.Head.DrawPosition);
|
||||||
AddAssert("tail blueprint positioned correctly", () => this.ChildrenOfType<HoldNoteNoteSelectionBlueprint>().ElementAt(1).DrawPosition == holdNote.Tail.DrawPosition);
|
AddAssert("tail blueprint positioned correctly", () => this.ChildrenOfType<HoldNoteNoteOverlay>().ElementAt(1).DrawPosition == holdNote.Tail.DrawPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setScrollStep(ScrollingDirection direction)
|
private void setScrollStep(ScrollingDirection direction)
|
||||||
|
@ -15,7 +15,6 @@ using osu.Game.Rulesets.Objects.Drawables;
|
|||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
using osuTK;
|
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests.Editor
|
namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||||
@ -35,7 +34,11 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestPlaceBeforeCurrentTimeDownwards()
|
public void TestPlaceBeforeCurrentTimeDownwards()
|
||||||
{
|
{
|
||||||
AddStep("move mouse before current time", () => InputManager.MoveMouseTo(this.ChildrenOfType<Column>().Single().ScreenSpaceDrawQuad.BottomLeft - new Vector2(0, 10)));
|
AddStep("move mouse before current time", () =>
|
||||||
|
{
|
||||||
|
var column = this.ChildrenOfType<Column>().Single();
|
||||||
|
InputManager.MoveMouseTo(column.ScreenSpacePositionAtTime(-100));
|
||||||
|
});
|
||||||
|
|
||||||
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
@ -45,7 +48,11 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestPlaceAfterCurrentTimeDownwards()
|
public void TestPlaceAfterCurrentTimeDownwards()
|
||||||
{
|
{
|
||||||
AddStep("move mouse after current time", () => InputManager.MoveMouseTo(this.ChildrenOfType<Column>().Single()));
|
AddStep("move mouse after current time", () =>
|
||||||
|
{
|
||||||
|
var column = this.ChildrenOfType<Column>().Single();
|
||||||
|
InputManager.MoveMouseTo(column.ScreenSpacePositionAtTime(100));
|
||||||
|
});
|
||||||
|
|
||||||
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
|||||||
Child = drawableObject = new DrawableNote(note)
|
Child = drawableObject = new DrawableNote(note)
|
||||||
};
|
};
|
||||||
|
|
||||||
AddBlueprint(new NoteSelectionBlueprint(drawableObject));
|
AddBlueprint(new NoteSelectionBlueprint(note), drawableObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Width = 0.5f,
|
Width = 0.5f,
|
||||||
Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground, 0), _ => new DefaultColumnBackground())
|
Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground), _ => new DefaultColumnBackground())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
}
|
}
|
||||||
@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Width = 0.5f,
|
Width = 0.5f,
|
||||||
Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground, 1), _ => new DefaultColumnBackground())
|
Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground), _ => new DefaultColumnBackground())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Width = 0.5f,
|
Width = 0.5f,
|
||||||
Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea, 0), _ => new DefaultKeyArea())
|
Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
},
|
},
|
||||||
@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Width = 0.5f,
|
Width = 0.5f,
|
||||||
Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea, 1), _ => new DefaultKeyArea())
|
Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
},
|
},
|
||||||
|
@ -0,0 +1,67 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Timing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Tests
|
||||||
|
{
|
||||||
|
public class TestSceneDrawableManiaHitObject : OsuTestScene
|
||||||
|
{
|
||||||
|
private readonly ManualClock clock = new ManualClock();
|
||||||
|
|
||||||
|
private Column column;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp() => Schedule(() =>
|
||||||
|
{
|
||||||
|
Child = new ScrollingTestContainer(ScrollingDirection.Down)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
AutoSizeAxes = Axes.X,
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
TimeRange = 2000,
|
||||||
|
Clock = new FramedClock(clock),
|
||||||
|
Child = column = new Column(0)
|
||||||
|
{
|
||||||
|
Action = { Value = ManiaAction.Key1 },
|
||||||
|
Height = 0.85f,
|
||||||
|
AccentColour = Color4.Gray
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHoldNoteHeadVisibility()
|
||||||
|
{
|
||||||
|
DrawableHoldNote note = null;
|
||||||
|
AddStep("Add hold note", () =>
|
||||||
|
{
|
||||||
|
var h = new HoldNote
|
||||||
|
{
|
||||||
|
StartTime = 0,
|
||||||
|
Duration = 1000
|
||||||
|
};
|
||||||
|
h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
|
column.Add(note = new DrawableHoldNote(h));
|
||||||
|
});
|
||||||
|
AddStep("Hold key", () =>
|
||||||
|
{
|
||||||
|
clock.CurrentTime = 0;
|
||||||
|
note.OnPressed(ManiaAction.Key1);
|
||||||
|
});
|
||||||
|
AddStep("progress time", () => clock.CurrentTime = 500);
|
||||||
|
AddAssert("head is visible", () => note.Head.Alpha == 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,13 +5,11 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Framework.Testing;
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Replays;
|
using osu.Game.Replays;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
|
||||||
using osu.Game.Rulesets.Mania.Replays;
|
using osu.Game.Rulesets.Mania.Replays;
|
||||||
using osu.Game.Rulesets.Mania.Scoring;
|
using osu.Game.Rulesets.Mania.Scoring;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -414,14 +412,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
|
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
|
||||||
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
|
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
|
||||||
|
|
||||||
AddUntilStep("wait for head", () => currentPlayer.GameplayClockContainer.GameplayClock.CurrentTime >= time_head);
|
AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor?.HasCompleted.Value == true);
|
||||||
AddAssert("head is visible",
|
|
||||||
() => currentPlayer.ChildrenOfType<DrawableHoldNote>()
|
|
||||||
.Single(note => note.HitObject == beatmap.HitObjects[0])
|
|
||||||
.Head
|
|
||||||
.Alpha == 1);
|
|
||||||
|
|
||||||
AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ScoreAccessibleReplayPlayer : ReplayPlayer
|
private class ScoreAccessibleReplayPlayer : ReplayPlayer
|
||||||
|
@ -2,34 +2,35 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
|
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||||
{
|
{
|
||||||
public class HoldNoteNoteSelectionBlueprint : ManiaSelectionBlueprint
|
public class HoldNoteNoteOverlay : CompositeDrawable
|
||||||
{
|
{
|
||||||
protected new DrawableHoldNote DrawableObject => (DrawableHoldNote)base.DrawableObject;
|
private readonly HoldNoteSelectionBlueprint holdNoteBlueprint;
|
||||||
|
|
||||||
private readonly HoldNotePosition position;
|
private readonly HoldNotePosition position;
|
||||||
|
|
||||||
public HoldNoteNoteSelectionBlueprint(DrawableHoldNote holdNote, HoldNotePosition position)
|
public HoldNoteNoteOverlay(HoldNoteSelectionBlueprint holdNoteBlueprint, HoldNotePosition position)
|
||||||
: base(holdNote)
|
|
||||||
{
|
{
|
||||||
|
this.holdNoteBlueprint = holdNoteBlueprint;
|
||||||
this.position = position;
|
this.position = position;
|
||||||
InternalChild = new EditNotePiece { RelativeSizeAxes = Axes.X };
|
|
||||||
|
|
||||||
Select();
|
InternalChild = new EditNotePiece { RelativeSizeAxes = Axes.X };
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
|
var drawableObject = holdNoteBlueprint.DrawableObject;
|
||||||
|
|
||||||
// Todo: This shouldn't exist, mania should not reference the drawable hitobject directly.
|
// Todo: This shouldn't exist, mania should not reference the drawable hitobject directly.
|
||||||
if (DrawableObject.IsLoaded)
|
if (drawableObject.IsLoaded)
|
||||||
{
|
{
|
||||||
DrawableNote note = position == HoldNotePosition.Start ? (DrawableNote)DrawableObject.Head : DrawableObject.Tail;
|
DrawableNote note = position == HoldNotePosition.Start ? (DrawableNote)drawableObject.Head : drawableObject.Tail;
|
||||||
|
|
||||||
Anchor = note.Anchor;
|
Anchor = note.Anchor;
|
||||||
Origin = note.Origin;
|
Origin = note.Origin;
|
||||||
@ -38,8 +39,5 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
Position = note.DrawPosition;
|
Position = note.DrawPosition;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Todo: This is temporary, since the note masks don't do anything special yet. In the future they will handle input.
|
|
||||||
public override bool HandlePositionalInput => false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -8,13 +8,14 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Graphics.Primitives;
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||||
{
|
{
|
||||||
public class HoldNoteSelectionBlueprint : ManiaSelectionBlueprint
|
public class HoldNoteSelectionBlueprint : ManiaSelectionBlueprint<HoldNote>
|
||||||
{
|
{
|
||||||
public new DrawableHoldNote DrawableObject => (DrawableHoldNote)base.DrawableObject;
|
public new DrawableHoldNote DrawableObject => (DrawableHoldNote)base.DrawableObject;
|
||||||
|
|
||||||
@ -23,7 +24,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuColour colours { get; set; }
|
private OsuColour colours { get; set; }
|
||||||
|
|
||||||
public HoldNoteSelectionBlueprint(DrawableHoldNote hold)
|
public HoldNoteSelectionBlueprint(HoldNote hold)
|
||||||
: base(hold)
|
: base(hold)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -32,16 +33,11 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
private void load(IScrollingInfo scrollingInfo)
|
private void load(IScrollingInfo scrollingInfo)
|
||||||
{
|
{
|
||||||
direction.BindTo(scrollingInfo.Direction);
|
direction.BindTo(scrollingInfo.Direction);
|
||||||
}
|
|
||||||
|
|
||||||
protected override void LoadComplete()
|
|
||||||
{
|
|
||||||
base.LoadComplete();
|
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
new HoldNoteNoteSelectionBlueprint(DrawableObject, HoldNotePosition.Start),
|
new HoldNoteNoteOverlay(this, HoldNotePosition.Start),
|
||||||
new HoldNoteNoteSelectionBlueprint(DrawableObject, HoldNotePosition.End),
|
new HoldNoteNoteOverlay(this, HoldNotePosition.End),
|
||||||
new Container
|
new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
@ -4,22 +4,23 @@
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||||
{
|
{
|
||||||
public abstract class ManiaSelectionBlueprint : OverlaySelectionBlueprint
|
public abstract class ManiaSelectionBlueprint<T> : HitObjectSelectionBlueprint<T>
|
||||||
|
where T : ManiaHitObject
|
||||||
{
|
{
|
||||||
public new DrawableManiaHitObject DrawableObject => (DrawableManiaHitObject)base.DrawableObject;
|
public new DrawableManiaHitObject DrawableObject => (DrawableManiaHitObject)base.DrawableObject;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private IScrollingInfo scrollingInfo { get; set; }
|
private IScrollingInfo scrollingInfo { get; set; }
|
||||||
|
|
||||||
protected ManiaSelectionBlueprint(DrawableHitObject drawableObject)
|
protected ManiaSelectionBlueprint(T hitObject)
|
||||||
: base(drawableObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.None;
|
RelativeSizeAxes = Axes.None;
|
||||||
}
|
}
|
||||||
|
@ -3,13 +3,13 @@
|
|||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
|
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||||
{
|
{
|
||||||
public class NoteSelectionBlueprint : ManiaSelectionBlueprint
|
public class NoteSelectionBlueprint : ManiaSelectionBlueprint<Note>
|
||||||
{
|
{
|
||||||
public NoteSelectionBlueprint(DrawableNote note)
|
public NoteSelectionBlueprint(Note note)
|
||||||
: base(note)
|
: base(note)
|
||||||
{
|
{
|
||||||
AddInternal(new EditNotePiece { RelativeSizeAxes = Axes.X });
|
AddInternal(new EditNotePiece { RelativeSizeAxes = Axes.X });
|
||||||
|
@ -101,7 +101,7 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
foreach (var line in grid.Objects.OfType<DrawableGridLine>())
|
foreach (var line in grid.Objects.OfType<DrawableGridLine>())
|
||||||
availableLines.Push(line);
|
availableLines.Push(line);
|
||||||
|
|
||||||
grid.Clear(false);
|
grid.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectionTimeRange == null)
|
if (selectionTimeRange == null)
|
||||||
|
@ -3,9 +3,8 @@
|
|||||||
|
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Mania.Edit.Blueprints;
|
using osu.Game.Rulesets.Mania.Edit.Blueprints;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
|
||||||
using osu.Game.Screens.Edit.Compose.Components;
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Edit
|
namespace osu.Game.Rulesets.Mania.Edit
|
||||||
@ -17,18 +16,18 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject)
|
public override HitObjectSelectionBlueprint CreateHitObjectBlueprintFor(HitObject hitObject)
|
||||||
{
|
{
|
||||||
switch (hitObject)
|
switch (hitObject)
|
||||||
{
|
{
|
||||||
case DrawableNote note:
|
case Note note:
|
||||||
return new NoteSelectionBlueprint(note);
|
return new NoteSelectionBlueprint(note);
|
||||||
|
|
||||||
case DrawableHoldNote holdNote:
|
case HoldNote holdNote:
|
||||||
return new HoldNoteSelectionBlueprint(holdNote);
|
return new HoldNoteSelectionBlueprint(holdNote);
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.CreateBlueprintFor(hitObject);
|
return base.CreateHitObjectBlueprintFor(hitObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override SelectionHandler<HitObject> CreateSelectionHandler() => new ManiaSelectionHandler();
|
protected override SelectionHandler<HitObject> CreateSelectionHandler() => new ManiaSelectionHandler();
|
||||||
|
@ -5,7 +5,6 @@ using System;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Mania.Edit.Blueprints;
|
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
@ -23,8 +22,8 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
|
|
||||||
public override bool HandleMovement(MoveSelectionEvent<HitObject> moveEvent)
|
public override bool HandleMovement(MoveSelectionEvent<HitObject> moveEvent)
|
||||||
{
|
{
|
||||||
var maniaBlueprint = (ManiaSelectionBlueprint)moveEvent.Blueprint;
|
var hitObjectBlueprint = (HitObjectSelectionBlueprint)moveEvent.Blueprint;
|
||||||
int lastColumn = maniaBlueprint.DrawableObject.HitObject.Column;
|
int lastColumn = ((ManiaHitObject)hitObjectBlueprint.Item).Column;
|
||||||
|
|
||||||
performColumnMovement(lastColumn, moveEvent);
|
performColumnMovement(lastColumn, moveEvent);
|
||||||
|
|
||||||
@ -59,8 +58,9 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
|
|
||||||
EditorBeatmap.PerformOnSelection(h =>
|
EditorBeatmap.PerformOnSelection(h =>
|
||||||
{
|
{
|
||||||
if (h is ManiaHitObject maniaObj)
|
maniaPlayfield.Remove(h);
|
||||||
maniaObj.Column += columnDelta;
|
((ManiaHitObject)h).Column += columnDelta;
|
||||||
|
maniaPlayfield.Add(h);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,12 +9,6 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
{
|
{
|
||||||
public class ManiaSkinComponent : GameplaySkinComponent<ManiaSkinComponents>
|
public class ManiaSkinComponent : GameplaySkinComponent<ManiaSkinComponents>
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// The intended <see cref="Column"/> index for this component.
|
|
||||||
/// May be null if the component does not exist in a <see cref="Column"/>.
|
|
||||||
/// </summary>
|
|
||||||
public readonly int? TargetColumn;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The intended <see cref="StageDefinition"/> for this component.
|
/// The intended <see cref="StageDefinition"/> for this component.
|
||||||
/// May be null if the component is not a direct member of a <see cref="Stage"/>.
|
/// May be null if the component is not a direct member of a <see cref="Stage"/>.
|
||||||
@ -25,12 +19,10 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
/// Creates a new <see cref="ManiaSkinComponent"/>.
|
/// Creates a new <see cref="ManiaSkinComponent"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="component">The component.</param>
|
/// <param name="component">The component.</param>
|
||||||
/// <param name="targetColumn">The intended <see cref="Column"/> index for this component. May be null if the component does not exist in a <see cref="Column"/>.</param>
|
|
||||||
/// <param name="stageDefinition">The intended <see cref="StageDefinition"/> for this component. May be null if the component is not a direct member of a <see cref="Stage"/>.</param>
|
/// <param name="stageDefinition">The intended <see cref="StageDefinition"/> for this component. May be null if the component is not a direct member of a <see cref="Stage"/>.</param>
|
||||||
public ManiaSkinComponent(ManiaSkinComponents component, int? targetColumn = null, StageDefinition? stageDefinition = null)
|
public ManiaSkinComponent(ManiaSkinComponents component, StageDefinition? stageDefinition = null)
|
||||||
: base(component)
|
: base(component)
|
||||||
{
|
{
|
||||||
TargetColumn = targetColumn;
|
|
||||||
StageDefinition = stageDefinition;
|
StageDefinition = stageDefinition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -12,6 +13,7 @@ using osu.Game.Rulesets.Objects.Drawables;
|
|||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||||
{
|
{
|
||||||
@ -29,21 +31,21 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
public DrawableHoldNoteHead Head => headContainer.Child;
|
public DrawableHoldNoteHead Head => headContainer.Child;
|
||||||
public DrawableHoldNoteTail Tail => tailContainer.Child;
|
public DrawableHoldNoteTail Tail => tailContainer.Child;
|
||||||
|
|
||||||
private readonly Container<DrawableHoldNoteHead> headContainer;
|
private Container<DrawableHoldNoteHead> headContainer;
|
||||||
private readonly Container<DrawableHoldNoteTail> tailContainer;
|
private Container<DrawableHoldNoteTail> tailContainer;
|
||||||
private readonly Container<DrawableHoldNoteTick> tickContainer;
|
private Container<DrawableHoldNoteTick> tickContainer;
|
||||||
|
|
||||||
/// <summary>
|
/// <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.
|
/// 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>
|
/// </summary>
|
||||||
private readonly Container sizingContainer;
|
private Container sizingContainer;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Contains the contents of the hold note that should be masked as the hold note is being pressed. Follows changes in the size of <see cref="sizingContainer"/>.
|
/// Contains the contents of the hold note that should be masked as the hold note is being pressed. Follows changes in the size of <see cref="sizingContainer"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly Container maskingContainer;
|
private Container maskingContainer;
|
||||||
|
|
||||||
private readonly SkinnableDrawable bodyPiece;
|
private SkinnableDrawable bodyPiece;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Time at which the user started holding this hold note. Null if the user is not holding this hold note.
|
/// Time at which the user started holding this hold note. Null if the user is not holding this hold note.
|
||||||
@ -60,11 +62,19 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private double? releaseTime;
|
private double? releaseTime;
|
||||||
|
|
||||||
|
public DrawableHoldNote()
|
||||||
|
: this(null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public DrawableHoldNote(HoldNote hitObject)
|
public DrawableHoldNote(HoldNote hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
Container maskedContents;
|
Container maskedContents;
|
||||||
|
|
||||||
AddRangeInternal(new Drawable[]
|
AddRangeInternal(new Drawable[]
|
||||||
@ -86,7 +96,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
headContainer = new Container<DrawableHoldNoteHead> { RelativeSizeAxes = Axes.Both }
|
headContainer = new Container<DrawableHoldNoteHead> { RelativeSizeAxes = Axes.Both }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
bodyPiece = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HoldNoteBody, hitObject.Column), _ => new DefaultBodyPiece
|
bodyPiece = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HoldNoteBody), _ => new DefaultBodyPiece
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
})
|
})
|
||||||
@ -105,6 +115,16 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnApply()
|
||||||
|
{
|
||||||
|
base.OnApply();
|
||||||
|
|
||||||
|
sizingContainer.Size = Vector2.One;
|
||||||
|
HoldStartTime = null;
|
||||||
|
HoldBrokenTime = null;
|
||||||
|
releaseTime = null;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void AddNestedHitObject(DrawableHitObject hitObject)
|
protected override void AddNestedHitObject(DrawableHitObject hitObject)
|
||||||
{
|
{
|
||||||
base.AddNestedHitObject(hitObject);
|
base.AddNestedHitObject(hitObject);
|
||||||
@ -128,37 +148,23 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
protected override void ClearNestedHitObjects()
|
protected override void ClearNestedHitObjects()
|
||||||
{
|
{
|
||||||
base.ClearNestedHitObjects();
|
base.ClearNestedHitObjects();
|
||||||
headContainer.Clear();
|
headContainer.Clear(false);
|
||||||
tailContainer.Clear();
|
tailContainer.Clear(false);
|
||||||
tickContainer.Clear();
|
tickContainer.Clear(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
|
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
|
||||||
{
|
{
|
||||||
switch (hitObject)
|
switch (hitObject)
|
||||||
{
|
{
|
||||||
case TailNote _:
|
case TailNote tail:
|
||||||
return new DrawableHoldNoteTail(this)
|
return new DrawableHoldNoteTail(tail);
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
AccentColour = { BindTarget = AccentColour }
|
|
||||||
};
|
|
||||||
|
|
||||||
case Note _:
|
case HeadNote head:
|
||||||
return new DrawableHoldNoteHead(this)
|
return new DrawableHoldNoteHead(head);
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
AccentColour = { BindTarget = AccentColour }
|
|
||||||
};
|
|
||||||
|
|
||||||
case HoldNoteTick tick:
|
case HoldNoteTick tick:
|
||||||
return new DrawableHoldNoteTick(tick)
|
return new DrawableHoldNoteTick(tick);
|
||||||
{
|
|
||||||
HoldStartTime = () => HoldStartTime,
|
|
||||||
AccentColour = { BindTarget = AccentColour }
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.CreateNestedHitObject(hitObject);
|
return base.CreateNestedHitObject(hitObject);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||||
@ -12,11 +13,18 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
{
|
{
|
||||||
protected override ManiaSkinComponents Component => ManiaSkinComponents.HoldNoteHead;
|
protected override ManiaSkinComponents Component => ManiaSkinComponents.HoldNoteHead;
|
||||||
|
|
||||||
public DrawableHoldNoteHead(DrawableHoldNote holdNote)
|
public DrawableHoldNoteHead()
|
||||||
: base(holdNote.HitObject.Head)
|
: this(null)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DrawableHoldNoteHead(HeadNote headNote)
|
||||||
|
: base(headNote)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre;
|
||||||
|
Origin = Anchor.TopCentre;
|
||||||
|
}
|
||||||
|
|
||||||
public void UpdateResult() => base.UpdateResult(true);
|
public void UpdateResult() => base.UpdateResult(true);
|
||||||
|
|
||||||
protected override void UpdateInitialTransforms()
|
protected override void UpdateInitialTransforms()
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||||
@ -20,12 +21,18 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
|
|
||||||
protected override ManiaSkinComponents Component => ManiaSkinComponents.HoldNoteTail;
|
protected override ManiaSkinComponents Component => ManiaSkinComponents.HoldNoteTail;
|
||||||
|
|
||||||
private readonly DrawableHoldNote holdNote;
|
protected DrawableHoldNote HoldNote => (DrawableHoldNote)ParentHitObject;
|
||||||
|
|
||||||
public DrawableHoldNoteTail(DrawableHoldNote holdNote)
|
public DrawableHoldNoteTail()
|
||||||
: base(holdNote.HitObject.Tail)
|
: this(null)
|
||||||
{
|
{
|
||||||
this.holdNote = holdNote;
|
}
|
||||||
|
|
||||||
|
public DrawableHoldNoteTail(TailNote tailNote)
|
||||||
|
: base(tailNote)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre;
|
||||||
|
Origin = Anchor.TopCentre;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateResult() => base.UpdateResult(true);
|
public void UpdateResult() => base.UpdateResult(true);
|
||||||
@ -54,7 +61,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
ApplyResult(r =>
|
ApplyResult(r =>
|
||||||
{
|
{
|
||||||
// If the head wasn't hit or the hold note was broken, cap the max score to Meh.
|
// If the head wasn't hit or the hold note was broken, cap the max score to Meh.
|
||||||
if (result > HitResult.Meh && (!holdNote.Head.IsHit || holdNote.HoldBrokenTime != null))
|
if (result > HitResult.Meh && (!HoldNote.Head.IsHit || HoldNote.HoldBrokenTime != null))
|
||||||
result = HitResult.Meh;
|
result = HitResult.Meh;
|
||||||
|
|
||||||
r.Type = result;
|
r.Type = result;
|
||||||
|
@ -2,7 +2,8 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osuTK;
|
using System.Diagnostics;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -19,38 +20,48 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// References the time at which the user started holding the hold note.
|
/// References the time at which the user started holding the hold note.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Func<double?> HoldStartTime;
|
private Func<double?> holdStartTime;
|
||||||
|
|
||||||
|
private Container glowContainer;
|
||||||
|
|
||||||
|
public DrawableHoldNoteTick()
|
||||||
|
: this(null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public DrawableHoldNoteTick(HoldNoteTick hitObject)
|
public DrawableHoldNoteTick(HoldNoteTick hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
Container glowContainer;
|
|
||||||
|
|
||||||
Anchor = Anchor.TopCentre;
|
Anchor = Anchor.TopCentre;
|
||||||
Origin = Anchor.TopCentre;
|
Origin = Anchor.TopCentre;
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
Size = new Vector2(1);
|
}
|
||||||
|
|
||||||
AddRangeInternal(new[]
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
AddInternal(glowContainer = new CircularContainer
|
||||||
{
|
{
|
||||||
glowContainer = new CircularContainer
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
Children = new[]
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre,
|
new Box
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Masking = true,
|
|
||||||
Children = new[]
|
|
||||||
{
|
{
|
||||||
new Box
|
RelativeSizeAxes = Axes.Both,
|
||||||
{
|
Alpha = 0,
|
||||||
RelativeSizeAxes = Axes.Both,
|
AlwaysPresent = true
|
||||||
Alpha = 0,
|
|
||||||
AlwaysPresent = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
AccentColour.BindValueChanged(colour =>
|
AccentColour.BindValueChanged(colour =>
|
||||||
{
|
{
|
||||||
@ -64,12 +75,29 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnApply()
|
||||||
|
{
|
||||||
|
base.OnApply();
|
||||||
|
|
||||||
|
Debug.Assert(ParentHitObject != null);
|
||||||
|
|
||||||
|
var holdNote = (DrawableHoldNote)ParentHitObject;
|
||||||
|
holdStartTime = () => holdNote.HoldStartTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnFree()
|
||||||
|
{
|
||||||
|
base.OnFree();
|
||||||
|
|
||||||
|
holdStartTime = null;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||||
{
|
{
|
||||||
if (Time.Current < HitObject.StartTime)
|
if (Time.Current < HitObject.StartTime)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var startTime = HoldStartTime?.Invoke();
|
var startTime = holdStartTime?.Invoke();
|
||||||
|
|
||||||
if (startTime == null || startTime > HitObject.StartTime)
|
if (startTime == null || startTime > HitObject.StartTime)
|
||||||
ApplyResult(r => r.Type = r.Judgement.MinResult);
|
ApplyResult(r => r.Type = r.Judgement.MinResult);
|
||||||
|
@ -50,6 +50,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
protected DrawableManiaHitObject(ManiaHitObject hitObject)
|
protected DrawableManiaHitObject(ManiaHitObject hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
@ -59,9 +60,31 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
Action.BindTo(action);
|
Action.BindTo(action);
|
||||||
|
|
||||||
Direction.BindTo(scrollingInfo.Direction);
|
Direction.BindTo(scrollingInfo.Direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
Direction.BindValueChanged(OnDirectionChanged, true);
|
Direction.BindValueChanged(OnDirectionChanged, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnApply()
|
||||||
|
{
|
||||||
|
base.OnApply();
|
||||||
|
|
||||||
|
if (ParentHitObject != null)
|
||||||
|
AccentColour.BindTo(ParentHitObject.AccentColour);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnFree()
|
||||||
|
{
|
||||||
|
base.OnFree();
|
||||||
|
|
||||||
|
if (ParentHitObject != null)
|
||||||
|
AccentColour.UnbindFrom(ParentHitObject.AccentColour);
|
||||||
|
}
|
||||||
|
|
||||||
private double computedLifetimeStart;
|
private double computedLifetimeStart;
|
||||||
|
|
||||||
public override double LifetimeStart
|
public override double LifetimeStart
|
||||||
@ -147,12 +170,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
public abstract class DrawableManiaHitObject<TObject> : DrawableManiaHitObject
|
public abstract class DrawableManiaHitObject<TObject> : DrawableManiaHitObject
|
||||||
where TObject : ManiaHitObject
|
where TObject : ManiaHitObject
|
||||||
{
|
{
|
||||||
public new readonly TObject HitObject;
|
public new TObject HitObject => (TObject)base.HitObject;
|
||||||
|
|
||||||
protected DrawableManiaHitObject(TObject hitObject)
|
protected DrawableManiaHitObject(TObject hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
HitObject = hitObject;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,31 +33,37 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
|
|
||||||
protected virtual ManiaSkinComponents Component => ManiaSkinComponents.Note;
|
protected virtual ManiaSkinComponents Component => ManiaSkinComponents.Note;
|
||||||
|
|
||||||
private readonly Drawable headPiece;
|
private Drawable headPiece;
|
||||||
|
|
||||||
|
public DrawableNote()
|
||||||
|
: this(null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public DrawableNote(Note hitObject)
|
public DrawableNote(Note hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
|
||||||
AutoSizeAxes = Axes.Y;
|
AutoSizeAxes = Axes.Y;
|
||||||
|
|
||||||
AddInternal(headPiece = new SkinnableDrawable(new ManiaSkinComponent(Component, hitObject.Column), _ => new DefaultNotePiece())
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(ManiaRulesetConfigManager rulesetConfig)
|
private void load(ManiaRulesetConfigManager rulesetConfig)
|
||||||
{
|
{
|
||||||
rulesetConfig?.BindWith(ManiaRulesetSetting.TimingBasedNoteColouring, configTimingBasedNoteColouring);
|
rulesetConfig?.BindWith(ManiaRulesetSetting.TimingBasedNoteColouring, configTimingBasedNoteColouring);
|
||||||
|
|
||||||
|
AddInternal(headPiece = new SkinnableDrawable(new ManiaSkinComponent(Component), _ => new DefaultNotePiece())
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
HitObject.StartTimeBindable.BindValueChanged(_ => updateSnapColour());
|
base.LoadComplete();
|
||||||
configTimingBasedNoteColouring.BindValueChanged(_ => updateSnapColour(), true);
|
|
||||||
|
configTimingBasedNoteColouring.BindValueChanged(_ => updateSnapColour());
|
||||||
|
StartTimeBindable.BindValueChanged(_ => updateSnapColour(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDirectionChanged(ValueChangedEvent<ScrollingDirection> e)
|
protected override void OnDirectionChanged(ValueChangedEvent<ScrollingDirection> e)
|
||||||
@ -102,7 +108,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
|
|
||||||
private void updateSnapColour()
|
private void updateSnapColour()
|
||||||
{
|
{
|
||||||
if (beatmap == null) return;
|
if (beatmap == null || HitObject == null) return;
|
||||||
|
|
||||||
int snapDivisor = beatmap.ControlPointInfo.GetClosestBeatDivisor(HitObject.StartTime);
|
int snapDivisor = beatmap.ControlPointInfo.GetClosestBeatDivisor(HitObject.StartTime);
|
||||||
|
|
||||||
|
9
osu.Game.Rulesets.Mania/Objects/HeadNote.cs
Normal file
9
osu.Game.Rulesets.Mania/Objects/HeadNote.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Objects
|
||||||
|
{
|
||||||
|
public class HeadNote : Note
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Mania.Objects
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The head note of the hold.
|
/// The head note of the hold.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Note Head { get; private set; }
|
public HeadNote Head { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The tail note of the hold.
|
/// The tail note of the hold.
|
||||||
@ -98,7 +98,7 @@ namespace osu.Game.Rulesets.Mania.Objects
|
|||||||
|
|
||||||
createTicks(cancellationToken);
|
createTicks(cancellationToken);
|
||||||
|
|
||||||
AddNested(Head = new Note
|
AddNested(Head = new HeadNote
|
||||||
{
|
{
|
||||||
StartTime = StartTime,
|
StartTime = StartTime,
|
||||||
Column = Column,
|
Column = Column,
|
||||||
|
@ -17,6 +17,7 @@ using osu.Game.Rulesets.UI.Scrolling;
|
|||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.UI
|
namespace osu.Game.Rulesets.Mania.UI
|
||||||
@ -55,7 +56,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
RelativeSizeAxes = Axes.Y;
|
RelativeSizeAxes = Axes.Y;
|
||||||
Width = COLUMN_WIDTH;
|
Width = COLUMN_WIDTH;
|
||||||
|
|
||||||
Drawable background = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground, Index), _ => new DefaultColumnBackground())
|
Drawable background = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground), _ => new DefaultColumnBackground())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
};
|
};
|
||||||
@ -66,7 +67,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
// For input purposes, the background is added at the highest depth, but is then proxied back below all other elements
|
// For input purposes, the background is added at the highest depth, but is then proxied back below all other elements
|
||||||
background.CreateProxy(),
|
background.CreateProxy(),
|
||||||
HitObjectArea = new ColumnHitObjectArea(Index, HitObjectContainer) { RelativeSizeAxes = Axes.Both },
|
HitObjectArea = new ColumnHitObjectArea(Index, HitObjectContainer) { RelativeSizeAxes = Axes.Both },
|
||||||
new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea, Index), _ => new DefaultKeyArea())
|
new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
},
|
},
|
||||||
@ -83,6 +84,19 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
hitPolicy = new OrderedHitPolicy(HitObjectContainer);
|
hitPolicy = new OrderedHitPolicy(HitObjectContainer);
|
||||||
|
|
||||||
TopLevelContainer.Add(HitObjectArea.Explosions.CreateProxy());
|
TopLevelContainer.Add(HitObjectArea.Explosions.CreateProxy());
|
||||||
|
|
||||||
|
RegisterPool<Note, DrawableNote>(10, 50);
|
||||||
|
RegisterPool<HoldNote, DrawableHoldNote>(10, 50);
|
||||||
|
RegisterPool<HeadNote, DrawableHoldNoteHead>(10, 50);
|
||||||
|
RegisterPool<TailNote, DrawableHoldNoteTail>(10, 50);
|
||||||
|
RegisterPool<HoldNoteTick, DrawableHoldNoteTick>(50, 250);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
NewResult += OnNewResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ColumnType ColumnType { get; set; }
|
public ColumnType ColumnType { get; set; }
|
||||||
@ -98,28 +112,14 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
return dependencies;
|
return dependencies;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
protected override void OnNewDrawableHitObject(DrawableHitObject drawableHitObject)
|
||||||
/// Adds a DrawableHitObject to this Playfield.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="hitObject">The DrawableHitObject to add.</param>
|
|
||||||
public override void Add(DrawableHitObject hitObject)
|
|
||||||
{
|
{
|
||||||
hitObject.AccentColour.Value = AccentColour;
|
base.OnNewDrawableHitObject(drawableHitObject);
|
||||||
hitObject.OnNewResult += OnNewResult;
|
|
||||||
|
|
||||||
DrawableManiaHitObject maniaObject = (DrawableManiaHitObject)hitObject;
|
DrawableManiaHitObject maniaObject = (DrawableManiaHitObject)drawableHitObject;
|
||||||
|
|
||||||
|
maniaObject.AccentColour.Value = AccentColour;
|
||||||
maniaObject.CheckHittable = hitPolicy.IsHittable;
|
maniaObject.CheckHittable = hitPolicy.IsHittable;
|
||||||
|
|
||||||
base.Add(hitObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Remove(DrawableHitObject h)
|
|
||||||
{
|
|
||||||
if (!base.Remove(h))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
h.OnNewResult -= OnNewResult;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
||||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Depth = 2,
|
Depth = 2,
|
||||||
},
|
},
|
||||||
hitTarget = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitTarget, columnIndex), _ => new DefaultHitTarget())
|
hitTarget = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitTarget), _ => new DefaultHitTarget())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Depth = 1
|
Depth = 1
|
||||||
|
@ -18,7 +18,6 @@ using osu.Game.Replays;
|
|||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.Configuration;
|
using osu.Game.Rulesets.Mania.Configuration;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
|
||||||
using osu.Game.Rulesets.Mania.Replays;
|
using osu.Game.Rulesets.Mania.Replays;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -134,20 +133,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
|
|
||||||
protected override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant);
|
protected override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant);
|
||||||
|
|
||||||
public override DrawableHitObject<ManiaHitObject> CreateDrawableRepresentation(ManiaHitObject h)
|
public override DrawableHitObject<ManiaHitObject> CreateDrawableRepresentation(ManiaHitObject h) => null;
|
||||||
{
|
|
||||||
switch (h)
|
|
||||||
{
|
|
||||||
case HoldNote holdNote:
|
|
||||||
return new DrawableHoldNote(holdNote);
|
|
||||||
|
|
||||||
case Note note:
|
|
||||||
return new DrawableNote(note);
|
|
||||||
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay);
|
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay);
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ using System.Linq;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -56,6 +57,10 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void Add(HitObject hitObject) => getStageByColumn(((ManiaHitObject)hitObject).Column).Add(hitObject);
|
||||||
|
|
||||||
|
public override bool Remove(HitObject hitObject) => getStageByColumn(((ManiaHitObject)hitObject).Column).Remove(hitObject);
|
||||||
|
|
||||||
public override void Add(DrawableHitObject h) => getStageByColumn(((ManiaHitObject)h.HitObject).Column).Add(h);
|
public override void Add(DrawableHitObject h) => getStageByColumn(((ManiaHitObject)h.HitObject).Column).Add(h);
|
||||||
|
|
||||||
public override bool Remove(DrawableHitObject h) => getStageByColumn(((ManiaHitObject)h.HitObject).Column).Remove(h);
|
public override bool Remove(DrawableHitObject h) => getStageByColumn(((ManiaHitObject)h.HitObject).Column).Remove(h);
|
||||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
InternalChild = skinnableExplosion = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitExplosion, column.Index), _ => new DefaultHitExplosion())
|
InternalChild = skinnableExplosion = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitExplosion), _ => new DefaultHitExplosion())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
};
|
};
|
||||||
|
@ -11,6 +11,7 @@ using osu.Game.Rulesets.Mania.Beatmaps;
|
|||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Mania.UI.Components;
|
using osu.Game.Rulesets.Mania.UI.Components;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
@ -132,33 +133,19 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Add(DrawableHitObject h)
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
var maniaObject = (ManiaHitObject)h.HitObject;
|
base.LoadComplete();
|
||||||
|
NewResult += OnNewResult;
|
||||||
int columnIndex = -1;
|
|
||||||
|
|
||||||
maniaObject.ColumnBindable.BindValueChanged(_ =>
|
|
||||||
{
|
|
||||||
if (columnIndex != -1)
|
|
||||||
Columns.ElementAt(columnIndex).Remove(h);
|
|
||||||
|
|
||||||
columnIndex = maniaObject.Column - firstColumnIndex;
|
|
||||||
Columns.ElementAt(columnIndex).Add(h);
|
|
||||||
}, true);
|
|
||||||
|
|
||||||
h.OnNewResult += OnNewResult;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool Remove(DrawableHitObject h)
|
public override void Add(HitObject hitObject) => Columns.ElementAt(((ManiaHitObject)hitObject).Column - firstColumnIndex).Add(hitObject);
|
||||||
{
|
|
||||||
var maniaObject = (ManiaHitObject)h.HitObject;
|
|
||||||
int columnIndex = maniaObject.Column - firstColumnIndex;
|
|
||||||
Columns.ElementAt(columnIndex).Remove(h);
|
|
||||||
|
|
||||||
h.OnNewResult -= OnNewResult;
|
public override bool Remove(HitObject hitObject) => Columns.ElementAt(((ManiaHitObject)hitObject).Column - firstColumnIndex).Remove(hitObject);
|
||||||
return true;
|
|
||||||
}
|
public override void Add(DrawableHitObject h) => Columns.ElementAt(((ManiaHitObject)h.HitObject).Column - firstColumnIndex).Add(h);
|
||||||
|
|
||||||
|
public override bool Remove(DrawableHitObject h) => Columns.ElementAt(((ManiaHitObject)h.HitObject).Column - firstColumnIndex).Remove(h);
|
||||||
|
|
||||||
public void Add(BarLine barline) => base.Add(new DrawableBarLine(barline));
|
public void Add(BarLine barline) => base.Add(new DrawableBarLine(barline));
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 });
|
hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 });
|
||||||
|
|
||||||
Add(drawableObject = new DrawableHitCircle(hitCircle));
|
Add(drawableObject = new DrawableHitCircle(hitCircle));
|
||||||
AddBlueprint(blueprint = new TestBlueprint(drawableObject));
|
AddBlueprint(blueprint = new TestBlueprint(hitCircle), drawableObject);
|
||||||
});
|
});
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -63,8 +63,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
{
|
{
|
||||||
public new HitCirclePiece CirclePiece => base.CirclePiece;
|
public new HitCirclePiece CirclePiece => base.CirclePiece;
|
||||||
|
|
||||||
public TestBlueprint(DrawableHitCircle drawableCircle)
|
public TestBlueprint(HitCircle circle)
|
||||||
: base(drawableCircle)
|
: base(circle)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 });
|
slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 });
|
||||||
|
|
||||||
Add(drawableObject = new DrawableSlider(slider));
|
Add(drawableObject = new DrawableSlider(slider));
|
||||||
AddBlueprint(new TestSliderBlueprint(drawableObject));
|
AddBlueprint(new TestSliderBlueprint(slider), drawableObject);
|
||||||
});
|
});
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -150,23 +150,23 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
private class TestSliderBlueprint : SliderSelectionBlueprint
|
private class TestSliderBlueprint : SliderSelectionBlueprint
|
||||||
{
|
{
|
||||||
public new SliderBodyPiece BodyPiece => base.BodyPiece;
|
public new SliderBodyPiece BodyPiece => base.BodyPiece;
|
||||||
public new TestSliderCircleBlueprint HeadBlueprint => (TestSliderCircleBlueprint)base.HeadBlueprint;
|
public new TestSliderCircleOverlay HeadOverlay => (TestSliderCircleOverlay)base.HeadOverlay;
|
||||||
public new TestSliderCircleBlueprint TailBlueprint => (TestSliderCircleBlueprint)base.TailBlueprint;
|
public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailOverlay;
|
||||||
public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser;
|
public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser;
|
||||||
|
|
||||||
public TestSliderBlueprint(DrawableSlider slider)
|
public TestSliderBlueprint(Slider slider)
|
||||||
: base(slider)
|
: base(slider)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) => new TestSliderCircleBlueprint(slider, position);
|
protected override SliderCircleOverlay CreateCircleOverlay(Slider slider, SliderPosition position) => new TestSliderCircleOverlay(slider, position);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestSliderCircleBlueprint : SliderCircleSelectionBlueprint
|
private class TestSliderCircleOverlay : SliderCircleOverlay
|
||||||
{
|
{
|
||||||
public new HitCirclePiece CirclePiece => base.CirclePiece;
|
public new HitCirclePiece CirclePiece => base.CirclePiece;
|
||||||
|
|
||||||
public TestSliderCircleBlueprint(DrawableSlider slider, SliderPosition position)
|
public TestSliderCircleOverlay(Slider slider, SliderPosition position)
|
||||||
: base(slider, position)
|
: base(slider, position)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 });
|
slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 });
|
||||||
|
|
||||||
Add(drawableObject = new DrawableSlider(slider));
|
Add(drawableObject = new DrawableSlider(slider));
|
||||||
AddBlueprint(blueprint = new TestSliderBlueprint(drawableObject));
|
AddBlueprint(blueprint = new TestSliderBlueprint(slider), drawableObject);
|
||||||
});
|
});
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -174,10 +174,10 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
AddAssert("body positioned correctly", () => blueprint.BodyPiece.Position == slider.StackedPosition);
|
AddAssert("body positioned correctly", () => blueprint.BodyPiece.Position == slider.StackedPosition);
|
||||||
|
|
||||||
AddAssert("head positioned correctly",
|
AddAssert("head positioned correctly",
|
||||||
() => Precision.AlmostEquals(blueprint.HeadBlueprint.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.HeadCircle.ScreenSpaceDrawQuad.Centre));
|
() => Precision.AlmostEquals(blueprint.HeadOverlay.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.HeadCircle.ScreenSpaceDrawQuad.Centre));
|
||||||
|
|
||||||
AddAssert("tail positioned correctly",
|
AddAssert("tail positioned correctly",
|
||||||
() => Precision.AlmostEquals(blueprint.TailBlueprint.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.TailCircle.ScreenSpaceDrawQuad.Centre));
|
() => Precision.AlmostEquals(blueprint.TailOverlay.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.TailCircle.ScreenSpaceDrawQuad.Centre));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void moveMouseToControlPoint(int index)
|
private void moveMouseToControlPoint(int index)
|
||||||
@ -195,23 +195,23 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
private class TestSliderBlueprint : SliderSelectionBlueprint
|
private class TestSliderBlueprint : SliderSelectionBlueprint
|
||||||
{
|
{
|
||||||
public new SliderBodyPiece BodyPiece => base.BodyPiece;
|
public new SliderBodyPiece BodyPiece => base.BodyPiece;
|
||||||
public new TestSliderCircleBlueprint HeadBlueprint => (TestSliderCircleBlueprint)base.HeadBlueprint;
|
public new TestSliderCircleOverlay HeadOverlay => (TestSliderCircleOverlay)base.HeadOverlay;
|
||||||
public new TestSliderCircleBlueprint TailBlueprint => (TestSliderCircleBlueprint)base.TailBlueprint;
|
public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailOverlay;
|
||||||
public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser;
|
public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser;
|
||||||
|
|
||||||
public TestSliderBlueprint(DrawableSlider slider)
|
public TestSliderBlueprint(Slider slider)
|
||||||
: base(slider)
|
: base(slider)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) => new TestSliderCircleBlueprint(slider, position);
|
protected override SliderCircleOverlay CreateCircleOverlay(Slider slider, SliderPosition position) => new TestSliderCircleOverlay(slider, position);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestSliderCircleBlueprint : SliderCircleSelectionBlueprint
|
private class TestSliderCircleOverlay : SliderCircleOverlay
|
||||||
{
|
{
|
||||||
public new HitCirclePiece CirclePiece => base.CirclePiece;
|
public new HitCirclePiece CirclePiece => base.CirclePiece;
|
||||||
|
|
||||||
public TestSliderCircleBlueprint(DrawableSlider slider, SliderPosition position)
|
public TestSliderCircleOverlay(Slider slider, SliderPosition position)
|
||||||
: base(slider, position)
|
: base(slider, position)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
Child = drawableSpinner = new DrawableSpinner(spinner)
|
Child = drawableSpinner = new DrawableSpinner(spinner)
|
||||||
});
|
});
|
||||||
|
|
||||||
AddBlueprint(new SpinnerSelectionBlueprint(drawableSpinner) { Size = new Vector2(0.5f) });
|
AddBlueprint(new SpinnerSelectionBlueprint(spinner) { Size = new Vector2(0.5f) }, drawableSpinner);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,8 +15,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
|
|||||||
|
|
||||||
protected readonly HitCirclePiece CirclePiece;
|
protected readonly HitCirclePiece CirclePiece;
|
||||||
|
|
||||||
public HitCircleSelectionBlueprint(DrawableHitCircle drawableCircle)
|
public HitCircleSelectionBlueprint(HitCircle circle)
|
||||||
: base(drawableCircle)
|
: base(circle)
|
||||||
{
|
{
|
||||||
InternalChild = CirclePiece = new HitCirclePiece();
|
InternalChild = CirclePiece = new HitCirclePiece();
|
||||||
}
|
}
|
||||||
|
@ -2,20 +2,20 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints
|
namespace osu.Game.Rulesets.Osu.Edit.Blueprints
|
||||||
{
|
{
|
||||||
public abstract class OsuSelectionBlueprint<T> : OverlaySelectionBlueprint
|
public abstract class OsuSelectionBlueprint<T> : HitObjectSelectionBlueprint<T>
|
||||||
where T : OsuHitObject
|
where T : OsuHitObject
|
||||||
{
|
{
|
||||||
protected T HitObject => (T)DrawableObject.HitObject;
|
protected new DrawableOsuHitObject DrawableObject => (DrawableOsuHitObject)base.DrawableObject;
|
||||||
|
|
||||||
protected override bool AlwaysShowWhenSelected => true;
|
protected override bool AlwaysShowWhenSelected => true;
|
||||||
|
|
||||||
protected OsuSelectionBlueprint(DrawableHitObject drawableObject)
|
protected OsuSelectionBlueprint(T hitObject)
|
||||||
: base(drawableObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,36 +1,32 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||||
{
|
{
|
||||||
public class SliderCircleSelectionBlueprint : OsuSelectionBlueprint<Slider>
|
public class SliderCircleOverlay : CompositeDrawable
|
||||||
{
|
{
|
||||||
protected readonly HitCirclePiece CirclePiece;
|
protected readonly HitCirclePiece CirclePiece;
|
||||||
|
|
||||||
|
private readonly Slider slider;
|
||||||
private readonly SliderPosition position;
|
private readonly SliderPosition position;
|
||||||
|
|
||||||
public SliderCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position)
|
public SliderCircleOverlay(Slider slider, SliderPosition position)
|
||||||
: base(slider)
|
|
||||||
{
|
{
|
||||||
|
this.slider = slider;
|
||||||
this.position = position;
|
this.position = position;
|
||||||
|
|
||||||
InternalChild = CirclePiece = new HitCirclePiece();
|
InternalChild = CirclePiece = new HitCirclePiece();
|
||||||
|
|
||||||
Select();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
CirclePiece.UpdateFrom(position == SliderPosition.Start ? (HitCircle)HitObject.HeadCircle : HitObject.TailCircle);
|
CirclePiece.UpdateFrom(position == SliderPosition.Start ? (HitCircle)slider.HeadCircle : slider.TailCircle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Todo: This is temporary, since the slider circle masks don't do anything special yet. In the future they will handle input.
|
|
||||||
public override bool HandlePositionalInput => false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -16,7 +16,6 @@ using osu.Game.Rulesets.Edit;
|
|||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Screens.Edit.Compose;
|
using osu.Game.Screens.Edit.Compose;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -27,14 +26,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
public class SliderSelectionBlueprint : OsuSelectionBlueprint<Slider>
|
public class SliderSelectionBlueprint : OsuSelectionBlueprint<Slider>
|
||||||
{
|
{
|
||||||
protected SliderBodyPiece BodyPiece { get; private set; }
|
protected SliderBodyPiece BodyPiece { get; private set; }
|
||||||
protected SliderCircleSelectionBlueprint HeadBlueprint { get; private set; }
|
protected SliderCircleOverlay HeadOverlay { get; private set; }
|
||||||
protected SliderCircleSelectionBlueprint TailBlueprint { get; private set; }
|
protected SliderCircleOverlay TailOverlay { get; private set; }
|
||||||
|
|
||||||
[CanBeNull]
|
[CanBeNull]
|
||||||
protected PathControlPointVisualiser ControlPointVisualiser { get; private set; }
|
protected PathControlPointVisualiser ControlPointVisualiser { get; private set; }
|
||||||
|
|
||||||
private readonly DrawableSlider slider;
|
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private HitObjectComposer composer { get; set; }
|
private HitObjectComposer composer { get; set; }
|
||||||
|
|
||||||
@ -52,10 +49,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
private readonly BindableList<PathControlPoint> controlPoints = new BindableList<PathControlPoint>();
|
private readonly BindableList<PathControlPoint> controlPoints = new BindableList<PathControlPoint>();
|
||||||
private readonly IBindable<int> pathVersion = new Bindable<int>();
|
private readonly IBindable<int> pathVersion = new Bindable<int>();
|
||||||
|
|
||||||
public SliderSelectionBlueprint(DrawableSlider slider)
|
public SliderSelectionBlueprint(Slider slider)
|
||||||
: base(slider)
|
: base(slider)
|
||||||
{
|
{
|
||||||
this.slider = slider;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -64,8 +60,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
BodyPiece = new SliderBodyPiece(),
|
BodyPiece = new SliderBodyPiece(),
|
||||||
HeadBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.Start),
|
HeadOverlay = CreateCircleOverlay(HitObject, SliderPosition.Start),
|
||||||
TailBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.End),
|
TailOverlay = CreateCircleOverlay(HitObject, SliderPosition.End),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
|
|
||||||
protected override void OnSelected()
|
protected override void OnSelected()
|
||||||
{
|
{
|
||||||
AddInternal(ControlPointVisualiser = new PathControlPointVisualiser(slider.HitObject, true)
|
AddInternal(ControlPointVisualiser = new PathControlPointVisualiser(HitObject, true)
|
||||||
{
|
{
|
||||||
RemoveControlPointsRequested = removeControlPoints
|
RemoveControlPointsRequested = removeControlPoints
|
||||||
});
|
});
|
||||||
@ -215,7 +211,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If there are 0 or 1 remaining control points, the slider is in a degenerate (single point) form and should be deleted
|
// If there are 0 or 1 remaining control points, the slider is in a degenerate (single point) form and should be deleted
|
||||||
if (controlPoints.Count <= 1 || !slider.HitObject.Path.HasValidLength)
|
if (controlPoints.Count <= 1 || !HitObject.Path.HasValidLength)
|
||||||
{
|
{
|
||||||
placementHandler?.Delete(HitObject);
|
placementHandler?.Delete(HitObject);
|
||||||
return;
|
return;
|
||||||
@ -245,6 +241,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) =>
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) =>
|
||||||
BodyPiece.ReceivePositionalInputAt(screenSpacePos) || ControlPointVisualiser?.Pieces.Any(p => p.ReceivePositionalInputAt(screenSpacePos)) == true;
|
BodyPiece.ReceivePositionalInputAt(screenSpacePos) || ControlPointVisualiser?.Pieces.Any(p => p.ReceivePositionalInputAt(screenSpacePos)) == true;
|
||||||
|
|
||||||
protected virtual SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) => new SliderCircleSelectionBlueprint(slider, position);
|
protected virtual SliderCircleOverlay CreateCircleOverlay(Slider slider, SliderPosition position) => new SliderCircleOverlay(slider, position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
|
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
|
||||||
@ -12,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
|
|||||||
{
|
{
|
||||||
private readonly SpinnerPiece piece;
|
private readonly SpinnerPiece piece;
|
||||||
|
|
||||||
public SpinnerSelectionBlueprint(DrawableSpinner spinner)
|
public SpinnerSelectionBlueprint(Spinner spinner)
|
||||||
: base(spinner)
|
: base(spinner)
|
||||||
{
|
{
|
||||||
InternalChild = piece = new SpinnerPiece();
|
InternalChild = piece = new SpinnerPiece();
|
||||||
|
@ -3,11 +3,10 @@
|
|||||||
|
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Screens.Edit.Compose.Components;
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit
|
namespace osu.Game.Rulesets.Osu.Edit
|
||||||
@ -21,21 +20,21 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
protected override SelectionHandler<HitObject> CreateSelectionHandler() => new OsuSelectionHandler();
|
protected override SelectionHandler<HitObject> CreateSelectionHandler() => new OsuSelectionHandler();
|
||||||
|
|
||||||
public override OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject)
|
public override HitObjectSelectionBlueprint CreateHitObjectBlueprintFor(HitObject hitObject)
|
||||||
{
|
{
|
||||||
switch (hitObject)
|
switch (hitObject)
|
||||||
{
|
{
|
||||||
case DrawableHitCircle circle:
|
case HitCircle circle:
|
||||||
return new HitCircleSelectionBlueprint(circle);
|
return new HitCircleSelectionBlueprint(circle);
|
||||||
|
|
||||||
case DrawableSlider slider:
|
case Slider slider:
|
||||||
return new SliderSelectionBlueprint(slider);
|
return new SliderSelectionBlueprint(slider);
|
||||||
|
|
||||||
case DrawableSpinner spinner:
|
case Spinner spinner:
|
||||||
return new SpinnerSelectionBlueprint(spinner);
|
return new SpinnerSelectionBlueprint(spinner);
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.CreateBlueprintFor(hitObject);
|
return base.CreateHitObjectBlueprintFor(hitObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,12 +12,23 @@ using osu.Game.Rulesets.Objects;
|
|||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Screens.Edit.Compose.Components;
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
using osuTK;
|
using Vector2 = osuTK.Vector2;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit
|
namespace osu.Game.Rulesets.Osu.Edit
|
||||||
{
|
{
|
||||||
public class OsuSelectionHandler : EditorSelectionHandler
|
public class OsuSelectionHandler : EditorSelectionHandler
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// During a transform, the initial origin is stored so it can be used throughout the operation.
|
||||||
|
/// </summary>
|
||||||
|
private Vector2? referenceOrigin;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// During a transform, the initial path types of a single selected slider are stored so they
|
||||||
|
/// can be maintained throughout the operation.
|
||||||
|
/// </summary>
|
||||||
|
private List<PathType?> referencePathTypes;
|
||||||
|
|
||||||
protected override void OnSelectionChanged()
|
protected override void OnSelectionChanged()
|
||||||
{
|
{
|
||||||
base.OnSelectionChanged();
|
base.OnSelectionChanged();
|
||||||
@ -50,17 +61,6 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// During a transform, the initial origin is stored so it can be used throughout the operation.
|
|
||||||
/// </summary>
|
|
||||||
private Vector2? referenceOrigin;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// During a transform, the initial path types of a single selected slider are stored so they
|
|
||||||
/// can be maintained throughout the operation.
|
|
||||||
/// </summary>
|
|
||||||
private List<PathType?> referencePathTypes;
|
|
||||||
|
|
||||||
public override bool HandleReverse()
|
public override bool HandleReverse()
|
||||||
{
|
{
|
||||||
var hitObjects = EditorBeatmap.SelectedHitObjects;
|
var hitObjects = EditorBeatmap.SelectedHitObjects;
|
||||||
@ -114,24 +114,10 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
var hitObjects = selectedMovableObjects;
|
var hitObjects = selectedMovableObjects;
|
||||||
|
|
||||||
var selectedObjectsQuad = getSurroundingQuad(hitObjects);
|
var selectedObjectsQuad = getSurroundingQuad(hitObjects);
|
||||||
var centre = selectedObjectsQuad.Centre;
|
|
||||||
|
|
||||||
foreach (var h in hitObjects)
|
foreach (var h in hitObjects)
|
||||||
{
|
{
|
||||||
var pos = h.Position;
|
h.Position = GetFlippedPosition(direction, selectedObjectsQuad, h.Position);
|
||||||
|
|
||||||
switch (direction)
|
|
||||||
{
|
|
||||||
case Direction.Horizontal:
|
|
||||||
pos.X = centre.X - (pos.X - centre.X);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Direction.Vertical:
|
|
||||||
pos.Y = centre.Y - (pos.Y - centre.Y);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Position = pos;
|
|
||||||
|
|
||||||
if (h is Slider slider)
|
if (h is Slider slider)
|
||||||
{
|
{
|
||||||
@ -204,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
referencePathTypes ??= slider.Path.ControlPoints.Select(p => p.Type.Value).ToList();
|
referencePathTypes ??= slider.Path.ControlPoints.Select(p => p.Type.Value).ToList();
|
||||||
|
|
||||||
Quad sliderQuad = getSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position.Value));
|
Quad sliderQuad = GetSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position.Value));
|
||||||
|
|
||||||
// Limit minimum distance between control points after scaling to almost 0. Less than 0 causes the slider to flip, exactly 0 causes a crash through division by 0.
|
// Limit minimum distance between control points after scaling to almost 0. Less than 0 causes the slider to flip, exactly 0 causes a crash through division by 0.
|
||||||
scale = Vector2.ComponentMax(new Vector2(Precision.FLOAT_EPSILON), sliderQuad.Size + scale) - sliderQuad.Size;
|
scale = Vector2.ComponentMax(new Vector2(Precision.FLOAT_EPSILON), sliderQuad.Size + scale) - sliderQuad.Size;
|
||||||
@ -333,7 +319,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="hitObjects">The hit objects to calculate a quad for.</param>
|
/// <param name="hitObjects">The hit objects to calculate a quad for.</param>
|
||||||
private Quad getSurroundingQuad(OsuHitObject[] hitObjects) =>
|
private Quad getSurroundingQuad(OsuHitObject[] hitObjects) =>
|
||||||
getSurroundingQuad(hitObjects.SelectMany(h =>
|
GetSurroundingQuad(hitObjects.SelectMany(h =>
|
||||||
{
|
{
|
||||||
if (h is IHasPath path)
|
if (h is IHasPath path)
|
||||||
{
|
{
|
||||||
@ -348,30 +334,6 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
return new[] { h.Position };
|
return new[] { h.Position };
|
||||||
}));
|
}));
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns a gamefield-space quad surrounding the provided points.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="points">The points to calculate a quad for.</param>
|
|
||||||
private Quad getSurroundingQuad(IEnumerable<Vector2> points)
|
|
||||||
{
|
|
||||||
if (!EditorBeatmap.SelectedHitObjects.Any())
|
|
||||||
return new Quad();
|
|
||||||
|
|
||||||
Vector2 minPosition = new Vector2(float.MaxValue, float.MaxValue);
|
|
||||||
Vector2 maxPosition = new Vector2(float.MinValue, float.MinValue);
|
|
||||||
|
|
||||||
// Go through all hitobjects to make sure they would remain in the bounds of the editor after movement, before any movement is attempted
|
|
||||||
foreach (var p in points)
|
|
||||||
{
|
|
||||||
minPosition = Vector2.ComponentMin(minPosition, p);
|
|
||||||
maxPosition = Vector2.ComponentMax(maxPosition, p);
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector2 size = maxPosition - minPosition;
|
|
||||||
|
|
||||||
return new Quad(minPosition.X, minPosition.Y, size.X, size.Y);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All osu! hitobjects which can be moved/rotated/scaled.
|
/// All osu! hitobjects which can be moved/rotated/scaled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -166,7 +166,7 @@ namespace osu.Game.Rulesets.Osu.Statistics
|
|||||||
|
|
||||||
var point = new HitPoint(pointType, this)
|
var point = new HitPoint(pointType, this)
|
||||||
{
|
{
|
||||||
Colour = pointType == HitPointType.Hit ? new Color4(102, 255, 204, 255) : new Color4(255, 102, 102, 255)
|
BaseColour = pointType == HitPointType.Hit ? new Color4(102, 255, 204, 255) : new Color4(255, 102, 102, 255)
|
||||||
};
|
};
|
||||||
|
|
||||||
points[r][c] = point;
|
points[r][c] = point;
|
||||||
@ -234,6 +234,11 @@ namespace osu.Game.Rulesets.Osu.Statistics
|
|||||||
|
|
||||||
private class HitPoint : Circle
|
private class HitPoint : Circle
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The base colour which will be lightened/darkened depending on the value of this <see cref="HitPoint"/>.
|
||||||
|
/// </summary>
|
||||||
|
public Color4 BaseColour;
|
||||||
|
|
||||||
private readonly HitPointType pointType;
|
private readonly HitPointType pointType;
|
||||||
private readonly AccuracyHeatmap heatmap;
|
private readonly AccuracyHeatmap heatmap;
|
||||||
|
|
||||||
@ -284,7 +289,7 @@ namespace osu.Game.Rulesets.Osu.Statistics
|
|||||||
|
|
||||||
Alpha = Math.Min(amount / lighten_cutoff, 1);
|
Alpha = Math.Min(amount / lighten_cutoff, 1);
|
||||||
if (pointType == HitPointType.Hit)
|
if (pointType == HitPointType.Hit)
|
||||||
Colour = ((Color4)Colour).Lighten(Math.Max(0, amount - lighten_cutoff));
|
Colour = BaseColour.Lighten(Math.Max(0, amount - lighten_cutoff));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
|
|
||||||
[SetUpSteps]
|
[SetUpSteps]
|
||||||
public void SetUp()
|
public void SetUp()
|
||||||
=> AddStep("clear SHOC", () => hitObjectContainer.Clear(false));
|
=> AddStep("clear SHOC", () => hitObjectContainer.Clear());
|
||||||
|
|
||||||
protected void AddHitObject(DrawableHitObject hitObject)
|
protected void AddHitObject(DrawableHitObject hitObject)
|
||||||
=> AddStep("add to SHOC", () => hitObjectContainer.Add(hitObject));
|
=> AddStep("add to SHOC", () => hitObjectContainer.Add(hitObject));
|
||||||
|
@ -3,14 +3,14 @@
|
|||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
||||||
{
|
{
|
||||||
public class TaikoSelectionBlueprint : OverlaySelectionBlueprint
|
public class TaikoSelectionBlueprint : HitObjectSelectionBlueprint
|
||||||
{
|
{
|
||||||
public TaikoSelectionBlueprint(DrawableHitObject hitObject)
|
public TaikoSelectionBlueprint(HitObject hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.None;
|
RelativeSizeAxes = Axes.None;
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
|
||||||
using osu.Game.Rulesets.Taiko.Edit.Blueprints;
|
using osu.Game.Rulesets.Taiko.Edit.Blueprints;
|
||||||
using osu.Game.Screens.Edit.Compose.Components;
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
|
|
||||||
@ -18,7 +17,7 @@ namespace osu.Game.Rulesets.Taiko.Edit
|
|||||||
|
|
||||||
protected override SelectionHandler<HitObject> CreateSelectionHandler() => new TaikoSelectionHandler();
|
protected override SelectionHandler<HitObject> CreateSelectionHandler() => new TaikoSelectionHandler();
|
||||||
|
|
||||||
public override OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) =>
|
public override HitObjectSelectionBlueprint CreateHitObjectBlueprintFor(HitObject hitObject) =>
|
||||||
new TaikoSelectionBlueprint(hitObject);
|
new TaikoSelectionBlueprint(hitObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
175
osu.Game.Tests/Editing/TestSceneHitObjectContainerEventBuffer.cs
Normal file
175
osu.Game.Tests/Editing/TestSceneHitObjectContainerEventBuffer.cs
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Screens.Edit.Compose;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Editing
|
||||||
|
{
|
||||||
|
[HeadlessTest]
|
||||||
|
public class TestSceneHitObjectContainerEventBuffer : OsuTestScene
|
||||||
|
{
|
||||||
|
private readonly TestHitObject testObj = new TestHitObject();
|
||||||
|
|
||||||
|
private TestPlayfield playfield1;
|
||||||
|
private TestPlayfield playfield2;
|
||||||
|
private TestDrawable intermediateDrawable;
|
||||||
|
private HitObjectUsageEventBuffer eventBuffer;
|
||||||
|
|
||||||
|
private HitObject beganUsage;
|
||||||
|
private HitObject finishedUsage;
|
||||||
|
private HitObject transferredUsage;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup() => Schedule(() =>
|
||||||
|
{
|
||||||
|
reset();
|
||||||
|
|
||||||
|
if (eventBuffer != null)
|
||||||
|
{
|
||||||
|
eventBuffer.HitObjectUsageBegan -= onHitObjectUsageBegan;
|
||||||
|
eventBuffer.HitObjectUsageFinished -= onHitObjectUsageFinished;
|
||||||
|
eventBuffer.HitObjectUsageTransferred -= onHitObjectUsageTransferred;
|
||||||
|
}
|
||||||
|
|
||||||
|
var topPlayfield = new TestPlayfield();
|
||||||
|
topPlayfield.AddNested(playfield1 = new TestPlayfield());
|
||||||
|
topPlayfield.AddNested(playfield2 = new TestPlayfield());
|
||||||
|
|
||||||
|
eventBuffer = new HitObjectUsageEventBuffer(topPlayfield);
|
||||||
|
eventBuffer.HitObjectUsageBegan += onHitObjectUsageBegan;
|
||||||
|
eventBuffer.HitObjectUsageFinished += onHitObjectUsageFinished;
|
||||||
|
eventBuffer.HitObjectUsageTransferred += onHitObjectUsageTransferred;
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
topPlayfield,
|
||||||
|
intermediateDrawable = new TestDrawable(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
private void onHitObjectUsageBegan(HitObject obj) => beganUsage = obj;
|
||||||
|
|
||||||
|
private void onHitObjectUsageFinished(HitObject obj) => finishedUsage = obj;
|
||||||
|
|
||||||
|
private void onHitObjectUsageTransferred(HitObject obj, DrawableHitObject drawableObj) => transferredUsage = obj;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestUsageBeganAfterAdd()
|
||||||
|
{
|
||||||
|
AddStep("add hitobject", () => playfield1.Add(testObj));
|
||||||
|
addCheckStep(began: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestUsageFinishedAfterRemove()
|
||||||
|
{
|
||||||
|
AddStep("add hitobject", () => playfield1.Add(testObj));
|
||||||
|
addResetStep();
|
||||||
|
AddStep("remove hitobject", () => playfield1.Remove(testObj));
|
||||||
|
addCheckStep(finished: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestUsageTransferredWhenMovedBetweenPlayfields()
|
||||||
|
{
|
||||||
|
AddStep("add hitobject", () => playfield1.Add(testObj));
|
||||||
|
addResetStep();
|
||||||
|
AddStep("transfer hitobject to other playfield", () =>
|
||||||
|
{
|
||||||
|
playfield1.Remove(testObj);
|
||||||
|
playfield2.Add(testObj);
|
||||||
|
});
|
||||||
|
|
||||||
|
addCheckStep(transferred: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRemoveImmediatelyAfterUsageBegan()
|
||||||
|
{
|
||||||
|
AddStep("add hitobject and schedule removal", () =>
|
||||||
|
{
|
||||||
|
playfield1.Add(testObj);
|
||||||
|
intermediateDrawable.Schedule(() => playfield1.Remove(testObj));
|
||||||
|
});
|
||||||
|
|
||||||
|
addCheckStep(began: true, finished: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRemoveImmediatelyAfterTransferred()
|
||||||
|
{
|
||||||
|
AddStep("add hitobject", () => playfield1.Add(testObj));
|
||||||
|
addResetStep();
|
||||||
|
AddStep("transfer hitobject to other playfield and schedule removal", () =>
|
||||||
|
{
|
||||||
|
playfield1.Remove(testObj);
|
||||||
|
playfield2.Add(testObj);
|
||||||
|
intermediateDrawable.Schedule(() => playfield2.Remove(testObj));
|
||||||
|
});
|
||||||
|
|
||||||
|
addCheckStep(transferred: true, finished: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateAfterChildren()
|
||||||
|
{
|
||||||
|
base.UpdateAfterChildren();
|
||||||
|
eventBuffer.Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addResetStep() => AddStep("reset", reset);
|
||||||
|
|
||||||
|
private void reset()
|
||||||
|
{
|
||||||
|
beganUsage = null;
|
||||||
|
finishedUsage = null;
|
||||||
|
transferredUsage = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addCheckStep(bool began = false, bool finished = false, bool transferred = false)
|
||||||
|
=> AddAssert($"began = {began}, finished = {finished}, transferred = {transferred}",
|
||||||
|
() => (beganUsage == testObj) == began && (finishedUsage == testObj) == finished && (transferredUsage == testObj) == transferred);
|
||||||
|
|
||||||
|
private class TestPlayfield : Playfield
|
||||||
|
{
|
||||||
|
public TestPlayfield()
|
||||||
|
{
|
||||||
|
RegisterPool<TestHitObject, TestDrawableHitObject>(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public new void AddNested(Playfield playfield)
|
||||||
|
{
|
||||||
|
AddInternal(playfield);
|
||||||
|
base.AddNested(playfield);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override HitObjectLifetimeEntry CreateLifetimeEntry(HitObject hitObject)
|
||||||
|
{
|
||||||
|
var entry = base.CreateLifetimeEntry(hitObject);
|
||||||
|
entry.KeepAlive = true;
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestHitObject : HitObject
|
||||||
|
{
|
||||||
|
public override string ToString() => "TestHitObject";
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestDrawableHitObject : DrawableHitObject
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestDrawable : Drawable
|
||||||
|
{
|
||||||
|
public new void Schedule(Action action) => base.Schedule(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -45,15 +45,16 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
AddStep("Create DHO", () =>
|
AddStep("Create DHO", () =>
|
||||||
{
|
{
|
||||||
dho = new TestDrawableHitObject(null);
|
dho = new TestDrawableHitObject(null);
|
||||||
dho.Apply(entry = new TestLifetimeEntry(new HitObject())
|
dho.Apply(entry = new TestLifetimeEntry(new HitObject()));
|
||||||
{
|
|
||||||
LifetimeStart = 0,
|
|
||||||
LifetimeEnd = 1000,
|
|
||||||
});
|
|
||||||
Child = dho;
|
Child = dho;
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("KeepAlive = true", () => entry.KeepAlive = true);
|
AddStep("KeepAlive = true", () =>
|
||||||
|
{
|
||||||
|
entry.LifetimeStart = 0;
|
||||||
|
entry.LifetimeEnd = 1000;
|
||||||
|
entry.KeepAlive = true;
|
||||||
|
});
|
||||||
AddAssert("Lifetime is overriden", () => entry.LifetimeStart == double.MinValue && entry.LifetimeEnd == double.MaxValue);
|
AddAssert("Lifetime is overriden", () => entry.LifetimeStart == double.MinValue && entry.LifetimeEnd == double.MaxValue);
|
||||||
|
|
||||||
AddStep("Set LifetimeStart", () => dho.LifetimeStart = 500);
|
AddStep("Set LifetimeStart", () => dho.LifetimeStart = 500);
|
||||||
|
152
osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs
Normal file
152
osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
// 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 System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.News.Sidebar;
|
||||||
|
using static osu.Game.Overlays.News.Sidebar.YearsPanel;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Online
|
||||||
|
{
|
||||||
|
public class TestSceneNewsSidebar : OsuTestScene
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
||||||
|
|
||||||
|
private TestNewsSidebar sidebar;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp() => Schedule(() => Child = sidebar = new TestNewsSidebar { YearChanged = onYearChanged });
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBasic()
|
||||||
|
{
|
||||||
|
AddStep("Add metadata", () => sidebar.Metadata.Value = getMetadata(2021));
|
||||||
|
AddUntilStep("Month sections exist", () => sidebar.ChildrenOfType<MonthSection>().Any());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMetadataWithNoPosts()
|
||||||
|
{
|
||||||
|
AddStep("Add data with no posts", () => sidebar.Metadata.Value = metadata_with_no_posts);
|
||||||
|
AddUntilStep("No month sections were created", () => !sidebar.ChildrenOfType<MonthSection>().Any());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestYearsPanelVisibility()
|
||||||
|
{
|
||||||
|
AddUntilStep("Years panel is hidden", () => yearsPanel?.Alpha == 0);
|
||||||
|
AddStep("Add data", () => sidebar.Metadata.Value = getMetadata(2021));
|
||||||
|
AddUntilStep("Years panel is visible", () => yearsPanel?.Alpha == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onYearChanged(int year) => sidebar.Metadata.Value = getMetadata(year);
|
||||||
|
|
||||||
|
private YearsPanel yearsPanel => sidebar.ChildrenOfType<YearsPanel>().FirstOrDefault();
|
||||||
|
|
||||||
|
private APINewsSidebar getMetadata(int year) => new APINewsSidebar
|
||||||
|
{
|
||||||
|
CurrentYear = year,
|
||||||
|
Years = new[]
|
||||||
|
{
|
||||||
|
2021,
|
||||||
|
2020,
|
||||||
|
2019,
|
||||||
|
2018,
|
||||||
|
2017,
|
||||||
|
2016,
|
||||||
|
2015,
|
||||||
|
2014,
|
||||||
|
2013
|
||||||
|
},
|
||||||
|
NewsPosts = new List<APINewsPost>
|
||||||
|
{
|
||||||
|
new APINewsPost
|
||||||
|
{
|
||||||
|
Title = "(Mar) Short title",
|
||||||
|
PublishedAt = new DateTime(year, 3, 1)
|
||||||
|
},
|
||||||
|
new APINewsPost
|
||||||
|
{
|
||||||
|
Title = "(Mar) Oh boy that's a long post title I wonder if it will break anything",
|
||||||
|
PublishedAt = new DateTime(year, 3, 1)
|
||||||
|
},
|
||||||
|
new APINewsPost
|
||||||
|
{
|
||||||
|
Title = "(Mar) Medium title, nothing to see here",
|
||||||
|
PublishedAt = new DateTime(year, 3, 1)
|
||||||
|
},
|
||||||
|
new APINewsPost
|
||||||
|
{
|
||||||
|
Title = "(Feb) Short title",
|
||||||
|
PublishedAt = new DateTime(year, 2, 1)
|
||||||
|
},
|
||||||
|
new APINewsPost
|
||||||
|
{
|
||||||
|
Title = "(Feb) Oh boy that's a long post title I wonder if it will break anything",
|
||||||
|
PublishedAt = new DateTime(year, 2, 1)
|
||||||
|
},
|
||||||
|
new APINewsPost
|
||||||
|
{
|
||||||
|
Title = "(Feb) Medium title, nothing to see here",
|
||||||
|
PublishedAt = new DateTime(year, 2, 1)
|
||||||
|
},
|
||||||
|
new APINewsPost
|
||||||
|
{
|
||||||
|
Title = "Short title",
|
||||||
|
PublishedAt = new DateTime(year, 1, 1)
|
||||||
|
},
|
||||||
|
new APINewsPost
|
||||||
|
{
|
||||||
|
Title = "Oh boy that's a long post title I wonder if it will break anything",
|
||||||
|
PublishedAt = new DateTime(year, 1, 1)
|
||||||
|
},
|
||||||
|
new APINewsPost
|
||||||
|
{
|
||||||
|
Title = "Medium title, nothing to see here",
|
||||||
|
PublishedAt = new DateTime(year, 1, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly APINewsSidebar metadata_with_no_posts = new APINewsSidebar
|
||||||
|
{
|
||||||
|
CurrentYear = 2021,
|
||||||
|
Years = new[]
|
||||||
|
{
|
||||||
|
2021,
|
||||||
|
2020,
|
||||||
|
2019,
|
||||||
|
2018,
|
||||||
|
2017,
|
||||||
|
2016,
|
||||||
|
2015,
|
||||||
|
2014,
|
||||||
|
2013
|
||||||
|
},
|
||||||
|
NewsPosts = Array.Empty<APINewsPost>()
|
||||||
|
};
|
||||||
|
|
||||||
|
private class TestNewsSidebar : NewsSidebar
|
||||||
|
{
|
||||||
|
public Action<int> YearChanged;
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
Metadata.BindValueChanged(metadata =>
|
||||||
|
{
|
||||||
|
foreach (var b in this.ChildrenOfType<YearButton>())
|
||||||
|
b.Action = () => YearChanged?.Invoke(b.Year);
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -11,5 +11,8 @@ namespace osu.Game.Online.API.Requests
|
|||||||
{
|
{
|
||||||
[JsonProperty("news_posts")]
|
[JsonProperty("news_posts")]
|
||||||
public IEnumerable<APINewsPost> NewsPosts;
|
public IEnumerable<APINewsPost> NewsPosts;
|
||||||
|
|
||||||
|
[JsonProperty("news_sidebar")]
|
||||||
|
public APINewsSidebar SidebarMetadata;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
20
osu.Game/Online/API/Requests/Responses/APINewsSidebar.cs
Normal file
20
osu.Game/Online/API/Requests/Responses/APINewsSidebar.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// 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 Newtonsoft.Json;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace osu.Game.Online.API.Requests.Responses
|
||||||
|
{
|
||||||
|
public class APINewsSidebar
|
||||||
|
{
|
||||||
|
[JsonProperty("current_year")]
|
||||||
|
public int CurrentYear { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("news_posts")]
|
||||||
|
public IEnumerable<APINewsPost> NewsPosts { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("years")]
|
||||||
|
public int[] Years { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Overlays;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Online.Chat
|
namespace osu.Game.Online.Chat
|
||||||
@ -20,7 +21,10 @@ namespace osu.Game.Online.Chat
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Each word part of a chat link (split for word-wrap support).
|
/// Each word part of a chat link (split for word-wrap support).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<Drawable> Parts;
|
public readonly List<Drawable> Parts;
|
||||||
|
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
private OverlayColourProvider overlayColourProvider { get; set; }
|
||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parts.Any(d => d.ReceivePositionalInputAt(screenSpacePos));
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parts.Any(d => d.ReceivePositionalInputAt(screenSpacePos));
|
||||||
|
|
||||||
@ -34,7 +38,7 @@ namespace osu.Game.Online.Chat
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
IdleColour = colours.Blue;
|
IdleColour = overlayColourProvider?.Light2 ?? colours.Blue;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IEnumerable<Drawable> EffectTargets => Parts;
|
protected override IEnumerable<Drawable> EffectTargets => Parts;
|
||||||
|
@ -92,10 +92,6 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private UserLookupCache userLookupCache { get; set; } = null!;
|
private UserLookupCache userLookupCache { get; set; } = null!;
|
||||||
|
|
||||||
// Only exists for compatibility with old osu-server-spectator build.
|
|
||||||
// Todo: Can be removed on 2021/02/26.
|
|
||||||
private long defaultPlaylistItemId;
|
|
||||||
|
|
||||||
private Room? apiRoom;
|
private Room? apiRoom;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -143,7 +139,6 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
{
|
{
|
||||||
Room = joinedRoom;
|
Room = joinedRoom;
|
||||||
apiRoom = room;
|
apiRoom = room;
|
||||||
defaultPlaylistItemId = apiRoom.Playlist.FirstOrDefault()?.ID ?? 0;
|
|
||||||
foreach (var user in joinedRoom.Users)
|
foreach (var user in joinedRoom.Users)
|
||||||
updateUserPlayingState(user.UserID, user.State);
|
updateUserPlayingState(user.UserID, user.State);
|
||||||
}, cancellationSource.Token).ConfigureAwait(false);
|
}, cancellationSource.Token).ConfigureAwait(false);
|
||||||
@ -581,7 +576,7 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
|
|
||||||
void updateItem(PlaylistItem item)
|
void updateItem(PlaylistItem item)
|
||||||
{
|
{
|
||||||
item.ID = settings.PlaylistItemId == 0 ? defaultPlaylistItemId : settings.PlaylistItemId;
|
item.ID = settings.PlaylistItemId;
|
||||||
item.Beatmap.Value = beatmap;
|
item.Beatmap.Value = beatmap;
|
||||||
item.Ruleset.Value = ruleset.RulesetInfo;
|
item.Ruleset.Value = ruleset.RulesetInfo;
|
||||||
item.RequiredMods.Clear();
|
item.RequiredMods.Clear();
|
||||||
|
179
osu.Game/Overlays/News/Sidebar/MonthSection.cs
Normal file
179
osu.Game/Overlays/News/Sidebar/MonthSection.cs
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
// 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.Containers;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osuTK;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.News.Sidebar
|
||||||
|
{
|
||||||
|
public class MonthSection : CompositeDrawable
|
||||||
|
{
|
||||||
|
private const int animation_duration = 250;
|
||||||
|
|
||||||
|
public readonly BindableBool Expanded = new BindableBool();
|
||||||
|
|
||||||
|
public MonthSection(int month, int year, IEnumerable<APINewsPost> posts)
|
||||||
|
{
|
||||||
|
Debug.Assert(posts.All(p => p.PublishedAt.Month == month && p.PublishedAt.Year == year));
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
Masking = true;
|
||||||
|
|
||||||
|
InternalChild = new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new DropdownHeader(month, year)
|
||||||
|
{
|
||||||
|
Expanded = { BindTarget = Expanded }
|
||||||
|
},
|
||||||
|
new PostsContainer
|
||||||
|
{
|
||||||
|
Expanded = { BindTarget = Expanded },
|
||||||
|
Children = posts.Select(p => new PostButton(p)).ToArray()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DropdownHeader : OsuClickableContainer
|
||||||
|
{
|
||||||
|
public readonly BindableBool Expanded = new BindableBool();
|
||||||
|
|
||||||
|
private readonly SpriteIcon icon;
|
||||||
|
|
||||||
|
public DropdownHeader(int month, int year)
|
||||||
|
{
|
||||||
|
var date = new DateTime(year, month, 1);
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
Height = 15;
|
||||||
|
Action = Expanded.Toggle;
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold),
|
||||||
|
Text = date.ToString("MMM yyyy")
|
||||||
|
},
|
||||||
|
icon = new SpriteIcon
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreRight,
|
||||||
|
Origin = Anchor.CentreRight,
|
||||||
|
Size = new Vector2(10),
|
||||||
|
Icon = FontAwesome.Solid.ChevronDown
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
Expanded.BindValueChanged(open =>
|
||||||
|
{
|
||||||
|
icon.Scale = new Vector2(1, open.NewValue ? -1 : 1);
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PostButton : OsuHoverContainer
|
||||||
|
{
|
||||||
|
protected override IEnumerable<Drawable> EffectTargets => new[] { text };
|
||||||
|
|
||||||
|
private readonly TextFlowContainer text;
|
||||||
|
private readonly APINewsPost post;
|
||||||
|
|
||||||
|
public PostButton(APINewsPost post)
|
||||||
|
{
|
||||||
|
this.post = post;
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
Child = text = new TextFlowContainer(t => t.Font = OsuFont.GetFont(size: 12))
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Text = post.Title
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider overlayColours, GameHost host)
|
||||||
|
{
|
||||||
|
IdleColour = overlayColours.Light2;
|
||||||
|
HoverColour = overlayColours.Light1;
|
||||||
|
|
||||||
|
TooltipText = "view in browser";
|
||||||
|
Action = () => host.OpenUrlExternally("https://osu.ppy.sh/home/news/" + post.Slug);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PostsContainer : Container
|
||||||
|
{
|
||||||
|
public readonly BindableBool Expanded = new BindableBool();
|
||||||
|
|
||||||
|
protected override Container<Drawable> Content { get; }
|
||||||
|
|
||||||
|
public PostsContainer()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
AutoSizeDuration = animation_duration;
|
||||||
|
AutoSizeEasing = Easing.Out;
|
||||||
|
InternalChild = Content = new FillFlowContainer
|
||||||
|
{
|
||||||
|
Margin = new MarginPadding { Top = 5 },
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Spacing = new Vector2(0, 5),
|
||||||
|
Alpha = 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
Expanded.BindValueChanged(updateState, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateState(ValueChangedEvent<bool> expanded)
|
||||||
|
{
|
||||||
|
ClearTransforms(true);
|
||||||
|
|
||||||
|
if (expanded.NewValue)
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
Content.FadeIn(animation_duration, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.None;
|
||||||
|
this.ResizeHeightTo(0, animation_duration, Easing.OutQuint);
|
||||||
|
|
||||||
|
Content.FadeOut(animation_duration, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
103
osu.Game/Overlays/News/Sidebar/NewsSidebar.cs
Normal file
103
osu.Game/Overlays/News/Sidebar/NewsSidebar.cs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
// 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.Bindables;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osuTK;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.News.Sidebar
|
||||||
|
{
|
||||||
|
public class NewsSidebar : CompositeDrawable
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
public readonly Bindable<APINewsSidebar> Metadata = new Bindable<APINewsSidebar>();
|
||||||
|
|
||||||
|
private FillFlowContainer<MonthSection> monthsFlow;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider colourProvider)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Y;
|
||||||
|
Width = 250;
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = colourProvider.Background4
|
||||||
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Padding = new MarginPadding
|
||||||
|
{
|
||||||
|
Vertical = 20,
|
||||||
|
Left = 50,
|
||||||
|
Right = 30
|
||||||
|
},
|
||||||
|
Child = new FillFlowContainer
|
||||||
|
{
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Spacing = new Vector2(0, 20),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new YearsPanel(),
|
||||||
|
monthsFlow = new FillFlowContainer<MonthSection>
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Spacing = new Vector2(0, 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
Metadata.BindValueChanged(onMetadataChanged, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onMetadataChanged(ValueChangedEvent<APINewsSidebar> metadata)
|
||||||
|
{
|
||||||
|
monthsFlow.Clear();
|
||||||
|
|
||||||
|
if (metadata.NewValue == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var allPosts = metadata.NewValue.NewsPosts;
|
||||||
|
|
||||||
|
if (allPosts?.Any() != true)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var lookup = metadata.NewValue.NewsPosts.ToLookup(post => post.PublishedAt.Month);
|
||||||
|
|
||||||
|
var keys = lookup.Select(kvp => kvp.Key);
|
||||||
|
var sortedKeys = keys.OrderByDescending(k => k).ToList();
|
||||||
|
|
||||||
|
var year = metadata.NewValue.CurrentYear;
|
||||||
|
|
||||||
|
for (int i = 0; i < sortedKeys.Count; i++)
|
||||||
|
{
|
||||||
|
var month = sortedKeys[i];
|
||||||
|
var posts = lookup[month];
|
||||||
|
|
||||||
|
monthsFlow.Add(new MonthSection(month, year, posts)
|
||||||
|
{
|
||||||
|
Expanded = { Value = i == 0 }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
113
osu.Game/Overlays/News/Sidebar/YearsPanel.cs
Normal file
113
osu.Game/Overlays/News/Sidebar/YearsPanel.cs
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
// 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.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.News.Sidebar
|
||||||
|
{
|
||||||
|
public class YearsPanel : CompositeDrawable
|
||||||
|
{
|
||||||
|
private readonly Bindable<APINewsSidebar> metadata = new Bindable<APINewsSidebar>();
|
||||||
|
|
||||||
|
private FillFlowContainer yearsFlow;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider overlayColours, Bindable<APINewsSidebar> metadata)
|
||||||
|
{
|
||||||
|
this.metadata.BindTo(metadata);
|
||||||
|
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
Masking = true;
|
||||||
|
CornerRadius = 6;
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = overlayColours.Background3
|
||||||
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Padding = new MarginPadding(5),
|
||||||
|
Child = yearsFlow = new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Spacing = new Vector2(0, 5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
metadata.BindValueChanged(_ => recreateDrawables(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recreateDrawables()
|
||||||
|
{
|
||||||
|
yearsFlow.Clear();
|
||||||
|
|
||||||
|
if (metadata.Value == null)
|
||||||
|
{
|
||||||
|
Hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentYear = metadata.Value.CurrentYear;
|
||||||
|
|
||||||
|
foreach (var y in metadata.Value.Years)
|
||||||
|
yearsFlow.Add(new YearButton(y, y == currentYear));
|
||||||
|
|
||||||
|
Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class YearButton : OsuHoverContainer
|
||||||
|
{
|
||||||
|
public int Year { get; }
|
||||||
|
|
||||||
|
private readonly bool isCurrent;
|
||||||
|
|
||||||
|
public YearButton(int year, bool isCurrent)
|
||||||
|
{
|
||||||
|
Year = year;
|
||||||
|
this.isCurrent = isCurrent;
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
Width = 0.25f;
|
||||||
|
Height = 15;
|
||||||
|
|
||||||
|
Child = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Font = OsuFont.GetFont(size: 12, weight: isCurrent ? FontWeight.SemiBold : FontWeight.Medium),
|
||||||
|
Text = year.ToString()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider colourProvider)
|
||||||
|
{
|
||||||
|
IdleColour = isCurrent ? Color4.White : colourProvider.Light2;
|
||||||
|
HoverColour = isCurrent ? Color4.White : colourProvider.Light1;
|
||||||
|
Action = () => { }; // Avoid button being disabled since there's no proper action assigned.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -9,12 +9,12 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Edit
|
namespace osu.Game.Rulesets.Edit
|
||||||
{
|
{
|
||||||
public abstract class OverlaySelectionBlueprint : SelectionBlueprint<HitObject>
|
public abstract class HitObjectSelectionBlueprint : SelectionBlueprint<HitObject>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The <see cref="DrawableHitObject"/> which this <see cref="OverlaySelectionBlueprint"/> applies to.
|
/// The <see cref="DrawableHitObject"/> which this <see cref="HitObjectSelectionBlueprint"/> applies to.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly DrawableHitObject DrawableObject;
|
public DrawableHitObject DrawableObject { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the blueprint should be shown even when the <see cref="DrawableObject"/> is not alive.
|
/// Whether the blueprint should be shown even when the <see cref="DrawableObject"/> is not alive.
|
||||||
@ -23,10 +23,9 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
|
|
||||||
protected override bool ShouldBeAlive => (DrawableObject.IsAlive && DrawableObject.IsPresent) || (AlwaysShowWhenSelected && State == SelectionState.Selected);
|
protected override bool ShouldBeAlive => (DrawableObject.IsAlive && DrawableObject.IsPresent) || (AlwaysShowWhenSelected && State == SelectionState.Selected);
|
||||||
|
|
||||||
protected OverlaySelectionBlueprint(DrawableHitObject drawableObject)
|
protected HitObjectSelectionBlueprint(HitObject hitObject)
|
||||||
: base(drawableObject.HitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
DrawableObject = drawableObject;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => DrawableObject.ReceivePositionalInputAt(screenSpacePos);
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => DrawableObject.ReceivePositionalInputAt(screenSpacePos);
|
||||||
@ -35,4 +34,15 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
|
|
||||||
public override Quad SelectionQuad => DrawableObject.ScreenSpaceDrawQuad;
|
public override Quad SelectionQuad => DrawableObject.ScreenSpaceDrawQuad;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract class HitObjectSelectionBlueprint<T> : HitObjectSelectionBlueprint
|
||||||
|
where T : HitObject
|
||||||
|
{
|
||||||
|
public T HitObject => (T)Item;
|
||||||
|
|
||||||
|
protected HitObjectSelectionBlueprint(T item)
|
||||||
|
: base(item)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -105,34 +105,34 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
protected override bool ShouldBeConsideredForInput(Drawable child) => State == SelectionState.Selected;
|
protected override bool ShouldBeConsideredForInput(Drawable child) => State == SelectionState.Selected;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Selects this <see cref="OverlaySelectionBlueprint"/>, causing it to become visible.
|
/// Selects this <see cref="SelectionBlueprint{T}"/>, causing it to become visible.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Select() => State = SelectionState.Selected;
|
public void Select() => State = SelectionState.Selected;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deselects this <see cref="OverlaySelectionBlueprint"/>, causing it to become invisible.
|
/// Deselects this <see cref="HitObjectSelectionBlueprint"/>, causing it to become invisible.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Deselect() => State = SelectionState.NotSelected;
|
public void Deselect() => State = SelectionState.NotSelected;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Toggles the selection state of this <see cref="OverlaySelectionBlueprint"/>.
|
/// Toggles the selection state of this <see cref="HitObjectSelectionBlueprint"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void ToggleSelection() => State = IsSelected ? SelectionState.NotSelected : SelectionState.Selected;
|
public void ToggleSelection() => State = IsSelected ? SelectionState.NotSelected : SelectionState.Selected;
|
||||||
|
|
||||||
public bool IsSelected => State == SelectionState.Selected;
|
public bool IsSelected => State == SelectionState.Selected;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The <see cref="MenuItem"/>s to be displayed in the context menu for this <see cref="OverlaySelectionBlueprint"/>.
|
/// The <see cref="MenuItem"/>s to be displayed in the context menu for this <see cref="HitObjectSelectionBlueprint"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual MenuItem[] ContextMenuItems => Array.Empty<MenuItem>();
|
public virtual MenuItem[] ContextMenuItems => Array.Empty<MenuItem>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The screen-space point that causes this <see cref="OverlaySelectionBlueprint"/> to be selected via a drag.
|
/// The screen-space point that causes this <see cref="HitObjectSelectionBlueprint"/> to be selected via a drag.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual Vector2 ScreenSpaceSelectionPoint => ScreenSpaceDrawQuad.Centre;
|
public virtual Vector2 ScreenSpaceSelectionPoint => ScreenSpaceDrawQuad.Centre;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The screen-space quad that outlines this <see cref="OverlaySelectionBlueprint"/> for selections.
|
/// The screen-space quad that outlines this <see cref="HitObjectSelectionBlueprint"/> for selections.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual Quad SelectionQuad => ScreenSpaceDrawQuad;
|
public virtual Quad SelectionQuad => ScreenSpaceDrawQuad;
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using osu.Framework.Graphics.Performance;
|
using osu.Framework.Graphics.Performance;
|
||||||
using osu.Framework.Graphics.Pooling;
|
using osu.Framework.Graphics.Pooling;
|
||||||
@ -18,7 +19,7 @@ namespace osu.Game.Rulesets.Objects.Pooling
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The entry holding essential state of this <see cref="PoolableDrawableWithLifetime{TEntry}"/>.
|
/// The entry holding essential state of this <see cref="PoolableDrawableWithLifetime{TEntry}"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected TEntry? Entry { get; private set; }
|
public TEntry? Entry { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether <see cref="Entry"/> is applied to this <see cref="PoolableDrawableWithLifetime{TEntry}"/>.
|
/// Whether <see cref="Entry"/> is applied to this <see cref="PoolableDrawableWithLifetime{TEntry}"/>.
|
||||||
@ -28,14 +29,28 @@ namespace osu.Game.Rulesets.Objects.Pooling
|
|||||||
|
|
||||||
public override double LifetimeStart
|
public override double LifetimeStart
|
||||||
{
|
{
|
||||||
get => base.LifetimeStart;
|
get => Entry?.LifetimeStart ?? double.MinValue;
|
||||||
set => setLifetime(value, LifetimeEnd);
|
set
|
||||||
|
{
|
||||||
|
if (Entry == null && LifetimeStart != value)
|
||||||
|
throw new InvalidOperationException($"Cannot modify lifetime of {nameof(PoolableDrawableWithLifetime<TEntry>)} when entry is not set");
|
||||||
|
|
||||||
|
if (Entry != null)
|
||||||
|
Entry.LifetimeStart = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override double LifetimeEnd
|
public override double LifetimeEnd
|
||||||
{
|
{
|
||||||
get => base.LifetimeEnd;
|
get => Entry?.LifetimeEnd ?? double.MaxValue;
|
||||||
set => setLifetime(LifetimeStart, value);
|
set
|
||||||
|
{
|
||||||
|
if (Entry == null && LifetimeEnd != value)
|
||||||
|
throw new InvalidOperationException($"Cannot modify lifetime of {nameof(PoolableDrawableWithLifetime<TEntry>)} when entry is not set");
|
||||||
|
|
||||||
|
if (Entry != null)
|
||||||
|
Entry.LifetimeEnd = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool RemoveWhenNotAlive => false;
|
public override bool RemoveWhenNotAlive => false;
|
||||||
@ -64,11 +79,8 @@ namespace osu.Game.Rulesets.Objects.Pooling
|
|||||||
if (HasEntryApplied)
|
if (HasEntryApplied)
|
||||||
free();
|
free();
|
||||||
|
|
||||||
setLifetime(entry.LifetimeStart, entry.LifetimeEnd);
|
|
||||||
Entry = entry;
|
Entry = entry;
|
||||||
|
|
||||||
OnApply(entry);
|
OnApply(entry);
|
||||||
|
|
||||||
HasEntryApplied = true;
|
HasEntryApplied = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,27 +107,12 @@ namespace osu.Game.Rulesets.Objects.Pooling
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setLifetime(double start, double end)
|
|
||||||
{
|
|
||||||
base.LifetimeStart = start;
|
|
||||||
base.LifetimeEnd = end;
|
|
||||||
|
|
||||||
if (Entry != null)
|
|
||||||
{
|
|
||||||
Entry.LifetimeStart = start;
|
|
||||||
Entry.LifetimeEnd = end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void free()
|
private void free()
|
||||||
{
|
{
|
||||||
Debug.Assert(Entry != null && HasEntryApplied);
|
Debug.Assert(Entry != null && HasEntryApplied);
|
||||||
|
|
||||||
OnFree(Entry);
|
OnFree(Entry);
|
||||||
|
|
||||||
Entry = null;
|
Entry = null;
|
||||||
setLifetime(double.MaxValue, double.MaxValue);
|
|
||||||
|
|
||||||
HasEntryApplied = false;
|
HasEntryApplied = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,8 +17,18 @@ using osu.Game.Rulesets.Objects.Drawables;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.UI
|
namespace osu.Game.Rulesets.UI
|
||||||
{
|
{
|
||||||
public class HitObjectContainer : LifetimeManagementContainer, IHitObjectContainer
|
public class HitObjectContainer : CompositeDrawable, IHitObjectContainer
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// All entries in this <see cref="HitObjectContainer"/> including dead entries.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<HitObjectLifetimeEntry> Entries => allEntries;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// All alive entries and <see cref="DrawableHitObject"/>s used by the entries.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<(HitObjectLifetimeEntry Entry, DrawableHitObject Drawable)> AliveEntries => aliveDrawableMap.Select(x => (x.Key, x.Value));
|
||||||
|
|
||||||
public IEnumerable<DrawableHitObject> Objects => InternalChildren.Cast<DrawableHitObject>().OrderBy(h => h.HitObject.StartTime);
|
public IEnumerable<DrawableHitObject> Objects => InternalChildren.Cast<DrawableHitObject>().OrderBy(h => h.HitObject.StartTime);
|
||||||
|
|
||||||
public IEnumerable<DrawableHitObject> AliveObjects => AliveInternalChildren.Cast<DrawableHitObject>().OrderBy(h => h.HitObject.StartTime);
|
public IEnumerable<DrawableHitObject> AliveObjects => AliveInternalChildren.Cast<DrawableHitObject>().OrderBy(h => h.HitObject.StartTime);
|
||||||
@ -60,8 +70,12 @@ namespace osu.Game.Rulesets.UI
|
|||||||
internal double FutureLifetimeExtension { get; set; }
|
internal double FutureLifetimeExtension { get; set; }
|
||||||
|
|
||||||
private readonly Dictionary<DrawableHitObject, IBindable> startTimeMap = new Dictionary<DrawableHitObject, IBindable>();
|
private readonly Dictionary<DrawableHitObject, IBindable> startTimeMap = new Dictionary<DrawableHitObject, IBindable>();
|
||||||
private readonly Dictionary<HitObjectLifetimeEntry, DrawableHitObject> drawableMap = new Dictionary<HitObjectLifetimeEntry, DrawableHitObject>();
|
|
||||||
|
private readonly Dictionary<HitObjectLifetimeEntry, DrawableHitObject> aliveDrawableMap = new Dictionary<HitObjectLifetimeEntry, DrawableHitObject>();
|
||||||
|
private readonly Dictionary<HitObjectLifetimeEntry, DrawableHitObject> nonPooledDrawableMap = new Dictionary<HitObjectLifetimeEntry, DrawableHitObject>();
|
||||||
|
|
||||||
private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager();
|
private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager();
|
||||||
|
private readonly HashSet<HitObjectLifetimeEntry> allEntries = new HashSet<HitObjectLifetimeEntry>();
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private IPooledHitObjectProvider pooledObjectProvider { get; set; }
|
private IPooledHitObjectProvider pooledObjectProvider { get; set; }
|
||||||
@ -72,6 +86,7 @@ namespace osu.Game.Rulesets.UI
|
|||||||
|
|
||||||
lifetimeManager.EntryBecameAlive += entryBecameAlive;
|
lifetimeManager.EntryBecameAlive += entryBecameAlive;
|
||||||
lifetimeManager.EntryBecameDead += entryBecameDead;
|
lifetimeManager.EntryBecameDead += entryBecameDead;
|
||||||
|
lifetimeManager.EntryCrossedBoundary += entryCrossedBoundary;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadAsyncComplete()
|
protected override void LoadAsyncComplete()
|
||||||
@ -84,93 +99,113 @@ namespace osu.Game.Rulesets.UI
|
|||||||
|
|
||||||
#region Pooling support
|
#region Pooling support
|
||||||
|
|
||||||
public void Add(HitObjectLifetimeEntry entry) => lifetimeManager.AddEntry(entry);
|
public void Add(HitObjectLifetimeEntry entry)
|
||||||
|
|
||||||
public bool Remove(HitObjectLifetimeEntry entry) => lifetimeManager.RemoveEntry(entry);
|
|
||||||
|
|
||||||
private void entryBecameAlive(LifetimeEntry entry) => addDrawable((HitObjectLifetimeEntry)entry);
|
|
||||||
|
|
||||||
private void entryBecameDead(LifetimeEntry entry) => removeDrawable((HitObjectLifetimeEntry)entry);
|
|
||||||
|
|
||||||
private void addDrawable(HitObjectLifetimeEntry entry)
|
|
||||||
{
|
{
|
||||||
Debug.Assert(!drawableMap.ContainsKey(entry));
|
allEntries.Add(entry);
|
||||||
|
lifetimeManager.AddEntry(entry);
|
||||||
|
}
|
||||||
|
|
||||||
var drawable = pooledObjectProvider?.GetPooledDrawableRepresentation(entry.HitObject, null);
|
public bool Remove(HitObjectLifetimeEntry entry)
|
||||||
|
{
|
||||||
|
if (!lifetimeManager.RemoveEntry(entry)) return false;
|
||||||
|
|
||||||
|
// This logic is not in `Remove(DrawableHitObject)` because a non-pooled drawable may be removed by specifying its entry.
|
||||||
|
if (nonPooledDrawableMap.Remove(entry, out var drawable))
|
||||||
|
removeDrawable(drawable);
|
||||||
|
|
||||||
|
allEntries.Remove(entry);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void entryBecameAlive(LifetimeEntry lifetimeEntry)
|
||||||
|
{
|
||||||
|
var entry = (HitObjectLifetimeEntry)lifetimeEntry;
|
||||||
|
Debug.Assert(!aliveDrawableMap.ContainsKey(entry));
|
||||||
|
|
||||||
|
bool isNonPooled = nonPooledDrawableMap.TryGetValue(entry, out var drawable);
|
||||||
|
drawable ??= pooledObjectProvider?.GetPooledDrawableRepresentation(entry.HitObject, null);
|
||||||
if (drawable == null)
|
if (drawable == null)
|
||||||
throw new InvalidOperationException($"A drawable representation could not be retrieved for hitobject type: {entry.HitObject.GetType().ReadableName()}.");
|
throw new InvalidOperationException($"A drawable representation could not be retrieved for hitobject type: {entry.HitObject.GetType().ReadableName()}.");
|
||||||
|
|
||||||
|
aliveDrawableMap[entry] = drawable;
|
||||||
|
OnAdd(drawable);
|
||||||
|
|
||||||
|
if (isNonPooled) return;
|
||||||
|
|
||||||
|
addDrawable(drawable);
|
||||||
|
HitObjectUsageBegan?.Invoke(entry.HitObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void entryBecameDead(LifetimeEntry lifetimeEntry)
|
||||||
|
{
|
||||||
|
var entry = (HitObjectLifetimeEntry)lifetimeEntry;
|
||||||
|
Debug.Assert(aliveDrawableMap.ContainsKey(entry));
|
||||||
|
|
||||||
|
var drawable = aliveDrawableMap[entry];
|
||||||
|
bool isNonPooled = nonPooledDrawableMap.ContainsKey(entry);
|
||||||
|
|
||||||
|
drawable.OnKilled();
|
||||||
|
aliveDrawableMap.Remove(entry);
|
||||||
|
OnRemove(drawable);
|
||||||
|
|
||||||
|
if (isNonPooled) return;
|
||||||
|
|
||||||
|
removeDrawable(drawable);
|
||||||
|
// The hit object is not freed when the DHO was not pooled.
|
||||||
|
HitObjectUsageFinished?.Invoke(entry.HitObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addDrawable(DrawableHitObject drawable)
|
||||||
|
{
|
||||||
drawable.OnNewResult += onNewResult;
|
drawable.OnNewResult += onNewResult;
|
||||||
drawable.OnRevertResult += onRevertResult;
|
drawable.OnRevertResult += onRevertResult;
|
||||||
|
|
||||||
bindStartTime(drawable);
|
bindStartTime(drawable);
|
||||||
AddInternal(drawableMap[entry] = drawable, false);
|
AddInternal(drawable);
|
||||||
OnAdd(drawable);
|
|
||||||
|
|
||||||
HitObjectUsageBegan?.Invoke(entry.HitObject);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeDrawable(HitObjectLifetimeEntry entry)
|
private void removeDrawable(DrawableHitObject drawable)
|
||||||
{
|
{
|
||||||
Debug.Assert(drawableMap.ContainsKey(entry));
|
|
||||||
|
|
||||||
var drawable = drawableMap[entry];
|
|
||||||
|
|
||||||
// OnKilled can potentially change the hitobject's result, so it needs to run first before unbinding.
|
|
||||||
drawable.OnKilled();
|
|
||||||
drawable.OnNewResult -= onNewResult;
|
drawable.OnNewResult -= onNewResult;
|
||||||
drawable.OnRevertResult -= onRevertResult;
|
drawable.OnRevertResult -= onRevertResult;
|
||||||
|
|
||||||
drawableMap.Remove(entry);
|
|
||||||
|
|
||||||
OnRemove(drawable);
|
|
||||||
unbindStartTime(drawable);
|
unbindStartTime(drawable);
|
||||||
RemoveInternal(drawable);
|
|
||||||
|
|
||||||
HitObjectUsageFinished?.Invoke(entry.HitObject);
|
RemoveInternal(drawable);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Non-pooling support
|
#region Non-pooling support
|
||||||
|
|
||||||
public virtual void Add(DrawableHitObject hitObject)
|
public virtual void Add(DrawableHitObject drawable)
|
||||||
{
|
{
|
||||||
bindStartTime(hitObject);
|
if (drawable.Entry == null)
|
||||||
|
throw new InvalidOperationException($"May not add a {nameof(DrawableHitObject)} without {nameof(HitObject)} associated");
|
||||||
|
|
||||||
hitObject.OnNewResult += onNewResult;
|
nonPooledDrawableMap.Add(drawable.Entry, drawable);
|
||||||
hitObject.OnRevertResult += onRevertResult;
|
addDrawable(drawable);
|
||||||
|
Add(drawable.Entry);
|
||||||
AddInternal(hitObject);
|
|
||||||
OnAdd(hitObject);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual bool Remove(DrawableHitObject hitObject)
|
public virtual bool Remove(DrawableHitObject drawable)
|
||||||
{
|
{
|
||||||
OnRemove(hitObject);
|
if (drawable.Entry == null)
|
||||||
if (!RemoveInternal(hitObject))
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
hitObject.OnNewResult -= onNewResult;
|
return Remove(drawable.Entry);
|
||||||
hitObject.OnRevertResult -= onRevertResult;
|
|
||||||
|
|
||||||
unbindStartTime(hitObject);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int IndexOf(DrawableHitObject hitObject) => IndexOfInternal(hitObject);
|
public int IndexOf(DrawableHitObject hitObject) => IndexOfInternal(hitObject);
|
||||||
|
|
||||||
protected override void OnChildLifetimeBoundaryCrossed(LifetimeBoundaryCrossedEvent e)
|
private void entryCrossedBoundary(LifetimeEntry entry, LifetimeBoundaryKind kind, LifetimeBoundaryCrossingDirection direction)
|
||||||
{
|
{
|
||||||
if (!(e.Child is DrawableHitObject hitObject))
|
if (nonPooledDrawableMap.TryGetValue((HitObjectLifetimeEntry)entry, out var drawable))
|
||||||
return;
|
OnChildLifetimeBoundaryCrossed(new LifetimeBoundaryCrossedEvent(drawable, kind, direction));
|
||||||
|
}
|
||||||
|
|
||||||
if ((e.Kind == LifetimeBoundaryKind.End && e.Direction == LifetimeBoundaryCrossingDirection.Forward)
|
protected virtual void OnChildLifetimeBoundaryCrossed(LifetimeBoundaryCrossedEvent e)
|
||||||
|| (e.Kind == LifetimeBoundaryKind.Start && e.Direction == LifetimeBoundaryCrossingDirection.Backward))
|
{
|
||||||
{
|
|
||||||
hitObject.OnKilled();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@ -195,12 +230,13 @@ namespace osu.Game.Rulesets.UI
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void Clear(bool disposeChildren = true)
|
public virtual void Clear()
|
||||||
{
|
{
|
||||||
lifetimeManager.ClearEntries();
|
lifetimeManager.ClearEntries();
|
||||||
|
foreach (var drawable in nonPooledDrawableMap.Values)
|
||||||
ClearInternal(disposeChildren);
|
removeDrawable(drawable);
|
||||||
unbindAllStartTimes();
|
nonPooledDrawableMap.Clear();
|
||||||
|
Debug.Assert(InternalChildren.Count == 0 && startTimeMap.Count == 0 && aliveDrawableMap.Count == 0, "All hit objects should have been removed");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool CheckChildrenLife()
|
protected override bool CheckChildrenLife()
|
||||||
|
@ -354,8 +354,11 @@ namespace osu.Game.Rulesets.UI
|
|||||||
|
|
||||||
// If this is the first time this DHO is being used, then apply the DHO mods.
|
// If this is the first time this DHO is being used, then apply the DHO mods.
|
||||||
// This is done before Apply() so that the state is updated once when the hitobject is applied.
|
// This is done before Apply() so that the state is updated once when the hitobject is applied.
|
||||||
foreach (var m in mods.OfType<IApplicableToDrawableHitObjects>())
|
if (mods != null)
|
||||||
m.ApplyToDrawableHitObjects(dho.Yield());
|
{
|
||||||
|
foreach (var m in mods.OfType<IApplicableToDrawableHitObjects>())
|
||||||
|
m.ApplyToDrawableHitObjects(dho.Yield());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!lifetimeEntryMap.TryGetValue(hitObject, out var entry))
|
if (!lifetimeEntryMap.TryGetValue(hitObject, out var entry))
|
||||||
|
@ -50,9 +50,9 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
|||||||
timeRange.ValueChanged += _ => layoutCache.Invalidate();
|
timeRange.ValueChanged += _ => layoutCache.Invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Clear(bool disposeChildren = true)
|
public override void Clear()
|
||||||
{
|
{
|
||||||
base.Clear(disposeChildren);
|
base.Clear();
|
||||||
|
|
||||||
toComputeLifetime.Clear();
|
toComputeLifetime.Clear();
|
||||||
layoutComputed.Clear();
|
layoutComputed.Clear();
|
||||||
|
@ -299,6 +299,13 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves an item's blueprint.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The item to retrieve the blueprint of.</param>
|
||||||
|
/// <returns>The blueprint.</returns>
|
||||||
|
protected SelectionBlueprint<T> GetBlueprintFor(T item) => blueprintMap[item];
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Selection
|
#region Selection
|
||||||
|
@ -74,6 +74,14 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void TransferBlueprintFor(HitObject hitObject, DrawableHitObject drawableObject)
|
||||||
|
{
|
||||||
|
base.TransferBlueprintFor(hitObject, drawableObject);
|
||||||
|
|
||||||
|
var blueprint = (HitObjectSelectionBlueprint)GetBlueprintFor(hitObject);
|
||||||
|
blueprint.DrawableObject = drawableObject;
|
||||||
|
}
|
||||||
|
|
||||||
protected override bool OnKeyDown(KeyDownEvent e)
|
protected override bool OnKeyDown(KeyDownEvent e)
|
||||||
{
|
{
|
||||||
if (e.ControlPressed)
|
if (e.ControlPressed)
|
||||||
@ -246,10 +254,10 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
if (drawable == null)
|
if (drawable == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return CreateBlueprintFor(drawable);
|
return CreateHitObjectBlueprintFor(item).With(b => b.DrawableObject = drawable);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) => null;
|
public virtual HitObjectSelectionBlueprint CreateHitObjectBlueprintFor(HitObject hitObject) => null;
|
||||||
|
|
||||||
protected override void OnBlueprintAdded(HitObject item)
|
protected override void OnBlueprintAdded(HitObject item)
|
||||||
{
|
{
|
||||||
|
@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Compose.Components
|
namespace osu.Game.Screens.Edit.Compose.Components
|
||||||
{
|
{
|
||||||
@ -22,6 +23,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
protected readonly HitObjectComposer Composer;
|
protected readonly HitObjectComposer Composer;
|
||||||
|
|
||||||
|
private HitObjectUsageEventBuffer usageEventBuffer;
|
||||||
|
|
||||||
protected EditorBlueprintContainer(HitObjectComposer composer)
|
protected EditorBlueprintContainer(HitObjectComposer composer)
|
||||||
{
|
{
|
||||||
Composer = composer;
|
Composer = composer;
|
||||||
@ -45,11 +48,19 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
foreach (var obj in Composer.HitObjects)
|
foreach (var obj in Composer.HitObjects)
|
||||||
AddBlueprintFor(obj.HitObject);
|
AddBlueprintFor(obj.HitObject);
|
||||||
|
|
||||||
Composer.Playfield.HitObjectUsageBegan += AddBlueprintFor;
|
usageEventBuffer = new HitObjectUsageEventBuffer(Composer.Playfield);
|
||||||
Composer.Playfield.HitObjectUsageFinished += RemoveBlueprintFor;
|
usageEventBuffer.HitObjectUsageBegan += AddBlueprintFor;
|
||||||
|
usageEventBuffer.HitObjectUsageFinished += RemoveBlueprintFor;
|
||||||
|
usageEventBuffer.HitObjectUsageTransferred += TransferBlueprintFor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
usageEventBuffer?.Update();
|
||||||
|
}
|
||||||
|
|
||||||
protected override IEnumerable<SelectionBlueprint<HitObject>> SortForMovement(IReadOnlyList<SelectionBlueprint<HitObject>> blueprints)
|
protected override IEnumerable<SelectionBlueprint<HitObject>> SortForMovement(IReadOnlyList<SelectionBlueprint<HitObject>> blueprints)
|
||||||
=> blueprints.OrderBy(b => b.Item.StartTime);
|
=> blueprints.OrderBy(b => b.Item.StartTime);
|
||||||
|
|
||||||
@ -80,6 +91,15 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
base.AddBlueprintFor(item);
|
base.AddBlueprintFor(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked when a <see cref="HitObject"/> has been transferred to another <see cref="DrawableHitObject"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hitObject">The hit object which has been assigned to a new drawable.</param>
|
||||||
|
/// <param name="drawableObject">The new drawable that is representing the hit object.</param>
|
||||||
|
protected virtual void TransferBlueprintFor(HitObject hitObject, DrawableHitObject drawableObject)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
protected override void DragOperationCompleted()
|
protected override void DragOperationCompleted()
|
||||||
{
|
{
|
||||||
base.DragOperationCompleted();
|
base.DragOperationCompleted();
|
||||||
@ -133,11 +153,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
Beatmap.HitObjectRemoved -= RemoveBlueprintFor;
|
Beatmap.HitObjectRemoved -= RemoveBlueprintFor;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Composer != null)
|
usageEventBuffer?.Dispose();
|
||||||
{
|
|
||||||
Composer.Playfield.HitObjectUsageBegan -= AddBlueprintFor;
|
|
||||||
Composer.Playfield.HitObjectUsageFinished -= RemoveBlueprintFor;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ using osuTK;
|
|||||||
namespace osu.Game.Screens.Edit.Compose.Components
|
namespace osu.Game.Screens.Edit.Compose.Components
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An event which occurs when a <see cref="OverlaySelectionBlueprint"/> is moved.
|
/// An event which occurs when a <see cref="SelectionBlueprint{T}"/> is moved.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class MoveSelectionEvent<T>
|
public class MoveSelectionEvent<T>
|
||||||
{
|
{
|
||||||
|
@ -350,5 +350,55 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
=> Enumerable.Empty<MenuItem>();
|
=> Enumerable.Empty<MenuItem>();
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Helper Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Given a flip direction, a surrounding quad for all selected objects, and a position,
|
||||||
|
/// will return the flipped position in screen space coordinates.
|
||||||
|
/// </summary>
|
||||||
|
protected static Vector2 GetFlippedPosition(Direction direction, Quad quad, Vector2 position)
|
||||||
|
{
|
||||||
|
var centre = quad.Centre;
|
||||||
|
|
||||||
|
switch (direction)
|
||||||
|
{
|
||||||
|
case Direction.Horizontal:
|
||||||
|
position.X = centre.X - (position.X - centre.X);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Direction.Vertical:
|
||||||
|
position.Y = centre.Y - (position.Y - centre.Y);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a quad surrounding the provided points.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="points">The points to calculate a quad for.</param>
|
||||||
|
protected static Quad GetSurroundingQuad(IEnumerable<Vector2> points)
|
||||||
|
{
|
||||||
|
if (!points.Any())
|
||||||
|
return new Quad();
|
||||||
|
|
||||||
|
Vector2 minPosition = new Vector2(float.MaxValue, float.MaxValue);
|
||||||
|
Vector2 maxPosition = new Vector2(float.MinValue, float.MinValue);
|
||||||
|
|
||||||
|
// Go through all hitobjects to make sure they would remain in the bounds of the editor after movement, before any movement is attempted
|
||||||
|
foreach (var p in points)
|
||||||
|
{
|
||||||
|
minPosition = Vector2.ComponentMin(minPosition, p);
|
||||||
|
maxPosition = Vector2.ComponentMax(maxPosition, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector2 size = maxPosition - minPosition;
|
||||||
|
|
||||||
|
return new Quad(minPosition.X, minPosition.Y, size.X, size.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
83
osu.Game/Screens/Edit/Compose/HitObjectUsageEventBuffer.cs
Normal file
83
osu.Game/Screens/Edit/Compose/HitObjectUsageEventBuffer.cs
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
// 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 System.Linq;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit.Compose
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Buffers events from the many <see cref="HitObjectContainer"/>s in a nested <see cref="Playfield"/> hierarchy
|
||||||
|
/// to ensure correct ordering of events.
|
||||||
|
/// </summary>
|
||||||
|
internal class HitObjectUsageEventBuffer : IDisposable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked when a <see cref="HitObject"/> becomes used by a <see cref="DrawableHitObject"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If the ruleset uses pooled objects, this represents the time when the <see cref="HitObject"/>s become alive.
|
||||||
|
/// </remarks>
|
||||||
|
public event Action<HitObject> HitObjectUsageBegan;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked when a <see cref="HitObject"/> becomes unused by a <see cref="DrawableHitObject"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If the ruleset uses pooled objects, this represents the time when the <see cref="HitObject"/>s become dead.
|
||||||
|
/// </remarks>
|
||||||
|
public event Action<HitObject> HitObjectUsageFinished;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked when a <see cref="HitObject"/> has been transferred to another <see cref="DrawableHitObject"/>.
|
||||||
|
/// </summary>
|
||||||
|
public event Action<HitObject, DrawableHitObject> HitObjectUsageTransferred;
|
||||||
|
|
||||||
|
private readonly Playfield playfield;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="HitObjectUsageEventBuffer"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="playfield">The most top-level <see cref="Playfield"/>.</param>
|
||||||
|
public HitObjectUsageEventBuffer([NotNull] Playfield playfield)
|
||||||
|
{
|
||||||
|
this.playfield = playfield;
|
||||||
|
|
||||||
|
playfield.HitObjectUsageBegan += onHitObjectUsageBegan;
|
||||||
|
playfield.HitObjectUsageFinished += onHitObjectUsageFinished;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly List<HitObject> usageFinishedHitObjects = new List<HitObject>();
|
||||||
|
|
||||||
|
private void onHitObjectUsageBegan(HitObject hitObject)
|
||||||
|
{
|
||||||
|
if (usageFinishedHitObjects.Remove(hitObject))
|
||||||
|
HitObjectUsageTransferred?.Invoke(hitObject, playfield.AllHitObjects.Single(d => d.HitObject == hitObject));
|
||||||
|
else
|
||||||
|
HitObjectUsageBegan?.Invoke(hitObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onHitObjectUsageFinished(HitObject hitObject) => usageFinishedHitObjects.Add(hitObject);
|
||||||
|
|
||||||
|
public void Update()
|
||||||
|
{
|
||||||
|
foreach (var hitObject in usageFinishedHitObjects)
|
||||||
|
HitObjectUsageFinished?.Invoke(hitObject);
|
||||||
|
usageFinishedHitObjects.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (playfield != null)
|
||||||
|
{
|
||||||
|
playfield.HitObjectUsageBegan -= onHitObjectUsageBegan;
|
||||||
|
playfield.HitObjectUsageFinished -= onHitObjectUsageFinished;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -154,6 +154,9 @@ namespace osu.Game.Screens.Play
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
|
if (!LoadedBeatmapSuccessfully)
|
||||||
|
return;
|
||||||
|
|
||||||
// replays should never be recorded or played back when autoplay is enabled
|
// replays should never be recorded or played back when autoplay is enabled
|
||||||
if (!Mods.Value.Any(m => m is ModAutoplay))
|
if (!Mods.Value.Any(m => m is ModAutoplay))
|
||||||
PrepareReplay();
|
PrepareReplay();
|
||||||
@ -926,11 +929,11 @@ namespace osu.Game.Screens.Play
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="score">The <see cref="Score"/> to import.</param>
|
/// <param name="score">The <see cref="Score"/> to import.</param>
|
||||||
/// <returns>The imported score.</returns>
|
/// <returns>The imported score.</returns>
|
||||||
protected virtual Task ImportScore(Score score)
|
protected virtual async Task ImportScore(Score score)
|
||||||
{
|
{
|
||||||
// Replays are already populated and present in the game's database, so should not be re-imported.
|
// Replays are already populated and present in the game's database, so should not be re-imported.
|
||||||
if (DrawableRuleset.ReplayScore != null)
|
if (DrawableRuleset.ReplayScore != null)
|
||||||
return Task.CompletedTask;
|
return;
|
||||||
|
|
||||||
LegacyByteArrayReader replayReader;
|
LegacyByteArrayReader replayReader;
|
||||||
|
|
||||||
@ -940,7 +943,18 @@ namespace osu.Game.Screens.Play
|
|||||||
replayReader = new LegacyByteArrayReader(stream.ToArray(), "replay.osr");
|
replayReader = new LegacyByteArrayReader(stream.ToArray(), "replay.osr");
|
||||||
}
|
}
|
||||||
|
|
||||||
return scoreManager.Import(score.ScoreInfo, replayReader);
|
// For the time being, online ID responses are not really useful for anything.
|
||||||
|
// In addition, the IDs provided via new (lazer) endpoints are based on a different autoincrement from legacy (stable) scores.
|
||||||
|
//
|
||||||
|
// Until we better define the server-side logic behind this, let's not store the online ID to avoid potential unique constraint
|
||||||
|
// conflicts across various systems (ie. solo and multiplayer).
|
||||||
|
long? onlineScoreId = score.ScoreInfo.OnlineScoreID;
|
||||||
|
score.ScoreInfo.OnlineScoreID = null;
|
||||||
|
|
||||||
|
await scoreManager.Import(score.ScoreInfo, replayReader).ConfigureAwait(false);
|
||||||
|
|
||||||
|
// ... And restore the online ID for other processes to handle correctly (e.g. de-duplication for the results screen).
|
||||||
|
score.ScoreInfo.OnlineScoreID = onlineScoreId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -116,12 +116,7 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
request.Success += s =>
|
request.Success += s =>
|
||||||
{
|
{
|
||||||
// For the time being, online ID responses are not really useful for anything.
|
score.ScoreInfo.OnlineScoreID = s.ID;
|
||||||
// In addition, the IDs provided via new (lazer) endpoints are based on a different autoincrement from legacy (stable) scores.
|
|
||||||
//
|
|
||||||
// Until we better define the server-side logic behind this, let's not store the online ID to avoid potential unique constraint
|
|
||||||
// conflicts across various systems (ie. solo and multiplayer).
|
|
||||||
// score.ScoreInfo.OnlineScoreID = s.ID;
|
|
||||||
tcs.SetResult(true);
|
tcs.SetResult(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -124,7 +124,7 @@ namespace osu.Game.Skinning.Editor
|
|||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => drawable.ReceivePositionalInputAt(screenSpacePos);
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => drawable.ReceivePositionalInputAt(screenSpacePos);
|
||||||
|
|
||||||
public override Vector2 ScreenSpaceSelectionPoint => drawable.ScreenSpaceDrawQuad.Centre;
|
public override Vector2 ScreenSpaceSelectionPoint => drawable.ToScreenSpace(drawable.OriginPosition);
|
||||||
|
|
||||||
public override Quad SelectionQuad => drawable.ScreenSpaceDrawQuad;
|
public override Quad SelectionQuad => drawable.ScreenSpaceDrawQuad;
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,10 @@ namespace osu.Game.Skinning.Editor
|
|||||||
Spacing = new Vector2(20)
|
Spacing = new Vector2(20)
|
||||||
};
|
};
|
||||||
|
|
||||||
var skinnableTypes = typeof(OsuGame).Assembly.GetTypes().Where(t => typeof(ISkinnableDrawable).IsAssignableFrom(t)).ToArray();
|
var skinnableTypes = typeof(OsuGame).Assembly.GetTypes()
|
||||||
|
.Where(t => !t.IsInterface)
|
||||||
|
.Where(t => typeof(ISkinnableDrawable).IsAssignableFrom(t))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
foreach (var type in skinnableTypes)
|
foreach (var type in skinnableTypes)
|
||||||
{
|
{
|
||||||
|
@ -43,10 +43,16 @@ namespace osu.Game.Skinning.Editor
|
|||||||
|
|
||||||
public override bool HandleFlip(Direction direction)
|
public override bool HandleFlip(Direction direction)
|
||||||
{
|
{
|
||||||
// TODO: this is temporary as well.
|
var selectionQuad = GetSurroundingQuad(SelectedBlueprints.Select(b => b.ScreenSpaceSelectionPoint));
|
||||||
foreach (var c in SelectedBlueprints)
|
|
||||||
|
foreach (var b in SelectedBlueprints)
|
||||||
{
|
{
|
||||||
((Drawable)c.Item).Scale *= new Vector2(
|
var drawableItem = (Drawable)b.Item;
|
||||||
|
|
||||||
|
drawableItem.Position =
|
||||||
|
drawableItem.Parent.ToLocalSpace(GetFlippedPosition(direction, selectionQuad, b.ScreenSpaceSelectionPoint)) - drawableItem.AnchorPosition;
|
||||||
|
|
||||||
|
drawableItem.Scale *= new Vector2(
|
||||||
direction == Direction.Horizontal ? -1 : 1,
|
direction == Direction.Horizontal ? -1 : 1,
|
||||||
direction == Direction.Vertical ? -1 : 1
|
direction == Direction.Vertical ? -1 : 1
|
||||||
);
|
);
|
||||||
|
@ -5,6 +5,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual
|
namespace osu.Game.Tests.Visual
|
||||||
{
|
{
|
||||||
@ -22,10 +23,11 @@ namespace osu.Game.Tests.Visual
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void AddBlueprint(OverlaySelectionBlueprint blueprint)
|
protected void AddBlueprint(HitObjectSelectionBlueprint blueprint, DrawableHitObject drawableObject)
|
||||||
{
|
{
|
||||||
Add(blueprint.With(d =>
|
Add(blueprint.With(d =>
|
||||||
{
|
{
|
||||||
|
d.DrawableObject = drawableObject;
|
||||||
d.Depth = float.MinValue;
|
d.Depth = float.MinValue;
|
||||||
d.Select();
|
d.Select();
|
||||||
}));
|
}));
|
||||||
|
Loading…
Reference in New Issue
Block a user