1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-13 03:25:03 +08:00

Merge branch 'master' into fix-wrapped-storage-native-open

This commit is contained in:
Dean Herbert 2020-05-25 22:50:36 +09:00
commit f8d9c044df
24 changed files with 344 additions and 193 deletions

View File

@ -8,7 +8,7 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats;
using osu.Game.IO;
using osu.Game.IO.Archives;
using osu.Game.Resources;
using osu.Game.Tests.Resources;
namespace osu.Game.Benchmarks
{
@ -18,8 +18,8 @@ namespace osu.Game.Benchmarks
public override void SetUp()
{
using (var resources = new DllResourceStore(OsuResources.ResourceAssembly))
using (var archive = resources.GetStream("Beatmaps/241526 Soleily - Renatus.osz"))
using (var resources = new DllResourceStore(typeof(TestResources).Assembly))
using (var archive = resources.GetStream("Resources/Archives/241526 Soleily - Renatus.osz"))
using (var reader = new ZipArchiveReader(archive))
reader.GetStream("Soleily - Renatus (Gamu) [Insane].osu").CopyTo(beatmapStream);
}

View File

@ -13,6 +13,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\osu.Game.Tests\osu.Game.Tests.csproj" />
<ProjectReference Include="..\osu.Game\osu.Game.csproj" />
</ItemGroup>

View File

@ -4,6 +4,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Caching;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
@ -12,6 +16,7 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
namespace osu.Game.Rulesets.Osu.Edit
{
@ -32,9 +37,80 @@ namespace osu.Game.Rulesets.Osu.Edit
new SpinnerCompositionTool()
};
[BackgroundDependencyLoader]
private void load()
{
LayerBelowRuleset.Add(distanceSnapGridContainer = new Container { RelativeSizeAxes = Axes.Both });
EditorBeatmap.SelectedHitObjects.CollectionChanged += (_, __) => updateDistanceSnapGrid();
EditorBeatmap.PlacementObject.ValueChanged += _ => updateDistanceSnapGrid();
}
protected override ComposeBlueprintContainer CreateBlueprintContainer() => new OsuBlueprintContainer(HitObjects);
protected override DistanceSnapGrid CreateDistanceSnapGrid(IEnumerable<HitObject> selectedHitObjects)
private DistanceSnapGrid distanceSnapGrid;
private Container distanceSnapGridContainer;
private readonly Cached distanceSnapGridCache = new Cached();
private double? lastDistanceSnapGridTime;
protected override void Update()
{
base.Update();
if (!(BlueprintContainer.CurrentTool is SelectTool))
{
if (EditorClock.CurrentTime != lastDistanceSnapGridTime)
{
distanceSnapGridCache.Invalidate();
lastDistanceSnapGridTime = EditorClock.CurrentTime;
}
if (!distanceSnapGridCache.IsValid)
updateDistanceSnapGrid();
}
}
public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition)
{
if (distanceSnapGrid == null)
return base.SnapScreenSpacePositionToValidTime(screenSpacePosition);
(Vector2 pos, double time) = distanceSnapGrid.GetSnappedPosition(distanceSnapGrid.ToLocalSpace(screenSpacePosition));
return new SnapResult(distanceSnapGrid.ToScreenSpace(pos), time);
}
private void updateDistanceSnapGrid()
{
distanceSnapGridContainer.Clear();
distanceSnapGridCache.Invalidate();
switch (BlueprintContainer.CurrentTool)
{
case SelectTool _:
if (!EditorBeatmap.SelectedHitObjects.Any())
return;
distanceSnapGrid = createDistanceSnapGrid(EditorBeatmap.SelectedHitObjects);
break;
default:
if (!CursorInPlacementArea)
return;
distanceSnapGrid = createDistanceSnapGrid(Enumerable.Empty<HitObject>());
break;
}
if (distanceSnapGrid != null)
{
distanceSnapGridContainer.Add(distanceSnapGrid);
distanceSnapGridCache.Validate();
}
}
private DistanceSnapGrid createDistanceSnapGrid(IEnumerable<HitObject> selectedHitObjects)
{
if (BlueprintContainer.CurrentTool is SpinnerCompositionTool)
return null;
@ -42,7 +118,8 @@ namespace osu.Game.Rulesets.Osu.Edit
var objects = selectedHitObjects.ToList();
if (objects.Count == 0)
return createGrid(h => h.StartTime <= EditorClock.CurrentTime);
// use accurate time value to give more instantaneous feedback to the user.
return createGrid(h => h.StartTime <= EditorClock.CurrentTimeAccurate);
double minTime = objects.Min(h => h.StartTime);
return createGrid(h => h.StartTime < minTime, objects.Count + 1);

View File

@ -4,8 +4,8 @@
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Components;
using osuTK;
@ -17,9 +17,8 @@ namespace osu.Game.Tests.Visual.Editing
[BackgroundDependencyLoader]
private void load()
{
var clock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
Dependencies.CacheAs<IAdjustableClock>(clock);
Dependencies.CacheAs<IFrameBasedClock>(clock);
var clock = new EditorClock { IsCoupled = false };
Dependencies.CacheAs(clock);
var playback = new PlaybackControl
{

View File

@ -7,7 +7,6 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Edit;
@ -69,7 +68,7 @@ namespace osu.Game.Tests.Visual.Editing
private IBindable<WorkingBeatmap> beatmap { get; set; }
[Resolved]
private IAdjustableClock adjustableClock { get; set; }
private EditorClock editorClock { get; set; }
public AudioVisualiser()
{
@ -96,13 +95,15 @@ namespace osu.Game.Tests.Visual.Editing
base.Update();
if (beatmap.Value.Track.IsLoaded)
marker.X = (float)(adjustableClock.CurrentTime / beatmap.Value.Track.Length);
marker.X = (float)(editorClock.CurrentTime / beatmap.Value.Track.Length);
}
}
private class StartStopButton : OsuButton
{
private IAdjustableClock adjustableClock;
[Resolved]
private EditorClock editorClock { get; set; }
private bool started;
public StartStopButton()
@ -114,22 +115,16 @@ namespace osu.Game.Tests.Visual.Editing
Action = onClick;
}
[BackgroundDependencyLoader]
private void load(IAdjustableClock adjustableClock)
{
this.adjustableClock = adjustableClock;
}
private void onClick()
{
if (started)
{
adjustableClock.Stop();
editorClock.Stop();
Text = "Start";
}
else
{
adjustableClock.Start();
editorClock.Start();
Text = "Stop";
}

View File

@ -27,7 +27,6 @@ using osu.Game.Online.API.Requests;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects;
using Decoder = osu.Game.Beatmaps.Formats.Decoder;
using ZipArchive = SharpCompress.Archives.Zip.ZipArchive;
namespace osu.Game.Beatmaps
{
@ -66,7 +65,6 @@ namespace osu.Game.Beatmaps
private readonly AudioManager audioManager;
private readonly GameHost host;
private readonly BeatmapOnlineLookupQueue onlineLookupQueue;
private readonly Storage exportStorage;
public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, AudioManager audioManager, GameHost host = null,
WorkingBeatmap defaultBeatmap = null)
@ -83,7 +81,6 @@ namespace osu.Game.Beatmaps
beatmaps.BeatmapRestored += b => beatmapRestored.Value = new WeakReference<BeatmapInfo>(b);
onlineLookupQueue = new BeatmapOnlineLookupQueue(api, storage);
exportStorage = storage.GetStorageForDirectory("exports");
}
protected override ArchiveDownloadRequest<BeatmapSetInfo> CreateDownloadRequest(BeatmapSetInfo set, bool minimiseDownloadSize) =>
@ -214,26 +211,6 @@ namespace osu.Game.Beatmaps
workingCache.Remove(working);
}
/// <summary>
/// Exports a <see cref="BeatmapSetInfo"/> to an .osz package.
/// </summary>
/// <param name="set">The <see cref="BeatmapSetInfo"/> to export.</param>
public void Export(BeatmapSetInfo set)
{
var localSet = QueryBeatmapSet(s => s.ID == set.ID);
using (var archive = ZipArchive.Create())
{
foreach (var file in localSet.Files)
archive.AddEntry(file.Filename, Files.Storage.GetStream(file.FileInfo.StoragePath));
using (var outputStream = exportStorage.GetStream($"{set}.osz", FileAccess.Write, FileMode.Create))
archive.SaveTo(outputStream);
exportStorage.OpenInNativeExplorer();
}
}
private readonly WeakList<WorkingBeatmap> workingCache = new WeakList<WorkingBeatmap>();
/// <summary>

View File

@ -22,6 +22,7 @@ using osu.Game.IO.Archives;
using osu.Game.IPC;
using osu.Game.Overlays.Notifications;
using osu.Game.Utils;
using SharpCompress.Archives.Zip;
using SharpCompress.Common;
using FileInfo = osu.Game.IO.FileInfo;
@ -82,6 +83,8 @@ namespace osu.Game.Database
// ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised)
private ArchiveImportIPCChannel ipc;
private readonly Storage exportStorage;
protected ArchiveModelManager(Storage storage, IDatabaseContextFactory contextFactory, MutableDatabaseBackedStoreWithFileIncludes<TModel, TFileModel> modelStore, IIpcHost importHost = null)
{
ContextFactory = contextFactory;
@ -90,6 +93,8 @@ namespace osu.Game.Database
ModelStore.ItemAdded += item => handleEvent(() => itemAdded.Value = new WeakReference<TModel>(item));
ModelStore.ItemRemoved += item => handleEvent(() => itemRemoved.Value = new WeakReference<TModel>(item));
exportStorage = storage.GetStorageForDirectory("exports");
Files = new FileStore(contextFactory, storage);
if (importHost != null)
@ -369,6 +374,29 @@ namespace osu.Game.Database
return item;
}, cancellationToken, TaskCreationOptions.HideScheduler, import_scheduler).Unwrap();
/// <summary>
/// Exports an item to a legacy (.zip based) package.
/// </summary>
/// <param name="item">The item to export.</param>
public void Export(TModel item)
{
var retrievedItem = ModelStore.ConsumableItems.FirstOrDefault(s => s.ID == item.ID);
if (retrievedItem == null)
throw new ArgumentException("Specified model could not be found", nameof(item));
using (var archive = ZipArchive.Create())
{
foreach (var file in retrievedItem.Files)
archive.AddEntry(file.Filename, Files.Storage.GetStream(file.FileInfo.StoragePath));
using (var outputStream = exportStorage.GetStream($"{getValidFilename(item.ToString())}{HandledExtensions.First()}", FileAccess.Write, FileMode.Create))
archive.SaveTo(outputStream);
exportStorage.OpenInNativeExplorer();
}
}
public void UpdateFile(TModel model, TFileModel file, Stream contents)
{
using (var usage = ContextFactory.GetForWrite())
@ -710,5 +738,12 @@ namespace osu.Game.Database
}
#endregion
private string getValidFilename(string filename)
{
foreach (char c in Path.GetInvalidFileNameChars())
filename = filename.Replace(c, '_');
return filename;
}
}
}

