mirror of
https://github.com/osukey/osukey.git
synced 2025-08-05 15:44:04 +09:00
Merge branch 'master' into import-screen
This commit is contained in:
@ -41,7 +41,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
private IEditorChangeHandler changeHandler { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private EditorClock editorClock { get; set; }
|
||||
protected EditorClock EditorClock { get; private set; }
|
||||
|
||||
[Resolved]
|
||||
protected EditorBeatmap Beatmap { get; private set; }
|
||||
@ -118,8 +118,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual Container<SelectionBlueprint> CreateSelectionBlueprintContainer() =>
|
||||
new Container<SelectionBlueprint> { RelativeSizeAxes = Axes.Both };
|
||||
protected virtual Container<SelectionBlueprint> CreateSelectionBlueprintContainer() => new HitObjectOrderedSelectionContainer { RelativeSizeAxes = Axes.Both };
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="Components.SelectionHandler"/> which outlines <see cref="DrawableHitObject"/>s and handles movement of selections.
|
||||
@ -171,7 +170,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
if (clickedBlueprint == null || SelectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered) != clickedBlueprint)
|
||||
return false;
|
||||
|
||||
editorClock?.SeekTo(clickedBlueprint.HitObject.StartTime);
|
||||
EditorClock?.SeekTo(clickedBlueprint.HitObject.StartTime);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -188,7 +187,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
if (e.Button == MouseButton.Right)
|
||||
return false;
|
||||
|
||||
if (movementBlueprint != null)
|
||||
if (movementBlueprints != null)
|
||||
{
|
||||
isDraggingBlueprint = true;
|
||||
changeHandler?.BeginChange();
|
||||
@ -300,7 +299,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
|
||||
SelectionBlueprints.Remove(blueprint);
|
||||
|
||||
if (movementBlueprint == blueprint)
|
||||
if (movementBlueprints?.Contains(blueprint) == true)
|
||||
finishSelectionMovement();
|
||||
|
||||
OnBlueprintRemoved(hitObject);
|
||||
@ -338,7 +337,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
/// <returns>Whether a selection was performed.</returns>
|
||||
private bool beginClickSelection(MouseButtonEvent e)
|
||||
{
|
||||
foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren)
|
||||
// Iterate from the top of the input stack (blueprints closest to the front of the screen first).
|
||||
foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren.Reverse())
|
||||
{
|
||||
if (!blueprint.IsHovered) continue;
|
||||
|
||||
@ -381,7 +381,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
|
||||
case SelectionState.Selected:
|
||||
// if the editor is playing, we generally don't want to deselect objects even if outside the selection area.
|
||||
if (!editorClock.IsRunning && !isValidForSelection())
|
||||
if (!EditorClock.IsRunning && !isValidForSelection())
|
||||
blueprint.Deselect();
|
||||
break;
|
||||
}
|
||||
@ -424,8 +424,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
|
||||
#region Selection Movement
|
||||
|
||||
private Vector2? movementBlueprintOriginalPosition;
|
||||
private SelectionBlueprint movementBlueprint;
|
||||
private Vector2[] movementBlueprintOriginalPositions;
|
||||
private SelectionBlueprint[] movementBlueprints;
|
||||
private bool isDraggingBlueprint;
|
||||
|
||||
/// <summary>
|
||||
@ -442,8 +442,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
return;
|
||||
|
||||
// Movement is tracked from the blueprint of the earliest hitobject, since it only makes sense to distance snap from that hitobject
|
||||
movementBlueprint = SelectionHandler.SelectedBlueprints.OrderBy(b => b.HitObject.StartTime).First();
|
||||
movementBlueprintOriginalPosition = movementBlueprint.ScreenSpaceSelectionPoint; // todo: unsure if correct
|
||||
movementBlueprints = SelectionHandler.SelectedBlueprints.OrderBy(b => b.HitObject.StartTime).ToArray();
|
||||
movementBlueprintOriginalPositions = movementBlueprints.Select(m => m.ScreenSpaceSelectionPoint).ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -453,36 +453,50 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
/// <returns>Whether a movement was active.</returns>
|
||||
private bool moveCurrentSelection(DragEvent e)
|
||||
{
|
||||
if (movementBlueprint == null)
|
||||
if (movementBlueprints == null)
|
||||
return false;
|
||||
|
||||
if (snapProvider == null)
|
||||
return true;
|
||||
|
||||
Debug.Assert(movementBlueprintOriginalPosition != null);
|
||||
Debug.Assert(movementBlueprintOriginalPositions != null);
|
||||
|
||||
HitObject draggedObject = movementBlueprint.HitObject;
|
||||
Vector2 distanceTravelled = e.ScreenSpaceMousePosition - e.ScreenSpaceMouseDownPosition;
|
||||
|
||||
// check for positional snap for every object in selection (for things like object-object snapping)
|
||||
for (var i = 0; i < movementBlueprintOriginalPositions.Length; i++)
|
||||
{
|
||||
var testPosition = movementBlueprintOriginalPositions[i] + distanceTravelled;
|
||||
|
||||
var positionalResult = snapProvider.SnapScreenSpacePositionToValidPosition(testPosition);
|
||||
|
||||
if (positionalResult.ScreenSpacePosition == testPosition) continue;
|
||||
|
||||
// attempt to move the objects, and abort any time based snapping if we can.
|
||||
if (SelectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprints[i], positionalResult.ScreenSpacePosition)))
|
||||
return true;
|
||||
}
|
||||
|
||||
// if no positional snapping could be performed, try unrestricted snapping from the earliest
|
||||
// hitobject in the selection.
|
||||
|
||||
// The final movement position, relative to movementBlueprintOriginalPosition.
|
||||
Vector2 movePosition = movementBlueprintOriginalPosition.Value + e.ScreenSpaceMousePosition - e.ScreenSpaceMouseDownPosition;
|
||||
Vector2 movePosition = movementBlueprintOriginalPositions.First() + distanceTravelled;
|
||||
|
||||
// Retrieve a snapped position.
|
||||
var result = snapProvider.SnapScreenSpacePositionToValidTime(movePosition);
|
||||
|
||||
// Move the hitobjects.
|
||||
if (!SelectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprint, result.ScreenSpacePosition)))
|
||||
if (!SelectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprints.First(), result.ScreenSpacePosition)))
|
||||
return true;
|
||||
|
||||
if (result.Time.HasValue)
|
||||
{
|
||||
// Apply the start time at the newly snapped-to position
|
||||
double offset = result.Time.Value - draggedObject.StartTime;
|
||||
double offset = result.Time.Value - movementBlueprints.First().HitObject.StartTime;
|
||||
|
||||
foreach (HitObject obj in Beatmap.SelectedHitObjects)
|
||||
{
|
||||
obj.StartTime += offset;
|
||||
Beatmap.Update(obj);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -494,11 +508,11 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
/// <returns>Whether a movement was active.</returns>
|
||||
private bool finishSelectionMovement()
|
||||
{
|
||||
if (movementBlueprint == null)
|
||||
if (movementBlueprints == null)
|
||||
return false;
|
||||
|
||||
movementBlueprintOriginalPosition = null;
|
||||
movementBlueprint = null;
|
||||
movementBlueprintOriginalPositions = null;
|
||||
movementBlueprints = null;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -101,7 +101,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
|
||||
case TernaryState.True:
|
||||
if (existingSample == null)
|
||||
samples.Add(new HitSampleInfo { Name = sampleName });
|
||||
samples.Add(new HitSampleInfo(sampleName));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -157,7 +157,10 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
{
|
||||
var snapResult = Composer.SnapScreenSpacePositionToValidTime(inputManager.CurrentState.Mouse.Position);
|
||||
|
||||
currentPlacement.UpdatePosition(snapResult);
|
||||
// if no time was found from positional snapping, we should still quantize to the beat.
|
||||
snapResult.Time ??= Beatmap.SnapTime(EditorClock.CurrentTime, null);
|
||||
|
||||
currentPlacement.UpdateTimeAndPosition(snapResult);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -209,7 +212,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
if (blueprint != null)
|
||||
{
|
||||
// doing this post-creations as adding the default hit sample should be the case regardless of the ruleset.
|
||||
blueprint.HitObject.Samples.Add(new HitSampleInfo { Name = HitSampleInfo.HIT_NORMAL });
|
||||
blueprint.HitObject.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_NORMAL));
|
||||
|
||||
placementBlueprintContainer.Child = currentPlacement = blueprint;
|
||||
|
||||
|
@ -0,0 +1,77 @@
|
||||
// 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 osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Compose.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// A container for <see cref="SelectionBlueprint"/> ordered by their <see cref="HitObject"/> start times.
|
||||
/// </summary>
|
||||
public sealed class HitObjectOrderedSelectionContainer : Container<SelectionBlueprint>
|
||||
{
|
||||
public override void Add(SelectionBlueprint drawable)
|
||||
{
|
||||
base.Add(drawable);
|
||||
bindStartTime(drawable);
|
||||
}
|
||||
|
||||
public override bool Remove(SelectionBlueprint drawable)
|
||||
{
|
||||
if (!base.Remove(drawable))
|
||||
return false;
|
||||
|
||||
unbindStartTime(drawable);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Clear(bool disposeChildren)
|
||||
{
|
||||
base.Clear(disposeChildren);
|
||||
unbindAllStartTimes();
|
||||
}
|
||||
|
||||
private readonly Dictionary<SelectionBlueprint, IBindable> startTimeMap = new Dictionary<SelectionBlueprint, IBindable>();
|
||||
|
||||
private void bindStartTime(SelectionBlueprint blueprint)
|
||||
{
|
||||
var bindable = blueprint.HitObject.StartTimeBindable.GetBoundCopy();
|
||||
|
||||
bindable.BindValueChanged(_ =>
|
||||
{
|
||||
if (LoadState >= LoadState.Ready)
|
||||
SortInternal();
|
||||
});
|
||||
|
||||
startTimeMap[blueprint] = bindable;
|
||||
}
|
||||
|
||||
private void unbindStartTime(SelectionBlueprint blueprint)
|
||||
{
|
||||
startTimeMap[blueprint].UnbindAll();
|
||||
startTimeMap.Remove(blueprint);
|
||||
}
|
||||
|
||||
private void unbindAllStartTimes()
|
||||
{
|
||||
foreach (var kvp in startTimeMap)
|
||||
kvp.Value.UnbindAll();
|
||||
startTimeMap.Clear();
|
||||
}
|
||||
|
||||
protected override int Compare(Drawable x, Drawable y)
|
||||
{
|
||||
var xObj = (SelectionBlueprint)x;
|
||||
var yObj = (SelectionBlueprint)y;
|
||||
|
||||
// Put earlier blueprints towards the end of the list, so they handle input first
|
||||
int i = yObj.HitObject.StartTime.CompareTo(xObj.HitObject.StartTime);
|
||||
return i == 0 ? CompareReverseChildID(x, y) : i;
|
||||
}
|
||||
}
|
||||
}
|
@ -328,7 +328,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
if (h.Samples.Any(s => s.Name == sampleName))
|
||||
continue;
|
||||
|
||||
h.Samples.Add(new HitSampleInfo { Name = sampleName });
|
||||
h.Samples.Add(new HitSampleInfo(sampleName));
|
||||
}
|
||||
|
||||
EditorBeatmap.EndChange();
|
||||
|
@ -224,6 +224,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
/// </summary>
|
||||
public double VisibleRange => track.Length / Zoom;
|
||||
|
||||
public SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition) =>
|
||||
new SnapResult(screenSpacePosition, null);
|
||||
|
||||
public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) =>
|
||||
new SnapResult(screenSpacePosition, beatSnapProvider.SnapTime(getTimeFromPosition(Content.ToLocalSpace(screenSpacePosition))));
|
||||
|
||||
|
@ -201,7 +201,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
|
||||
public TimelineSelectionBlueprintContainer()
|
||||
{
|
||||
AddInternal(new TimelinePart<SelectionBlueprint>(Content = new Container<SelectionBlueprint> { RelativeSizeAxes = Axes.Both }) { RelativeSizeAxes = Axes.Both });
|
||||
AddInternal(new TimelinePart<SelectionBlueprint>(Content = new HitObjectOrderedSelectionContainer { RelativeSizeAxes = Axes.Both }) { RelativeSizeAxes = Axes.Both });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
@ -19,8 +18,8 @@ using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@ -28,32 +27,26 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
{
|
||||
public class TimelineHitObjectBlueprint : SelectionBlueprint
|
||||
{
|
||||
private readonly Circle circle;
|
||||
private const float thickness = 5;
|
||||
private const float shadow_radius = 5;
|
||||
private const float circle_size = 24;
|
||||
|
||||
public Action<DragEvent> OnDragHandled;
|
||||
|
||||
[UsedImplicitly]
|
||||
private readonly Bindable<double> startTime;
|
||||
|
||||
public Action<DragEvent> OnDragHandled;
|
||||
private Bindable<int> indexInCurrentComboBindable;
|
||||
private Bindable<int> comboIndexBindable;
|
||||
|
||||
private readonly Circle circle;
|
||||
private readonly DragBar dragBar;
|
||||
|
||||
private readonly List<Container> shadowComponents = new List<Container>();
|
||||
|
||||
private DrawableHitObject drawableHitObject;
|
||||
|
||||
private Bindable<Color4> comboColour;
|
||||
|
||||
private readonly Container mainComponents;
|
||||
|
||||
private readonly OsuSpriteText comboIndexText;
|
||||
|
||||
private Bindable<int> comboIndex;
|
||||
|
||||
private const float thickness = 5;
|
||||
|
||||
private const float shadow_radius = 5;
|
||||
|
||||
private const float circle_size = 24;
|
||||
[Resolved]
|
||||
private ISkinSource skin { get; set; }
|
||||
|
||||
public TimelineHitObjectBlueprint(HitObject hitObject)
|
||||
: base(hitObject)
|
||||
@ -152,46 +145,42 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
updateShadows();
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(HitObjectComposer composer)
|
||||
{
|
||||
if (composer != null)
|
||||
{
|
||||
// best effort to get the drawable representation for grabbing colour and what not.
|
||||
drawableHitObject = composer.HitObjects.FirstOrDefault(d => d.HitObject == HitObject);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
if (HitObject is IHasComboInformation comboInfo)
|
||||
{
|
||||
comboIndex = comboInfo.IndexInCurrentComboBindable.GetBoundCopy();
|
||||
comboIndex.BindValueChanged(combo =>
|
||||
{
|
||||
comboIndexText.Text = (combo.NewValue + 1).ToString();
|
||||
}, true);
|
||||
indexInCurrentComboBindable = comboInfo.IndexInCurrentComboBindable.GetBoundCopy();
|
||||
indexInCurrentComboBindable.BindValueChanged(_ => updateComboIndex(), true);
|
||||
|
||||
comboIndexBindable = comboInfo.ComboIndexBindable.GetBoundCopy();
|
||||
comboIndexBindable.BindValueChanged(_ => updateComboColour(), true);
|
||||
|
||||
skin.SourceChanged += updateComboColour;
|
||||
}
|
||||
}
|
||||
|
||||
if (drawableHitObject != null)
|
||||
{
|
||||
comboColour = drawableHitObject.AccentColour.GetBoundCopy();
|
||||
comboColour.BindValueChanged(colour =>
|
||||
{
|
||||
if (HitObject is IHasDuration)
|
||||
mainComponents.Colour = ColourInfo.GradientHorizontal(drawableHitObject.AccentColour.Value, Color4.White);
|
||||
else
|
||||
mainComponents.Colour = drawableHitObject.AccentColour.Value;
|
||||
private void updateComboIndex() => comboIndexText.Text = (indexInCurrentComboBindable.Value + 1).ToString();
|
||||
|
||||
var col = mainComponents.Colour.TopLeft.Linear;
|
||||
float brightness = col.R + col.G + col.B;
|
||||
private void updateComboColour()
|
||||
{
|
||||
if (!(HitObject is IHasComboInformation combo))
|
||||
return;
|
||||
|
||||
// decide the combo index colour based on brightness?
|
||||
comboIndexText.Colour = brightness > 0.5f ? Color4.Black : Color4.White;
|
||||
}, true);
|
||||
}
|
||||
var comboColours = skin.GetConfig<GlobalSkinColours, IReadOnlyList<Color4>>(GlobalSkinColours.ComboColours)?.Value ?? Array.Empty<Color4>();
|
||||
var comboColour = combo.GetComboColour(comboColours);
|
||||
|
||||
if (HitObject is IHasDuration)
|
||||
mainComponents.Colour = ColourInfo.GradientHorizontal(comboColour, Color4.White);
|
||||
else
|
||||
mainComponents.Colour = comboColour;
|
||||
|
||||
var col = mainComponents.Colour.TopLeft.Linear;
|
||||
float brightness = col.R + col.G + col.B;
|
||||
|
||||
// decide the combo index colour based on brightness?
|
||||
comboIndexText.Colour = brightness > 0.5f ? Color4.Black : Color4.White;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
|
@ -25,6 +25,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
[Resolved]
|
||||
private BindableBeatDivisor beatDivisor { get; set; }
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private IEditorChangeHandler changeHandler { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; }
|
||||
|
||||
@ -38,7 +41,16 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
beatDivisor.BindValueChanged(_ => tickCache.Invalidate());
|
||||
beatDivisor.BindValueChanged(_ => invalidateTicks());
|
||||
|
||||
if (changeHandler != null)
|
||||
// currently this is the best way to handle any kind of timing changes.
|
||||
changeHandler.OnStateChange += invalidateTicks;
|
||||
}
|
||||
|
||||
private void invalidateTicks()
|
||||
{
|
||||
tickCache.Invalidate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -165,5 +177,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
return point;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (changeHandler != null)
|
||||
changeHandler.OnStateChange -= invalidateTicks;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Diagnostics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
@ -32,7 +33,8 @@ namespace osu.Game.Screens.Edit.Compose
|
||||
composer = ruleset?.CreateHitObjectComposer();
|
||||
|
||||
// make the composer available to the timeline and other components in this screen.
|
||||
dependencies.CacheAs(composer);
|
||||
if (composer != null)
|
||||
dependencies.CacheAs(composer);
|
||||
|
||||
return dependencies;
|
||||
}
|
||||
@ -42,6 +44,21 @@ namespace osu.Game.Screens.Edit.Compose
|
||||
if (ruleset == null || composer == null)
|
||||
return new ScreenWhiteBox.UnderConstructionMessage(ruleset == null ? "This beatmap" : $"{ruleset.Description}'s composer");
|
||||
|
||||
return wrapSkinnableContent(composer);
|
||||
}
|
||||
|
||||
protected override Drawable CreateTimelineContent()
|
||||
{
|
||||
if (ruleset == null || composer == null)
|
||||
return base.CreateTimelineContent();
|
||||
|
||||
return wrapSkinnableContent(new TimelineBlueprintContainer(composer));
|
||||
}
|
||||
|
||||
private Drawable wrapSkinnableContent(Drawable content)
|
||||
{
|
||||
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
|
||||
@ -50,9 +67,7 @@ namespace osu.Game.Screens.Edit.Compose
|
||||
|
||||
// load the skinning hierarchy first.
|
||||
// this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources.
|
||||
return beatmapSkinProvider.WithChild(rulesetSkinProvider.WithChild(composer));
|
||||
return beatmapSkinProvider.WithChild(rulesetSkinProvider.WithChild(content));
|
||||
}
|
||||
|
||||
protected override Drawable CreateTimelineContent() => composer == null ? base.CreateTimelineContent() : new TimelineBlueprintContainer(composer);
|
||||
}
|
||||
}
|
||||
|
@ -106,6 +106,12 @@ namespace osu.Game.Screens.Edit
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours, GameHost host, OsuConfigManager config)
|
||||
{
|
||||
if (Beatmap.Value is DummyWorkingBeatmap)
|
||||
{
|
||||
isNewBeatmap = true;
|
||||
Beatmap.Value = beatmapManager.CreateNew(Ruleset.Value, api.LocalUser.Value);
|
||||
}
|
||||
|
||||
beatDivisor.Value = Beatmap.Value.BeatmapInfo.BeatDivisor;
|
||||
beatDivisor.BindValueChanged(divisor => Beatmap.Value.BeatmapInfo.BeatDivisor = divisor.NewValue);
|
||||
|
||||
@ -122,12 +128,6 @@ namespace osu.Game.Screens.Edit
|
||||
// todo: remove caching of this and consume via editorBeatmap?
|
||||
dependencies.Cache(beatDivisor);
|
||||
|
||||
if (Beatmap.Value is DummyWorkingBeatmap)
|
||||
{
|
||||
isNewBeatmap = true;
|
||||
Beatmap.Value = beatmapManager.CreateNew(Ruleset.Value, api.LocalUser.Value);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset);
|
||||
@ -375,6 +375,9 @@ namespace osu.Game.Screens.Edit
|
||||
|
||||
protected override bool OnScroll(ScrollEvent e)
|
||||
{
|
||||
if (e.ControlPressed || e.AltPressed || e.SuperPressed)
|
||||
return false;
|
||||
|
||||
const double precision = 1;
|
||||
|
||||
double scrollComponent = e.ScrollDelta.X + e.ScrollDelta.Y;
|
||||
|
@ -76,7 +76,7 @@ namespace osu.Game.Screens.Edit
|
||||
var newState = stream.ToArray();
|
||||
|
||||
// if the previous state is binary equal we don't need to push a new one, unless this is the initial state.
|
||||
if (savedStates.Count > 0 && newState.SequenceEqual(savedStates.Last())) return;
|
||||
if (savedStates.Count > 0 && newState.SequenceEqual(savedStates[currentState])) return;
|
||||
|
||||
if (currentState < savedStates.Count - 1)
|
||||
savedStates.RemoveRange(currentState + 1, savedStates.Count - currentState - 1);
|
||||
|
@ -37,8 +37,8 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
Current = new BindableFloat(Beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize)
|
||||
{
|
||||
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
|
||||
MinValue = 2,
|
||||
MaxValue = 7,
|
||||
MinValue = 0,
|
||||
MaxValue = 10,
|
||||
Precision = 0.1f,
|
||||
}
|
||||
},
|
||||
|
@ -3,10 +3,12 @@
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
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.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
|
||||
@ -21,6 +23,9 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
|
||||
private readonly IBindable<FileInfo> currentFile = new Bindable<FileInfo>();
|
||||
|
||||
[Resolved]
|
||||
private SectionsContainer<SetupSection> sectionsContainer { get; set; }
|
||||
|
||||
public FileChooserLabelledTextBox()
|
||||
{
|
||||
currentFile.BindValueChanged(onFileSelected);
|
||||
@ -47,14 +52,16 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
|
||||
public void DisplayFileChooser()
|
||||
{
|
||||
Target.Child = new FileSelector(validFileExtensions: ResourcesSection.AudioExtensions)
|
||||
FileSelector fileSelector;
|
||||
|
||||
Target.Child = fileSelector = new FileSelector(validFileExtensions: ResourcesSection.AudioExtensions)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 400,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
CurrentFile = { BindTarget = currentFile }
|
||||
};
|
||||
|
||||
sectionsContainer.ScrollTo(fileSelector);
|
||||
}
|
||||
|
||||
internal class FileChooserOsuTextBox : OsuTextBox
|
||||
|
@ -113,8 +113,7 @@ namespace osu.Game.Screens.Menu
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Alpha = 0.5f,
|
||||
AccentColour = Color4.DarkBlue,
|
||||
Colour = Color4.DarkBlue,
|
||||
Size = new Vector2(0.96f)
|
||||
},
|
||||
new Circle
|
||||
|
@ -11,7 +11,6 @@ using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Graphics.Shaders;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
@ -20,13 +19,14 @@ using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
|
||||
namespace osu.Game.Screens.Menu
|
||||
{
|
||||
/// <summary>
|
||||
/// A visualiser that reacts to music coming from beatmaps.
|
||||
/// </summary>
|
||||
public class LogoVisualisation : Drawable, IHasAccentColour
|
||||
public class LogoVisualisation : Drawable
|
||||
{
|
||||
private readonly IBindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
|
||||
|
||||
@ -67,8 +67,6 @@ namespace osu.Game.Screens.Menu
|
||||
|
||||
private int indexOffset;
|
||||
|
||||
public Color4 AccentColour { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The relative movement of bars based on input amplification. Defaults to 1.
|
||||
/// </summary>
|
||||
@ -176,7 +174,8 @@ namespace osu.Game.Screens.Menu
|
||||
// Assuming the logo is a circle, we don't need a second dimension.
|
||||
private float size;
|
||||
|
||||
private Color4 colour;
|
||||
private static readonly Color4 transparent_white = Color4.White.Opacity(0.2f);
|
||||
|
||||
private float[] audioData;
|
||||
|
||||
private readonly QuadBatch<TexturedVertex2D> vertexBatch = new QuadBatch<TexturedVertex2D>(100, 10);
|
||||
@ -193,7 +192,6 @@ namespace osu.Game.Screens.Menu
|
||||
shader = Source.shader;
|
||||
texture = Source.texture;
|
||||
size = Source.DrawSize.X;
|
||||
colour = Source.AccentColour;
|
||||
audioData = Source.frequencyAmplitudes;
|
||||
}
|
||||
|
||||
@ -206,7 +204,7 @@ namespace osu.Game.Screens.Menu
|
||||
Vector2 inflation = DrawInfo.MatrixInverse.ExtractScale().Xy;
|
||||
|
||||
ColourInfo colourInfo = DrawColourInfo.Colour;
|
||||
colourInfo.ApplyChild(colour);
|
||||
colourInfo.ApplyChild(transparent_white);
|
||||
|
||||
if (audioData != null)
|
||||
{
|
||||
|
@ -7,7 +7,6 @@ using osu.Game.Online.API;
|
||||
using osu.Game.Users;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
|
||||
namespace osu.Game.Screens.Menu
|
||||
{
|
||||
@ -28,12 +27,10 @@ namespace osu.Game.Screens.Menu
|
||||
|
||||
private void updateColour()
|
||||
{
|
||||
Color4 defaultColour = Color4.White.Opacity(0.2f);
|
||||
|
||||
if (user.Value?.IsSupporter ?? false)
|
||||
AccentColour = skin.Value.GetConfig<GlobalSkinColours, Color4>(GlobalSkinColours.MenuGlow)?.Value ?? defaultColour;
|
||||
Colour = skin.Value.GetConfig<GlobalSkinColours, Color4>(GlobalSkinColours.MenuGlow)?.Value ?? Color4.White;
|
||||
else
|
||||
AccentColour = defaultColour;
|
||||
Colour = Color4.White;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -81,6 +81,8 @@ namespace osu.Game.Screens.Menu
|
||||
set => rippleContainer.FadeTo(value ? 1 : 0, transition_length, Easing.OutQuint);
|
||||
}
|
||||
|
||||
private const float visualizer_default_alpha = 0.5f;
|
||||
|
||||
private readonly Box flashLayer;
|
||||
|
||||
private readonly Container impactContainer;
|
||||
@ -144,7 +146,7 @@ namespace osu.Game.Screens.Menu
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
Alpha = 0.5f,
|
||||
Alpha = visualizer_default_alpha,
|
||||
Size = new Vector2(0.96f)
|
||||
},
|
||||
new Container
|
||||
@ -282,8 +284,7 @@ namespace osu.Game.Screens.Menu
|
||||
this.Delay(early_activation).Schedule(() => sampleBeat.Play());
|
||||
|
||||
logoBeatContainer
|
||||
.ScaleTo(1 - 0.02f * amplitudeAdjust, early_activation, Easing.Out)
|
||||
.Then()
|
||||
.ScaleTo(1 - 0.02f * amplitudeAdjust, early_activation, Easing.Out).Then()
|
||||
.ScaleTo(1, beatLength * 2, Easing.OutQuint);
|
||||
|
||||
ripple.ClearTransforms();
|
||||
@ -296,15 +297,13 @@ namespace osu.Game.Screens.Menu
|
||||
{
|
||||
flashLayer.ClearTransforms();
|
||||
flashLayer
|
||||
.FadeTo(0.2f * amplitudeAdjust, early_activation, Easing.Out)
|
||||
.Then()
|
||||
.FadeTo(0.2f * amplitudeAdjust, early_activation, Easing.Out).Then()
|
||||
.FadeOut(beatLength);
|
||||
|
||||
visualizer.ClearTransforms();
|
||||
visualizer
|
||||
.FadeTo(0.9f * amplitudeAdjust, early_activation, Easing.Out)
|
||||
.Then()
|
||||
.FadeTo(0.5f, beatLength);
|
||||
.FadeTo(visualizer_default_alpha * 1.8f * amplitudeAdjust, early_activation, Easing.Out).Then()
|
||||
.FadeTo(visualizer_default_alpha, beatLength);
|
||||
}
|
||||
}
|
||||
|
||||
|
22
osu.Game/Screens/Multi/Components/OverlinedPlaylistHeader.cs
Normal file
22
osu.Game/Screens/Multi/Components/OverlinedPlaylistHeader.cs
Normal file
@ -0,0 +1,22 @@
|
||||
// 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.Game.Online.Multiplayer;
|
||||
|
||||
namespace osu.Game.Screens.Multi.Components
|
||||
{
|
||||
public class OverlinedPlaylistHeader : OverlinedHeader
|
||||
{
|
||||
public OverlinedPlaylistHeader()
|
||||
: base("Playlist")
|
||||
{
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Playlist.BindCollectionChanged((_, __) => Details.Value = Playlist.GetTotalDuration(), true);
|
||||
}
|
||||
}
|
||||
}
|
@ -67,7 +67,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components
|
||||
}
|
||||
}
|
||||
},
|
||||
new Drawable[] { new OverlinedHeader("Playlist"), },
|
||||
new Drawable[] { new OverlinedPlaylistHeader(), },
|
||||
new Drawable[]
|
||||
{
|
||||
new DrawableRoomPlaylist(false, false)
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using Humanizer;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
@ -69,6 +70,7 @@ namespace osu.Game.Screens.Multi.Match.Components
|
||||
private OsuSpriteText typeLabel;
|
||||
private LoadingLayer loadingLayer;
|
||||
private DrawableRoomPlaylist playlist;
|
||||
private OsuSpriteText playlistLength;
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private IRoomManager manager { get; set; }
|
||||
@ -229,6 +231,15 @@ namespace osu.Game.Screens.Multi.Match.Components
|
||||
playlist = new DrawableRoomPlaylist(true, true) { RelativeSizeAxes = Axes.Both }
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
playlistLength = new OsuSpriteText
|
||||
{
|
||||
Margin = new MarginPadding { Vertical = 5 },
|
||||
Colour = colours.Yellow,
|
||||
Font = OsuFont.GetFont(size: 12),
|
||||
}
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
new PurpleTriangleButton
|
||||
{
|
||||
@ -243,6 +254,7 @@ namespace osu.Game.Screens.Multi.Match.Components
|
||||
{
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -315,6 +327,7 @@ namespace osu.Game.Screens.Multi.Match.Components
|
||||
Duration.BindValueChanged(duration => DurationField.Current.Value = duration.NewValue, true);
|
||||
|
||||
playlist.Items.BindTo(Playlist);
|
||||
Playlist.BindCollectionChanged(onPlaylistChanged, true);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
@ -324,6 +337,9 @@ namespace osu.Game.Screens.Multi.Match.Components
|
||||
ApplyButton.Enabled.Value = hasValidSettings;
|
||||
}
|
||||
|
||||
private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e) =>
|
||||
playlistLength.Text = $"Length: {Playlist.GetTotalDuration()}";
|
||||
|
||||
private bool hasValidSettings => RoomID.Value == null && NameField.Text.Length > 0 && Playlist.Count > 0;
|
||||
|
||||
private void apply()
|
||||
|
@ -135,7 +135,7 @@ namespace osu.Game.Screens.Multi.Match
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[] { new OverlinedHeader("Playlist"), },
|
||||
new Drawable[] { new OverlinedPlaylistHeader(), },
|
||||
new Drawable[]
|
||||
{
|
||||
new DrawableRoomPlaylistWithResults
|
||||
|
@ -92,7 +92,7 @@ namespace osu.Game.Screens.Multi.Play
|
||||
protected override ResultsScreen CreateResults(ScoreInfo score)
|
||||
{
|
||||
Debug.Assert(roomId.Value != null);
|
||||
return new TimeshiftResultsScreen(score, roomId.Value.Value, playlistItem);
|
||||
return new TimeshiftResultsScreen(score, roomId.Value.Value, playlistItem, true);
|
||||
}
|
||||
|
||||
protected override ScoreInfo CreateScore()
|
||||
|
@ -32,7 +32,7 @@ namespace osu.Game.Screens.Multi.Ranking
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; }
|
||||
|
||||
public TimeshiftResultsScreen(ScoreInfo score, int roomId, PlaylistItem playlistItem, bool allowRetry = true)
|
||||
public TimeshiftResultsScreen(ScoreInfo score, int roomId, PlaylistItem playlistItem, bool allowRetry)
|
||||
: base(score, allowRetry)
|
||||
{
|
||||
this.roomId = roomId;
|
||||
|
@ -9,7 +9,6 @@ using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Overlays;
|
||||
@ -19,7 +18,6 @@ using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Screens.Play
|
||||
{
|
||||
@ -181,7 +179,7 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
notificationOverlay?.Post(new SimpleNotification
|
||||
{
|
||||
Text = @"The score overlay is currently disabled. You can toggle this by pressing Shift+Tab."
|
||||
Text = $"The score overlay is currently disabled. You can toggle this by pressing {config.LookupKeyBindings(GlobalAction.ToggleInGameInterface)}."
|
||||
});
|
||||
}
|
||||
|
||||
@ -273,37 +271,6 @@ namespace osu.Game.Screens.Play
|
||||
Progress.BindDrawableRuleset(drawableRuleset);
|
||||
}
|
||||
|
||||
protected override bool OnKeyDown(KeyDownEvent e)
|
||||
{
|
||||
if (e.Repeat) return false;
|
||||
|
||||
if (e.ShiftPressed)
|
||||
{
|
||||
switch (e.Key)
|
||||
{
|
||||
case Key.Tab:
|
||||
switch (configVisibilityMode.Value)
|
||||
{
|
||||
case HUDVisibilityMode.Never:
|
||||
configVisibilityMode.Value = HUDVisibilityMode.HideDuringGameplay;
|
||||
break;
|
||||
|
||||
case HUDVisibilityMode.HideDuringGameplay:
|
||||
configVisibilityMode.Value = HUDVisibilityMode.Always;
|
||||
break;
|
||||
|
||||
case HUDVisibilityMode.Always:
|
||||
configVisibilityMode.Value = HUDVisibilityMode.Never;
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return base.OnKeyDown(e);
|
||||
}
|
||||
|
||||
protected virtual SkinnableAccuracyCounter CreateAccuracyCounter() => new SkinnableAccuracyCounter();
|
||||
|
||||
protected virtual SkinnableScoreCounter CreateScoreCounter() => new SkinnableScoreCounter();
|
||||
@ -377,6 +344,24 @@ namespace osu.Game.Screens.Play
|
||||
holdingForHUD = true;
|
||||
updateVisibility();
|
||||
return true;
|
||||
|
||||
case GlobalAction.ToggleInGameInterface:
|
||||
switch (configVisibilityMode.Value)
|
||||
{
|
||||
case HUDVisibilityMode.Never:
|
||||
configVisibilityMode.Value = HUDVisibilityMode.HideDuringGameplay;
|
||||
break;
|
||||
|
||||
case HUDVisibilityMode.HideDuringGameplay:
|
||||
configVisibilityMode.Value = HUDVisibilityMode.Always;
|
||||
break;
|
||||
|
||||
case HUDVisibilityMode.Always:
|
||||
configVisibilityMode.Value = HUDVisibilityMode.Never;
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -339,7 +339,11 @@ namespace osu.Game.Screens.Play
|
||||
AlwaysVisible = { BindTarget = DrawableRuleset.HasReplayLoaded },
|
||||
IsCounting = false
|
||||
},
|
||||
RequestSeek = GameplayClockContainer.Seek,
|
||||
RequestSeek = time =>
|
||||
{
|
||||
GameplayClockContainer.Seek(time);
|
||||
GameplayClockContainer.Start();
|
||||
},
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre
|
||||
},
|
||||
@ -545,7 +549,7 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
protected override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !GameplayClockContainer.IsPaused.Value;
|
||||
|
||||
protected virtual ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score);
|
||||
protected virtual ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score, true);
|
||||
|
||||
#region Fail Logic
|
||||
|
||||
|
@ -1,12 +1,14 @@
|
||||
// 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.Input.Bindings;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Ranking;
|
||||
|
||||
namespace osu.Game.Screens.Play
|
||||
{
|
||||
public class ReplayPlayer : Player
|
||||
public class ReplayPlayer : Player, IKeyBindingHandler<GlobalAction>
|
||||
{
|
||||
protected readonly Score Score;
|
||||
|
||||
@ -35,5 +37,24 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
return Score.ScoreInfo;
|
||||
}
|
||||
|
||||
public bool OnPressed(GlobalAction action)
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case GlobalAction.TogglePauseReplay:
|
||||
if (GameplayClockContainer.IsPaused.Value)
|
||||
GameplayClockContainer.Start();
|
||||
else
|
||||
GameplayClockContainer.Stop();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnReleased(GlobalAction action)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -133,6 +133,9 @@ namespace osu.Game.Screens.Play
|
||||
switch (action)
|
||||
{
|
||||
case GlobalAction.SkipCutscene:
|
||||
if (!button.Enabled.Value)
|
||||
return false;
|
||||
|
||||
button.Click();
|
||||
return true;
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ namespace osu.Game.Screens.Play
|
||||
public class SpectatorResultsScreen : SoloResultsScreen
|
||||
{
|
||||
public SpectatorResultsScreen(ScoreInfo score)
|
||||
: base(score)
|
||||
: base(score, false)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Backgrounds;
|
||||
using osu.Game.Screens.Play;
|
||||
@ -57,7 +58,7 @@ namespace osu.Game.Screens.Ranking
|
||||
|
||||
private readonly bool allowRetry;
|
||||
|
||||
protected ResultsScreen(ScoreInfo score, bool allowRetry = true)
|
||||
protected ResultsScreen(ScoreInfo score, bool allowRetry)
|
||||
{
|
||||
Score = score;
|
||||
this.allowRetry = allowRetry;
|
||||
@ -149,7 +150,12 @@ namespace osu.Game.Screens.Ranking
|
||||
};
|
||||
|
||||
if (Score != null)
|
||||
ScorePanelList.AddScore(Score, true);
|
||||
{
|
||||
// only show flair / animation when arriving after watching a play that isn't autoplay.
|
||||
bool shouldFlair = player != null && !Score.Mods.Any(m => m is ModAutoplay);
|
||||
|
||||
ScorePanelList.AddScore(Score, shouldFlair);
|
||||
}
|
||||
|
||||
if (player != null && allowRetry)
|
||||
{
|
||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Screens.Ranking
|
||||
[Resolved]
|
||||
private RulesetStore rulesets { get; set; }
|
||||
|
||||
public SoloResultsScreen(ScoreInfo score, bool allowRetry = true)
|
||||
public SoloResultsScreen(ScoreInfo score, bool allowRetry)
|
||||
: base(score, allowRetry)
|
||||
{
|
||||
}
|
||||
|
@ -91,7 +91,7 @@ namespace osu.Game.Screens.Select
|
||||
/// </summary>
|
||||
public bool BeatmapSetsLoaded { get; private set; }
|
||||
|
||||
private readonly CarouselScrollContainer scroll;
|
||||
protected readonly CarouselScrollContainer Scroll;
|
||||
|
||||
private IEnumerable<CarouselBeatmapSet> beatmapSets => root.Children.OfType<CarouselBeatmapSet>();
|
||||
|
||||
@ -112,9 +112,9 @@ namespace osu.Game.Screens.Select
|
||||
if (selectedBeatmapSet != null && !beatmapSets.Contains(selectedBeatmapSet.BeatmapSet))
|
||||
selectedBeatmapSet = null;
|
||||
|
||||
ScrollableContent.Clear(false);
|
||||
Scroll.Clear(false);
|
||||
itemsCache.Invalidate();
|
||||
scrollPositionCache.Invalidate();
|
||||
ScrollToSelected();
|
||||
|
||||
// apply any pending filter operation that may have been delayed (see applyActiveCriteria's scheduling behaviour when BeatmapSetsLoaded is false).
|
||||
FlushPendingFilterOperations();
|
||||
@ -130,9 +130,7 @@ namespace osu.Game.Screens.Select
|
||||
private readonly List<CarouselItem> visibleItems = new List<CarouselItem>();
|
||||
|
||||
private readonly Cached itemsCache = new Cached();
|
||||
private readonly Cached scrollPositionCache = new Cached();
|
||||
|
||||
protected readonly Container<DrawableCarouselItem> ScrollableContent;
|
||||
private PendingScrollOperation pendingScrollOperation = PendingScrollOperation.None;
|
||||
|
||||
public Bindable<bool> RightClickScrollingEnabled = new Bindable<bool>();
|
||||
|
||||
@ -155,17 +153,12 @@ namespace osu.Game.Screens.Select
|
||||
InternalChild = new OsuContextMenuContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = scroll = new CarouselScrollContainer
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Masking = false,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
setPool,
|
||||
Scroll = new CarouselScrollContainer
|
||||
{
|
||||
setPool,
|
||||
ScrollableContent = new Container<DrawableCarouselItem>
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
}
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -180,7 +173,7 @@ namespace osu.Game.Screens.Select
|
||||
config.BindWith(OsuSetting.RandomSelectAlgorithm, RandomAlgorithm);
|
||||
config.BindWith(OsuSetting.SongSelectRightMouseScroll, RightClickScrollingEnabled);
|
||||
|
||||
RightClickScrollingEnabled.ValueChanged += enabled => scroll.RightMouseScrollbar = enabled.NewValue;
|
||||
RightClickScrollingEnabled.ValueChanged += enabled => Scroll.RightMouseScrollbar = enabled.NewValue;
|
||||
RightClickScrollingEnabled.TriggerChange();
|
||||
|
||||
itemUpdated = beatmaps.ItemUpdated.GetBoundCopy();
|
||||
@ -421,12 +414,12 @@ namespace osu.Game.Screens.Select
|
||||
/// <summary>
|
||||
/// The position of the lower visible bound with respect to the current scroll position.
|
||||
/// </summary>
|
||||
private float visibleBottomBound => scroll.Current + DrawHeight + BleedBottom;
|
||||
private float visibleBottomBound => Scroll.Current + DrawHeight + BleedBottom;
|
||||
|
||||
/// <summary>
|
||||
/// The position of the upper visible bound with respect to the current scroll position.
|
||||
/// </summary>
|
||||
private float visibleUpperBound => scroll.Current - BleedTop;
|
||||
private float visibleUpperBound => Scroll.Current - BleedTop;
|
||||
|
||||
public void FlushPendingFilterOperations()
|
||||
{
|
||||
@ -468,8 +461,8 @@ namespace osu.Game.Screens.Select
|
||||
root.Filter(activeCriteria);
|
||||
itemsCache.Invalidate();
|
||||
|
||||
if (alwaysResetScrollPosition || !scroll.UserScrolling)
|
||||
ScrollToSelected();
|
||||
if (alwaysResetScrollPosition || !Scroll.UserScrolling)
|
||||
ScrollToSelected(true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -478,7 +471,12 @@ namespace osu.Game.Screens.Select
|
||||
/// <summary>
|
||||
/// Scroll to the current <see cref="SelectedBeatmap"/>.
|
||||
/// </summary>
|
||||
public void ScrollToSelected() => scrollPositionCache.Invalidate();
|
||||
/// <param name="immediate">
|
||||
/// Whether the scroll position should immediately be shifted to the target, delegating animation to visible panels.
|
||||
/// This should be true for operations like filtering - where panels are changing visibility state - to avoid large jumps in animation.
|
||||
/// </param>
|
||||
public void ScrollToSelected(bool immediate = false) =>
|
||||
pendingScrollOperation = immediate ? PendingScrollOperation.Immediate : PendingScrollOperation.Standard;
|
||||
|
||||
#region Key / button selection logic
|
||||
|
||||
@ -488,12 +486,12 @@ namespace osu.Game.Screens.Select
|
||||
{
|
||||
case Key.Left:
|
||||
if (!e.Repeat)
|
||||
beginRepeatSelection(() => SelectNext(-1, true), e.Key);
|
||||
beginRepeatSelection(() => SelectNext(-1), e.Key);
|
||||
return true;
|
||||
|
||||
case Key.Right:
|
||||
if (!e.Repeat)
|
||||
beginRepeatSelection(() => SelectNext(1, true), e.Key);
|
||||
beginRepeatSelection(() => SelectNext(), e.Key);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -580,6 +578,11 @@ namespace osu.Game.Screens.Select
|
||||
if (revalidateItems)
|
||||
updateYPositions();
|
||||
|
||||
// if there is a pending scroll action we apply it without animation and transfer the difference in position to the panels.
|
||||
// this is intentionally applied before updating the visible range below, to avoid animating new items (sourced from pool) from locations off-screen, as it looks bad.
|
||||
if (pendingScrollOperation != PendingScrollOperation.None)
|
||||
updateScrollPosition();
|
||||
|
||||
// This data is consumed to find the currently displayable range.
|
||||
// This is the range we want to keep drawables for, and should exceed the visible range slightly to avoid drawable churn.
|
||||
var newDisplayRange = getDisplayRange();
|
||||
@ -594,7 +597,7 @@ namespace osu.Game.Screens.Select
|
||||
{
|
||||
var toDisplay = visibleItems.GetRange(displayedRange.first, displayedRange.last - displayedRange.first + 1);
|
||||
|
||||
foreach (var panel in ScrollableContent.Children)
|
||||
foreach (var panel in Scroll.Children)
|
||||
{
|
||||
if (toDisplay.Remove(panel.Item))
|
||||
{
|
||||
@ -620,24 +623,14 @@ namespace osu.Game.Screens.Select
|
||||
panel.Depth = item.CarouselYPosition;
|
||||
panel.Y = item.CarouselYPosition;
|
||||
|
||||
ScrollableContent.Add(panel);
|
||||
Scroll.Add(panel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, if the filtered items have changed, animate drawables to their new locations.
|
||||
// This is common if a selected/collapsed state has changed.
|
||||
if (revalidateItems)
|
||||
{
|
||||
foreach (DrawableCarouselItem panel in ScrollableContent.Children)
|
||||
{
|
||||
panel.MoveToY(panel.Item.CarouselYPosition, 800, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
|
||||
// Update externally controlled state of currently visible items (e.g. x-offset and opacity).
|
||||
// This is a per-frame update on all drawable panels.
|
||||
foreach (DrawableCarouselItem item in ScrollableContent.Children)
|
||||
foreach (DrawableCarouselItem item in Scroll.Children)
|
||||
{
|
||||
updateItem(item);
|
||||
|
||||
@ -670,14 +663,6 @@ namespace osu.Game.Screens.Select
|
||||
return (firstIndex, lastIndex);
|
||||
}
|
||||
|
||||
protected override void UpdateAfterChildren()
|
||||
{
|
||||
base.UpdateAfterChildren();
|
||||
|
||||
if (!scrollPositionCache.IsValid)
|
||||
updateScrollPosition();
|
||||
}
|
||||
|
||||
private void beatmapRemoved(ValueChangedEvent<WeakReference<BeatmapSetInfo>> weakItem)
|
||||
{
|
||||
if (weakItem.NewValue.TryGetTarget(out var item))
|
||||
@ -789,7 +774,8 @@ namespace osu.Game.Screens.Select
|
||||
}
|
||||
|
||||
currentY += visibleHalfHeight;
|
||||
ScrollableContent.Height = currentY;
|
||||
|
||||
Scroll.ScrollContent.Height = currentY;
|
||||
|
||||
if (BeatmapSetsLoaded && (selectedBeatmapSet == null || selectedBeatmap == null || selectedBeatmapSet.State.Value != CarouselItemState.Selected))
|
||||
{
|
||||
@ -809,12 +795,31 @@ namespace osu.Game.Screens.Select
|
||||
if (firstScroll)
|
||||
{
|
||||
// reduce movement when first displaying the carousel.
|
||||
scroll.ScrollTo(scrollTarget.Value - 200, false);
|
||||
Scroll.ScrollTo(scrollTarget.Value - 200, false);
|
||||
firstScroll = false;
|
||||
}
|
||||
|
||||
scroll.ScrollTo(scrollTarget.Value);
|
||||
scrollPositionCache.Validate();
|
||||
switch (pendingScrollOperation)
|
||||
{
|
||||
case PendingScrollOperation.Standard:
|
||||
Scroll.ScrollTo(scrollTarget.Value);
|
||||
break;
|
||||
|
||||
case PendingScrollOperation.Immediate:
|
||||
// in order to simplify animation logic, rather than using the animated version of ScrollTo,
|
||||
// we take the difference in scroll height and apply to all visible panels.
|
||||
// this avoids edge cases like when the visible panels is reduced suddenly, causing ScrollContainer
|
||||
// to enter clamp-special-case mode where it animates completely differently to normal.
|
||||
float scrollChange = scrollTarget.Value - Scroll.Current;
|
||||
|
||||
Scroll.ScrollTo(scrollTarget.Value, false);
|
||||
|
||||
foreach (var i in Scroll.Children)
|
||||
i.Y += scrollChange;
|
||||
break;
|
||||
}
|
||||
|
||||
pendingScrollOperation = PendingScrollOperation.None;
|
||||
}
|
||||
}
|
||||
|
||||
@ -844,7 +849,7 @@ namespace osu.Game.Screens.Select
|
||||
/// <param name="parent">For nested items, the parent of the item to be updated.</param>
|
||||
private void updateItem(DrawableCarouselItem item, DrawableCarouselItem parent = null)
|
||||
{
|
||||
Vector2 posInScroll = ScrollableContent.ToLocalSpace(item.Header.ScreenSpaceDrawQuad.Centre);
|
||||
Vector2 posInScroll = Scroll.ScrollContent.ToLocalSpace(item.Header.ScreenSpaceDrawQuad.Centre);
|
||||
float itemDrawY = posInScroll.Y - visibleUpperBound;
|
||||
float dist = Math.Abs(1f - itemDrawY / visibleHalfHeight);
|
||||
|
||||
@ -858,6 +863,13 @@ namespace osu.Game.Screens.Select
|
||||
item.SetMultiplicativeAlpha(Math.Clamp(1.75f - 1.5f * dist, 0, 1));
|
||||
}
|
||||
|
||||
private enum PendingScrollOperation
|
||||
{
|
||||
None,
|
||||
Standard,
|
||||
Immediate,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A carousel item strictly used for binary search purposes.
|
||||
/// </summary>
|
||||
@ -889,7 +901,7 @@ namespace osu.Game.Screens.Select
|
||||
}
|
||||
}
|
||||
|
||||
private class CarouselScrollContainer : OsuScrollContainer
|
||||
protected class CarouselScrollContainer : OsuScrollContainer<DrawableCarouselItem>
|
||||
{
|
||||
private bool rightMouseScrollBlocked;
|
||||
|
||||
@ -898,6 +910,15 @@ namespace osu.Game.Screens.Select
|
||||
/// </summary>
|
||||
public bool UserScrolling { get; private set; }
|
||||
|
||||
public CarouselScrollContainer()
|
||||
{
|
||||
// size is determined by the carousel itself, due to not all content necessarily being loaded.
|
||||
ScrollContent.AutoSizeAxes = Axes.None;
|
||||
|
||||
// the scroll container may get pushed off-screen by global screen changes, but we still want panels to display outside of the bounds.
|
||||
Masking = false;
|
||||
}
|
||||
|
||||
// ReSharper disable once OptionalParameterHierarchyMismatch 2020.3 EAP4 bug. (https://youtrack.jetbrains.com/issue/RSRP-481535?p=RIDER-51910)
|
||||
protected override void OnUserScroll(float value, bool animated = true, double? distanceDecay = default)
|
||||
{
|
||||
|
@ -45,7 +45,7 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
|
||||
protected override CarouselItem GetNextToSelect()
|
||||
{
|
||||
if (LastSelected == null)
|
||||
if (LastSelected == null || LastSelected.Filtered.Value)
|
||||
{
|
||||
if (GetRecommendedBeatmap?.Invoke(Children.OfType<CarouselBeatmap>().Where(b => !b.Filtered.Value).Select(b => b.Beatmap)) is BeatmapInfo recommended)
|
||||
return Children.OfType<CarouselBeatmap>().First(b => b.Beatmap == recommended);
|
||||
|
@ -10,6 +10,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Collections;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
@ -60,6 +61,25 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
viewDetails = beatmapOverlay.FetchAndShowBeatmapSet;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
// position updates should not occur if the item is filtered away.
|
||||
// this avoids panels flying across the screen only to be eventually off-screen or faded out.
|
||||
if (!Item.Visible)
|
||||
return;
|
||||
|
||||
float targetY = Item.CarouselYPosition;
|
||||
|
||||
if (Precision.AlmostEquals(targetY, Y))
|
||||
Y = targetY;
|
||||
else
|
||||
// algorithm for this is taken from ScrollContainer.
|
||||
// while it doesn't necessarily need to match 1:1, as we are emulating scroll in some cases this feels most correct.
|
||||
Y = (float)Interpolation.Lerp(targetY, Y, Math.Exp(-0.01 * Time.Elapsed));
|
||||
}
|
||||
|
||||
protected override void UpdateItem()
|
||||
{
|
||||
base.UpdateItem();
|
||||
@ -80,8 +100,14 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
background = new DelayedLoadWrapper(() => new SetPanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault()))
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
}, 300),
|
||||
mainFlow = new DelayedLoadWrapper(() => new SetPanelContent((CarouselBeatmapSet)Item), 100),
|
||||
}, 300)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
mainFlow = new DelayedLoadWrapper(() => new SetPanelContent((CarouselBeatmapSet)Item), 100)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
};
|
||||
|
||||
background.DelayedLoadComplete += fadeContentIn;
|
||||
|
@ -38,7 +38,7 @@ namespace osu.Game.Screens.Select
|
||||
}
|
||||
|
||||
protected void PresentScore(ScoreInfo score) =>
|
||||
FinaliseSelection(score.Beatmap, score.Ruleset, () => this.Push(new SoloResultsScreen(score)));
|
||||
FinaliseSelection(score.Beatmap, score.Ruleset, () => this.Push(new SoloResultsScreen(score, false)));
|
||||
|
||||
protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea();
|
||||
|
||||
|
@ -376,7 +376,7 @@ namespace osu.Game.Screens.Select
|
||||
if (selectionChangedDebounce?.Completed == false)
|
||||
{
|
||||
selectionChangedDebounce.RunTask();
|
||||
selectionChangedDebounce.Cancel(); // cancel the already scheduled task.
|
||||
selectionChangedDebounce?.Cancel(); // cancel the already scheduled task.
|
||||
selectionChangedDebounce = null;
|
||||
}
|
||||
|
||||
@ -465,19 +465,30 @@ namespace osu.Game.Screens.Select
|
||||
|
||||
void run()
|
||||
{
|
||||
// clear pending task immediately to track any potential nested debounce operation.
|
||||
selectionChangedDebounce = null;
|
||||
|
||||
Logger.Log($"updating selection with beatmap:{beatmap?.ID.ToString() ?? "null"} ruleset:{ruleset?.ID.ToString() ?? "null"}");
|
||||
|
||||
if (transferRulesetValue())
|
||||
{
|
||||
Mods.Value = Array.Empty<Mod>();
|
||||
|
||||
// transferRulesetValue() may trigger a refilter. If the current selection does not match the new ruleset, we want to switch away from it.
|
||||
// transferRulesetValue() may trigger a re-filter. If the current selection does not match the new ruleset, we want to switch away from it.
|
||||
// The default logic on WorkingBeatmap change is to switch to a matching ruleset (see workingBeatmapChanged()), but we don't want that here.
|
||||
// We perform an early selection attempt and clear out the beatmap selection to avoid a second ruleset change (revert).
|
||||
if (beatmap != null && !Carousel.SelectBeatmap(beatmap, false))
|
||||
beatmap = null;
|
||||
}
|
||||
|
||||
if (selectionChangedDebounce != null)
|
||||
{
|
||||
// a new nested operation was started; switch to it for further selection.
|
||||
// this avoids having two separate debounces trigger from the same source.
|
||||
selectionChangedDebounce.RunTask();
|
||||
return;
|
||||
}
|
||||
|
||||
// We may be arriving here due to another component changing the bindable Beatmap.
|
||||
// In these cases, the other component has already loaded the beatmap, so we don't need to do so again.
|
||||
if (!EqualityComparer<BeatmapInfo>.Default.Equals(beatmap, Beatmap.Value.BeatmapInfo))
|
||||
|
Reference in New Issue
Block a user