diff --git a/osu.Game.Rulesets.Karaoke.Tests/Skinning/KaraokeSkinnableColumnTestScene.cs b/osu.Game.Rulesets.Karaoke.Tests/Skinning/KaraokeSkinnableColumnTestScene.cs index 65962d892..dbed4779c 100644 --- a/osu.Game.Rulesets.Karaoke.Tests/Skinning/KaraokeSkinnableColumnTestScene.cs +++ b/osu.Game.Rulesets.Karaoke.Tests/Skinning/KaraokeSkinnableColumnTestScene.cs @@ -48,9 +48,12 @@ protected KaraokeSkinnableColumnTestScene() [BackgroundDependencyLoader] private void load(RulesetConfigCache configCache) { - // Cache ruleset config manager because karaoke input manager need it. + // Cache ruleset config manager and session because karaoke input manager need it. var config = (KaraokeRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); + var session = new KaraokeSessionStatics(config, null); + Dependencies.Cache(config); + Dependencies.Cache(session); } [Test] diff --git a/osu.Game.Rulesets.Karaoke.Tests/Skinning/TestSceneNotePlayfield.cs b/osu.Game.Rulesets.Karaoke.Tests/Skinning/TestSceneNotePlayfield.cs index 4c9fd16cc..e2546c4ab 100644 --- a/osu.Game.Rulesets.Karaoke.Tests/Skinning/TestSceneNotePlayfield.cs +++ b/osu.Game.Rulesets.Karaoke.Tests/Skinning/TestSceneNotePlayfield.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Game.Rulesets.Karaoke.Configuration; using osu.Game.Rulesets.Karaoke.UI; namespace osu.Game.Rulesets.Karaoke.Tests.Skinning @@ -12,9 +11,6 @@ public class TestSceneNotePlayfield : KaraokeSkinnableColumnTestScene [BackgroundDependencyLoader] private void load(RulesetConfigCache configCache) { - var config = (KaraokeRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); - Dependencies.Cache(new KaraokeSessionStatics(config, null)); - SetContents(() => new KaraokeInputManager(new KaraokeRuleset().RulesetInfo) { Child = new NotePlayfield(COLUMN_NUMBER) diff --git a/osu.Game.Rulesets.Karaoke.Tests/UI/TestCaseSaitenStatus.cs b/osu.Game.Rulesets.Karaoke.Tests/UI/TestCaseSaitenStatus.cs new file mode 100644 index 000000000..28d3a1750 --- /dev/null +++ b/osu.Game.Rulesets.Karaoke.Tests/UI/TestCaseSaitenStatus.cs @@ -0,0 +1,39 @@ +// Copyright (c) andy840119 . Licensed under the GPL Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Karaoke.UI.Components; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Karaoke.Tests.UI +{ + [TestFixture] + public class TestCaseSaitenStatus : OsuTestScene + { + [TestCase(SaitenStatusMode.AndroidMicrophonePermissionDeclined)] + [TestCase(SaitenStatusMode.AndroidDoesNotSupported)] + [TestCase(SaitenStatusMode.IOSMicrophonePermissionDeclined)] + [TestCase(SaitenStatusMode.IOSDoesNotSupported)] + [TestCase(SaitenStatusMode.OSXMicrophonePermissionDeclined)] + [TestCase(SaitenStatusMode.OSXDoesNotSupported)] + [TestCase(SaitenStatusMode.WindowsMicrophonePermissionDeclined)] + [TestCase(SaitenStatusMode.NoMicrophoneDevice)] + [TestCase(SaitenStatusMode.NotSaitening)] + [TestCase(SaitenStatusMode.AutoPlay)] + [TestCase(SaitenStatusMode.Edit)] + [TestCase(SaitenStatusMode.Saitening)] + [TestCase(SaitenStatusMode.NotInitialized)] + public void TestMode(SaitenStatusMode mode) + { + AddStep("create mod display", () => + { + Child = new SaitenStatus(mode) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }; + }); + } + } +} diff --git a/osu.Game.Rulesets.Karaoke/Configuration/KaraokeSessionStatics.cs b/osu.Game.Rulesets.Karaoke/Configuration/KaraokeSessionStatics.cs index 83c9e0648..8264105f2 100644 --- a/osu.Game.Rulesets.Karaoke/Configuration/KaraokeSessionStatics.cs +++ b/osu.Game.Rulesets.Karaoke/Configuration/KaraokeSessionStatics.cs @@ -6,6 +6,7 @@ using osu.Game.Configuration; using osu.Game.Rulesets.Karaoke.Beatmaps; using osu.Game.Rulesets.Karaoke.Objects; +using osu.Game.Rulesets.Karaoke.UI.Components; namespace osu.Game.Rulesets.Karaoke.Configuration { @@ -50,6 +51,9 @@ public KaraokeSessionStatics(KaraokeRulesetConfigManager config, IBeatmap beatma // Practice Set(KaraokeRulesetSession.NowLyric, null); + + // Saiten stsus + Set(KaraokeRulesetSession.SaitenStatus, SaitenStatusMode.NotInitialized); } private T getValue(KaraokeRulesetSetting setting) => rulesetConfigManager.GetBindable(setting).Value; @@ -75,5 +79,8 @@ public enum KaraokeRulesetSession // Practice NowLyric, + + // Saiten status + SaitenStatus, } } diff --git a/osu.Game.Rulesets.Karaoke/KaraokeInputManager.cs b/osu.Game.Rulesets.Karaoke/KaraokeInputManager.cs index 42ed0428c..b04a68c7e 100644 --- a/osu.Game.Rulesets.Karaoke/KaraokeInputManager.cs +++ b/osu.Game.Rulesets.Karaoke/KaraokeInputManager.cs @@ -12,11 +12,13 @@ using osu.Framework.Input.Handlers.Microphone; using osu.Framework.Input.StateChanges.Events; using osu.Framework.Input.States; +using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Input.Handlers; using osu.Game.Rulesets.Karaoke.Beatmaps; using osu.Game.Rulesets.Karaoke.Configuration; using osu.Game.Rulesets.Karaoke.Mods; +using osu.Game.Rulesets.Karaoke.UI.Components; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit; @@ -34,27 +36,49 @@ public KaraokeInputManager(RulesetInfo ruleset) private IBeatmap beatmap; [BackgroundDependencyLoader(true)] - private void load(KaraokeRulesetConfigManager config, IBindable> mods, IBindable beatmap, EditorBeatmap editorBeatmap) + private void load(KaraokeRulesetConfigManager config, IBindable> mods, IBindable beatmap, KaraokeSessionStatics session, EditorBeatmap editorBeatmap) { if (editorBeatmap != null) + { + session.Set(KaraokeRulesetSession.SaitenStatus, SaitenStatusMode.Edit); return; + } this.beatmap = beatmap.Value.Beatmap; var disableMicrophoneDeviceByMod = mods.Value.OfType().Any(x => !x.MicrophoneEnabled); + if (disableMicrophoneDeviceByMod) + { + session.Set(KaraokeRulesetSession.SaitenStatus, SaitenStatusMode.AutoPlay); return; + } var beatmapSaitenable = beatmap.Value.Beatmap.IsScorable(); + if (!beatmapSaitenable) + { + session.Set(KaraokeRulesetSession.SaitenStatus, SaitenStatusMode.NotSaitening); return; + } + + try + { + var selectedDevice = config.GetBindable(KaraokeRulesetSetting.MicrophoneDevice).Value; + var microphoneList = new MicrophoneManager().MicrophoneDeviceNames.ToList(); - var selectedDevice = config.GetBindable(KaraokeRulesetSetting.MicrophoneDevice).Value; - var microphoneList = new MicrophoneManager().MicrophoneDeviceNames.ToList(); + // Find index by selection id + var deviceIndex = microphoneList.IndexOf(selectedDevice); + AddHandler(new OsuTKMicrophoneHandler(deviceIndex)); - // Find index by selection id - var deviceIndex = microphoneList.IndexOf(selectedDevice); - AddHandler(new OsuTKMicrophoneHandler(deviceIndex)); + session.Set(KaraokeRulesetSession.SaitenStatus, SaitenStatusMode.Saitening); + } + catch (Exception ex) + { + Logger.Error(ex, "Microphone initialize error."); + // todo : set real error by exception + session.Set(KaraokeRulesetSession.SaitenStatus, SaitenStatusMode.WindowsMicrophonePermissionDeclined); + } } protected override InputState CreateInitialState() diff --git a/osu.Game.Rulesets.Karaoke/KaraokeRuleset.cs b/osu.Game.Rulesets.Karaoke/KaraokeRuleset.cs index 21294f28e..39f3bd3fd 100644 --- a/osu.Game.Rulesets.Karaoke/KaraokeRuleset.cs +++ b/osu.Game.Rulesets.Karaoke/KaraokeRuleset.cs @@ -24,7 +24,6 @@ using osu.Game.Rulesets.Karaoke.Mods; using osu.Game.Rulesets.Karaoke.Objects; using osu.Game.Rulesets.Karaoke.Replays; -using osu.Game.Rulesets.Karaoke.Resources.Fonts; using osu.Game.Rulesets.Karaoke.Scoring; using osu.Game.Rulesets.Karaoke.Skinning; using osu.Game.Rulesets.Karaoke.UI; diff --git a/osu.Game.Rulesets.Karaoke/Mods/KaraokeModDisableNote.cs b/osu.Game.Rulesets.Karaoke/Mods/KaraokeModDisableNote.cs index a1c02dbf0..aae74dce0 100644 --- a/osu.Game.Rulesets.Karaoke/Mods/KaraokeModDisableNote.cs +++ b/osu.Game.Rulesets.Karaoke/Mods/KaraokeModDisableNote.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Karaoke.Mods { - public class KaraokeModDisableNote : Mod, IApplicableToHitObject, IApplicableToMicrophone + public class KaraokeModDisableNote : Mod, IApplicableToHitObject { public override string Name => "Disable note"; public override string Acronym => "DN"; @@ -17,8 +17,6 @@ public class KaraokeModDisableNote : Mod, IApplicableToHitObject, IApplicableToM public override IconUsage? Icon => KaraokeIcon.ModDisableNote; public override ModType Type => ModType.Fun; - public bool MicrophoneEnabled => false; - public void ApplyToHitObject(HitObject hitObject) { if (hitObject is Note note) diff --git a/osu.Game.Rulesets.Karaoke/Overlays/Changelog/ChangeLogMarkdownContainer.cs b/osu.Game.Rulesets.Karaoke/Overlays/Changelog/ChangeLogMarkdownContainer.cs index cc634aed0..b0631e279 100644 --- a/osu.Game.Rulesets.Karaoke/Overlays/Changelog/ChangeLogMarkdownContainer.cs +++ b/osu.Game.Rulesets.Karaoke/Overlays/Changelog/ChangeLogMarkdownContainer.cs @@ -177,7 +177,7 @@ private void computeImageSize() } } - private IDictionary githubUrls = new Dictionary + private readonly IDictionary githubUrls = new Dictionary { { "karaoke", "https://github.com/karaoke-dev/karaoke/" }, { "edge", "https://github.com/karaoke-dev/karaoke" }, @@ -193,13 +193,14 @@ protected override void AddLinkText(string text, LinkInline linkInline) var baseUri = new Uri(githubUrls[text]); // Get hash tag with number - var pattern = @"(\#[0-9]+\b)(?!;)"; + const string pattern = @"(\#[0-9]+\b)(?!;)"; var issueOrRequests = Regex.Matches(linkInline.Url, pattern, RegexOptions.IgnoreCase); if (!issueOrRequests.Any()) return; AddText("("); + foreach (var issue in issueOrRequests.Select(x=>x.Value)) { AddDrawable(new MarkdownLinkText($"{text}{issue}", new LinkInline @@ -210,10 +211,12 @@ protected override void AddLinkText(string text, LinkInline linkInline) if(issue != issueOrRequests.LastOrDefault()?.Value) AddText(", "); } + AddText(")"); // add use name if has user var user = linkInline.Url.Split('@').LastOrDefault(); + if (!string.IsNullOrEmpty(user)) { var textScale = new Vector2(0.7f); diff --git a/osu.Game.Rulesets.Karaoke/Skinning/KaraokeInternalSkin.cs b/osu.Game.Rulesets.Karaoke/Skinning/KaraokeInternalSkin.cs index 72d78200a..fbbc2962e 100644 --- a/osu.Game.Rulesets.Karaoke/Skinning/KaraokeInternalSkin.cs +++ b/osu.Game.Rulesets.Karaoke/Skinning/KaraokeInternalSkin.cs @@ -27,7 +27,7 @@ public abstract class KaraokeInternalSkin : ISkin protected abstract string ResourceName { get; } - public KaraokeInternalSkin() + protected KaraokeInternalSkin() { // TODO : need a better way to load resource var assembly = Assembly.GetExecutingAssembly(); diff --git a/osu.Game.Rulesets.Karaoke/Skinning/KaraokeLegacySkinTransformer.cs b/osu.Game.Rulesets.Karaoke/Skinning/KaraokeLegacySkinTransformer.cs index e6c93c1ad..3bfee7bda 100644 --- a/osu.Game.Rulesets.Karaoke/Skinning/KaraokeLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Karaoke/Skinning/KaraokeLegacySkinTransformer.cs @@ -5,11 +5,8 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; -using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Textures; -using osu.Game.Audio; using osu.Game.IO; using osu.Game.Rulesets.Karaoke.Beatmaps.Formats; using osu.Game.Rulesets.Karaoke.Skinning.Components; diff --git a/osu.Game.Rulesets.Karaoke/UI/Components/SaitenStatus.cs b/osu.Game.Rulesets.Karaoke/UI/Components/SaitenStatus.cs new file mode 100644 index 000000000..544b5a271 --- /dev/null +++ b/osu.Game.Rulesets.Karaoke/UI/Components/SaitenStatus.cs @@ -0,0 +1,206 @@ +// Copyright (c) andy840119 . Licensed under the GPL Licence. +// See the LICENCE file in the repository root for full licence text. + +using Markdig.Syntax; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Containers.Markdown; +using osu.Framework.Graphics.Sprites; +using osuTK; +using osuTK.Graphics; +using System.ComponentModel; +using System.Linq; + +namespace osu.Game.Rulesets.Karaoke.UI.Components +{ + [Cached(Type = typeof(IMarkdownTextComponent))] + public class SaitenStatus : FillFlowContainer, IMarkdownTextComponent + { + private const float size = 22; + + private Drawable CreateIcon(bool saitenable) => new SpriteIcon + { + Size = new Vector2(size), + Icon = saitenable ? FontAwesome.Regular.DotCircle : FontAwesome.Regular.PauseCircle, + Colour = saitenable ? Color4.Red : Color4.LightGray + }; + + private Drawable CreateStatusSpriteText(string markdownText) => new StatusSpriteText(markdownText) + { + RelativeSizeAxes = Axes.None, + AutoSizeAxes = Axes.Both + }; + + public SpriteText CreateSpriteText() => new SpriteText(); + + public SaitenStatus(SaitenStatusMode statusMode) + { + Spacing = new Vector2(5); + Direction = FillDirection.Horizontal; + AutoSizeAxes = Axes.Both; + SaitenStatusMode = statusMode; + } + + private SaitenStatusMode statusMode; + + public SaitenStatusMode SaitenStatusMode + { + get => statusMode; + set + { + statusMode = value; + Children = new[] + { + CreateIcon(statusMode == SaitenStatusMode.Saitening), + CreateStatusSpriteText(GetSaitenStatusText(statusMode)) + }; + } + } + + protected virtual string GetSaitenStatusText(SaitenStatusMode statusMode) + { + switch (statusMode) + { + case SaitenStatusMode.AndroidMicrophonePermissionDeclined: + return "Go to setting to open permission for lazer."; + + case SaitenStatusMode.AndroidDoesNotSupported: + return "Android device haven't support saiten system yet :("; + + case SaitenStatusMode.IOSMicrophonePermissionDeclined: + return "Go to setting to open permission for lazer."; + + case SaitenStatusMode.IOSDoesNotSupported: + return "iOS device haven't support saiten system yet :("; + + case SaitenStatusMode.OSXMicrophonePermissionDeclined: + return "Go to setting to open permission for lazer."; + + case SaitenStatusMode.OSXDoesNotSupported: + return "Osx device haven't support saiten system yet :("; + + case SaitenStatusMode.WindowsMicrophonePermissionDeclined: + return "Open lazer with admin permission to enable saiten system."; + + case SaitenStatusMode.NotSaitening: + return "This beatmap is not saitenable."; + + case SaitenStatusMode.AutoPlay: + return "Auto play mode."; + + case SaitenStatusMode.Edit: + return "Edit mode."; + + case SaitenStatusMode.Saitening: + return "Saiteining..."; + + case SaitenStatusMode.NotInitialized: + return "Seems microphone device is not ready."; + + default: + return "Weird... Should not goes to here either :oops:"; + } + } + + internal class StatusSpriteText : MarkdownTextFlowContainer + { + public StatusSpriteText(string text) + { + var block = Markdig.Markdown.Parse(text).OfType().FirstOrDefault(); + + if (block != null) + AddInlineText(block.Inline); + } + } + } + + public enum SaitenStatusMode + { + /// + /// Due to android device does not authorize microphone access. + /// + [Description("Android permission declined.")] + AndroidMicrophonePermissionDeclined, + + /// + /// Saiten system does not support android device. + /// Will throw this if osu.framework.microphone does not supportu it yet. + /// Or official client does not open this permission. + /// + [Description("Android target not supported.")] + AndroidDoesNotSupported, + + /// + /// Due to iOS device does not authorize microphone access. + /// + [Description("iOS permission declined.")] + IOSMicrophonePermissionDeclined, + + /// + /// Saiten system does not support iOS device. + /// Will throw this if osu.framework.microphone does not supportu it yet. + /// Or official client does not open this permission. + /// + [Description("iOS target not supported.")] + IOSDoesNotSupported, + + /// + /// Due to osx device does not authorize microphone access. + /// + [Description("osx permission declined.")] + OSXMicrophonePermissionDeclined, + + /// + /// Saiten system does not support osx device. + /// Will throw this if osu.framework.microphone does not supportu it yet. + /// Or official client does not open this permission. + /// + [Description("osx target not supported.")] + OSXDoesNotSupported, + + /// + /// Due to windows device does not authorize microphone access. + /// Windows client don't need to ask permission. + /// Open lazer client with admin permission can solve that. + /// + [Description("Windows permission declined.")] + WindowsMicrophonePermissionDeclined, + + /// + /// No microphone device in this computer/macbook. + /// + [Description("No microphone device.")] + NoMicrophoneDevice, + + /// + /// Beatmap is not scoring. + /// + [Description("No saitening.")] + NotSaitening, + + /// + /// Beatmap is not scoring. + /// + [Description("Autoplay.")] + AutoPlay, + + /// + /// In edit mode. + /// + [Description("Edit mode.")] + Edit, + + /// + /// Everything works well. + /// + [Description("Saitening...")] + Saitening, + + /// + /// Microphone statis is not initialized. + /// + [Description("Not initialized.")] + NotInitialized, + } +} diff --git a/osu.Game.Rulesets.Karaoke/UI/NotePlayfield.cs b/osu.Game.Rulesets.Karaoke/UI/NotePlayfield.cs index 826c35336..6e455914a 100644 --- a/osu.Game.Rulesets.Karaoke/UI/NotePlayfield.cs +++ b/osu.Game.Rulesets.Karaoke/UI/NotePlayfield.cs @@ -47,6 +47,8 @@ public class NotePlayfield : ScrollingPlayfield, IKeyBindingHandler(KaraokeRulesetSession.SaitenStatus).BindValueChanged(e => + { + saitenStatus.SaitenStatusMode = e.NewValue; + }); } public bool OnPressed(KaraokeSaitenAction action) diff --git a/osu.Game.Rulesets.Karaoke/osu.Game.Rulesets.Karaoke.csproj b/osu.Game.Rulesets.Karaoke/osu.Game.Rulesets.Karaoke.csproj index 1a68a9246..d2f64ea3c 100644 --- a/osu.Game.Rulesets.Karaoke/osu.Game.Rulesets.Karaoke.csproj +++ b/osu.Game.Rulesets.Karaoke/osu.Game.Rulesets.Karaoke.csproj @@ -13,7 +13,7 @@ - +