View File

@ -7,6 +7,7 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Logging;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
using osu.Game.Skinning;
@ -38,9 +39,11 @@ namespace osu.Game.Overlays.Settings.Sections
private void load(OsuConfigManager config)
{
FlowContent.Spacing = new Vector2(0, 5);
Children = new Drawable[]
{
skinDropdown = new SkinSettingsDropdown(),
new ExportSkinButton(),
new SettingsSlider<float, SizeSlider>
{
LabelText = "Menu cursor size",
@ -117,5 +120,35 @@ namespace osu.Game.Overlays.Settings.Sections
protected override DropdownMenu CreateMenu() => base.CreateMenu().With(m => m.MaxHeight = 200);
}
}
private class ExportSkinButton : SettingsButton
{
[Resolved]
private SkinManager skins { get; set; }
private Bindable<Skin> currentSkin;
[BackgroundDependencyLoader]
private void load()
{
Text = "Export selected skin";
Action = export;
currentSkin = skins.CurrentSkin.GetBoundCopy();
currentSkin.BindValueChanged(skin => Enabled.Value = skin.NewValue.SkinInfo.ID > 0, true);
}
private void export()
{
try
{
skins.Export(currentSkin.Value.SkinInfo);
}
catch (Exception e)
{
Logger.Log($"Could not export current skin: {e.Message}", level: LogLevel.Error);
}
}
}
}
}

