diff --git a/VRCOSC.Desktop/VRCOSC.Desktop.csproj b/VRCOSC.Desktop/VRCOSC.Desktop.csproj index af85ae82..8d69ed32 100644 --- a/VRCOSC.Desktop/VRCOSC.Desktop.csproj +++ b/VRCOSC.Desktop/VRCOSC.Desktop.csproj @@ -6,12 +6,12 @@ game.ico app.manifest 0.0.0 - 2022.1203.0 + 2022.1211.0 VRCOSC VolcanicArts VolcanicArts enable - 2022.1203.0 + 2022.1211.0 diff --git a/VRCOSC.Game.Tests/Tests/TimedTaskTests.cs b/VRCOSC.Game.Tests/Tests/TimedTaskTests.cs new file mode 100644 index 00000000..438aa351 --- /dev/null +++ b/VRCOSC.Game.Tests/Tests/TimedTaskTests.cs @@ -0,0 +1,115 @@ +// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. +// See the LICENSE file in the repository root for full license text. + +using System.Threading; +using System.Threading.Tasks; +using NUnit.Framework; +using VRCOSC.Game.Modules; + +namespace VRCOSC.Game.Tests.Tests; + +public class TimedTaskTests +{ + [Test] + public void TestStart() + { + var actionInvoked = false; + + Task action() + { + actionInvoked = true; + return Task.CompletedTask; + } + + var task = new TimedTask(action, 100); + task.Start().Wait(); + + Thread.Sleep(500); + + Assert.IsTrue(actionInvoked); + } + + [Test] + public void TestStop() + { + var actionInvoked = false; + + Task action() + { + actionInvoked = true; + return Task.CompletedTask; + } + + var task = new TimedTask(action, 100); + task.Start().Wait(); + + Thread.Sleep(500); + Assert.IsTrue(actionInvoked); + + task.Stop().Wait(); + + actionInvoked = false; + + Thread.Sleep(500); + Assert.IsFalse(actionInvoked); + } + + [Test] + public void TestExecuteOnceImmediately() + { + var actionInvoked = false; + + Task action() + { + actionInvoked = true; + return Task.CompletedTask; + } + + var task = new TimedTask(action, 100, true); + task.Start().Wait(); + + Assert.IsTrue(actionInvoked); + + Thread.Sleep(500); + Assert.IsTrue(actionInvoked); + } + + [Test] + public void TestStartMultipleTimes() + { + var actionInvoked = false; + + Task action() + { + actionInvoked = true; + return Task.CompletedTask; + } + + var task = new TimedTask(action, 100); + task.Start().Wait(); + task.Start().Wait(); + + Thread.Sleep(500); + + Assert.IsTrue(actionInvoked); + } + + [Test] + public void TestStopWithoutStart() + { + var actionInvoked = false; + + Task action() + { + actionInvoked = true; + return Task.CompletedTask; + } + + var task = new TimedTask(action, 100); + + task.Stop().Wait(); + + Thread.Sleep(500); + Assert.IsFalse(actionInvoked); + } +} diff --git a/VRCOSC.Game.Tests/VRCOSCTestBrowser.cs b/VRCOSC.Game.Tests/VRCOSCTestBrowser.cs index f381bb68..a045934d 100644 --- a/VRCOSC.Game.Tests/VRCOSCTestBrowser.cs +++ b/VRCOSC.Game.Tests/VRCOSCTestBrowser.cs @@ -8,7 +8,7 @@ namespace VRCOSC.Game.Tests; -public class VRCOSCTestBrowser : VRCOSCGameBase +public partial class VRCOSCTestBrowser : VRCOSCGameBase { protected override void LoadComplete() { diff --git a/VRCOSC.Game.Tests/Visual/TestNotifications.cs b/VRCOSC.Game.Tests/Visual/TestNotifications.cs deleted file mode 100644 index fc04506f..00000000 --- a/VRCOSC.Game.Tests/Visual/TestNotifications.cs +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. -// See the LICENSE file in the repository root for full license text. - -using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Utils; -using VRCOSC.Game.Graphics; -using VRCOSC.Game.Graphics.Notifications; - -namespace VRCOSC.Game.Tests.Visual; - -public class TestNotifications : VRCOSCTestScene -{ - private NotificationContainer notifications = null!; - - [SetUp] - public void SetUp() - { - Add(new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Colour = Colour4.Gray - }); - Add(notifications = new NotificationContainer()); - } - - [Test] - public void TestNotificationContainer() - { - AddStep("show", () => notifications.Show()); - AddUntilStep("ensure hidden", () => Precision.AlmostEquals(notifications.Position.X, 1)); - AddStep("notify", notifyBasic); - AddUntilStep("ensure shown", () => Precision.AlmostEquals(notifications.Position.X, 0)); - AddStep("hide", () => notifications.Hide()); - AddUntilStep("ensure hidden", () => Precision.AlmostEquals(notifications.Position.X, 1)); - } - - [Test] - public void TestBasicNotification() - { - AddStep("notify", notifyBasic); - } - - [Test] - public void TestProgressNotification() - { - ProgressNotification? progress = null; - AddStep("notify", () => progress = notifyProgress()); - AddSliderStep("set progress", 0f, 1f, 0f, p => - { - if (progress is not null) - progress.Progress = p; - }); - } - - private void notifyBasic() - { - notifications.Notify(new BasicNotification - { - Title = "Basic Title", - Description = "This is basic", - Colour = VRCOSCColour.GreenLight, - Icon = FontAwesome.Solid.Check - }); - } - - private ProgressNotification notifyProgress() - { - var progress = new ProgressNotification - { - Title = "Progress Title", - Description = "This is progressing", - Colour = VRCOSCColour.YellowDark, - Icon = FontAwesome.Solid.ExclamationTriangle - }; - notifications.Notify(progress); - return progress; - } -} diff --git a/VRCOSC.Game.Tests/Visual/VRCOSCGame.cs b/VRCOSC.Game.Tests/Visual/VRCOSCGame.cs index 2416fc87..28a29095 100644 --- a/VRCOSC.Game.Tests/Visual/VRCOSCGame.cs +++ b/VRCOSC.Game.Tests/Visual/VRCOSCGame.cs @@ -6,7 +6,7 @@ namespace VRCOSC.Game.Tests.Visual; -public class VRCOSCGame : VRCOSCTestScene +public partial class VRCOSCGame : VRCOSCTestScene { [SetUp] public void SetUp() diff --git a/VRCOSC.Game.Tests/Visual/VRCOSCTestScene.cs b/VRCOSC.Game.Tests/Visual/VRCOSCTestScene.cs index eb456082..f955428c 100644 --- a/VRCOSC.Game.Tests/Visual/VRCOSCTestScene.cs +++ b/VRCOSC.Game.Tests/Visual/VRCOSCTestScene.cs @@ -5,14 +5,14 @@ namespace VRCOSC.Game.Tests.Visual; -public class VRCOSCTestScene : TestScene +public partial class VRCOSCTestScene : TestScene { protected override ITestSceneTestRunner CreateRunner() { return new VRCOSCTestSceneTestRunner(); } - private class VRCOSCTestSceneTestRunner : VRCOSCGameBase, ITestSceneTestRunner + private partial class VRCOSCTestSceneTestRunner : VRCOSCGameBase, ITestSceneTestRunner { private TestSceneTestRunner.TestRunner runner = null!; diff --git a/VRCOSC.Game/Config/VRCOSCConfigManager.cs b/VRCOSC.Game/Config/VRCOSCConfigManager.cs index 6e714af5..5d3be613 100644 --- a/VRCOSC.Game/Config/VRCOSCConfigManager.cs +++ b/VRCOSC.Game/Config/VRCOSCConfigManager.cs @@ -4,6 +4,7 @@ using osu.Framework.Configuration; using osu.Framework.Platform; using VRCOSC.Game.Graphics.Settings; +using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Config; @@ -25,6 +26,8 @@ protected override void InitialiseDefaults() SetDefault(VRCOSCSetting.ReceivePort, 9001); SetDefault(VRCOSCSetting.UpdateMode, UpdateMode.Auto); SetDefault(VRCOSCSetting.UpdateRepo, @"https://github.com/VolcanicArts/VRCOSC"); + SetDefault(VRCOSCSetting.Theme, ColourTheme.Dark); + SetDefault(VRCOSCSetting.ChatBoxTimeSpan, 1500); } } @@ -36,5 +39,7 @@ public enum VRCOSCSetting SendPort, ReceivePort, UpdateMode, - UpdateRepo + UpdateRepo, + Theme, + ChatBoxTimeSpan } diff --git a/VRCOSC.Game/Constants.cs b/VRCOSC.Game/Constants.cs new file mode 100644 index 00000000..4b24f404 --- /dev/null +++ b/VRCOSC.Game/Constants.cs @@ -0,0 +1,17 @@ +// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. +// See the LICENSE file in the repository root for full license text. + +// ReSharper disable MemberCanBePrivate.Global + +namespace VRCOSC.Game; + +public static class Constants +{ + public const string OSC_ADDRESS_AVATAR_PARAMETERS_PREFIX = @"/avatar/parameters/"; + public const string OSC_ADDRESS_AVATAR_CHANGE = @"/avatar/change"; + public const string OSC_ADDRESS_CHATBOX_INPUT = @"/chatbox/input"; + public const string OSC_ADDRESS_CHATBOX_TYPING = @"/chatbox/typing"; + + public const int OSC_UPDATE_FREQUENCY = 20; + public const int OSC_UPDATE_DELTA = (int)(1f / OSC_UPDATE_FREQUENCY * 1000f); +} diff --git a/VRCOSC.Game/Modules/Util/TypeExtensions.cs b/VRCOSC.Game/Extensions.cs similarity index 81% rename from VRCOSC.Game/Modules/Util/TypeExtensions.cs rename to VRCOSC.Game/Extensions.cs index e47b7d9d..105a2767 100644 --- a/VRCOSC.Game/Modules/Util/TypeExtensions.cs +++ b/VRCOSC.Game/Extensions.cs @@ -3,18 +3,20 @@ using System; -namespace VRCOSC.Game.Modules.Util; +namespace VRCOSC.Game; + +public static class EnumExtensions +{ + public static string ToLookup(this Enum key) => key.ToString().ToLowerInvariant(); +} public static class TypeExtensions { public static string ToReadableName(this Type type) { - if (type.IsSubclassOf(typeof(Enum))) - return "Enum"; - - var typeCode = Type.GetTypeCode(type); + if (type.IsSubclassOf(typeof(Enum))) return "Enum"; - return typeCode switch + return Type.GetTypeCode(type) switch { TypeCode.Empty => "Null", TypeCode.Object => "Object", diff --git a/VRCOSC.Game/Graphics/About/AboutScreen.cs b/VRCOSC.Game/Graphics/About/AboutScreen.cs index 34d56f18..6b13b104 100644 --- a/VRCOSC.Game/Graphics/About/AboutScreen.cs +++ b/VRCOSC.Game/Graphics/About/AboutScreen.cs @@ -11,6 +11,7 @@ using osu.Framework.Platform; using osuTK; using VRCOSC.Game.Config; +using VRCOSC.Game.Graphics.Themes; using VRCOSC.Game.Graphics.UI.Button; namespace VRCOSC.Game.Graphics.About; @@ -36,7 +37,7 @@ public AboutScreen() new Box { RelativeSizeAxes = Axes.Both, - Colour = VRCOSCColour.Gray5 + Colour = ThemeManager.Current[ThemeAttribute.Light] }, new Container { @@ -53,7 +54,7 @@ public AboutScreen() Direction = FillDirection.Full, Spacing = new Vector2(5) }, - text = new TextFlowContainer + text = new TextFlowContainer(t => t.Colour = ThemeManager.Current[ThemeAttribute.Text]) { RelativeSizeAxes = Axes.Both, TextAnchor = Anchor.BottomCentre diff --git a/VRCOSC.Game/Graphics/LineSeparator.cs b/VRCOSC.Game/Graphics/LineSeparator.cs index d41dcede..3e46ca1c 100644 --- a/VRCOSC.Game/Graphics/LineSeparator.cs +++ b/VRCOSC.Game/Graphics/LineSeparator.cs @@ -5,6 +5,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osuTK; +using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics; @@ -20,7 +21,7 @@ public LineSeparator() Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Colour = VRCOSCColour.Gray2 + Colour = ThemeManager.Current[ThemeAttribute.Darker] }; } } diff --git a/VRCOSC.Game/Graphics/MainContent.cs b/VRCOSC.Game/Graphics/MainContent.cs index b1e29dcf..297ff548 100644 --- a/VRCOSC.Game/Graphics/MainContent.cs +++ b/VRCOSC.Game/Graphics/MainContent.cs @@ -33,7 +33,7 @@ private void load() ColumnDimensions = new[] { new Dimension(GridSizeMode.Absolute, 75), - new Dimension(), + new Dimension() }, Content = new[] { diff --git a/VRCOSC.Game/Graphics/ModuleEditing/AttributeFlow.cs b/VRCOSC.Game/Graphics/ModuleEditing/AttributeFlow.cs index b4167334..7f621435 100644 --- a/VRCOSC.Game/Graphics/ModuleEditing/AttributeFlow.cs +++ b/VRCOSC.Game/Graphics/ModuleEditing/AttributeFlow.cs @@ -54,10 +54,20 @@ protected override void LoadComplete() { Clear(); AttributesList.ForEach(attributeData => Add(generateCard(attributeData))); + checkShouldDisplay(); }, true); } - private static AttributeCard generateCard(ModuleAttribute attributeData) + private void checkShouldDisplay() + { + this.ForEach(card => + { + card.Enable = card.AttributeData.Enabled; + card.FadeTo(card.AttributeData.Enabled ? 1 : 0.25f, 250, Easing.OutQuad); + }); + } + + private AttributeCard generateCard(ModuleAttribute attributeData) { return attributeData switch { @@ -67,7 +77,7 @@ private static AttributeCard generateCard(ModuleAttribute attributeData) }; } - private static AttributeCard generateListCard(ModuleAttributeList attributeData) + private AttributeCard generateListCard(ModuleAttributeList attributeData) { if (attributeData.Type == typeof(int)) return new IntTextAttributeCardList(attributeData); @@ -78,12 +88,13 @@ private static AttributeCard generateListCard(ModuleAttributeList attributeData) throw new ArgumentOutOfRangeException(nameof(attributeData), "Cannot generate lists for non-text values"); } - private static AttributeCard generateSingleCard(ModuleAttributeSingle attributeData) + private AttributeCard generateSingleCard(ModuleAttributeSingle attributeData) { var value = attributeData.Attribute.Value; if (value.GetType().IsSubclassOf(typeof(Enum))) { + attributeData.Attribute.BindValueChanged(_ => checkShouldDisplay()); Type instanceType = typeof(DropdownAttributeCard<>).MakeGenericType(value.GetType()); return (Activator.CreateInstance(instanceType, attributeData) as AttributeCard)!; } @@ -123,6 +134,7 @@ private static AttributeCard generateSingleCard(ModuleAttributeSingle attributeD return new IntTextAttributeCard(attributeData); case bool: + attributeData.Attribute.BindValueChanged(_ => checkShouldDisplay()); return new ToggleAttributeCard(attributeData); default: diff --git a/VRCOSC.Game/Graphics/ModuleEditing/Attributes/AttributeCard.cs b/VRCOSC.Game/Graphics/ModuleEditing/Attributes/AttributeCard.cs index 4b1095b1..bf9abdf5 100644 --- a/VRCOSC.Game/Graphics/ModuleEditing/Attributes/AttributeCard.cs +++ b/VRCOSC.Game/Graphics/ModuleEditing/Attributes/AttributeCard.cs @@ -2,12 +2,12 @@ // See the LICENSE file in the repository root for full license text. using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osuTK; +using VRCOSC.Game.Graphics.Themes; using VRCOSC.Game.Graphics.UI; using VRCOSC.Game.Graphics.UI.Button; using VRCOSC.Game.Modules; @@ -20,11 +20,14 @@ public abstract partial class AttributeCard : Container protected FillFlowContainer ContentFlow = null!; protected FillFlowContainer LayoutFlow = null!; - private readonly ModuleAttribute attributeData; + public readonly ModuleAttribute AttributeData; + public bool Enable { get; set; } = true; + + protected override bool ShouldBeConsideredForInput(Drawable child) => Enable; protected AttributeCard(ModuleAttribute attributeData) { - this.attributeData = attributeData; + AttributeData = attributeData; } [BackgroundDependencyLoader] @@ -54,8 +57,8 @@ private void load() IconPadding = 4, CornerRadius = 10, BorderThickness = 2, - BorderColour = VRCOSCColour.Gray0, - BackgroundColour = VRCOSCColour.BlueDark.Darken(0.25f), + BorderColour = ThemeManager.Current[ThemeAttribute.Border], + BackgroundColour = ThemeManager.Current[ThemeAttribute.Action], Icon = FontAwesome.Solid.Undo, IconShadow = true } @@ -68,7 +71,7 @@ private void load() AutoSizeAxes = Axes.Y, Masking = true, CornerRadius = 10, - BorderColour = VRCOSCColour.Gray0, + BorderColour = ThemeManager.Current[ThemeAttribute.Border], BorderThickness = 2, Children = new Drawable[] { @@ -77,7 +80,7 @@ private void load() Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Colour = VRCOSCColour.Gray2 + Colour = ThemeManager.Current[ThemeAttribute.Darker] }, LayoutFlow = new FillFlowContainer { @@ -105,27 +108,28 @@ private void load() RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 10), + Spacing = new Vector2(0, 10) } } } } } }; - textFlow.AddText(attributeData.Metadata.DisplayName, t => + textFlow.AddText(AttributeData.Metadata.DisplayName, t => { t.Font = FrameworkFont.Regular.With(size: 30); + t.Colour = ThemeManager.Current[ThemeAttribute.Text]; }); - textFlow.AddParagraph(attributeData.Metadata.Description, t => + textFlow.AddParagraph(AttributeData.Metadata.Description, t => { t.Font = FrameworkFont.Regular.With(size: 20); - t.Colour = VRCOSCColour.Gray9; + t.Colour = ThemeManager.Current[ThemeAttribute.SubText]; }); } protected virtual void SetDefault() { - attributeData.SetDefault(); + AttributeData.SetDefault(); } protected void UpdateResetToDefault(bool show) @@ -145,7 +149,7 @@ protected static VRCOSCTextBox CreateTextBox() Height = 40, Masking = true, CornerRadius = 5, - BorderColour = VRCOSCColour.Gray0, + BorderColour = ThemeManager.Current[ThemeAttribute.Border], BorderThickness = 2 }; } diff --git a/VRCOSC.Game/Graphics/ModuleEditing/Attributes/AttributeCardList.cs b/VRCOSC.Game/Graphics/ModuleEditing/Attributes/AttributeCardList.cs index 9aeb9cf4..efc2d078 100644 --- a/VRCOSC.Game/Graphics/ModuleEditing/Attributes/AttributeCardList.cs +++ b/VRCOSC.Game/Graphics/ModuleEditing/Attributes/AttributeCardList.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using VRCOSC.Game.Graphics.Themes; using VRCOSC.Game.Graphics.UI.Button; using VRCOSC.Game.Modules; @@ -14,7 +15,7 @@ namespace VRCOSC.Game.Graphics.ModuleEditing.Attributes; public abstract partial class AttributeCardList : AttributeCard { - protected ModuleAttributeList AttributeData; + protected new ModuleAttributeList AttributeData; protected AttributeCardList(ModuleAttributeList attributeData) : base(attributeData) @@ -40,7 +41,7 @@ private void load() RelativeSizeAxes = Axes.Both, Width = 0.8f, Icon = FontAwesome.Solid.Plus, - BackgroundColour = VRCOSCColour.Gray3, + BackgroundColour = ThemeManager.Current[ThemeAttribute.Dark], CornerRadius = 5, ShouldAnimate = false, Action = () => AddItem(GetDefaultItem()) @@ -62,11 +63,11 @@ protected void AddContent(Drawable content) ColumnDimensions = new[] { new Dimension(), - new Dimension(GridSizeMode.Absolute, 36), + new Dimension(GridSizeMode.Absolute, 36) }, Content = new[] { - new Drawable[] + new[] { content, new Container @@ -77,7 +78,7 @@ protected void AddContent(Drawable content) Padding = new MarginPadding { Vertical = 4, - Left = 4, + Left = 4 }, Child = removeButton = new IconButton { @@ -86,7 +87,7 @@ protected void AddContent(Drawable content) RelativeSizeAxes = Axes.Both, Icon = FontAwesome.Solid.Get(0xf00d), CornerRadius = 5, - BackgroundColour = VRCOSCColour.Gray4 + BackgroundColour = ThemeManager.Current[ThemeAttribute.Mid] } } } diff --git a/VRCOSC.Game/Graphics/ModuleEditing/Attributes/AttributeCardSingle.cs b/VRCOSC.Game/Graphics/ModuleEditing/Attributes/AttributeCardSingle.cs index d3f4ebe8..a4ae5308 100644 --- a/VRCOSC.Game/Graphics/ModuleEditing/Attributes/AttributeCardSingle.cs +++ b/VRCOSC.Game/Graphics/ModuleEditing/Attributes/AttributeCardSingle.cs @@ -1,6 +1,7 @@ // Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. // See the LICENSE file in the repository root for full license text. +using System; using osu.Framework.Bindables; using VRCOSC.Game.Modules; @@ -8,17 +9,33 @@ namespace VRCOSC.Game.Graphics.ModuleEditing.Attributes; public abstract partial class AttributeCardSingle : AttributeCard { - protected readonly ModuleAttributeSingle AttributeData; + protected new readonly ModuleAttributeSingle AttributeData; + protected virtual bool ShouldLimitSaves => false; + + private DateTimeOffset lastUpdateTime; + private readonly TimeSpan timeUntilSave = TimeSpan.FromSeconds(0.5f); + + private object lastValue = null!; protected AttributeCardSingle(ModuleAttributeSingle attributeData) : base(attributeData) { AttributeData = attributeData; + lastUpdateTime = DateTimeOffset.Now; } protected override void LoadComplete() { AttributeData.Attribute.BindValueChanged(e => Schedule(performAttributeUpdate, e), true); + lastValue = AttributeData.Attribute.Value; + } + + protected override void Update() + { + if (lastUpdateTime + timeUntilSave < DateTimeOffset.Now && !AttributeData.Attribute.Value.Equals(lastValue)) + { + AttributeData.Attribute.Value = lastValue; + } } private void performAttributeUpdate(ValueChangedEvent e) @@ -29,7 +46,12 @@ private void performAttributeUpdate(ValueChangedEvent e) protected virtual void UpdateValues(object value) { - AttributeData.Attribute.Value = value; + lastValue = value; + + if (ShouldLimitSaves) + lastUpdateTime = DateTimeOffset.Now; + else + AttributeData.Attribute.Value = value; } protected override void Dispose(bool isDisposing) diff --git a/VRCOSC.Game/Graphics/ModuleEditing/Attributes/Dropdown/DropdownAttributeCard.cs b/VRCOSC.Game/Graphics/ModuleEditing/Attributes/Dropdown/DropdownAttributeCard.cs index 9f4de837..496064e1 100644 --- a/VRCOSC.Game/Graphics/ModuleEditing/Attributes/Dropdown/DropdownAttributeCard.cs +++ b/VRCOSC.Game/Graphics/ModuleEditing/Attributes/Dropdown/DropdownAttributeCard.cs @@ -22,7 +22,7 @@ public DropdownAttributeCard(ModuleAttributeSingle attributeData) [BackgroundDependencyLoader] private void load() { - ContentFlow.Add(dropdown = new VRCOSCDropdown() + ContentFlow.Add(dropdown = new VRCOSCDropdown { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, diff --git a/VRCOSC.Game/Graphics/ModuleEditing/Attributes/Slider/SliderAttributeCard.cs b/VRCOSC.Game/Graphics/ModuleEditing/Attributes/Slider/SliderAttributeCard.cs index c1b125b4..07f810c7 100644 --- a/VRCOSC.Game/Graphics/ModuleEditing/Attributes/Slider/SliderAttributeCard.cs +++ b/VRCOSC.Game/Graphics/ModuleEditing/Attributes/Slider/SliderAttributeCard.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using VRCOSC.Game.Graphics.Themes; using VRCOSC.Game.Graphics.UI; using VRCOSC.Game.Modules; @@ -31,7 +32,7 @@ private void load() Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.X, Height = 40, - BorderColour = VRCOSCColour.Gray0, + BorderColour = ThemeManager.Current[ThemeAttribute.Border], BorderThickness = 2, Current = CreateCurrent() }); diff --git a/VRCOSC.Game/Graphics/ModuleEditing/Attributes/Text/ButtonTextAttributeCard.cs b/VRCOSC.Game/Graphics/ModuleEditing/Attributes/Text/ButtonTextAttributeCard.cs index 9633760e..e913a2a2 100644 --- a/VRCOSC.Game/Graphics/ModuleEditing/Attributes/Text/ButtonTextAttributeCard.cs +++ b/VRCOSC.Game/Graphics/ModuleEditing/Attributes/Text/ButtonTextAttributeCard.cs @@ -4,6 +4,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osuTK; +using VRCOSC.Game.Graphics.Themes; using VRCOSC.Game.Graphics.UI.Button; using VRCOSC.Game.Modules; @@ -47,7 +48,7 @@ protected override Drawable CreateContent() Masking = true, CornerRadius = 5, Action = attributeSingleWithButton.ButtonAction, - BackgroundColour = VRCOSCColour.Blue + BackgroundColour = ThemeManager.Current[ThemeAttribute.Action] } } } diff --git a/VRCOSC.Game/Graphics/ModuleEditing/Attributes/Text/TextAttributeCard.cs b/VRCOSC.Game/Graphics/ModuleEditing/Attributes/Text/TextAttributeCard.cs index 1b9d5157..3490bb31 100644 --- a/VRCOSC.Game/Graphics/ModuleEditing/Attributes/Text/TextAttributeCard.cs +++ b/VRCOSC.Game/Graphics/ModuleEditing/Attributes/Text/TextAttributeCard.cs @@ -11,6 +11,8 @@ namespace VRCOSC.Game.Graphics.ModuleEditing.Attributes.Text; public partial class TextAttributeCard : AttributeCardSingle { + protected override bool ShouldLimitSaves => true; + private VRCOSCTextBox textBox = null!; public TextAttributeCard(ModuleAttributeSingle attributeData) diff --git a/VRCOSC.Game/Graphics/ModuleEditing/Attributes/Toggle/ToggleAttributeCard.cs b/VRCOSC.Game/Graphics/ModuleEditing/Attributes/Toggle/ToggleAttributeCard.cs index 866d2df1..d23b150b 100644 --- a/VRCOSC.Game/Graphics/ModuleEditing/Attributes/Toggle/ToggleAttributeCard.cs +++ b/VRCOSC.Game/Graphics/ModuleEditing/Attributes/Toggle/ToggleAttributeCard.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using VRCOSC.Game.Graphics.Themes; using VRCOSC.Game.Graphics.UI.Button; using VRCOSC.Game.Modules; @@ -34,7 +35,7 @@ private void load() Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, CornerRadius = 10, - BorderColour = VRCOSCColour.Gray0, + BorderColour = ThemeManager.Current[ThemeAttribute.Border], BorderThickness = 2, ShouldAnimate = false, State = { Value = (bool)AttributeData.Attribute.Value } diff --git a/VRCOSC.Game/Graphics/ModuleEditing/ModuleEditingContent.cs b/VRCOSC.Game/Graphics/ModuleEditing/ModuleEditingContent.cs index 7c0b566d..b5969ba5 100644 --- a/VRCOSC.Game/Graphics/ModuleEditing/ModuleEditingContent.cs +++ b/VRCOSC.Game/Graphics/ModuleEditing/ModuleEditingContent.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osuTK; +using VRCOSC.Game.Graphics.Themes; using VRCOSC.Game.Modules; namespace VRCOSC.Game.Graphics.ModuleEditing; @@ -50,6 +51,7 @@ public ModuleEditingContent() Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Font = FrameworkFont.Regular.With(size: 75), + Colour = ThemeManager.Current[ThemeAttribute.Text], Margin = new MarginPadding { Vertical = 10 @@ -65,7 +67,7 @@ public ModuleEditingContent() Spacing = new Vector2(0, 5), Children = new[] { - settings = new SeparatedAttributeFlow(), + settings = new SeparatedAttributeFlow() } } } @@ -109,7 +111,7 @@ public SeparatedAttributeFlow() Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre }, - attributeFlow = new AttributeFlow(), + attributeFlow = new AttributeFlow() }; } diff --git a/VRCOSC.Game/Graphics/ModuleEditing/ModuleEditingPopover.cs b/VRCOSC.Game/Graphics/ModuleEditing/ModuleEditingPopover.cs index 04b243d5..4e93302c 100644 --- a/VRCOSC.Game/Graphics/ModuleEditing/ModuleEditingPopover.cs +++ b/VRCOSC.Game/Graphics/ModuleEditing/ModuleEditingPopover.cs @@ -5,6 +5,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using VRCOSC.Game.Graphics.Themes; using VRCOSC.Game.Modules; namespace VRCOSC.Game.Graphics.ModuleEditing; @@ -21,7 +22,7 @@ public ModuleEditingPopover() new Box { RelativeSizeAxes = Axes.Both, - Colour = VRCOSCColour.Gray4 + Colour = ThemeManager.Current[ThemeAttribute.Mid] }, new ModuleEditingContent { diff --git a/VRCOSC.Game/Graphics/ModuleListing/Footer.cs b/VRCOSC.Game/Graphics/ModuleListing/Footer.cs index acfb40a5..04f6cbc3 100644 --- a/VRCOSC.Game/Graphics/ModuleListing/Footer.cs +++ b/VRCOSC.Game/Graphics/ModuleListing/Footer.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Shapes; using osuTK; using VRCOSC.Game.Config; +using VRCOSC.Game.Graphics.Themes; using VRCOSC.Game.Graphics.UI.Button; namespace VRCOSC.Game.Graphics.ModuleListing; @@ -36,7 +37,7 @@ public Footer() new Box { RelativeSizeAxes = Axes.Both, - Colour = VRCOSCColour.Gray4 + Colour = ThemeManager.Current[ThemeAttribute.Mid] }, runButton = new TextButton { @@ -49,7 +50,7 @@ public Footer() Masking = true, CornerRadius = 5, Text = "Run", - BackgroundColour = VRCOSCColour.Green, + BackgroundColour = ThemeManager.Current[ThemeAttribute.Success], Action = () => modulesRunning.Value = true } }; diff --git a/VRCOSC.Game/Graphics/ModuleListing/Header.cs b/VRCOSC.Game/Graphics/ModuleListing/Header.cs index 2d8e55ac..7c12c652 100644 --- a/VRCOSC.Game/Graphics/ModuleListing/Header.cs +++ b/VRCOSC.Game/Graphics/ModuleListing/Header.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using VRCOSC.Game.Graphics.Themes; using VRCOSC.Game.Graphics.UI; namespace VRCOSC.Game.Graphics.ModuleListing; @@ -61,7 +62,7 @@ private void load(VRCOSCGame game) PlaceholderText = "Search", Current = game.SearchTermFilter, Masking = true, - BorderColour = Colour4.Black, + BorderColour = ThemeManager.Current[ThemeAttribute.Border], BorderThickness = 2 } } diff --git a/VRCOSC.Game/Graphics/ModuleListing/Listing.cs b/VRCOSC.Game/Graphics/ModuleListing/Listing.cs index 0923d58f..fa719584 100644 --- a/VRCOSC.Game/Graphics/ModuleListing/Listing.cs +++ b/VRCOSC.Game/Graphics/ModuleListing/Listing.cs @@ -79,7 +79,7 @@ private void filter() moduleCardFlow.ForEach(moduleCard => { var hasValidTitle = moduleCard.Module.Title.Contains(searchTerm, StringComparison.InvariantCultureIgnoreCase); - var hasValidType = type is null || moduleCard.Module.ModuleType.Equals(type); + var hasValidType = type is null || moduleCard.Module.Type.Equals(type); if (hasValidTitle && hasValidType) moduleCard.Show(); diff --git a/VRCOSC.Game/Graphics/ModuleListing/ModuleCard.cs b/VRCOSC.Game/Graphics/ModuleListing/ModuleCard.cs index ec0812ac..8a1f1cd7 100644 --- a/VRCOSC.Game/Graphics/ModuleListing/ModuleCard.cs +++ b/VRCOSC.Game/Graphics/ModuleListing/ModuleCard.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osuTK; +using VRCOSC.Game.Graphics.Themes; using VRCOSC.Game.Graphics.UI.Button; using VRCOSC.Game.Modules; @@ -44,7 +45,7 @@ public ModuleCard(Module module) Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Colour = VRCOSCColour.Gray2 + Colour = ThemeManager.Current[ThemeAttribute.Darker] }, new Box { @@ -112,6 +113,7 @@ public ModuleCard(Module module) Origin = Anchor.CentreRight, RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fit, + Alpha = Module.HasParameters ? 1 : 0.5f, Child = new IconButton { Anchor = Anchor.Centre, @@ -121,7 +123,8 @@ public ModuleCard(Module module) IconPadding = 5, CornerRadius = 5, Action = () => infoModule.Value = Module, - BackgroundColour = VRCOSCColour.Gray5 + BackgroundColour = ThemeManager.Current[ThemeAttribute.Light], + Enabled = { Value = Module.HasParameters } } }, new Container @@ -140,7 +143,7 @@ public ModuleCard(Module module) IconPadding = 5, CornerRadius = 5, Action = () => editingModule.Value = Module, - BackgroundColour = VRCOSCColour.Gray5, + BackgroundColour = ThemeManager.Current[ThemeAttribute.Light], Enabled = { Value = Module.HasSettings } } } @@ -151,6 +154,7 @@ public ModuleCard(Module module) metadataTextFlow.AddText(Module.Title, t => { t.Font = FrameworkFont.Regular.With(size: 25); + t.Colour = ThemeManager.Current[ThemeAttribute.Text]; }); var description = Module.Description; @@ -159,17 +163,19 @@ public ModuleCard(Module module) metadataTextFlow.AddParagraph(description, t => { t.Font = FrameworkFont.Regular.With(size: 20); + t.Colour = ThemeManager.Current[ThemeAttribute.Text]; }); } private Colour4 calculateModuleColour() { - return Module.ModuleType switch + return Module.Type switch { - ModuleType.General => VRCOSCColour.GrayD, - ModuleType.Health => VRCOSCColour.Red, - ModuleType.Integrations => VRCOSCColour.Yellow, - ModuleType.Accessibility => VRCOSCColour.Blue, + Module.ModuleType.General => Colour4.White.Darken(0.15f), + Module.ModuleType.Health => Colour4.Red, + Module.ModuleType.Integrations => Colour4.Yellow.Darken(0.25f), + Module.ModuleType.Accessibility => Colour4.FromHex(@"66ccff"), + Module.ModuleType.OpenVR => Colour4.FromHex(@"04144d"), _ => throw new ArgumentOutOfRangeException() }; } diff --git a/VRCOSC.Game/Graphics/ModuleListing/ModuleInfoPopover.cs b/VRCOSC.Game/Graphics/ModuleListing/ModuleInfoPopover.cs index 471cc47c..f5074aeb 100644 --- a/VRCOSC.Game/Graphics/ModuleListing/ModuleInfoPopover.cs +++ b/VRCOSC.Game/Graphics/ModuleListing/ModuleInfoPopover.cs @@ -11,8 +11,8 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osuTK; +using VRCOSC.Game.Graphics.Themes; using VRCOSC.Game.Modules; -using VRCOSC.Game.Modules.Util; namespace VRCOSC.Game.Graphics.ModuleListing; @@ -33,7 +33,7 @@ public ModuleInfoPopover() new Box { RelativeSizeAxes = Axes.Both, - Colour = VRCOSCColour.Gray4 + Colour = ThemeManager.Current[ThemeAttribute.Mid] }, new Container { @@ -66,9 +66,14 @@ public ModuleInfoPopover() { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Font = FrameworkFont.Regular.With(size: 75) + Font = FrameworkFont.Regular.With(size: 75), + Colour = ThemeManager.Current[ThemeAttribute.Text] }, - description = new TextFlowContainer(t => t.Font = FrameworkFont.Regular.With(size: 30)) + description = new TextFlowContainer(t => + { + t.Font = FrameworkFont.Regular.With(size: 30); + t.Colour = ThemeManager.Current[ThemeAttribute.Text]; + }) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -96,7 +101,8 @@ public ModuleInfoPopover() Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Font = FrameworkFont.Regular.With(size: 35), - Text = "Parameters" + Text = "Parameters", + Colour = ThemeManager.Current[ThemeAttribute.Text] }, parameters = new FillFlowContainer { @@ -130,7 +136,7 @@ public ParameterData() Masking = true; CornerRadius = 5; BorderThickness = 2; - BorderColour = VRCOSCColour.Gray0; + BorderColour = ThemeManager.Current[ThemeAttribute.Border]; } [BackgroundDependencyLoader] @@ -146,7 +152,7 @@ private void load() { new Box { - Colour = VRCOSCColour.Gray2, + Colour = ThemeManager.Current[ThemeAttribute.Darker], RelativeSizeAxes = Axes.Both }, new FillFlowContainer @@ -157,7 +163,7 @@ private void load() Padding = new MarginPadding(5), Children = new Drawable[] { - new TextFlowContainer + new TextFlowContainer(t => t.Colour = ThemeManager.Current[ThemeAttribute.Text]) { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, diff --git a/VRCOSC.Game/Graphics/ModuleListing/ModuleListingScreen.cs b/VRCOSC.Game/Graphics/ModuleListing/ModuleListingScreen.cs index 97e8d021..113f5e8d 100644 --- a/VRCOSC.Game/Graphics/ModuleListing/ModuleListingScreen.cs +++ b/VRCOSC.Game/Graphics/ModuleListing/ModuleListingScreen.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Shapes; using VRCOSC.Game.Graphics.ModuleEditing; using VRCOSC.Game.Graphics.ModuleRun; +using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.ModuleListing; @@ -22,7 +23,7 @@ public ModuleListingScreen() new Box { RelativeSizeAxes = Axes.Both, - Colour = VRCOSCColour.Gray5 + Colour = ThemeManager.Current[ThemeAttribute.Light] }, new GridContainer { diff --git a/VRCOSC.Game/Graphics/ModuleListing/TypeFilter.cs b/VRCOSC.Game/Graphics/ModuleListing/TypeFilter.cs index 365e806b..3e073616 100644 --- a/VRCOSC.Game/Graphics/ModuleListing/TypeFilter.cs +++ b/VRCOSC.Game/Graphics/ModuleListing/TypeFilter.cs @@ -44,8 +44,8 @@ private enum Group General = 0, Health = 1, Integrations = 2, - Accessibility = 3 + OpenVR = 4 } - private static ModuleType? groupToType(Group group) => group == Group.All ? null : (ModuleType)(int)group; + private static Module.ModuleType? groupToType(Group group) => group == Group.All ? null : (Module.ModuleType)(int)group; } diff --git a/VRCOSC.Game/Graphics/ModuleRun/ModuleRunPopover.cs b/VRCOSC.Game/Graphics/ModuleRun/ModuleRunPopover.cs index 060777e4..75ad7784 100644 --- a/VRCOSC.Game/Graphics/ModuleRun/ModuleRunPopover.cs +++ b/VRCOSC.Game/Graphics/ModuleRun/ModuleRunPopover.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.ModuleRun; @@ -24,7 +25,7 @@ public ModuleRunPopover() new Box { RelativeSizeAxes = Axes.Both, - Colour = VRCOSCColour.Gray3 + Colour = ThemeManager.Current[ThemeAttribute.Dark] }, new Container { diff --git a/VRCOSC.Game/Graphics/ModuleRun/ParameterContainer.cs b/VRCOSC.Game/Graphics/ModuleRun/ParameterContainer.cs index f480b225..028aacfc 100644 --- a/VRCOSC.Game/Graphics/ModuleRun/ParameterContainer.cs +++ b/VRCOSC.Game/Graphics/ModuleRun/ParameterContainer.cs @@ -5,11 +5,13 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using VRCOSC.Game.Graphics.Themes; using VRCOSC.Game.Modules; +using VRCOSC.OSC; namespace VRCOSC.Game.Graphics.ModuleRun; -public sealed partial class ParameterContainer : Container +public sealed partial class ParameterContainer : Container, IOscListener { [Resolved] private ModuleManager moduleManager { get; set; } = null!; @@ -63,10 +65,17 @@ public ParameterContainer() protected override void LoadComplete() { - base.LoadComplete(); + moduleManager.OscClient.RegisterListener(this); + } + + void IOscListener.OnDataSent(OscData data) + { + outgoingParameterDisplay.AddEntry(data.Address, data.Values[0]); + } - moduleManager.OscClient.OnParameterSent += (key, value) => outgoingParameterDisplay.AddEntry(key, value); - moduleManager.OscClient.OnParameterReceived += (key, value) => incomingParameterDisplay.AddEntry(key, value); + void IOscListener.OnDataReceived(OscData data) + { + incomingParameterDisplay.AddEntry(data.Address, data.Values[0]); } public void ClearParameters() @@ -96,7 +105,7 @@ private void load() new Box { RelativeSizeAxes = Axes.Both, - Colour = VRCOSCColour.Gray2, + Colour = ThemeManager.Current[ThemeAttribute.Darker] }, parameterDisplay = new ParameterDisplay { @@ -118,4 +127,10 @@ public void AddEntry(string key, object value) public void ClearContent() => parameterDisplay.ClearContent(); } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + moduleManager.OscClient.DeRegisterListener(this); + } } diff --git a/VRCOSC.Game/Graphics/ModuleRun/ParameterDisplay.cs b/VRCOSC.Game/Graphics/ModuleRun/ParameterDisplay.cs index 90a6bf29..a1f33dc3 100644 --- a/VRCOSC.Game/Graphics/ModuleRun/ParameterDisplay.cs +++ b/VRCOSC.Game/Graphics/ModuleRun/ParameterDisplay.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.ModuleRun; @@ -22,7 +23,7 @@ public ParameterDisplay() { Masking = true; BorderThickness = 2; - BorderColour = VRCOSCColour.Gray0; + BorderColour = ThemeManager.Current[ThemeAttribute.Border]; } [BackgroundDependencyLoader] @@ -50,6 +51,7 @@ private void load() Origin = Anchor.Centre, Text = Title, Font = FrameworkFont.Regular.With(size: 30), + Colour = ThemeManager.Current[ThemeAttribute.Text] } }, new Drawable[] @@ -72,7 +74,7 @@ private void load() Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y - }, + } } } } diff --git a/VRCOSC.Game/Graphics/ModuleRun/ParameterEntry.cs b/VRCOSC.Game/Graphics/ModuleRun/ParameterEntry.cs index 898754fc..3a9b6b90 100644 --- a/VRCOSC.Game/Graphics/ModuleRun/ParameterEntry.cs +++ b/VRCOSC.Game/Graphics/ModuleRun/ParameterEntry.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.ModuleRun; @@ -36,7 +37,7 @@ private void load() Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Font = FrameworkFont.Regular.With(size: 20), - Colour = VRCOSCColour.Gray8, + Colour = ThemeManager.Current[ThemeAttribute.SubText], Text = Key }, new Container @@ -49,14 +50,14 @@ private void load() background = new Box { RelativeSizeAxes = Axes.Both, - Colour = VRCOSCColour.Invisible + Colour = new Colour4(0, 0, 0, 0) }, valueText = new SpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, Font = FrameworkFont.Regular.With(size: 20), - Colour = VRCOSCColour.Gray8 + Colour = ThemeManager.Current[ThemeAttribute.SubText] } } } @@ -70,7 +71,7 @@ protected override void LoadComplete() Value.BindValueChanged(e => { valueText.Text = e.NewValue; - background.FlashColour(VRCOSCColour.GrayD, 500, Easing.OutCubic); + background.FlashColour(Colour4.White.Darken(0.15f), 500, Easing.OutCubic); }, true); } } diff --git a/VRCOSC.Game/Graphics/ModuleRun/TerminalContainer.cs b/VRCOSC.Game/Graphics/ModuleRun/TerminalContainer.cs index 2dfb6f6b..87157c87 100644 --- a/VRCOSC.Game/Graphics/ModuleRun/TerminalContainer.cs +++ b/VRCOSC.Game/Graphics/ModuleRun/TerminalContainer.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Pooling; using osu.Framework.Graphics.Shapes; using osu.Framework.Logging; +using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.ModuleRun; @@ -39,7 +40,7 @@ public TerminalContainer() new Box { RelativeSizeAxes = Axes.Both, - Colour = VRCOSCColour.Gray2, + Colour = ThemeManager.Current[ThemeAttribute.Darker] }, new Container { @@ -60,7 +61,7 @@ public TerminalContainer() Padding = new MarginPadding { Horizontal = 3 - }, + } } } } diff --git a/VRCOSC.Game/Graphics/ModuleRun/TerminalEntry.cs b/VRCOSC.Game/Graphics/ModuleRun/TerminalEntry.cs index 3430c2b5..249f8c55 100644 --- a/VRCOSC.Game/Graphics/ModuleRun/TerminalEntry.cs +++ b/VRCOSC.Game/Graphics/ModuleRun/TerminalEntry.cs @@ -4,6 +4,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; +using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.ModuleRun; @@ -19,14 +20,17 @@ public TerminalEntry() RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - InternalChild = new TextFlowContainer(t => t.Font = FrameworkFont.Regular.With(size: 20)) + InternalChild = new TextFlowContainer(t => + { + t.Font = FrameworkFont.Regular.With(size: 20); + t.Colour = ThemeManager.Current[ThemeAttribute.SubText]; + }) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - TextAnchor = Anchor.CentreLeft, - Colour = VRCOSCColour.Gray8 + TextAnchor = Anchor.CentreLeft }; } diff --git a/VRCOSC.Game/Graphics/Notifications/BasicNotification.cs b/VRCOSC.Game/Graphics/Notifications/BasicNotification.cs index bbe993b0..adc76606 100644 --- a/VRCOSC.Game/Graphics/Notifications/BasicNotification.cs +++ b/VRCOSC.Game/Graphics/Notifications/BasicNotification.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osuTK; +using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.Notifications; @@ -34,7 +35,7 @@ protected override Drawable CreateForeground() ColumnDimensions = new[] { new Dimension(GridSizeMode.Absolute, 55), - new Dimension(), + new Dimension() }, Content = new[] { @@ -92,10 +93,12 @@ protected override Drawable CreateForeground() textFlow.AddText(Title, s => { s.Font = FrameworkFont.Regular.With(size: 20); + s.Colour = ThemeManager.Current[ThemeAttribute.Text]; }); textFlow.AddText($"\n{Description}", s => { s.Font = FrameworkFont.Regular.With(size: 13); + s.Colour = ThemeManager.Current[ThemeAttribute.Text]; }); return foreground; diff --git a/VRCOSC.Game/Graphics/Notifications/Notification.cs b/VRCOSC.Game/Graphics/Notifications/Notification.cs index 9e97a238..1e097e83 100644 --- a/VRCOSC.Game/Graphics/Notifications/Notification.cs +++ b/VRCOSC.Game/Graphics/Notifications/Notification.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.Notifications; @@ -43,7 +44,7 @@ protected Notification() new Box { RelativeSizeAxes = Axes.Both, - Colour = VRCOSCColour.Gray3 + Colour = ThemeManager.Current[ThemeAttribute.Dark] }, Content = new Container { diff --git a/VRCOSC.Game/Graphics/Notifications/NotificationContainer.cs b/VRCOSC.Game/Graphics/Notifications/NotificationContainer.cs index 04bb9c3b..e06f6d71 100644 --- a/VRCOSC.Game/Graphics/Notifications/NotificationContainer.cs +++ b/VRCOSC.Game/Graphics/Notifications/NotificationContainer.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; +using VRCOSC.Game.Graphics.Themes; using VRCOSC.Game.Graphics.UI.Button; namespace VRCOSC.Game.Graphics.Notifications; @@ -42,7 +43,7 @@ public NotificationContainer() new Box { RelativeSizeAxes = Axes.Both, - Colour = VRCOSCColour.Gray4 + Colour = ThemeManager.Current[ThemeAttribute.Mid] }, new GridContainer { @@ -50,7 +51,7 @@ public NotificationContainer() ColumnDimensions = new[] { new Dimension(GridSizeMode.Absolute, 15), - new Dimension(), + new Dimension() }, Content = new[] { @@ -100,7 +101,7 @@ public CloseButton() background = new Box { RelativeSizeAxes = Axes.Both, - Colour = VRCOSCColour.Gray3.Opacity(0.75f) + Colour = ThemeManager.Current[ThemeAttribute.Dark].Opacity(0.75f) }, new Container { @@ -112,23 +113,24 @@ public CloseButton() Child = new SpriteIcon { RelativeSizeAxes = Axes.Both, - Icon = FontAwesome.Solid.Get(0xf101) + Icon = FontAwesome.Solid.Get(0xf101), + Colour = ThemeManager.Current[ThemeAttribute.Text] } - }, + } }; } protected override bool OnHover(HoverEvent e) { base.OnHover(e); - background.FadeColour(VRCOSCColour.Gray5, 100, Easing.OutQuad); + background.FadeColour(ThemeManager.Current[ThemeAttribute.Lighter], 100, Easing.OutQuad); return true; } protected override void OnHoverLost(HoverLostEvent e) { base.OnHoverLost(e); - background.FadeColour(VRCOSCColour.Gray3.Opacity(0.75f), 100, Easing.InQuad); + background.FadeColour(ThemeManager.Current[ThemeAttribute.Dark].Opacity(0.75f), 100, Easing.InQuad); } } } diff --git a/VRCOSC.Game/Graphics/Notifications/ProgressNotification.cs b/VRCOSC.Game/Graphics/Notifications/ProgressNotification.cs index 6f635622..fd93e6c8 100644 --- a/VRCOSC.Game/Graphics/Notifications/ProgressNotification.cs +++ b/VRCOSC.Game/Graphics/Notifications/ProgressNotification.cs @@ -5,6 +5,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; +using VRCOSC.Game.Graphics.Themes; using VRCOSC.Game.Graphics.UI; namespace VRCOSC.Game.Graphics.Notifications; @@ -32,7 +33,7 @@ protected override Drawable CreateForeground() RowDimensions = new[] { new Dimension(), - new Dimension(GridSizeMode.Absolute, 5), + new Dimension(GridSizeMode.Absolute, 5) }, Content = new[] { @@ -45,8 +46,8 @@ protected override Drawable CreateForeground() progressBar = new ProgressBar { RelativeSizeAxes = Axes.Both, - BackgroundColour = VRCOSCColour.Gray2, - SelectionColour = VRCOSCColour.GreenLight + BackgroundColour = ThemeManager.Current[ThemeAttribute.Darker], + SelectionColour = ThemeManager.Current[ThemeAttribute.Success] } } } diff --git a/VRCOSC.Game/Graphics/PopoverScreen.cs b/VRCOSC.Game/Graphics/PopoverScreen.cs index a9c43479..aba3ab3b 100644 --- a/VRCOSC.Game/Graphics/PopoverScreen.cs +++ b/VRCOSC.Game/Graphics/PopoverScreen.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osuTK; +using VRCOSC.Game.Graphics.Themes; using VRCOSC.Game.Graphics.UI.Button; namespace VRCOSC.Game.Graphics; @@ -34,7 +35,7 @@ protected PopoverScreen() CornerRadius = 10, BorderThickness = 2, EdgeEffect = VRCOSCEdgeEffects.DispersedShadow, - BorderColour = VRCOSCColour.Gray0 + BorderColour = ThemeManager.Current[ThemeAttribute.Border] }, new Container { diff --git a/VRCOSC.Game/Graphics/Settings/Cards/SettingCard.cs b/VRCOSC.Game/Graphics/Settings/Cards/SettingCard.cs index f82ee4f1..55249040 100644 --- a/VRCOSC.Game/Graphics/Settings/Cards/SettingCard.cs +++ b/VRCOSC.Game/Graphics/Settings/Cards/SettingCard.cs @@ -2,12 +2,12 @@ // See the LICENSE file in the repository root for full license text. using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osuTK; +using VRCOSC.Game.Graphics.Themes; using VRCOSC.Game.Graphics.UI; using VRCOSC.Game.Graphics.UI.Button; @@ -50,8 +50,8 @@ protected SettingCard(string title, string description, Bindable settingBinda IconPadding = 4, CornerRadius = 10, BorderThickness = 2, - BorderColour = VRCOSCColour.Gray0, - BackgroundColour = VRCOSCColour.BlueDark.Darken(0.25f), + BorderColour = ThemeManager.Current[ThemeAttribute.Border], + BackgroundColour = ThemeManager.Current[ThemeAttribute.Action], Icon = FontAwesome.Solid.Undo, IconShadow = true } @@ -62,14 +62,14 @@ protected SettingCard(string title, string description, Bindable settingBinda AutoSizeAxes = Axes.Y, Masking = true, CornerRadius = 10, - BorderColour = VRCOSCColour.Gray0, + BorderColour = ThemeManager.Current[ThemeAttribute.Border], BorderThickness = 2, Children = new Drawable[] { new Box { RelativeSizeAxes = Axes.Both, - Colour = VRCOSCColour.Gray2 + Colour = ThemeManager.Current[ThemeAttribute.Darker] }, new FillFlowContainer { @@ -95,7 +95,7 @@ protected SettingCard(string title, string description, Bindable settingBinda RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 10), + Spacing = new Vector2(0, 10) } } } @@ -106,11 +106,12 @@ protected SettingCard(string title, string description, Bindable settingBinda textFlow.AddText(title, t => { t.Font = FrameworkFont.Regular.With(size: 25); + t.Colour = ThemeManager.Current[ThemeAttribute.Text]; }); textFlow.AddParagraph(description, t => { t.Font = FrameworkFont.Regular.With(size: 20); - t.Colour = VRCOSCColour.Gray9; + t.Colour = ThemeManager.Current[ThemeAttribute.SubText]; }); } @@ -159,7 +160,7 @@ protected static VRCOSCTextBox CreateTextBox() Height = 40, Masking = true, CornerRadius = 5, - BorderColour = VRCOSCColour.Gray0, + BorderColour = ThemeManager.Current[ThemeAttribute.Border], BorderThickness = 2 }; } diff --git a/VRCOSC.Game/Graphics/Settings/Cards/ToggleSettingCard.cs b/VRCOSC.Game/Graphics/Settings/Cards/ToggleSettingCard.cs index 38da05bf..7cd63b53 100644 --- a/VRCOSC.Game/Graphics/Settings/Cards/ToggleSettingCard.cs +++ b/VRCOSC.Game/Graphics/Settings/Cards/ToggleSettingCard.cs @@ -5,6 +5,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using VRCOSC.Game.Graphics.Themes; using VRCOSC.Game.Graphics.UI.Button; namespace VRCOSC.Game.Graphics.Settings.Cards; @@ -34,7 +35,7 @@ private void load() Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, CornerRadius = 10, - BorderColour = VRCOSCColour.Gray0, + BorderColour = ThemeManager.Current[ThemeAttribute.Border], BorderThickness = 2, ShouldAnimate = false, State = { Value = SettingBindable.Value } diff --git a/VRCOSC.Game/Graphics/Settings/GeneralSection.cs b/VRCOSC.Game/Graphics/Settings/GeneralSection.cs new file mode 100644 index 00000000..a409bac3 --- /dev/null +++ b/VRCOSC.Game/Graphics/Settings/GeneralSection.cs @@ -0,0 +1,18 @@ +// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. +// See the LICENSE file in the repository root for full license text. + +using VRCOSC.Game.Config; +using VRCOSC.Game.Graphics.Themes; + +namespace VRCOSC.Game.Graphics.Settings; + +public partial class GeneralSection : SectionContainer +{ + protected override string Title => "General"; + + protected override void GenerateItems() + { + AddDropdown("Theme", "Select a theme and restart to see the effect", ConfigManager.GetBindable(VRCOSCSetting.Theme)); + AddIntTextBox("ChatBox Time Span", "The delay between the ChatBox updating (milliseconds)\nIf you're experiencing ChatBox timeouts, increase this number by a few hundred milliseconds", ConfigManager.GetBindable(VRCOSCSetting.ChatBoxTimeSpan)); + } +} diff --git a/VRCOSC.Game/Graphics/Settings/SectionContainer.cs b/VRCOSC.Game/Graphics/Settings/SectionContainer.cs index bb5b8d75..38774d6d 100644 --- a/VRCOSC.Game/Graphics/Settings/SectionContainer.cs +++ b/VRCOSC.Game/Graphics/Settings/SectionContainer.cs @@ -10,6 +10,7 @@ using osuTK; using VRCOSC.Game.Config; using VRCOSC.Game.Graphics.Settings.Cards; +using VRCOSC.Game.Graphics.Themes; using VRCOSC.Game.Graphics.UI.Button; namespace VRCOSC.Game.Graphics.Settings; @@ -38,7 +39,7 @@ protected SectionContainer() AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, Padding = new MarginPadding(5), - Spacing = new Vector2(0, 5), + Spacing = new Vector2(0, 5) }; } @@ -52,6 +53,7 @@ private void load(VRCOSCConfigManager configManager) Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Font = FrameworkFont.Regular.With(size: 35), + Colour = ThemeManager.Current[ThemeAttribute.Text], Text = Title }); @@ -97,7 +99,7 @@ protected void AddButton(string text, Colour4 colour, Action? action = null) BackgroundColour = colour, Text = text, Action = action, - BorderColour = Colour4.Black, + BorderColour = ThemeManager.Current[ThemeAttribute.Border], BorderThickness = 2 } }); diff --git a/VRCOSC.Game/Graphics/Settings/SettingsScreen.cs b/VRCOSC.Game/Graphics/Settings/SettingsScreen.cs index 459e3ee1..88e8cbed 100644 --- a/VRCOSC.Game/Graphics/Settings/SettingsScreen.cs +++ b/VRCOSC.Game/Graphics/Settings/SettingsScreen.cs @@ -5,6 +5,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osuTK; +using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.Settings; @@ -19,7 +20,7 @@ public SettingsScreen() new Box { RelativeSizeAxes = Axes.Both, - Colour = VRCOSCColour.Gray5 + Colour = ThemeManager.Current[ThemeAttribute.Light] }, new BasicScrollContainer { @@ -32,7 +33,11 @@ public SettingsScreen() AutoSizeAxes = Axes.Y, Children = new Drawable[] { - new TextFlowContainer(t => t.Font = FrameworkFont.Regular.With(size: 80)) + new TextFlowContainer(t => + { + t.Font = FrameworkFont.Regular.With(size: 80); + t.Colour = ThemeManager.Current[ThemeAttribute.Text]; + }) { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -48,6 +53,7 @@ public SettingsScreen() Spacing = new Vector2(0, 20), Children = new SectionContainer[] { + new GeneralSection(), new OscSection(), new ModulesSection(), new UpdateSection() diff --git a/VRCOSC.Game/Graphics/Settings/UpdateSection.cs b/VRCOSC.Game/Graphics/Settings/UpdateSection.cs index 6dc2301d..158e1bf4 100644 --- a/VRCOSC.Game/Graphics/Settings/UpdateSection.cs +++ b/VRCOSC.Game/Graphics/Settings/UpdateSection.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using VRCOSC.Game.Config; +using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.Settings; @@ -19,6 +20,6 @@ public sealed partial class UpdateSection : SectionContainer protected override void GenerateItems() { AddDropdown("Update Mode", "How should VRCOSC handle updating?", ConfigManager.GetBindable(VRCOSCSetting.UpdateMode)); - AddButton("Check For Update", VRCOSCColour.Gray4, () => game.UpdateManager.CheckForUpdate(configManager.Get(VRCOSCSetting.UpdateRepo))); + AddButton("Check For Update", ThemeManager.Current[ThemeAttribute.Mid], () => game.UpdateManager.CheckForUpdate(configManager.Get(VRCOSCSetting.UpdateRepo))); } } diff --git a/VRCOSC.Game/Graphics/TabBar/DrawableTab.cs b/VRCOSC.Game/Graphics/TabBar/DrawableTab.cs index 28b76eaf..a6f0a73a 100644 --- a/VRCOSC.Game/Graphics/TabBar/DrawableTab.cs +++ b/VRCOSC.Game/Graphics/TabBar/DrawableTab.cs @@ -9,14 +9,15 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osuTK; +using VRCOSC.Game.Graphics.Themes; using VRCOSC.Game.Modules; namespace VRCOSC.Game.Graphics.TabBar; public sealed partial class DrawableTab : ClickableContainer { - private static readonly Colour4 default_colour = VRCOSCColour.Invisible; - private static readonly Colour4 hover_colour = VRCOSCColour.Gray5; + private static readonly Colour4 default_colour = new(0, 0, 0, 0); + private static readonly Colour4 hover_colour = ThemeManager.Current[ThemeAttribute.Light]; private const int onhover_duration = 100; private const int onhoverlost_duration = onhover_duration; @@ -59,7 +60,7 @@ private void load() Size = new Vector2(0.35f), FillMode = FillMode.Fit, Icon = Icon, - Colour = Colour4.White + Colour = Colour = ThemeManager.Current[ThemeAttribute.Text] }, indicator = new SelectedIndicator { @@ -73,7 +74,7 @@ private void load() Child = new SpriteText { Text = Tab.ToString(), - Colour = Colour4.White, + Colour = ThemeManager.Current[ThemeAttribute.Text], Font = FrameworkFont.Regular.With(size: 20) } } @@ -102,7 +103,7 @@ protected override bool OnClick(ClickEvent e) { if (moduleManager.State.Value is ManagerState.Starting or ManagerState.Started) { - background.FlashColour(VRCOSCColour.Red, 250, Easing.OutQuad); + background.FlashColour(ThemeManager.Current[ThemeAttribute.Failure], 250, Easing.OutQuad); return true; } diff --git a/VRCOSC.Game/Graphics/TabBar/SelectedIndicator.cs b/VRCOSC.Game/Graphics/TabBar/SelectedIndicator.cs index efac829a..b6107bf1 100644 --- a/VRCOSC.Game/Graphics/TabBar/SelectedIndicator.cs +++ b/VRCOSC.Game/Graphics/TabBar/SelectedIndicator.cs @@ -5,6 +5,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osuTK; +using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.TabBar; @@ -22,7 +23,7 @@ public SelectedIndicator() Child = new Box { RelativeSizeAxes = Axes.Both, - Colour = Colour4.White + Colour = ThemeManager.Current[ThemeAttribute.Accent] } }; } diff --git a/VRCOSC.Game/Graphics/TabBar/TabPopover.cs b/VRCOSC.Game/Graphics/TabBar/TabPopover.cs index e3783bbf..ed01a452 100644 --- a/VRCOSC.Game/Graphics/TabBar/TabPopover.cs +++ b/VRCOSC.Game/Graphics/TabBar/TabPopover.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osuTK; +using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.TabBar; @@ -13,7 +14,7 @@ public sealed partial class TabPopover : Popover { public TabPopover() { - Background.Colour = VRCOSCColour.Gray3; + Background.Colour = ThemeManager.Current[ThemeAttribute.Dark]; Content.Padding = new MarginPadding(10); RelativePositionAxes = Axes.X; } @@ -27,7 +28,7 @@ protected override void LoadComplete() protected override Drawable CreateArrow() => new EquilateralTriangle { - Colour = VRCOSCColour.Gray3, + Colour = ThemeManager.Current[ThemeAttribute.Dark], Origin = Anchor.TopCentre, Scale = new Vector2(1.05f) }; diff --git a/VRCOSC.Game/Graphics/TabBar/TabSelector.cs b/VRCOSC.Game/Graphics/TabBar/TabSelector.cs index 5d5a37b4..d1180ad7 100644 --- a/VRCOSC.Game/Graphics/TabBar/TabSelector.cs +++ b/VRCOSC.Game/Graphics/TabBar/TabSelector.cs @@ -3,13 +3,12 @@ using System; using System.Collections.Generic; -using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.TabBar; @@ -24,9 +23,6 @@ public sealed partial class TabSelector : Container protected override FillFlowContainer Content { get; } - [Resolved] - private Bindable selectedTab { get; set; } = null!; - public TabSelector() { RelativeSizeAxes = Axes.Both; @@ -36,7 +32,7 @@ public TabSelector() new Box { RelativeSizeAxes = Axes.Both, - Colour = VRCOSCColour.Gray3 + Colour = ThemeManager.Current[ThemeAttribute.Dark] }, Content = new FillFlowContainer { diff --git a/VRCOSC.Game/Graphics/Themes/ThemeManager.cs b/VRCOSC.Game/Graphics/Themes/ThemeManager.cs new file mode 100644 index 00000000..5d122c74 --- /dev/null +++ b/VRCOSC.Game/Graphics/Themes/ThemeManager.cs @@ -0,0 +1,173 @@ +// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. +// See the LICENSE file in the repository root for full license text. + +using System.Collections.Generic; +using osu.Framework.Extensions.Color4Extensions; +using osuTK.Graphics; + +namespace VRCOSC.Game.Graphics.Themes; + +public static class ThemeManager +{ + public static ColourTheme Theme; + + public static Dictionary Current => schemes[Theme]; + + private static readonly Dictionary> schemes = new() + { + { + ColourTheme.Dark, new Dictionary + { + { ThemeAttribute.Darker, Color4Extensions.FromHex(@"222222") }, + { ThemeAttribute.Dark, Color4Extensions.FromHex(@"333333") }, + { ThemeAttribute.Mid, Color4Extensions.FromHex(@"444444") }, + { ThemeAttribute.Light, Color4Extensions.FromHex(@"555555") }, + { ThemeAttribute.Lighter, Color4Extensions.FromHex(@"666666") }, + { ThemeAttribute.Success, Color4Extensions.FromHex(@"88B300") }, + { ThemeAttribute.Pending, Color4Extensions.FromHex(@"FFCC22") }, + { ThemeAttribute.Failure, Color4Extensions.FromHex(@"ED1121") }, + { ThemeAttribute.Action, Color4Extensions.FromHex(@"44AADD") }, + { ThemeAttribute.Text, Color4Extensions.FromHex(@"FFFFFF") }, + { ThemeAttribute.SubText, Color4Extensions.FromHex(@"999999") }, + { ThemeAttribute.Border, Color4Extensions.FromHex(@"000000") }, + { ThemeAttribute.Accent, Color4Extensions.FromHex(@"FFFFFF") } + } + }, + { + ColourTheme.Light, new Dictionary + { + { ThemeAttribute.Darker, Color4Extensions.FromHex(@"DDDDDD") }, + { ThemeAttribute.Dark, Color4Extensions.FromHex(@"CCCCCC") }, + { ThemeAttribute.Mid, Color4Extensions.FromHex(@"BBBBBB") }, + { ThemeAttribute.Light, Color4Extensions.FromHex(@"AAAAAA") }, + { ThemeAttribute.Lighter, Color4Extensions.FromHex(@"999999") }, + { ThemeAttribute.Success, Color4Extensions.FromHex(@"88B300") }, + { ThemeAttribute.Pending, Color4Extensions.FromHex(@"FFCC22") }, + { ThemeAttribute.Failure, Color4Extensions.FromHex(@"ED1121") }, + { ThemeAttribute.Action, Color4Extensions.FromHex(@"44AADD") }, + { ThemeAttribute.Text, Color4Extensions.FromHex(@"000000") }, + { ThemeAttribute.SubText, Color4Extensions.FromHex(@"666666") }, + { ThemeAttribute.Border, Color4Extensions.FromHex(@"FFFFFF") }, + { ThemeAttribute.Accent, Color4Extensions.FromHex(@"000000") } + } + }, + { + ColourTheme.Discord, new Dictionary + { + { ThemeAttribute.Darker, Color4Extensions.FromHex(@"1e2124") }, + { ThemeAttribute.Dark, Color4Extensions.FromHex(@"282b30") }, + { ThemeAttribute.Mid, Color4Extensions.FromHex(@"36393e") }, + { ThemeAttribute.Light, Color4Extensions.FromHex(@"424549") }, + { ThemeAttribute.Lighter, Color4Extensions.FromHex(@"7289da") }, + { ThemeAttribute.Success, Color4Extensions.FromHex(@"88B300") }, + { ThemeAttribute.Pending, Color4Extensions.FromHex(@"FFCC22") }, + { ThemeAttribute.Failure, Color4Extensions.FromHex(@"ED1121") }, + { ThemeAttribute.Action, Color4Extensions.FromHex(@"7289da") }, + { ThemeAttribute.Text, Color4Extensions.FromHex(@"FFFFFF") }, + { ThemeAttribute.SubText, Color4Extensions.FromHex(@"999999") }, + { ThemeAttribute.Border, Color4Extensions.FromHex(@"000000") }, + { ThemeAttribute.Accent, Color4Extensions.FromHex(@"7289da") } + } + }, + { + ColourTheme.SpaceGrey, new Dictionary + { + { ThemeAttribute.Darker, Color4Extensions.FromHex(@"343d46") }, + { ThemeAttribute.Dark, Color4Extensions.FromHex(@"4f5b66") }, + { ThemeAttribute.Mid, Color4Extensions.FromHex(@"65737e") }, + { ThemeAttribute.Light, Color4Extensions.FromHex(@"a7adba").Darken(0.25f) }, + { ThemeAttribute.Lighter, Color4Extensions.FromHex(@"c0c5ce") }, + { ThemeAttribute.Success, Color4Extensions.FromHex(@"88B300") }, + { ThemeAttribute.Pending, Color4Extensions.FromHex(@"FFCC22") }, + { ThemeAttribute.Failure, Color4Extensions.FromHex(@"ED1121") }, + { ThemeAttribute.Action, Color4Extensions.FromHex(@"44AADD") }, + { ThemeAttribute.Text, Color4Extensions.FromHex(@"FFFFFF") }, + { ThemeAttribute.SubText, Color4Extensions.FromHex(@"999999") }, + { ThemeAttribute.Border, Color4Extensions.FromHex(@"000000") }, + { ThemeAttribute.Accent, Color4Extensions.FromHex(@"FFFFFF") } + } + }, + { + ColourTheme.NightSky, new Dictionary + { + { ThemeAttribute.Darker, Color4Extensions.FromHex(@"2e4482").Darken(0.25f) }, + { ThemeAttribute.Dark, Color4Extensions.FromHex(@"2e4482") }, + { ThemeAttribute.Mid, Color4Extensions.FromHex(@"546bab") }, + { ThemeAttribute.Light, Color4Extensions.FromHex(@"546bab").Lighten(0.25f) }, + { ThemeAttribute.Lighter, Color4Extensions.FromHex(@"546bab").Lighten(0.5f) }, + { ThemeAttribute.Success, Color4Extensions.FromHex(@"88B300") }, + { ThemeAttribute.Pending, Color4Extensions.FromHex(@"FFCC22") }, + { ThemeAttribute.Failure, Color4Extensions.FromHex(@"ED1121") }, + { ThemeAttribute.Action, Color4Extensions.FromHex(@"44AADD") }, + { ThemeAttribute.Text, Color4Extensions.FromHex(@"FFFFFF") }, + { ThemeAttribute.SubText, Color4Extensions.FromHex(@"999999") }, + { ThemeAttribute.Border, Color4Extensions.FromHex(@"000000") }, + { ThemeAttribute.Accent, Color4Extensions.FromHex(@"FFFFFF") } + } + }, + { + ColourTheme.Purple, new Dictionary + { + { ThemeAttribute.Darker, Color4Extensions.FromHex(@"660066").Darken(0.25f) }, + { ThemeAttribute.Dark, Color4Extensions.FromHex(@"800080").Darken(0.25f) }, + { ThemeAttribute.Mid, Color4Extensions.FromHex(@"be29ec").Darken(0.25f) }, + { ThemeAttribute.Light, Color4Extensions.FromHex(@"d896ff").Darken(0.25f) }, + { ThemeAttribute.Lighter, Color4Extensions.FromHex(@"efbbff").Darken(0.25f) }, + { ThemeAttribute.Success, Color4Extensions.FromHex(@"88B300") }, + { ThemeAttribute.Pending, Color4Extensions.FromHex(@"FFCC22") }, + { ThemeAttribute.Failure, Color4Extensions.FromHex(@"ED1121") }, + { ThemeAttribute.Action, Color4Extensions.FromHex(@"44AADD") }, + { ThemeAttribute.Text, Color4Extensions.FromHex(@"FFFFFF") }, + { ThemeAttribute.SubText, Color4Extensions.FromHex(@"999999") }, + { ThemeAttribute.Border, Color4Extensions.FromHex(@"000000") }, + { ThemeAttribute.Accent, Color4Extensions.FromHex(@"FFFFFF") } + } + }, + { + ColourTheme.Teal, new Dictionary + { + { ThemeAttribute.Darker, Color4Extensions.FromHex(@"003333").Darken(0.25f) }, + { ThemeAttribute.Dark, Color4Extensions.FromHex(@"004444").Darken(0.25f) }, + { ThemeAttribute.Mid, Color4Extensions.FromHex(@"005555").Darken(0.25f) }, + { ThemeAttribute.Light, Color4Extensions.FromHex(@"006666").Darken(0.25f) }, + { ThemeAttribute.Lighter, Color4Extensions.FromHex(@"007777").Darken(0.25f) }, + { ThemeAttribute.Success, Color4Extensions.FromHex(@"88B300") }, + { ThemeAttribute.Pending, Color4Extensions.FromHex(@"FFCC22") }, + { ThemeAttribute.Failure, Color4Extensions.FromHex(@"ED1121") }, + { ThemeAttribute.Action, Color4Extensions.FromHex(@"44AADD") }, + { ThemeAttribute.Text, Color4Extensions.FromHex(@"FFFFFF") }, + { ThemeAttribute.SubText, Color4Extensions.FromHex(@"999999") }, + { ThemeAttribute.Border, Color4Extensions.FromHex(@"000000") }, + { ThemeAttribute.Accent, Color4Extensions.FromHex(@"FFFFFF") } + } + } + }; +} + +public enum ColourTheme +{ + Dark, + Light, + Discord, + SpaceGrey, + NightSky, + Purple, + Teal +} + +public enum ThemeAttribute +{ + Darker, + Dark, + Mid, + Light, + Lighter, + Success, + Pending, + Failure, + Action, + Text, + SubText, + Border, + Accent +} diff --git a/VRCOSC.Game/Graphics/UI/Button/BasicButton.cs b/VRCOSC.Game/Graphics/UI/Button/BasicButton.cs index f9043b55..1ae43829 100644 --- a/VRCOSC.Game/Graphics/UI/Button/BasicButton.cs +++ b/VRCOSC.Game/Graphics/UI/Button/BasicButton.cs @@ -6,13 +6,14 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.UI.Button; public partial class BasicButton : VRCOSCButton { - private Colour4 backgroundColourStateOff = VRCOSCColour.Red; - private Colour4 backgroundColourStateOn = VRCOSCColour.Green; + private Colour4 backgroundColourStateOff = ThemeManager.Current[ThemeAttribute.Failure]; + private Colour4 backgroundColourStateOn = ThemeManager.Current[ThemeAttribute.Success]; protected Drawable BackgroundBox = null!; diff --git a/VRCOSC.Game/Graphics/UI/Button/IconButton.cs b/VRCOSC.Game/Graphics/UI/Button/IconButton.cs index 7c703688..328ea751 100644 --- a/VRCOSC.Game/Graphics/UI/Button/IconButton.cs +++ b/VRCOSC.Game/Graphics/UI/Button/IconButton.cs @@ -5,6 +5,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.UI.Button; @@ -13,7 +14,7 @@ public partial class IconButton : BasicButton private IconUsage iconStateOff = FontAwesome.Solid.PowerOff; private IconUsage iconStateOn = FontAwesome.Solid.PowerOff; - private SpriteIcon spriteIcon = null!; + protected SpriteIcon spriteIcon = null!; public IconUsage Icon { @@ -38,7 +39,7 @@ public IconUsage IconStateOff public IconUsage IconStateOn { - get => IconStateOn; + get => iconStateOn; set { iconStateOn = value; @@ -48,7 +49,7 @@ public IconUsage IconStateOn public int IconPadding { get; init; } = 8; - public bool IconShadow { get; init; } = false; + public bool IconShadow { get; init; } [BackgroundDependencyLoader] private void load() @@ -60,7 +61,7 @@ private void load() RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fit, Padding = new MarginPadding(IconPadding), - Child = spriteIcon = CreateSpriteIcon() + Child = spriteIcon = createSpriteIcon() }); State.BindValueChanged(_ => updateIcon(), true); @@ -82,14 +83,12 @@ private void updateIcon() spriteIcon.Icon = iconStateOff; } - private SpriteIcon CreateSpriteIcon() + private SpriteIcon createSpriteIcon() => new() { - return new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Shadow = IconShadow - }; - } + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Shadow = IconShadow, + Colour = ThemeManager.Current[ThemeAttribute.Text] + }; } diff --git a/VRCOSC.Game/Graphics/UI/Button/TextButton.cs b/VRCOSC.Game/Graphics/UI/Button/TextButton.cs index 1990d329..c3008930 100644 --- a/VRCOSC.Game/Graphics/UI/Button/TextButton.cs +++ b/VRCOSC.Game/Graphics/UI/Button/TextButton.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; +using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.UI.Button; @@ -33,6 +34,7 @@ private void load() Anchor = Anchor.Centre, Origin = Anchor.Centre, Font = FrameworkFont.Regular.With(size: FontSize), + Colour = ThemeManager.Current[ThemeAttribute.Text], Text = Text, Shadow = true }); diff --git a/VRCOSC.Game/Graphics/UI/Button/ToggleButton.cs b/VRCOSC.Game/Graphics/UI/Button/ToggleButton.cs index 0ee794e8..29076866 100644 --- a/VRCOSC.Game/Graphics/UI/Button/ToggleButton.cs +++ b/VRCOSC.Game/Graphics/UI/Button/ToggleButton.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; +using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.UI.Button; @@ -32,7 +33,7 @@ private void load() Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Colour = VRCOSCColour.Gray4 + Colour = ThemeManager.Current[ThemeAttribute.Mid] }, new Container { @@ -46,6 +47,7 @@ private void load() Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, Icon = FontAwesome.Solid.Check, + Colour = ThemeManager.Current[ThemeAttribute.Text], Alpha = State.Value ? 1 : 0 } } diff --git a/VRCOSC.Game/Graphics/UI/Button/VRCOSCButton.cs b/VRCOSC.Game/Graphics/UI/Button/VRCOSCButton.cs index 15d97dd8..63c2dac1 100644 --- a/VRCOSC.Game/Graphics/UI/Button/VRCOSCButton.cs +++ b/VRCOSC.Game/Graphics/UI/Button/VRCOSCButton.cs @@ -17,7 +17,7 @@ public partial class VRCOSCButton : osu.Framework.Graphics.UserInterface.Button public bool ShouldAnimate { get; init; } = true; - public VRCOSCButton() + protected VRCOSCButton() { Masking = true; Enabled.Value = true; diff --git a/VRCOSC.Game/Graphics/UI/ProgressBar.cs b/VRCOSC.Game/Graphics/UI/ProgressBar.cs index f1a62521..011c0a7b 100644 --- a/VRCOSC.Game/Graphics/UI/ProgressBar.cs +++ b/VRCOSC.Game/Graphics/UI/ProgressBar.cs @@ -3,6 +3,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics.UserInterface; +using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.UI; @@ -13,8 +14,8 @@ public sealed partial class ProgressBar : BasicSliderBar public ProgressBar() { - BackgroundColour = VRCOSCColour.Gray3; - SelectionColour = VRCOSCColour.Gray5; + BackgroundColour = ThemeManager.Current[ThemeAttribute.Dark]; + SelectionColour = ThemeManager.Current[ThemeAttribute.Light]; Current = new BindableNumber { MinValue = 0f, diff --git a/VRCOSC.Game/Graphics/UI/VRCOSCDropdown.cs b/VRCOSC.Game/Graphics/UI/VRCOSCDropdown.cs index d5ba1d07..4ffed2ed 100644 --- a/VRCOSC.Game/Graphics/UI/VRCOSCDropdown.cs +++ b/VRCOSC.Game/Graphics/UI/VRCOSCDropdown.cs @@ -11,6 +11,7 @@ using osu.Framework.Localisation; using osuTK; using osuTK.Graphics; +using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.UI; @@ -42,9 +43,9 @@ public VRCOSCDropdownMenu() [BackgroundDependencyLoader(true)] private void load() { - BackgroundColour = VRCOSCColour.Gray2; - HoverColour = VRCOSCColour.Gray5; - SelectionColour = VRCOSCColour.Gray5; + BackgroundColour = ThemeManager.Current[ThemeAttribute.Darker]; + HoverColour = ThemeManager.Current[ThemeAttribute.Light]; + SelectionColour = ThemeManager.Current[ThemeAttribute.Light]; } private bool wasOpened; @@ -172,7 +173,7 @@ protected override void UpdateForegroundColour() { base.UpdateForegroundColour(); - if (Foreground.Children.FirstOrDefault() is Content content) + if (Foreground.FirstOrDefault() is Content content) content.Hovering = IsHovered; } @@ -206,14 +207,15 @@ public Content() X = chevron_offset, Margin = new MarginPadding { Left = 3, Right = 3 }, Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft }, Label = new SpriteText { X = 15, Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, - }, + Colour = ThemeManager.Current[ThemeAttribute.Text] + } }; } @@ -275,7 +277,7 @@ public VRCOSCDropdownHeader() Margin = new MarginPadding { Bottom = 4 }; CornerRadius = corner_radius; Masking = true; - BorderColour = VRCOSCColour.Gray0; + BorderColour = ThemeManager.Current[ThemeAttribute.Border]; BorderThickness = 2; Height = 40; @@ -285,12 +287,12 @@ public VRCOSCDropdownHeader() AutoSizeAxes = Axes.Y, RowDimensions = new[] { - new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize) }, ColumnDimensions = new[] { new Dimension(), - new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize) }, Content = new[] { @@ -302,7 +304,8 @@ public VRCOSCDropdownHeader() Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.X, Truncate = true, - Font = FrameworkFont.Regular.With(size: 25) + Font = FrameworkFont.Regular.With(size: 25), + Colour = ThemeManager.Current[ThemeAttribute.Text] }, Icon = new SpriteIcon { @@ -310,7 +313,8 @@ public VRCOSCDropdownHeader() Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, Size = new Vector2(16), - }, + Colour = ThemeManager.Current[ThemeAttribute.Text] + } } } }; @@ -319,8 +323,8 @@ public VRCOSCDropdownHeader() [BackgroundDependencyLoader] private void load() { - BackgroundColour = VRCOSCColour.Gray2; - BackgroundColourHover = VRCOSCColour.Gray4; + BackgroundColour = ThemeManager.Current[ThemeAttribute.Darker]; + BackgroundColourHover = ThemeManager.Current[ThemeAttribute.Mid]; } } } diff --git a/VRCOSC.Game/Graphics/UI/VRCOSCScrollContainer.cs b/VRCOSC.Game/Graphics/UI/VRCOSCScrollContainer.cs index 605f57a8..b2f0e053 100644 --- a/VRCOSC.Game/Graphics/UI/VRCOSCScrollContainer.cs +++ b/VRCOSC.Game/Graphics/UI/VRCOSCScrollContainer.cs @@ -5,6 +5,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osuTK; +using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.UI; @@ -48,7 +49,7 @@ public VRCOSCScrollbar(Direction direction) Child = new Box { RelativeSizeAxes = Axes.Both, - Colour = VRCOSCColour.Gray2 + Colour = ThemeManager.Current[ThemeAttribute.Darker] } }; } diff --git a/VRCOSC.Game/Graphics/UI/VRCOSCSlider.cs b/VRCOSC.Game/Graphics/UI/VRCOSCSlider.cs index 398e04a1..7746ba72 100644 --- a/VRCOSC.Game/Graphics/UI/VRCOSCSlider.cs +++ b/VRCOSC.Game/Graphics/UI/VRCOSCSlider.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.UI; @@ -18,8 +19,8 @@ public sealed partial class VRCOSCSlider : BasicSliderBar where T : struct public VRCOSCSlider() { - BackgroundColour = VRCOSCColour.Gray4; - SelectionColour = VRCOSCColour.Gray6; + BackgroundColour = ThemeManager.Current[ThemeAttribute.Mid]; + SelectionColour = ThemeManager.Current[ThemeAttribute.Lighter]; Masking = true; CornerRadius = 10; } @@ -41,19 +42,22 @@ private void load() Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Font = FrameworkFont.Regular.With(size: 30), + Colour = ThemeManager.Current[ThemeAttribute.Text], Text = CurrentNumber.MinValue.ToString()! }, valueText = new SpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Font = FrameworkFont.Regular.With(size: 30) + Font = FrameworkFont.Regular.With(size: 30), + Colour = ThemeManager.Current[ThemeAttribute.Text] }, new SpriteText { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, Font = FrameworkFont.Regular.With(size: 30), + Colour = ThemeManager.Current[ThemeAttribute.Text], Text = CurrentNumber.MaxValue.ToString()! } } diff --git a/VRCOSC.Game/Graphics/UI/VRCOSCTextBox.cs b/VRCOSC.Game/Graphics/UI/VRCOSCTextBox.cs index afd71df2..073dcaeb 100644 --- a/VRCOSC.Game/Graphics/UI/VRCOSCTextBox.cs +++ b/VRCOSC.Game/Graphics/UI/VRCOSCTextBox.cs @@ -2,10 +2,12 @@ // See the LICENSE file in the repository root for full license text. using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Platform; +using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.UI; @@ -16,8 +18,8 @@ public sealed partial class VRCOSCTextBox : BasicTextBox public VRCOSCTextBox() { - BackgroundFocused = VRCOSCColour.Gray3; - BackgroundUnfocused = VRCOSCColour.Gray3; + BackgroundFocused = ThemeManager.Current[ThemeAttribute.Dark]; + BackgroundUnfocused = ThemeManager.Current[ThemeAttribute.Dark]; Masking = true; CommitOnFocusLost = true; } @@ -32,9 +34,20 @@ protected override void LoadComplete() protected override SpriteText CreatePlaceholder() { - return base.CreatePlaceholder().With(t => t.Colour = Colour4.White.Opacity(0.5f)); + return base.CreatePlaceholder().With(t => t.Colour = ThemeManager.Current[ThemeAttribute.Text].Opacity(0.5f)); } + protected override Drawable GetDrawableCharacter(char c) => new FallingDownContainer + { + AutoSizeAxes = Axes.Both, + Child = new SpriteText + { + Text = c.ToString(), + Font = FrameworkFont.Condensed.With(size: CalculatedTextSize), + Colour = ThemeManager.Current[ThemeAttribute.Text] + } + }; + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); diff --git a/VRCOSC.Game/Graphics/Updater/VRCOSCUpdateManager.cs b/VRCOSC.Game/Graphics/Updater/VRCOSCUpdateManager.cs index 6b5da1a1..efcbff43 100644 --- a/VRCOSC.Game/Graphics/Updater/VRCOSCUpdateManager.cs +++ b/VRCOSC.Game/Graphics/Updater/VRCOSCUpdateManager.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Platform; using VRCOSC.Game.Graphics.Notifications; +using VRCOSC.Game.Graphics.Themes; namespace VRCOSC.Game.Graphics.Updater; @@ -24,7 +25,7 @@ protected void PostCheckNotification() => Schedule(() => { Title = "Update Available", Description = "Click to install", - Colour = VRCOSCColour.Green, + Colour = ThemeManager.Current[ThemeAttribute.Pending], Icon = FontAwesome.Solid.ExclamationTriangle, ClickCallback = () => ApplyUpdates() }); @@ -35,7 +36,7 @@ protected ProgressNotification PostProgressNotification() var progressNotification = new ProgressNotification { Title = "Installing Update", - Colour = VRCOSCColour.Yellow, + Colour = ThemeManager.Current[ThemeAttribute.Pending], Icon = FontAwesome.Solid.Cog }; @@ -49,7 +50,7 @@ protected void PostSuccessNotification() => Schedule(() => { Title = "Update Complete", Description = "Click to restart", - Colour = VRCOSCColour.Green, + Colour = ThemeManager.Current[ThemeAttribute.Success], Icon = FontAwesome.Solid.ExclamationTriangle, ClickCallback = RequestRestart }); @@ -61,7 +62,7 @@ protected void PostFailNotification() => Schedule(() => { Title = "Update Failed", Description = "Click to reinstall", - Colour = VRCOSCColour.Red, + Colour = ThemeManager.Current[ThemeAttribute.Failure], Icon = FontAwesome.Solid.ExclamationTriangle, ClickCallback = () => host.OpenUrlExternally("https://github.com/VolcanicArts/VRCOSC/releases/latest") }); diff --git a/VRCOSC.Game/Graphics/VRCOSCColour.cs b/VRCOSC.Game/Graphics/VRCOSCColour.cs deleted file mode 100644 index 06c9cb12..00000000 --- a/VRCOSC.Game/Graphics/VRCOSCColour.cs +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. -// See the LICENSE file in the repository root for full license text. - -using osu.Framework.Extensions.Color4Extensions; -using osuTK.Graphics; - -// ReSharper disable InconsistentNaming -// ReSharper disable StringLiteralTypo -// ReSharper disable UnusedMember.Global - -namespace VRCOSC.Game.Graphics; - -// Taken and modified from https://github.com/ppy/osu/blob/master/osu.Game/Graphics/OsuColour.cs -public static class VRCOSCColour -{ - public static readonly Color4 Invisible = new(0, 0, 0, 0); - - public static readonly Color4 PurpleLighter = Color4Extensions.FromHex(@"eeeeff"); - public static readonly Color4 PurpleLight = Color4Extensions.FromHex(@"aa88ff"); - public static readonly Color4 PurpleLightAlternative = Color4Extensions.FromHex(@"cba4da"); - public static readonly Color4 Purple = Color4Extensions.FromHex(@"8866ee"); - public static readonly Color4 PurpleDark = Color4Extensions.FromHex(@"6644cc"); - public static readonly Color4 PurpleDarkAlternative = Color4Extensions.FromHex(@"312436"); - public static readonly Color4 PurpleDarker = Color4Extensions.FromHex(@"441188"); - - public static readonly Color4 PinkLighter = Color4Extensions.FromHex(@"ffddee"); - public static readonly Color4 PinkLight = Color4Extensions.FromHex(@"ff99cc"); - public static readonly Color4 Pink = Color4Extensions.FromHex(@"ff66aa"); - public static readonly Color4 PinkDark = Color4Extensions.FromHex(@"cc5288"); - public static readonly Color4 PinkDarker = Color4Extensions.FromHex(@"bb1177"); - - public static readonly Color4 BlueLighter = Color4Extensions.FromHex(@"ddffff"); - public static readonly Color4 BlueLight = Color4Extensions.FromHex(@"99eeff"); - public static readonly Color4 Blue = Color4Extensions.FromHex(@"66ccff"); - public static readonly Color4 BlueDark = Color4Extensions.FromHex(@"44aadd"); - public static readonly Color4 BlueDarker = Color4Extensions.FromHex(@"2299bb"); - - public static readonly Color4 YellowLighter = Color4Extensions.FromHex(@"ffffdd"); - public static readonly Color4 YellowLight = Color4Extensions.FromHex(@"ffdd55"); - public static readonly Color4 Yellow = Color4Extensions.FromHex(@"ffcc22"); - public static readonly Color4 YellowDark = Color4Extensions.FromHex(@"eeaa00"); - public static readonly Color4 YellowDarker = Color4Extensions.FromHex(@"cc6600"); - - public static readonly Color4 GreenLighter = Color4Extensions.FromHex(@"eeffcc"); - public static readonly Color4 GreenLight = Color4Extensions.FromHex(@"b3d944"); - public static readonly Color4 Green = Color4Extensions.FromHex(@"88b300"); - public static readonly Color4 GreenDark = Color4Extensions.FromHex(@"668800"); - public static readonly Color4 GreenDarker = Color4Extensions.FromHex(@"445500"); - - public static readonly Color4 Sky = Color4Extensions.FromHex(@"6bb5ff"); - public static readonly Color4 GreySkyLighter = Color4Extensions.FromHex(@"c6e3f4"); - public static readonly Color4 GreySkyLight = Color4Extensions.FromHex(@"8ab3cc"); - public static readonly Color4 GreySky = Color4Extensions.FromHex(@"405461"); - public static readonly Color4 GreySkyDark = Color4Extensions.FromHex(@"303d47"); - public static readonly Color4 GreySkyDarker = Color4Extensions.FromHex(@"21272c"); - - public static readonly Color4 SeaFoam = Color4Extensions.FromHex(@"05ffa2"); - public static readonly Color4 GreySeaFoamLighter = Color4Extensions.FromHex(@"9ebab1"); - public static readonly Color4 GreySeaFoamLight = Color4Extensions.FromHex(@"4d7365"); - public static readonly Color4 GreySeaFoam = Color4Extensions.FromHex(@"33413c"); - public static readonly Color4 GreySeaFoamDark = Color4Extensions.FromHex(@"2c3532"); - public static readonly Color4 GreySeaFoamDarker = Color4Extensions.FromHex(@"1e2422"); - - public static readonly Color4 Cyan = Color4Extensions.FromHex(@"05f4fd"); - public static readonly Color4 GreyCyanLighter = Color4Extensions.FromHex(@"77b1b3"); - public static readonly Color4 GreyCyanLight = Color4Extensions.FromHex(@"436d6f"); - public static readonly Color4 GreyCyan = Color4Extensions.FromHex(@"293d3e"); - public static readonly Color4 GreyCyanDark = Color4Extensions.FromHex(@"243536"); - public static readonly Color4 GreyCyanDarker = Color4Extensions.FromHex(@"1e2929"); - - public static readonly Color4 Lime = Color4Extensions.FromHex(@"82ff05"); - public static readonly Color4 GreyLimeLighter = Color4Extensions.FromHex(@"deff87"); - public static readonly Color4 GreyLimeLight = Color4Extensions.FromHex(@"657259"); - public static readonly Color4 GreyLime = Color4Extensions.FromHex(@"3f443a"); - public static readonly Color4 GreyLimeDark = Color4Extensions.FromHex(@"32352e"); - public static readonly Color4 GreyLimeDarker = Color4Extensions.FromHex(@"2e302b"); - - public static readonly Color4 Violet = Color4Extensions.FromHex(@"bf04ff"); - public static readonly Color4 GreyVioletLighter = Color4Extensions.FromHex(@"ebb8fe"); - public static readonly Color4 GreyVioletLight = Color4Extensions.FromHex(@"685370"); - public static readonly Color4 GreyViolet = Color4Extensions.FromHex(@"46334d"); - public static readonly Color4 GreyVioletDark = Color4Extensions.FromHex(@"2c2230"); - public static readonly Color4 GreyVioletDarker = Color4Extensions.FromHex(@"201823"); - - public static readonly Color4 Carmine = Color4Extensions.FromHex(@"ff0542"); - public static readonly Color4 GreyCarmineLighter = Color4Extensions.FromHex(@"deaab4"); - public static readonly Color4 GreyCarmineLight = Color4Extensions.FromHex(@"644f53"); - public static readonly Color4 GreyCarmine = Color4Extensions.FromHex(@"342b2d"); - public static readonly Color4 GreyCarmineDark = Color4Extensions.FromHex(@"302a2b"); - public static readonly Color4 GreyCarmineDarker = Color4Extensions.FromHex(@"241d1e"); - - public static readonly Color4 RedLighter = Color4Extensions.FromHex(@"ffeded"); - public static readonly Color4 RedLight = Color4Extensions.FromHex(@"ed7787"); - public static readonly Color4 Red = Color4Extensions.FromHex(@"ed1121"); - public static readonly Color4 RedDark = Color4Extensions.FromHex(@"ba0011"); - public static readonly Color4 RedDarker = Color4Extensions.FromHex(@"870000"); - - public static readonly Color4 Gray0 = Color4Extensions.FromHex(@"000"); - public static readonly Color4 Gray1 = Color4Extensions.FromHex(@"111"); - public static readonly Color4 Gray2 = Color4Extensions.FromHex(@"222"); - public static readonly Color4 Gray3 = Color4Extensions.FromHex(@"333"); - public static readonly Color4 Gray4 = Color4Extensions.FromHex(@"444"); - public static readonly Color4 Gray5 = Color4Extensions.FromHex(@"555"); - public static readonly Color4 Gray6 = Color4Extensions.FromHex(@"666"); - public static readonly Color4 Gray7 = Color4Extensions.FromHex(@"777"); - public static readonly Color4 Gray8 = Color4Extensions.FromHex(@"888"); - public static readonly Color4 Gray9 = Color4Extensions.FromHex(@"999"); - public static readonly Color4 GrayA = Color4Extensions.FromHex(@"aaa"); - public static readonly Color4 GrayB = Color4Extensions.FromHex(@"bbb"); - public static readonly Color4 GrayC = Color4Extensions.FromHex(@"ccc"); - public static readonly Color4 GrayD = Color4Extensions.FromHex(@"ddd"); - public static readonly Color4 GrayE = Color4Extensions.FromHex(@"eee"); - public static readonly Color4 GrayF = Color4Extensions.FromHex(@"fff"); -} diff --git a/VRCOSC.Game/Graphics/VRCOSCEdgeEffects.cs b/VRCOSC.Game/Graphics/VRCOSCEdgeEffects.cs index 8a908c5e..0307e6b8 100644 --- a/VRCOSC.Game/Graphics/VRCOSCEdgeEffects.cs +++ b/VRCOSC.Game/Graphics/VRCOSCEdgeEffects.cs @@ -14,7 +14,7 @@ public static class VRCOSCEdgeEffects { public static readonly EdgeEffectParameters NoShadow = new() { - Colour = VRCOSCColour.Invisible, + Colour = new Color4(0, 0, 0, 0), Radius = 0, Type = EdgeEffectType.Shadow, Offset = Vector2.Zero diff --git a/VRCOSC.Game/Modules/Websocket/BaseWebSocket.cs b/VRCOSC.Game/Modules/BaseWebSocket.cs similarity index 95% rename from VRCOSC.Game/Modules/Websocket/BaseWebSocket.cs rename to VRCOSC.Game/Modules/BaseWebSocket.cs index 64876d1d..ac2489bf 100644 --- a/VRCOSC.Game/Modules/Websocket/BaseWebSocket.cs +++ b/VRCOSC.Game/Modules/BaseWebSocket.cs @@ -5,12 +5,11 @@ using System.Threading; using System.Threading.Tasks; using SuperSocket.ClientEngine; -using VRCOSC.Game.Util; using WebSocket4Net; // ReSharper disable MemberCanBeProtected.Global -namespace VRCOSC.Game.Modules.Websocket; +namespace VRCOSC.Game.Modules; public class BaseWebSocket : IDisposable { @@ -46,7 +45,7 @@ public void Disconnect() webSocket.Close(); } - protected void Send(string data) + public void Send(string data) { webSocket.Send(data); } diff --git a/VRCOSC.Game/Modules/ChatBox.cs b/VRCOSC.Game/Modules/ChatBoxInterface.cs similarity index 81% rename from VRCOSC.Game/Modules/ChatBox.cs rename to VRCOSC.Game/Modules/ChatBoxInterface.cs index 160ea53e..98e08a62 100644 --- a/VRCOSC.Game/Modules/ChatBox.cs +++ b/VRCOSC.Game/Modules/ChatBoxInterface.cs @@ -7,42 +7,42 @@ using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; +using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; -using VRCOSC.Game.Modules.Util; using VRCOSC.OSC; namespace VRCOSC.Game.Modules; -public class ChatBox +public class ChatBoxInterface { - private const int chatbox_reset_milli = 1500; - private const string chatbox_address_typing = "/chatbox/typing"; - private const string chatbox_address_text = "/chatbox/input"; - - private readonly OscClient oscClient; private readonly ConcurrentQueue timedQueue = new(); private readonly ConcurrentDictionary alwaysDict = new(); + private readonly OscClient oscClient; + private readonly IBindable resetMilli; private TimedTask? queueTask; private ChatBoxData? currentData; private DateTimeOffset? sendReset; private DateTimeOffset sendExpire; private bool alreadyClear; - private bool sendEnabled; - public ChatBox(OscClient oscClient) + public bool SendEnabled { get; private set; } + + public ChatBoxInterface(OscClient oscClient, IBindable resetMilli) { this.oscClient = oscClient; + this.resetMilli = resetMilli; } public void SetSending(bool canSend) { - sendEnabled = canSend; + SendEnabled = canSend; + if (!SendEnabled) clear(); } public void SetTyping(bool typing) { - oscClient.SendValue(chatbox_address_typing, typing); + oscClient.SendValue(Constants.OSC_ADDRESS_CHATBOX_TYPING, typing); } public void Init() @@ -51,10 +51,11 @@ public void Init() currentData = null; sendExpire = DateTimeOffset.Now; sendReset = null; - sendEnabled = true; + SendEnabled = true; timedQueue.Clear(); alwaysDict.Clear(); - queueTask = new TimedTask(update, 5).Start(); + queueTask = new TimedTask(update, 5); + _ = queueTask.Start(); } public async Task Shutdown() @@ -65,7 +66,7 @@ public async Task Shutdown() private void clear() { - oscClient.SendValues(chatbox_address_text, new List { string.Empty, true }); + oscClient.SendValues(Constants.OSC_ADDRESS_CHATBOX_INPUT, new List { string.Empty, true }); alreadyClear = true; } @@ -139,8 +140,8 @@ private void trySendText() if (currentData.Text is null) return; - if (sendEnabled) oscClient.SendValues(chatbox_address_text, new List { currentData.Text!, true }); - sendReset = DateTimeOffset.Now + TimeSpan.FromMilliseconds(chatbox_reset_milli); + if (SendEnabled) oscClient.SendValues(Constants.OSC_ADDRESS_CHATBOX_INPUT, new List { currentData.Text!, true }); + sendReset = DateTimeOffset.Now + TimeSpan.FromMilliseconds(resetMilli.Value); } private class ChatBoxData diff --git a/VRCOSC.Game/Modules/MediaController.cs b/VRCOSC.Game/Modules/MediaController.cs deleted file mode 100644 index 3b16757f..00000000 --- a/VRCOSC.Game/Modules/MediaController.cs +++ /dev/null @@ -1,173 +0,0 @@ -// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. -// See the LICENSE file in the repository root for full license text. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Windows.Media.Control; -using osu.Framework.Extensions.IEnumerableExtensions; - -namespace VRCOSC.Game.Modules; - -// Taken from https://github.com/DubyaDude/WindowsMediaController -// Applied .NET6 specific modifications - -public class MediaManager : IDisposable -{ - public delegate void SessionChangeDelegate(MediaSession mediaSession); - - public delegate void PlaybackChangeDelegate(MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionPlaybackInfo playbackInfo); - - public delegate void SongChangeDelegate(MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionMediaProperties mediaProperties); - - public event SessionChangeDelegate? OnAnySessionOpened; - public event SessionChangeDelegate? OnAnySessionClosed; - public event PlaybackChangeDelegate? OnAnyPlaybackStateChanged; - public event SongChangeDelegate? OnAnyMediaPropertyChanged; - - public readonly Dictionary CurrentMediaSessions = new(); - - private bool isStarted { get; set; } - - private GlobalSystemMediaTransportControlsSessionManager? windowsSessionManager { get; set; } - - public async Task Start() - { - if (!isStarted) - { - windowsSessionManager = await GlobalSystemMediaTransportControlsSessionManager.RequestAsync(); - sessionsChanged(windowsSessionManager); - windowsSessionManager.SessionsChanged += sessionsChanged; - isStarted = true; - } - else - { - throw new InvalidOperationException("MediaManager already started"); - } - } - - private void sessionsChanged(GlobalSystemMediaTransportControlsSessionManager winSessionManager, SessionsChangedEventArgs? args = null) - { - var controlSessionList = winSessionManager.GetSessions(); - - foreach (var controlSession in controlSessionList) - { - if (!CurrentMediaSessions.ContainsKey(controlSession.SourceAppUserModelId)) - { - var mediaSession = new MediaSession(controlSession, this); - CurrentMediaSessions[controlSession.SourceAppUserModelId] = mediaSession; - - try { OnAnySessionOpened?.Invoke(mediaSession); } - catch { } - - mediaSession.OnSongChange(controlSession); - } - } - - //Checking if a source fell off the session list without doing a proper Closed event (I.E. Spotify) - var controlSessionIds = controlSessionList.Select(x => x.SourceAppUserModelId); - (from session in CurrentMediaSessions where !controlSessionIds.Contains(session.Key) select session.Value).ForEach(x => x.Dispose()); - } - - private bool removeSource(MediaSession mediaSession) - { - if (CurrentMediaSessions.ContainsKey(mediaSession.Id)) - { - CurrentMediaSessions.Remove(mediaSession.Id); - - try { OnAnySessionClosed?.Invoke(mediaSession); } - catch { } - - return true; - } - - return false; - } - - public void Dispose() - { - OnAnySessionOpened = null; - OnAnySessionClosed = null; - OnAnyMediaPropertyChanged = null; - OnAnyPlaybackStateChanged = null; - - var keys = CurrentMediaSessions.Keys.ToList(); - - foreach (var key in keys) - { - CurrentMediaSessions[key].Dispose(); - } - - CurrentMediaSessions?.Clear(); - - isStarted = false; - windowsSessionManager = null; - - GC.SuppressFinalize(this); - } - - public class MediaSession - { - public event SessionChangeDelegate? OnSessionClosed; - public event PlaybackChangeDelegate? OnPlaybackStateChanged; - public event SongChangeDelegate? OnMediaPropertyChanged; - public GlobalSystemMediaTransportControlsSession? ControlSession { get; private set; } - - public readonly string Id; - - private readonly MediaManager mediaManagerInstance; - - internal MediaSession(GlobalSystemMediaTransportControlsSession controlSession, MediaManager mediaMangerInstance) - { - mediaManagerInstance = mediaMangerInstance; - ControlSession = controlSession; - Id = ControlSession.SourceAppUserModelId; - ControlSession.MediaPropertiesChanged += OnSongChange; - ControlSession.PlaybackInfoChanged += OnPlaybackInfoChanged; - } - - private void OnPlaybackInfoChanged(GlobalSystemMediaTransportControlsSession controlSession, PlaybackInfoChangedEventArgs? args = null) - { - var playbackInfo = controlSession.GetPlaybackInfo(); - - if (playbackInfo.PlaybackStatus == GlobalSystemMediaTransportControlsSessionPlaybackStatus.Closed) - { - Dispose(); - } - else - { - try { OnPlaybackStateChanged?.Invoke(this, playbackInfo); } - catch { } - - try { mediaManagerInstance.OnAnyPlaybackStateChanged?.Invoke(this, playbackInfo); } - catch { } - } - } - - internal async void OnSongChange(GlobalSystemMediaTransportControlsSession controlSession, MediaPropertiesChangedEventArgs? args = null) - { - var mediaProperties = await controlSession.TryGetMediaPropertiesAsync(); - - try { OnMediaPropertyChanged?.Invoke(this, mediaProperties); } - catch { } - - try { mediaManagerInstance.OnAnyMediaPropertyChanged?.Invoke(this, mediaProperties); } - catch { } - } - - internal void Dispose() - { - if (mediaManagerInstance.removeSource(this)) - { - OnPlaybackStateChanged = null; - OnMediaPropertyChanged = null; - OnSessionClosed = null; - ControlSession = null; - - try { OnSessionClosed?.Invoke(this); } - catch { } - } - } - } -} diff --git a/VRCOSC.Game/Modules/Module.cs b/VRCOSC.Game/Modules/Module.cs index d7ea3c9e..8a235207 100644 --- a/VRCOSC.Game/Modules/Module.cs +++ b/VRCOSC.Game/Modules/Module.cs @@ -13,8 +13,6 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Logging; using osu.Framework.Platform; -using VRCOSC.Game.Modules.Util; -using VRCOSC.Game.Util; using VRCOSC.OSC; // ReSharper disable MemberCanBePrivate.Global @@ -23,15 +21,18 @@ namespace VRCOSC.Game.Modules; [SuppressMessage("Usage", "CA2208:Instantiate argument exceptions correctly")] -public abstract class Module +public abstract class Module : IOscListener { private GameHost Host = null!; private Storage Storage = null!; private OscClient OscClient = null!; private TerminalLogger Terminal = null!; - protected Bindable State = null!; + private ChatBoxInterface chatBoxInterface = null!; private TimedTask? updateTask; - private ChatBox ChatBox = null!; + + protected Player Player = null!; + protected OpenVRInterface OpenVrInterface = null!; + protected Bindable State = null!; public readonly BindableBool Enabled = new(); public readonly Dictionary Settings = new(); @@ -40,24 +41,29 @@ public abstract class Module public virtual string Title => string.Empty; public virtual string Description => string.Empty; public virtual string Author => string.Empty; - public virtual ModuleType ModuleType => ModuleType.General; public virtual string Prefab => string.Empty; + public virtual ModuleType Type => ModuleType.General; protected virtual int DeltaUpdate => int.MaxValue; - protected virtual bool ExecuteUpdateImmediately => true; protected virtual int ChatBoxPriority => 0; - protected Player Player = null!; - protected OpenVrInterface OpenVrInterface = null!; + private bool IsEnabled => Enabled.Value; + private bool ShouldUpdate => DeltaUpdate != int.MaxValue; + private string FileName => @$"{GetType().Name}.ini"; - public const float vrc_osc_update_rate = 20; - public static readonly int vrc_osc_delta_update = (int)((1f / vrc_osc_update_rate) * 1000f); + protected bool IsStarting => State.Value == ModuleState.Starting; + protected bool HasStarted => State.Value == ModuleState.Started; + protected bool IsStopping => State.Value == ModuleState.Stopping; + protected bool HasStopped => State.Value == ModuleState.Stopped; + + public bool HasSettings => Settings.Any(); + public bool HasParameters => Parameters.Any(); - public void Initialise(GameHost host, Storage storage, OscClient oscClient, ChatBox chatBox, OpenVrInterface openVrInterface) + public void Initialise(GameHost host, Storage storage, OscClient oscClient, ChatBoxInterface chatBoxInterface, OpenVRInterface openVrInterface) { Host = host; Storage = storage; OscClient = oscClient; - ChatBox = chatBox; + this.chatBoxInterface = chatBoxInterface; Terminal = new TerminalLogger(GetType().Name); State = new Bindable(ModuleState.Stopped); Player = new Player(OscClient); @@ -69,83 +75,51 @@ public void Initialise(GameHost host, Storage storage, OscClient oscClient, Chat State.ValueChanged += _ => Log(State.Value.ToString()); } - #region Properties - - public bool HasSettings => Settings.Any(); - - private bool IsEnabled => Enabled.Value; - private bool ShouldUpdate => DeltaUpdate != int.MaxValue; - private string FileName => @$"{GetType().Name}.ini"; - - protected bool IsStarting => State.Value == ModuleState.Starting; - protected bool HasStarted => State.Value == ModuleState.Started; - protected bool IsStopping => State.Value == ModuleState.Stopping; - protected bool HasStopped => State.Value == ModuleState.Stopped; - - public const string VRChatOscPrefix = @"/avatar/parameters/"; - - #endregion - #region Attributes protected virtual void CreateAttributes() { } - protected void CreateSetting(Enum lookup, string displayName, string description, bool defaultValue) - => addSingleSetting(lookup, displayName, description, defaultValue); + protected void CreateSetting(Enum lookup, string displayName, string description, bool defaultValue, Func? dependsOn = null) + => addSingleSetting(lookup, displayName, description, defaultValue, dependsOn); - protected void CreateSetting(Enum lookup, string displayName, string description, int defaultValue) - => addSingleSetting(lookup, displayName, description, defaultValue); + protected void CreateSetting(Enum lookup, string displayName, string description, int defaultValue, Func? dependsOn = null) + => addSingleSetting(lookup, displayName, description, defaultValue, dependsOn); - protected void CreateSetting(Enum lookup, string displayName, string description, string defaultValue) - => addSingleSetting(lookup, displayName, description, defaultValue); + protected void CreateSetting(Enum lookup, string displayName, string description, string defaultValue, Func? dependsOn = null) + => addSingleSetting(lookup, displayName, description, defaultValue, dependsOn); - protected void CreateSetting(Enum lookup, string displayName, string description, Enum defaultValue) - => addSingleSetting(lookup, displayName, description, defaultValue); + protected void CreateSetting(Enum lookup, string displayName, string description, Enum defaultValue, Func? dependsOn = null) + => addSingleSetting(lookup, displayName, description, defaultValue, dependsOn); - protected void CreateSetting(Enum lookup, string displayName, string description, int defaultValue, int minValue, int maxValue) - => addRangedSetting(lookup, displayName, description, defaultValue, minValue, maxValue); + protected void CreateSetting(Enum lookup, string displayName, string description, int defaultValue, int minValue, int maxValue, Func? dependsOn = null) + => addRangedSetting(lookup, displayName, description, defaultValue, minValue, maxValue, dependsOn); - protected void CreateSetting(Enum lookup, string displayName, string description, float defaultValue, float minValue, float maxValue) - => addRangedSetting(lookup, displayName, description, defaultValue, minValue, maxValue); + protected void CreateSetting(Enum lookup, string displayName, string description, float defaultValue, float minValue, float maxValue, Func? dependsOn = null) + => addRangedSetting(lookup, displayName, description, defaultValue, minValue, maxValue, dependsOn); - protected void CreateSetting(Enum lookup, string displayName, string description, IEnumerable defaultValues, bool canBeEmpty) - => addEnumerableSetting(lookup, displayName, description, defaultValues, canBeEmpty); + protected void CreateSetting(Enum lookup, string displayName, string description, IEnumerable defaultValues, bool canBeEmpty, Func? dependsOn = null) + => addEnumerableSetting(lookup, displayName, description, defaultValues, canBeEmpty, dependsOn); - protected void CreateSetting(Enum lookup, string displayName, string description, IEnumerable defaultValues, bool canBeEmpty) - => addEnumerableSetting(lookup, displayName, description, defaultValues, canBeEmpty); + protected void CreateSetting(Enum lookup, string displayName, string description, IEnumerable defaultValues, bool canBeEmpty, Func? dependsOn = null) + => addEnumerableSetting(lookup, displayName, description, defaultValues, canBeEmpty, dependsOn); - protected void CreateSetting(Enum lookup, string displayName, string description, string defaultValue, string buttonText, Action buttonAction) - => addTextAndButtonSetting(lookup, displayName, description, defaultValue, buttonText, buttonAction); + protected void CreateSetting(Enum lookup, string displayName, string description, string defaultValue, string buttonText, Action buttonAction, Func? dependsOn = null) + => addTextAndButtonSetting(lookup, displayName, description, defaultValue, buttonText, buttonAction, dependsOn); - protected void CreateParameter(Enum lookup, ParameterMode mode, string parameterName, string description, ActionMenu menuLink = ActionMenu.None) - { - if (!mode.HasFlagFast(ParameterMode.Read) && menuLink != ActionMenu.None) - { - throw new InvalidOperationException("Cannot set an action menu link on a write-only parameter"); - } + protected void CreateParameter(Enum lookup, ParameterMode mode, string parameterName, string description) + => Parameters.Add(lookup, new ParameterMetadata(mode, parameterName, description, typeof(T))); - Parameters.Add(lookup, new ParameterMetadata(mode, parameterName, description, typeof(T), menuLink)); - } + private void addSingleSetting(Enum lookup, string displayName, string description, object defaultValue, Func? dependsOn) + => Settings.Add(lookup.ToLookup(), new ModuleAttributeSingle(new ModuleAttributeMetadata(displayName, description), defaultValue, dependsOn)); - private void addSingleSetting(Enum lookup, string displayName, string description, object defaultValue) - { - Settings.Add(lookup.ToString().ToLowerInvariant(), new ModuleAttributeSingle(new ModuleAttributeMetadata(displayName, description), defaultValue)); - } - - private void addEnumerableSetting(Enum lookup, string displayName, string description, IEnumerable defaultValues, bool canBeEmpty) - { - Settings.Add(lookup.ToString().ToLowerInvariant(), new ModuleAttributeList(new ModuleAttributeMetadata(displayName, description), defaultValues.Cast(), typeof(T), canBeEmpty)); - } + private void addEnumerableSetting(Enum lookup, string displayName, string description, IEnumerable defaultValues, bool canBeEmpty, Func? dependsOn) + => Settings.Add(lookup.ToLookup(), new ModuleAttributeList(new ModuleAttributeMetadata(displayName, description), defaultValues.Cast(), typeof(T), canBeEmpty, dependsOn)); - private void addRangedSetting(Enum lookup, string displayName, string description, T defaultValue, T minValue, T maxValue) where T : struct - { - Settings.Add(lookup.ToString().ToLowerInvariant(), new ModuleAttributeSingleWithBounds(new ModuleAttributeMetadata(displayName, description), defaultValue, minValue, maxValue)); - } + private void addRangedSetting(Enum lookup, string displayName, string description, T defaultValue, T minValue, T maxValue, Func? dependsOn) where T : struct + => Settings.Add(lookup.ToLookup(), new ModuleAttributeSingleWithBounds(new ModuleAttributeMetadata(displayName, description), defaultValue, minValue, maxValue, dependsOn)); - private void addTextAndButtonSetting(Enum lookup, string displayName, string description, string defaultValue, string buttonText, Action buttonAction) - { - Settings.Add(lookup.ToString().ToLowerInvariant(), new ModuleAttributeSingleWithButton(new ModuleAttributeMetadata(displayName, description), defaultValue, buttonText, buttonAction)); - } + private void addTextAndButtonSetting(Enum lookup, string displayName, string description, string defaultValue, string buttonText, Action buttonAction, Func? dependsOn) + => Settings.Add(lookup.ToLookup(), new ModuleAttributeSingleWithButton(new ModuleAttributeMetadata(displayName, description), defaultValue, buttonText, buttonAction, dependsOn)); #endregion @@ -160,9 +134,10 @@ internal async Task start(CancellationToken cancellationToken) await OnStart(cancellationToken); - if (ShouldUpdate) updateTask = new TimedTask(OnUpdate, DeltaUpdate, ExecuteUpdateImmediately).Start(); + if (ShouldUpdate) updateTask = new TimedTask(OnUpdate, DeltaUpdate, true); + await (updateTask?.Start() ?? Task.CompletedTask); - OscClient.OnParameterReceived += onParameterReceived; + OscClient.RegisterListener(this); State.Value = ModuleState.Started; } @@ -173,7 +148,7 @@ internal async Task stop() State.Value = ModuleState.Stopping; - OscClient.OnParameterReceived -= onParameterReceived; + OscClient.DeRegisterListener(this); if (updateTask is not null) await updateTask.Stop(); @@ -195,7 +170,7 @@ protected virtual void OnPlayerStateUpdate(VRChatInputParameter key) { } protected T GetSetting(Enum lookup) { - var setting = Settings[lookup.ToString().ToLowerInvariant()]; + var setting = Settings[lookup.ToLookup()]; object? value; @@ -206,17 +181,11 @@ protected T GetSetting(Enum lookup) break; case ModuleAttributeList settingList when settingList.Type == typeof(string): - var originalListStr = settingList.AttributeList.ToList(); - var convertedListStr = new List(); - originalListStr.ForEach(obj => convertedListStr.Add((string)obj.Value)); - value = convertedListStr; + value = settingList.GetValueList(); break; case ModuleAttributeList settingList when settingList.Type == typeof(int): - var originalListInt = settingList.AttributeList.ToList(); - var convertedList = new List(); - originalListInt.ForEach(obj => convertedList.Add((int)obj.Value)); - value = convertedList; + value = settingList.GetValueList(); break; default: @@ -232,60 +201,76 @@ protected T GetSetting(Enum lookup) #endregion - #region IncomingParameters + #region Parameters + + protected void SendParameter(Enum lookup, T value) where T : struct + { + if (HasStopped) return; + + if (!Parameters.ContainsKey(lookup)) throw new InvalidOperationException($"Parameter {lookup} has not been defined"); + + var data = Parameters[lookup]; + if (!data.Mode.HasFlagFast(ParameterMode.Write)) throw new InvalidOperationException("Cannot send a value to a read-only parameter"); + + OscClient.SendValue(data.FormattedAddress, value); + } + + void IOscListener.OnDataSent(OscData data) { } - private void onParameterReceived(string address, object value) + void IOscListener.OnDataReceived(OscData data) { - if (address.StartsWith("/avatar/change")) + if (!data.Values.Any()) return; + + var address = data.Address; + var value = data.Values[0]; + + if (address.StartsWith(Constants.OSC_ADDRESS_AVATAR_CHANGE)) { OnAvatarChange(); return; } - if (!address.StartsWith(VRChatOscPrefix)) return; + if (!address.StartsWith(Constants.OSC_ADDRESS_AVATAR_PARAMETERS_PREFIX)) return; - var parameterName = address.Remove(0, VRChatOscPrefix.Length); + var parameterName = address.Remove(0, Constants.OSC_ADDRESS_AVATAR_PARAMETERS_PREFIX.Length); updatePlayerState(parameterName, value); try { Enum lookup = Parameters.Single(pair => pair.Value.Name == parameterName).Key; - var data = Parameters[lookup]; + var parameterData = Parameters[lookup]; - if (!data.Mode.HasFlagFast(ParameterMode.Read)) return; + if (!parameterData.Mode.HasFlagFast(ParameterMode.Read)) return; - if (value.GetType() != data.ExpectedType) + if (value.GetType() != parameterData.ExpectedType) { - Log($@"Cannot accept input parameter. `{lookup}` expects type `{data.ExpectedType}` but received type `{value.GetType()}`"); + Log($@"Cannot accept input parameter. `{lookup}` expects type `{parameterData.ExpectedType}` but received type `{value.GetType()}`"); return; } - notifyParameterReceived(lookup, value, data); + switch (value) + { + case bool boolValue: + OnBoolParameterReceived(lookup, boolValue); + break; + + case int intValue: + OnIntParameterReceived(lookup, intValue); + break; + + case float floatValue: + OnFloatParameterReceived(lookup, floatValue); + break; + } } catch (InvalidOperationException) { } } - private void notifyParameterReceived(Enum key, object value, ParameterMetadata data) - { - switch (value) - { - case bool boolValue: - OnBoolParameterReceived(key, boolValue); - if (data.Menu == ActionMenu.Button && boolValue) OnButtonPressed(key); - break; - - case int intValue: - OnIntParameterReceived(key, intValue); - break; - - case float floatValue: - OnFloatParameterReceived(key, floatValue); - if (data.Menu == ActionMenu.Radial) OnRadialPuppetChange(key, floatValue); - break; - } - } + protected virtual void OnBoolParameterReceived(Enum key, bool value) { } + protected virtual void OnIntParameterReceived(Enum key, int value) { } + protected virtual void OnFloatParameterReceived(Enum key, float value) { } private void updatePlayerState(string parameterName, object value) { @@ -366,53 +351,12 @@ private void updatePlayerState(string parameterName, object value) break; default: - throw new ArgumentOutOfRangeException(nameof(vrChatInputParameter), vrChatInputParameter, "Unknown VRChatInputParameter"); + throw new ArgumentOutOfRangeException(nameof(vrChatInputParameter), vrChatInputParameter, $"Unknown {nameof(VRChatInputParameter)}"); } OnPlayerStateUpdate(vrChatInputParameter); } - protected virtual void OnBoolParameterReceived(Enum key, bool value) { } - protected virtual void OnIntParameterReceived(Enum key, int value) { } - protected virtual void OnFloatParameterReceived(Enum key, float value) { } - protected virtual void OnButtonPressed(Enum key) { } - protected virtual void OnRadialPuppetChange(Enum key, float radialValue) { } - - #endregion - - #region OutgoingParameters - - protected class OutputParameter - { - private readonly OscClient oscClient; - private readonly string address; - - public OutputParameter(OscClient oscClient, string address) - { - this.oscClient = oscClient; - this.address = address; - } - - public void SendValue(T value) where T : struct => oscClient.SendValue(address, value); - } - - protected OutputParameter GetOutputParameter(Enum lookup) - { - if (!Parameters.ContainsKey(lookup)) throw new InvalidOperationException($"Parameter {lookup.ToString()} has not been defined"); - - var data = Parameters[lookup]; - if (!data.Mode.HasFlagFast(ParameterMode.Write)) throw new InvalidOperationException("Cannot send a value to a read-only parameter"); - - return new OutputParameter(OscClient, data.FormattedAddress); - } - - protected void SendParameter(Enum lookup, T value) where T : struct - { - if (HasStopped) return; - - GetOutputParameter(lookup).SendValue(value); - } - #endregion #region Loading @@ -678,9 +622,9 @@ private void performSettingsSave(TextWriter writer) protected void OpenUrlExternally(string Url) => Host.OpenUrlExternally(Url); - protected DateTimeOffset SetChatBoxText(string? text, TimeSpan displayLength) => ChatBox.SetText(text, ChatBoxPriority, displayLength); + protected DateTimeOffset SetChatBoxText(string? text, TimeSpan displayLength) => chatBoxInterface.SetText(text, ChatBoxPriority, displayLength); - protected void SetChatBoxTyping(bool typing) => ChatBox.SetTyping(typing); + protected void SetChatBoxTyping(bool typing) => chatBoxInterface.SetTyping(typing); protected static float Map(float source, float sMin, float sMax, float dMin, float dMax) => dMin + (dMax - dMin) * ((source - sMin) / (sMax - sMin)); @@ -693,4 +637,13 @@ public enum ModuleState Stopping, Stopped } + + public enum ModuleType + { + General = 0, + Health = 1, + Integrations = 2, + Accessibility = 3, + OpenVR = 4 + } } diff --git a/VRCOSC.Game/Modules/ModuleAttribute.cs b/VRCOSC.Game/Modules/ModuleAttribute.cs index a8d5cd2a..4d9c681f 100644 --- a/VRCOSC.Game/Modules/ModuleAttribute.cs +++ b/VRCOSC.Game/Modules/ModuleAttribute.cs @@ -12,12 +12,16 @@ namespace VRCOSC.Game.Modules; public abstract class ModuleAttribute { public readonly ModuleAttributeMetadata Metadata; + private readonly Func? dependsOn; - protected ModuleAttribute(ModuleAttributeMetadata metadata) + protected ModuleAttribute(ModuleAttributeMetadata metadata, Func? dependsOn) { Metadata = metadata; + this.dependsOn = dependsOn; } + public bool Enabled => dependsOn?.Invoke() ?? true; + public abstract void SetDefault(); public abstract bool IsDefault(); @@ -27,8 +31,8 @@ public class ModuleAttributeSingle : ModuleAttribute { public readonly Bindable Attribute; - public ModuleAttributeSingle(ModuleAttributeMetadata metadata, object defaultValue) - : base(metadata) + public ModuleAttributeSingle(ModuleAttributeMetadata metadata, object defaultValue, Func? dependsOn) + : base(metadata, dependsOn) { Attribute = new Bindable(defaultValue); } @@ -43,8 +47,8 @@ public sealed class ModuleAttributeSingleWithButton : ModuleAttributeSingle public readonly Action ButtonAction; public readonly string ButtonText; - public ModuleAttributeSingleWithButton(ModuleAttributeMetadata metadata, object defaultValue, string buttonText, Action buttonAction) - : base(metadata, defaultValue) + public ModuleAttributeSingleWithButton(ModuleAttributeMetadata metadata, object defaultValue, string buttonText, Action buttonAction, Func? dependsOn) + : base(metadata, defaultValue, dependsOn) { ButtonText = buttonText; ButtonAction = buttonAction; @@ -56,8 +60,8 @@ public sealed class ModuleAttributeSingleWithBounds : ModuleAttributeSingle public readonly object MinValue; public readonly object MaxValue; - public ModuleAttributeSingleWithBounds(ModuleAttributeMetadata metadata, object defaultValue, object minValue, object maxValue) - : base(metadata, defaultValue) + public ModuleAttributeSingleWithBounds(ModuleAttributeMetadata metadata, object defaultValue, object minValue, object maxValue, Func? dependsOn) + : base(metadata, defaultValue, dependsOn) { MinValue = minValue; MaxValue = maxValue; @@ -71,8 +75,8 @@ public sealed class ModuleAttributeList : ModuleAttribute public readonly Type Type; public readonly bool CanBeEmpty; - public ModuleAttributeList(ModuleAttributeMetadata metadata, IEnumerable defaultValues, Type type, bool canBeEmpty) - : base(metadata) + public ModuleAttributeList(ModuleAttributeMetadata metadata, IEnumerable defaultValues, Type type, bool canBeEmpty, Func? dependsOn) + : base(metadata, dependsOn) { AttributeList = new BindableList>(); this.defaultValues = defaultValues; @@ -92,6 +96,8 @@ public override void SetDefault() public override bool IsDefault() => AttributeList.Count == defaultValues.Count() && !AttributeList.Where((t, i) => !t.Value.Equals(defaultValues.ElementAt(i))).Any(); + public List GetValueList() => AttributeList.Select(attribute => (T)attribute.Value).ToList(); + public void AddAt(int index, Bindable value) { try @@ -103,13 +109,6 @@ public void AddAt(int index, Bindable value) AttributeList.Insert(index, value); } } - - public IEnumerable GetValueList() - { - List list = new(); - AttributeList.ForEach(attribute => list.Add((T)attribute.Value)); - return list; - } } public sealed class ModuleAttributeMetadata diff --git a/VRCOSC.Game/Modules/ModuleManager.cs b/VRCOSC.Game/Modules/ModuleManager.cs index 87779528..ffc5547d 100644 --- a/VRCOSC.Game/Modules/ModuleManager.cs +++ b/VRCOSC.Game/Modules/ModuleManager.cs @@ -15,37 +15,36 @@ using osu.Framework.Logging; using osu.Framework.Platform; using VRCOSC.Game.Config; +using VRCOSC.Game.Modules.Modules.ChatBoxText; using VRCOSC.Game.Modules.Modules.Clock; using VRCOSC.Game.Modules.Modules.Discord; using VRCOSC.Game.Modules.Modules.HardwareStats; using VRCOSC.Game.Modules.Modules.Heartrate.HypeRate; using VRCOSC.Game.Modules.Modules.Heartrate.Pulsoid; using VRCOSC.Game.Modules.Modules.Media; -//using VRCOSC.Game.Modules.Modules.OpenVR; +using VRCOSC.Game.Modules.Modules.OpenVR; using VRCOSC.Game.Modules.Modules.Random; -//using VRCOSC.Game.Modules.Modules.SpeechToText; -using VRCOSC.Game.Modules.Util; -using VRCOSC.Game.Util; using VRCOSC.OSC; namespace VRCOSC.Game.Modules; -public sealed partial class ModuleManager : Component +public sealed partial class ModuleManager : Component, IOscListener { private static readonly IReadOnlyList module_types = new[] { typeof(HypeRateModule), typeof(PulsoidModule), + typeof(OpenVRStatisticsModule), + typeof(OpenVRControllerStatisticsModule), + typeof(GestureExtensionsModule), + typeof(MediaModule), + typeof(DiscordModule), typeof(ClockModule), + typeof(ChatBoxTextModule), + typeof(HardwareStatsModule), typeof(RandomBoolModule), typeof(RandomIntModule), - typeof(RandomFloatModule), - typeof(HardwareStatsModule), - //typeof(OpenVRBatteryModule), - //typeof(IndexControllerModule), - typeof(MediaModule), - typeof(DiscordModule), - //typeof(SpeechToTextModule), + typeof(RandomFloatModule) }; private const int vr_chat_process_check_interval_milliseconds = 5000; @@ -58,7 +57,7 @@ public sealed partial class ModuleManager : Component private Bindable autoStartStop = null!; private readonly TerminalLogger terminal = new(nameof(ModuleManager)); private CancellationTokenSource startCancellationTokenSource = null!; - private ChatBox chatBox = null!; + private ChatBoxInterface chatBoxInterface = null!; [Resolved] private VRCOSCConfigManager ConfigManager { get; set; } = null!; @@ -67,16 +66,16 @@ public sealed partial class ModuleManager : Component private BindableBool ModuleRun { get; set; } = null!; [BackgroundDependencyLoader] - private void load(GameHost host, Storage storage, OpenVrInterface openVrInterface) + private void load(GameHost host, Storage storage, OpenVRInterface openVrInterface) { - chatBox = new ChatBox(OscClient); + chatBoxInterface = new ChatBoxInterface(OscClient, ConfigManager.GetBindable(VRCOSCSetting.ChatBoxTimeSpan)); autoStartStop = ConfigManager.GetBindable(VRCOSCSetting.AutoStartStop); var moduleStorage = storage.GetStorageForDirectory("modules"); module_types.ForEach(type => { var module = (Module)Activator.CreateInstance(type)!; - module.Initialise(host, moduleStorage, OscClient, chatBox, openVrInterface); + module.Initialise(host, moduleStorage, OscClient, chatBoxInterface, openVrInterface); Modules.Add(module); }); } @@ -139,6 +138,8 @@ private async Task start() await Task.Delay(250, startCancellationTokenSource.Token); + OscClient.RegisterListener(this); + enableOsc(); if (Modules.All(module => !module.Enabled.Value)) @@ -147,7 +148,9 @@ private async Task start() return; } - chatBox.Init(); + chatBoxInterface.Init(); + + sendInitialValues(); foreach (var module in Modules) { @@ -182,7 +185,7 @@ private async Task stop() { State.Value = ManagerState.Stopping; - startCancellationTokenSource?.Cancel(); + startCancellationTokenSource.Cancel(); await OscClient.DisableReceive(); @@ -191,12 +194,35 @@ private async Task stop() await module.stop(); } - await chatBox.Shutdown(); + await chatBoxInterface.Shutdown(); + + OscClient.DeRegisterListener(this); OscClient.DisableSend(); State.Value = ManagerState.Stopped; } + + private void sendInitialValues() + { + OscClient.SendValue("/avatar/parameters/VRCOSC/Controls/ChatBox", chatBoxInterface.SendEnabled); + } + + void IOscListener.OnDataSent(OscData data) { } + + void IOscListener.OnDataReceived(OscData data) + { + switch (data.Address) + { + case "/avatar/parameters/VRCOSC/Controls/ChatBox": + chatBoxInterface.SetSending((bool)data.Values[0]); + break; + + case "/avatar/change": + sendInitialValues(); + break; + } + } } public enum ManagerState diff --git a/VRCOSC.Game/Modules/ModuleType.cs b/VRCOSC.Game/Modules/ModuleType.cs deleted file mode 100644 index 3c428e4d..00000000 --- a/VRCOSC.Game/Modules/ModuleType.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. -// See the LICENSE file in the repository root for full license text. - -namespace VRCOSC.Game.Modules; - -public enum ModuleType -{ - General = 0, - Health = 1, - Integrations = 2, - Accessibility = 3 -} diff --git a/VRCOSC.Game/Modules/Modules/ChatBoxModule.cs b/VRCOSC.Game/Modules/Modules/ChatBoxModule.cs index f91f3f48..1ca54857 100644 --- a/VRCOSC.Game/Modules/Modules/ChatBoxModule.cs +++ b/VRCOSC.Game/Modules/Modules/ChatBoxModule.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using VRCOSC.Game.Modules.Util; namespace VRCOSC.Game.Modules.Modules; @@ -23,15 +22,25 @@ protected override void CreateAttributes() { var chatboxFormat = ChatBoxFormatValues.Aggregate("How should details about this module be displayed in the ChatBox? Available values: ", (current, value) => current + $"{value}, ").Trim().TrimEnd(','); CreateSetting(ChatBoxSetting.ChatBoxDisplay, "ChatBox Display", "If details about this module should be displayed in the ChatBox", DefaultChatBoxDisplay); - CreateSetting(ChatBoxSetting.ChatBoxFormat, "ChatBox Format", chatboxFormat, DefaultChatBoxFormat); - CreateSetting(ChatBoxSetting.ChatBoxMode, "ChatBox Mode", "Should this module display every X seconds or always show?", ChatBoxMode.Always); - CreateSetting(ChatBoxSetting.ChatBoxTimer, "ChatBox Display Timer", $"How long should this module wait between showing details in the ChatBox? (sec)\nRequires ChatBox Mode to be {ChatBoxMode.Timed}", 60); - CreateSetting(ChatBoxSetting.ChatBoxLength, "ChatBox Display Length", $"How long should this module's details be shown in the ChatBox (sec)\nRequires ChatBox Mode to be {ChatBoxMode.Timed}", 5); + + if (ChatBoxFormatValues.Any()) + { + CreateSetting(ChatBoxSetting.ChatBoxFormat, "ChatBox Format", chatboxFormat, DefaultChatBoxFormat, + () => GetSetting(ChatBoxSetting.ChatBoxDisplay)); + } + + CreateSetting(ChatBoxSetting.ChatBoxMode, "ChatBox Mode", "Should this module display every X seconds or always show?", ChatBoxMode.Always, + () => GetSetting(ChatBoxSetting.ChatBoxDisplay)); + CreateSetting(ChatBoxSetting.ChatBoxTimer, "ChatBox Display Timer", $"How long should this module wait between showing details in the ChatBox? (sec)\nRequires ChatBox Mode to be {ChatBoxMode.Timed}", 60, + () => (GetSetting(ChatBoxSetting.ChatBoxDisplay) && GetSetting(ChatBoxSetting.ChatBoxMode) == ChatBoxMode.Timed)); + CreateSetting(ChatBoxSetting.ChatBoxLength, "ChatBox Display Length", $"How long should this module's details be shown in the ChatBox (sec)\nRequires ChatBox Mode to be {ChatBoxMode.Timed}", 5, + () => (GetSetting(ChatBoxSetting.ChatBoxDisplay) && GetSetting(ChatBoxSetting.ChatBoxMode) == ChatBoxMode.Timed)); } protected override Task OnStart(CancellationToken cancellationToken) { - nextSendTask = new TimedTask(trySend, 10f).Start(); + nextSendTask = new TimedTask(trySend, 10f); + _ = nextSendTask.Start(); nextSendTime = DateTimeOffset.Now; return Task.CompletedTask; } @@ -83,7 +92,7 @@ protected enum ChatBoxSetting ChatBoxLength } - protected enum ChatBoxMode + private enum ChatBoxMode { Timed, Always diff --git a/VRCOSC.Game/Modules/Modules/ChatBoxText/ChatBoxTextModule.cs b/VRCOSC.Game/Modules/Modules/ChatBoxText/ChatBoxTextModule.cs new file mode 100644 index 00000000..f04607a5 --- /dev/null +++ b/VRCOSC.Game/Modules/Modules/ChatBoxText/ChatBoxTextModule.cs @@ -0,0 +1,28 @@ +// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. +// See the LICENSE file in the repository root for full license text. + +namespace VRCOSC.Game.Modules.Modules.ChatBoxText; + +public class ChatBoxTextModule : ChatBoxModule +{ + public override string Title => "ChatBox Text"; + public override string Description => "Display custom text in the ChatBox"; + public override string Author => "VolcanicArts"; + public override ModuleType Type => ModuleType.General; + protected override int ChatBoxPriority => 2; + + protected override bool DefaultChatBoxDisplay => true; + + protected override string? GetChatBoxText() => GetSetting(ChatBoxTextSetting.ChatBoxText); + + protected override void CreateAttributes() + { + CreateSetting(ChatBoxTextSetting.ChatBoxText, "ChatBox Text", "What text should be displayed in the ChatBox?", string.Empty); + base.CreateAttributes(); + } + + private enum ChatBoxTextSetting + { + ChatBoxText + } +} diff --git a/VRCOSC.Game/Modules/Modules/Clock/ClockModule.cs b/VRCOSC.Game/Modules/Modules/Clock/ClockModule.cs index bc18f794..9bddb4e1 100644 --- a/VRCOSC.Game/Modules/Modules/Clock/ClockModule.cs +++ b/VRCOSC.Game/Modules/Modules/Clock/ClockModule.cs @@ -13,11 +13,11 @@ public sealed class ClockModule : ChatBoxModule public override string Description => "Sends your local time as hours, minutes, and seconds"; public override string Author => "VolcanicArts"; public override string Prefab => "VRCOSC-Watch"; - public override ModuleType ModuleType => ModuleType.General; - protected override int DeltaUpdate => GetSetting(ClockSetting.SmoothSecond) ? vrc_osc_delta_update : 1000; + public override ModuleType Type => ModuleType.General; + protected override int DeltaUpdate => GetSetting(ClockSetting.SmoothSecond) ? Constants.OSC_UPDATE_DELTA : 1000; protected override bool DefaultChatBoxDisplay => true; - protected override string DefaultChatBoxFormat => "Local Time %h%:%m%%period%"; + protected override string DefaultChatBoxFormat => "Local Time %h%:%m%%period%"; protected override IEnumerable ChatBoxFormatValues => new[] { "%h%", "%m%", "%s%", "%period%" }; private DateTime time; @@ -41,7 +41,7 @@ protected override void CreateAttributes() { var chatBoxTime = timezoneToTime(GetSetting(ClockSetting.Timezone)); var textHour = GetSetting(ClockSetting.Mode) == ClockMode.Twelve ? (chatBoxTime.Hour % 12).ToString("00") : chatBoxTime.Hour.ToString("00"); - return GetSetting(ClockSetting.ChatBoxFormat) + return GetSetting(ChatBoxSetting.ChatBoxFormat) .Replace("%h%", textHour) .Replace("%m%", chatBoxTime.Minute.ToString("00")) .Replace("%s%", chatBoxTime.Second.ToString("00")) @@ -57,7 +57,7 @@ protected override Task OnUpdate() var seconds = GetSetting(ClockSetting.SmoothSecond) ? getSmoothedSeconds(time) : time.Second; var normalisationComponent = GetSetting(ClockSetting.Mode) == ClockMode.Twelve ? 12f : 24f; - var hourNormalised = (hours % normalisationComponent) / normalisationComponent; + var hourNormalised = hours % normalisationComponent / normalisationComponent; var minuteNormalised = minutes / 60f; var secondNormalised = seconds / 60f; @@ -91,7 +91,7 @@ private enum ClockParameter { Hours, Minutes, - Seconds, + Seconds } private enum ClockSetting @@ -100,9 +100,7 @@ private enum ClockSetting SmoothSecond, SmoothMinute, SmoothHour, - Mode, - UseChatBox, - ChatBoxFormat + Mode } private enum ClockMode diff --git a/VRCOSC.Game/Modules/Modules/Discord/DiscordModule.cs b/VRCOSC.Game/Modules/Modules/Discord/DiscordModule.cs index 015fe1d6..e75ec1a6 100644 --- a/VRCOSC.Game/Modules/Modules/Discord/DiscordModule.cs +++ b/VRCOSC.Game/Modules/Modules/Discord/DiscordModule.cs @@ -2,7 +2,6 @@ // See the LICENSE file in the repository root for full license text. using System; -using VRCOSC.Game.Modules.Util; namespace VRCOSC.Game.Modules.Modules.Discord; @@ -12,21 +11,21 @@ public sealed class DiscordModule : IntegrationModule public override string Description => "Integration with the Discord desktop app"; public override string Author => "VolcanicArts"; public override string Prefab => "VRCOSC-Discord"; - public override ModuleType ModuleType => ModuleType.Integrations; + public override ModuleType Type => ModuleType.Integrations; protected override string TargetProcess => "discord"; protected override void CreateAttributes() { - CreateParameter(DiscordParameter.Mic, ParameterMode.Read, "VRCOSC/Discord/Mic", "Becomes true to toggle the mic", ActionMenu.Button); - CreateParameter(DiscordParameter.Deafen, ParameterMode.Read, "VRCOSC/Discord/Deafen", "Becomes true to toggle deafen", ActionMenu.Button); + CreateParameter(DiscordParameter.Mic, ParameterMode.Read, "VRCOSC/Discord/Mic", "Becomes true to toggle the mic"); + CreateParameter(DiscordParameter.Deafen, ParameterMode.Read, "VRCOSC/Discord/Deafen", "Becomes true to toggle deafen"); RegisterKeyCombination(DiscordParameter.Mic, WindowsVKey.VK_LCONTROL, WindowsVKey.VK_LSHIFT, WindowsVKey.VK_M); RegisterKeyCombination(DiscordParameter.Deafen, WindowsVKey.VK_LCONTROL, WindowsVKey.VK_LSHIFT, WindowsVKey.VK_D); } - protected override void OnButtonPressed(Enum key) + protected override void OnBoolParameterReceived(Enum key, bool value) { - ExecuteKeyCombination(key); + if (value) ExecuteKeyCombination(key); } private enum DiscordParameter diff --git a/VRCOSC.Game/Modules/Modules/HardwareStats/HardwareStatsModule.cs b/VRCOSC.Game/Modules/Modules/HardwareStats/HardwareStatsModule.cs index 1cd47087..f89a908e 100644 --- a/VRCOSC.Game/Modules/Modules/HardwareStats/HardwareStatsModule.cs +++ b/VRCOSC.Game/Modules/Modules/HardwareStats/HardwareStatsModule.cs @@ -12,7 +12,7 @@ public sealed class HardwareStatsModule : ChatBoxModule public override string Title => "Hardware Stats"; public override string Description => "Sends hardware stats and displays them in the ChatBox"; public override string Author => "VolcanicArts"; - public override ModuleType ModuleType => ModuleType.General; + public override ModuleType Type => ModuleType.General; protected override int DeltaUpdate => 2000; protected override bool DefaultChatBoxDisplay => true; @@ -36,19 +36,19 @@ protected override void CreateAttributes() protected override string? GetChatBoxText() { - if (hardwareStatsProvider is null || !hardwareStatsProvider.CanAcceptQueries) return null; + if (!(hardwareStatsProvider?.CanAcceptQueries ?? false)) return null; - hardwareStatsProvider!.Update(); + hardwareStatsProvider?.Update(); return GetSetting(ChatBoxSetting.ChatBoxFormat) - .Replace("$cpuusage$", (hardwareStatsProvider!.CpuUsage).ToString("0.00")) - .Replace("$gpuusage$", (hardwareStatsProvider!.GpuUsage).ToString("0.00")) - .Replace("$ramusage$", (hardwareStatsProvider!.RamUsage).ToString("0.00")) - .Replace("$cputemp$", (hardwareStatsProvider!.CpuTemp).ToString()) - .Replace("$gputemp$", (hardwareStatsProvider!.GpuTemp).ToString()) - .Replace("$ramtotal$", (hardwareStatsProvider!.RamTotal).ToString("0.0")) - .Replace("$ramused$", (hardwareStatsProvider!.RamUsed).ToString("0.0")) - .Replace("$ramavailable$", (hardwareStatsProvider!.RamAvailable).ToString("0.0")); + .Replace("$cpuusage$", hardwareStatsProvider?.CpuUsage.ToString("0.00") ?? "0.00") + .Replace("$gpuusage$", hardwareStatsProvider?.GpuUsage.ToString("0.00") ?? "0.00") + .Replace("$ramusage$", hardwareStatsProvider?.RamUsage.ToString("0.00") ?? "0.00") + .Replace("$cputemp$", hardwareStatsProvider?.CpuTemp.ToString() ?? "0") + .Replace("$gputemp$", hardwareStatsProvider?.GpuTemp.ToString() ?? "0") + .Replace("$ramtotal$", hardwareStatsProvider?.RamTotal.ToString("0.0") ?? "0.0") + .Replace("$ramused$", hardwareStatsProvider?.RamUsed.ToString("0.0") ?? "0.0") + .Replace("$ramavailable$", hardwareStatsProvider?.RamAvailable.ToString("0.0") ?? "0.0"); } protected override async Task OnStart(CancellationToken cancellationToken) diff --git a/VRCOSC.Game/Modules/Modules/Heartrate/HeartRateModule.cs b/VRCOSC.Game/Modules/Modules/Heartrate/HeartRateModule.cs index 6568ffca..21cc2705 100644 --- a/VRCOSC.Game/Modules/Modules/Heartrate/HeartRateModule.cs +++ b/VRCOSC.Game/Modules/Modules/Heartrate/HeartRateModule.cs @@ -15,7 +15,7 @@ public abstract class HeartRateModule : ChatBoxModule public override string Author => "VolcanicArts"; public override string Prefab => "VRCOSC-Heartrate"; - public override ModuleType ModuleType => ModuleType.Health; + public override ModuleType Type => ModuleType.Health; protected override int DeltaUpdate => 2000; protected override int ChatBoxPriority => 1; @@ -28,7 +28,7 @@ public abstract class HeartRateModule : ChatBoxModule private DateTimeOffset lastHeartrateTime; private int connectionCount; - protected bool IsReceiving => lastHeartrateTime + heartrate_timeout >= DateTimeOffset.Now; + private bool isReceiving => lastHeartrateTime + heartrate_timeout >= DateTimeOffset.Now; protected abstract HeartRateProvider CreateHeartRateProvider(); @@ -67,13 +67,16 @@ private void attemptConnection() heartRateProvider = CreateHeartRateProvider(); heartRateProvider.OnHeartRateUpdate += HandleHeartRateUpdate; heartRateProvider.OnConnected += () => connectionCount = 0; - heartRateProvider.OnDisconnected += async () => + heartRateProvider.OnDisconnected += () => { - if (IsStopping || HasStopped) return; - - SendParameter(HeartrateParameter.Enabled, false); - await Task.Delay(2000); - attemptConnection(); + Task.Run(async () => + { + if (IsStopping || HasStopped) return; + + SendParameter(HeartrateParameter.Enabled, false); + await Task.Delay(2000); + attemptConnection(); + }); }; heartRateProvider.Initialise(); heartRateProvider.Connect(); @@ -90,7 +93,7 @@ protected override async Task OnStop() protected override Task OnUpdate() { - if (!IsReceiving) + if (!isReceiving) { SendParameter(HeartrateParameter.Enabled, false); } diff --git a/VRCOSC.Game/Modules/Modules/Heartrate/HeartRateProvider.cs b/VRCOSC.Game/Modules/Modules/Heartrate/HeartRateProvider.cs index 03e4055f..97da74c2 100644 --- a/VRCOSC.Game/Modules/Modules/Heartrate/HeartRateProvider.cs +++ b/VRCOSC.Game/Modules/Modules/Heartrate/HeartRateProvider.cs @@ -3,8 +3,7 @@ using System; using System.Threading.Tasks; -using VRCOSC.Game.Modules.Util; -using VRCOSC.Game.Modules.Websocket; +using Newtonsoft.Json; namespace VRCOSC.Game.Modules.Modules.Heartrate; @@ -14,7 +13,7 @@ public abstract class HeartRateProvider protected virtual int WebSocketHeartBeat => int.MaxValue; protected virtual bool SendWsHeartBeat => true; - private JsonWebSocket? webSocket; + private BaseWebSocket? webSocket; private TimedTask? wsHeartBeatTask; public Action? OnConnected; @@ -24,7 +23,7 @@ public abstract class HeartRateProvider public void Initialise() { - webSocket = new JsonWebSocket(WebSocketUrl); + webSocket = new BaseWebSocket(WebSocketUrl); webSocket.OnWsConnected += () => { HandleWsConnected(); @@ -51,7 +50,7 @@ public void Connect() if (webSocket is null || wsHeartBeatTask is null) throw new InvalidOperationException("Please call Initialise first"); webSocket.Connect(); - if (SendWsHeartBeat) wsHeartBeatTask.Start(); + if (SendWsHeartBeat) _ = wsHeartBeatTask.Start(); } public async Task Disconnect() @@ -66,7 +65,7 @@ protected void SendData(object data) { if (webSocket is null) throw new InvalidOperationException("Please call Initialise first"); - webSocket.SendAsJson(data); + webSocket.Send(JsonConvert.SerializeObject(data)); } protected virtual void HandleWsConnected() { } diff --git a/VRCOSC.Game/Modules/Modules/Heartrate/HypeRate/HypeRateProvider.cs b/VRCOSC.Game/Modules/Modules/Heartrate/HypeRate/HypeRateProvider.cs index dd81a00f..8bdb2b58 100644 --- a/VRCOSC.Game/Modules/Modules/Heartrate/HypeRate/HypeRateProvider.cs +++ b/VRCOSC.Game/Modules/Modules/Heartrate/HypeRate/HypeRateProvider.cs @@ -3,7 +3,6 @@ using Newtonsoft.Json; using VRCOSC.Game.Modules.Modules.Heartrate.HypeRate.Models; -using VRCOSC.Game.Util; namespace VRCOSC.Game.Modules.Modules.Heartrate.HypeRate; diff --git a/VRCOSC.Game/Modules/Modules/Heartrate/Pulsoid/PulsoidProvider.cs b/VRCOSC.Game/Modules/Modules/Heartrate/Pulsoid/PulsoidProvider.cs index 17d9e272..859895bd 100644 --- a/VRCOSC.Game/Modules/Modules/Heartrate/Pulsoid/PulsoidProvider.cs +++ b/VRCOSC.Game/Modules/Modules/Heartrate/Pulsoid/PulsoidProvider.cs @@ -3,7 +3,6 @@ using Newtonsoft.Json; using VRCOSC.Game.Modules.Modules.Heartrate.Pulsoid.Models; -using VRCOSC.Game.Util; namespace VRCOSC.Game.Modules.Modules.Heartrate.Pulsoid; diff --git a/VRCOSC.Game/Modules/IntegrationModule.cs b/VRCOSC.Game/Modules/Modules/IntegrationModule.cs similarity index 85% rename from VRCOSC.Game/Modules/IntegrationModule.cs rename to VRCOSC.Game/Modules/Modules/IntegrationModule.cs index a5ee4b50..3437a7ba 100644 --- a/VRCOSC.Game/Modules/IntegrationModule.cs +++ b/VRCOSC.Game/Modules/Modules/IntegrationModule.cs @@ -1,4 +1,4 @@ -// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. +// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. // See the LICENSE file in the repository root for full license text. using System; @@ -8,18 +8,17 @@ using System.Linq; using System.Threading.Tasks; using osu.Framework.Logging; -using VRCOSC.Game.Modules.Util; -using VRCOSC.Game.Util; +using VRCOSC.Game.Processes; -namespace VRCOSC.Game.Modules; +namespace VRCOSC.Game.Modules.Modules; public abstract class IntegrationModule : Module { private const int delay = 10; protected virtual string TargetProcess => string.Empty; - protected virtual string ReturnProcess => "vrchat"; - protected virtual string TargetExe => $@"{TargetProcess}.exe"; + protected string ReturnProcess => "vrchat"; + protected string TargetExe => $@"{TargetProcess}.exe"; private readonly Dictionary keyCombinations = new(); @@ -109,13 +108,13 @@ private static async Task focusProcess(Process process) { if (process.MainWindowHandle == IntPtr.Zero) { - ProcessExtensions.ShowMainWindow(process, ShowWindowEnum.Restore); + process.ShowMainWindow(ShowWindowEnum.Restore); await Task.Delay(delay); } - ProcessExtensions.ShowMainWindow(process, ShowWindowEnum.ShowDefault); + process.ShowMainWindow(ShowWindowEnum.ShowDefault); await Task.Delay(delay); - ProcessExtensions.SetMainWindowForeground(process); + process.SetMainWindowForeground(); } private async Task performKeyCombination(Enum lookup) diff --git a/VRCOSC.Game/Modules/Modules/Media/MediaModule.cs b/VRCOSC.Game/Modules/Modules/Media/MediaModule.cs index 7dffc6af..78e780bf 100644 --- a/VRCOSC.Game/Modules/Modules/Media/MediaModule.cs +++ b/VRCOSC.Game/Modules/Modules/Media/MediaModule.cs @@ -18,7 +18,7 @@ public sealed class MediaModule : ChatBoxModule public override string Author => "VolcanicArts"; public override string Prefab => "VRCOSC-Media"; protected override int DeltaUpdate => 2000; - public override ModuleType ModuleType => ModuleType.Integrations; + public override ModuleType Type => ModuleType.Integrations; protected override int ChatBoxPriority => 2; protected override bool DefaultChatBoxDisplay => true; @@ -30,18 +30,19 @@ public sealed class MediaModule : ChatBoxModule protected override void CreateAttributes() { CreateSetting(MediaSetting.PausedBehaviour, "Paused Behaviour", "When the media is paused, should the ChatBox be empty or display that it's paused?", MediaPausedBehaviour.Empty); - CreateSetting(MediaSetting.PausedText, "Paused Text", $"The text to display when media is paused. Only applicable when Paused Behaviour is set to {MediaPausedBehaviour.Display}", "[Paused]"); + CreateSetting(MediaSetting.PausedText, "Paused Text", $"The text to display when media is paused. Only applicable when Paused Behaviour is set to {MediaPausedBehaviour.Display}", "[Paused]", + () => GetSetting(MediaSetting.PausedBehaviour) == MediaPausedBehaviour.Display); CreateSetting(MediaSetting.StartList, "Start List", "A list of exe locations to start with this module. This is handy for starting media apps on module start. For example, Spotify", new[] { @$"C:\Users\{Environment.UserName}\AppData\Roaming\Spotify\spotify.exe" }, true); base.CreateAttributes(); CreateParameter(MediaParameter.Play, ParameterMode.ReadWrite, "VRCOSC/Media/Play", "True for playing. False for paused"); - CreateParameter(MediaParameter.Volume, ParameterMode.ReadWrite, "VRCOSC/Media/Volume", "The volume of the process that is controlling the media", ActionMenu.Radial); + CreateParameter(MediaParameter.Volume, ParameterMode.ReadWrite, "VRCOSC/Media/Volume", "The volume of the process that is controlling the media"); CreateParameter(MediaParameter.Muted, ParameterMode.ReadWrite, "VRCOSC/Media/Muted", "True to mute. False to unmute"); CreateParameter(MediaParameter.Repeat, ParameterMode.ReadWrite, "VRCOSC/Media/Repeat", "0 for disabled. 1 for single. 2 for list"); CreateParameter(MediaParameter.Shuffle, ParameterMode.ReadWrite, "VRCOSC/Media/Shuffle", "True for enabled. False for disabled"); - CreateParameter(MediaParameter.Next, ParameterMode.Read, "VRCOSC/Media/Next", "Becoming true causes the next track to play", ActionMenu.Button); - CreateParameter(MediaParameter.Previous, ParameterMode.Read, "VRCOSC/Media/Previous", "Becoming true causes the previous track to play", ActionMenu.Button); + CreateParameter(MediaParameter.Next, ParameterMode.Read, "VRCOSC/Media/Next", "Becoming true causes the next track to play"); + CreateParameter(MediaParameter.Previous, ParameterMode.Read, "VRCOSC/Media/Previous", "Becoming true causes the previous track to play"); } protected override string? GetChatBoxText() @@ -97,7 +98,7 @@ protected override Task OnUpdate() return Task.CompletedTask; } - protected override void OnRadialPuppetChange(Enum key, float value) + protected override void OnFloatParameterReceived(Enum key, float value) { switch (key) { @@ -111,12 +112,12 @@ protected override void OnBoolParameterReceived(Enum key, bool value) { switch (key) { - case MediaParameter.Play: - if (value) - mediaProvider.Controller?.TryPlayAsync(); - else - mediaProvider.Controller?.TryPauseAsync(); + case MediaParameter.Play when value: + mediaProvider.Controller?.TryPlayAsync(); + break; + case MediaParameter.Play when !value: + mediaProvider.Controller?.TryPauseAsync(); break; case MediaParameter.Shuffle: @@ -126,6 +127,14 @@ protected override void OnBoolParameterReceived(Enum key, bool value) case MediaParameter.Muted: mediaProvider.SetMuted(value); break; + + case MediaParameter.Next when value: + mediaProvider.Controller?.TrySkipNextAsync(); + break; + + case MediaParameter.Previous when value: + mediaProvider.Controller?.TrySkipPreviousAsync(); + break; } } @@ -139,28 +148,11 @@ protected override void OnIntParameterReceived(Enum key, int value) } } - protected override void OnButtonPressed(Enum key) - { - switch (key) - { - case MediaParameter.Next: - mediaProvider.Controller?.TrySkipNextAsync(); - break; - - case MediaParameter.Previous: - mediaProvider.Controller?.TrySkipPreviousAsync(); - break; - - default: - throw new ArgumentOutOfRangeException(nameof(key), key, null); - } - } - private async void OnMediaSessionOpened() { // We have to wait a little bit to allow the media app that just opened to take control await Task.Delay(500); - await mediaProvider.ForceUpdate(); + mediaProvider.ForceUpdate(); } private void OnMediaUpdate() @@ -202,6 +194,6 @@ private enum MediaParameter Shuffle, Repeat, Volume, - Muted, + Muted } } diff --git a/VRCOSC.Game/Modules/Modules/Media/MediaProvider.cs b/VRCOSC.Game/Modules/Modules/Media/MediaProvider.cs index d99660c4..5c321006 100644 --- a/VRCOSC.Game/Modules/Modules/Media/MediaProvider.cs +++ b/VRCOSC.Game/Modules/Modules/Media/MediaProvider.cs @@ -7,7 +7,8 @@ using System.Threading.Tasks; using Windows.Media; using Windows.Media.Control; -using VRCOSC.Game.Util; +using VRCOSC.Game.Processes; +using WindowsMediaController; namespace VRCOSC.Game.Modules.Modules.Media; @@ -32,9 +33,10 @@ public async Task StartMediaHook() mediaManager = new MediaManager(); mediaManager.OnAnySessionOpened += MediaManager_OnAnySessionOpened; mediaManager.OnAnySessionClosed += MediaManager_OnAnySessionClosed; + mediaManager.OnFocusedSessionChanged += MediaManager_OnFocusedSessionChanged; mediaManager.OnAnyPlaybackStateChanged += MediaManager_OnAnyPlaybackStateChanged; mediaManager.OnAnyMediaPropertyChanged += MediaManager_OnAnyMediaPropertyChanged; - await mediaManager.Start(); + await mediaManager.StartAsync(); } public void StopMediaHook() @@ -45,32 +47,26 @@ public void StopMediaHook() trackedProcess = null; } + public void ForceUpdate() + { + Controller?.TryPlayAsync(); + } + private void MediaManager_OnAnySessionOpened(MediaManager.MediaSession sender) { updateTrackedProcess(sender.Id); OnMediaSessionOpened?.Invoke(); } - public async Task ForceUpdate() + private void MediaManager_OnAnySessionClosed(MediaManager.MediaSession sender) { - if (Controller?.GetPlaybackInfo().PlaybackStatus == GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing) - { - await Controller.TryPauseAsync(); - await Task.Delay(50); - await Controller.TryPlayAsync(); - } - else if (Controller?.GetPlaybackInfo().PlaybackStatus == GlobalSystemMediaTransportControlsSessionPlaybackStatus.Paused) - { - await Controller.TryPlayAsync(); - await Task.Delay(50); - await Controller.TryPauseAsync(); - } + updateTrackedProcess(mediaManager?.CurrentMediaSessions.FirstOrDefault().Value.Id ?? string.Empty); + ForceUpdate(); } - private void MediaManager_OnAnySessionClosed(MediaManager.MediaSession sender) + private void MediaManager_OnFocusedSessionChanged(MediaManager.MediaSession sender) { updateTrackedProcess(mediaManager?.CurrentMediaSessions.FirstOrDefault().Value.Id ?? string.Empty); - _ = ForceUpdate(); } private void MediaManager_OnAnyPlaybackStateChanged(MediaManager.MediaSession sender, GlobalSystemMediaTransportControlsSessionPlaybackInfo args) diff --git a/VRCOSC.Game/Modules/Modules/OpenVR/GestureExtensionsModule.cs b/VRCOSC.Game/Modules/Modules/OpenVR/GestureExtensionsModule.cs new file mode 100644 index 00000000..ffdd153c --- /dev/null +++ b/VRCOSC.Game/Modules/Modules/OpenVR/GestureExtensionsModule.cs @@ -0,0 +1,103 @@ +// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. +// See the LICENSE file in the repository root for full license text. + +using System.Threading; +using System.Threading.Tasks; + +namespace VRCOSC.Game.Modules.Modules.OpenVR; + +public class GestureExtensionsModule : Module +{ + public override string Title => "Gesture Extensions"; + public override string Description => "Detect a range of custom gestures from Index controllers"; + public override string Author => "VolcanicArts"; + public override ModuleType Type => ModuleType.OpenVR; + protected override int DeltaUpdate => Constants.OSC_UPDATE_DELTA; + + private float lowerThreshold; + private float upperThreshold; + + protected override void CreateAttributes() + { + CreateSetting(GestureExtensionsSetting.LowerThreshold, "Lower Threshold", "How far down a finger should be until it's not considered up", 0.5f, 0, 1); + CreateSetting(GestureExtensionsSetting.UpperThreshold, "Upper Threshold", "How far down a finger should be before it's considered down", 0.5f, 0, 1); + + CreateParameter(GestureExtensionsParameter.GestureLeft, ParameterMode.Write, "VRCOSC/Gestures/Left", "Custom left hand gesture value"); + CreateParameter(GestureExtensionsParameter.GestureRight, ParameterMode.Write, "VRCOSC/Gestures/Right", "Custom right hand gesture value"); + } + + protected override Task OnStart(CancellationToken cancellationToken) + { + lowerThreshold = GetSetting(GestureExtensionsSetting.LowerThreshold); + upperThreshold = GetSetting(GestureExtensionsSetting.UpperThreshold); + + return Task.CompletedTask; + } + + protected override Task OnUpdate() + { + if (!OpenVrInterface.HasInitialised) return Task.CompletedTask; + + if (OpenVrInterface.IsLeftControllerConnected()) SendParameter(GestureExtensionsParameter.GestureLeft, (int)getLeftControllerGesture()); + if (OpenVrInterface.IsRightControllerConnected()) SendParameter(GestureExtensionsParameter.GestureRight, (int)getRightControllerGesture()); + + return Task.CompletedTask; + } + + private GestureNames getLeftControllerGesture() => getControllerGesture(OpenVrInterface.LeftController); + private GestureNames getRightControllerGesture() => getControllerGesture(OpenVrInterface.RightController); + + private GestureNames getControllerGesture(ControllerData controllerData) + { + if (isGestureDoubleGun(controllerData)) return GestureNames.DoubleGun; + if (isGestureMiddleFinger(controllerData)) return GestureNames.MiddleFinger; + if (isGesturePinkyFinger(controllerData)) return GestureNames.PinkyFinger; + + return GestureNames.None; + } + + private bool isGestureDoubleGun(ControllerData controllerData) + { + return controllerData.IndexFinger < lowerThreshold + && controllerData.MiddleFinger < lowerThreshold + && controllerData.RingFinger > upperThreshold + && controllerData.PinkyFinger > upperThreshold + && !controllerData.ThumbDown; + } + + private bool isGestureMiddleFinger(ControllerData controllerData) + { + return controllerData.IndexFinger > upperThreshold + && controllerData.MiddleFinger < lowerThreshold + && controllerData.RingFinger > upperThreshold + && controllerData.PinkyFinger > upperThreshold; + } + + private bool isGesturePinkyFinger(ControllerData controllerData) + { + return controllerData.IndexFinger > upperThreshold + && controllerData.MiddleFinger > upperThreshold + && controllerData.RingFinger > upperThreshold + && controllerData.PinkyFinger < lowerThreshold; + } + + private enum GestureExtensionsSetting + { + LowerThreshold, + UpperThreshold + } + + private enum GestureNames + { + None, + DoubleGun, + MiddleFinger, + PinkyFinger + } + + private enum GestureExtensionsParameter + { + GestureLeft, + GestureRight + } +} diff --git a/VRCOSC.Game/Modules/Modules/OpenVR/IndexControllerModule.cs b/VRCOSC.Game/Modules/Modules/OpenVR/IndexControllerModule.cs deleted file mode 100644 index 977d4647..00000000 --- a/VRCOSC.Game/Modules/Modules/OpenVR/IndexControllerModule.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. -// See the LICENSE file in the repository root for full license text. - -using System.Threading.Tasks; - -namespace VRCOSC.Game.Modules.Modules.OpenVR; - -public class IndexControllerModule : Module -{ - public override string Title => "Index Controller Stats"; - public override string Description => "Gets finger and thumb values and positions from Index controllers"; - public override string Author => "VolcanicArts"; - public override ModuleType ModuleType => ModuleType.General; - protected override int DeltaUpdate => 50; - - protected override Task OnUpdate() - { - //OpenVrInterface.GetCurrentButtonPressRightController(); - - return Task.CompletedTask; - } -} diff --git a/VRCOSC.Game/Modules/Modules/OpenVR/OpenVRBatteryModule.cs b/VRCOSC.Game/Modules/Modules/OpenVR/OpenVRBatteryModule.cs deleted file mode 100644 index 4e21a6cb..00000000 --- a/VRCOSC.Game/Modules/Modules/OpenVR/OpenVRBatteryModule.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. -// See the LICENSE file in the repository root for full license text. - -using System.Linq; -using System.Threading.Tasks; - -namespace VRCOSC.Game.Modules.Modules.OpenVR; - -public class OpenVRBatteryModule : Module -{ - public override string Title => "OpenVR Battery"; - public override string Description => "Gets battery stats from your OpenVR (SteamVR) session"; - public override string Author => "VolcanicArts"; - public override ModuleType ModuleType => ModuleType.General; - protected override int DeltaUpdate => 5000; - protected override bool ExecuteUpdateImmediately => false; - - protected override void CreateAttributes() - { - CreateParameter(OpenVRParameter.HMD_Battery, ParameterMode.Write, "VRCOSC/OpenVR/Battery/HMD", "The battery percentage normalised of your headset"); - - CreateParameter(OpenVRParameter.LeftController_Battery, ParameterMode.Write, "VRCOSC/OpenVR/Battery/LeftController", "The battery percentage normalised of your left controller"); - CreateParameter(OpenVRParameter.RightController_Battery, ParameterMode.Write, "VRCOSC/OpenVR/Battery/RightController", "The battery percentage normalised of your right controller"); - - CreateParameter(OpenVRParameter.Tracker1_Battery, ParameterMode.Write, "VRCOSC/OpenVR/Battery/Trackers/1", "The battery percentage normalised of tracker 1"); - CreateParameter(OpenVRParameter.Tracker2_Battery, ParameterMode.Write, "VRCOSC/OpenVR/Battery/Trackers/2", "The battery percentage normalised of tracker 2"); - CreateParameter(OpenVRParameter.Tracker3_Battery, ParameterMode.Write, "VRCOSC/OpenVR/Battery/Trackers/3", "The battery percentage normalised of tracker 3"); - CreateParameter(OpenVRParameter.Tracker4_Battery, ParameterMode.Write, "VRCOSC/OpenVR/Battery/Trackers/4", "The battery percentage normalised of tracker 4"); - CreateParameter(OpenVRParameter.Tracker5_Battery, ParameterMode.Write, "VRCOSC/OpenVR/Battery/Trackers/5", "The battery percentage normalised of tracker 5"); - CreateParameter(OpenVRParameter.Tracker6_Battery, ParameterMode.Write, "VRCOSC/OpenVR/Battery/Trackers/6", "The battery percentage normalised of tracker 6"); - CreateParameter(OpenVRParameter.Tracker7_Battery, ParameterMode.Write, "VRCOSC/OpenVR/Battery/Trackers/7", "The battery percentage normalised of tracker 7"); - CreateParameter(OpenVRParameter.Tracker8_Battery, ParameterMode.Write, "VRCOSC/OpenVR/Battery/Trackers/8", "The battery percentage normalised of tracker 8"); - } - - protected override Task OnUpdate() - { - var battery = OpenVrInterface.GetHMDBatteryPercentage(); - if (battery is not null) SendParameter(OpenVRParameter.HMD_Battery, (float)battery); - - var batteryLeft = OpenVrInterface.GetLeftControllerBatteryPercentage(); - SendParameter(OpenVRParameter.LeftController_Battery, batteryLeft); - - var batteryRight = OpenVrInterface.GetRightControllerBatteryPercentage(); - SendParameter(OpenVRParameter.RightController_Battery, batteryRight); - - var trackerBatteries = OpenVrInterface.GetTrackersBatteryPercentages().ToList(); - - for (int i = 0; i < trackerBatteries.Count; i++) - { - SendParameter(OpenVRParameter.Tracker1_Battery + i, trackerBatteries[i]); - } - - return Task.CompletedTask; - } - - private enum OpenVRParameter - { - HMD_Battery, - LeftController_Battery, - RightController_Battery, - Tracker1_Battery, - Tracker2_Battery, - Tracker3_Battery, - Tracker4_Battery, - Tracker5_Battery, - Tracker6_Battery, - Tracker7_Battery, - Tracker8_Battery - } -} diff --git a/VRCOSC.Game/Modules/Modules/OpenVR/OpenVRControllerStatisticsModule.cs b/VRCOSC.Game/Modules/Modules/OpenVR/OpenVRControllerStatisticsModule.cs new file mode 100644 index 00000000..64baa1d3 --- /dev/null +++ b/VRCOSC.Game/Modules/Modules/OpenVR/OpenVRControllerStatisticsModule.cs @@ -0,0 +1,87 @@ +// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. +// See the LICENSE file in the repository root for full license text. + +using System.Threading.Tasks; + +namespace VRCOSC.Game.Modules.Modules.OpenVR; + +public class OpenVRControllerStatisticsModule : Module +{ + public override string Title => "OpenVR Controller Statistics"; + public override string Description => "Gets controller statistics from your OpenVR (SteamVR) session"; + public override string Author => "VolcanicArts"; + public override ModuleType Type => ModuleType.OpenVR; + protected override int DeltaUpdate => Constants.OSC_UPDATE_DELTA; + + protected override void CreateAttributes() + { + CreateParameter(OpenVRControllerStatisticsParameter.LeftATouch, ParameterMode.Write, "VRCOSC/OpenVR/LeftController/Input/A/Touch", "Whether the left a button is currently touched"); + CreateParameter(OpenVRControllerStatisticsParameter.LeftBTouch, ParameterMode.Write, "VRCOSC/OpenVR/LeftController/Input/B/Touch", "Whether the left b button is currently touched"); + CreateParameter(OpenVRControllerStatisticsParameter.LeftPadTouch, ParameterMode.Write, "VRCOSC/OpenVR/LeftController/Input/Pad/Touch", "Whether the left pad is currently touched"); + CreateParameter(OpenVRControllerStatisticsParameter.LeftStickTouch, ParameterMode.Write, "VRCOSC/OpenVR/LeftController/Input/Stick/Touch", "Whether the left stick is currently touched"); + CreateParameter(OpenVRControllerStatisticsParameter.LeftIndex, ParameterMode.Write, "VRCOSC/OpenVR/LeftController/Input/Finger/Index", "The touch value of your left index finger"); + CreateParameter(OpenVRControllerStatisticsParameter.LeftMiddle, ParameterMode.Write, "VRCOSC/OpenVR/LeftController/Input/Finger/Middle", "The touch value of your left middle finger"); + CreateParameter(OpenVRControllerStatisticsParameter.LeftRing, ParameterMode.Write, "VRCOSC/OpenVR/LeftController/Input/Finger/Ring", "The touch value of your left ring finger"); + CreateParameter(OpenVRControllerStatisticsParameter.LeftPinky, ParameterMode.Write, "VRCOSC/OpenVR/LeftController/Input/Finger/Pinky", "The touch value of your left pinky finger"); + + CreateParameter(OpenVRControllerStatisticsParameter.RightATouch, ParameterMode.Write, "VRCOSC/OpenVR/RightController/Input/A/Touch", "Whether the right a button is currently touched"); + CreateParameter(OpenVRControllerStatisticsParameter.RightBTouch, ParameterMode.Write, "VRCOSC/OpenVR/RightController/Input/B/Touch", "Whether the right b button is currently touched"); + CreateParameter(OpenVRControllerStatisticsParameter.RightPadTouch, ParameterMode.Write, "VRCOSC/OpenVR/RightController/Input/Pad/Touch", "Whether the right pad is currently touched"); + CreateParameter(OpenVRControllerStatisticsParameter.RightStickTouch, ParameterMode.Write, "VRCOSC/OpenVR/RightController/Input/Stick/Touch", "Whether the right stick is currently touched"); + CreateParameter(OpenVRControllerStatisticsParameter.RightIndex, ParameterMode.Write, "VRCOSC/OpenVR/RightController/Input/Finger/Index", "The touch value of your right index finger"); + CreateParameter(OpenVRControllerStatisticsParameter.RightMiddle, ParameterMode.Write, "VRCOSC/OpenVR/RightController/Input/Finger/Middle", "The touch value of your right middle finger"); + CreateParameter(OpenVRControllerStatisticsParameter.RightRing, ParameterMode.Write, "VRCOSC/OpenVR/RightController/Input/Finger/Ring", "The touch value of your right ring finger"); + CreateParameter(OpenVRControllerStatisticsParameter.RightPinky, ParameterMode.Write, "VRCOSC/OpenVR/RightController/Input/Finger/Pinky", "The touch value of your right pinky finger"); + } + + protected override Task OnUpdate() + { + if (!OpenVrInterface.HasInitialised) return Task.CompletedTask; + + if (OpenVrInterface.IsLeftControllerConnected()) + { + SendParameter(OpenVRControllerStatisticsParameter.LeftATouch, OpenVrInterface.LeftController.ATouched); + SendParameter(OpenVRControllerStatisticsParameter.LeftBTouch, OpenVrInterface.LeftController.BTouched); + SendParameter(OpenVRControllerStatisticsParameter.LeftPadTouch, OpenVrInterface.LeftController.PadTouched); + SendParameter(OpenVRControllerStatisticsParameter.LeftStickTouch, OpenVrInterface.LeftController.StickTouched); + SendParameter(OpenVRControllerStatisticsParameter.LeftIndex, OpenVrInterface.LeftController.IndexFinger); + SendParameter(OpenVRControllerStatisticsParameter.LeftMiddle, OpenVrInterface.LeftController.MiddleFinger); + SendParameter(OpenVRControllerStatisticsParameter.LeftRing, OpenVrInterface.LeftController.RingFinger); + SendParameter(OpenVRControllerStatisticsParameter.LeftPinky, OpenVrInterface.LeftController.PinkyFinger); + } + + if (OpenVrInterface.IsRightControllerConnected()) + { + SendParameter(OpenVRControllerStatisticsParameter.RightATouch, OpenVrInterface.RightController.ATouched); + SendParameter(OpenVRControllerStatisticsParameter.RightBTouch, OpenVrInterface.RightController.BTouched); + SendParameter(OpenVRControllerStatisticsParameter.RightPadTouch, OpenVrInterface.RightController.PadTouched); + SendParameter(OpenVRControllerStatisticsParameter.RightStickTouch, OpenVrInterface.RightController.StickTouched); + SendParameter(OpenVRControllerStatisticsParameter.RightIndex, OpenVrInterface.RightController.IndexFinger); + SendParameter(OpenVRControllerStatisticsParameter.RightMiddle, OpenVrInterface.RightController.MiddleFinger); + SendParameter(OpenVRControllerStatisticsParameter.RightRing, OpenVrInterface.RightController.RingFinger); + SendParameter(OpenVRControllerStatisticsParameter.RightPinky, OpenVrInterface.RightController.PinkyFinger); + } + + return Task.CompletedTask; + } + + private enum OpenVRControllerStatisticsParameter + { + LeftATouch, + LeftBTouch, + LeftPadTouch, + LeftStickTouch, + LeftIndex, + LeftMiddle, + LeftRing, + LeftPinky, + RightATouch, + RightBTouch, + RightPadTouch, + RightStickTouch, + RightIndex, + RightMiddle, + RightRing, + RightPinky + } +} diff --git a/VRCOSC.Game/Modules/Modules/OpenVR/OpenVRStatisticsModule.cs b/VRCOSC.Game/Modules/Modules/OpenVR/OpenVRStatisticsModule.cs new file mode 100644 index 00000000..40d471b2 --- /dev/null +++ b/VRCOSC.Game/Modules/Modules/OpenVR/OpenVRStatisticsModule.cs @@ -0,0 +1,138 @@ +// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. +// See the LICENSE file in the repository root for full license text. + +using System.Linq; +using System.Threading.Tasks; + +namespace VRCOSC.Game.Modules.Modules.OpenVR; + +public class OpenVRStatisticsModule : Module +{ + private const int max_tracker_count = 8; + + public override string Title => "OpenVR Statistics"; + public override string Description => "Gets statistics from your OpenVR (SteamVR) session"; + public override string Author => "VolcanicArts"; + public override ModuleType Type => ModuleType.OpenVR; + protected override int DeltaUpdate => 5000; + + protected override void CreateAttributes() + { + CreateParameter(OpenVrParameter.HMD_Connected, ParameterMode.Write, "VRCOSC/OpenVR/HMD/Connected", "Whether your HMD is connected"); + CreateParameter(OpenVrParameter.HMD_Battery, ParameterMode.Write, "VRCOSC/OpenVR/HMD/Battery", "The battery percentage normalised of your headset"); + CreateParameter(OpenVrParameter.HMD_Charging, ParameterMode.Write, "VRCOSC/OpenVR/HMD/Charging", "The charge state of your headset"); + + CreateParameter(OpenVrParameter.LeftController_Connected, ParameterMode.Write, "VRCOSC/OpenVR/LeftController/Connected", "Whether your left controller is connected"); + CreateParameter(OpenVrParameter.LeftController_Battery, ParameterMode.Write, "VRCOSC/OpenVR/LeftController/Battery", "The battery percentage normalised of your left controller"); + CreateParameter(OpenVrParameter.LeftController_Charging, ParameterMode.Write, "VRCOSC/OpenVR/LeftController/Charging", "The charge state of your left controller"); + CreateParameter(OpenVrParameter.RightController_Connected, ParameterMode.Write, "VRCOSC/OpenVR/RightController/Connected", "Whether your right controller is connected"); + CreateParameter(OpenVrParameter.RightController_Battery, ParameterMode.Write, "VRCOSC/OpenVR/RightController/Battery", "The battery percentage normalised of your right controller"); + CreateParameter(OpenVrParameter.RightController_Charging, ParameterMode.Write, "VRCOSC/OpenVR/RightController/Charging", "The charge state of your right controller"); + + for (int i = 0; i < max_tracker_count; i++) + { + CreateParameter(OpenVrParameter.Tracker1_Connected + i, ParameterMode.Write, $"VRCOSC/OpenVR/Trackers/{i + 1}/Connected", $"Whether tracker {i + 1} is connected"); + CreateParameter(OpenVrParameter.Tracker1_Battery + i, ParameterMode.Write, $"VRCOSC/OpenVR/Trackers/{i + 1}/Battery", $"The battery percentage normalised (0-1) of tracker {i + 1}"); + CreateParameter(OpenVrParameter.Tracker1_Charging + i, ParameterMode.Write, $"VRCOSC/OpenVR/Trackers/{i + 1}/Charging", $"Whether tracker {i + 1} is currently charging"); + } + } + + protected override Task OnUpdate() + { + if (!OpenVrInterface.HasInitialised) return Task.CompletedTask; + + handleHmd(); + handleControllers(); + handleTrackers(); + + return Task.CompletedTask; + } + + private void handleHmd() + { + SendParameter(OpenVrParameter.HMD_Connected, OpenVrInterface.IsHmdConnected()); + + if (OpenVrInterface.IsHmdConnected()) + { + if (OpenVrInterface.CanHmdProvideBatteryData()) + { + SendParameter(OpenVrParameter.HMD_Battery, OpenVrInterface.GetHmdBatteryPercentage()); + SendParameter(OpenVrParameter.HMD_Charging, OpenVrInterface.IsHmdCharging()); + } + } + } + + private void handleControllers() + { + SendParameter(OpenVrParameter.LeftController_Connected, OpenVrInterface.IsLeftControllerConnected()); + + if (OpenVrInterface.IsLeftControllerConnected()) + { + SendParameter(OpenVrParameter.LeftController_Battery, OpenVrInterface.GetLeftControllerBatteryPercentage()); + SendParameter(OpenVrParameter.LeftController_Charging, OpenVrInterface.IsLeftControllerCharging()); + } + + SendParameter(OpenVrParameter.RightController_Connected, OpenVrInterface.IsRightControllerConnected()); + + if (OpenVrInterface.IsRightControllerConnected()) + { + SendParameter(OpenVrParameter.RightController_Battery, OpenVrInterface.GetRightControllerBatteryPercentage()); + SendParameter(OpenVrParameter.RightController_Charging, OpenVrInterface.IsRightControllerCharging()); + } + } + + private void handleTrackers() + { + var trackers = OpenVrInterface.GetTrackers().ToList(); + + for (int i = 0; i < max_tracker_count; i++) + { + uint trackerIndex = i >= trackers.Count ? uint.MaxValue : trackers[i]; + + SendParameter(OpenVrParameter.Tracker1_Connected + i, OpenVrInterface.IsTrackerConnected(trackerIndex)); + + if (OpenVrInterface.IsTrackerConnected(trackerIndex)) + { + SendParameter(OpenVrParameter.Tracker1_Battery + i, OpenVrInterface.GetTrackerBatteryPercentage(trackerIndex)); + SendParameter(OpenVrParameter.Tracker1_Charging + i, OpenVrInterface.IsTrackerCharging(trackerIndex)); + } + } + } + + private enum OpenVrParameter + { + HMD_Connected, + LeftController_Connected, + RightController_Connected, + Tracker1_Connected, + Tracker2_Connected, + Tracker3_Connected, + Tracker4_Connected, + Tracker5_Connected, + Tracker6_Connected, + Tracker7_Connected, + Tracker8_Connected, + HMD_Battery, + LeftController_Battery, + RightController_Battery, + Tracker1_Battery, + Tracker2_Battery, + Tracker3_Battery, + Tracker4_Battery, + Tracker5_Battery, + Tracker6_Battery, + Tracker7_Battery, + Tracker8_Battery, + HMD_Charging, + LeftController_Charging, + RightController_Charging, + Tracker1_Charging, + Tracker2_Charging, + Tracker3_Charging, + Tracker4_Charging, + Tracker5_Charging, + Tracker6_Charging, + Tracker7_Charging, + Tracker8_Charging + } +} diff --git a/VRCOSC.Game/Modules/Modules/Random/RandomModule.cs b/VRCOSC.Game/Modules/Modules/Random/RandomModule.cs index 55f2e04f..94683e1d 100644 --- a/VRCOSC.Game/Modules/Modules/Random/RandomModule.cs +++ b/VRCOSC.Game/Modules/Modules/Random/RandomModule.cs @@ -3,7 +3,6 @@ using System; using System.Threading.Tasks; -using VRCOSC.Game.Modules.Util; namespace VRCOSC.Game.Modules.Modules.Random; @@ -12,7 +11,7 @@ public abstract class RandomModule : Module where T : struct public override string Title => $"Random {typeof(T).ToReadableName()}"; public override string Description => $"Sends a random {typeof(T).ToReadableName().ToLowerInvariant()} over a variable time period"; public override string Author => "VolcanicArts"; - public override ModuleType ModuleType => ModuleType.General; + public override ModuleType Type => ModuleType.General; protected override int DeltaUpdate => GetSetting(RandomSetting.DeltaUpdate); private readonly System.Random random = new(); diff --git a/VRCOSC.Game/Modules/Modules/SpeechToText/SpeechToTextModule.cs b/VRCOSC.Game/Modules/Modules/SpeechToText/SpeechToTextModule.cs deleted file mode 100644 index 4bd80446..00000000 --- a/VRCOSC.Game/Modules/Modules/SpeechToText/SpeechToTextModule.cs +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. -// See the LICENSE file in the repository root for full license text. - -using System.IO; -using System.Linq; -using System.Speech.Recognition; -using System.Threading.Tasks; -using Newtonsoft.Json; -using Vosk; -using System; -using System.Threading; - -namespace VRCOSC.Game.Modules.Modules.SpeechToText; - -public sealed class SpeechToTextModule : Module -{ - public override string Title => "Speech To Text"; - public override string Description => "Speech to text using VOSK's local processing for VRChat's ChatBox"; - public override string Author => "VolcanicArts"; - public override ModuleType ModuleType => ModuleType.Accessibility; - protected override int ChatBoxPriority => 4; - - private readonly SpeechRecognitionEngine speechRecognitionEngine = new(); - private VoskRecognizer recognizer = null!; - - public SpeechToTextModule() - { - speechRecognitionEngine.SetInputToDefaultAudioDevice(); - speechRecognitionEngine.LoadGrammar(new DictationGrammar()); - - Vosk.Vosk.SetLogLevel(-1); - } - - protected override void CreateAttributes() - { - base.CreateAttributes(); - CreateSetting(SpeechToTextSetting.ModelLocation, "Model Location", "The folder location of the speech model you'd like to use.\nFor standard English, download 'vosk-model-small-en-us-0.15'", string.Empty, "Download a model", - () => OpenUrlExternally("https://alphacephei.com/vosk/models")); - CreateSetting(SpeechToTextSetting.DisplayPeriod, "Display Period", "How long should a valid recognition be shown for? (Milliseconds)", 10000); - CreateSetting(SpeechToTextSetting.FollowMute, "Follow Mute", "Should speech to text only be enabled if you're muted in game?", false); - } - - protected override async Task OnStart(CancellationToken cancellationToken) - { - await base.OnStart(cancellationToken); - - if (!Directory.Exists(GetSetting(SpeechToTextSetting.ModelLocation))) - { - Log("Please enter a valid model folder path"); - return; - } - - speechRecognitionEngine.SpeechHypothesized += onTalkingDetected; - speechRecognitionEngine.SpeechRecognized += onTalkingFinished; - speechRecognitionEngine.RecognizeAsync(RecognizeMode.Multiple); - - Log("Loading model..."); - - var model = new Model(GetSetting(SpeechToTextSetting.ModelLocation)); - recognizer = new VoskRecognizer(model, 16000); - recognizer.SetMaxAlternatives(0); - recognizer.SetWords(true); - - Log("Model loaded!"); - - SetChatBoxTyping(false); - } - - protected override async Task OnStop() - { - await base.OnStop(); - - speechRecognitionEngine.RecognizeAsyncStop(); - speechRecognitionEngine.SpeechHypothesized -= onTalkingDetected; - speechRecognitionEngine.SpeechRecognized -= onTalkingFinished; - - recognizer.Dispose(); - - SetChatBoxTyping(false); - } - - private void onTalkingDetected(object? sender, SpeechHypothesizedEventArgs e) - { - if (GetSetting(SpeechToTextSetting.FollowMute) && !(Player.IsMuted ?? false)) return; - - SetChatBoxTyping(true); - } - - private void onTalkingFinished(object? sender, SpeechRecognizedEventArgs e) - { - if (GetSetting(SpeechToTextSetting.FollowMute) && !(Player.IsMuted ?? false)) return; - if (e.Result.Audio is null) return; - - using var memoryStream = new MemoryStream(); - e.Result.Audio.WriteToWaveStream(memoryStream); - - var buffer = new byte[4096]; - int bytesRead; - - // using a 2nd memory stream as GetBuffer() must be called - using var wavStream = new MemoryStream(memoryStream.GetBuffer()); - - while ((bytesRead = wavStream.Read(buffer, 0, buffer.Length)) > 0) - { - recognizer.AcceptWaveform(buffer, bytesRead); - } - - var finalResult = JsonConvert.DeserializeObject(recognizer.FinalResult())?.Text ?? string.Empty; - - SetChatBoxTyping(false); - recognizer.Reset(); - - if (string.IsNullOrEmpty(finalResult)) return; - - finalResult = string.Concat(finalResult.First().ToString().ToUpper(), finalResult.AsSpan(1)); - - Log($"Recognised: {finalResult}"); - // TODO: Find a way to allow SpeechToText to override the whole ChatBox system - //SetChatBoxText(finalResult, GetSetting(SpeechToTextSetting.DisplayPeriod)); - } - - private class Recognition - { - [JsonProperty("text")] - public string Text = null!; - } - - private enum SpeechToTextSetting - { - ModelLocation, - DisplayPeriod, - FollowMute - } -} diff --git a/VRCOSC.Game/Modules/OpenVRInterface.cs b/VRCOSC.Game/Modules/OpenVRInterface.cs new file mode 100644 index 00000000..b90e3ad5 --- /dev/null +++ b/VRCOSC.Game/Modules/OpenVRInterface.cs @@ -0,0 +1,287 @@ +// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. +// See the LICENSE file in the repository root for full license text. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using osu.Framework.Logging; +using osu.Framework.Platform; +using Valve.VR; + +// ReSharper disable MemberCanBeMadeStatic.Global + +namespace VRCOSC.Game.Modules; + +[SuppressMessage("Performance", "CA1822:Mark members as static")] +public class OpenVRInterface +{ + private static readonly uint vrevent_t_size = (uint)Unsafe.SizeOf(); + private static readonly uint vractiveactonset_t_size = (uint)Unsafe.SizeOf(); + private static readonly uint inputanalogactiondata_t_size = (uint)Unsafe.SizeOf(); + private static readonly uint inputdigitalactiondata_t_size = (uint)Unsafe.SizeOf(); + + public readonly ControllerData LeftController = new(); + public readonly ControllerData RightController = new(); + + private ulong actionSetHandle; + private readonly ulong[] leftController = new ulong[8]; + private readonly ulong[] rightController = new ulong[8]; + + private readonly Storage storage; + public bool HasInitialised { get; private set; } + + public OpenVRInterface(Storage storage) + { + this.storage = storage.GetStorageForDirectory("openvr"); + } + + public void Init() + { + if (HasInitialised) return; + + var err = new EVRInitError(); + var state = OpenVR.InitInternal(ref err, EVRApplicationType.VRApplication_Background); + + if (err != EVRInitError.None || state == 0) + { + HasInitialised = false; + return; + } + + HasInitialised = true; + + OpenVR.Input.SetActionManifestPath(storage.GetFullPath("action_manifest.json")); + + OpenVR.Input.GetActionHandle("/actions/main/in/lefta", ref leftController[0]); + OpenVR.Input.GetActionHandle("/actions/main/in/leftb", ref leftController[1]); + OpenVR.Input.GetActionHandle("/actions/main/in/leftpad", ref leftController[2]); + OpenVR.Input.GetActionHandle("/actions/main/in/leftstick", ref leftController[3]); + OpenVR.Input.GetActionHandle("/actions/main/in/leftfingerindex", ref leftController[4]); + OpenVR.Input.GetActionHandle("/actions/main/in/leftfingermiddle", ref leftController[5]); + OpenVR.Input.GetActionHandle("/actions/main/in/leftfingerring", ref leftController[6]); + OpenVR.Input.GetActionHandle("/actions/main/in/leftfingerpinky", ref leftController[7]); + + OpenVR.Input.GetActionHandle("/actions/main/in/righta", ref rightController[0]); + OpenVR.Input.GetActionHandle("/actions/main/in/rightb", ref rightController[1]); + OpenVR.Input.GetActionHandle("/actions/main/in/rightpad", ref rightController[2]); + OpenVR.Input.GetActionHandle("/actions/main/in/rightstick", ref rightController[3]); + OpenVR.Input.GetActionHandle("/actions/main/in/rightfingerindex", ref rightController[4]); + OpenVR.Input.GetActionHandle("/actions/main/in/rightfingermiddle", ref rightController[5]); + OpenVR.Input.GetActionHandle("/actions/main/in/rightfingerring", ref rightController[6]); + OpenVR.Input.GetActionHandle("/actions/main/in/rightfingerpinky", ref rightController[7]); + + OpenVR.Input.GetActionSetHandle("/actions/main", ref actionSetHandle); + } + + public bool IsHmdConnected() => getIndexForTrackedDeviceClass(ETrackedDeviceClass.HMD) != uint.MaxValue && OpenVR.IsHmdPresent(); + public bool IsLeftControllerConnected() => getLeftControllerIndex() != uint.MaxValue && OpenVR.System.IsTrackedDeviceConnected(getLeftControllerIndex()); + public bool IsRightControllerConnected() => getRightControllerIndex() != uint.MaxValue && OpenVR.System.IsTrackedDeviceConnected(getRightControllerIndex()); + public bool IsTrackerConnected(uint trackerIndex) => trackerIndex != uint.MaxValue && OpenVR.System.IsTrackedDeviceConnected(trackerIndex); + + public bool IsHmdCharging() => CanHmdProvideBatteryData() && getBoolTrackedDeviceProperty(getHmdIndex(), ETrackedDeviceProperty.Prop_DeviceIsCharging_Bool); + public bool IsLeftControllerCharging() => getBoolTrackedDeviceProperty(getLeftControllerIndex(), ETrackedDeviceProperty.Prop_DeviceIsCharging_Bool); + public bool IsRightControllerCharging() => getBoolTrackedDeviceProperty(getRightControllerIndex(), ETrackedDeviceProperty.Prop_DeviceIsCharging_Bool); + public bool IsTrackerCharging(uint trackerIndex) => getBoolTrackedDeviceProperty(trackerIndex, ETrackedDeviceProperty.Prop_DeviceIsCharging_Bool); + + public float GetHmdBatteryPercentage() => getFloatTrackedDeviceProperty(getHmdIndex(), ETrackedDeviceProperty.Prop_DeviceBatteryPercentage_Float); + public float GetLeftControllerBatteryPercentage() => getFloatTrackedDeviceProperty(getLeftControllerIndex(), ETrackedDeviceProperty.Prop_DeviceBatteryPercentage_Float); + public float GetRightControllerBatteryPercentage() => getFloatTrackedDeviceProperty(getRightControllerIndex(), ETrackedDeviceProperty.Prop_DeviceBatteryPercentage_Float); + public float GetTrackerBatteryPercentage(uint trackerIndex) => getFloatTrackedDeviceProperty(trackerIndex, ETrackedDeviceProperty.Prop_DeviceBatteryPercentage_Float); + + public bool CanHmdProvideBatteryData() + { + var error = new ETrackedPropertyError(); + var canProvideBattery = OpenVR.System.GetBoolTrackedDeviceProperty(getHmdIndex(), ETrackedDeviceProperty.Prop_DeviceProvidesBatteryStatus_Bool, ref error); + return error == ETrackedPropertyError.TrackedProp_Success && canProvideBattery; + } + + #region Events + + public void Poll() + { + var evenT = new VREvent_t(); + + while (OpenVR.System.PollNextEvent(ref evenT, vrevent_t_size)) + { + var eventType = (EVREventType)evenT.eventType; + + if (eventType == EVREventType.VREvent_Quit) + { + OpenVR.System.AcknowledgeQuit_Exiting(); + OpenVR.Shutdown(); + HasInitialised = false; + return; + } + } + + var activeActionSet = new VRActiveActionSet_t[] { new() }; + activeActionSet[0].ulActionSet = actionSetHandle; + activeActionSet[0].ulRestrictedToDevice = OpenVR.k_ulInvalidInputValueHandle; + activeActionSet[0].nPriority = 0; + OpenVR.Input.UpdateActionState(activeActionSet, vractiveactonset_t_size); + extractControllerData(); + } + + private void extractControllerData() + { + LeftController.ATouched = getDigitalInput(leftController[0]).bState; + LeftController.BTouched = getDigitalInput(leftController[1]).bState; + LeftController.PadTouched = getDigitalInput(leftController[2]).bState; + LeftController.StickTouched = getDigitalInput(leftController[3]).bState; + LeftController.IndexFinger = getAnalogueInput(leftController[4]).x; + LeftController.MiddleFinger = getAnalogueInput(leftController[5]).x; + LeftController.RingFinger = getAnalogueInput(leftController[6]).x; + LeftController.PinkyFinger = getAnalogueInput(leftController[7]).x; + + RightController.ATouched = getDigitalInput(rightController[0]).bState; + RightController.BTouched = getDigitalInput(rightController[1]).bState; + RightController.PadTouched = getDigitalInput(rightController[2]).bState; + RightController.StickTouched = getDigitalInput(rightController[3]).bState; + RightController.IndexFinger = getAnalogueInput(rightController[4]).x; + RightController.MiddleFinger = getAnalogueInput(rightController[5]).x; + RightController.RingFinger = getAnalogueInput(rightController[6]).x; + RightController.PinkyFinger = getAnalogueInput(rightController[7]).x; + } + + private InputAnalogActionData_t getAnalogueInput(ulong identifier) + { + var data = new InputAnalogActionData_t(); + OpenVR.Input.GetAnalogActionData(identifier, ref data, inputanalogactiondata_t_size, OpenVR.k_ulInvalidInputValueHandle); + return data; + } + + private InputDigitalActionData_t getDigitalInput(ulong identifier) + { + var data = new InputDigitalActionData_t(); + OpenVR.Input.GetDigitalActionData(identifier, ref data, inputdigitalactiondata_t_size, OpenVR.k_ulInvalidInputValueHandle); + return data; + } + + #endregion + + #region OpenVR Abstraction + + private uint getHmdIndex() => getIndexForTrackedDeviceClass(ETrackedDeviceClass.HMD); + private uint getLeftControllerIndex() => getController("left"); + private uint getRightControllerIndex() => getController("right"); + public IEnumerable GetTrackers() => getIndexesForTrackedDeviceClass(ETrackedDeviceClass.GenericTracker); + + // GetTrackedDeviceIndexForControllerRole doesn't work when a tracker thinks it's a controller and assumes that role + // We can forcibly find the correct indexes by using the model name + private uint getController(string controllerHint) + { + var indexes = getIndexesForTrackedDeviceClass(ETrackedDeviceClass.Controller); + + foreach (var index in indexes) + { + var renderModelName = getStringTrackedDeviceProperty(index, ETrackedDeviceProperty.Prop_RenderModelName_String); + if (renderModelName.Contains(controllerHint, StringComparison.InvariantCultureIgnoreCase)) return index; + } + + return uint.MaxValue; + } + + private uint getIndexForTrackedDeviceClass(ETrackedDeviceClass klass) + { + var indexes = getIndexesForTrackedDeviceClass(klass).ToArray(); + return indexes.Any() ? indexes[0] : uint.MaxValue; + } + + private IEnumerable getIndexesForTrackedDeviceClass(ETrackedDeviceClass klass) + { + for (uint i = 0; i < OpenVR.k_unMaxTrackedDeviceCount; i++) + { + if (OpenVR.System.GetTrackedDeviceClass(i) == klass) yield return i; + } + } + + private bool getBoolTrackedDeviceProperty(uint index, ETrackedDeviceProperty property) + { + var error = new ETrackedPropertyError(); + var value = OpenVR.System.GetBoolTrackedDeviceProperty(index, property, ref error); + + if (error != ETrackedPropertyError.TrackedProp_Success) + { + log($"GetBoolTrackedDeviceProperty has given an error: {error}"); + return false; + } + + return value; + } + + private int getInt32TrackedDeviceProperty(uint index, ETrackedDeviceProperty property) + { + var error = new ETrackedPropertyError(); + var value = OpenVR.System.GetInt32TrackedDeviceProperty(index, property, ref error); + + if (error != ETrackedPropertyError.TrackedProp_Success) + { + log($"GetInt32TrackedDeviceProperty has given an error: {error}"); + return 0; + } + + return value; + } + + private float getFloatTrackedDeviceProperty(uint index, ETrackedDeviceProperty property) + { + var error = new ETrackedPropertyError(); + var value = OpenVR.System.GetFloatTrackedDeviceProperty(index, property, ref error); + + if (error != ETrackedPropertyError.TrackedProp_Success) + { + log($"GetFloatTrackedDeviceProperty has given an error: {error}"); + return 0f; + } + + return value; + } + + private readonly StringBuilder sb = new((int)OpenVR.k_unMaxPropertyStringSize); + private readonly object stringLock = new(); + + private string getStringTrackedDeviceProperty(uint index, ETrackedDeviceProperty property) + { + string str; + + lock (stringLock) + { + var error = new ETrackedPropertyError(); + sb.Clear(); + OpenVR.System.GetStringTrackedDeviceProperty(index, property, sb, OpenVR.k_unMaxPropertyStringSize, ref error); + + if (error != ETrackedPropertyError.TrackedProp_Success) + { + log($"GetStringTrackedDeviceProperty has given an error: {error}"); + return string.Empty; + } + + str = sb.ToString(); + } + + return str; + } + + #endregion + + private static void log(string message) + { + Logger.Log($"[OpenVR] {message}"); + } +} + +public class ControllerData +{ + public bool ATouched; + public bool BTouched; + public bool PadTouched; + public bool StickTouched; + public bool ThumbDown => ATouched || BTouched || PadTouched || StickTouched; + public float IndexFinger; + public float MiddleFinger; + public float RingFinger; + public float PinkyFinger; +} diff --git a/VRCOSC.Game/Modules/ParameterMetadata.cs b/VRCOSC.Game/Modules/ParameterMetadata.cs index 5ec1e9c1..20bd2fdc 100644 --- a/VRCOSC.Game/Modules/ParameterMetadata.cs +++ b/VRCOSC.Game/Modules/ParameterMetadata.cs @@ -11,17 +11,15 @@ public class ParameterMetadata public readonly string Name; public readonly string Description; public readonly Type ExpectedType; - public readonly ActionMenu Menu; public string FormattedAddress => $"/avatar/parameters/{Name}"; - public ParameterMetadata(ParameterMode mode, string name, string description, Type expectedType, ActionMenu menu) + public ParameterMetadata(ParameterMode mode, string name, string description, Type expectedType) { Mode = mode; Name = name; Description = description; ExpectedType = expectedType; - Menu = menu; } } diff --git a/VRCOSC.Game/Util/TerminalLogger.cs b/VRCOSC.Game/Modules/TerminalLogger.cs similarity index 94% rename from VRCOSC.Game/Util/TerminalLogger.cs rename to VRCOSC.Game/Modules/TerminalLogger.cs index b96ab1d1..8591617a 100644 --- a/VRCOSC.Game/Util/TerminalLogger.cs +++ b/VRCOSC.Game/Modules/TerminalLogger.cs @@ -4,7 +4,7 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Logging; -namespace VRCOSC.Game.Util; +namespace VRCOSC.Game.Modules; public sealed class TerminalLogger { diff --git a/VRCOSC.Game/Modules/Util/TimedTask.cs b/VRCOSC.Game/Modules/TimedTask.cs similarity index 89% rename from VRCOSC.Game/Modules/Util/TimedTask.cs rename to VRCOSC.Game/Modules/TimedTask.cs index b7282570..3b4bb346 100644 --- a/VRCOSC.Game/Modules/Util/TimedTask.cs +++ b/VRCOSC.Game/Modules/TimedTask.cs @@ -5,7 +5,7 @@ using System.Threading; using System.Threading.Tasks; -namespace VRCOSC.Game.Modules.Util; +namespace VRCOSC.Game.Modules; public sealed class TimedTask { @@ -23,17 +23,20 @@ public TimedTask(Func action, double deltaTimeMilli, bool executeOnceImmed this.executeOnceImmediately = executeOnceImmediately; } - public TimedTask Start() + public async Task Start() { + timer?.Dispose(); + await (timerTask ?? Task.CompletedTask); + timer = new PeriodicTimer(TimeSpan.FromMilliseconds(deltaTimeMilli)); + + if (executeOnceImmediately) await action.Invoke(); + timerTask = Task.Run(executeWork); - return this; } private async void executeWork() { - if (executeOnceImmediately) await action.Invoke(); - while (await timer!.WaitForNextTickAsync()) { await action.Invoke(); diff --git a/VRCOSC.Game/Modules/Util/OpenVRInterface.cs b/VRCOSC.Game/Modules/Util/OpenVRInterface.cs deleted file mode 100644 index fc4985e2..00000000 --- a/VRCOSC.Game/Modules/Util/OpenVRInterface.cs +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. -// See the LICENSE file in the repository root for full license text. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Valve.VR; - -namespace VRCOSC.Game.Modules.Util; - -public class OpenVrInterface -{ - private readonly Dictionary touchTracker = new() - { - { EVRButtonId.k_EButton_IndexController_A, false }, - { EVRButtonId.k_EButton_IndexController_B, false }, - { EVRButtonId.k_EButton_SteamVR_Touchpad, false }, - { EVRButtonId.k_EButton_IndexController_JoyStick, false } - }; - - public bool Init() - { - var err = new EVRInitError(); - OpenVR.Init(ref err, EVRApplicationType.VRApplication_Utility); - return err == EVRInitError.None; - } - - public unsafe void Poll() - { - try - { - bool hasEvents; - - do - { - var evenT = new VREvent_t(); - hasEvents = OpenVR.System.PollNextEvent(ref evenT, (uint)sizeof(VREvent_t)); - - switch ((EVREventType)evenT.eventType) - { - case EVREventType.VREvent_ButtonTouch: - var touchedButton = (EVRButtonId)evenT.data.controller.button; - - if (touchTracker.ContainsKey(touchedButton)) - { - touchTracker[touchedButton] = true; - Console.WriteLine($"Setting {touchedButton} state to true"); - } - - break; - - case EVREventType.VREvent_ButtonUntouch: - var untouchedButton = (EVRButtonId)evenT.data.controller.button; - - if (touchTracker.ContainsKey(untouchedButton)) - { - touchTracker[untouchedButton] = false; - Console.WriteLine($"Setting {untouchedButton} state to false"); - } - - break; - } - } while (hasEvents); - } - catch (NullReferenceException) { } - } - - public float? GetHMDBatteryPercentage() - { - try - { - var error = new ETrackedPropertyError(); - - var id = getIndexesForTrackedDeviceClass(ETrackedDeviceClass.HMD)[0]; - var canProvideBattery = OpenVR.System.GetBoolTrackedDeviceProperty(id, ETrackedDeviceProperty.Prop_DeviceProvidesBatteryStatus_Bool, ref error); - - if (!canProvideBattery) return null; - - return getFloatTrackedDeviceProperty(id, ETrackedDeviceProperty.Prop_DeviceBatteryPercentage_Float); - } - catch (NullReferenceException) - { - return null; - } - } - - public float GetLeftControllerBatteryPercentage() - { - var id = getControllerUint("left"); - return id == uint.MaxValue ? 0 : getFloatTrackedDeviceProperty(id, ETrackedDeviceProperty.Prop_DeviceBatteryPercentage_Float); - } - - public float GetRightControllerBatteryPercentage() - { - var id = getControllerUint("right"); - return id == uint.MaxValue ? 0 : getFloatTrackedDeviceProperty(id, ETrackedDeviceProperty.Prop_DeviceBatteryPercentage_Float); - } - - public IEnumerable GetTrackersBatteryPercentages() - { - try - { - var ids = getIndexesForTrackedDeviceClass(ETrackedDeviceClass.GenericTracker); - return ids.Select(id => getFloatTrackedDeviceProperty(id, ETrackedDeviceProperty.Prop_DeviceBatteryPercentage_Float)); - } - catch (NullReferenceException) - { - return Array.Empty(); - } - } - - private uint getControllerUint(string identifier) - { - try - { - var ids = getIndexesForTrackedDeviceClass(ETrackedDeviceClass.Controller); - - foreach (var id in ids) - { - var name = getStringTrackedDeviceProperty(id, ETrackedDeviceProperty.Prop_RenderModelName_String); - if (name.Contains(identifier)) return id; - } - - return uint.MaxValue; - } - catch (NullReferenceException) - { - return uint.MaxValue; - } - } - - private string getStringTrackedDeviceProperty(uint index, ETrackedDeviceProperty property) - { - var error = new ETrackedPropertyError(); - StringBuilder sb = new StringBuilder((int)OpenVR.k_unMaxPropertyStringSize); - OpenVR.System.GetStringTrackedDeviceProperty(index, property, sb, OpenVR.k_unMaxPropertyStringSize, ref error); - return sb.ToString(); - } - - private float getFloatTrackedDeviceProperty(uint index, ETrackedDeviceProperty property) - { - var error = new ETrackedPropertyError(); - return OpenVR.System.GetFloatTrackedDeviceProperty(index, property, ref error); - } - - private List getIndexesForTrackedDeviceClass(ETrackedDeviceClass klass) - { - var result = new List(); - - for (uint i = 0; i < OpenVR.k_unMaxTrackedDeviceCount; i++) - { - if (OpenVR.System.GetTrackedDeviceClass(i) == klass) result.Add(i); - } - - return result; - } -} - -public enum OpenVRButton -{ - None, - A, - B, - Stick, - Pad -} diff --git a/VRCOSC.Game/Modules/Websocket/JsonWebSocket.cs b/VRCOSC.Game/Modules/Websocket/JsonWebSocket.cs deleted file mode 100644 index 03e1baaa..00000000 --- a/VRCOSC.Game/Modules/Websocket/JsonWebSocket.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. -// See the LICENSE file in the repository root for full license text. - -using Newtonsoft.Json; - -namespace VRCOSC.Game.Modules.Websocket; - -public class JsonWebSocket : BaseWebSocket -{ - public JsonWebSocket(string uri) - : base(uri) - { - } - - public void SendAsJson(object data) - { - Send(JsonConvert.SerializeObject(data)); - } -} diff --git a/VRCOSC.Game/Modules/Util/WindowsVKey.cs b/VRCOSC.Game/Modules/WindowsVKey.cs similarity index 97% rename from VRCOSC.Game/Modules/Util/WindowsVKey.cs rename to VRCOSC.Game/Modules/WindowsVKey.cs index 8a803c62..99819d8a 100644 --- a/VRCOSC.Game/Modules/Util/WindowsVKey.cs +++ b/VRCOSC.Game/Modules/WindowsVKey.cs @@ -4,7 +4,7 @@ // ReSharper disable IdentifierTypo // ReSharper disable UnusedMember.Global -namespace VRCOSC.Game.Modules.Util; +namespace VRCOSC.Game.Modules; public enum WindowsVKey { diff --git a/VRCOSC.Game/Util/ProcessExtensions.cs b/VRCOSC.Game/Processes/ProcessExtensions.cs similarity index 78% rename from VRCOSC.Game/Util/ProcessExtensions.cs rename to VRCOSC.Game/Processes/ProcessExtensions.cs index 4389dad1..d31d30d0 100644 --- a/VRCOSC.Game/Util/ProcessExtensions.cs +++ b/VRCOSC.Game/Processes/ProcessExtensions.cs @@ -6,9 +6,9 @@ using System.Linq; using System.Threading.Tasks; using osu.Framework.Extensions.IEnumerableExtensions; -using VRCOSC.Game.Modules.Util; +using VRCOSC.Game.Modules; -namespace VRCOSC.Game.Util; +namespace VRCOSC.Game.Processes; public static class ProcessExtensions { @@ -22,23 +22,16 @@ public static async Task PressKeys(IEnumerable keys, int pressTime) windowsVKeys.ForEach(key => ProcessKey.ReleaseKey((int)key)); } - public static async Task PressKey(WindowsVKey key, int pressTime) - { - ProcessKey.HoldKey((int)key); - await Task.Delay(pressTime); - ProcessKey.ReleaseKey((int)key); - } - #endregion #region Window - public static void ShowMainWindow(Process process, ShowWindowEnum showWindowEnum) + public static void ShowMainWindow(this Process process, ShowWindowEnum showWindowEnum) { ProcessWindow.ShowMainWindow(process, showWindowEnum); } - public static void SetMainWindowForeground(Process process) + public static void SetMainWindowForeground(this Process process) { ProcessWindow.SetMainWindowForeground(process); } diff --git a/VRCOSC.Game/Util/ProcessKey.cs b/VRCOSC.Game/Processes/ProcessKey.cs similarity index 95% rename from VRCOSC.Game/Util/ProcessKey.cs rename to VRCOSC.Game/Processes/ProcessKey.cs index 695a0de3..b708d9ce 100644 --- a/VRCOSC.Game/Util/ProcessKey.cs +++ b/VRCOSC.Game/Processes/ProcessKey.cs @@ -3,7 +3,7 @@ using System.Runtime.InteropServices; -namespace VRCOSC.Game.Util; +namespace VRCOSC.Game.Processes; internal static class ProcessKey { diff --git a/VRCOSC.Game/Util/ProcessVolume.cs b/VRCOSC.Game/Processes/ProcessVolume.cs similarity index 98% rename from VRCOSC.Game/Util/ProcessVolume.cs rename to VRCOSC.Game/Processes/ProcessVolume.cs index 23deca81..6e23e1e6 100644 --- a/VRCOSC.Game/Util/ProcessVolume.cs +++ b/VRCOSC.Game/Processes/ProcessVolume.cs @@ -1,4 +1,4 @@ -// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. +// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. // See the LICENSE file in the repository root for full license text. using System; @@ -7,7 +7,7 @@ // ReSharper disable SuspiciousTypeConversion.Global // ReSharper disable InconsistentNaming -namespace VRCOSC.Game.Util; +namespace VRCOSC.Game.Processes; // https://stackoverflow.com/questions/20938934/controlling-applications-volume-by-process-id internal static class ProcessVolume @@ -59,7 +59,7 @@ internal static void SetApplicationMute(string processName, bool mute) private static ISimpleAudioVolume? getVolumeObject(string processName) { // get the speakers (1st render + multimedia) device - IMMDeviceEnumerator deviceEnumerator = (IMMDeviceEnumerator)(new MMDeviceEnumerator()); + IMMDeviceEnumerator deviceEnumerator = (IMMDeviceEnumerator)new MMDeviceEnumerator(); deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia, out var speakers); // activate the session manager. we need the enumerator diff --git a/VRCOSC.Game/Util/ProcessWindow.cs b/VRCOSC.Game/Processes/ProcessWindow.cs similarity index 64% rename from VRCOSC.Game/Util/ProcessWindow.cs rename to VRCOSC.Game/Processes/ProcessWindow.cs index 5cd9d0a9..1c13d9c4 100644 --- a/VRCOSC.Game/Util/ProcessWindow.cs +++ b/VRCOSC.Game/Processes/ProcessWindow.cs @@ -1,11 +1,11 @@ -// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. +// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. // See the LICENSE file in the repository root for full license text. using System; using System.Diagnostics; using System.Runtime.InteropServices; -namespace VRCOSC.Game.Util; +namespace VRCOSC.Game.Processes; internal static class ProcessWindow { @@ -23,15 +23,22 @@ internal static void ShowMainWindow(Process handle, ShowWindowEnum showWindowEnu internal static void SetMainWindowForeground(Process handle) { - SetForegroundWindow(handle.MainWindowHandle); + _ = SetForegroundWindow(handle.MainWindowHandle); } } public enum ShowWindowEnum { - Hide = 0, - ShowNormal = 1, ShowMinimized = 2, ShowMaximized = 3, - Maximize = 3, ShowNormalNoActivate = 4, Show = 5, - Minimize = 6, ShowMinNoActivate = 7, ShowNoActivate = 8, - Restore = 9, ShowDefault = 10, ForceMinimized = 11 -}; + Hide, + ShowNormal, + ShowMinimized, + ShowMaximized, + ShowNormalNoActivate, + Show, + Minimize, + ShowMinNoActivate, + ShowNoActivate, + Restore, + ShowDefault, + ForceMinimized +} diff --git a/VRCOSC.Game/VRCOSC.Game.csproj b/VRCOSC.Game/VRCOSC.Game.csproj index cf5a4e5b..30f0af22 100644 --- a/VRCOSC.Game/VRCOSC.Game.csproj +++ b/VRCOSC.Game/VRCOSC.Game.csproj @@ -3,18 +3,16 @@ net6.0-windows10.0.22000.0 enable 10 - true + - - diff --git a/VRCOSC.Game/VRCOSCGame.cs b/VRCOSC.Game/VRCOSCGame.cs index 10a5df1f..4e3994a8 100644 --- a/VRCOSC.Game/VRCOSCGame.cs +++ b/VRCOSC.Game/VRCOSCGame.cs @@ -1,20 +1,22 @@ // Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. // See the LICENSE file in the repository root for full license text. +using System.IO; +using System.Linq; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Platform; -using Valve.VR; using VRCOSC.Game.Config; using VRCOSC.Game.Graphics; using VRCOSC.Game.Graphics.Notifications; using VRCOSC.Game.Graphics.Settings; using VRCOSC.Game.Graphics.TabBar; +using VRCOSC.Game.Graphics.Themes; using VRCOSC.Game.Graphics.Updater; using VRCOSC.Game.Modules; -using VRCOSC.Game.Modules.Util; // ReSharper disable InconsistentNaming @@ -35,10 +37,10 @@ public abstract partial class VRCOSCGame : VRCOSCGameBase public VRCOSCUpdateManager UpdateManager = null!; private NotificationContainer notificationContainer = null!; - private OpenVrInterface openVrInterface = null!; + private OpenVRInterface openVrInterface = null!; public Bindable SearchTermFilter = new(string.Empty); - public Bindable TypeFilter = new(); + public Bindable TypeFilter = new(); [Cached] private Bindable SelectedTab = new(); @@ -52,13 +54,18 @@ public abstract partial class VRCOSCGame : VRCOSCGameBase [Cached(name: "InfoModule")] private IBindable InfoModule = new Bindable(); + [Resolved] + private Storage storage { get; set; } = null!; + [BackgroundDependencyLoader] private void load() { + ThemeManager.Theme = ConfigManager.Get(VRCOSCSetting.Theme); + notificationContainer = new NotificationContainer(); DependencyContainer.CacheAs(notificationContainer); - openVrInterface = new OpenVrInterface(); + openVrInterface = new OpenVRInterface(storage); DependencyContainer.CacheAs(openVrInterface); Children = new Drawable[] @@ -73,19 +80,26 @@ private void load() } protected override void Update() + { + if (openVrInterface.HasInitialised) handleOpenVR(); + } + + private void handleOpenVR() { openVrInterface.Poll(); } - protected override void LoadComplete() + protected override async void LoadComplete() { base.LoadComplete(); + await copyOpenVrFiles(); + + Scheduler.AddDelayed(() => Task.Run(() => openVrInterface.Init()), 50d, true); + checkUpdates(); checkVersion(); - openVrInterface.Init(); - notificationContainer.Notify(new TimedNotification { Title = "Join The Community!", @@ -119,14 +133,27 @@ private void checkVersion() Title = "VRCOSC Updated", Description = "Click to see the changes", Icon = FontAwesome.Solid.Download, - Colour = VRCOSCColour.GreenDark, - ClickCallback = () => host.OpenUrlExternally(latest_release_url), + Colour = ThemeManager.Current[ThemeAttribute.Success], + ClickCallback = () => host.OpenUrlExternally(latest_release_url) }); } ConfigManager.SetValue(VRCOSCSetting.Version, Version); } + private async Task copyOpenVrFiles() + { + var tempStorage = storage.GetStorageForDirectory("openvr"); + var tempStoragePath = tempStorage.GetFullPath(string.Empty); + + var openVrFiles = Resources.GetAvailableResources().Where(file => file.StartsWith("OpenVR")); + + foreach (var file in openVrFiles) + { + await File.WriteAllBytesAsync(Path.Combine(tempStoragePath, file.Split('/')[1]), await Resources.GetAsync(file)); + } + } + protected override bool OnExiting() { moduleManager.State.BindValueChanged(e => @@ -136,8 +163,6 @@ protected override bool OnExiting() ModulesRunning.Value = false; - OpenVR.Shutdown(); - return true; } diff --git a/VRCOSC.Game/VRCOSCGameBase.cs b/VRCOSC.Game/VRCOSCGameBase.cs index 8ac7ec6a..d1b41b43 100644 --- a/VRCOSC.Game/VRCOSCGameBase.cs +++ b/VRCOSC.Game/VRCOSCGameBase.cs @@ -30,9 +30,9 @@ public partial class VRCOSCGameBase : osu.Framework.Game private Bindable versionBindable = null!; - private Version assemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version(); + private static Version assemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version(); - public string Version => $@"{assemblyVersion.Major}.{assemblyVersion.Minor}.{assemblyVersion.Build}"; + protected string Version => $@"{assemblyVersion.Major}.{assemblyVersion.Minor}.{assemblyVersion.Build}"; protected VRCOSCGameBase() { diff --git a/VRCOSC.Game/Modules/ActionMenu.cs b/VRCOSC.OSC/IOscListener.cs similarity index 53% rename from VRCOSC.Game/Modules/ActionMenu.cs rename to VRCOSC.OSC/IOscListener.cs index 7c96cc8c..2810d59a 100644 --- a/VRCOSC.Game/Modules/ActionMenu.cs +++ b/VRCOSC.OSC/IOscListener.cs @@ -1,12 +1,10 @@ // Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. // See the LICENSE file in the repository root for full license text. -namespace VRCOSC.Game.Modules; +namespace VRCOSC.OSC; -public enum ActionMenu +public interface IOscListener { - Button, - Radial, - Axes, - None + void OnDataSent(OscData data); + void OnDataReceived(OscData data); } diff --git a/VRCOSC.OSC/OscClient.cs b/VRCOSC.OSC/OscClient.cs index 20fd569b..a12beb6c 100644 --- a/VRCOSC.OSC/OscClient.cs +++ b/VRCOSC.OSC/OscClient.cs @@ -15,17 +15,18 @@ public sealed class OscClient private bool sendingEnabled; private bool receivingEnabled; - public Action? OnParameterSent; - public Action? OnParameterReceived; - - /// - /// Initialises the with the required data. - /// - /// This can be called multiple times without having to make a new as long as the is disabled - /// The IP address which which to send and receive data on - /// The port with which to send data - /// The port with which to receive data - /// If the is currently enabled + private readonly List listeners = new(); + + public void RegisterListener(IOscListener listener) + { + listeners.Add(listener); + } + + public void DeRegisterListener(IOscListener listener) + { + listeners.Remove(listener); + } + public void Initialise(string ipAddress, int sendPort, int receivePort) { if (sendingEnabled || receivingEnabled) throw new InvalidOperationException($"Cannot initialise when {nameof(OscClient)} is already enabled"); @@ -37,9 +38,6 @@ public void Initialise(string ipAddress, int sendPort, int receivePort) receivingClient.Bind(new IPEndPoint(IPAddress.Parse(ipAddress), receivePort)); } - /// - /// Enables the to start sending and receiving data - /// public void Enable() { tokenSource = new CancellationTokenSource(); @@ -48,18 +46,6 @@ public void Enable() receivingEnabled = true; } - /// - /// Disables both sending and receiving - /// - public async Task Disable() - { - await DisableReceive(); - DisableSend(); - } - - /// - /// Disables just receiving - /// public async Task DisableReceive() { receivingEnabled = false; @@ -76,9 +62,6 @@ public async Task DisableReceive() receivingClient = null; } - /// - /// Disables just sending - /// public void DisableSend() { sendingEnabled = false; @@ -86,27 +69,19 @@ public void DisableSend() sendingClient = null; } - /// - /// Sends a value to a specified address - /// - /// The address to send the value to - /// The value to send - /// If the value is not of type bool, int, float, or string - public void SendValue(string oscAddress, object value) => SendValues(oscAddress, new List { value }); - - /// - /// Sends values to a specified address - /// - /// The address to send the value to - /// The values to send - /// If the values are not of type bool, int, float, or string - public void SendValues(string oscAddress, List values) + public void SendValue(string address, object value) => SendValues(address, new List { value }); + + public void SendValues(string address, List values) => SendData(new OscData { - if (!values.All(value => value is (bool or int or float or string))) - throw new ArgumentOutOfRangeException(nameof(values), "Cannot send values that are not of type bool, int, float, or string"); + Address = address, + Values = values + }); - sendingClient?.SendOscMessage(new OscMessage(oscAddress, values)); - OnParameterSent?.Invoke(oscAddress, values.First()); + public void SendData(OscData data) + { + data.PreValidate(); + sendingClient?.SendOscMessage(new OscMessage(data.Address, data.Values)); + listeners.ForEach(listener => listener.OnDataSent(data)); } private async void runReceiveLoop() @@ -116,9 +91,15 @@ private async void runReceiveLoop() while (!tokenSource!.Token.IsCancellationRequested) { var message = await receivingClient!.ReceiveOscMessageAsync(tokenSource.Token); - if (message is null || !message.Values.Any()) continue; + if (message is null) continue; + + var data = new OscData + { + Address = message.Address, + Values = message.Values + }; - OnParameterReceived?.Invoke(message.Address, message.Values.First()!); + listeners.ForEach(listener => listener.OnDataReceived(data)); } } catch (OperationCanceledException) { } diff --git a/VRCOSC.OSC/OscData.cs b/VRCOSC.OSC/OscData.cs new file mode 100644 index 00000000..0e628871 --- /dev/null +++ b/VRCOSC.OSC/OscData.cs @@ -0,0 +1,16 @@ +// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. +// See the LICENSE file in the repository root for full license text. + +namespace VRCOSC.OSC; + +public class OscData +{ + public string Address { get; init; } = string.Empty; + public List Values { get; init; } = Array.Empty().ToList(); + + public void PreValidate() + { + if (!Values.All(value => value is (bool or int or float or string))) + throw new ArgumentOutOfRangeException(nameof(Values), "Cannot send values that are not of type bool, int, float, or string"); + } +} diff --git a/VRCOSC.OSC/OscMessage.cs b/VRCOSC.OSC/OscMessage.cs index 1eb07aab..f59b25c9 100644 --- a/VRCOSC.OSC/OscMessage.cs +++ b/VRCOSC.OSC/OscMessage.cs @@ -53,18 +53,18 @@ public byte[] GetBytes() var typeString = typeStringBuilder.ToString(); - var addressLen = OscUtils.AlignedStringLength(Address); - var typeLen = OscUtils.AlignedStringLength(typeString); + var addressLen = (Encoding.UTF8.GetBytes(Address).Length / 4 + 1) * 4; + var typeLen = (Encoding.UTF8.GetBytes(typeString).Length / 4 + 1) * 4; var total = addressLen + typeLen + parts.Sum(x => x.Length); var output = new byte[total]; var index = 0; - Encoding.ASCII.GetBytes(Address).CopyTo(output, index); + Encoding.UTF8.GetBytes(Address).CopyTo(output, index); index += addressLen; - Encoding.ASCII.GetBytes(typeString).CopyTo(output, index); + Encoding.UTF8.GetBytes(typeString).CopyTo(output, index); index += typeLen; foreach (var part in parts) diff --git a/VRCOSC.OSC/OscPacket.cs b/VRCOSC.OSC/OscPacket.cs index 0fbf2bde..a164eb3c 100644 --- a/VRCOSC.OSC/OscPacket.cs +++ b/VRCOSC.OSC/OscPacket.cs @@ -57,7 +57,7 @@ public abstract class OscPacket case 's': var stringVal = getString(msg, index); values.Add(stringVal); - index += stringVal.Length; + index += Encoding.UTF8.GetBytes(stringVal).Length; break; case 'T': @@ -90,7 +90,7 @@ private static string getAddress(byte[] msg, int index) { if (i == 0) return string.Empty; - address = Encoding.ASCII.GetString(msg.SubArray(index, i - 1)); + address = Encoding.UTF8.GetString(msg.SubArray(index, i - 1)); break; } } @@ -110,7 +110,7 @@ private static char[] getTypes(byte[] msg, int index) { if (msg[i - 1] == 0) { - types = Encoding.ASCII.GetChars(msg.SubArray(index, i - index)); + types = Encoding.UTF8.GetChars(msg.SubArray(index, i - index)); break; } } @@ -147,7 +147,7 @@ private static string getString(byte[] msg, int index) { if (msg[i - 1] == 0) { - output = Encoding.ASCII.GetString(msg.SubArray(index, i - index)); + output = Encoding.UTF8.GetString(msg.SubArray(index, i - index)); break; } } @@ -186,10 +186,9 @@ protected static byte[] SetFloat(float value) protected static byte[] SetString(string value) { - var len = OscUtils.AlignedStringLength(value); - var msg = new byte[len]; + var bytes = Encoding.UTF8.GetBytes(value); - var bytes = Encoding.ASCII.GetBytes(value); + var msg = new byte[(bytes.Length / 4 + 1) * 4]; bytes.CopyTo(msg, 0); return msg; diff --git a/VRCOSC.OSC/OscUtils.cs b/VRCOSC.OSC/OscUtils.cs deleted file mode 100644 index f565bfe0..00000000 --- a/VRCOSC.OSC/OscUtils.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. -// See the LICENSE file in the repository root for full license text. - -namespace VRCOSC.OSC; - -public static class OscUtils -{ - public static int AlignedStringLength(string val) - { - var len = val.Length + (4 - val.Length % 4); - if (len <= val.Length) len += 4; - - return len; - } -} diff --git a/VRCOSC.Resources/OpenVR/action_manifest.json b/VRCOSC.Resources/OpenVR/action_manifest.json new file mode 100644 index 00000000..dc6e30a5 --- /dev/null +++ b/VRCOSC.Resources/OpenVR/action_manifest.json @@ -0,0 +1,100 @@ +{ + "action_sets" : [ + { + "name" : "/actions/main", + "usage" : "leftright" + } + ], + "actions" : [ + { + "name" : "/actions/main/in/lefta", + "requirement" : "mandatory", + "type" : "boolean" + }, + { + "name" : "/actions/main/in/leftb", + "requirement" : "mandatory", + "type" : "boolean" + }, + { + "name" : "/actions/main/in/leftpad", + "requirement" : "mandatory", + "type" : "boolean" + }, + { + "name" : "/actions/main/in/leftstick", + "requirement" : "mandatory", + "type" : "boolean" + }, + { + "name" : "/actions/main/in/leftfingerindex", + "requirement" : "mandatory", + "type" : "vector1" + }, + { + "name" : "/actions/main/in/leftfingermiddle", + "requirement" : "mandatory", + "type" : "vector1" + }, + { + "name" : "/actions/main/in/leftfingerring", + "requirement" : "mandatory", + "type" : "vector1" + }, + { + "name" : "/actions/main/in/leftfingerpinky", + "requirement" : "mandatory", + "type" : "vector1" + }, + { + "name" : "/actions/main/in/righta", + "requirement" : "mandatory", + "type" : "boolean" + }, + { + "name" : "/actions/main/in/rightb", + "requirement" : "mandatory", + "type" : "boolean" + }, + { + "name" : "/actions/main/in/rightpad", + "requirement" : "mandatory", + "type" : "boolean" + }, + { + "name" : "/actions/main/in/rightstick", + "requirement" : "mandatory", + "type" : "boolean" + }, + { + "name" : "/actions/main/in/rightfingerindex", + "requirement" : "mandatory", + "type" : "vector1" + }, + { + "name" : "/actions/main/in/rightfingermiddle", + "requirement" : "mandatory", + "type" : "vector1" + }, + { + "name" : "/actions/main/in/rightfingerring", + "requirement" : "mandatory", + "type" : "vector1" + }, + { + "name" : "/actions/main/in/rightfingerpinky", + "requirement" : "mandatory", + "type" : "vector1" + } + ], + "default_bindings" : [ + { + "binding_url" : "knuckles_bindings.json", + "controller_type" : "knuckles" + }, + { + "binding_url" : "oculus_touch_bindings.json", + "controller_type" : "oculus_touch" + } + ] +} \ No newline at end of file diff --git a/VRCOSC.Resources/OpenVR/knuckles_bindings.json b/VRCOSC.Resources/OpenVR/knuckles_bindings.json new file mode 100644 index 00000000..d3f710c0 --- /dev/null +++ b/VRCOSC.Resources/OpenVR/knuckles_bindings.json @@ -0,0 +1,159 @@ +{ + "alias_info" : {}, + "app_key" : "volcanicarts.vrcosc", + "bindings" : { + "/actions/main" : { + "sources" : [ + { + "inputs" : { + "touch" : { + "output" : "/actions/main/in/leftstick" + } + }, + "mode" : "button", + "path" : "/user/hand/left/input/thumbstick" + }, + { + "inputs" : { + "touch" : { + "output" : "/actions/main/in/lefta" + } + }, + "mode" : "button", + "path" : "/user/hand/left/input/a" + }, + { + "inputs" : { + "touch" : { + "output" : "/actions/main/in/leftb" + } + }, + "mode" : "button", + "path" : "/user/hand/left/input/b" + }, + { + "inputs" : { + "touch" : { + "output" : "/actions/main/in/leftpad" + } + }, + "mode" : "trackpad", + "path" : "/user/hand/left/input/trackpad" + }, + { + "inputs" : { + "pull" : { + "output" : "/actions/main/in/leftfingerindex" + } + }, + "mode" : "trigger", + "path" : "/user/hand/left/input/finger/index" + }, + { + "inputs" : { + "pull" : { + "output" : "/actions/main/in/leftfingermiddle" + } + }, + "mode" : "trigger", + "path" : "/user/hand/left/input/finger/middle" + }, + { + "inputs" : { + "pull" : { + "output" : "/actions/main/in/leftfingerring" + } + }, + "mode" : "trigger", + "path" : "/user/hand/left/input/finger/ring" + }, + { + "inputs" : { + "pull" : { + "output" : "/actions/main/in/leftfingerpinky" + } + }, + "mode" : "trigger", + "path" : "/user/hand/left/input/finger/pinky" + }, + { + "inputs" : { + "touch" : { + "output" : "/actions/main/in/rightstick" + } + }, + "mode" : "button", + "path" : "/user/hand/right/input/thumbstick" + }, + { + "inputs" : { + "touch" : { + "output" : "/actions/main/in/righta" + } + }, + "mode" : "button", + "path" : "/user/hand/right/input/a" + }, + { + "inputs" : { + "touch" : { + "output" : "/actions/main/in/rightb" + } + }, + "mode" : "button", + "path" : "/user/hand/right/input/b" + }, + { + "inputs" : { + "touch" : { + "output" : "/actions/main/in/rightpad" + } + }, + "mode" : "trackpad", + "path" : "/user/hand/right/input/trackpad" + }, + { + "inputs" : { + "pull" : { + "output" : "/actions/main/in/rightfingerindex" + } + }, + "mode" : "trigger", + "path" : "/user/hand/right/input/finger/index" + }, + { + "inputs" : { + "pull" : { + "output" : "/actions/main/in/rightfingermiddle" + } + }, + "mode" : "trigger", + "path" : "/user/hand/right/input/finger/middle" + }, + { + "inputs" : { + "pull" : { + "output" : "/actions/main/in/rightfingerring" + } + }, + "mode" : "trigger", + "path" : "/user/hand/right/input/finger/ring" + }, + { + "inputs" : { + "pull" : { + "output" : "/actions/main/in/rightfingerpinky" + } + }, + "mode" : "trigger", + "path" : "/user/hand/right/input/finger/pinky" + } + ] + } + }, + "controller_type" : "knuckles", + "description" : "A modular OSC program creator made for VRChat", + "name" : "VRCOSC", + "options" : {}, + "simulated_actions" : [] +} \ No newline at end of file diff --git a/VRCOSC.Resources/OpenVR/oculus_touch_bindings.json b/VRCOSC.Resources/OpenVR/oculus_touch_bindings.json new file mode 100644 index 00000000..1d583e69 --- /dev/null +++ b/VRCOSC.Resources/OpenVR/oculus_touch_bindings.json @@ -0,0 +1,69 @@ +{ + "alias_info" : {}, + "app_key" : "volcanicarts.vrcosc", + "bindings" : { + "/actions/main" : { + "sources" : [ + { + "inputs" : { + "touch" : { + "output" : "/actions/main/in/leftstick" + } + }, + "mode" : "button", + "path" : "/user/hand/left/input/thumbstick" + }, + { + "inputs" : { + "touch" : { + "output" : "/actions/main/in/lefta" + } + }, + "mode" : "button", + "path" : "/user/hand/left/input/x" + }, + { + "inputs" : { + "touch" : { + "output" : "/actions/main/in/leftb" + } + }, + "mode" : "button", + "path" : "/user/hand/left/input/y" + } + { + "inputs" : { + "touch" : { + "output" : "/actions/main/in/rightstick" + } + }, + "mode" : "button", + "path" : "/user/hand/right/input/thumbstick" + }, + { + "inputs" : { + "touch" : { + "output" : "/actions/main/in/righta" + } + }, + "mode" : "button", + "path" : "/user/hand/right/input/a" + }, + { + "inputs" : { + "touch" : { + "output" : "/actions/main/in/rightb" + } + }, + "mode" : "button", + "path" : "/user/hand/right/input/b" + } + ] + } + }, + "controller_type" : "oculus_touch", + "description" : "A modular OSC program creator made for VRChat", + "name" : "VRCOSC", + "options" : {}, + "simulated_actions" : [] +} \ No newline at end of file diff --git a/VRCOSC.Resources/VRCOSC.Resources.csproj b/VRCOSC.Resources/VRCOSC.Resources.csproj index c0ac5c7e..95802a81 100644 --- a/VRCOSC.Resources/VRCOSC.Resources.csproj +++ b/VRCOSC.Resources/VRCOSC.Resources.csproj @@ -7,5 +7,6 @@ +