mirror of
https://github.com/ppy/osu
synced 2025-03-30 07:06:56 +00:00
Merge branch 'master' into auto-restart
This commit is contained in:
commit
3648e0ca26
@ -51,8 +51,8 @@
|
|||||||
<Reference Include="Java.Interop" />
|
<Reference Include="Java.Interop" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.810.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.813.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.811.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.813.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Transitive Dependencies">
|
<ItemGroup Label="Transitive Dependencies">
|
||||||
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
||||||
|
@ -40,6 +40,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
{
|
{
|
||||||
AddSliderStep<float>("circle size", 0, 8, 5, createCatcher);
|
AddSliderStep<float>("circle size", 0, 8, 5, createCatcher);
|
||||||
AddToggleStep("hyper dash", t => this.ChildrenOfType<TestCatcherArea>().ForEach(area => area.ToggleHyperDash(t)));
|
AddToggleStep("hyper dash", t => this.ChildrenOfType<TestCatcherArea>().ForEach(area => area.ToggleHyperDash(t)));
|
||||||
|
AddToggleStep("toggle hit lighting", lighting => config.SetValue(OsuSetting.HitLighting, lighting));
|
||||||
|
|
||||||
AddStep("catch centered fruit", () => attemptCatch(new Fruit()));
|
AddStep("catch centered fruit", () => attemptCatch(new Fruit()));
|
||||||
AddStep("catch many random fruit", () =>
|
AddStep("catch many random fruit", () =>
|
||||||
|
@ -9,6 +9,7 @@ namespace osu.Game.Rulesets.Catch
|
|||||||
Banana,
|
Banana,
|
||||||
Droplet,
|
Droplet,
|
||||||
Catcher,
|
Catcher,
|
||||||
CatchComboCounter
|
CatchComboCounter,
|
||||||
|
HitExplosion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
129
osu.Game.Rulesets.Catch/Skinning/Default/DefaultHitExplosion.cs
Normal file
129
osu.Game.Rulesets.Catch/Skinning/Default/DefaultHitExplosion.cs
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Effects;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
using osu.Game.Utils;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Skinning.Default
|
||||||
|
{
|
||||||
|
public class DefaultHitExplosion : CompositeDrawable, IHitExplosion
|
||||||
|
{
|
||||||
|
private CircularContainer largeFaint;
|
||||||
|
private CircularContainer smallFaint;
|
||||||
|
private CircularContainer directionalGlow1;
|
||||||
|
private CircularContainer directionalGlow2;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Size = new Vector2(20);
|
||||||
|
Anchor = Anchor.BottomCentre;
|
||||||
|
Origin = Anchor.BottomCentre;
|
||||||
|
|
||||||
|
// scale roughly in-line with visual appearance of notes
|
||||||
|
const float initial_height = 10;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
largeFaint = new CircularContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
},
|
||||||
|
smallFaint = new CircularContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
},
|
||||||
|
directionalGlow1 = new CircularContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
Size = new Vector2(0.01f, initial_height),
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
},
|
||||||
|
directionalGlow2 = new CircularContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
Size = new Vector2(0.01f, initial_height),
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Animate(HitExplosionEntry entry)
|
||||||
|
{
|
||||||
|
X = entry.Position;
|
||||||
|
Scale = new Vector2(entry.HitObject.Scale);
|
||||||
|
setColour(entry.ObjectColour);
|
||||||
|
|
||||||
|
using (BeginAbsoluteSequence(entry.LifetimeStart))
|
||||||
|
applyTransforms(entry.HitObject.RandomSeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyTransforms(int randomSeed)
|
||||||
|
{
|
||||||
|
const double duration = 400;
|
||||||
|
|
||||||
|
// we want our size to be very small so the glow dominates it.
|
||||||
|
largeFaint.Size = new Vector2(0.8f);
|
||||||
|
largeFaint
|
||||||
|
.ResizeTo(largeFaint.Size * new Vector2(5, 1), duration, Easing.OutQuint)
|
||||||
|
.FadeOut(duration * 2);
|
||||||
|
|
||||||
|
const float angle_variangle = 15; // should be less than 45
|
||||||
|
directionalGlow1.Rotation = StatelessRNG.NextSingle(-angle_variangle, angle_variangle, randomSeed, 4);
|
||||||
|
directionalGlow2.Rotation = StatelessRNG.NextSingle(-angle_variangle, angle_variangle, randomSeed, 5);
|
||||||
|
|
||||||
|
this.FadeInFromZero(50).Then().FadeOut(duration, Easing.Out);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setColour(Color4 objectColour)
|
||||||
|
{
|
||||||
|
const float roundness = 100;
|
||||||
|
|
||||||
|
largeFaint.EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Glow,
|
||||||
|
Colour = Interpolation.ValueAt(0.1f, objectColour, Color4.White, 0, 1).Opacity(0.3f),
|
||||||
|
Roundness = 160,
|
||||||
|
Radius = 200,
|
||||||
|
};
|
||||||
|
|
||||||
|
smallFaint.EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Glow,
|
||||||
|
Colour = Interpolation.ValueAt(0.6f, objectColour, Color4.White, 0, 1),
|
||||||
|
Roundness = 20,
|
||||||
|
Radius = 50,
|
||||||
|
};
|
||||||
|
|
||||||
|
directionalGlow1.EdgeEffect = directionalGlow2.EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Glow,
|
||||||
|
Colour = Interpolation.ValueAt(0.4f, objectColour, Color4.White, 0, 1),
|
||||||
|
Roundness = roundness,
|
||||||
|
Radius = 40,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -70,13 +70,11 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
|||||||
|
|
||||||
if (version < 2.3m)
|
if (version < 2.3m)
|
||||||
{
|
{
|
||||||
if (GetTexture(@"fruit-ryuuta") != null ||
|
if (hasOldStyleCatcherSprite())
|
||||||
GetTexture(@"fruit-ryuuta-0") != null)
|
|
||||||
return new LegacyCatcherOld();
|
return new LegacyCatcherOld();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GetTexture(@"fruit-catcher-idle") != null ||
|
if (hasNewStyleCatcherSprite())
|
||||||
GetTexture(@"fruit-catcher-idle-0") != null)
|
|
||||||
return new LegacyCatcherNew();
|
return new LegacyCatcherNew();
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@ -86,12 +84,26 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
|||||||
return new LegacyCatchComboCounter(Skin);
|
return new LegacyCatchComboCounter(Skin);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
case CatchSkinComponents.HitExplosion:
|
||||||
|
if (hasOldStyleCatcherSprite() || hasNewStyleCatcherSprite())
|
||||||
|
return new LegacyHitExplosion();
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.GetDrawableComponent(component);
|
return base.GetDrawableComponent(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool hasOldStyleCatcherSprite() =>
|
||||||
|
GetTexture(@"fruit-ryuuta") != null
|
||||||
|
|| GetTexture(@"fruit-ryuuta-0") != null;
|
||||||
|
|
||||||
|
private bool hasNewStyleCatcherSprite() =>
|
||||||
|
GetTexture(@"fruit-catcher-idle") != null
|
||||||
|
|| GetTexture(@"fruit-catcher-idle-0") != null;
|
||||||
|
|
||||||
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
||||||
{
|
{
|
||||||
switch (lookup)
|
switch (lookup)
|
||||||
|
@ -0,0 +1,94 @@
|
|||||||
|
// 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.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||||
|
{
|
||||||
|
public class LegacyHitExplosion : CompositeDrawable, IHitExplosion
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private Catcher catcher { get; set; }
|
||||||
|
|
||||||
|
private const float catch_margin = (1 - Catcher.ALLOWED_CATCH_RANGE) / 2;
|
||||||
|
|
||||||
|
private readonly Sprite explosion1;
|
||||||
|
private readonly Sprite explosion2;
|
||||||
|
|
||||||
|
public LegacyHitExplosion()
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomCentre;
|
||||||
|
Origin = Anchor.BottomCentre;
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
Scale = new Vector2(0.5f);
|
||||||
|
|
||||||
|
InternalChildren = new[]
|
||||||
|
{
|
||||||
|
explosion1 = new Sprite
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomCentre,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Alpha = 0,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
Rotation = -90
|
||||||
|
},
|
||||||
|
explosion2 = new Sprite
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomCentre,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Alpha = 0,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
Rotation = -90
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(SkinManager skins)
|
||||||
|
{
|
||||||
|
var defaultLegacySkin = skins.DefaultLegacySkin;
|
||||||
|
|
||||||
|
// sprite names intentionally swapped to match stable member naming / ease of cross-referencing
|
||||||
|
explosion1.Texture = defaultLegacySkin.GetTexture("scoreboard-explosion-2");
|
||||||
|
explosion2.Texture = defaultLegacySkin.GetTexture("scoreboard-explosion-1");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Animate(HitExplosionEntry entry)
|
||||||
|
{
|
||||||
|
Colour = entry.ObjectColour;
|
||||||
|
|
||||||
|
using (BeginAbsoluteSequence(entry.LifetimeStart))
|
||||||
|
{
|
||||||
|
float halfCatchWidth = catcher.CatchWidth / 2;
|
||||||
|
float explosionOffset = Math.Clamp(entry.Position, -halfCatchWidth + catch_margin * 3, halfCatchWidth - catch_margin * 3);
|
||||||
|
|
||||||
|
if (!(entry.HitObject is Droplet))
|
||||||
|
{
|
||||||
|
float scale = Math.Clamp(entry.JudgementResult.ComboAtJudgement / 200f, 0.35f, 1.125f);
|
||||||
|
|
||||||
|
explosion1.Scale = new Vector2(1, 0.9f);
|
||||||
|
explosion1.Position = new Vector2(explosionOffset, 0);
|
||||||
|
|
||||||
|
explosion1.FadeOutFromOne(300);
|
||||||
|
explosion1.ScaleTo(new Vector2(16 * scale, 1.1f), 160, Easing.Out);
|
||||||
|
}
|
||||||
|
|
||||||
|
explosion2.Scale = new Vector2(0.9f, 1);
|
||||||
|
explosion2.Position = new Vector2(explosionOffset, 0);
|
||||||
|
|
||||||
|
explosion2.FadeOutFromOne(700);
|
||||||
|
explosion2.ScaleTo(new Vector2(0.9f, 1.3f), 500, Easing.Out);
|
||||||
|
|
||||||
|
this.Delay(700).FadeOutFromOne();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -23,6 +23,7 @@ using osuTK.Graphics;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.UI
|
namespace osu.Game.Rulesets.Catch.UI
|
||||||
{
|
{
|
||||||
|
[Cached]
|
||||||
public class Catcher : SkinReloadableDrawable
|
public class Catcher : SkinReloadableDrawable
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -106,7 +107,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Width of the area that can be used to attempt catches during gameplay.
|
/// Width of the area that can be used to attempt catches during gameplay.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly float catchWidth;
|
public readonly float CatchWidth;
|
||||||
|
|
||||||
private readonly SkinnableCatcher body;
|
private readonly SkinnableCatcher body;
|
||||||
|
|
||||||
@ -133,7 +134,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
if (difficulty != null)
|
if (difficulty != null)
|
||||||
Scale = calculateScale(difficulty);
|
Scale = calculateScale(difficulty);
|
||||||
|
|
||||||
catchWidth = CalculateCatchWidth(Scale);
|
CatchWidth = CalculateCatchWidth(Scale);
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -193,7 +194,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
if (!(hitObject is PalpableCatchHitObject fruit))
|
if (!(hitObject is PalpableCatchHitObject fruit))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
float halfCatchWidth = catchWidth * 0.5f;
|
float halfCatchWidth = CatchWidth * 0.5f;
|
||||||
return fruit.EffectiveX >= X - halfCatchWidth &&
|
return fruit.EffectiveX >= X - halfCatchWidth &&
|
||||||
fruit.EffectiveX <= X + halfCatchWidth;
|
fruit.EffectiveX <= X + halfCatchWidth;
|
||||||
}
|
}
|
||||||
@ -216,7 +217,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
placeCaughtObject(palpableObject, positionInStack);
|
placeCaughtObject(palpableObject, positionInStack);
|
||||||
|
|
||||||
if (hitLighting.Value)
|
if (hitLighting.Value)
|
||||||
addLighting(hitObject, positionInStack.X, drawableObject.AccentColour.Value);
|
addLighting(result, drawableObject.AccentColour.Value, positionInStack.X);
|
||||||
}
|
}
|
||||||
|
|
||||||
// droplet doesn't affect the catcher state
|
// droplet doesn't affect the catcher state
|
||||||
@ -365,8 +366,8 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addLighting(CatchHitObject hitObject, float x, Color4 colour) =>
|
private void addLighting(JudgementResult judgementResult, Color4 colour, float x) =>
|
||||||
hitExplosionContainer.Add(new HitExplosionEntry(Time.Current, x, hitObject.Scale, colour, hitObject.RandomSeed));
|
hitExplosionContainer.Add(new HitExplosionEntry(Time.Current, judgementResult, colour, x));
|
||||||
|
|
||||||
private CaughtObject getCaughtObject(PalpableCatchHitObject source)
|
private CaughtObject getCaughtObject(PalpableCatchHitObject source)
|
||||||
{
|
{
|
||||||
|
@ -1,129 +1,56 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Game.Rulesets.Catch.Skinning.Default;
|
||||||
using osu.Framework.Graphics.Effects;
|
|
||||||
using osu.Framework.Utils;
|
|
||||||
using osu.Game.Rulesets.Objects.Pooling;
|
using osu.Game.Rulesets.Objects.Pooling;
|
||||||
using osu.Game.Utils;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
|
||||||
using osuTK.Graphics;
|
#nullable enable
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.UI
|
namespace osu.Game.Rulesets.Catch.UI
|
||||||
{
|
{
|
||||||
public class HitExplosion : PoolableDrawableWithLifetime<HitExplosionEntry>
|
public class HitExplosion : PoolableDrawableWithLifetime<HitExplosionEntry>
|
||||||
{
|
{
|
||||||
private readonly CircularContainer largeFaint;
|
private readonly SkinnableDrawable skinnableExplosion;
|
||||||
private readonly CircularContainer smallFaint;
|
|
||||||
private readonly CircularContainer directionalGlow1;
|
|
||||||
private readonly CircularContainer directionalGlow2;
|
|
||||||
|
|
||||||
public HitExplosion()
|
public HitExplosion()
|
||||||
{
|
{
|
||||||
Size = new Vector2(20);
|
RelativeSizeAxes = Axes.Both;
|
||||||
Anchor = Anchor.TopCentre;
|
Anchor = Anchor.BottomCentre;
|
||||||
Origin = Anchor.BottomCentre;
|
Origin = Anchor.BottomCentre;
|
||||||
|
|
||||||
// scale roughly in-line with visual appearance of notes
|
InternalChild = skinnableExplosion = new SkinnableDrawable(new CatchSkinComponent(CatchSkinComponents.HitExplosion), _ => new DefaultHitExplosion())
|
||||||
const float initial_height = 10;
|
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
|
||||||
{
|
{
|
||||||
largeFaint = new CircularContainer
|
CentreComponent = false,
|
||||||
{
|
Anchor = Anchor.BottomCentre,
|
||||||
Anchor = Anchor.Centre,
|
Origin = Anchor.BottomCentre
|
||||||
Origin = Anchor.Centre,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Masking = true,
|
|
||||||
Blending = BlendingParameters.Additive,
|
|
||||||
},
|
|
||||||
smallFaint = new CircularContainer
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Masking = true,
|
|
||||||
Blending = BlendingParameters.Additive,
|
|
||||||
},
|
|
||||||
directionalGlow1 = new CircularContainer
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Masking = true,
|
|
||||||
Size = new Vector2(0.01f, initial_height),
|
|
||||||
Blending = BlendingParameters.Additive,
|
|
||||||
},
|
|
||||||
directionalGlow2 = new CircularContainer
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Masking = true,
|
|
||||||
Size = new Vector2(0.01f, initial_height),
|
|
||||||
Blending = BlendingParameters.Additive,
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnApply(HitExplosionEntry entry)
|
protected override void OnApply(HitExplosionEntry entry)
|
||||||
{
|
{
|
||||||
X = entry.Position;
|
base.OnApply(entry);
|
||||||
Scale = new Vector2(entry.Scale);
|
if (IsLoaded)
|
||||||
setColour(entry.ObjectColour);
|
apply(entry);
|
||||||
|
|
||||||
using (BeginAbsoluteSequence(entry.LifetimeStart))
|
|
||||||
applyTransforms(entry.RNGSeed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyTransforms(int randomSeed)
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
apply(Entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void apply(HitExplosionEntry? entry)
|
||||||
|
{
|
||||||
|
if (entry == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ApplyTransformsAt(double.MinValue, true);
|
||||||
ClearTransforms(true);
|
ClearTransforms(true);
|
||||||
|
|
||||||
const double duration = 400;
|
(skinnableExplosion.Drawable as IHitExplosion)?.Animate(entry);
|
||||||
|
LifetimeEnd = skinnableExplosion.Drawable.LatestTransformEndTime;
|
||||||
// we want our size to be very small so the glow dominates it.
|
|
||||||
largeFaint.Size = new Vector2(0.8f);
|
|
||||||
largeFaint
|
|
||||||
.ResizeTo(largeFaint.Size * new Vector2(5, 1), duration, Easing.OutQuint)
|
|
||||||
.FadeOut(duration * 2);
|
|
||||||
|
|
||||||
const float angle_variangle = 15; // should be less than 45
|
|
||||||
directionalGlow1.Rotation = StatelessRNG.NextSingle(-angle_variangle, angle_variangle, randomSeed, 4);
|
|
||||||
directionalGlow2.Rotation = StatelessRNG.NextSingle(-angle_variangle, angle_variangle, randomSeed, 5);
|
|
||||||
|
|
||||||
this.FadeInFromZero(50).Then().FadeOut(duration, Easing.Out).Expire();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setColour(Color4 objectColour)
|
|
||||||
{
|
|
||||||
const float roundness = 100;
|
|
||||||
|
|
||||||
largeFaint.EdgeEffect = new EdgeEffectParameters
|
|
||||||
{
|
|
||||||
Type = EdgeEffectType.Glow,
|
|
||||||
Colour = Interpolation.ValueAt(0.1f, objectColour, Color4.White, 0, 1).Opacity(0.3f),
|
|
||||||
Roundness = 160,
|
|
||||||
Radius = 200,
|
|
||||||
};
|
|
||||||
|
|
||||||
smallFaint.EdgeEffect = new EdgeEffectParameters
|
|
||||||
{
|
|
||||||
Type = EdgeEffectType.Glow,
|
|
||||||
Colour = Interpolation.ValueAt(0.6f, objectColour, Color4.White, 0, 1),
|
|
||||||
Roundness = 20,
|
|
||||||
Radius = 50,
|
|
||||||
};
|
|
||||||
|
|
||||||
directionalGlow1.EdgeEffect = directionalGlow2.EdgeEffect = new EdgeEffectParameters
|
|
||||||
{
|
|
||||||
Type = EdgeEffectType.Glow,
|
|
||||||
Colour = Interpolation.ValueAt(0.4f, objectColour, Color4.White, 0, 1),
|
|
||||||
Roundness = roundness,
|
|
||||||
Radius = 40,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Pooling;
|
using osu.Framework.Graphics.Pooling;
|
||||||
using osu.Game.Rulesets.Objects.Pooling;
|
using osu.Game.Rulesets.Objects.Pooling;
|
||||||
|
|
||||||
@ -14,6 +15,8 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
public HitExplosionContainer()
|
public HitExplosionContainer()
|
||||||
{
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
AddInternal(pool = new DrawablePool<HitExplosion>(10));
|
AddInternal(pool = new DrawablePool<HitExplosion>(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,24 +2,42 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Graphics.Performance;
|
using osu.Framework.Graphics.Performance;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.UI
|
namespace osu.Game.Rulesets.Catch.UI
|
||||||
{
|
{
|
||||||
public class HitExplosionEntry : LifetimeEntry
|
public class HitExplosionEntry : LifetimeEntry
|
||||||
{
|
{
|
||||||
public readonly float Position;
|
/// <summary>
|
||||||
public readonly float Scale;
|
/// The judgement result that triggered this explosion.
|
||||||
public readonly Color4 ObjectColour;
|
/// </summary>
|
||||||
public readonly int RNGSeed;
|
public JudgementResult JudgementResult { get; }
|
||||||
|
|
||||||
public HitExplosionEntry(double startTime, float position, float scale, Color4 objectColour, int rngSeed)
|
/// <summary>
|
||||||
|
/// The hitobject which triggered this explosion.
|
||||||
|
/// </summary>
|
||||||
|
public CatchHitObject HitObject => (CatchHitObject)JudgementResult.HitObject;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The accent colour of the object caught.
|
||||||
|
/// </summary>
|
||||||
|
public Color4 ObjectColour { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The position at which the object was caught.
|
||||||
|
/// </summary>
|
||||||
|
public float Position { get; }
|
||||||
|
|
||||||
|
public HitExplosionEntry(double startTime, JudgementResult judgementResult, Color4 objectColour, float position)
|
||||||
{
|
{
|
||||||
LifetimeStart = startTime;
|
LifetimeStart = startTime;
|
||||||
Position = position;
|
Position = position;
|
||||||
Scale = scale;
|
JudgementResult = judgementResult;
|
||||||
ObjectColour = objectColour;
|
ObjectColour = objectColour;
|
||||||
RNGSeed = rngSeed;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
18
osu.Game.Rulesets.Catch/UI/IHitExplosion.cs
Normal file
18
osu.Game.Rulesets.Catch/UI/IHitExplosion.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// 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 enable
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.UI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Common interface for all hit explosion skinnables.
|
||||||
|
/// </summary>
|
||||||
|
public interface IHitExplosion
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Begins animating this <see cref="IHitExplosion"/>.
|
||||||
|
/// </summary>
|
||||||
|
void Animate(HitExplosionEntry entry);
|
||||||
|
}
|
||||||
|
}
|
@ -41,6 +41,11 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
protected override GameplayCursorContainer CreateCursor() => null;
|
protected override GameplayCursorContainer CreateCursor() => null;
|
||||||
|
|
||||||
|
public OsuEditorPlayfield()
|
||||||
|
{
|
||||||
|
HitPolicy = new AnyOrderHitPolicy();
|
||||||
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuConfigManager config)
|
private void load(OsuConfigManager config)
|
||||||
{
|
{
|
||||||
|
@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
@ -32,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||||
{
|
{
|
||||||
drawableRuleset.Overlays.Add(blinds = new DrawableOsuBlinds(drawableRuleset.Playfield.HitObjectContainer, drawableRuleset.Beatmap));
|
drawableRuleset.Overlays.Add(blinds = new DrawableOsuBlinds(drawableRuleset.Playfield, drawableRuleset.Beatmap));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ApplyToHealthProcessor(HealthProcessor healthProcessor)
|
public void ApplyToHealthProcessor(HealthProcessor healthProcessor)
|
||||||
@ -128,8 +129,21 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
float start = Parent.ToLocalSpace(restrictTo.ScreenSpaceDrawQuad.TopLeft).X;
|
float start, end;
|
||||||
float end = Parent.ToLocalSpace(restrictTo.ScreenSpaceDrawQuad.TopRight).X;
|
|
||||||
|
if (Precision.AlmostEquals(restrictTo.Rotation, 0))
|
||||||
|
{
|
||||||
|
start = Parent.ToLocalSpace(restrictTo.ScreenSpaceDrawQuad.TopLeft).X;
|
||||||
|
end = Parent.ToLocalSpace(restrictTo.ScreenSpaceDrawQuad.TopRight).X;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
float center = restrictTo.ToSpaceOfOtherDrawable(restrictTo.OriginPosition, Parent).X;
|
||||||
|
float halfDiagonal = (restrictTo.DrawSize / 2).LengthFast;
|
||||||
|
|
||||||
|
start = center - halfDiagonal;
|
||||||
|
end = center + halfDiagonal;
|
||||||
|
}
|
||||||
|
|
||||||
float rawWidth = end - start;
|
float rawWidth = end - start;
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ using osu.Framework.Input.Events;
|
|||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets.Osu.UI.Cursor;
|
using osu.Game.Rulesets.Osu.UI.Cursor;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||||
{
|
{
|
||||||
@ -21,6 +22,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
private double lastTrailTime;
|
private double lastTrailTime;
|
||||||
private IBindable<float> cursorSize;
|
private IBindable<float> cursorSize;
|
||||||
|
|
||||||
|
private Vector2? currentPosition;
|
||||||
|
|
||||||
public LegacyCursorTrail(ISkin skin)
|
public LegacyCursorTrail(ISkin skin)
|
||||||
{
|
{
|
||||||
this.skin = skin;
|
this.skin = skin;
|
||||||
@ -54,22 +57,34 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected override double FadeDuration => disjointTrail ? 150 : 500;
|
protected override double FadeDuration => disjointTrail ? 150 : 500;
|
||||||
|
protected override float FadeExponent => 1;
|
||||||
|
|
||||||
protected override bool InterpolateMovements => !disjointTrail;
|
protected override bool InterpolateMovements => !disjointTrail;
|
||||||
|
|
||||||
protected override float IntervalMultiplier => 1 / Math.Max(cursorSize.Value, 1);
|
protected override float IntervalMultiplier => 1 / Math.Max(cursorSize.Value, 1);
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
if (!disjointTrail || !currentPosition.HasValue)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (Time.Current - lastTrailTime >= disjoint_trail_time_separation)
|
||||||
|
{
|
||||||
|
lastTrailTime = Time.Current;
|
||||||
|
AddTrail(currentPosition.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override bool OnMouseMove(MouseMoveEvent e)
|
protected override bool OnMouseMove(MouseMoveEvent e)
|
||||||
{
|
{
|
||||||
if (!disjointTrail)
|
if (!disjointTrail)
|
||||||
return base.OnMouseMove(e);
|
return base.OnMouseMove(e);
|
||||||
|
|
||||||
if (Time.Current - lastTrailTime >= disjoint_trail_time_separation)
|
currentPosition = e.ScreenSpaceMousePosition;
|
||||||
{
|
|
||||||
lastTrailTime = Time.Current;
|
|
||||||
return base.OnMouseMove(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Intentionally block the base call as we're adding the trails ourselves.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
22
osu.Game.Rulesets.Osu/UI/AnyOrderHitPolicy.cs
Normal file
22
osu.Game.Rulesets.Osu/UI/AnyOrderHitPolicy.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.UI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An <see cref="IHitPolicy"/> which allows hitobjects to be hit in any order.
|
||||||
|
/// </summary>
|
||||||
|
public class AnyOrderHitPolicy : IHitPolicy
|
||||||
|
{
|
||||||
|
public IHitObjectContainer HitObjectContainer { get; set; }
|
||||||
|
|
||||||
|
public bool IsHittable(DrawableHitObject hitObject, double time) => true;
|
||||||
|
|
||||||
|
public void HandleHit(DrawableHitObject hitObject)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -26,6 +26,11 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
{
|
{
|
||||||
private const int max_sprites = 2048;
|
private const int max_sprites = 2048;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An exponentiating factor to ease the trail fade.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual float FadeExponent => 1.7f;
|
||||||
|
|
||||||
private readonly TrailPart[] parts = new TrailPart[max_sprites];
|
private readonly TrailPart[] parts = new TrailPart[max_sprites];
|
||||||
private int currentIndex;
|
private int currentIndex;
|
||||||
private IShader shader;
|
private IShader shader;
|
||||||
@ -141,22 +146,25 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
|
|
||||||
protected override bool OnMouseMove(MouseMoveEvent e)
|
protected override bool OnMouseMove(MouseMoveEvent e)
|
||||||
{
|
{
|
||||||
Vector2 pos = e.ScreenSpaceMousePosition;
|
AddTrail(e.ScreenSpaceMousePosition);
|
||||||
|
return base.OnMouseMove(e);
|
||||||
|
}
|
||||||
|
|
||||||
if (lastPosition == null)
|
protected void AddTrail(Vector2 position)
|
||||||
|
{
|
||||||
|
if (InterpolateMovements)
|
||||||
{
|
{
|
||||||
lastPosition = pos;
|
if (!lastPosition.HasValue)
|
||||||
resampler.AddPosition(lastPosition.Value);
|
|
||||||
return base.OnMouseMove(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (Vector2 pos2 in resampler.AddPosition(pos))
|
|
||||||
{
|
|
||||||
Trace.Assert(lastPosition.HasValue);
|
|
||||||
|
|
||||||
if (InterpolateMovements)
|
|
||||||
{
|
{
|
||||||
// ReSharper disable once PossibleInvalidOperationException
|
lastPosition = position;
|
||||||
|
resampler.AddPosition(lastPosition.Value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Vector2 pos2 in resampler.AddPosition(position))
|
||||||
|
{
|
||||||
|
Trace.Assert(lastPosition.HasValue);
|
||||||
|
|
||||||
Vector2 pos1 = lastPosition.Value;
|
Vector2 pos1 = lastPosition.Value;
|
||||||
Vector2 diff = pos2 - pos1;
|
Vector2 diff = pos2 - pos1;
|
||||||
float distance = diff.Length;
|
float distance = diff.Length;
|
||||||
@ -170,14 +178,12 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
addPart(lastPosition.Value);
|
addPart(lastPosition.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
lastPosition = pos2;
|
|
||||||
addPart(lastPosition.Value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
return base.OnMouseMove(e);
|
{
|
||||||
|
lastPosition = position;
|
||||||
|
addPart(lastPosition.Value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addPart(Vector2 screenSpacePosition)
|
private void addPart(Vector2 screenSpacePosition)
|
||||||
@ -206,10 +212,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
private Texture texture;
|
private Texture texture;
|
||||||
|
|
||||||
private float time;
|
private float time;
|
||||||
|
private float fadeExponent;
|
||||||
|
|
||||||
private readonly TrailPart[] parts = new TrailPart[max_sprites];
|
private readonly TrailPart[] parts = new TrailPart[max_sprites];
|
||||||
private Vector2 size;
|
private Vector2 size;
|
||||||
|
|
||||||
private Vector2 originPosition;
|
private Vector2 originPosition;
|
||||||
|
|
||||||
private readonly QuadBatch<TexturedTrailVertex> vertexBatch = new QuadBatch<TexturedTrailVertex>(max_sprites, 1);
|
private readonly QuadBatch<TexturedTrailVertex> vertexBatch = new QuadBatch<TexturedTrailVertex>(max_sprites, 1);
|
||||||
@ -227,6 +233,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
texture = Source.texture;
|
texture = Source.texture;
|
||||||
size = Source.partSize;
|
size = Source.partSize;
|
||||||
time = Source.time;
|
time = Source.time;
|
||||||
|
fadeExponent = Source.FadeExponent;
|
||||||
|
|
||||||
originPosition = Vector2.Zero;
|
originPosition = Vector2.Zero;
|
||||||
|
|
||||||
@ -249,6 +256,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
|
|
||||||
shader.Bind();
|
shader.Bind();
|
||||||
shader.GetUniform<float>("g_FadeClock").UpdateValue(ref time);
|
shader.GetUniform<float>("g_FadeClock").UpdateValue(ref time);
|
||||||
|
shader.GetUniform<float>("g_FadeExponent").UpdateValue(ref fadeExponent);
|
||||||
|
|
||||||
texture.TextureGL.Bind();
|
texture.TextureGL.Bind();
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Online;
|
using osu.Game.Online;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -15,6 +16,7 @@ using osuTK.Graphics;
|
|||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Components
|
namespace osu.Game.Tests.Visual.Components
|
||||||
{
|
{
|
||||||
|
[HeadlessTest]
|
||||||
public class TestScenePollingComponent : OsuTestScene
|
public class TestScenePollingComponent : OsuTestScene
|
||||||
{
|
{
|
||||||
private Container pollBox;
|
private Container pollBox;
|
||||||
|
@ -40,6 +40,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
});
|
});
|
||||||
|
|
||||||
AddStep("add local player", () => createLeaderboardScore(playerScore, new User { Username = "You", Id = 3 }, true));
|
AddStep("add local player", () => createLeaderboardScore(playerScore, new User { Username = "You", Id = 3 }, true));
|
||||||
|
AddStep("toggle expanded", () => leaderboard.Expanded.Value = !leaderboard.Expanded.Value);
|
||||||
AddSliderStep("set player score", 50, 5000000, 1222333, v => playerScore.Value = v);
|
AddSliderStep("set player score", 50, 5000000, 1222333, v => playerScore.Value = v);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,19 +84,38 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddStep("add frenzibyte", () => createRandomScore(new User { Username = "frenzibyte", Id = 14210502 }));
|
AddStep("add frenzibyte", () => createRandomScore(new User { Username = "frenzibyte", Id = 14210502 }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMaxHeight()
|
||||||
|
{
|
||||||
|
int playerNumber = 1;
|
||||||
|
AddRepeatStep("add 3 other players", () => createRandomScore(new User { Username = $"Player {playerNumber++}" }), 3);
|
||||||
|
checkHeight(4);
|
||||||
|
|
||||||
|
AddRepeatStep("add 4 other players", () => createRandomScore(new User { Username = $"Player {playerNumber++}" }), 4);
|
||||||
|
checkHeight(8);
|
||||||
|
|
||||||
|
AddRepeatStep("add 4 other players", () => createRandomScore(new User { Username = $"Player {playerNumber++}" }), 4);
|
||||||
|
checkHeight(8);
|
||||||
|
|
||||||
|
void checkHeight(int panelCount)
|
||||||
|
=> AddAssert($"leaderboard height is {panelCount} panels high", () => leaderboard.DrawHeight == (GameplayLeaderboardScore.PANEL_HEIGHT + leaderboard.Spacing) * panelCount);
|
||||||
|
}
|
||||||
|
|
||||||
private void createRandomScore(User user) => createLeaderboardScore(new BindableDouble(RNG.Next(0, 5_000_000)), user);
|
private void createRandomScore(User user) => createLeaderboardScore(new BindableDouble(RNG.Next(0, 5_000_000)), user);
|
||||||
|
|
||||||
private void createLeaderboardScore(BindableDouble score, User user, bool isTracked = false)
|
private void createLeaderboardScore(BindableDouble score, User user, bool isTracked = false)
|
||||||
{
|
{
|
||||||
var leaderboardScore = leaderboard.AddPlayer(user, isTracked);
|
var leaderboardScore = leaderboard.Add(user, isTracked);
|
||||||
leaderboardScore.TotalScore.BindTo(score);
|
leaderboardScore.TotalScore.BindTo(score);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestGameplayLeaderboard : GameplayLeaderboard
|
private class TestGameplayLeaderboard : GameplayLeaderboard
|
||||||
{
|
{
|
||||||
|
public float Spacing => Flow.Spacing.Y;
|
||||||
|
|
||||||
public bool CheckPositionByUsername(string username, int? expectedPosition)
|
public bool CheckPositionByUsername(string username, int? expectedPosition)
|
||||||
{
|
{
|
||||||
var scoreItem = this.FirstOrDefault(i => i.User?.Username == username);
|
var scoreItem = Flow.FirstOrDefault(i => i.User?.Username == username);
|
||||||
|
|
||||||
return scoreItem != null && scoreItem.ScorePosition == expectedPosition;
|
return scoreItem != null && scoreItem.ScorePosition == expectedPosition;
|
||||||
}
|
}
|
||||||
|
42
osu.Game.Tests/Visual/Menus/TestSceneSideOverlays.cs
Normal file
42
osu.Game.Tests/Visual/Menus/TestSceneSideOverlays.cs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Tests.Visual.Navigation;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Menus
|
||||||
|
{
|
||||||
|
public class TestSceneSideOverlays : OsuGameTestScene
|
||||||
|
{
|
||||||
|
[SetUpSteps]
|
||||||
|
public override void SetUpSteps()
|
||||||
|
{
|
||||||
|
base.SetUpSteps();
|
||||||
|
|
||||||
|
AddAssert("no screen offset applied", () => Game.ScreenOffsetContainer.X == 0f);
|
||||||
|
AddUntilStep("wait for overlays", () => Game.Settings.IsLoaded && Game.Notifications.IsLoaded);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestScreenOffsettingOnSettingsOverlay()
|
||||||
|
{
|
||||||
|
AddStep("open settings", () => Game.Settings.Show());
|
||||||
|
AddUntilStep("right screen offset applied", () => Game.ScreenOffsetContainer.X == SettingsPanel.WIDTH * TestOsuGame.SIDE_OVERLAY_OFFSET_RATIO);
|
||||||
|
|
||||||
|
AddStep("hide settings", () => Game.Settings.Hide());
|
||||||
|
AddUntilStep("screen offset removed", () => Game.ScreenOffsetContainer.X == 0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestScreenOffsettingOnNotificationOverlay()
|
||||||
|
{
|
||||||
|
AddStep("open notifications", () => Game.Notifications.Show());
|
||||||
|
AddUntilStep("right screen offset applied", () => Game.ScreenOffsetContainer.X == -NotificationOverlay.WIDTH * TestOsuGame.SIDE_OVERLAY_OFFSET_RATIO);
|
||||||
|
|
||||||
|
AddStep("hide notifications", () => Game.Notifications.Hide());
|
||||||
|
AddUntilStep("screen offset removed", () => Game.ScreenOffsetContainer.X == 0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
168
osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs
Normal file
168
osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
// 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.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Online.Rooms;
|
||||||
|
using osu.Game.Online.Rooms.RoomStatuses;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
using osu.Game.Users;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Multiplayer
|
||||||
|
{
|
||||||
|
public class TestSceneDrawableRoom : OsuTestScene
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private readonly Bindable<Room> selectedRoom = new Bindable<Room>();
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Plum);
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMultipleStatuses()
|
||||||
|
{
|
||||||
|
AddStep("create rooms", () =>
|
||||||
|
{
|
||||||
|
Child = new FillFlowContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Size = new Vector2(0.9f),
|
||||||
|
Spacing = new Vector2(10),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
createDrawableRoom(new Room
|
||||||
|
{
|
||||||
|
Name = { Value = "Flyte's Trash Playlist" },
|
||||||
|
Status = { Value = new RoomStatusOpen() },
|
||||||
|
EndDate = { Value = DateTimeOffset.Now.AddDays(1) },
|
||||||
|
Playlist =
|
||||||
|
{
|
||||||
|
new PlaylistItem
|
||||||
|
{
|
||||||
|
Beatmap =
|
||||||
|
{
|
||||||
|
Value = new TestBeatmap(new OsuRuleset().RulesetInfo)
|
||||||
|
{
|
||||||
|
BeatmapInfo =
|
||||||
|
{
|
||||||
|
StarDifficulty = 2.5
|
||||||
|
}
|
||||||
|
}.BeatmapInfo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
createDrawableRoom(new Room
|
||||||
|
{
|
||||||
|
Name = { Value = "Room 2" },
|
||||||
|
Status = { Value = new RoomStatusPlaying() },
|
||||||
|
EndDate = { Value = DateTimeOffset.Now.AddDays(1) },
|
||||||
|
Playlist =
|
||||||
|
{
|
||||||
|
new PlaylistItem
|
||||||
|
{
|
||||||
|
Beatmap =
|
||||||
|
{
|
||||||
|
Value = new TestBeatmap(new OsuRuleset().RulesetInfo)
|
||||||
|
{
|
||||||
|
BeatmapInfo =
|
||||||
|
{
|
||||||
|
StarDifficulty = 2.5
|
||||||
|
}
|
||||||
|
}.BeatmapInfo,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new PlaylistItem
|
||||||
|
{
|
||||||
|
Beatmap =
|
||||||
|
{
|
||||||
|
Value = new TestBeatmap(new OsuRuleset().RulesetInfo)
|
||||||
|
{
|
||||||
|
BeatmapInfo =
|
||||||
|
{
|
||||||
|
StarDifficulty = 4.5
|
||||||
|
}
|
||||||
|
}.BeatmapInfo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
createDrawableRoom(new Room
|
||||||
|
{
|
||||||
|
Name = { Value = "Room 3" },
|
||||||
|
Status = { Value = new RoomStatusEnded() },
|
||||||
|
EndDate = { Value = DateTimeOffset.Now },
|
||||||
|
}),
|
||||||
|
createDrawableRoom(new Room
|
||||||
|
{
|
||||||
|
Name = { Value = "Room 4 (realtime)" },
|
||||||
|
Status = { Value = new RoomStatusOpen() },
|
||||||
|
Category = { Value = RoomCategory.Realtime },
|
||||||
|
}),
|
||||||
|
createDrawableRoom(new Room
|
||||||
|
{
|
||||||
|
Name = { Value = "Room 4 (spotlight)" },
|
||||||
|
Status = { Value = new RoomStatusOpen() },
|
||||||
|
Category = { Value = RoomCategory.Spotlight },
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestEnableAndDisablePassword()
|
||||||
|
{
|
||||||
|
DrawableRoom drawableRoom = null;
|
||||||
|
Room room = null;
|
||||||
|
|
||||||
|
AddStep("create room", () => Child = drawableRoom = createDrawableRoom(room = new Room
|
||||||
|
{
|
||||||
|
Name = { Value = "Room with password" },
|
||||||
|
Status = { Value = new RoomStatusOpen() },
|
||||||
|
Category = { Value = RoomCategory.Realtime },
|
||||||
|
}));
|
||||||
|
|
||||||
|
AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType<DrawableRoom.PasswordProtectedIcon>().Single().Alpha));
|
||||||
|
|
||||||
|
AddStep("set password", () => room.Password.Value = "password");
|
||||||
|
AddAssert("password icon visible", () => Precision.AlmostEquals(1, drawableRoom.ChildrenOfType<DrawableRoom.PasswordProtectedIcon>().Single().Alpha));
|
||||||
|
|
||||||
|
AddStep("unset password", () => room.Password.Value = string.Empty);
|
||||||
|
AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType<DrawableRoom.PasswordProtectedIcon>().Single().Alpha));
|
||||||
|
}
|
||||||
|
|
||||||
|
private DrawableRoom createDrawableRoom(Room room)
|
||||||
|
{
|
||||||
|
room.Host.Value ??= new User { Username = "peppy", Id = 2 };
|
||||||
|
|
||||||
|
if (room.RecentParticipants.Count == 0)
|
||||||
|
{
|
||||||
|
room.RecentParticipants.AddRange(Enumerable.Range(0, 20).Select(i => new User
|
||||||
|
{
|
||||||
|
Id = i,
|
||||||
|
Username = $"User {i}"
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
var drawableRoom = new DrawableRoom(room) { MatchingFilter = true };
|
||||||
|
drawableRoom.Action = () => drawableRoom.State = drawableRoom.State == SelectionState.Selected ? SelectionState.NotSelected : SelectionState.Selected;
|
||||||
|
|
||||||
|
return drawableRoom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,49 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using NUnit.Framework;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Game.Online.Rooms;
|
|
||||||
using osu.Game.Online.Rooms.RoomStatuses;
|
|
||||||
using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
|
||||||
using osu.Game.Tests.Visual.OnlinePlay;
|
|
||||||
using osu.Game.Users;
|
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Multiplayer
|
|
||||||
{
|
|
||||||
public class TestSceneLoungeRoomInfo : OnlinePlayTestScene
|
|
||||||
{
|
|
||||||
[SetUp]
|
|
||||||
public new void Setup() => Schedule(() =>
|
|
||||||
{
|
|
||||||
SelectedRoom.Value = new Room();
|
|
||||||
|
|
||||||
Child = new RoomInfo
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Width = 500
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestNonSelectedRoom()
|
|
||||||
{
|
|
||||||
AddStep("set null room", () => SelectedRoom.Value.RoomID.Value = null);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestOpenRoom()
|
|
||||||
{
|
|
||||||
AddStep("set open room", () =>
|
|
||||||
{
|
|
||||||
SelectedRoom.Value.RoomID.Value = 0;
|
|
||||||
SelectedRoom.Value.Name.Value = "Room 0";
|
|
||||||
SelectedRoom.Value.Host.Value = new User { Username = "peppy", Id = 2 };
|
|
||||||
SelectedRoom.Value.EndDate.Value = DateTimeOffset.Now.AddMonths(1);
|
|
||||||
SelectedRoom.Value.Status.Value = new RoomStatusOpen();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -6,9 +6,11 @@ using System.Linq;
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
|
using osu.Game.Online.Multiplayer;
|
||||||
using osu.Game.Rulesets.Osu.Scoring;
|
using osu.Game.Rulesets.Osu.Scoring;
|
||||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate;
|
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate;
|
||||||
using osu.Game.Screens.Play.HUD;
|
using osu.Game.Screens.Play.HUD;
|
||||||
|
using osu.Game.Users;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Multiplayer
|
namespace osu.Game.Tests.Visual.Multiplayer
|
||||||
{
|
{
|
||||||
@ -31,7 +33,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
};
|
};
|
||||||
|
|
||||||
foreach (var (userId, _) in clocks)
|
foreach (var (userId, _) in clocks)
|
||||||
|
{
|
||||||
SpectatorClient.StartPlay(userId, 0);
|
SpectatorClient.StartPlay(userId, 0);
|
||||||
|
OnlinePlayDependencies.Client.AddUser(new User { Id = userId });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("create leaderboard", () =>
|
AddStep("create leaderboard", () =>
|
||||||
@ -41,7 +46,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
var scoreProcessor = new OsuScoreProcessor();
|
var scoreProcessor = new OsuScoreProcessor();
|
||||||
scoreProcessor.ApplyBeatmap(playable);
|
scoreProcessor.ApplyBeatmap(playable);
|
||||||
|
|
||||||
LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, clocks.Keys.ToArray()) { Expanded = { Value = true } }, Add);
|
LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, clocks.Keys.Select(id => new MultiplayerRoomUser(id)).ToArray()) { Expanded = { Value = true } }, Add);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("wait for load", () => leaderboard.IsLoaded);
|
AddUntilStep("wait for load", () => leaderboard.IsLoaded);
|
||||||
|
@ -8,6 +8,8 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Online.Multiplayer;
|
||||||
|
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate;
|
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
@ -26,7 +28,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
private MultiSpectatorScreen spectatorScreen;
|
private MultiSpectatorScreen spectatorScreen;
|
||||||
|
|
||||||
private readonly List<int> playingUserIds = new List<int>();
|
private readonly List<MultiplayerRoomUser> playingUsers = new List<MultiplayerRoomUser>();
|
||||||
|
|
||||||
private BeatmapSetInfo importedSet;
|
private BeatmapSetInfo importedSet;
|
||||||
private BeatmapInfo importedBeatmap;
|
private BeatmapInfo importedBeatmap;
|
||||||
@ -41,7 +43,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
}
|
}
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public new void Setup() => Schedule(() => playingUserIds.Clear());
|
public new void Setup() => Schedule(() => playingUsers.Clear());
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestDelayedStart()
|
public void TestDelayedStart()
|
||||||
@ -51,8 +53,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
OnlinePlayDependencies.Client.AddUser(new User { Id = PLAYER_1_ID }, true);
|
OnlinePlayDependencies.Client.AddUser(new User { Id = PLAYER_1_ID }, true);
|
||||||
OnlinePlayDependencies.Client.AddUser(new User { Id = PLAYER_2_ID }, true);
|
OnlinePlayDependencies.Client.AddUser(new User { Id = PLAYER_2_ID }, true);
|
||||||
|
|
||||||
playingUserIds.Add(PLAYER_1_ID);
|
playingUsers.Add(new MultiplayerRoomUser(PLAYER_1_ID));
|
||||||
playingUserIds.Add(PLAYER_2_ID);
|
playingUsers.Add(new MultiplayerRoomUser(PLAYER_2_ID));
|
||||||
});
|
});
|
||||||
|
|
||||||
loadSpectateScreen(false);
|
loadSpectateScreen(false);
|
||||||
@ -78,6 +80,38 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
AddWaitStep("wait a bit", 20);
|
AddWaitStep("wait a bit", 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTeamDisplay()
|
||||||
|
{
|
||||||
|
AddStep("start players", () =>
|
||||||
|
{
|
||||||
|
var player1 = OnlinePlayDependencies.Client.AddUser(new User { Id = PLAYER_1_ID }, true);
|
||||||
|
player1.MatchState = new TeamVersusUserState
|
||||||
|
{
|
||||||
|
TeamID = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
var player2 = OnlinePlayDependencies.Client.AddUser(new User { Id = PLAYER_2_ID }, true);
|
||||||
|
player2.MatchState = new TeamVersusUserState
|
||||||
|
{
|
||||||
|
TeamID = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
SpectatorClient.StartPlay(player1.UserID, importedBeatmapId);
|
||||||
|
SpectatorClient.StartPlay(player2.UserID, importedBeatmapId);
|
||||||
|
|
||||||
|
playingUsers.Add(player1);
|
||||||
|
playingUsers.Add(player2);
|
||||||
|
});
|
||||||
|
|
||||||
|
loadSpectateScreen();
|
||||||
|
|
||||||
|
sendFrames(PLAYER_1_ID, 1000);
|
||||||
|
sendFrames(PLAYER_2_ID, 1000);
|
||||||
|
|
||||||
|
AddWaitStep("wait a bit", 20);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestTimeDoesNotProgressWhileAllPlayersPaused()
|
public void TestTimeDoesNotProgressWhileAllPlayersPaused()
|
||||||
{
|
{
|
||||||
@ -254,7 +288,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
Beatmap.Value = beatmapManager.GetWorkingBeatmap(importedBeatmap);
|
Beatmap.Value = beatmapManager.GetWorkingBeatmap(importedBeatmap);
|
||||||
Ruleset.Value = importedBeatmap.Ruleset;
|
Ruleset.Value = importedBeatmap.Ruleset;
|
||||||
|
|
||||||
LoadScreen(spectatorScreen = new MultiSpectatorScreen(playingUserIds.ToArray()));
|
LoadScreen(spectatorScreen = new MultiSpectatorScreen(playingUsers.ToArray()));
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded && (!waitForPlayerLoad || spectatorScreen.AllPlayersLoaded));
|
AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded && (!waitForPlayerLoad || spectatorScreen.AllPlayersLoaded));
|
||||||
@ -269,7 +303,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
OnlinePlayDependencies.Client.AddUser(new User { Id = id }, true);
|
OnlinePlayDependencies.Client.AddUser(new User { Id = id }, true);
|
||||||
|
|
||||||
SpectatorClient.StartPlay(id, beatmapId ?? importedBeatmapId);
|
SpectatorClient.StartPlay(id, beatmapId ?? importedBeatmapId);
|
||||||
playingUserIds.Add(id);
|
playingUsers.Add(new MultiplayerRoomUser(id));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ using osu.Game.Screens;
|
|||||||
using osu.Game.Screens.OnlinePlay.Components;
|
using osu.Game.Screens.OnlinePlay.Components;
|
||||||
using osu.Game.Screens.OnlinePlay.Lounge;
|
using osu.Game.Screens.OnlinePlay.Lounge;
|
||||||
using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
||||||
using osu.Game.Screens.OnlinePlay.Match.Components;
|
using osu.Game.Screens.OnlinePlay.Match;
|
||||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
||||||
using osu.Game.Tests.Resources;
|
using osu.Game.Tests.Resources;
|
||||||
@ -87,6 +87,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
public void TestEmpty()
|
public void TestEmpty()
|
||||||
{
|
{
|
||||||
// used to test the flow of multiplayer from visual tests.
|
// used to test the flow of multiplayer from visual tests.
|
||||||
|
AddStep("empty step", () => { });
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -312,6 +313,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
InputManager.Click(MouseButton.Left);
|
InputManager.Click(MouseButton.Left);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for spectating user state", () => client.LocalUser?.State == MultiplayerUserState.Spectating);
|
||||||
|
|
||||||
AddStep("start match externally", () => client.StartMatch());
|
AddStep("start match externally", () => client.StartMatch());
|
||||||
|
|
||||||
AddAssert("play not started", () => multiplayerScreen.IsCurrentScreen());
|
AddAssert("play not started", () => multiplayerScreen.IsCurrentScreen());
|
||||||
@ -348,6 +351,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
InputManager.Click(MouseButton.Left);
|
InputManager.Click(MouseButton.Left);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for spectating user state", () => client.LocalUser?.State == MultiplayerUserState.Spectating);
|
||||||
|
|
||||||
AddStep("start match externally", () => client.StartMatch());
|
AddStep("start match externally", () => client.StartMatch());
|
||||||
|
|
||||||
AddStep("restore beatmap", () =>
|
AddStep("restore beatmap", () =>
|
||||||
@ -396,7 +401,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("open mod overlay", () => this.ChildrenOfType<PurpleTriangleButton>().ElementAt(2).TriggerClick());
|
AddStep("open mod overlay", () => this.ChildrenOfType<RoomSubScreen.UserModSelectButton>().Single().TriggerClick());
|
||||||
|
|
||||||
AddStep("invoke on back button", () => multiplayerScreen.OnBackButton());
|
AddStep("invoke on back button", () => multiplayerScreen.OnBackButton());
|
||||||
|
|
||||||
@ -404,8 +409,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
AddAssert("dialog overlay is hidden", () => DialogOverlay.State.Value == Visibility.Hidden);
|
AddAssert("dialog overlay is hidden", () => DialogOverlay.State.Value == Visibility.Hidden);
|
||||||
|
|
||||||
testLeave("lounge tab item", () => this.ChildrenOfType<BreadcrumbControl<IScreen>.BreadcrumbTabItem>().First().TriggerClick());
|
|
||||||
|
|
||||||
testLeave("back button", () => multiplayerScreen.OnBackButton());
|
testLeave("back button", () => multiplayerScreen.OnBackButton());
|
||||||
|
|
||||||
// mimics home button and OS window close
|
// mimics home button and OS window close
|
||||||
@ -423,10 +426,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
private void createRoom(Func<Room> room)
|
private void createRoom(Func<Room> room)
|
||||||
{
|
{
|
||||||
AddStep("open room", () =>
|
AddUntilStep("wait for lounge", () => multiplayerScreen.ChildrenOfType<LoungeSubScreen>().SingleOrDefault()?.IsLoaded == true);
|
||||||
{
|
AddStep("open room", () => multiplayerScreen.ChildrenOfType<LoungeSubScreen>().Single().Open(room()));
|
||||||
multiplayerScreen.OpenNewRoom(room());
|
|
||||||
});
|
|
||||||
|
|
||||||
AddUntilStep("wait for room open", () => this.ChildrenOfType<MultiplayerMatchSubScreen>().FirstOrDefault()?.IsLoaded == true);
|
AddUntilStep("wait for room open", () => this.ChildrenOfType<MultiplayerMatchSubScreen>().FirstOrDefault()?.IsLoaded == true);
|
||||||
AddWaitStep("wait for transition", 2);
|
AddWaitStep("wait for transition", 2);
|
||||||
|
@ -12,6 +12,7 @@ using osu.Framework.Testing;
|
|||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Online.Multiplayer;
|
||||||
using osu.Game.Online.Spectator;
|
using osu.Game.Online.Spectator;
|
||||||
using osu.Game.Replays.Legacy;
|
using osu.Game.Replays.Legacy;
|
||||||
using osu.Game.Rulesets.Osu.Scoring;
|
using osu.Game.Rulesets.Osu.Scoring;
|
||||||
@ -51,12 +52,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
OsuScoreProcessor scoreProcessor;
|
OsuScoreProcessor scoreProcessor;
|
||||||
Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value);
|
Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value);
|
||||||
|
|
||||||
var playable = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value);
|
var playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value);
|
||||||
|
var multiplayerUsers = new List<MultiplayerRoomUser>();
|
||||||
|
|
||||||
foreach (var user in users)
|
foreach (var user in users)
|
||||||
{
|
{
|
||||||
SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0);
|
SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0);
|
||||||
OnlinePlayDependencies.Client.AddUser(new User { Id = user }, true);
|
multiplayerUsers.Add(OnlinePlayDependencies.Client.AddUser(new User { Id = user }, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
@ -64,9 +66,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
scoreProcessor = new OsuScoreProcessor(),
|
scoreProcessor = new OsuScoreProcessor(),
|
||||||
};
|
};
|
||||||
|
|
||||||
scoreProcessor.ApplyBeatmap(playable);
|
scoreProcessor.ApplyBeatmap(playableBeatmap);
|
||||||
|
|
||||||
LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, users.ToArray())
|
LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, multiplayerUsers.ToArray())
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
|
@ -0,0 +1,121 @@
|
|||||||
|
// 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.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Online.Multiplayer;
|
||||||
|
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
|
||||||
|
using osu.Game.Online.Rooms;
|
||||||
|
using osu.Game.Rulesets.Osu.Scoring;
|
||||||
|
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||||
|
using osu.Game.Screens.Play.HUD;
|
||||||
|
using osu.Game.Tests.Visual.OnlinePlay;
|
||||||
|
using osu.Game.Tests.Visual.Spectator;
|
||||||
|
using osu.Game.Users;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Multiplayer
|
||||||
|
{
|
||||||
|
public class TestSceneMultiplayerGameplayLeaderboardTeams : MultiplayerTestScene
|
||||||
|
{
|
||||||
|
private static IEnumerable<int> users => Enumerable.Range(0, 16);
|
||||||
|
|
||||||
|
public new TestSceneMultiplayerGameplayLeaderboard.TestMultiplayerSpectatorClient SpectatorClient =>
|
||||||
|
(TestSceneMultiplayerGameplayLeaderboard.TestMultiplayerSpectatorClient)OnlinePlayDependencies?.SpectatorClient;
|
||||||
|
|
||||||
|
protected override OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new TestDependencies();
|
||||||
|
|
||||||
|
protected class TestDependencies : MultiplayerTestSceneDependencies
|
||||||
|
{
|
||||||
|
protected override TestSpectatorClient CreateSpectatorClient() => new TestSceneMultiplayerGameplayLeaderboard.TestMultiplayerSpectatorClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
private MultiplayerGameplayLeaderboard leaderboard;
|
||||||
|
private GameplayMatchScoreDisplay gameplayScoreDisplay;
|
||||||
|
|
||||||
|
protected override Room CreateRoom()
|
||||||
|
{
|
||||||
|
var room = base.CreateRoom();
|
||||||
|
room.Type.Value = MatchType.TeamVersus;
|
||||||
|
return room;
|
||||||
|
}
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public override void SetUpSteps()
|
||||||
|
{
|
||||||
|
AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = LookupCache.GetUserAsync(1).Result);
|
||||||
|
|
||||||
|
AddStep("create leaderboard", () =>
|
||||||
|
{
|
||||||
|
leaderboard?.Expire();
|
||||||
|
|
||||||
|
OsuScoreProcessor scoreProcessor;
|
||||||
|
Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value);
|
||||||
|
|
||||||
|
var playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value);
|
||||||
|
var multiplayerUsers = new List<MultiplayerRoomUser>();
|
||||||
|
|
||||||
|
foreach (var user in users)
|
||||||
|
{
|
||||||
|
SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0);
|
||||||
|
var roomUser = OnlinePlayDependencies.Client.AddUser(new User { Id = user }, true);
|
||||||
|
|
||||||
|
roomUser.MatchState = new TeamVersusUserState
|
||||||
|
{
|
||||||
|
TeamID = RNG.Next(0, 2)
|
||||||
|
};
|
||||||
|
|
||||||
|
multiplayerUsers.Add(roomUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
scoreProcessor = new OsuScoreProcessor(),
|
||||||
|
};
|
||||||
|
|
||||||
|
scoreProcessor.ApplyBeatmap(playableBeatmap);
|
||||||
|
|
||||||
|
LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, multiplayerUsers.ToArray())
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
}, gameplayLeaderboard =>
|
||||||
|
{
|
||||||
|
LoadComponentAsync(new MatchScoreDisplay
|
||||||
|
{
|
||||||
|
Team1Score = { BindTarget = leaderboard.TeamScores[0] },
|
||||||
|
Team2Score = { BindTarget = leaderboard.TeamScores[1] }
|
||||||
|
}, Add);
|
||||||
|
|
||||||
|
LoadComponentAsync(gameplayScoreDisplay = new GameplayMatchScoreDisplay
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomCentre,
|
||||||
|
Origin = Anchor.BottomCentre,
|
||||||
|
Team1Score = { BindTarget = leaderboard.TeamScores[0] },
|
||||||
|
Team2Score = { BindTarget = leaderboard.TeamScores[1] }
|
||||||
|
}, Add);
|
||||||
|
|
||||||
|
Add(gameplayLeaderboard);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for load", () => leaderboard.IsLoaded);
|
||||||
|
AddUntilStep("wait for user population", () => Client.CurrentMatchPlayingUserIds.Count > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestScoreUpdates()
|
||||||
|
{
|
||||||
|
AddRepeatStep("update state", () => SpectatorClient.RandomlyUpdateState(), 100);
|
||||||
|
AddToggleStep("switch compact mode", expanded =>
|
||||||
|
{
|
||||||
|
leaderboard.Expanded.Value = expanded;
|
||||||
|
gameplayScoreDisplay.Expanded.Value = expanded;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -129,6 +129,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
InputManager.Click(MouseButton.Left);
|
InputManager.Click(MouseButton.Left);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for spectating user state", () => Client.LocalUser?.State == MultiplayerUserState.Spectating);
|
||||||
|
|
||||||
AddUntilStep("wait for ready button to be enabled", () => this.ChildrenOfType<MultiplayerReadyButton>().Single().ChildrenOfType<ReadyButton>().Single().Enabled.Value);
|
AddUntilStep("wait for ready button to be enabled", () => this.ChildrenOfType<MultiplayerReadyButton>().Single().ChildrenOfType<ReadyButton>().Single().Enabled.Value);
|
||||||
|
|
||||||
AddStep("click ready button", () =>
|
AddStep("click ready button", () =>
|
||||||
|
@ -155,6 +155,42 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
AddUntilStep("second user crown visible", () => this.ChildrenOfType<ParticipantPanel>().ElementAt(1).ChildrenOfType<SpriteIcon>().First().Alpha == 1);
|
AddUntilStep("second user crown visible", () => this.ChildrenOfType<ParticipantPanel>().ElementAt(1).ChildrenOfType<SpriteIcon>().First().Alpha == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestKickButtonOnlyPresentWhenHost()
|
||||||
|
{
|
||||||
|
AddStep("add user", () => Client.AddUser(new User
|
||||||
|
{
|
||||||
|
Id = 3,
|
||||||
|
Username = "Second",
|
||||||
|
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
||||||
|
}));
|
||||||
|
|
||||||
|
AddUntilStep("kick buttons visible", () => this.ChildrenOfType<ParticipantPanel.KickButton>().Count(d => d.IsPresent) == 1);
|
||||||
|
|
||||||
|
AddStep("make second user host", () => Client.TransferHost(3));
|
||||||
|
|
||||||
|
AddUntilStep("kick buttons not visible", () => this.ChildrenOfType<ParticipantPanel.KickButton>().Count(d => d.IsPresent) == 0);
|
||||||
|
|
||||||
|
AddStep("make local user host again", () => Client.TransferHost(API.LocalUser.Value.Id));
|
||||||
|
|
||||||
|
AddUntilStep("kick buttons visible", () => this.ChildrenOfType<ParticipantPanel.KickButton>().Count(d => d.IsPresent) == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestKickButtonKicks()
|
||||||
|
{
|
||||||
|
AddStep("add user", () => Client.AddUser(new User
|
||||||
|
{
|
||||||
|
Id = 3,
|
||||||
|
Username = "Second",
|
||||||
|
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
||||||
|
}));
|
||||||
|
|
||||||
|
AddStep("kick second user", () => this.ChildrenOfType<ParticipantPanel.KickButton>().Single(d => d.IsPresent).TriggerClick());
|
||||||
|
|
||||||
|
AddAssert("second user kicked", () => Client.Room?.Users.Single().UserID == API.LocalUser.Value.Id);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestManyUsers()
|
public void TestManyUsers()
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,51 @@
|
|||||||
|
// 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.Game.Online.Rooms;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||||
|
using osu.Game.Users;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Multiplayer
|
||||||
|
{
|
||||||
|
public class TestSceneMultiplayerResults : ScreenTestScene
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestDisplayResults()
|
||||||
|
{
|
||||||
|
MultiplayerResultsScreen screen = null;
|
||||||
|
|
||||||
|
AddStep("show results screen", () =>
|
||||||
|
{
|
||||||
|
var rulesetInfo = new OsuRuleset().RulesetInfo;
|
||||||
|
var beatmapInfo = CreateBeatmap(rulesetInfo).BeatmapInfo;
|
||||||
|
|
||||||
|
var score = new ScoreInfo
|
||||||
|
{
|
||||||
|
Rank = ScoreRank.B,
|
||||||
|
TotalScore = 987654,
|
||||||
|
Accuracy = 0.8,
|
||||||
|
MaxCombo = 500,
|
||||||
|
Combo = 250,
|
||||||
|
Beatmap = beatmapInfo,
|
||||||
|
User = new User { Username = "Test user" },
|
||||||
|
Date = DateTimeOffset.Now,
|
||||||
|
OnlineScoreID = 12345,
|
||||||
|
Ruleset = rulesetInfo,
|
||||||
|
};
|
||||||
|
|
||||||
|
PlaylistItem playlistItem = new PlaylistItem
|
||||||
|
{
|
||||||
|
BeatmapID = beatmapInfo.ID,
|
||||||
|
};
|
||||||
|
|
||||||
|
Stack.Push(screen = new MultiplayerResultsScreen(score, 1, playlistItem));
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for loaded", () => screen.IsLoaded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Game.Online.Rooms;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||||
|
using osu.Game.Users;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Multiplayer
|
||||||
|
{
|
||||||
|
public class TestSceneMultiplayerTeamResults : ScreenTestScene
|
||||||
|
{
|
||||||
|
[TestCase(7483253, 1048576)]
|
||||||
|
[TestCase(1048576, 7483253)]
|
||||||
|
[TestCase(1048576, 1048576)]
|
||||||
|
public void TestDisplayTeamResults(int team1Score, int team2Score)
|
||||||
|
{
|
||||||
|
MultiplayerResultsScreen screen = null;
|
||||||
|
|
||||||
|
AddStep("show results screen", () =>
|
||||||
|
{
|
||||||
|
var rulesetInfo = new OsuRuleset().RulesetInfo;
|
||||||
|
var beatmapInfo = CreateBeatmap(rulesetInfo).BeatmapInfo;
|
||||||
|
|
||||||
|
var score = new ScoreInfo
|
||||||
|
{
|
||||||
|
Rank = ScoreRank.B,
|
||||||
|
TotalScore = 987654,
|
||||||
|
Accuracy = 0.8,
|
||||||
|
MaxCombo = 500,
|
||||||
|
Combo = 250,
|
||||||
|
Beatmap = beatmapInfo,
|
||||||
|
User = new User { Username = "Test user" },
|
||||||
|
Date = DateTimeOffset.Now,
|
||||||
|
OnlineScoreID = 12345,
|
||||||
|
Ruleset = rulesetInfo,
|
||||||
|
};
|
||||||
|
|
||||||
|
PlaylistItem playlistItem = new PlaylistItem
|
||||||
|
{
|
||||||
|
BeatmapID = beatmapInfo.ID,
|
||||||
|
};
|
||||||
|
|
||||||
|
SortedDictionary<int, BindableInt> teamScores = new SortedDictionary<int, BindableInt>
|
||||||
|
{
|
||||||
|
{ 0, new BindableInt(team1Score) },
|
||||||
|
{ 1, new BindableInt(team2Score) }
|
||||||
|
};
|
||||||
|
|
||||||
|
Stack.Push(screen = new MultiplayerTeamResultsScreen(score, 1, playlistItem, teamScores));
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for loaded", () => screen.IsLoaded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
95
osu.Game.Tests/Visual/Multiplayer/TestSceneRankRangePill.cs
Normal file
95
osu.Game.Tests/Visual/Multiplayer/TestSceneRankRangePill.cs
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
||||||
|
using osu.Game.Users;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Multiplayer
|
||||||
|
{
|
||||||
|
public class TestSceneRankRangePill : MultiplayerTestScene
|
||||||
|
{
|
||||||
|
[SetUp]
|
||||||
|
public new void Setup() => Schedule(() =>
|
||||||
|
{
|
||||||
|
Child = new RankRangePill
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSingleUser()
|
||||||
|
{
|
||||||
|
AddStep("add user", () =>
|
||||||
|
{
|
||||||
|
Client.AddUser(new User
|
||||||
|
{
|
||||||
|
Id = 2,
|
||||||
|
Statistics = { GlobalRank = 1234 }
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove the local user so only the one above is displayed.
|
||||||
|
Client.RemoveUser(API.LocalUser.Value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMultipleUsers()
|
||||||
|
{
|
||||||
|
AddStep("add users", () =>
|
||||||
|
{
|
||||||
|
Client.AddUser(new User
|
||||||
|
{
|
||||||
|
Id = 2,
|
||||||
|
Statistics = { GlobalRank = 1234 }
|
||||||
|
});
|
||||||
|
|
||||||
|
Client.AddUser(new User
|
||||||
|
{
|
||||||
|
Id = 3,
|
||||||
|
Statistics = { GlobalRank = 3333 }
|
||||||
|
});
|
||||||
|
|
||||||
|
Client.AddUser(new User
|
||||||
|
{
|
||||||
|
Id = 4,
|
||||||
|
Statistics = { GlobalRank = 4321 }
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove the local user so only the ones above are displayed.
|
||||||
|
Client.RemoveUser(API.LocalUser.Value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(1, 10)]
|
||||||
|
[TestCase(10, 100)]
|
||||||
|
[TestCase(100, 1000)]
|
||||||
|
[TestCase(1000, 10000)]
|
||||||
|
[TestCase(10000, 100000)]
|
||||||
|
[TestCase(100000, 1000000)]
|
||||||
|
[TestCase(1000000, 10000000)]
|
||||||
|
public void TestRange(int min, int max)
|
||||||
|
{
|
||||||
|
AddStep("add users", () =>
|
||||||
|
{
|
||||||
|
Client.AddUser(new User
|
||||||
|
{
|
||||||
|
Id = 2,
|
||||||
|
Statistics = { GlobalRank = min }
|
||||||
|
});
|
||||||
|
|
||||||
|
Client.AddUser(new User
|
||||||
|
{
|
||||||
|
Id = 3,
|
||||||
|
Statistics = { GlobalRank = max }
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove the local user so only the ones above are displayed.
|
||||||
|
Client.RemoveUser(API.LocalUser.Value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,143 @@
|
|||||||
|
// 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.Testing;
|
||||||
|
using osu.Game.Online.Rooms;
|
||||||
|
using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
||||||
|
using osu.Game.Tests.Visual.OnlinePlay;
|
||||||
|
using osu.Game.Users;
|
||||||
|
using osu.Game.Users.Drawables;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Multiplayer
|
||||||
|
{
|
||||||
|
public class TestSceneRecentParticipantsList : OnlinePlayTestScene
|
||||||
|
{
|
||||||
|
private RecentParticipantsList list;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public new void Setup() => Schedule(() =>
|
||||||
|
{
|
||||||
|
SelectedRoom.Value = new Room { Name = { Value = "test room" } };
|
||||||
|
|
||||||
|
Child = list = new RecentParticipantsList
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
NumberOfCircles = 4
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCircleCountNearLimit()
|
||||||
|
{
|
||||||
|
AddStep("add 8 users", () =>
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 8; i++)
|
||||||
|
addUser(i);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("set 8 circles", () => list.NumberOfCircles = 8);
|
||||||
|
AddAssert("0 hidden users", () => list.ChildrenOfType<RecentParticipantsList.HiddenUserCount>().Single().Count == 0);
|
||||||
|
|
||||||
|
AddStep("add one more user", () => addUser(9));
|
||||||
|
AddAssert("2 hidden users", () => list.ChildrenOfType<RecentParticipantsList.HiddenUserCount>().Single().Count == 2);
|
||||||
|
|
||||||
|
AddStep("remove first user", () => removeUserAt(0));
|
||||||
|
AddAssert("0 hidden users", () => list.ChildrenOfType<RecentParticipantsList.HiddenUserCount>().Single().Count == 0);
|
||||||
|
|
||||||
|
AddStep("add one more user", () => addUser(9));
|
||||||
|
AddAssert("2 hidden users", () => list.ChildrenOfType<RecentParticipantsList.HiddenUserCount>().Single().Count == 2);
|
||||||
|
|
||||||
|
AddStep("remove last user", () => removeUserAt(8));
|
||||||
|
AddAssert("0 hidden users", () => list.ChildrenOfType<RecentParticipantsList.HiddenUserCount>().Single().Count == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHiddenUsersBecomeDisplayed()
|
||||||
|
{
|
||||||
|
AddStep("add 8 users", () =>
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 8; i++)
|
||||||
|
addUser(i);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("set 3 circles", () => list.NumberOfCircles = 3);
|
||||||
|
|
||||||
|
for (int i = 0; i < 8; i++)
|
||||||
|
{
|
||||||
|
AddStep("remove user", () => removeUserAt(0));
|
||||||
|
int remainingUsers = 7 - i;
|
||||||
|
|
||||||
|
int displayedUsers = remainingUsers > 3 ? 2 : remainingUsers;
|
||||||
|
AddAssert($"{displayedUsers} avatars displayed", () => list.ChildrenOfType<UpdateableAvatar>().Count() == displayedUsers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCircleCount()
|
||||||
|
{
|
||||||
|
AddStep("add 50 users", () =>
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 50; i++)
|
||||||
|
addUser(i);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("set 3 circles", () => list.NumberOfCircles = 3);
|
||||||
|
AddAssert("2 users displayed", () => list.ChildrenOfType<UpdateableAvatar>().Count() == 2);
|
||||||
|
AddAssert("48 hidden users", () => list.ChildrenOfType<RecentParticipantsList.HiddenUserCount>().Single().Count == 48);
|
||||||
|
|
||||||
|
AddStep("set 10 circles", () => list.NumberOfCircles = 10);
|
||||||
|
AddAssert("9 users displayed", () => list.ChildrenOfType<UpdateableAvatar>().Count() == 9);
|
||||||
|
AddAssert("41 hidden users", () => list.ChildrenOfType<RecentParticipantsList.HiddenUserCount>().Single().Count == 41);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAddAndRemoveUsers()
|
||||||
|
{
|
||||||
|
AddStep("add 50 users", () =>
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 50; i++)
|
||||||
|
addUser(i);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("remove from start", () => removeUserAt(0));
|
||||||
|
AddAssert("3 circles displayed", () => list.ChildrenOfType<UpdateableAvatar>().Count() == 3);
|
||||||
|
AddAssert("46 hidden users", () => list.ChildrenOfType<RecentParticipantsList.HiddenUserCount>().Single().Count == 46);
|
||||||
|
|
||||||
|
AddStep("remove from end", () => removeUserAt(SelectedRoom.Value.RecentParticipants.Count - 1));
|
||||||
|
AddAssert("3 circles displayed", () => list.ChildrenOfType<UpdateableAvatar>().Count() == 3);
|
||||||
|
AddAssert("45 hidden users", () => list.ChildrenOfType<RecentParticipantsList.HiddenUserCount>().Single().Count == 45);
|
||||||
|
|
||||||
|
AddRepeatStep("remove 45 users", () => removeUserAt(0), 45);
|
||||||
|
AddAssert("3 circles displayed", () => list.ChildrenOfType<UpdateableAvatar>().Count() == 3);
|
||||||
|
AddAssert("0 hidden users", () => list.ChildrenOfType<RecentParticipantsList.HiddenUserCount>().Single().Count == 0);
|
||||||
|
AddAssert("hidden users bubble hidden", () => list.ChildrenOfType<RecentParticipantsList.HiddenUserCount>().Single().Alpha < 0.5f);
|
||||||
|
|
||||||
|
AddStep("remove another user", () => removeUserAt(0));
|
||||||
|
AddAssert("2 circles displayed", () => list.ChildrenOfType<UpdateableAvatar>().Count() == 2);
|
||||||
|
AddAssert("0 hidden users", () => list.ChildrenOfType<RecentParticipantsList.HiddenUserCount>().Single().Count == 0);
|
||||||
|
|
||||||
|
AddRepeatStep("remove the remaining two users", () => removeUserAt(0), 2);
|
||||||
|
AddAssert("0 circles displayed", () => !list.ChildrenOfType<UpdateableAvatar>().Any());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addUser(int id)
|
||||||
|
{
|
||||||
|
SelectedRoom.Value.RecentParticipants.Add(new User
|
||||||
|
{
|
||||||
|
Id = id,
|
||||||
|
Username = $"User {id}"
|
||||||
|
});
|
||||||
|
SelectedRoom.Value.ParticipantCount.Value++;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeUserAt(int index)
|
||||||
|
{
|
||||||
|
SelectedRoom.Value.RecentParticipants.RemoveAt(index);
|
||||||
|
SelectedRoom.Value.ParticipantCount.Value--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,81 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using NUnit.Framework;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Testing;
|
|
||||||
using osu.Framework.Utils;
|
|
||||||
using osu.Game.Online.Rooms;
|
|
||||||
using osu.Game.Online.Rooms.RoomStatuses;
|
|
||||||
using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Multiplayer
|
|
||||||
{
|
|
||||||
public class TestSceneRoomStatus : OsuTestScene
|
|
||||||
{
|
|
||||||
[Test]
|
|
||||||
public void TestMultipleStatuses()
|
|
||||||
{
|
|
||||||
AddStep("create rooms", () =>
|
|
||||||
{
|
|
||||||
Child = new FillFlowContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Width = 0.5f,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new DrawableRoom(new Room
|
|
||||||
{
|
|
||||||
Name = { Value = "Open - ending in 1 day" },
|
|
||||||
Status = { Value = new RoomStatusOpen() },
|
|
||||||
EndDate = { Value = DateTimeOffset.Now.AddDays(1) }
|
|
||||||
}) { MatchingFilter = true },
|
|
||||||
new DrawableRoom(new Room
|
|
||||||
{
|
|
||||||
Name = { Value = "Playing - ending in 1 day" },
|
|
||||||
Status = { Value = new RoomStatusPlaying() },
|
|
||||||
EndDate = { Value = DateTimeOffset.Now.AddDays(1) }
|
|
||||||
}) { MatchingFilter = true },
|
|
||||||
new DrawableRoom(new Room
|
|
||||||
{
|
|
||||||
Name = { Value = "Ended" },
|
|
||||||
Status = { Value = new RoomStatusEnded() },
|
|
||||||
EndDate = { Value = DateTimeOffset.Now }
|
|
||||||
}) { MatchingFilter = true },
|
|
||||||
new DrawableRoom(new Room
|
|
||||||
{
|
|
||||||
Name = { Value = "Open" },
|
|
||||||
Status = { Value = new RoomStatusOpen() },
|
|
||||||
Category = { Value = RoomCategory.Realtime }
|
|
||||||
}) { MatchingFilter = true },
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestEnableAndDisablePassword()
|
|
||||||
{
|
|
||||||
DrawableRoom drawableRoom = null;
|
|
||||||
Room room = null;
|
|
||||||
|
|
||||||
AddStep("create room", () => Child = drawableRoom = new DrawableRoom(room = new Room
|
|
||||||
{
|
|
||||||
Name = { Value = "Room with password" },
|
|
||||||
Status = { Value = new RoomStatusOpen() },
|
|
||||||
Category = { Value = RoomCategory.Realtime },
|
|
||||||
}) { MatchingFilter = true });
|
|
||||||
|
|
||||||
AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType<DrawableRoom.PasswordProtectedIcon>().Single().Alpha));
|
|
||||||
|
|
||||||
AddStep("set password", () => room.Password.Value = "password");
|
|
||||||
AddAssert("password icon visible", () => Precision.AlmostEquals(1, drawableRoom.ChildrenOfType<DrawableRoom.PasswordProtectedIcon>().Single().Alpha));
|
|
||||||
|
|
||||||
AddStep("unset password", () => room.Password.Value = string.Empty);
|
|
||||||
AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType<DrawableRoom.PasswordProtectedIcon>().Single().Alpha));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -17,6 +17,7 @@ using osu.Game.Rulesets;
|
|||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Screens;
|
using osu.Game.Screens;
|
||||||
using osu.Game.Screens.OnlinePlay.Components;
|
using osu.Game.Screens.OnlinePlay.Components;
|
||||||
|
using osu.Game.Screens.OnlinePlay.Lounge;
|
||||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
||||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Participants;
|
using osu.Game.Screens.OnlinePlay.Multiplayer.Participants;
|
||||||
@ -150,10 +151,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
private void createRoom(Func<Room> room)
|
private void createRoom(Func<Room> room)
|
||||||
{
|
{
|
||||||
AddStep("open room", () =>
|
AddStep("open room", () => multiplayerScreen.ChildrenOfType<LoungeSubScreen>().Single().Open(room()));
|
||||||
{
|
|
||||||
multiplayerScreen.OpenNewRoom(room());
|
|
||||||
});
|
|
||||||
|
|
||||||
AddUntilStep("wait for room open", () => this.ChildrenOfType<MultiplayerMatchSubScreen>().FirstOrDefault()?.IsLoaded == true);
|
AddUntilStep("wait for room open", () => this.ChildrenOfType<MultiplayerMatchSubScreen>().FirstOrDefault()?.IsLoaded == true);
|
||||||
AddWaitStep("wait for transition", 2);
|
AddWaitStep("wait for transition", 2);
|
||||||
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
@ -95,6 +96,8 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
|
|
||||||
public class TestOsuGame : OsuGame
|
public class TestOsuGame : OsuGame
|
||||||
{
|
{
|
||||||
|
public new const float SIDE_OVERLAY_OFFSET_RATIO = OsuGame.SIDE_OVERLAY_OFFSET_RATIO;
|
||||||
|
|
||||||
public new ScreenStack ScreenStack => base.ScreenStack;
|
public new ScreenStack ScreenStack => base.ScreenStack;
|
||||||
|
|
||||||
public new BackButton BackButton => base.BackButton;
|
public new BackButton BackButton => base.BackButton;
|
||||||
@ -103,7 +106,11 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
|
|
||||||
public new ScoreManager ScoreManager => base.ScoreManager;
|
public new ScoreManager ScoreManager => base.ScoreManager;
|
||||||
|
|
||||||
public new SettingsPanel Settings => base.Settings;
|
public new Container ScreenOffsetContainer => base.ScreenOffsetContainer;
|
||||||
|
|
||||||
|
public new SettingsOverlay Settings => base.Settings;
|
||||||
|
|
||||||
|
public new NotificationOverlay Notifications => base.Notifications;
|
||||||
|
|
||||||
public new MusicController MusicController => base.MusicController;
|
public new MusicController MusicController => base.MusicController;
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ using osu.Game.Overlays.Toolbar;
|
|||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Screens.OnlinePlay.Components;
|
using osu.Game.Screens.OnlinePlay.Components;
|
||||||
|
using osu.Game.Screens.OnlinePlay.Lounge;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Screens.Ranking;
|
using osu.Game.Screens.Ranking;
|
||||||
using osu.Game.Screens.Select;
|
using osu.Game.Screens.Select;
|
||||||
@ -316,7 +317,8 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
|
|
||||||
PushAndConfirm(() => multiplayer = new TestMultiplayer());
|
PushAndConfirm(() => multiplayer = new TestMultiplayer());
|
||||||
|
|
||||||
AddStep("open room", () => multiplayer.OpenNewRoom());
|
AddUntilStep("wait for lounge", () => multiplayer.ChildrenOfType<LoungeSubScreen>().SingleOrDefault()?.IsLoaded == true);
|
||||||
|
AddStep("open room", () => multiplayer.ChildrenOfType<LoungeSubScreen>().Single().Open());
|
||||||
AddStep("press back button", () => Game.ChildrenOfType<BackButton>().First().Action());
|
AddStep("press back button", () => Game.ChildrenOfType<BackButton>().First().Action());
|
||||||
AddWaitStep("wait two frames", 2);
|
AddWaitStep("wait two frames", 2);
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests;
|
using osu.Game.Online.API.Requests;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
@ -95,9 +96,11 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
AddAssert(@"no stream selected", () => changelog.Header.Streams.Current.Value == null);
|
AddAssert(@"no stream selected", () => changelog.Header.Streams.Current.Value == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[TestCase(false)]
|
||||||
public void ShowWithBuild()
|
[TestCase(true)]
|
||||||
|
public void ShowWithBuild(bool isSupporter)
|
||||||
{
|
{
|
||||||
|
AddStep(@"set supporter", () => dummyAPI.LocalUser.Value.IsSupporter = isSupporter);
|
||||||
showBuild(() => new APIChangelogBuild
|
showBuild(() => new APIChangelogBuild
|
||||||
{
|
{
|
||||||
Version = "2018.712.0",
|
Version = "2018.712.0",
|
||||||
@ -155,6 +158,8 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
AddUntilStep(@"wait for streams", () => changelog.Streams?.Count > 0);
|
AddUntilStep(@"wait for streams", () => changelog.Streams?.Count > 0);
|
||||||
AddAssert(@"correct build displayed", () => changelog.Current.Value.Version == "2018.712.0");
|
AddAssert(@"correct build displayed", () => changelog.Current.Value.Version == "2018.712.0");
|
||||||
AddAssert(@"correct stream selected", () => changelog.Header.Streams.Current.Value.Id == 5);
|
AddAssert(@"correct stream selected", () => changelog.Header.Streams.Current.Value.Id == 5);
|
||||||
|
AddUntilStep(@"wait for content load", () => changelog.ChildrenOfType<ChangelogSupporterPromo>().Any());
|
||||||
|
AddAssert(@"supporter promo showed", () => changelog.ChildrenOfType<ChangelogSupporterPromo>().First().Alpha == (isSupporter ? 0 : 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.Changelog;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Online
|
||||||
|
{
|
||||||
|
public class TestSceneChangelogSupporterPromo : OsuTestScene
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
||||||
|
|
||||||
|
public TestSceneChangelogSupporterPromo()
|
||||||
|
{
|
||||||
|
Child = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = colourProvider.Background4,
|
||||||
|
},
|
||||||
|
new ChangelogSupporterPromo(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,84 +3,52 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Overlays.Comments;
|
using osu.Game.Overlays.Comments;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
using osu.Game.Graphics.UserInterface;
|
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osuTK;
|
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using NUnit.Framework;
|
using osu.Framework.Testing;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Online
|
namespace osu.Game.Tests.Visual.Online
|
||||||
{
|
{
|
||||||
public class TestSceneCommentsPage : OsuTestScene
|
public class TestSceneOfflineCommentsContainer : OsuTestScene
|
||||||
{
|
{
|
||||||
[Cached]
|
[Cached]
|
||||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
|
||||||
|
|
||||||
private readonly BindableBool showDeleted = new BindableBool();
|
private TestCommentsContainer comments;
|
||||||
private readonly Container content;
|
|
||||||
|
|
||||||
private TestCommentsPage commentsPage;
|
[SetUp]
|
||||||
|
public void SetUp() => Schedule(() =>
|
||||||
public TestSceneCommentsPage()
|
|
||||||
{
|
{
|
||||||
Add(new FillFlowContainer
|
Clear();
|
||||||
|
Add(new BasicScrollContainer
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Both,
|
||||||
RelativeSizeAxes = Axes.X,
|
Child = comments = new TestCommentsContainer()
|
||||||
Direction = FillDirection.Vertical,
|
|
||||||
Spacing = new Vector2(0, 10),
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new Container
|
|
||||||
{
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
Width = 200,
|
|
||||||
Child = new OsuCheckbox
|
|
||||||
{
|
|
||||||
Current = showDeleted,
|
|
||||||
LabelText = @"Show Deleted"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
content = new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestAppendDuplicatedComment()
|
public void TestAppendDuplicatedComment()
|
||||||
{
|
{
|
||||||
AddStep("Create page", () => createPage(getCommentBundle()));
|
AddStep("Add comment bundle", () => comments.ShowComments(getCommentBundle()));
|
||||||
AddAssert("Dictionary length is 10", () => commentsPage?.DictionaryLength == 10);
|
AddUntilStep("Dictionary length is 10", () => comments.DictionaryLength == 10);
|
||||||
AddStep("Append existing comment", () => commentsPage?.AppendComments(getCommentSubBundle()));
|
AddStep("Append existing comment", () => comments.AppendComments(getCommentSubBundle()));
|
||||||
AddAssert("Dictionary length is 10", () => commentsPage?.DictionaryLength == 10);
|
AddAssert("Dictionary length is 10", () => comments.DictionaryLength == 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestEmptyBundle()
|
public void TestLocalCommentBundle()
|
||||||
{
|
{
|
||||||
AddStep("Create page", () => createPage(getEmptyCommentBundle()));
|
AddStep("Add comment bundle", () => comments.ShowComments(getCommentBundle()));
|
||||||
AddAssert("Dictionary length is 0", () => commentsPage?.DictionaryLength == 0);
|
AddStep("Add empty comment bundle", () => comments.ShowComments(getEmptyCommentBundle()));
|
||||||
}
|
|
||||||
|
|
||||||
private void createPage(CommentBundle commentBundle)
|
|
||||||
{
|
|
||||||
commentsPage = null;
|
|
||||||
content.Clear();
|
|
||||||
content.Add(commentsPage = new TestCommentsPage(commentBundle)
|
|
||||||
{
|
|
||||||
ShowDeleted = { BindTarget = showDeleted }
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private CommentBundle getEmptyCommentBundle() => new CommentBundle
|
private CommentBundle getEmptyCommentBundle() => new CommentBundle
|
||||||
@ -193,6 +161,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
Username = "Good_Admin"
|
Username = "Good_Admin"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Total = 10
|
||||||
};
|
};
|
||||||
|
|
||||||
private CommentBundle getCommentSubBundle() => new CommentBundle
|
private CommentBundle getCommentSubBundle() => new CommentBundle
|
||||||
@ -211,16 +180,18 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
IncludedComments = new List<Comment>(),
|
IncludedComments = new List<Comment>(),
|
||||||
};
|
};
|
||||||
|
|
||||||
private class TestCommentsPage : CommentsPage
|
private class TestCommentsContainer : CommentsContainer
|
||||||
{
|
{
|
||||||
public TestCommentsPage(CommentBundle commentBundle)
|
|
||||||
: base(commentBundle)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public new void AppendComments([NotNull] CommentBundle bundle) => base.AppendComments(bundle);
|
public new void AppendComments([NotNull] CommentBundle bundle) => base.AppendComments(bundle);
|
||||||
|
|
||||||
public int DictionaryLength => CommentDictionary.Count;
|
public int DictionaryLength => CommentDictionary.Count;
|
||||||
|
|
||||||
|
public void ShowComments(CommentBundle bundle)
|
||||||
|
{
|
||||||
|
this.ChildrenOfType<TotalCommentsCounter>().Single().Current.Value = 0;
|
||||||
|
ClearComments();
|
||||||
|
OnSuccess(bundle);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,23 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Playlists
|
|
||||||
{
|
|
||||||
public class TestScenePlaylistsFilterControl : OsuTestScene
|
|
||||||
{
|
|
||||||
public TestScenePlaylistsFilterControl()
|
|
||||||
{
|
|
||||||
Child = new PlaylistsFilterControl
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Width = 0.7f,
|
|
||||||
Height = 80,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -62,6 +62,24 @@ namespace osu.Game.Tests.Visual.Playlists
|
|||||||
AddUntilStep("last room is not masked", () => checkRoomVisible(roomsContainer.Rooms[^1]));
|
AddUntilStep("last room is not masked", () => checkRoomVisible(roomsContainer.Rooms[^1]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestEnteringRoomTakesLeaseOnSelection()
|
||||||
|
{
|
||||||
|
AddStep("add rooms", () => RoomManager.AddRooms(1));
|
||||||
|
|
||||||
|
AddAssert("selected room is not disabled", () => !OnlinePlayDependencies.SelectedRoom.Disabled);
|
||||||
|
|
||||||
|
AddStep("select room", () => roomsContainer.Rooms[0].TriggerClick());
|
||||||
|
AddAssert("selected room is non-null", () => OnlinePlayDependencies.SelectedRoom.Value != null);
|
||||||
|
|
||||||
|
AddStep("enter room", () => roomsContainer.Rooms[0].TriggerClick());
|
||||||
|
|
||||||
|
AddUntilStep("wait for match load", () => Stack.CurrentScreen is PlaylistsRoomSubScreen);
|
||||||
|
|
||||||
|
AddAssert("selected room is non-null", () => OnlinePlayDependencies.SelectedRoom.Value != null);
|
||||||
|
AddAssert("selected room is disabled", () => OnlinePlayDependencies.SelectedRoom.Disabled);
|
||||||
|
}
|
||||||
|
|
||||||
private bool checkRoomVisible(DrawableRoom room) =>
|
private bool checkRoomVisible(DrawableRoom room) =>
|
||||||
loungeScreen.ChildrenOfType<OsuScrollContainer>().First().ScreenSpaceDrawQuad
|
loungeScreen.ChildrenOfType<OsuScrollContainer>().First().ScreenSpaceDrawQuad
|
||||||
.Contains(room.ScreenSpaceDrawQuad.Centre);
|
.Contains(room.ScreenSpaceDrawQuad.Centre);
|
||||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Settings
|
|||||||
new TabletSettings(tabletHandler)
|
new TabletSettings(tabletHandler)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.None,
|
RelativeSizeAxes = Axes.None,
|
||||||
Width = SettingsPanel.WIDTH,
|
Width = SettingsPanel.PANEL_WIDTH,
|
||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
Origin = Anchor.TopCentre,
|
Origin = Anchor.TopCentre,
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ namespace osu.Game.Tournament.Tests.Components
|
|||||||
|
|
||||||
public TestSceneMatchScoreDisplay()
|
public TestSceneMatchScoreDisplay()
|
||||||
{
|
{
|
||||||
Add(new MatchScoreDisplay
|
Add(new TournamentMatchScoreDisplay
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
|
@ -16,7 +16,8 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Tournament.Screens.Gameplay.Components
|
namespace osu.Game.Tournament.Screens.Gameplay.Components
|
||||||
{
|
{
|
||||||
public class MatchScoreDisplay : CompositeDrawable
|
// TODO: Update to derive from osu-side class?
|
||||||
|
public class TournamentMatchScoreDisplay : CompositeDrawable
|
||||||
{
|
{
|
||||||
private const float bar_height = 18;
|
private const float bar_height = 18;
|
||||||
|
|
||||||
@ -29,7 +30,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
|
|||||||
private readonly Drawable score1Bar;
|
private readonly Drawable score1Bar;
|
||||||
private readonly Drawable score2Bar;
|
private readonly Drawable score2Bar;
|
||||||
|
|
||||||
public MatchScoreDisplay()
|
public TournamentMatchScoreDisplay()
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
AutoSizeAxes = Axes.Y;
|
AutoSizeAxes = Axes.Y;
|
@ -86,7 +86,7 @@ namespace osu.Game.Tournament.Screens.Gameplay
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
scoreDisplay = new MatchScoreDisplay
|
scoreDisplay = new TournamentMatchScoreDisplay
|
||||||
{
|
{
|
||||||
Y = -147,
|
Y = -147,
|
||||||
Anchor = Anchor.BottomCentre,
|
Anchor = Anchor.BottomCentre,
|
||||||
@ -148,7 +148,7 @@ namespace osu.Game.Tournament.Screens.Gameplay
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ScheduledDelegate scheduledOperation;
|
private ScheduledDelegate scheduledOperation;
|
||||||
private MatchScoreDisplay scoreDisplay;
|
private TournamentMatchScoreDisplay scoreDisplay;
|
||||||
|
|
||||||
private TourneyState lastState;
|
private TourneyState lastState;
|
||||||
private MatchHeader header;
|
private MatchHeader header;
|
||||||
|
@ -26,8 +26,8 @@ namespace osu.Game.Tournament
|
|||||||
{
|
{
|
||||||
public static ColourInfo GetTeamColour(TeamColour teamColour) => teamColour == TeamColour.Red ? COLOUR_RED : COLOUR_BLUE;
|
public static ColourInfo GetTeamColour(TeamColour teamColour) => teamColour == TeamColour.Red ? COLOUR_RED : COLOUR_BLUE;
|
||||||
|
|
||||||
public static readonly Color4 COLOUR_RED = Color4Extensions.FromHex("#AA1414");
|
public static readonly Color4 COLOUR_RED = new OsuColour().TeamColourRed;
|
||||||
public static readonly Color4 COLOUR_BLUE = Color4Extensions.FromHex("#1462AA");
|
public static readonly Color4 COLOUR_BLUE = new OsuColour().TeamColourBlue;
|
||||||
|
|
||||||
public static readonly Color4 ELEMENT_BACKGROUND_COLOUR = Color4Extensions.FromHex("#fff");
|
public static readonly Color4 ELEMENT_BACKGROUND_COLOUR = Color4Extensions.FromHex("#fff");
|
||||||
public static readonly Color4 ELEMENT_FOREGROUND_COLOUR = Color4Extensions.FromHex("#000");
|
public static readonly Color4 ELEMENT_FOREGROUND_COLOUR = Color4Extensions.FromHex("#000");
|
||||||
|
@ -101,7 +101,7 @@ namespace osu.Game.Configuration
|
|||||||
SetDefault(OsuSetting.HitLighting, true);
|
SetDefault(OsuSetting.HitLighting, true);
|
||||||
|
|
||||||
SetDefault(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Always);
|
SetDefault(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Always);
|
||||||
SetDefault(OsuSetting.ShowProgressGraph, true);
|
SetDefault(OsuSetting.ShowDifficultyGraph, true);
|
||||||
SetDefault(OsuSetting.ShowHealthDisplayWhenCantFail, true);
|
SetDefault(OsuSetting.ShowHealthDisplayWhenCantFail, true);
|
||||||
SetDefault(OsuSetting.FadePlayfieldWhenHealthLow, true);
|
SetDefault(OsuSetting.FadePlayfieldWhenHealthLow, true);
|
||||||
SetDefault(OsuSetting.KeyOverlay, false);
|
SetDefault(OsuSetting.KeyOverlay, false);
|
||||||
@ -217,7 +217,7 @@ namespace osu.Game.Configuration
|
|||||||
AlwaysPlayFirstComboBreak,
|
AlwaysPlayFirstComboBreak,
|
||||||
FloatingComments,
|
FloatingComments,
|
||||||
HUDVisibilityMode,
|
HUDVisibilityMode,
|
||||||
ShowProgressGraph,
|
ShowDifficultyGraph,
|
||||||
ShowHealthDisplayWhenCantFail,
|
ShowHealthDisplayWhenCantFail,
|
||||||
FadePlayfieldWhenHealthLow,
|
FadePlayfieldWhenHealthLow,
|
||||||
MouseDisableButtons,
|
MouseDisableButtons,
|
||||||
|
@ -130,6 +130,9 @@ namespace osu.Game.Graphics
|
|||||||
return Gray(brightness > 0.5f ? 0.2f : 0.9f);
|
return Gray(brightness > 0.5f ? 0.2f : 0.9f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public readonly Color4 TeamColourRed = Color4Extensions.FromHex("#AA1414");
|
||||||
|
public readonly Color4 TeamColourBlue = Color4Extensions.FromHex("#1462AA");
|
||||||
|
|
||||||
// See https://github.com/ppy/osu-web/blob/master/resources/assets/less/colors.less
|
// See https://github.com/ppy/osu-web/blob/master/resources/assets/less/colors.less
|
||||||
public readonly Color4 PurpleLighter = Color4Extensions.FromHex(@"eeeeff");
|
public readonly Color4 PurpleLighter = Color4Extensions.FromHex(@"eeeeff");
|
||||||
public readonly Color4 PurpleLight = Color4Extensions.FromHex(@"aa88ff");
|
public readonly Color4 PurpleLight = Color4Extensions.FromHex(@"aa88ff");
|
||||||
|
24
osu.Game/Localisation/MultiplayerTeamResultsScreenStrings.cs
Normal file
24
osu.Game/Localisation/MultiplayerTeamResultsScreenStrings.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
|
||||||
|
namespace osu.Game.Localisation
|
||||||
|
{
|
||||||
|
public static class MultiplayerTeamResultsScreenStrings
|
||||||
|
{
|
||||||
|
private const string prefix = @"osu.Game.Resources.Localisation.MultiplayerTeamResultsScreen";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Team {0} wins!"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString TeamWins(string winner) => new TranslatableString(getKey(@"team_wins"), @"Team {0} wins!", winner);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "The teams are tied!"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString TheTeamsAreTied => new TranslatableString(getKey(@"the_teams_are_tied"), @"The teams are tied!");
|
||||||
|
|
||||||
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
|
}
|
||||||
|
}
|
@ -31,6 +31,15 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
/// <param name="user">The user.</param>
|
/// <param name="user">The user.</param>
|
||||||
Task UserLeft(MultiplayerRoomUser user);
|
Task UserLeft(MultiplayerRoomUser user);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Signals that a user has been kicked from the room.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This will also be sent to the user that was kicked.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="user">The user.</param>
|
||||||
|
Task UserKicked(MultiplayerRoomUser user);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Signal that the host of the room has changed.
|
/// Signal that the host of the room has changed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -27,6 +27,14 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
/// <exception cref="NotJoinedRoomException">If the user is not in a room.</exception>
|
/// <exception cref="NotJoinedRoomException">If the user is not in a room.</exception>
|
||||||
Task TransferHost(int userId);
|
Task TransferHost(int userId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// As the host, kick another user from the room.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId">The user to kick..</param>
|
||||||
|
/// <exception cref="NotHostException">A user other than the current host is attempting to kick a user.</exception>
|
||||||
|
/// <exception cref="NotJoinedRoomException">If the user is not in a room.</exception>
|
||||||
|
Task KickUser(int userId);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// As the host, update the settings of the currently joined room.
|
/// As the host, update the settings of the currently joined room.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -293,6 +293,8 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
|
|
||||||
public abstract Task TransferHost(int userId);
|
public abstract Task TransferHost(int userId);
|
||||||
|
|
||||||
|
public abstract Task KickUser(int userId);
|
||||||
|
|
||||||
public abstract Task ChangeSettings(MultiplayerRoomSettings settings);
|
public abstract Task ChangeSettings(MultiplayerRoomSettings settings);
|
||||||
|
|
||||||
public abstract Task ChangeState(MultiplayerUserState newState);
|
public abstract Task ChangeState(MultiplayerUserState newState);
|
||||||
@ -387,6 +389,18 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Task IMultiplayerClient.UserKicked(MultiplayerRoomUser user)
|
||||||
|
{
|
||||||
|
if (LocalUser == null)
|
||||||
|
return Task.CompletedTask;
|
||||||
|
|
||||||
|
if (user.Equals(LocalUser))
|
||||||
|
LeaveRoom();
|
||||||
|
|
||||||
|
// TODO: also inform users of the kick operation.
|
||||||
|
return ((IMultiplayerClient)this).UserLeft(user);
|
||||||
|
}
|
||||||
|
|
||||||
Task IMultiplayerClient.HostChanged(int userId)
|
Task IMultiplayerClient.HostChanged(int userId)
|
||||||
{
|
{
|
||||||
if (Room == null)
|
if (Room == null)
|
||||||
|
@ -50,6 +50,7 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
connection.On<MultiplayerRoomState>(nameof(IMultiplayerClient.RoomStateChanged), ((IMultiplayerClient)this).RoomStateChanged);
|
connection.On<MultiplayerRoomState>(nameof(IMultiplayerClient.RoomStateChanged), ((IMultiplayerClient)this).RoomStateChanged);
|
||||||
connection.On<MultiplayerRoomUser>(nameof(IMultiplayerClient.UserJoined), ((IMultiplayerClient)this).UserJoined);
|
connection.On<MultiplayerRoomUser>(nameof(IMultiplayerClient.UserJoined), ((IMultiplayerClient)this).UserJoined);
|
||||||
connection.On<MultiplayerRoomUser>(nameof(IMultiplayerClient.UserLeft), ((IMultiplayerClient)this).UserLeft);
|
connection.On<MultiplayerRoomUser>(nameof(IMultiplayerClient.UserLeft), ((IMultiplayerClient)this).UserLeft);
|
||||||
|
connection.On<MultiplayerRoomUser>(nameof(IMultiplayerClient.UserKicked), ((IMultiplayerClient)this).UserKicked);
|
||||||
connection.On<int>(nameof(IMultiplayerClient.HostChanged), ((IMultiplayerClient)this).HostChanged);
|
connection.On<int>(nameof(IMultiplayerClient.HostChanged), ((IMultiplayerClient)this).HostChanged);
|
||||||
connection.On<MultiplayerRoomSettings>(nameof(IMultiplayerClient.SettingsChanged), ((IMultiplayerClient)this).SettingsChanged);
|
connection.On<MultiplayerRoomSettings>(nameof(IMultiplayerClient.SettingsChanged), ((IMultiplayerClient)this).SettingsChanged);
|
||||||
connection.On<int, MultiplayerUserState>(nameof(IMultiplayerClient.UserStateChanged), ((IMultiplayerClient)this).UserStateChanged);
|
connection.On<int, MultiplayerUserState>(nameof(IMultiplayerClient.UserStateChanged), ((IMultiplayerClient)this).UserStateChanged);
|
||||||
@ -91,6 +92,14 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
return connection.InvokeAsync(nameof(IMultiplayerServer.TransferHost), userId);
|
return connection.InvokeAsync(nameof(IMultiplayerServer.TransferHost), userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override Task KickUser(int userId)
|
||||||
|
{
|
||||||
|
if (!IsConnected.Value)
|
||||||
|
return Task.CompletedTask;
|
||||||
|
|
||||||
|
return connection.InvokeAsync(nameof(IMultiplayerServer.KickUser), userId);
|
||||||
|
}
|
||||||
|
|
||||||
public override Task ChangeSettings(MultiplayerRoomSettings settings)
|
public override Task ChangeSettings(MultiplayerRoomSettings settings)
|
||||||
{
|
{
|
||||||
if (!IsConnected.Value)
|
if (!IsConnected.Value)
|
||||||
|
@ -8,7 +8,7 @@ namespace osu.Game.Online.Rooms.RoomStatuses
|
|||||||
{
|
{
|
||||||
public class RoomStatusEnded : RoomStatus
|
public class RoomStatusEnded : RoomStatus
|
||||||
{
|
{
|
||||||
public override string Message => @"Ended";
|
public override string Message => "Ended";
|
||||||
public override Color4 GetAppropriateColour(OsuColour colours) => colours.YellowDarker;
|
public override Color4 GetAppropriateColour(OsuColour colours) => colours.YellowDarker;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ namespace osu.Game.Online.Rooms.RoomStatuses
|
|||||||
{
|
{
|
||||||
public class RoomStatusOpen : RoomStatus
|
public class RoomStatusOpen : RoomStatus
|
||||||
{
|
{
|
||||||
public override string Message => @"Welcoming Players";
|
public override string Message => "Open";
|
||||||
public override Color4 GetAppropriateColour(OsuColour colours) => colours.GreenLight;
|
public override Color4 GetAppropriateColour(OsuColour colours) => colours.GreenLight;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ namespace osu.Game.Online.Rooms.RoomStatuses
|
|||||||
{
|
{
|
||||||
public class RoomStatusPlaying : RoomStatus
|
public class RoomStatusPlaying : RoomStatus
|
||||||
{
|
{
|
||||||
public override string Message => @"Now Playing";
|
public override string Message => "Playing";
|
||||||
public override Color4 GetAppropriateColour(OsuColour colours) => colours.Purple;
|
public override Color4 GetAppropriateColour(OsuColour colours) => colours.Purple;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,6 +64,11 @@ namespace osu.Game
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class OsuGame : OsuGameBase, IKeyBindingHandler<GlobalAction>
|
public class OsuGame : OsuGameBase, IKeyBindingHandler<GlobalAction>
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The amount of global offset to apply when a left/right anchored overlay is displayed (ie. settings or notifications).
|
||||||
|
/// </summary>
|
||||||
|
protected const float SIDE_OVERLAY_OFFSET_RATIO = 0.05f;
|
||||||
|
|
||||||
public Toolbar Toolbar;
|
public Toolbar Toolbar;
|
||||||
|
|
||||||
private ChatOverlay chatOverlay;
|
private ChatOverlay chatOverlay;
|
||||||
@ -71,7 +76,7 @@ namespace osu.Game
|
|||||||
private ChannelManager channelManager;
|
private ChannelManager channelManager;
|
||||||
|
|
||||||
[NotNull]
|
[NotNull]
|
||||||
private readonly NotificationOverlay notifications = new NotificationOverlay();
|
protected readonly NotificationOverlay Notifications = new NotificationOverlay();
|
||||||
|
|
||||||
private BeatmapListingOverlay beatmapListing;
|
private BeatmapListingOverlay beatmapListing;
|
||||||
|
|
||||||
@ -97,7 +102,7 @@ namespace osu.Game
|
|||||||
|
|
||||||
private ScalingContainer screenContainer;
|
private ScalingContainer screenContainer;
|
||||||
|
|
||||||
private Container screenOffsetContainer;
|
protected Container ScreenOffsetContainer { get; private set; }
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private FrameworkConfigManager frameworkConfig { get; set; }
|
private FrameworkConfigManager frameworkConfig { get; set; }
|
||||||
@ -312,7 +317,7 @@ namespace osu.Game
|
|||||||
case LinkAction.OpenEditorTimestamp:
|
case LinkAction.OpenEditorTimestamp:
|
||||||
case LinkAction.JoinMultiplayerMatch:
|
case LinkAction.JoinMultiplayerMatch:
|
||||||
case LinkAction.Spectate:
|
case LinkAction.Spectate:
|
||||||
waitForReady(() => notifications, _ => notifications.Post(new SimpleNotification
|
waitForReady(() => Notifications, _ => Notifications.Post(new SimpleNotification
|
||||||
{
|
{
|
||||||
Text = @"This link type is not yet supported!",
|
Text = @"This link type is not yet supported!",
|
||||||
Icon = FontAwesome.Solid.LifeRing,
|
Icon = FontAwesome.Solid.LifeRing,
|
||||||
@ -611,12 +616,12 @@ namespace osu.Game
|
|||||||
MenuCursorContainer.CanShowCursor = menuScreen?.CursorVisible ?? false;
|
MenuCursorContainer.CanShowCursor = menuScreen?.CursorVisible ?? false;
|
||||||
|
|
||||||
// todo: all archive managers should be able to be looped here.
|
// todo: all archive managers should be able to be looped here.
|
||||||
SkinManager.PostNotification = n => notifications.Post(n);
|
SkinManager.PostNotification = n => Notifications.Post(n);
|
||||||
|
|
||||||
BeatmapManager.PostNotification = n => notifications.Post(n);
|
BeatmapManager.PostNotification = n => Notifications.Post(n);
|
||||||
BeatmapManager.PresentImport = items => PresentBeatmap(items.First());
|
BeatmapManager.PresentImport = items => PresentBeatmap(items.First());
|
||||||
|
|
||||||
ScoreManager.PostNotification = n => notifications.Post(n);
|
ScoreManager.PostNotification = n => Notifications.Post(n);
|
||||||
ScoreManager.PresentImport = items => PresentScore(items.First());
|
ScoreManager.PresentImport = items => PresentScore(items.First());
|
||||||
|
|
||||||
// make config aware of how to lookup skins for on-screen display purposes.
|
// make config aware of how to lookup skins for on-screen display purposes.
|
||||||
@ -655,7 +660,7 @@ namespace osu.Game
|
|||||||
ActionRequested = action => volume.Adjust(action),
|
ActionRequested = action => volume.Adjust(action),
|
||||||
ScrollActionRequested = (action, amount, isPrecise) => volume.Adjust(action, amount, isPrecise),
|
ScrollActionRequested = (action, amount, isPrecise) => volume.Adjust(action, amount, isPrecise),
|
||||||
},
|
},
|
||||||
screenOffsetContainer = new Container
|
ScreenOffsetContainer = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
@ -724,7 +729,7 @@ namespace osu.Game
|
|||||||
|
|
||||||
loadComponentSingleFile(onScreenDisplay, Add, true);
|
loadComponentSingleFile(onScreenDisplay, Add, true);
|
||||||
|
|
||||||
loadComponentSingleFile(notifications.With(d =>
|
loadComponentSingleFile(Notifications.With(d =>
|
||||||
{
|
{
|
||||||
d.GetToolbarHeight = () => ToolbarOffset;
|
d.GetToolbarHeight = () => ToolbarOffset;
|
||||||
d.Anchor = Anchor.TopRight;
|
d.Anchor = Anchor.TopRight;
|
||||||
@ -733,7 +738,7 @@ namespace osu.Game
|
|||||||
|
|
||||||
loadComponentSingleFile(new CollectionManager(Storage)
|
loadComponentSingleFile(new CollectionManager(Storage)
|
||||||
{
|
{
|
||||||
PostNotification = n => notifications.Post(n),
|
PostNotification = n => Notifications.Post(n),
|
||||||
}, Add, true);
|
}, Add, true);
|
||||||
|
|
||||||
loadComponentSingleFile(stableImportManager, Add);
|
loadComponentSingleFile(stableImportManager, Add);
|
||||||
@ -785,7 +790,7 @@ namespace osu.Game
|
|||||||
Add(new MusicKeyBindingHandler());
|
Add(new MusicKeyBindingHandler());
|
||||||
|
|
||||||
// side overlays which cancel each other.
|
// side overlays which cancel each other.
|
||||||
var singleDisplaySideOverlays = new OverlayContainer[] { Settings, notifications };
|
var singleDisplaySideOverlays = new OverlayContainer[] { Settings, Notifications };
|
||||||
|
|
||||||
foreach (var overlay in singleDisplaySideOverlays)
|
foreach (var overlay in singleDisplaySideOverlays)
|
||||||
{
|
{
|
||||||
@ -828,21 +833,6 @@ namespace osu.Game
|
|||||||
{
|
{
|
||||||
if (mode.NewValue != OverlayActivation.All) CloseAllOverlays();
|
if (mode.NewValue != OverlayActivation.All) CloseAllOverlays();
|
||||||
};
|
};
|
||||||
|
|
||||||
void updateScreenOffset()
|
|
||||||
{
|
|
||||||
float offset = 0;
|
|
||||||
|
|
||||||
if (Settings.State.Value == Visibility.Visible)
|
|
||||||
offset += Toolbar.HEIGHT / 2;
|
|
||||||
if (notifications.State.Value == Visibility.Visible)
|
|
||||||
offset -= Toolbar.HEIGHT / 2;
|
|
||||||
|
|
||||||
screenOffsetContainer.MoveToX(offset, SettingsPanel.TRANSITION_LENGTH, Easing.OutQuint);
|
|
||||||
}
|
|
||||||
|
|
||||||
Settings.State.ValueChanged += _ => updateScreenOffset();
|
|
||||||
notifications.State.ValueChanged += _ => updateScreenOffset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showOverlayAboveOthers(OverlayContainer overlay, OverlayContainer[] otherOverlays)
|
private void showOverlayAboveOthers(OverlayContainer overlay, OverlayContainer[] otherOverlays)
|
||||||
@ -874,7 +864,7 @@ namespace osu.Game
|
|||||||
|
|
||||||
if (recentLogCount < short_term_display_limit)
|
if (recentLogCount < short_term_display_limit)
|
||||||
{
|
{
|
||||||
Schedule(() => notifications.Post(new SimpleErrorNotification
|
Schedule(() => Notifications.Post(new SimpleErrorNotification
|
||||||
{
|
{
|
||||||
Icon = entry.Level == LogLevel.Important ? FontAwesome.Solid.ExclamationCircle : FontAwesome.Solid.Bomb,
|
Icon = entry.Level == LogLevel.Important ? FontAwesome.Solid.ExclamationCircle : FontAwesome.Solid.Bomb,
|
||||||
Text = entry.Message.Truncate(256) + (entry.Exception != null && IsDeployedBuild ? "\n\nThis error has been automatically reported to the devs." : string.Empty),
|
Text = entry.Message.Truncate(256) + (entry.Exception != null && IsDeployedBuild ? "\n\nThis error has been automatically reported to the devs." : string.Empty),
|
||||||
@ -882,7 +872,7 @@ namespace osu.Game
|
|||||||
}
|
}
|
||||||
else if (recentLogCount == short_term_display_limit)
|
else if (recentLogCount == short_term_display_limit)
|
||||||
{
|
{
|
||||||
Schedule(() => notifications.Post(new SimpleNotification
|
Schedule(() => Notifications.Post(new SimpleNotification
|
||||||
{
|
{
|
||||||
Icon = FontAwesome.Solid.EllipsisH,
|
Icon = FontAwesome.Solid.EllipsisH,
|
||||||
Text = "Subsequent messages have been logged. Click to view log files.",
|
Text = "Subsequent messages have been logged. Click to view log files.",
|
||||||
@ -1023,9 +1013,18 @@ namespace osu.Game
|
|||||||
{
|
{
|
||||||
base.UpdateAfterChildren();
|
base.UpdateAfterChildren();
|
||||||
|
|
||||||
screenOffsetContainer.Padding = new MarginPadding { Top = ToolbarOffset };
|
ScreenOffsetContainer.Padding = new MarginPadding { Top = ToolbarOffset };
|
||||||
overlayContent.Padding = new MarginPadding { Top = ToolbarOffset };
|
overlayContent.Padding = new MarginPadding { Top = ToolbarOffset };
|
||||||
|
|
||||||
|
var horizontalOffset = 0f;
|
||||||
|
|
||||||
|
if (Settings.IsLoaded && Settings.IsPresent)
|
||||||
|
horizontalOffset += ToLocalSpace(Settings.ScreenSpaceDrawQuad.TopRight).X * SIDE_OVERLAY_OFFSET_RATIO;
|
||||||
|
if (Notifications.IsLoaded && Notifications.IsPresent)
|
||||||
|
horizontalOffset += (ToLocalSpace(Notifications.ScreenSpaceDrawQuad.TopLeft).X - DrawWidth) * SIDE_OVERLAY_OFFSET_RATIO;
|
||||||
|
|
||||||
|
ScreenOffsetContainer.X = horizontalOffset;
|
||||||
|
|
||||||
MenuCursorContainer.CanShowCursor = (ScreenStack.CurrentScreen as IOsuScreen)?.CursorVisible ?? false;
|
MenuCursorContainer.CanShowCursor = (ScreenStack.CurrentScreen as IOsuScreen)?.CursorVisible ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,6 +71,17 @@ namespace osu.Game.Overlays.Changelog
|
|||||||
Colour = colourProvider.Background6,
|
Colour = colourProvider.Background6,
|
||||||
Margin = new MarginPadding { Top = 30 },
|
Margin = new MarginPadding { Top = 30 },
|
||||||
},
|
},
|
||||||
|
new ChangelogSupporterPromo
|
||||||
|
{
|
||||||
|
Alpha = api.LocalUser.Value.IsSupporter ? 0 : 1,
|
||||||
|
},
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = 2,
|
||||||
|
Colour = colourProvider.Background6,
|
||||||
|
Alpha = api.LocalUser.Value.IsSupporter ? 0 : 1,
|
||||||
|
},
|
||||||
comments = new CommentsContainer()
|
comments = new CommentsContainer()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
187
osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs
Normal file
187
osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
// 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 osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Effects;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Graphics.Textures;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Online.Chat;
|
||||||
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Changelog
|
||||||
|
{
|
||||||
|
public class ChangelogSupporterPromo : CompositeDrawable
|
||||||
|
{
|
||||||
|
private const float image_container_width = 164;
|
||||||
|
|
||||||
|
private readonly FillFlowContainer textContainer;
|
||||||
|
private readonly Container imageContainer;
|
||||||
|
|
||||||
|
public ChangelogSupporterPromo()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
Padding = new MarginPadding
|
||||||
|
{
|
||||||
|
Vertical = 20,
|
||||||
|
Horizontal = 50,
|
||||||
|
};
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Masking = true,
|
||||||
|
CornerRadius = 6,
|
||||||
|
EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Shadow,
|
||||||
|
Colour = Color4.Black.Opacity(0.25f),
|
||||||
|
Offset = new Vector2(0, 1),
|
||||||
|
Radius = 3,
|
||||||
|
},
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Color4.Black.Opacity(0.3f),
|
||||||
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = 200,
|
||||||
|
Padding = new MarginPadding { Horizontal = 75 },
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
textContainer = new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Padding = new MarginPadding { Right = 50 + image_container_width },
|
||||||
|
},
|
||||||
|
imageContainer = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
Width = image_container_width,
|
||||||
|
Anchor = Anchor.CentreRight,
|
||||||
|
Origin = Anchor.CentreRight,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colour, TextureStore textures)
|
||||||
|
{
|
||||||
|
SupporterPromoLinkFlowContainer supportLinkText;
|
||||||
|
textContainer.Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = ChangelogStrings.SupportHeading,
|
||||||
|
Font = OsuFont.GetFont(size: 20, weight: FontWeight.Light),
|
||||||
|
Margin = new MarginPadding { Bottom = 20 },
|
||||||
|
},
|
||||||
|
supportLinkText = new SupporterPromoLinkFlowContainer(t =>
|
||||||
|
{
|
||||||
|
t.Font = t.Font.With(size: 14);
|
||||||
|
t.Colour = colour.PinkLighter;
|
||||||
|
})
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
},
|
||||||
|
new OsuTextFlowContainer(t =>
|
||||||
|
{
|
||||||
|
t.Font = t.Font.With(size: 12);
|
||||||
|
t.Colour = colour.PinkLighter;
|
||||||
|
})
|
||||||
|
{
|
||||||
|
Text = ChangelogStrings.SupportText2.ToString(),
|
||||||
|
Margin = new MarginPadding { Top = 10 },
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
supportLinkText.AddText("Support further development of osu! and ");
|
||||||
|
supportLinkText.AddLink("become and osu!supporter", "https://osu.ppy.sh/home/support", t => t.Font = t.Font.With(weight: FontWeight.Bold));
|
||||||
|
supportLinkText.AddText(" today!");
|
||||||
|
|
||||||
|
imageContainer.Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Sprite
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
FillMode = FillMode.Fill,
|
||||||
|
Texture = textures.Get(@"Online/supporter-pippi"),
|
||||||
|
},
|
||||||
|
new Sprite
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Width = 75,
|
||||||
|
Height = 75,
|
||||||
|
Margin = new MarginPadding { Top = 70 },
|
||||||
|
Texture = textures.Get(@"Online/supporter-heart"),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SupporterPromoLinkFlowContainer : LinkFlowContainer
|
||||||
|
{
|
||||||
|
public SupporterPromoLinkFlowContainer(Action<SpriteText> defaultCreationParameters)
|
||||||
|
: base(defaultCreationParameters)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public new void AddLink(string text, string url, Action<SpriteText> creationParameters) =>
|
||||||
|
AddInternal(new SupporterPromoLinkCompiler(AddText(text, creationParameters)) { Url = url });
|
||||||
|
|
||||||
|
private class SupporterPromoLinkCompiler : DrawableLinkCompiler
|
||||||
|
{
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
private OsuGame game { get; set; }
|
||||||
|
|
||||||
|
public string Url;
|
||||||
|
|
||||||
|
public SupporterPromoLinkCompiler(IEnumerable<Drawable> parts)
|
||||||
|
: base(parts)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colour)
|
||||||
|
{
|
||||||
|
TooltipText = Url;
|
||||||
|
Action = () => game?.HandleLink(Url);
|
||||||
|
IdleColour = colour.PinkDark;
|
||||||
|
HoverColour = Color4.White;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -14,6 +14,9 @@ using System.Linq;
|
|||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Comments
|
namespace osu.Game.Overlays.Comments
|
||||||
{
|
{
|
||||||
@ -147,7 +150,7 @@ namespace osu.Game.Overlays.Comments
|
|||||||
|
|
||||||
private void refetchComments()
|
private void refetchComments()
|
||||||
{
|
{
|
||||||
clearComments();
|
ClearComments();
|
||||||
getComments();
|
getComments();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,50 +163,125 @@ namespace osu.Game.Overlays.Comments
|
|||||||
loadCancellation?.Cancel();
|
loadCancellation?.Cancel();
|
||||||
scheduledCommentsLoad?.Cancel();
|
scheduledCommentsLoad?.Cancel();
|
||||||
request = new GetCommentsRequest(id.Value, type.Value, Sort.Value, currentPage++, 0);
|
request = new GetCommentsRequest(id.Value, type.Value, Sort.Value, currentPage++, 0);
|
||||||
request.Success += res => scheduledCommentsLoad = Schedule(() => onSuccess(res));
|
request.Success += res => scheduledCommentsLoad = Schedule(() => OnSuccess(res));
|
||||||
api.PerformAsync(request);
|
api.PerformAsync(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clearComments()
|
protected void ClearComments()
|
||||||
{
|
{
|
||||||
currentPage = 1;
|
currentPage = 1;
|
||||||
deletedCommentsCounter.Count.Value = 0;
|
deletedCommentsCounter.Count.Value = 0;
|
||||||
moreButton.Show();
|
moreButton.Show();
|
||||||
moreButton.IsLoading = true;
|
moreButton.IsLoading = true;
|
||||||
content.Clear();
|
content.Clear();
|
||||||
|
CommentDictionary.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onSuccess(CommentBundle response)
|
protected readonly Dictionary<long, DrawableComment> CommentDictionary = new Dictionary<long, DrawableComment>();
|
||||||
|
|
||||||
|
protected void OnSuccess(CommentBundle response)
|
||||||
{
|
{
|
||||||
loadCancellation = new CancellationTokenSource();
|
commentCounter.Current.Value = response.Total;
|
||||||
|
|
||||||
LoadComponentAsync(new CommentsPage(response)
|
if (!response.Comments.Any())
|
||||||
{
|
{
|
||||||
ShowDeleted = { BindTarget = ShowDeleted },
|
content.Add(new NoCommentsPlaceholder());
|
||||||
Sort = { BindTarget = Sort },
|
moreButton.Hide();
|
||||||
Type = { BindTarget = type },
|
return;
|
||||||
CommentableId = { BindTarget = id }
|
}
|
||||||
}, loaded =>
|
|
||||||
|
AppendComments(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Appends retrieved comments to the subtree rooted of comments in this page.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bundle">The bundle of comments to add.</param>
|
||||||
|
protected void AppendComments([NotNull] CommentBundle bundle)
|
||||||
|
{
|
||||||
|
var topLevelComments = new List<DrawableComment>();
|
||||||
|
var orphaned = new List<Comment>();
|
||||||
|
|
||||||
|
foreach (var comment in bundle.Comments.Concat(bundle.IncludedComments))
|
||||||
{
|
{
|
||||||
content.Add(loaded);
|
// Exclude possible duplicated comments.
|
||||||
|
if (CommentDictionary.ContainsKey(comment.Id))
|
||||||
|
continue;
|
||||||
|
|
||||||
deletedCommentsCounter.Count.Value += response.Comments.Count(c => c.IsDeleted && c.IsTopLevel);
|
addNewComment(comment);
|
||||||
|
}
|
||||||
|
|
||||||
if (response.HasMore)
|
// Comments whose parents were seen later than themselves can now be added.
|
||||||
|
foreach (var o in orphaned)
|
||||||
|
addNewComment(o);
|
||||||
|
|
||||||
|
if (topLevelComments.Any())
|
||||||
|
{
|
||||||
|
LoadComponentsAsync(topLevelComments, loaded =>
|
||||||
{
|
{
|
||||||
int loadedTopLevelComments = 0;
|
content.AddRange(loaded);
|
||||||
content.Children.OfType<FillFlowContainer>().ForEach(p => loadedTopLevelComments += p.Children.OfType<DrawableComment>().Count());
|
|
||||||
|
|
||||||
moreButton.Current.Value = response.TopLevelCount - loadedTopLevelComments;
|
deletedCommentsCounter.Count.Value += topLevelComments.Select(d => d.Comment).Count(c => c.IsDeleted && c.IsTopLevel);
|
||||||
moreButton.IsLoading = false;
|
|
||||||
|
if (bundle.HasMore)
|
||||||
|
{
|
||||||
|
int loadedTopLevelComments = 0;
|
||||||
|
content.Children.OfType<DrawableComment>().ForEach(p => loadedTopLevelComments++);
|
||||||
|
|
||||||
|
moreButton.Current.Value = bundle.TopLevelCount - loadedTopLevelComments;
|
||||||
|
moreButton.IsLoading = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
moreButton.Hide();
|
||||||
|
}
|
||||||
|
}, (loadCancellation = new CancellationTokenSource()).Token);
|
||||||
|
}
|
||||||
|
|
||||||
|
void addNewComment(Comment comment)
|
||||||
|
{
|
||||||
|
var drawableComment = getDrawableComment(comment);
|
||||||
|
|
||||||
|
if (comment.ParentId == null)
|
||||||
|
{
|
||||||
|
// Comments that have no parent are added as top-level comments to the flow.
|
||||||
|
topLevelComments.Add(drawableComment);
|
||||||
|
}
|
||||||
|
else if (CommentDictionary.TryGetValue(comment.ParentId.Value, out var parentDrawable))
|
||||||
|
{
|
||||||
|
// The comment's parent has already been seen, so the parent<-> child links can be added.
|
||||||
|
comment.ParentComment = parentDrawable.Comment;
|
||||||
|
parentDrawable.Replies.Add(drawableComment);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
moreButton.Hide();
|
// The comment's parent has not been seen yet, so keep it orphaned for the time being. This can occur if the comments arrive out of order.
|
||||||
|
// Since this comment has now been seen, any further children can be added to it without being orphaned themselves.
|
||||||
|
orphaned.Add(comment);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
commentCounter.Current.Value = response.Total;
|
private DrawableComment getDrawableComment(Comment comment)
|
||||||
}, loadCancellation.Token);
|
{
|
||||||
|
if (CommentDictionary.TryGetValue(comment.Id, out var existing))
|
||||||
|
return existing;
|
||||||
|
|
||||||
|
return CommentDictionary[comment.Id] = new DrawableComment(comment)
|
||||||
|
{
|
||||||
|
ShowDeleted = { BindTarget = ShowDeleted },
|
||||||
|
Sort = { BindTarget = Sort },
|
||||||
|
RepliesRequested = onCommentRepliesRequested
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onCommentRepliesRequested(DrawableComment drawableComment, int page)
|
||||||
|
{
|
||||||
|
var req = new GetCommentsRequest(id.Value, type.Value, Sort.Value, page, drawableComment.Comment.Id);
|
||||||
|
|
||||||
|
req.Success += response => Schedule(() => AppendComments(response));
|
||||||
|
|
||||||
|
api.PerformAsync(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
@ -212,5 +290,30 @@ namespace osu.Game.Overlays.Comments
|
|||||||
loadCancellation?.Cancel();
|
loadCancellation?.Cancel();
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class NoCommentsPlaceholder : CompositeDrawable
|
||||||
|
{
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider colourProvider)
|
||||||
|
{
|
||||||
|
Height = 80;
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
AddRangeInternal(new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = colourProvider.Background4
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Margin = new MarginPadding { Left = 50 },
|
||||||
|
Text = @"No comments yet."
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,161 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
|
||||||
using osu.Framework.Graphics.Shapes;
|
|
||||||
using osu.Game.Graphics.Sprites;
|
|
||||||
using System.Linq;
|
|
||||||
using osu.Game.Online.API.Requests;
|
|
||||||
using osu.Game.Online.API;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Comments
|
|
||||||
{
|
|
||||||
public class CommentsPage : CompositeDrawable
|
|
||||||
{
|
|
||||||
public readonly BindableBool ShowDeleted = new BindableBool();
|
|
||||||
public readonly Bindable<CommentsSortCriteria> Sort = new Bindable<CommentsSortCriteria>();
|
|
||||||
public readonly Bindable<CommentableType> Type = new Bindable<CommentableType>();
|
|
||||||
public readonly BindableLong CommentableId = new BindableLong();
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private IAPIProvider api { get; set; }
|
|
||||||
|
|
||||||
private readonly CommentBundle commentBundle;
|
|
||||||
private FillFlowContainer flow;
|
|
||||||
|
|
||||||
public CommentsPage(CommentBundle commentBundle)
|
|
||||||
{
|
|
||||||
this.commentBundle = commentBundle;
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(OverlayColourProvider colourProvider)
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X;
|
|
||||||
AutoSizeAxes = Axes.Y;
|
|
||||||
|
|
||||||
AddRangeInternal(new Drawable[]
|
|
||||||
{
|
|
||||||
new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = colourProvider.Background5
|
|
||||||
},
|
|
||||||
flow = new FillFlowContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
Direction = FillDirection.Vertical,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!commentBundle.Comments.Any())
|
|
||||||
{
|
|
||||||
flow.Add(new NoCommentsPlaceholder());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
AppendComments(commentBundle);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DrawableComment getDrawableComment(Comment comment)
|
|
||||||
{
|
|
||||||
if (CommentDictionary.TryGetValue(comment.Id, out var existing))
|
|
||||||
return existing;
|
|
||||||
|
|
||||||
return CommentDictionary[comment.Id] = new DrawableComment(comment)
|
|
||||||
{
|
|
||||||
ShowDeleted = { BindTarget = ShowDeleted },
|
|
||||||
Sort = { BindTarget = Sort },
|
|
||||||
RepliesRequested = onCommentRepliesRequested
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onCommentRepliesRequested(DrawableComment drawableComment, int page)
|
|
||||||
{
|
|
||||||
var request = new GetCommentsRequest(CommentableId.Value, Type.Value, Sort.Value, page, drawableComment.Comment.Id);
|
|
||||||
|
|
||||||
request.Success += response => Schedule(() => AppendComments(response));
|
|
||||||
|
|
||||||
api.PerformAsync(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected readonly Dictionary<long, DrawableComment> CommentDictionary = new Dictionary<long, DrawableComment>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Appends retrieved comments to the subtree rooted of comments in this page.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="bundle">The bundle of comments to add.</param>
|
|
||||||
protected void AppendComments([NotNull] CommentBundle bundle)
|
|
||||||
{
|
|
||||||
var orphaned = new List<Comment>();
|
|
||||||
|
|
||||||
foreach (var comment in bundle.Comments.Concat(bundle.IncludedComments))
|
|
||||||
{
|
|
||||||
// Exclude possible duplicated comments.
|
|
||||||
if (CommentDictionary.ContainsKey(comment.Id))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
addNewComment(comment);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Comments whose parents were seen later than themselves can now be added.
|
|
||||||
foreach (var o in orphaned)
|
|
||||||
addNewComment(o);
|
|
||||||
|
|
||||||
void addNewComment(Comment comment)
|
|
||||||
{
|
|
||||||
var drawableComment = getDrawableComment(comment);
|
|
||||||
|
|
||||||
if (comment.ParentId == null)
|
|
||||||
{
|
|
||||||
// Comments that have no parent are added as top-level comments to the flow.
|
|
||||||
flow.Add(drawableComment);
|
|
||||||
}
|
|
||||||
else if (CommentDictionary.TryGetValue(comment.ParentId.Value, out var parentDrawable))
|
|
||||||
{
|
|
||||||
// The comment's parent has already been seen, so the parent<-> child links can be added.
|
|
||||||
comment.ParentComment = parentDrawable.Comment;
|
|
||||||
parentDrawable.Replies.Add(drawableComment);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// The comment's parent has not been seen yet, so keep it orphaned for the time being. This can occur if the comments arrive out of order.
|
|
||||||
// Since this comment has now been seen, any further children can be added to it without being orphaned themselves.
|
|
||||||
orphaned.Add(comment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class NoCommentsPlaceholder : CompositeDrawable
|
|
||||||
{
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(OverlayColourProvider colourProvider)
|
|
||||||
{
|
|
||||||
Height = 80;
|
|
||||||
RelativeSizeAxes = Axes.X;
|
|
||||||
AddRangeInternal(new Drawable[]
|
|
||||||
{
|
|
||||||
new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = colourProvider.Background4
|
|
||||||
},
|
|
||||||
new OsuSpriteText
|
|
||||||
{
|
|
||||||
Anchor = Anchor.CentreLeft,
|
|
||||||
Origin = Anchor.CentreLeft,
|
|
||||||
Margin = new MarginPadding { Left = 50 },
|
|
||||||
Text = @"No comments yet."
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -24,7 +24,7 @@ namespace osu.Game.Overlays
|
|||||||
public LocalisableString Title => NotificationsStrings.HeaderTitle;
|
public LocalisableString Title => NotificationsStrings.HeaderTitle;
|
||||||
public LocalisableString Description => NotificationsStrings.HeaderDescription;
|
public LocalisableString Description => NotificationsStrings.HeaderDescription;
|
||||||
|
|
||||||
private const float width = 320;
|
public const float WIDTH = 320;
|
||||||
|
|
||||||
public const float TRANSITION_LENGTH = 600;
|
public const float TRANSITION_LENGTH = 600;
|
||||||
|
|
||||||
@ -38,7 +38,8 @@ namespace osu.Game.Overlays
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
Width = width;
|
X = WIDTH;
|
||||||
|
Width = WIDTH;
|
||||||
RelativeSizeAxes = Axes.Y;
|
RelativeSizeAxes = Axes.Y;
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
@ -152,7 +153,7 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
markAllRead();
|
markAllRead();
|
||||||
|
|
||||||
this.MoveToX(width, TRANSITION_LENGTH, Easing.OutQuint);
|
this.MoveToX(WIDTH, TRANSITION_LENGTH, Easing.OutQuint);
|
||||||
this.FadeTo(0, TRANSITION_LENGTH, Easing.OutQuint);
|
this.FadeTo(0, TRANSITION_LENGTH, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ namespace osu.Game.Overlays
|
|||||||
public static OverlayColourProvider Green { get; } = new OverlayColourProvider(OverlayColourScheme.Green);
|
public static OverlayColourProvider Green { get; } = new OverlayColourProvider(OverlayColourScheme.Green);
|
||||||
public static OverlayColourProvider Purple { get; } = new OverlayColourProvider(OverlayColourScheme.Purple);
|
public static OverlayColourProvider Purple { get; } = new OverlayColourProvider(OverlayColourScheme.Purple);
|
||||||
public static OverlayColourProvider Blue { get; } = new OverlayColourProvider(OverlayColourScheme.Blue);
|
public static OverlayColourProvider Blue { get; } = new OverlayColourProvider(OverlayColourScheme.Blue);
|
||||||
|
public static OverlayColourProvider Plum { get; } = new OverlayColourProvider(OverlayColourScheme.Plum);
|
||||||
|
|
||||||
public OverlayColourProvider(OverlayColourScheme colourScheme)
|
public OverlayColourProvider(OverlayColourScheme colourScheme)
|
||||||
{
|
{
|
||||||
@ -80,6 +81,9 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
case OverlayColourScheme.Blue:
|
case OverlayColourScheme.Blue:
|
||||||
return 200 / 360f;
|
return 200 / 360f;
|
||||||
|
|
||||||
|
case OverlayColourScheme.Plum:
|
||||||
|
return 320 / 360f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -92,6 +96,7 @@ namespace osu.Game.Overlays
|
|||||||
Lime,
|
Lime,
|
||||||
Green,
|
Green,
|
||||||
Purple,
|
Purple,
|
||||||
Blue
|
Blue,
|
||||||
|
Plum,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
|
|||||||
new SettingsCheckbox
|
new SettingsCheckbox
|
||||||
{
|
{
|
||||||
LabelText = "Show difficulty graph on progress bar",
|
LabelText = "Show difficulty graph on progress bar",
|
||||||
Current = config.GetBindable<bool>(OsuSetting.ShowProgressGraph)
|
Current = config.GetBindable<bool>(OsuSetting.ShowDifficultyGraph)
|
||||||
},
|
},
|
||||||
new SettingsCheckbox
|
new SettingsCheckbox
|
||||||
{
|
{
|
||||||
|
@ -9,7 +9,6 @@ using osu.Game.Overlays.Settings.Sections;
|
|||||||
using osu.Game.Overlays.Settings.Sections.Input;
|
using osu.Game.Overlays.Settings.Sections.Input;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
@ -38,6 +37,8 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
private readonly List<SettingsSubPanel> subPanels = new List<SettingsSubPanel>();
|
private readonly List<SettingsSubPanel> subPanels = new List<SettingsSubPanel>();
|
||||||
|
|
||||||
|
private SettingsSubPanel lastOpenedSubPanel;
|
||||||
|
|
||||||
protected override Drawable CreateHeader() => new SettingsHeader(Title, Description);
|
protected override Drawable CreateHeader() => new SettingsHeader(Title, Description);
|
||||||
protected override Drawable CreateFooter() => new SettingsFooter();
|
protected override Drawable CreateFooter() => new SettingsFooter();
|
||||||
|
|
||||||
@ -46,21 +47,21 @@ namespace osu.Game.Overlays
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool AcceptsFocus => subPanels.All(s => s.State.Value != Visibility.Visible);
|
public override bool AcceptsFocus => lastOpenedSubPanel == null || lastOpenedSubPanel.State.Value == Visibility.Hidden;
|
||||||
|
|
||||||
private T createSubPanel<T>(T subPanel)
|
private T createSubPanel<T>(T subPanel)
|
||||||
where T : SettingsSubPanel
|
where T : SettingsSubPanel
|
||||||
{
|
{
|
||||||
subPanel.Depth = 1;
|
subPanel.Depth = 1;
|
||||||
subPanel.Anchor = Anchor.TopRight;
|
subPanel.Anchor = Anchor.TopRight;
|
||||||
subPanel.State.ValueChanged += subPanelStateChanged;
|
subPanel.State.ValueChanged += e => subPanelStateChanged(subPanel, e);
|
||||||
|
|
||||||
subPanels.Add(subPanel);
|
subPanels.Add(subPanel);
|
||||||
|
|
||||||
return subPanel;
|
return subPanel;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void subPanelStateChanged(ValueChangedEvent<Visibility> state)
|
private void subPanelStateChanged(SettingsSubPanel panel, ValueChangedEvent<Visibility> state)
|
||||||
{
|
{
|
||||||
switch (state.NewValue)
|
switch (state.NewValue)
|
||||||
{
|
{
|
||||||
@ -68,7 +69,9 @@ namespace osu.Game.Overlays
|
|||||||
Sidebar?.FadeColour(Color4.DarkGray, 300, Easing.OutQuint);
|
Sidebar?.FadeColour(Color4.DarkGray, 300, Easing.OutQuint);
|
||||||
|
|
||||||
SectionsContainer.FadeOut(300, Easing.OutQuint);
|
SectionsContainer.FadeOut(300, Easing.OutQuint);
|
||||||
ContentContainer.MoveToX(-WIDTH, 500, Easing.OutQuint);
|
ContentContainer.MoveToX(-PANEL_WIDTH, 500, Easing.OutQuint);
|
||||||
|
|
||||||
|
lastOpenedSubPanel = panel;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Visibility.Hidden:
|
case Visibility.Hidden:
|
||||||
@ -80,7 +83,7 @@ namespace osu.Game.Overlays
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override float ExpandedPosition => subPanels.Any(s => s.State.Value == Visibility.Visible) ? -WIDTH : base.ExpandedPosition;
|
protected override float ExpandedPosition => lastOpenedSubPanel?.State.Value == Visibility.Visible ? -PANEL_WIDTH : base.ExpandedPosition;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
|
@ -28,7 +28,15 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
private const float sidebar_width = Sidebar.DEFAULT_WIDTH;
|
private const float sidebar_width = Sidebar.DEFAULT_WIDTH;
|
||||||
|
|
||||||
public const float WIDTH = 400;
|
/// <summary>
|
||||||
|
/// The width of the settings panel content, excluding the sidebar.
|
||||||
|
/// </summary>
|
||||||
|
public const float PANEL_WIDTH = 400;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The full width of the settings panel, including the sidebar.
|
||||||
|
/// </summary>
|
||||||
|
public const float WIDTH = sidebar_width + PANEL_WIDTH;
|
||||||
|
|
||||||
protected Container<Drawable> ContentContainer;
|
protected Container<Drawable> ContentContainer;
|
||||||
|
|
||||||
@ -64,7 +72,8 @@ namespace osu.Game.Overlays
|
|||||||
{
|
{
|
||||||
InternalChild = ContentContainer = new NonMaskedContent
|
InternalChild = ContentContainer = new NonMaskedContent
|
||||||
{
|
{
|
||||||
Width = WIDTH,
|
X = -WIDTH + ExpandedPosition,
|
||||||
|
Width = PANEL_WIDTH,
|
||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Y,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
|
@ -1,117 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Extensions;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Game.Graphics;
|
|
||||||
using osu.Game.Online.Rooms;
|
|
||||||
using osu.Game.Online.Rooms.RoomStatuses;
|
|
||||||
|
|
||||||
namespace osu.Game.Screens.OnlinePlay.Components
|
|
||||||
{
|
|
||||||
public class RoomStatusInfo : OnlinePlayComposite
|
|
||||||
{
|
|
||||||
public RoomStatusInfo()
|
|
||||||
{
|
|
||||||
AutoSizeAxes = Axes.Both;
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load()
|
|
||||||
{
|
|
||||||
StatusPart statusPart;
|
|
||||||
EndDatePart endDatePart;
|
|
||||||
|
|
||||||
InternalChild = new FillFlowContainer
|
|
||||||
{
|
|
||||||
AutoSizeAxes = Axes.Both,
|
|
||||||
Direction = FillDirection.Vertical,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
statusPart = new StatusPart
|
|
||||||
{
|
|
||||||
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 14)
|
|
||||||
},
|
|
||||||
endDatePart = new EndDatePart { Font = OsuFont.GetFont(size: 14) }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
statusPart.EndDate.BindTo(EndDate);
|
|
||||||
statusPart.Status.BindTo(Status);
|
|
||||||
statusPart.Availability.BindTo(Availability);
|
|
||||||
endDatePart.EndDate.BindTo(EndDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class EndDatePart : DrawableDate
|
|
||||||
{
|
|
||||||
public readonly IBindable<DateTimeOffset?> EndDate = new Bindable<DateTimeOffset?>();
|
|
||||||
|
|
||||||
public EndDatePart()
|
|
||||||
: base(DateTimeOffset.UtcNow)
|
|
||||||
{
|
|
||||||
EndDate.BindValueChanged(date =>
|
|
||||||
{
|
|
||||||
// If null, set a very large future date to prevent unnecessary schedules.
|
|
||||||
Date = date.NewValue ?? DateTimeOffset.Now.AddYears(1);
|
|
||||||
}, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override string Format()
|
|
||||||
{
|
|
||||||
if (EndDate.Value == null)
|
|
||||||
return string.Empty;
|
|
||||||
|
|
||||||
var diffToNow = Date.Subtract(DateTimeOffset.Now);
|
|
||||||
|
|
||||||
if (diffToNow.TotalSeconds < -5)
|
|
||||||
return $"Closed {base.Format()}";
|
|
||||||
|
|
||||||
if (diffToNow.TotalSeconds < 0)
|
|
||||||
return "Closed";
|
|
||||||
|
|
||||||
if (diffToNow.TotalSeconds < 5)
|
|
||||||
return "Closing soon";
|
|
||||||
|
|
||||||
return $"Closing {base.Format()}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class StatusPart : EndDatePart
|
|
||||||
{
|
|
||||||
public readonly IBindable<RoomStatus> Status = new Bindable<RoomStatus>();
|
|
||||||
public readonly IBindable<RoomAvailability> Availability = new Bindable<RoomAvailability>();
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private OsuColour colours { get; set; }
|
|
||||||
|
|
||||||
public StatusPart()
|
|
||||||
{
|
|
||||||
EndDate.BindValueChanged(_ => Format());
|
|
||||||
Status.BindValueChanged(_ => Format());
|
|
||||||
Availability.BindValueChanged(_ => Format());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void LoadComplete()
|
|
||||||
{
|
|
||||||
base.LoadComplete();
|
|
||||||
|
|
||||||
Text = Format();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override string Format()
|
|
||||||
{
|
|
||||||
if (!IsLoaded)
|
|
||||||
return string.Empty;
|
|
||||||
|
|
||||||
RoomStatus status = Date < DateTimeOffset.Now ? new RoomStatusEnded() : Status.Value ?? new RoomStatusOpen();
|
|
||||||
|
|
||||||
this.FadeColour(status.GetAppropriateColour(colours), 100);
|
|
||||||
return $"{Availability.Value.GetDescription()}, {status.Message}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +1,14 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Screens.Ranking.Expanded;
|
using osu.Game.Screens.Ranking.Expanded;
|
||||||
@ -85,6 +87,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
|||||||
|
|
||||||
minDisplay.Current.Value = minDifficulty;
|
minDisplay.Current.Value = minDifficulty;
|
||||||
maxDisplay.Current.Value = maxDifficulty;
|
maxDisplay.Current.Value = maxDifficulty;
|
||||||
|
maxDisplay.Alpha = Precision.AlmostEquals(Math.Round(minDifficulty.Stars, 2), Math.Round(maxDifficulty.Stars, 2)) ? 0 : 1;
|
||||||
|
|
||||||
minBackground.Colour = colours.ForStarDifficulty(minDifficulty.Stars);
|
minBackground.Colour = colours.ForStarDifficulty(minDifficulty.Stars);
|
||||||
maxBackground.Colour = colours.ForStarDifficulty(maxDifficulty.Stars);
|
maxBackground.Colour = colours.ForStarDifficulty(maxDifficulty.Stars);
|
||||||
|
@ -2,19 +2,15 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
|
||||||
using osu.Framework.Graphics.UserInterface;
|
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Graphics.UserInterface;
|
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
|
||||||
|
|
||||||
namespace osu.Game.Screens.OnlinePlay
|
namespace osu.Game.Screens.OnlinePlay
|
||||||
{
|
{
|
||||||
@ -22,52 +18,30 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
{
|
{
|
||||||
public const float HEIGHT = 80;
|
public const float HEIGHT = 80;
|
||||||
|
|
||||||
|
private readonly ScreenStack stack;
|
||||||
|
private readonly MultiHeaderTitle title;
|
||||||
|
|
||||||
public Header(string mainTitle, ScreenStack stack)
|
public Header(string mainTitle, ScreenStack stack)
|
||||||
{
|
{
|
||||||
|
this.stack = stack;
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
Height = HEIGHT;
|
Height = HEIGHT;
|
||||||
|
Padding = new MarginPadding { Left = WaveOverlayContainer.WIDTH_PADDING };
|
||||||
|
|
||||||
HeaderBreadcrumbControl breadcrumbs;
|
Child = title = new MultiHeaderTitle(mainTitle)
|
||||||
MultiHeaderTitle title;
|
|
||||||
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
{
|
||||||
new Box
|
Anchor = Anchor.CentreLeft,
|
||||||
{
|
Origin = Anchor.CentreLeft,
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = Color4Extensions.FromHex(@"#1f1921"),
|
|
||||||
},
|
|
||||||
new Container
|
|
||||||
{
|
|
||||||
Anchor = Anchor.CentreLeft,
|
|
||||||
Origin = Anchor.CentreLeft,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Padding = new MarginPadding { Left = WaveOverlayContainer.WIDTH_PADDING + OsuScreen.HORIZONTAL_OVERFLOW_PADDING },
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
title = new MultiHeaderTitle(mainTitle)
|
|
||||||
{
|
|
||||||
Anchor = Anchor.CentreLeft,
|
|
||||||
Origin = Anchor.BottomLeft,
|
|
||||||
},
|
|
||||||
breadcrumbs = new HeaderBreadcrumbControl(stack)
|
|
||||||
{
|
|
||||||
Anchor = Anchor.BottomLeft,
|
|
||||||
Origin = Anchor.BottomLeft
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
breadcrumbs.Current.ValueChanged += screen =>
|
// unnecessary to unbind these as this header has the same lifetime as the screen stack we are attaching to.
|
||||||
{
|
stack.ScreenPushed += (_, __) => updateSubScreenTitle();
|
||||||
if (screen.NewValue is IOnlinePlaySubScreen onlineSubScreen)
|
stack.ScreenExited += (_, __) => updateSubScreenTitle();
|
||||||
title.Screen = onlineSubScreen;
|
|
||||||
};
|
|
||||||
|
|
||||||
breadcrumbs.Current.TriggerChange();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateSubScreenTitle() => title.Screen = stack.CurrentScreen as IOnlinePlaySubScreen;
|
||||||
|
|
||||||
private class MultiHeaderTitle : CompositeDrawable
|
private class MultiHeaderTitle : CompositeDrawable
|
||||||
{
|
{
|
||||||
private const float spacing = 6;
|
private const float spacing = 6;
|
||||||
@ -75,9 +49,10 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
private readonly OsuSpriteText dot;
|
private readonly OsuSpriteText dot;
|
||||||
private readonly OsuSpriteText pageTitle;
|
private readonly OsuSpriteText pageTitle;
|
||||||
|
|
||||||
|
[CanBeNull]
|
||||||
public IOnlinePlaySubScreen Screen
|
public IOnlinePlaySubScreen Screen
|
||||||
{
|
{
|
||||||
set => pageTitle.Text = value.ShortTitle.Titleize();
|
set => pageTitle.Text = value?.ShortTitle.Titleize() ?? string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MultiHeaderTitle(string mainTitle)
|
public MultiHeaderTitle(string mainTitle)
|
||||||
@ -125,35 +100,5 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
pageTitle.Colour = dot.Colour = colours.Yellow;
|
pageTitle.Colour = dot.Colour = colours.Yellow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class HeaderBreadcrumbControl : ScreenBreadcrumbControl
|
|
||||||
{
|
|
||||||
public HeaderBreadcrumbControl(ScreenStack stack)
|
|
||||||
: base(stack)
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X;
|
|
||||||
StripColour = Color4.Transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void LoadComplete()
|
|
||||||
{
|
|
||||||
base.LoadComplete();
|
|
||||||
AccentColour = Color4Extensions.FromHex("#e35c99");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override TabItem<IScreen> CreateTabItem(IScreen value) => new HeaderBreadcrumbTabItem(value)
|
|
||||||
{
|
|
||||||
AccentColour = AccentColour
|
|
||||||
};
|
|
||||||
|
|
||||||
private class HeaderBreadcrumbTabItem : BreadcrumbTabItem
|
|
||||||
{
|
|
||||||
public HeaderBreadcrumbTabItem(IScreen value)
|
|
||||||
: base(value)
|
|
||||||
{
|
|
||||||
Bar.Colour = Color4.Transparent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Colour;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Cursor;
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Framework.Graphics.Effects;
|
using osu.Framework.Graphics.Effects;
|
||||||
@ -20,7 +21,6 @@ using osu.Framework.Graphics.UserInterface;
|
|||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.Drawables;
|
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
@ -28,6 +28,7 @@ using osu.Game.Graphics.UserInterface;
|
|||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Screens.OnlinePlay.Components;
|
using osu.Game.Screens.OnlinePlay.Components;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
@ -37,21 +38,18 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
|||||||
public class DrawableRoom : OsuClickableContainer, IStateful<SelectionState>, IFilterable, IHasContextMenu, IHasPopover, IKeyBindingHandler<GlobalAction>
|
public class DrawableRoom : OsuClickableContainer, IStateful<SelectionState>, IFilterable, IHasContextMenu, IHasPopover, IKeyBindingHandler<GlobalAction>
|
||||||
{
|
{
|
||||||
public const float SELECTION_BORDER_WIDTH = 4;
|
public const float SELECTION_BORDER_WIDTH = 4;
|
||||||
private const float corner_radius = 5;
|
private const float corner_radius = 10;
|
||||||
private const float transition_duration = 60;
|
private const float transition_duration = 60;
|
||||||
private const float content_padding = 10;
|
private const float height = 100;
|
||||||
private const float height = 110;
|
|
||||||
private const float side_strip_width = 5;
|
|
||||||
private const float cover_width = 145;
|
|
||||||
|
|
||||||
public event Action<SelectionState> StateChanged;
|
public event Action<SelectionState> StateChanged;
|
||||||
|
|
||||||
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds();
|
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds();
|
||||||
|
|
||||||
private readonly Box selectionBox;
|
private Drawable selectionBox;
|
||||||
|
|
||||||
[Resolved(canBeNull: true)]
|
[Resolved(canBeNull: true)]
|
||||||
private OnlinePlayScreen parentScreen { get; set; }
|
private LoungeSubScreen loungeScreen { get; set; }
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private BeatmapManager beatmaps { get; set; }
|
private BeatmapManager beatmaps { get; set; }
|
||||||
@ -74,14 +72,18 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
|||||||
get => state;
|
get => state;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (value == state) return;
|
if (value == state)
|
||||||
|
return;
|
||||||
|
|
||||||
state = value;
|
state = value;
|
||||||
|
|
||||||
if (state == SelectionState.Selected)
|
if (selectionBox != null)
|
||||||
selectionBox.FadeIn(transition_duration);
|
{
|
||||||
else
|
if (state == SelectionState.Selected)
|
||||||
selectionBox.FadeOut(transition_duration);
|
selectionBox.FadeIn(transition_duration);
|
||||||
|
else
|
||||||
|
selectionBox.FadeOut(transition_duration);
|
||||||
|
}
|
||||||
|
|
||||||
StateChanged?.Invoke(State);
|
StateChanged?.Invoke(State);
|
||||||
}
|
}
|
||||||
@ -108,6 +110,25 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int numberOfAvatars = 7;
|
||||||
|
|
||||||
|
public int NumberOfAvatars
|
||||||
|
{
|
||||||
|
get => numberOfAvatars;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
numberOfAvatars = value;
|
||||||
|
|
||||||
|
if (recentParticipantsList != null)
|
||||||
|
recentParticipantsList.NumberOfCircles = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly Bindable<RoomCategory> roomCategory = new Bindable<RoomCategory>();
|
||||||
|
|
||||||
|
private RecentParticipantsList recentParticipantsList;
|
||||||
|
private RoomSpecialCategoryPill specialCategoryPill;
|
||||||
|
|
||||||
public bool FilteringActive { get; set; }
|
public bool FilteringActive { get; set; }
|
||||||
|
|
||||||
private PasswordProtectedIcon passwordIcon;
|
private PasswordProtectedIcon passwordIcon;
|
||||||
@ -119,114 +140,208 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
|||||||
Room = room;
|
Room = room;
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
Height = height + SELECTION_BORDER_WIDTH * 2;
|
Height = height;
|
||||||
CornerRadius = corner_radius + SELECTION_BORDER_WIDTH / 2;
|
|
||||||
Masking = true;
|
|
||||||
|
|
||||||
// create selectionBox here so State can be set before being loaded
|
Masking = true;
|
||||||
selectionBox = new Box
|
CornerRadius = corner_radius + SELECTION_BORDER_WIDTH / 2;
|
||||||
|
EdgeEffect = new EdgeEffectParameters
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
Type = EdgeEffectType.Shadow,
|
||||||
Alpha = 0f,
|
Colour = Color4.Black.Opacity(40),
|
||||||
|
Radius = 5,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours, AudioManager audio)
|
private void load(OverlayColourProvider colours, AudioManager audio)
|
||||||
{
|
{
|
||||||
float stripWidth = side_strip_width * (Room.Category.Value == RoomCategory.Spotlight ? 2 : 1);
|
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new StatusColouredContainer(transition_duration)
|
// This resolves internal 1px gaps due to applying the (parenting) corner radius and masking across multiple filling background sprites.
|
||||||
|
new BufferedContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Child = selectionBox
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = colours.Background5,
|
||||||
|
},
|
||||||
|
new OnlinePlayBackgroundSprite
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
},
|
||||||
|
}
|
||||||
},
|
},
|
||||||
new Container
|
new Container
|
||||||
{
|
{
|
||||||
|
Name = @"Room content",
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Padding = new MarginPadding(SELECTION_BORDER_WIDTH),
|
// This negative padding resolves 1px gaps between this background and the background above.
|
||||||
|
Padding = new MarginPadding { Left = 20, Vertical = -0.5f },
|
||||||
Child = new Container
|
Child = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Masking = true,
|
Masking = true,
|
||||||
CornerRadius = corner_radius,
|
CornerRadius = corner_radius,
|
||||||
EdgeEffect = new EdgeEffectParameters
|
|
||||||
{
|
|
||||||
Type = EdgeEffectType.Shadow,
|
|
||||||
Colour = Color4.Black.Opacity(40),
|
|
||||||
Radius = 5,
|
|
||||||
},
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new Box
|
// This resolves internal 1px gaps due to applying the (parenting) corner radius and masking across multiple filling background sprites.
|
||||||
|
new BufferedContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = Color4Extensions.FromHex(@"212121"),
|
Children = new Drawable[]
|
||||||
},
|
{
|
||||||
new StatusColouredContainer(transition_duration)
|
new GridContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Width = stripWidth,
|
ColumnDimensions = new[]
|
||||||
Child = new Box { RelativeSizeAxes = Axes.Both }
|
{
|
||||||
},
|
new Dimension(GridSizeMode.Relative, 0.2f)
|
||||||
new Container
|
},
|
||||||
{
|
Content = new[]
|
||||||
RelativeSizeAxes = Axes.Y,
|
{
|
||||||
Width = cover_width,
|
new Drawable[]
|
||||||
Masking = true,
|
{
|
||||||
Margin = new MarginPadding { Left = stripWidth },
|
new Box
|
||||||
Child = new OnlinePlayBackgroundSprite(BeatmapSetCoverType.List) { RelativeSizeAxes = Axes.Both }
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = colours.Background5,
|
||||||
|
},
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = ColourInfo.GradientHorizontal(colours.Background5, colours.Background5.Opacity(0.3f))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
new Container
|
new Container
|
||||||
{
|
{
|
||||||
|
Name = @"Left details",
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Padding = new MarginPadding
|
Padding = new MarginPadding
|
||||||
{
|
{
|
||||||
Vertical = content_padding,
|
Left = 20,
|
||||||
Left = stripWidth + cover_width + content_padding,
|
Vertical = 5
|
||||||
Right = content_padding,
|
|
||||||
},
|
},
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new FillFlowContainer
|
new FillFlowContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
AutoSizeAxes = Axes.Both,
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
Direction = FillDirection.Vertical,
|
Direction = FillDirection.Vertical,
|
||||||
Spacing = new Vector2(5f),
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new RoomName { Font = OsuFont.GetFont(size: 18) },
|
new FillFlowContainer
|
||||||
new ParticipantInfo(),
|
{
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
Spacing = new Vector2(5),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new RoomStatusPill
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft
|
||||||
|
},
|
||||||
|
specialCategoryPill = new RoomSpecialCategoryPill
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft
|
||||||
|
},
|
||||||
|
new EndDateInfo
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Padding = new MarginPadding { Top = 3 },
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new RoomNameText(),
|
||||||
|
new RoomHostText(),
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
new FillFlowContainer
|
new FillFlowContainer
|
||||||
{
|
{
|
||||||
Anchor = Anchor.BottomLeft,
|
Anchor = Anchor.BottomLeft,
|
||||||
Origin = Anchor.BottomLeft,
|
Origin = Anchor.BottomLeft,
|
||||||
RelativeSizeAxes = Axes.X,
|
AutoSizeAxes = Axes.Both,
|
||||||
AutoSizeAxes = Axes.Y,
|
Direction = FillDirection.Horizontal,
|
||||||
Direction = FillDirection.Vertical,
|
Spacing = new Vector2(5),
|
||||||
Spacing = new Vector2(0, 5),
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new RoomStatusInfo(),
|
new PlaylistCountPill
|
||||||
new BeatmapTitle { TextSize = 14 },
|
{
|
||||||
},
|
Anchor = Anchor.CentreLeft,
|
||||||
},
|
Origin = Anchor.CentreLeft,
|
||||||
new ModeTypeInfo
|
},
|
||||||
{
|
new StarRatingRangeDisplay
|
||||||
Anchor = Anchor.BottomRight,
|
{
|
||||||
Origin = Anchor.BottomRight,
|
Anchor = Anchor.CentreLeft,
|
||||||
},
|
Origin = Anchor.CentreLeft,
|
||||||
|
Scale = new Vector2(0.8f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
Name = "Right content",
|
||||||
|
Anchor = Anchor.CentreRight,
|
||||||
|
Origin = Anchor.CentreRight,
|
||||||
|
AutoSizeAxes = Axes.X,
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
Padding = new MarginPadding
|
||||||
|
{
|
||||||
|
Right = 10,
|
||||||
|
Vertical = 5
|
||||||
},
|
},
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
recentParticipantsList = new RecentParticipantsList
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreRight,
|
||||||
|
Origin = Anchor.CentreRight,
|
||||||
|
NumberOfCircles = NumberOfAvatars
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
passwordIcon = new PasswordProtectedIcon { Alpha = 0 }
|
passwordIcon = new PasswordProtectedIcon { Alpha = 0 }
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
new StatusColouredContainer(transition_duration)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = selectionBox = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = state == SelectionState.Selected ? 1 : 0,
|
||||||
|
Masking = true,
|
||||||
|
CornerRadius = corner_radius,
|
||||||
|
BorderThickness = SELECTION_BORDER_WIDTH,
|
||||||
|
BorderColour = Color4.White,
|
||||||
|
Child = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0,
|
||||||
|
AlwaysPresent = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
sampleSelect = audio.Samples.Get($@"UI/{HoverSampleSet.Default.GetDescription()}-select");
|
sampleSelect = audio.Samples.Get($@"UI/{HoverSampleSet.Default.GetDescription()}-select");
|
||||||
@ -250,6 +365,15 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
|||||||
else
|
else
|
||||||
Alpha = 0;
|
Alpha = 0;
|
||||||
|
|
||||||
|
roomCategory.BindTo(Room.Category);
|
||||||
|
roomCategory.BindValueChanged(c =>
|
||||||
|
{
|
||||||
|
if (c.NewValue == RoomCategory.Spotlight)
|
||||||
|
specialCategoryPill.Show();
|
||||||
|
else
|
||||||
|
specialCategoryPill.Hide();
|
||||||
|
}, true);
|
||||||
|
|
||||||
hasPassword.BindTo(Room.HasPassword);
|
hasPassword.BindTo(Room.HasPassword);
|
||||||
hasPassword.BindValueChanged(v => passwordIcon.Alpha = v.NewValue ? 1 : 0, true);
|
hasPassword.BindValueChanged(v => passwordIcon.Alpha = v.NewValue ? 1 : 0, true);
|
||||||
}
|
}
|
||||||
@ -260,7 +384,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
|||||||
{
|
{
|
||||||
new OsuMenuItem("Create copy", MenuItemType.Standard, () =>
|
new OsuMenuItem("Create copy", MenuItemType.Standard, () =>
|
||||||
{
|
{
|
||||||
parentScreen?.OpenNewRoom(Room.DeepClone());
|
lounge?.Open(Room.DeepClone());
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -307,11 +431,16 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
|||||||
return base.OnClick(e);
|
return base.OnClick(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class RoomName : OsuSpriteText
|
private class RoomNameText : OsuSpriteText
|
||||||
{
|
{
|
||||||
[Resolved(typeof(Room), nameof(Online.Rooms.Room.Name))]
|
[Resolved(typeof(Room), nameof(Online.Rooms.Room.Name))]
|
||||||
private Bindable<string> name { get; set; }
|
private Bindable<string> name { get; set; }
|
||||||
|
|
||||||
|
public RoomNameText()
|
||||||
|
{
|
||||||
|
Font = OsuFont.GetFont(size: 28);
|
||||||
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
@ -319,6 +448,41 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class RoomHostText : OnlinePlayComposite
|
||||||
|
{
|
||||||
|
private LinkFlowContainer hostText;
|
||||||
|
|
||||||
|
public RoomHostText()
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
InternalChild = hostText = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 16))
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
Host.BindValueChanged(host =>
|
||||||
|
{
|
||||||
|
hostText.Clear();
|
||||||
|
|
||||||
|
if (host.NewValue != null)
|
||||||
|
{
|
||||||
|
hostText.AddText("hosted by ");
|
||||||
|
hostText.AddUserLink(host.NewValue);
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class PasswordProtectedIcon : CompositeDrawable
|
public class PasswordProtectedIcon : CompositeDrawable
|
||||||
{
|
{
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -366,7 +530,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
|||||||
private OsuPasswordTextBox passwordTextbox;
|
private OsuPasswordTextBox passwordTextbox;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load()
|
||||||
{
|
{
|
||||||
Child = new FillFlowContainer
|
Child = new FillFlowContainer
|
||||||
{
|
{
|
||||||
|
65
osu.Game/Screens/OnlinePlay/Lounge/Components/EndDateInfo.cs
Normal file
65
osu.Game/Screens/OnlinePlay/Lounge/Components/EndDateInfo.cs
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
// 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.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||||
|
{
|
||||||
|
public class EndDateInfo : OnlinePlayComposite
|
||||||
|
{
|
||||||
|
public EndDateInfo()
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
InternalChild = new EndDatePart
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 12),
|
||||||
|
EndDate = { BindTarget = EndDate }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private class EndDatePart : DrawableDate
|
||||||
|
{
|
||||||
|
public readonly IBindable<DateTimeOffset?> EndDate = new Bindable<DateTimeOffset?>();
|
||||||
|
|
||||||
|
public EndDatePart()
|
||||||
|
: base(DateTimeOffset.UtcNow)
|
||||||
|
{
|
||||||
|
EndDate.BindValueChanged(date =>
|
||||||
|
{
|
||||||
|
// If null, set a very large future date to prevent unnecessary schedules.
|
||||||
|
Date = date.NewValue ?? DateTimeOffset.Now.AddYears(1);
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string Format()
|
||||||
|
{
|
||||||
|
if (EndDate.Value == null)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
var diffToNow = Date.Subtract(DateTimeOffset.Now);
|
||||||
|
|
||||||
|
if (diffToNow.TotalSeconds < -5)
|
||||||
|
return $"Closed {base.Format()}";
|
||||||
|
|
||||||
|
if (diffToNow.TotalSeconds < 0)
|
||||||
|
return "Closed";
|
||||||
|
|
||||||
|
if (diffToNow.TotalSeconds < 5)
|
||||||
|
return "Closing soon";
|
||||||
|
|
||||||
|
return $"Closing {base.Format()}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,135 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Graphics.Shapes;
|
|
||||||
using osu.Framework.Threading;
|
|
||||||
using osu.Game.Graphics;
|
|
||||||
using osu.Game.Graphics.UserInterface;
|
|
||||||
using osu.Game.Rulesets;
|
|
||||||
using osuTK.Graphics;
|
|
||||||
|
|
||||||
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
|
||||||
{
|
|
||||||
public abstract class FilterControl : CompositeDrawable
|
|
||||||
{
|
|
||||||
protected const float VERTICAL_PADDING = 10;
|
|
||||||
protected const float HORIZONTAL_PADDING = 80;
|
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
|
||||||
private Bindable<FilterCriteria> filter { get; set; }
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private IBindable<RulesetInfo> ruleset { get; set; }
|
|
||||||
|
|
||||||
private readonly Box tabStrip;
|
|
||||||
private readonly SearchTextBox search;
|
|
||||||
private readonly PageTabControl<RoomStatusFilter> tabs;
|
|
||||||
|
|
||||||
protected FilterControl()
|
|
||||||
{
|
|
||||||
InternalChildren = new Drawable[]
|
|
||||||
{
|
|
||||||
new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = Color4.Black,
|
|
||||||
Alpha = 0.25f,
|
|
||||||
},
|
|
||||||
tabStrip = new Box
|
|
||||||
{
|
|
||||||
Anchor = Anchor.BottomLeft,
|
|
||||||
Origin = Anchor.BottomLeft,
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Height = 1,
|
|
||||||
},
|
|
||||||
new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Padding = new MarginPadding
|
|
||||||
{
|
|
||||||
Top = VERTICAL_PADDING,
|
|
||||||
Horizontal = HORIZONTAL_PADDING
|
|
||||||
},
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
search = new FilterSearchTextBox
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
},
|
|
||||||
tabs = new PageTabControl<RoomStatusFilter>
|
|
||||||
{
|
|
||||||
Anchor = Anchor.BottomLeft,
|
|
||||||
Origin = Anchor.BottomLeft,
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
tabs.Current.Value = RoomStatusFilter.Open;
|
|
||||||
tabs.Current.TriggerChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(OsuColour colours)
|
|
||||||
{
|
|
||||||
filter ??= new Bindable<FilterCriteria>();
|
|
||||||
tabStrip.Colour = colours.Yellow;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void LoadComplete()
|
|
||||||
{
|
|
||||||
base.LoadComplete();
|
|
||||||
|
|
||||||
search.Current.BindValueChanged(_ => updateFilterDebounced());
|
|
||||||
ruleset.BindValueChanged(_ => UpdateFilter());
|
|
||||||
tabs.Current.BindValueChanged(_ => UpdateFilter(), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ScheduledDelegate scheduledFilterUpdate;
|
|
||||||
|
|
||||||
private void updateFilterDebounced()
|
|
||||||
{
|
|
||||||
scheduledFilterUpdate?.Cancel();
|
|
||||||
scheduledFilterUpdate = Scheduler.AddDelayed(UpdateFilter, 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void UpdateFilter() => Scheduler.AddOnce(updateFilter);
|
|
||||||
|
|
||||||
private void updateFilter()
|
|
||||||
{
|
|
||||||
scheduledFilterUpdate?.Cancel();
|
|
||||||
|
|
||||||
var criteria = CreateCriteria();
|
|
||||||
criteria.SearchString = search.Current.Value;
|
|
||||||
criteria.Status = tabs.Current.Value;
|
|
||||||
criteria.Ruleset = ruleset.Value;
|
|
||||||
|
|
||||||
filter.Value = criteria;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual FilterCriteria CreateCriteria() => new FilterCriteria();
|
|
||||||
|
|
||||||
public bool HoldFocus
|
|
||||||
{
|
|
||||||
get => search.HoldFocus;
|
|
||||||
set => search.HoldFocus = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void TakeFocus() => search.TakeFocus();
|
|
||||||
|
|
||||||
private class FilterSearchTextBox : SearchTextBox
|
|
||||||
{
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load()
|
|
||||||
{
|
|
||||||
BackgroundUnfocused = OsuColour.Gray(0.06f);
|
|
||||||
BackgroundFocused = OsuColour.Gray(0.12f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,88 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using Humanizer;
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Game.Graphics;
|
|
||||||
using osu.Game.Graphics.Containers;
|
|
||||||
using osu.Game.Graphics.Sprites;
|
|
||||||
using osu.Game.Users.Drawables;
|
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
|
||||||
{
|
|
||||||
public class ParticipantInfo : OnlinePlayComposite
|
|
||||||
{
|
|
||||||
public ParticipantInfo()
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X;
|
|
||||||
Height = 15f;
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(OsuColour colours)
|
|
||||||
{
|
|
||||||
OsuSpriteText summary;
|
|
||||||
Container flagContainer;
|
|
||||||
LinkFlowContainer hostText;
|
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
|
||||||
{
|
|
||||||
new FillFlowContainer
|
|
||||||
{
|
|
||||||
AutoSizeAxes = Axes.X,
|
|
||||||
RelativeSizeAxes = Axes.Y,
|
|
||||||
Direction = FillDirection.Horizontal,
|
|
||||||
Spacing = new Vector2(5f, 0f),
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
flagContainer = new Container
|
|
||||||
{
|
|
||||||
Width = 22f,
|
|
||||||
RelativeSizeAxes = Axes.Y,
|
|
||||||
},
|
|
||||||
hostText = new LinkFlowContainer
|
|
||||||
{
|
|
||||||
Anchor = Anchor.CentreLeft,
|
|
||||||
Origin = Anchor.CentreLeft,
|
|
||||||
AutoSizeAxes = Axes.Both
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
new FillFlowContainer
|
|
||||||
{
|
|
||||||
Anchor = Anchor.CentreRight,
|
|
||||||
Origin = Anchor.CentreRight,
|
|
||||||
AutoSizeAxes = Axes.Both,
|
|
||||||
Direction = FillDirection.Horizontal,
|
|
||||||
Colour = colours.Gray9,
|
|
||||||
Children = new[]
|
|
||||||
{
|
|
||||||
summary = new OsuSpriteText
|
|
||||||
{
|
|
||||||
Text = "0 participants",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
Host.BindValueChanged(host =>
|
|
||||||
{
|
|
||||||
hostText.Clear();
|
|
||||||
flagContainer.Clear();
|
|
||||||
|
|
||||||
if (host.NewValue != null)
|
|
||||||
{
|
|
||||||
hostText.AddText("hosted by ");
|
|
||||||
hostText.AddUserLink(host.NewValue, s => s.Font = s.Font.With(Typeface.Torus, weight: FontWeight.Bold, italics: true));
|
|
||||||
|
|
||||||
flagContainer.Child = new UpdateableFlag(host.NewValue.Country) { RelativeSizeAxes = Axes.Both };
|
|
||||||
}
|
|
||||||
}, true);
|
|
||||||
|
|
||||||
ParticipantCount.BindValueChanged(count => summary.Text = "participant".ToQuantity(count.NewValue), true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,81 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Displays contents in a "pill".
|
||||||
|
/// </summary>
|
||||||
|
public class PillContainer : Container
|
||||||
|
{
|
||||||
|
private const float padding = 8;
|
||||||
|
|
||||||
|
public readonly Drawable Background;
|
||||||
|
|
||||||
|
protected override Container<Drawable> Content => content;
|
||||||
|
private readonly Container content;
|
||||||
|
|
||||||
|
public PillContainer()
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.X;
|
||||||
|
Height = 16;
|
||||||
|
|
||||||
|
InternalChild = new CircularContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
AutoSizeAxes = Axes.X,
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
Masking = true,
|
||||||
|
Children = new[]
|
||||||
|
{
|
||||||
|
Background = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Color4.Black,
|
||||||
|
Alpha = 0.5f
|
||||||
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Padding = new MarginPadding { Horizontal = padding },
|
||||||
|
Child = new GridContainer
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
ColumnDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(GridSizeMode.AutoSize, minSize: 80 - 2 * padding)
|
||||||
|
},
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Padding = new MarginPadding { Bottom = 2 },
|
||||||
|
Child = content = new Container
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
// 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.Collections.Specialized;
|
||||||
|
using Humanizer;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A pill that displays the playlist item count.
|
||||||
|
/// </summary>
|
||||||
|
public class PlaylistCountPill : OnlinePlayComposite
|
||||||
|
{
|
||||||
|
private OsuTextFlowContainer count;
|
||||||
|
|
||||||
|
public PlaylistCountPill()
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
InternalChild = new PillContainer
|
||||||
|
{
|
||||||
|
Child = count = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12))
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
Playlist.BindCollectionChanged(updateCount, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateCount(object sender, NotifyCollectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
count.Clear();
|
||||||
|
count.AddText(Playlist.Count.ToString(), s => s.Font = s.Font.With(weight: FontWeight.Bold));
|
||||||
|
count.AddText(" ");
|
||||||
|
count.AddText("Beatmap".ToQuantity(Playlist.Count, ShowQuantityAs.None));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,59 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.UserInterface;
|
|
||||||
using osu.Game.Graphics.UserInterface;
|
|
||||||
|
|
||||||
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
|
||||||
{
|
|
||||||
public class PlaylistsFilterControl : FilterControl
|
|
||||||
{
|
|
||||||
private readonly Dropdown<PlaylistsCategory> dropdown;
|
|
||||||
|
|
||||||
public PlaylistsFilterControl()
|
|
||||||
{
|
|
||||||
AddInternal(dropdown = new SlimEnumDropdown<PlaylistsCategory>
|
|
||||||
{
|
|
||||||
Anchor = Anchor.BottomRight,
|
|
||||||
Origin = Anchor.TopRight,
|
|
||||||
RelativeSizeAxes = Axes.None,
|
|
||||||
Width = 160,
|
|
||||||
X = -HORIZONTAL_PADDING,
|
|
||||||
Y = -30
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void LoadComplete()
|
|
||||||
{
|
|
||||||
base.LoadComplete();
|
|
||||||
|
|
||||||
dropdown.Current.BindValueChanged(_ => UpdateFilter());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override FilterCriteria CreateCriteria()
|
|
||||||
{
|
|
||||||
var criteria = base.CreateCriteria();
|
|
||||||
|
|
||||||
switch (dropdown.Current.Value)
|
|
||||||
{
|
|
||||||
case PlaylistsCategory.Normal:
|
|
||||||
criteria.Category = "normal";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PlaylistsCategory.Spotlight:
|
|
||||||
criteria.Category = "spotlight";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return criteria;
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum PlaylistsCategory
|
|
||||||
{
|
|
||||||
Any,
|
|
||||||
Normal,
|
|
||||||
Spotlight
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,80 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||||
|
{
|
||||||
|
public class RankRangePill : MultiplayerRoomComposite
|
||||||
|
{
|
||||||
|
private OsuTextFlowContainer rankFlow;
|
||||||
|
|
||||||
|
public RankRangePill()
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
InternalChild = new PillContainer
|
||||||
|
{
|
||||||
|
Child = new FillFlowContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Spacing = new Vector2(4),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new SpriteIcon
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Size = new Vector2(8),
|
||||||
|
Icon = FontAwesome.Solid.User
|
||||||
|
},
|
||||||
|
rankFlow = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12))
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnRoomUpdated()
|
||||||
|
{
|
||||||
|
base.OnRoomUpdated();
|
||||||
|
|
||||||
|
rankFlow.Clear();
|
||||||
|
|
||||||
|
if (Room == null || Room.Users.All(u => u.User == null))
|
||||||
|
{
|
||||||
|
rankFlow.AddText("-");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int minRank = Room.Users.Select(u => u.User?.Statistics.GlobalRank ?? 0).DefaultIfEmpty(0).Min();
|
||||||
|
int maxRank = Room.Users.Select(u => u.User?.Statistics.GlobalRank ?? 0).DefaultIfEmpty(0).Max();
|
||||||
|
|
||||||
|
rankFlow.AddText("#");
|
||||||
|
rankFlow.AddText(minRank.ToString("#,0"), s => s.Font = s.Font.With(weight: FontWeight.Bold));
|
||||||
|
|
||||||
|
rankFlow.AddText(" - ");
|
||||||
|
|
||||||
|
rankFlow.AddText("#");
|
||||||
|
rankFlow.AddText(maxRank.ToString("#,0"), s => s.Font = s.Font.With(weight: FontWeight.Bold));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,278 @@
|
|||||||
|
// 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.Collections.Specialized;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Users;
|
||||||
|
using osu.Game.Users.Drawables;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||||
|
{
|
||||||
|
public class RecentParticipantsList : OnlinePlayComposite
|
||||||
|
{
|
||||||
|
private const float avatar_size = 36;
|
||||||
|
|
||||||
|
private FillFlowContainer<CircularAvatar> avatarFlow;
|
||||||
|
|
||||||
|
private HiddenUserCount hiddenUsers;
|
||||||
|
private OsuSpriteText totalCount;
|
||||||
|
|
||||||
|
public RecentParticipantsList()
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.X;
|
||||||
|
Height = 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider colours)
|
||||||
|
{
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
CornerRadius = 10,
|
||||||
|
Shear = new Vector2(0.2f, 0),
|
||||||
|
Child = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = colours.Background4,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
Spacing = new Vector2(4),
|
||||||
|
Padding = new MarginPadding { Right = 16 },
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new SpriteIcon
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Size = new Vector2(16),
|
||||||
|
Margin = new MarginPadding { Left = 8 },
|
||||||
|
Icon = FontAwesome.Solid.User,
|
||||||
|
},
|
||||||
|
totalCount = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Font = OsuFont.Default.With(weight: FontWeight.Bold),
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
},
|
||||||
|
avatarFlow = new FillFlowContainer<CircularAvatar>
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
Spacing = new Vector2(4),
|
||||||
|
Margin = new MarginPadding { Left = 4 },
|
||||||
|
},
|
||||||
|
hiddenUsers = new HiddenUserCount
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
RecentParticipants.BindCollectionChanged(onParticipantsChanged, true);
|
||||||
|
ParticipantCount.BindValueChanged(_ =>
|
||||||
|
{
|
||||||
|
updateHiddenUsers();
|
||||||
|
totalCount.Text = ParticipantCount.Value.ToString();
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int numberOfCircles = 4;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum number of circles visible (including the "hidden count" circle in the overflow case).
|
||||||
|
/// </summary>
|
||||||
|
public int NumberOfCircles
|
||||||
|
{
|
||||||
|
get => numberOfCircles;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
numberOfCircles = value;
|
||||||
|
|
||||||
|
if (LoadState < LoadState.Loaded)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Reinitialising the list looks janky, but this is unlikely to be used in a setting where it's visible.
|
||||||
|
clearUsers();
|
||||||
|
foreach (var u in RecentParticipants)
|
||||||
|
addUser(u);
|
||||||
|
|
||||||
|
updateHiddenUsers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onParticipantsChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
switch (e.Action)
|
||||||
|
{
|
||||||
|
case NotifyCollectionChangedAction.Add:
|
||||||
|
foreach (var added in e.NewItems.OfType<User>())
|
||||||
|
addUser(added);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NotifyCollectionChangedAction.Remove:
|
||||||
|
foreach (var removed in e.OldItems.OfType<User>())
|
||||||
|
removeUser(removed);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NotifyCollectionChangedAction.Reset:
|
||||||
|
clearUsers();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NotifyCollectionChangedAction.Replace:
|
||||||
|
case NotifyCollectionChangedAction.Move:
|
||||||
|
// Easiest is to just reinitialise the whole list. These are unlikely to ever be use cases.
|
||||||
|
clearUsers();
|
||||||
|
foreach (var u in RecentParticipants)
|
||||||
|
addUser(u);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateHiddenUsers();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int displayedCircles => avatarFlow.Count + (hiddenUsers.Count > 0 ? 1 : 0);
|
||||||
|
|
||||||
|
private void addUser(User user)
|
||||||
|
{
|
||||||
|
if (displayedCircles < NumberOfCircles)
|
||||||
|
avatarFlow.Add(new CircularAvatar { User = user });
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeUser(User user)
|
||||||
|
{
|
||||||
|
avatarFlow.RemoveAll(a => a.User == user);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearUsers()
|
||||||
|
{
|
||||||
|
avatarFlow.Clear();
|
||||||
|
updateHiddenUsers();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateHiddenUsers()
|
||||||
|
{
|
||||||
|
int hiddenCount = 0;
|
||||||
|
if (RecentParticipants.Count > NumberOfCircles)
|
||||||
|
hiddenCount = ParticipantCount.Value - NumberOfCircles + 1;
|
||||||
|
|
||||||
|
hiddenUsers.Count = hiddenCount;
|
||||||
|
|
||||||
|
if (displayedCircles > NumberOfCircles)
|
||||||
|
avatarFlow.Remove(avatarFlow.Last());
|
||||||
|
else if (displayedCircles < NumberOfCircles)
|
||||||
|
{
|
||||||
|
var nextUser = RecentParticipants.FirstOrDefault(u => avatarFlow.All(a => a.User != u));
|
||||||
|
if (nextUser != null) addUser(nextUser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CircularAvatar : CompositeDrawable
|
||||||
|
{
|
||||||
|
public User User
|
||||||
|
{
|
||||||
|
get => avatar.User;
|
||||||
|
set => avatar.User = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly UpdateableAvatar avatar = new UpdateableAvatar(showUsernameTooltip: true) { RelativeSizeAxes = Axes.Both };
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider colours)
|
||||||
|
{
|
||||||
|
Size = new Vector2(avatar_size);
|
||||||
|
|
||||||
|
InternalChild = new CircularContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = colours.Background5,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
avatar
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class HiddenUserCount : CompositeDrawable
|
||||||
|
{
|
||||||
|
public int Count
|
||||||
|
{
|
||||||
|
get => count;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
count = value;
|
||||||
|
countText.Text = $"+{count}";
|
||||||
|
|
||||||
|
if (count > 0)
|
||||||
|
Show();
|
||||||
|
else
|
||||||
|
Hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int count;
|
||||||
|
|
||||||
|
private readonly SpriteText countText = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Font = OsuFont.Default.With(weight: FontWeight.Bold),
|
||||||
|
};
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider colours)
|
||||||
|
{
|
||||||
|
Size = new Vector2(avatar_size);
|
||||||
|
Alpha = 0;
|
||||||
|
|
||||||
|
InternalChild = new CircularContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = colours.Background5,
|
||||||
|
},
|
||||||
|
countText
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,86 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Game.Graphics;
|
|
||||||
using osu.Game.Graphics.Containers;
|
|
||||||
using osu.Game.Screens.OnlinePlay.Components;
|
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
|
||||||
{
|
|
||||||
public class RoomInfo : OnlinePlayComposite
|
|
||||||
{
|
|
||||||
private readonly List<Drawable> statusElements = new List<Drawable>();
|
|
||||||
private readonly OsuTextFlowContainer roomName;
|
|
||||||
|
|
||||||
public RoomInfo()
|
|
||||||
{
|
|
||||||
AutoSizeAxes = Axes.Y;
|
|
||||||
|
|
||||||
RoomLocalUserInfo localUserInfo;
|
|
||||||
RoomStatusInfo statusInfo;
|
|
||||||
ModeTypeInfo typeInfo;
|
|
||||||
ParticipantInfo participantInfo;
|
|
||||||
|
|
||||||
InternalChild = new FillFlowContainer
|
|
||||||
{
|
|
||||||
Anchor = Anchor.CentreLeft,
|
|
||||||
Origin = Anchor.CentreLeft,
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Spacing = new Vector2(0, 10),
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
Direction = FillDirection.Vertical,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
roomName = new OsuTextFlowContainer(t => t.Font = OsuFont.GetFont(size: 30))
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
},
|
|
||||||
participantInfo = new ParticipantInfo(),
|
|
||||||
new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
statusInfo = new RoomStatusInfo(),
|
|
||||||
typeInfo = new ModeTypeInfo
|
|
||||||
{
|
|
||||||
Anchor = Anchor.BottomRight,
|
|
||||||
Origin = Anchor.BottomRight
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
localUserInfo = new RoomLocalUserInfo(),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
statusElements.AddRange(new Drawable[]
|
|
||||||
{
|
|
||||||
statusInfo, typeInfo, participantInfo, localUserInfo
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void LoadComplete()
|
|
||||||
{
|
|
||||||
base.LoadComplete();
|
|
||||||
if (RoomID.Value == null)
|
|
||||||
statusElements.ForEach(e => e.FadeOut());
|
|
||||||
RoomID.BindValueChanged(id =>
|
|
||||||
{
|
|
||||||
if (id.NewValue == null)
|
|
||||||
statusElements.ForEach(e => e.FadeOut(100));
|
|
||||||
else
|
|
||||||
statusElements.ForEach(e => e.FadeIn(100));
|
|
||||||
}, true);
|
|
||||||
RoomName.BindValueChanged(name =>
|
|
||||||
{
|
|
||||||
roomName.Text = name.NewValue ?? "No room selected";
|
|
||||||
}, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,91 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Graphics.Shapes;
|
|
||||||
using osu.Game.Beatmaps;
|
|
||||||
using osu.Game.Graphics;
|
|
||||||
using osu.Game.Screens.OnlinePlay.Components;
|
|
||||||
using osuTK.Graphics;
|
|
||||||
|
|
||||||
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
|
||||||
{
|
|
||||||
public class RoomInspector : OnlinePlayComposite
|
|
||||||
{
|
|
||||||
private const float transition_duration = 100;
|
|
||||||
|
|
||||||
private readonly MarginPadding contentPadding = new MarginPadding { Horizontal = 20, Vertical = 10 };
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private BeatmapManager beatmaps { get; set; }
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(OsuColour colours)
|
|
||||||
{
|
|
||||||
OverlinedHeader participantsHeader;
|
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
|
||||||
{
|
|
||||||
new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = Color4.Black,
|
|
||||||
Alpha = 0.25f
|
|
||||||
},
|
|
||||||
new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Padding = new MarginPadding { Horizontal = 30 },
|
|
||||||
Child = new GridContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Content = new[]
|
|
||||||
{
|
|
||||||
new Drawable[]
|
|
||||||
{
|
|
||||||
new FillFlowContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
Direction = FillDirection.Vertical,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new RoomInfo
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Margin = new MarginPadding { Vertical = 60 },
|
|
||||||
},
|
|
||||||
participantsHeader = new OverlinedHeader("Recent Participants"),
|
|
||||||
new ParticipantsDisplay(Direction.Vertical)
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Height = ParticipantsList.TILE_SIZE * 3,
|
|
||||||
Details = { BindTarget = participantsHeader.Details }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new Drawable[] { new OverlinedPlaylistHeader(), },
|
|
||||||
new Drawable[]
|
|
||||||
{
|
|
||||||
new DrawableRoomPlaylist(false, false)
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Items = { BindTarget = Playlist }
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
RowDimensions = new[]
|
|
||||||
{
|
|
||||||
new Dimension(GridSizeMode.AutoSize),
|
|
||||||
new Dimension(GridSizeMode.AutoSize),
|
|
||||||
new Dimension(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,49 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||||
|
{
|
||||||
|
public class RoomSpecialCategoryPill : OnlinePlayComposite
|
||||||
|
{
|
||||||
|
private SpriteText text;
|
||||||
|
|
||||||
|
public RoomSpecialCategoryPill()
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
InternalChild = new PillContainer
|
||||||
|
{
|
||||||
|
Background =
|
||||||
|
{
|
||||||
|
Colour = colours.Pink,
|
||||||
|
Alpha = 1
|
||||||
|
},
|
||||||
|
Child = text = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 12),
|
||||||
|
Colour = Color4.Black
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
Category.BindValueChanged(c => text.Text = c.NewValue.ToString(), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
// 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.Graphics;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Online.Rooms;
|
||||||
|
using osu.Game.Online.Rooms.RoomStatuses;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A pill that displays the room's current status.
|
||||||
|
/// </summary>
|
||||||
|
public class RoomStatusPill : OnlinePlayComposite
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private OsuColour colours { get; set; }
|
||||||
|
|
||||||
|
private PillContainer pill;
|
||||||
|
private SpriteText statusText;
|
||||||
|
|
||||||
|
public RoomStatusPill()
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
InternalChild = pill = new PillContainer
|
||||||
|
{
|
||||||
|
Child = statusText = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 12),
|
||||||
|
Colour = Color4.Black
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
EndDate.BindValueChanged(_ => updateDisplay());
|
||||||
|
Status.BindValueChanged(_ => updateDisplay(), true);
|
||||||
|
|
||||||
|
FinishTransforms(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateDisplay()
|
||||||
|
{
|
||||||
|
RoomStatus status = getDisplayStatus();
|
||||||
|
|
||||||
|
pill.Background.Alpha = 1;
|
||||||
|
pill.Background.FadeColour(status.GetAppropriateColour(colours), 100);
|
||||||
|
statusText.Text = status.Message;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RoomStatus getDisplayStatus()
|
||||||
|
{
|
||||||
|
if (EndDate.Value < DateTimeOffset.Now)
|
||||||
|
return new RoomStatusEnded();
|
||||||
|
|
||||||
|
return Status.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -50,6 +50,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
|||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
AutoSizeAxes = Axes.Y;
|
AutoSizeAxes = Axes.Y;
|
||||||
|
|
||||||
|
// account for the fact we are in a scroll container and want a bit of spacing from the scroll bar.
|
||||||
|
Padding = new MarginPadding { Right = 5 };
|
||||||
|
|
||||||
InternalChild = new OsuContextMenuContainer
|
InternalChild = new OsuContextMenuContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
@ -59,7 +62,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
|||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
Direction = FillDirection.Vertical,
|
Direction = FillDirection.Vertical,
|
||||||
Spacing = new Vector2(2),
|
Spacing = new Vector2(10),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -137,7 +140,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
|||||||
|
|
||||||
roomFlow.Remove(toRemove);
|
roomFlow.Remove(toRemove);
|
||||||
|
|
||||||
selectedRoom.Value = null;
|
// selection may have a lease due to being in a sub screen.
|
||||||
|
if (!selectedRoom.Disabled)
|
||||||
|
selectedRoom.Value = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,7 +154,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
|||||||
|
|
||||||
protected override bool OnClick(ClickEvent e)
|
protected override bool OnClick(ClickEvent e)
|
||||||
{
|
{
|
||||||
selectedRoom.Value = null;
|
if (!selectedRoom.Disabled)
|
||||||
|
selectedRoom.Value = null;
|
||||||
return base.OnClick(e);
|
return base.OnClick(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,6 +217,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
|||||||
|
|
||||||
private void selectNext(int direction)
|
private void selectNext(int direction)
|
||||||
{
|
{
|
||||||
|
if (selectedRoom.Disabled)
|
||||||
|
return;
|
||||||
|
|
||||||
var visibleRooms = Rooms.AsEnumerable().Where(r => r.IsPresent);
|
var visibleRooms = Rooms.AsEnumerable().Where(r => r.IsPresent);
|
||||||
|
|
||||||
Room room;
|
Room room;
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
@ -9,15 +11,20 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
|
using osu.Framework.Threading;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
||||||
using osu.Game.Screens.OnlinePlay.Match;
|
using osu.Game.Screens.OnlinePlay.Match;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Screens.OnlinePlay.Lounge
|
namespace osu.Game.Screens.OnlinePlay.Lounge
|
||||||
{
|
{
|
||||||
@ -28,11 +35,16 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
|
|||||||
|
|
||||||
protected override UserActivity InitialActivity => new UserActivity.SearchingForLobby();
|
protected override UserActivity InitialActivity => new UserActivity.SearchingForLobby();
|
||||||
|
|
||||||
|
protected Container<OsuButton> Buttons { get; } = new Container<OsuButton>
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
AutoSizeAxes = Axes.Both
|
||||||
|
};
|
||||||
|
|
||||||
private readonly IBindable<bool> initialRoomsReceived = new Bindable<bool>();
|
private readonly IBindable<bool> initialRoomsReceived = new Bindable<bool>();
|
||||||
private readonly IBindable<bool> operationInProgress = new Bindable<bool>();
|
private readonly IBindable<bool> operationInProgress = new Bindable<bool>();
|
||||||
|
|
||||||
private FilterControl filter;
|
|
||||||
private Container content;
|
|
||||||
private LoadingLayer loadingLayer;
|
private LoadingLayer loadingLayer;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
@ -44,53 +56,112 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
|
|||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private OngoingOperationTracker ongoingOperationTracker { get; set; }
|
private OngoingOperationTracker ongoingOperationTracker { get; set; }
|
||||||
|
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
private Bindable<FilterCriteria> filter { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private IBindable<RulesetInfo> ruleset { get; set; }
|
||||||
|
|
||||||
[CanBeNull]
|
[CanBeNull]
|
||||||
private IDisposable joiningRoomOperation { get; set; }
|
private IDisposable joiningRoomOperation { get; set; }
|
||||||
|
|
||||||
private RoomsContainer roomsContainer;
|
private RoomsContainer roomsContainer;
|
||||||
|
private SearchTextBox searchTextBox;
|
||||||
|
private Dropdown<RoomStatusFilter> statusDropdown;
|
||||||
|
|
||||||
|
[CanBeNull]
|
||||||
|
private LeasedBindable<Room> selectionLease;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
|
filter ??= new Bindable<FilterCriteria>(new FilterCriteria());
|
||||||
|
|
||||||
OsuScrollContainer scrollContainer;
|
OsuScrollContainer scrollContainer;
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new[]
|
||||||
{
|
{
|
||||||
content = new Container
|
loadingLayer = new LoadingLayer(true),
|
||||||
|
new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Children = new Drawable[]
|
Padding = new MarginPadding
|
||||||
{
|
{
|
||||||
new Container
|
Left = WaveOverlayContainer.WIDTH_PADDING,
|
||||||
|
Right = WaveOverlayContainer.WIDTH_PADDING,
|
||||||
|
},
|
||||||
|
Child = new GridContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
RowDimensions = new[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
new Dimension(GridSizeMode.Absolute, Header.HEIGHT),
|
||||||
Width = 0.55f,
|
new Dimension(GridSizeMode.Absolute, 25),
|
||||||
Children = new Drawable[]
|
new Dimension(GridSizeMode.Absolute, 20)
|
||||||
|
},
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new Drawable[]
|
||||||
{
|
{
|
||||||
scrollContainer = new OsuScrollContainer
|
searchTextBox = new LoungeSearchTextBox
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreRight,
|
||||||
|
Origin = Anchor.CentreRight,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Width = 0.6f,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
ScrollbarOverlapsContent = false,
|
Depth = float.MinValue, // Contained filters should appear over the top of rooms.
|
||||||
Padding = new MarginPadding(10),
|
Children = new Drawable[]
|
||||||
Child = roomsContainer = new RoomsContainer()
|
{
|
||||||
|
Buttons.WithChild(CreateNewRoomButton().With(d =>
|
||||||
|
{
|
||||||
|
d.Anchor = Anchor.BottomLeft;
|
||||||
|
d.Origin = Anchor.BottomLeft;
|
||||||
|
d.Size = new Vector2(150, 37.5f);
|
||||||
|
d.Action = () => Open();
|
||||||
|
})),
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopRight,
|
||||||
|
Origin = Anchor.TopRight,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
Spacing = new Vector2(10),
|
||||||
|
ChildrenEnumerable = CreateFilterControls().Select(f => f.With(d =>
|
||||||
|
{
|
||||||
|
d.Anchor = Anchor.TopRight;
|
||||||
|
d.Origin = Anchor.TopRight;
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
scrollContainer = new OsuScrollContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
ScrollbarOverlapsContent = false,
|
||||||
|
Child = roomsContainer = new RoomsContainer()
|
||||||
|
},
|
||||||
|
}
|
||||||
},
|
},
|
||||||
loadingLayer = new LoadingLayer(true),
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
new RoomInspector
|
|
||||||
{
|
|
||||||
Anchor = Anchor.TopRight,
|
|
||||||
Origin = Anchor.TopRight,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Width = 0.45f,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
filter = CreateFilterControl().With(d =>
|
|
||||||
{
|
|
||||||
d.RelativeSizeAxes = Axes.X;
|
|
||||||
d.Height = 80;
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// scroll selected room into view on selection.
|
// scroll selected room into view on selection.
|
||||||
@ -106,6 +177,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
|
searchTextBox.Current.BindValueChanged(_ => updateFilterDebounced());
|
||||||
|
ruleset.BindValueChanged(_ => UpdateFilter());
|
||||||
|
|
||||||
initialRoomsReceived.BindTo(RoomManager.InitialRoomsReceived);
|
initialRoomsReceived.BindTo(RoomManager.InitialRoomsReceived);
|
||||||
initialRoomsReceived.BindValueChanged(_ => updateLoadingLayer());
|
initialRoomsReceived.BindValueChanged(_ => updateLoadingLayer());
|
||||||
|
|
||||||
@ -114,24 +188,49 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
|
|||||||
operationInProgress.BindTo(ongoingOperationTracker.InProgress);
|
operationInProgress.BindTo(ongoingOperationTracker.InProgress);
|
||||||
operationInProgress.BindValueChanged(_ => updateLoadingLayer(), true);
|
operationInProgress.BindValueChanged(_ => updateLoadingLayer(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateAfterChildren()
|
#region Filtering
|
||||||
{
|
|
||||||
base.UpdateAfterChildren();
|
|
||||||
|
|
||||||
content.Padding = new MarginPadding
|
protected void UpdateFilter() => Scheduler.AddOnce(updateFilter);
|
||||||
|
|
||||||
|
private ScheduledDelegate scheduledFilterUpdate;
|
||||||
|
|
||||||
|
private void updateFilterDebounced()
|
||||||
|
{
|
||||||
|
scheduledFilterUpdate?.Cancel();
|
||||||
|
scheduledFilterUpdate = Scheduler.AddDelayed(UpdateFilter, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateFilter()
|
||||||
|
{
|
||||||
|
scheduledFilterUpdate?.Cancel();
|
||||||
|
filter.Value = CreateFilterCriteria();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual FilterCriteria CreateFilterCriteria() => new FilterCriteria
|
||||||
|
{
|
||||||
|
SearchString = searchTextBox.Current.Value,
|
||||||
|
Ruleset = ruleset.Value,
|
||||||
|
Status = statusDropdown.Current.Value
|
||||||
|
};
|
||||||
|
|
||||||
|
protected virtual IEnumerable<Drawable> CreateFilterControls()
|
||||||
|
{
|
||||||
|
statusDropdown = new SlimEnumDropdown<RoomStatusFilter>
|
||||||
{
|
{
|
||||||
Top = filter.DrawHeight,
|
RelativeSizeAxes = Axes.None,
|
||||||
Left = WaveOverlayContainer.WIDTH_PADDING - DrawableRoom.SELECTION_BORDER_WIDTH + HORIZONTAL_OVERFLOW_PADDING,
|
Width = 160,
|
||||||
Right = WaveOverlayContainer.WIDTH_PADDING + HORIZONTAL_OVERFLOW_PADDING,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
statusDropdown.Current.BindValueChanged(_ => UpdateFilter());
|
||||||
|
|
||||||
|
yield return statusDropdown;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnFocus(FocusEvent e)
|
#endregion
|
||||||
{
|
|
||||||
filter.TakeFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnEntering(IScreen last)
|
public override void OnEntering(IScreen last)
|
||||||
{
|
{
|
||||||
@ -144,6 +243,11 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
|
|||||||
{
|
{
|
||||||
base.OnResuming(last);
|
base.OnResuming(last);
|
||||||
|
|
||||||
|
Debug.Assert(selectionLease != null);
|
||||||
|
|
||||||
|
selectionLease.Return();
|
||||||
|
selectionLease = null;
|
||||||
|
|
||||||
if (selectedRoom.Value?.RoomID.Value == null)
|
if (selectedRoom.Value?.RoomID.Value == null)
|
||||||
selectedRoom.Value = new Room();
|
selectedRoom.Value = new Room();
|
||||||
|
|
||||||
@ -164,14 +268,19 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
|
|||||||
base.OnSuspending(next);
|
base.OnSuspending(next);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnFocus(FocusEvent e)
|
||||||
|
{
|
||||||
|
searchTextBox.TakeFocus();
|
||||||
|
}
|
||||||
|
|
||||||
private void onReturning()
|
private void onReturning()
|
||||||
{
|
{
|
||||||
filter.HoldFocus = true;
|
searchTextBox.HoldFocus = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onLeaving()
|
private void onLeaving()
|
||||||
{
|
{
|
||||||
filter.HoldFocus = false;
|
searchTextBox.HoldFocus = false;
|
||||||
|
|
||||||
// ensure any password prompt is dismissed.
|
// ensure any password prompt is dismissed.
|
||||||
this.HidePopover();
|
this.HidePopover();
|
||||||
@ -199,23 +308,32 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Push a room as a new subscreen.
|
/// Push a room as a new subscreen.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Open(Room room) => Schedule(() =>
|
/// <param name="room">An optional template to use when creating the room.</param>
|
||||||
|
public void Open(Room room = null) => Schedule(() =>
|
||||||
{
|
{
|
||||||
// Handles the case where a room is clicked 3 times in quick succession
|
// Handles the case where a room is clicked 3 times in quick succession
|
||||||
if (!this.IsCurrentScreen())
|
if (!this.IsCurrentScreen())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
OpenNewRoom(room);
|
OpenNewRoom(room ?? CreateNewRoom());
|
||||||
});
|
});
|
||||||
|
|
||||||
protected virtual void OpenNewRoom(Room room)
|
protected virtual void OpenNewRoom(Room room)
|
||||||
{
|
{
|
||||||
selectedRoom.Value = room;
|
selectionLease = selectedRoom.BeginLease(false);
|
||||||
|
Debug.Assert(selectionLease != null);
|
||||||
|
selectionLease.Value = room;
|
||||||
|
|
||||||
this.Push(CreateRoomSubScreen(room));
|
this.Push(CreateRoomSubScreen(room));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract FilterControl CreateFilterControl();
|
protected abstract OsuButton CreateNewRoomButton();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new room.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The created <see cref="Room"/>.</returns>
|
||||||
|
protected abstract Room CreateNewRoom();
|
||||||
|
|
||||||
protected abstract RoomSubScreen CreateRoomSubScreen(Room room);
|
protected abstract RoomSubScreen CreateRoomSubScreen(Room room);
|
||||||
|
|
||||||
@ -226,5 +344,15 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
|
|||||||
else
|
else
|
||||||
loadingLayer.Hide();
|
loadingLayer.Hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class LoungeSearchTextBox : SearchTextBox
|
||||||
|
{
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
BackgroundUnfocused = OsuColour.Gray(0.06f);
|
||||||
|
BackgroundFocused = OsuColour.Gray(0.12f);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
|
SpriteText.Font = SpriteText.Font.With(size: 14);
|
||||||
Triangles.TriangleScale = 1.5f;
|
Triangles.TriangleScale = 1.5f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,8 +8,10 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Audio.Sample;
|
using osu.Framework.Audio.Sample;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -17,6 +19,7 @@ using osu.Game.Online.Rooms;
|
|||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Mods;
|
using osu.Game.Overlays.Mods;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Screens.OnlinePlay.Match.Components;
|
||||||
|
|
||||||
namespace osu.Game.Screens.OnlinePlay.Match
|
namespace osu.Game.Screens.OnlinePlay.Match
|
||||||
{
|
{
|
||||||
@ -61,8 +64,15 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
|||||||
|
|
||||||
protected RoomSubScreen()
|
protected RoomSubScreen()
|
||||||
{
|
{
|
||||||
|
Padding = new MarginPadding { Top = Header.HEIGHT };
|
||||||
|
|
||||||
AddRangeInternal(new Drawable[]
|
AddRangeInternal(new Drawable[]
|
||||||
{
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Color4Extensions.FromHex(@"3e3a44") // This is super temporary.
|
||||||
|
},
|
||||||
BeatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker
|
BeatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker
|
||||||
{
|
{
|
||||||
SelectedItem = { BindTarget = SelectedItem }
|
SelectedItem = { BindTarget = SelectedItem }
|
||||||
@ -250,5 +260,9 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
|||||||
private class UserModSelectOverlay : LocalPlayerModSelectOverlay
|
private class UserModSelectOverlay : LocalPlayerModSelectOverlay
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class UserModSelectButton : PurpleTriangleButton
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Screens.Play.HUD;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||||
|
{
|
||||||
|
public class GameplayMatchScoreDisplay : MatchScoreDisplay
|
||||||
|
{
|
||||||
|
public Bindable<bool> Expanded = new Bindable<bool>();
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
Scale = new Vector2(0.5f);
|
||||||
|
|
||||||
|
Expanded.BindValueChanged(expandedChanged, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expandedChanged(ValueChangedEvent<bool> expanded)
|
||||||
|
{
|
||||||
|
if (expanded.NewValue)
|
||||||
|
{
|
||||||
|
Score1Text.FadeIn(500, Easing.OutQuint);
|
||||||
|
Score2Text.FadeIn(500, Easing.OutQuint);
|
||||||
|
this.ResizeWidthTo(2, 500, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Score1Text.FadeOut(500, Easing.OutQuint);
|
||||||
|
Score2Text.FadeOut(500, Easing.OutQuint);
|
||||||
|
this.ResizeWidthTo(1, 500, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,9 +4,7 @@
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Game.Graphics.UserInterface;
|
|
||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
using osu.Game.Online.Rooms;
|
|
||||||
using osu.Game.Screens.OnlinePlay.Components;
|
using osu.Game.Screens.OnlinePlay.Components;
|
||||||
using osu.Game.Screens.OnlinePlay.Lounge;
|
using osu.Game.Screens.OnlinePlay.Lounge;
|
||||||
|
|
||||||
@ -54,20 +52,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
Logger.Log($"Polling adjusted (listing: {multiplayerRoomManager.TimeBetweenListingPolls.Value}, selection: {multiplayerRoomManager.TimeBetweenSelectionPolls.Value})");
|
Logger.Log($"Polling adjusted (listing: {multiplayerRoomManager.TimeBetweenListingPolls.Value}, selection: {multiplayerRoomManager.TimeBetweenSelectionPolls.Value})");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Room CreateNewRoom() =>
|
|
||||||
new Room
|
|
||||||
{
|
|
||||||
Name = { Value = $"{API.LocalUser}'s awesome room" },
|
|
||||||
Category = { Value = RoomCategory.Realtime },
|
|
||||||
Type = { Value = MatchType.HeadToHead },
|
|
||||||
};
|
|
||||||
|
|
||||||
protected override string ScreenTitle => "Multiplayer";
|
protected override string ScreenTitle => "Multiplayer";
|
||||||
|
|
||||||
protected override RoomManager CreateRoomManager() => new MultiplayerRoomManager();
|
protected override RoomManager CreateRoomManager() => new MultiplayerRoomManager();
|
||||||
|
|
||||||
protected override LoungeSubScreen CreateLounge() => new MultiplayerLoungeSubScreen();
|
protected override LoungeSubScreen CreateLounge() => new MultiplayerLoungeSubScreen();
|
||||||
|
|
||||||
protected override OsuButton CreateNewMultiplayerGameButton() => new CreateMultiplayerMatchButton();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
|
||||||
|
|
||||||
namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|
||||||
{
|
|
||||||
public class MultiplayerFilterControl : FilterControl
|
|
||||||
{
|
|
||||||
protected override FilterCriteria CreateCriteria()
|
|
||||||
{
|
|
||||||
var criteria = base.CreateCriteria();
|
|
||||||
criteria.Category = "realtime";
|
|
||||||
return criteria;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
using osu.Game.Screens.OnlinePlay.Lounge;
|
using osu.Game.Screens.OnlinePlay.Lounge;
|
||||||
@ -13,13 +15,30 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
{
|
{
|
||||||
public class MultiplayerLoungeSubScreen : LoungeSubScreen
|
public class MultiplayerLoungeSubScreen : LoungeSubScreen
|
||||||
{
|
{
|
||||||
protected override FilterControl CreateFilterControl() => new MultiplayerFilterControl();
|
[Resolved]
|
||||||
|
private IAPIProvider api { get; set; }
|
||||||
protected override RoomSubScreen CreateRoomSubScreen(Room room) => new MultiplayerMatchSubScreen(room);
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private MultiplayerClient client { get; set; }
|
private MultiplayerClient client { get; set; }
|
||||||
|
|
||||||
|
protected override FilterCriteria CreateFilterCriteria()
|
||||||
|
{
|
||||||
|
var criteria = base.CreateFilterCriteria();
|
||||||
|
criteria.Category = @"realtime";
|
||||||
|
return criteria;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override OsuButton CreateNewRoomButton() => new CreateMultiplayerMatchButton();
|
||||||
|
|
||||||
|
protected override Room CreateNewRoom() => new Room
|
||||||
|
{
|
||||||
|
Name = { Value = $"{api.LocalUser}'s awesome room" },
|
||||||
|
Category = { Value = RoomCategory.Realtime },
|
||||||
|
Type = { Value = MatchType.HeadToHead },
|
||||||
|
};
|
||||||
|
|
||||||
|
protected override RoomSubScreen CreateRoomSubScreen(Room room) => new MultiplayerMatchSubScreen(room);
|
||||||
|
|
||||||
protected override void OpenNewRoom(Room room)
|
protected override void OpenNewRoom(Room room)
|
||||||
{
|
{
|
||||||
if (client?.IsConnected.Value != true)
|
if (client?.IsConnected.Value != true)
|
||||||
|
@ -176,7 +176,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
Spacing = new Vector2(10, 0),
|
Spacing = new Vector2(10, 0),
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new PurpleTriangleButton
|
new UserModSelectButton
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
@ -274,7 +274,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
isConnected.BindValueChanged(connected =>
|
isConnected.BindValueChanged(connected =>
|
||||||
{
|
{
|
||||||
if (!connected.NewValue)
|
if (!connected.NewValue)
|
||||||
Schedule(this.Exit);
|
handleRoomLost();
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
currentRoom.BindValueChanged(room =>
|
currentRoom.BindValueChanged(room =>
|
||||||
@ -284,7 +284,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
// the room has gone away.
|
// the room has gone away.
|
||||||
// this could mean something happened during the join process, or an external connection issue occurred.
|
// this could mean something happened during the join process, or an external connection issue occurred.
|
||||||
// one specific scenario is where the underlying room is created, but the signalr server returns an error during the join process. this triggers a PartRoom operation (see https://github.com/ppy/osu/blob/7654df94f6f37b8382be7dfcb4f674e03bd35427/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs#L97)
|
// one specific scenario is where the underlying room is created, but the signalr server returns an error during the join process. this triggers a PartRoom operation (see https://github.com/ppy/osu/blob/7654df94f6f37b8382be7dfcb4f674e03bd35427/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs#L97)
|
||||||
Schedule(this.Exit);
|
handleRoomLost();
|
||||||
}
|
}
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
@ -448,9 +448,24 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
|
|
||||||
private void onRoomUpdated()
|
private void onRoomUpdated()
|
||||||
{
|
{
|
||||||
|
// may happen if the client is kicked or otherwise removed from the room.
|
||||||
|
if (client.Room == null)
|
||||||
|
{
|
||||||
|
handleRoomLost();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Scheduler.AddOnce(UpdateMods);
|
Scheduler.AddOnce(UpdateMods);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleRoomLost() => Schedule(() =>
|
||||||
|
{
|
||||||
|
if (this.IsCurrentScreen())
|
||||||
|
this.Exit();
|
||||||
|
else
|
||||||
|
ValidForResume = false;
|
||||||
|
});
|
||||||
|
|
||||||
private void onLoadRequested()
|
private void onLoadRequested()
|
||||||
{
|
{
|
||||||
if (BeatmapAvailability.Value.State != DownloadState.LocallyAvailable)
|
if (BeatmapAvailability.Value.State != DownloadState.LocallyAvailable)
|
||||||
@ -475,16 +490,18 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
protected override Screen CreateGameplayScreen()
|
protected override Screen CreateGameplayScreen()
|
||||||
{
|
{
|
||||||
Debug.Assert(client.LocalUser != null);
|
Debug.Assert(client.LocalUser != null);
|
||||||
|
Debug.Assert(client.Room != null);
|
||||||
|
|
||||||
int[] userIds = client.CurrentMatchPlayingUserIds.ToArray();
|
int[] userIds = client.CurrentMatchPlayingUserIds.ToArray();
|
||||||
|
MultiplayerRoomUser[] users = userIds.Select(id => client.Room.Users.First(u => u.UserID == id)).ToArray();
|
||||||
|
|
||||||
switch (client.LocalUser.State)
|
switch (client.LocalUser.State)
|
||||||
{
|
{
|
||||||
case MultiplayerUserState.Spectating:
|
case MultiplayerUserState.Spectating:
|
||||||
return new MultiSpectatorScreen(userIds);
|
return new MultiSpectatorScreen(users.Take(PlayerGrid.MAX_PLAYERS).ToArray());
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return new PlayerLoader(() => new MultiplayerPlayer(SelectedItem.Value, userIds));
|
return new PlayerLoader(() => new MultiplayerPlayer(SelectedItem.Value, users));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,9 +3,12 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
@ -34,16 +37,17 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
|
|
||||||
private MultiplayerGameplayLeaderboard leaderboard;
|
private MultiplayerGameplayLeaderboard leaderboard;
|
||||||
|
|
||||||
private readonly int[] userIds;
|
private readonly MultiplayerRoomUser[] users;
|
||||||
|
|
||||||
private LoadingLayer loadingDisplay;
|
private LoadingLayer loadingDisplay;
|
||||||
|
private FillFlowContainer leaderboardFlow;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Construct a multiplayer player.
|
/// Construct a multiplayer player.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="playlistItem">The playlist item to be played.</param>
|
/// <param name="playlistItem">The playlist item to be played.</param>
|
||||||
/// <param name="userIds">The users which are participating in this game.</param>
|
/// <param name="users">The users which are participating in this game.</param>
|
||||||
public MultiplayerPlayer(PlaylistItem playlistItem, int[] userIds)
|
public MultiplayerPlayer(PlaylistItem playlistItem, MultiplayerRoomUser[] users)
|
||||||
: base(playlistItem, new PlayerConfiguration
|
: base(playlistItem, new PlayerConfiguration
|
||||||
{
|
{
|
||||||
AllowPause = false,
|
AllowPause = false,
|
||||||
@ -51,14 +55,41 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
AllowSkipping = false,
|
AllowSkipping = false,
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
this.userIds = userIds;
|
this.users = users;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
|
if (!LoadedBeatmapSuccessfully)
|
||||||
|
return;
|
||||||
|
|
||||||
|
HUDOverlay.Add(leaderboardFlow = new FillFlowContainer
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
});
|
||||||
|
|
||||||
// todo: this should be implemented via a custom HUD implementation, and correctly masked to the main content area.
|
// todo: this should be implemented via a custom HUD implementation, and correctly masked to the main content area.
|
||||||
LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(ScoreProcessor, userIds), HUDOverlay.Add);
|
LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(ScoreProcessor, users), l =>
|
||||||
|
{
|
||||||
|
if (!LoadedBeatmapSuccessfully)
|
||||||
|
return;
|
||||||
|
|
||||||
|
((IBindable<bool>)leaderboard.Expanded).BindTo(HUDOverlay.ShowHud);
|
||||||
|
|
||||||
|
leaderboardFlow.Add(l);
|
||||||
|
|
||||||
|
if (leaderboard.TeamScores.Count >= 2)
|
||||||
|
{
|
||||||
|
LoadComponentAsync(new GameplayMatchScoreDisplay
|
||||||
|
{
|
||||||
|
Team1Score = { BindTarget = leaderboard.TeamScores.First().Value },
|
||||||
|
Team2Score = { BindTarget = leaderboard.TeamScores.Last().Value },
|
||||||
|
Expanded = { BindTarget = HUDOverlay.ShowHud },
|
||||||
|
}, leaderboardFlow.Add);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
HUDOverlay.Add(loadingDisplay = new LoadingLayer(true) { Depth = float.MaxValue });
|
HUDOverlay.Add(loadingDisplay = new LoadingLayer(true) { Depth = float.MaxValue });
|
||||||
}
|
}
|
||||||
@ -67,6 +98,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
{
|
{
|
||||||
base.LoadAsyncComplete();
|
base.LoadAsyncComplete();
|
||||||
|
|
||||||
|
if (!LoadedBeatmapSuccessfully)
|
||||||
|
return;
|
||||||
|
|
||||||
if (!ValidForResume)
|
if (!ValidForResume)
|
||||||
return; // token retrieval may have failed.
|
return; // token retrieval may have failed.
|
||||||
|
|
||||||
@ -92,13 +126,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
Debug.Assert(client.Room != null);
|
Debug.Assert(client.Room != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
|
||||||
{
|
|
||||||
base.LoadComplete();
|
|
||||||
|
|
||||||
((IBindable<bool>)leaderboard.Expanded).BindTo(HUDOverlay.ShowHud);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void StartGameplay()
|
protected override void StartGameplay()
|
||||||
{
|
{
|
||||||
// block base call, but let the server know we are ready to start.
|
// block base call, but let the server know we are ready to start.
|
||||||
@ -118,6 +145,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
|
if (!LoadedBeatmapSuccessfully)
|
||||||
|
return;
|
||||||
|
|
||||||
adjustLeaderboardPosition();
|
adjustLeaderboardPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,7 +156,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
{
|
{
|
||||||
const float padding = 44; // enough margin to avoid the hit error display.
|
const float padding = 44; // enough margin to avoid the hit error display.
|
||||||
|
|
||||||
leaderboard.Position = new Vector2(padding, padding + HUDOverlay.TopScoringElementsHeight);
|
leaderboardFlow.Position = new Vector2(padding, padding + HUDOverlay.TopScoringElementsHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onMatchStarted() => Scheduler.Add(() =>
|
private void onMatchStarted() => Scheduler.Add(() =>
|
||||||
@ -150,7 +181,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
protected override ResultsScreen CreateResults(ScoreInfo score)
|
protected override ResultsScreen CreateResults(ScoreInfo score)
|
||||||
{
|
{
|
||||||
Debug.Assert(RoomId.Value != null);
|
Debug.Assert(RoomId.Value != null);
|
||||||
return new MultiplayerResultsScreen(score, RoomId.Value.Value, PlaylistItem);
|
return leaderboard.TeamScores.Count == 2
|
||||||
|
? new MultiplayerTeamResultsScreen(score, RoomId.Value.Value, PlaylistItem, leaderboard.TeamScores)
|
||||||
|
: new MultiplayerResultsScreen(score, RoomId.Value.Value, PlaylistItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
|
@ -0,0 +1,152 @@
|
|||||||
|
// 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.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Colour;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Effects;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
using osu.Game.Online.Rooms;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Screens.Play.HUD;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||||
|
{
|
||||||
|
public class MultiplayerTeamResultsScreen : MultiplayerResultsScreen
|
||||||
|
{
|
||||||
|
private readonly SortedDictionary<int, BindableInt> teamScores;
|
||||||
|
|
||||||
|
private Container winnerBackground;
|
||||||
|
private Drawable winnerText;
|
||||||
|
|
||||||
|
public MultiplayerTeamResultsScreen(ScoreInfo score, long roomId, PlaylistItem playlistItem, SortedDictionary<int, BindableInt> teamScores)
|
||||||
|
: base(score, roomId, playlistItem)
|
||||||
|
{
|
||||||
|
if (teamScores.Count != 2)
|
||||||
|
throw new NotSupportedException(@"This screen currently only supports 2 teams");
|
||||||
|
|
||||||
|
this.teamScores = teamScores;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuColour colours { get; set; }
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
const float winner_background_half_height = 250;
|
||||||
|
|
||||||
|
VerticalScrollContent.Anchor = VerticalScrollContent.Origin = Anchor.TopCentre;
|
||||||
|
VerticalScrollContent.Scale = new Vector2(0.9f);
|
||||||
|
VerticalScrollContent.Y = 75;
|
||||||
|
|
||||||
|
var redScore = teamScores.First().Value;
|
||||||
|
var blueScore = teamScores.Last().Value;
|
||||||
|
|
||||||
|
LocalisableString winner;
|
||||||
|
Colour4 winnerColour;
|
||||||
|
|
||||||
|
int comparison = redScore.Value.CompareTo(blueScore.Value);
|
||||||
|
|
||||||
|
if (comparison < 0)
|
||||||
|
{
|
||||||
|
// team name should eventually be coming from the multiplayer match state.
|
||||||
|
winner = MultiplayerTeamResultsScreenStrings.TeamWins(@"Blue");
|
||||||
|
winnerColour = colours.TeamColourBlue;
|
||||||
|
}
|
||||||
|
else if (comparison > 0)
|
||||||
|
{
|
||||||
|
// team name should eventually be coming from the multiplayer match state.
|
||||||
|
winner = MultiplayerTeamResultsScreenStrings.TeamWins(@"Red");
|
||||||
|
winnerColour = colours.TeamColourRed;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
winner = MultiplayerTeamResultsScreenStrings.TheTeamsAreTied;
|
||||||
|
winnerColour = Colour4.White.Opacity(0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
AddRangeInternal(new Drawable[]
|
||||||
|
{
|
||||||
|
new MatchScoreDisplay
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Team1Score = { BindTarget = redScore },
|
||||||
|
Team2Score = { BindTarget = blueScore },
|
||||||
|
},
|
||||||
|
winnerBackground = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Alpha = 0,
|
||||||
|
Children = new[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = winner_background_half_height,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.BottomCentre,
|
||||||
|
Colour = ColourInfo.GradientVertical(Colour4.Black.Opacity(0), Colour4.Black.Opacity(0.4f))
|
||||||
|
},
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = winner_background_half_height,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Colour = ColourInfo.GradientVertical(Colour4.Black.Opacity(0.4f), Colour4.Black.Opacity(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(winnerText = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Alpha = 0,
|
||||||
|
Font = OsuFont.Torus.With(size: 80, weight: FontWeight.Bold),
|
||||||
|
Text = winner,
|
||||||
|
Blending = BlendingParameters.Additive
|
||||||
|
}).WithEffect(new GlowEffect
|
||||||
|
{
|
||||||
|
Colour = winnerColour,
|
||||||
|
}).With(e =>
|
||||||
|
{
|
||||||
|
e.Anchor = Anchor.Centre;
|
||||||
|
e.Origin = Anchor.Centre;
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
using (BeginDelayedSequence(300))
|
||||||
|
{
|
||||||
|
const double fade_in_duration = 600;
|
||||||
|
|
||||||
|
winnerText.FadeInFromZero(fade_in_duration, Easing.InQuint);
|
||||||
|
winnerBackground.FadeInFromZero(fade_in_duration, Easing.InQuint);
|
||||||
|
|
||||||
|
winnerText
|
||||||
|
.ScaleTo(10)
|
||||||
|
.ScaleTo(1, 600, Easing.InQuad)
|
||||||
|
.Then()
|
||||||
|
.ScaleTo(1.02f, 1600, Easing.OutQuint)
|
||||||
|
.FadeOut(5000, Easing.InQuad);
|
||||||
|
winnerBackground.Delay(2200).FadeOut(2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
@ -42,6 +43,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
|||||||
private ModDisplay userModsDisplay;
|
private ModDisplay userModsDisplay;
|
||||||
private StateDisplay userStateDisplay;
|
private StateDisplay userStateDisplay;
|
||||||
|
|
||||||
|
private IconButton kickButton;
|
||||||
|
|
||||||
public ParticipantPanel(MultiplayerRoomUser user)
|
public ParticipantPanel(MultiplayerRoomUser user)
|
||||||
{
|
{
|
||||||
User = user;
|
User = user;
|
||||||
@ -64,7 +67,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
|||||||
{
|
{
|
||||||
new Dimension(GridSizeMode.Absolute, 18),
|
new Dimension(GridSizeMode.Absolute, 18),
|
||||||
new Dimension(GridSizeMode.AutoSize),
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
new Dimension()
|
new Dimension(),
|
||||||
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
},
|
},
|
||||||
Content = new[]
|
Content = new[]
|
||||||
{
|
{
|
||||||
@ -157,7 +161,20 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
|||||||
Margin = new MarginPadding { Right = 10 },
|
Margin = new MarginPadding { Right = 10 },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
kickButton = new KickButton
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Alpha = 0,
|
||||||
|
Margin = new MarginPadding(4),
|
||||||
|
Action = () =>
|
||||||
|
{
|
||||||
|
Debug.Assert(user != null);
|
||||||
|
|
||||||
|
Client.KickUser(user.Id);
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -167,7 +184,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
|||||||
{
|
{
|
||||||
base.OnRoomUpdated();
|
base.OnRoomUpdated();
|
||||||
|
|
||||||
if (Room == null)
|
if (Room == null || Client.LocalUser == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const double fade_time = 50;
|
const double fade_time = 50;
|
||||||
@ -179,6 +196,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
|||||||
|
|
||||||
userStateDisplay.UpdateStatus(User.State, User.BeatmapAvailability);
|
userStateDisplay.UpdateStatus(User.State, User.BeatmapAvailability);
|
||||||
|
|
||||||
|
if (Client.IsHost && !User.Equals(Client.LocalUser))
|
||||||
|
kickButton.FadeIn(fade_time);
|
||||||
|
else
|
||||||
|
kickButton.FadeOut(fade_time);
|
||||||
|
|
||||||
if (Room.Host?.Equals(User) == true)
|
if (Room.Host?.Equals(User) == true)
|
||||||
crown.FadeIn(fade_time);
|
crown.FadeIn(fade_time);
|
||||||
else
|
else
|
||||||
@ -211,13 +233,36 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
|||||||
new OsuMenuItem("Give host", MenuItemType.Standard, () =>
|
new OsuMenuItem("Give host", MenuItemType.Standard, () =>
|
||||||
{
|
{
|
||||||
// Ensure the local user is still host.
|
// Ensure the local user is still host.
|
||||||
if (Room.Host?.UserID != api.LocalUser.Value.Id)
|
if (!Client.IsHost)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Client.TransferHost(targetUser);
|
Client.TransferHost(targetUser);
|
||||||
|
}),
|
||||||
|
new OsuMenuItem("Kick", MenuItemType.Destructive, () =>
|
||||||
|
{
|
||||||
|
// Ensure the local user is still host.
|
||||||
|
if (!Client.IsHost)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Client.KickUser(targetUser);
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class KickButton : IconButton
|
||||||
|
{
|
||||||
|
public KickButton()
|
||||||
|
{
|
||||||
|
Icon = FontAwesome.Solid.UserTimes;
|
||||||
|
TooltipText = "Kick";
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
IconHoverColour = colours.Red;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
|
using osu.Game.Online.Multiplayer;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Screens.Play.HUD;
|
using osu.Game.Screens.Play.HUD;
|
||||||
|
|
||||||
@ -11,8 +12,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
|||||||
{
|
{
|
||||||
public class MultiSpectatorLeaderboard : MultiplayerGameplayLeaderboard
|
public class MultiSpectatorLeaderboard : MultiplayerGameplayLeaderboard
|
||||||
{
|
{
|
||||||
public MultiSpectatorLeaderboard([NotNull] ScoreProcessor scoreProcessor, int[] userIds)
|
public MultiSpectatorLeaderboard([NotNull] ScoreProcessor scoreProcessor, MultiplayerRoomUser[] users)
|
||||||
: base(scoreProcessor, userIds)
|
: base(scoreProcessor, users)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,7 +33,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
|||||||
((SpectatingTrackedUserData)data).Clock = null;
|
((SpectatingTrackedUserData)data).Clock = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override TrackedUserData CreateUserData(int userId, ScoreProcessor scoreProcessor) => new SpectatingTrackedUserData(userId, scoreProcessor);
|
protected override TrackedUserData CreateUserData(MultiplayerRoomUser user, ScoreProcessor scoreProcessor) => new SpectatingTrackedUserData(user, scoreProcessor);
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
@ -47,8 +48,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
|||||||
[CanBeNull]
|
[CanBeNull]
|
||||||
public IClock Clock;
|
public IClock Clock;
|
||||||
|
|
||||||
public SpectatingTrackedUserData(int userId, ScoreProcessor scoreProcessor)
|
public SpectatingTrackedUserData(MultiplayerRoomUser user, ScoreProcessor scoreProcessor)
|
||||||
: base(userId, scoreProcessor)
|
: base(user, scoreProcessor)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
using osu.Game.Online.Spectator;
|
using osu.Game.Online.Spectator;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
|
using osu.Game.Screens.Play.HUD;
|
||||||
using osu.Game.Screens.Spectate;
|
using osu.Game.Screens.Spectate;
|
||||||
|
|
||||||
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||||
@ -45,20 +46,26 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
|||||||
private PlayerArea currentAudioSource;
|
private PlayerArea currentAudioSource;
|
||||||
private bool canStartMasterClock;
|
private bool canStartMasterClock;
|
||||||
|
|
||||||
|
private readonly MultiplayerRoomUser[] users;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="MultiSpectatorScreen"/>.
|
/// Creates a new <see cref="MultiSpectatorScreen"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="userIds">The players to spectate.</param>
|
/// <param name="users">The players to spectate.</param>
|
||||||
public MultiSpectatorScreen(int[] userIds)
|
public MultiSpectatorScreen(MultiplayerRoomUser[] users)
|
||||||
: base(userIds.Take(PlayerGrid.MAX_PLAYERS).ToArray())
|
: base(users.Select(u => u.UserID).ToArray())
|
||||||
{
|
{
|
||||||
instances = new PlayerArea[UserIds.Count];
|
this.users = users;
|
||||||
|
|
||||||
|
instances = new PlayerArea[Users.Count];
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
Container leaderboardContainer;
|
Container leaderboardContainer;
|
||||||
|
Container scoreDisplayContainer;
|
||||||
|
|
||||||
masterClockContainer = new MasterGameplayClockContainer(Beatmap.Value, 0);
|
masterClockContainer = new MasterGameplayClockContainer(Beatmap.Value, 0);
|
||||||
|
|
||||||
InternalChildren = new[]
|
InternalChildren = new[]
|
||||||
@ -67,28 +74,44 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
|||||||
masterClockContainer.WithChild(new GridContainer
|
masterClockContainer.WithChild(new GridContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
ColumnDimensions = new[]
|
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
|
||||||
{
|
|
||||||
new Dimension(GridSizeMode.AutoSize)
|
|
||||||
},
|
|
||||||
Content = new[]
|
Content = new[]
|
||||||
{
|
{
|
||||||
new Drawable[]
|
new Drawable[]
|
||||||
{
|
{
|
||||||
leaderboardContainer = new Container
|
scoreDisplayContainer = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.X,
|
||||||
AutoSizeAxes = Axes.X
|
AutoSizeAxes = Axes.Y
|
||||||
},
|
},
|
||||||
grid = new PlayerGrid { RelativeSizeAxes = Axes.Both }
|
},
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
new GridContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
leaderboardContainer = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
AutoSizeAxes = Axes.X
|
||||||
|
},
|
||||||
|
grid = new PlayerGrid { RelativeSizeAxes = Axes.Both }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
for (int i = 0; i < UserIds.Count; i++)
|
for (int i = 0; i < Users.Count; i++)
|
||||||
{
|
{
|
||||||
grid.Add(instances[i] = new PlayerArea(UserIds[i], masterClockContainer.GameplayClock));
|
grid.Add(instances[i] = new PlayerArea(Users[i], masterClockContainer.GameplayClock));
|
||||||
syncManager.AddPlayerClock(instances[i].GameplayClock);
|
syncManager.AddPlayerClock(instances[i].GameplayClock);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,7 +120,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
|||||||
var scoreProcessor = Ruleset.Value.CreateInstance().CreateScoreProcessor();
|
var scoreProcessor = Ruleset.Value.CreateInstance().CreateScoreProcessor();
|
||||||
scoreProcessor.ApplyBeatmap(playableBeatmap);
|
scoreProcessor.ApplyBeatmap(playableBeatmap);
|
||||||
|
|
||||||
LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, UserIds.ToArray())
|
LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, users)
|
||||||
{
|
{
|
||||||
Expanded = { Value = true },
|
Expanded = { Value = true },
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
@ -108,6 +131,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
|||||||
leaderboard.AddClock(instance.UserId, instance.GameplayClock);
|
leaderboard.AddClock(instance.UserId, instance.GameplayClock);
|
||||||
|
|
||||||
leaderboardContainer.Add(leaderboard);
|
leaderboardContainer.Add(leaderboard);
|
||||||
|
|
||||||
|
if (leaderboard.TeamScores.Count == 2)
|
||||||
|
{
|
||||||
|
LoadComponentAsync(new MatchScoreDisplay
|
||||||
|
{
|
||||||
|
Team1Score = { BindTarget = leaderboard.TeamScores.First().Value },
|
||||||
|
Team2Score = { BindTarget = leaderboard.TeamScores.Last().Value },
|
||||||
|
}, scoreDisplayContainer.Add);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,6 +35,9 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
[Resolved(typeof(Room))]
|
[Resolved(typeof(Room))]
|
||||||
protected BindableList<PlaylistItem> Playlist { get; private set; }
|
protected BindableList<PlaylistItem> Playlist { get; private set; }
|
||||||
|
|
||||||
|
[Resolved(typeof(Room))]
|
||||||
|
protected Bindable<RoomCategory> Category { get; private set; }
|
||||||
|
|
||||||
[Resolved(typeof(Room))]
|
[Resolved(typeof(Room))]
|
||||||
protected BindableList<User> RecentParticipants { get; private set; }
|
protected BindableList<User> RecentParticipants { get; private set; }
|
||||||
|
|
||||||
|
@ -13,7 +13,6 @@ using osu.Framework.Logging;
|
|||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Game.Beatmaps.Drawables;
|
using osu.Game.Beatmaps.Drawables;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.UserInterface;
|
|
||||||
using osu.Game.Input;
|
using osu.Game.Input;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
@ -22,15 +21,18 @@ using osu.Game.Screens.Menu;
|
|||||||
using osu.Game.Screens.OnlinePlay.Components;
|
using osu.Game.Screens.OnlinePlay.Components;
|
||||||
using osu.Game.Screens.OnlinePlay.Lounge;
|
using osu.Game.Screens.OnlinePlay.Lounge;
|
||||||
using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
||||||
using osu.Game.Screens.OnlinePlay.Match;
|
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Screens.OnlinePlay
|
namespace osu.Game.Screens.OnlinePlay
|
||||||
{
|
{
|
||||||
[Cached]
|
[Cached]
|
||||||
public abstract class OnlinePlayScreen : OsuScreen, IHasSubScreenStack
|
public abstract class OnlinePlayScreen : OsuScreen, IHasSubScreenStack
|
||||||
{
|
{
|
||||||
|
[Cached]
|
||||||
|
protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Plum);
|
||||||
|
|
||||||
public override bool CursorVisible => (screenStack?.CurrentScreen as IOnlinePlaySubScreen)?.CursorVisible ?? true;
|
public override bool CursorVisible => (screenStack?.CurrentScreen as IOnlinePlaySubScreen)?.CursorVisible ?? true;
|
||||||
|
|
||||||
// this is required due to PlayerLoader eventually being pushed to the main stack
|
// this is required due to PlayerLoader eventually being pushed to the main stack
|
||||||
@ -38,12 +40,8 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
public override bool DisallowExternalBeatmapRulesetChanges => true;
|
public override bool DisallowExternalBeatmapRulesetChanges => true;
|
||||||
|
|
||||||
private MultiplayerWaveContainer waves;
|
private MultiplayerWaveContainer waves;
|
||||||
|
|
||||||
private OsuButton createButton;
|
|
||||||
|
|
||||||
private ScreenStack screenStack;
|
|
||||||
|
|
||||||
private LoungeSubScreen loungeSubScreen;
|
private LoungeSubScreen loungeSubScreen;
|
||||||
|
private ScreenStack screenStack;
|
||||||
|
|
||||||
private readonly IBindable<bool> isIdle = new BindableBool();
|
private readonly IBindable<bool> isIdle = new BindableBool();
|
||||||
|
|
||||||
@ -74,9 +72,6 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private OsuLogo logo { get; set; }
|
private OsuLogo logo { get; set; }
|
||||||
|
|
||||||
private Drawable header;
|
|
||||||
private Drawable headerBackground;
|
|
||||||
|
|
||||||
protected OnlinePlayScreen()
|
protected OnlinePlayScreen()
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre;
|
Anchor = Anchor.Centre;
|
||||||
@ -107,59 +102,31 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
new Container
|
new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Padding = new MarginPadding { Top = Header.HEIGHT },
|
Children = new Drawable[]
|
||||||
Children = new[]
|
|
||||||
{
|
{
|
||||||
header = new Container
|
new BufferedContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Height = 400,
|
BlurSigma = new Vector2(10),
|
||||||
Children = new[]
|
Child = new BeatmapBackgroundSprite
|
||||||
{
|
{
|
||||||
headerBackground = new Container
|
RelativeSizeAxes = Axes.Both
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Width = 1.25f,
|
|
||||||
Masking = true,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new HeaderBackgroundSprite
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Height = 400 // Keep a static height so the header doesn't change as it's resized between subscreens
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Padding = new MarginPadding { Bottom = -1 }, // 1px padding to avoid a 1px gap due to masking
|
|
||||||
Child = new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = ColourInfo.GradientVertical(backgroundColour.Opacity(0.5f), backgroundColour)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
screenStack = new OnlinePlaySubScreenStack { RelativeSizeAxes = Axes.Both }
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.9f), Color4.Black.Opacity(0.6f))
|
||||||
|
},
|
||||||
|
screenStack = new OnlinePlaySubScreenStack
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new Header(ScreenTitle, screenStack),
|
new Header(ScreenTitle, screenStack),
|
||||||
createButton = CreateNewMultiplayerGameButton().With(button =>
|
|
||||||
{
|
|
||||||
button.Anchor = Anchor.TopRight;
|
|
||||||
button.Origin = Anchor.TopRight;
|
|
||||||
button.Size = new Vector2(150, Header.HEIGHT - 20);
|
|
||||||
button.Margin = new MarginPadding
|
|
||||||
{
|
|
||||||
Top = 10,
|
|
||||||
Right = 10 + HORIZONTAL_OVERFLOW_PADDING,
|
|
||||||
};
|
|
||||||
button.Action = () => OpenNewRoom();
|
|
||||||
}),
|
|
||||||
RoomManager,
|
RoomManager,
|
||||||
ongoingOperationTracker,
|
ongoingOperationTracker
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -292,18 +259,6 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
logo.Delay(WaveContainer.DISAPPEAR_DURATION / 2).FadeOut();
|
logo.Delay(WaveContainer.DISAPPEAR_DURATION / 2).FadeOut();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates and opens the newly-created room.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="room">An optional template to use when creating the room.</param>
|
|
||||||
public void OpenNewRoom(Room room = null) => loungeSubScreen.Open(room ?? CreateNewRoom());
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new room.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The created <see cref="Room"/>.</returns>
|
|
||||||
protected abstract Room CreateNewRoom();
|
|
||||||
|
|
||||||
private void screenPushed(IScreen lastScreen, IScreen newScreen)
|
private void screenPushed(IScreen lastScreen, IScreen newScreen)
|
||||||
{
|
{
|
||||||
subScreenChanged(lastScreen, newScreen);
|
subScreenChanged(lastScreen, newScreen);
|
||||||
@ -319,19 +274,6 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
|
|
||||||
private void subScreenChanged(IScreen lastScreen, IScreen newScreen)
|
private void subScreenChanged(IScreen lastScreen, IScreen newScreen)
|
||||||
{
|
{
|
||||||
switch (newScreen)
|
|
||||||
{
|
|
||||||
case LoungeSubScreen _:
|
|
||||||
header.Delay(OnlinePlaySubScreen.RESUME_TRANSITION_DELAY).ResizeHeightTo(400, OnlinePlaySubScreen.APPEAR_DURATION, Easing.OutQuint);
|
|
||||||
headerBackground.MoveToX(0, OnlinePlaySubScreen.X_MOVE_DURATION, Easing.OutQuint);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case RoomSubScreen _:
|
|
||||||
header.ResizeHeightTo(135, OnlinePlaySubScreen.APPEAR_DURATION, Easing.OutQuint);
|
|
||||||
headerBackground.MoveToX(-OnlinePlaySubScreen.X_SHIFT, OnlinePlaySubScreen.X_MOVE_DURATION, Easing.OutQuint);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastScreen is IOsuScreen lastOsuScreen)
|
if (lastScreen is IOsuScreen lastOsuScreen)
|
||||||
Activity.UnbindFrom(lastOsuScreen.Activity);
|
Activity.UnbindFrom(lastOsuScreen.Activity);
|
||||||
|
|
||||||
@ -339,7 +281,6 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
((IBindable<UserActivity>)Activity).BindTo(newOsuScreen.Activity);
|
((IBindable<UserActivity>)Activity).BindTo(newOsuScreen.Activity);
|
||||||
|
|
||||||
UpdatePollingRate(isIdle.Value);
|
UpdatePollingRate(isIdle.Value);
|
||||||
createButton.FadeTo(newScreen is LoungeSubScreen ? 1 : 0, 200);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected IScreen CurrentSubScreen => screenStack.CurrentScreen;
|
protected IScreen CurrentSubScreen => screenStack.CurrentScreen;
|
||||||
@ -350,8 +291,6 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
|
|
||||||
protected abstract LoungeSubScreen CreateLounge();
|
protected abstract LoungeSubScreen CreateLounge();
|
||||||
|
|
||||||
protected abstract OsuButton CreateNewMultiplayerGameButton();
|
|
||||||
|
|
||||||
private class MultiplayerWaveContainer : WaveContainer
|
private class MultiplayerWaveContainer : WaveContainer
|
||||||
{
|
{
|
||||||
protected override bool StartHidden => true;
|
protected override bool StartHidden => true;
|
||||||
@ -365,13 +304,13 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class HeaderBackgroundSprite : OnlinePlayBackgroundSprite
|
private class BeatmapBackgroundSprite : OnlinePlayBackgroundSprite
|
||||||
{
|
{
|
||||||
protected override UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new BackgroundSprite { RelativeSizeAxes = Axes.Both };
|
protected override UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new BackgroundSprite { RelativeSizeAxes = Axes.Both };
|
||||||
|
|
||||||
private class BackgroundSprite : UpdateableBeatmapBackgroundSprite
|
private class BackgroundSprite : UpdateableBeatmapBackgroundSprite
|
||||||
{
|
{
|
||||||
protected override double TransformDuration => 200;
|
protected override double LoadDelay => 200;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,6 +59,8 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
|
LeftArea.Padding = new MarginPadding { Top = Header.HEIGHT };
|
||||||
|
|
||||||
initialBeatmap = Beatmap.Value;
|
initialBeatmap = Beatmap.Value;
|
||||||
initialRuleset = Ruleset.Value;
|
initialRuleset = Ruleset.Value;
|
||||||
initialMods = Mods.Value.ToList();
|
initialMods = Mods.Value.ToList();
|
||||||
|
@ -3,8 +3,6 @@
|
|||||||
|
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Game.Graphics.UserInterface;
|
|
||||||
using osu.Game.Online.Rooms;
|
|
||||||
using osu.Game.Screens.OnlinePlay.Components;
|
using osu.Game.Screens.OnlinePlay.Components;
|
||||||
using osu.Game.Screens.OnlinePlay.Lounge;
|
using osu.Game.Screens.OnlinePlay.Lounge;
|
||||||
using osu.Game.Screens.OnlinePlay.Match;
|
using osu.Game.Screens.OnlinePlay.Match;
|
||||||
@ -46,21 +44,10 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
|||||||
Logger.Log($"Polling adjusted (listing: {playlistsManager.TimeBetweenListingPolls.Value}, selection: {playlistsManager.TimeBetweenSelectionPolls.Value})");
|
Logger.Log($"Polling adjusted (listing: {playlistsManager.TimeBetweenListingPolls.Value}, selection: {playlistsManager.TimeBetweenSelectionPolls.Value})");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Room CreateNewRoom()
|
|
||||||
{
|
|
||||||
return new Room
|
|
||||||
{
|
|
||||||
Name = { Value = $"{API.LocalUser}'s awesome playlist" },
|
|
||||||
Type = { Value = MatchType.Playlists }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override string ScreenTitle => "Playlists";
|
protected override string ScreenTitle => "Playlists";
|
||||||
|
|
||||||
protected override RoomManager CreateRoomManager() => new PlaylistsRoomManager();
|
protected override RoomManager CreateRoomManager() => new PlaylistsRoomManager();
|
||||||
|
|
||||||
protected override LoungeSubScreen CreateLounge() => new PlaylistsLoungeSubScreen();
|
protected override LoungeSubScreen CreateLounge() => new PlaylistsLoungeSubScreen();
|
||||||
|
|
||||||
protected override OsuButton CreateNewMultiplayerGameButton() => new CreatePlaylistsRoomButton();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user