View File

@ -3,15 +3,14 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using osu.Framework.Input.Events;
using osu.Framework.Logging;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Configuration;
@ -38,22 +37,20 @@ namespace osu.Game.Rulesets.Edit
protected readonly Ruleset Ruleset;
[Resolved]
protected IFrameBasedClock EditorClock { get; private set; }
protected EditorClock EditorClock { get; private set; }
[Resolved]
protected EditorBeatmap EditorBeatmap { get; private set; }
[Resolved]
private IAdjustableClock adjustableClock { get; set; }
[Resolved]
protected IBeatSnapProvider BeatSnapProvider { get; private set; }
protected ComposeBlueprintContainer BlueprintContainer { get; private set; }
private DrawableEditRulesetWrapper<TObject> drawableRulesetWrapper;
private Container distanceSnapGridContainer;
private DistanceSnapGrid distanceSnapGrid;
protected readonly Container LayerBelowRuleset = new Container { RelativeSizeAxes = Axes.Both };
private readonly List<Container> layerContainers = new List<Container>();
private InputManager inputManager;
@ -67,7 +64,7 @@ namespace osu.Game.Rulesets.Edit
}
[BackgroundDependencyLoader]
private void load(IFrameBasedClock framedClock)
private void load()
{
Config = Dependencies.Get<RulesetConfigCache>().GetConfigFor(Ruleset);
@ -75,7 +72,7 @@ namespace osu.Game.Rulesets.Edit
{
drawableRulesetWrapper = new DrawableEditRulesetWrapper<TObject>(CreateDrawableRuleset(Ruleset, EditorBeatmap.PlayableBeatmap))
{
Clock = framedClock,
Clock = EditorClock,
ProcessCustomClock = false
};
}
@ -87,7 +84,7 @@ namespace osu.Game.Rulesets.Edit
var layerBelowRuleset = drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer().WithChildren(new Drawable[]
{
distanceSnapGridContainer = new Container { RelativeSizeAxes = Axes.Both },
LayerBelowRuleset,
new EditorPlayfieldBorder { RelativeSizeAxes = Axes.Both }
});
@ -139,7 +136,7 @@ namespace osu.Game.Rulesets.Edit
setSelectTool();
BlueprintContainer.SelectionChanged += selectionChanged;
EditorBeatmap.SelectedHitObjects.CollectionChanged += selectionChanged;
}
protected override bool OnKeyDown(KeyDownEvent e)
@ -165,16 +162,6 @@ namespace osu.Game.Rulesets.Edit
inputManager = GetContainingInputManager();
}
private double lastGridUpdateTime;
protected override void Update()
{
base.Update();
if (EditorClock.CurrentTime != lastGridUpdateTime && !(BlueprintContainer.CurrentTool is SelectTool))
showGridFor(Enumerable.Empty<HitObject>());
}
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
@ -188,19 +175,13 @@ namespace osu.Game.Rulesets.Edit
});
}
private void selectionChanged(IEnumerable<HitObject> selectedHitObjects)
private void selectionChanged(object sender, NotifyCollectionChangedEventArgs changedArgs)
{
var hitObjects = selectedHitObjects.ToArray();
if (hitObjects.Any())
if (EditorBeatmap.SelectedHitObjects.Any())
{
// ensure in selection mode if a selection is made.
setSelectTool();
showGridFor(hitObjects);
}
else
distanceSnapGridContainer.Hide();
}
private void setSelectTool() => toolboxCollection.Items.First().Select();
@ -209,30 +190,12 @@ namespace osu.Game.Rulesets.Edit
{
BlueprintContainer.CurrentTool = tool;
if (tool is SelectTool)
distanceSnapGridContainer.Hide();
else
{
if (!(tool is SelectTool))
EditorBeatmap.SelectedHitObjects.Clear();
showGridFor(Enumerable.Empty<HitObject>());
}
}
private void showGridFor(IEnumerable<HitObject> selectedHitObjects)
{
distanceSnapGridContainer.Clear();
distanceSnapGrid = CreateDistanceSnapGrid(selectedHitObjects);
if (distanceSnapGrid != null)
{
distanceSnapGridContainer.Child = distanceSnapGrid;
distanceSnapGridContainer.Show();
}
lastGridUpdateTime = EditorClock.CurrentTime;
}
public override IEnumerable<DrawableHitObject> HitObjects => drawableRulesetWrapper.Playfield.AllHitObjects;
public override bool CursorInPlacementArea => drawableRulesetWrapper.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position);
protected abstract IReadOnlyList<HitObjectCompositionTool> CompositionTools { get; }
@ -254,24 +217,14 @@ namespace osu.Game.Rulesets.Edit
{
EditorBeatmap.Add(hitObject);
if (adjustableClock.CurrentTime < hitObject.StartTime)
adjustableClock.Seek(hitObject.StartTime);
if (EditorClock.CurrentTime < hitObject.StartTime)
EditorClock.SeekTo(hitObject.StartTime);
}
showGridFor(Enumerable.Empty<HitObject>());
}
public void Delete(HitObject hitObject) => EditorBeatmap.Remove(hitObject);
public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition)
{
if (distanceSnapGrid == null) return new SnapResult(screenSpacePosition, null);
// TODO: move distance snap grid to OsuHitObjectComposer.
(Vector2 pos, double time) = distanceSnapGrid.GetSnappedPosition(distanceSnapGrid.ToLocalSpace(screenSpacePosition));
return new SnapResult(distanceSnapGrid.ToScreenSpace(pos), time);
}
public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, null);
public override float GetBeatSnapDistanceAt(double referenceTime)
{
@ -321,14 +274,6 @@ namespace osu.Game.Rulesets.Edit
/// </summary>
public abstract bool CursorInPlacementArea { get; }
/// <summary>
/// Creates the <see cref="DistanceSnapGrid"/> applicable for a <see cref="HitObject"/> selection.
/// </summary>
/// <param name="selectedHitObjects">The <see cref="HitObject"/> selection.</param>
/// <returns>The <see cref="DistanceSnapGrid"/> for <paramref name="selectedHitObjects"/>. If empty, a grid is returned for the current point in time.</returns>
[CanBeNull]
protected virtual DistanceSnapGrid CreateDistanceSnapGrid([NotNull] IEnumerable<HitObject> selectedHitObjects) => null;
public abstract SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition);
public abstract float GetBeatSnapDistanceAt(double referenceTime);

