1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-29 06:43:02 +08:00

Merge branch 'master' into scoredatabase

This commit is contained in:
Dean Herbert 2018-11-30 14:59:58 +09:00 committed by GitHub
commit 271dcded3c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 787 additions and 124 deletions

View File

@ -0,0 +1,52 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Timing;
using osu.Game.Rulesets.Mania.Edit;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Tests.Visual;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Tests
{
[Cached(Type = typeof(IManiaHitObjectComposer))]
public abstract class ManiaPlacementBlueprintTestCase : PlacementBlueprintTestCase, IManiaHitObjectComposer
{
private readonly Column column;
protected ManiaPlacementBlueprintTestCase()
{
Add(column = new Column(0)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AccentColour = Color4.OrangeRed,
Clock = new FramedClock(new StopwatchClock()), // No scroll
});
}
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
dependencies.CacheAs(((ScrollingTestContainer)HitObjectContainer).ScrollingInfo);
return dependencies;
}
protected override Container CreateHitObjectContainer() => new ScrollingTestContainer(ScrollingDirection.Down) { RelativeSizeAxes = Axes.Both };
protected override void AddHitObject(DrawableHitObject hitObject) => column.Add((DrawableManiaHitObject)hitObject);
public Column ColumnAt(Vector2 screenSpacePosition) => column;
public int TotalColumns => 1;
}
}

View File

@ -2,14 +2,37 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Timing; using osu.Framework.Timing;
using osu.Game.Rulesets.Mania.Edit;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Tests namespace osu.Game.Rulesets.Mania.Tests
{ {
public abstract class ManiaSelectionBlueprintTestCase : SelectionBlueprintTestCase [Cached(Type = typeof(IManiaHitObjectComposer))]
public abstract class ManiaSelectionBlueprintTestCase : SelectionBlueprintTestCase, IManiaHitObjectComposer
{ {
[Cached(Type = typeof(IAdjustableClock))] [Cached(Type = typeof(IAdjustableClock))]
private readonly IAdjustableClock clock = new StopwatchClock(); private readonly IAdjustableClock clock = new StopwatchClock();
private readonly Column column;
protected ManiaSelectionBlueprintTestCase()
{
Add(column = new Column(0)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AccentColour = Color4.OrangeRed,
Clock = new FramedClock(new StopwatchClock()), // No scroll
});
}
public Column ColumnAt(Vector2 screenSpacePosition) => column;
public int TotalColumns => 1;
} }
} }

View File

