mirror of
https://github.com/ppy/osu.git
synced 2026-06-06 09:14:21 +08:00
Compare commits
178 Commits
@@ -0,0 +1,19 @@
|
||||
name: Copy labels from linked issues
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened]
|
||||
|
||||
permissions:
|
||||
issues: write # to read the labels of any linked issue(s), and to put the found labels if any on the PR
|
||||
# not granting any `pull_requests` permissions because in github's modeling pull requests are a subset of issues. it's confusing.
|
||||
|
||||
jobs:
|
||||
copy-labels:
|
||||
runs-on: ubuntu-latest
|
||||
name: Copy labels from linked issues
|
||||
steps:
|
||||
- name: Copy labels
|
||||
uses: michalvankodev/copy-issue-labels@v1.3.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2026.108.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2026.209.0" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||
|
||||
@@ -146,9 +146,13 @@ namespace osu.Desktop
|
||||
{
|
||||
base.SetHost(host);
|
||||
|
||||
var iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico");
|
||||
if (iconStream != null)
|
||||
host.Window.SetIconFromStream(iconStream);
|
||||
// Apple operating systems use a better icon provided via external assets.
|
||||
if (!RuntimeInfo.IsApple)
|
||||
{
|
||||
var iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico");
|
||||
if (iconStream != null)
|
||||
host.Window.SetIconFromStream(iconStream);
|
||||
}
|
||||
|
||||
host.Window.Title = Name;
|
||||
}
|
||||
|
||||
@@ -35,11 +35,9 @@
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
</array>
|
||||
<key>XSAppIconAssets</key>
|
||||
<string>Assets.xcassets/AppIcon.appiconset</string>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
</plist>
|
||||
|
||||
@@ -176,15 +176,20 @@ namespace osu.Game.Rulesets.Catch
|
||||
|
||||
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetCatch };
|
||||
|
||||
protected override IEnumerable<HitResult> GetValidHitResults()
|
||||
public override IEnumerable<HitResult> GetValidHitResults()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
HitResult.Great,
|
||||
HitResult.Miss,
|
||||
|
||||
HitResult.LargeTickHit,
|
||||
HitResult.LargeTickMiss,
|
||||
HitResult.SmallTickHit,
|
||||
HitResult.SmallTickMiss,
|
||||
HitResult.LargeBonus,
|
||||
HitResult.IgnoreHit,
|
||||
HitResult.IgnoreMiss,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -35,11 +35,9 @@
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
</array>
|
||||
<key>XSAppIconAssets</key>
|
||||
<string>Assets.xcassets/AppIcon.appiconset</string>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
</plist>
|
||||
|
||||
@@ -383,7 +383,7 @@ namespace osu.Game.Rulesets.Mania
|
||||
return (PlayfieldType)Enum.GetValues(typeof(PlayfieldType)).Cast<int>().OrderDescending().First(v => variant >= v);
|
||||
}
|
||||
|
||||
protected override IEnumerable<HitResult> GetValidHitResults()
|
||||
public override IEnumerable<HitResult> GetValidHitResults()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
@@ -392,9 +392,11 @@ namespace osu.Game.Rulesets.Mania
|
||||
HitResult.Good,
|
||||
HitResult.Ok,
|
||||
HitResult.Meh,
|
||||
HitResult.Miss,
|
||||
|
||||
// HitResult.SmallBonus is used for awarding perfect bonus score but is not included here as
|
||||
// it would be a bit redundant to show this to the user.
|
||||
HitResult.IgnoreHit,
|
||||
HitResult.ComboBreak,
|
||||
HitResult.IgnoreMiss,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ using osu.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Rulesets.Mania.Configuration;
|
||||
@@ -31,47 +31,45 @@ namespace osu.Game.Rulesets.Mania
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new SettingsEnumDropdown<ManiaScrollingDirection>
|
||||
new SettingsItemV2(new FormEnumDropdown<ManiaScrollingDirection>
|
||||
{
|
||||
LabelText = RulesetSettingsStrings.ScrollingDirection,
|
||||
Caption = RulesetSettingsStrings.ScrollingDirection,
|
||||
Current = config.GetBindable<ManiaScrollingDirection>(ManiaRulesetSetting.ScrollDirection)
|
||||
},
|
||||
new SettingsSlider<double, ManiaScrollSlider>
|
||||
}),
|
||||
new SettingsItemV2(new FormSliderBar<double>
|
||||
{
|
||||
LabelText = RulesetSettingsStrings.ScrollSpeed,
|
||||
Caption = RulesetSettingsStrings.ScrollSpeed,
|
||||
Current = config.GetBindable<double>(ManiaRulesetSetting.ScrollSpeed),
|
||||
KeyboardStep = 1
|
||||
},
|
||||
new SettingsCheckbox
|
||||
KeyboardStep = 1,
|
||||
LabelFormat = v => RulesetSettingsStrings.ScrollSpeedTooltip((int)DrawableManiaRuleset.ComputeScrollTime(v), v),
|
||||
}),
|
||||
new SettingsItemV2(new FormCheckBox
|
||||
{
|
||||
Caption = RulesetSettingsStrings.TimingBasedColouring,
|
||||
Current = config.GetBindable<bool>(ManiaRulesetSetting.TimingBasedNoteColouring),
|
||||
})
|
||||
{
|
||||
Keywords = new[] { "color" },
|
||||
LabelText = RulesetSettingsStrings.TimingBasedColouring,
|
||||
Current = config.GetBindable<bool>(ManiaRulesetSetting.TimingBasedNoteColouring),
|
||||
},
|
||||
};
|
||||
|
||||
Add(new SettingsCheckbox
|
||||
Add(new SettingsItemV2(new FormCheckBox
|
||||
{
|
||||
LabelText = RulesetSettingsStrings.TouchOverlay,
|
||||
Caption = RulesetSettingsStrings.TouchOverlay,
|
||||
Current = config.GetBindable<bool>(ManiaRulesetSetting.TouchOverlay)
|
||||
});
|
||||
}));
|
||||
|
||||
if (RuntimeInfo.IsMobile)
|
||||
{
|
||||
Add(new SettingsEnumDropdown<ManiaMobileLayout>
|
||||
Add(new SettingsItemV2(new FormEnumDropdown<ManiaMobileLayout>
|
||||
{
|
||||
LabelText = RulesetSettingsStrings.MobileLayout,
|
||||
Caption = RulesetSettingsStrings.MobileLayout,
|
||||
Current = config.GetBindable<ManiaMobileLayout>(ManiaRulesetSetting.MobileLayout),
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
Items = Enum.GetValues<ManiaMobileLayout>().Where(l => l != ManiaMobileLayout.LandscapeWithOverlay),
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
private partial class ManiaScrollSlider : RoundedSliderBar<double>
|
||||
{
|
||||
public override LocalisableString TooltipText => RulesetSettingsStrings.ScrollSpeedTooltip((int)DrawableManiaRuleset.ComputeScrollTime(Current.Value), Current.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
|
||||
var hitWindows = new ManiaHitWindows();
|
||||
|
||||
AddInternal(judgementPooler = new JudgementPooler<DrawableManiaJudgement>(Enum.GetValues<HitResult>().Where(r => hitWindows.IsHitResultAllowed(r))));
|
||||
AddInternal(judgementPooler = new JudgementPooler<DrawableManiaJudgement>(Enum.GetValues<HitResult>().Where(hitWindows.IsHitResultAllowed)));
|
||||
|
||||
RegisterPool<BarLine, DrawableBarLine>(50, 200);
|
||||
}
|
||||
|
||||
@@ -35,11 +35,9 @@
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
</array>
|
||||
<key>XSAppIconAssets</key>
|
||||
<string>Assets.xcassets/AppIcon.appiconset</string>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
</plist>
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
Debug.Assert(drawableHitObject.HitObject.HitWindows != null);
|
||||
|
||||
double delay = drawableHitObject.HitObject.StartTime - (drawableHitObject.HitObject.HitWindows.WindowFor(HitResult.Miss) + RNG.Next(0, 300)) - Time.Current;
|
||||
scheduledTasks.Add(Scheduler.AddDelayed(() => drawableHitObject.TriggerJudgement(), delay));
|
||||
scheduledTasks.Add(Scheduler.AddDelayed(drawableHitObject.TriggerJudgement, delay));
|
||||
|
||||
return drawableHitObject;
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
[SettingSource(
|
||||
"Max size at combo",
|
||||
"The combo count at which the cursor reaches its maximum size",
|
||||
SettingControlType = typeof(SettingsSlider<int, RoundedSliderBar<int>>)
|
||||
SettingControlType = typeof(SettingsSlider<int, MaxSizeComboSlider>)
|
||||
)]
|
||||
public BindableInt MaxSizeComboCount { get; } = new BindableInt(50)
|
||||
{
|
||||
@@ -85,4 +85,12 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
cursor.ModScaleAdjust.Value = (float)Interpolation.Lerp(cursor.ModScaleAdjust.Value, currentSize, Math.Clamp(cursor.Time.Elapsed / TRANSITION_DURATION, 0, 1));
|
||||
}
|
||||
}
|
||||
|
||||
public partial class MaxSizeComboSlider : RoundedSliderBar<int>
|
||||
{
|
||||
public MaxSizeComboSlider()
|
||||
{
|
||||
KeyboardStep = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
@@ -71,6 +73,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
}
|
||||
}
|
||||
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModTargetPractice)).ToArray();
|
||||
|
||||
protected override void ApplySettings(BeatmapDifficulty difficulty)
|
||||
{
|
||||
base.ApplySettings(difficulty);
|
||||
|
||||
@@ -27,8 +27,10 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
public override LocalisableString Description => "Burn the notes into your memory.";
|
||||
|
||||
//Alters the transforms of the approach circles, breaking the effects of these mods.
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModApproachDifferent), typeof(OsuModTransform), typeof(OsuModDepth) }).ToArray();
|
||||
/// <remarks>
|
||||
/// Incompatible with all mods that directly modify or indirectly depend on <see cref="OsuHitObject.TimePreempt"/>, or alter the behaviour of approach circles.
|
||||
/// </remarks>
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModApproachDifferent), typeof(OsuModTransform), typeof(OsuModDepth), typeof(OsuModHidden) }).ToArray();
|
||||
|
||||
public override ModType Type => ModType.Fun;
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override LocalisableString Description => @"Play with no approach circles and fading circles/sliders.";
|
||||
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1;
|
||||
|
||||
public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn), typeof(OsuModDepth) };
|
||||
public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn), typeof(OsuModDepth), typeof(OsuModFreezeFrame) };
|
||||
|
||||
public const double FADE_IN_DURATION_MULTIPLIER = 0.4;
|
||||
public const double FADE_OUT_DURATION_MULTIPLIER = 0.3;
|
||||
|
||||
@@ -48,7 +48,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
typeof(OsuModSpunOut),
|
||||
typeof(OsuModStrictTracking),
|
||||
typeof(OsuModSuddenDeath),
|
||||
typeof(OsuModDepth)
|
||||
typeof(OsuModDepth),
|
||||
typeof(OsuModDifficultyAdjust),
|
||||
}).ToArray();
|
||||
|
||||
[SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(SettingsNumberBox))]
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override string Name => "Traceable";
|
||||
public override string Acronym => "TC";
|
||||
public override IconUsage? Icon => OsuIcon.ModTraceable;
|
||||
public override ModType Type => ModType.Fun;
|
||||
public override ModType Type => ModType.DifficultyIncrease;
|
||||
public override LocalisableString Description => "Put your faith in the approach circles...";
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override bool Ranked => true;
|
||||
|
||||
@@ -175,7 +175,7 @@ namespace osu.Game.Rulesets.Osu
|
||||
new OsuModHardRock(),
|
||||
new MultiMod(new OsuModSuddenDeath(), new OsuModPerfect()),
|
||||
new MultiMod(new OsuModDoubleTime(), new OsuModNightcore()),
|
||||
new OsuModHidden(),
|
||||
new MultiMod(new OsuModHidden(), new OsuModTraceable()),
|
||||
new MultiMod(new OsuModFlashlight(), new OsuModBlinds()),
|
||||
new OsuModStrictTracking(),
|
||||
new OsuModAccuracyChallenge(),
|
||||
@@ -209,7 +209,6 @@ namespace osu.Game.Rulesets.Osu
|
||||
new OsuModSpinIn(),
|
||||
new MultiMod(new OsuModGrow(), new OsuModDeflate()),
|
||||
new MultiMod(new ModWindUp(), new ModWindDown()),
|
||||
new OsuModTraceable(),
|
||||
new OsuModBarrelRoll(),
|
||||
new OsuModApproachDifferent(),
|
||||
new OsuModMuted(),
|
||||
@@ -278,19 +277,24 @@ namespace osu.Game.Rulesets.Osu
|
||||
|
||||
public override IRulesetConfigManager CreateConfig(SettingsStore? settings) => new OsuRulesetConfigManager(settings, RulesetInfo);
|
||||
|
||||
protected override IEnumerable<HitResult> GetValidHitResults()
|
||||
public override IEnumerable<HitResult> GetValidHitResults()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
HitResult.Great,
|
||||
HitResult.Ok,
|
||||
HitResult.Meh,
|
||||
HitResult.Miss,
|
||||
|
||||
HitResult.LargeTickHit,
|
||||
HitResult.LargeTickMiss,
|
||||
HitResult.SmallTickHit,
|
||||
HitResult.SmallTickMiss,
|
||||
HitResult.SliderTailHit,
|
||||
HitResult.SmallBonus,
|
||||
HitResult.LargeBonus,
|
||||
HitResult.IgnoreHit,
|
||||
HitResult.IgnoreMiss,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Rulesets.Osu.Configuration;
|
||||
@@ -27,32 +29,34 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new SettingsCheckbox
|
||||
new SettingsItemV2(new FormCheckBox
|
||||
{
|
||||
LabelText = RulesetSettingsStrings.SnakingInSliders,
|
||||
Caption = RulesetSettingsStrings.SnakingInSliders,
|
||||
Current = config.GetBindable<bool>(OsuRulesetSetting.SnakingInSliders)
|
||||
},
|
||||
new SettingsCheckbox
|
||||
}),
|
||||
new SettingsItemV2(new FormCheckBox
|
||||
{
|
||||
ClassicDefault = false,
|
||||
LabelText = RulesetSettingsStrings.SnakingOutSliders,
|
||||
Caption = RulesetSettingsStrings.SnakingOutSliders,
|
||||
Current = config.GetBindable<bool>(OsuRulesetSetting.SnakingOutSliders)
|
||||
},
|
||||
new SettingsCheckbox
|
||||
})
|
||||
{
|
||||
LabelText = RulesetSettingsStrings.CursorTrail,
|
||||
ApplyClassicDefault = c => ((IHasCurrentValue<bool>)c).Current.Value = false,
|
||||
},
|
||||
new SettingsItemV2(new FormCheckBox
|
||||
{
|
||||
Caption = RulesetSettingsStrings.CursorTrail,
|
||||
Current = config.GetBindable<bool>(OsuRulesetSetting.ShowCursorTrail)
|
||||
},
|
||||
new SettingsCheckbox
|
||||
}),
|
||||
new SettingsItemV2(new FormCheckBox
|
||||
{
|
||||
LabelText = RulesetSettingsStrings.CursorRipples,
|
||||
Caption = RulesetSettingsStrings.CursorRipples,
|
||||
Current = config.GetBindable<bool>(OsuRulesetSetting.ShowCursorRipples)
|
||||
},
|
||||
new SettingsEnumDropdown<PlayfieldBorderStyle>
|
||||
}),
|
||||
new SettingsItemV2(new FormEnumDropdown<PlayfieldBorderStyle>
|
||||
{
|
||||
LabelText = RulesetSettingsStrings.PlayfieldBorderStyle,
|
||||
Caption = RulesetSettingsStrings.PlayfieldBorderStyle,
|
||||
Current = config.GetBindable<PlayfieldBorderStyle>(OsuRulesetSetting.PlayfieldBorderStyle),
|
||||
},
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,11 +35,9 @@
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
</array>
|
||||
<key>XSAppIconAssets</key>
|
||||
<string>Assets.xcassets/AppIcon.appiconset</string>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
</plist>
|
||||
|
||||
@@ -222,15 +222,18 @@ namespace osu.Game.Rulesets.Taiko
|
||||
|
||||
public override RulesetSettingsSubsection CreateSettings() => new TaikoSettingsSubsection(this);
|
||||
|
||||
protected override IEnumerable<HitResult> GetValidHitResults()
|
||||
public override IEnumerable<HitResult> GetValidHitResults()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
HitResult.Great,
|
||||
HitResult.Ok,
|
||||
HitResult.Miss,
|
||||
|
||||
HitResult.SmallBonus,
|
||||
HitResult.LargeBonus,
|
||||
HitResult.IgnoreHit,
|
||||
HitResult.IgnoreMiss,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Rulesets.Taiko.Configuration;
|
||||
@@ -26,11 +27,11 @@ namespace osu.Game.Rulesets.Taiko
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new SettingsEnumDropdown<TaikoTouchControlScheme>
|
||||
new SettingsItemV2(new FormEnumDropdown<TaikoTouchControlScheme>
|
||||
{
|
||||
LabelText = RulesetSettingsStrings.TouchControlScheme,
|
||||
Caption = RulesetSettingsStrings.TouchControlScheme,
|
||||
Current = config.GetBindable<TaikoTouchControlScheme>(TaikoRulesetSetting.TouchControlScheme)
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
|
||||
var hitWindows = new TaikoHitWindows();
|
||||
|
||||
HitResult[] usableHitResults = Enum.GetValues<HitResult>().Where(r => hitWindows.IsHitResultAllowed(r)).ToArray();
|
||||
HitResult[] usableHitResults = Enum.GetValues<HitResult>().Where(hitWindows.IsHitResultAllowed).ToArray();
|
||||
|
||||
AddInternal(judgementPooler = new JudgementPooler<DrawableTaikoJudgement>(usableHitResults));
|
||||
|
||||
|
||||
@@ -35,8 +35,31 @@
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
</array>
|
||||
<key>XSAppIconAssets</key>
|
||||
<string>Assets.xcassets/AppIcon.appiconset</string>
|
||||
<key>CFBundleIcons~ipad</key>
|
||||
<dict>
|
||||
<key>CFBundlePrimaryIcon</key>
|
||||
<dict>
|
||||
<key>CFBundleIconFiles</key>
|
||||
<array>
|
||||
<string>AppIcon60x60</string>
|
||||
</array>
|
||||
<key>CFBundleIconName</key>
|
||||
<string>AppIcon</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>CFBundleIcons</key>
|
||||
<dict>
|
||||
<key>CFBundlePrimaryIcon</key>
|
||||
<dict>
|
||||
<key>CFBundleIconFiles</key>
|
||||
<array>
|
||||
<string>AppIcon60x60</string>
|
||||
<string>AppIcon76x76</string>
|
||||
</array>
|
||||
<key>CFBundleIconName</key>
|
||||
<string>AppIcon</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -15,6 +16,7 @@ using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Tests.Beatmaps.IO;
|
||||
using osu.Game.Tests.Visual;
|
||||
@@ -34,6 +36,12 @@ namespace osu.Game.Tests.Beatmaps
|
||||
|
||||
private IBindable<StarDifficulty> starDifficultyBindable;
|
||||
|
||||
[Resolved]
|
||||
private BeatmapManager beatmapManager { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private BeatmapDifficultyCache actualDifficultyCache { get; set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuGameBase osu)
|
||||
{
|
||||
@@ -55,6 +63,36 @@ namespace osu.Game.Tests.Beatmaps
|
||||
AddUntilStep($"star difficulty -> {BASE_STARS}", () => starDifficultyBindable.Value.Stars == BASE_STARS);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestInvalidationFlow()
|
||||
{
|
||||
BeatmapInfo postEditBeatmapInfo = null;
|
||||
BeatmapInfo preEditBeatmapInfo = null;
|
||||
|
||||
IBindable<StarDifficulty> bindableDifficulty = null;
|
||||
|
||||
AddStep("get bindable stars", () =>
|
||||
{
|
||||
preEditBeatmapInfo = importedSet.Beatmaps.First();
|
||||
bindableDifficulty = actualDifficultyCache.GetBindableDifficulty(preEditBeatmapInfo);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for stars retrieved", () => bindableDifficulty.Value.Stars, () => Is.GreaterThan(0));
|
||||
|
||||
AddStep("remove all hitobjects", () =>
|
||||
{
|
||||
var working = beatmapManager.GetWorkingBeatmap(preEditBeatmapInfo);
|
||||
|
||||
((IList<HitObject>)working.Beatmap.HitObjects).Clear();
|
||||
|
||||
beatmapManager.Save(working.BeatmapInfo, working.Beatmap);
|
||||
postEditBeatmapInfo = working.BeatmapInfo;
|
||||
});
|
||||
|
||||
AddAssert("stars is now zero", () => actualDifficultyCache.GetDifficultyAsync(postEditBeatmapInfo).GetResultSafely()!.Value.Stars, () => Is.Zero);
|
||||
AddUntilStep("bindable stars is now zero", () => bindableDifficulty.Value.Stars, () => Is.Zero);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStarDifficultyChangesOnModSettings()
|
||||
{
|
||||
@@ -76,6 +114,30 @@ namespace osu.Game.Tests.Beatmaps
|
||||
AddUntilStep($"star difficulty -> {BASE_STARS + 1.75}", () => starDifficultyBindable.Value.Stars == BASE_STARS + 1.75);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStarDifficultyChangesOnModSettingsCorrectlyTrackAcrossReferenceChanges()
|
||||
{
|
||||
OsuModDoubleTime dt = null;
|
||||
|
||||
AddStep("set computation function", () => difficultyCache.ComputeDifficulty = lookup =>
|
||||
{
|
||||
var modRateAdjust = (ModRateAdjust)lookup.OrderedMods.SingleOrDefault(mod => mod is ModRateAdjust);
|
||||
return new StarDifficulty(BASE_STARS + modRateAdjust?.SpeedChange.Value ?? 0, 0);
|
||||
});
|
||||
|
||||
AddStep("change selected mod to DT", () => SelectedMods.Value = new[] { dt = new OsuModDoubleTime { SpeedChange = { Value = 1.5 } } });
|
||||
AddUntilStep($"star difficulty -> {BASE_STARS + 1.5}", () => starDifficultyBindable.Value.Stars == BASE_STARS + 1.5);
|
||||
|
||||
AddStep("change DT speed to 1.25", () => dt.SpeedChange.Value = 1.25);
|
||||
AddUntilStep($"star difficulty -> {BASE_STARS + 1.25}", () => starDifficultyBindable.Value.Stars == BASE_STARS + 1.25);
|
||||
|
||||
AddStep("reconstruct DT mod with same settings", () => SelectedMods.Value = new[] { dt = (OsuModDoubleTime)dt.DeepClone() });
|
||||
AddUntilStep($"star difficulty -> {BASE_STARS + 1.25}", () => starDifficultyBindable.Value.Stars == BASE_STARS + 1.25);
|
||||
|
||||
AddStep("change DT speed to 1.25", () => dt.SpeedChange.Value = 2);
|
||||
AddUntilStep($"star difficulty -> {BASE_STARS + 2}", () => starDifficultyBindable.Value.Stars == BASE_STARS + 2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStarDifficultyAdjustHashCodeConflict()
|
||||
{
|
||||
@@ -122,8 +184,10 @@ namespace osu.Game.Tests.Beatmaps
|
||||
[Test]
|
||||
public void TestKeyDoesntEqualWithDifferentModSettings()
|
||||
{
|
||||
var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.1 } } });
|
||||
var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.9 } } });
|
||||
var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 },
|
||||
new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.1 } } });
|
||||
var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 },
|
||||
new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.9 } } });
|
||||
|
||||
Assert.That(key1, Is.Not.EqualTo(key2));
|
||||
Assert.That(key1.GetHashCode(), Is.Not.EqualTo(key2.GetHashCode()));
|
||||
@@ -132,8 +196,10 @@ namespace osu.Game.Tests.Beatmaps
|
||||
[Test]
|
||||
public void TestKeyEqualWithMatchingModSettings()
|
||||
{
|
||||
var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } });
|
||||
var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } });
|
||||
var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 },
|
||||
new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } });
|
||||
var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 },
|
||||
new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } });
|
||||
|
||||
Assert.That(key1, Is.EqualTo(key2));
|
||||
Assert.That(key1.GetHashCode(), Is.EqualTo(key2.GetHashCode()));
|
||||
|
||||
@@ -247,7 +247,7 @@ namespace osu.Game.Tests.Beatmaps
|
||||
|
||||
AddStep("change all start times", () =>
|
||||
{
|
||||
editorBeatmap.HitObjectUpdated += h => updatedObjects.Add(h);
|
||||
editorBeatmap.HitObjectUpdated += updatedObjects.Add;
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
allHitObjects[i].StartTime += 10;
|
||||
@@ -282,7 +282,7 @@ namespace osu.Game.Tests.Beatmaps
|
||||
|
||||
AddStep("change start time twice", () =>
|
||||
{
|
||||
editorBeatmap.HitObjectUpdated += h => updatedObjects.Add(h);
|
||||
editorBeatmap.HitObjectUpdated += updatedObjects.Add;
|
||||
|
||||
editorBeatmap.HitObjects[0].StartTime = 10;
|
||||
editorBeatmap.HitObjects[0].StartTime = 20;
|
||||
|
||||
@@ -104,10 +104,7 @@ namespace osu.Game.Tests.Database
|
||||
Assert.AreNotEqual(detachedBeatmapSet.Status, BeatmapOnlineStatus.Ranked);
|
||||
detachedBeatmapSet.Status = BeatmapOnlineStatus.Ranked;
|
||||
|
||||
beatmapSet.PerformWrite(s =>
|
||||
{
|
||||
detachedBeatmapSet.CopyChangesToRealm(s);
|
||||
});
|
||||
beatmapSet.PerformWrite(detachedBeatmapSet.CopyChangesToRealm);
|
||||
|
||||
beatmapSet.PerformRead(s =>
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Catch;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
@@ -116,6 +117,69 @@ namespace osu.Game.Tests.Database
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFakedRulesetIdIsDetected()
|
||||
{
|
||||
RunTestWithRealm((realm, storage) =>
|
||||
{
|
||||
LoadTestRuleset.HasImplementations = true;
|
||||
LoadTestRuleset.Version = Ruleset.CURRENT_RULESET_API_VERSION;
|
||||
|
||||
var ruleset = new LoadTestRuleset();
|
||||
string rulesetShortName = ruleset.RulesetInfo.ShortName;
|
||||
|
||||
realm.Write(r => r.Add(new RulesetInfo(rulesetShortName, ruleset.RulesetInfo.Name, ruleset.RulesetInfo.InstantiationInfo, 0)
|
||||
{
|
||||
Available = true,
|
||||
}));
|
||||
|
||||
Assert.That(realm.Run(r => r.Find<RulesetInfo>(rulesetShortName)!.Available), Is.True);
|
||||
|
||||
// Availability is updated on construction of a RealmRulesetStore
|
||||
using var _ = new RealmRulesetStore(realm, storage);
|
||||
|
||||
Assert.That(realm.Run(r => r.Find<RulesetInfo>(rulesetShortName)!.Available), Is.False);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMultipleRulesetWithSameOnlineIdsAreDetected()
|
||||
{
|
||||
RunTestWithRealm((realm, storage) =>
|
||||
{
|
||||
LoadTestRuleset.HasImplementations = true;
|
||||
LoadTestRuleset.Version = Ruleset.CURRENT_RULESET_API_VERSION;
|
||||
LoadTestRuleset.OnlineID = 2;
|
||||
|
||||
var first = new LoadTestRuleset();
|
||||
var second = new CatchRuleset();
|
||||
|
||||
realm.Write(r => r.Add(new RulesetInfo(first.ShortName, first.RulesetInfo.Name, first.RulesetInfo.InstantiationInfo, first.RulesetInfo.OnlineID)
|
||||
{
|
||||
Available = true,
|
||||
}));
|
||||
realm.Write(r => r.Add(new RulesetInfo(second.ShortName, second.RulesetInfo.Name, second.RulesetInfo.InstantiationInfo, second.RulesetInfo.OnlineID)
|
||||
{
|
||||
Available = true,
|
||||
}));
|
||||
|
||||
Assert.That(realm.Run(r => r.Find<RulesetInfo>(first.ShortName)!.Available), Is.True);
|
||||
Assert.That(realm.Run(r => r.Find<RulesetInfo>(second.ShortName)!.Available), Is.True);
|
||||
|
||||
// Availability is updated on construction of a RealmRulesetStore
|
||||
using var _ = new RealmRulesetStore(realm, storage);
|
||||
|
||||
Assert.That(realm.Run(r => r.Find<RulesetInfo>(first.ShortName)!.Available), Is.False);
|
||||
Assert.That(realm.Run(r => r.Find<RulesetInfo>(second.ShortName)!.Available), Is.False);
|
||||
|
||||
realm.Write(r => r.Remove(r.Find<RulesetInfo>(first.ShortName)!));
|
||||
|
||||
using var __ = new RealmRulesetStore(realm, storage);
|
||||
|
||||
Assert.That(realm.Run(r => r.Find<RulesetInfo>(second.ShortName)!.Available), Is.True);
|
||||
});
|
||||
}
|
||||
|
||||
private class LoadTestRuleset : Ruleset
|
||||
{
|
||||
public override string RulesetAPIVersionSupported => Version;
|
||||
@@ -124,6 +188,13 @@ namespace osu.Game.Tests.Database
|
||||
|
||||
public static string Version { get; set; } = CURRENT_RULESET_API_VERSION;
|
||||
|
||||
public static int OnlineID { get; set; } = -1;
|
||||
|
||||
public LoadTestRuleset()
|
||||
{
|
||||
RulesetInfo.OnlineID = OnlineID;
|
||||
}
|
||||
|
||||
public override IEnumerable<Mod> GetModsFor(ModType type)
|
||||
{
|
||||
if (!HasImplementations)
|
||||
|
||||
@@ -526,7 +526,7 @@ namespace osu.Game.Tests.Rulesets.Scoring
|
||||
// ReSharper disable once MemberHidesStaticFromOuterClass
|
||||
private class TestRuleset : Ruleset
|
||||
{
|
||||
protected override IEnumerable<HitResult> GetValidHitResults() => new[] { HitResult.Great };
|
||||
public override IEnumerable<HitResult> GetValidHitResults() => new[] { HitResult.Great };
|
||||
|
||||
public override IEnumerable<Mod> GetModsFor(ModType type) => throw new NotImplementedException();
|
||||
|
||||
|
||||
@@ -151,7 +151,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
List<SkinBlueprint> blueprints = new List<SkinBlueprint>();
|
||||
|
||||
AddStep("clear list", () => blueprints.Clear());
|
||||
AddStep("clear list", blueprints.Clear);
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
// 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.Testing;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Users.Drawables;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
public partial class TestSceneClickableTeamFlag : OsuManualInputManagerTestScene
|
||||
{
|
||||
[SetUpSteps]
|
||||
public void SetUp()
|
||||
{
|
||||
AddStep("create flags", () =>
|
||||
{
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Spacing = new Vector2(10f),
|
||||
Children = new[]
|
||||
{
|
||||
new ClickableTeamFlag(
|
||||
new APITeam
|
||||
{
|
||||
Id = 1,
|
||||
Name = "Collective Wangs",
|
||||
ShortName = "WANG",
|
||||
}, showTooltipOnHover: false) { Width = 300, Height = 150 },
|
||||
new ClickableTeamFlag(
|
||||
new APITeam
|
||||
{
|
||||
Id = 2,
|
||||
Name = "mom?",
|
||||
ShortName = "MOM",
|
||||
FlagUrl = "https://assets.ppy.sh/teams/flag/1/b46fb10dbfd8a35dc50e6c00296c0dc6172dffc3ed3d3a4b379277ba498399fe.png",
|
||||
}, showTooltipOnHover: true) { Width = 300, Height = 150 },
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHover()
|
||||
{
|
||||
AddStep("hover flag with no tooltip", () => InputManager.MoveMouseTo(this.ChildrenOfType<ClickableTeamFlag>().ElementAt(0)));
|
||||
AddWaitStep("wait", 3);
|
||||
AddAssert("tooltip is not visible", () => this.ChildrenOfType<OsuTooltipContainer.OsuTooltip>().FirstOrDefault()?.State.Value, () => Is.EqualTo(Visibility.Hidden));
|
||||
AddStep("hover flag with tooltip", () => InputManager.MoveMouseTo(this.ChildrenOfType<ClickableTeamFlag>().ElementAt(1)));
|
||||
AddUntilStep("wait for tooltip to show", () => this.ChildrenOfType<OsuTooltipContainer.OsuTooltip>().FirstOrDefault()?.State.Value, () => Is.EqualTo(Visibility.Visible));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,18 +3,16 @@
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Metadata;
|
||||
using osu.Game.Online.Spectator;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Dashboard;
|
||||
using osu.Game.Screens.OnlinePlay.Match.Components;
|
||||
using osu.Game.Overlays.Dashboard.CurrentlyOnline;
|
||||
using osu.Game.Tests.Visual.Metadata;
|
||||
using osu.Game.Tests.Visual.Spectator;
|
||||
using osu.Game.Users;
|
||||
@@ -23,6 +21,26 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
public partial class TestSceneCurrentlyOnlineDisplay : OsuTestScene
|
||||
{
|
||||
private static readonly string[] usernames =
|
||||
{
|
||||
"fieryrage",
|
||||
"Kerensa",
|
||||
"MillhioreF",
|
||||
"Player01",
|
||||
"smoogipoo",
|
||||
"Ephemeral",
|
||||
"BTMC",
|
||||
"Cilvery",
|
||||
"m980",
|
||||
"HappyStick",
|
||||
"LittleEndu",
|
||||
"frenzibyte",
|
||||
"Zallius",
|
||||
"BanchoBot",
|
||||
"rocketminer210",
|
||||
"pishifat"
|
||||
};
|
||||
|
||||
private readonly APIUser streamingUser = new APIUser { Id = 2, Username = "Test user" };
|
||||
|
||||
private TestSpectatorClient spectatorClient = null!;
|
||||
@@ -36,11 +54,34 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
spectatorClient = new TestSpectatorClient();
|
||||
metadataClient = new TestMetadataClient();
|
||||
var lookupCache = new TestUserLookupCache();
|
||||
|
||||
((DummyAPIAccess)API).HandleRequest = req =>
|
||||
{
|
||||
switch (req)
|
||||
{
|
||||
case LookupUsersRequest lookupUsersRequest:
|
||||
var users = lookupUsersRequest.UserIds.Select(id =>
|
||||
{
|
||||
// tests against failed lookups
|
||||
if (id == 13)
|
||||
return null;
|
||||
|
||||
return new APIUser
|
||||
{
|
||||
Id = id,
|
||||
Username = usernames[id % usernames.Length],
|
||||
};
|
||||
}).ToList();
|
||||
lookupUsersRequest.TriggerSuccess(new GetUsersResponse { Users = users });
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
lookupCache,
|
||||
spectatorClient,
|
||||
metadataClient,
|
||||
new DependencyProvidingContainer
|
||||
@@ -50,13 +91,9 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
(typeof(SpectatorClient), spectatorClient),
|
||||
(typeof(MetadataClient), metadataClient),
|
||||
(typeof(UserLookupCache), lookupCache),
|
||||
(typeof(OverlayColourProvider), new OverlayColourProvider(OverlayColourScheme.Purple)),
|
||||
},
|
||||
Child = currentlyOnline = new CurrentlyOnlineDisplay
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
}
|
||||
Child = currentlyOnline = new CurrentlyOnlineDisplay()
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -69,17 +106,18 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
AddStep("Begin watching user presence", () => token = metadataClient.BeginWatchingUserPresence());
|
||||
AddStep("Add online user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.ChoosingBeatmap() }));
|
||||
AddUntilStep("Panel loaded", () => currentlyOnline.ChildrenOfType<UserGridPanel>().FirstOrDefault()?.User.Id == 2);
|
||||
AddAssert("Spectate button disabled", () => currentlyOnline.ChildrenOfType<PurpleRoundedButton>().First().Enabled.Value, () => Is.False);
|
||||
AddUntilStep("Panel loaded", () => currentlyOnline.ChildrenOfType<OnlineUserPanel>().FirstOrDefault()?.User.Id == 2);
|
||||
AddAssert("Spectate button disabled", () => currentlyOnline.ChildrenOfType<OnlineUserPanel>().First().CanSpectate.Value, () => Is.False);
|
||||
|
||||
AddStep("User began playing", () => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.InSoloGame() }));
|
||||
AddAssert("Spectate button enabled", () => currentlyOnline.ChildrenOfType<PurpleRoundedButton>().First().Enabled.Value, () => Is.True);
|
||||
AddAssert("Spectate button enabled", () => currentlyOnline.ChildrenOfType<OnlineUserPanel>().First().CanSpectate.Value, () => Is.True);
|
||||
|
||||
AddStep("User finished playing", () => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.ChoosingBeatmap() }));
|
||||
AddAssert("Spectate button disabled", () => currentlyOnline.ChildrenOfType<PurpleRoundedButton>().First().Enabled.Value, () => Is.False);
|
||||
AddStep("User finished playing",
|
||||
() => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.ChoosingBeatmap() }));
|
||||
AddAssert("Spectate button disabled", () => currentlyOnline.ChildrenOfType<OnlineUserPanel>().First().CanSpectate.Value, () => Is.False);
|
||||
|
||||
AddStep("Remove playing user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, null));
|
||||
AddUntilStep("Panel no longer present", () => !currentlyOnline.ChildrenOfType<UserGridPanel>().Any());
|
||||
AddUntilStep("Panel no longer present", () => !currentlyOnline.ChildrenOfType<OnlineUserPanel>().Any());
|
||||
AddStep("End watching user presence", () => token.Dispose());
|
||||
}
|
||||
|
||||
@@ -90,49 +128,14 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
AddStep("Begin watching user presence", () => token = metadataClient.BeginWatchingUserPresence());
|
||||
AddStep("Add online user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.InSoloGame() }));
|
||||
AddUntilStep("Panel loaded", () => currentlyOnline.ChildrenOfType<UserGridPanel>().FirstOrDefault()?.User.Id == streamingUser.Id);
|
||||
AddAssert("Spectate button enabled", () => currentlyOnline.ChildrenOfType<PurpleRoundedButton>().First().Enabled.Value, () => Is.True);
|
||||
AddUntilStep("Panel loaded", () => currentlyOnline.ChildrenOfType<OnlineUserPanel>().FirstOrDefault()?.User.Id == streamingUser.Id);
|
||||
AddAssert("Spectate button enabled", () => currentlyOnline.ChildrenOfType<OnlineUserPanel>().First().CanSpectate.Value, () => Is.True);
|
||||
|
||||
AddStep("User finished playing", () => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.ChoosingBeatmap() }));
|
||||
AddAssert("Spectate button disabled", () => currentlyOnline.ChildrenOfType<PurpleRoundedButton>().First().Enabled.Value, () => Is.False);
|
||||
AddStep("User finished playing",
|
||||
() => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.ChoosingBeatmap() }));
|
||||
AddAssert("Spectate button disabled", () => currentlyOnline.ChildrenOfType<OnlineUserPanel>().First().CanSpectate.Value, () => Is.False);
|
||||
AddStep("Remove playing user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, null));
|
||||
AddStep("End watching user presence", () => token.Dispose());
|
||||
}
|
||||
|
||||
internal partial class TestUserLookupCache : UserLookupCache
|
||||
{
|
||||
private static readonly string[] usernames =
|
||||
{
|
||||
"fieryrage",
|
||||
"Kerensa",
|
||||
"MillhioreF",
|
||||
"Player01",
|
||||
"smoogipoo",
|
||||
"Ephemeral",
|
||||
"BTMC",
|
||||
"Cilvery",
|
||||
"m980",
|
||||
"HappyStick",
|
||||
"LittleEndu",
|
||||
"frenzibyte",
|
||||
"Zallius",
|
||||
"BanchoBot",
|
||||
"rocketminer210",
|
||||
"pishifat"
|
||||
};
|
||||
|
||||
protected override Task<APIUser?> ComputeValueAsync(int lookup, CancellationToken token = default)
|
||||
{
|
||||
// tests against failed lookups
|
||||
if (lookup == 13)
|
||||
return Task.FromResult<APIUser?>(null);
|
||||
|
||||
return Task.FromResult<APIUser?>(new APIUser
|
||||
{
|
||||
Id = lookup,
|
||||
Username = usernames[lookup % usernames.Length],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Metadata;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Dashboard.CurrentlyOnline;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Tests.Visual.Metadata;
|
||||
using osu.Game.Users;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class TestSceneOnlineUserPanel : OsuTestScene
|
||||
{
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
||||
|
||||
[Resolved]
|
||||
private IRulesetStore rulesetStore { get; set; } = null!;
|
||||
|
||||
private TestMetadataClient metadataClient = null!;
|
||||
private OnlineUserListPanel panel = null!;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
Child = new DependencyProvidingContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
CachedDependencies =
|
||||
[
|
||||
(typeof(MetadataClient), metadataClient = new TestMetadataClient())
|
||||
],
|
||||
Children = new Drawable[]
|
||||
{
|
||||
metadataClient,
|
||||
new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Spacing = new Vector2(10f),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OnlineUserGridPanel(new APIUser
|
||||
{
|
||||
Username = @"flyte",
|
||||
Id = 3103765,
|
||||
CountryCode = CountryCode.JP,
|
||||
CoverUrl = @"https://assets.ppy.sh/user-cover-presets/1/df28696b58541a9e67f6755918951d542d93bdf1da41720fcca2fd2c1ea8cf51.jpeg",
|
||||
WasRecentlyOnline = true
|
||||
}),
|
||||
new OnlineUserGridPanel(new APIUser
|
||||
{
|
||||
Username = @"peppy",
|
||||
Id = 2,
|
||||
CountryCode = CountryCode.AU,
|
||||
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/8195163/4a8e2ad5a02a2642b631438cfa6c6bd7e2f9db289be881cb27df18331f64144c.jpeg",
|
||||
IsSupporter = true,
|
||||
SupportLevel = 3,
|
||||
}),
|
||||
new OnlineUserListPanel(new APIUser
|
||||
{
|
||||
Username = @"flyte",
|
||||
Id = 3103765,
|
||||
CountryCode = CountryCode.JP,
|
||||
CoverUrl = @"https://assets.ppy.sh/user-cover-presets/1/df28696b58541a9e67f6755918951d542d93bdf1da41720fcca2fd2c1ea8cf51.jpeg",
|
||||
WasRecentlyOnline = true
|
||||
}),
|
||||
panel = new OnlineUserListPanel(new APIUser
|
||||
{
|
||||
Username = @"peppy",
|
||||
Id = 2,
|
||||
CountryCode = CountryCode.AU,
|
||||
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/8195163/4a8e2ad5a02a2642b631438cfa6c6bd7e2f9db289be881cb27df18331f64144c.jpeg",
|
||||
LastVisit = DateTimeOffset.Now
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
metadataClient.BeginWatchingUserPresence();
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestUserActivity()
|
||||
{
|
||||
AddStep("idle", () => setPresence(UserStatus.Online, null));
|
||||
AddStep("in game", () => setPresence(UserStatus.Online, new UserActivity.InSoloGame(new BeatmapInfo(), rulesetStore.GetRuleset(0)!)));
|
||||
}
|
||||
|
||||
private void setPresence(UserStatus status, UserActivity? activity, int? userId = null)
|
||||
{
|
||||
if (status == UserStatus.Offline)
|
||||
metadataClient.UserPresenceUpdated(userId ?? panel.User.OnlineID, null);
|
||||
else
|
||||
metadataClient.UserPresenceUpdated(userId ?? panel.User.OnlineID, new UserPresence { Status = status, Activity = activity });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// 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.Online.API.Requests.Responses;
|
||||
using osu.Game.Users.Drawables;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class TestSceneUpdateableTeamFlag : OsuTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestHideOnNull()
|
||||
{
|
||||
UpdateableTeamFlag flag = null!;
|
||||
|
||||
AddStep("create flag with team", () => Child = flag = new UpdateableTeamFlag(createTeam(), hideOnNull: true) { Width = 300, Height = 150 });
|
||||
AddAssert("flag is present", () => flag.IsPresent, () => Is.True);
|
||||
AddStep("set team to null", () => flag.Team = null);
|
||||
AddAssert("flag is not present", () => flag.IsPresent, () => Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DontHideOnNull()
|
||||
{
|
||||
UpdateableTeamFlag flag = null!;
|
||||
|
||||
AddStep("create flag with team", () => Child = flag = new UpdateableTeamFlag(createTeam(), hideOnNull: false) { Width = 300, Height = 150 });
|
||||
AddAssert("flag is present", () => flag.IsPresent, () => Is.True);
|
||||
AddStep("set team to null", () => flag.Team = null);
|
||||
AddAssert("flag is present", () => flag.IsPresent, () => Is.True);
|
||||
}
|
||||
|
||||
private static APITeam createTeam() => new APITeam
|
||||
{
|
||||
Id = 2,
|
||||
Name = "mom?",
|
||||
ShortName = "MOM",
|
||||
FlagUrl = @"https://assets.ppy.sh/teams/flag/1/b46fb10dbfd8a35dc50e6c00296c0dc6172dffc3ed3d3a4b379277ba498399fe.png",
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -240,6 +240,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
CoverUrl = TestResources.COVER_IMAGE_1,
|
||||
JoinDate = DateTimeOffset.Now.AddDays(-1),
|
||||
LastVisit = DateTimeOffset.Now,
|
||||
PreviousUsernames = ["ForgetMe", "MySpaceLover", "i once was a man named enis", "mr anderson"],
|
||||
Groups = new[]
|
||||
{
|
||||
new APIUserGroup { Colour = "#EB47D0", ShortName = "DEV", Name = "Developers" },
|
||||
|
||||
@@ -11,6 +11,7 @@ using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Settings.Sections.Audio;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Tests.Visual.Ranking;
|
||||
@@ -25,6 +26,9 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
[Cached]
|
||||
private SessionAverageHitErrorTracker tracker = new SessionAverageHitErrorTracker();
|
||||
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
||||
|
||||
private Container content = null!;
|
||||
protected override Container Content => content;
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Overlays.Settings.Sections.Input;
|
||||
using osu.Game.Rulesets.Taiko;
|
||||
using osuTK.Input;
|
||||
@@ -70,6 +71,15 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
checkBinding("Increase volume", "Shift");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRulesetBindingSingleModifier()
|
||||
{
|
||||
scrollToAndStartBinding("Left button");
|
||||
AddStep("press left shift", () => InputManager.Key(Key.ShiftLeft));
|
||||
AddStep("release left shift", () => InputManager.ReleaseKey(Key.ShiftLeft));
|
||||
checkBinding("Left button", "LShift");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBindingSingleKeyWithModifier()
|
||||
{
|
||||
@@ -202,16 +212,16 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
InputManager.ReleaseKey(Key.P);
|
||||
});
|
||||
|
||||
AddUntilStep("restore button shown", () => settingsKeyBindingRow.ChildrenOfType<RevertToDefaultButton<bool>>().First().Alpha > 0);
|
||||
AddUntilStep("restore button shown", () => settingsKeyBindingRow.ChildrenOfType<SettingsRevertToDefaultButton>().First().Alpha > 0);
|
||||
|
||||
AddStep("click reset button for bindings", () =>
|
||||
{
|
||||
var resetButton = settingsKeyBindingRow.ChildrenOfType<RevertToDefaultButton<bool>>().First();
|
||||
var resetButton = settingsKeyBindingRow.ChildrenOfType<SettingsRevertToDefaultButton>().First();
|
||||
|
||||
resetButton.TriggerClick();
|
||||
});
|
||||
|
||||
AddUntilStep("restore button hidden", () => settingsKeyBindingRow.ChildrenOfType<RevertToDefaultButton<bool>>().First().Alpha == 0);
|
||||
AddUntilStep("restore button hidden", () => settingsKeyBindingRow.ChildrenOfType<SettingsRevertToDefaultButton>().First().Alpha == 0);
|
||||
|
||||
AddAssert("binding cleared",
|
||||
() => settingsKeyBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().ElementAt(0).KeyBinding.Value.KeyCombination.Equals(settingsKeyBindingRow.Defaults.ElementAt(0)));
|
||||
@@ -232,7 +242,7 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
InputManager.ReleaseKey(Key.P);
|
||||
});
|
||||
|
||||
AddUntilStep("restore button shown", () => settingsKeyBindingRow.ChildrenOfType<RevertToDefaultButton<bool>>().First().Alpha > 0);
|
||||
AddUntilStep("restore button shown", () => settingsKeyBindingRow.ChildrenOfType<SettingsRevertToDefaultButton>().First().Alpha > 0);
|
||||
|
||||
AddStep("click reset button for bindings", () =>
|
||||
{
|
||||
@@ -241,7 +251,7 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
resetButton.TriggerClick();
|
||||
});
|
||||
|
||||
AddUntilStep("restore button hidden", () => settingsKeyBindingRow.ChildrenOfType<RevertToDefaultButton<bool>>().First().Alpha == 0);
|
||||
AddUntilStep("restore button hidden", () => settingsKeyBindingRow.ChildrenOfType<SettingsRevertToDefaultButton>().First().Alpha == 0);
|
||||
|
||||
AddAssert("binding cleared",
|
||||
() => settingsKeyBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().ElementAt(0).KeyBinding.Value.KeyCombination.Equals(settingsKeyBindingRow.Defaults.ElementAt(0)));
|
||||
@@ -394,7 +404,7 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
AddStep("reset Left (centre) to default", () =>
|
||||
{
|
||||
var row = panel.ChildrenOfType<KeyBindingRow>().First(r => r.ChildrenOfType<OsuSpriteText>().Any(s => s.Text.ToString() == "Left (centre)"));
|
||||
row.ChildrenOfType<RevertToDefaultButton<bool>>().Single().TriggerClick();
|
||||
row.ChildrenOfType<SettingsRevertToDefaultButton>().Single().TriggerClick();
|
||||
});
|
||||
|
||||
KeyBindingConflictPopover popover = null;
|
||||
@@ -450,7 +460,7 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
AddStep("revert row to default", () =>
|
||||
{
|
||||
var row = panel.ChildrenOfType<KeyBindingRow>().First(r => r.ChildrenOfType<OsuSpriteText>().Any(s => s.Text.ToString() == "Left (centre)"));
|
||||
InputManager.MoveMouseTo(row.ChildrenOfType<RevertToDefaultButton<bool>>().Single());
|
||||
InputManager.MoveMouseTo(row.ChildrenOfType<SettingsRevertToDefaultButton>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddWaitStep("wait a bit", 3);
|
||||
|
||||
@@ -10,6 +10,7 @@ using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Overlays.Settings.Sections.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Settings
|
||||
@@ -45,7 +46,7 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
row.KeyBindings.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.Escape)));
|
||||
row.KeyBindings.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.ExtraMouseButton1)));
|
||||
});
|
||||
AddUntilStep("revert to default button not shown", () => row.ChildrenOfType<RevertToDefaultButton<bool>>().Single().Alpha, () => Is.Zero);
|
||||
AddUntilStep("revert to default button not shown", () => row.ChildrenOfType<SettingsRevertToDefaultButton>().Single().Alpha, () => Is.Zero);
|
||||
|
||||
AddStep("change key bindings", () =>
|
||||
{
|
||||
@@ -54,7 +55,7 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
row.KeyBindings.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.Z)));
|
||||
row.KeyBindings.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.I)));
|
||||
});
|
||||
AddUntilStep("revert to default button not shown", () => row.ChildrenOfType<RevertToDefaultButton<bool>>().Single().Alpha, () => Is.Not.Zero);
|
||||
AddUntilStep("revert to default button not shown", () => row.ChildrenOfType<SettingsRevertToDefaultButton>().Single().Alpha, () => Is.Not.Zero);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Containers;
|
||||
@@ -31,7 +32,6 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
||||
|
||||
private FormSliderBar<float> sliderBar = null!;
|
||||
private FormSliderBar<float> classicSliderBar = null!;
|
||||
|
||||
private SearchContainer searchContainer = null!;
|
||||
|
||||
@@ -173,7 +173,7 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
{
|
||||
ShowRevertToDefaultButton = false
|
||||
},
|
||||
new SettingsItemV2(classicSliderBar = new FormSliderBar<float>
|
||||
new SettingsItemV2(new FormSliderBar<float>
|
||||
{
|
||||
Caption = "Slider with classic default",
|
||||
Current = new BindableFloat
|
||||
@@ -185,7 +185,7 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
},
|
||||
})
|
||||
{
|
||||
ApplyClassicDefault = () => classicSliderBar.Current.Value = 2,
|
||||
ApplyClassicDefault = c => ((IHasCurrentValue<float>)c).Current.Value = 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -8,6 +8,7 @@ using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Handlers;
|
||||
using osu.Framework.Input.Handlers.Tablet;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
@@ -69,7 +70,7 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
{
|
||||
AddStep("Test with wide tablet", () => tabletHandler.SetTabletSize(new Vector2(160, 100)));
|
||||
|
||||
AddStep("Reset to full area", () => settings.ChildrenOfType<DangerousSettingsButton>().First().TriggerClick());
|
||||
AddStep("Reset to full area", () => settings.ChildrenOfType<DangerousSettingsButtonV2>().First().TriggerClick());
|
||||
ensureValid();
|
||||
|
||||
AddStep("rotate 10", () => tabletHandler.Rotation.Value = 10);
|
||||
@@ -129,7 +130,7 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
|
||||
private void ensureInvalid() => AddAssert("area invalid", () => !settings.AreaSelection.IsWithinBounds);
|
||||
|
||||
public class TestTabletHandler : ITabletHandler
|
||||
public class TestTabletHandler : InputHandler, ITabletHandler
|
||||
{
|
||||
public Bindable<Vector2> AreaOffset { get; } = new Bindable<Vector2>();
|
||||
public Bindable<Vector2> AreaSize { get; } = new Bindable<Vector2>();
|
||||
@@ -149,7 +150,7 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
|
||||
private readonly Bindable<TabletInfo> tablet = new Bindable<TabletInfo>();
|
||||
|
||||
public BindableBool Enabled { get; } = new BindableBool(true);
|
||||
public override bool IsActive => true;
|
||||
|
||||
public void SetTabletSize(Vector2 size)
|
||||
{
|
||||
|
||||
@@ -44,6 +44,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
|
||||
protected TestBeatmapCarousel Carousel = null!;
|
||||
|
||||
protected bool RetainSelection { get; set; }
|
||||
|
||||
protected OsuScrollContainer<Drawable> Scroll => Carousel.ChildrenOfType<OsuScrollContainer<Drawable>>().Single();
|
||||
|
||||
[Cached(typeof(BeatmapStore))]
|
||||
@@ -78,7 +80,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
Dependencies.Cache(Realm);
|
||||
}
|
||||
|
||||
protected void CreateCarousel()
|
||||
protected void CreateCarousel(bool retainSelection = false)
|
||||
{
|
||||
AddStep("create components", () =>
|
||||
{
|
||||
@@ -87,6 +89,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
BeatmapRecommendationFunction = null;
|
||||
NewItemsPresentedInvocationCount = 0;
|
||||
|
||||
GroupedBeatmap? previousSelection = retainSelection ? Carousel.CurrentGroupedBeatmap : null;
|
||||
|
||||
Box topBox;
|
||||
Children = new Drawable[]
|
||||
{
|
||||
@@ -120,6 +124,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
Carousel = new TestBeatmapCarousel
|
||||
{
|
||||
CurrentGroupedBeatmap = previousSelection,
|
||||
NewItemsPresented = _ => NewItemsPresentedInvocationCount++,
|
||||
RequestSelection = b =>
|
||||
{
|
||||
@@ -222,6 +227,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
protected void SelectPrevPanel() => AddStep("select prev panel", () => InputManager.Key(Key.Up));
|
||||
protected void SelectNextSet() => AddStep("select next set", () => InputManager.Key(Key.Right));
|
||||
protected void SelectPrevSet() => AddStep("select prev set", () => InputManager.Key(Key.Left));
|
||||
protected void SelectRandomSet() => AddStep("select random set", () => Carousel.NextRandom());
|
||||
|
||||
protected void Select() => AddStep("select", () => InputManager.Key(Key.Enter));
|
||||
|
||||
|
||||
@@ -253,6 +253,54 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
CheckDisplayedBeatmapsCount(30);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestGroupDoesExpandAfterRandomTraversal()
|
||||
{
|
||||
SelectNextSet();
|
||||
|
||||
ToggleGroupCollapse();
|
||||
AddAssert("group not expanded", () => Carousel.ExpandedGroup, () => Is.Null);
|
||||
|
||||
SelectRandomSet();
|
||||
|
||||
AddAssert("group expanded", () => Carousel.ExpandedGroup, () => Is.Not.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFilterWhileCollapsedUpdatesVisualStateCorrectly()
|
||||
{
|
||||
SelectNextSet();
|
||||
|
||||
CheckHasSelection();
|
||||
AddAssert("group expanded", () => Carousel.ExpandedGroup, () => Is.Not.Null);
|
||||
AddAssert("has expanded set", () => Carousel.ExpandedBeatmapSet != null);
|
||||
|
||||
AddAssert("has visible beatmaps", () => Carousel.GetCarouselItems()!.Count(item => item.Model is GroupedBeatmap && item.IsVisible), () => Is.EqualTo(3));
|
||||
AddAssert("has visually expanded set", () => Carousel.GetCarouselItems()!.Count(item => item.Model is GroupedBeatmapSet && item.IsExpanded && item.IsVisible), () => Is.EqualTo(1));
|
||||
|
||||
ToggleGroupCollapse();
|
||||
|
||||
CheckHasSelection();
|
||||
AddAssert("group not expanded", () => Carousel.ExpandedGroup, () => Is.Null);
|
||||
AddAssert("has expanded set", () => Carousel.ExpandedBeatmapSet != null);
|
||||
|
||||
AddAssert("has no visible beatmaps", () => Carousel.GetCarouselItems()!.Count(item => item.Model is GroupedBeatmap && item.IsVisible), () => Is.Zero);
|
||||
AddAssert("has no visually expanded set", () => Carousel.GetCarouselItems()!.Count(item => item.Model is GroupedBeatmapSet && item.IsExpanded && item.IsVisible), () => Is.Zero);
|
||||
|
||||
// filter while collapsed.
|
||||
ApplyToFilterAndWaitForFilter("filter", c => c.SearchText = Carousel.SelectedBeatmapSet!.Metadata.Title);
|
||||
|
||||
// then expand.
|
||||
ToggleGroupCollapse();
|
||||
|
||||
CheckHasSelection();
|
||||
AddAssert("group expanded", () => Carousel.ExpandedGroup, () => Is.Not.Null);
|
||||
AddAssert("has expanded set", () => Carousel.ExpandedBeatmapSet != null);
|
||||
|
||||
AddAssert("has visible beatmaps", () => Carousel.GetCarouselItems()!.Count(item => item.Model is GroupedBeatmap && item.IsVisible), () => Is.EqualTo(3));
|
||||
AddAssert("has visually expanded set", () => Carousel.GetCarouselItems()!.Count(item => item.Model is GroupedBeatmapSet && item.IsExpanded && item.IsVisible), () => Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestGroupDoesNotExpandAgainOnRefilterIfManuallyCollapsed()
|
||||
{
|
||||
|
||||
@@ -23,6 +23,33 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
SortAndGroupBy(SortMode.Title, GroupMode.Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestInitialVisualState()
|
||||
{
|
||||
AddBeatmaps(3, splitApart: true);
|
||||
|
||||
WaitForDrawablePanels();
|
||||
SelectNextSet();
|
||||
WaitForSetSelection(set: 0, diff: 0);
|
||||
|
||||
AddAssert("selected item is visible", () => GetSelectedPanel()?.Item?.IsVisible, () => Is.True);
|
||||
AddAssert("has visually expanded set", () => Carousel.GetCarouselItems()!.Count(item => item.Model is GroupedBeatmapSet && item.IsExpanded && item.IsVisible), () => Is.EqualTo(1));
|
||||
|
||||
CreateCarousel(retainSelection: true);
|
||||
WaitForDrawablePanels();
|
||||
WaitForSetSelection(set: 0, diff: 0);
|
||||
|
||||
AddAssert("selected item is visible", () => GetSelectedPanel()?.Item?.IsVisible, () => Is.True);
|
||||
AddAssert("has visually expanded set", () => Carousel.GetCarouselItems()!.Count(item => item.Model is GroupedBeatmapSet && item.IsExpanded && item.IsVisible), () => Is.EqualTo(1));
|
||||
|
||||
CreateCarousel(retainSelection: true);
|
||||
WaitForDrawablePanels();
|
||||
WaitForSetSelection(set: 0, diff: 0);
|
||||
|
||||
AddAssert("selected item is visible", () => GetSelectedPanel()?.Item?.IsVisible, () => Is.True);
|
||||
AddAssert("has visually expanded set", () => Carousel.GetCarouselItems()!.Count(item => item.Model is GroupedBeatmapSet && item.IsExpanded && item.IsVisible), () => Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSetTraversal()
|
||||
{
|
||||
|
||||
@@ -7,8 +7,10 @@ using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Models;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
@@ -53,8 +55,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
|
||||
working.Metadata.Source = string.Empty;
|
||||
|
||||
onlineLookupResult.Value = online;
|
||||
Beatmap.Value = working;
|
||||
onlineLookupResult.Value = online;
|
||||
});
|
||||
AddStep("no success rate", () =>
|
||||
{
|
||||
@@ -63,8 +65,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
online.Result!.Beatmaps.Single().PlayCount = 0;
|
||||
online.Result!.Beatmaps.Single().PassCount = 0;
|
||||
|
||||
onlineLookupResult.Value = online;
|
||||
Beatmap.Value = working;
|
||||
onlineLookupResult.Value = online;
|
||||
});
|
||||
AddStep("no user ratings", () =>
|
||||
{
|
||||
@@ -72,8 +74,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
|
||||
online.Result!.Ratings = Array.Empty<int>();
|
||||
|
||||
onlineLookupResult.Value = online;
|
||||
Beatmap.Value = working;
|
||||
onlineLookupResult.Value = online;
|
||||
});
|
||||
AddStep("no fail times", () =>
|
||||
{
|
||||
@@ -81,8 +83,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
|
||||
online.Result!.Beatmaps.Single().FailTimes = null;
|
||||
|
||||
onlineLookupResult.Value = online;
|
||||
Beatmap.Value = working;
|
||||
onlineLookupResult.Value = online;
|
||||
});
|
||||
AddStep("no metrics", () =>
|
||||
{
|
||||
@@ -91,8 +93,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
online.Result!.Ratings = Array.Empty<int>();
|
||||
online.Result!.Beatmaps.Single().FailTimes = null;
|
||||
|
||||
onlineLookupResult.Value = online;
|
||||
Beatmap.Value = working;
|
||||
onlineLookupResult.Value = online;
|
||||
});
|
||||
AddStep("local beatmap", () =>
|
||||
{
|
||||
@@ -100,8 +102,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
|
||||
working.BeatmapInfo.OnlineID = 0;
|
||||
|
||||
onlineLookupResult.Value = null;
|
||||
Beatmap.Value = working;
|
||||
onlineLookupResult.Value = Screens.SelectV2.SongSelect.BeatmapSetLookupResult.Completed(null);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -119,8 +121,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
online.Result!.Language = new BeatmapSetOnlineLanguage { Id = 12, Name = "Verrrrryyyy llooonngggggg language" };
|
||||
online.Result!.Beatmaps.Single().TopTags = Enumerable.Repeat(online.Result!.Beatmaps.Single().TopTags, 3).SelectMany(t => t!).ToArray();
|
||||
|
||||
onlineLookupResult.Value = online;
|
||||
Beatmap.Value = working;
|
||||
onlineLookupResult.Value = online;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -137,20 +139,28 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
|
||||
working.BeatmapInfo.ResetOnlineInfo();
|
||||
|
||||
onlineLookupResult.Value = lookupResult;
|
||||
Beatmap.Value = working;
|
||||
onlineLookupResult.Value = lookupResult;
|
||||
});
|
||||
AddUntilStep("rating wedge hidden", () => !wedge.RatingsVisible);
|
||||
AddUntilStep("fail time wedge hidden", () => !wedge.FailRetryVisible);
|
||||
|
||||
// just check for text everywhere on the wedge as the classes are private and generic
|
||||
AddAssert("genre is still visible", () => wedge.ChildrenOfType<OsuSpriteText>().Any(t => t.Text == "Pop"));
|
||||
AddAssert("language is still visible", () => wedge.ChildrenOfType<OsuSpriteText>().Any(t => t.Text == "English"));
|
||||
|
||||
AddStep("local beatmap", () =>
|
||||
{
|
||||
var (working, _) = createTestBeatmap();
|
||||
|
||||
onlineLookupResult.Value = null;
|
||||
Beatmap.Value = working;
|
||||
onlineLookupResult.Value = Screens.SelectV2.SongSelect.BeatmapSetLookupResult.Completed(null);
|
||||
});
|
||||
AddAssert("rating wedge still hidden", () => !wedge.RatingsVisible);
|
||||
AddAssert("fail time wedge still hidden", () => !wedge.FailRetryVisible);
|
||||
|
||||
AddAssert("genre is cleared", () => wedge.ChildrenOfType<OsuSpriteText>().All(t => t.Text != "Pop"));
|
||||
AddAssert("language is cleared", () => wedge.ChildrenOfType<OsuSpriteText>().All(t => t.Text != "English"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -166,8 +176,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
online.Result!.RelatedTags = null;
|
||||
working.BeatmapSetInfo.Beatmaps.Single().Metadata.UserTags.Clear();
|
||||
|
||||
onlineLookupResult.Value = online;
|
||||
Beatmap.Value = working;
|
||||
onlineLookupResult.Value = online;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -178,9 +188,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
var (working, online) = createTestBeatmap();
|
||||
|
||||
Beatmap.Value = working;
|
||||
onlineLookupResult.Value = Screens.SelectV2.SongSelect.BeatmapSetLookupResult.InProgress();
|
||||
Scheduler.AddDelayed(() => onlineLookupResult.Value = online, 500);
|
||||
Beatmap.Value = working;
|
||||
});
|
||||
AddWaitStep("wait", 5);
|
||||
|
||||
@@ -192,9 +202,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
online.Result!.RelatedTags[1].Name = "another/tag";
|
||||
online.Result!.RelatedTags[2].Name = "some/tag";
|
||||
|
||||
Beatmap.Value = working;
|
||||
onlineLookupResult.Value = Screens.SelectV2.SongSelect.BeatmapSetLookupResult.InProgress();
|
||||
Scheduler.AddDelayed(() => onlineLookupResult.Value = online, 500);
|
||||
Beatmap.Value = working;
|
||||
});
|
||||
AddWaitStep("wait", 5);
|
||||
|
||||
@@ -206,9 +216,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
online.Result!.RelatedTags = null;
|
||||
working.BeatmapSetInfo.Beatmaps.Single().Metadata.UserTags.Clear();
|
||||
|
||||
Beatmap.Value = working;
|
||||
onlineLookupResult.Value = Screens.SelectV2.SongSelect.BeatmapSetLookupResult.InProgress();
|
||||
Scheduler.AddDelayed(() => onlineLookupResult.Value = online, 500);
|
||||
Beatmap.Value = working;
|
||||
});
|
||||
AddWaitStep("wait", 5);
|
||||
|
||||
@@ -220,9 +230,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
online.Result!.RelatedTags = null;
|
||||
working.BeatmapSetInfo.Beatmaps.Single().Metadata.UserTags.Clear();
|
||||
|
||||
Beatmap.Value = working;
|
||||
onlineLookupResult.Value = Screens.SelectV2.SongSelect.BeatmapSetLookupResult.InProgress();
|
||||
Scheduler.AddDelayed(() => onlineLookupResult.Value = online, 500);
|
||||
Beatmap.Value = working;
|
||||
});
|
||||
AddWaitStep("wait", 5);
|
||||
}
|
||||
|
||||
@@ -197,15 +197,15 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
AddUntilStep("favourites count is 2345", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().Text.ToString(), () => Is.EqualTo("2,345"));
|
||||
|
||||
AddStep("click favourite button", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().TriggerClick());
|
||||
AddStep("allow request to complete", () => resetEvent.Set());
|
||||
AddStep("allow request to complete", resetEvent.Set);
|
||||
AddUntilStep("favourites count is 2346", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().Text.ToString(), () => Is.EqualTo("2,346"));
|
||||
|
||||
AddStep("reset event", () => resetEvent.Reset());
|
||||
AddStep("reset event", resetEvent.Reset);
|
||||
AddStep("click favourite button", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().TriggerClick());
|
||||
AddStep("allow request to complete", () => resetEvent.Set());
|
||||
AddStep("allow request to complete", resetEvent.Set);
|
||||
AddUntilStep("favourites count is 2345", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().Text.ToString(), () => Is.EqualTo("2,345"));
|
||||
|
||||
AddStep("reset event", () => resetEvent.Reset());
|
||||
AddStep("reset event", resetEvent.Reset);
|
||||
AddStep("click favourite button", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().TriggerClick());
|
||||
AddStep("change to another beatmap", () =>
|
||||
{
|
||||
@@ -217,7 +217,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
Beatmap.Value = working;
|
||||
onlineLookupResult.Value = online;
|
||||
});
|
||||
AddStep("allow request to complete", () => resetEvent.Set());
|
||||
AddStep("allow request to complete", resetEvent.Set);
|
||||
AddUntilStep("favourites count is 9999", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().Text.ToString(), () => Is.EqualTo("9,999"));
|
||||
|
||||
AddStep("set up request handler to fail", () =>
|
||||
@@ -239,11 +239,11 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
}
|
||||
};
|
||||
});
|
||||
AddStep("reset event", () => resetEvent.Reset());
|
||||
AddStep("reset event", resetEvent.Reset);
|
||||
AddStep("click favourite button", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().TriggerClick());
|
||||
AddAssert("spinner visible", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single()
|
||||
.ChildrenOfType<LoadingSpinner>().Single().State.Value, () => Is.EqualTo(Visibility.Visible));
|
||||
AddStep("allow request to complete", () => resetEvent.Set());
|
||||
AddStep("allow request to complete", resetEvent.Set);
|
||||
AddAssert("spinner hidden", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single()
|
||||
.ChildrenOfType<LoadingSpinner>().Single().State.Value, () => Is.EqualTo(Visibility.Hidden));
|
||||
}
|
||||
@@ -295,7 +295,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
AddUntilStep($"displayed bpm is {target}", () =>
|
||||
{
|
||||
var label = titleWedge.ChildrenOfType<BeatmapTitleWedge.Statistic>().Single(l => l.TooltipText == BeatmapsetsStrings.ShowStatsBpm);
|
||||
return label.Text == target;
|
||||
return label.Text.ToString() == target;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -389,6 +389,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
SortBy(SortMode.Artist);
|
||||
checkMatchedBeatmaps(6);
|
||||
|
||||
AddUntilStep("wait for spread indicator", () => this.ChildrenOfType<PanelBeatmapSet.SpreadDisplay>().Any(d => d.Enabled.Value));
|
||||
AddStep("click spread indicator", () => this.ChildrenOfType<PanelBeatmapSet.SpreadDisplay>().Single(d => d.Enabled.Value).TriggerClick());
|
||||
WaitForFiltering();
|
||||
checkMatchedBeatmaps(3);
|
||||
@@ -412,6 +413,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
WaitForFiltering();
|
||||
checkMatchedBeatmaps(3);
|
||||
|
||||
AddUntilStep("wait for spread indicator", () => this.ChildrenOfType<PanelBeatmapSet.SpreadDisplay>().Any(d => d.Enabled.Value));
|
||||
AddStep("click spread indicator", () => this.ChildrenOfType<PanelBeatmapSet.SpreadDisplay>().Single(d => d.Enabled.Value).TriggerClick());
|
||||
WaitForFiltering();
|
||||
checkMatchedBeatmaps(3);
|
||||
|
||||
@@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
AddStep("Move cursor to button", () => InputManager.MoveMouseTo(settingsButton));
|
||||
AddAssert("Button is hovered", () => settingsButton.IsHovered);
|
||||
AddStep("Move cursor to padded area", () => InputManager.MoveMouseTo(settingsButton.ScreenSpaceDrawQuad.TopLeft + new Vector2(SettingsPanel.CONTENT_MARGINS / 2f, 10)));
|
||||
AddStep("Move cursor to padded area", () => InputManager.MoveMouseTo(settingsButton.ScreenSpaceDrawQuad.TopLeft + new Vector2(SettingsPanel.CONTENT_PADDING.Left / 2f, 10)));
|
||||
AddAssert("Cursor within a button", () => settingsButton.ScreenSpaceDrawQuad.Contains(InputManager.CurrentState.Mouse.Position));
|
||||
AddAssert("Button is not hovered", () => !settingsButton.IsHovered);
|
||||
}
|
||||
|
||||
@@ -169,6 +169,17 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
InputManager.Key(Key.Enter);
|
||||
});
|
||||
AddAssert("slider is still at 1", () => slider.Current.Value, () => Is.EqualTo(1));
|
||||
|
||||
AddStep("re-enable slider", () => slider.Current.Disabled = false);
|
||||
|
||||
AddStep("move mouse to nub", () => InputManager.MoveMouseTo(slider.ChildrenOfType<Circle>().Single()));
|
||||
|
||||
AddStep("double click nub", () =>
|
||||
{
|
||||
InputManager.Click(MouseButton.Left);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddAssert("slider is at 5", () => slider.Current.Value, () => Is.EqualTo(5));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -212,5 +223,133 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
});
|
||||
AddAssert("no text selected", () => slider.ChildrenOfType<FormTextBox.InnerTextBox>().Single().SelectedText, () => Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDisplayAsPercentageFloat()
|
||||
{
|
||||
OsuSpriteText text;
|
||||
FormSliderBar<float> slider = null!;
|
||||
|
||||
AddStep("create content", () =>
|
||||
{
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Width = 0.5f,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(10),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
text = new OsuSpriteText(),
|
||||
slider = new FormSliderBar<float>
|
||||
{
|
||||
Caption = "Slider",
|
||||
Current = new BindableFloat
|
||||
{
|
||||
MinValue = 0,
|
||||
MaxValue = 1,
|
||||
Precision = 0.01f,
|
||||
Default = 0.5f,
|
||||
Value = 0.5f,
|
||||
},
|
||||
DisplayAsPercentage = true,
|
||||
},
|
||||
}
|
||||
};
|
||||
slider.Current.BindValueChanged(_ => text.Text = $"Current value is: {slider.Current.Value}", true);
|
||||
});
|
||||
|
||||
AddStep("click on textbox part", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(slider.ChildrenOfType<FormTextBox.InnerTextBox>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddAssert("text selected", () => slider.ChildrenOfType<FormTextBox.InnerTextBox>().Single().SelectedText, () => Is.EqualTo("50"));
|
||||
AddStep("input 9%", () =>
|
||||
{
|
||||
slider.ChildrenOfType<FormTextBox.InnerTextBox>().Single().Text = "9";
|
||||
InputManager.Key(Key.Enter);
|
||||
});
|
||||
AddAssert("slider is at 0.09", () => slider.Current.Value, () => Is.EqualTo(0.09f));
|
||||
|
||||
AddStep("start dragging nub", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(slider.ChildrenOfType<FormSliderBar<float>.InnerSliderNub>().Single());
|
||||
InputManager.PressButton(MouseButton.Left);
|
||||
});
|
||||
AddStep("drag nub to 50%", () =>
|
||||
{
|
||||
var innerSlider = slider.ChildrenOfType<FormSliderBar<float>.InnerSlider>().Single();
|
||||
InputManager.MoveMouseTo((innerSlider.ScreenSpaceDrawQuad.TopLeft + innerSlider.ScreenSpaceDrawQuad.TopRight) / 2);
|
||||
InputManager.ReleaseButton(MouseButton.Left);
|
||||
});
|
||||
AddAssert("slider is at ~0.5", () => slider.Current.Value, () => Is.EqualTo(0.5).Within(0.01f));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDisplayAsPercentageInt()
|
||||
{
|
||||
OsuSpriteText text;
|
||||
FormSliderBar<int> slider = null!;
|
||||
|
||||
AddStep("create content", () =>
|
||||
{
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Width = 0.5f,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(10),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
text = new OsuSpriteText(),
|
||||
slider = new FormSliderBar<int>
|
||||
{
|
||||
Caption = "Slider",
|
||||
Current = new BindableInt
|
||||
{
|
||||
MinValue = 0,
|
||||
MaxValue = 100,
|
||||
Precision = 1,
|
||||
Default = 50,
|
||||
Value = 50,
|
||||
},
|
||||
DisplayAsPercentage = true,
|
||||
},
|
||||
}
|
||||
};
|
||||
slider.Current.BindValueChanged(_ => text.Text = $"Current value is: {slider.Current.Value}", true);
|
||||
});
|
||||
|
||||
AddStep("click on textbox part", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(slider.ChildrenOfType<FormTextBox.InnerTextBox>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddAssert("text selected", () => slider.ChildrenOfType<FormTextBox.InnerTextBox>().Single().SelectedText, () => Is.EqualTo("50"));
|
||||
AddStep("input 9%", () =>
|
||||
{
|
||||
slider.ChildrenOfType<FormTextBox.InnerTextBox>().Single().Text = "9";
|
||||
InputManager.Key(Key.Enter);
|
||||
});
|
||||
AddAssert("slider is at 9", () => slider.Current.Value, () => Is.EqualTo(9));
|
||||
|
||||
AddStep("start dragging nub", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(slider.ChildrenOfType<FormSliderBar<int>.InnerSliderNub>().Single());
|
||||
InputManager.PressButton(MouseButton.Left);
|
||||
});
|
||||
AddStep("drag nub to 50%", () =>
|
||||
{
|
||||
var innerSlider = slider.ChildrenOfType<FormSliderBar<int>.InnerSlider>().Single();
|
||||
InputManager.MoveMouseTo((innerSlider.ScreenSpaceDrawQuad.TopLeft + innerSlider.ScreenSpaceDrawQuad.TopRight) / 2);
|
||||
InputManager.ReleaseButton(MouseButton.Left);
|
||||
});
|
||||
AddAssert("slider is at ~50", () => slider.Current.Value, () => Is.EqualTo(50).Within(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,20 +40,20 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
firedText
|
||||
};
|
||||
|
||||
AddStep("start confirming", () => overlay.Begin());
|
||||
AddStep("abort confirming", () => overlay.Abort());
|
||||
AddStep("start confirming", overlay.Begin);
|
||||
AddStep("abort confirming", overlay.Abort);
|
||||
|
||||
AddAssert("ensure not fired internally", () => !overlay.Fired);
|
||||
AddAssert("ensure aborted", () => !fired);
|
||||
|
||||
AddStep("start confirming", () => overlay.Begin());
|
||||
AddStep("start confirming", overlay.Begin);
|
||||
|
||||
AddUntilStep("wait until confirmed", () => fired);
|
||||
AddAssert("ensure fired internally", () => overlay.Fired);
|
||||
|
||||
AddStep("abort after fire", () => overlay.Abort());
|
||||
AddStep("abort after fire", overlay.Abort);
|
||||
AddAssert("ensure not fired internally", () => !overlay.Fired);
|
||||
AddStep("start confirming", () => overlay.Begin());
|
||||
AddStep("start confirming", overlay.Begin);
|
||||
AddUntilStep("wait until fired again", () => overlay.Fired);
|
||||
}
|
||||
|
||||
|
||||
@@ -80,6 +80,35 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNumericHotkeys()
|
||||
{
|
||||
AddStep("set osu! ruleset", () => Ruleset.Value = rulesets.GetRuleset(0));
|
||||
AddStep("create content", () => Child = new ModPresetColumn
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
});
|
||||
|
||||
AddUntilStep("3 panels visible", () => this.ChildrenOfType<ModPresetPanel>().Count() == 3);
|
||||
|
||||
AddStep("select first preset", () => InputManager.Key(Key.Number1));
|
||||
AddAssert("first panel selected", () => this.ChildrenOfType<ModPresetPanel>().ElementAt(0).Active.Value);
|
||||
|
||||
AddAssert("selected mods match correct preset", () => SelectedMods.Value, () => Is.EquivalentTo(createTestPresets().ElementAt(1).Mods));
|
||||
|
||||
AddStep("select third preset", () => InputManager.Key(Key.Number3));
|
||||
AddAssert("first panel not selected", () => !this.ChildrenOfType<ModPresetPanel>().ElementAt(0).Active.Value);
|
||||
AddAssert("third panel selected", () => this.ChildrenOfType<ModPresetPanel>().ElementAt(2).Active.Value);
|
||||
|
||||
AddAssert("selected mods match correct preset", () => SelectedMods.Value, () => Is.EquivalentTo(createTestPresets().ElementAt(2).Mods));
|
||||
|
||||
AddStep("deselect third preset", () => InputManager.Key(Key.Number3));
|
||||
AddAssert("third panel not selected", () => !this.ChildrenOfType<ModPresetPanel>().ElementAt(2).Active.Value);
|
||||
|
||||
AddAssert("no selected mods", () => SelectedMods.Value.Count == 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBasicOperation()
|
||||
{
|
||||
|
||||
@@ -133,9 +133,9 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
|
||||
var loadedBackgrounds = backgrounds.Where(b => b.ContentLoaded);
|
||||
|
||||
AddUntilStep("some loaded", () => loadedBackgrounds.Any());
|
||||
AddUntilStep("some loaded", loadedBackgrounds.Any);
|
||||
AddStep("scroll to bottom", () => scrollContainer.ScrollToEnd());
|
||||
AddUntilStep("all unloaded", () => !loadedBackgrounds.Any());
|
||||
AddUntilStep("all unloaded", loadedBackgrounds.Any, () => Is.False);
|
||||
}
|
||||
|
||||
private partial class TestUpdateableBeatmapBackgroundSprite : UpdateableBeatmapBackgroundSprite
|
||||
|
||||
@@ -89,9 +89,9 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
|
||||
var loadedCovers = covers.Where(c => c.ChildrenOfType<OnlineBeatmapSetCover>().SingleOrDefault()?.IsLoaded ?? false);
|
||||
|
||||
AddUntilStep("some loaded", () => loadedCovers.Any());
|
||||
AddUntilStep("some loaded", loadedCovers.Any);
|
||||
AddStep("scroll to end", () => scroll.ScrollToEnd());
|
||||
AddUntilStep("all unloaded", () => !loadedCovers.Any());
|
||||
AddUntilStep("all unloaded", loadedCovers.Any, () => Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
OsuSpriteText sort;
|
||||
OsuSpriteText displayStyle;
|
||||
|
||||
Add(toolbar = new UserListToolbar
|
||||
Add(toolbar = new UserListToolbar(true)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
|
||||
@@ -98,7 +98,7 @@ namespace osu.Game.Tournament.Screens.Editors
|
||||
Width = 0.2f,
|
||||
Margin = new MarginPadding(10),
|
||||
Text = "Add beatmap",
|
||||
Action = () => beatmapEditor.CreateNew()
|
||||
Action = beatmapEditor.CreateNew
|
||||
},
|
||||
beatmapEditor
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ namespace osu.Game.Tournament.Screens.Editors
|
||||
Width = 0.2f,
|
||||
Margin = new MarginPadding(10),
|
||||
Text = "Add beatmap",
|
||||
Action = () => beatmapEditor.CreateNew()
|
||||
Action = beatmapEditor.CreateNew
|
||||
},
|
||||
beatmapEditor
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ namespace osu.Game.Tournament.Screens.Editors
|
||||
new SettingsButton
|
||||
{
|
||||
Text = "Add player",
|
||||
Action = () => playerEditor.CreateNew()
|
||||
Action = playerEditor.CreateNew
|
||||
},
|
||||
new Container
|
||||
{
|
||||
|
||||
@@ -75,6 +75,13 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
currentMods.BindValueChanged(mods =>
|
||||
{
|
||||
// A change in bindable here doesn't guarantee that mods have actually changed.
|
||||
// However, we *do* want to make sure that the mod *references* are the same;
|
||||
// `SequenceEqual()` without a comparer would fall back to `IEquatable`.
|
||||
// Failing to ensure reference equality can cause setting change tracking to fail later.
|
||||
if (mods.OldValue.SequenceEqual(mods.NewValue, ReferenceEqualityComparer.Instance))
|
||||
return;
|
||||
|
||||
modSettingChangeTracker?.Dispose();
|
||||
|
||||
Scheduler.AddOnce(updateTrackedBindables);
|
||||
@@ -82,15 +89,37 @@ namespace osu.Game.Beatmaps
|
||||
modSettingChangeTracker = new ModSettingChangeTracker(mods.NewValue);
|
||||
modSettingChangeTracker.SettingChanged += _ =>
|
||||
{
|
||||
debouncedModSettingsChange?.Cancel();
|
||||
debouncedModSettingsChange = Scheduler.AddDelayed(updateTrackedBindables, 100);
|
||||
lock (bindableUpdateLock)
|
||||
{
|
||||
debouncedModSettingsChange?.Cancel();
|
||||
debouncedModSettingsChange = Scheduler.AddDelayed(updateTrackedBindables, 100);
|
||||
}
|
||||
};
|
||||
}, true);
|
||||
}
|
||||
|
||||
public void Invalidate(IBeatmapInfo beatmap)
|
||||
/// <summary>
|
||||
/// Notify this cache that a beatmap has been invalidated/updated.
|
||||
/// </summary>
|
||||
/// <param name="oldBeatmap">The old beatmap model.</param>
|
||||
/// <param name="newBeatmap">The updated beatmap model.</param>
|
||||
public void Invalidate(IBeatmapInfo oldBeatmap, IBeatmapInfo newBeatmap)
|
||||
{
|
||||
base.Invalidate(lookup => lookup.BeatmapInfo.Equals(beatmap));
|
||||
base.Invalidate(lookup => lookup.BeatmapInfo.Equals(oldBeatmap));
|
||||
|
||||
lock (bindableUpdateLock)
|
||||
{
|
||||
bool trackedBindablesRefreshRequired = false;
|
||||
|
||||
foreach (var bsd in trackedBindables.Where(bsd => bsd.BeatmapInfo.Equals(oldBeatmap)))
|
||||
{
|
||||
bsd.BeatmapInfo = newBeatmap;
|
||||
trackedBindablesRefreshRequired = true;
|
||||
}
|
||||
|
||||
if (trackedBindablesRefreshRequired)
|
||||
Scheduler.AddOnce(updateTrackedBindables);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -195,6 +224,9 @@ namespace osu.Game.Beatmaps
|
||||
{
|
||||
lock (bindableUpdateLock)
|
||||
{
|
||||
debouncedModSettingsChange?.Cancel();
|
||||
debouncedModSettingsChange = null;
|
||||
|
||||
trackedUpdateCancellationSource.Cancel();
|
||||
trackedUpdateCancellationSource = new CancellationTokenSource();
|
||||
|
||||
@@ -348,7 +380,7 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
private class BindableStarDifficulty : Bindable<StarDifficulty>
|
||||
{
|
||||
public readonly IBeatmapInfo BeatmapInfo;
|
||||
public IBeatmapInfo BeatmapInfo;
|
||||
public readonly CancellationToken CancellationToken;
|
||||
|
||||
public BindableStarDifficulty(IBeatmapInfo beatmapInfo, CancellationToken cancellationToken)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
@@ -53,13 +52,11 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
foreach (BeatmapInfo beatmap in beatmapSet.Beatmaps)
|
||||
{
|
||||
difficultyCache.Invalidate(beatmap);
|
||||
|
||||
var working = workingBeatmapCache.GetWorkingBeatmap(beatmap);
|
||||
|
||||
difficultyCache.Invalidate(beatmap, working.BeatmapInfo);
|
||||
|
||||
var ruleset = working.BeatmapInfo.Ruleset.CreateInstance();
|
||||
|
||||
Debug.Assert(ruleset != null);
|
||||
|
||||
var calculator = ruleset.CreateDifficultyCalculator(working);
|
||||
|
||||
beatmap.StarRating = calculator.Calculate().StarRating;
|
||||
|
||||
@@ -170,21 +170,21 @@ namespace osu.Game.Beatmaps.Formats
|
||||
{
|
||||
SampleControlPoint sampleControlPoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(hitObject.StartTime + CONTROL_POINT_LENIENCY + 1)
|
||||
?? SampleControlPoint.DEFAULT;
|
||||
hitObject.Samples = hitObject.Samples.Select(o => sampleControlPoint.ApplyTo(o)).ToList();
|
||||
hitObject.Samples = hitObject.Samples.Select(sampleControlPoint.ApplyTo).ToList();
|
||||
|
||||
for (int i = 0; i < hasRepeats.NodeSamples.Count; i++)
|
||||
{
|
||||
double time = hitObject.StartTime + i * hasRepeats.Duration / hasRepeats.SpanCount() + CONTROL_POINT_LENIENCY;
|
||||
var nodeSamplePoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(time) ?? SampleControlPoint.DEFAULT;
|
||||
|
||||
hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Select(o => nodeSamplePoint.ApplyTo(o)).ToList();
|
||||
hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Select(nodeSamplePoint.ApplyTo).ToList();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SampleControlPoint sampleControlPoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(hitObject.GetEndTime() + CONTROL_POINT_LENIENCY)
|
||||
?? SampleControlPoint.DEFAULT;
|
||||
hitObject.Samples = hitObject.Samples.Select(o => sampleControlPoint.ApplyTo(o)).ToList();
|
||||
hitObject.Samples = hitObject.Samples.Select(sampleControlPoint.ApplyTo).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
@@ -104,6 +105,10 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
beatmapInfo = beatmapInfo.Detach();
|
||||
|
||||
// If this ever gets hit, a request has arrived with an outdated BeatmapInfo.
|
||||
// An outdated BeatmapInfo may contain a reference to a previous version of the beatmap's files on disk.
|
||||
Debug.Assert(confirmFileHashIsUpToDate(beatmapInfo), "working beatmap returned with outdated path");
|
||||
|
||||
workingCache.Add(working = new BeatmapManagerWorkingBeatmap(beatmapInfo, this));
|
||||
|
||||
// best effort; may be higher than expected.
|
||||
@@ -113,6 +118,12 @@ namespace osu.Game.Beatmaps
|
||||
}
|
||||
}
|
||||
|
||||
private bool confirmFileHashIsUpToDate(BeatmapInfo beatmapInfo)
|
||||
{
|
||||
string refetchPath = realm.Run(r => r.Find<BeatmapInfo>(beatmapInfo.ID)?.File?.File.Hash);
|
||||
return refetchPath == null || refetchPath == beatmapInfo.File?.File.Hash;
|
||||
}
|
||||
|
||||
#region IResourceStorageProvider
|
||||
|
||||
TextureStore IBeatmapResourceProvider.LargeTextureStore => largeTextureStore;
|
||||
|
||||
@@ -15,6 +15,7 @@ using osu.Game.Input;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Dashboard.Friends;
|
||||
using osu.Game.Overlays.Mods.Input;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
@@ -234,6 +235,9 @@ namespace osu.Game.Configuration
|
||||
|
||||
// intentionally uses `DateTime?` and not `DateTimeOffset?` because the latter fails due to `DateTimeOffset` not implementing `IConvertible`
|
||||
SetDefault(OsuSetting.LastOnlineTagsPopulation, (DateTime?)null);
|
||||
|
||||
SetDefault(OsuSetting.DashboardSortMode, UserSortCriteria.LastVisit);
|
||||
SetDefault(OsuSetting.DashboardDisplayStyle, OverlayPanelDisplayStyle.Card);
|
||||
}
|
||||
|
||||
protected override bool CheckLookupContainsPrivateInformation(OsuSetting lookup)
|
||||
@@ -486,5 +490,8 @@ namespace osu.Game.Configuration
|
||||
LastOnlineTagsPopulation,
|
||||
|
||||
AutomaticallyAdjustBeatmapOffset,
|
||||
|
||||
DashboardSortMode,
|
||||
DashboardDisplayStyle,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ namespace osu.Game.Configuration
|
||||
LabelText = attr.Label,
|
||||
TooltipText = attr.Description,
|
||||
Current = bNumber,
|
||||
KeyboardStep = 0.1f,
|
||||
KeyboardStep = bNumber.Precision,
|
||||
};
|
||||
|
||||
break;
|
||||
@@ -151,7 +151,7 @@ namespace osu.Game.Configuration
|
||||
LabelText = attr.Label,
|
||||
TooltipText = attr.Description,
|
||||
Current = bNumber,
|
||||
KeyboardStep = 0.1f,
|
||||
KeyboardStep = (float)bNumber.Precision,
|
||||
};
|
||||
|
||||
break;
|
||||
@@ -161,7 +161,8 @@ namespace osu.Game.Configuration
|
||||
{
|
||||
LabelText = attr.Label,
|
||||
TooltipText = attr.Description,
|
||||
Current = bNumber
|
||||
Current = bNumber,
|
||||
KeyboardStep = bNumber.Precision,
|
||||
};
|
||||
|
||||
break;
|
||||
|
||||
@@ -76,6 +76,15 @@ namespace osu.Game.Database
|
||||
statistics.Value.Usage = cache.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Completely purge the cache.
|
||||
/// </summary>
|
||||
public virtual void Clear()
|
||||
{
|
||||
cache.Clear();
|
||||
statistics.Value.Usage = 0;
|
||||
}
|
||||
|
||||
protected bool CheckExists(TLookup lookup, [MaybeNullWhen(false)] out TValue value) =>
|
||||
cache.TryGetValue(lookup, out value);
|
||||
|
||||
|
||||
@@ -474,8 +474,10 @@ namespace osu.Game.Database
|
||||
|
||||
foreach (RealmNamedFileUsage file in item.Files.Where(f => HashableFileTypes.Any(ext => f.Filename.EndsWith(ext, StringComparison.OrdinalIgnoreCase))).OrderBy(f => f.Filename))
|
||||
{
|
||||
using (Stream s = Files.Store.GetStream(file.File.GetStoragePath()))
|
||||
s.CopyTo(hashable);
|
||||
using (Stream? s = Files.Store.GetStream(file.File.GetStoragePath()))
|
||||
{
|
||||
s?.CopyTo(hashable);
|
||||
}
|
||||
}
|
||||
|
||||
if (hashable.Length > 0)
|
||||
|
||||
@@ -51,7 +51,7 @@ namespace osu.Game.Database
|
||||
|
||||
var beatmap = new Beatmap();
|
||||
|
||||
HitResult maxRulesetJudgement = ruleset.GetHitResults().First().result;
|
||||
HitResult maxRulesetJudgement = ruleset.GetHitResultsForDisplay().First().result;
|
||||
|
||||
// This is a list of all results, ordered from best to worst.
|
||||
// We are constructing a "best possible" score from the statistics provided because it's the best we can do.
|
||||
|
||||
@@ -561,7 +561,12 @@ namespace osu.Game.Graphics.Carousel
|
||||
}
|
||||
}
|
||||
|
||||
private enum TraversalType { Keyboard, Set, Group }
|
||||
private enum TraversalType
|
||||
{
|
||||
Keyboard,
|
||||
Set,
|
||||
Group
|
||||
}
|
||||
|
||||
private record TraversalOperation(TraversalType Type, int Direction);
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Utils;
|
||||
|
||||
namespace osu.Game.Graphics.Cursor
|
||||
{
|
||||
@@ -93,10 +94,10 @@ namespace osu.Game.Graphics.Cursor
|
||||
protected override void PopIn()
|
||||
{
|
||||
instantMovement |= !IsPresent;
|
||||
this.FadeIn(500, Easing.OutQuint);
|
||||
this.FadeIn(300, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override void PopOut() => this.Delay(150).FadeOut(500, Easing.OutQuint);
|
||||
protected override void PopOut() => this.Delay(150).FadeOut(300, Easing.OutQuint);
|
||||
|
||||
public override void Move(Vector2 pos)
|
||||
{
|
||||
@@ -107,7 +108,8 @@ namespace osu.Game.Graphics.Cursor
|
||||
}
|
||||
else
|
||||
{
|
||||
this.MoveTo(pos, 200, Easing.OutQuint);
|
||||
// This method is called every frame so we can do this safely here.
|
||||
Position = Interpolation.ValueAt(Time.Elapsed, Position, pos, 0, 120, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
// 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.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Utils;
|
||||
@@ -21,10 +18,8 @@ namespace osu.Game.Graphics.UserInterface
|
||||
/// </summary>
|
||||
public partial class HoverClickSounds : HoverSounds
|
||||
{
|
||||
public Bindable<bool> Enabled = new Bindable<bool>(true);
|
||||
|
||||
private Sample sampleClick;
|
||||
private Sample sampleClickDisabled;
|
||||
private Sample? sampleClick;
|
||||
private Sample? sampleClickDisabled;
|
||||
|
||||
private readonly MouseButton[] buttons;
|
||||
|
||||
@@ -36,7 +31,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
/// Array of button codes which should trigger the click sound.
|
||||
/// If this optional parameter is omitted or set to <code>null</code>, the click sound will only be played on left click.
|
||||
/// </param>
|
||||
public HoverClickSounds(HoverSampleSet sampleSet = HoverSampleSet.Default, MouseButton[] buttons = null)
|
||||
public HoverClickSounds(HoverSampleSet sampleSet = HoverSampleSet.Default, MouseButton[]? buttons = null)
|
||||
: base(sampleSet)
|
||||
{
|
||||
this.buttons = buttons ?? new[] { MouseButton.Left };
|
||||
@@ -60,14 +55,6 @@ namespace osu.Game.Graphics.UserInterface
|
||||
return base.OnClick(e);
|
||||
}
|
||||
|
||||
public override void PlayHoverSample()
|
||||
{
|
||||
if (!Enabled.Value)
|
||||
return;
|
||||
|
||||
base.PlayHoverSample();
|
||||
}
|
||||
|
||||
public void PlayClickSample()
|
||||
{
|
||||
var channel = Enabled.Value ? sampleClick?.GetChannel() : sampleClickDisabled?.GetChannel();
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Utils;
|
||||
@@ -18,6 +19,8 @@ namespace osu.Game.Graphics.UserInterface
|
||||
/// </summary>
|
||||
public partial class HoverSounds : HoverSampleDebounceComponent
|
||||
{
|
||||
public readonly Bindable<bool> Enabled = new Bindable<bool>(true);
|
||||
|
||||
private Sample sampleHover;
|
||||
|
||||
protected readonly HoverSampleSet SampleSet;
|
||||
@@ -37,6 +40,9 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
public override void PlayHoverSample()
|
||||
{
|
||||
if (!Enabled.Value)
|
||||
return;
|
||||
|
||||
sampleHover.Frequency.Value = 0.98 + RNG.NextDouble(0.04);
|
||||
sampleHover.Play();
|
||||
}
|
||||
|
||||
@@ -104,9 +104,17 @@ namespace osu.Game.Graphics.UserInterface
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2? targetSize;
|
||||
|
||||
// todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring
|
||||
protected override void UpdateSize(Vector2 newSize)
|
||||
{
|
||||
// TODO: should probably fix this at a framework level (this method is running every frame which can spam transforms)
|
||||
if (newSize == targetSize)
|
||||
return;
|
||||
|
||||
targetSize = newSize;
|
||||
|
||||
if (Direction == Direction.Vertical)
|
||||
{
|
||||
Width = newSize.X;
|
||||
|
||||
@@ -42,6 +42,8 @@ namespace osu.Game.Graphics.UserInterface
|
||||
Margin = new MarginPadding { Left = 2 },
|
||||
};
|
||||
|
||||
protected bool DrawBorder { get; init; } = true;
|
||||
|
||||
private OsuCaret? caret;
|
||||
|
||||
private bool selectionStarted;
|
||||
@@ -256,7 +258,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
protected override void OnFocus(FocusEvent e)
|
||||
{
|
||||
if (Masking)
|
||||
if (DrawBorder)
|
||||
BorderThickness = 3;
|
||||
|
||||
base.OnFocus(e);
|
||||
@@ -268,7 +270,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
protected override void OnFocusLost(FocusLostEvent e)
|
||||
{
|
||||
if (Masking)
|
||||
if (DrawBorder)
|
||||
BorderThickness = 0;
|
||||
|
||||
base.OnFocusLost(e);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
@@ -22,7 +23,7 @@ namespace osu.Game.Graphics.UserInterfaceV2.FileSelection
|
||||
Origin = Anchor.CentreLeft;
|
||||
LabelTextFlowContainer.Anchor = Anchor.CentreLeft;
|
||||
LabelTextFlowContainer.Origin = Anchor.CentreLeft;
|
||||
LabelText = @"Show hidden";
|
||||
LabelText = UserInterfaceStrings.ShowHidden;
|
||||
|
||||
Scale = new Vector2(0.8f);
|
||||
}
|
||||
|
||||
+8
-6
@@ -8,7 +8,9 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
|
||||
@@ -26,9 +28,9 @@ namespace osu.Game.Graphics.UserInterfaceV2.FileSelection
|
||||
d.Alpha = 0;
|
||||
});
|
||||
|
||||
protected override DirectorySelectorDirectory CreateRootDirectoryItem() => new OsuBreadcrumbDisplayComputer();
|
||||
protected override DirectorySelectorDirectory CreateRootDirectoryItem() => new OsuBreadcrumbDisplayDevice();
|
||||
|
||||
protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string? displayName = null) => new OsuBreadcrumbDisplayDirectory(directory, displayName);
|
||||
protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, LocalisableString? displayName = null) => new OsuBreadcrumbDisplayDirectory(directory, displayName);
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
@@ -47,19 +49,19 @@ namespace osu.Game.Graphics.UserInterfaceV2.FileSelection
|
||||
});
|
||||
}
|
||||
|
||||
private partial class OsuBreadcrumbDisplayComputer : OsuBreadcrumbDisplayDirectory
|
||||
private partial class OsuBreadcrumbDisplayDevice : OsuBreadcrumbDisplayDirectory
|
||||
{
|
||||
protected override IconUsage? Icon => null;
|
||||
|
||||
public OsuBreadcrumbDisplayComputer()
|
||||
: base(null, "Computer")
|
||||
public OsuBreadcrumbDisplayDevice()
|
||||
: base(null, UserInterfaceStrings.Device)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private partial class OsuBreadcrumbDisplayDirectory : DirectorySelectorDirectory
|
||||
{
|
||||
public OsuBreadcrumbDisplayDirectory(DirectoryInfo? directory, string? displayName = null)
|
||||
public OsuBreadcrumbDisplayDirectory(DirectoryInfo? directory, LocalisableString? displayName = null)
|
||||
: base(directory, displayName)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -6,13 +6,14 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterfaceV2.FileSelection
|
||||
{
|
||||
internal partial class OsuDirectorySelectorDirectory : DirectorySelectorDirectory
|
||||
{
|
||||
public OsuDirectorySelectorDirectory(DirectoryInfo directory, string? displayName = null)
|
||||
public OsuDirectorySelectorDirectory(DirectoryInfo directory, LocalisableString? displayName = null)
|
||||
: base(directory, displayName)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -9,11 +9,9 @@ using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics.Backgrounds;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
@@ -70,8 +68,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
|
||||
private Container content = null!;
|
||||
private Box background = null!;
|
||||
private FormControlBackground background = null!;
|
||||
private OsuTextFlowContainer text = null!;
|
||||
private Button button = null!;
|
||||
|
||||
@@ -81,7 +78,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
InternalChild = content = new Container
|
||||
InternalChild = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
@@ -90,21 +87,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
CornerExponent = 2.5f,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
background = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background5,
|
||||
},
|
||||
new TrianglesV2
|
||||
{
|
||||
SpawnRatio = 0.5f,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = ColourInfo.GradientVertical(colourProvider.Background4, colourProvider.Background5),
|
||||
},
|
||||
new HoverClickSounds(HoverSampleSet.Button)
|
||||
{
|
||||
Enabled = { BindTarget = Enabled },
|
||||
},
|
||||
background = new FormControlBackground(),
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
@@ -175,7 +158,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
if (Enabled.Value)
|
||||
{
|
||||
background.FlashColour(ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark2), 800, Easing.OutQuint);
|
||||
background.Flash();
|
||||
button.TriggerClick();
|
||||
}
|
||||
|
||||
@@ -186,19 +169,14 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
text.Colour = Enabled.Value ? colourProvider.Content1 : colourProvider.Background1;
|
||||
|
||||
background.FadeColour(IsHovered
|
||||
? ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark4)
|
||||
: colourProvider.Background5, 200, Easing.OutQuint);
|
||||
|
||||
content.BorderThickness = IsHovered ? 2 : 0;
|
||||
|
||||
if (BackgroundColour != null)
|
||||
{
|
||||
button.BackgroundColour = BackgroundColour.Value;
|
||||
content.BorderColour = Enabled.Value ? BackgroundColour.Value : Interpolation.ValueAt(0.75, BackgroundColour.Value, colourProvider.Dark1, 0, 1);
|
||||
}
|
||||
if (!Enabled.Value)
|
||||
background.VisualStyle = VisualStyle.Disabled;
|
||||
else if (IsHovered)
|
||||
background.VisualStyle = VisualStyle.Hovered;
|
||||
else
|
||||
content.BorderColour = Enabled.Value ? colourProvider.Light4 : colourProvider.Dark1;
|
||||
background.VisualStyle = VisualStyle.Normal;
|
||||
|
||||
// TODO: Support BackgroundColour?
|
||||
}
|
||||
|
||||
public partial class Button : OsuButton
|
||||
|
||||
@@ -4,21 +4,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
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.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
@@ -42,34 +35,23 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
/// </summary>
|
||||
public LocalisableString HintText { get; init; }
|
||||
|
||||
private Box background = null!;
|
||||
private FormControlBackground background = null!;
|
||||
private FormFieldCaption caption = null!;
|
||||
private OsuSpriteText text = null!;
|
||||
|
||||
private Sample? sampleChecked;
|
||||
private Sample? sampleUnchecked;
|
||||
private Sample? sampleDisabled;
|
||||
private SwitchButton switchButton = null!;
|
||||
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio)
|
||||
private void load()
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
Masking = true;
|
||||
CornerRadius = 5;
|
||||
CornerExponent = 2.5f;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
background = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background5,
|
||||
},
|
||||
background = new FormControlBackground(),
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
@@ -77,13 +59,13 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
Padding = new MarginPadding(9),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
new Container
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Padding = new MarginPadding { Right = SwitchButton.WIDTH + 5 },
|
||||
Spacing = new Vector2(0f, 4f),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
caption = new FormFieldCaption
|
||||
@@ -91,13 +73,9 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
Caption = Caption,
|
||||
TooltipText = HintText,
|
||||
},
|
||||
text = new OsuSpriteText
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
},
|
||||
},
|
||||
},
|
||||
new SwitchButton
|
||||
switchButton = new SwitchButton
|
||||
{
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
@@ -106,9 +84,6 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
},
|
||||
},
|
||||
};
|
||||
sampleChecked = audio.Samples.Get(@"UI/check-on");
|
||||
sampleUnchecked = audio.Samples.Get(@"UI/check-off");
|
||||
sampleDisabled = audio.Samples.Get(@"UI/default-select-disabled");
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@@ -118,22 +93,13 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
current.BindValueChanged(_ =>
|
||||
{
|
||||
updateState();
|
||||
playSamples();
|
||||
background.FlashColour(ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark2), 800, Easing.OutQuint);
|
||||
background.Flash();
|
||||
|
||||
ValueChanged?.Invoke();
|
||||
});
|
||||
current.BindDisabledChanged(_ => updateState(), true);
|
||||
}
|
||||
|
||||
private void playSamples()
|
||||
{
|
||||
if (Current.Value)
|
||||
sampleChecked?.Play();
|
||||
else
|
||||
sampleUnchecked?.Play();
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
updateState();
|
||||
@@ -148,28 +114,20 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
if (!Current.Disabled)
|
||||
Current.Value = !Current.Value;
|
||||
else
|
||||
sampleDisabled?.Play();
|
||||
|
||||
switchButton.TriggerClick();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
caption.Colour = Current.Disabled ? colourProvider.Background1 : colourProvider.Content2;
|
||||
text.Colour = Current.Disabled ? colourProvider.Background1 : colourProvider.Content1;
|
||||
|
||||
text.Text = Current.Value ? CommonStrings.Enabled : CommonStrings.Disabled;
|
||||
|
||||
// use FadeColour to override any existing colour transform (i.e. FlashColour on click).
|
||||
background.FadeColour(IsHovered
|
||||
? ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark4)
|
||||
: colourProvider.Background5);
|
||||
|
||||
BorderThickness = IsHovered ? 2 : 0;
|
||||
BorderColour = Current.Disabled ? colourProvider.Dark1 : colourProvider.Light4;
|
||||
if (IsDisabled)
|
||||
background.VisualStyle = VisualStyle.Disabled;
|
||||
else if (IsHovered)
|
||||
background.VisualStyle = VisualStyle.Hovered;
|
||||
else
|
||||
background.VisualStyle = VisualStyle.Normal;
|
||||
}
|
||||
|
||||
public IEnumerable<LocalisableString> FilterTerms => Caption.Yield();
|
||||
@@ -181,5 +139,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
public void SetDefault() => Current.SetDefault();
|
||||
|
||||
public bool IsDisabled => Current.Disabled;
|
||||
|
||||
public float MainDrawHeight => DrawHeight;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
|
||||
public BindableBool CanAdd { get; } = new BindableBool(true);
|
||||
|
||||
private Box background = null!;
|
||||
private FormControlBackground background = null!;
|
||||
private FormFieldCaption caption = null!;
|
||||
private FillFlowContainer flow = null!;
|
||||
private RoundedButton addButton = null!;
|
||||
@@ -47,16 +47,9 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
Masking = true;
|
||||
CornerRadius = 5;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
background = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background5,
|
||||
},
|
||||
background = new FormControlBackground(),
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
@@ -140,13 +133,12 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
background.Colour = colourProvider.Background5;
|
||||
caption.Colour = colourProvider.Content2;
|
||||
|
||||
BorderThickness = IsHovered ? 2 : 0;
|
||||
|
||||
if (IsHovered)
|
||||
BorderColour = colourProvider.Light4;
|
||||
background.VisualStyle = VisualStyle.Hovered;
|
||||
else
|
||||
background.VisualStyle = VisualStyle.Normal;
|
||||
}
|
||||
|
||||
private void updateColours()
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
// 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.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
public partial class FormControlBackground : CompositeDrawable
|
||||
{
|
||||
public const float CORNER_EXPONENT = 2.5f;
|
||||
public const float BORDER_THICKNESS = 2.5f;
|
||||
|
||||
private VisualStyle visualStyle;
|
||||
|
||||
public VisualStyle VisualStyle
|
||||
{
|
||||
get => visualStyle;
|
||||
set
|
||||
{
|
||||
visualStyle = value;
|
||||
updateStyle();
|
||||
}
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
|
||||
private readonly Box box;
|
||||
|
||||
private readonly HoverSounds sounds;
|
||||
|
||||
public FormControlBackground()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
Masking = true;
|
||||
CornerRadius = 5;
|
||||
|
||||
CornerExponent = CORNER_EXPONENT;
|
||||
BorderThickness = BORDER_THICKNESS;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
box = new Box
|
||||
{
|
||||
Colour = Color4.White,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
sounds = new HoverSounds(),
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
updateStyle();
|
||||
FinishTransforms(true);
|
||||
}
|
||||
|
||||
public void Flash()
|
||||
{
|
||||
box.FlashColour(ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark2), 800, Easing.OutQuint);
|
||||
}
|
||||
|
||||
private void updateStyle()
|
||||
{
|
||||
sounds.Enabled.Value = visualStyle != VisualStyle.Disabled;
|
||||
|
||||
ColourInfo colour;
|
||||
ColourInfo borderColour;
|
||||
|
||||
bool border = false;
|
||||
|
||||
switch (visualStyle)
|
||||
{
|
||||
case VisualStyle.Normal:
|
||||
colour = colourProvider.Background4.Darken(0.1f);
|
||||
borderColour = colourProvider.Light4;
|
||||
break;
|
||||
|
||||
case VisualStyle.Disabled:
|
||||
colour = colourProvider.Background4;
|
||||
borderColour = colourProvider.Dark1;
|
||||
break;
|
||||
|
||||
case VisualStyle.Hovered:
|
||||
colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark4);
|
||||
borderColour = colourProvider.Light4;
|
||||
border = true;
|
||||
break;
|
||||
|
||||
case VisualStyle.Focused:
|
||||
colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark3);
|
||||
border = true;
|
||||
borderColour = colourProvider.Highlight1;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
this.TransformTo(nameof(BorderColour), border ? borderColour : colour, 250, Easing.OutQuint);
|
||||
|
||||
box.FadeColour(colour, 250, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
|
||||
public enum VisualStyle
|
||||
{
|
||||
Normal,
|
||||
Disabled,
|
||||
Hovered,
|
||||
Focused
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,11 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
/// <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 => header.HintText;
|
||||
set => header.HintText = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The maximum height of the dropdown's menu.
|
||||
@@ -49,10 +53,6 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
|
||||
header.Caption = Caption;
|
||||
header.HintText = HintText;
|
||||
|
||||
// there's bottom margin applied inside the header to give spacing between the header and the menu.
|
||||
// however when the menu is closed the extra spacing remains present. to remove it, apply negative bottom padding here.
|
||||
Margin = new MarginPadding { Bottom = -header_menu_spacing };
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@@ -80,6 +80,8 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
|
||||
public bool IsDisabled => Current.Disabled;
|
||||
|
||||
public float MainDrawHeight => header.DrawHeight;
|
||||
|
||||
protected override DropdownHeader CreateHeader() => header = new FormDropdownHeader
|
||||
{
|
||||
Dropdown = this,
|
||||
@@ -141,6 +143,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
private FormFieldCaption caption = null!;
|
||||
private OsuSpriteText label = null!;
|
||||
private SpriteIcon chevron = null!;
|
||||
private FormControlBackground background = null!;
|
||||
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
@@ -151,46 +154,51 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
Masking = true;
|
||||
CornerRadius = 5;
|
||||
|
||||
Margin = new MarginPadding { Bottom = header_menu_spacing };
|
||||
// We use our own background for more control.
|
||||
Background.Alpha = 0;
|
||||
|
||||
Foreground.Padding = new MarginPadding(9);
|
||||
Foreground.Children = new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
background = new FormControlBackground(),
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0, 4),
|
||||
Padding = new MarginPadding(9),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
caption = new FormFieldCaption
|
||||
{
|
||||
Caption = Caption,
|
||||
TooltipText = HintText,
|
||||
},
|
||||
label = new TruncatingSpriteText
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Padding = new MarginPadding { Right = 25 },
|
||||
AlwaysPresent = true,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0, 4),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
caption = new FormFieldCaption
|
||||
{
|
||||
Caption = Caption,
|
||||
TooltipText = HintText,
|
||||
},
|
||||
label = new TruncatingSpriteText
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Padding = new MarginPadding { Right = 25 },
|
||||
AlwaysPresent = true,
|
||||
},
|
||||
}
|
||||
},
|
||||
chevron = new SpriteIcon
|
||||
{
|
||||
Icon = FontAwesome.Solid.ChevronDown,
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
Size = new Vector2(16),
|
||||
Margin = new MarginPadding { Right = 5 },
|
||||
},
|
||||
}
|
||||
},
|
||||
chevron = new SpriteIcon
|
||||
{
|
||||
Icon = FontAwesome.Solid.ChevronDown,
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
Size = new Vector2(16),
|
||||
Margin = new MarginPadding { Right = 5 },
|
||||
},
|
||||
};
|
||||
|
||||
AddInternal(new HoverClickSounds
|
||||
{
|
||||
Enabled = { BindTarget = Enabled },
|
||||
});
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@@ -236,25 +244,21 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
else
|
||||
label.Alpha = 1;
|
||||
|
||||
BorderThickness = IsHovered || dropdownOpen ? 2 : 0;
|
||||
|
||||
if (Dropdown.Current.Disabled)
|
||||
BorderColour = colourProvider.Dark1;
|
||||
else
|
||||
BorderColour = dropdownOpen ? colourProvider.Highlight1 : colourProvider.Light4;
|
||||
|
||||
if (dropdownOpen)
|
||||
Background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark3);
|
||||
background.VisualStyle = VisualStyle.Disabled;
|
||||
else if (dropdownOpen)
|
||||
background.VisualStyle = VisualStyle.Focused;
|
||||
else if (IsHovered)
|
||||
Background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark4);
|
||||
background.VisualStyle = VisualStyle.Hovered;
|
||||
else
|
||||
Background.Colour = colourProvider.Background5;
|
||||
background.VisualStyle = VisualStyle.Normal;
|
||||
}
|
||||
|
||||
private void updateChevron()
|
||||
{
|
||||
bool open = Dropdown.Menu.State == MenuState.Open;
|
||||
chevron.ScaleTo(open ? new Vector2(1f, -1f) : Vector2.One, 300, Easing.OutQuint);
|
||||
chevron.MoveToY(open ? -chevron.DrawHeight : 0, 300, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,7 +291,8 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
ItemsContainer.Padding = new MarginPadding(9);
|
||||
|
||||
MaskingContainer.BorderThickness = 2;
|
||||
MaskingContainer.BorderThickness = FormControlBackground.BORDER_THICKNESS;
|
||||
MaskingContainer.CornerExponent = FormControlBackground.CORNER_EXPONENT;
|
||||
MaskingContainer.BorderColour = colourProvider.Highlight1;
|
||||
}
|
||||
|
||||
@@ -295,16 +300,16 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
base.AnimateOpen();
|
||||
|
||||
// there's negative bottom margin applied on the whole dropdown control to remove extra spacing when the menu is closed.
|
||||
// however, when the menu is open, we want spacing between the menu and the next control below it. therefore apply bottom margin here.
|
||||
// we use a transform to keep the open animation smooth while margin is adjusted.
|
||||
this.TransformTo(nameof(Margin), new MarginPadding { Bottom = header_menu_spacing }, 300, Easing.OutQuint);
|
||||
this.TransformTo(nameof(Margin), new MarginPadding
|
||||
{
|
||||
Top = header_menu_spacing,
|
||||
}, 300, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override void AnimateClose()
|
||||
{
|
||||
base.AnimateClose();
|
||||
this.TransformTo(nameof(Margin), new MarginPadding { Bottom = 0 }, 300, Easing.OutQuint);
|
||||
this.TransformTo(nameof(Margin), new MarginPadding(), 300, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
InternalChild = textFlow = new OsuTextFlowContainer(t => t.Font = OsuFont.Default.With(size: 12, weight: FontWeight.SemiBold))
|
||||
InternalChild = textFlow = new OsuTextFlowContainer(t => t.Font = OsuFont.Style.Caption1)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
|
||||
@@ -11,7 +11,6 @@ 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;
|
||||
@@ -70,7 +69,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
|
||||
public Container PreviewContainer { get; private set; } = null!;
|
||||
|
||||
private Box background = null!;
|
||||
private FormControlBackground background = null!;
|
||||
|
||||
private FormFieldCaption caption = null!;
|
||||
private OsuSpriteText placeholderText = null!;
|
||||
@@ -93,16 +92,9 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
Masking = true;
|
||||
CornerRadius = 5;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
background = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background5,
|
||||
},
|
||||
background = new FormControlBackground(),
|
||||
PreviewContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
@@ -194,7 +186,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
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);
|
||||
background.Flash();
|
||||
}
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
@@ -220,22 +212,14 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
caption.Colour = Current.Disabled ? colourProvider.Foreground1 : colourProvider.Content2;
|
||||
filenameText.Colour = Current.Disabled || Current.Value == null ? 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;
|
||||
}
|
||||
if (Current.Disabled)
|
||||
background.VisualStyle = VisualStyle.Disabled;
|
||||
else if (popoverState.Value == Visibility.Visible)
|
||||
background.VisualStyle = VisualStyle.Focused;
|
||||
else if (IsHovered)
|
||||
background.VisualStyle = VisualStyle.Hovered;
|
||||
else
|
||||
{
|
||||
background.Colour = colourProvider.Background4;
|
||||
}
|
||||
background.VisualStyle = VisualStyle.Normal;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
|
||||
@@ -22,6 +22,7 @@ using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK.Graphics;
|
||||
using Vector2 = osuTK.Vector2;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterfaceV2
|
||||
@@ -35,13 +36,20 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
set
|
||||
{
|
||||
current.Current = value;
|
||||
|
||||
// the above `Current` set could have disabled the instantaneous bindable too,
|
||||
// but we still need to copy out `Default` manually,
|
||||
// so lift that disable for a second and then restore it
|
||||
currentNumberInstantaneous.Disabled = false;
|
||||
currentNumberInstantaneous.Default = current.Default;
|
||||
currentNumberInstantaneous.Disabled = current.Disabled;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly BindableNumberWithCurrent<T> current = new BindableNumberWithCurrent<T>();
|
||||
|
||||
private readonly BindableNumber<T> currentNumberInstantaneous = new BindableNumber<T>();
|
||||
private readonly InnerSlider slider;
|
||||
|
||||
/// <summary>
|
||||
/// Whether changes to the value should instantaneously transfer to outside bindables.
|
||||
@@ -84,20 +92,13 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
/// </summary>
|
||||
public LocalisableString HintText { get; init; }
|
||||
|
||||
private float keyboardStep;
|
||||
|
||||
/// <summary>
|
||||
/// A custom step value for each key press which actuates a change on this control.
|
||||
/// </summary>
|
||||
public float KeyboardStep
|
||||
{
|
||||
get => keyboardStep;
|
||||
set
|
||||
{
|
||||
keyboardStep = value;
|
||||
if (IsLoaded)
|
||||
slider.KeyboardStep = value;
|
||||
}
|
||||
get => slider.KeyboardStep;
|
||||
set => slider.KeyboardStep = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -108,7 +109,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
/// <summary>
|
||||
/// Whether sound effects should play when adjusting this slider.
|
||||
/// </summary>
|
||||
public bool PlaySamplesOnAdjust { get; init; }
|
||||
public bool PlaySamplesOnAdjust { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// The string formatting function to use for the value label.
|
||||
@@ -121,11 +122,10 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
/// </summary>
|
||||
public Func<T, LocalisableString> TooltipFormat { get; init; }
|
||||
|
||||
private Box background = null!;
|
||||
private FormControlBackground background = null!;
|
||||
private Box flashLayer = null!;
|
||||
private FormTextBox.InnerTextBox textBox = null!;
|
||||
private OsuSpriteText valueLabel = null!;
|
||||
private InnerSlider slider = null!;
|
||||
private FormFieldCaption captionText = null!;
|
||||
private IFocusManager focusManager = null!;
|
||||
|
||||
@@ -140,6 +140,56 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
LabelFormat ??= defaultLabelFormat;
|
||||
TooltipFormat ??= v => LabelFormat(v);
|
||||
|
||||
// the reason why this slider is created in constructor rather than in BDL like the rest of drawable hierarchy is as follows:
|
||||
// `SliderBar<T>` (the base framework class for all sliders) also does its `Current` initialisation in its ctor.
|
||||
// if that precedent is not followed, it is possible to run into a crippling issue
|
||||
// when a `FormSliderBar` instance is on a screen and said screen is exited before said instance's `LoadComplete()` is invoked.
|
||||
// in that case, the screen exit will unbind the `InnerSlider`'s internal bindings & value change callbacks:
|
||||
// https://github.com/ppy/osu-framework/blob/23ac694fa2c342ce39f563c8a1b975119249d5e9/osu.Framework/Screens/ScreenStack.cs#L353
|
||||
// the callbacks are supposed to propagate `{Min,Max}Value` from `Current` to its internal `currentNumberInstantaneous` bindable:
|
||||
// https://github.com/ppy/osu-framework/blob/64624795b0816261dfc5e930e1d9b9ec7e8bb8c5/osu.Framework/Graphics/UserInterface/SliderBar.cs#L62-L63
|
||||
// thus, the callbacks getting unbound by the screen exit prevents `{Min,Max}Value` from ever correctly propagating, which finally causes a crash at
|
||||
// https://github.com/ppy/osu-framework/blob/64624795b0816261dfc5e930e1d9b9ec7e8bb8c5/osu.Framework/Graphics/UserInterface/SliderBar.cs#L112 ->
|
||||
// https://github.com/ppy/osu-framework/blob/64624795b0816261dfc5e930e1d9b9ec7e8bb8c5/osu.Framework/Graphics/UserInterface/SliderBar.cs#L88-L92.
|
||||
// moving the slider creation & binding to constructor does little to fix the issue other than to make it less likely to be hit.
|
||||
slider = new InnerSlider
|
||||
{
|
||||
Current = currentNumberInstantaneous,
|
||||
OnCommit = () => current.Value = currentNumberInstantaneous.Value,
|
||||
TooltipFormat = TooltipFormat,
|
||||
DisplayAsPercentage = DisplayAsPercentage,
|
||||
PlaySamplesOnAdjust = PlaySamplesOnAdjust,
|
||||
ResetToDefault = () =>
|
||||
{
|
||||
if (!IsDisabled)
|
||||
SetDefault();
|
||||
}
|
||||
};
|
||||
|
||||
current.ValueChanged += e =>
|
||||
{
|
||||
currentNumberInstantaneous.Value = e.NewValue;
|
||||
ValueChanged?.Invoke();
|
||||
};
|
||||
|
||||
current.MinValueChanged += v => currentNumberInstantaneous.MinValue = v;
|
||||
current.MaxValueChanged += v => currentNumberInstantaneous.MaxValue = v;
|
||||
current.PrecisionChanged += v => currentNumberInstantaneous.Precision = v;
|
||||
current.DisabledChanged += disabled =>
|
||||
{
|
||||
if (disabled)
|
||||
{
|
||||
// revert any changes before disabling to make sure we are in a consistent state.
|
||||
currentNumberInstantaneous.Value = current.Value;
|
||||
}
|
||||
|
||||
currentNumberInstantaneous.Disabled = disabled;
|
||||
if (IsLoaded)
|
||||
updateState();
|
||||
};
|
||||
|
||||
current.CopyTo(currentNumberInstantaneous);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@@ -150,14 +200,11 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
|
||||
Masking = true;
|
||||
CornerRadius = 5;
|
||||
CornerExponent = 2.5f;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
background = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background5,
|
||||
},
|
||||
background = new FormControlBackground(),
|
||||
flashLayer = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
@@ -224,23 +271,13 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
},
|
||||
},
|
||||
},
|
||||
slider = new InnerSlider
|
||||
slider.With(s =>
|
||||
{
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 0.5f,
|
||||
Current = currentNumberInstantaneous,
|
||||
OnCommit = () => current.Value = currentNumberInstantaneous.Value,
|
||||
TooltipFormat = TooltipFormat,
|
||||
DisplayAsPercentage = DisplayAsPercentage,
|
||||
PlaySamplesOnAdjust = PlaySamplesOnAdjust,
|
||||
ResetToDefault = () =>
|
||||
{
|
||||
if (!IsDisabled)
|
||||
SetDefault();
|
||||
}
|
||||
}
|
||||
s.Anchor = Anchor.CentreRight;
|
||||
s.Origin = Anchor.CentreRight;
|
||||
s.RelativeSizeAxes = Axes.X;
|
||||
s.Width = 0.5f;
|
||||
})
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -253,7 +290,6 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
slider.KeyboardStep = keyboardStep;
|
||||
captionText.Caption = caption;
|
||||
|
||||
focusManager = GetContainingFocusManager()!;
|
||||
@@ -265,28 +301,6 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
slider.IsDragging.BindValueChanged(_ => updateState());
|
||||
slider.Focused.BindValueChanged(_ => updateState());
|
||||
|
||||
current.ValueChanged += e =>
|
||||
{
|
||||
currentNumberInstantaneous.Value = e.NewValue;
|
||||
ValueChanged?.Invoke();
|
||||
};
|
||||
|
||||
current.MinValueChanged += v => currentNumberInstantaneous.MinValue = v;
|
||||
current.MaxValueChanged += v => currentNumberInstantaneous.MaxValue = v;
|
||||
current.PrecisionChanged += v => currentNumberInstantaneous.Precision = v;
|
||||
current.DisabledChanged += disabled =>
|
||||
{
|
||||
if (disabled)
|
||||
{
|
||||
// revert any changes before disabling to make sure we are in a consistent state.
|
||||
currentNumberInstantaneous.Value = current.Value;
|
||||
}
|
||||
|
||||
currentNumberInstantaneous.Disabled = disabled;
|
||||
updateState();
|
||||
};
|
||||
|
||||
current.CopyTo(currentNumberInstantaneous);
|
||||
currentLanguage.BindValueChanged(_ => Schedule(updateValueDisplay));
|
||||
currentNumberInstantaneous.BindDisabledChanged(_ => updateState());
|
||||
currentNumberInstantaneous.BindValueChanged(e =>
|
||||
@@ -313,8 +327,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
currentNumberInstantaneous.TriggerChange();
|
||||
current.Value = currentNumberInstantaneous.Value;
|
||||
|
||||
flashLayer.Colour = ColourInfo.GradientVertical(colourProvider.Dark2.Opacity(0), colourProvider.Dark2);
|
||||
flashLayer.FadeOutFromOne(800, Easing.OutQuint);
|
||||
background.Flash();
|
||||
}
|
||||
|
||||
private void tryUpdateSliderFromTextBox()
|
||||
@@ -382,19 +395,14 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
textBox.Colour = currentNumberInstantaneous.Disabled ? colourProvider.Background1 : colourProvider.Content1;
|
||||
valueLabel.Colour = currentNumberInstantaneous.Disabled ? colourProvider.Background1 : colourProvider.Content1;
|
||||
|
||||
BorderThickness = childHasFocus || IsHovered || slider.IsDragging.Value ? 2 : 0;
|
||||
|
||||
if (Current.Disabled)
|
||||
BorderColour = colourProvider.Dark1;
|
||||
else
|
||||
BorderColour = childHasFocus ? colourProvider.Highlight1 : colourProvider.Light4;
|
||||
|
||||
if (childHasFocus)
|
||||
background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark3);
|
||||
background.VisualStyle = VisualStyle.Disabled;
|
||||
else if (childHasFocus)
|
||||
background.VisualStyle = VisualStyle.Focused;
|
||||
else if (IsHovered || slider.IsDragging.Value)
|
||||
background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark4);
|
||||
background.VisualStyle = VisualStyle.Hovered;
|
||||
else
|
||||
background.Colour = colourProvider.Background5;
|
||||
background.VisualStyle = VisualStyle.Normal;
|
||||
}
|
||||
|
||||
private void updateValueDisplay()
|
||||
@@ -405,8 +413,21 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
double floatValue = double.CreateTruncating(currentNumberInstantaneous.Value);
|
||||
|
||||
if (currentNumberInstantaneous.Value is int)
|
||||
floatValue /= 100;
|
||||
// if `DisplayAsPercentage` is true and `T` is not `int`, then `Current` / `currentNumberInstantaneous` are in the range of [0,1].
|
||||
// in the text box, we want to show the percentage in the range of [0,100], but without the percentage sign.
|
||||
// the reason we don't want a percentage sign is that `TextBox`es with numerical `TextInputType`s
|
||||
// have framework-side limitations on which characters they accept and they won't accept a percentage sign.
|
||||
//
|
||||
// therefore, the instantaneous value needs to be multiplied by 100 if it's not `int`, so that `ToStandardFormattedString()`,
|
||||
// which is called *intentionally* without `asPercentage: true` specified as to not emit the percentage sign, spits out the correct number.
|
||||
//
|
||||
// additionally note that `ToStandardFormattedString()`, when called with `asPercentage: true` specified, does the *inverse* of this,
|
||||
// which is that it brings the formatted number *into* the [0,1] range,
|
||||
// because .NET number formatting *automatically* multiplies the formatted number by 100 when it is told to stringify a number as percentage
|
||||
// (https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-numeric-format-strings#the--custom-specifier-3).
|
||||
// it's all very confusing.
|
||||
if (currentNumberInstantaneous.Value is not int)
|
||||
floatValue *= 100;
|
||||
|
||||
textBox.Text = floatValue.ToStandardFormattedString(Math.Max(0, OsuSliderBar<T>.MAX_DECIMAL_DIGITS - 2));
|
||||
}
|
||||
@@ -418,7 +439,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
|
||||
private LocalisableString defaultLabelFormat(T value) => currentNumberInstantaneous.Value.ToStandardFormattedString(OsuSliderBar<T>.MAX_DECIMAL_DIGITS, DisplayAsPercentage);
|
||||
|
||||
private partial class InnerSlider : OsuSliderBar<T>
|
||||
public partial class InnerSlider : OsuSliderBar<T>
|
||||
{
|
||||
public BindableBool Focused { get; } = new BindableBool();
|
||||
|
||||
@@ -435,7 +456,6 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
private Box leftBox = null!;
|
||||
private Box rightBox = null!;
|
||||
private InnerSliderNub nub = null!;
|
||||
private HoverClickSounds sounds = null!;
|
||||
public const float NUB_WIDTH = 10;
|
||||
|
||||
[Resolved]
|
||||
@@ -480,14 +500,15 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
ResetToDefault = ResetToDefault,
|
||||
}
|
||||
},
|
||||
sounds = new HoverClickSounds()
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Current.BindDisabledChanged(_ => updateState(), true);
|
||||
FinishTransforms(true);
|
||||
}
|
||||
|
||||
protected override void UpdateAfterChildren()
|
||||
@@ -540,24 +561,29 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
sounds.Enabled.Value = !Current.Disabled;
|
||||
rightBox.Colour = colourProvider.Background6;
|
||||
rightBox.Colour = colourProvider.Background5;
|
||||
|
||||
Color4 leftColour = colourProvider.Light4;
|
||||
Color4 nubColour;
|
||||
|
||||
if (IsHovered || HasFocus || IsDragged)
|
||||
nubColour = colourProvider.Highlight1;
|
||||
else
|
||||
nubColour = colourProvider.Highlight1.Darken(0.1f);
|
||||
|
||||
if (Current.Disabled)
|
||||
{
|
||||
leftBox.Colour = colourProvider.Dark3;
|
||||
nub.Colour = colourProvider.Dark1;
|
||||
}
|
||||
else
|
||||
{
|
||||
leftBox.Colour = HasFocus || IsHovered || IsDragged ? colourProvider.Highlight1.Opacity(0.5f) : colourProvider.Highlight1.Opacity(0.3f);
|
||||
nub.Colour = HasFocus || IsHovered || IsDragged ? colourProvider.Highlight1 : colourProvider.Light4;
|
||||
nubColour = nubColour.Darken(0.4f);
|
||||
leftColour = leftColour.Darken(0.4f);
|
||||
}
|
||||
|
||||
leftBox.FadeColour(leftColour, 250, Easing.OutQuint);
|
||||
nub.FadeColour(nubColour, 250, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override void UpdateValue(float value)
|
||||
{
|
||||
nub.MoveToX(value, 200, Easing.OutPow10);
|
||||
nub.MoveToX(value, 250, Easing.OutElasticQuarter);
|
||||
}
|
||||
|
||||
protected override bool Commit()
|
||||
@@ -573,13 +599,14 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
protected sealed override LocalisableString GetTooltipText(T value) => TooltipFormat(value);
|
||||
}
|
||||
|
||||
private partial class InnerSliderNub : Circle
|
||||
public partial class InnerSliderNub : Circle
|
||||
{
|
||||
public Action? ResetToDefault { get; set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
CornerExponent = 2.5f;
|
||||
Width = InnerSlider.NUB_WIDTH;
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
RelativePositionAxes = Axes.X;
|
||||
@@ -604,5 +631,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
public void SetDefault() => Current.SetDefault();
|
||||
|
||||
public bool IsDisabled => Current.Disabled;
|
||||
|
||||
public float MainDrawHeight => DrawHeight;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
/// </summary>
|
||||
public LocalisableString PlaceholderText { get; init; }
|
||||
|
||||
private Box background = null!;
|
||||
private FormControlBackground background = null!;
|
||||
private Box flashLayer = null!;
|
||||
private InnerTextBox textBox = null!;
|
||||
private FormFieldCaption caption = null!;
|
||||
@@ -92,17 +92,9 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
Masking = true;
|
||||
CornerRadius = 5;
|
||||
CornerExponent = 2.5f;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
background = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background5,
|
||||
},
|
||||
background = new FormControlBackground(),
|
||||
flashLayer = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
@@ -193,19 +185,14 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
caption.Colour = disabled ? colourProvider.Background1 : colourProvider.Content2;
|
||||
textBox.Colour = disabled ? colourProvider.Foreground1 : colourProvider.Content1;
|
||||
|
||||
BorderThickness = IsHovered || textBox.Focused.Value ? 2 : 0;
|
||||
|
||||
if (disabled)
|
||||
BorderColour = colourProvider.Dark1;
|
||||
else
|
||||
BorderColour = textBox.Focused.Value ? colourProvider.Highlight1 : colourProvider.Light4;
|
||||
|
||||
if (textBox.Focused.Value)
|
||||
background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark3);
|
||||
if (Current.Disabled)
|
||||
background.VisualStyle = VisualStyle.Disabled;
|
||||
else if (textBox.Focused.Value)
|
||||
background.VisualStyle = VisualStyle.Focused;
|
||||
else if (IsHovered)
|
||||
background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark4);
|
||||
background.VisualStyle = VisualStyle.Hovered;
|
||||
else
|
||||
background.Colour = colourProvider.Background5;
|
||||
background.VisualStyle = VisualStyle.Normal;
|
||||
}
|
||||
|
||||
internal partial class InnerTextBox : OsuTextBox
|
||||
@@ -216,12 +203,16 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
|
||||
protected override float LeftRightPadding => 0;
|
||||
|
||||
public InnerTextBox()
|
||||
{
|
||||
DrawBorder = false;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Height = 16;
|
||||
TextContainer.Height = 1;
|
||||
Masking = false;
|
||||
BackgroundUnfocused = BackgroundFocused = BackgroundCommit = Colour4.Transparent;
|
||||
}
|
||||
|
||||
@@ -258,5 +249,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
public bool IsDisabled => current.Disabled;
|
||||
|
||||
public IEnumerable<LocalisableString> FilterTerms => Caption.Yield();
|
||||
|
||||
public float MainDrawHeight => DrawHeight;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,5 +31,11 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
/// Whether the control is currently disabled.
|
||||
/// </summary>
|
||||
bool IsDisabled { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The height of the main part of the control (when not expanded).
|
||||
/// This is used to attach external elements.
|
||||
/// </summary>
|
||||
float MainDrawHeight { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterfaceV2.FileSelection;
|
||||
using osu.Game.Overlays;
|
||||
@@ -67,7 +68,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
|
||||
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, LocalisableString? displayName = null) => new OsuDirectorySelectorDirectory(directory, displayName);
|
||||
|
||||
protected override void NotifySelectionError() => this.FlashColour(Colour4.Red, 300);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterfaceV2.FileSelection;
|
||||
@@ -69,7 +70,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
|
||||
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, LocalisableString? displayName = null) => new OsuDirectorySelectorDirectory(directory, displayName);
|
||||
|
||||
protected override DirectoryListingFile CreateFileItem(FileInfo file) => new OsuDirectoryListingFile(file);
|
||||
|
||||
|
||||
@@ -19,55 +19,39 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
public partial class SwitchButton : Checkbox
|
||||
{
|
||||
public const float WIDTH = 45;
|
||||
|
||||
private const float border_thickness = 4.5f;
|
||||
private const float padding = 1.25f;
|
||||
public const float WIDTH = 56;
|
||||
|
||||
private readonly Box fill;
|
||||
private readonly Container nubContainer;
|
||||
private readonly Drawable nub;
|
||||
private readonly CircularContainer content;
|
||||
private readonly Container content;
|
||||
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
|
||||
public bool ExpandOnCurrent { get; init; } = true;
|
||||
|
||||
private Sample? sampleChecked;
|
||||
private Sample? sampleUnchecked;
|
||||
|
||||
public SwitchButton()
|
||||
{
|
||||
Size = new Vector2(WIDTH, 20);
|
||||
Size = new Vector2(WIDTH, 16);
|
||||
|
||||
InternalChild = content = new CircularContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
BorderColour = Color4.White,
|
||||
BorderThickness = border_thickness,
|
||||
BorderThickness = 3.2f,
|
||||
Masking = true,
|
||||
CornerExponent = 2.5f,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
fill = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
AlwaysPresent = true,
|
||||
Alpha = 0
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding(border_thickness + padding),
|
||||
Child = nubContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = nub = new Circle
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
FillMode = FillMode.Fit,
|
||||
Masking = true,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -83,71 +67,65 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Current.BindDisabledChanged(_ => updateColours());
|
||||
Current.BindDisabledChanged(_ => updateState());
|
||||
Current.BindValueChanged(_ => updateState(), true);
|
||||
|
||||
FinishTransforms(true);
|
||||
}
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
nub.MoveToX(Current.Value ? nubContainer.DrawWidth - nub.DrawWidth : 0, 200, Easing.OutQuint);
|
||||
fill.FadeTo(Current.Value ? 1 : 0, 250, Easing.OutQuint);
|
||||
|
||||
updateColours();
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
updateColours();
|
||||
updateState();
|
||||
return base.OnHover(e);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
updateColours();
|
||||
updateState();
|
||||
base.OnHoverLost(e);
|
||||
}
|
||||
|
||||
protected override void OnUserChange(bool value)
|
||||
{
|
||||
base.OnUserChange(value);
|
||||
PlaySample(value);
|
||||
}
|
||||
|
||||
public void PlaySample(bool value)
|
||||
{
|
||||
if (value)
|
||||
sampleChecked?.Play();
|
||||
else
|
||||
sampleUnchecked?.Play();
|
||||
}
|
||||
|
||||
private void updateColours()
|
||||
private void updateState()
|
||||
{
|
||||
ColourInfo borderColour;
|
||||
ColourInfo switchColour;
|
||||
Color4 fillColour = colourProvider.Background5.Opacity(0);
|
||||
Color4 borderColour = colourProvider.Light4;
|
||||
|
||||
if (IsHovered)
|
||||
borderColour = colourProvider.Highlight1;
|
||||
else if (Current.Value)
|
||||
borderColour = colourProvider.Highlight1.Darken(0.1f);
|
||||
|
||||
if (Current.Value)
|
||||
fillColour = borderColour;
|
||||
|
||||
if (Current.Disabled)
|
||||
{
|
||||
borderColour = colourProvider.Dark2;
|
||||
switchColour = colourProvider.Dark1;
|
||||
fill.Colour = colourProvider.Dark5;
|
||||
fillColour = fillColour.Darken(0.4f);
|
||||
borderColour = borderColour.Darken(0.4f);
|
||||
}
|
||||
|
||||
fill.FadeColour(fillColour, 250, Easing.OutQuint);
|
||||
|
||||
content.TransformTo(nameof(BorderColour), (ColourInfo)borderColour, 250, Easing.OutQuint);
|
||||
|
||||
if (ExpandOnCurrent && Current.Value)
|
||||
content.ResizeWidthTo(1f, 200, Easing.OutElasticQuarter);
|
||||
else
|
||||
{
|
||||
bool hover = IsHovered && !Current.Disabled;
|
||||
|
||||
borderColour = hover ? colourProvider.Highlight1.Opacity(0.5f) : colourProvider.Highlight1.Opacity(0.3f);
|
||||
switchColour = hover ? colourProvider.Highlight1 : colourProvider.Light4;
|
||||
|
||||
if (!Current.Value)
|
||||
{
|
||||
borderColour = borderColour.MultiplyAlpha(0.8f);
|
||||
switchColour = switchColour.MultiplyAlpha(0.8f);
|
||||
}
|
||||
|
||||
fill.Colour = colourProvider.Background6;
|
||||
}
|
||||
|
||||
nubContainer.FadeColour(switchColour, 250, Easing.OutQuint);
|
||||
content.TransformTo(nameof(BorderColour), borderColour, 250, Easing.OutQuint);
|
||||
content.ResizeWidthTo(0.75f, 120, Easing.OutExpo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,6 +105,8 @@ namespace osu.Game.Input.Bindings
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.R }, GlobalAction.ResetInputSettings),
|
||||
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.R }, GlobalAction.RandomSkin),
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.E }, GlobalAction.PreviousSkin),
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.T }, GlobalAction.NextSkin),
|
||||
|
||||
new KeyBinding(InputKey.F10, GlobalAction.ToggleGameplayMouseButtons),
|
||||
new KeyBinding(InputKey.F12, GlobalAction.TakeScreenshot),
|
||||
@@ -520,6 +522,12 @@ namespace osu.Game.Input.Bindings
|
||||
|
||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleCurrentGroup))]
|
||||
ToggleCurrentGroup,
|
||||
|
||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.PreviousSkin))]
|
||||
PreviousSkin,
|
||||
|
||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.NextSkin))]
|
||||
NextSkin,
|
||||
}
|
||||
|
||||
public enum GlobalActionCategory
|
||||
|
||||
@@ -69,6 +69,16 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString Date => new TranslatableString(getKey(@"date"), @"Date");
|
||||
|
||||
/// <summary>
|
||||
/// "Personal Best"
|
||||
/// </summary>
|
||||
public static LocalisableString PersonalBest => new TranslatableString(getKey(@"personal_best"), @"Personal Best");
|
||||
|
||||
/// <summary>
|
||||
/// "Personal Best (#{0:N0} of {1:N0})"
|
||||
/// </summary>
|
||||
public static LocalisableString PersonalBestWithPosition(int position, int totalCount) => new TranslatableString(getKey(@"personal_best_with_position"), @"Personal Best (#{0:N0} of {1:N0})", position, totalCount);
|
||||
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@ namespace osu.Game.Localisation
|
||||
private const string prefix = @"osu.Game.Resources.Localisation.BindingSettings";
|
||||
|
||||
/// <summary>
|
||||
/// "Shortcut and gameplay bindings"
|
||||
/// "Shortcuts and gameplay bindings"
|
||||
/// </summary>
|
||||
public static LocalisableString ShortcutAndGameplayBindings => new TranslatableString(getKey(@"shortcut_and_gameplay_bindings"), @"Shortcut and gameplay bindings");
|
||||
public static LocalisableString ShortcutAndGameplayBindings => new TranslatableString(getKey(@"shortcut_and_gameplay_bindings"), @"Shortcuts and gameplay bindings");
|
||||
|
||||
/// <summary>
|
||||
/// "Configure"
|
||||
|
||||
@@ -59,6 +59,16 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString DailyChallenge => new TranslatableString(getKey(@"daily_challenge"), @"daily challenge");
|
||||
|
||||
/// <summary>
|
||||
/// "lounge"
|
||||
/// </summary>
|
||||
public static LocalisableString Lounge => new TranslatableString(getKey(@"lounge"), @"lounge");
|
||||
|
||||
/// <summary>
|
||||
/// "quick play"
|
||||
/// </summary>
|
||||
public static LocalisableString QuickPlay => new TranslatableString(getKey(@"quick_play"), @"quick play");
|
||||
|
||||
/// <summary>
|
||||
/// "A few important words from your dev team!"
|
||||
/// </summary>
|
||||
|
||||
@@ -29,6 +29,26 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString Cancel => new TranslatableString(getKey(@"cancel"), @"No! Abort mission");
|
||||
|
||||
/// <summary>
|
||||
/// "Failed to automatically locate an osu!stable installation."
|
||||
/// </summary>
|
||||
public static LocalisableString StableDirectoryLocationHeaderText => new TranslatableString(getKey(@"stable_directory_location_header_text"), @"Failed to automatically locate an osu!stable installation.");
|
||||
|
||||
/// <summary>
|
||||
/// "An existing install could not be located. If you know where it is, you can help locate it."
|
||||
/// </summary>
|
||||
public static LocalisableString StableDirectoryLocationBodyText => new TranslatableString(getKey(@"stable_directory_location_body_text"), @"An existing install could not be located. If you know where it is, you can help locate it.");
|
||||
|
||||
/// <summary>
|
||||
/// "Sure! I know where it is located!"
|
||||
/// </summary>
|
||||
public static LocalisableString StableDirectoryLocationOkButton => new TranslatableString(getKey(@"stable_directory_location_ok_button"), @"Sure! I know where it is located!");
|
||||
|
||||
/// <summary>
|
||||
/// "Actually I don't have osu!stable installed."
|
||||
/// </summary>
|
||||
public static LocalisableString StableDirectoryLocationCancelButton => new TranslatableString(getKey(@"stable_directory_location_cancel_button"), @"Actually I don't have osu!stable installed.");
|
||||
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,6 +188,11 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString AudioTrack => new TranslatableString(getKey(@"audio_track"), @"Audio Track");
|
||||
|
||||
/// <summary>
|
||||
/// "Custom sample sets"
|
||||
/// </summary>
|
||||
public static LocalisableString CustomSampleSets => new TranslatableString(getKey(@"custom_sample_sets"), @"Custom sample sets");
|
||||
|
||||
/// <summary>
|
||||
/// "Click to select a track"
|
||||
/// </summary>
|
||||
|
||||
@@ -19,6 +19,11 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString WaveformOpacity => new TranslatableString(getKey(@"waveform_opacity"), @"Waveform opacity");
|
||||
|
||||
/// <summary>
|
||||
/// "Show storyboard"
|
||||
/// </summary>
|
||||
public static LocalisableString ShowStoryboard => new TranslatableString(getKey(@"show_storyboard"), @"Show storyboard");
|
||||
|
||||
/// <summary>
|
||||
/// "Show hit markers"
|
||||
/// </summary>
|
||||
|
||||
@@ -120,9 +120,9 @@ namespace osu.Game.Localisation
|
||||
public static LocalisableString ModsHeader => new TranslatableString(getKey(@"mods_header"), @"Mods");
|
||||
|
||||
/// <summary>
|
||||
/// "Increase visibility of first object when visual impairment mods are enabled"
|
||||
/// "Increase first object visibility on visual impairment mods"
|
||||
/// </summary>
|
||||
public static LocalisableString IncreaseFirstObjectVisibility => new TranslatableString(getKey(@"increase_first_object_visibility"), @"Increase visibility of first object when visual impairment mods are enabled");
|
||||
public static LocalisableString IncreaseFirstObjectVisibility => new TranslatableString(getKey(@"increase_first_object_visibility"), @"Increase first object visibility on visual impairment mods");
|
||||
|
||||
/// <summary>
|
||||
/// "Hide during gameplay"
|
||||
|
||||
@@ -229,6 +229,16 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString RandomSkin => new TranslatableString(getKey(@"random_skin"), @"Random skin");
|
||||
|
||||
/// <summary>
|
||||
/// "Previous skin"
|
||||
/// </summary>
|
||||
public static LocalisableString PreviousSkin => new TranslatableString(getKey(@"previous_skin"), @"Previous skin");
|
||||
|
||||
/// <summary>
|
||||
/// "Next skin"
|
||||
/// </summary>
|
||||
public static LocalisableString NextSkin => new TranslatableString(getKey(@"next_skin"), @"Next skin");
|
||||
|
||||
/// <summary>
|
||||
/// "Pause / resume replay"
|
||||
/// </summary>
|
||||
|
||||
@@ -14,6 +14,11 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString InputSectionHeader => new TranslatableString(getKey(@"input_section_header"), @"Input");
|
||||
|
||||
/// <summary>
|
||||
/// "Device: {0}"
|
||||
/// </summary>
|
||||
public static LocalisableString Device(LocalisableString text) => new TranslatableString(getKey(@"device"), @"Device: {0}", text);
|
||||
|
||||
/// <summary>
|
||||
/// "Global"
|
||||
/// </summary>
|
||||
@@ -72,7 +77,8 @@ namespace osu.Game.Localisation
|
||||
/// <summary>
|
||||
/// "The binding you've selected conflicts with another existing binding."
|
||||
/// </summary>
|
||||
public static LocalisableString KeyBindingConflictDetected => new TranslatableString(getKey(@"key_binding_conflict_detected"), @"The binding you've selected conflicts with another existing binding.");
|
||||
public static LocalisableString KeyBindingConflictDetected =>
|
||||
new TranslatableString(getKey(@"key_binding_conflict_detected"), @"The binding you've selected conflicts with another existing binding.");
|
||||
|
||||
/// <summary>
|
||||
/// "Keep existing"
|
||||
|
||||
@@ -100,9 +100,9 @@ namespace osu.Game.Localisation
|
||||
public static LocalisableString ModCustomisationSettings => new TranslatableString(getKey(@"mod_customisation_settings"), @"Many mods have customisation settings that drastically change how they function. Click the Customise button in mod select to view settings!");
|
||||
|
||||
/// <summary>
|
||||
/// "Press {0} to switch to a random skin!"
|
||||
/// "Press {0} to switch to a random skin! You can also use {1} and {2} to cycle through skins."
|
||||
/// </summary>
|
||||
public static LocalisableString RandomSkinShortcut(LocalisableString keybind) => new TranslatableString(getKey(@"random_skin_shortcut"), @"Press {0} to switch to a random skin!", keybind);
|
||||
public static LocalisableString SkinChangeShortcuts(LocalisableString[] keybind) => new TranslatableString(getKey(@"random_skin_shortcut"), @"Press {0} to switch to a random skin! You can also use {1} and {2} to cycle through skins.", keybind[0], keybind[1], keybind[2]);
|
||||
|
||||
/// <summary>
|
||||
/// "While watching a replay, press {0} to toggle replay settings!"
|
||||
|
||||
@@ -19,6 +19,11 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString Mods(int count) => new TranslatableString(getKey(@"mods"), @"{0} mods", count);
|
||||
|
||||
/// <summary>
|
||||
/// "all mods"
|
||||
/// </summary>
|
||||
public static LocalisableString AllMods => new TranslatableString(getKey(@"all_mods"), @"all mods");
|
||||
|
||||
/// <summary>
|
||||
/// "Mods provide different ways to enjoy gameplay. Some have an effect on the score you can achieve during ranked play. Others are just for fun."
|
||||
/// </summary>
|
||||
|
||||
@@ -24,6 +24,31 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString InviteFailedUserOptOut => new TranslatableString(getKey(@"cant_invite_this_user_as1"), @"Can't invite this user as they have opted out of non-friend communications.");
|
||||
|
||||
/// <summary>
|
||||
/// "Add to playlist"
|
||||
/// </summary>
|
||||
public static LocalisableString FooterButtonPlaylistAdd => new TranslatableString(getKey(@"footer_button_playlist_add"), @"Add to playlist");
|
||||
|
||||
/// <summary>
|
||||
/// "Freemods"
|
||||
/// </summary>
|
||||
public static LocalisableString FooterButtonFreemods => new TranslatableString(getKey(@"footer_button_freemods"), @"Freemods");
|
||||
|
||||
/// <summary>
|
||||
/// "Freestyle"
|
||||
/// </summary>
|
||||
public static LocalisableString FooterButtonFreestyle => new TranslatableString(getKey(@"footer_button_freestyle"), @"Freestyle");
|
||||
|
||||
/// <summary>
|
||||
/// "{0} item(s)"
|
||||
/// </summary>
|
||||
public static LocalisableString PlaylistTrayItems(int count) => new TranslatableString(getKey(@"playlist_tray_items"), @"{0} item(s)", count);
|
||||
|
||||
/// <summary>
|
||||
/// "Manage items on previous screen"
|
||||
/// </summary>
|
||||
public static LocalisableString PlaylistTrayDescription => new TranslatableString(getKey(@"playlist_tray_description"), @"Manage items on previous screen");
|
||||
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user