1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-23 22:53:04 +08:00

Merge branch 'master' into feature/command-handler

This commit is contained in:
Marvin Schürz 2024-10-10 15:27:09 +02:00
commit 597396d64a
18 changed files with 246 additions and 77 deletions

View File

@ -10,7 +10,7 @@
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk> <EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.1007.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2024.1009.0" />
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>
<!-- Fody does not handle Android build well, and warns when unchanged. <!-- Fody does not handle Android build well, and warns when unchanged.

View File

@ -24,7 +24,7 @@
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" /> <ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="System.IO.Packaging" Version="8.0.0" /> <PackageReference Include="System.IO.Packaging" Version="8.0.1" />
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24" /> <PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />
<PackageReference Include="Velopack" Version="0.0.630-g9c52e40" /> <PackageReference Include="Velopack" Version="0.0.630-g9c52e40" />
</ItemGroup> </ItemGroup>

View File

@ -0,0 +1,126 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Edit;
using osuTK;
using osuTK.Input;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints
{
public partial class GridPlacementBlueprint : PlacementBlueprint
{
[Resolved]
private HitObjectComposer? hitObjectComposer { get; set; }
private OsuGridToolboxGroup gridToolboxGroup = null!;
private Vector2 originalOrigin;
private float originalSpacing;
private float originalRotation;
[BackgroundDependencyLoader]
private void load(OsuGridToolboxGroup gridToolboxGroup)
{
this.gridToolboxGroup = gridToolboxGroup;
originalOrigin = gridToolboxGroup.StartPosition.Value;
originalSpacing = gridToolboxGroup.Spacing.Value;
originalRotation = gridToolboxGroup.GridLinesRotation.Value;
}
public override void EndPlacement(bool commit)
{
if (!commit && PlacementActive != PlacementState.Finished)
{
gridToolboxGroup.StartPosition.Value = originalOrigin;
gridToolboxGroup.Spacing.Value = originalSpacing;
if (!gridToolboxGroup.GridLinesRotation.Disabled)
gridToolboxGroup.GridLinesRotation.Value = originalRotation;
}
base.EndPlacement(commit);
// You typically only place the grid once, so we switch back to the last tool after placement.
if (commit && hitObjectComposer is OsuHitObjectComposer osuHitObjectComposer)
osuHitObjectComposer.SetLastTool();
}
protected override bool OnClick(ClickEvent e)
{
if (e.Button == MouseButton.Left)
{
switch (PlacementActive)
{
case PlacementState.Waiting:
BeginPlacement(true);
return true;
case PlacementState.Active:
EndPlacement(true);
return true;
}
}
return base.OnClick(e);
}
protected override bool OnMouseDown(MouseDownEvent e)
{
if (e.Button == MouseButton.Right)
{
// Reset the grid to the default values.
gridToolboxGroup.StartPosition.Value = gridToolboxGroup.StartPosition.Default;
gridToolboxGroup.Spacing.Value = gridToolboxGroup.Spacing.Default;
if (!gridToolboxGroup.GridLinesRotation.Disabled)
gridToolboxGroup.GridLinesRotation.Value = gridToolboxGroup.GridLinesRotation.Default;
EndPlacement(true);
return true;
}
return base.OnMouseDown(e);
}
protected override bool OnDragStart(DragStartEvent e)
{
if (e.Button == MouseButton.Left)
{
BeginPlacement(true);
return true;
}
return base.OnDragStart(e);
}
protected override void OnDragEnd(DragEndEvent e)
{
if (PlacementActive == PlacementState.Active)
EndPlacement(true);
base.OnDragEnd(e);
}
public override SnapType SnapType => ~SnapType.GlobalGrids;
public override void UpdateTimeAndPosition(SnapResult result)
{
var pos = ToLocalSpace(result.ScreenSpacePosition);
if (PlacementActive != PlacementState.Active)
gridToolboxGroup.StartPosition.Value = pos;
else
{
// Default to the original spacing and rotation if the distance is too small.
if (Vector2.Distance(gridToolboxGroup.StartPosition.Value, pos) < 2)
{
gridToolboxGroup.Spacing.Value = originalSpacing;
if (!gridToolboxGroup.GridLinesRotation.Disabled)
gridToolboxGroup.GridLinesRotation.Value = originalRotation;
}
else
{
gridToolboxGroup.SetGridFromPoints(gridToolboxGroup.StartPosition.Value, pos);
}
}
}
}
}

