// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. #nullable disable using System; using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Containers; using osu.Game.Screens.Play; using osuTK; namespace osu.Game.Screens.Backgrounds { public partial class BackgroundScreenBeatmap : BackgroundScreen { /// /// The amount of blur to apply when full user blur is requested. /// public const float USER_BLUR_FACTOR = 25; protected Background Background; private WorkingBeatmap beatmap; /// /// Whether or not user-configured settings relating to brightness of elements should be ignored. /// /// /// Beatmap background screens should not apply user settings by default. /// public readonly Bindable IgnoreUserSettings = new Bindable(true); /// /// Whether or not the storyboard loaded should completely hide the background behind it. /// public readonly Bindable StoryboardReplacesBackground = new Bindable(); /// /// The amount of blur to be applied in addition to user-specified blur. /// public readonly Bindable BlurAmount = new BindableFloat(); /// /// The amount of dim to be used when is true. /// public readonly Bindable DimWhenUserSettingsIgnored = new Bindable(); internal readonly Bindable IsBreakTime = new Bindable(); private readonly DimmableBackground dimmable; protected virtual DimmableBackground CreateFadeContainer() => new DimmableBackground { RelativeSizeAxes = Axes.Both }; public BackgroundScreenBeatmap(WorkingBeatmap beatmap = null) { Beatmap = beatmap; InternalChild = dimmable = CreateFadeContainer(); dimmable.StoryboardReplacesBackground.BindTo(StoryboardReplacesBackground); dimmable.IgnoreUserSettings.BindTo(IgnoreUserSettings); dimmable.IsBreakTime.BindTo(IsBreakTime); dimmable.BlurAmount.BindTo(BlurAmount); dimmable.DimWhenUserSettingsIgnored.BindTo(DimWhenUserSettingsIgnored); } [BackgroundDependencyLoader] private void load() { var background = new BeatmapBackground(beatmap); LoadComponent(background); switchBackground(background); } private CancellationTokenSource cancellationSource; public WorkingBeatmap Beatmap { get => beatmap; set { if (beatmap == value && beatmap != null) return; beatmap = value; Schedule(() => { if ((Background as BeatmapBackground)?.Beatmap.BeatmapInfo.BackgroundEquals(beatmap?.BeatmapInfo) ?? false) return; cancellationSource?.Cancel(); LoadComponentAsync(new BeatmapBackground(beatmap), switchBackground, (cancellationSource = new CancellationTokenSource()).Token); }); } } /// /// Reloads beatmap's background. /// public void RefreshBackground() { Schedule(() => { cancellationSource?.Cancel(); LoadComponentAsync(new BeatmapBackground(beatmap), switchBackground, (cancellationSource = new CancellationTokenSource()).Token); }); } private void switchBackground(BeatmapBackground b) { float newDepth = 0; if (Background != null) { newDepth = Background.Depth + 1; Background.FinishTransforms(); Background.FadeOut(250); Background.Expire(); } b.Depth = newDepth; dimmable.Background = Background = b; } public override bool Equals(BackgroundScreen other) { if (!(other is BackgroundScreenBeatmap otherBeatmapBackground)) return false; return base.Equals(other) && beatmap == otherBeatmapBackground.Beatmap; } public partial class DimmableBackground : UserDimContainer { /// /// The amount of blur to be applied to the background in addition to user-specified blur. /// /// /// Used in contexts where there can potentially be both user and screen-specified blurring occuring at the same time, such as in /// public readonly Bindable BlurAmount = new BindableFloat(); public readonly Bindable StoryboardReplacesBackground = new Bindable(); public Background Background { get => background; set { background?.Expire(); base.Add(background = value); background.BlurTo(blurTarget, 0, Easing.OutQuint); } } private Bindable userBlurLevel { get; set; } private Background background; public override void Add(Drawable drawable) { ArgumentNullException.ThrowIfNull(drawable); if (drawable is Background) throw new InvalidOperationException($"Use {nameof(Background)} to set a background."); base.Add(drawable); } /// /// As an optimisation, we add the two blur portions to be applied rather than actually applying two separate blurs. /// private Vector2 blurTarget => !IgnoreUserSettings.Value ? new Vector2(BlurAmount.Value + (float)userBlurLevel.Value * USER_BLUR_FACTOR) : new Vector2(BlurAmount.Value); [BackgroundDependencyLoader] private void load(OsuConfigManager config) { userBlurLevel = config.GetBindable(OsuSetting.BlurLevel); } protected override void LoadComplete() { base.LoadComplete(); userBlurLevel.ValueChanged += _ => UpdateVisuals(); BlurAmount.ValueChanged += _ => UpdateVisuals(); StoryboardReplacesBackground.ValueChanged += _ => UpdateVisuals(); } protected override float DimLevel { get { if ((IgnoreUserSettings.Value || ShowStoryboard.Value) && StoryboardReplacesBackground.Value) return 1; return base.DimLevel; } } protected override void UpdateVisuals() { base.UpdateVisuals(); Background?.BlurTo(blurTarget, BACKGROUND_FADE_DURATION, Easing.OutQuint); } } } }