View File

@ -6,10 +6,10 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Objects;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose;
using osuTK;
@ -30,10 +30,13 @@ namespace osu.Game.Rulesets.Edit
/// </summary>
protected readonly HitObject HitObject;
protected IClock EditorClock { get; private set; }
[Resolved(canBeNull: true)]
protected EditorClock EditorClock { get; private set; }
private readonly IBindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
private Bindable<double> startTimeBindable;
[Resolved]
private IPlacementHandler placementHandler { get; set; }
@ -49,13 +52,12 @@ namespace osu.Game.Rulesets.Edit
}
[BackgroundDependencyLoader]
private void load(IBindable<WorkingBeatmap> beatmap, IAdjustableClock clock)
private void load(IBindable<WorkingBeatmap> beatmap)
{
this.beatmap.BindTo(beatmap);
EditorClock = clock;
ApplyDefaultsToHitObject();
startTimeBindable = HitObject.StartTimeBindable.GetBoundCopy();
startTimeBindable.BindValueChanged(_ => ApplyDefaultsToHitObject(), true);
}
/// <summary>
@ -81,9 +83,6 @@ namespace osu.Game.Rulesets.Edit
PlacementActive = false;
}
[Resolved(canBeNull: true)]
private IFrameBasedClock editorClock { get; set; }
/// <summary>
/// Updates the position of this <see cref="PlacementBlueprint"/> to a new screen-space position.
/// </summary>
@ -91,7 +90,7 @@ namespace osu.Game.Rulesets.Edit
public virtual void UpdatePosition(SnapResult snapResult)
{
if (!PlacementActive)
HitObject.StartTime = snapResult.Time ?? editorClock?.CurrentTime ?? Time.Current;
HitObject.StartTime = snapResult.Time ?? EditorClock?.CurrentTime ?? Time.Current;
}
/// <summary>

View File

@ -13,7 +13,6 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Framework.Timing;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
@ -26,7 +25,7 @@ namespace osu.Game.Screens.Edit.Components
private IconButton playButton;
[Resolved]
private IAdjustableClock adjustableClock { get; set; }
private EditorClock editorClock { get; set; }
private readonly BindableNumber<double> tempo = new BindableDouble(1);
@ -87,17 +86,17 @@ namespace osu.Game.Screens.Edit.Components
private void togglePause()
{
if (adjustableClock.IsRunning)
adjustableClock.Stop();
if (editorClock.IsRunning)
editorClock.Stop();
else
adjustableClock.Start();
editorClock.Start();
}
protected override void Update()
{
base.Update();
playButton.Icon = adjustableClock.IsRunning ? FontAwesome.Regular.PauseCircle : FontAwesome.Regular.PlayCircle;
playButton.Icon = editorClock.IsRunning ? FontAwesome.Regular.PauseCircle : FontAwesome.Regular.PlayCircle;
}
private class PlaybackTabControl : OsuTabControl<double>

View File

@ -5,7 +5,6 @@ using osu.Framework.Graphics;
using osu.Game.Graphics.Sprites;
using System;
using osu.Framework.Allocation;
using osu.Framework.Timing;
using osu.Game.Graphics;
namespace osu.Game.Screens.Edit.Components
@ -15,7 +14,7 @@ namespace osu.Game.Screens.Edit.Components
private readonly OsuSpriteText trackTimer;
[Resolved]
private IAdjustableClock adjustableClock { get; set; }
private EditorClock editorClock { get; set; }
public TimeInfoContainer()
{
@ -35,7 +34,7 @@ namespace osu.Game.Screens.Edit.Components
{
base.Update();
trackTimer.Text = TimeSpan.FromMilliseconds(adjustableClock.CurrentTime).ToString(@"mm\:ss\:fff");
trackTimer.Text = TimeSpan.FromMilliseconds(editorClock.CurrentTime).ToString(@"mm\:ss\:fff");
}
}
}

View File

@ -9,7 +9,6 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Framework.Threading;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
@ -20,14 +19,14 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
/// </summary>
public class MarkerPart : TimelinePart
{
private readonly Drawable marker;
private Drawable marker;
private readonly IAdjustableClock adjustableClock;
[Resolved]
private EditorClock editorClock { get; set; }
public MarkerPart(IAdjustableClock adjustableClock)
[BackgroundDependencyLoader]
private void load()
{
this.adjustableClock = adjustableClock;
Add(marker = new MarkerVisualisation());
}
@ -59,14 +58,14 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
return;
float markerPos = Math.Clamp(ToLocalSpace(screenPosition).X, 0, DrawWidth);
adjustableClock.Seek(markerPos / DrawWidth * Beatmap.Value.Track.Length);
editorClock.SeekTo(markerPos / DrawWidth * editorClock.TrackLength);
});
}
protected override void Update()
{
base.Update();
marker.X = (float)adjustableClock.CurrentTime;
marker.X = (float)editorClock.CurrentTime;
}
protected override void LoadBeatmap(WorkingBeatmap beatmap)

View File

