From 5e5ad9a8999dcb80969cb53beed10c0e63098097 Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Tue, 16 May 2017 12:53:50 +0900 Subject: [PATCH 1/5] Add basic on-screen display --- .../Tests/TestCaseOnScreenDisplay.cs | 47 ++++ .../osu.Desktop.VisualTests.csproj | 1 + osu.Game/OsuGame.cs | 1 + osu.Game/Overlays/OnScreenDisplay.cs | 262 ++++++++++++++++++ osu.Game/osu.Game.csproj | 1 + 5 files changed, 312 insertions(+) create mode 100644 osu.Desktop.VisualTests/Tests/TestCaseOnScreenDisplay.cs create mode 100644 osu.Game/Overlays/OnScreenDisplay.cs diff --git a/osu.Desktop.VisualTests/Tests/TestCaseOnScreenDisplay.cs b/osu.Desktop.VisualTests/Tests/TestCaseOnScreenDisplay.cs new file mode 100644 index 0000000000..3cefb8a3d2 --- /dev/null +++ b/osu.Desktop.VisualTests/Tests/TestCaseOnScreenDisplay.cs @@ -0,0 +1,47 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Testing; +using osu.Game.Overlays; + +namespace osu.Desktop.VisualTests.Tests +{ + internal class TestCaseOnScreenDisplay : TestCase + { + private FrameworkConfigManager config; + private Bindable<FrameSync> frameSyncMode; + + public override string Description => @"Make it easier to see setting changes"; + + public override void Reset() + { + base.Reset(); + + Add(new OnScreenDisplay()); + + frameSyncMode = config.GetBindable<FrameSync>(FrameworkSetting.FrameSync); + + FrameSync initial = frameSyncMode.Value; + + AddRepeatStep(@"Change frame limiter", setNextMode, 3); + + AddStep(@"Restore frame limiter", () => frameSyncMode.Value = initial); + } + + private void setNextMode() + { + var nextMode = frameSyncMode.Value + 1; + if (nextMode > FrameSync.Unlimited) + nextMode = FrameSync.VSync; + frameSyncMode.Value = nextMode; + } + + [BackgroundDependencyLoader] + private void load(FrameworkConfigManager config) + { + this.config = config; + } + } +} diff --git a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj index 9b07ebf90b..dbb1750b72 100644 --- a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj +++ b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj @@ -195,6 +195,7 @@ <Compile Include="Tests\TestCaseMenuOverlays.cs" /> <Compile Include="Tests\TestCaseMusicController.cs" /> <Compile Include="Tests\TestCaseNotificationManager.cs" /> + <Compile Include="Tests\TestCaseOnScreenDisplay.cs" /> <Compile Include="Tests\TestCasePlayer.cs" /> <Compile Include="Tests\TestCaseHitObjects.cs" /> <Compile Include="Tests\TestCaseKeyCounter.cs" /> diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 9ce725661b..dfa94b2bfe 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -146,6 +146,7 @@ namespace osu.Game }, volume = new VolumeControl(), overlayContent = new Container{ RelativeSizeAxes = Axes.Both }, + new OnScreenDisplay(), new GlobalHotkeys //exists because UserInputManager is at a level below us. { Handler = globalHotkeyPressed diff --git a/osu.Game/Overlays/OnScreenDisplay.cs b/osu.Game/Overlays/OnScreenDisplay.cs new file mode 100644 index 0000000000..1c837eb602 --- /dev/null +++ b/osu.Game/Overlays/OnScreenDisplay.cs @@ -0,0 +1,262 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Extensions.Color4Extensions; + +namespace osu.Game.Overlays +{ + public class OnScreenDisplay : Container + { + private readonly Container box; + + public override bool HandleInput => false; + + private readonly SpriteText textLine1; + private readonly SpriteText textLine2; + private readonly SpriteText textLine3; + + private const float height = 110; + private const float height_contracted = height * 0.9f; + + private readonly FillFlowContainer<OptionLight> optionLights; + + public OnScreenDisplay() + { + RelativeSizeAxes = Axes.Both; + + Children = new Drawable[] + { + box = new Container + { + Origin = Anchor.Centre, + RelativePositionAxes = Axes.Both, + Position = new Vector2(0.5f, 0.75f), + Masking = true, + AutoSizeAxes = Axes.X, + Height = height_contracted, + Alpha = 0, + CornerRadius = 20, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = 0.7f, + }, + new Container // purely to add a minimum width + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 240, + RelativeSizeAxes = Axes.Y, + }, + textLine1 = new SpriteText + { + Padding = new MarginPadding(10), + Font = @"Exo2.0-Black", + Spacing = new Vector2(1, 0), + TextSize = 14, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }, + textLine2 = new SpriteText + { + TextSize = 24, + Font = @"Exo2.0-Light", + Anchor = Anchor.Centre, + Origin = Anchor.BottomCentre, + }, + new FillFlowContainer + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + optionLights = new FillFlowContainer<OptionLight> + { + Padding = new MarginPadding { Top = 20, Bottom = 5 }, + Spacing = new Vector2(5, 0), + Direction = FillDirection.Horizontal, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Both + }, + textLine3 = new SpriteText + { + Padding = new MarginPadding { Bottom = 15 }, + Font = @"Exo2.0-Bold", + TextSize = 12, + Alpha = 0.3f, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }, + } + } + } + }, + }; + } + + [BackgroundDependencyLoader] + private void load(FrameworkConfigManager frameworkConfig) + { + trackSetting(frameworkConfig.GetBindable<FrameSync>(FrameworkSetting.FrameSync), v => display(v, "Frame Limiter", v.GetDescription(), "Ctrl+F7")); + trackSetting(frameworkConfig.GetBindable<string>(FrameworkSetting.AudioDevice), v => display(v, "Audio Device", v, v)); + trackSetting(frameworkConfig.GetBindable<bool>(FrameworkSetting.ShowLogOverlay), v => display(v, "Debug Logs", v ? "visible" : "hidden", "Ctrl+F10")); + + Action displayResolution = delegate { display(null, "Screen Resolution", frameworkConfig.Get<int>(FrameworkSetting.Width) + "x" + frameworkConfig.Get<int>(FrameworkSetting.Height)); }; + + trackSetting(frameworkConfig.GetBindable<int>(FrameworkSetting.Width), v => displayResolution()); + trackSetting(frameworkConfig.GetBindable<int>(FrameworkSetting.Height), v => displayResolution()); + + trackSetting(frameworkConfig.GetBindable<WindowMode>(FrameworkSetting.WindowMode), v => display(v, "Screen Mode", v.ToString(), "Alt+Enter")); + } + + private readonly List<IBindable> references = new List<IBindable>(); + + private void trackSetting<T>(Bindable<T> bindable, Bindable<T>.BindableValueChanged<T> action) + { + // we need to keep references as we bind + references.Add(bindable); + + bindable.ValueChanged += action; + } + + private void display(object rawValue, string settingName, string settingValue, string shortcut = @"") + { + Schedule(() => + { + textLine1.Text = settingName.ToUpper(); + textLine2.Text = settingValue; + textLine3.Text = shortcut.ToUpper(); + + box.FadeIn(500, EasingTypes.OutQuint); + box.ResizeHeightTo(height, 500, EasingTypes.OutQuint); + + using (box.BeginDelayedSequence(500)) + { + box.FadeOutFromOne(1500, EasingTypes.InQuint); + box.ResizeHeightTo(height_contracted, 1500, EasingTypes.InQuint); + } + + int optionCount = 0; + int selectedOption = -1; + + if (rawValue is bool) + { + optionCount = 1; + if ((bool)rawValue) selectedOption = 0; + } + else if (rawValue is Enum) + { + var values = Enum.GetValues(rawValue.GetType()); + optionCount = values.Length; + selectedOption = Convert.ToInt32(rawValue); + } + + textLine2.Origin = optionCount > 0 ? Anchor.BottomCentre : Anchor.Centre; + textLine2.Y = optionCount > 0 ? 0 : 5; + + if (optionLights.Children.Count() != optionCount) + { + optionLights.Clear(); + for (int i = 0; i < optionCount; i++) + optionLights.Add(new OptionLight()); + } + + for (int i = 0; i < optionCount; i++) + optionLights.Children.Skip(i).First().Glowing = i == selectedOption; + }); + } + + private class OptionLight : Container + { + private Color4 glowingColour, idleColour; + + private const float transition_speed = 300; + + private const float glow_strength = 0.4f; + + private readonly Box fill; + + public OptionLight() + { + Children = new[] + { + fill = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 1, + }, + }; + } + + private bool glowing; + + public bool Glowing + { + set + { + glowing = value; + if (!IsLoaded) return; + + updateGlow(); + } + } + + private void updateGlow() + { + if (glowing) + { + fill.FadeColour(glowingColour, transition_speed, EasingTypes.OutQuint); + FadeEdgeEffectTo(glow_strength, transition_speed, EasingTypes.OutQuint); + } + else + { + FadeEdgeEffectTo(0, transition_speed, EasingTypes.OutQuint); + fill.FadeColour(idleColour, transition_speed, EasingTypes.OutQuint); + } + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + fill.Colour = idleColour = Color4.White.Opacity(0.4f); + glowingColour = Color4.White; + + Size = new Vector2(25, 5); + + Masking = true; + CornerRadius = 3; + + EdgeEffect = new EdgeEffect + { + Colour = colours.BlueDark.Opacity(glow_strength), + Type = EdgeEffectType.Glow, + Radius = 8, + }; + + FadeEdgeEffectTo(0); + + updateGlow(); + Flush(true); + } + } + } +} diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 1f866f0e17..2e94b3d658 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -79,6 +79,7 @@ <Compile Include="Overlays\Music\FilterControl.cs" /> <Compile Include="Overlays\Music\PlaylistItem.cs" /> <Compile Include="Overlays\Music\PlaylistList.cs" /> + <Compile Include="Overlays\OnScreenDisplay.cs" /> <Compile Include="Overlays\Settings\SettingsHeader.cs" /> <Compile Include="Overlays\Settings\Sections\Audio\MainMenuSettings.cs" /> <Compile Include="Overlays\Toolbar\ToolbarChatButton.cs" /> From 694b85c4b0399801c288ce9ab71c07819850c880 Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Tue, 16 May 2017 16:29:18 +0900 Subject: [PATCH 2/5] Update framework --- osu-framework | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu-framework b/osu-framework index 7146c07159..30cd231fc6 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 7146c07159d2cf3d07a8d371fa50ef8b200ba038 +Subproject commit 30cd231fc6b3e971c70456433dbcf03591141f28 From b016d53b85e94c1088652c97b99cdca6dfe79f74 Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Tue, 16 May 2017 17:57:40 +0900 Subject: [PATCH 3/5] Fix TwoLayerButton not capturing MouseDown events Caused clicks to pass through the skip button to the progress bar. --- osu.Game/Graphics/UserInterface/TwoLayerButton.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs index 601970e36d..237aaa44a6 100644 --- a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs +++ b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs @@ -216,6 +216,11 @@ namespace osu.Game.Graphics.UserInterface }); } + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + return true; + } + protected override bool OnClick(InputState state) { var flash = new Box From 6487bf45cff7f69e61cedb5718ee88b1317d82c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Tue, 16 May 2017 17:55:35 +0900 Subject: [PATCH 4/5] Eagerly attempt to pause the game when the window is not focused --- osu.Game/Screens/Play/Player.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index de390b63b3..8ee0de12b9 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -224,6 +224,15 @@ namespace osu.Game.Screens.Play }; } + protected override void Update() + { + // eagerly pause when we lose window focus (if we are locally playing). + if (!Game.IsActive && !HitRenderer.HasReplayLoaded) + Pause(); + + base.Update(); + } + private void initializeSkipButton() { const double skip_required_cutoff = 3000; From 712bd21e2524b90bc253ee8fb3f1d6cb345f70fc Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Tue, 16 May 2017 18:25:03 +0900 Subject: [PATCH 5/5] Fix duplicate channels being created on connection loss Resolves #763 --- osu.Game/Overlays/ChatOverlay.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index e6dffbfe63..4748eb7de6 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -284,10 +284,21 @@ namespace osu.Game.Overlays { if (channel == null) return; - careChannels.Add(channel); - channelTabs.AddItem(channel); + var existing = careChannels.Find(c => c.Id == channel.Id); - // we need to get a good number of messages initially for each channel we care about. + if (existing != null) + { + // if we already have this channel loaded, we don't want to make a second one. + channel = existing; + } + else + { + + careChannels.Add(channel); + channelTabs.AddItem(channel); + } + + // let's fetch a small number of messages to bring us up-to-date with the backlog. fetchInitialMessages(channel); if (CurrentChannel == null)