mirror of
https://github.com/osukey/osukey.git
synced 2025-08-04 15:16:38 +09:00
Merge branch 'master' into refactor-combo-colour-retrieval
This commit is contained in:
@ -3,9 +3,12 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
@ -24,6 +27,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
/// Includes selection and manipulation support via a <see cref="Components.SelectionHandler{T}"/>.
|
||||
/// </summary>
|
||||
public abstract class BlueprintContainer<T> : CompositeDrawable, IKeyBindingHandler<PlatformAction>
|
||||
where T : class
|
||||
{
|
||||
protected DragBox DragBox { get; private set; }
|
||||
|
||||
@ -39,6 +43,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
[Resolved(CanBeNull = true)]
|
||||
private IEditorChangeHandler changeHandler { get; set; }
|
||||
|
||||
protected readonly BindableList<T> SelectedItems = new BindableList<T>();
|
||||
|
||||
protected BlueprintContainer()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
@ -47,6 +53,24 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
SelectedItems.CollectionChanged += (selectedObjects, args) =>
|
||||
{
|
||||
switch (args.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
foreach (var o in args.NewItems)
|
||||
SelectionBlueprints.FirstOrDefault(b => b.Item == o)?.Select();
|
||||
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
foreach (var o in args.OldItems)
|
||||
SelectionBlueprints.FirstOrDefault(b => b.Item == o)?.Deselect();
|
||||
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
SelectionHandler = CreateSelectionHandler();
|
||||
SelectionHandler.DeselectAll = deselectAll;
|
||||
|
||||
@ -71,6 +95,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
/// Creates a <see cref="SelectionBlueprint{T}"/> for a specific item.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to create the overlay for.</param>
|
||||
[CanBeNull]
|
||||
protected virtual SelectionBlueprint<T> CreateBlueprintFor(T item) => null;
|
||||
|
||||
protected virtual DragBox CreateDragBox(Action<RectangleF> performSelect) => new DragBox(performSelect);
|
||||
@ -276,6 +301,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
|
||||
|
||||
#region Selection
|
||||
|
@ -4,6 +4,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Humanizer;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
@ -61,6 +62,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
|
||||
inputManager = GetContainingInputManager();
|
||||
|
||||
Beatmap.HitObjectAdded += hitObjectAdded;
|
||||
|
||||
// updates to selected are handled for us by SelectionHandler.
|
||||
NewCombo.BindTo(SelectionHandler.SelectionNewComboState);
|
||||
|
||||
@ -74,6 +77,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)
|
||||
{
|
||||
if (e.ControlPressed)
|
||||
@ -246,15 +257,15 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
if (drawable == null)
|
||||
return null;
|
||||
|
||||
return CreateBlueprintFor(drawable);
|
||||
return CreateHitObjectBlueprintFor(item)?.With(b => b.DrawableObject = drawable);
|
||||
}
|
||||
|
||||
public virtual OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) => null;
|
||||
[CanBeNull]
|
||||
public virtual HitObjectSelectionBlueprint CreateHitObjectBlueprintFor(HitObject hitObject) => null;
|
||||
|
||||
protected override void OnBlueprintAdded(HitObject item)
|
||||
private void hitObjectAdded(HitObject obj)
|
||||
{
|
||||
base.OnBlueprintAdded(item);
|
||||
|
||||
// refresh the tool to handle the case of placement completing.
|
||||
refreshTool();
|
||||
|
||||
// on successful placement, the new combo button should be reset as this is the most common user interaction.
|
||||
|
@ -2,15 +2,14 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Compose.Components
|
||||
{
|
||||
@ -24,7 +23,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
|
||||
protected readonly HitObjectComposer Composer;
|
||||
|
||||
private readonly BindableList<HitObject> selectedHitObjects = new BindableList<HitObject>();
|
||||
private HitObjectUsageEventBuffer usageEventBuffer;
|
||||
|
||||
protected EditorBlueprintContainer(HitObjectComposer composer)
|
||||
{
|
||||
@ -34,23 +33,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
selectedHitObjects.BindTo(Beatmap.SelectedHitObjects);
|
||||
selectedHitObjects.CollectionChanged += (selectedObjects, args) =>
|
||||
{
|
||||
switch (args.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
foreach (var o in args.NewItems)
|
||||
SelectionBlueprints.FirstOrDefault(b => b.Item == o)?.Select();
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
foreach (var o in args.OldItems)
|
||||
SelectionBlueprints.FirstOrDefault(b => b.Item == o)?.Deselect();
|
||||
|
||||
break;
|
||||
}
|
||||
};
|
||||
SelectedItems.BindTo(Beatmap.SelectedHitObjects);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@ -65,11 +48,19 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
foreach (var obj in Composer.HitObjects)
|
||||
AddBlueprintFor(obj.HitObject);
|
||||
|
||||
Composer.Playfield.HitObjectUsageBegan += AddBlueprintFor;
|
||||
Composer.Playfield.HitObjectUsageFinished += RemoveBlueprintFor;
|
||||
usageEventBuffer = new HitObjectUsageEventBuffer(Composer.Playfield);
|
||||
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)
|
||||
=> blueprints.OrderBy(b => b.Item.StartTime);
|
||||
|
||||
@ -86,7 +77,13 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
double offset = result.Time.Value - blueprints.First().Item.StartTime;
|
||||
|
||||
if (offset != 0)
|
||||
Beatmap.PerformOnSelection(obj => obj.StartTime += offset);
|
||||
{
|
||||
Beatmap.PerformOnSelection(obj =>
|
||||
{
|
||||
obj.StartTime += offset;
|
||||
Beatmap.Update(obj);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -100,6 +97,15 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
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()
|
||||
{
|
||||
base.DragOperationCompleted();
|
||||
@ -153,11 +159,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
Beatmap.HitObjectRemoved -= RemoveBlueprintFor;
|
||||
}
|
||||
|
||||
if (Composer != null)
|
||||
{
|
||||
Composer.Playfield.HitObjectUsageBegan -= AddBlueprintFor;
|
||||
Composer.Playfield.HitObjectUsageFinished -= RemoveBlueprintFor;
|
||||
}
|
||||
usageEventBuffer?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -108,17 +108,6 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a selection target and a function of truth, retrieve the correct ternary state for display.
|
||||
/// </summary>
|
||||
protected TernaryState GetStateFromSelection<T>(IEnumerable<T> selection, Func<T, bool> func)
|
||||
{
|
||||
if (selection.Any(func))
|
||||
return selection.All(func) ? TernaryState.True : TernaryState.Indeterminate;
|
||||
|
||||
return TernaryState.False;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Ternary state changes
|
||||
@ -136,6 +125,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
return;
|
||||
|
||||
h.Samples.Add(new HitSampleInfo(sampleName));
|
||||
EditorBeatmap.Update(h);
|
||||
});
|
||||
}
|
||||
|
||||
@ -145,7 +135,11 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
/// <param name="sampleName">The name of the hit sample.</param>
|
||||
public void RemoveHitSample(string sampleName)
|
||||
{
|
||||
EditorBeatmap.PerformOnSelection(h => h.SamplesBindable.RemoveAll(s => s.Name == sampleName));
|
||||
EditorBeatmap.PerformOnSelection(h =>
|
||||
{
|
||||
h.SamplesBindable.RemoveAll(s => s.Name == sampleName);
|
||||
EditorBeatmap.Update(h);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -179,13 +173,13 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
{
|
||||
if (SelectedBlueprints.All(b => b.Item is IHasComboInformation))
|
||||
{
|
||||
yield return new TernaryStateMenuItem("New combo") { State = { BindTarget = SelectionNewComboState } };
|
||||
yield return new TernaryStateToggleMenuItem("New combo") { State = { BindTarget = SelectionNewComboState } };
|
||||
}
|
||||
|
||||
yield return new OsuMenuItem("Sound")
|
||||
{
|
||||
Items = SelectionSampleStates.Select(kvp =>
|
||||
new TernaryStateMenuItem(kvp.Value.Description) { State = { BindTarget = kvp.Value } }).ToArray()
|
||||
new TernaryStateToggleMenuItem(kvp.Value.Description) { State = { BindTarget = kvp.Value } }).ToArray()
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@ using osuTK;
|
||||
namespace osu.Game.Screens.Edit.Compose.Components
|
||||
{
|
||||
/// <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>
|
||||
public class MoveSelectionEvent<T>
|
||||
{
|
||||
|
@ -111,7 +111,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
}
|
||||
}
|
||||
|
||||
private Container dragHandles;
|
||||
private SelectionBoxDragHandleContainer dragHandles;
|
||||
private FillFlowContainer buttons;
|
||||
|
||||
private OsuSpriteText selectionDetailsText;
|
||||
@ -195,7 +195,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
},
|
||||
}
|
||||
},
|
||||
dragHandles = new Container
|
||||
dragHandles = new SelectionBoxDragHandleContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
// ensures that the centres of all drag handles line up with the middle of the selection box border.
|
||||
@ -220,75 +220,76 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
|
||||
private void addRotationComponents()
|
||||
{
|
||||
const float separation = 40;
|
||||
|
||||
addButton(FontAwesome.Solid.Undo, "Rotate 90 degrees counter-clockwise", () => OnRotation?.Invoke(-90));
|
||||
addButton(FontAwesome.Solid.Redo, "Rotate 90 degrees clockwise", () => OnRotation?.Invoke(90));
|
||||
|
||||
AddRangeInternal(new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Depth = float.MaxValue,
|
||||
Colour = colours.YellowLight,
|
||||
Blending = BlendingParameters.Additive,
|
||||
Alpha = 0.3f,
|
||||
Size = new Vector2(BORDER_RADIUS, separation),
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
},
|
||||
new SelectionBoxDragHandleButton(FontAwesome.Solid.Redo, "Free rotate")
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Y = -separation,
|
||||
HandleDrag = e => OnRotation?.Invoke(convertDragEventToAngleOfRotation(e)),
|
||||
OperationStarted = operationStarted,
|
||||
OperationEnded = operationEnded
|
||||
}
|
||||
});
|
||||
addRotateHandle(Anchor.TopLeft);
|
||||
addRotateHandle(Anchor.TopRight);
|
||||
addRotateHandle(Anchor.BottomLeft);
|
||||
addRotateHandle(Anchor.BottomRight);
|
||||
}
|
||||
|
||||
private void addYScaleComponents()
|
||||
{
|
||||
addButton(FontAwesome.Solid.ArrowsAltV, "Flip vertically (Ctrl-J)", () => OnFlip?.Invoke(Direction.Vertical));
|
||||
|
||||
addDragHandle(Anchor.TopCentre);
|
||||
addDragHandle(Anchor.BottomCentre);
|
||||
addScaleHandle(Anchor.TopCentre);
|
||||
addScaleHandle(Anchor.BottomCentre);
|
||||
}
|
||||
|
||||
private void addFullScaleComponents()
|
||||
{
|
||||
addDragHandle(Anchor.TopLeft);
|
||||
addDragHandle(Anchor.TopRight);
|
||||
addDragHandle(Anchor.BottomLeft);
|
||||
addDragHandle(Anchor.BottomRight);
|
||||
addScaleHandle(Anchor.TopLeft);
|
||||
addScaleHandle(Anchor.TopRight);
|
||||
addScaleHandle(Anchor.BottomLeft);
|
||||
addScaleHandle(Anchor.BottomRight);
|
||||
}
|
||||
|
||||
private void addXScaleComponents()
|
||||
{
|
||||
addButton(FontAwesome.Solid.ArrowsAltH, "Flip horizontally (Ctrl-H)", () => OnFlip?.Invoke(Direction.Horizontal));
|
||||
|
||||
addDragHandle(Anchor.CentreLeft);
|
||||
addDragHandle(Anchor.CentreRight);
|
||||
addScaleHandle(Anchor.CentreLeft);
|
||||
addScaleHandle(Anchor.CentreRight);
|
||||
}
|
||||
|
||||
private void addButton(IconUsage icon, string tooltip, Action action)
|
||||
{
|
||||
buttons.Add(new SelectionBoxDragHandleButton(icon, tooltip)
|
||||
var button = new SelectionBoxButton(icon, tooltip)
|
||||
{
|
||||
OperationStarted = operationStarted,
|
||||
OperationEnded = operationEnded,
|
||||
Action = action
|
||||
});
|
||||
};
|
||||
|
||||
button.OperationStarted += operationStarted;
|
||||
button.OperationEnded += operationEnded;
|
||||
buttons.Add(button);
|
||||
}
|
||||
|
||||
private void addDragHandle(Anchor anchor) => dragHandles.Add(new SelectionBoxDragHandle
|
||||
private void addScaleHandle(Anchor anchor)
|
||||
{
|
||||
Anchor = anchor,
|
||||
HandleDrag = e => OnScale?.Invoke(e.Delta, anchor),
|
||||
OperationStarted = operationStarted,
|
||||
OperationEnded = operationEnded
|
||||
});
|
||||
var handle = new SelectionBoxScaleHandle
|
||||
{
|
||||
Anchor = anchor,
|
||||
HandleDrag = e => OnScale?.Invoke(e.Delta, anchor)
|
||||
};
|
||||
|
||||
handle.OperationStarted += operationStarted;
|
||||
handle.OperationEnded += operationEnded;
|
||||
dragHandles.AddScaleHandle(handle);
|
||||
}
|
||||
|
||||
private void addRotateHandle(Anchor anchor)
|
||||
{
|
||||
var handle = new SelectionBoxRotationHandle
|
||||
{
|
||||
Anchor = anchor,
|
||||
HandleDrag = e => OnRotation?.Invoke(convertDragEventToAngleOfRotation(e))
|
||||
};
|
||||
|
||||
handle.OperationStarted += operationStarted;
|
||||
handle.OperationEnded += operationEnded;
|
||||
dragHandles.AddRotationHandle(handle);
|
||||
}
|
||||
|
||||
private int activeOperations;
|
||||
|
||||
|
@ -7,15 +7,13 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Compose.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// A drag "handle" which shares the visual appearance but behaves more like a clickable button.
|
||||
/// </summary>
|
||||
public sealed class SelectionBoxDragHandleButton : SelectionBoxDragHandle, IHasTooltip
|
||||
public sealed class SelectionBoxButton : SelectionBoxControl, IHasTooltip
|
||||
{
|
||||
private SpriteIcon icon;
|
||||
|
||||
@ -23,7 +21,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
|
||||
public Action Action;
|
||||
|
||||
public SelectionBoxDragHandleButton(IconUsage iconUsage, string tooltip)
|
||||
public SelectionBoxButton(IconUsage iconUsage, string tooltip)
|
||||
{
|
||||
this.iconUsage = iconUsage;
|
||||
|
||||
@ -36,7 +34,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Size *= 2;
|
||||
Size = new Vector2(20);
|
||||
AddInternal(icon = new SpriteIcon
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
@ -49,18 +47,18 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
OperationStarted?.Invoke();
|
||||
TriggerOperationStarted();
|
||||
Action?.Invoke();
|
||||
OperationEnded?.Invoke();
|
||||
TriggerOperatoinEnded();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void UpdateHoverState()
|
||||
{
|
||||
base.UpdateHoverState();
|
||||
icon.Colour = !HandlingMouse && IsHovered ? Color4.White : Color4.Black;
|
||||
icon.FadeColour(!IsHeld && IsHovered ? Color4.White : Color4.Black, TRANSFORM_DURATION, Easing.OutQuint);
|
||||
}
|
||||
|
||||
public string TooltipText { get; }
|
||||
public LocalisableString TooltipText { get; }
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
// 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.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Compose.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the base appearance for UI controls of the <see cref="SelectionBox"/>,
|
||||
/// such as scale handles, rotation handles, buttons, etc...
|
||||
/// </summary>
|
||||
public abstract class SelectionBoxControl : CompositeDrawable
|
||||
{
|
||||
public const double TRANSFORM_DURATION = 100;
|
||||
|
||||
public event Action OperationStarted;
|
||||
public event Action OperationEnded;
|
||||
|
||||
private Circle circle;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the user is currently holding the control with mouse.
|
||||
/// </summary>
|
||||
public bool IsHeld { get; private set; }
|
||||
|
||||
[Resolved]
|
||||
protected OsuColour Colours { get; private set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
circle = new Circle
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
UpdateHoverState();
|
||||
FinishTransforms(true);
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
UpdateHoverState();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
UpdateHoverState();
|
||||
}
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
{
|
||||
IsHeld = true;
|
||||
UpdateHoverState();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnMouseUp(MouseUpEvent e)
|
||||
{
|
||||
IsHeld = false;
|
||||
UpdateHoverState();
|
||||
}
|
||||
|
||||
protected virtual void UpdateHoverState()
|
||||
{
|
||||
if (IsHeld)
|
||||
circle.FadeColour(Colours.GrayF, TRANSFORM_DURATION, Easing.OutQuint);
|
||||
else
|
||||
circle.FadeColour(IsHovered ? Colours.Red : Colours.YellowDark, TRANSFORM_DURATION, Easing.OutQuint);
|
||||
|
||||
this.ScaleTo(IsHeld || IsHovered ? 1.5f : 1, TRANSFORM_DURATION, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected void TriggerOperationStarted() => OperationStarted?.Invoke();
|
||||
|
||||
protected void TriggerOperatoinEnded() => OperationEnded?.Invoke();
|
||||
}
|
||||
}
|
@ -2,75 +2,17 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Compose.Components
|
||||
{
|
||||
public class SelectionBoxDragHandle : Container
|
||||
public abstract class SelectionBoxDragHandle : SelectionBoxControl
|
||||
{
|
||||
public Action OperationStarted;
|
||||
public Action OperationEnded;
|
||||
|
||||
public Action<DragEvent> HandleDrag { get; set; }
|
||||
|
||||
private Circle circle;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Size = new Vector2(10);
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
circle = new Circle
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
UpdateHoverState();
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
UpdateHoverState();
|
||||
return base.OnHover(e);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
base.OnHoverLost(e);
|
||||
UpdateHoverState();
|
||||
}
|
||||
|
||||
protected bool HandlingMouse;
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
{
|
||||
HandlingMouse = true;
|
||||
UpdateHoverState();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override bool OnDragStart(DragStartEvent e)
|
||||
{
|
||||
OperationStarted?.Invoke();
|
||||
TriggerOperationStarted();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -82,24 +24,45 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
|
||||
protected override void OnDragEnd(DragEndEvent e)
|
||||
{
|
||||
HandlingMouse = false;
|
||||
OperationEnded?.Invoke();
|
||||
TriggerOperatoinEnded();
|
||||
|
||||
UpdateHoverState();
|
||||
base.OnDragEnd(e);
|
||||
}
|
||||
|
||||
protected override void OnMouseUp(MouseUpEvent e)
|
||||
#region Internal events for SelectionBoxDragHandleContainer
|
||||
|
||||
internal event Action HoverGained;
|
||||
internal event Action HoverLost;
|
||||
internal event Action MouseDown;
|
||||
internal event Action MouseUp;
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
HandlingMouse = false;
|
||||
UpdateHoverState();
|
||||
base.OnMouseUp(e);
|
||||
bool result = base.OnHover(e);
|
||||
HoverGained?.Invoke();
|
||||
return result;
|
||||
}
|
||||
|
||||
protected virtual void UpdateHoverState()
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
circle.Colour = HandlingMouse ? colours.GrayF : (IsHovered ? colours.Red : colours.YellowDark);
|
||||
this.ScaleTo(HandlingMouse || IsHovered ? 1.5f : 1, 100, Easing.OutQuint);
|
||||
base.OnHoverLost(e);
|
||||
HoverLost?.Invoke();
|
||||
}
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
{
|
||||
bool result = base.OnMouseDown(e);
|
||||
MouseDown?.Invoke();
|
||||
return result;
|
||||
}
|
||||
|
||||
protected override void OnMouseUp(MouseUpEvent e)
|
||||
{
|
||||
base.OnMouseUp(e);
|
||||
MouseUp?.Invoke();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,109 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Compose.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a display composite containing and managing the visibility state of the selection box's drag handles.
|
||||
/// </summary>
|
||||
public class SelectionBoxDragHandleContainer : CompositeDrawable
|
||||
{
|
||||
private Container<SelectionBoxScaleHandle> scaleHandles;
|
||||
private Container<SelectionBoxRotationHandle> rotationHandles;
|
||||
|
||||
private readonly List<SelectionBoxDragHandle> allDragHandles = new List<SelectionBoxDragHandle>();
|
||||
|
||||
public new MarginPadding Padding
|
||||
{
|
||||
get => base.Padding;
|
||||
set => base.Padding = value;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
scaleHandles = new Container<SelectionBoxScaleHandle>
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
rotationHandles = new Container<SelectionBoxRotationHandle>
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding(-12.5f),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public void AddScaleHandle(SelectionBoxScaleHandle handle)
|
||||
{
|
||||
bindDragHandle(handle);
|
||||
scaleHandles.Add(handle);
|
||||
}
|
||||
|
||||
public void AddRotationHandle(SelectionBoxRotationHandle handle)
|
||||
{
|
||||
handle.Alpha = 0;
|
||||
handle.AlwaysPresent = true;
|
||||
|
||||
bindDragHandle(handle);
|
||||
rotationHandles.Add(handle);
|
||||
}
|
||||
|
||||
private void bindDragHandle(SelectionBoxDragHandle handle)
|
||||
{
|
||||
handle.HoverGained += updateRotationHandlesVisibility;
|
||||
handle.HoverLost += updateRotationHandlesVisibility;
|
||||
handle.MouseDown += updateRotationHandlesVisibility;
|
||||
handle.MouseUp += updateRotationHandlesVisibility;
|
||||
allDragHandles.Add(handle);
|
||||
}
|
||||
|
||||
private SelectionBoxRotationHandle displayedRotationHandle;
|
||||
private SelectionBoxDragHandle activeHandle;
|
||||
|
||||
private void updateRotationHandlesVisibility()
|
||||
{
|
||||
// if the active handle is a rotation handle and is held or hovered,
|
||||
// then no need to perform any updates to the rotation handles visibility.
|
||||
if (activeHandle is SelectionBoxRotationHandle && (activeHandle?.IsHeld == true || activeHandle?.IsHovered == true))
|
||||
return;
|
||||
|
||||
displayedRotationHandle?.FadeOut(SelectionBoxControl.TRANSFORM_DURATION, Easing.OutQuint);
|
||||
displayedRotationHandle = null;
|
||||
|
||||
// if the active handle is not a rotation handle but is held, then keep the rotation handle hidden.
|
||||
if (activeHandle?.IsHeld == true)
|
||||
return;
|
||||
|
||||
activeHandle = rotationHandles.FirstOrDefault(h => h.IsHeld || h.IsHovered);
|
||||
activeHandle ??= allDragHandles.FirstOrDefault(h => h.IsHovered);
|
||||
|
||||
if (activeHandle != null)
|
||||
{
|
||||
displayedRotationHandle = getCorrespondingRotationHandle(activeHandle, rotationHandles);
|
||||
displayedRotationHandle?.FadeIn(SelectionBoxControl.TRANSFORM_DURATION, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the rotation handle corresponding to the given handle.
|
||||
/// </summary>
|
||||
[CanBeNull]
|
||||
private static SelectionBoxRotationHandle getCorrespondingRotationHandle(SelectionBoxDragHandle handle, IEnumerable<SelectionBoxRotationHandle> rotationHandles)
|
||||
{
|
||||
if (handle is SelectionBoxRotationHandle rotationHandle)
|
||||
return rotationHandle;
|
||||
|
||||
return rotationHandles.SingleOrDefault(r => r.Anchor == handle.Anchor);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
// 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.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Compose.Components
|
||||
{
|
||||
public class SelectionBoxRotationHandle : SelectionBoxDragHandle
|
||||
{
|
||||
private SpriteIcon icon;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Size = new Vector2(15f);
|
||||
AddInternal(icon = new SpriteIcon
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Size = new Vector2(0.5f),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Icon = FontAwesome.Solid.Redo,
|
||||
Scale = new Vector2
|
||||
{
|
||||
X = Anchor.HasFlagFast(Anchor.x0) ? 1f : -1f,
|
||||
Y = Anchor.HasFlagFast(Anchor.y0) ? 1f : -1f
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected override void UpdateHoverState()
|
||||
{
|
||||
base.UpdateHoverState();
|
||||
icon.FadeColour(!IsHeld && IsHovered ? Color4.White : Color4.Black, TRANSFORM_DURATION, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
// 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 osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Compose.Components
|
||||
{
|
||||
public class SelectionBoxScaleHandle : SelectionBoxDragHandle
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Size = new Vector2(10);
|
||||
}
|
||||
}
|
||||
}
|
@ -14,6 +14,7 @@ using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
@ -236,6 +237,17 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
DeleteSelected();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a selection target and a function of truth, retrieve the correct ternary state for display.
|
||||
/// </summary>
|
||||
protected static TernaryState GetStateFromSelection<TObject>(IEnumerable<TObject> selection, Func<TObject, bool> func)
|
||||
{
|
||||
if (selection.Any(func))
|
||||
return selection.All(func) ? TernaryState.True : TernaryState.Indeterminate;
|
||||
|
||||
return TernaryState.False;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called whenever the deletion of items has been requested.
|
||||
/// </summary>
|
||||
@ -274,8 +286,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
int count = SelectedItems.Count;
|
||||
|
||||
SelectionBox.Text = count > 0 ? count.ToString() : string.Empty;
|
||||
|
||||
SelectionBox.FadeTo(count > 0 ? 1 : 0);
|
||||
|
||||
OnSelectionChanged();
|
||||
}
|
||||
|
||||
@ -339,5 +351,98 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
=> Enumerable.Empty<MenuItem>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
/// <summary>
|
||||
/// Rotate a point around an arbitrary origin.
|
||||
/// </summary>
|
||||
/// <param name="point">The point.</param>
|
||||
/// <param name="origin">The centre origin to rotate around.</param>
|
||||
/// <param name="angle">The angle to rotate (in degrees).</param>
|
||||
protected static Vector2 RotatePointAroundOrigin(Vector2 point, Vector2 origin, float angle)
|
||||
{
|
||||
angle = -angle;
|
||||
|
||||
point.X -= origin.X;
|
||||
point.Y -= origin.Y;
|
||||
|
||||
Vector2 ret;
|
||||
ret.X = point.X * MathF.Cos(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Sin(MathUtils.DegreesToRadians(angle));
|
||||
ret.Y = point.X * -MathF.Sin(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Cos(MathUtils.DegreesToRadians(angle));
|
||||
|
||||
ret.X += origin.X;
|
||||
ret.Y += origin.Y;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// Given a scale vector, a surrounding quad for all selected objects, and a position,
|
||||
/// will return the scaled position in screen space coordinates.
|
||||
/// </summary>
|
||||
protected static Vector2 GetScaledPosition(Anchor reference, Vector2 scale, Quad selectionQuad, Vector2 position)
|
||||
{
|
||||
// adjust the direction of scale depending on which side the user is dragging.
|
||||
float xOffset = ((reference & Anchor.x0) > 0) ? -scale.X : 0;
|
||||
float yOffset = ((reference & Anchor.y0) > 0) ? -scale.Y : 0;
|
||||
|
||||
// guard against no-ops and NaN.
|
||||
if (scale.X != 0 && selectionQuad.Width > 0)
|
||||
position.X = selectionQuad.TopLeft.X + xOffset + (position.X - selectionQuad.TopLeft.X) / selectionQuad.Width * (selectionQuad.Width + scale.X);
|
||||
|
||||
if (scale.Y != 0 && selectionQuad.Height > 0)
|
||||
position.Y = selectionQuad.TopLeft.Y + yOffset + (position.Y - selectionQuad.TopLeft.Y) / selectionQuad.Height * (selectionQuad.Height + scale.Y);
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -89,7 +90,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
}
|
||||
else
|
||||
{
|
||||
placementBlueprint = CreateBlueprintFor(obj.NewValue);
|
||||
placementBlueprint = CreateBlueprintFor(obj.NewValue).AsNonNull();
|
||||
|
||||
placementBlueprint.Colour = Color4.MediumPurple;
|
||||
|
||||
@ -276,7 +277,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
var timingPoint = EditorBeatmap.ControlPointInfo.TimingPointAt(selected.First().StartTime);
|
||||
double adjustment = timingPoint.BeatLength / EditorBeatmap.BeatDivisor * amount;
|
||||
|
||||
EditorBeatmap.PerformOnSelection(h => h.StartTime += adjustment);
|
||||
EditorBeatmap.PerformOnSelection(h =>
|
||||
{
|
||||
h.StartTime += adjustment;
|
||||
EditorBeatmap.Update(h);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,6 +38,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
|
||||
private Bindable<int> indexInCurrentComboBindable;
|
||||
private Bindable<int> comboIndexBindable;
|
||||
private Bindable<Color4> displayColourBindable;
|
||||
|
||||
private readonly ExtendableCircle circle;
|
||||
private readonly Border border;
|
||||
@ -107,43 +108,61 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
if (Item is IHasComboInformation comboInfo)
|
||||
switch (Item)
|
||||
{
|
||||
indexInCurrentComboBindable = comboInfo.IndexInCurrentComboBindable.GetBoundCopy();
|
||||
indexInCurrentComboBindable.BindValueChanged(_ => updateComboIndex(), true);
|
||||
case IHasDisplayColour displayColour:
|
||||
displayColourBindable = displayColour.DisplayColour.GetBoundCopy();
|
||||
displayColourBindable.BindValueChanged(_ => updateColour(), true);
|
||||
break;
|
||||
|
||||
comboIndexBindable = comboInfo.ComboIndexBindable.GetBoundCopy();
|
||||
comboIndexBindable.BindValueChanged(_ => updateComboColour(), true);
|
||||
case IHasComboInformation comboInfo:
|
||||
indexInCurrentComboBindable = comboInfo.IndexInCurrentComboBindable.GetBoundCopy();
|
||||
indexInCurrentComboBindable.BindValueChanged(_ => updateComboIndex(), true);
|
||||
|
||||
skin.SourceChanged += updateComboColour;
|
||||
comboIndexBindable = comboInfo.ComboIndexBindable.GetBoundCopy();
|
||||
comboIndexBindable.BindValueChanged(_ => updateColour(), true);
|
||||
|
||||
skin.SourceChanged += updateColour;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnSelected()
|
||||
{
|
||||
// base logic hides selected blueprints when not selected, but timeline doesn't do that.
|
||||
updateComboColour();
|
||||
updateColour();
|
||||
}
|
||||
|
||||
protected override void OnDeselected()
|
||||
{
|
||||
// base logic hides selected blueprints when not selected, but timeline doesn't do that.
|
||||
updateComboColour();
|
||||
updateColour();
|
||||
}
|
||||
|
||||
private void updateComboIndex() => comboIndexText.Text = (indexInCurrentComboBindable.Value + 1).ToString();
|
||||
|
||||
private void updateComboColour()
|
||||
private void updateColour()
|
||||
{
|
||||
if (!(Item is IHasComboInformation combo))
|
||||
return;
|
||||
Color4 colour;
|
||||
|
||||
var comboColour = combo.GetComboColour(skin);
|
||||
switch (Item)
|
||||
{
|
||||
case IHasDisplayColour displayColour:
|
||||
colour = displayColour.DisplayColour.Value;
|
||||
break;
|
||||
|
||||
case IHasComboInformation combo:
|
||||
colour = combo.GetComboColour(skin);
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsSelected)
|
||||
{
|
||||
border.Show();
|
||||
comboColour = comboColour.Lighten(0.3f);
|
||||
colour = colour.Lighten(0.3f);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -151,9 +170,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
}
|
||||
|
||||
if (Item is IHasDuration duration && duration.Duration > 0)
|
||||
circle.Colour = ColourInfo.GradientHorizontal(comboColour, comboColour.Lighten(0.4f));
|
||||
circle.Colour = ColourInfo.GradientHorizontal(colour, colour.Lighten(0.4f));
|
||||
else
|
||||
circle.Colour = comboColour;
|
||||
circle.Colour = colour;
|
||||
|
||||
var col = circle.Colour.TopLeft.Linear;
|
||||
colouredComponents.Colour = OsuColour.ForegroundTextColourFor(col);
|
||||
|
@ -73,15 +73,7 @@ namespace osu.Game.Screens.Edit.Compose
|
||||
{
|
||||
Debug.Assert(ruleset != null);
|
||||
|
||||
var beatmapSkinProvider = new BeatmapSkinProvidingContainer(beatmap.Value.Skin);
|
||||
|
||||
// the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation
|
||||
// full access to all skin sources.
|
||||
var rulesetSkinProvider = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider, EditorBeatmap.PlayableBeatmap));
|
||||
|
||||
// load the skinning hierarchy first.
|
||||
// this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources.
|
||||
return beatmapSkinProvider.WithChild(rulesetSkinProvider.WithChild(content));
|
||||
return new RulesetSkinProvidingContainer(ruleset, EditorBeatmap.PlayableBeatmap, beatmap.Value.Skin).WithChild(content);
|
||||
}
|
||||
|
||||
#region Input Handling
|
||||
|
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user