View File

@ -180,7 +180,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
private bool isSplittable(PathControlPointPiece<T> p) => private bool isSplittable(PathControlPointPiece<T> p) =>
// A hit object can only be split on control points which connect two different path segments. // A hit object can only be split on control points which connect two different path segments.
p.ControlPoint.Type.HasValue && p != Pieces.FirstOrDefault() && p != Pieces.LastOrDefault(); p.ControlPoint.Type.HasValue && p.ControlPoint != controlPoints.FirstOrDefault() && p.ControlPoint != controlPoints.LastOrDefault();
private void onControlPointsChanged(object sender, NotifyCollectionChangedEventArgs e) private void onControlPointsChanged(object sender, NotifyCollectionChangedEventArgs e)
{ {

View File

@ -0,0 +1,29 @@
// 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.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Osu.Edit.Blueprints;
namespace osu.Game.Rulesets.Osu.Edit
{
public partial class GridFromPointsTool : CompositionTool
{
public GridFromPointsTool()
: base("Grid")
{
TooltipText = """
Left click to set the origin.
Left click again to set the spacing and rotation.
Right click to reset to default.
Click and drag to set the origin, spacing and rotation.
""";
}
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.Solid.DraftingCompass };
public override PlacementBlueprint CreatePlacementBlueprint() => new GridPlacementBlueprint();
}
}

View File

@ -11,12 +11,10 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Utils;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Input.Bindings; using osu.Game.Input.Bindings;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Osu.UI;
using osu.Game.Screens.Edit; using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Components.RadioButtons; using osu.Game.Screens.Edit.Components.RadioButtons;
@ -40,7 +38,6 @@ namespace osu.Game.Rulesets.Osu.Edit
{ {
MinValue = 0f, MinValue = 0f,
MaxValue = OsuPlayfield.BASE_SIZE.X, MaxValue = OsuPlayfield.BASE_SIZE.X,
Precision = 1f
}; };
/// <summary> /// <summary>
@ -50,7 +47,6 @@ namespace osu.Game.Rulesets.Osu.Edit
{ {
MinValue = 0f, MinValue = 0f,
MaxValue = OsuPlayfield.BASE_SIZE.Y, MaxValue = OsuPlayfield.BASE_SIZE.Y,
Precision = 1f
}; };
/// <summary> /// <summary>
@ -60,7 +56,6 @@ namespace osu.Game.Rulesets.Osu.Edit
{ {
MinValue = 4f, MinValue = 4f,
MaxValue = 128f, MaxValue = 128f,
Precision = 1f
}; };
/// <summary> /// <summary>
@ -70,14 +65,13 @@ namespace osu.Game.Rulesets.Osu.Edit
{ {
MinValue = -180f, MinValue = -180f,
MaxValue = 180f, MaxValue = 180f,
Precision = 1f
}; };
/// <summary> /// <summary>
/// Read-only bindable representing the grid's origin. /// Read-only bindable representing the grid's origin.
/// Equivalent to <code>new Vector2(StartPositionX, StartPositionY)</code> /// Equivalent to <code>new Vector2(StartPositionX, StartPositionY)</code>
/// </summary> /// </summary>
public Bindable<Vector2> StartPosition { get; } = new Bindable<Vector2>(); public Bindable<Vector2> StartPosition { get; } = new Bindable<Vector2>(OsuPlayfield.BASE_SIZE / 2);
/// <summary> /// <summary>
/// Read-only bindable representing the grid's spacing in both the X and Y dimension. /// Read-only bindable representing the grid's spacing in both the X and Y dimension.
@ -93,8 +87,6 @@ namespace osu.Game.Rulesets.Osu.Edit
private ExpandableSlider<float> gridLinesRotationSlider = null!; private ExpandableSlider<float> gridLinesRotationSlider = null!;
private EditorRadioButtonCollection gridTypeButtons = null!; private EditorRadioButtonCollection gridTypeButtons = null!;
private ExpandableButton useSelectedObjectPositionButton = null!;
public OsuGridToolboxGroup() public OsuGridToolboxGroup()
: base("grid") : base("grid")
{ {
@ -102,6 +94,26 @@ namespace osu.Game.Rulesets.Osu.Edit
private const float max_automatic_spacing = 64; private const float max_automatic_spacing = 64;
public void SetGridFromPoints(Vector2 point1, Vector2 point2)
{
StartPositionX.Value = point1.X;
StartPositionY.Value = point1.Y;
// Get the angle between the two points and normalize to the valid range.
if (!GridLinesRotation.Disabled)
{
float period = GridLinesRotation.MaxValue - GridLinesRotation.MinValue;
GridLinesRotation.Value = normalizeRotation(MathHelper.RadiansToDegrees(MathF.Atan2(point2.Y - point1.Y, point2.X - point1.X)), period);
}
// Divide the distance so that there is a good density of grid lines.
// This matches the maximum grid size of the grid size cycling hotkey.
float dist = Vector2.Distance(point1, point2);
while (dist >= max_automatic_spacing)
dist /= 2;
Spacing.Value = dist;
}
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
@ -117,20 +129,6 @@ namespace osu.Game.Rulesets.Osu.Edit
Current = StartPositionY, Current = StartPositionY,
KeyboardStep = 1, KeyboardStep = 1,
}, },
useSelectedObjectPositionButton = new ExpandableButton
{
ExpandedLabelText = "Centre on selected object",
Action = () =>
{
if (editorBeatmap.SelectedHitObjects.Count != 1)
return;
var position = ((IHasPosition)editorBeatmap.SelectedHitObjects.Single()).Position;
StartPosition.Value = new Vector2(MathF.Round(position.X), MathF.Round(position.Y));
updateEnabledStates();
},
RelativeSizeAxes = Axes.X,
},
spacingSlider = new ExpandableSlider<float> spacingSlider = new ExpandableSlider<float>
{ {
Current = Spacing, Current = Spacing,
@ -179,15 +177,15 @@ namespace osu.Game.Rulesets.Osu.Edit
StartPositionX.BindValueChanged(x => StartPositionX.BindValueChanged(x =>
{ {
startPositionXSlider.ContractedLabelText = $"X: {x.NewValue:N0}"; startPositionXSlider.ContractedLabelText = $"X: {x.NewValue:#,0.##}";
startPositionXSlider.ExpandedLabelText = $"X Offset: {x.NewValue:N0}"; startPositionXSlider.ExpandedLabelText = $"X Offset: {x.NewValue:#,0.##}";
StartPosition.Value = new Vector2(x.NewValue, StartPosition.Value.Y); StartPosition.Value = new Vector2(x.NewValue, StartPosition.Value.Y);
}, true); }, true);
StartPositionY.BindValueChanged(y => StartPositionY.BindValueChanged(y =>
{ {
startPositionYSlider.ContractedLabelText = $"Y: {y.NewValue:N0}"; startPositionYSlider.ContractedLabelText = $"Y: {y.NewValue:#,0.##}";
startPositionYSlider.ExpandedLabelText = $"Y Offset: {y.NewValue:N0}"; startPositionYSlider.ExpandedLabelText = $"Y Offset: {y.NewValue:#,0.##}";
StartPosition.Value = new Vector2(StartPosition.Value.X, y.NewValue); StartPosition.Value = new Vector2(StartPosition.Value.X, y.NewValue);
}, true); }, true);
@ -195,13 +193,12 @@ namespace osu.Game.Rulesets.Osu.Edit
{ {
StartPositionX.Value = pos.NewValue.X; StartPositionX.Value = pos.NewValue.X;
StartPositionY.Value = pos.NewValue.Y; StartPositionY.Value = pos.NewValue.Y;
updateEnabledStates();
}); });
Spacing.BindValueChanged(spacing => Spacing.BindValueChanged(spacing =>
{ {
spacingSlider.ContractedLabelText = $"S: {spacing.NewValue:N0}"; spacingSlider.ContractedLabelText = $"S: {spacing.NewValue:#,0.##}";
spacingSlider.ExpandedLabelText = $"Spacing: {spacing.NewValue:N0}"; spacingSlider.ExpandedLabelText = $"Spacing: {spacing.NewValue:#,0.##}";
SpacingVector.Value = new Vector2(spacing.NewValue); SpacingVector.Value = new Vector2(spacing.NewValue);
editorBeatmap.BeatmapInfo.GridSize = (int)spacing.NewValue; editorBeatmap.BeatmapInfo.GridSize = (int)spacing.NewValue;
}, true); }, true);
@ -219,34 +216,29 @@ namespace osu.Game.Rulesets.Osu.Edit
switch (v.NewValue) switch (v.NewValue)
{ {
case PositionSnapGridType.Square: case PositionSnapGridType.Square:
GridLinesRotation.Value = ((GridLinesRotation.Value + 405) % 90) - 45; GridLinesRotation.Value = normalizeRotation(GridLinesRotation.Value, 90);
GridLinesRotation.MinValue = -45; GridLinesRotation.MinValue = -45;
GridLinesRotation.MaxValue = 45; GridLinesRotation.MaxValue = 45;
break; break;
case PositionSnapGridType.Triangle: case PositionSnapGridType.Triangle:
GridLinesRotation.Value = ((GridLinesRotation.Value + 390) % 60) - 30; GridLinesRotation.Value = normalizeRotation(GridLinesRotation.Value, 60);
GridLinesRotation.MinValue = -30; GridLinesRotation.MinValue = -30;
GridLinesRotation.MaxValue = 30; GridLinesRotation.MaxValue = 30;
break; break;
} }
}, true); }, true);
editorBeatmap.BeatmapReprocessed += updateEnabledStates;
editorBeatmap.SelectedHitObjects.BindCollectionChanged((_, _) => updateEnabledStates());
expandingContainer?.Expanded.BindValueChanged(v => expandingContainer?.Expanded.BindValueChanged(v =>
{ {
gridTypeButtons.FadeTo(v.NewValue ? 1f : 0f, 500, Easing.OutQuint); gridTypeButtons.FadeTo(v.NewValue ? 1f : 0f, 500, Easing.OutQuint);
gridTypeButtons.BypassAutoSizeAxes = !v.NewValue ? Axes.Y : Axes.None; gridTypeButtons.BypassAutoSizeAxes = !v.NewValue ? Axes.Y : Axes.None;
updateEnabledStates();
}, true); }, true);
} }
private void updateEnabledStates() private float normalizeRotation(float rotation, float period)
{ {
useSelectedObjectPositionButton.Enabled.Value = expandingContainer?.Expanded.Value == true return ((rotation + 360 + period * 0.5f) % period) - period * 0.5f;
&& editorBeatmap.SelectedHitObjects.Count == 1
&& !Precision.AlmostEquals(StartPosition.Value, ((IHasPosition)editorBeatmap.SelectedHitObjects.Single()).Position, 0.5f);
} }
private void nextGridSize() private void nextGridSize()

View File

@ -45,7 +45,8 @@ namespace osu.Game.Rulesets.Osu.Edit
{ {
new HitCircleCompositionTool(), new HitCircleCompositionTool(),
new SliderCompositionTool(), new SliderCompositionTool(),
new SpinnerCompositionTool() new SpinnerCompositionTool(),
new GridFromPointsTool()
}; };
private readonly Bindable<TernaryState> rectangularGridSnapToggle = new Bindable<TernaryState>(); private readonly Bindable<TernaryState> rectangularGridSnapToggle = new Bindable<TernaryState>();
@ -79,13 +80,12 @@ namespace osu.Game.Rulesets.Osu.Edit
// Give a bit of breathing room around the playfield content. // Give a bit of breathing room around the playfield content.
PlayfieldContentContainer.Padding = new MarginPadding(10); PlayfieldContentContainer.Padding = new MarginPadding(10);
LayerBelowRuleset.AddRange(new Drawable[] LayerBelowRuleset.Add(
{
distanceSnapGridContainer = new Container distanceSnapGridContainer = new Container
{ {
RelativeSizeAxes = Axes.Both RelativeSizeAxes = Axes.Both
} }
}); );
selectedHitObjects = EditorBeatmap.SelectedHitObjects.GetBoundCopy(); selectedHitObjects = EditorBeatmap.SelectedHitObjects.GetBoundCopy();
selectedHitObjects.CollectionChanged += (_, _) => updateDistanceSnapGrid(); selectedHitObjects.CollectionChanged += (_, _) => updateDistanceSnapGrid();

