mirror of
https://github.com/ppy/osu.git
synced 2025-01-21 05:22:55 +08:00
Merge branch 'master' into fix-romanised-searching
This commit is contained in:
commit
048e3c2505
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@ -135,5 +135,8 @@ jobs:
|
|||||||
- name: Install .NET Workloads
|
- name: Install .NET Workloads
|
||||||
run: dotnet workload install maui-ios
|
run: dotnet workload install maui-ios
|
||||||
|
|
||||||
|
- name: Select Xcode 16
|
||||||
|
run: sudo xcode-select -s /Applications/Xcode_16.app/Contents/Developer
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: dotnet build -c Debug osu.iOS
|
run: dotnet build -c Debug osu.iOS
|
||||||
|
@ -66,7 +66,7 @@ namespace osu.Desktop.Updater
|
|||||||
{
|
{
|
||||||
Activated = () =>
|
Activated = () =>
|
||||||
{
|
{
|
||||||
restartToApplyUpdate();
|
Task.Run(restartToApplyUpdate);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -88,7 +88,11 @@ namespace osu.Desktop.Updater
|
|||||||
{
|
{
|
||||||
notification = new UpdateProgressNotification
|
notification = new UpdateProgressNotification
|
||||||
{
|
{
|
||||||
CompletionClickAction = restartToApplyUpdate,
|
CompletionClickAction = () =>
|
||||||
|
{
|
||||||
|
Task.Run(restartToApplyUpdate);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
Schedule(() => notificationOverlay.Post(notification));
|
Schedule(() => notificationOverlay.Post(notification));
|
||||||
@ -127,13 +131,10 @@ namespace osu.Desktop.Updater
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool restartToApplyUpdate()
|
private async Task restartToApplyUpdate()
|
||||||
{
|
{
|
||||||
// TODO: Migrate this to async flow whenever available (see https://github.com/ppy/osu/pull/28743#discussion_r1740505665).
|
await updateManager.WaitExitThenApplyUpdatesAsync(pendingUpdate?.TargetFullRelease).ConfigureAwait(false);
|
||||||
// Currently there's an internal Thread.Sleep(300) which will cause a stutter when the user clicks to restart.
|
|
||||||
updateManager.WaitExitThenApplyUpdates(pendingUpdate?.TargetFullRelease);
|
|
||||||
Schedule(() => game.AttemptExit());
|
Schedule(() => game.AttemptExit());
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
<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.0" />
|
||||||
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />
|
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />
|
||||||
<PackageReference Include="Velopack" Version="0.0.598-g933b2ab" />
|
<PackageReference Include="Velopack" Version="0.0.630-g9c52e40" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Resources">
|
<ItemGroup Label="Resources">
|
||||||
<EmbeddedResource Include="lazer.ico" />
|
<EmbeddedResource Include="lazer.ico" />
|
||||||
|
@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
|||||||
contentContainer.Playfield.HitObjectContainer.Add(hitObject);
|
contentContainer.Playfield.HitObjectContainer.Add(hitObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override SnapResult SnapForBlueprint(PlacementBlueprint blueprint)
|
protected override SnapResult SnapForBlueprint(HitObjectPlacementBlueprint blueprint)
|
||||||
{
|
{
|
||||||
var result = base.SnapForBlueprint(blueprint);
|
var result = base.SnapForBlueprint(blueprint);
|
||||||
result.Time = Math.Round(HitObjectContainer.TimeAtScreenSpacePosition(result.ScreenSpacePosition) / TIME_SNAP) * TIME_SNAP;
|
result.Time = Math.Round(HitObjectContainer.TimeAtScreenSpacePosition(result.ScreenSpacePosition) / TIME_SNAP) * TIME_SNAP;
|
||||||
|
@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
|||||||
{
|
{
|
||||||
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableBananaShower((BananaShower)hitObject);
|
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableBananaShower((BananaShower)hitObject);
|
||||||
|
|
||||||
protected override PlacementBlueprint CreateBlueprint() => new BananaShowerPlacementBlueprint();
|
protected override HitObjectPlacementBlueprint CreateBlueprint() => new BananaShowerPlacementBlueprint();
|
||||||
|
|
||||||
protected override void AddHitObject(DrawableHitObject hitObject)
|
protected override void AddHitObject(DrawableHitObject hitObject)
|
||||||
{
|
{
|
||||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
|||||||
{
|
{
|
||||||
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableFruit((Fruit)hitObject);
|
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableFruit((Fruit)hitObject);
|
||||||
|
|
||||||
protected override PlacementBlueprint CreateBlueprint() => new FruitPlacementBlueprint();
|
protected override HitObjectPlacementBlueprint CreateBlueprint() => new FruitPlacementBlueprint();
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestFruitPlacementPosition()
|
public void TestFruitPlacementPosition()
|
||||||
|
@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
|||||||
|
|
||||||
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableJuiceStream((JuiceStream)hitObject);
|
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableJuiceStream((JuiceStream)hitObject);
|
||||||
|
|
||||||
protected override PlacementBlueprint CreateBlueprint() => new JuiceStreamPlacementBlueprint();
|
protected override HitObjectPlacementBlueprint CreateBlueprint() => new JuiceStreamPlacementBlueprint();
|
||||||
|
|
||||||
private void addMoveAndClickSteps(double time, float position, bool end = false)
|
private void addMoveAndClickSteps(double time, float position, bool end = false)
|
||||||
{
|
{
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Catch.Beatmaps;
|
using osu.Game.Rulesets.Catch.Beatmaps;
|
||||||
using osu.Game.Rulesets.Catch.Difficulty.Preprocessing;
|
using osu.Game.Rulesets.Catch.Difficulty.Preprocessing;
|
||||||
@ -44,7 +43,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
|||||||
StarRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier,
|
StarRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier,
|
||||||
Mods = mods,
|
Mods = mods,
|
||||||
ApproachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0,
|
ApproachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0,
|
||||||
MaxCombo = beatmap.HitObjects.Count(h => h is Fruit) + beatmap.HitObjects.OfType<JuiceStream>().SelectMany(j => j.NestedHitObjects).Count(h => !(h is TinyDroplet)),
|
MaxCombo = beatmap.GetMaxCombo(),
|
||||||
};
|
};
|
||||||
|
|
||||||
return attributes;
|
return attributes;
|
||||||
|
@ -10,7 +10,7 @@ using osu.Game.Rulesets.Edit.Tools;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Edit
|
namespace osu.Game.Rulesets.Catch.Edit
|
||||||
{
|
{
|
||||||
public class BananaShowerCompositionTool : HitObjectCompositionTool
|
public class BananaShowerCompositionTool : CompositionTool
|
||||||
{
|
{
|
||||||
public BananaShowerCompositionTool()
|
public BananaShowerCompositionTool()
|
||||||
: base(nameof(BananaShower))
|
: base(nameof(BananaShower))
|
||||||
@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
|
|
||||||
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners);
|
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners);
|
||||||
|
|
||||||
public override PlacementBlueprint CreatePlacementBlueprint() => new BananaShowerPlacementBlueprint();
|
public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new BananaShowerPlacementBlueprint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ using osu.Game.Rulesets.UI.Scrolling;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
||||||
{
|
{
|
||||||
public partial class CatchPlacementBlueprint<THitObject> : PlacementBlueprint
|
public partial class CatchPlacementBlueprint<THitObject> : HitObjectPlacementBlueprint
|
||||||
where THitObject : CatchHitObject, new()
|
where THitObject : CatchHitObject, new()
|
||||||
{
|
{
|
||||||
protected new THitObject HitObject => (THitObject)base.HitObject;
|
protected new THitObject HitObject => (THitObject)base.HitObject;
|
||||||
|
@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
|
|
||||||
protected override BeatSnapGrid CreateBeatSnapGrid() => new CatchBeatSnapGrid();
|
protected override BeatSnapGrid CreateBeatSnapGrid() => new CatchBeatSnapGrid();
|
||||||
|
|
||||||
protected override IReadOnlyList<HitObjectCompositionTool> CompositionTools => new HitObjectCompositionTool[]
|
protected override IReadOnlyList<CompositionTool> CompositionTools => new CompositionTool[]
|
||||||
{
|
{
|
||||||
new FruitCompositionTool(),
|
new FruitCompositionTool(),
|
||||||
new JuiceStreamCompositionTool(),
|
new JuiceStreamCompositionTool(),
|
||||||
|
@ -10,7 +10,7 @@ using osu.Game.Rulesets.Edit.Tools;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Edit
|
namespace osu.Game.Rulesets.Catch.Edit
|
||||||
{
|
{
|
||||||
public class FruitCompositionTool : HitObjectCompositionTool
|
public class FruitCompositionTool : CompositionTool
|
||||||
{
|
{
|
||||||
public FruitCompositionTool()
|
public FruitCompositionTool()
|
||||||
: base(nameof(Fruit))
|
: base(nameof(Fruit))
|
||||||
@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
|
|
||||||
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles);
|
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles);
|
||||||
|
|
||||||
public override PlacementBlueprint CreatePlacementBlueprint() => new FruitPlacementBlueprint();
|
public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new FruitPlacementBlueprint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ using osu.Game.Rulesets.Edit.Tools;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Edit
|
namespace osu.Game.Rulesets.Catch.Edit
|
||||||
{
|
{
|
||||||
public class JuiceStreamCompositionTool : HitObjectCompositionTool
|
public class JuiceStreamCompositionTool : CompositionTool
|
||||||
{
|
{
|
||||||
public JuiceStreamCompositionTool()
|
public JuiceStreamCompositionTool()
|
||||||
: base(nameof(JuiceStream))
|
: base(nameof(JuiceStream))
|
||||||
@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
|
|
||||||
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders);
|
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders);
|
||||||
|
|
||||||
public override PlacementBlueprint CreatePlacementBlueprint() => new JuiceStreamPlacementBlueprint();
|
public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new JuiceStreamPlacementBlueprint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override SnapResult SnapForBlueprint(PlacementBlueprint blueprint)
|
protected override SnapResult SnapForBlueprint(HitObjectPlacementBlueprint blueprint)
|
||||||
{
|
{
|
||||||
double time = column.TimeAtScreenSpacePosition(InputManager.CurrentState.Mouse.Position);
|
double time = column.TimeAtScreenSpacePosition(InputManager.CurrentState.Mouse.Position);
|
||||||
var pos = column.ScreenSpacePositionAtTime(time);
|
var pos = column.ScreenSpacePositionAtTime(time);
|
||||||
|
@ -13,6 +13,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
|||||||
public partial class TestSceneHoldNotePlacementBlueprint : ManiaPlacementBlueprintTestScene
|
public partial class TestSceneHoldNotePlacementBlueprint : ManiaPlacementBlueprintTestScene
|
||||||
{
|
{
|
||||||
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableHoldNote((HoldNote)hitObject);
|
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableHoldNote((HoldNote)hitObject);
|
||||||
protected override PlacementBlueprint CreateBlueprint() => new HoldNotePlacementBlueprint();
|
protected override HitObjectPlacementBlueprint CreateBlueprint() => new HoldNotePlacementBlueprint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,6 +64,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
|||||||
private Note getNote() => this.ChildrenOfType<DrawableNote>().FirstOrDefault()?.HitObject;
|
private Note getNote() => this.ChildrenOfType<DrawableNote>().FirstOrDefault()?.HitObject;
|
||||||
|
|
||||||
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableNote((Note)hitObject);
|
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableNote((Note)hitObject);
|
||||||
protected override PlacementBlueprint CreateBlueprint() => new NotePlacementBlueprint();
|
protected override HitObjectPlacementBlueprint CreateBlueprint() => new NotePlacementBlueprint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ using osuTK.Input;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||||
{
|
{
|
||||||
public abstract partial class ManiaPlacementBlueprint<T> : PlacementBlueprint
|
public abstract partial class ManiaPlacementBlueprint<T> : HitObjectPlacementBlueprint
|
||||||
where T : ManiaHitObject
|
where T : ManiaHitObject
|
||||||
{
|
{
|
||||||
protected new T HitObject => (T)base.HitObject;
|
protected new T HitObject => (T)base.HitObject;
|
||||||
|
@ -9,7 +9,7 @@ using osu.Game.Rulesets.Mania.Edit.Blueprints;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Edit
|
namespace osu.Game.Rulesets.Mania.Edit
|
||||||
{
|
{
|
||||||
public class HoldNoteCompositionTool : HitObjectCompositionTool
|
public class HoldNoteCompositionTool : CompositionTool
|
||||||
{
|
{
|
||||||
public HoldNoteCompositionTool()
|
public HoldNoteCompositionTool()
|
||||||
: base("Hold")
|
: base("Hold")
|
||||||
@ -18,6 +18,6 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
|
|
||||||
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders);
|
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders);
|
||||||
|
|
||||||
public override PlacementBlueprint CreatePlacementBlueprint() => new HoldNotePlacementBlueprint();
|
public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new HoldNotePlacementBlueprint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
|
|
||||||
protected override BeatSnapGrid CreateBeatSnapGrid() => new ManiaBeatSnapGrid();
|
protected override BeatSnapGrid CreateBeatSnapGrid() => new ManiaBeatSnapGrid();
|
||||||
|
|
||||||
protected override IReadOnlyList<HitObjectCompositionTool> CompositionTools => new HitObjectCompositionTool[]
|
protected override IReadOnlyList<CompositionTool> CompositionTools => new CompositionTool[]
|
||||||
{
|
{
|
||||||
new NoteCompositionTool(),
|
new NoteCompositionTool(),
|
||||||
new HoldNoteCompositionTool()
|
new HoldNoteCompositionTool()
|
||||||
|
@ -10,7 +10,7 @@ using osu.Game.Rulesets.Mania.Objects;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Edit
|
namespace osu.Game.Rulesets.Mania.Edit
|
||||||
{
|
{
|
||||||
public class NoteCompositionTool : HitObjectCompositionTool
|
public class NoteCompositionTool : CompositionTool
|
||||||
{
|
{
|
||||||
public NoteCompositionTool()
|
public NoteCompositionTool()
|
||||||
: base(nameof(Note))
|
: base(nameof(Note))
|
||||||
@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
|
|
||||||
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles);
|
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles);
|
||||||
|
|
||||||
public override PlacementBlueprint CreatePlacementBlueprint() => new NotePlacementBlueprint();
|
public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new NotePlacementBlueprint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
public partial class TestSceneHitCirclePlacementBlueprint : PlacementBlueprintTestScene
|
public partial class TestSceneHitCirclePlacementBlueprint : PlacementBlueprintTestScene
|
||||||
{
|
{
|
||||||
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableHitCircle((HitCircle)hitObject);
|
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableHitCircle((HitCircle)hitObject);
|
||||||
protected override PlacementBlueprint CreateBlueprint() => new HitCirclePlacementBlueprint();
|
protected override HitObjectPlacementBlueprint CreateBlueprint() => new HitCirclePlacementBlueprint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
() => EditorBeatmap.HitObjects.OfType<HitCircle>().ElementAt(1).Position,
|
() => EditorBeatmap.HitObjects.OfType<HitCircle>().ElementAt(1).Position,
|
||||||
() => Is.EqualTo(OsuPlayfield.BASE_SIZE - new Vector2(200)));
|
() => Is.EqualTo(OsuPlayfield.BASE_SIZE - new Vector2(200)));
|
||||||
|
|
||||||
AddStep("change rotation origin", () => getPopover().ChildrenOfType<EditorRadioButton>().ElementAt(1).TriggerClick());
|
AddStep("change rotation origin", () => getPopover().ChildrenOfType<EditorRadioButton>().ElementAt(2).TriggerClick());
|
||||||
AddAssert("first object rotated 90deg around selection centre",
|
AddAssert("first object rotated 90deg around selection centre",
|
||||||
() => EditorBeatmap.HitObjects.OfType<HitCircle>().ElementAt(0).Position, () => Is.EqualTo(new Vector2(200, 200)));
|
() => EditorBeatmap.HitObjects.OfType<HitCircle>().ElementAt(0).Position, () => Is.EqualTo(new Vector2(200, 200)));
|
||||||
AddAssert("second object rotated 90deg around selection centre",
|
AddAssert("second object rotated 90deg around selection centre",
|
||||||
|
@ -514,6 +514,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
private Slider? getSlider() => HitObjectContainer.Count > 0 ? ((DrawableSlider)HitObjectContainer[0]).HitObject : null;
|
private Slider? getSlider() => HitObjectContainer.Count > 0 ? ((DrawableSlider)HitObjectContainer[0]).HitObject : null;
|
||||||
|
|
||||||
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableSlider((Slider)hitObject);
|
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableSlider((Slider)hitObject);
|
||||||
protected override PlacementBlueprint CreateBlueprint() => new SliderPlacementBlueprint();
|
protected override HitObjectPlacementBlueprint CreateBlueprint() => new SliderPlacementBlueprint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
{
|
{
|
||||||
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableSpinner((Spinner)hitObject);
|
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableSpinner((Spinner)hitObject);
|
||||||
|
|
||||||
protected override PlacementBlueprint CreateBlueprint() => new SpinnerPlacementBlueprint();
|
protected override HitObjectPlacementBlueprint CreateBlueprint() => new SpinnerPlacementBlueprint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
|
|
||||||
double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate;
|
double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate;
|
||||||
double drainRate = beatmap.Difficulty.DrainRate;
|
double drainRate = beatmap.Difficulty.DrainRate;
|
||||||
int maxCombo = beatmap.GetMaxCombo();
|
|
||||||
|
|
||||||
int hitCirclesCount = beatmap.HitObjects.Count(h => h is HitCircle);
|
int hitCirclesCount = beatmap.HitObjects.Count(h => h is HitCircle);
|
||||||
int sliderCount = beatmap.HitObjects.Count(h => h is Slider);
|
int sliderCount = beatmap.HitObjects.Count(h => h is Slider);
|
||||||
@ -104,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5,
|
ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5,
|
||||||
OverallDifficulty = (80 - hitWindowGreat) / 6,
|
OverallDifficulty = (80 - hitWindowGreat) / 6,
|
||||||
DrainRate = drainRate,
|
DrainRate = drainRate,
|
||||||
MaxCombo = maxCombo,
|
MaxCombo = beatmap.GetMaxCombo(),
|
||||||
HitCircleCount = hitCirclesCount,
|
HitCircleCount = hitCirclesCount,
|
||||||
SliderCount = sliderCount,
|
SliderCount = sliderCount,
|
||||||
SpinnerCount = spinnerCount,
|
SpinnerCount = spinnerCount,
|
||||||
|
@ -9,7 +9,7 @@ using osuTK.Input;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
|
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
|
||||||
{
|
{
|
||||||
public partial class HitCirclePlacementBlueprint : PlacementBlueprint
|
public partial class HitCirclePlacementBlueprint : HitObjectPlacementBlueprint
|
||||||
{
|
{
|
||||||
public new HitCircle HitObject => (HitCircle)base.HitObject;
|
public new HitCircle HitObject => (HitCircle)base.HitObject;
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ using osuTK.Input;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||||
{
|
{
|
||||||
public partial class SliderPlacementBlueprint : PlacementBlueprint
|
public partial class SliderPlacementBlueprint : HitObjectPlacementBlueprint
|
||||||
{
|
{
|
||||||
public new Slider HitObject => (Slider)base.HitObject;
|
public new Slider HitObject => (Slider)base.HitObject;
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ using osuTK.Input;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
|
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
|
||||||
{
|
{
|
||||||
public partial class SpinnerPlacementBlueprint : PlacementBlueprint
|
public partial class SpinnerPlacementBlueprint : HitObjectPlacementBlueprint
|
||||||
{
|
{
|
||||||
public new Spinner HitObject => (Spinner)base.HitObject;
|
public new Spinner HitObject => (Spinner)base.HitObject;
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ using osu.Game.Rulesets.Osu.Objects;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit
|
namespace osu.Game.Rulesets.Osu.Edit
|
||||||
{
|
{
|
||||||
public class HitCircleCompositionTool : HitObjectCompositionTool
|
public class HitCircleCompositionTool : CompositionTool
|
||||||
{
|
{
|
||||||
public HitCircleCompositionTool()
|
public HitCircleCompositionTool()
|
||||||
: base(nameof(HitCircle))
|
: base(nameof(HitCircle))
|
||||||
@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles);
|
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles);
|
||||||
|
|
||||||
public override PlacementBlueprint CreatePlacementBlueprint() => new HitCirclePlacementBlueprint();
|
public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new HitCirclePlacementBlueprint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
protected override DrawableRuleset<OsuHitObject> CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
protected override DrawableRuleset<OsuHitObject> CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
||||||
=> new DrawableOsuEditorRuleset(ruleset, beatmap, mods);
|
=> new DrawableOsuEditorRuleset(ruleset, beatmap, mods);
|
||||||
|
|
||||||
protected override IReadOnlyList<HitObjectCompositionTool> CompositionTools => new HitObjectCompositionTool[]
|
protected override IReadOnlyList<CompositionTool> CompositionTools => new CompositionTool[]
|
||||||
{
|
{
|
||||||
new HitCircleCompositionTool(),
|
new HitCircleCompositionTool(),
|
||||||
new SliderCompositionTool(),
|
new SliderCompositionTool(),
|
||||||
@ -106,6 +106,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler,
|
RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler,
|
||||||
ScaleHandler = (OsuSelectionScaleHandler)BlueprintContainer.SelectionHandler.ScaleHandler,
|
ScaleHandler = (OsuSelectionScaleHandler)BlueprintContainer.SelectionHandler.ScaleHandler,
|
||||||
|
GridToolbox = OsuGridToolboxGroup,
|
||||||
},
|
},
|
||||||
new GenerateToolboxGroup(),
|
new GenerateToolboxGroup(),
|
||||||
FreehandSliderToolboxGroup
|
FreehandSliderToolboxGroup
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Primitives;
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
@ -25,6 +26,9 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
public partial class OsuSelectionHandler : EditorSelectionHandler
|
public partial class OsuSelectionHandler : EditorSelectionHandler
|
||||||
{
|
{
|
||||||
|
[Resolved]
|
||||||
|
private OsuGridToolboxGroup gridToolbox { get; set; } = null!;
|
||||||
|
|
||||||
protected override void OnSelectionChanged()
|
protected override void OnSelectionChanged()
|
||||||
{
|
{
|
||||||
base.OnSelectionChanged();
|
base.OnSelectionChanged();
|
||||||
@ -123,13 +127,43 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
var hitObjects = selectedMovableObjects;
|
var hitObjects = selectedMovableObjects;
|
||||||
|
|
||||||
var flipQuad = flipOverOrigin ? new Quad(0, 0, OsuPlayfield.BASE_SIZE.X, OsuPlayfield.BASE_SIZE.Y) : GeometryUtils.GetSurroundingQuad(hitObjects);
|
// If we're flipping over the origin, we take the grid origin position from the grid toolbox.
|
||||||
|
var flipQuad = flipOverOrigin ? new Quad(gridToolbox.StartPositionX.Value, gridToolbox.StartPositionY.Value, 0, 0) : GeometryUtils.GetSurroundingQuad(hitObjects);
|
||||||
|
Vector2 flipAxis = direction == Direction.Vertical ? Vector2.UnitY : Vector2.UnitX;
|
||||||
|
|
||||||
|
if (flipOverOrigin)
|
||||||
|
{
|
||||||
|
// If we're flipping over the origin, we take one of the axes of the grid.
|
||||||
|
// Take the axis closest to the direction we want to flip over.
|
||||||
|
switch (gridToolbox.GridType.Value)
|
||||||
|
{
|
||||||
|
case PositionSnapGridType.Square:
|
||||||
|
flipAxis = GeometryUtils.RotateVector(Vector2.UnitX, -((gridToolbox.GridLinesRotation.Value + 360 + 45) % 90 - 45));
|
||||||
|
flipAxis = direction == Direction.Vertical ? flipAxis.PerpendicularLeft : flipAxis;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PositionSnapGridType.Triangle:
|
||||||
|
// Hex grid has 3 axes, so you can not directly flip over one of the axes,
|
||||||
|
// however it's still possible to achieve that flip by combining multiple flips over the other axes.
|
||||||
|
// Angle degree range for vertical = (-120, -60]
|
||||||
|
// Angle degree range for horizontal = [-30, 30)
|
||||||
|
flipAxis = direction == Direction.Vertical
|
||||||
|
? GeometryUtils.RotateVector(Vector2.UnitX, -((gridToolbox.GridLinesRotation.Value + 360 + 30) % 60 + 60))
|
||||||
|
: GeometryUtils.RotateVector(Vector2.UnitX, -((gridToolbox.GridLinesRotation.Value + 360) % 60 - 30));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var controlPointFlipQuad = new Quad();
|
||||||
|
|
||||||
bool didFlip = false;
|
bool didFlip = false;
|
||||||
|
|
||||||
foreach (var h in hitObjects)
|
foreach (var h in hitObjects)
|
||||||
{
|
{
|
||||||
var flippedPosition = GeometryUtils.GetFlippedPosition(direction, flipQuad, h.Position);
|
var flippedPosition = GeometryUtils.GetFlippedPosition(flipAxis, flipQuad, h.Position);
|
||||||
|
|
||||||
|
// Clamp the flipped position inside the playfield bounds, because the flipped position might be outside the playfield bounds if the origin is not centered.
|
||||||
|
flippedPosition = Vector2.Clamp(flippedPosition, Vector2.Zero, OsuPlayfield.BASE_SIZE);
|
||||||
|
|
||||||
if (!Precision.AlmostEquals(flippedPosition, h.Position))
|
if (!Precision.AlmostEquals(flippedPosition, h.Position))
|
||||||
{
|
{
|
||||||
@ -142,12 +176,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
didFlip = true;
|
didFlip = true;
|
||||||
|
|
||||||
foreach (var cp in slider.Path.ControlPoints)
|
foreach (var cp in slider.Path.ControlPoints)
|
||||||
{
|
cp.Position = GeometryUtils.GetFlippedPosition(flipAxis, controlPointFlipQuad, cp.Position);
|
||||||
cp.Position = new Vector2(
|
|
||||||
(direction == Direction.Horizontal ? -1 : 1) * cp.Position.X,
|
|
||||||
(direction == Direction.Vertical ? -1 : 1) * cp.Position.Y
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,6 +69,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
private Dictionary<OsuHitObject, OriginalHitObjectState>? objectsInScale;
|
private Dictionary<OsuHitObject, OriginalHitObjectState>? objectsInScale;
|
||||||
private Vector2? defaultOrigin;
|
private Vector2? defaultOrigin;
|
||||||
|
private List<Vector2>? originalConvexHull;
|
||||||
|
|
||||||
public override void Begin()
|
public override void Begin()
|
||||||
{
|
{
|
||||||
@ -84,9 +85,12 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
? GeometryUtils.GetSurroundingQuad(slider.Path.ControlPoints.Select(p => slider.Position + p.Position))
|
? GeometryUtils.GetSurroundingQuad(slider.Path.ControlPoints.Select(p => slider.Position + p.Position))
|
||||||
: GeometryUtils.GetSurroundingQuad(objectsInScale.Keys);
|
: GeometryUtils.GetSurroundingQuad(objectsInScale.Keys);
|
||||||
defaultOrigin = OriginalSurroundingQuad.Value.Centre;
|
defaultOrigin = OriginalSurroundingQuad.Value.Centre;
|
||||||
|
originalConvexHull = objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider2
|
||||||
|
? GeometryUtils.GetConvexHull(slider2.Path.ControlPoints.Select(p => slider2.Position + p.Position))
|
||||||
|
: GeometryUtils.GetConvexHull(objectsInScale.Keys);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both)
|
public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both, float axisRotation = 0)
|
||||||
{
|
{
|
||||||
if (!OperationInProgress.Value)
|
if (!OperationInProgress.Value)
|
||||||
throw new InvalidOperationException($"Cannot {nameof(Update)} a scale operation without calling {nameof(Begin)} first!");
|
throw new InvalidOperationException($"Cannot {nameof(Update)} a scale operation without calling {nameof(Begin)} first!");
|
||||||
@ -94,23 +98,22 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
Debug.Assert(objectsInScale != null && defaultOrigin != null && OriginalSurroundingQuad != null);
|
Debug.Assert(objectsInScale != null && defaultOrigin != null && OriginalSurroundingQuad != null);
|
||||||
|
|
||||||
Vector2 actualOrigin = origin ?? defaultOrigin.Value;
|
Vector2 actualOrigin = origin ?? defaultOrigin.Value;
|
||||||
|
scale = clampScaleToAdjustAxis(scale, adjustAxis);
|
||||||
|
|
||||||
// for the time being, allow resizing of slider paths only if the slider is
|
// for the time being, allow resizing of slider paths only if the slider is
|
||||||
// the only hit object selected. with a group selection, it's likely the user
|
// the only hit object selected. with a group selection, it's likely the user
|
||||||
// is not looking to change the duration of the slider but expand the whole pattern.
|
// is not looking to change the duration of the slider but expand the whole pattern.
|
||||||
if (objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider)
|
if (objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider)
|
||||||
{
|
{
|
||||||
var originalInfo = objectsInScale[slider];
|
scaleSlider(slider, scale, actualOrigin, objectsInScale[slider], axisRotation);
|
||||||
Debug.Assert(originalInfo.PathControlPointPositions != null && originalInfo.PathControlPointTypes != null);
|
|
||||||
scaleSlider(slider, scale, originalInfo.PathControlPointPositions, originalInfo.PathControlPointTypes);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
scale = ClampScaleToPlayfieldBounds(scale, actualOrigin);
|
scale = ClampScaleToPlayfieldBounds(scale, actualOrigin, adjustAxis, axisRotation);
|
||||||
|
|
||||||
foreach (var (ho, originalState) in objectsInScale)
|
foreach (var (ho, originalState) in objectsInScale)
|
||||||
{
|
{
|
||||||
ho.Position = GeometryUtils.GetScaledPosition(scale, actualOrigin, originalState.Position);
|
ho.Position = GeometryUtils.GetScaledPosition(scale, actualOrigin, originalState.Position, axisRotation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,21 +137,45 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
private IEnumerable<OsuHitObject> selectedMovableObjects => selectedItems.Cast<OsuHitObject>()
|
private IEnumerable<OsuHitObject> selectedMovableObjects => selectedItems.Cast<OsuHitObject>()
|
||||||
.Where(h => h is not Spinner);
|
.Where(h => h is not Spinner);
|
||||||
|
|
||||||
private void scaleSlider(Slider slider, Vector2 scale, Vector2[] originalPathPositions, PathType?[] originalPathTypes)
|
private Vector2 clampScaleToAdjustAxis(Vector2 scale, Axes adjustAxis)
|
||||||
{
|
{
|
||||||
|
switch (adjustAxis)
|
||||||
|
{
|
||||||
|
case Axes.Y:
|
||||||
|
scale.X = 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Axes.X:
|
||||||
|
scale.Y = 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Axes.None:
|
||||||
|
scale = Vector2.One;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scaleSlider(Slider slider, Vector2 scale, Vector2 origin, OriginalHitObjectState originalInfo, float axisRotation = 0)
|
||||||
|
{
|
||||||
|
Debug.Assert(originalInfo.PathControlPointPositions != null && originalInfo.PathControlPointTypes != null);
|
||||||
|
|
||||||
scale = Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON));
|
scale = Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON));
|
||||||
|
|
||||||
// Maintain the path types in case they were defaulted to bezier at some point during scaling
|
// Maintain the path types in case they were defaulted to bezier at some point during scaling
|
||||||
for (int i = 0; i < slider.Path.ControlPoints.Count; i++)
|
for (int i = 0; i < slider.Path.ControlPoints.Count; i++)
|
||||||
{
|
{
|
||||||
slider.Path.ControlPoints[i].Position = originalPathPositions[i] * scale;
|
slider.Path.ControlPoints[i].Position = GeometryUtils.GetScaledPosition(scale, Vector2.Zero, originalInfo.PathControlPointPositions[i], axisRotation);
|
||||||
slider.Path.ControlPoints[i].Type = originalPathTypes[i];
|
slider.Path.ControlPoints[i].Type = originalInfo.PathControlPointTypes[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Snap the slider's length to the current beat divisor
|
// Snap the slider's length to the current beat divisor
|
||||||
// to calculate the final resulting duration / bounding box before the final checks.
|
// to calculate the final resulting duration / bounding box before the final checks.
|
||||||
slider.SnapTo(snapProvider);
|
slider.SnapTo(snapProvider);
|
||||||
|
|
||||||
|
slider.Position = GeometryUtils.GetScaledPosition(scale, origin, originalInfo.Position, axisRotation);
|
||||||
|
|
||||||
//if sliderhead or sliderend end up outside playfield, revert scaling.
|
//if sliderhead or sliderend end up outside playfield, revert scaling.
|
||||||
Quad scaledQuad = GeometryUtils.GetSurroundingQuad(new OsuHitObject[] { slider });
|
Quad scaledQuad = GeometryUtils.GetSurroundingQuad(new OsuHitObject[] { slider });
|
||||||
(bool xInBounds, bool yInBounds) = isQuadInBounds(scaledQuad);
|
(bool xInBounds, bool yInBounds) = isQuadInBounds(scaledQuad);
|
||||||
@ -157,7 +184,9 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
for (int i = 0; i < slider.Path.ControlPoints.Count; i++)
|
for (int i = 0; i < slider.Path.ControlPoints.Count; i++)
|
||||||
slider.Path.ControlPoints[i].Position = originalPathPositions[i];
|
slider.Path.ControlPoints[i].Position = originalInfo.PathControlPointPositions[i];
|
||||||
|
|
||||||
|
slider.Position = originalInfo.Position;
|
||||||
|
|
||||||
// Snap the slider's length again to undo the potentially-invalid length applied by the previous snap.
|
// Snap the slider's length again to undo the potentially-invalid length applied by the previous snap.
|
||||||
slider.SnapTo(snapProvider);
|
slider.SnapTo(snapProvider);
|
||||||
@ -176,11 +205,13 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="origin">The origin from which the scale operation is performed</param>
|
/// <param name="origin">The origin from which the scale operation is performed</param>
|
||||||
/// <param name="scale">The scale to be clamped</param>
|
/// <param name="scale">The scale to be clamped</param>
|
||||||
|
/// <param name="adjustAxis">The axes to adjust the scale in.</param>
|
||||||
|
/// <param name="axisRotation">The rotation of the axes in degrees</param>
|
||||||
/// <returns>The clamped scale vector</returns>
|
/// <returns>The clamped scale vector</returns>
|
||||||
public Vector2 ClampScaleToPlayfieldBounds(Vector2 scale, Vector2? origin = null)
|
public Vector2 ClampScaleToPlayfieldBounds(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both, float axisRotation = 0)
|
||||||
{
|
{
|
||||||
//todo: this is not always correct for selections involving sliders. This approximation assumes each point is scaled independently, but sliderends move with the sliderhead.
|
//todo: this is not always correct for selections involving sliders. This approximation assumes each point is scaled independently, but sliderends move with the sliderhead.
|
||||||
if (objectsInScale == null)
|
if (objectsInScale == null || adjustAxis == Axes.None)
|
||||||
return scale;
|
return scale;
|
||||||
|
|
||||||
Debug.Assert(defaultOrigin != null && OriginalSurroundingQuad != null);
|
Debug.Assert(defaultOrigin != null && OriginalSurroundingQuad != null);
|
||||||
@ -188,24 +219,60 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
if (objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider)
|
if (objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider)
|
||||||
origin = slider.Position;
|
origin = slider.Position;
|
||||||
|
|
||||||
|
float cos = MathF.Cos(float.DegreesToRadians(-axisRotation));
|
||||||
|
float sin = MathF.Sin(float.DegreesToRadians(-axisRotation));
|
||||||
|
scale = clampScaleToAdjustAxis(scale, adjustAxis);
|
||||||
Vector2 actualOrigin = origin ?? defaultOrigin.Value;
|
Vector2 actualOrigin = origin ?? defaultOrigin.Value;
|
||||||
var selectionQuad = OriginalSurroundingQuad.Value;
|
IEnumerable<Vector2> points;
|
||||||
|
|
||||||
var tl1 = Vector2.Divide(-actualOrigin, selectionQuad.TopLeft - actualOrigin);
|
if (axisRotation == 0)
|
||||||
var tl2 = Vector2.Divide(OsuPlayfield.BASE_SIZE - actualOrigin, selectionQuad.TopLeft - actualOrigin);
|
{
|
||||||
var br1 = Vector2.Divide(-actualOrigin, selectionQuad.BottomRight - actualOrigin);
|
var selectionQuad = OriginalSurroundingQuad.Value;
|
||||||
var br2 = Vector2.Divide(OsuPlayfield.BASE_SIZE - actualOrigin, selectionQuad.BottomRight - actualOrigin);
|
points = new[]
|
||||||
|
{
|
||||||
|
selectionQuad.TopLeft,
|
||||||
|
selectionQuad.TopRight,
|
||||||
|
selectionQuad.BottomLeft,
|
||||||
|
selectionQuad.BottomRight
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
points = originalConvexHull!;
|
||||||
|
|
||||||
if (!Precision.AlmostEquals(selectionQuad.TopLeft.X - actualOrigin.X, 0))
|
foreach (var point in points)
|
||||||
scale.X = selectionQuad.TopLeft.X - actualOrigin.X < 0 ? MathHelper.Clamp(scale.X, tl2.X, tl1.X) : MathHelper.Clamp(scale.X, tl1.X, tl2.X);
|
{
|
||||||
if (!Precision.AlmostEquals(selectionQuad.TopLeft.Y - actualOrigin.Y, 0))
|
scale = clampToBound(scale, point, Vector2.Zero);
|
||||||
scale.Y = selectionQuad.TopLeft.Y - actualOrigin.Y < 0 ? MathHelper.Clamp(scale.Y, tl2.Y, tl1.Y) : MathHelper.Clamp(scale.Y, tl1.Y, tl2.Y);
|
scale = clampToBound(scale, point, OsuPlayfield.BASE_SIZE);
|
||||||
if (!Precision.AlmostEquals(selectionQuad.BottomRight.X - actualOrigin.X, 0))
|
}
|
||||||
scale.X = selectionQuad.BottomRight.X - actualOrigin.X < 0 ? MathHelper.Clamp(scale.X, br2.X, br1.X) : MathHelper.Clamp(scale.X, br1.X, br2.X);
|
|
||||||
if (!Precision.AlmostEquals(selectionQuad.BottomRight.Y - actualOrigin.Y, 0))
|
|
||||||
scale.Y = selectionQuad.BottomRight.Y - actualOrigin.Y < 0 ? MathHelper.Clamp(scale.Y, br2.Y, br1.Y) : MathHelper.Clamp(scale.Y, br1.Y, br2.Y);
|
|
||||||
|
|
||||||
return Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON));
|
return Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON));
|
||||||
|
|
||||||
|
float minPositiveComponent(Vector2 v) => MathF.Min(v.X < 0 ? float.PositiveInfinity : v.X, v.Y < 0 ? float.PositiveInfinity : v.Y);
|
||||||
|
|
||||||
|
Vector2 clampToBound(Vector2 s, Vector2 p, Vector2 bound)
|
||||||
|
{
|
||||||
|
p -= actualOrigin;
|
||||||
|
bound -= actualOrigin;
|
||||||
|
var a = new Vector2(cos * cos * p.X - sin * cos * p.Y, -sin * cos * p.X + sin * sin * p.Y);
|
||||||
|
var b = new Vector2(sin * sin * p.X + sin * cos * p.Y, sin * cos * p.X + cos * cos * p.Y);
|
||||||
|
|
||||||
|
switch (adjustAxis)
|
||||||
|
{
|
||||||
|
case Axes.X:
|
||||||
|
s.X = MathF.Min(scale.X, minPositiveComponent(Vector2.Divide(bound - b, a)));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Axes.Y:
|
||||||
|
s.Y = MathF.Min(scale.Y, minPositiveComponent(Vector2.Divide(bound - a, b)));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Axes.Both:
|
||||||
|
s = Vector2.ComponentMin(s, s * minPositiveComponent(Vector2.Divide(bound, a * s.X + b * s.Y)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void moveSelectionInBounds()
|
private void moveSelectionInBounds()
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -19,16 +20,19 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
private readonly SelectionRotationHandler rotationHandler;
|
private readonly SelectionRotationHandler rotationHandler;
|
||||||
|
|
||||||
private readonly Bindable<PreciseRotationInfo> rotationInfo = new Bindable<PreciseRotationInfo>(new PreciseRotationInfo(0, RotationOrigin.PlayfieldCentre));
|
private readonly OsuGridToolboxGroup gridToolbox;
|
||||||
|
|
||||||
|
private readonly Bindable<PreciseRotationInfo> rotationInfo = new Bindable<PreciseRotationInfo>(new PreciseRotationInfo(0, RotationOrigin.GridCentre));
|
||||||
|
|
||||||
private SliderWithTextBoxInput<float> angleInput = null!;
|
private SliderWithTextBoxInput<float> angleInput = null!;
|
||||||
private EditorRadioButtonCollection rotationOrigin = null!;
|
private EditorRadioButtonCollection rotationOrigin = null!;
|
||||||
|
|
||||||
private RadioButton selectionCentreButton = null!;
|
private RadioButton selectionCentreButton = null!;
|
||||||
|
|
||||||
public PreciseRotationPopover(SelectionRotationHandler rotationHandler)
|
public PreciseRotationPopover(SelectionRotationHandler rotationHandler, OsuGridToolboxGroup gridToolbox)
|
||||||
{
|
{
|
||||||
this.rotationHandler = rotationHandler;
|
this.rotationHandler = rotationHandler;
|
||||||
|
this.gridToolbox = gridToolbox;
|
||||||
|
|
||||||
AllowableAnchors = new[] { Anchor.CentreLeft, Anchor.CentreRight };
|
AllowableAnchors = new[] { Anchor.CentreLeft, Anchor.CentreRight };
|
||||||
}
|
}
|
||||||
@ -58,6 +62,9 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Items = new[]
|
Items = new[]
|
||||||
{
|
{
|
||||||
|
new RadioButton("Grid centre",
|
||||||
|
() => rotationInfo.Value = rotationInfo.Value with { Origin = RotationOrigin.GridCentre },
|
||||||
|
() => new SpriteIcon { Icon = FontAwesome.Regular.PlusSquare }),
|
||||||
new RadioButton("Playfield centre",
|
new RadioButton("Playfield centre",
|
||||||
() => rotationInfo.Value = rotationInfo.Value with { Origin = RotationOrigin.PlayfieldCentre },
|
() => rotationInfo.Value = rotationInfo.Value with { Origin = RotationOrigin.PlayfieldCentre },
|
||||||
() => new SpriteIcon { Icon = FontAwesome.Regular.Square }),
|
() => new SpriteIcon { Icon = FontAwesome.Regular.Square }),
|
||||||
@ -93,10 +100,19 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
rotationInfo.BindValueChanged(rotation =>
|
rotationInfo.BindValueChanged(rotation =>
|
||||||
{
|
{
|
||||||
rotationHandler.Update(rotation.NewValue.Degrees, rotation.NewValue.Origin == RotationOrigin.PlayfieldCentre ? OsuPlayfield.BASE_SIZE / 2 : null);
|
rotationHandler.Update(rotation.NewValue.Degrees, getOriginPosition(rotation.NewValue));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Vector2? getOriginPosition(PreciseRotationInfo rotation) =>
|
||||||
|
rotation.Origin switch
|
||||||
|
{
|
||||||
|
RotationOrigin.GridCentre => gridToolbox.StartPosition.Value,
|
||||||
|
RotationOrigin.PlayfieldCentre => OsuPlayfield.BASE_SIZE / 2,
|
||||||
|
RotationOrigin.SelectionCentre => null,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(rotation))
|
||||||
|
};
|
||||||
|
|
||||||
protected override void PopIn()
|
protected override void PopIn()
|
||||||
{
|
{
|
||||||
base.PopIn();
|
base.PopIn();
|
||||||
@ -114,6 +130,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
public enum RotationOrigin
|
public enum RotationOrigin
|
||||||
{
|
{
|
||||||
|
GridCentre,
|
||||||
PlayfieldCentre,
|
PlayfieldCentre,
|
||||||
SelectionCentre
|
SelectionCentre
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,10 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Screens.Edit.Components.RadioButtons;
|
using osu.Game.Screens.Edit.Components.RadioButtons;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -20,28 +23,36 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
private readonly OsuSelectionScaleHandler scaleHandler;
|
private readonly OsuSelectionScaleHandler scaleHandler;
|
||||||
|
|
||||||
private readonly Bindable<PreciseScaleInfo> scaleInfo = new Bindable<PreciseScaleInfo>(new PreciseScaleInfo(1, ScaleOrigin.PlayfieldCentre, true, true));
|
private readonly OsuGridToolboxGroup gridToolbox;
|
||||||
|
|
||||||
|
private readonly Bindable<PreciseScaleInfo> scaleInfo = new Bindable<PreciseScaleInfo>(new PreciseScaleInfo(1, ScaleOrigin.GridCentre, true, true));
|
||||||
|
|
||||||
private SliderWithTextBoxInput<float> scaleInput = null!;
|
private SliderWithTextBoxInput<float> scaleInput = null!;
|
||||||
private BindableNumber<float> scaleInputBindable = null!;
|
private BindableNumber<float> scaleInputBindable = null!;
|
||||||
private EditorRadioButtonCollection scaleOrigin = null!;
|
private EditorRadioButtonCollection scaleOrigin = null!;
|
||||||
|
|
||||||
|
private RadioButton gridCentreButton = null!;
|
||||||
private RadioButton playfieldCentreButton = null!;
|
private RadioButton playfieldCentreButton = null!;
|
||||||
private RadioButton selectionCentreButton = null!;
|
private RadioButton selectionCentreButton = null!;
|
||||||
|
|
||||||
private OsuCheckbox xCheckBox = null!;
|
private OsuCheckbox xCheckBox = null!;
|
||||||
private OsuCheckbox yCheckBox = null!;
|
private OsuCheckbox yCheckBox = null!;
|
||||||
|
|
||||||
public PreciseScalePopover(OsuSelectionScaleHandler scaleHandler)
|
private BindableList<HitObject> selectedItems { get; } = new BindableList<HitObject>();
|
||||||
|
|
||||||
|
public PreciseScalePopover(OsuSelectionScaleHandler scaleHandler, OsuGridToolboxGroup gridToolbox)
|
||||||
{
|
{
|
||||||
this.scaleHandler = scaleHandler;
|
this.scaleHandler = scaleHandler;
|
||||||
|
this.gridToolbox = gridToolbox;
|
||||||
|
|
||||||
AllowableAnchors = new[] { Anchor.CentreLeft, Anchor.CentreRight };
|
AllowableAnchors = new[] { Anchor.CentreLeft, Anchor.CentreRight };
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load(EditorBeatmap editorBeatmap)
|
||||||
{
|
{
|
||||||
|
selectedItems.BindTo(editorBeatmap.SelectedHitObjects);
|
||||||
|
|
||||||
Child = new FillFlowContainer
|
Child = new FillFlowContainer
|
||||||
{
|
{
|
||||||
Width = 220,
|
Width = 220,
|
||||||
@ -66,6 +77,9 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Items = new[]
|
Items = new[]
|
||||||
{
|
{
|
||||||
|
gridCentreButton = new RadioButton("Grid centre",
|
||||||
|
() => setOrigin(ScaleOrigin.GridCentre),
|
||||||
|
() => new SpriteIcon { Icon = FontAwesome.Regular.PlusSquare }),
|
||||||
playfieldCentreButton = new RadioButton("Playfield centre",
|
playfieldCentreButton = new RadioButton("Playfield centre",
|
||||||
() => setOrigin(ScaleOrigin.PlayfieldCentre),
|
() => setOrigin(ScaleOrigin.PlayfieldCentre),
|
||||||
() => new SpriteIcon { Icon = FontAwesome.Regular.Square }),
|
() => new SpriteIcon { Icon = FontAwesome.Regular.Square }),
|
||||||
@ -97,6 +111,10 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
gridCentreButton.Selected.DisabledChanged += isDisabled =>
|
||||||
|
{
|
||||||
|
gridCentreButton.TooltipText = isDisabled ? "The current selection cannot be scaled relative to grid centre." : string.Empty;
|
||||||
|
};
|
||||||
playfieldCentreButton.Selected.DisabledChanged += isDisabled =>
|
playfieldCentreButton.Selected.DisabledChanged += isDisabled =>
|
||||||
{
|
{
|
||||||
playfieldCentreButton.TooltipText = isDisabled ? "The current selection cannot be scaled relative to playfield centre." : string.Empty;
|
playfieldCentreButton.TooltipText = isDisabled ? "The current selection cannot be scaled relative to playfield centre." : string.Empty;
|
||||||
@ -123,19 +141,20 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
selectionCentreButton.Selected.Disabled = !(scaleHandler.CanScaleX.Value || scaleHandler.CanScaleY.Value);
|
selectionCentreButton.Selected.Disabled = !(scaleHandler.CanScaleX.Value || scaleHandler.CanScaleY.Value);
|
||||||
playfieldCentreButton.Selected.Disabled = scaleHandler.IsScalingSlider.Value && !selectionCentreButton.Selected.Disabled;
|
playfieldCentreButton.Selected.Disabled = scaleHandler.IsScalingSlider.Value && !selectionCentreButton.Selected.Disabled;
|
||||||
|
gridCentreButton.Selected.Disabled = playfieldCentreButton.Selected.Disabled;
|
||||||
|
|
||||||
scaleOrigin.Items.First(b => !b.Selected.Disabled).Select();
|
scaleOrigin.Items.First(b => !b.Selected.Disabled).Select();
|
||||||
|
|
||||||
scaleInfo.BindValueChanged(scale =>
|
scaleInfo.BindValueChanged(scale =>
|
||||||
{
|
{
|
||||||
var newScale = new Vector2(scale.NewValue.XAxis ? scale.NewValue.Scale : 1, scale.NewValue.YAxis ? scale.NewValue.Scale : 1);
|
var newScale = new Vector2(scale.NewValue.Scale, scale.NewValue.Scale);
|
||||||
scaleHandler.Update(newScale, getOriginPosition(scale.NewValue));
|
scaleHandler.Update(newScale, getOriginPosition(scale.NewValue), getAdjustAxis(scale.NewValue), getRotation(scale.NewValue));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateAxisCheckBoxesEnabled()
|
private void updateAxisCheckBoxesEnabled()
|
||||||
{
|
{
|
||||||
if (scaleInfo.Value.Origin == ScaleOrigin.PlayfieldCentre)
|
if (scaleInfo.Value.Origin != ScaleOrigin.SelectionCentre)
|
||||||
{
|
{
|
||||||
toggleAxisAvailable(xCheckBox.Current, true);
|
toggleAxisAvailable(xCheckBox.Current, true);
|
||||||
toggleAxisAvailable(yCheckBox.Current, true);
|
toggleAxisAvailable(yCheckBox.Current, true);
|
||||||
@ -162,7 +181,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
const float max_scale = 10;
|
const float max_scale = 10;
|
||||||
var scale = scaleHandler.ClampScaleToPlayfieldBounds(new Vector2(max_scale), getOriginPosition(scaleInfo.Value));
|
var scale = scaleHandler.ClampScaleToPlayfieldBounds(new Vector2(max_scale), getOriginPosition(scaleInfo.Value), getAdjustAxis(scaleInfo.Value), getRotation(scaleInfo.Value));
|
||||||
|
|
||||||
if (!scaleInfo.Value.XAxis)
|
if (!scaleInfo.Value.XAxis)
|
||||||
scale.X = max_scale;
|
scale.X = max_scale;
|
||||||
@ -179,7 +198,30 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
updateAxisCheckBoxesEnabled();
|
updateAxisCheckBoxesEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Vector2? getOriginPosition(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.PlayfieldCentre ? OsuPlayfield.BASE_SIZE / 2 : null;
|
private Vector2? getOriginPosition(PreciseScaleInfo scale)
|
||||||
|
{
|
||||||
|
switch (scale.Origin)
|
||||||
|
{
|
||||||
|
case ScaleOrigin.GridCentre:
|
||||||
|
return gridToolbox.StartPosition.Value;
|
||||||
|
|
||||||
|
case ScaleOrigin.PlayfieldCentre:
|
||||||
|
return OsuPlayfield.BASE_SIZE / 2;
|
||||||
|
|
||||||
|
case ScaleOrigin.SelectionCentre:
|
||||||
|
if (selectedItems.Count == 1 && selectedItems.First() is Slider slider)
|
||||||
|
return slider.Position;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(scale));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Axes getAdjustAxis(PreciseScaleInfo scale) => scale.XAxis ? scale.YAxis ? Axes.Both : Axes.X : Axes.Y;
|
||||||
|
|
||||||
|
private float getRotation(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.GridCentre ? gridToolbox.GridLinesRotation.Value : 0;
|
||||||
|
|
||||||
private void setAxis(bool x, bool y)
|
private void setAxis(bool x, bool y)
|
||||||
{
|
{
|
||||||
@ -204,6 +246,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
public enum ScaleOrigin
|
public enum ScaleOrigin
|
||||||
{
|
{
|
||||||
|
GridCentre,
|
||||||
PlayfieldCentre,
|
PlayfieldCentre,
|
||||||
SelectionCentre
|
SelectionCentre
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ using osu.Game.Rulesets.Osu.Objects;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit
|
namespace osu.Game.Rulesets.Osu.Edit
|
||||||
{
|
{
|
||||||
public class SliderCompositionTool : HitObjectCompositionTool
|
public class SliderCompositionTool : CompositionTool
|
||||||
{
|
{
|
||||||
public SliderCompositionTool()
|
public SliderCompositionTool()
|
||||||
: base(nameof(Slider))
|
: base(nameof(Slider))
|
||||||
@ -26,6 +26,6 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders);
|
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders);
|
||||||
|
|
||||||
public override PlacementBlueprint CreatePlacementBlueprint() => new SliderPlacementBlueprint();
|
public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new SliderPlacementBlueprint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ using osu.Game.Rulesets.Osu.Objects;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit
|
namespace osu.Game.Rulesets.Osu.Edit
|
||||||
{
|
{
|
||||||
public class SpinnerCompositionTool : HitObjectCompositionTool
|
public class SpinnerCompositionTool : CompositionTool
|
||||||
{
|
{
|
||||||
public SpinnerCompositionTool()
|
public SpinnerCompositionTool()
|
||||||
: base(nameof(Spinner))
|
: base(nameof(Spinner))
|
||||||
@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners);
|
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners);
|
||||||
|
|
||||||
public override PlacementBlueprint CreatePlacementBlueprint() => new SpinnerPlacementBlueprint();
|
public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new SpinnerPlacementBlueprint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,8 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
public SelectionRotationHandler RotationHandler { get; init; } = null!;
|
public SelectionRotationHandler RotationHandler { get; init; } = null!;
|
||||||
public OsuSelectionScaleHandler ScaleHandler { get; init; } = null!;
|
public OsuSelectionScaleHandler ScaleHandler { get; init; } = null!;
|
||||||
|
|
||||||
|
public OsuGridToolboxGroup GridToolbox { get; init; } = null!;
|
||||||
|
|
||||||
public TransformToolboxGroup()
|
public TransformToolboxGroup()
|
||||||
: base("transform")
|
: base("transform")
|
||||||
{
|
{
|
||||||
@ -44,10 +46,10 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
rotateButton = new EditorToolButton("Rotate",
|
rotateButton = new EditorToolButton("Rotate",
|
||||||
() => new SpriteIcon { Icon = FontAwesome.Solid.Undo },
|
() => new SpriteIcon { Icon = FontAwesome.Solid.Undo },
|
||||||
() => new PreciseRotationPopover(RotationHandler)),
|
() => new PreciseRotationPopover(RotationHandler, GridToolbox)),
|
||||||
scaleButton = new EditorToolButton("Scale",
|
scaleButton = new EditorToolButton("Scale",
|
||||||
() => new SpriteIcon { Icon = FontAwesome.Solid.ArrowsAlt },
|
() => new SpriteIcon { Icon = FontAwesome.Solid.ArrowsAlt },
|
||||||
() => new PreciseScalePopover(ScaleHandler))
|
() => new PreciseScalePopover(ScaleHandler, GridToolbox))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -91,20 +91,35 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
drawableObjectPiece.ApplyCustomUpdateState -= applyDimToDrawableHitObject;
|
drawableObjectPiece.ApplyCustomUpdateState -= applyDimToDrawableHitObject;
|
||||||
drawableObjectPiece.ApplyCustomUpdateState += applyDimToDrawableHitObject;
|
drawableObjectPiece.ApplyCustomUpdateState += applyDimToDrawableHitObject;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
applyDim(piece);
|
|
||||||
}
|
|
||||||
|
|
||||||
void applyDim(Drawable piece)
|
// but at the end apply the transforms now regardless of whether this is a DHO or not.
|
||||||
{
|
// the above is just to ensure they don't get overwritten later.
|
||||||
piece.FadeColour(new Color4(195, 195, 195, 255));
|
applyDim(piece);
|
||||||
using (piece.BeginDelayedSequence(InitialLifetimeOffset - OsuHitWindows.MISS_WINDOW))
|
|
||||||
piece.FadeColour(Color4.White, 100);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void applyDimToDrawableHitObject(DrawableHitObject dho, ArmedState _) => applyDim(dho);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void ClearNestedHitObjects()
|
||||||
|
{
|
||||||
|
base.ClearNestedHitObjects();
|
||||||
|
|
||||||
|
// any dimmable pieces that are DHOs will be pooled separately.
|
||||||
|
// `applyDimToDrawableHitObject` is a closure that implicitly captures `this`,
|
||||||
|
// and because of separate pooling of parent and child objects, there is no guarantee that the pieces will be associated with `this` again on re-use.
|
||||||
|
// therefore, clean up the subscription here to avoid crosstalk.
|
||||||
|
// not doing so can result in the callback attempting to read things from `this` when it is in a completely bogus state (not in use or similar).
|
||||||
|
foreach (var piece in DimmablePieces.OfType<DrawableHitObject>())
|
||||||
|
piece.ApplyCustomUpdateState -= applyDimToDrawableHitObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyDim(Drawable piece)
|
||||||
|
{
|
||||||
|
piece.FadeColour(new Color4(195, 195, 195, 255));
|
||||||
|
using (piece.BeginDelayedSequence(InitialLifetimeOffset - OsuHitWindows.MISS_WINDOW))
|
||||||
|
piece.FadeColour(Color4.White, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyDimToDrawableHitObject(DrawableHitObject dho, ArmedState _) => applyDim(dho);
|
||||||
|
|
||||||
protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt;
|
protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt;
|
||||||
|
|
||||||
private OsuInputManager osuActionInputManager;
|
private OsuInputManager osuActionInputManager;
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Game.Audio;
|
||||||
|
using osu.Game.Rulesets.Taiko.Skinning.Argon;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Tests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class VolumeAwareHitSampleInfoTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestVolumeAwareHitSampleInfoIsNotEqualToItsUnderlyingSample(
|
||||||
|
[Values(HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP)]
|
||||||
|
string sample,
|
||||||
|
[Values(HitSampleInfo.BANK_NORMAL, HitSampleInfo.BANK_SOFT)]
|
||||||
|
string bank,
|
||||||
|
[Values(30, 70, 100)] int volume)
|
||||||
|
{
|
||||||
|
var underlyingSample = new HitSampleInfo(sample, bank, volume: volume);
|
||||||
|
var volumeAwareSample = new VolumeAwareHitSampleInfo(underlyingSample);
|
||||||
|
|
||||||
|
Assert.That(underlyingSample, Is.Not.EqualTo(volumeAwareSample));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -14,7 +14,6 @@ using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
|
|||||||
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Colour;
|
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Colour;
|
||||||
using osu.Game.Rulesets.Taiko.Difficulty.Skills;
|
using osu.Game.Rulesets.Taiko.Difficulty.Skills;
|
||||||
using osu.Game.Rulesets.Taiko.Mods;
|
using osu.Game.Rulesets.Taiko.Mods;
|
||||||
using osu.Game.Rulesets.Taiko.Objects;
|
|
||||||
using osu.Game.Rulesets.Taiko.Scoring;
|
using osu.Game.Rulesets.Taiko.Scoring;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Difficulty
|
namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||||
@ -100,7 +99,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
ColourDifficulty = colourRating,
|
ColourDifficulty = colourRating,
|
||||||
PeakDifficulty = combinedRating,
|
PeakDifficulty = combinedRating,
|
||||||
GreatHitWindow = hitWindows.WindowFor(HitResult.Great) / clockRate,
|
GreatHitWindow = hitWindows.WindowFor(HitResult.Great) / clockRate,
|
||||||
MaxCombo = beatmap.HitObjects.Count(h => h is Hit),
|
MaxCombo = beatmap.GetMaxCombo(),
|
||||||
};
|
};
|
||||||
|
|
||||||
return attributes;
|
return attributes;
|
||||||
|
@ -10,7 +10,7 @@ using osuTK.Input;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
||||||
{
|
{
|
||||||
public partial class HitPlacementBlueprint : PlacementBlueprint
|
public partial class HitPlacementBlueprint : HitObjectPlacementBlueprint
|
||||||
{
|
{
|
||||||
private readonly HitPiece piece;
|
private readonly HitPiece piece;
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ using osuTK.Input;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
||||||
{
|
{
|
||||||
public partial class TaikoSpanPlacementBlueprint : PlacementBlueprint
|
public partial class TaikoSpanPlacementBlueprint : HitObjectPlacementBlueprint
|
||||||
{
|
{
|
||||||
private readonly HitPiece headPiece;
|
private readonly HitPiece headPiece;
|
||||||
private readonly HitPiece tailPiece;
|
private readonly HitPiece tailPiece;
|
||||||
|
@ -10,7 +10,7 @@ using osu.Game.Rulesets.Taiko.Objects;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Edit
|
namespace osu.Game.Rulesets.Taiko.Edit
|
||||||
{
|
{
|
||||||
public class DrumRollCompositionTool : HitObjectCompositionTool
|
public class DrumRollCompositionTool : CompositionTool
|
||||||
{
|
{
|
||||||
public DrumRollCompositionTool()
|
public DrumRollCompositionTool()
|
||||||
: base(nameof(DrumRoll))
|
: base(nameof(DrumRoll))
|
||||||
@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Taiko.Edit
|
|||||||
|
|
||||||
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders);
|
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders);
|
||||||
|
|
||||||
public override PlacementBlueprint CreatePlacementBlueprint() => new DrumRollPlacementBlueprint();
|
public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new DrumRollPlacementBlueprint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ using osu.Game.Rulesets.Taiko.Objects;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Edit
|
namespace osu.Game.Rulesets.Taiko.Edit
|
||||||
{
|
{
|
||||||
public class HitCompositionTool : HitObjectCompositionTool
|
public class HitCompositionTool : CompositionTool
|
||||||
{
|
{
|
||||||
public HitCompositionTool()
|
public HitCompositionTool()
|
||||||
: base(nameof(Hit))
|
: base(nameof(Hit))
|
||||||
@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Taiko.Edit
|
|||||||
|
|
||||||
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles);
|
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles);
|
||||||
|
|
||||||
public override PlacementBlueprint CreatePlacementBlueprint() => new HitPlacementBlueprint();
|
public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new HitPlacementBlueprint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ using osu.Game.Rulesets.Taiko.Objects;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Edit
|
namespace osu.Game.Rulesets.Taiko.Edit
|
||||||
{
|
{
|
||||||
public class SwellCompositionTool : HitObjectCompositionTool
|
public class SwellCompositionTool : CompositionTool
|
||||||
{
|
{
|
||||||
public SwellCompositionTool()
|
public SwellCompositionTool()
|
||||||
: base(nameof(Swell))
|
: base(nameof(Swell))
|
||||||
@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Taiko.Edit
|
|||||||
|
|
||||||
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners);
|
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners);
|
||||||
|
|
||||||
public override PlacementBlueprint CreatePlacementBlueprint() => new SwellPlacementBlueprint();
|
public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new SwellPlacementBlueprint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Taiko.Edit
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IReadOnlyList<HitObjectCompositionTool> CompositionTools => new HitObjectCompositionTool[]
|
protected override IReadOnlyList<CompositionTool> CompositionTools => new CompositionTool[]
|
||||||
{
|
{
|
||||||
new HitCompositionTool(),
|
new HitCompositionTool(),
|
||||||
new DrumRollCompositionTool(),
|
new DrumRollCompositionTool(),
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
|
|
||||||
@ -48,5 +49,24 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
|
|||||||
return originalBank;
|
return originalBank;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool Equals(HitSampleInfo? other) => other is VolumeAwareHitSampleInfo && base.Equals(other);
|
||||||
|
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// This override attempts to match the <see cref="Equals"/> override above, but in theory it is not strictly necessary.
|
||||||
|
/// Recall that <see cref="GetHashCode"/> <a href="https://learn.microsoft.com/en-us/dotnet/api/system.object.gethashcode?view=net-8.0#notes-to-inheritors">must meet the following requirements</a>:
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// "If two objects compare as equal, the <see cref="GetHashCode"/> method for each object must return the same value.
|
||||||
|
/// However, if two objects do not compare as equal, <see cref="GetHashCode"/> methods for the two objects do not have to return different values."
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Making this override combine the value generated by the base <see cref="GetHashCode"/> implementation with a constant means
|
||||||
|
/// that <see cref="HitSampleInfo"/> and <see cref="VolumeAwareHitSampleInfo"/> instances which have the same values of their members
|
||||||
|
/// will not have equal hash codes, which is slightly more efficient when these objects are used as dictionary keys.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
52
osu.Game.Tests/Utils/BindableValueAccessorTest.cs
Normal file
52
osu.Game.Tests/Utils/BindableValueAccessorTest.cs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Game.Utils;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Utils
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class BindableValueAccessorTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void GetValue()
|
||||||
|
{
|
||||||
|
const int value = 1337;
|
||||||
|
|
||||||
|
BindableInt bindable = new BindableInt(value);
|
||||||
|
Assert.That(BindableValueAccessor.GetValue(bindable), Is.EqualTo(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void SetValue()
|
||||||
|
{
|
||||||
|
const int value = 1337;
|
||||||
|
|
||||||
|
BindableInt bindable = new BindableInt();
|
||||||
|
BindableValueAccessor.SetValue(bindable, value);
|
||||||
|
|
||||||
|
Assert.That(bindable.Value, Is.EqualTo(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void GetInvalidBindable()
|
||||||
|
{
|
||||||
|
BindableList<object> list = new BindableList<object>();
|
||||||
|
Assert.That(BindableValueAccessor.GetValue(list), Is.EqualTo(list));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void SetInvalidBindable()
|
||||||
|
{
|
||||||
|
const int value = 1337;
|
||||||
|
|
||||||
|
BindableList<int> list = new BindableList<int> { value };
|
||||||
|
BindableValueAccessor.SetValue(list, 2);
|
||||||
|
|
||||||
|
Assert.That(list, Has.Exactly(1).Items);
|
||||||
|
Assert.That(list[0], Is.EqualTo(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
33
osu.Game.Tests/Utils/GeometryUtilsTest.cs
Normal file
33
osu.Game.Tests/Utils/GeometryUtilsTest.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Game.Utils;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Utils
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class GeometryUtilsTest
|
||||||
|
{
|
||||||
|
[TestCase(new int[] { }, new int[] { })]
|
||||||
|
[TestCase(new[] { 0, 0 }, new[] { 0, 0 })]
|
||||||
|
[TestCase(new[] { 0, 0, 1, 1, 1, -1, 2, 0 }, new[] { 0, 0, 1, 1, 2, 0, 1, -1 })]
|
||||||
|
[TestCase(new[] { 0, 0, 1, 1, 1, -1, 2, 0, 1, 0 }, new[] { 0, 0, 1, 1, 2, 0, 1, -1 })]
|
||||||
|
[TestCase(new[] { 0, 0, 1, 1, 2, -1, 2, 0, 1, 0, 4, 10 }, new[] { 0, 0, 4, 10, 2, -1 })]
|
||||||
|
public void TestConvexHull(int[] values, int[] expected)
|
||||||
|
{
|
||||||
|
var points = new Vector2[values.Length / 2];
|
||||||
|
for (int i = 0; i < values.Length; i += 2)
|
||||||
|
points[i / 2] = new Vector2(values[i], values[i + 1]);
|
||||||
|
|
||||||
|
var expectedPoints = new Vector2[expected.Length / 2];
|
||||||
|
for (int i = 0; i < expected.Length; i += 2)
|
||||||
|
expectedPoints[i / 2] = new Vector2(expected[i], expected[i + 1]);
|
||||||
|
|
||||||
|
var hull = GeometryUtils.GetConvexHull(points);
|
||||||
|
|
||||||
|
Assert.That(hull, Is.EquivalentTo(expectedPoints));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -134,7 +134,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
OriginalSurroundingQuad = new Quad(targetContainer!.X, targetContainer.Y, targetContainer.Width, targetContainer.Height);
|
OriginalSurroundingQuad = new Quad(targetContainer!.X, targetContainer.Y, targetContainer.Width, targetContainer.Height);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both)
|
public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both, float axisRotation = 0)
|
||||||
{
|
{
|
||||||
if (targetContainer == null)
|
if (targetContainer == null)
|
||||||
throw new InvalidOperationException($"Cannot {nameof(Update)} a scale operation without calling {nameof(Begin)} first!");
|
throw new InvalidOperationException($"Cannot {nameof(Update)} a scale operation without calling {nameof(Begin)} first!");
|
||||||
|
@ -144,6 +144,28 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
exitViaEscapeAndConfirm();
|
exitViaEscapeAndConfirm();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestEnterGameplayWhileFilteringToNoSelection()
|
||||||
|
{
|
||||||
|
TestPlaySongSelect songSelect = null;
|
||||||
|
|
||||||
|
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||||
|
AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
|
||||||
|
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
|
||||||
|
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
|
||||||
|
|
||||||
|
AddStep("force selection", () =>
|
||||||
|
{
|
||||||
|
songSelect.FinaliseSelection();
|
||||||
|
songSelect.FilterControl.CurrentTextSearch.Value = "test";
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for player", () => !songSelect.IsCurrentScreen());
|
||||||
|
AddStep("return to song select", () => songSelect.MakeCurrent());
|
||||||
|
|
||||||
|
AddUntilStep("wait for selection lost", () => songSelect.Beatmap.IsDefault);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestSongSelectBackActionHandling()
|
public void TestSongSelectBackActionHandling()
|
||||||
{
|
{
|
||||||
|
@ -5,14 +5,18 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Online;
|
using osu.Game.Online;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Difficulty;
|
using osu.Game.Rulesets.Difficulty;
|
||||||
@ -23,6 +27,7 @@ using osu.Game.Screens.Ranking.Statistics;
|
|||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Screens.Ranking.Statistics.User;
|
||||||
using osu.Game.Tests.Resources;
|
using osu.Game.Tests.Resources;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -80,6 +85,69 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
loadPanel(null);
|
loadPanel(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestStatisticsShownCorrectlyIfUpdateDeliveredBeforeLoad()
|
||||||
|
{
|
||||||
|
UserStatisticsWatcher userStatisticsWatcher = null!;
|
||||||
|
ScoreInfo score = null!;
|
||||||
|
|
||||||
|
AddStep("create user statistics watcher", () => Add(userStatisticsWatcher = new UserStatisticsWatcher()));
|
||||||
|
AddStep("set user statistics update", () =>
|
||||||
|
{
|
||||||
|
score = TestResources.CreateTestScoreInfo();
|
||||||
|
score.OnlineID = 1234;
|
||||||
|
((Bindable<UserStatisticsUpdate>)userStatisticsWatcher.LatestUpdate).Value = new UserStatisticsUpdate(score,
|
||||||
|
new UserStatistics
|
||||||
|
{
|
||||||
|
Level = new UserStatistics.LevelInfo
|
||||||
|
{
|
||||||
|
Current = 5,
|
||||||
|
Progress = 20,
|
||||||
|
},
|
||||||
|
GlobalRank = 38000,
|
||||||
|
CountryRank = 12006,
|
||||||
|
PP = 2134,
|
||||||
|
RankedScore = 21123849,
|
||||||
|
Accuracy = 0.985,
|
||||||
|
PlayCount = 13375,
|
||||||
|
PlayTime = 354490,
|
||||||
|
TotalScore = 128749597,
|
||||||
|
TotalHits = 0,
|
||||||
|
MaxCombo = 1233,
|
||||||
|
}, new UserStatistics
|
||||||
|
{
|
||||||
|
Level = new UserStatistics.LevelInfo
|
||||||
|
{
|
||||||
|
Current = 5,
|
||||||
|
Progress = 30,
|
||||||
|
},
|
||||||
|
GlobalRank = 36000,
|
||||||
|
CountryRank = 12000,
|
||||||
|
PP = (decimal)2134.5,
|
||||||
|
RankedScore = 23897015,
|
||||||
|
Accuracy = 0.984,
|
||||||
|
PlayCount = 13376,
|
||||||
|
PlayTime = 35789,
|
||||||
|
TotalScore = 132218497,
|
||||||
|
TotalHits = 0,
|
||||||
|
MaxCombo = 1233,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
AddStep("load user statistics panel", () => Child = new DependencyProvidingContainer
|
||||||
|
{
|
||||||
|
CachedDependencies = [(typeof(UserStatisticsWatcher), userStatisticsWatcher)],
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = new UserStatisticsPanel(score)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
State = { Value = Visibility.Visible },
|
||||||
|
Score = { Value = score, }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
AddUntilStep("overall ranking present", () => this.ChildrenOfType<OverallRanking>().Any());
|
||||||
|
AddUntilStep("loading spinner not visible", () => this.ChildrenOfType<LoadingLayer>().All(l => l.State.Value == Visibility.Hidden));
|
||||||
|
}
|
||||||
|
|
||||||
private void loadPanel(ScoreInfo score) => AddStep("load panel", () =>
|
private void loadPanel(ScoreInfo score) => AddStep("load panel", () =>
|
||||||
{
|
{
|
||||||
Child = new UserStatisticsPanel(score)
|
Child = new UserStatisticsPanel(score)
|
||||||
|
@ -9,6 +9,11 @@ namespace osu.Game.Tests.Visual.Settings
|
|||||||
{
|
{
|
||||||
public partial class TestSceneDirectorySelector : ThemeComparisonTestScene
|
public partial class TestSceneDirectorySelector : ThemeComparisonTestScene
|
||||||
{
|
{
|
||||||
|
public TestSceneDirectorySelector()
|
||||||
|
: base(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
protected override Drawable CreateContent() => new OsuDirectorySelector
|
protected override Drawable CreateContent() => new OsuDirectorySelector
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
|
@ -1,37 +1,49 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Graphics;
|
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Tests.Visual.UserInterface;
|
using osu.Game.Tests.Visual.UserInterface;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Settings
|
namespace osu.Game.Tests.Visual.Settings
|
||||||
{
|
{
|
||||||
public partial class TestSceneFileSelector : ThemeComparisonTestScene
|
public partial class TestSceneFileSelector : ThemeComparisonTestScene
|
||||||
{
|
{
|
||||||
[Resolved]
|
public TestSceneFileSelector()
|
||||||
private OsuColour colours { get; set; } = null!;
|
: base(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestJpgFilesOnly()
|
public void TestJpgFilesOnly()
|
||||||
{
|
{
|
||||||
AddStep("create", () =>
|
AddStep("create", () =>
|
||||||
{
|
{
|
||||||
ContentContainer.Children = new Drawable[]
|
var colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
||||||
|
|
||||||
|
ContentContainer.Child = new DependencyProvidingContainer
|
||||||
{
|
{
|
||||||
new Box
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
CachedDependencies = new (Type, object)[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
(typeof(OverlayColourProvider), colourProvider)
|
||||||
Colour = colours.GreySeaFoam
|
|
||||||
},
|
},
|
||||||
new OsuFileSelector(validFileExtensions: new[] { ".jpg" })
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
new Box
|
||||||
},
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = colourProvider.Background3
|
||||||
|
},
|
||||||
|
new OsuFileSelector(validFileExtensions: new[] { ".jpg" })
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ using NUnit.Framework;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Overlays.Settings;
|
using osu.Game.Overlays.Settings;
|
||||||
@ -19,16 +20,20 @@ namespace osu.Game.Tests.Visual.Settings
|
|||||||
{
|
{
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new FillFlowContainer
|
new PopoverContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Direction = FillDirection.Vertical,
|
Child = new FillFlowContainer
|
||||||
Spacing = new Vector2(20),
|
{
|
||||||
Width = 0.5f,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Anchor = Anchor.Centre,
|
Direction = FillDirection.Vertical,
|
||||||
Origin = Anchor.Centre,
|
Spacing = new Vector2(20),
|
||||||
Padding = new MarginPadding(50),
|
Width = 0.5f,
|
||||||
ChildrenEnumerable = new TestTargetClass().CreateSettingsControls()
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Padding = new MarginPadding(50),
|
||||||
|
ChildrenEnumerable = new TestTargetClass().CreateSettingsControls()
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -66,6 +71,13 @@ namespace osu.Game.Tests.Visual.Settings
|
|||||||
|
|
||||||
[SettingSource("Sample number textbox", "Textbox number entry", SettingControlType = typeof(SettingsNumberBox))]
|
[SettingSource("Sample number textbox", "Textbox number entry", SettingControlType = typeof(SettingsNumberBox))]
|
||||||
public Bindable<int?> IntTextBoxBindable { get; } = new Bindable<int?>();
|
public Bindable<int?> IntTextBoxBindable { get; } = new Bindable<int?>();
|
||||||
|
|
||||||
|
[SettingSource("Sample colour", "Change the colour", SettingControlType = typeof(SettingsColour))]
|
||||||
|
public BindableColour4 ColourBindable { get; } = new BindableColour4
|
||||||
|
{
|
||||||
|
Default = Colour4.White,
|
||||||
|
Value = Colour4.Red
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum TestEnum
|
private enum TestEnum
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Cursor;
|
using osu.Framework.Graphics.Cursor;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Graphics.Cursor;
|
using osu.Game.Graphics.Cursor;
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
@ -68,6 +70,41 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
HintText = EditorSetupStrings.LetterboxDuringBreaksDescription,
|
HintText = EditorSetupStrings.LetterboxDuringBreaksDescription,
|
||||||
Current = { Disabled = true },
|
Current = { Disabled = true },
|
||||||
},
|
},
|
||||||
|
new FormSliderBar<float>
|
||||||
|
{
|
||||||
|
Caption = "Instantaneous slider",
|
||||||
|
Current = new BindableFloat
|
||||||
|
{
|
||||||
|
MinValue = 0,
|
||||||
|
MaxValue = 10,
|
||||||
|
Value = 5,
|
||||||
|
Precision = 0.1f,
|
||||||
|
},
|
||||||
|
TabbableContentContainer = this,
|
||||||
|
},
|
||||||
|
new FormSliderBar<float>
|
||||||
|
{
|
||||||
|
Caption = "Non-instantaneous slider",
|
||||||
|
Current = new BindableFloat
|
||||||
|
{
|
||||||
|
MinValue = 0,
|
||||||
|
MaxValue = 10,
|
||||||
|
Value = 5,
|
||||||
|
Precision = 0.1f,
|
||||||
|
},
|
||||||
|
Instantaneous = false,
|
||||||
|
TabbableContentContainer = this,
|
||||||
|
},
|
||||||
|
new FormEnumDropdown<CountdownType>
|
||||||
|
{
|
||||||
|
Caption = EditorSetupStrings.EnableCountdown,
|
||||||
|
HintText = EditorSetupStrings.CountdownDescription,
|
||||||
|
},
|
||||||
|
new FormFileSelector
|
||||||
|
{
|
||||||
|
Caption = "Audio file",
|
||||||
|
PlaceholderText = "Select an audio file",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,75 @@
|
|||||||
|
// 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.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
using osu.Game.Overlays.Settings;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
|
{
|
||||||
|
public partial class TestSceneSettingsColour : OsuManualInputManagerTestScene
|
||||||
|
{
|
||||||
|
private SettingsColour? component;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestColour()
|
||||||
|
{
|
||||||
|
createContent();
|
||||||
|
|
||||||
|
AddRepeatStep("set random colour", () => component!.Current.Value = randomColour(), 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestUserInteractions()
|
||||||
|
{
|
||||||
|
createContent();
|
||||||
|
|
||||||
|
AddStep("click colour", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(component!);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("colour picker spawned", () => this.ChildrenOfType<OsuColourPicker>().Any());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createContent()
|
||||||
|
{
|
||||||
|
AddStep("create component", () =>
|
||||||
|
{
|
||||||
|
Child = new PopoverContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = new FillFlowContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Width = 500,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
component = new SettingsColour
|
||||||
|
{
|
||||||
|
LabelText = "a sample component",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Colour4 randomColour() => new Color4(
|
||||||
|
RNG.NextSingle(),
|
||||||
|
RNG.NextSingle(),
|
||||||
|
RNG.NextSingle(),
|
||||||
|
1);
|
||||||
|
}
|
||||||
|
}
|
@ -27,6 +27,9 @@ namespace osu.Game.Tournament.Screens.Setup
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private MatchIPCInfo ipc { get; set; } = null!;
|
private MatchIPCInfo ipc { get; set; } = null!;
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
|
||||||
|
|
||||||
private OsuDirectorySelector directorySelector = null!;
|
private OsuDirectorySelector directorySelector = null!;
|
||||||
private DialogOverlay? overlay;
|
private DialogOverlay? overlay;
|
||||||
|
|
||||||
|
@ -96,7 +96,7 @@ namespace osu.Game.Audio
|
|||||||
public virtual HitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<string?> newSuffix = default, Optional<int> newVolume = default)
|
public virtual HitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<string?> newSuffix = default, Optional<int> newVolume = default)
|
||||||
=> new HitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newSuffix.GetOr(Suffix), newVolume.GetOr(Volume));
|
=> new HitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newSuffix.GetOr(Suffix), newVolume.GetOr(Volume));
|
||||||
|
|
||||||
public bool Equals(HitSampleInfo? other)
|
public virtual bool Equals(HitSampleInfo? other)
|
||||||
=> other != null && Name == other.Name && Bank == other.Bank && Suffix == other.Suffix;
|
=> other != null && Name == other.Name && Bank == other.Bank && Suffix == other.Suffix;
|
||||||
|
|
||||||
public override bool Equals(object? obj)
|
public override bool Equals(object? obj)
|
||||||
|
@ -198,8 +198,11 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
if (beatmapSet.OnlineID > 0)
|
if (beatmapSet.OnlineID > 0)
|
||||||
{
|
{
|
||||||
|
// Required local for iOS. Will cause runtime crash if inlined.
|
||||||
|
int onlineId = beatmapSet.OnlineID;
|
||||||
|
|
||||||
// OnlineID should really be unique, but to avoid catastrophic failure let's iterate just to be sure.
|
// OnlineID should really be unique, but to avoid catastrophic failure let's iterate just to be sure.
|
||||||
foreach (var existingSetWithSameOnlineID in realm.All<BeatmapSetInfo>().Where(b => b.OnlineID == beatmapSet.OnlineID))
|
foreach (var existingSetWithSameOnlineID in realm.All<BeatmapSetInfo>().Where(b => b.OnlineID == onlineId))
|
||||||
{
|
{
|
||||||
existingSetWithSameOnlineID.DeletePending = true;
|
existingSetWithSameOnlineID.DeletePending = true;
|
||||||
existingSetWithSameOnlineID.OnlineID = -1;
|
existingSetWithSameOnlineID.OnlineID = -1;
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
@ -15,6 +14,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Overlays.Settings;
|
using osu.Game.Overlays.Settings;
|
||||||
|
using osu.Game.Utils;
|
||||||
|
|
||||||
namespace osu.Game.Configuration
|
namespace osu.Game.Configuration
|
||||||
{
|
{
|
||||||
@ -186,6 +186,16 @@ namespace osu.Game.Configuration
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case BindableColour4 bColour:
|
||||||
|
yield return new SettingsColour
|
||||||
|
{
|
||||||
|
LabelText = attr.Label,
|
||||||
|
TooltipText = attr.Description,
|
||||||
|
Current = bColour
|
||||||
|
};
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
case IBindable bindable:
|
case IBindable bindable:
|
||||||
var dropdownType = typeof(ModSettingsEnumDropdown<>).MakeGenericType(bindable.GetType().GetGenericArguments()[0]);
|
var dropdownType = typeof(ModSettingsEnumDropdown<>).MakeGenericType(bindable.GetType().GetGenericArguments()[0]);
|
||||||
var dropdown = (Drawable)Activator.CreateInstance(dropdownType)!;
|
var dropdown = (Drawable)Activator.CreateInstance(dropdownType)!;
|
||||||
@ -227,11 +237,11 @@ namespace osu.Game.Configuration
|
|||||||
case Bindable<bool> b:
|
case Bindable<bool> b:
|
||||||
return b.Value;
|
return b.Value;
|
||||||
|
|
||||||
|
case BindableColour4 c:
|
||||||
|
return c.Value.ToHex();
|
||||||
|
|
||||||
case IBindable u:
|
case IBindable u:
|
||||||
// An unknown (e.g. enum) generic type.
|
return BindableValueAccessor.GetValue(u);
|
||||||
var valueMethod = u.GetType().GetProperty(nameof(IBindable<int>.Value));
|
|
||||||
Debug.Assert(valueMethod != null);
|
|
||||||
return valueMethod.GetValue(u)!;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// fall back for non-bindable cases.
|
// fall back for non-bindable cases.
|
||||||
|
@ -75,7 +75,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(OverlayColourProvider? colourProvider, OsuColour colours, AudioManager audio)
|
private void load(OverlayColourProvider? colourProvider, OsuColour colours, AudioManager audio)
|
||||||
{
|
{
|
||||||
BackgroundColour = colourProvider?.Background5 ?? Color4.Black.Opacity(0.5f);
|
BackgroundColour = colourProvider?.Background5 ?? Color4.Black;
|
||||||
HoverColour = colourProvider?.Light4 ?? colours.PinkDarker;
|
HoverColour = colourProvider?.Light4 ?? colours.PinkDarker;
|
||||||
SelectionColour = colourProvider?.Background3 ?? colours.PinkDarker.Opacity(0.5f);
|
SelectionColour = colourProvider?.Background3 ?? colours.PinkDarker.Opacity(0.5f);
|
||||||
|
|
||||||
@ -397,7 +397,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
{
|
{
|
||||||
bool hovered = Enabled.Value && IsHovered;
|
bool hovered = Enabled.Value && IsHovered;
|
||||||
var hoveredColour = colourProvider?.Light4 ?? colours.PinkDarker;
|
var hoveredColour = colourProvider?.Light4 ?? colours.PinkDarker;
|
||||||
var unhoveredColour = colourProvider?.Background5 ?? Color4.Black.Opacity(0.5f);
|
var unhoveredColour = colourProvider?.Background5 ?? Color4.Black;
|
||||||
|
|
||||||
Colour = Color4.White;
|
Colour = Color4.White;
|
||||||
Alpha = Enabled.Value ? 1 : 0.3f;
|
Alpha = Enabled.Value ? 1 : 0.3f;
|
||||||
|
@ -46,7 +46,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
CurrentNumber.BindValueChanged(current => TooltipText = getTooltipText(current.NewValue), true);
|
CurrentNumber.BindValueChanged(current => TooltipText = GetDisplayableValue(current.NewValue), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnUserChange(T value)
|
protected override void OnUserChange(T value)
|
||||||
@ -55,7 +55,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
|
|
||||||
playSample(value);
|
playSample(value);
|
||||||
|
|
||||||
TooltipText = getTooltipText(value);
|
TooltipText = GetDisplayableValue(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void playSample(T value)
|
private void playSample(T value)
|
||||||
@ -83,7 +83,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
channel.Play();
|
channel.Play();
|
||||||
}
|
}
|
||||||
|
|
||||||
private LocalisableString getTooltipText(T value)
|
public LocalisableString GetDisplayableValue(T value)
|
||||||
{
|
{
|
||||||
if (CurrentNumber.IsInteger)
|
if (CurrentNumber.IsInteger)
|
||||||
return int.CreateTruncating(value).ToString("N0");
|
return int.CreateTruncating(value).ToString("N0");
|
||||||
|
@ -0,0 +1,59 @@
|
|||||||
|
// 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.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
|
||||||
|
namespace osu.Game.Graphics.UserInterfaceV2.FileSelection
|
||||||
|
{
|
||||||
|
internal partial class BackgroundLayer : CompositeDrawable
|
||||||
|
{
|
||||||
|
private Box background = null!;
|
||||||
|
|
||||||
|
private readonly float defaultAlpha;
|
||||||
|
|
||||||
|
public BackgroundLayer(float defaultAlpha = 0f)
|
||||||
|
{
|
||||||
|
Depth = float.MaxValue;
|
||||||
|
|
||||||
|
this.defaultAlpha = defaultAlpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider overlayColourProvider)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
Masking = true;
|
||||||
|
CornerRadius = 5;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new HoverClickSounds(),
|
||||||
|
background = new Box
|
||||||
|
{
|
||||||
|
Alpha = defaultAlpha,
|
||||||
|
Colour = overlayColourProvider.Background3,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
{
|
||||||
|
background.FadeTo(1, 200, Easing.OutQuint);
|
||||||
|
return base.OnHover(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
|
{
|
||||||
|
base.OnHoverLost(e);
|
||||||
|
background.FadeTo(defaultAlpha, 500, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,20 +8,23 @@ using osu.Game.Overlays;
|
|||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Graphics.UserInterfaceV2
|
namespace osu.Game.Graphics.UserInterfaceV2.FileSelection
|
||||||
{
|
{
|
||||||
internal partial class OsuDirectorySelectorHiddenToggle : OsuCheckbox
|
internal partial class HiddenFilesToggleCheckbox : OsuCheckbox
|
||||||
{
|
{
|
||||||
public OsuDirectorySelectorHiddenToggle()
|
public HiddenFilesToggleCheckbox()
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.None;
|
RelativeSizeAxes = Axes.None;
|
||||||
AutoSizeAxes = Axes.None;
|
AutoSizeAxes = Axes.None;
|
||||||
Size = new Vector2(100, 50);
|
Size = new Vector2(140, OsuDirectorySelectorBreadcrumbDisplay.HEIGHT);
|
||||||
|
Margin = new MarginPadding { Right = OsuDirectorySelectorBreadcrumbDisplay.HORIZONTAL_PADDING, };
|
||||||
Anchor = Anchor.CentreLeft;
|
Anchor = Anchor.CentreLeft;
|
||||||
Origin = Anchor.CentreLeft;
|
Origin = Anchor.CentreLeft;
|
||||||
LabelTextFlowContainer.Anchor = Anchor.CentreLeft;
|
LabelTextFlowContainer.Anchor = Anchor.CentreLeft;
|
||||||
LabelTextFlowContainer.Origin = Anchor.CentreLeft;
|
LabelTextFlowContainer.Origin = Anchor.CentreLeft;
|
||||||
LabelText = @"Show hidden";
|
LabelText = @"Show hidden";
|
||||||
|
|
||||||
|
Scale = new Vector2(0.8f);
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
@ -0,0 +1,106 @@
|
|||||||
|
// 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.IO;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Graphics.UserInterfaceV2.FileSelection
|
||||||
|
{
|
||||||
|
internal partial class OsuDirectorySelectorBreadcrumbDisplay : DirectorySelectorBreadcrumbDisplay
|
||||||
|
{
|
||||||
|
public const float HEIGHT = 45;
|
||||||
|
public const float HORIZONTAL_PADDING = 20;
|
||||||
|
|
||||||
|
protected override Drawable CreateCaption() => Empty().With(d =>
|
||||||
|
{
|
||||||
|
d.Origin = Anchor.CentreLeft;
|
||||||
|
d.Anchor = Anchor.CentreLeft;
|
||||||
|
d.Alpha = 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
protected override DirectorySelectorDirectory CreateRootDirectoryItem() => new OsuBreadcrumbDisplayComputer();
|
||||||
|
|
||||||
|
protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string? displayName = null) => new OsuBreadcrumbDisplayDirectory(directory, displayName);
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider colourProvider)
|
||||||
|
{
|
||||||
|
((FillFlowContainer)InternalChild).Padding = new MarginPadding
|
||||||
|
{
|
||||||
|
Horizontal = HORIZONTAL_PADDING,
|
||||||
|
Vertical = 10,
|
||||||
|
};
|
||||||
|
|
||||||
|
AddInternal(new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = colourProvider.Background4,
|
||||||
|
Depth = 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private partial class OsuBreadcrumbDisplayComputer : OsuBreadcrumbDisplayDirectory
|
||||||
|
{
|
||||||
|
protected override IconUsage? Icon => null;
|
||||||
|
|
||||||
|
public OsuBreadcrumbDisplayComputer()
|
||||||
|
: base(null, "Computer")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private partial class OsuBreadcrumbDisplayDirectory : DirectorySelectorDirectory
|
||||||
|
{
|
||||||
|
public OsuBreadcrumbDisplayDirectory(DirectoryInfo? directory, string? displayName = null)
|
||||||
|
: base(directory, displayName)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft;
|
||||||
|
Origin = Anchor.CentreLeft;
|
||||||
|
|
||||||
|
Flow.AutoSizeAxes = Axes.X;
|
||||||
|
Flow.Height = 25;
|
||||||
|
Flow.Margin = new MarginPadding { Horizontal = 10, };
|
||||||
|
|
||||||
|
AddRangeInternal(new Drawable[]
|
||||||
|
{
|
||||||
|
new BackgroundLayer(0.5f)
|
||||||
|
{
|
||||||
|
Depth = 1
|
||||||
|
},
|
||||||
|
new HoverClickSounds(),
|
||||||
|
});
|
||||||
|
|
||||||
|
Flow.Add(new SpriteIcon
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Icon = FontAwesome.Solid.ChevronRight,
|
||||||
|
Size = new Vector2(FONT_SIZE / 2),
|
||||||
|
Margin = new MarginPadding { Left = 5, },
|
||||||
|
});
|
||||||
|
Flow.Colour = colourProvider.Light3;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override SpriteText CreateSpriteText() => new OsuSpriteText().With(t => t.Font = OsuFont.Default.With(weight: FontWeight.SemiBold));
|
||||||
|
|
||||||
|
protected override IconUsage? Icon => Directory.Name.Contains(Path.DirectorySeparatorChar) ? FontAwesome.Solid.Database : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
// 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.IO;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
|
||||||
|
namespace osu.Game.Graphics.UserInterfaceV2.FileSelection
|
||||||
|
{
|
||||||
|
internal partial class OsuDirectorySelectorDirectory : DirectorySelectorDirectory
|
||||||
|
{
|
||||||
|
public OsuDirectorySelectorDirectory(DirectoryInfo directory, string? displayName = null)
|
||||||
|
: base(directory, displayName)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
Flow.AutoSizeAxes = Axes.X;
|
||||||
|
Flow.Height = OsuDirectorySelector.ITEM_HEIGHT;
|
||||||
|
|
||||||
|
AddInternal(new BackgroundLayer());
|
||||||
|
|
||||||
|
Colour = colours.Orange1;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override SpriteText CreateSpriteText() => new OsuSpriteText().With(t => t.Font = OsuFont.Default.With(weight: FontWeight.Bold));
|
||||||
|
|
||||||
|
protected override IconUsage? Icon => Directory.Name.Contains(Path.DirectorySeparatorChar)
|
||||||
|
? FontAwesome.Solid.Database
|
||||||
|
: FontAwesome.Regular.Folder;
|
||||||
|
}
|
||||||
|
}
|
@ -2,9 +2,11 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
|
||||||
namespace osu.Game.Graphics.UserInterfaceV2
|
namespace osu.Game.Graphics.UserInterfaceV2.FileSelection
|
||||||
{
|
{
|
||||||
internal partial class OsuDirectorySelectorParentDirectory : OsuDirectorySelectorDirectory
|
internal partial class OsuDirectorySelectorParentDirectory : OsuDirectorySelectorDirectory
|
||||||
{
|
{
|
||||||
@ -14,5 +16,11 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
: base(directory, "..")
|
: base(directory, "..")
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider colourProvider)
|
||||||
|
{
|
||||||
|
Colour = colourProvider.Content1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -29,7 +29,14 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
|
|
||||||
private readonly BindableWithCurrent<bool> current = new BindableWithCurrent<bool>();
|
private readonly BindableWithCurrent<bool> current = new BindableWithCurrent<bool>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Caption describing this slider bar, displayed on top of the controls.
|
||||||
|
/// </summary>
|
||||||
public LocalisableString Caption { get; init; }
|
public LocalisableString Caption { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hint text containing an extended description of this slider bar, displayed in a tooltip when hovering the caption.
|
||||||
|
/// </summary>
|
||||||
public LocalisableString HintText { get; init; }
|
public LocalisableString HintText { get; init; }
|
||||||
|
|
||||||
private Box background = null!;
|
private Box background = null!;
|
||||||
|
251
osu.Game/Graphics/UserInterfaceV2/FormDropdown.cs
Normal file
251
osu.Game/Graphics/UserInterfaceV2/FormDropdown.cs
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Colour;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Graphics.UserInterfaceV2
|
||||||
|
{
|
||||||
|
public partial class FormDropdown<T> : OsuDropdown<T>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Caption describing this slider bar, displayed on top of the controls.
|
||||||
|
/// </summary>
|
||||||
|
public LocalisableString Caption { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hint text containing an extended description of this slider bar, displayed in a tooltip when hovering the caption.
|
||||||
|
/// </summary>
|
||||||
|
public LocalisableString HintText { get; init; }
|
||||||
|
|
||||||
|
private FormDropdownHeader header = null!;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
|
||||||
|
header.Caption = Caption;
|
||||||
|
header.HintText = HintText;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override DropdownHeader CreateHeader() => header = new FormDropdownHeader
|
||||||
|
{
|
||||||
|
Dropdown = this,
|
||||||
|
};
|
||||||
|
|
||||||
|
protected override DropdownMenu CreateMenu() => new FormDropdownMenu();
|
||||||
|
|
||||||
|
private partial class FormDropdownHeader : DropdownHeader
|
||||||
|
{
|
||||||
|
public FormDropdown<T> Dropdown { get; set; } = null!;
|
||||||
|
|
||||||
|
protected override DropdownSearchBar CreateSearchBar() => SearchBar = new FormDropdownSearchBar();
|
||||||
|
|
||||||
|
private LocalisableString captionText;
|
||||||
|
private LocalisableString hintText;
|
||||||
|
private LocalisableString labelText;
|
||||||
|
|
||||||
|
public LocalisableString Caption
|
||||||
|
{
|
||||||
|
get => captionText;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
captionText = value;
|
||||||
|
|
||||||
|
if (caption.IsNotNull())
|
||||||
|
caption.Caption = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalisableString HintText
|
||||||
|
{
|
||||||
|
get => hintText;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
hintText = value;
|
||||||
|
|
||||||
|
if (caption.IsNotNull())
|
||||||
|
caption.TooltipText = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override LocalisableString Label
|
||||||
|
{
|
||||||
|
get => labelText;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
labelText = value;
|
||||||
|
|
||||||
|
if (label.IsNotNull())
|
||||||
|
label.Text = labelText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected new FormDropdownSearchBar SearchBar { get; set; } = null!;
|
||||||
|
|
||||||
|
private FormFieldCaption caption = null!;
|
||||||
|
private OsuSpriteText label = null!;
|
||||||
|
private SpriteIcon chevron = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
AutoSizeAxes = Axes.None;
|
||||||
|
Height = 50;
|
||||||
|
|
||||||
|
Masking = true;
|
||||||
|
CornerRadius = 5;
|
||||||
|
|
||||||
|
Foreground.AutoSizeAxes = Axes.None;
|
||||||
|
Foreground.RelativeSizeAxes = Axes.Both;
|
||||||
|
Foreground.Padding = new MarginPadding(9);
|
||||||
|
Foreground.Children = new Drawable[]
|
||||||
|
{
|
||||||
|
caption = new FormFieldCaption
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopLeft,
|
||||||
|
Origin = Anchor.TopLeft,
|
||||||
|
Caption = Caption,
|
||||||
|
TooltipText = HintText,
|
||||||
|
},
|
||||||
|
label = new OsuSpriteText
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
},
|
||||||
|
chevron = new SpriteIcon
|
||||||
|
{
|
||||||
|
Icon = FontAwesome.Solid.ChevronDown,
|
||||||
|
Anchor = Anchor.CentreRight,
|
||||||
|
Origin = Anchor.CentreRight,
|
||||||
|
Size = new Vector2(16),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
AddInternal(new HoverClickSounds());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
Dropdown.Current.BindDisabledChanged(_ => updateState());
|
||||||
|
SearchBar.SearchTerm.BindValueChanged(_ => updateState(), true);
|
||||||
|
Dropdown.Menu.StateChanged += _ =>
|
||||||
|
{
|
||||||
|
updateState();
|
||||||
|
updateChevron();
|
||||||
|
};
|
||||||
|
SearchBar.TextBox.OnCommit += (_, _) =>
|
||||||
|
{
|
||||||
|
Background.FlashColour(ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark2), 800, Easing.OutQuint);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
{
|
||||||
|
updateState();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
|
{
|
||||||
|
base.OnHoverLost(e);
|
||||||
|
updateState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateState()
|
||||||
|
{
|
||||||
|
label.Alpha = string.IsNullOrEmpty(SearchBar.SearchTerm.Value) ? 1 : 0;
|
||||||
|
|
||||||
|
caption.Colour = Dropdown.Current.Disabled ? colourProvider.Foreground1 : colourProvider.Content2;
|
||||||
|
label.Colour = Dropdown.Current.Disabled ? colourProvider.Foreground1 : colourProvider.Content1;
|
||||||
|
chevron.Colour = Dropdown.Current.Disabled ? colourProvider.Foreground1 : colourProvider.Content1;
|
||||||
|
DisabledColour = Colour4.White;
|
||||||
|
|
||||||
|
bool dropdownOpen = Dropdown.Menu.State == MenuState.Open;
|
||||||
|
|
||||||
|
if (!Dropdown.Current.Disabled)
|
||||||
|
{
|
||||||
|
BorderThickness = IsHovered || dropdownOpen ? 2 : 0;
|
||||||
|
BorderColour = dropdownOpen ? colourProvider.Highlight1 : colourProvider.Light4;
|
||||||
|
|
||||||
|
if (dropdownOpen)
|
||||||
|
Background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark3);
|
||||||
|
else if (IsHovered)
|
||||||
|
Background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark4);
|
||||||
|
else
|
||||||
|
Background.Colour = colourProvider.Background5;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Background.Colour = colourProvider.Background4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateChevron()
|
||||||
|
{
|
||||||
|
bool open = Dropdown.Menu.State == MenuState.Open;
|
||||||
|
chevron.ScaleTo(open ? new Vector2(1f, -1f) : Vector2.One, 300, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private partial class FormDropdownSearchBar : DropdownSearchBar
|
||||||
|
{
|
||||||
|
public FormTextBox.InnerTextBox TextBox { get; private set; } = null!;
|
||||||
|
|
||||||
|
protected override void PopIn() => this.FadeIn();
|
||||||
|
protected override void PopOut() => this.FadeOut();
|
||||||
|
|
||||||
|
protected override TextBox CreateTextBox() => TextBox = new FormTextBox.InnerTextBox();
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
TextBox.Anchor = Anchor.BottomLeft;
|
||||||
|
TextBox.Origin = Anchor.BottomLeft;
|
||||||
|
TextBox.RelativeSizeAxes = Axes.X;
|
||||||
|
TextBox.Margin = new MarginPadding(9);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private partial class FormDropdownMenu : OsuDropdownMenu
|
||||||
|
{
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider colourProvider)
|
||||||
|
{
|
||||||
|
ItemsContainer.Padding = new MarginPadding(9);
|
||||||
|
Margin = new MarginPadding { Top = 5 };
|
||||||
|
|
||||||
|
MaskingContainer.BorderThickness = 2;
|
||||||
|
MaskingContainer.BorderColour = colourProvider.Highlight1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class FormEnumDropdown<T> : FormDropdown<T>
|
||||||
|
where T : struct, Enum
|
||||||
|
{
|
||||||
|
public FormEnumDropdown()
|
||||||
|
{
|
||||||
|
Items = Enum.GetValues<T>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
277
osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs
Normal file
277
osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Colour;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
using osu.Game.Database;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Graphics.UserInterfaceV2
|
||||||
|
{
|
||||||
|
public partial class FormFileSelector : CompositeDrawable, IHasCurrentValue<FileInfo?>, ICanAcceptFiles, IHasPopover
|
||||||
|
{
|
||||||
|
public Bindable<FileInfo?> Current
|
||||||
|
{
|
||||||
|
get => current.Current;
|
||||||
|
set => current.Current = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly BindableWithCurrent<FileInfo?> current = new BindableWithCurrent<FileInfo?>();
|
||||||
|
|
||||||
|
public IEnumerable<string> HandledExtensions => handledExtensions;
|
||||||
|
|
||||||
|
private readonly string[] handledExtensions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The initial path to use when displaying the <see cref="FileChooserPopover"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Uses a <see langword="null"/> value before the first selection is made
|
||||||
|
/// to ensure that the first selection starts at <see cref="GameHost.InitialFileSelectorPath"/>.
|
||||||
|
/// </remarks>
|
||||||
|
private string? initialChooserPath;
|
||||||
|
|
||||||
|
private readonly Bindable<Visibility> popoverState = new Bindable<Visibility>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Caption describing this file selector, displayed on top of the controls.
|
||||||
|
/// </summary>
|
||||||
|
public LocalisableString Caption { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hint text containing an extended description of this file selector, displayed in a tooltip when hovering the caption.
|
||||||
|
/// </summary>
|
||||||
|
public LocalisableString HintText { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Text displayed in the selector when no file is selected.
|
||||||
|
/// </summary>
|
||||||
|
public LocalisableString PlaceholderText { get; init; }
|
||||||
|
|
||||||
|
private Box background = null!;
|
||||||
|
|
||||||
|
private FormFieldCaption caption = null!;
|
||||||
|
private OsuSpriteText placeholderText = null!;
|
||||||
|
private OsuSpriteText filenameText = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuGameBase game { get; set; } = null!;
|
||||||
|
|
||||||
|
public FormFileSelector(params string[] handledExtensions)
|
||||||
|
{
|
||||||
|
this.handledExtensions = handledExtensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
Height = 50;
|
||||||
|
|
||||||
|
Masking = true;
|
||||||
|
CornerRadius = 5;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
background = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = colourProvider.Background5,
|
||||||
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Padding = new MarginPadding(9),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
caption = new FormFieldCaption
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopLeft,
|
||||||
|
Origin = Anchor.TopLeft,
|
||||||
|
Caption = Caption,
|
||||||
|
TooltipText = HintText,
|
||||||
|
},
|
||||||
|
placeholderText = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Width = 1,
|
||||||
|
Text = PlaceholderText,
|
||||||
|
Colour = colourProvider.Foreground1,
|
||||||
|
},
|
||||||
|
filenameText = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Width = 1,
|
||||||
|
},
|
||||||
|
new SpriteIcon
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomRight,
|
||||||
|
Origin = Anchor.BottomRight,
|
||||||
|
Icon = FontAwesome.Solid.FolderOpen,
|
||||||
|
Size = new Vector2(16),
|
||||||
|
Colour = colourProvider.Light1,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
popoverState.BindValueChanged(_ => updateState());
|
||||||
|
current.BindValueChanged(_ =>
|
||||||
|
{
|
||||||
|
updateState();
|
||||||
|
onFileSelected();
|
||||||
|
});
|
||||||
|
current.BindDisabledChanged(_ => updateState(), true);
|
||||||
|
game.RegisterImportHandler(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onFileSelected()
|
||||||
|
{
|
||||||
|
if (Current.Value != null)
|
||||||
|
this.HidePopover();
|
||||||
|
|
||||||
|
initialChooserPath = Current.Value?.DirectoryName;
|
||||||
|
placeholderText.Alpha = Current.Value == null ? 1 : 0;
|
||||||
|
filenameText.Text = Current.Value?.Name ?? string.Empty;
|
||||||
|
background.FlashColour(ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark2), 800, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnClick(ClickEvent e)
|
||||||
|
{
|
||||||
|
this.ShowPopover();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
{
|
||||||
|
updateState();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
|
{
|
||||||
|
base.OnHoverLost(e);
|
||||||
|
updateState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateState()
|
||||||
|
{
|
||||||
|
caption.Colour = Current.Disabled ? colourProvider.Foreground1 : colourProvider.Content2;
|
||||||
|
filenameText.Colour = Current.Disabled ? colourProvider.Foreground1 : colourProvider.Content1;
|
||||||
|
|
||||||
|
if (!Current.Disabled)
|
||||||
|
{
|
||||||
|
BorderThickness = IsHovered || popoverState.Value == Visibility.Visible ? 2 : 0;
|
||||||
|
BorderColour = popoverState.Value == Visibility.Visible ? colourProvider.Highlight1 : colourProvider.Light4;
|
||||||
|
|
||||||
|
if (popoverState.Value == Visibility.Visible)
|
||||||
|
background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark3);
|
||||||
|
else if (IsHovered)
|
||||||
|
background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark4);
|
||||||
|
else
|
||||||
|
background.Colour = colourProvider.Background5;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
background.Colour = colourProvider.Background4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
if (game.IsNotNull())
|
||||||
|
game.UnregisterImportHandler(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Task ICanAcceptFiles.Import(params string[] paths)
|
||||||
|
{
|
||||||
|
Schedule(() => Current.Value = new FileInfo(paths.First()));
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
Task ICanAcceptFiles.Import(ImportTask[] tasks, ImportParameters parameters) => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public Popover GetPopover()
|
||||||
|
{
|
||||||
|
var popover = new FileChooserPopover(handledExtensions, Current, initialChooserPath);
|
||||||
|
popoverState.UnbindBindings();
|
||||||
|
popoverState.BindTo(popover.State);
|
||||||
|
return popover;
|
||||||
|
}
|
||||||
|
|
||||||
|
private partial class FileChooserPopover : OsuPopover
|
||||||
|
{
|
||||||
|
protected override string PopInSampleName => "UI/overlay-big-pop-in";
|
||||||
|
protected override string PopOutSampleName => "UI/overlay-big-pop-out";
|
||||||
|
|
||||||
|
public FileChooserPopover(string[] handledExtensions, Bindable<FileInfo?> currentFile, string? chooserPath)
|
||||||
|
: base(false)
|
||||||
|
{
|
||||||
|
Child = new Container
|
||||||
|
{
|
||||||
|
Size = new Vector2(600, 400),
|
||||||
|
Child = new OsuFileSelector(chooserPath, handledExtensions)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
CurrentFile = { BindTarget = currentFile }
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider colourProvider)
|
||||||
|
{
|
||||||
|
Add(new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
BorderThickness = 2,
|
||||||
|
CornerRadius = 10,
|
||||||
|
BorderColour = colourProvider.Highlight1,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = Color4.Transparent,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
374
osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs
Normal file
374
osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs
Normal file
@ -0,0 +1,374 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Numerics;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Colour;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Framework.Input;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
|
||||||
|
namespace osu.Game.Graphics.UserInterfaceV2
|
||||||
|
{
|
||||||
|
public partial class FormSliderBar<T> : CompositeDrawable, IHasCurrentValue<T>
|
||||||
|
where T : struct, INumber<T>, IMinMaxValue<T>
|
||||||
|
{
|
||||||
|
public Bindable<T> Current
|
||||||
|
{
|
||||||
|
get => current.Current;
|
||||||
|
set => current.Current = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool instantaneous = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether changes to the slider should instantaneously transfer to the text box (and vice versa).
|
||||||
|
/// If <see langword="false"/>, the transfer will happen on text box commit (explicit, or implicit via focus loss), or on slider drag end.
|
||||||
|
/// </summary>
|
||||||
|
public bool Instantaneous
|
||||||
|
{
|
||||||
|
get => instantaneous;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
instantaneous = value;
|
||||||
|
|
||||||
|
if (slider.IsNotNull())
|
||||||
|
slider.TransferValueOnCommit = !instantaneous;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private CompositeDrawable? tabbableContentContainer;
|
||||||
|
|
||||||
|
public CompositeDrawable? TabbableContentContainer
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
tabbableContentContainer = value;
|
||||||
|
|
||||||
|
if (textBox.IsNotNull())
|
||||||
|
textBox.TabbableContentContainer = tabbableContentContainer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly BindableNumberWithCurrent<T> current = new BindableNumberWithCurrent<T>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Caption describing this slider bar, displayed on top of the controls.
|
||||||
|
/// </summary>
|
||||||
|
public LocalisableString Caption { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hint text containing an extended description of this slider bar, displayed in a tooltip when hovering the caption.
|
||||||
|
/// </summary>
|
||||||
|
public LocalisableString HintText { get; init; }
|
||||||
|
|
||||||
|
private Box background = null!;
|
||||||
|
private Box flashLayer = null!;
|
||||||
|
private FormTextBox.InnerTextBox textBox = null!;
|
||||||
|
private Slider slider = null!;
|
||||||
|
private FormFieldCaption caption = null!;
|
||||||
|
private IFocusManager focusManager = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
Height = 50;
|
||||||
|
|
||||||
|
Masking = true;
|
||||||
|
CornerRadius = 5;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
background = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = colourProvider.Background5,
|
||||||
|
},
|
||||||
|
flashLayer = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Colour4.Transparent,
|
||||||
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Padding = new MarginPadding(9),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
caption = new FormFieldCaption
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopLeft,
|
||||||
|
Origin = Anchor.TopLeft,
|
||||||
|
Caption = Caption,
|
||||||
|
TooltipText = HintText,
|
||||||
|
},
|
||||||
|
textBox = new FormNumberBox.InnerNumberBox
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Width = 0.5f,
|
||||||
|
CommitOnFocusLost = true,
|
||||||
|
SelectAllOnFocus = true,
|
||||||
|
AllowDecimals = true,
|
||||||
|
OnInputError = () =>
|
||||||
|
{
|
||||||
|
flashLayer.Colour = ColourInfo.GradientVertical(colours.Red3.Opacity(0), colours.Red3);
|
||||||
|
flashLayer.FadeOutFromOne(200, Easing.OutQuint);
|
||||||
|
},
|
||||||
|
TabbableContentContainer = tabbableContentContainer,
|
||||||
|
},
|
||||||
|
slider = new Slider
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreRight,
|
||||||
|
Origin = Anchor.CentreRight,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Width = 0.5f,
|
||||||
|
Current = Current,
|
||||||
|
TransferValueOnCommit = !instantaneous,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
focusManager = GetContainingFocusManager()!;
|
||||||
|
|
||||||
|
textBox.Focused.BindValueChanged(_ => updateState());
|
||||||
|
textBox.OnCommit += textCommitted;
|
||||||
|
textBox.Current.BindValueChanged(textChanged);
|
||||||
|
|
||||||
|
slider.IsDragging.BindValueChanged(_ => updateState());
|
||||||
|
|
||||||
|
current.BindValueChanged(_ =>
|
||||||
|
{
|
||||||
|
updateState();
|
||||||
|
updateTextBoxFromSlider();
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool updatingFromTextBox;
|
||||||
|
|
||||||
|
private void textChanged(ValueChangedEvent<string> change)
|
||||||
|
{
|
||||||
|
if (!instantaneous) return;
|
||||||
|
|
||||||
|
tryUpdateSliderFromTextBox();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void textCommitted(TextBox t, bool isNew)
|
||||||
|
{
|
||||||
|
tryUpdateSliderFromTextBox();
|
||||||
|
|
||||||
|
// If the attempted update above failed, restore text box to match the slider.
|
||||||
|
Current.TriggerChange();
|
||||||
|
|
||||||
|
flashLayer.Colour = ColourInfo.GradientVertical(colourProvider.Dark2.Opacity(0), colourProvider.Dark2);
|
||||||
|
flashLayer.FadeOutFromOne(800, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tryUpdateSliderFromTextBox()
|
||||||
|
{
|
||||||
|
updatingFromTextBox = true;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
switch (Current)
|
||||||
|
{
|
||||||
|
case Bindable<int> bindableInt:
|
||||||
|
bindableInt.Value = int.Parse(textBox.Current.Value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Bindable<double> bindableDouble:
|
||||||
|
bindableDouble.Value = double.Parse(textBox.Current.Value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
Current.Parse(textBox.Current.Value, CultureInfo.CurrentCulture);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignore parsing failures.
|
||||||
|
// sane state will eventually be restored by a commit (either explicit, or implicit via focus loss).
|
||||||
|
}
|
||||||
|
|
||||||
|
updatingFromTextBox = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
{
|
||||||
|
updateState();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
|
{
|
||||||
|
base.OnHoverLost(e);
|
||||||
|
updateState();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnClick(ClickEvent e)
|
||||||
|
{
|
||||||
|
focusManager.ChangeFocus(textBox);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateState()
|
||||||
|
{
|
||||||
|
textBox.Alpha = 1;
|
||||||
|
|
||||||
|
background.Colour = Current.Disabled ? colourProvider.Background4 : colourProvider.Background5;
|
||||||
|
caption.Colour = Current.Disabled ? colourProvider.Foreground1 : colourProvider.Content2;
|
||||||
|
textBox.Colour = Current.Disabled ? colourProvider.Foreground1 : colourProvider.Content1;
|
||||||
|
|
||||||
|
BorderThickness = IsHovered || textBox.Focused.Value || slider.IsDragging.Value ? 2 : 0;
|
||||||
|
BorderColour = textBox.Focused.Value ? colourProvider.Highlight1 : colourProvider.Light4;
|
||||||
|
|
||||||
|
if (textBox.Focused.Value)
|
||||||
|
background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark3);
|
||||||
|
else if (IsHovered || slider.IsDragging.Value)
|
||||||
|
background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark4);
|
||||||
|
else
|
||||||
|
background.Colour = colourProvider.Background5;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTextBoxFromSlider()
|
||||||
|
{
|
||||||
|
if (updatingFromTextBox) return;
|
||||||
|
|
||||||
|
textBox.Text = slider.GetDisplayableValue(Current.Value).ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private partial class Slider : OsuSliderBar<T>
|
||||||
|
{
|
||||||
|
public BindableBool IsDragging { get; set; } = new BindableBool();
|
||||||
|
|
||||||
|
private Box leftBox = null!;
|
||||||
|
private Box rightBox = null!;
|
||||||
|
private Circle nub = null!;
|
||||||
|
private const float nub_width = 10;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Height = 40;
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
RangePadding = nub_width / 2;
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
CornerRadius = 5,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
leftBox = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
},
|
||||||
|
rightBox = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.CentreRight,
|
||||||
|
Origin = Anchor.CentreRight,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Padding = new MarginPadding { Horizontal = RangePadding, },
|
||||||
|
Child = nub = new Circle
|
||||||
|
{
|
||||||
|
Width = nub_width,
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
RelativePositionAxes = Axes.X,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new HoverClickSounds()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
updateState();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateAfterChildren()
|
||||||
|
{
|
||||||
|
base.UpdateAfterChildren();
|
||||||
|
leftBox.Width = Math.Clamp(RangePadding + nub.DrawPosition.X, 0, Math.Max(0, DrawWidth)) / DrawWidth;
|
||||||
|
rightBox.Width = Math.Clamp(DrawWidth - nub.DrawPosition.X - RangePadding, 0, Math.Max(0, DrawWidth)) / DrawWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnDragStart(DragStartEvent e)
|
||||||
|
{
|
||||||
|
bool dragging = base.OnDragStart(e);
|
||||||
|
IsDragging.Value = dragging;
|
||||||
|
updateState();
|
||||||
|
return dragging;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnDragEnd(DragEndEvent e)
|
||||||
|
{
|
||||||
|
base.OnDragEnd(e);
|
||||||
|
IsDragging.Value = false;
|
||||||
|
updateState();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
{
|
||||||
|
updateState();
|
||||||
|
return base.OnHover(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
|
{
|
||||||
|
updateState();
|
||||||
|
base.OnHoverLost(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateState()
|
||||||
|
{
|
||||||
|
rightBox.Colour = colourProvider.Background6;
|
||||||
|
leftBox.Colour = IsHovered || IsDragged ? colourProvider.Highlight1.Opacity(0.5f) : colourProvider.Dark2;
|
||||||
|
nub.Colour = IsHovered || IsDragged ? colourProvider.Highlight1 : colourProvider.Light4;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateValue(float value)
|
||||||
|
{
|
||||||
|
nub.MoveToX(value, 200, Easing.OutPow10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -59,8 +59,19 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
|
|
||||||
private readonly BindableWithCurrent<string> current = new BindableWithCurrent<string>();
|
private readonly BindableWithCurrent<string> current = new BindableWithCurrent<string>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Caption describing this slider bar, displayed on top of the controls.
|
||||||
|
/// </summary>
|
||||||
public LocalisableString Caption { get; init; }
|
public LocalisableString Caption { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hint text containing an extended description of this slider bar, displayed in a tooltip when hovering the caption.
|
||||||
|
/// </summary>
|
||||||
public LocalisableString HintText { get; init; }
|
public LocalisableString HintText { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Text displayed in the text box when its contents are empty.
|
||||||
|
/// </summary>
|
||||||
public LocalisableString PlaceholderText { get; init; }
|
public LocalisableString PlaceholderText { get; init; }
|
||||||
|
|
||||||
private Box background = null!;
|
private Box background = null!;
|
||||||
@ -122,7 +133,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
|
|
||||||
if (!current.Disabled && !ReadOnly)
|
if (!current.Disabled && !ReadOnly)
|
||||||
{
|
{
|
||||||
flashLayer.Colour = ColourInfo.GradientVertical(colourProvider.Dark1.Opacity(0), colourProvider.Dark2);
|
flashLayer.Colour = ColourInfo.GradientVertical(colourProvider.Dark2.Opacity(0), colourProvider.Dark2);
|
||||||
flashLayer.FadeOutFromOne(800, Easing.OutQuint);
|
flashLayer.FadeOutFromOne(800, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,41 +1,73 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.UserInterfaceV2.FileSelection;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
|
||||||
namespace osu.Game.Graphics.UserInterfaceV2
|
namespace osu.Game.Graphics.UserInterfaceV2
|
||||||
{
|
{
|
||||||
public partial class OsuDirectorySelector : DirectorySelector
|
public partial class OsuDirectorySelector : DirectorySelector
|
||||||
{
|
{
|
||||||
public const float ITEM_HEIGHT = 20;
|
public const float ITEM_HEIGHT = 16;
|
||||||
|
|
||||||
public OsuDirectorySelector(string initialPath = null)
|
private Box hiddenToggleBackground = null!;
|
||||||
|
|
||||||
|
public OsuDirectorySelector(string? initialPath = null)
|
||||||
: base(initialPath)
|
: base(initialPath)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load(OverlayColourProvider colourProvider)
|
||||||
{
|
{
|
||||||
Padding = new MarginPadding(10);
|
AddInternal(new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = colourProvider.Background5,
|
||||||
|
Depth = float.MaxValue,
|
||||||
|
});
|
||||||
|
|
||||||
|
hiddenToggleBackground.Colour = colourProvider.Background4;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override ScrollContainer<Drawable> CreateScrollContainer() => new OsuScrollContainer();
|
protected override ScrollContainer<Drawable> CreateScrollContainer() => new OsuScrollContainer
|
||||||
|
{
|
||||||
|
Padding = new MarginPadding
|
||||||
|
{
|
||||||
|
Horizontal = 20,
|
||||||
|
Vertical = 15,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
protected override DirectorySelectorBreadcrumbDisplay CreateBreadcrumb() => new OsuDirectorySelectorBreadcrumbDisplay();
|
protected override DirectorySelectorBreadcrumbDisplay CreateBreadcrumb() => new OsuDirectorySelectorBreadcrumbDisplay();
|
||||||
|
|
||||||
protected override Drawable CreateHiddenToggleButton() => new OsuDirectorySelectorHiddenToggle { Current = { BindTarget = ShowHiddenItems } };
|
protected override Drawable CreateHiddenToggleButton() => new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
AutoSizeAxes = Axes.X,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
hiddenToggleBackground = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
new HiddenFilesToggleCheckbox
|
||||||
|
{
|
||||||
|
Current = { BindTarget = ShowHiddenItems },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
protected override DirectorySelectorDirectory CreateParentDirectoryItem(DirectoryInfo directory) => new OsuDirectorySelectorParentDirectory(directory);
|
protected override DirectorySelectorDirectory CreateParentDirectoryItem(DirectoryInfo directory) => new OsuDirectorySelectorParentDirectory(directory);
|
||||||
|
|
||||||
protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string displayName = null) => new OsuDirectorySelectorDirectory(directory, displayName);
|
protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string? displayName = null) => new OsuDirectorySelectorDirectory(directory, displayName);
|
||||||
|
|
||||||
protected override void NotifySelectionError() => this.FlashColour(Colour4.Red, 300);
|
protected override void NotifySelectionError() => this.FlashColour(Colour4.Red, 300);
|
||||||
}
|
}
|
||||||
|
@ -1,65 +0,0 @@
|
|||||||
// 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.
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.IO;
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Sprites;
|
|
||||||
using osu.Framework.Graphics.UserInterface;
|
|
||||||
using osu.Game.Graphics.Sprites;
|
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Graphics.UserInterfaceV2
|
|
||||||
{
|
|
||||||
internal partial class OsuDirectorySelectorBreadcrumbDisplay : DirectorySelectorBreadcrumbDisplay
|
|
||||||
{
|
|
||||||
protected override Drawable CreateCaption() => new OsuSpriteText
|
|
||||||
{
|
|
||||||
Text = "Current Directory: ",
|
|
||||||
Font = OsuFont.Default.With(size: OsuDirectorySelector.ITEM_HEIGHT),
|
|
||||||
};
|
|
||||||
|
|
||||||
protected override DirectorySelectorDirectory CreateRootDirectoryItem() => new OsuBreadcrumbDisplayComputer();
|
|
||||||
|
|
||||||
protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string displayName = null) => new OsuBreadcrumbDisplayDirectory(directory, displayName);
|
|
||||||
|
|
||||||
public OsuDirectorySelectorBreadcrumbDisplay()
|
|
||||||
{
|
|
||||||
Padding = new MarginPadding(15);
|
|
||||||
}
|
|
||||||
|
|
||||||
private partial class OsuBreadcrumbDisplayComputer : OsuBreadcrumbDisplayDirectory
|
|
||||||
{
|
|
||||||
protected override IconUsage? Icon => null;
|
|
||||||
|
|
||||||
public OsuBreadcrumbDisplayComputer()
|
|
||||||
: base(null, "Computer")
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private partial class OsuBreadcrumbDisplayDirectory : OsuDirectorySelectorDirectory
|
|
||||||
{
|
|
||||||
public OsuBreadcrumbDisplayDirectory(DirectoryInfo directory, string displayName = null)
|
|
||||||
: base(directory, displayName)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load()
|
|
||||||
{
|
|
||||||
Flow.Add(new SpriteIcon
|
|
||||||
{
|
|
||||||
Anchor = Anchor.CentreLeft,
|
|
||||||
Origin = Anchor.CentreLeft,
|
|
||||||
Icon = FontAwesome.Solid.ChevronRight,
|
|
||||||
Size = new Vector2(FONT_SIZE / 2)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override IconUsage? Icon => Directory.Name.Contains(Path.DirectorySeparatorChar) ? base.Icon : null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,66 +0,0 @@
|
|||||||
// 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.
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.IO;
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Graphics.Shapes;
|
|
||||||
using osu.Framework.Graphics.Sprites;
|
|
||||||
using osu.Framework.Graphics.UserInterface;
|
|
||||||
using osu.Game.Graphics.Sprites;
|
|
||||||
using osu.Game.Graphics.UserInterface;
|
|
||||||
using osu.Game.Overlays;
|
|
||||||
|
|
||||||
namespace osu.Game.Graphics.UserInterfaceV2
|
|
||||||
{
|
|
||||||
internal partial class OsuDirectorySelectorDirectory : DirectorySelectorDirectory
|
|
||||||
{
|
|
||||||
public OsuDirectorySelectorDirectory(DirectoryInfo directory, string displayName = null)
|
|
||||||
: base(directory, displayName)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load()
|
|
||||||
{
|
|
||||||
Flow.AutoSizeAxes = Axes.X;
|
|
||||||
Flow.Height = OsuDirectorySelector.ITEM_HEIGHT;
|
|
||||||
|
|
||||||
AddRangeInternal(new Drawable[]
|
|
||||||
{
|
|
||||||
new Background
|
|
||||||
{
|
|
||||||
Depth = 1
|
|
||||||
},
|
|
||||||
new HoverClickSounds()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override SpriteText CreateSpriteText() => new OsuSpriteText();
|
|
||||||
|
|
||||||
protected override IconUsage? Icon => Directory.Name.Contains(Path.DirectorySeparatorChar)
|
|
||||||
? FontAwesome.Solid.Database
|
|
||||||
: FontAwesome.Regular.Folder;
|
|
||||||
|
|
||||||
internal partial class Background : CompositeDrawable
|
|
||||||
{
|
|
||||||
[BackgroundDependencyLoader(true)]
|
|
||||||
private void load(OverlayColourProvider overlayColourProvider, OsuColour colours)
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
|
||||||
|
|
||||||
Masking = true;
|
|
||||||
CornerRadius = 5;
|
|
||||||
|
|
||||||
InternalChild = new Box
|
|
||||||
{
|
|
||||||
Colour = overlayColourProvider?.Background5 ?? colours.GreySeaFoamDarker,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,43 +1,74 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterfaceV2.FileSelection;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
|
||||||
namespace osu.Game.Graphics.UserInterfaceV2
|
namespace osu.Game.Graphics.UserInterfaceV2
|
||||||
{
|
{
|
||||||
public partial class OsuFileSelector : FileSelector
|
public partial class OsuFileSelector : FileSelector
|
||||||
{
|
{
|
||||||
public OsuFileSelector(string initialPath = null, string[] validFileExtensions = null)
|
private Box hiddenToggleBackground = null!;
|
||||||
|
|
||||||
|
public OsuFileSelector(string? initialPath = null, string[]? validFileExtensions = null)
|
||||||
: base(initialPath, validFileExtensions)
|
: base(initialPath, validFileExtensions)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load(OverlayColourProvider colourProvider)
|
||||||
{
|
{
|
||||||
Padding = new MarginPadding(10);
|
AddInternal(new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = colourProvider.Background5,
|
||||||
|
Depth = float.MaxValue,
|
||||||
|
});
|
||||||
|
|
||||||
|
hiddenToggleBackground.Colour = colourProvider.Background4;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override ScrollContainer<Drawable> CreateScrollContainer() => new OsuScrollContainer();
|
protected override ScrollContainer<Drawable> CreateScrollContainer() => new OsuScrollContainer
|
||||||
|
{
|
||||||
|
Padding = new MarginPadding
|
||||||
|
{
|
||||||
|
Horizontal = 20,
|
||||||
|
Vertical = 15,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
protected override DirectorySelectorBreadcrumbDisplay CreateBreadcrumb() => new OsuDirectorySelectorBreadcrumbDisplay();
|
protected override DirectorySelectorBreadcrumbDisplay CreateBreadcrumb() => new OsuDirectorySelectorBreadcrumbDisplay();
|
||||||
|
|
||||||
protected override Drawable CreateHiddenToggleButton() => new OsuDirectorySelectorHiddenToggle { Current = { BindTarget = ShowHiddenItems } };
|
protected override Drawable CreateHiddenToggleButton() => new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
AutoSizeAxes = Axes.X,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
hiddenToggleBackground = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
new HiddenFilesToggleCheckbox
|
||||||
|
{
|
||||||
|
Current = { BindTarget = ShowHiddenItems },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
protected override DirectorySelectorDirectory CreateParentDirectoryItem(DirectoryInfo directory) => new OsuDirectorySelectorParentDirectory(directory);
|
protected override DirectorySelectorDirectory CreateParentDirectoryItem(DirectoryInfo directory) => new OsuDirectorySelectorParentDirectory(directory);
|
||||||
|
|
||||||
protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string displayName = null) => new OsuDirectorySelectorDirectory(directory, displayName);
|
protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string? displayName = null) => new OsuDirectorySelectorDirectory(directory, displayName);
|
||||||
|
|
||||||
protected override DirectoryListingFile CreateFileItem(FileInfo file) => new OsuDirectoryListingFile(file);
|
protected override DirectoryListingFile CreateFileItem(FileInfo file) => new OsuDirectoryListingFile(file);
|
||||||
|
|
||||||
@ -51,19 +82,14 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load(OverlayColourProvider colourProvider)
|
||||||
{
|
{
|
||||||
Flow.AutoSizeAxes = Axes.X;
|
Flow.AutoSizeAxes = Axes.X;
|
||||||
Flow.Height = OsuDirectorySelector.ITEM_HEIGHT;
|
Flow.Height = OsuDirectorySelector.ITEM_HEIGHT;
|
||||||
|
|
||||||
AddRangeInternal(new Drawable[]
|
AddInternal(new BackgroundLayer());
|
||||||
{
|
|
||||||
new OsuDirectorySelectorDirectory.Background
|
Colour = colourProvider.Light3;
|
||||||
{
|
|
||||||
Depth = 1
|
|
||||||
},
|
|
||||||
new HoverClickSounds()
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IconUsage? Icon
|
protected override IconUsage? Icon
|
||||||
@ -91,7 +117,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override SpriteText CreateSpriteText() => new OsuSpriteText();
|
protected override SpriteText CreateSpriteText() => new OsuSpriteText().With(t => t.Font = OsuFont.Default.With(weight: FontWeight.SemiBold));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,6 +59,26 @@ namespace osu.Game.Localisation.SkinComponents
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString ShowLabelDescription => new TranslatableString(getKey(@"show_label_description"), @"Whether the component's label should be shown.");
|
public static LocalisableString ShowLabelDescription => new TranslatableString(getKey(@"show_label_description"), @"Whether the component's label should be shown.");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Colour"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString Colour => new TranslatableString(getKey(@"colour"), @"Colour");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "The colour of the component."
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString ColourDescription => new TranslatableString(getKey(@"colour_description"), @"The colour of the component.");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Text colour"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString TextColour => new TranslatableString(getKey(@"text_colour"), @"Text colour");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "The colour of the text."
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString TextColourDescription => new TranslatableString(getKey(@"text_colour_description"), @"The colour of the text.");
|
||||||
|
|
||||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,10 @@ namespace osu.Game.Online
|
|||||||
// Used to interact with manager classes that don't support interface types. Will eventually be replaced.
|
// Used to interact with manager classes that don't support interface types. Will eventually be replaced.
|
||||||
var beatmapSetInfo = new BeatmapSetInfo { OnlineID = TrackedItem.OnlineID };
|
var beatmapSetInfo = new BeatmapSetInfo { OnlineID = TrackedItem.OnlineID };
|
||||||
|
|
||||||
realmSubscription = realm.RegisterForNotifications(r => r.All<BeatmapSetInfo>().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending), (items, _) =>
|
// Required local for iOS. Will cause runtime crash if inlined.
|
||||||
|
int onlineId = TrackedItem.OnlineID;
|
||||||
|
|
||||||
|
realmSubscription = realm.RegisterForNotifications(r => r.All<BeatmapSetInfo>().Where(s => s.OnlineID == onlineId && !s.DeletePending), (items, _) =>
|
||||||
{
|
{
|
||||||
if (items.Any())
|
if (items.Any())
|
||||||
Schedule(() => UpdateState(DownloadState.LocallyAvailable));
|
Schedule(() => UpdateState(DownloadState.LocallyAvailable));
|
||||||
|
@ -46,10 +46,15 @@ namespace osu.Game.Online
|
|||||||
Downloader.DownloadBegan += downloadBegan;
|
Downloader.DownloadBegan += downloadBegan;
|
||||||
Downloader.DownloadFailed += downloadFailed;
|
Downloader.DownloadFailed += downloadFailed;
|
||||||
|
|
||||||
|
// Required local for iOS. Will cause runtime crash if inlined.
|
||||||
|
long onlineId = TrackedItem.OnlineID;
|
||||||
|
long legacyOnlineId = TrackedItem.LegacyOnlineID;
|
||||||
|
string hash = TrackedItem.Hash;
|
||||||
|
|
||||||
realmSubscription = realm.RegisterForNotifications(r => r.All<ScoreInfo>().Where(s =>
|
realmSubscription = realm.RegisterForNotifications(r => r.All<ScoreInfo>().Where(s =>
|
||||||
((s.OnlineID > 0 && s.OnlineID == TrackedItem.OnlineID)
|
((s.OnlineID > 0 && s.OnlineID == onlineId)
|
||||||
|| (s.LegacyOnlineID > 0 && s.LegacyOnlineID == TrackedItem.LegacyOnlineID)
|
|| (s.LegacyOnlineID > 0 && s.LegacyOnlineID == legacyOnlineId)
|
||||||
|| (!string.IsNullOrEmpty(s.Hash) && s.Hash == TrackedItem.Hash))
|
|| (!string.IsNullOrEmpty(s.Hash) && s.Hash == hash))
|
||||||
&& !s.DeletePending), (items, _) =>
|
&& !s.DeletePending), (items, _) =>
|
||||||
{
|
{
|
||||||
if (items.Any())
|
if (items.Any())
|
||||||
|
@ -314,6 +314,7 @@ namespace osu.Game.Overlays.FirstRunSetup
|
|||||||
private partial class DirectoryChooserPopover : OsuPopover
|
private partial class DirectoryChooserPopover : OsuPopover
|
||||||
{
|
{
|
||||||
public DirectoryChooserPopover(Bindable<DirectoryInfo?> currentDirectory)
|
public DirectoryChooserPopover(Bindable<DirectoryInfo?> currentDirectory)
|
||||||
|
: base(false)
|
||||||
{
|
{
|
||||||
Child = new Container
|
Child = new Container
|
||||||
{
|
{
|
||||||
@ -325,6 +326,13 @@ namespace osu.Game.Overlays.FirstRunSetup
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider colourProvider)
|
||||||
|
{
|
||||||
|
Body.BorderColour = colourProvider.Highlight1;
|
||||||
|
Body.BorderThickness = 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,8 +48,11 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual DirectoryInfo InitialPath => null;
|
protected virtual DirectoryInfo InitialPath => null;
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load()
|
||||||
{
|
{
|
||||||
InternalChild = new Container
|
InternalChild = new Container
|
||||||
{
|
{
|
||||||
@ -64,7 +67,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
new Box
|
new Box
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = colours.GreySeaFoamDark
|
Colour = colourProvider.Background4,
|
||||||
},
|
},
|
||||||
new GridContainer
|
new GridContainer
|
||||||
{
|
{
|
||||||
|
80
osu.Game/Overlays/Settings/SettingsColour.cs
Normal file
80
osu.Game/Overlays/Settings/SettingsColour.cs
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
// 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.Bindables;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Settings
|
||||||
|
{
|
||||||
|
public partial class SettingsColour : SettingsItem<Colour4>
|
||||||
|
{
|
||||||
|
protected override Drawable CreateControl() => new ColourControl();
|
||||||
|
|
||||||
|
public partial class ColourControl : OsuClickableContainer, IHasPopover, IHasCurrentValue<Colour4>
|
||||||
|
{
|
||||||
|
private readonly BindableWithCurrent<Colour4> current = new BindableWithCurrent<Colour4>(Colour4.White);
|
||||||
|
|
||||||
|
public Bindable<Colour4> Current
|
||||||
|
{
|
||||||
|
get => current.Current;
|
||||||
|
set => current.Current = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly Box fill;
|
||||||
|
private readonly OsuSpriteText colourHexCode;
|
||||||
|
|
||||||
|
public ColourControl()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
Height = 40;
|
||||||
|
CornerRadius = 20;
|
||||||
|
Masking = true;
|
||||||
|
Action = this.ShowPopover;
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
fill = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
},
|
||||||
|
colourHexCode = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Font = OsuFont.Default.With(size: 20)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
Current.BindValueChanged(_ => updateColour(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateColour()
|
||||||
|
{
|
||||||
|
fill.Colour = Current.Value;
|
||||||
|
colourHexCode.Text = Current.Value.ToHex();
|
||||||
|
colourHexCode.Colour = OsuColour.ForegroundTextColourFor(Current.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Popover GetPopover() => new OsuPopover(false)
|
||||||
|
{
|
||||||
|
Child = new OsuColourPicker
|
||||||
|
{
|
||||||
|
Current = { BindTarget = Current }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -33,6 +33,7 @@ using osu.Game.Screens.Edit;
|
|||||||
using osu.Game.Screens.Edit.Components;
|
using osu.Game.Screens.Edit.Components;
|
||||||
using osu.Game.Screens.Edit.Components.Menus;
|
using osu.Game.Screens.Edit.Components.Menus;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.SkinEditor
|
namespace osu.Game.Overlays.SkinEditor
|
||||||
{
|
{
|
||||||
@ -118,107 +119,111 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
InternalChild = new OsuContextMenuContainer
|
InternalChild = new OsuContextMenuContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Child = new GridContainer
|
Child = new PopoverContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
RowDimensions = new[]
|
Child = new GridContainer
|
||||||
{
|
{
|
||||||
new Dimension(GridSizeMode.AutoSize),
|
RelativeSizeAxes = Axes.Both,
|
||||||
new Dimension(GridSizeMode.AutoSize),
|
RowDimensions = new[]
|
||||||
new Dimension(),
|
{
|
||||||
},
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
|
new Dimension(),
|
||||||
|
},
|
||||||
|
|
||||||
Content = new[]
|
Content = new[]
|
||||||
{
|
|
||||||
new Drawable[]
|
|
||||||
{
|
{
|
||||||
new Container
|
new Drawable[]
|
||||||
{
|
{
|
||||||
Name = @"Menu container",
|
new Container
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Depth = float.MinValue,
|
|
||||||
Height = MENU_HEIGHT,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
{
|
||||||
new EditorMenuBar
|
Name = @"Menu container",
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Depth = float.MinValue,
|
||||||
|
Height = MENU_HEIGHT,
|
||||||
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreLeft,
|
new EditorMenuBar
|
||||||
Origin = Anchor.CentreLeft,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Items = new[]
|
|
||||||
{
|
{
|
||||||
new MenuItem(CommonStrings.MenuBarFile)
|
Anchor = Anchor.CentreLeft,
|
||||||
{
|
Origin = Anchor.CentreLeft,
|
||||||
Items = new OsuMenuItem[]
|
|
||||||
{
|
|
||||||
new EditorMenuItem(Web.CommonStrings.ButtonsSave, MenuItemType.Standard, () => Save()),
|
|
||||||
new EditorMenuItem(CommonStrings.Export, MenuItemType.Standard, () => skins.ExportCurrentSkin()) { Action = { Disabled = !RuntimeInfo.IsDesktop } },
|
|
||||||
new OsuMenuItemSpacer(),
|
|
||||||
new EditorMenuItem(CommonStrings.RevertToDefault, MenuItemType.Destructive, () => dialogOverlay?.Push(new RevertConfirmDialog(revert))),
|
|
||||||
new OsuMenuItemSpacer(),
|
|
||||||
new EditorMenuItem(CommonStrings.Exit, MenuItemType.Standard, () => skinEditorOverlay?.Hide()),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
new MenuItem(CommonStrings.MenuBarEdit)
|
|
||||||
{
|
|
||||||
Items = new OsuMenuItem[]
|
|
||||||
{
|
|
||||||
undoMenuItem = new EditorMenuItem(CommonStrings.Undo, MenuItemType.Standard, Undo),
|
|
||||||
redoMenuItem = new EditorMenuItem(CommonStrings.Redo, MenuItemType.Standard, Redo),
|
|
||||||
new OsuMenuItemSpacer(),
|
|
||||||
cutMenuItem = new EditorMenuItem(CommonStrings.Cut, MenuItemType.Standard, Cut),
|
|
||||||
copyMenuItem = new EditorMenuItem(CommonStrings.Copy, MenuItemType.Standard, Copy),
|
|
||||||
pasteMenuItem = new EditorMenuItem(CommonStrings.Paste, MenuItemType.Standard, Paste),
|
|
||||||
cloneMenuItem = new EditorMenuItem(CommonStrings.Clone, MenuItemType.Standard, Clone),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
headerText = new OsuTextFlowContainer
|
|
||||||
{
|
|
||||||
TextAnchor = Anchor.TopRight,
|
|
||||||
Padding = new MarginPadding(5),
|
|
||||||
Anchor = Anchor.TopRight,
|
|
||||||
Origin = Anchor.TopRight,
|
|
||||||
AutoSizeAxes = Axes.X,
|
|
||||||
RelativeSizeAxes = Axes.Y,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
new Drawable[]
|
|
||||||
{
|
|
||||||
new SkinEditorSceneLibrary
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
new Drawable[]
|
|
||||||
{
|
|
||||||
new GridContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
ColumnDimensions = new[]
|
|
||||||
{
|
|
||||||
new Dimension(GridSizeMode.AutoSize),
|
|
||||||
new Dimension(),
|
|
||||||
new Dimension(GridSizeMode.AutoSize),
|
|
||||||
},
|
|
||||||
Content = new[]
|
|
||||||
{
|
|
||||||
new Drawable[]
|
|
||||||
{
|
|
||||||
componentsSidebar = new EditorSidebar(),
|
|
||||||
content = new Container
|
|
||||||
{
|
|
||||||
Depth = float.MaxValue,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Items = new[]
|
||||||
|
{
|
||||||
|
new MenuItem(CommonStrings.MenuBarFile)
|
||||||
|
{
|
||||||
|
Items = new OsuMenuItem[]
|
||||||
|
{
|
||||||
|
new EditorMenuItem(Web.CommonStrings.ButtonsSave, MenuItemType.Standard, () => Save()),
|
||||||
|
new EditorMenuItem(CommonStrings.Export, MenuItemType.Standard, () => skins.ExportCurrentSkin()) { Action = { Disabled = !RuntimeInfo.IsDesktop } },
|
||||||
|
new OsuMenuItemSpacer(),
|
||||||
|
new EditorMenuItem(CommonStrings.RevertToDefault, MenuItemType.Destructive, () => dialogOverlay?.Push(new RevertConfirmDialog(revert))),
|
||||||
|
new OsuMenuItemSpacer(),
|
||||||
|
new EditorMenuItem(CommonStrings.Exit, MenuItemType.Standard, () => skinEditorOverlay?.Hide()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
new MenuItem(CommonStrings.MenuBarEdit)
|
||||||
|
{
|
||||||
|
Items = new OsuMenuItem[]
|
||||||
|
{
|
||||||
|
undoMenuItem = new EditorMenuItem(CommonStrings.Undo, MenuItemType.Standard, Undo),
|
||||||
|
redoMenuItem = new EditorMenuItem(CommonStrings.Redo, MenuItemType.Standard, Redo),
|
||||||
|
new OsuMenuItemSpacer(),
|
||||||
|
cutMenuItem = new EditorMenuItem(CommonStrings.Cut, MenuItemType.Standard, Cut),
|
||||||
|
copyMenuItem = new EditorMenuItem(CommonStrings.Copy, MenuItemType.Standard, Copy),
|
||||||
|
pasteMenuItem = new EditorMenuItem(CommonStrings.Paste, MenuItemType.Standard, Paste),
|
||||||
|
cloneMenuItem = new EditorMenuItem(CommonStrings.Clone, MenuItemType.Standard, Clone),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
},
|
},
|
||||||
settingsSidebar = new EditorSidebar(),
|
headerText = new OsuTextFlowContainer
|
||||||
|
{
|
||||||
|
TextAnchor = Anchor.TopRight,
|
||||||
|
Padding = new MarginPadding(5),
|
||||||
|
Anchor = Anchor.TopRight,
|
||||||
|
Origin = Anchor.TopRight,
|
||||||
|
AutoSizeAxes = Axes.X,
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
new SkinEditorSceneLibrary
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
new GridContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
ColumnDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
|
new Dimension(),
|
||||||
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
|
},
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
componentsSidebar = new EditorSidebar(),
|
||||||
|
content = new Container
|
||||||
|
{
|
||||||
|
Depth = float.MaxValue,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
settingsSidebar = new EditorSidebar(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -73,7 +73,7 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
isFlippedY = false;
|
isFlippedY = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both)
|
public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both, float axisRotation = 0)
|
||||||
{
|
{
|
||||||
if (objectsInScale == null)
|
if (objectsInScale == null)
|
||||||
throw new InvalidOperationException($"Cannot {nameof(Update)} a scale operation without calling {nameof(Begin)} first!");
|
throw new InvalidOperationException($"Cannot {nameof(Update)} a scale operation without calling {nameof(Begin)} first!");
|
||||||
|
@ -323,7 +323,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// A "select" tool is automatically added as the first tool.
|
/// A "select" tool is automatically added as the first tool.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
protected abstract IReadOnlyList<HitObjectCompositionTool> CompositionTools { get; }
|
protected abstract IReadOnlyList<CompositionTool> CompositionTools { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A collection of states which will be displayed to the user in the toolbox.
|
/// A collection of states which will be displayed to the user in the toolbox.
|
||||||
@ -466,7 +466,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
|
|
||||||
private void setSelectTool() => toolboxCollection.Items.First().Select();
|
private void setSelectTool() => toolboxCollection.Items.First().Select();
|
||||||
|
|
||||||
private void toolSelected(HitObjectCompositionTool tool)
|
private void toolSelected(CompositionTool tool)
|
||||||
{
|
{
|
||||||
BlueprintContainer.CurrentTool = tool;
|
BlueprintContainer.CurrentTool = tool;
|
||||||
|
|
||||||
|
@ -9,9 +9,9 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
{
|
{
|
||||||
public class HitObjectCompositionToolButton : RadioButton
|
public class HitObjectCompositionToolButton : RadioButton
|
||||||
{
|
{
|
||||||
public HitObjectCompositionTool Tool { get; }
|
public CompositionTool Tool { get; }
|
||||||
|
|
||||||
public HitObjectCompositionToolButton(HitObjectCompositionTool tool, Action? action)
|
public HitObjectCompositionToolButton(CompositionTool tool, Action? action)
|
||||||
: base(tool.Name, action, tool.CreateIcon)
|
: base(tool.Name, action, tool.CreateIcon)
|
||||||
{
|
{
|
||||||
Tool = tool;
|
Tool = tool;
|
||||||
|
126
osu.Game/Rulesets/Edit/HitObjectPlacementBlueprint.cs
Normal file
126
osu.Game/Rulesets/Edit/HitObjectPlacementBlueprint.cs
Normal 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 System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Game.Audio;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
|
using osu.Game.Screens.Edit.Compose;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Edit
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A blueprint which governs the creation of a new <see cref="HitObject"/> to actualisation.
|
||||||
|
/// </summary>
|
||||||
|
public abstract partial class HitObjectPlacementBlueprint : PlacementBlueprint
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the sample bank should be taken from the previous hit object.
|
||||||
|
/// </summary>
|
||||||
|
public bool AutomaticBankAssignment { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="HitObject"/> that is being placed.
|
||||||
|
/// </summary>
|
||||||
|
public readonly HitObject HitObject;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
protected EditorClock EditorClock { get; private set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private EditorBeatmap beatmap { get; set; } = null!;
|
||||||
|
|
||||||
|
private Bindable<double> startTimeBindable = null!;
|
||||||
|
|
||||||
|
private HitObject? getPreviousHitObject() => beatmap.HitObjects.TakeWhile(h => h.StartTime <= startTimeBindable.Value).LastOrDefault();
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private IPlacementHandler placementHandler { get; set; } = null!;
|
||||||
|
|
||||||
|
protected HitObjectPlacementBlueprint(HitObject hitObject)
|
||||||
|
{
|
||||||
|
HitObject = hitObject;
|
||||||
|
|
||||||
|
// adding the default hit sample should be the case regardless of the ruleset.
|
||||||
|
HitObject.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_NORMAL));
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
startTimeBindable = HitObject.StartTimeBindable.GetBoundCopy();
|
||||||
|
startTimeBindable.BindValueChanged(_ => ApplyDefaultsToHitObject(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void BeginPlacement(bool commitStart = false)
|
||||||
|
{
|
||||||
|
base.BeginPlacement(commitStart);
|
||||||
|
|
||||||
|
placementHandler.BeginPlacement(HitObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void EndPlacement(bool commit)
|
||||||
|
{
|
||||||
|
base.EndPlacement(commit);
|
||||||
|
|
||||||
|
placementHandler.EndPlacement(HitObject, IsValidForPlacement && commit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the time and position of this <see cref="HitObjectPlacementBlueprint"/> based on the provided snap information.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="result">The snap result information.</param>
|
||||||
|
public override void UpdateTimeAndPosition(SnapResult result)
|
||||||
|
{
|
||||||
|
if (PlacementActive == PlacementState.Waiting)
|
||||||
|
{
|
||||||
|
HitObject.StartTime = result.Time ?? EditorClock.CurrentTime;
|
||||||
|
|
||||||
|
if (HitObject is IHasComboInformation comboInformation)
|
||||||
|
comboInformation.UpdateComboInformation(getPreviousHitObject() as IHasComboInformation);
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastHitObject = getPreviousHitObject();
|
||||||
|
|
||||||
|
if (AutomaticBankAssignment)
|
||||||
|
{
|
||||||
|
// Create samples based on the sample settings of the previous hit object
|
||||||
|
if (lastHitObject != null)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < HitObject.Samples.Count; i++)
|
||||||
|
HitObject.Samples[i] = lastHitObject.CreateHitSampleInfo(HitObject.Samples[i].Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var lastHitNormal = lastHitObject?.Samples?.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL);
|
||||||
|
|
||||||
|
if (lastHitNormal != null)
|
||||||
|
{
|
||||||
|
// Only inherit the volume from the previous hit object
|
||||||
|
for (int i = 0; i < HitObject.Samples.Count; i++)
|
||||||
|
HitObject.Samples[i] = HitObject.Samples[i].With(newVolume: lastHitNormal.Volume);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (HitObject is IHasRepeats hasRepeats)
|
||||||
|
{
|
||||||
|
// Make sure all the node samples are identical to the hit object's samples
|
||||||
|
for (int i = 0; i < hasRepeats.NodeSamples.Count; i++)
|
||||||
|
hasRepeats.NodeSamples[i] = HitObject.Samples.Select(o => o.With()).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invokes <see cref="Objects.HitObject.ApplyDefaults(ControlPointInfo,IBeatmapDifficultyInfo,CancellationToken)"/>,
|
||||||
|
/// refreshing <see cref="Objects.HitObject.NestedHitObjects"/> and parameters for the <see cref="HitObject"/>.
|
||||||
|
/// </summary>
|
||||||
|
protected void ApplyDefaultsToHitObject() => HitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty);
|
||||||
|
}
|
||||||
|
}
|
@ -1,29 +1,19 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Audio;
|
|
||||||
using osu.Game.Beatmaps;
|
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
|
||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
|
||||||
using osu.Game.Screens.Edit;
|
|
||||||
using osu.Game.Screens.Edit.Compose;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Edit
|
namespace osu.Game.Rulesets.Edit
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A blueprint which governs the creation of a new <see cref="HitObject"/> to actualisation.
|
/// A blueprint which governs the placement of something.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract partial class PlacementBlueprint : CompositeDrawable, IKeyBindingHandler<GlobalAction>
|
public abstract partial class PlacementBlueprint : CompositeDrawable, IKeyBindingHandler<GlobalAction>
|
||||||
{
|
{
|
||||||
@ -32,29 +22,6 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public PlacementState PlacementActive { get; private set; }
|
public PlacementState PlacementActive { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether the sample bank should be taken from the previous hit object.
|
|
||||||
/// </summary>
|
|
||||||
public bool AutomaticBankAssignment { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The <see cref="HitObject"/> that is being placed.
|
|
||||||
/// </summary>
|
|
||||||
public readonly HitObject HitObject;
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
protected EditorClock EditorClock { get; private set; } = null!;
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private EditorBeatmap beatmap { get; set; } = null!;
|
|
||||||
|
|
||||||
private Bindable<double> startTimeBindable = null!;
|
|
||||||
|
|
||||||
private HitObject? getPreviousHitObject() => beatmap.HitObjects.TakeWhile(h => h.StartTime <= startTimeBindable.Value).LastOrDefault();
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private IPlacementHandler placementHandler { get; set; } = null!;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether this blueprint is currently in a state that can be committed.
|
/// Whether this blueprint is currently in a state that can be committed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -64,13 +31,8 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
protected virtual bool IsValidForPlacement => true;
|
protected virtual bool IsValidForPlacement => true;
|
||||||
|
|
||||||
protected PlacementBlueprint(HitObject hitObject)
|
protected PlacementBlueprint()
|
||||||
{
|
{
|
||||||
HitObject = hitObject;
|
|
||||||
|
|
||||||
// adding the default hit sample should be the case regardless of the ruleset.
|
|
||||||
HitObject.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_NORMAL));
|
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
// This is required to allow the blueprint's position to be updated via OnMouseMove/Handle
|
// This is required to allow the blueprint's position to be updated via OnMouseMove/Handle
|
||||||
@ -78,30 +40,22 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
AlwaysPresent = true;
|
AlwaysPresent = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load()
|
|
||||||
{
|
|
||||||
startTimeBindable = HitObject.StartTimeBindable.GetBoundCopy();
|
|
||||||
startTimeBindable.BindValueChanged(_ => ApplyDefaultsToHitObject(), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Signals that the placement of <see cref="HitObject"/> has started.
|
/// Signals that the placement has started.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="commitStart">Whether this call is committing a value for HitObject.StartTime and continuing with further adjustments.</param>
|
/// <param name="commitStart">Whether this call is committing a value and continuing with further adjustments.</param>
|
||||||
protected void BeginPlacement(bool commitStart = false)
|
protected virtual void BeginPlacement(bool commitStart = false)
|
||||||
{
|
{
|
||||||
placementHandler.BeginPlacement(HitObject);
|
|
||||||
if (commitStart)
|
if (commitStart)
|
||||||
PlacementActive = PlacementState.Active;
|
PlacementActive = PlacementState.Active;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Signals that the placement of <see cref="HitObject"/> has finished.
|
/// Signals that the placement of <see cref="HitObject"/> has finished.
|
||||||
/// This will destroy this <see cref="PlacementBlueprint"/>, and add the HitObject.StartTime to the <see cref="Beatmap"/>.
|
/// This will destroy this <see cref="PlacementBlueprint"/>, and commit the changes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="commit">Whether the object should be committed. Note that a commit may fail if <see cref="IsValidForPlacement"/> is <c>false</c>.</param>
|
/// <param name="commit">Whether the changes should be committed. Note that a commit may fail if <see cref="IsValidForPlacement"/> is <c>false</c>.</param>
|
||||||
public void EndPlacement(bool commit)
|
public virtual void EndPlacement(bool commit)
|
||||||
{
|
{
|
||||||
switch (PlacementActive)
|
switch (PlacementActive)
|
||||||
{
|
{
|
||||||
@ -114,10 +68,17 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
placementHandler.EndPlacement(HitObject, IsValidForPlacement && commit);
|
|
||||||
PlacementActive = PlacementState.Finished;
|
PlacementActive = PlacementState.Finished;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the time and position of this <see cref="PlacementBlueprint"/> based on the provided snap information.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="result">The snap result information.</param>
|
||||||
|
public virtual void UpdateTimeAndPosition(SnapResult result)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||||
{
|
{
|
||||||
if (PlacementActive == PlacementState.Waiting)
|
if (PlacementActive == PlacementState.Waiting)
|
||||||
@ -138,57 +99,6 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Updates the time and position of this <see cref="PlacementBlueprint"/> based on the provided snap information.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="result">The snap result information.</param>
|
|
||||||
public virtual void UpdateTimeAndPosition(SnapResult result)
|
|
||||||
{
|
|
||||||
if (PlacementActive == PlacementState.Waiting)
|
|
||||||
{
|
|
||||||
HitObject.StartTime = result.Time ?? EditorClock.CurrentTime;
|
|
||||||
|
|
||||||
if (HitObject is IHasComboInformation comboInformation)
|
|
||||||
comboInformation.UpdateComboInformation(getPreviousHitObject() as IHasComboInformation);
|
|
||||||
}
|
|
||||||
|
|
||||||
var lastHitObject = getPreviousHitObject();
|
|
||||||
|
|
||||||
if (AutomaticBankAssignment)
|
|
||||||
{
|
|
||||||
// Create samples based on the sample settings of the previous hit object
|
|
||||||
if (lastHitObject != null)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < HitObject.Samples.Count; i++)
|
|
||||||
HitObject.Samples[i] = lastHitObject.CreateHitSampleInfo(HitObject.Samples[i].Name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var lastHitNormal = lastHitObject?.Samples?.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL);
|
|
||||||
|
|
||||||
if (lastHitNormal != null)
|
|
||||||
{
|
|
||||||
// Only inherit the volume from the previous hit object
|
|
||||||
for (int i = 0; i < HitObject.Samples.Count; i++)
|
|
||||||
HitObject.Samples[i] = HitObject.Samples[i].With(newVolume: lastHitNormal.Volume);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (HitObject is IHasRepeats hasRepeats)
|
|
||||||
{
|
|
||||||
// Make sure all the node samples are identical to the hit object's samples
|
|
||||||
for (int i = 0; i < hasRepeats.NodeSamples.Count; i++)
|
|
||||||
hasRepeats.NodeSamples[i] = HitObject.Samples.Select(o => o.With()).ToList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invokes <see cref="Objects.HitObject.ApplyDefaults(ControlPointInfo,IBeatmapDifficultyInfo,CancellationToken)"/>,
|
|
||||||
/// refreshing <see cref="Objects.HitObject.NestedHitObjects"/> and parameters for the <see cref="HitObject"/>.
|
|
||||||
/// </summary>
|
|
||||||
protected void ApplyDefaultsToHitObject() => HitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty);
|
|
||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
|
||||||
|
|
||||||
protected override bool Handle(UIEvent e)
|
protected override bool Handle(UIEvent e)
|
||||||
|
@ -6,13 +6,13 @@ using osu.Framework.Localisation;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Edit.Tools
|
namespace osu.Game.Rulesets.Edit.Tools
|
||||||
{
|
{
|
||||||
public abstract class HitObjectCompositionTool
|
public abstract class CompositionTool
|
||||||
{
|
{
|
||||||
public readonly string Name;
|
public readonly string Name;
|
||||||
|
|
||||||
public LocalisableString TooltipText { get; init; }
|
public LocalisableString TooltipText { get; init; }
|
||||||
|
|
||||||
protected HitObjectCompositionTool(string name)
|
protected CompositionTool(string name)
|
||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
}
|
}
|
@ -9,7 +9,7 @@ using osu.Game.Graphics;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Edit.Tools
|
namespace osu.Game.Rulesets.Edit.Tools
|
||||||
{
|
{
|
||||||
public class SelectTool : HitObjectCompositionTool
|
public class SelectTool : CompositionTool
|
||||||
{
|
{
|
||||||
public SelectTool()
|
public SelectTool()
|
||||||
: base("Select")
|
: base("Select")
|
||||||
@ -18,6 +18,6 @@ namespace osu.Game.Rulesets.Edit.Tools
|
|||||||
|
|
||||||
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorSelect };
|
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorSelect };
|
||||||
|
|
||||||
public override PlacementBlueprint CreatePlacementBlueprint() => null;
|
public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -266,8 +266,7 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
|
|
||||||
// TODO: special case for handling number types
|
// TODO: special case for handling number types
|
||||||
|
|
||||||
PropertyInfo property = targetSetting.GetType().GetProperty(nameof(Bindable<bool>.Value))!;
|
BindableValueAccessor.SetValue(targetSetting, BindableValueAccessor.GetValue(sourceSetting));
|
||||||
property.SetValue(targetSetting, property.GetValue(sourceSetting));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,6 +41,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
public PlacementBlueprint CurrentPlacement { get; private set; }
|
public PlacementBlueprint CurrentPlacement { get; private set; }
|
||||||
|
|
||||||
|
public HitObjectPlacementBlueprint CurrentHitObjectPlacement => CurrentPlacement as HitObjectPlacementBlueprint;
|
||||||
|
|
||||||
[Resolved(canBeNull: true)]
|
[Resolved(canBeNull: true)]
|
||||||
private EditorScreenWithTimeline editorScreen { get; set; }
|
private EditorScreenWithTimeline editorScreen { get; set; }
|
||||||
|
|
||||||
@ -164,13 +166,13 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
private void updatePlacementNewCombo()
|
private void updatePlacementNewCombo()
|
||||||
{
|
{
|
||||||
if (CurrentPlacement?.HitObject is IHasComboInformation c)
|
if (CurrentHitObjectPlacement?.HitObject is IHasComboInformation c)
|
||||||
c.NewCombo = NewCombo.Value == TernaryState.True;
|
c.NewCombo = NewCombo.Value == TernaryState.True;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updatePlacementSamples()
|
private void updatePlacementSamples()
|
||||||
{
|
{
|
||||||
if (CurrentPlacement == null) return;
|
if (CurrentHitObjectPlacement == null) return;
|
||||||
|
|
||||||
foreach (var kvp in SelectionHandler.SelectionSampleStates)
|
foreach (var kvp in SelectionHandler.SelectionSampleStates)
|
||||||
sampleChanged(kvp.Key, kvp.Value.Value);
|
sampleChanged(kvp.Key, kvp.Value.Value);
|
||||||
@ -181,9 +183,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
private void sampleChanged(string sampleName, TernaryState state)
|
private void sampleChanged(string sampleName, TernaryState state)
|
||||||
{
|
{
|
||||||
if (CurrentPlacement == null) return;
|
if (CurrentHitObjectPlacement == null) return;
|
||||||
|
|
||||||
var samples = CurrentPlacement.HitObject.Samples;
|
var samples = CurrentHitObjectPlacement.HitObject.Samples;
|
||||||
|
|
||||||
var existingSample = samples.FirstOrDefault(s => s.Name == sampleName);
|
var existingSample = samples.FirstOrDefault(s => s.Name == sampleName);
|
||||||
|
|
||||||
@ -196,19 +198,19 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
case TernaryState.True:
|
case TernaryState.True:
|
||||||
if (existingSample == null)
|
if (existingSample == null)
|
||||||
samples.Add(CurrentPlacement.HitObject.CreateHitSampleInfo(sampleName));
|
samples.Add(CurrentHitObjectPlacement.HitObject.CreateHitSampleInfo(sampleName));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void bankChanged(string bankName, TernaryState state)
|
private void bankChanged(string bankName, TernaryState state)
|
||||||
{
|
{
|
||||||
if (CurrentPlacement == null) return;
|
if (CurrentHitObjectPlacement == null) return;
|
||||||
|
|
||||||
if (bankName == EditorSelectionHandler.HIT_BANK_AUTO)
|
if (bankName == EditorSelectionHandler.HIT_BANK_AUTO)
|
||||||
CurrentPlacement.AutomaticBankAssignment = state == TernaryState.True;
|
CurrentHitObjectPlacement.AutomaticBankAssignment = state == TernaryState.True;
|
||||||
else if (state == TernaryState.True)
|
else if (state == TernaryState.True)
|
||||||
CurrentPlacement.HitObject.Samples = CurrentPlacement.HitObject.Samples.Select(s => s.With(newBank: bankName)).ToList();
|
CurrentHitObjectPlacement.HitObject.Samples = CurrentHitObjectPlacement.HitObject.Samples.Select(s => s.With(newBank: bankName)).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly Bindable<TernaryState> NewCombo = new Bindable<TernaryState> { Description = "New Combo" };
|
public readonly Bindable<TernaryState> NewCombo = new Bindable<TernaryState> { Description = "New Combo" };
|
||||||
@ -386,12 +388,12 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
CurrentPlacement = null;
|
CurrentPlacement = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private HitObjectCompositionTool currentTool;
|
private CompositionTool currentTool;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current placement tool.
|
/// The current placement tool.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public HitObjectCompositionTool CurrentTool
|
public CompositionTool CurrentTool
|
||||||
{
|
{
|
||||||
get => currentTool;
|
get => currentTool;
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -284,8 +285,12 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
Action = action
|
Action = action
|
||||||
};
|
};
|
||||||
|
|
||||||
|
button.OperationStarted += freezeButtonPosition;
|
||||||
|
button.HoverLost += unfreezeButtonPosition;
|
||||||
|
|
||||||
button.OperationStarted += operationStarted;
|
button.OperationStarted += operationStarted;
|
||||||
button.OperationEnded += operationEnded;
|
button.OperationEnded += operationEnded;
|
||||||
|
|
||||||
buttons.Add(button);
|
buttons.Add(button);
|
||||||
|
|
||||||
return button;
|
return button;
|
||||||
@ -357,9 +362,35 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
OperationStarted?.Invoke();
|
OperationStarted?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ensureButtonsOnScreen()
|
private Vector2? frozenButtonsPosition;
|
||||||
|
|
||||||
|
private void freezeButtonPosition()
|
||||||
{
|
{
|
||||||
buttons.Position = Vector2.Zero;
|
frozenButtonsPosition = buttons.ScreenSpaceDrawQuad.TopLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unfreezeButtonPosition()
|
||||||
|
{
|
||||||
|
if (frozenButtonsPosition != null)
|
||||||
|
{
|
||||||
|
frozenButtonsPosition = null;
|
||||||
|
ensureButtonsOnScreen(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureButtonsOnScreen(bool animated = false)
|
||||||
|
{
|
||||||
|
if (frozenButtonsPosition != null)
|
||||||
|
{
|
||||||
|
buttons.Anchor = Anchor.TopLeft;
|
||||||
|
buttons.Origin = Anchor.TopLeft;
|
||||||
|
|
||||||
|
buttons.Position = ToLocalSpace(frozenButtonsPosition.Value) - new Vector2(button_padding);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!animated && buttons.Transforms.Any())
|
||||||
|
return;
|
||||||
|
|
||||||
var thisQuad = ScreenSpaceDrawQuad;
|
var thisQuad = ScreenSpaceDrawQuad;
|
||||||
|
|
||||||
@ -374,24 +405,51 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
float minHeight = buttons.ScreenSpaceDrawQuad.Height;
|
float minHeight = buttons.ScreenSpaceDrawQuad.Height;
|
||||||
|
|
||||||
|
Anchor targetAnchor;
|
||||||
|
Anchor targetOrigin;
|
||||||
|
Vector2 targetPosition = Vector2.Zero;
|
||||||
|
|
||||||
if (topExcess < minHeight && bottomExcess < minHeight)
|
if (topExcess < minHeight && bottomExcess < minHeight)
|
||||||
{
|
{
|
||||||
buttons.Anchor = Anchor.BottomCentre;
|
targetAnchor = Anchor.BottomCentre;
|
||||||
buttons.Origin = Anchor.BottomCentre;
|
targetOrigin = Anchor.BottomCentre;
|
||||||
buttons.Y = Math.Min(0, ToLocalSpace(Parent!.ScreenSpaceDrawQuad.BottomLeft).Y - DrawHeight);
|
targetPosition.Y = Math.Min(0, ToLocalSpace(Parent!.ScreenSpaceDrawQuad.BottomLeft).Y - DrawHeight);
|
||||||
}
|
}
|
||||||
else if (topExcess > bottomExcess)
|
else if (topExcess > bottomExcess)
|
||||||
{
|
{
|
||||||
buttons.Anchor = Anchor.TopCentre;
|
targetAnchor = Anchor.TopCentre;
|
||||||
buttons.Origin = Anchor.BottomCentre;
|
targetOrigin = Anchor.BottomCentre;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
buttons.Anchor = Anchor.BottomCentre;
|
targetAnchor = Anchor.BottomCentre;
|
||||||
buttons.Origin = Anchor.TopCentre;
|
targetOrigin = Anchor.TopCentre;
|
||||||
}
|
}
|
||||||
|
|
||||||
buttons.X += ToLocalSpace(thisQuad.TopLeft - new Vector2(Math.Min(0, leftExcess)) + new Vector2(Math.Min(0, rightExcess))).X;
|
targetPosition.X += ToLocalSpace(thisQuad.TopLeft - new Vector2(Math.Min(0, leftExcess)) + new Vector2(Math.Min(0, rightExcess))).X;
|
||||||
|
|
||||||
|
if (animated)
|
||||||
|
{
|
||||||
|
var originalPosition = ToLocalSpace(buttons.ScreenSpaceDrawQuad.TopLeft);
|
||||||
|
|
||||||
|
buttons.Origin = targetOrigin;
|
||||||
|
buttons.Anchor = targetAnchor;
|
||||||
|
buttons.Position = targetPosition;
|
||||||
|
|
||||||
|
var newPosition = ToLocalSpace(buttons.ScreenSpaceDrawQuad.TopLeft);
|
||||||
|
|
||||||
|
var delta = newPosition - originalPosition;
|
||||||
|
|
||||||
|
buttons.Position -= delta;
|
||||||
|
|
||||||
|
buttons.MoveTo(targetPosition, 300, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
buttons.Anchor = targetAnchor;
|
||||||
|
buttons.Origin = targetOrigin;
|
||||||
|
buttons.Position = targetPosition;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
public Action? Action;
|
public Action? Action;
|
||||||
|
|
||||||
|
public event Action? HoverLost;
|
||||||
|
|
||||||
public SelectionBoxButton(IconUsage iconUsage, string tooltip)
|
public SelectionBoxButton(IconUsage iconUsage, string tooltip)
|
||||||
{
|
{
|
||||||
this.iconUsage = iconUsage;
|
this.iconUsage = iconUsage;
|
||||||
@ -61,6 +63,13 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
icon.FadeColour(!IsHeld && IsHovered ? Color4.White : Color4.Black, TRANSFORM_DURATION, Easing.OutQuint);
|
icon.FadeColour(!IsHeld && IsHovered ? Color4.White : Color4.Black, TRANSFORM_DURATION, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
|
{
|
||||||
|
base.OnHoverLost(e);
|
||||||
|
|
||||||
|
HoverLost?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
public LocalisableString TooltipText { get; }
|
public LocalisableString TooltipText { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,10 +52,11 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
/// If the default <see langword="null"/> value is supplied, a sane implementation-defined default will be used.
|
/// If the default <see langword="null"/> value is supplied, a sane implementation-defined default will be used.
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <param name="adjustAxis">The axes to adjust the scale in.</param>
|
/// <param name="adjustAxis">The axes to adjust the scale in.</param>
|
||||||
public void ScaleSelection(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both)
|
/// <param name="axisRotation">The rotation of the axes in degrees.</param>
|
||||||
|
public void ScaleSelection(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both, float axisRotation = 0)
|
||||||
{
|
{
|
||||||
Begin();
|
Begin();
|
||||||
Update(scale, origin, adjustAxis);
|
Update(scale, origin, adjustAxis, axisRotation);
|
||||||
Commit();
|
Commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,7 +92,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
/// If the default <see langword="null"/> value is supplied, a sane implementation-defined default will be used.
|
/// If the default <see langword="null"/> value is supplied, a sane implementation-defined default will be used.
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <param name="adjustAxis">The axes to adjust the scale in.</param>
|
/// <param name="adjustAxis">The axes to adjust the scale in.</param>
|
||||||
public virtual void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both)
|
/// <param name="axisRotation">The rotation of the axes in degrees.</param>
|
||||||
|
public virtual void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both, float axisRotation = 0)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ using osu.Framework.Localisation;
|
|||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
using osu.Game.Overlays;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Setup
|
namespace osu.Game.Screens.Edit.Setup
|
||||||
@ -118,6 +119,7 @@ namespace osu.Game.Screens.Edit.Setup
|
|||||||
protected override string PopOutSampleName => "UI/overlay-big-pop-out";
|
protected override string PopOutSampleName => "UI/overlay-big-pop-out";
|
||||||
|
|
||||||
public FileChooserPopover(string[] handledExtensions, Bindable<FileInfo?> currentFile, string? chooserPath)
|
public FileChooserPopover(string[] handledExtensions, Bindable<FileInfo?> currentFile, string? chooserPath)
|
||||||
|
: base(false)
|
||||||
{
|
{
|
||||||
Child = new Container
|
Child = new Container
|
||||||
{
|
{
|
||||||
@ -129,6 +131,13 @@ namespace osu.Game.Screens.Edit.Setup
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider colourProvider)
|
||||||
|
{
|
||||||
|
Body.BorderColour = colourProvider.Highlight1;
|
||||||
|
Body.BorderThickness = 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ using osu.Framework.Screens;
|
|||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
using osu.Game.Overlays;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Import
|
namespace osu.Game.Screens.Import
|
||||||
@ -36,8 +37,8 @@ namespace osu.Game.Screens.Import
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuGameBase game { get; set; }
|
private OsuGameBase game { get; set; }
|
||||||
|
|
||||||
[Resolved]
|
[Cached]
|
||||||
private OsuColour colours { get; set; }
|
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load()
|
private void load()
|
||||||
@ -52,11 +53,6 @@ namespace osu.Game.Screens.Import
|
|||||||
Size = new Vector2(0.9f, 0.8f),
|
Size = new Vector2(0.9f, 0.8f),
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new Box
|
|
||||||
{
|
|
||||||
Colour = colours.GreySeaFoamDark,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
},
|
|
||||||
fileSelector = new OsuFileSelector(validFileExtensions: game.HandledExtensions.ToArray())
|
fileSelector = new OsuFileSelector(validFileExtensions: game.HandledExtensions.ToArray())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
@ -72,7 +68,7 @@ namespace osu.Game.Screens.Import
|
|||||||
{
|
{
|
||||||
new Box
|
new Box
|
||||||
{
|
{
|
||||||
Colour = colours.GreySeaFoamDarker,
|
Colour = colourProvider.Background4,
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
},
|
},
|
||||||
new Container
|
new Container
|
||||||
|
@ -118,13 +118,20 @@ namespace osu.Game.Screens
|
|||||||
{
|
{
|
||||||
loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE));
|
loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE));
|
||||||
loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.BLUR));
|
loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.BLUR));
|
||||||
|
|
||||||
loadTargets.Add(manager.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE));
|
|
||||||
|
|
||||||
loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, "TriangleBorder"));
|
|
||||||
loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, "FastCircle"));
|
|
||||||
|
|
||||||
loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_3, FragmentShaderDescriptor.TEXTURE));
|
loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_3, FragmentShaderDescriptor.TEXTURE));
|
||||||
|
|
||||||
|
loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, @"TriangleBorder"));
|
||||||
|
loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, @"FastCircle"));
|
||||||
|
loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, @"CircularProgress"));
|
||||||
|
loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, @"ArgonBarPath"));
|
||||||
|
loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, @"ArgonBarPathBackground"));
|
||||||
|
loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, @"SaturationSelectorBackground"));
|
||||||
|
loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, @"HueSelectorBackground"));
|
||||||
|
loadTargets.Add(manager.Load(@"LogoAnimation", @"LogoAnimation"));
|
||||||
|
|
||||||
|
// Ruleset local shader usage (should probably move somewhere else).
|
||||||
|
loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, @"SpinnerGlow"));
|
||||||
|
loadTargets.Add(manager.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual bool AllLoaded => loadTargets.All(s => s.IsLoaded);
|
protected virtual bool AllLoaded => loadTargets.All(s => s.IsLoaded);
|
||||||
|
@ -3,7 +3,9 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -24,6 +26,21 @@ namespace osu.Game.Screens.Play
|
|||||||
private readonly Storyboard storyboard;
|
private readonly Storyboard storyboard;
|
||||||
private readonly IReadOnlyList<Mod> mods;
|
private readonly IReadOnlyList<Mod> mods;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// In certain circumstances, the storyboard cannot be hidden entirely even if it is fully dimmed. Such circumstances include:
|
||||||
|
/// <list type="bullet">
|
||||||
|
/// <item>
|
||||||
|
/// cases where the storyboard has an overlay layer sprite, as it should continue to display fully dimmed
|
||||||
|
/// <i>in front of</i> the playfield (https://github.com/ppy/osu/issues/29867),
|
||||||
|
/// </item>
|
||||||
|
/// <item>
|
||||||
|
/// cases where the storyboard includes samples - as they are played back via drawable samples,
|
||||||
|
/// they must be present for the playback to occur (https://github.com/ppy/osu/issues/9315).
|
||||||
|
/// </item>
|
||||||
|
/// </list>
|
||||||
|
/// </summary>
|
||||||
|
private readonly Lazy<bool> storyboardMustAlwaysBePresent;
|
||||||
|
|
||||||
private DrawableStoryboard drawableStoryboard;
|
private DrawableStoryboard drawableStoryboard;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -38,6 +55,8 @@ namespace osu.Game.Screens.Play
|
|||||||
{
|
{
|
||||||
this.storyboard = storyboard;
|
this.storyboard = storyboard;
|
||||||
this.mods = mods;
|
this.mods = mods;
|
||||||
|
|
||||||
|
storyboardMustAlwaysBePresent = new Lazy<bool>(() => storyboard.GetLayer(@"Overlay").Elements.Any() || storyboard.Layers.Any(l => l.Elements.OfType<StoryboardSampleInfo>().Any()));
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -54,7 +73,7 @@ namespace osu.Game.Screens.Play
|
|||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool ShowDimContent => IgnoreUserSettings.Value || (ShowStoryboard.Value && DimLevel < 1);
|
protected override bool ShowDimContent => IgnoreUserSettings.Value || (ShowStoryboard.Value && (DimLevel < 1 || storyboardMustAlwaysBePresent.Value));
|
||||||
|
|
||||||
private void initializeStoryboard(bool async)
|
private void initializeStoryboard(bool async)
|
||||||
{
|
{
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user