@ -6,7 +6,6 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Timing;
using osu.Game.Graphics;
using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts;
@ -18,11 +17,11 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary
public class SummaryTimeline : BottomBarContainer
{
[BackgroundDependencyLoader]
private void load(OsuColour colours, IAdjustableClock adjustableClock)
private void load(OsuColour colours)
{
Children = new Drawable[]
{
new MarkerPart(adjustableClock) { RelativeSizeAxes = Axes.Both },
new MarkerPart { RelativeSizeAxes = Axes.Both },
new ControlPointPart
{
Anchor = Anchor.Centre,

View File

@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Linq;
@ -14,7 +13,6 @@ using osu.Framework.Graphics.Primitives;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Framework.Timing;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
@ -29,8 +27,6 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// </summary>
public abstract class BlueprintContainer : CompositeDrawable, IKeyBindingHandler<PlatformAction>
{
public event Action<IEnumerable<HitObject>> SelectionChanged;
protected DragBox DragBox { get; private set; }
protected Container<SelectionBlueprint> SelectionBlueprints { get; private set; }
@ -41,7 +37,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
private IEditorChangeHandler changeHandler { get; set; }
[Resolved]
private IAdjustableClock adjustableClock { get; set; }
private EditorClock editorClock { get; set; }
[Resolved]
private EditorBeatmap beatmap { get; set; }
@ -88,8 +84,6 @@ namespace osu.Game.Screens.Edit.Compose.Components
SelectionBlueprints.FirstOrDefault(b => b.HitObject == o)?.Deselect();
break;
}
SelectionChanged?.Invoke(selectedHitObjects);
};
}
@ -149,7 +143,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
if (clickedBlueprint == null)
return false;
adjustableClock?.Seek(clickedBlueprint.HitObject.StartTime);
editorClock?.SeekTo(clickedBlueprint.HitObject.StartTime);
return true;
}

View File

@ -9,7 +9,6 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Audio;
using osu.Framework.Input.Events;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Edit;
@ -25,7 +24,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
public readonly IBindable<WorkingBeatmap> Beatmap = new Bindable<WorkingBeatmap>();
[Resolved]
private IAdjustableClock adjustableClock { get; set; }
private EditorClock editorClock { get; set; }
public Timeline()
{
@ -101,7 +100,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
Content.Margin = new MarginPadding { Horizontal = DrawWidth / 2 };
// This needs to happen after transforms are updated, but before the scroll position is updated in base.UpdateAfterChildren
if (adjustableClock.IsRunning)
if (editorClock.IsRunning)
scrollToTrackTime();
}
@ -111,21 +110,21 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
if (handlingDragInput)
seekTrackToCurrent();
else if (!adjustableClock.IsRunning)
else if (!editorClock.IsRunning)
{
// The track isn't running. There are two cases we have to be wary of:
// 1) The user flick-drags on this timeline: We want the track to follow us
// 2) The user changes the track time through some other means (scrolling in the editor or overview timeline): We want to follow the track time
// The simplest way to cover both cases is by checking whether the scroll position has changed and the audio hasn't been changed externally
if (Current != lastScrollPosition && adjustableClock.CurrentTime == lastTrackTime)
if (Current != lastScrollPosition && editorClock.CurrentTime == lastTrackTime)
seekTrackToCurrent();
else
scrollToTrackTime();
}
lastScrollPosition = Current;
lastTrackTime = adjustableClock.CurrentTime;
lastTrackTime = editorClock.CurrentTime;
}
private void seekTrackToCurrent()
@ -133,7 +132,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
if (!track.IsLoaded)
return;
adjustableClock.Seek(Current / Content.DrawWidth * track.Length);
editorClock.Seek(Current / Content.DrawWidth * track.Length);
}
private void scrollToTrackTime()
@ -141,7 +140,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
if (!track.IsLoaded || track.Length == 0)
return;
ScrollTo((float)(adjustableClock.CurrentTime / track.Length) * Content.DrawWidth, false);
ScrollTo((float)(editorClock.CurrentTime / track.Length) * Content.DrawWidth, false);
}
protected override bool OnMouseDown(MouseDownEvent e)
@ -164,15 +163,15 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
private void beginUserDrag()
{
handlingDragInput = true;
trackWasPlaying = adjustableClock.IsRunning;
adjustableClock.Stop();
trackWasPlaying = editorClock.IsRunning;
editorClock.Stop();
}
private void endUserDrag()
{
handlingDragInput = false;
if (trackWasPlaying)
adjustableClock.Start();
editorClock.Start();
}
[Resolved]

View File

@ -83,8 +83,8 @@ namespace osu.Game.Screens.Edit
clock = new EditorClock(Beatmap.Value, beatDivisor) { IsCoupled = false };
clock.ChangeSource(sourceClock);
dependencies.CacheAs<IFrameBasedClock>(clock);
dependencies.CacheAs<IAdjustableClock>(clock);
dependencies.CacheAs(clock);
AddInternal(clock);
// todo: remove caching of this and consume via editorBeatmap?
dependencies.Cache(beatDivisor);

View File

@ -3,6 +3,8 @@
using System;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Transforms;
using osu.Framework.Utils;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
@ -13,7 +15,7 @@ namespace osu.Game.Screens.Edit
/// <summary>
/// A decoupled clock which adds editor-specific functionality, such as snapping to a user-defined beat divisor.
/// </summary>
public class EditorClock : DecoupleableInterpolatingFramedClock
public class EditorClock : Component, IFrameBasedClock, IAdjustableClock, ISourceChangeableClock
{
public readonly double TrackLength;
@ -21,12 +23,11 @@ namespace osu.Game.Screens.Edit
private readonly BindableBeatDivisor beatDivisor;
public EditorClock(WorkingBeatmap beatmap, BindableBeatDivisor beatDivisor)
{
this.beatDivisor = beatDivisor;
private readonly DecoupleableInterpolatingFramedClock underlyingClock;
ControlPointInfo = beatmap.Beatmap.ControlPointInfo;
TrackLength = beatmap.Track.Length;
public EditorClock(WorkingBeatmap beatmap, BindableBeatDivisor beatDivisor)
: this(beatmap.Beatmap.ControlPointInfo, beatmap.Track.Length, beatDivisor)
{
}
public EditorClock(ControlPointInfo controlPointInfo, double trackLength, BindableBeatDivisor beatDivisor)
@ -35,6 +36,13 @@ namespace osu.Game.Screens.Edit
ControlPointInfo = controlPointInfo;
TrackLength = trackLength;
underlyingClock = new DecoupleableInterpolatingFramedClock();
}
public EditorClock()
: this(new ControlPointInfo(), 1000, new BindableBeatDivisor())
{
}
/// <summary>
@ -79,20 +87,22 @@ namespace osu.Game.Screens.Edit
private void seek(int direction, bool snapped, double amount = 1)
{
double current = CurrentTimeAccurate;
if (amount <= 0) throw new ArgumentException("Value should be greater than zero", nameof(amount));
var timingPoint = ControlPointInfo.TimingPointAt(CurrentTime);
var timingPoint = ControlPointInfo.TimingPointAt(current);
if (direction < 0 && timingPoint.Time == CurrentTime)
if (direction < 0 && timingPoint.Time == current)
// When going backwards and we're at the boundary of two timing points, we compute the seek distance with the timing point which we are seeking into
timingPoint = ControlPointInfo.TimingPointAt(CurrentTime - 1);
timingPoint = ControlPointInfo.TimingPointAt(current - 1);
double seekAmount = timingPoint.BeatLength / beatDivisor.Value * amount;
double seekTime = CurrentTime + seekAmount * direction;
double seekTime = current + seekAmount * direction;
if (!snapped || ControlPointInfo.TimingPoints.Count == 0)
{
Seek(seekTime);
SeekTo(seekTime);
return;
}
@ -110,7 +120,7 @@ namespace osu.Game.Screens.Edit
// Due to the rounding above, we may end up on the current beat. This will effectively cause 0 seeking to happen, but we don't want this.
// Instead, we'll go to the next beat in the direction when this is the case
if (Precision.AlmostEquals(CurrentTime, seekTime))
if (Precision.AlmostEquals(current, seekTime))
{
closestBeat += direction > 0 ? 1 : -1;
seekTime = timingPoint.Time + closestBeat * seekAmount;
@ -125,7 +135,97 @@ namespace osu.Game.Screens.Edit
// Ensure the sought point is within the boundaries
seekTime = Math.Clamp(seekTime, 0, TrackLength);
Seek(seekTime);
SeekTo(seekTime);
}
/// <summary>
/// The current time of this clock, include any active transform seeks performed via <see cref="SeekTo"/>.
/// </summary>
public double CurrentTimeAccurate =>
Transforms.OfType<TransformSeek>().FirstOrDefault()?.EndValue ?? CurrentTime;
public double CurrentTime => underlyingClock.CurrentTime;
public void Reset()
{
ClearTransforms();
underlyingClock.Reset();
}
public void Start()
{
ClearTransforms();
underlyingClock.Start();
}
public void Stop()
{
underlyingClock.Stop();
}
public bool Seek(double position)
{
ClearTransforms();
return underlyingClock.Seek(position);
}
public void ResetSpeedAdjustments() => underlyingClock.ResetSpeedAdjustments();
double IAdjustableClock.Rate
{
get => underlyingClock.Rate;
set => underlyingClock.Rate = value;
}
double IClock.Rate => underlyingClock.Rate;
public bool IsRunning => underlyingClock.IsRunning;
public void ProcessFrame() => underlyingClock.ProcessFrame();
public double ElapsedFrameTime => underlyingClock.ElapsedFrameTime;
public double FramesPerSecond => underlyingClock.FramesPerSecond;
public FrameTimeInfo TimeInfo => underlyingClock.TimeInfo;
public void ChangeSource(IClock source) => underlyingClock.ChangeSource(source);
public IClock Source => underlyingClock.Source;
public bool IsCoupled
{
get => underlyingClock.IsCoupled;
set => underlyingClock.IsCoupled = value;
}
private const double transform_time = 300;
public void SeekTo(double seekDestination)
{
if (IsRunning)
Seek(seekDestination);
else
transformSeekTo(seekDestination, transform_time, Easing.OutQuint);
}
private void transformSeekTo(double seek, double duration = 0, Easing easing = Easing.None)
=> this.TransformTo(this.PopulateTransform(new TransformSeek(), seek, duration, easing));
private double currentTime
{
get => underlyingClock.CurrentTime;
set => underlyingClock.Seek(value);
}
private class TransformSeek : Transform<double, EditorClock>
{
public override string TargetMember => nameof(currentTime);
protected override void Apply(EditorClock clock, double time) =>
clock.currentTime = Interpolation.ValueAt(time, StartValue, EndValue, StartTime, EndTime, Easing);
protected override void ReadIntoStartValue(EditorClock clock) => StartValue = clock.currentTime;
}
}
}

View File

@ -7,7 +7,6 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics;
@ -23,7 +22,7 @@ namespace osu.Game.Screens.Edit.Timing
private Bindable<ControlPointGroup> selectedGroup = new Bindable<ControlPointGroup>();
[Resolved]
private IAdjustableClock clock { get; set; }
private EditorClock clock { get; set; }
protected override Drawable CreateMainContent() => new GridContainer
{
@ -50,7 +49,7 @@ namespace osu.Game.Screens.Edit.Timing
selectedGroup.BindValueChanged(selected =>
{
if (selected.NewValue != null)
clock.Seek(selected.NewValue.Time);
clock.SeekTo(selected.NewValue.Time);
});
}
@ -62,7 +61,7 @@ namespace osu.Game.Screens.Edit.Timing
private IBindableList<ControlPointGroup> controlGroups;
[Resolved]
private IFrameBasedClock clock { get; set; }
private EditorClock clock { get; set; }
[Resolved]
protected IBindable<WorkingBeatmap> Beatmap { get; private set; }

View File

@ -24,8 +24,6 @@ namespace osu.Game.Skinning
public bool DeletePending { get; set; }
public string FullName => $"\"{Name}\" by {Creator}";
public static SkinInfo Default { get; } = new SkinInfo
{
Name = "osu!lazer",
@ -34,6 +32,10 @@ namespace osu.Game.Skinning
public bool Equals(SkinInfo other) => other != null && ID == other.ID;
public override string ToString() => FullName;
public override string ToString()
{
string author = Creator == null ? string.Empty : $"({Creator})";
return $"{Name} {author}".Trim();
}
}
}

View File

@ -30,8 +30,7 @@ namespace osu.Game.Tests.Visual
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
dependencies.Cache(BeatDivisor);
dependencies.CacheAs<IFrameBasedClock>(Clock);
dependencies.CacheAs<IAdjustableClock>(Clock);
dependencies.CacheAs(Clock);
return dependencies;
}

View File

@ -8,6 +8,7 @@ using osu.Framework.Timing;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose;
namespace osu.Game.Tests.Visual
@ -32,7 +33,7 @@ namespace osu.Game.Tests.Visual
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
dependencies.CacheAs<IAdjustableClock>(new StopwatchClock());
dependencies.CacheAs(new EditorClock());
return dependencies;
}

View File

@ -19,7 +19,7 @@
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="Dapper" Version="2.0.35" />
<PackageReference Include="DiffPlex" Version="1.6.1" />
<PackageReference Include="DiffPlex" Version="1.6.2" />
<PackageReference Include="Humanizer" Version="2.8.11" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
@ -27,7 +27,7 @@
<PackageReference Include="ppy.osu.Framework" Version="2020.519.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.512.0" />
<PackageReference Include="Sentry" Version="2.1.1" />
<PackageReference Include="SharpCompress" Version="0.25.0" />
<PackageReference Include="SharpCompress" Version="0.25.1" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.7.0" />
</ItemGroup>

View File

@ -75,13 +75,13 @@
</ItemGroup>
<!-- Xamarin.iOS does not automatically handle transitive dependencies from NuGet packages. -->
<ItemGroup Label="Transitive Dependencies">
<PackageReference Include="DiffPlex" Version="1.6.1" />
<PackageReference Include="DiffPlex" Version="1.6.2" />
<PackageReference Include="Humanizer" Version="2.8.11" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="ppy.osu.Framework" Version="2020.519.0" />
<PackageReference Include="SharpCompress" Version="0.25.0" />
<PackageReference Include="SharpCompress" Version="0.25.1" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="SharpRaven" Version="2.4.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.7.0" />