View File

@ -44,7 +44,7 @@ namespace osu.Game.Database
{ {
if (changes == null) if (changes == null)
{ {
if (detachedBeatmapSets.Count > 0 && sender.Count == 0) if (sender is RealmResetEmptySet<BeatmapSetInfo>)
{ {
// Usually we'd reset stuff here, but doing so triggers a silly flow which ends up deadlocking realm. // Usually we'd reset stuff here, but doing so triggers a silly flow which ends up deadlocking realm.
// Additionally, user should not be at song select when realm is blocking all operations in the first place. // Additionally, user should not be at song select when realm is blocking all operations in the first place.

View File

@ -568,7 +568,7 @@ namespace osu.Game.Database
lock (notificationsResetMap) lock (notificationsResetMap)
{ {
// Store an action which is used when blocking to ensure consumers don't use results of a stale changeset firing. // Store an action which is used when blocking to ensure consumers don't use results of a stale changeset firing.
notificationsResetMap.Add(action, () => callback(new EmptyRealmSet<T>(), null)); notificationsResetMap.Add(action, () => callback(new RealmResetEmptySet<T>(), null));
} }
return RegisterCustomSubscription(action); return RegisterCustomSubscription(action);

View File

@ -12,7 +12,13 @@ using Realms.Schema;
namespace osu.Game.Database namespace osu.Game.Database
{ {
public class EmptyRealmSet<T> : IRealmCollection<T> /// <summary>
/// This can arrive in <see cref="RealmAccess.RegisterForNotifications{T}"/> callbacks to imply that realm access has been reset.
/// </summary>
/// <remarks>
/// Usually implies that the original database may return soon and the callback can usually be silently ignored.
///</remarks>
public class RealmResetEmptySet<T> : IRealmCollection<T>
{ {
private IList<T> emptySet => Array.Empty<T>(); private IList<T> emptySet => Array.Empty<T>();

View File

@ -541,7 +541,10 @@ namespace osu.Game
realmBlocker = realm.BlockAllOperations("migration"); realmBlocker = realm.BlockAllOperations("migration");
success = true; success = true;
} }
catch { } catch (Exception ex)
{
Logger.Log($"Attempting to block all operations failed: {ex}", LoggingTarget.Database);
}
readyToRun.Set(); readyToRun.Set();
}, false); }, false);

View File

@ -72,7 +72,7 @@ namespace osu.Game.Overlays
private AudioFilter audioDuckFilter = null!; private AudioFilter audioDuckFilter = null!;
private readonly Bindable<RandomSelectAlgorithm> randomSelectAlgorithm = new Bindable<RandomSelectAlgorithm>(); private readonly Bindable<RandomSelectAlgorithm> randomSelectAlgorithm = new Bindable<RandomSelectAlgorithm>();
private readonly List<BeatmapSetInfo> previousRandomSets = new List<BeatmapSetInfo>(); private readonly List<Live<BeatmapSetInfo>> previousRandomSets = new List<Live<BeatmapSetInfo>>();
private int randomHistoryDirection; private int randomHistoryDirection;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -249,19 +249,19 @@ namespace osu.Game.Overlays
queuedDirection = TrackChangeDirection.Prev; queuedDirection = TrackChangeDirection.Prev;
BeatmapSetInfo? playableSet; Live<BeatmapSetInfo>? playableSet;
if (Shuffle.Value) if (Shuffle.Value)
playableSet = getNextRandom(-1, allowProtectedTracks); playableSet = getNextRandom(-1, allowProtectedTracks);
else else
{ {
playableSet = getBeatmapSets().AsEnumerable().TakeWhile(i => !i.Equals(current?.BeatmapSetInfo)).LastOrDefault(s => !s.Protected || allowProtectedTracks) playableSet = getBeatmapSets().TakeWhile(i => !i.Value.Equals(current?.BeatmapSetInfo)).LastOrDefault(s => !s.Value.Protected || allowProtectedTracks)
?? getBeatmapSets().AsEnumerable().LastOrDefault(s => !s.Protected || allowProtectedTracks); ?? getBeatmapSets().LastOrDefault(s => !s.Value.Protected || allowProtectedTracks);
} }
if (playableSet != null) if (playableSet != null)
{ {
changeBeatmap(beatmaps.GetWorkingBeatmap(playableSet.Beatmaps.First())); changeBeatmap(beatmaps.GetWorkingBeatmap(playableSet.Value.Beatmaps.First()));
restartTrack(); restartTrack();
return PreviousTrackResult.Previous; return PreviousTrackResult.Previous;
} }
@ -345,19 +345,19 @@ namespace osu.Game.Overlays
queuedDirection = TrackChangeDirection.Next; queuedDirection = TrackChangeDirection.Next;
BeatmapSetInfo? playableSet; Live<BeatmapSetInfo>? playableSet;
if (Shuffle.Value) if (Shuffle.Value)
playableSet = getNextRandom(1, allowProtectedTracks); playableSet = getNextRandom(1, allowProtectedTracks);
else else
{ {
playableSet = getBeatmapSets().AsEnumerable().SkipWhile(i => !i.Equals(current?.BeatmapSetInfo)) playableSet = getBeatmapSets().SkipWhile(i => !i.Value.Equals(current?.BeatmapSetInfo))
.Where(i => !i.Protected || allowProtectedTracks) .Where(i => !i.Value.Protected || allowProtectedTracks)
.ElementAtOrDefault(1) .ElementAtOrDefault(1)
?? getBeatmapSets().AsEnumerable().FirstOrDefault(i => !i.Protected || allowProtectedTracks); ?? getBeatmapSets().FirstOrDefault(i => !i.Value.Protected || allowProtectedTracks);
} }
var playableBeatmap = playableSet?.Beatmaps.FirstOrDefault(); var playableBeatmap = playableSet?.Value.Beatmaps.FirstOrDefault();
if (playableBeatmap != null) if (playableBeatmap != null)
{ {
@ -369,11 +369,11 @@ namespace osu.Game.Overlays
return false; return false;
} }
private BeatmapSetInfo? getNextRandom(int direction, bool allowProtectedTracks) private Live<BeatmapSetInfo>? getNextRandom(int direction, bool allowProtectedTracks)
{ {
BeatmapSetInfo result; Live<BeatmapSetInfo> result;
var possibleSets = getBeatmapSets().AsEnumerable().Where(s => !s.Protected || allowProtectedTracks).ToArray(); var possibleSets = getBeatmapSets().Where(s => !s.Value.Protected || allowProtectedTracks).ToArray();
if (possibleSets.Length == 0) if (possibleSets.Length == 0)
return null; return null;
@ -432,7 +432,9 @@ namespace osu.Game.Overlays
private TrackChangeDirection? queuedDirection; private TrackChangeDirection? queuedDirection;
private IQueryable<BeatmapSetInfo> getBeatmapSets() => realm.Realm.All<BeatmapSetInfo>().Where(s => !s.DeletePending); private IEnumerable<Live<BeatmapSetInfo>> getBeatmapSets() => realm.Realm.All<BeatmapSetInfo>().Where(s => !s.DeletePending)
.AsEnumerable()
.Select(s => new RealmLive<BeatmapSetInfo>(s, realm));
private void changeBeatmap(WorkingBeatmap newWorking) private void changeBeatmap(WorkingBeatmap newWorking)
{ {
@ -459,8 +461,8 @@ namespace osu.Game.Overlays
else else
{ {
// figure out the best direction based on order in playlist. // figure out the best direction based on order in playlist.
int last = getBeatmapSets().AsEnumerable().TakeWhile(b => !b.Equals(current.BeatmapSetInfo)).Count(); int last = getBeatmapSets().TakeWhile(b => !b.Value.Equals(current.BeatmapSetInfo)).Count();
int next = getBeatmapSets().AsEnumerable().TakeWhile(b => !b.Equals(newWorking.BeatmapSetInfo)).Count(); int next = getBeatmapSets().TakeWhile(b => !b.Value.Equals(newWorking.BeatmapSetInfo)).Count();
direction = last > next ? TrackChangeDirection.Prev : TrackChangeDirection.Next; direction = last > next ? TrackChangeDirection.Prev : TrackChangeDirection.Next;
} }

View File

@ -20,13 +20,13 @@ namespace osu.Game.Overlays.Settings
Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS, Right = SettingsPanel.CONTENT_MARGINS }; Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS, Right = SettingsPanel.CONTENT_MARGINS };
} }
public LocalisableString TooltipText { get; set; }
public IEnumerable<string> Keywords { get; set; } = Array.Empty<string>(); public IEnumerable<string> Keywords { get; set; } = Array.Empty<string>();
public BindableBool CanBeShown { get; } = new BindableBool(true); public BindableBool CanBeShown { get; } = new BindableBool(true);
IBindable<bool> IConditionalFilterable.CanBeShown => CanBeShown; IBindable<bool> IConditionalFilterable.CanBeShown => CanBeShown;
public LocalisableString TooltipText { get; set; }
public override IEnumerable<LocalisableString> FilterTerms public override IEnumerable<LocalisableString> FilterTerms
{ {
get get

View File

@ -91,6 +91,9 @@ namespace osu.Game.Rulesets.Edit
private Bindable<bool> autoSeekOnPlacement; private Bindable<bool> autoSeekOnPlacement;
private readonly Bindable<bool> composerFocusMode = new Bindable<bool>(); private readonly Bindable<bool> composerFocusMode = new Bindable<bool>();
[CanBeNull]
private RadioButton lastTool;
protected DrawableRuleset<TObject> DrawableRuleset { get; private set; } protected DrawableRuleset<TObject> DrawableRuleset { get; private set; }
protected HitObjectComposer(Ruleset ruleset) protected HitObjectComposer(Ruleset ruleset)
@ -214,8 +217,7 @@ namespace osu.Game.Rulesets.Edit
}, },
}; };
toolboxCollection.Items = CompositionTools toolboxCollection.Items = (CompositionTools.Prepend(new SelectTool()))
.Prepend(new SelectTool())
.Select(t => new HitObjectCompositionToolButton(t, () => toolSelected(t))) .Select(t => new HitObjectCompositionToolButton(t, () => toolSelected(t)))
.ToList(); .ToList();
@ -232,7 +234,7 @@ namespace osu.Game.Rulesets.Edit
sampleBankTogglesCollection.AddRange(BlueprintContainer.SampleBankTernaryStates.Select(b => new DrawableTernaryButton(b))); sampleBankTogglesCollection.AddRange(BlueprintContainer.SampleBankTernaryStates.Select(b => new DrawableTernaryButton(b)));
setSelectTool(); SetSelectTool();
EditorBeatmap.SelectedHitObjects.CollectionChanged += selectionChanged; EditorBeatmap.SelectedHitObjects.CollectionChanged += selectionChanged;
} }
@ -257,7 +259,7 @@ namespace osu.Game.Rulesets.Edit
{ {
// it's important this is performed before the similar code in EditorRadioButton disables the button. // it's important this is performed before the similar code in EditorRadioButton disables the button.
if (!timing.NewValue) if (!timing.NewValue)
setSelectTool(); SetSelectTool();
}); });
EditorBeatmap.HasTiming.BindValueChanged(hasTiming => EditorBeatmap.HasTiming.BindValueChanged(hasTiming =>
@ -461,14 +463,18 @@ namespace osu.Game.Rulesets.Edit
if (EditorBeatmap.SelectedHitObjects.Any()) if (EditorBeatmap.SelectedHitObjects.Any())
{ {
// ensure in selection mode if a selection is made. // ensure in selection mode if a selection is made.
setSelectTool(); SetSelectTool();
} }
} }
private void setSelectTool() => toolboxCollection.Items.First().Select(); public void SetSelectTool() => toolboxCollection.Items.First().Select();
public void SetLastTool() => (lastTool ?? toolboxCollection.Items.First()).Select();
private void toolSelected(CompositionTool tool) private void toolSelected(CompositionTool tool)
{ {
lastTool = toolboxCollection.Items.OfType<HitObjectCompositionToolButton>().FirstOrDefault(i => i.Tool == BlueprintContainer.CurrentTool);
BlueprintContainer.CurrentTool = tool; BlueprintContainer.CurrentTool = tool;
if (!(tool is SelectTool)) if (!(tool is SelectTool))

View File

@ -71,6 +71,11 @@ namespace osu.Game.Rulesets.Edit
PlacementActive = PlacementState.Finished; PlacementActive = PlacementState.Finished;
} }
/// <summary>
/// Determines which objects to snap to for the snap result in <see cref="UpdateTimeAndPosition"/>.
/// </summary>
public virtual SnapType SnapType => SnapType.All;
/// <summary> /// <summary>
/// Updates the time and position of this <see cref="PlacementBlueprint"/> based on the provided snap information. /// Updates the time and position of this <see cref="PlacementBlueprint"/> based on the provided snap information.
/// </summary> /// </summary>

View File

@ -297,7 +297,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
private void updatePlacementPosition() private void updatePlacementPosition()
{ {
var snapResult = Composer.FindSnappedPositionAndTime(InputManager.CurrentState.Mouse.Position); var snapResult = Composer.FindSnappedPositionAndTime(InputManager.CurrentState.Mouse.Position, CurrentPlacement.SnapType);
// if no time was found from positional snapping, we should still quantize to the beat. // if no time was found from positional snapping, we should still quantize to the beat.
snapResult.Time ??= Beatmap.SnapTime(EditorClock.CurrentTime, null); snapResult.Time ??= Beatmap.SnapTime(EditorClock.CurrentTime, null);

View File

@ -35,7 +35,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Realm" Version="11.5.0" /> <PackageReference Include="Realm" Version="11.5.0" />
<PackageReference Include="ppy.osu.Framework" Version="2024.1007.0" /> <PackageReference Include="ppy.osu.Framework" Version="2024.1009.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2024.1003.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2024.1003.0" />
<PackageReference Include="Sentry" Version="4.3.0" /> <PackageReference Include="Sentry" Version="4.3.0" />
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. --> <!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->

View File

@ -17,6 +17,6 @@
<MtouchInterpreter>-all</MtouchInterpreter> <MtouchInterpreter>-all</MtouchInterpreter>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Framework.iOS" Version="2024.1007.0" /> <PackageReference Include="ppy.osu.Framework.iOS" Version="2024.1009.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>