@ -49,8 +49,8 @@ namespace osu.Game.Rulesets.Mania.Tests
Spacing = new Vector2(20, 0), Spacing = new Vector2(20, 0),
Children = new[] Children = new[]
{ {
createColumn(ScrollingDirection.Up, ManiaAction.Key1), createColumn(ScrollingDirection.Up, ManiaAction.Key1, 0),
createColumn(ScrollingDirection.Down, ManiaAction.Key2) createColumn(ScrollingDirection.Down, ManiaAction.Key2, 1)
} }
}; };
} }
@ -85,9 +85,9 @@ namespace osu.Game.Rulesets.Mania.Tests
} }
} }
private Drawable createColumn(ScrollingDirection direction, ManiaAction action) private Drawable createColumn(ScrollingDirection direction, ManiaAction action, int index)
{ {
var column = new Column var column = new Column(index)
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,

View File

@ -0,0 +1,18 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Edit.Blueprints;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Mania.Tests
{
public class TestCaseHoldNotePlacementBlueprint : ManiaPlacementBlueprintTestCase
{
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableHoldNote((HoldNote)hitObject);
protected override PlacementBlueprint CreateBlueprint() => new HoldNotePlacementBlueprint();
}
}

View File

@ -0,0 +1,18 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Edit.Blueprints;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Mania.Tests
{
public class TestCaseNotePlacementBlueprint : ManiaPlacementBlueprintTestCase
{
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableNote((Note)hitObject);
protected override PlacementBlueprint CreateBlueprint() => new NotePlacementBlueprint();
}
}

View File

@ -0,0 +1,21 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components
{
public class EditBodyPiece : BodyPiece
{
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
AccentColour = colours.Yellow;
Background.Alpha = 0.5f;
Foreground.Alpha = 0;
}
}
}

View File

@ -0,0 +1,74 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
using osu.Game.Rulesets.Mania.Objects;
using osuTK;
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{
public class HoldNotePlacementBlueprint : ManiaPlacementBlueprint<HoldNote>
{
private readonly EditBodyPiece bodyPiece;
private readonly EditNotePiece headPiece;
private readonly EditNotePiece tailPiece;
public HoldNotePlacementBlueprint()
: base(new HoldNote())
{
RelativeSizeAxes = Axes.Both;
InternalChildren = new Drawable[]
{
bodyPiece = new EditBodyPiece { Origin = Anchor.TopCentre },
headPiece = new EditNotePiece { Origin = Anchor.Centre },
tailPiece = new EditNotePiece { Origin = Anchor.Centre }
};
}
protected override void Update()
{
base.Update();
if (Column != null)
{
headPiece.Y = PositionAt(HitObject.StartTime);
tailPiece.Y = PositionAt(HitObject.EndTime);
}
var topPosition = new Vector2(headPiece.DrawPosition.X, Math.Min(headPiece.DrawPosition.Y, tailPiece.DrawPosition.Y));
var bottomPosition = new Vector2(headPiece.DrawPosition.X, Math.Max(headPiece.DrawPosition.Y, tailPiece.DrawPosition.Y));
bodyPiece.Position = topPosition;
bodyPiece.Width = headPiece.Width;
bodyPiece.Height = (bottomPosition - topPosition).Y;
}
private double originalStartTime;
protected override bool OnMouseMove(MouseMoveEvent e)
{
base.OnMouseMove(e);
if (PlacementBegun)
{
var endTime = TimeAt(e.ScreenSpaceMousePosition);
HitObject.StartTime = endTime < originalStartTime ? endTime : originalStartTime;
HitObject.Duration = Math.Abs(endTime - originalStartTime);
}
else
{
headPiece.Width = tailPiece.Width = SnappedWidth;
headPiece.X = tailPiece.X = SnappedMousePosition.X;
originalStartTime = HitObject.StartTime = TimeAt(e.ScreenSpaceMousePosition);
}
return true;
}
}
}

View File

@ -0,0 +1,118 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Input;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{
public abstract class ManiaPlacementBlueprint<T> : PlacementBlueprint,
IRequireHighFrequencyMousePosition // the playfield could be moving behind us
where T : ManiaHitObject
{
protected new T HitObject => (T)base.HitObject;
protected Column Column;
/// <summary>
/// The current mouse position, snapped to the closest column.
/// </summary>
protected Vector2 SnappedMousePosition { get; private set; }
/// <summary>
/// The width of the closest column to the current mouse position.
/// </summary>
protected float SnappedWidth { get; private set; }
[Resolved]
private IManiaHitObjectComposer composer { get; set; }
[Resolved]
private IScrollingInfo scrollingInfo { get; set; }
protected ManiaPlacementBlueprint(T hitObject)
: base(hitObject)
{
RelativeSizeAxes = Axes.None;
}
protected override bool OnMouseDown(MouseDownEvent e)
{
if (Column == null)
return base.OnMouseDown(e);
HitObject.StartTime = TimeAt(e.ScreenSpaceMousePosition);
HitObject.Column = Column.Index;
BeginPlacement();
return true;
}
protected override bool OnMouseUp(MouseUpEvent e)
{
EndPlacement();
return base.OnMouseUp(e);
}
protected override bool OnMouseMove(MouseMoveEvent e)
{
if (!PlacementBegun)
Column = ColumnAt(e.ScreenSpaceMousePosition);
if (Column == null) return false;
SnappedWidth = Column.DrawWidth;
// Snap to the column
var parentPos = Parent.ToLocalSpace(Column.ToScreenSpace(new Vector2(Column.DrawWidth / 2, 0)));
SnappedMousePosition = new Vector2(parentPos.X, e.MousePosition.Y);
return true;
}
protected double TimeAt(Vector2 screenSpacePosition)
{
if (Column == null)
return 0;
var hitObjectContainer = Column.HitObjectContainer;
// If we're scrolling downwards, a position of 0 is actually further away from the hit target
// so we need to flip the vertical coordinate in the hitobject container's space
var hitObjectPos = Column.HitObjectContainer.ToLocalSpace(applyPositionOffset(screenSpacePosition, false)).Y;
if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
hitObjectPos = hitObjectContainer.DrawHeight - hitObjectPos;
return scrollingInfo.Algorithm.TimeAt(hitObjectPos,
EditorClock.CurrentTime,
scrollingInfo.TimeRange.Value,
hitObjectContainer.DrawHeight);
}
protected float PositionAt(double time)
{
var pos = scrollingInfo.Algorithm.PositionAt(time,
EditorClock.CurrentTime,
scrollingInfo.TimeRange.Value,
Column.HitObjectContainer.DrawHeight);
return applyPositionOffset(Column.HitObjectContainer.ToSpaceOfOtherDrawable(new Vector2(0, pos), Parent), true).Y;
}
protected Column ColumnAt(Vector2 screenSpacePosition)
=> composer.ColumnAt(applyPositionOffset(screenSpacePosition, false));
private Vector2 applyPositionOffset(Vector2 position, bool reverse)
{
position.Y += (scrollingInfo.Direction.Value == ScrollingDirection.Up && !reverse ? -1 : 1) * NotePiece.NOTE_HEIGHT / 2;
return position;
}
}
}

View File

@ -15,6 +15,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{ {
public class ManiaSelectionBlueprint : SelectionBlueprint public class ManiaSelectionBlueprint : SelectionBlueprint
{ {
public Vector2 ScreenSpaceDragPosition { get; private set; }
public Vector2 DragPosition { get; private set; }
protected new DrawableManiaHitObject HitObject => (DrawableManiaHitObject)base.HitObject; protected new DrawableManiaHitObject HitObject => (DrawableManiaHitObject)base.HitObject;
protected IClock EditorClock { get; private set; } protected IClock EditorClock { get; private set; }
@ -22,6 +25,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
[Resolved] [Resolved]
private IScrollingInfo scrollingInfo { get; set; } private IScrollingInfo scrollingInfo { get; set; }
[Resolved]
private IManiaHitObjectComposer composer { get; set; }
public ManiaSelectionBlueprint(DrawableHitObject hitObject) public ManiaSelectionBlueprint(DrawableHitObject hitObject)
: base(hitObject) : base(hitObject)
{ {
@ -41,27 +47,22 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
Position = Parent.ToLocalSpace(HitObject.ToScreenSpace(Vector2.Zero)); Position = Parent.ToLocalSpace(HitObject.ToScreenSpace(Vector2.Zero));
} }
public override void AdjustPosition(DragEvent dragEvent) protected override bool OnMouseDown(MouseDownEvent e)
{ {
var objectParent = HitObject.Parent; ScreenSpaceDragPosition = e.ScreenSpaceMousePosition;
DragPosition = HitObject.ToLocalSpace(e.ScreenSpaceMousePosition);
// Using the hitobject position is required since AdjustPosition can be invoked multiple times per frame return base.OnMouseDown(e);
// without the position having been updated by the parenting ScrollingHitObjectContainer }
HitObject.Y += dragEvent.Delta.Y;
float targetPosition; protected override bool OnDrag(DragEvent e)
{
var result = base.OnDrag(e);
// If we're scrolling downwards, a position of 0 is actually further away from the hit target ScreenSpaceDragPosition = e.ScreenSpaceMousePosition;
// so we need to flip the vertical coordinate in the hitobject container's space DragPosition = HitObject.ToLocalSpace(e.ScreenSpaceMousePosition);
if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
targetPosition = -HitObject.Position.Y;
else
targetPosition = HitObject.Position.Y;
HitObject.HitObject.StartTime = scrollingInfo.Algorithm.TimeAt(targetPosition, return result;
EditorClock.CurrentTime,
scrollingInfo.TimeRange.Value,
objectParent.DrawHeight);
} }
public override void Show() public override void Show()

View File

@ -0,0 +1,30 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
using osu.Game.Rulesets.Mania.Objects;
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{
public class NotePlacementBlueprint : ManiaPlacementBlueprint<Note>
{
public NotePlacementBlueprint()
: base(new Note())
{
Origin = Anchor.Centre;
AutoSizeAxes = Axes.Y;
InternalChild = new EditNotePiece { RelativeSizeAxes = Axes.X };
}
protected override void Update()
{
base.Update();
Width = SnappedWidth;
Position = SnappedMousePosition;
}
}
}

View File

@ -0,0 +1,19 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Mania.Edit.Blueprints;
namespace osu.Game.Rulesets.Mania.Edit
{
public class HoldNoteCompositionTool : HitObjectCompositionTool
{
public HoldNoteCompositionTool()
: base("Hold")
{
}
public override PlacementBlueprint CreatePlacementBlueprint() => new HoldNotePlacementBlueprint();
}
}

View File

@ -0,0 +1,15 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Mania.UI;
using osuTK;
namespace osu.Game.Rulesets.Mania.Edit
{
public interface IManiaHitObjectComposer
{
Column ColumnAt(Vector2 screenSpacePosition);
int TotalColumns { get; }
}
}

View File

@ -1,7 +1,6 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Edit.Tools;
@ -11,33 +10,54 @@ using osu.Game.Rulesets.Objects.Drawables;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Game.Rulesets.Mania.Edit.Blueprints; using osu.Game.Rulesets.Mania.Edit.Blueprints;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
namespace osu.Game.Rulesets.Mania.Edit namespace osu.Game.Rulesets.Mania.Edit
{ {
public class ManiaHitObjectComposer : HitObjectComposer<ManiaHitObject> [Cached(Type = typeof(IManiaHitObjectComposer))]
public class ManiaHitObjectComposer : HitObjectComposer<ManiaHitObject>, IManiaHitObjectComposer
{ {
protected new ManiaEditRulesetContainer RulesetContainer { get; private set; }
public ManiaHitObjectComposer(Ruleset ruleset) public ManiaHitObjectComposer(Ruleset ruleset)
: base(ruleset) : base(ruleset)
{ {
} }
/// <summary>
/// Retrieves the column that intersects a screen-space position.
/// </summary>
/// <param name="screenSpacePosition">The screen-space position.</param>
/// <returns>The column which intersects with <paramref name="screenSpacePosition"/>.</returns>
public Column ColumnAt(Vector2 screenSpacePosition) => RulesetContainer.GetColumnByPosition(screenSpacePosition);
private DependencyContainer dependencies; private DependencyContainer dependencies;
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
=> dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); => dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
public int TotalColumns => ((ManiaPlayfield)RulesetContainer.Playfield).TotalColumns;
protected override RulesetContainer<ManiaHitObject> CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) protected override RulesetContainer<ManiaHitObject> CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
{ {
var rulesetContainer = new ManiaEditRulesetContainer(ruleset, beatmap); RulesetContainer = new ManiaEditRulesetContainer(ruleset, beatmap);
// This is the earliest we can cache the scrolling info to ourselves, before masks are added to the hierarchy and inject it // This is the earliest we can cache the scrolling info to ourselves, before masks are added to the hierarchy and inject it
dependencies.CacheAs(rulesetContainer.ScrollingInfo); dependencies.CacheAs(RulesetContainer.ScrollingInfo);
return rulesetContainer; return RulesetContainer;
} }
protected override IReadOnlyList<HitObjectCompositionTool> CompositionTools => Array.Empty<HitObjectCompositionTool>(); protected override IReadOnlyList<HitObjectCompositionTool> CompositionTools => new HitObjectCompositionTool[]
{
new NoteCompositionTool(),
new HoldNoteCompositionTool()
};
public override SelectionHandler CreateSelectionHandler() => new ManiaSelectionHandler();
public override SelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) public override SelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject)
{ {

View File

@ -0,0 +1,125 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Input.Events;
using osu.Framework.Timing;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Edit.Blueprints;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
namespace osu.Game.Rulesets.Mania.Edit
{
public class ManiaSelectionHandler : SelectionHandler
{
[Resolved]
private IScrollingInfo scrollingInfo { get; set; }
[Resolved]
private IManiaHitObjectComposer composer { get; set; }
private IClock editorClock;
[BackgroundDependencyLoader]
private void load(IAdjustableClock clock)
{
editorClock = clock;
}
public override void HandleDrag(SelectionBlueprint blueprint, DragEvent dragEvent)
{
adjustOrigins((ManiaSelectionBlueprint)blueprint);
performDragMovement(dragEvent);
performColumnMovement(dragEvent);
base.HandleDrag(blueprint, dragEvent);
}
/// <summary>
/// Ensures that the position of hitobjects remains centred to the mouse position.
/// E.g. The hitobject position will change if the editor scrolls while a hitobject is dragged.
/// </summary>
/// <param name="reference">The <see cref="ManiaSelectionBlueprint"/> that received the drag event.</param>
private void adjustOrigins(ManiaSelectionBlueprint reference)
{
var referenceParent = (HitObjectContainer)reference.HitObject.Parent;
float offsetFromReferenceOrigin = reference.DragPosition.Y - reference.HitObject.OriginPosition.Y;
float targetPosition = referenceParent.ToLocalSpace(reference.ScreenSpaceDragPosition).Y - offsetFromReferenceOrigin;
// Flip the vertical coordinate space when scrolling downwards
if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
targetPosition = targetPosition - referenceParent.DrawHeight;
float movementDelta = targetPosition - reference.HitObject.Position.Y;
foreach (var b in SelectedBlueprints.OfType<ManiaSelectionBlueprint>())
b.HitObject.Y += movementDelta;
}
private void performDragMovement(DragEvent dragEvent)
{
foreach (var b in SelectedBlueprints)
{
var hitObject = b.HitObject;
var objectParent = (HitObjectContainer)hitObject.Parent;
// Using the hitobject position is required since AdjustPosition can be invoked multiple times per frame
// without the position having been updated by the parenting ScrollingHitObjectContainer
hitObject.Y += dragEvent.Delta.Y;
float targetPosition;
// If we're scrolling downwards, a position of 0 is actually further away from the hit target
// so we need to flip the vertical coordinate in the hitobject container's space
if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
targetPosition = -hitObject.Position.Y;
else
targetPosition = hitObject.Position.Y;
objectParent.Remove(hitObject);
hitObject.HitObject.StartTime = scrollingInfo.Algorithm.TimeAt(targetPosition,
editorClock.CurrentTime,
scrollingInfo.TimeRange.Value,
objectParent.DrawHeight);
objectParent.Add(hitObject);
}
}
private void performColumnMovement(DragEvent dragEvent)
{
var lastColumn = composer.ColumnAt(dragEvent.ScreenSpaceLastMousePosition);
var currentColumn = composer.ColumnAt(dragEvent.ScreenSpaceMousePosition);
if (lastColumn == null || currentColumn == null)
return;
int columnDelta = currentColumn.Index - lastColumn.Index;
if (columnDelta == 0)
return;
int minColumn = int.MaxValue;
int maxColumn = int.MinValue;
foreach (var obj in SelectedHitObjects.OfType<ManiaHitObject>())
{
if (obj.Column < minColumn)
minColumn = obj.Column;
if (obj.Column > maxColumn)
maxColumn = obj.Column;
}
columnDelta = MathHelper.Clamp(columnDelta, -minColumn, composer.TotalColumns - 1 - maxColumn);
foreach (var obj in SelectedHitObjects.OfType<ManiaHitObject>())
obj.Column += columnDelta;
}
}
}

View File

@ -0,0 +1,20 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Mania.Edit.Blueprints;
using osu.Game.Rulesets.Mania.Objects;
namespace osu.Game.Rulesets.Mania.Edit
{
public class NoteCompositionTool : HitObjectCompositionTool
{
public NoteCompositionTool()
: base(nameof(Note))
{
}
public override PlacementBlueprint CreatePlacementBlueprint() => new NotePlacementBlueprint();
}
}

View File

@ -15,12 +15,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
/// <summary> /// <summary>
/// Represents length-wise portion of a hold note. /// Represents length-wise portion of a hold note.
/// </summary> /// </summary>
internal class BodyPiece : Container, IHasAccentColour public class BodyPiece : Container, IHasAccentColour
{ {
private readonly Container subtractionLayer; private readonly Container subtractionLayer;
private readonly Drawable background; protected readonly Drawable Background;
private readonly BufferedContainer foreground; protected readonly BufferedContainer Foreground;
private readonly BufferedContainer subtractionContainer; private readonly BufferedContainer subtractionContainer;
public BodyPiece() public BodyPiece()
@ -29,8 +29,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
Children = new[] Children = new[]
{ {
background = new Box { RelativeSizeAxes = Axes.Both }, Background = new Box { RelativeSizeAxes = Axes.Both },
foreground = new BufferedContainer Foreground = new BufferedContainer
{ {
Blending = BlendingMode.Additive, Blending = BlendingMode.Additive,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
@ -123,7 +123,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
Radius = DrawWidth Radius = DrawWidth
}; };
foreground.ForceRedraw(); Foreground.ForceRedraw();
subtractionContainer.ForceRedraw(); subtractionContainer.ForceRedraw();
subtractionCache.Validate(); subtractionCache.Validate();
@ -137,18 +137,18 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
if (!IsLoaded) if (!IsLoaded)
return; return;
foreground.Colour = AccentColour.Opacity(0.5f); Foreground.Colour = AccentColour.Opacity(0.5f);
background.Colour = AccentColour.Opacity(0.7f); Background.Colour = AccentColour.Opacity(0.7f);
const float animation_length = 50; const float animation_length = 50;
foreground.ClearTransforms(false, nameof(foreground.Colour)); Foreground.ClearTransforms(false, nameof(Foreground.Colour));
if (hitting) if (hitting)
{ {
// wait for the next sync point // wait for the next sync point
double synchronisedOffset = animation_length * 2 - Time.Current % (animation_length * 2); double synchronisedOffset = animation_length * 2 - Time.Current % (animation_length * 2);
using (foreground.BeginDelayedSequence(synchronisedOffset)) using (Foreground.BeginDelayedSequence(synchronisedOffset))
foreground.FadeColour(AccentColour.Lighten(0.2f), animation_length).Then().FadeColour(foreground.Colour, animation_length).Loop(); Foreground.FadeColour(AccentColour.Lighten(0.2f), animation_length).Then().FadeColour(Foreground.Colour, animation_length).Loop();
} }
subtractionCache.Invalidate(); subtractionCache.Invalidate();

View File

@ -1,6 +1,7 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Configuration;
using osu.Game.Rulesets.Mania.Objects.Types; using osu.Game.Rulesets.Mania.Objects.Types;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
@ -8,7 +9,13 @@ namespace osu.Game.Rulesets.Mania.Objects
{ {
public abstract class ManiaHitObject : HitObject, IHasColumn public abstract class ManiaHitObject : HitObject, IHasColumn
{ {
public virtual int Column { get; set; } public readonly Bindable<int> ColumnBindable = new Bindable<int>();
public virtual int Column
{
get => ColumnBindable;
set => ColumnBindable.Value = value;
}
protected override HitWindows CreateHitWindows() => new ManiaHitWindows(); protected override HitWindows CreateHitWindows() => new ManiaHitWindows();
} }

View File

@ -13,6 +13,7 @@ using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.UI.Components; using osu.Game.Rulesets.Mania.UI.Components;
using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
namespace osu.Game.Rulesets.Mania.UI namespace osu.Game.Rulesets.Mania.UI
{ {
@ -21,6 +22,11 @@ namespace osu.Game.Rulesets.Mania.UI
private const float column_width = 45; private const float column_width = 45;
private const float special_column_width = 70; private const float special_column_width = 70;
/// <summary>
/// The index of this column as part of the whole playfield.
/// </summary>
public readonly int Index;
public readonly Bindable<ManiaAction> Action = new Bindable<ManiaAction>(); public readonly Bindable<ManiaAction> Action = new Bindable<ManiaAction>();
private readonly ColumnBackground background; private readonly ColumnBackground background;
@ -30,8 +36,10 @@ namespace osu.Game.Rulesets.Mania.UI
internal readonly Container TopLevelContainer; internal readonly Container TopLevelContainer;
private readonly Container explosionContainer; private readonly Container explosionContainer;
public Column() public Column(int index)
{ {
Index = index;
RelativeSizeAxes = Axes.Y; RelativeSizeAxes = Axes.Y;
Width = column_width; Width = column_width;
@ -137,11 +145,13 @@ namespace osu.Game.Rulesets.Mania.UI
HitObjectContainer.Add(hitObject); HitObjectContainer.Add(hitObject);
} }
public override void Remove(DrawableHitObject h) public override bool Remove(DrawableHitObject h)
{ {
h.OnNewResult -= OnNewResult; if (!base.Remove(h))
return false;
HitObjectContainer.Remove(h); h.OnNewResult -= OnNewResult;
return true;
} }
internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result) internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result)
@ -172,5 +182,9 @@ namespace osu.Game.Rulesets.Mania.UI
} }
public bool OnReleased(ManiaAction action) => false; public bool OnReleased(ManiaAction action) => false;
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
// This probably shouldn't exist as is, but the columns in the stage are separated by a 1px border
=> DrawRectangle.Inflate(new Vector2(ManiaStage.COLUMN_SPACING / 2, 0)).Contains(ToLocalSpace(screenSpacePos));
} }
} }

View File

@ -5,6 +5,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
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.Drawables; using osu.Game.Rulesets.Objects.Drawables;
@ -17,6 +18,8 @@ namespace osu.Game.Rulesets.Mania.UI
{ {
private readonly List<ManiaStage> stages = new List<ManiaStage>(); private readonly List<ManiaStage> stages = new List<ManiaStage>();
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => stages.Any(s => s.ReceivePositionalInputAt(screenSpacePos));
public ManiaPlayfield(List<StageDefinition> stageDefinitions) public ManiaPlayfield(List<StageDefinition> stageDefinitions)
{ {
if (stageDefinitions == null) if (stageDefinitions == null)
@ -52,10 +55,42 @@ namespace osu.Game.Rulesets.Mania.UI
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 void Remove(DrawableHitObject h) => getStageByColumn(((ManiaHitObject)h.HitObject).Column).Remove(h); public override bool Remove(DrawableHitObject h) => getStageByColumn(((ManiaHitObject)h.HitObject).Column).Remove(h);
public void Add(BarLine barline) => stages.ForEach(s => s.Add(barline)); public void Add(BarLine barline) => stages.ForEach(s => s.Add(barline));
/// <summary>
/// Retrieves a column from a screen-space position.
/// </summary>
/// <param name="screenSpacePosition">The screen-space position.</param>
/// <returns>The column which the <paramref name="screenSpacePosition"/> lies in.</returns>
public Column GetColumnByPosition(Vector2 screenSpacePosition)
{
Column found = null;
foreach (var stage in stages)
{
foreach (var column in stage.Columns)
{
if (column.ReceivePositionalInputAt(screenSpacePosition))
{
found = column;
break;
}
}
if (found != null)
break;
}
return found;
}
/// <summary>
/// Retrieves the total amount of columns across all stages in this playfield.
/// </summary>
public int TotalColumns => stages.Sum(s => s.Columns.Count);
private ManiaStage getStageByColumn(int column) private ManiaStage getStageByColumn(int column)
{ {
int sum = 0; int sum = 0;

View File

@ -25,6 +25,7 @@ using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
namespace osu.Game.Rulesets.Mania.UI namespace osu.Game.Rulesets.Mania.UI
{ {
@ -80,6 +81,13 @@ namespace osu.Game.Rulesets.Mania.UI
Config.BindWith(ManiaSetting.ScrollTime, TimeRange); Config.BindWith(ManiaSetting.ScrollTime, TimeRange);
} }
/// <summary>
/// Retrieves the column that intersects a screen-space position.
/// </summary>
/// <param name="screenSpacePosition">The screen-space position.</param>
/// <returns>The column which intersects with <paramref name="screenSpacePosition"/>.</returns>
public Column GetColumnByPosition(Vector2 screenSpacePosition) => Playfield.GetColumnByPosition(screenSpacePosition);
protected override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages) protected override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages)
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,

View File

@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Mania.UI
/// </summary> /// </summary>
public class ManiaStage : ScrollingPlayfield public class ManiaStage : ScrollingPlayfield
{ {
public const float COLUMN_SPACING = 1;
public const float HIT_TARGET_POSITION = 50; public const float HIT_TARGET_POSITION = 50;
public IReadOnlyList<Column> Columns => columnFlow.Children; public IReadOnlyList<Column> Columns => columnFlow.Children;
@ -40,6 +42,8 @@ namespace osu.Game.Rulesets.Mania.UI
private List<Color4> normalColumnColours = new List<Color4>(); private List<Color4> normalColumnColours = new List<Color4>();
private Color4 specialColumnColour; private Color4 specialColumnColour;
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Columns.Any(c => c.ReceivePositionalInputAt(screenSpacePos));
private readonly int firstColumnIndex; private readonly int firstColumnIndex;
public ManiaStage(int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction) public ManiaStage(int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction)
@ -84,8 +88,8 @@ namespace osu.Game.Rulesets.Mania.UI
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X, AutoSizeAxes = Axes.X,
Direction = FillDirection.Horizontal, Direction = FillDirection.Horizontal,
Padding = new MarginPadding { Left = 1, Right = 1 }, Padding = new MarginPadding { Left = COLUMN_SPACING, Right = COLUMN_SPACING },
Spacing = new Vector2(1, 0) Spacing = new Vector2(COLUMN_SPACING, 0)
}, },
} }
}, },
@ -123,7 +127,7 @@ namespace osu.Game.Rulesets.Mania.UI
for (int i = 0; i < definition.Columns; i++) for (int i = 0; i < definition.Columns; i++)
{ {
var isSpecial = definition.IsSpecialColumn(i); var isSpecial = definition.IsSpecialColumn(i);
var column = new Column var column = new Column(firstColumnIndex + i)
{ {
IsSpecial = isSpecial, IsSpecial = isSpecial,
Action = { Value = isSpecial ? specialColumnStartAction++ : normalColumnStartAction++ } Action = { Value = isSpecial ? specialColumnStartAction++ : normalColumnStartAction++ }
@ -152,18 +156,29 @@ namespace osu.Game.Rulesets.Mania.UI
public override void Add(DrawableHitObject h) public override void Add(DrawableHitObject h)
{ {
var maniaObject = (ManiaHitObject)h.HitObject; var maniaObject = (ManiaHitObject)h.HitObject;
int columnIndex = maniaObject.Column - firstColumnIndex;
int columnIndex = -1;
maniaObject.ColumnBindable.BindValueChanged(_ =>
{
if (columnIndex != -1)
Columns.ElementAt(columnIndex).Remove(h);
columnIndex = maniaObject.Column - firstColumnIndex;
Columns.ElementAt(columnIndex).Add(h); Columns.ElementAt(columnIndex).Add(h);
}, true);
h.OnNewResult += OnNewResult; h.OnNewResult += OnNewResult;
} }
public override void Remove(DrawableHitObject h) public override bool Remove(DrawableHitObject h)
{ {
var maniaObject = (ManiaHitObject)h.HitObject; var maniaObject = (ManiaHitObject)h.HitObject;
int columnIndex = maniaObject.Column - firstColumnIndex; int columnIndex = maniaObject.Column - firstColumnIndex;
Columns.ElementAt(columnIndex).Remove(h); Columns.ElementAt(columnIndex).Remove(h);
h.OnNewResult -= OnNewResult; h.OnNewResult -= OnNewResult;
return true;
} }
public void Add(BarLine barline) => base.Add(new DrawableBarLine(barline)); public void Add(BarLine barline) => base.Add(new DrawableBarLine(barline));

View File

@ -1,7 +1,6 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
@ -16,7 +15,5 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints
: base(hitObject) : base(hitObject)
{ {
} }
public override void AdjustPosition(DragEvent dragEvent) => OsuObject.Position += dragEvent.Delta;
} }
} }

View File

@ -1,7 +1,6 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Input.Events;
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.Rulesets.Osu.Objects.Drawables;
@ -10,13 +9,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{ {
public class SliderCircleSelectionBlueprint : OsuSelectionBlueprint public class SliderCircleSelectionBlueprint : OsuSelectionBlueprint
{ {
private readonly Slider slider;
public SliderCircleSelectionBlueprint(DrawableOsuHitObject hitObject, Slider slider, SliderPosition position) public SliderCircleSelectionBlueprint(DrawableOsuHitObject hitObject, Slider slider, SliderPosition position)
: base(hitObject) : base(hitObject)
{ {
this.slider = slider;
InternalChild = new SliderCirclePiece(slider, position); InternalChild = new SliderCirclePiece(slider, position);
Select(); Select();
@ -24,7 +19,5 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
// Todo: This is temporary, since the slider circle masks don't do anything special yet. In the future they will handle input. // 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; public override bool HandlePositionalInput => false;
public override void AdjustPosition(DragEvent dragEvent) => slider.Position += dragEvent.Delta;
} }
} }

View File

@ -1,7 +1,6 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Input.Events;
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 osu.Game.Rulesets.Osu.Objects.Drawables;
@ -20,10 +19,5 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
} }
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => piece.ReceivePositionalInputAt(screenSpacePos); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => piece.ReceivePositionalInputAt(screenSpacePos);
public override void AdjustPosition(DragEvent dragEvent)
{
// Spinners don't support position adjustments
}
} }
} }

View File

@ -15,6 +15,7 @@ using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Screens.Edit.Compose.Components;
namespace osu.Game.Rulesets.Osu.Edit namespace osu.Game.Rulesets.Osu.Edit
{ {
@ -35,6 +36,8 @@ namespace osu.Game.Rulesets.Osu.Edit
new SpinnerCompositionTool() new SpinnerCompositionTool()
}; };
public override SelectionHandler CreateSelectionHandler() => new OsuSelectionHandler();
protected override Container CreateLayerContainer() => new PlayfieldAdjustmentContainer { RelativeSizeAxes = Axes.Both }; protected override Container CreateLayerContainer() => new PlayfieldAdjustmentContainer { RelativeSizeAxes = Axes.Both };
public override SelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) public override SelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject)

View File

@ -0,0 +1,30 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit.Compose.Components;
namespace osu.Game.Rulesets.Osu.Edit
{
public class OsuSelectionHandler : SelectionHandler
{
public override void HandleDrag(SelectionBlueprint blueprint, DragEvent dragEvent)
{
foreach (var h in SelectedHitObjects.OfType<OsuHitObject>())
{
if (h is Spinner)
{
// Spinners don't support position adjustments
continue;
}
h.Position += dragEvent.Delta;
}
base.HandleDrag(blueprint, dragEvent);
}
}
}

View File

@ -16,8 +16,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
public class DrawableOsuHitObject : DrawableHitObject<OsuHitObject> public class DrawableOsuHitObject : DrawableHitObject<OsuHitObject>
{ {
public override bool IsPresent => base.IsPresent || State.Value == ArmedState.Idle && Clock?.CurrentTime >= HitObject.StartTime - HitObject.TimePreempt;
private readonly ShakeContainer shakeContainer; private readonly ShakeContainer shakeContainer;
protected DrawableOsuHitObject(OsuHitObject hitObject) protected DrawableOsuHitObject(OsuHitObject hitObject)

View File

@ -217,6 +217,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
switch (state) switch (state)
{ {
case ArmedState.Idle:
Expire(true);
break;
case ArmedState.Hit: case ArmedState.Hit:
sequence.ScaleTo(Scale * 1.2f, 320, Easing.Out); sequence.ScaleTo(Scale * 1.2f, 320, Easing.Out);
break; break;

View File

@ -29,7 +29,7 @@ namespace osu.Game.Tests.Visual
{ {
public override IReadOnlyList<Type> RequiredTypes => new[] public override IReadOnlyList<Type> RequiredTypes => new[]
{ {
typeof(SelectionBox), typeof(SelectionHandler),
typeof(DragBox), typeof(DragBox),
typeof(HitObjectComposer), typeof(HitObjectComposer),
typeof(OsuHitObjectComposer), typeof(OsuHitObjectComposer),

View File

@ -43,6 +43,7 @@ namespace osu.Game.Input.Bindings
new KeyBinding(InputKey.Space, GlobalAction.Select), new KeyBinding(InputKey.Space, GlobalAction.Select),
new KeyBinding(InputKey.Enter, GlobalAction.Select), new KeyBinding(InputKey.Enter, GlobalAction.Select),
new KeyBinding(InputKey.KeypadEnter, GlobalAction.Select),
}; };
public IEnumerable<KeyBinding> InGameKeyBindings => new[] public IEnumerable<KeyBinding> InGameKeyBindings => new[]

View File

@ -178,6 +178,7 @@ namespace osu.Game.Online.API
AddParameter("grant_type", GrantType); AddParameter("grant_type", GrantType);
AddParameter("client_id", ClientId); AddParameter("client_id", ClientId);
AddParameter("client_secret", ClientSecret); AddParameter("client_secret", ClientSecret);
AddParameter("scope", "*");
base.PrePerform(); base.PrePerform();
} }

View File

@ -68,9 +68,9 @@ namespace osu.Game.Rulesets.Edit
// Process object // Process object
var processor = ruleset.CreateBeatmapProcessor(beatmap); var processor = ruleset.CreateBeatmapProcessor(beatmap);
processor.PreProcess(); processor?.PreProcess();
tObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty); tObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty);
processor.PostProcess(); processor?.PostProcess();
// Add visual representation // Add visual representation
var drawableObject = rulesetContainer.GetVisualRepresentation(tObject); var drawableObject = rulesetContainer.GetVisualRepresentation(tObject);

View File

@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Edit
{ {
public abstract class HitObjectComposer : CompositeDrawable public abstract class HitObjectComposer : CompositeDrawable
{ {
public IEnumerable<DrawableHitObject> HitObjects => rulesetContainer.Playfield.AllHitObjects; public IEnumerable<DrawableHitObject> HitObjects => RulesetContainer.Playfield.AllHitObjects;
protected readonly Ruleset Ruleset; protected readonly Ruleset Ruleset;
@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Edit
private readonly List<Container> layerContainers = new List<Container>(); private readonly List<Container> layerContainers = new List<Container>();
private EditRulesetContainer rulesetContainer; protected EditRulesetContainer RulesetContainer { get; private set; }
private BlueprintContainer blueprintContainer; private BlueprintContainer blueprintContainer;
@ -54,8 +54,8 @@ namespace osu.Game.Rulesets.Edit
try try
{ {
rulesetContainer = CreateRulesetContainer(); RulesetContainer = CreateRulesetContainer();
rulesetContainer.Clock = framedClock; RulesetContainer.Clock = framedClock;
} }
catch (Exception e) catch (Exception e)
{ {
@ -97,7 +97,7 @@ namespace osu.Game.Rulesets.Edit
Children = new Drawable[] Children = new Drawable[]
{ {
layerBelowRuleset, layerBelowRuleset,
rulesetContainer, RulesetContainer,
layerAboveRuleset layerAboveRuleset
} }
} }
@ -140,25 +140,25 @@ namespace osu.Game.Rulesets.Edit
layerContainers.ForEach(l => layerContainers.ForEach(l =>
{ {
l.Anchor = rulesetContainer.Playfield.Anchor; l.Anchor = RulesetContainer.Playfield.Anchor;
l.Origin = rulesetContainer.Playfield.Origin; l.Origin = RulesetContainer.Playfield.Origin;
l.Position = rulesetContainer.Playfield.Position; l.Position = RulesetContainer.Playfield.Position;
l.Size = rulesetContainer.Playfield.Size; l.Size = RulesetContainer.Playfield.Size;
}); });
} }
/// <summary> /// <summary>
/// Whether the user's cursor is currently in an area of the <see cref="HitObjectComposer"/> that is valid for placement. /// Whether the user's cursor is currently in an area of the <see cref="HitObjectComposer"/> that is valid for placement.
/// </summary> /// </summary>
public virtual bool CursorInPlacementArea => rulesetContainer.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position); public virtual bool CursorInPlacementArea => RulesetContainer.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position);
/// <summary> /// <summary>
/// Adds a <see cref="HitObject"/> to the <see cref="Beatmaps.Beatmap"/> and visualises it. /// Adds a <see cref="HitObject"/> to the <see cref="Beatmaps.Beatmap"/> and visualises it.
/// </summary> /// </summary>
/// <param name="hitObject">The <see cref="HitObject"/> to add.</param> /// <param name="hitObject">The <see cref="HitObject"/> to add.</param>
public void Add(HitObject hitObject) => blueprintContainer.AddBlueprintFor(rulesetContainer.Add(hitObject)); public void Add(HitObject hitObject) => blueprintContainer.AddBlueprintFor(RulesetContainer.Add(hitObject));
public void Remove(HitObject hitObject) => blueprintContainer.RemoveBlueprintFor(rulesetContainer.Remove(hitObject)); public void Remove(HitObject hitObject) => blueprintContainer.RemoveBlueprintFor(RulesetContainer.Remove(hitObject));
internal abstract EditRulesetContainer CreateRulesetContainer(); internal abstract EditRulesetContainer CreateRulesetContainer();
@ -171,10 +171,9 @@ namespace osu.Game.Rulesets.Edit
public virtual SelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) => null; public virtual SelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) => null;
/// <summary> /// <summary>
/// Creates a <see cref="SelectionBox"/> which outlines <see cref="DrawableHitObject"/>s /// Creates a <see cref="SelectionHandler"/> which outlines <see cref="DrawableHitObject"/>s and handles movement of selections.
/// and handles hitobject pattern adjustments.
/// </summary> /// </summary>
public virtual SelectionBox CreateSelectionBox() => new SelectionBox(); public virtual SelectionHandler CreateSelectionHandler() => new SelectionHandler();
/// <summary> /// <summary>
/// Creates a <see cref="ScalableContainer"/> which provides a layer above or below the <see cref="Playfield"/>. /// Creates a <see cref="ScalableContainer"/> which provides a layer above or below the <see cref="Playfield"/>.

View File

@ -7,7 +7,6 @@ using osu.Framework.Allocation;
using osu.Framework.Configuration; using osu.Framework.Configuration;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Timing; using osu.Framework.Timing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
@ -20,7 +19,7 @@ namespace osu.Game.Rulesets.Edit
/// <summary> /// <summary>
/// A blueprint which governs the creation of a new <see cref="HitObject"/> to actualisation. /// A blueprint which governs the creation of a new <see cref="HitObject"/> to actualisation.
/// </summary> /// </summary>
public abstract class PlacementBlueprint : CompositeDrawable, IStateful<PlacementState>, IRequireHighFrequencyMousePosition public abstract class PlacementBlueprint : CompositeDrawable, IStateful<PlacementState>
{ {
/// <summary> /// <summary>
/// Invoked when <see cref="State"/> has changed. /// Invoked when <see cref="State"/> has changed.
@ -50,6 +49,10 @@ namespace osu.Game.Rulesets.Edit
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
// This is required to allow the blueprint's position to be updated via OnMouseMove/Handle
// on the same frame it is made visible via a PlacementState change.
AlwaysPresent = true;
Alpha = 0; Alpha = 0;
} }

View File

@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Edit
/// <summary> /// <summary>
/// Invoked when this <see cref="SelectionBlueprint"/> has requested drag. /// Invoked when this <see cref="SelectionBlueprint"/> has requested drag.
/// </summary> /// </summary>
public event Action<DragEvent> DragRequested; public event Action<SelectionBlueprint, DragEvent> DragRequested;
/// <summary> /// <summary>
/// The <see cref="DrawableHitObject"/> which this <see cref="SelectionBlueprint"/> applies to. /// The <see cref="DrawableHitObject"/> which this <see cref="SelectionBlueprint"/> applies to.
@ -130,12 +130,10 @@ namespace osu.Game.Rulesets.Edit
protected override bool OnDrag(DragEvent e) protected override bool OnDrag(DragEvent e)
{ {
DragRequested?.Invoke(e); DragRequested?.Invoke(this, e);
return true; return true;
} }
public abstract void AdjustPosition(DragEvent dragEvent);
/// <summary> /// <summary>
/// The screen-space point that causes this <see cref="SelectionBlueprint"/> to be selected. /// The screen-space point that causes this <see cref="SelectionBlueprint"/> to be selected.
/// </summary> /// </summary>

View File

@ -84,6 +84,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
public override bool RemoveCompletedTransforms => false; public override bool RemoveCompletedTransforms => false;
protected override bool RequiresChildrenUpdate => true; protected override bool RequiresChildrenUpdate => true;
public override bool IsPresent => base.IsPresent || State.Value == ArmedState.Idle && Clock?.CurrentTime >= LifetimeStart;
public readonly Bindable<ArmedState> State = new Bindable<ArmedState>(); public readonly Bindable<ArmedState> State = new Bindable<ArmedState>();
protected DrawableHitObject(HitObject hitObject) protected DrawableHitObject(HitObject hitObject)

View File

@ -80,7 +80,7 @@ namespace osu.Game.Rulesets.UI
/// Remove a DrawableHitObject from this Playfield. /// Remove a DrawableHitObject from this Playfield.
/// </summary> /// </summary>
/// <param name="h">The DrawableHitObject to remove.</param> /// <param name="h">The DrawableHitObject to remove.</param>
public virtual void Remove(DrawableHitObject h) => HitObjectContainer.Remove(h); public virtual bool Remove(DrawableHitObject h) => HitObjectContainer.Remove(h);
/// <summary> /// <summary>
/// Registers a <see cref="Playfield"/> as a nested <see cref="Playfield"/>. /// Registers a <see cref="Playfield"/> as a nested <see cref="Playfield"/>.

View File

@ -22,7 +22,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
private Container<PlacementBlueprint> placementBlueprintContainer; private Container<PlacementBlueprint> placementBlueprintContainer;
private PlacementBlueprint currentPlacement; private PlacementBlueprint currentPlacement;
private SelectionBox selectionBox; private SelectionHandler selectionHandler;
private IEnumerable<SelectionBlueprint> selections => selectionBlueprints.Children.Where(c => c.IsAlive); private IEnumerable<SelectionBlueprint> selections => selectionBlueprints.Children.Where(c => c.IsAlive);
@ -37,16 +37,16 @@ namespace osu.Game.Screens.Edit.Compose.Components
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
selectionBox = composer.CreateSelectionBox(); selectionHandler = composer.CreateSelectionHandler();
selectionBox.DeselectAll = deselectAll; selectionHandler.DeselectAll = deselectAll;
var dragBox = new DragBox(select); var dragBox = new DragBox(select);
dragBox.DragEnd += () => selectionBox.UpdateVisibility(); dragBox.DragEnd += () => selectionHandler.UpdateVisibility();
InternalChildren = new[] InternalChildren = new[]
{ {
dragBox, dragBox,
selectionBox, selectionHandler,
selectionBlueprints = new SelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }, selectionBlueprints = new SelectionBlueprintContainer { RelativeSizeAxes = Axes.Both },
placementBlueprintContainer = new Container<PlacementBlueprint> { RelativeSizeAxes = Axes.Both }, placementBlueprintContainer = new Container<PlacementBlueprint> { RelativeSizeAxes = Axes.Both },
dragBox.CreateProxy() dragBox.CreateProxy()
@ -168,19 +168,19 @@ namespace osu.Game.Screens.Edit.Compose.Components
private void onBlueprintSelected(SelectionBlueprint blueprint) private void onBlueprintSelected(SelectionBlueprint blueprint)
{ {
selectionBox.HandleSelected(blueprint); selectionHandler.HandleSelected(blueprint);
selectionBlueprints.ChangeChildDepth(blueprint, 1); selectionBlueprints.ChangeChildDepth(blueprint, 1);
} }
private void onBlueprintDeselected(SelectionBlueprint blueprint) private void onBlueprintDeselected(SelectionBlueprint blueprint)
{ {
selectionBox.HandleDeselected(blueprint); selectionHandler.HandleDeselected(blueprint);
selectionBlueprints.ChangeChildDepth(blueprint, 0); selectionBlueprints.ChangeChildDepth(blueprint, 0);
} }
private void onSelectionRequested(SelectionBlueprint blueprint, InputState state) => selectionBox.HandleSelectionRequested(blueprint, state); private void onSelectionRequested(SelectionBlueprint blueprint, InputState state) => selectionHandler.HandleSelectionRequested(blueprint, state);
private void onDragRequested(DragEvent dragEvent) => selectionBox.HandleDrag(dragEvent); private void onDragRequested(SelectionBlueprint blueprint, DragEvent dragEvent) => selectionHandler.HandleDrag(blueprint, dragEvent);
private class SelectionBlueprintContainer : Container<SelectionBlueprint> private class SelectionBlueprintContainer : Container<SelectionBlueprint>
{ {

View File

@ -46,7 +46,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
{ {
Masking = true, Masking = true,
BorderColour = Color4.White, BorderColour = Color4.White,
BorderThickness = SelectionBox.BORDER_RADIUS, BorderThickness = SelectionHandler.BORDER_RADIUS,
Child = new Box Child = new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,

View File

@ -12,26 +12,31 @@ using osu.Framework.Input.Events;
using osu.Framework.Input.States; using osu.Framework.Input.States;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osuTK; using osuTK;
using osuTK.Input; using osuTK.Input;
namespace osu.Game.Screens.Edit.Compose.Components namespace osu.Game.Screens.Edit.Compose.Components
{ {
/// <summary> /// <summary>
/// A box which surrounds <see cref="SelectionBlueprint"/>s and provides interactive handles, context menus etc. /// A component which outlines <see cref="DrawableHitObject"/>s and handles movement of selections.
/// </summary> /// </summary>
public class SelectionBox : CompositeDrawable public class SelectionHandler : CompositeDrawable
{ {
public const float BORDER_RADIUS = 2; public const float BORDER_RADIUS = 2;
protected IEnumerable<SelectionBlueprint> SelectedBlueprints => selectedBlueprints;
private readonly List<SelectionBlueprint> selectedBlueprints; private readonly List<SelectionBlueprint> selectedBlueprints;
protected IEnumerable<HitObject> SelectedHitObjects => selectedBlueprints.Select(b => b.HitObject.HitObject);
private Drawable outline; private Drawable outline;
[Resolved] [Resolved]
private IPlacementHandler placementHandler { get; set; } private IPlacementHandler placementHandler { get; set; }
public SelectionBox() public SelectionHandler()
{ {
selectedBlueprints = new List<SelectionBlueprint>(); selectedBlueprints = new List<SelectionBlueprint>();
@ -59,12 +64,13 @@ namespace osu.Game.Screens.Edit.Compose.Components
#region User Input Handling #region User Input Handling
public void HandleDrag(DragEvent dragEvent) /// <summary>
/// Handles the selected <see cref="DrawableHitObject"/>s being dragged.
/// </summary>
/// <param name="blueprint">The <see cref="SelectionBlueprint"/> that received the drag event.</param>
/// <param name="dragEvent">The drag event.</param>
public virtual void HandleDrag(SelectionBlueprint blueprint, DragEvent dragEvent)
{ {
// Todo: Various forms of snapping
foreach (var blueprint in selectedBlueprints)
blueprint.AdjustPosition(dragEvent);
} }
protected override bool OnKeyDown(KeyDownEvent e) protected override bool OnKeyDown(KeyDownEvent e)
@ -90,19 +96,19 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// <summary> /// <summary>
/// Bind an action to deselect all selected blueprints. /// Bind an action to deselect all selected blueprints.
/// </summary> /// </summary>
public Action DeselectAll { private get; set; } internal Action DeselectAll { private get; set; }
/// <summary> /// <summary>
/// Handle a blueprint becoming selected. /// Handle a blueprint becoming selected.
/// </summary> /// </summary>
/// <param name="blueprint">The blueprint.</param> /// <param name="blueprint">The blueprint.</param>
public void HandleSelected(SelectionBlueprint blueprint) => selectedBlueprints.Add(blueprint); internal void HandleSelected(SelectionBlueprint blueprint) => selectedBlueprints.Add(blueprint);
/// <summary> /// <summary>
/// Handle a blueprint becoming deselected. /// Handle a blueprint becoming deselected.
/// </summary> /// </summary>
/// <param name="blueprint">The blueprint.</param> /// <param name="blueprint">The blueprint.</param>
public void HandleDeselected(SelectionBlueprint blueprint) internal void HandleDeselected(SelectionBlueprint blueprint)
{ {
selectedBlueprints.Remove(blueprint); selectedBlueprints.Remove(blueprint);
@ -115,7 +121,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// Handle a blueprint requesting selection. /// Handle a blueprint requesting selection.
/// </summary> /// </summary>
/// <param name="blueprint">The blueprint.</param> /// <param name="blueprint">The blueprint.</param>
public void HandleSelectionRequested(SelectionBlueprint blueprint, InputState state) internal void HandleSelectionRequested(SelectionBlueprint blueprint, InputState state)
{ {
if (state.Keyboard.ControlPressed) if (state.Keyboard.ControlPressed)
{ {
@ -139,7 +145,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
#endregion #endregion
/// <summary> /// <summary>
/// Updates whether this <see cref="SelectionBox"/> is visible. /// Updates whether this <see cref="SelectionHandler"/> is visible.
/// </summary> /// </summary>
internal void UpdateVisibility() internal void UpdateVisibility()
{ {

View File

@ -15,18 +15,14 @@ namespace osu.Game.Tests.Visual
[Cached(Type = typeof(IPlacementHandler))] [Cached(Type = typeof(IPlacementHandler))]
public abstract class PlacementBlueprintTestCase : OsuTestCase, IPlacementHandler public abstract class PlacementBlueprintTestCase : OsuTestCase, IPlacementHandler
{ {
private readonly Container hitObjectContainer; protected readonly Container HitObjectContainer;
private PlacementBlueprint currentBlueprint; private PlacementBlueprint currentBlueprint;
protected PlacementBlueprintTestCase() protected PlacementBlueprintTestCase()
{ {
Beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize = 2; Beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize = 2;
Add(hitObjectContainer = new Container Add(HitObjectContainer = CreateHitObjectContainer());
{
RelativeSizeAxes = Axes.Both,
Clock = new FramedClock(new StopwatchClock())
});
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -49,7 +45,7 @@ namespace osu.Game.Tests.Visual
public void EndPlacement(HitObject hitObject) public void EndPlacement(HitObject hitObject)
{ {
hitObjectContainer.Add(CreateHitObject(hitObject)); AddHitObject(CreateHitObject(hitObject));
Remove(currentBlueprint); Remove(currentBlueprint);
Add(currentBlueprint = CreateBlueprint()); Add(currentBlueprint = CreateBlueprint());
@ -59,6 +55,10 @@ namespace osu.Game.Tests.Visual
{ {
} }
protected virtual Container CreateHitObjectContainer() => new Container { RelativeSizeAxes = Axes.Both };
protected virtual void AddHitObject(DrawableHitObject hitObject) => HitObjectContainer.Add(hitObject);
protected abstract DrawableHitObject CreateHitObject(HitObject hitObject); protected abstract DrawableHitObject CreateHitObject(HitObject hitObject);
protected abstract PlacementBlueprint CreateBlueprint(); protected abstract PlacementBlueprint CreateBlueprint();
} }

View File

@ -24,6 +24,8 @@ namespace osu.Game.Tests.Visual
public double TimeRange { set => scrollingInfo.TimeRange.Value = value; } public double TimeRange { set => scrollingInfo.TimeRange.Value = value; }
public IScrollingInfo ScrollingInfo => scrollingInfo;
[Cached(Type = typeof(IScrollingInfo))] [Cached(Type = typeof(IScrollingInfo))]
private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo(); private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo();